v2.5.9 AU validation — two bugs found
Context: Tested vanilla v2.5.9 (fresh bootstrap + upgrade.sh) on a SenseCAP M1
(AU Mid 915.075 MHz / SF9 / BW125 / CR4/5, sync_word 5156, lorawan_public: false).
chan_multiSF RX, Neighbours tab, and ADVERT forwarding all working correctly.
Two bugs found and fixed locally; reporting for upstream.
Bug 1 — Companion pings broken: bridge_engine._store_tx_echo_hash drops TRACE_RESP
Symptom: Companion ping (e.g. TCP companion → remote node) shows "Ping failed — timeout"
even though the TRACE reaches the target and a TRACE_RESP is received cleanly on channel_a.
Log evidence:
BridgeEngine WARNING [TX_ECHO] Dropped echo on channel_a (hash=7d53abcde0b5, age=0.3s, total=4)
The RESP arrives at −36 dBm / SNR 9.5 — strong clean signal — and is immediately discarded.
Root cause: bridge_engine._store_tx_echo_hash stores the outgoing TRACE packet's hash
with no type guard. Because TRACE_RESP shares the same stable hash as the outgoing TRACE,
the RESP is matched and dropped as a TX echo before it reaches repeater_handler / TraceHelper.
The guard exists in wm1303_backend.py at both TX storage points (committed in our fork),
but bridge_engine._store_tx_echo_hash (line ~1211 in v2.5.9) has no equivalent check.
Fix — one line at the top of _store_tx_echo_hash:
def _store_tx_echo_hash(self, data, channel_id, ...):
if len(data) > 0 and ((data[0] >> 2) & 0x0F) == 0x09: # skip TRACE type
return
# ... rest of method unchanged
Verified: After applying this guard, companion pings to all WM1303 nodes work correctly
(956a→0e, 956a→75 both confirmed with SNR/RSSI reported). 956a→1a fails as expected
(rightup build doesn't implement TRACE).
Bug 2 — /api/stats returns HTTP 500 (TypeError: bytes not JSON serializable)
Symptom: WM1303 UI "System Config" stats panel fails to load. Server log shows:
TypeError: bytes not JSON serializable
File "api_endpoints.py", line ..., in stats
return stats
Root cause: bridge_engine.get_stats() (and possibly other components aggregated by
stats_getter()) includes bytes values for fields like channel_id / rule identifiers.
CherryPy's json_out decorator calls json.dumps() on the return value, which raises
TypeError on bytes. The try/except in stats() doesn't catch it because the
exception is raised after return, inside the CherryPy serialisation layer.
Fix — sanitize bytes in api_endpoints.py stats() before returning:
def _b(obj):
"""Recursively convert bytes → hex string for JSON serialization."""
if isinstance(obj, bytes):
return obj.hex()
if isinstance(obj, dict):
return {_b(k): _b(v) for k, v in obj.items()}
if isinstance(obj, (list, tuple)):
return [_b(i) for i in obj]
return obj
@cherrypy.expose
@cherrypy.tools.json_out()
def stats(self):
stats = self._stats_getter()
return _b(stats) # wrap here; catches bytes from all aggregated components
Note: Patching bridge_engine.get_stats() alone is insufficient — bytes come from
multiple components inside stats_getter(). Wrapping at the stats() return catches all
of them in one place.
Verified: After applying this fix, the UI System Config panel loads correctly.
Note — NF shown as "unknown" with Channel E in lbt_cad_spectrum_only
Not a bug — noting for documentation. When Channel E is configured as lbt_cad_spectrum_only
(spectrum scan only, not active LoRa RX), NoiseFloorMonitor does not start and the UI
shows "unknown" for NF. This is arguably the correct behaviour — the previous behaviour on
our fork was producing −63.5 dBm spikes from RX estimation artifacts, so "unknown" is cleaner.
Overall v2.5.9 AU validation
- chan_multiSF RX on channel_a: ✓ (ARB
0xFF fix + lorawan_public: false working)
- Neighbours tab: ✓ (populating with full history, map, topology)
- ADVERT forwarding: ✓ (multi-hop confirmed)
- Companion pings: ✓ after Bug 1 fix above
/api/stats: ✓ after Bug 2 fix above
Thank you for the v2.5.3 attribution — much appreciated.
v2.5.9 AU validation — two bugs found
Context: Tested vanilla v2.5.9 (fresh bootstrap +
upgrade.sh) on a SenseCAP M1(AU Mid 915.075 MHz / SF9 / BW125 / CR4/5, sync_word 5156, lorawan_public: false).
chan_multiSF RX, Neighbours tab, and ADVERT forwarding all working correctly.
Two bugs found and fixed locally; reporting for upstream.
Bug 1 — Companion pings broken:
bridge_engine._store_tx_echo_hashdrops TRACE_RESPSymptom: Companion ping (e.g. TCP companion → remote node) shows "Ping failed — timeout"
even though the TRACE reaches the target and a TRACE_RESP is received cleanly on channel_a.
Log evidence:
The RESP arrives at −36 dBm / SNR 9.5 — strong clean signal — and is immediately discarded.
Root cause:
bridge_engine._store_tx_echo_hashstores the outgoing TRACE packet's hashwith no type guard. Because TRACE_RESP shares the same stable hash as the outgoing TRACE,
the RESP is matched and dropped as a TX echo before it reaches
repeater_handler/ TraceHelper.The guard exists in
wm1303_backend.pyat both TX storage points (committed in our fork),but
bridge_engine._store_tx_echo_hash(line ~1211 in v2.5.9) has no equivalent check.Fix — one line at the top of
_store_tx_echo_hash:Verified: After applying this guard, companion pings to all WM1303 nodes work correctly
(956a→0e, 956a→75 both confirmed with SNR/RSSI reported). 956a→1a fails as expected
(rightup build doesn't implement TRACE).
Bug 2 —
/api/statsreturns HTTP 500 (TypeError: bytes not JSON serializable)Symptom: WM1303 UI "System Config" stats panel fails to load. Server log shows:
Root cause:
bridge_engine.get_stats()(and possibly other components aggregated bystats_getter()) includesbytesvalues for fields likechannel_id/ rule identifiers.CherryPy's
json_outdecorator callsjson.dumps()on the return value, which raisesTypeErroronbytes. Thetry/exceptinstats()doesn't catch it because theexception is raised after
return, inside the CherryPy serialisation layer.Fix — sanitize
bytesinapi_endpoints.py stats()before returning:Note: Patching
bridge_engine.get_stats()alone is insufficient — bytes come frommultiple components inside
stats_getter(). Wrapping at thestats()return catches allof them in one place.
Verified: After applying this fix, the UI System Config panel loads correctly.
Note — NF shown as "unknown" with Channel E in
lbt_cad_spectrum_onlyNot a bug — noting for documentation. When Channel E is configured as
lbt_cad_spectrum_only(spectrum scan only, not active LoRa RX),
NoiseFloorMonitordoes not start and the UIshows "unknown" for NF. This is arguably the correct behaviour — the previous behaviour on
our fork was producing −63.5 dBm spikes from RX estimation artifacts, so "unknown" is cleaner.
Overall v2.5.9 AU validation
0xFFfix +lorawan_public: falseworking)/api/stats: ✓ after Bug 2 fix aboveThank you for the v2.5.3 attribution — much appreciated.