Only test/fixtures/cli_reply_text.json conflicted; messages and profiles
identical, source token hashes regenerated for the merged sources.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A TX line had no RAW of its own, so meshcoretomqtt published each
transmission with the last frame it had received, and a serial observer's
own radio2 transmissions never showed. The repeater and room server now
print the frame as sent (RAW), and RADIO: radio2 when it goes out on radio2,
just before the dispatcher's TX line, as they already do for RX.
The dispatcher calls mesh::usbLogTxFrame, a weak function only those two
roles define; Companion builds keep it undefined and pay a null test. RX/TX
lines and compact logging are unchanged.
Test: test_usb_log_radio2.py compiles both roles' real hook and runs the
lines through meshcoretomqtt's parsing: the TX now carries the sent frame,
tagged radio2 only when sent on radio2. cli_reply_text.json re-pinned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i5tJJtj95MB4oUgncCUe5
Only test/fixtures/cli_reply_text.json conflicted; messages and profiles
were identical, source token hashes regenerated for the merged sources.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isTempRadioActive() on CommonCLICallbacks is only needed by OTA and by
radio presets, so declare it under ENABLE_OTA || MESH_ENABLE_RADIO_PRESETS
again (no vtable slot or override thunk otherwise). The repeater's
login_full_admin flag is also preset-only. wio-e5-mini_repeater now links
byte-for-byte the same size as upstream.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CommonCLI owns a RadioPresetCLI wired through static hook trampolines
(current/accepts/apply via the existing primary-radio save+apply path,
/presets storage via RadioPresetFile.h). begin() runs at the end of
loadPrefs, loop() next to the radio profiles, and dispatch precedes
radio profiles with remote = sender_timestamp != 0. A fresh strict-admin
repeater login (LoRa or ESP-NOW bridge) commits a pending remote load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The marker in Dispatcher::logPacketLine cost the RAK4631 Full Companion 72
bytes it doesn't have (712776 of 712704). Companions aren't serial observers,
so print it where only repeaters and room servers pay for it: their logRxRaw,
right after the RAW line and before the RX line, using the radio's current
receive profile.
12:34:56 - 29/10/2026 U RAW: 15A3…
12:34:56 - 29/10/2026 U RADIO: radio2
12:34:56 - 29/10/2026 U: RX, len=64 …
The shared dispatcher is back to upstream, so Companion images are unchanged.
Only receptions are marked in the USB log now; the MQTT bridge still tags
both directions.
test_usb_log_radio2.py compiles both roles' real logRxRaw and checks the
marker against meshcoretomqtt's patterns, and that the dispatcher and
Companion sources don't carry it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i5tJJtj95MB4oUgncCUe5
Full ESP32 repeater builds keep up to 254 neighbours, and a 16-byte radio2
field on each pushed them past the DRAM check (checkprogsize). Instead:
- Radio2DiscoverTrailer is 8 bytes (bandwidth in 100 Hz steps, which is all
the status format prints; sf/cr/state/mode/temporary in bitfields).
- The repeater keeps a 24-entry cache keyed by key prefix (288 bytes): a
reply without a trailer changes nothing, a node's newer report replaces
its older one, and a full cache keeps On (MediaMesh) reports over Off ones.
- The discovery snapshot reads from it; JSON output is unchanged.
The room server keeps its per-neighbour field (now 8 bytes).
Tests: test_radio2_neighbour_cache.py compiles the real cache functions;
the trailer test pins the 8-byte size; the lifecycle harness looks radio2 up
the same way the repeater does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i5tJJtj95MB4oUgncCUe5
Observers already publish radio2 packets, but nothing says they came over
radio2, so analyzers can't tell MediaMesh traffic or MediaMesh repeaters
apart. Add optional keys, all absent on the main radio, so existing
consumers see byte-identical messages:
- packets and raw: "rx_radio_id"/"tx_radio_id": "radio2" and
"mediamesh": true, from the packet's own radio_profile (captured on
receive, kept by outbound copies), not the radio's profile at publish
time. rx_radio_id is CoreScope's multi-radio key (#1869).
- status: "radios": [{id: "radio2", radio, mode, mediamesh, temporary}]
while radio2 is on with the MeshCore protocol; "radio" stays the main
radio.
- neighbors (repeater and room server): the refresh's DISCOVER_REQ sets
the radio2 flag (0x02, docs/radio_profiles.md); each neighbour's trailer
is kept and published as "radio2" {mode, radio, temporary}, with
"self.radio2" for this node. No trailer leaves the key out (unknown,
never off).
Radio2DiscoverTrailer parses the trailer (host-tested). publishRaw now
takes the direction. MQTT_IMPLEMENTATION.md documents the keys.
Tests: new payload-builder cases (golden prefix, rx/tx/raw tags, status
radios, neighbors radio2, measurements), trailer parsing, and the
neighbor lifecycle fixture now checks radio2 end to end for both roles.
Harnesses updated for the new field and the publishRaw signature.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i5tJJtj95MB4oUgncCUe5
Omit unbound forwarding-region callbacks only for exclusive Companion source roles. Reuse the compact neighbor sorter in repeaters for every RPC order and the CLI list, removing duplicate sort implementations while preserving storage and size gates.
Repeated preamble/header activity can keep a requested primary or TempRadio
change busy indefinitely. Bound unfinished-RX admission for accepted LR2021
changes while preserving completed packets, TX, BUSY and exclusive owners.
Ordinary scans and CAD keep their existing admission rules. Expose the failing
stage and native status through get radio.apply.
Add saved radio.xtrim capacitor calibration and measured radio.freqerror in
signed Hz from the last valid LoRa packet. Preserve native trim by default,
restore explicit calibration after resets/Morse, verify saves before applying,
wait two seconds for the acknowledgement, and roll back failed changes.
Calibration motivation and prior upstream work:
https://github.com/meshcore-dev/MeshCore/issues/3365https://github.com/recrof/MeshCore/tree/lr2021-trim
Packet-status format follows Semtech's LR20xx driver and RadioLib staging.
Tests execute production activity/admission and transaction paths with noisy
RX, completion races, protected owners, failed setters, RX restart and save
rollback. Physical W12 retuning and RF calibration still require confirmation.
Temporarily disable incompatible cached side detectors while applying a temporary primary tuple, preserving their saved configuration for normal restoration. Use primary-only CAD while those detectors are suspended. Keep compatible temporary detectors and real hardware errors intact.
Report pending TempRadio starts instead of off, preserve live-tuple status during replacements and future schedules, and include the two-second delay in the accepted reply. Share primary validation to retain STM32 Mini flash capacity without dropping features.
Validated production CLI status, scheduler and persistence cases; LR2021 detector suspension, restoration, CAD and hardware-error paths; reply handoff, room/sensor recovery, Morse and OTA automation regressions. W12 repeater, room-server and sensor builds pass. Canonical STM32 Mini Repeater passes flash and runtime RAM admission with 436 bytes of flash remaining. Physical W12 RF testing remains unverified.
Persist set freq and set radio atomically, then apply through the existing safe radio scheduler without rebooting. Retain the old tuple until the delay and reply handoff finish, including queued room/sensor replies and TX-complete callbacks. End temporary sessions when accepting a permanent change.
Display applied RF settings even when radio2 is off. Share radio validation across immediate and scheduled setters to preserve STM32 Mini flash capacity. Update command documentation, regression coverage, and CI.
Validated production timing, persistence, display, CLI, fleet/reply, RX, sleep and Morse regressions. W12 repeater, room-server and sensor builds pass. Canonical STM32 Mini Repeater passes flash and RAM admission with 448 bytes of flash remaining. Physical W12 RF testing was not performed.
Save valid busy RX, fast-switch and Companion tuple changes once, then
apply the latest accepted intent at a safe radio gap. Preserve checked-save
rollback, runtime RXEN intent, TempRadio ownership and exact remote reply
delivery. Repair failed preamble compensation without hiding uncertain saves.
Retry WebConfig, MQTT and NimBLE startup failures automatically. Preserve
explicit off choices, OTA ownership and the latest WiFi credentials through
cooperative shutdown. Add regression coverage and update command guides.
Validation: focused host transaction, rollback, ownership and lifecycle suites;
Full RAK4631, Heltec V4 R8 TFT and RC32 packages; representative room, sensor,
LR1110, LR2021, SX1276, STM32WL and ESP-NOW builds. Linked RAK crypto and OTA
workspace checks pass; its complete OTA image uses 712644/712704 bytes.
Default to one hour after sunset through sunrise, using fresh cached GPS or recent direct RF neighbor locations. Provide saved CLI and WebConfig controls with a small integer solar approximation and no additional framebuffer.
An enabled MQTT bridge without a saved SSID forced WebConfig back on at boot,
even after set webui off. Its setup AP also suspended an enabled ESP-NOW bridge.
Use first-time MQTT setup as the default only when no WebUI choice is saved.
Apply the saved on/off setting afterward in Repeater and Room Server. Preserve
first-time provisioning and explicit starts.
Add executable boot regressions for both roles with unset, on and off WebUI
settings and configured/unconfigured WiFi. Update setup and picker guidance.
Resolve Serial macro and ESP-NOW wrapper compilation errors. Preserve queue, neighbor, advert-history and Room post capacities while using qualified RTC, PSRAM and word-only IRAM storage on constrained ESP32 targets. Reduce Full infrastructure flash through lossless Room web compression, compact crypto and unused exception metadata removal.
Add focused native regressions for storage ownership, exact protocol output, OTA proofs and build policy. Keep physical flash sizes, OTA slots and RAM guard formulas unchanged.