fix: correct postcard encoding for u8/i8 fields (was using varint/zigzag)
ROOT CAUSE: postcard encodes u8 and i8 as raw bytes, NOT varint/zigzag. The Python encoders used varint() for u8 and zigzag() for i8. For small values this was silently correct (varint(7) == raw byte 7), but for tx_power_dbm: zigzag(14) = 28, which the new validation correctly rejected as out of range (-9..22). This was a pre-existing bug masked by the absence of validation. Fix all RadioConfig field encoders in both donglora.py and meshcore_rx.py. Fix docs/PROTOCOL.md to correctly document i8 as raw byte, not zigzag. Restore full config validation.
This commit is contained in:
3 files changed
+22
-15
No files matched your search
+2
-1
@@ -26,8 +26,9 @@ Postcard is a compact binary format. The subset used by DongLoRa:
|
|||||||
| Type | Encoding |
|
| Type | Encoding |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
| `u8` | Raw byte |
|
| `u8` | Raw byte |
|
||||||
|
| `i8` | Raw byte (two's complement) |
|
||||||
| `u16`, `u32` | Varint (LEB128: 7 data bits per byte, MSB = continuation) |
|
| `u16`, `u32` | Varint (LEB128: 7 data bits per byte, MSB = continuation) |
|
||||||
| `i8`, `i16` | Zigzag then varint (`(n << 1) ^ (n >> bits-1)`) |
|
| `i16`, `i32` | Zigzag then varint (`(n << 1) ^ (n >> bits-1)`) |
|
||||||
| Enum variant | Varint of 0-based variant index, then variant fields |
|
| Enum variant | Varint of 0-based variant index, then variant fields |
|
||||||
| `Option<T>` | `0x00` = None, `0x01` + T = Some |
|
| `Option<T>` | `0x00` = None, `0x01` + T = Some |
|
||||||
| `Vec<u8, N>` | Varint length, then raw bytes |
|
| `Vec<u8, N>` | Varint length, then raw bytes |
|
||||||
|
|||||||
+12
-7
@@ -12,6 +12,7 @@ See docs/PROTOCOL.md for the full specification.
|
|||||||
import glob
|
import glob
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
import time
|
import time
|
||||||
from cobs import cobs
|
from cobs import cobs
|
||||||
@@ -112,13 +113,17 @@ def decode_zigzag(data: bytes) -> tuple[int, bytes]:
|
|||||||
# ── Command encoding ─────────────────────────────────────────────
|
# ── Command encoding ─────────────────────────────────────────────
|
||||||
|
|
||||||
def encode_config(cfg: dict) -> bytes:
|
def encode_config(cfg: dict) -> bytes:
|
||||||
"""Encode a RadioConfig dict to postcard bytes."""
|
"""Encode a RadioConfig dict to postcard bytes.
|
||||||
out = varint(cfg["freq_hz"])
|
|
||||||
out += varint(cfg["bw"])
|
Postcard wire format: u8/i8 are raw bytes, u16/u32 are varint,
|
||||||
out += varint(cfg["sf"])
|
i16/i32 are zigzag+varint, enums are varint variant index.
|
||||||
out += varint(cfg["cr"])
|
"""
|
||||||
out += varint(cfg["sync_word"])
|
out = varint(cfg["freq_hz"]) # u32: varint
|
||||||
out += zigzag(cfg["tx_power_dbm"])
|
out += varint(cfg["bw"]) # enum: varint variant index
|
||||||
|
out += struct.pack("B", cfg["sf"]) # u8: raw byte
|
||||||
|
out += struct.pack("B", cfg["cr"]) # u8: raw byte
|
||||||
|
out += varint(cfg["sync_word"]) # u16: varint
|
||||||
|
out += struct.pack("b", cfg["tx_power_dbm"]) # i8: raw signed byte
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -426,12 +426,12 @@ def zigzag(n: int) -> bytes:
|
|||||||
|
|
||||||
|
|
||||||
def encode_radio_config(cfg: dict) -> bytes:
|
def encode_radio_config(cfg: dict) -> bytes:
|
||||||
out = varint(cfg["freq_hz"])
|
out = varint(cfg["freq_hz"]) # u32: varint
|
||||||
out += varint(cfg["bw"])
|
out += varint(cfg["bw"]) # enum: varint variant index
|
||||||
out += varint(cfg["sf"])
|
out += struct.pack("B", cfg["sf"]) # u8: raw byte
|
||||||
out += varint(cfg["cr"])
|
out += struct.pack("B", cfg["cr"]) # u8: raw byte
|
||||||
out += varint(cfg["sync_word"])
|
out += varint(cfg["sync_word"]) # u16: varint
|
||||||
out += zigzag(cfg["tx_power_dbm"])
|
out += struct.pack("b", cfg["tx_power_dbm"]) # i8: raw byte (signed)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -931,7 +931,8 @@ def _format_rssi_snr(rssi: int, snr: int) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def configure_and_listen(ser: serial.Serial):
|
def configure_and_listen(ser: serial.Serial):
|
||||||
send_cmd(ser, {"type": "SetConfig", "config": RADIO_CONFIG}, "SetConfig 910.525/62.5k/SF7/CR4_5")
|
send_cmd(ser, {"type": "Ping"}, "Ping")
|
||||||
|
send_cmd(ser, {"type": "SetConfig", "config": RADIO_CONFIG}, "SetConfig 910.525/62.5k/SF7/CR4/5")
|
||||||
send_cmd(ser, {"type": "StartRx"}, "StartRx")
|
send_cmd(ser, {"type": "StartRx"}, "StartRx")
|
||||||
|
|
||||||
print(f"\n{BWHT}Listening for packets{RST} {DIM}(Ctrl+C to stop){RST}\n")
|
print(f"\n{BWHT}Listening for packets{RST} {DIM}(Ctrl+C to stop){RST}\n")
|
||||||
|
|||||||
Reference in new issue
Block a user