From 0d0b31eb52fa794f9e2b08764baf04b039159623 Mon Sep 17 00:00:00 2001 From: Robert Ekl Date: Sun, 8 Mar 2026 21:31:08 -0600 Subject: [PATCH 1/3] docs: sync CLI and payload docs with implementation --- docs/cli_commands.md | 83 ++++++++++++++++++++++++++++++++++++-------- docs/faq.md | 10 +++--- docs/payloads.md | 35 ++++++++----------- 3 files changed, 90 insertions(+), 38 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 7ade97066f..68e8fb9ef9 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -53,7 +53,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore - `time ` **Parameters:** -- `epoc_seconds`: Unix epoc time +- `epoch_seconds`: Unix epoch time --- @@ -63,6 +63,12 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +### Send a zero-hop advert +**Usage:** +- `advert.zerohop` + +--- + ### Start an Over-The-Air (OTA) firmware update **Usage:** - `start ota` @@ -136,7 +142,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -### End capture of rx log to node sotrage +### End capture of rx log to node storage **Usage:** `log stop` --- @@ -200,7 +206,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Default:** Varies by board -**Notes:** This setting only controls the power level of the LoRa chip. Some nodes have an additional power amplifier stage which increases the total output. Referr to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.** +**Notes:** This setting only controls the power level of the LoRa chip. Some nodes have an additional power amplifier stage which increases the total output. Refer to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.** --- @@ -230,6 +236,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Default:** `869.525` **Note:** Requires reboot to apply +**Serial Only:** `set freq ` ### System @@ -287,7 +294,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Serial Only:** - `get prv.key`: Yes -- `set prv.key`: No +- `set prv.key`: Yes **Note:** Requires reboot to take effect after setting @@ -295,16 +302,16 @@ This document provides an overview of CLI commands that can be sent to MeshCore #### Change this node's admin password **Usage:** -- `password ` +- `password ` **Parameters:** -- `password`: Admin password +- `new_password`: New admin password **Set by build flag:** `ADMIN_PASSWORD` **Default:** `password` -**Note:** Echoed back for confirmation +**Note:** Command reply echoes the updated password for confirmation. **Note:** Any node using this password will be added to the admin ACL list. @@ -354,13 +361,25 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +#### View this node's public key +**Usage:** `get public.key` + +--- + +#### View this node's configured role +**Usage:** `get role` + +--- + #### View or change this node's power saving flag (Repeater Only) **Usage:** -- `powersaving ` - `powersaving` +- `powersaving on` +- `powersaving off` **Parameters:** -- `state`: `on`|`off` +- `on`: enable power saving +- `off`: disable power saving **Default:** `on` @@ -769,7 +788,7 @@ region save - `gps advert ` **Parameters:** -- `policy`: `none`|`shared`|`prefs` +- `policy`: `none`|`share`|`prefs` - `none`: don't include location in adverts - `share`: share gps location (from SensorManager) - `prefs`: location stored in node's lat and lon settings @@ -803,6 +822,11 @@ region save ### Bridge (When bridge support is compiled in) +#### View the compiled bridge type +**Usage:** `get bridge.type` + +--- + #### View or change the bridge enabled flag **Usage:** - `get bridge.enabled` @@ -840,10 +864,10 @@ region save **Parameters:** - `source`: - - `rx`: bridges received packets - - `tx`: bridges transmitted packets + - `logRx`: bridges received packets + - `logTx`: bridges transmitted packets -**Default:** `tx` +**Default:** `logTx` --- @@ -875,8 +899,39 @@ region save - `set bridge.secret ` **Parameters:** -- `secret`: 16-character encryption secret +- `secret`: ESP-NOW bridge secret, up to 15 characters **Default:** Varies by board --- + +#### View the bootloader version (nRF52 only) +**Usage:** `get bootloader.ver` + +--- + +#### View power management support +**Usage:** `get pwrmgt.support` + +--- + +#### View the current power source +**Usage:** `get pwrmgt.source` + +**Note:** Returns an error on boards without power management support. + +--- + +#### View the boot reset and shutdown reasons +**Usage:** `get pwrmgt.bootreason` + +**Note:** Returns an error on boards without power management support. + +--- + +#### View the boot voltage +**Usage:** `get pwrmgt.bootmv` + +**Note:** Returns an error on boards without power management support. + +--- diff --git a/docs/faq.md b/docs/faq.md index 220b8971cb..530f97013b 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -221,11 +221,11 @@ MeshCore allows you to manually broadcast your name, position and public encrypt * Zero hop means your advert is broadcasted out to anyone that can hear it, and that's it. * Flooded means it's broadcasted out and then repeated by all the repeaters that hear it. -MeshCore clients only advertise themselves when the user initiates it. A repeater sends a flood advert once every 3 hours by default. This interval can be configured using the following command: +MeshCore clients only advertise themselves when the user initiates it. A repeater sends a flood advert once every 12 hours by default. This interval can be configured using the following command: -`set advert.interval {minutes}` +`set flood.advert.interval {hours}` -As of Aug 20 2025, a pending PR on github will change the flood advert to 12 hours to minimize airtime utilization caused by repeaters' flood adverts. +The separate `set advert.interval {minutes}` command controls the local zero-hop advert timer. ### 2.5. Q: Is there a hop limit? @@ -260,7 +260,9 @@ Repeater or room server can be administered with one of the options below: ### 3.2. Q: Do I need to set the location for a repeater? **A:** While not required, with location set for a repeater it will show up on the MeshCore map in the future. Set location with the following command: -`set lat set long ` +`set lat ` + +`set lon ` You can get the latitude and longitude from Google Maps by right-clicking the location you are at on the map. diff --git a/docs/payloads.md b/docs/payloads.md index 3648b65579..15fec7578a 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -90,23 +90,17 @@ Returned path messages provide a description of the route a packet took from the ## Request -| Field | Size (bytes) | Description | -|--------------|-----------------|----------------------------| -| timestamp | 4 | send time (unix timestamp) | -| request type | 1 | see below | -| request data | rest of payload | depends on request type | +| Field | Size (bytes) | Description | +|--------------|-----------------|------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| request data | rest of payload | application-defined request payload body | -Request type +For the common chat/server helpers in `BaseChatMesh`, the current request type values are: | Value | Name | Description | |--------|----------------------|---------------------------------------| | `0x01` | get stats | get stats of repeater or room server | -| `0x02` | keepalive | (deprecated) | -| `0x03` | get telemetry data | TODO | -| `0x04` | get min,max,avg data | sensor nodes - get min, max, average for given time span | -| `0x05` | get access list | get node's approved access list | -| `0x06` | get neighbors | get repeater node's neighbors | -| `0x07` | get owner info | get repeater firmware-ver/name/owner info | +| `0x02` | keepalive | keep-alive request used for maintained connections | ### Get stats @@ -133,35 +127,36 @@ Gets information about the node, possibly including the following: ### Get telemetry data -Request data about sensors on the node, including battery level. +Not defined in `BaseChatMesh`. Sensor- and application-specific request payloads may be implemented by higher-level firmware. ### Get Telemetry -TODO +Not defined in `BaseChatMesh`. ### Get Min/Max/Ave (Sensor nodes) -TODO +Not defined in `BaseChatMesh`. ### Get Access List -TODO +Not defined in `BaseChatMesh`. ### Get Neighors -TODO +Not defined in `BaseChatMesh`. ### Get Owner Info -TODO +Not defined in `BaseChatMesh`. ## Response | Field | Size (bytes) | Description | |---------|-----------------|-------------| -| tag | 4 | TODO | -| content | rest of payload | TODO | +| content | rest of payload | application-defined response body | + +Response contents are opaque application data. There is no single generic response envelope beyond the encrypted payload wrapper shown above. ## Plain text message From 6677b409540843a1b4b00b11515d11f8d5357d78 Mon Sep 17 00:00:00 2001 From: Robert Ekl Date: Sun, 8 Mar 2026 21:31:08 -0600 Subject: [PATCH 2/3] docs: sync companion and kiss protocol docs --- docs/companion_protocol.md | 176 ++++++++++++------------------------ docs/kiss_modem_protocol.md | 6 +- 2 files changed, 59 insertions(+), 123 deletions(-) diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index 9d45b59efa..11ba0ab24c 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -1,6 +1,6 @@ # Companion Protocol -- **Last Updated**: 2026-01-03 +- **Last Updated**: 2026-03-08 - **Protocol Version**: Companion Firmware v1.12.0+ > NOTE: This document is still in development. Some information may be inaccurate. @@ -100,7 +100,7 @@ When writing commands to the RX characteristic, specify the write type: ### MTU (Maximum Transmission Unit) -The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SET_CHANNEL` (66 bytes), you may need to: +The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SET_CHANNEL` (50 bytes), you may need to: 1. **Request Larger MTU**: Request MTU of 512 bytes if supported - Android: `gatt.requestMtu(512)` @@ -167,16 +167,16 @@ The first byte indicates the packet type (see [Response Parsing](#response-parsi **Command Format**: ``` Byte 0: 0x01 -Byte 1: 0x03 -Bytes 2-10: "mccli" (ASCII, null-padded to 9 bytes) +Bytes 1-7: Reserved (currently ignored by firmware) +Bytes 8+: Application name (UTF-8, optional) ``` **Example** (hex): ``` -01 03 6d 63 63 6c 69 00 00 00 00 +01 00 00 00 00 00 00 00 6d 63 63 6c 69 ``` -**Response**: `PACKET_OK` (0x00) +**Response**: `PACKET_SELF_INFO` (0x05) --- @@ -216,8 +216,6 @@ Byte 1: Channel Index (0-7) **Response**: `PACKET_CHANNEL_INFO` (0x12) with channel details -**Note**: The device does not return channel secrets for security reasons. Store secrets locally when creating channels. - --- ### 4. Set Channel @@ -229,10 +227,10 @@ Byte 1: Channel Index (0-7) Byte 0: 0x20 Byte 1: Channel Index (0-7) Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded) -Bytes 34-65: Secret (32 bytes) +Bytes 34-49: Secret (16 bytes) ``` -**Total Length**: 66 bytes +**Total Length**: 50 bytes **Channel Index**: - Index 0: Reserved for public channels (no secret) @@ -243,16 +241,18 @@ Bytes 34-65: Secret (32 bytes) - Maximum 32 bytes - Padded with null bytes (0x00) if shorter -**Secret Field** (32 bytes): -- For **private channels**: 32-byte secret +**Secret Field** (16 bytes): +- For **private channels**: 16-byte secret - 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) - [32 bytes of secret] + [16 bytes of secret] ``` +**Note**: The 32-byte secret variant is unsupported and returns `PACKET_ERROR`. + **Response**: `PACKET_OK` (0x00) on success, `PACKET_ERROR` (0x01) on failure --- @@ -304,9 +304,9 @@ Byte 0: 0x0A --- -### 7. Get Battery +### 7. Get Battery and Storage -**Purpose**: Query device battery level. +**Purpose**: Query device battery voltage and storage usage. **Command Format**: ``` @@ -318,7 +318,7 @@ Byte 0: 0x14 14 ``` -**Response**: `PACKET_BATTERY` (0x0C) with battery percentage +**Response**: `PACKET_BATTERY` (0x0C) with battery millivolts and storage information --- @@ -346,7 +346,7 @@ Byte 0: 0x14 1. **Set Channel**: - Fetch all channel slots, and find one with empty name and all-zero secret - Generate or provide a 16-byte secret - - Send `CMD_SET_CHANNEL` with name and secret + - Send `CMD_SET_CHANNEL` with name and a 16-byte secret 2. **Get Channel**: - Send `CMD_GET_CHANNEL` with channel index - Parse `RESP_CODE_CHANNEL_INFO` response @@ -360,7 +360,7 @@ Byte 0: 0x14 ### Receiving Messages -Messages are received via the RX characteristic (notifications). The device sends: +Messages are received via the TX characteristic (notifications). The device sends: 1. **Channel Messages**: - `PACKET_CHANNEL_MSG_RECV` (0x08) - Standard format @@ -544,10 +544,10 @@ Byte 1: Error code (optional) Byte 0: 0x12 Byte 1: Channel Index Bytes 2-33: Channel Name (32 bytes, null-terminated) -Bytes 34-65: Secret (32 bytes, but device typically only returns 20 bytes total) +Bytes 34-49: Secret (16 bytes) ``` -**Note**: The device may not return the full 66-byte packet. Parse what is available. The secret field is typically not returned for security reasons. +**Note**: The device returns the 16-byte channel secret in this response. **PACKET_DEVICE_INFO** (0x0D): ``` @@ -562,6 +562,8 @@ 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) +Byte 80: Client repeat enabled/preferred (firmware v9+) +Byte 81: Path hash mode (firmware v10+) ``` **Parsing Pseudocode**: @@ -587,9 +589,7 @@ def parse_device_info(data): **PACKET_BATTERY** (0x0C): ``` Byte 0: 0x0C -Bytes 1-2: Battery Level (16-bit little-endian, percentage 0-100) - -Optional (if data size > 3): +Bytes 1-2: Battery Voltage (16-bit little-endian, millivolts) Bytes 3-6: Used Storage (32-bit little-endian, KB) Bytes 7-10: Total Storage (32-bit little-endian, KB) ``` @@ -600,14 +600,12 @@ def parse_battery(data): if len(data) < 3: return None - level = int.from_bytes(data[1:3], 'little') - info = {'level': level} + mv = int.from_bytes(data[1:3], 'little') + info = {'battery_mv': mv} - if len(data) > 3: - used_kb = int.from_bytes(data[3:7], 'little') - total_kb = int.from_bytes(data[7:11], 'little') - info['used_kb'] = used_kb - info['total_kb'] = total_kb + if len(data) >= 11: + info['used_kb'] = int.from_bytes(data[3:7], 'little') + info['total_kb'] = int.from_bytes(data[7:11], 'little') return info ``` @@ -629,7 +627,7 @@ 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 -Bytes 58+: Device Name (UTF-8, variable length, null-terminated) +Bytes 58+: Device Name (UTF-8, variable length, no null terminator required) ``` **Parsing Pseudocode**: @@ -680,9 +678,9 @@ def parse_self_info(data): **PACKET_MSG_SENT** (0x06): ``` Byte 0: 0x06 -Byte 1: Message Type -Bytes 2-5: Expected ACK (4 bytes, hex) -Bytes 6-9: Suggested Timeout (32-bit little-endian, seconds) +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) ``` **PACKET_ACK** (0x82): @@ -710,89 +708,32 @@ Bytes 1-6: ACK Code (6 bytes, hex) **Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response. -### Partial Packet Handling - -BLE notifications may arrive in chunks, especially for larger packets. Implement buffering: - -**Implementation**: -```python -class PacketBuffer: - def __init__(self): - self.buffer = bytearray() - self.expected_length = None - - def add_data(self, data): - self.buffer.extend(data) - - # Check if we have a complete packet - if len(self.buffer) >= 1: - packet_type = self.buffer[0] - - # Determine expected length based on packet type - expected = self.get_expected_length(packet_type) - - if expected is not None and len(self.buffer) >= expected: - # Complete packet - packet = bytes(self.buffer[:expected]) - self.buffer = self.buffer[expected:] - return packet - elif expected is None: - # Variable length packet - try to parse what we have - # Some packets have minimum length requirements - if self.can_parse_partial(packet_type): - return self.try_parse_partial() - - return None # Incomplete packet - - def get_expected_length(self, packet_type): - # Fixed-length packets - fixed_lengths = { - 0x00: 5, # PACKET_OK (minimum) - 0x01: 2, # PACKET_ERROR (minimum) - 0x0A: 1, # PACKET_NO_MORE_MSGS - 0x14: 3, # PACKET_BATTERY (minimum) - } - return fixed_lengths.get(packet_type) - - def can_parse_partial(self, packet_type): - # Some packets can be parsed partially - return packet_type in [0x12, 0x08, 0x11, 0x07, 0x10, 0x05, 0x0D] - - def try_parse_partial(self): - # Try to parse with available data - # Return packet if successfully parsed, None otherwise - # This is packet-type specific - pass -``` +### Frame Handling -**Usage**: -```python -buffer = PacketBuffer() +BLE implementations enqueue and deliver one protocol frame per BLE write/notification at the firmware layer. -def on_notification_received(data): - packet = buffer.add_data(data) - if packet: - parse_and_handle_packet(packet) -``` +- 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 ### Response Handling 1. **Command-Response Pattern**: - - Send command via TX characteristic - - Wait for response via RX characteristic (notification) + - Send command via RX characteristic + - Wait for response via TX characteristic (notification) - 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**: - - Device may send messages at any time via RX characteristic + - Device may send messages at any time via TX characteristic - Handle `PACKET_MESSAGES_WAITING` (0x83) by polling `GET_MESSAGE` command - Parse incoming messages and route to appropriate handlers - - Buffer partial packets until complete + - Validate frame length before decoding 3. **Response Matching**: - Match responses to commands by expected packet type: - - `APP_START` → `PACKET_OK` + - `APP_START` → `PACKET_SELF_INFO` - `DEVICE_QUERY` → `PACKET_DEVICE_INFO` - `GET_CHANNEL` → `PACKET_CHANNEL_INFO` - `SET_CHANNEL` → `PACKET_OK` or `PACKET_ERROR` @@ -825,16 +766,16 @@ device = scan_for_device("MeshCore") gatt = connect_to_device(device) # 3. Discover services and characteristics -service = discover_service(gatt, "0000ff00-0000-1000-8000-00805f9b34fb") -rx_char = discover_characteristic(service, "0000ff01-0000-1000-8000-00805f9b34fb") -tx_char = discover_characteristic(service, "0000ff02-0000-1000-8000-00805f9b34fb") +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") -# 4. Enable notifications on RX characteristic -enable_notifications(rx_char, on_notification_received) +# 4. Enable notifications on TX characteristic +enable_notifications(tx_char, on_notification_received) # 5. Send AppStart command -send_command(tx_char, build_app_start()) -wait_for_response(PACKET_OK) +send_command(rx_char, build_app_start()) +wait_for_response(PACKET_SELF_INFO) ``` ### Creating a Private Channel @@ -844,21 +785,16 @@ wait_for_response(PACKET_OK) secret_16_bytes = generate_secret(16) # Use CSPRNG secret_hex = secret_16_bytes.hex() -# 2. Expand secret to 32 bytes using SHA-512 -import hashlib -sha512_hash = hashlib.sha512(secret_16_bytes).digest() -secret_32_bytes = sha512_hash[:32] - -# 3. Build SET_CHANNEL command +# 2. Build SET_CHANNEL command channel_name = "YourChannelName" channel_index = 1 # Use 1-7 for private channels -command = build_set_channel(channel_index, channel_name, secret_32_bytes) +command = build_set_channel(channel_index, channel_name, secret_16_bytes) -# 4. Send command -send_command(tx_char, command) +# 3. Send command +send_command(rx_char, command) response = wait_for_response(PACKET_OK) -# 5. Store secret locally (device won't return it) +# 4. Store secret locally store_channel_secret(channel_index, secret_hex) ``` @@ -872,7 +808,7 @@ timestamp = int(time.time()) command = build_channel_message(channel_index, message, timestamp) # 2. Send command -send_command(tx_char, command) +send_command(rx_char, command) response = wait_for_response(PACKET_MSG_SENT) ``` @@ -887,7 +823,7 @@ def on_notification_received(data): handle_channel_message(message) elif packet_type == PACKET_MESSAGES_WAITING: # Poll for messages - send_command(tx_char, build_get_message()) + send_command(rx_char, build_get_message()) ``` --- diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 6a08614fa1..8fbb57e456 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -190,7 +190,7 @@ All values little-endian. | Field | Size | Description | |-------|------|-------------| | MAC | 2 bytes | HMAC-SHA256 truncated to 2 bytes | -| Ciphertext | variable | AES-128-CBC encrypted data | +| Ciphertext | variable | AES-128 block-encrypted data with zero padding | ### Airtime (Airtime response) @@ -268,7 +268,7 @@ Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs. |-----------|-----------| | Identity / Signing / Verification | Ed25519 | | Key Exchange | X25519 (ECDH) | -| Encryption | AES-128-CBC + HMAC-SHA256 (MAC truncated to 2 bytes) | +| Encryption | AES-128 block encryption with zero padding + HMAC-SHA256 (MAC truncated to 2 bytes) | | Hashing | SHA-256 | ## Notes @@ -279,4 +279,4 @@ Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs. - SNR values in RxMeta are multiplied by 4 for 0.25 dB precision - TxDone is sent as a SetHardware event after each transmission - Standard KISS clients receive only type 0x00 data frames and can safely ignore all SetHardware (0x06) frames -- See [packet_structure.md](./packet_structure.md) for packet format +- See [packet_format.md](./packet_format.md) for packet format From 095f97b3556e94dd8e32d58b2b6db20c73182478 Mon Sep 17 00:00:00 2001 From: Robert Ekl Date: Tue, 10 Mar 2026 22:12:55 -0500 Subject: [PATCH 3/3] set prv.key doc update --- docs/cli_commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 68e8fb9ef9..03c7e509f3 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -294,7 +294,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Serial Only:** - `get prv.key`: Yes -- `set prv.key`: Yes +- `set prv.key`: No **Note:** Requires reboot to take effect after setting