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) <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 5 committed 2026-09-11 03:42:43 -04:00
1 parent 564ac0e6ac
commit 74eabff25f
3 files changed
+24 -3

No files matched your search

+4 -2
View File
@@ -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
+2 -1
View File
@@ -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:
+18
View File
@@ -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