From 0b4eb045917a6a36d37a574eb278f149cc57b960 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Sun, 29 Mar 2026 14:41:26 -0600 Subject: [PATCH] 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. --- docs/PROTOCOL.md | 3 ++- examples/donglora.py | 19 ++++++++++++------- examples/meshcore/meshcore_rx.py | 15 ++++++++------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 1a1b17e3..fe889cd2 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -26,8 +26,9 @@ 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) | -| `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 | | `Option` | `0x00` = None, `0x01` + T = Some | | `Vec` | Varint length, then raw bytes | diff --git a/examples/donglora.py b/examples/donglora.py index 4a279e60..a80789fa 100644 --- a/examples/donglora.py +++ b/examples/donglora.py @@ -12,6 +12,7 @@ See docs/PROTOCOL.md for the full specification. import glob import struct import subprocess + import serial import time from cobs import cobs @@ -112,13 +113,17 @@ def decode_zigzag(data: bytes) -> tuple[int, bytes]: # ── Command encoding ───────────────────────────────────────────── def encode_config(cfg: dict) -> bytes: - """Encode a RadioConfig dict to postcard bytes.""" - out = varint(cfg["freq_hz"]) - out += varint(cfg["bw"]) - out += varint(cfg["sf"]) - out += varint(cfg["cr"]) - out += varint(cfg["sync_word"]) - out += zigzag(cfg["tx_power_dbm"]) + """Encode a RadioConfig dict to postcard bytes. + + Postcard wire format: u8/i8 are raw bytes, u16/u32 are varint, + i16/i32 are zigzag+varint, enums are varint variant index. + """ + out = varint(cfg["freq_hz"]) # u32: varint + 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 diff --git a/examples/meshcore/meshcore_rx.py b/examples/meshcore/meshcore_rx.py index bfce55cc..faab482c 100755 --- a/examples/meshcore/meshcore_rx.py +++ b/examples/meshcore/meshcore_rx.py @@ -426,12 +426,12 @@ def zigzag(n: int) -> bytes: def encode_radio_config(cfg: dict) -> bytes: - out = varint(cfg["freq_hz"]) - out += varint(cfg["bw"]) - out += varint(cfg["sf"]) - out += varint(cfg["cr"]) - out += varint(cfg["sync_word"]) - out += zigzag(cfg["tx_power_dbm"]) + out = varint(cfg["freq_hz"]) # u32: varint + 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 byte (signed) return out @@ -931,7 +931,8 @@ def _format_rssi_snr(rssi: int, snr: int) -> str: 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") print(f"\n{BWHT}Listening for packets{RST} {DIM}(Ctrl+C to stop){RST}\n")