fix(config): name the radios[] entry with a stray sync word

build_radio_stack pops _radio_id before building each radio, so the
warning never named the entry on a real node. A _radio_label survives the
pop, and the warning now points at radios[<id>].radio, saying the value is
inherited only when it is.

config.yaml.example documents sync_word.
This commit is contained in:
agessaman authored and Rightup committed 2026-10-02 10:57:24 +01:00
1 parent 0ed70f4d1b
commit d5e4bd754c
3 files changed
+86 -6

No files matched your search

+5
View File
@@ -599,6 +599,11 @@ radio:
# Preamble length in symbols
preamble_length: 32
# LoRa sync word, used by modem_tcp/modem_usb radios (the SX1262 driver
# ignores it). MeshCore uses 0x12; a modem on any other value hears nothing.
# radios[] entries inherit this value unless they set their own.
# sync_word: 0x12
# Use implicit header mode
implicit_header: false
+16 -5
View File
@@ -517,19 +517,26 @@ def _checked_modem_sync_word(sync_word: int, board_config: dict, radio_type: str
These are the drivers that actually push the value to the radio, and a
modem on another sync word connects, answers pings and transmits, yet hears
no MeshCore traffic -- a silent failure. A radios[] entry inherits the
top-level ``radio.sync_word``, which the SX1262 driver ignores, so a stray
top-level value is harmless until a modem entry picks it up.
top-level ``radio.sync_word``, which the SX1262 driver ignores (SX1262Radio
stores it but never calls setSyncWord), so a stray top-level value is
harmless until a modem entry picks it up.
"""
if sync_word != MESHCORE_SYNC_WORD:
radio_id = board_config.get("_radio_id")
radio_id = board_config.get("_radio_label") or board_config.get("_radio_id")
if radio_id:
where = f"radios[{radio_id}].radio"
if board_config.get("_sync_word_inherited"):
where += " (it inherits the top-level radio.sync_word)"
else:
where = "its radio section"
logger.warning(
"%s sync_word is 0x%X, not MeshCore's 0x%02X: the modem will not hear "
"MeshCore traffic. Set sync_word: 0x%02X in its radio section%s.",
"MeshCore traffic. Set sync_word: 0x%02X in %s.",
f"Radio {radio_id!r} ({radio_type})" if radio_id else f"{radio_type} radio",
sync_word,
MESHCORE_SYNC_WORD,
MESHCORE_SYNC_WORD,
" (radios[] entries inherit the top-level radio.sync_word)" if radio_id else "",
where,
)
return sync_word
@@ -920,6 +927,10 @@ def _merge_radio_entry(global_config: dict, entry: dict) -> dict:
raise ValueError(f"radios[] entry {radio_id!r} missing radio_type")
merged["_radio_id"] = str(radio_id)
# build_radio_stack pops _radio_id; this survives it, to name the radio in logs.
merged["_radio_label"] = str(radio_id)
own_radio = entry.get("radio") if isinstance(entry.get("radio"), dict) else {}
merged["_sync_word_inherited"] = "sync_word" not in own_radio
# Hint for CH341 path: do not install process-global SPI/GPIO defaults.
merged["_ch341_per_instance"] = True
return merged
+65 -1
View File
@@ -364,7 +364,7 @@ def test_a_radios_entry_inheriting_a_stray_top_level_sync_word_is_named(monkeypa
assert captured["sync_word"] == 0x3444
assert "Radio 'narrow' (modem_tcp) sync_word is 0x3444" in caplog.text
assert "inherit the top-level radio.sync_word" in caplog.text
assert "inherits the top-level radio.sync_word" in caplog.text
def test_a_radios_entry_overriding_the_stray_sync_word_is_not_warned_about(monkeypatch, caplog):
@@ -398,3 +398,67 @@ def test_an_explicit_null_sync_word_means_the_default(monkeypatch, caplog, radio
assert captured["sync_word"] == 0x12
assert "sync_word" not in caplog.text
def test_build_radio_stack_names_the_radios_entry_with_the_stray_sync_word(monkeypatch, caplog):
"""The real path: build_radio_stack pops _radio_id before building the
radio, so the warning must still name the entry."""
from repeater.config import build_radio_stack
captured = _capture_modem(monkeypatch, "modem_tcp")
config = {
"radio_type": "sx1262",
"radio": {**_modem_radio_cfg(), "sync_word": 13380},
"radios": [
{
"id": "narrow",
"radio_type": "modem_tcp",
"radio": {"frequency": 910525000},
"modem_tcp": {"host": "192.168.50.154"},
}
],
}
with caplog.at_level("WARNING", logger="Config"):
build_radio_stack(config)
assert captured["sync_word"] == 0x3444
assert "Radio 'narrow' (modem_tcp) sync_word is 0x3444" in caplog.text
assert "inherits the top-level radio.sync_word" in caplog.text
def test_each_modem_entry_gets_its_own_sync_word_and_warning(monkeypatch, caplog):
from repeater.config import build_radio_stack
pytest.importorskip("openhop_core.hardware.tcp_radio")
built = []
class _Dummy(_DummyRadio):
def __init__(self, **kwargs):
built.append(kwargs)
monkeypatch.setattr("openhop_core.hardware.tcp_radio.TCPLoRaRadio", _Dummy)
config = {
"radio_type": "sx1262",
"radio": {**_modem_radio_cfg(), "sync_word": 13380},
"radios": [
{"id": "local", "radio_type": "modem_tcp", "modem_tcp": {"host": "10.0.0.1"}},
{
"id": "narrow",
"radio_type": "modem_tcp",
"radio": {"sync_word": 0x34},
"modem_tcp": {"host": "10.0.0.2"},
},
],
"fabric": {"default_radio": "local"},
}
with caplog.at_level("WARNING", logger="Config"):
build_radio_stack(config)
assert [b["sync_word"] for b in built] == [0x3444, 0x34]
lines = [r.getMessage() for r in caplog.records]
local = next(m for m in lines if "Radio 'local'" in m)
narrow = next(m for m in lines if "Radio 'narrow'" in m)
assert "radios[local].radio (it inherits" in local
assert "radios[narrow].radio." in narrow and "inherits" not in narrow