150 lines
5.3 KiB
Markdown
150 lines
5.3 KiB
Markdown
# DongLoRa USB Protocol Specification
|
||
|
||
DongLoRa communicates over USB CDC-ACM (virtual serial port) using
|
||
binary messages. No baud rate configuration is needed — USB handles
|
||
the transport.
|
||
|
||
**USB identifiers:** VID `1209`, PID `5741`
|
||
|
||
## Framing
|
||
|
||
Each message is serialized with [postcard](https://postcard.jamesmunns.com/)
|
||
and framed with [COBS](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing):
|
||
|
||
```
|
||
[COBS-encoded postcard bytes] [0x00 sentinel]
|
||
```
|
||
|
||
COBS guarantees that `0x00` never appears in the encoded data, so the
|
||
sentinel byte unambiguously marks the end of each frame. Maximum frame
|
||
size is 512 bytes. USB packets are chunked at 64 bytes.
|
||
|
||
## Serialization (postcard)
|
||
|
||
Postcard is a compact binary format. The subset used by DongLoRa:
|
||
|
||
| Type | Encoding |
|
||
|------|----------|
|
||
| `u8` | Raw byte |
|
||
| `i8` | Raw byte (two's complement) |
|
||
| `u16`, `u32` | Varint (LEB128: 7 data bits per byte, MSB = continuation) |
|
||
| `i16`, `i32` | Zigzag then varint (`(n << 1) ^ (n >> bits-1)`) |
|
||
| Enum variant | Varint of 0-based variant index, then variant fields |
|
||
| `Option<T>` | `0x00` = None, `0x01` + T = Some |
|
||
| `Vec<u8, N>` | Varint length, then raw bytes |
|
||
| Struct | Fields in declaration order, no delimiters |
|
||
|
||
## Commands (Host → Firmware)
|
||
|
||
| Index | Command | Fields | Description |
|
||
|-------|---------|--------|-------------|
|
||
| 0 | `Ping` | — | Health check. Returns `Pong`. |
|
||
| 1 | `GetConfig` | — | Request current radio config. Returns `Config` or `Error(NotConfigured)`. |
|
||
| 2 | `SetConfig` | `RadioConfig` | Set radio parameters. Validated against hardware limits. Returns `Ok` or `Error(InvalidConfig)`. |
|
||
| 3 | `StartRx` | — | Enter continuous receive mode. Returns `Ok` or `Error`. |
|
||
| 4 | `StopRx` | — | Return to idle. Returns `Ok`. |
|
||
| 5 | `Transmit` | `Option<RadioConfig>`, `Vec<u8, 256>` | Transmit a packet. Optional per-packet config override. Returns `TxDone` or `Error`. Automatically resumes RX if it was active. |
|
||
| 6 | `DisplayOn` | — | Turn on the OLED display. |
|
||
| 7 | `DisplayOff` | — | Turn off the OLED display. |
|
||
|
||
## Responses (Firmware → Host)
|
||
|
||
| Index | Response | Fields | Description |
|
||
|-------|----------|--------|-------------|
|
||
| 0 | `Pong` | — | Reply to `Ping`. |
|
||
| 1 | `Config` | `RadioConfig` | Current radio configuration. |
|
||
| 2 | `RxPacket` | `rssi: i16`, `snr: i16`, `payload: Vec<u8, 256>` | Received packet with signal quality. |
|
||
| 3 | `TxDone` | — | Transmission complete. |
|
||
| 4 | `Ok` | — | Command succeeded (SetConfig, StartRx, StopRx). |
|
||
| 5 | `Error` | `ErrorCode` | Command failed. |
|
||
|
||
## RadioConfig
|
||
|
||
| Field | Type | Wire encoding | Valid range | Description |
|
||
|-------|------|---------------|-------------|-------------|
|
||
| `freq_hz` | `u32` | varint | 150,000,000 – 960,000,000 | Frequency in Hz |
|
||
| `bw` | `Bandwidth` | varint (0–9) | see table | Signal bandwidth |
|
||
| `sf` | `u8` | raw byte | 5 – 12 | Spreading factor |
|
||
| `cr` | `u8` | raw byte | 5 – 8 | Coding rate denominator (5 = CR 4/5) |
|
||
| `sync_word` | `u16` | varint | any | LoRa sync word (e.g. `0x3444`) |
|
||
| `tx_power_dbm` | `i8` | raw signed byte | board-dependent | TX power in dBm. `-128` = board's max power. |
|
||
|
||
## Bandwidth
|
||
|
||
| Wire 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 |
|
||
|
||
## ErrorCode
|
||
|
||
| Wire value | Error | Meaning |
|
||
|------------|-------|---------|
|
||
| 0 | InvalidConfig | Radio config validation failed |
|
||
| 1 | RadioBusy | Radio is busy (e.g. RX restart failed) |
|
||
| 2 | TxTimeout | Transmission timed out |
|
||
| 3 | CrcError | CRC error on received packet |
|
||
| 4 | NotConfigured | Command requires SetConfig first |
|
||
| 5 | NoDisplay | Display command sent but no display attached |
|
||
|
||
## Worked Example
|
||
|
||
Configure the radio for 915 MHz, 125 kHz BW, SF7, CR 4/5, sync word `0x3444`,
|
||
14 dBm TX power.
|
||
|
||
### 1. Build the RadioConfig
|
||
|
||
| Field | Value | Wire bytes | Encoding |
|
||
|-------|-------|-----------|----------|
|
||
| `freq_hz` | 915,000,000 | `C0 95 A7 B4 03` | varint |
|
||
| `bw` | 7 (125 kHz) | `07` | varint (enum index) |
|
||
| `sf` | 7 | `07` | raw byte |
|
||
| `cr` | 5 (CR 4/5) | `05` | raw byte |
|
||
| `sync_word` | 0x3444 | `C4 68` | varint |
|
||
| `tx_power_dbm` | 14 | `0E` | raw signed byte |
|
||
|
||
Or use `-128` (`0x80`) for `tx_power_dbm` to request the board's max power.
|
||
|
||
### 2. Build the SetConfig command
|
||
|
||
Prepend the variant index for `SetConfig` (index 2):
|
||
|
||
```
|
||
02 C0 95 A7 B4 03 07 07 05 C4 68 0E
|
||
```
|
||
|
||
### 3. COBS-encode and send
|
||
|
||
COBS encoding wraps the bytes so `0x00` never appears in the data,
|
||
then appends a `0x00` sentinel:
|
||
|
||
```
|
||
[COBS-encoded bytes] 00
|
||
```
|
||
|
||
Write the resulting bytes to the USB serial port. Read back a COBS
|
||
frame, decode it, and the first byte is the response variant index
|
||
(4 = `Ok`).
|
||
|
||
## Host Implementation Checklist
|
||
|
||
1. Open the USB serial port (find by VID:PID `1209:5741`)
|
||
2. Implement varint encoding/decoding (for `u16`/`u32` and enum variants)
|
||
3. Note: `u8` and `i8` are raw bytes, NOT varint/zigzag
|
||
4. Implement COBS encode/decode (use a library — available in every language)
|
||
5. Build `SetConfig` with your desired radio parameters (use `-128` for max TX power)
|
||
6. Send it, read back `Ok`
|
||
7. Send `StartRx`, read back `Ok`
|
||
8. Loop: read frames, decode `RxPacket` responses
|
||
9. To transmit: send `Transmit` with payload, read back `TxDone`
|
||
|
||
See `examples/` for working Python implementations.
|