2026-02-03 17:51:22 +13:00
# Companion Protocol
2026-01-02 12:54:57 +11:00
2026-03-08 21:31:08 -06:00
- **Last Updated**: 2026-03-08
2026-02-03 17:51:22 +13:00
- **Protocol Version**: Companion Firmware v1.12.0+
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
> NOTE: This document is still in development. Some information may be inaccurate.
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
This document provides a comprehensive guide for communicating with MeshCore devices over Bluetooth Low Energy (BLE).
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
It is platform-agnostic and can be used for Android, iOS, Python, JavaScript, or any other platform that supports BLE.
## Official Libraries
Please see the following repos for existing MeshCore Companion Protocol libraries.
- JavaScript: [https://github.com/meshcore-dev/meshcore.js ](https://github.com/meshcore-dev/meshcore.js )
- Python: [https://github.com/meshcore-dev/meshcore_py ](https://github.com/meshcore-dev/meshcore_py )
## Important Security Note
All secrets, hashes, and cryptographic values shown in this guide are example values only.
- All hex values, public keys and hashes are for demonstration purposes only
- Never use example secrets in production
- Always generate new cryptographically secure random secrets
- Please implement proper security practices in your implementation
- This guide is for protocol documentation only
2026-01-02 12:54:57 +11:00
## Table of Contents
1. [BLE Connection ](#ble-connection )
2026-02-03 17:51:22 +13:00
2. [Packet Structure ](#packet-structure )
2026-01-02 12:54:57 +11:00
3. [Commands ](#commands )
4. [Channel Management ](#channel-management )
2026-02-03 17:51:22 +13:00
5. [Message Handling ](#message-handling )
6. [Response Parsing ](#response-parsing )
7. [Example Implementation Flow ](#example-implementation-flow )
8. [Best Practices ](#best-practices )
9. [Troubleshooting ](#troubleshooting )
2026-01-02 12:54:57 +11:00
---
## BLE Connection
### Service and Characteristics
2026-02-03 17:51:22 +13:00
MeshCore Companion devices expose a BLE service with the following UUIDs:
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
- **Service UUID**: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
- **RX Characteristic** (App → Firmware): `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
- **TX Characteristic** (Firmware → App): `6E400003-B5A3-F393-E0A9-E50E24DCCA9E`
2026-01-02 12:54:57 +11:00
### Connection Steps
1. **Scan for Devices**
2026-02-03 17:51:22 +13:00
- Scan for BLE devices advertising the MeshCore Service UUID
- Optionally filter by device name (typically contains "MeshCore" prefix)
- Note the device MAC address for reconnection
2026-01-02 12:54:57 +11:00
2. **Connect to GATT**
2026-02-03 17:51:22 +13:00
- Connect to the device using the discovered MAC address
- Wait for connection to be established
2026-01-02 12:54:57 +11:00
3. **Discover Services and Characteristics**
2026-02-03 17:51:22 +13:00
- Discover the service with UUID `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
- Discover the RX characteristic `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
- Your app writes to this, the firmware reads from this
- Discover the TX characteristic `6E400003-B5A3-F393-E0A9-E50E24DCCA9E`
- The firmware writes to this, your app reads from this
2026-01-02 12:54:57 +11:00
4. **Enable Notifications**
2026-02-03 17:51:22 +13:00
- Subscribe to notifications on the TX characteristic to receive data from the firmware
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
5. **Send Initial Commands**
- Send `CMD_APP_START` to identify your app to firmware and get radio settings
- Send `CMD_DEVICE_QEURY` to fetch device info and negotiate supported protocol versions
- Send `CMD_SET_DEVICE_TIME` to set the firmware clock
- Send `CMD_GET_CONTACTS` to fetch all contacts
- Send `CMD_GET_CHANNEL` multiple times to fetch all channel slots
- Send `CMD_SYNC_NEXT_MESSAGE` to fetch the next message stored in firmware
- Setup listeners for push codes, such as `PUSH_CODE_MSG_WAITING` or `PUSH_CODE_ADVERT`
- See [Commands ](#commands ) section for information on other commands
2026-01-02 12:54:57 +11:00
**Note** : MeshCore devices may disconnect after periods of inactivity. Implement auto-reconnect logic with exponential backoff.
### BLE Write Type
2026-02-03 17:51:22 +13:00
When writing commands to the RX characteristic, specify the write type:
2026-01-02 12:54:57 +11:00
- **Write with Response** (default): Waits for acknowledgment from device
- **Write without Response**: Faster but no acknowledgment
**Platform-specific** :
2026-02-03 17:51:22 +13:00
2026-01-02 12:54:57 +11:00
- **Android**: Use `BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT` or `WRITE_TYPE_NO_RESPONSE`
- **iOS**: Use `CBCharacteristicWriteType.withResponse` or `.withoutResponse`
- **Python (bleak)**: Use `write_gatt_char()` with `response=True` or `False`
2026-02-03 17:51:22 +13:00
**Recommendation** : Use write with response for reliability.
2026-01-02 12:54:57 +11:00
### MTU (Maximum Transmission Unit)
2026-03-08 21:31:08 -06:00
The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SET_CHANNEL` (50 bytes), you may need to:
2026-01-02 12:54:57 +11:00
1. **Request Larger MTU** : Request MTU of 512 bytes if supported
2026-02-03 17:51:22 +13:00
- Android: `gatt.requestMtu(512)`
- iOS: `peripheral.maximumWriteValueLength(for:)`
- Python (bleak): MTU is negotiated automatically
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
### Command Sequencing
2026-01-02 12:54:57 +11:00
**Critical** : Commands must be sent in the correct sequence:
1. **After Connection** :
2026-02-03 17:51:22 +13:00
- Wait for BLE connection to be established
- Wait for services/characteristics to be discovered
- Wait for notifications to be enabled
- Now you can safely send commands to the firmware
2026-01-02 12:54:57 +11:00
2. **Command-Response Matching** :
2026-02-03 17:51:22 +13:00
- Send one command at a time
- Wait for a response before sending another command
- Use a timeout (typically 5 seconds)
- Match response to command by type (e.g: `CMD_GET_CHANNEL` → `RESP_CODE_CHANNEL_INFO` )
2026-01-02 12:54:57 +11:00
### Command Queue Management
2026-02-03 17:51:22 +13:00
For reliable operation, implement a command queue.
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
**Queue Structure** :
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
- Maintain a queue of pending commands
- Track which command is currently waiting for a response
- Only send next command after receiving response or timeout
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
**Error Handling** :
- On timeout, clear current command, process next in queue
- On error, log error, clear current command, process next
2026-01-02 12:54:57 +11:00
---
2026-02-03 17:51:22 +13:00
## Packet Structure
2026-01-02 12:54:57 +11:00
The MeshCore protocol uses a binary format with the following structure:
2026-02-03 17:51:22 +13:00
- **Commands**: Sent from app to firmware via RX characteristic
- **Responses**: Received from firmware via TX characteristic notifications
- **All multi-byte integers**: Little-endian byte order (except CayenneLPP which is Big-endian)
2026-01-02 12:54:57 +11:00
- **All strings**: UTF-8 encoding
Most packets follow this format:
```
[Packet Type (1 byte)] [Data (variable length)]
```
The first byte indicates the packet type (see [Response Parsing ](#response-parsing )).
---
## Commands
### 1. App Start
**Purpose** : Initialize communication with the device. Must be sent first after connection.
**Command Format** :
```
Byte 0: 0x01
2026-03-08 21:31:08 -06:00
Bytes 1-7: Reserved (currently ignored by firmware)
Bytes 8+: Application name (UTF-8, optional)
2026-01-02 12:54:57 +11:00
```
**Example** (hex):
```
2026-03-08 21:31:08 -06:00
01 00 00 00 00 00 00 00 6d 63 63 6c 69
2026-01-02 12:54:57 +11:00
```
2026-03-08 21:31:08 -06:00
**Response** : `PACKET_SELF_INFO` (0x05)
2026-01-02 12:54:57 +11:00
---
### 2. Device Query
**Purpose** : Query device information.
**Command Format** :
```
Byte 0: 0x16
Byte 1: 0x03
```
**Example** (hex):
```
16 03
```
**Response** : `PACKET_DEVICE_INFO` (0x0D) with device information
---
### 3. Get Channel Info
**Purpose** : Retrieve information about a specific channel.
**Command Format** :
```
Byte 0: 0x1F
Byte 1: Channel Index (0-7)
```
**Example** (get channel 1):
```
1F 01
```
**Response** : `PACKET_CHANNEL_INFO` (0x12) with channel details
---
### 4. Set Channel
**Purpose** : Create or update a channel on the device.
**Command Format** :
```
Byte 0: 0x20
Byte 1: Channel Index (0-7)
Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded)
2026-03-08 21:31:08 -06:00
Bytes 34-49: Secret (16 bytes)
2026-01-02 12:54:57 +11:00
```
2026-03-08 21:31:08 -06:00
**Total Length** : 50 bytes
2026-01-02 12:54:57 +11:00
**Channel Index** :
- Index 0: Reserved for public channels (no secret)
- Indices 1-7: Available for private channels
**Channel Name** :
- UTF-8 encoded
- Maximum 32 bytes
- Padded with null bytes (0x00) if shorter
2026-03-08 21:31:08 -06:00
**Secret Field** (16 bytes):
- For **private channels** : 16-byte secret
2026-01-02 12:54:57 +11:00
- For **public channels** : All zeros (0x00)
**Example** (create channel "YourChannelName" at index 1 with secret):
```
20 01 53 4D 53 00 00 ... (name padded to 32 bytes)
2026-03-08 21:31:08 -06:00
[16 bytes of secret]
2026-01-02 12:54:57 +11:00
```
2026-03-08 21:31:08 -06:00
**Note** : The 32-byte secret variant is unsupported and returns `PACKET_ERROR` .
2026-01-02 12:54:57 +11:00
**Response** : `PACKET_OK` (0x00) on success, `PACKET_ERROR` (0x01) on failure
---
### 5. Send Channel Message
**Purpose** : Send a text message to a channel.
**Command Format** :
```
Byte 0: 0x03
Byte 1: 0x00
Byte 2: Channel Index (0-7)
Bytes 3-6: Timestamp (32-bit little-endian Unix timestamp, seconds)
Bytes 7+: Message Text (UTF-8, variable length)
```
**Timestamp** : Unix timestamp in seconds (32-bit unsigned integer, little-endian)
**Example** (send "Hello" to channel 1 at timestamp 1234567890):
```
03 00 01 D2 02 96 49 48 65 6C 6C 6F
```
**Response** : `PACKET_MSG_SENT` (0x06) on success
---
### 6. Get Message
**Purpose** : Request the next queued message from the device.
**Command Format** :
```
Byte 0: 0x0A
```
**Example** (hex):
```
0A
```
**Response** :
- `PACKET_CHANNEL_MSG_RECV` (0x08) or `PACKET_CHANNEL_MSG_RECV_V3` (0x11) for channel messages
- `PACKET_CONTACT_MSG_RECV` (0x07) or `PACKET_CONTACT_MSG_RECV_V3` (0x10) for contact messages
- `PACKET_NO_MORE_MSGS` (0x0A) if no messages available
**Note** : Poll this command periodically to retrieve queued messages. The device may also send `PACKET_MESSAGES_WAITING` (0x83) as a notification when messages are available.
---
2026-03-08 21:31:08 -06:00
### 7. Get Battery and Storage
2026-01-02 12:54:57 +11:00
2026-03-08 21:31:08 -06:00
**Purpose** : Query device battery voltage and storage usage.
2026-01-02 12:54:57 +11:00
**Command Format** :
```
Byte 0: 0x14
```
**Example** (hex):
```
14
```
2026-03-08 21:31:08 -06:00
**Response** : `PACKET_BATTERY` (0x0C) with battery millivolts and storage information
2026-01-02 12:54:57 +11:00
---
## Channel Management
### Channel Types
2026-02-03 17:51:22 +13:00
1. **Public Channel**
- Uses a publicly known 16-byte key: `8b3387e9c5cdea6ac9e5edbaa115cd72`
- Anyone can join this channel, messages should be considered public
- Used as the default public group chat
2. **Hashtag Channels**
- Uses a secret key derived from the channel name
- It is the first 16 bytes of `sha256("#test")`
- For example hashtag channel `#test` has the key: `9cd8fcf22a47333b591d96a2b848b73f`
- Used as a topic based public group chat, separate from the default public channel
3. **Private Channels**
- Uses a randomly generated 16-byte secret key
- Messages should be considered private between those that know the secret
- Users should keep the key secret, and only share with those you want to communicate with
- Used as a secure private group chat
2026-01-02 12:54:57 +11:00
### Channel Lifecycle
2026-02-03 17:51:22 +13:00
1. **Set Channel** :
- Fetch all channel slots, and find one with empty name and all-zero secret
- Generate or provide a 16-byte secret
2026-03-08 21:31:08 -06:00
- Send `CMD_SET_CHANNEL` with name and a 16-byte secret
2026-02-03 17:51:22 +13:00
2. **Get Channel** :
- Send `CMD_GET_CHANNEL` with channel index
- Parse `RESP_CODE_CHANNEL_INFO` response
2026-01-02 12:54:57 +11:00
3. **Delete Channel** :
2026-02-03 17:51:22 +13:00
- Send `CMD_SET_CHANNEL` with empty name and all-zero secret
- Or overwrite with a new channel
2026-01-02 12:54:57 +11:00
---
## Message Handling
### Receiving Messages
2026-03-08 21:31:08 -06:00
Messages are received via the TX characteristic (notifications). The device sends:
2026-01-02 12:54:57 +11:00
1. **Channel Messages** :
- `PACKET_CHANNEL_MSG_RECV` (0x08) - Standard format
- `PACKET_CHANNEL_MSG_RECV_V3` (0x11) - Version 3 with SNR
2. **Contact Messages** :
- `PACKET_CONTACT_MSG_RECV` (0x07) - Standard format
- `PACKET_CONTACT_MSG_RECV_V3` (0x10) - Version 3 with SNR
3. **Notifications** :
- `PACKET_MESSAGES_WAITING` (0x83) - Indicates messages are queued
### Contact Message Format
**Standard Format** (`PACKET_CONTACT_MSG_RECV` , 0x07):
```
Byte 0: 0x07 (packet type)
Bytes 1-6: Public Key Prefix (6 bytes, hex)
Byte 7: Path Length
Byte 8: Text Type
Bytes 9-12: Timestamp (32-bit little-endian)
Bytes 13-16: Signature (4 bytes, only if txt_type == 2)
Bytes 17+: Message Text (UTF-8)
```
**V3 Format** (`PACKET_CONTACT_MSG_RECV_V3` , 0x10):
```
Byte 0: 0x10 (packet type)
Byte 1: SNR (signed byte, multiplied by 4)
Bytes 2-3: Reserved
Bytes 4-9: Public Key Prefix (6 bytes, hex)
Byte 10: Path Length
Byte 11: Text Type
Bytes 12-15: Timestamp (32-bit little-endian)
Bytes 16-19: Signature (4 bytes, only if txt_type == 2)
Bytes 20+: Message Text (UTF-8)
```
**Parsing Pseudocode** :
```python
def parse_contact_message ( data ):
packet_type = data [ 0 ]
offset = 1
# Check for V3 format
if packet_type == 0x10 : # V3
snr_byte = data [ offset ]
snr = (( snr_byte if snr_byte < 128 else snr_byte - 256 ) / 4.0 )
offset += 3 # Skip SNR + reserved
pubkey_prefix = data [ offset : offset + 6 ] . hex ()
offset += 6
path_len = data [ offset ]
txt_type = data [ offset + 1 ]
offset += 2
timestamp = int . from_bytes ( data [ offset : offset + 4 ], 'little' )
offset += 4
# If txt_type == 2, skip 4-byte signature
if txt_type == 2 :
offset += 4
message = data [ offset :] . decode ( 'utf-8' )
return {
'pubkey_prefix' : pubkey_prefix ,
'path_len' : path_len ,
'txt_type' : txt_type ,
'timestamp' : timestamp ,
'message' : message ,
'snr' : snr if packet_type == 0x10 else None
}
```
### Channel Message Format
**Standard Format** (`PACKET_CHANNEL_MSG_RECV` , 0x08):
```
Byte 0: 0x08 (packet type)
Byte 1: Channel Index (0-7)
Byte 2: Path Length
Byte 3: Text Type
Bytes 4-7: Timestamp (32-bit little-endian)
Bytes 8+: Message Text (UTF-8)
```
**V3 Format** (`PACKET_CHANNEL_MSG_RECV_V3` , 0x11):
```
Byte 0: 0x11 (packet type)
Byte 1: SNR (signed byte, multiplied by 4)
Bytes 2-3: Reserved
Byte 4: Channel Index (0-7)
Byte 5: Path Length
Byte 6: Text Type
Bytes 7-10: Timestamp (32-bit little-endian)
Bytes 11+: Message Text (UTF-8)
```
**Parsing Pseudocode** :
```python
def parse_channel_message ( data ):
packet_type = data [ 0 ]
offset = 1
# Check for V3 format
if packet_type == 0x11 : # V3
snr_byte = data [ offset ]
snr = (( snr_byte if snr_byte < 128 else snr_byte - 256 ) / 4.0 )
offset += 3 # Skip SNR + reserved
channel_idx = data [ offset ]
path_len = data [ offset + 1 ]
txt_type = data [ offset + 2 ]
timestamp = int . from_bytes ( data [ offset + 3 : offset + 7 ], 'little' )
message = data [ offset + 7 :] . decode ( 'utf-8' )
return {
'channel_idx' : channel_idx ,
'timestamp' : timestamp ,
'message' : message ,
'snr' : snr if packet_type == 0x11 else None
}
```
### Sending Messages
Use the `SEND_CHANNEL_MESSAGE` command (see [Commands ](#commands )).
**Important** :
- Messages are limited to 133 characters per MeshCore specification
- Long messages should be split into chunks
- Include a chunk indicator (e.g., "[1/3] message text")
---
## Response Parsing
### Packet Types
2026-02-03 17:51:22 +13:00
| Value | Name | Description |
|-------|----------------------------|-------------------------------|
| 0x00 | PACKET_OK | Command succeeded |
| 0x01 | PACKET_ERROR | Command failed |
| 0x02 | PACKET_CONTACT_START | Start of contact list |
| 0x03 | PACKET_CONTACT | Contact information |
| 0x04 | PACKET_CONTACT_END | End of contact list |
| 0x05 | PACKET_SELF_INFO | Device self-information |
| 0x06 | PACKET_MSG_SENT | Message sent confirmation |
| 0x07 | PACKET_CONTACT_MSG_RECV | Contact message (standard) |
| 0x08 | PACKET_CHANNEL_MSG_RECV | Channel message (standard) |
| 0x09 | PACKET_CURRENT_TIME | Current time response |
| 0x0A | PACKET_NO_MORE_MSGS | No more messages available |
| 0x0C | PACKET_BATTERY | Battery level |
| 0x0D | PACKET_DEVICE_INFO | Device information |
| 0x10 | PACKET_CONTACT_MSG_RECV_V3 | Contact message (V3 with SNR) |
| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) |
| 0x12 | PACKET_CHANNEL_INFO | Channel information |
| 0x80 | PACKET_ADVERTISEMENT | Advertisement packet |
| 0x82 | PACKET_ACK | Acknowledgment |
| 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification |
| 0x88 | PACKET_LOG_DATA | RF log data (can be ignored) |
2026-01-02 12:54:57 +11:00
### Parsing Responses
**PACKET_OK** (0x00):
```
Byte 0: 0x00
Bytes 1-4: Optional value (32-bit little-endian integer)
```
**PACKET_ERROR** (0x01):
```
Byte 0: 0x01
Byte 1: Error code (optional)
```
**PACKET_CHANNEL_INFO** (0x12):
```
Byte 0: 0x12
Byte 1: Channel Index
Bytes 2-33: Channel Name (32 bytes, null-terminated)
2026-03-08 21:31:08 -06:00
Bytes 34-49: Secret (16 bytes)
2026-01-02 12:54:57 +11:00
```
2026-03-08 21:31:08 -06:00
**Note** : The device returns the 16-byte channel secret in this response.
2026-01-02 12:54:57 +11:00
**PACKET_DEVICE_INFO** (0x0D):
```
Byte 0: 0x0D
Byte 1: Firmware Version (uint8)
Bytes 2+: Variable length based on firmware version
For firmware version >= 3:
Byte 2: Max Contacts Raw (uint8, actual = value * 2)
Byte 3: Max Channels (uint8)
Bytes 4-7: BLE PIN (32-bit little-endian)
Bytes 8-19: Firmware Build (12 bytes, UTF-8, null-padded)
Bytes 20-59: Model (40 bytes, UTF-8, null-padded)
Bytes 60-79: Version (20 bytes, UTF-8, null-padded)
2026-03-08 21:31:08 -06:00
Byte 80: Client repeat enabled/preferred (firmware v9+)
Byte 81: Path hash mode (firmware v10+)
2026-01-02 12:54:57 +11:00
```
**Parsing Pseudocode** :
```python
def parse_device_info ( data ):
if len ( data ) < 2 :
return None
fw_ver = data [ 1 ]
info = { 'fw_ver' : fw_ver }
if fw_ver >= 3 and len ( data ) >= 80 :
info [ 'max_contacts' ] = data [ 2 ] * 2
info [ 'max_channels' ] = data [ 3 ]
info [ 'ble_pin' ] = int . from_bytes ( data [ 4 : 8 ], 'little' )
info [ 'fw_build' ] = data [ 8 : 20 ] . decode ( 'utf-8' ) . rstrip ( ' \x00 ' ) . strip ()
info [ 'model' ] = data [ 20 : 60 ] . decode ( 'utf-8' ) . rstrip ( ' \x00 ' ) . strip ()
info [ 'ver' ] = data [ 60 : 80 ] . decode ( 'utf-8' ) . rstrip ( ' \x00 ' ) . strip ()
return info
```
**PACKET_BATTERY** (0x0C):
```
Byte 0: 0x0C
2026-03-08 21:31:08 -06:00
Bytes 1-2: Battery Voltage (16-bit little-endian, millivolts)
2026-01-02 12:54:57 +11:00
Bytes 3-6: Used Storage (32-bit little-endian, KB)
Bytes 7-10: Total Storage (32-bit little-endian, KB)
```
**Parsing Pseudocode** :
```python
def parse_battery ( data ):
if len ( data ) < 3 :
return None
2026-03-08 21:31:08 -06:00
mv = int . from_bytes ( data [ 1 : 3 ], 'little' )
info = { 'battery_mv' : mv }
2026-01-02 12:54:57 +11:00
2026-03-08 21:31:08 -06:00
if len ( data ) >= 11 :
info [ 'used_kb' ] = int . from_bytes ( data [ 3 : 7 ], 'little' )
info [ 'total_kb' ] = int . from_bytes ( data [ 7 : 11 ], 'little' )
2026-01-02 12:54:57 +11:00
return info
```
**PACKET_SELF_INFO** (0x05):
```
Byte 0: 0x05
Byte 1: Advertisement Type
Byte 2: TX Power
Byte 3: Max TX Power
Bytes 4-35: Public Key (32 bytes, hex)
Bytes 36-39: Advertisement Latitude (32-bit little-endian, divided by 1e6)
Bytes 40-43: Advertisement Longitude (32-bit little-endian, divided by 1e6)
Byte 44: Multi ACKs
Byte 45: Advertisement Location Policy
Byte 46: Telemetry Mode (bitfield)
Byte 47: Manual Add Contacts (bool)
Bytes 48-51: Radio Frequency (32-bit little-endian, divided by 1000.0)
Bytes 52-55: Radio Bandwidth (32-bit little-endian, divided by 1000.0)
Byte 56: Radio Spreading Factor
Byte 57: Radio Coding Rate
2026-03-08 21:31:08 -06:00
Bytes 58+: Device Name (UTF-8, variable length, no null terminator required)
2026-01-02 12:54:57 +11:00
```
**Parsing Pseudocode** :
```python
def parse_self_info ( data ):
if len ( data ) < 36 :
return None
offset = 1
info = {
'adv_type' : data [ offset ],
'tx_power' : data [ offset + 1 ],
'max_tx_power' : data [ offset + 2 ],
'public_key' : data [ offset + 3 : offset + 35 ] . hex ()
}
offset += 35
lat = int . from_bytes ( data [ offset : offset + 4 ], 'little' ) / 1e6
lon = int . from_bytes ( data [ offset + 4 : offset + 8 ], 'little' ) / 1e6
info [ 'adv_lat' ] = lat
info [ 'adv_lon' ] = lon
offset += 8
info [ 'multi_acks' ] = data [ offset ]
info [ 'adv_loc_policy' ] = data [ offset + 1 ]
telemetry_mode = data [ offset + 2 ]
info [ 'telemetry_mode_env' ] = ( telemetry_mode >> 4 ) & 0b11
info [ 'telemetry_mode_loc' ] = ( telemetry_mode >> 2 ) & 0b11
info [ 'telemetry_mode_base' ] = telemetry_mode & 0b11
info [ 'manual_add_contacts' ] = data [ offset + 3 ] > 0
offset += 4
freq = int . from_bytes ( data [ offset : offset + 4 ], 'little' ) / 1000.0
bw = int . from_bytes ( data [ offset + 4 : offset + 8 ], 'little' ) / 1000.0
info [ 'radio_freq' ] = freq
info [ 'radio_bw' ] = bw
info [ 'radio_sf' ] = data [ offset + 8 ]
info [ 'radio_cr' ] = data [ offset + 9 ]
offset += 10
if offset < len ( data ):
name_bytes = data [ offset :]
info [ 'name' ] = name_bytes . decode ( 'utf-8' ) . rstrip ( ' \x00 ' ) . strip ()
return info
```
**PACKET_MSG_SENT** (0x06):
```
Byte 0: 0x06
2026-03-08 21:31:08 -06:00
Byte 1: Route Flag (0 = direct, 1 = flood)
Bytes 2-5: Tag / Expected ACK (4 bytes, little-endian)
Bytes 6-9: Suggested Timeout (32-bit little-endian, milliseconds)
2026-01-02 12:54:57 +11:00
```
**PACKET_ACK** (0x82):
```
Byte 0: 0x82
Bytes 1-6: ACK Code (6 bytes, hex)
```
### Error Codes
**PACKET_ERROR** (0x01) may include an error code in byte 1:
| Error Code | Description |
|------------|-------------|
| 0x00 | Generic error (no specific code) |
| 0x01 | Invalid command |
| 0x02 | Invalid parameter |
| 0x03 | Channel not found |
| 0x04 | Channel already exists |
| 0x05 | Channel index out of range |
| 0x06 | Secret mismatch |
| 0x07 | Message too long |
| 0x08 | Device busy |
| 0x09 | Not enough storage |
**Note** : Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response.
2026-03-08 21:31:08 -06:00
### Frame Handling
2026-01-02 12:54:57 +11:00
2026-03-08 21:31:08 -06:00
BLE implementations enqueue and deliver one protocol frame per BLE write/notification at the firmware layer.
2026-01-02 12:54:57 +11:00
2026-03-08 21:31:08 -06:00
- Apps should treat each characteristic write/notification as exactly one companion protocol frame
- Apps should still validate frame lengths before parsing
- Future transports or firmware revisions may differ, so avoid assuming fixed payload sizes for variable-length responses
2026-01-02 12:54:57 +11:00
### Response Handling
1. **Command-Response Pattern** :
2026-03-08 21:31:08 -06:00
- Send command via RX characteristic
- Wait for response via TX characteristic (notification)
2026-01-02 12:54:57 +11:00
- Match response to command using sequence numbers or command type
- Handle timeout (typically 5 seconds)
- Use command queue to prevent concurrent commands
2. **Asynchronous Messages** :
2026-03-08 21:31:08 -06:00
- Device may send messages at any time via TX characteristic
2026-01-02 12:54:57 +11:00
- Handle `PACKET_MESSAGES_WAITING` (0x83) by polling `GET_MESSAGE` command
- Parse incoming messages and route to appropriate handlers
2026-03-08 21:31:08 -06:00
- Validate frame length before decoding
2026-01-02 12:54:57 +11:00
3. **Response Matching** :
- Match responses to commands by expected packet type:
2026-03-08 21:31:08 -06:00
- `APP_START` → `PACKET_SELF_INFO`
2026-01-02 12:54:57 +11:00
- `DEVICE_QUERY` → `PACKET_DEVICE_INFO`
- `GET_CHANNEL` → `PACKET_CHANNEL_INFO`
- `SET_CHANNEL` → `PACKET_OK` or `PACKET_ERROR`
- `SEND_CHANNEL_MESSAGE` → `PACKET_MSG_SENT`
- `GET_MESSAGE` → `PACKET_CHANNEL_MSG_RECV` , `PACKET_CONTACT_MSG_RECV` , or `PACKET_NO_MORE_MSGS`
- `GET_BATTERY` → `PACKET_BATTERY`
4. **Timeout Handling** :
- Default timeout: 5 seconds per command
- On timeout: Log error, clear current command, proceed to next in queue
- Some commands may take longer (e.g., `SET_CHANNEL` may need 1-2 seconds)
- Consider longer timeout for channel operations
5. **Error Recovery** :
- On `PACKET_ERROR` : Log error code, clear current command
- On connection loss: Clear command queue, attempt reconnection
- On invalid response: Log warning, clear current command, proceed
---
## Example Implementation Flow
### Initialization
```python
# 1. Scan for MeshCore device
device = scan_for_device ( "MeshCore" )
# 2. Connect to BLE GATT
gatt = connect_to_device ( device )
# 3. Discover services and characteristics
2026-03-08 21:31:08 -06:00
service = discover_service ( gatt , "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" )
rx_char = discover_characteristic ( service , "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" )
tx_char = discover_characteristic ( service , "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" )
2026-01-02 12:54:57 +11:00
2026-03-08 21:31:08 -06:00
# 4. Enable notifications on TX characteristic
enable_notifications ( tx_char , on_notification_received )
2026-01-02 12:54:57 +11:00
# 5. Send AppStart command
2026-03-08 21:31:08 -06:00
send_command ( rx_char , build_app_start ())
wait_for_response ( PACKET_SELF_INFO )
2026-01-02 12:54:57 +11:00
```
### Creating a Private Channel
```python
# 1. Generate 16-byte secret
secret_16_bytes = generate_secret ( 16 ) # Use CSPRNG
secret_hex = secret_16_bytes . hex ()
2026-03-08 21:31:08 -06:00
# 2. Build SET_CHANNEL command
2026-01-02 12:54:57 +11:00
channel_name = "YourChannelName"
channel_index = 1 # Use 1-7 for private channels
2026-03-08 21:31:08 -06:00
command = build_set_channel ( channel_index , channel_name , secret_16_bytes )
2026-01-02 12:54:57 +11:00
2026-03-08 21:31:08 -06:00
# 3. Send command
send_command ( rx_char , command )
2026-01-02 12:54:57 +11:00
response = wait_for_response ( PACKET_OK )
2026-03-08 21:31:08 -06:00
# 4. Store secret locally
2026-01-02 12:54:57 +11:00
store_channel_secret ( channel_index , secret_hex )
```
### Sending a Message
```python
# 1. Build channel message command
channel_index = 1
message = "Hello, MeshCore!"
timestamp = int ( time . time ())
command = build_channel_message ( channel_index , message , timestamp )
# 2. Send command
2026-03-08 21:31:08 -06:00
send_command ( rx_char , command )
2026-01-02 12:54:57 +11:00
response = wait_for_response ( PACKET_MSG_SENT )
```
### Receiving Messages
```python
def on_notification_received ( data ):
packet_type = data [ 0 ]
if packet_type == PACKET_CHANNEL_MSG_RECV or packet_type == PACKET_CHANNEL_MSG_RECV_V3 :
message = parse_channel_message ( data )
handle_channel_message ( message )
elif packet_type == PACKET_MESSAGES_WAITING :
# Poll for messages
2026-03-08 21:31:08 -06:00
send_command ( rx_char , build_get_message ())
2026-01-02 12:54:57 +11:00
```
---
## Best Practices
1. **Connection Management** :
- Implement auto-reconnect with exponential backoff
- Handle disconnections gracefully
- Store last connected device address for quick reconnection
2. **Secret Management** :
- Always use cryptographically secure random number generators
- Store secrets securely (encrypted storage)
- Never log or transmit secrets in plain text
3. **Message Handling** :
2026-02-03 17:51:22 +13:00
- Send `CMD_SYNC_NEXT_MESSAGE` when `PUSH_CODE_MSG_WAITING` is received
- Implement message deduplication to avoid display the same message twice
2026-01-02 12:54:57 +11:00
2026-02-03 17:51:22 +13:00
4. **Channel Management** :
- Fetch all channel slots even if you encounter an empty slot
- Ideally save new channels into the first empty slot
5. **Error Handling** :
2026-01-02 12:54:57 +11:00
- Implement timeouts for all commands (typically 5 seconds)
2026-02-03 17:51:22 +13:00
- Handle `RESP_CODE_ERR` responses appropriately
2026-01-02 12:54:57 +11:00
---
## Troubleshooting
### Connection Issues
2026-02-03 17:51:22 +13:00
2026-01-02 12:54:57 +11:00
- **Device not found**: Ensure device is powered on and advertising
- **Connection timeout**: Check Bluetooth permissions and device proximity
- **GATT errors**: Ensure proper service/characteristic discovery
### Command Issues
2026-02-03 17:51:22 +13:00
2026-01-02 12:54:57 +11:00
- **No response**: Verify notifications are enabled, check connection state
2026-02-03 17:51:22 +13:00
- **Error responses**: Verify command format and check error code
- **Timeout**: Increase timeout value or try again
2026-01-02 12:54:57 +11:00
### Message Issues
2026-02-03 17:51:22 +13:00
2026-01-02 12:54:57 +11:00
- **Messages not received**: Poll `GET_MESSAGE` command periodically
2026-02-03 17:51:22 +13:00
- **Duplicate messages**: Implement message deduplication using timestamp/content as a unique id
- **Message truncation**: Send long messages as separate shorter messages