From b705d5489f136477a93f13b9e407277903717787 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Fri, 24 Apr 2026 04:24:20 -0700 Subject: [PATCH 1/3] Update companion protocol documentation and enhance data type definitions for clarity --- docs/companion_protocol.md | 135 +++++++++++++++++++++++++++-------- src/helpers/TxtDataHelpers.h | 3 + 2 files changed, 109 insertions(+), 29 deletions(-) diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index bbad1e40f0..fc2ad32da6 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -283,32 +283,101 @@ Bytes 7+: Message Text (UTF-8, variable length) ### 6. Send Channel Data Datagram -**Purpose**: Send binary datagram data to a channel. +**Purpose**: Send a binary datagram to a channel. Unlike channel text messages, datagrams carry no built-in sender identity and no timestamp — applications needing either must encode them inside the binary payload. **Command Format**: ``` -Byte 0: 0x3E -Bytes 1-2: Data Type (`data_type`, 16-bit little-endian) -Byte 3: Channel Index (0-7) -Bytes 4+: Binary payload bytes (variable length) +Byte 0: 0x3E +Byte 1: Channel Index (0-7) +Byte 2: Path Length (0xFF = flood, otherwise actual path length) +Bytes 3 .. 2+path_len: Path (omitted when path_len == 0xFF) +Next 2 bytes (little-endian): Data Type (`data_type`, uint16) +Remaining bytes: Binary payload (variable length) +``` + +**Example** (flood, `DATA_TYPE_DEV`, payload `A1 B2 C3`, channel 1): +``` +3E 01 FF FF FF A1 B2 C3 ``` **Data Type / Transport Mapping**: -- `0x0000` is invalid for this command. +- `0x0000` (`DATA_TYPE_RESERVED`) is invalid and rejected with `PACKET_ERROR`. - `0xFFFF` (`DATA_TYPE_DEV`) is the developer namespace for experimenting and developing apps. -- Other non-zero values can be used as assigned application/community namespaces. - -**Note**: Applications that need a timestamp should encode it inside the binary payload. +- Values `0x0001`–`0xFFFE` are available for registered application/community namespaces. See the [Registered data_type values](#registered-data_type-values) table below. **Limits**: -- Maximum payload length is `163` bytes. -- Larger payloads are rejected with `PACKET_ERROR`. +- Maximum payload length is `MAX_CHANNEL_DATA_LENGTH = MAX_FRAME_SIZE - 9 = 163` bytes. +- Larger payloads are rejected with `PACKET_ERROR` (`ERR_CODE_ILLEGAL_ARG`). + +**Response**: `PACKET_OK` (0x00) on success, or `PACKET_ERROR` (0x01) with one of: +- `ERR_CODE_NOT_FOUND` (2) — unknown `channel_idx` +- `ERR_CODE_ILLEGAL_ARG` (6) — invalid `path_len`, reserved `data_type` (`0x0000`), or payload larger than `MAX_CHANNEL_DATA_LENGTH` +- `ERR_CODE_TABLE_FULL` (3) — outbound send queue is full; retry later + +**Inbound datagrams** are delivered to the host via `RESP_CODE_CHANNEL_DATA_RECV` (0x1B); see [Receive Channel Data Datagram](#receive-channel-data-datagram). + +#### Registered `data_type` values + +Declared in `src/helpers/TxtDataHelpers.h`. These values have agreed-upon payload schemas so different client apps on the same channel can interoperate. + +| Value | Constant | Purpose | Payload schema | +|--------|----------------------|---------------------------------------------|--------------------------------------------------------------------------------------------------------| +| 0x0000 | `DATA_TYPE_RESERVED` | Reserved; invalid on send | — | +| 0x0001 | `DATA_TYPE_SMAZ_TEXT`| Raw SMAZ-compressed UTF-8 text | `[sender_name_len: u8][sender_name: UTF-8 × sender_name_len][smaz_bytes: remaining]` | +| 0x0002 | `DATA_TYPE_GIPHY_GIF`| Giphy GIF id (avoids base64 tax) | `[sender_name_len: u8][sender_name: UTF-8 × sender_name_len][giphy_id: ASCII, remaining]` | +| 0x0003 | `DATA_TYPE_REACTION` | Emoji reaction targeting a prior message | `[target_hash: 2 bytes LE][emoji_index: u8][sender_name_len: u8][sender_name: UTF-8 × sender_name_len]` | +| 0xFFFF | `DATA_TYPE_DEV` | Developer/experimental namespace | Application-defined | + +The firmware does not inspect the payload contents — `data_type` is transported opaquely, and the schemas above are a client-side contract between cooperating apps. + +To request a new registered value, submit a PR adding a `#define` to `TxtDataHelpers.h` and a row to this table. + +--- + +### Receive Channel Data Datagram + +Inbound group datagrams (radio-level `PAYLOAD_TYPE_GRP_DATA`, 0x06) are forwarded to the host as `RESP_CODE_CHANNEL_DATA_RECV` notifications. + +**Frame Format** (`RESP_CODE_CHANNEL_DATA_RECV`, 0x1B): +``` +Byte 0: 0x1B (packet type) +Byte 1: SNR (signed int8, scaled ×4 — divide by 4.0 to recover dB) +Bytes 2-3: Reserved +Byte 4: Channel Index (0-7) +Byte 5: Path Length (actual path when flooded, otherwise 0xFF) +Bytes 6-7: Data Type (uint16 little-endian) +Byte 8: Data Length +Bytes 9 .. 8+data_len: Payload +``` -**Response**: `PACKET_OK` (0x00) on success +**Note**: The device may also emit `PACKET_MESSAGES_WAITING` (0x83) to notify the host that datagrams are queued; poll with `CMD_SYNC_NEXT_MESSAGE` (0x0A) to retrieve them. + +**Parsing Pseudocode**: +```python +def parse_channel_data_recv(data): + if len(data) < 9: + return None + snr_byte = data[1] + snr = (snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0 + channel_idx = data[4] + path_len = data[5] + data_type = int.from_bytes(data[6:8], 'little') + data_len = data[8] + if 9 + data_len > len(data): + return None + payload = data[9:9 + data_len] + return { + 'snr': snr, + 'channel_idx': channel_idx, + 'path_len': path_len, + 'data_type': data_type, + 'payload': bytes(payload), + } +``` --- -### 6. Get Message +### 7. Get Message **Purpose**: Request the next queued message from the device. @@ -325,13 +394,14 @@ Byte 0: 0x0A **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_CHANNEL_DATA_RECV` (0x1B) for channel data datagrams - `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. --- -### 7. Get Battery and Storage +### 8. Get Battery and Storage **Purpose**: Query device battery voltage and storage usage. @@ -527,6 +597,15 @@ Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)). ## Response Parsing +### Terminology + +This document uses a spec-level naming convention (`PACKET_*`) for bytes the firmware sends back to the host. In the firmware source these same values are split across two `#define` families by purpose: + +- `RESP_CODE_*` — direct replies to a command (e.g. `RESP_CODE_CHANNEL_DATA_RECV` = `PACKET_CHANNEL_DATA_RECV` = 0x1B). +- `PUSH_CODE_*` — asynchronous notifications not tied to a specific command (e.g. `PUSH_CODE_MSG_WAITING` = `PACKET_MESSAGES_WAITING` = 0x83). + +Byte values are authoritative; names are aliases. When reading firmware source, `RESP_CODE_X` / `PUSH_CODE_X` correspond to this doc's `PACKET_X` of the same numeric value. + ### Packet Types | Value | Name | Description | @@ -547,6 +626,7 @@ Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)). | 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 | +| 0x1B | PACKET_CHANNEL_DATA_RECV | Channel data datagram | | 0x80 | PACKET_ADVERTISEMENT | Advertisement packet | | 0x82 | PACKET_ACK | Acknowledgment | | 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification | @@ -718,22 +798,18 @@ Bytes 1-6: ACK Code (6 bytes, hex) ### Error Codes -**PACKET_ERROR** (0x01) may include an error code in byte 1: +`PACKET_ERROR` (0x01) carries a single-byte error code in byte 1. Values match the `ERR_CODE_*` constants defined in `examples/companion_radio/MyMesh.cpp`: -| 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 | +| Code | Constant (firmware) | Description | +|------|----------------------------|------------------------------------------------------------------------------| +| 1 | `ERR_CODE_UNSUPPORTED_CMD` | Unknown or unsupported command byte / sub-command | +| 2 | `ERR_CODE_NOT_FOUND` | Target not found (channel, contact, message, etc.) | +| 3 | `ERR_CODE_TABLE_FULL` | Internal queue or table is full — retry later | +| 4 | `ERR_CODE_BAD_STATE` | Operation not valid in current device state (e.g. iterator already running) | +| 5 | `ERR_CODE_FILE_IO_ERROR` | Filesystem or storage I/O failure | +| 6 | `ERR_CODE_ILLEGAL_ARG` | Invalid argument (bad length, out-of-range value, reserved field, etc.) | -**Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response. +**Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response, and treat unknown codes as generic errors. ### Frame Handling @@ -765,7 +841,8 @@ BLE implementations enqueue and deliver one protocol frame per BLE write/notific - `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_MESSAGE` → `PACKET_CHANNEL_MSG_RECV`, `PACKET_CONTACT_MSG_RECV`, `PACKET_CHANNEL_DATA_RECV`, or `PACKET_NO_MORE_MSGS` + - `SEND_CHANNEL_DATA` → `PACKET_OK` or `PACKET_ERROR` - `GET_BATTERY` → `PACKET_BATTERY` 4. **Timeout Handling**: diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index ece494f291..432c0f1233 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -7,6 +7,9 @@ #define TXT_TYPE_CLI_DATA 1 // a CLI command #define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender #define DATA_TYPE_RESERVED 0x0000 // reserved for future use +#define DATA_TYPE_SMAZ_TEXT 0x0001 // raw SMAZ-compressed UTF-8 text meshcore-open Flutter client. +#define DATA_TYPE_GIPHY_GIF 0x0002 // ASCII Giphy GIF id meshcore-open Flutter client. +#define DATA_TYPE_REACTION 0x0003 // 2-byte target message hash + 1-byte emoji index meshcore-open Flutter client. #define DATA_TYPE_DEV 0xFFFF // developer namespace for experimenting with group/channel datagrams and building apps class StrHelper { From b6d0b7a5dd06d3e141e089697ddb44dd154dcc15 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Mon, 27 Apr 2026 10:22:17 -0700 Subject: [PATCH 2/3] Refine data type definitions and update registration process in documentation --- docs/companion_protocol.md | 20 +++++++++----------- docs/number_allocations.md | 1 + src/helpers/TxtDataHelpers.h | 3 --- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index fc2ad32da6..8129bc903e 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -318,19 +318,17 @@ Remaining bytes: Binary payload (variable length) #### Registered `data_type` values -Declared in `src/helpers/TxtDataHelpers.h`. These values have agreed-upon payload schemas so different client apps on the same channel can interoperate. +`data_type` is an **application identifier**, not a payload-format identifier. Each registered value identifies an application that owns its own internal payload schemas. The firmware does not inspect payload contents — `data_type` is transported opaquely. -| Value | Constant | Purpose | Payload schema | -|--------|----------------------|---------------------------------------------|--------------------------------------------------------------------------------------------------------| -| 0x0000 | `DATA_TYPE_RESERVED` | Reserved; invalid on send | — | -| 0x0001 | `DATA_TYPE_SMAZ_TEXT`| Raw SMAZ-compressed UTF-8 text | `[sender_name_len: u8][sender_name: UTF-8 × sender_name_len][smaz_bytes: remaining]` | -| 0x0002 | `DATA_TYPE_GIPHY_GIF`| Giphy GIF id (avoids base64 tax) | `[sender_name_len: u8][sender_name: UTF-8 × sender_name_len][giphy_id: ASCII, remaining]` | -| 0x0003 | `DATA_TYPE_REACTION` | Emoji reaction targeting a prior message | `[target_hash: 2 bytes LE][emoji_index: u8][sender_name_len: u8][sender_name: UTF-8 × sender_name_len]` | -| 0xFFFF | `DATA_TYPE_DEV` | Developer/experimental namespace | Application-defined | +| Value | Constant | Purpose | +|-----------------|----------------------|--------------------------------------------------------------------------| +| 0x0000 | `DATA_TYPE_RESERVED` | Reserved; invalid on send | +| 0x0001 – 0x00FF | — | Reserved for internal use | +| 0x0100 – 0xFEFF | — | Registered application namespaces (see [number_allocations.md](number_allocations.md)) | +| 0xFF00 – 0xFFFE | — | Testing/development; no registration required | +| 0xFFFF | `DATA_TYPE_DEV` | Developer/experimental namespace | -The firmware does not inspect the payload contents — `data_type` is transported opaquely, and the schemas above are a client-side contract between cooperating apps. - -To request a new registered value, submit a PR adding a `#define` to `TxtDataHelpers.h` and a row to this table. +To register a new application, submit a PR adding a row to the table in [docs/number_allocations.md](number_allocations.md). Internal sub-formats within an allocated application ID are owned by that application and are not tracked in MeshCore firmware or this document. --- diff --git a/docs/number_allocations.md b/docs/number_allocations.md index 94ad1efda1..c4715c5eb6 100644 --- a/docs/number_allocations.md +++ b/docs/number_allocations.md @@ -15,6 +15,7 @@ Once you have a working app/project, you need to be able to demonstrate it exist | Data-Type range | App name | Contact | |-----------------|-----------------------------|------------------------------------------------------| | 0000 - 00FF | -reserved for internal use- | | +| 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open | | FF00 - FFFF | -reserved for testing/dev- | | (add rows, inside the range 0100 - FEFF for custom apps) diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 432c0f1233..ece494f291 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -7,9 +7,6 @@ #define TXT_TYPE_CLI_DATA 1 // a CLI command #define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender #define DATA_TYPE_RESERVED 0x0000 // reserved for future use -#define DATA_TYPE_SMAZ_TEXT 0x0001 // raw SMAZ-compressed UTF-8 text meshcore-open Flutter client. -#define DATA_TYPE_GIPHY_GIF 0x0002 // ASCII Giphy GIF id meshcore-open Flutter client. -#define DATA_TYPE_REACTION 0x0003 // 2-byte target message hash + 1-byte emoji index meshcore-open Flutter client. #define DATA_TYPE_DEV 0xFFFF // developer namespace for experimenting with group/channel datagrams and building apps class StrHelper { From 9c8eb301a484413c358742f69d37ee32070ec01d Mon Sep 17 00:00:00 2001 From: zjs81 Date: Mon, 27 Apr 2026 10:49:55 -0700 Subject: [PATCH 3/3] Clarify path length semantics and data handling in inbound group datagrams --- docs/companion_protocol.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index 8129bc903e..9cc610ff39 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -340,14 +340,25 @@ Inbound group datagrams (radio-level `PAYLOAD_TYPE_GRP_DATA`, 0x06) are forwarde ``` Byte 0: 0x1B (packet type) Byte 1: SNR (signed int8, scaled ×4 — divide by 4.0 to recover dB) -Bytes 2-3: Reserved +Bytes 2-3: Reserved (clients MUST ignore) Byte 4: Channel Index (0-7) -Byte 5: Path Length (actual path when flooded, otherwise 0xFF) +Byte 5: Path Length (actual path length when flooded, otherwise 0xFF for direct) Bytes 6-7: Data Type (uint16 little-endian) Byte 8: Data Length Bytes 9 .. 8+data_len: Payload ``` +**Path bytes are not forwarded**: Only `path_len` is reported in the receive frame — the path itself is not copied to the host. There are no path bytes between byte 5 and the data_type field at bytes 6–7, regardless of `path_len`. + +**Path Length semantics differ between send and receive**: + +| Direction | `path_len = 0xFF` | `path_len ≠ 0xFF` | +|-----------|---------------------------------|-------------------------------------------------------------------------------------| +| Send | Flood the network | Direct route; the encoded path follows (low 6 bits = hash count, top 2 bits + 1 = hash size; on-wire byte count = `hash_count × hash_size`) | +| Receive | Packet arrived via direct route | Packet was flooded; this is the encoded `pkt->path_len` field as observed (no path bytes follow) | + +In other words, the meaning of `0xFF` is inverted between the two directions, and on receive the field carries metadata only — never a routable path. `path_len` is an encoded byte (see `Packet::isValidPathLen` / `Packet::writePath` in `src/Packet.cpp`), not a raw byte count. + **Note**: The device may also emit `PACKET_MESSAGES_WAITING` (0x83) to notify the host that datagrams are queued; poll with `CMD_SYNC_NEXT_MESSAGE` (0x0A) to retrieve them. **Parsing Pseudocode**: