diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index bbad1e40f0..9cc610ff39 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -283,32 +283,110 @@ 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 + +`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 | +|-----------------|----------------------|--------------------------------------------------------------------------| +| 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 | -**Response**: `PACKET_OK` (0x00) on success +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. --- -### 6. Get Message +### 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 (clients MUST ignore) +Byte 4: Channel Index (0-7) +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**: +```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), + } +``` + +--- + +### 7. Get Message **Purpose**: Request the next queued message from the device. @@ -325,13 +403,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 +606,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 +635,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 +807,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 +850,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/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)