diff --git a/meshterm/core/remote_config.py b/meshterm/core/remote_config.py index 81b3739..4f9f3fb 100644 --- a/meshterm/core/remote_config.py +++ b/meshterm/core/remote_config.py @@ -30,8 +30,10 @@ three of its shapes only make sense against that source: Replies are parsed *loosely* on purpose: repeater firmware answers tersely and has changed its phrasing across versions (``"> 20"``, ``"tx: 20"``, ``"OK - repeat is now ON"``), so values are extracted rather than pattern-matched. Firmware without a given key answers -with an error string (``??: key`` to a ``get``, ``unknown config: …`` to a ``set``), which -the screen records as *this node doesn't have it* rather than breaking. +with an error string (``??: key`` to a ``get``, ``unknown config: …`` to a ``set``) — or, +because ``get`` matches keys by prefix, with a shorter sibling's value (v1.15 answers +``get radio.fem.rxgain`` with the ``get radio`` line). The screen records either as *this +node doesn't have it* rather than breaking. """ from __future__ import annotations diff --git a/meshterm/ui/repeater_admin.py b/meshterm/ui/repeater_admin.py index 0c30f4b..dfd5abf 100644 --- a/meshterm/ui/repeater_admin.py +++ b/meshterm/ui/repeater_admin.py @@ -433,7 +433,8 @@ def _value_text(spec: RemoteSetting, cache: dict, pending: dict[str, str]) -> Te Four states, each its own word: the value; ``empty`` for a string the node holds blank; ``n/a`` when the node answered with something that can't be this setting's value — its - firmware has no such setting; ``?`` when it was never read (or never answered). No age: the lane is the value, and a stamp beside it only crowded it. + firmware has no such setting; ``?`` when it was never read (or never answered). No age: + the lane is the value, and a stamp beside it only crowded it. """ cached = cache.get(spec.key) if not spec.readable: diff --git a/tests/test_repeater_admin.py b/tests/test_repeater_admin.py index 393d0a2..414f2ff 100644 --- a/tests/test_repeater_admin.py +++ b/tests/test_repeater_admin.py @@ -498,6 +498,24 @@ async def test_read_one_setting_sends_one_read(tui_ctx, admin_device) -> None: assert tui_ctx.remote_store.settings(NODE)["cr"].value == "5" # the same answer filled it +def test_a_reply_that_cannot_be_the_value_reads_n_a(tui_ctx) -> None: + """A node answering a key it lacks with anything but its value reads ``n/a``, not ``?``. + + Regression: v1.15 matches ``get`` keys by prefix, so ``get radio.fem.rxgain`` came back + as the ``get radio`` line. It parsed as nothing, and the row stayed ``?`` — never asked. + """ + replies = { + "radio.fem.rxgain": "> 910.525,62.5,7,5", # a shorter sibling's branch answered + "gps": "Can't find GPS", + "bridge.delay": "??: bridge.delay", + } + for key, reply in replies.items(): + assert repeater_admin.remember_reply(tui_ctx, NODE, [get_setting(key)], reply) == 0 + cache = tui_ctx.remote_store.settings(NODE) + for key in replies: + assert repeater_admin._value_text(get_setting(key), cache, {}).plain == "n/a" + + async def test_apply_reads_the_radio_first_then_sends_one_line(tui_ctx, admin_device) -> None: """A radio field staged unread restates its siblings from a fresh read, never a guess.""" ctx = tui_ctx