# DongLoRa USB Protocol DongLoRa communicates over USB CDC-ACM (virtual serial port). No baud rate configuration needed. **USB identifiers:** VID `1209`, PID `5741` ## Framing Each message is [COBS](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing)-encoded and terminated with a `0x00` sentinel byte: ``` [COBS-encoded bytes] [0x00] ``` COBS guarantees `0x00` never appears in the encoded data, so the sentinel unambiguously marks frame boundaries. USB packets are chunked at 64 bytes. ## Encoding All integers are **fixed-size little-endian**. No variable-length encoding. Every field has a known size at a known offset. | Type | Size | Encoding | |------|------|----------| | `u8` | 1 | Raw byte | | `i8` | 1 | Raw byte (two's complement) | | `u16` | 2 | Little-endian | | `i16` | 2 | Little-endian (two's complement) | | `u32` | 4 | Little-endian | ## RadioConfig (10 bytes) | Offset | Size | Field | Type | Valid range | |--------|------|-------|------|-------------| | 0 | 4 | `freq_hz` | u32 LE | 150,000,000 – 960,000,000 | | 4 | 1 | `bw` | u8 | 0–9 (see bandwidth table) | | 5 | 1 | `sf` | u8 | 5–12 | | 6 | 1 | `cr` | u8 | 5–8 (coding rate denominator) | | 7 | 2 | `sync_word` | u16 LE | any | | 9 | 1 | `tx_power_dbm` | i8 | board-dependent, or `-128` for max | Set `tx_power_dbm` to `-128` (0x80) to use the board's maximum TX power. The firmware resolves this to the actual hardware max. ## Commands (Host → Firmware) Each command is a tag byte followed by fixed-size fields. | Tag | Command | Payload | |-----|---------|---------| | 0 | Ping | — | | 1 | GetConfig | — | | 2 | SetConfig | RadioConfig (10 bytes) | | 3 | StartRx | — | | 4 | StopRx | — | | 5 | Transmit | has_config (1) + [RadioConfig if 1] + len (u16 LE) + payload | | 6 | DisplayOn | — | | 7 | DisplayOff | — | | 8 | GetMac | — | ## Responses (Firmware → Host) | Tag | Response | Payload | |-----|----------|---------| | 0 | Pong | — | | 1 | Config | RadioConfig (10 bytes) | | 2 | RxPacket | rssi (i16 LE) + snr (i16 LE) + len (u16 LE) + payload | | 3 | TxDone | — | | 4 | Ok | — | | 5 | Error | code (u8) | | 6 | MacAddress | 6 raw bytes (board's MAC/device address) | ## Bandwidth | Value | Bandwidth | |-------|-----------| | 0 | 7.8 kHz | | 1 | 10.4 kHz | | 2 | 15.6 kHz | | 3 | 20.8 kHz | | 4 | 31.25 kHz | | 5 | 41.7 kHz | | 6 | 62.5 kHz | | 7 | 125 kHz | | 8 | 250 kHz | | 9 | 500 kHz | ## Error Codes | Value | Error | Meaning | |-------|-------|---------| | 0 | InvalidConfig | Radio config validation failed | | 1 | RadioBusy | Radio busy (e.g. RX restart failed) | | 2 | TxTimeout | Transmission timed out | | 3 | CrcError | CRC error on received packet | | 4 | NotConfigured | SetConfig required first | | 5 | NoDisplay | No display attached | ## Worked Example Configure for 915 MHz, 125 kHz BW, SF7, CR 4/5, max TX power: ### 1. Build the bytes ```python import struct config = struct.pack("