feat(app): ask a selected companion for its telemetry each cycle, zero-hop, and keep what it answers per node - #578
Open
khagele wants to merge 2 commits into
Open
Conversation
… advert each cycle a companion is the target The hunter's companion never announced itself (efiten#576): the app sends Discover broadcasts and trace-pings, which carry no identity, and nothing else. MeshCore firmware answers a request only from a sender it can look up in its contact list, and a node adds us to that list when it hears our advert. So a companion that has not heard us cannot answer the telemetry request efiten#553 wants to send. An advert is also the first time the app would transmit who the hunter is, so it is a setting, off by default: Share my node name, in a new Identity section of Settings, with the plain words under it. With it on, every auto-discover cycle that has a companion among the selected targets sends one CMD_SEND_SELF_ADVERT (7) with the route byte at 0, which the firmware sends zero-hop, never a flood. Per cycle rather than once at switch-on, because the target can be out of range at that moment and it has to hear us before it can answer. A repeater target keeps its trace-ping and gets no advert: selectedCompanionIds is the selection minus the repeater reading, so the two never disagree about one node. The FAB pulses and the tx cue sounds, as for every frame that goes out. The setting lights the settings dot like every other non-default. What the advert carries is the companion's own: its public key and name, and a position only if the owner set the location policy in the MeshCore app. Verified: app 906 unit tests, build and eslint clean; web 509 (the changelog copies). Eight mutation checks over the two rounds, each red on one test. Browser at 360 with a stub transport: nothing selected sends no advert; a companion selected sends one [7,0] per cycle; the same node read as a repeater gets a trace-ping and no advert; the setting off, or the selection cleared, sends none; the setting survives a reload. Closes efiten#576 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o-hop, and keep what it answers per node A companion selected as target got no probe (efiten#553). The firmware check in the issue thread settled what a companion answers: not a trace (repeat is off by default), no anonymous request, no Discover, and one thing: a telemetry request from a sender it has as a contact, with its battery voltage and MCU temperature. So where a repeater target gets a trace-ping, a companion target now gets CMD_SEND_TELEMETRY_REQ (39), one per auto-ping cycle rotating over the selected companions, since the firmware keeps one pending telemetry tag. Nothing floods. The firmware floods a request when the contact's out_path_len is unknown, so the app runs the contact-path dance ported from coredrive-rx (contactpath.js, tests ported with it): read the contact, force out_path_len to 0 for the ask, send, put the contact back as it was, whether the ask went out or not. An override that does not ack means no ask. A session that dies between override and restore leaves a localStorage record that the next connect to the same companion replays. The reply on the RX log is a RESPONSE datagram carrying only the 1-byte source hash; like a trace reply (efiten#481) it is named after the node we asked while that ask is live and unambiguous (telemetry_reply, full pubkey). The 0x8B push that follows carries a 6-byte prefix and the CayenneLPP payload (voltage type 116, temperature type 103); what it says goes into a new nodes store (IndexedDB v3, keyed by pubkey), apart from the receptions. Nothing renders it yet. The FAB says "target ping" for a companion target too. docs/2026-09-04-companion-telemetry-probe.md records the decisions: 39 over path discovery (52 always floods and the firmware discards its telemetry), the three conditions, the cadence. Verified: app 938 unit tests, build and eslint clean; web 509 and the whatsnew e2e. Browser at 360 with a stub companion answering the commands: a selected companion runs 30, 9 (byte 35 = 0), 39, 9 (byte 35 restored) each cycle, the ask is remembered and the restore record cleared; a 0x8B push lands 3.97 V and 25 °C on the node; a real Response packet with that source hash is captured as telemetry_reply with the target's pubkey; NOT_FOUND sends no request; a failed override sends no request and keeps the restore record. Closes efiten#553 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #553
Stacked on #577 (Share my node name): the probe needs a target that has us as a contact, and it reuses
selectedCompanionIdsfrom that branch. That commit shows in this diff until #577 merges.The problem
A companion selected as target got no probe: the auto-ping loop only trace-pings nodes that behave as repeaters, and the FAB label was the only hint. The firmware check in the #553 thread settled what a companion answers: not a directed TRACE (repeat is off by default), no anonymous request, no Discover, and one thing: a telemetry request from a sender it has as a contact, answered with its battery voltage and MCU temperature.
My changes in this PR
CMD_SEND_TELEMETRY_REQ(39) where a repeater target gets a trace-ping. One per auto-ping cycle, rotating over the selected companions (nextTelemetryTarget): the firmware keeps one pending telemetry tag (clearPendingReqson every send), so two in flight would orphan a reply. The sweep stays trace-only. The FAB says "target ping" for a companion target too.out_path_lenis unknown and source-routes over a stored path otherwise.app/src/contactpath.jsis the contact-path dance ported fromefiten/coredrive-rx, tests ported with it: read the contact (CMD_GET_CONTACT_BY_KEY), force byte 35 to 0 for the ask (CMD_ADD_UPDATE_CONTACT), send, put the contact back exactly as it was. An override that does not ack means no ask this cycle. A session that dies between override and restore leaves a localStorage record, replayed on the next connect to the same companion.app/src/telemetryreq.js): the reply on the RX log is aRESPONSEdatagram with only the 1-byte source hash. Like a trace reply (app: a trace reply we asked for lands as an anonymous point, although our own tag names its target #481) it is named after the node we asked while that ask is live and unambiguous; two live asks sharing a first byte are refused. The record getssender_kind: 'telemetry_reply'and the target's full pubkey, so names resolve and the map has the node.PUSH_CODE_TELEMETRY_RESPONSE(0x8B) carries a 6-byte prefix and the CayenneLPP payload (voltage type 116 in 0.01 V, temperature type 103 in 0.1 °C signed). It goes into a newnodesstore in IndexedDB (schema v3, additive), keyed by pubkey. Nothing renders it yet.docs/2026-09-04-companion-telemetry-probe.md. Changelog entry in both copies.What it needs on the other side
The target must have us as a contact (it looks the sender up by hash for the shared secret), which is what #577's Share my node name is for, and its telemetry permission must not be deny (firmware default deny, the MeshCore app sets it; companions answer in the field). Our companion must have the target as a contact, which auto-add gives it on the target's advert;
NOT_FOUNDskips the cycle.Verification
whatsnew.spec.js,--fail-on-flaky-testsUnit tests cover the frames against the firmware layouts, the LPP parser (signed temperature, skipped and unknown fields, truncation), the ask matching (live, ambiguous, expired), the rotation, the contact-frame override and restore byte diffs, the crash-safety record, and the nodes store including the v2 to v3 upgrade with
fake-indexeddb.Browser at 360x780 with a stub companion answering the commands: a selected companion runs
30, 9, 39, 9each cycle with byte 35 at 0 in the override and 0xFF in the restore, the ask is remembered and the restore record cleared; a 0x8B push lands 3.97 V and 25 °C on the node; a real captured Response packet with that source hash (thedecode.test.jsfixture) is captured astelemetry_replywith the target's pubkey; aNOT_FOUNDcontact sends no request; a failed override sends no request and keeps the restore record for the next connect.Not verified here: a companion on air. The reply's route is the target's choice, direct when it knows a path to us and otherwise a flood of its own; we hear it zero-hop either way.
Decisions worth a look
MyMesh.cpp:770, "telemetry data in 'extra' is discarded at present").telemetry_replystays outsideTARGET_KINDSand the repeater reading, liketrace_reply.🤖 Generated with Claude Code