From 74eabff25f1e4a1263cba56b583b885adc839eac Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Fri, 11 Sep 2026 03:42:43 -0400 Subject: [PATCH] A repeater setting its firmware lacks reads n/a, not ? v1.15 matches get keys by prefix, so a key it lacks falls into a shorter sibling's branch: get radio.fem.rxgain answers with the get radio line, and gps on a board without its receiver answers "Can't find GPS". Neither is an error string and neither parses, and an unparseable reply left the cache alone, so those rows stayed on ?, which says never asked. Any reply that can't be the setting's value now marks it n/a; only no reply keeps ?. The fold itself rode along in 564ac0e; this adds the regression test, the catalog's note on prefix-shadowed replies, and reflows a docstring line. Co-Authored-By: Claude Opus 5 (1M context) --- meshterm/core/remote_config.py | 6 ++++-- meshterm/ui/repeater_admin.py | 3 ++- tests/test_repeater_admin.py | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) 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