Commit Graph
393 Commits
Author SHA1 Message Date
mikecarper 0b9a80d7ed Avoid repeated missing SPIFFS scans during ESP32 startup 2026-10-05 14:12:44 -07:00
mikecarper 19d02c0dfa Preserve flood policies across qualified RAM capacity changes 2026-10-05 03:33:44 -07:00
mikecarper e4420519a4 Fit Access Control replies to encrypted packet route budgets 2026-10-04 14:24:03 -07:00
mikecarper 63f4642252 Merge observer OTA channel switching into keymindCascade 2026-10-04 00:26:46 -07:00
mikecarper d9088f9e86 Preserve telemetry preferences and routes across storage failures 2026-10-03 18:34:12 -07:00
mikecarper eeb25a8b5f Merge USB session safety and preserve GPS preference compatibility 2026-10-03 16:12:32 -07:00
mikecarper 7803d4fb07 Add saved repeater trace exception while repeating is disabled 2026-10-03 15:48:44 -07:00
agessaman baf01a5c16 Merge branch 'feat/ota-branch-switch' into merge/ota-branch-into-observer-firmware-dev
# Conflicts:
#	examples/simple_repeater/MyMesh.cpp
2026-10-03 15:47:01 -07:00
mikecarper 65512661c3 Fix USB logging session safety and add hourly GPS sync interval 2026-10-03 15:43:35 -07:00
mikecarper 9b82c74275 Merge origin/keymindCascade USB reconnect fixes 2026-10-03 15:37:49 -07:00
agessaman f652e7d185 fix(ota): flash the channel ota update checked, not a later ota branch
The deferred flash re-read the channel preference ~2.5 s after ota update
acknowledged its target; capture it when the update is scheduled.
2026-10-03 15:37:49 -07:00
mikecarper 3add4695cc Harden USB logging and persist watchdog events in management reports 2026-10-03 15:29:19 -07:00
agessaman ee88192acb Merge branch 'feat/ota-branch-switch' into merge/ota-branch-into-observer-firmware-dev
# Conflicts:
#	.github/workflows/build-observer-firmwares.yml
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_room_server/MyMesh.cpp
#	src/helpers/ESP32Board.cpp
#	src/helpers/ESP32Board.h
#	test/test_config_serializer/test_config_serializer.cpp
2026-10-03 15:24:33 -07:00
Elektr0Vodka 942a19eb63 feat(ota): pass runtime manifest base through otaFromManifest
(cherry picked from commit 0bee84c0546780b2e7147148c32087e92d894ac0)
2026-10-03 15:17:36 -07:00
mikecarper b4c8c0ab23 Fix USB logging ownership, persistence, and reconnect safety 2026-10-03 14:45:57 -07:00
mikecarper 7324bf06ef Support 2+ path-hash width filters 2026-10-02 10:21:17 -07:00
mikecarper d084f5b837 Add encoded path-hash width selectors to forwarding rules 2026-10-02 10:07:48 -07:00
mikecarper 2d78b8f06c Add configurable nRF52 battery profiles with LiFePO4 cutoff 2026-09-30 01:11:10 -07:00
mikecarper 001e9a62e7 Protect remote admin identity backup replies 2026-09-26 18:01:43 -07:00
mikecarper e6b002759d fix(build): clear matrix failures while preserving features 2026-09-22 23:23:25 -07:00
agessaman da6406e92f fix(network): rebuild the DHCP hostname when the node is renamed
The hostname was built once in begin() from the node name, so `set name` left
the node advertising its old name to DHCP until the next reflash. It is now
rebuilt on rename through a new CommonCLICallbacks::onNodeNameChanged() hook,
and begin() shares the same helper.

What this cannot do is rename a live lease. IDF 4.4 states that a hostname
changed after the interface is up "would only be reflected once the interface
restarts/reconnects", and Arduino 2.x's WiFi.setHostname() does not touch a
running netif at all — it writes a static string that the netif reads at
bring-up. Forcing the issue would mean bouncing the link, which costs every
MQTT slot a reconnect; that is not a reasonable price for a rename, so the new
name lands at the next reconnect or boot instead.

Only automatic (Ethernet-preferred) links carry a hostname today, exactly as
before. Giving plain Wi-Fi observers one would change the DHCP identity of the
existing fleet and is a separate decision.
2026-09-19 15:57:45 -07:00
agessaman 25f26c3058 Merge fix/mqtt-review-2026-09-09 into feat/abstract-networking
#54 lands first, so the network abstraction is reworked on top of the
per-slot client ownership model instead of alongside it.

Two resolutions are semantic, not textual:

- Link-transition teardown goes through the ownership API. It called
  softDisconnect() on every started client, which bypassed client_state and
  ignored the typed result. A slot that is still Starting now gets a real
  stop: softDisconnect() returns immediately on a client that is not yet
  connected, so its attempt would otherwise complete against the old route and
  deliver a CONNECTED event indistinguishable from the new one's (F04). On a
  medium switch the old route can still be briefly usable, so that is not
  hypothetical. A connected client keeps the cheap bounded path, and a
  quarantined one is left alone.

- NetworkLink::applyPowerPrefs() adopts WifiPowerSavePolicy. Its local
  `2 ? MAX : NONE` mapping would have reintroduced F11 and read the new stored
  value 3 (explicit `min`) as none. It also applies the setting when the link
  starts already associated, which is the case the bridge used to cover.

The NTP probe from #54 keeps its validation and its one-attempt-per-server
bound, but resolves and gates on the selected link rather than on WiFi, so it
works on an Ethernet-preferred node.
2026-09-19 15:15:07 -07:00
agessaman 16562621a2 fix(mqtt): gate room-server OTA on an unproven stop; reap late acks
- The room server checked canFlashAfterStop() only when the bridge was
  running at OTA time. After a timed-out restart the bridge reads as
  stopped while its unacknowledged task may still own TLS/client state,
  so an OTA could erase and write flash under it. It now refuses while
  the stop is unproven, after first reaping any late ack. The repeater
  already gated unconditionally.
- MyMesh::loop() now reaps a late stop acknowledgement whenever it lands,
  releasing the withheld queue and buffers, and restarts only when a
  resume is pending and the bridge is enabled. Before, a bridge disabled
  during StopUnproven kept those resources until re-enabled or rebooted.
  pollLateStopAck() is public for this.
- The wrapper's destructor no longer stops an already-stopped client:
  destroySlotClients() had just stopped it, so every shutdown logged five
  spurious "esp_mqtt_client_stop failed: ESP_FAIL" errors.

Hardware (Heltec V4, 1 s test stop deadline): restart -> StopUnproven ->
`set bridge.enabled off`; the late ack was reaped ("releasing withheld
resources"), status read "not running", and `set bridge.enabled on`
started cleanly without a second release.
2026-09-19 14:22:00 -07:00
agessaman d180f097ea fix(mqtt): an explicit bridge stop cancels a pending late-ack resume
setBridgeState(false) returned early when the bridge was already down,
so `set bridge.enabled off` after a timed-out restart left the resume
pending and MQTT restarted against the operator's choice once the task
acknowledged. Disabling now clears the pending resume before that early
return, and the resume also requires bridge_enabled. The StopUnproven
log and `get mqtt.status` no longer say only "reboot to recover", since
a late ack now recovers on its own.

Hardware (Heltec V4, 5 live slots, 1 s test stop deadline): a restart
went StopUnproven, the task acknowledged 27 s later and the bridge
resumed with all slots reconnecting; with `set bridge.enabled off` in
between it stayed stopped until `set bridge.enabled on`.
2026-09-19 13:58:49 -07:00
agessaman f50a6a3940 fix(mqtt): resume after a late stop ack; bound the hourly NTP refresh
- A stop that timed out into StopUnproven refused begin(), and only a
  later begin() honoured the task's late acknowledgement. Nothing called
  it, so restartBridge() (any `set mqtt...` restart) or an aborted OTA left
  the observer offline for the rest of the boot. MyMesh now records that a
  start was refused and restarts the bridge once stopAcknowledgedLate()
  reports the ack. The OTA-abort alert no longer claims "bridge resumed"
  when the restart was refused.
- refreshNTP() now runs the validated probe with one attempt per server.
  With two attempts and a 1 s pause it blocked the MQTT task ~18 s every
  hour on networks that drop UDP/123, where the old async SNTP cost
  nothing. Loop comment and docs updated to match.
2026-09-19 11:27:01 -07:00
agessaman 3c39908720 fix(webconfig): read the LAN address after pinning the route
startLanMode() took the caller's IP, read before the route lock. A
Wi-Fi/Ethernet switch in between left WebConfig locked to the new link
while advertising the old link's address. It now reads the selected
link's address after lockSwitching(), as startOTAUpdate() already does.
2026-09-19 10:53:39 -07:00
agessaman 4c683b13b3 fix(network): pre-merge review fixes
- Guard the boot-time link bootstrap to observer ESP32 builds. It ran
  unconditionally in MyMesh::begin(), breaking every non-observer
  repeater/room server build (ESP32 and nRF52).
- Rename NetworkInterface -> NetworkLink (class, accessor, files).
  Arduino-ESP32 3.x ships its own NetworkInterface class and header,
  which broke the ESP32-C6 builds. Drop WiFi.setAutoConnect(), a no-op
  on 2.x and removed in 3.x.
- Refresh stored Wi-Fi credentials every bridge tick so the STA
  reconnect loop picks up `set wifi.ssid` / `set wifi.pwd` without a
  reboot, as the bridge did before the link moved out of it. Skip
  reconnects while the SSID is empty.
- Restore the "WiFi connected: <ip>" / "WiFi disconnected: reason N"
  debug lines the bridge used to print.
- Alert on Ethernet only once it has held a lease this boot or when no
  Wi-Fi is configured; Wi-Fi-only installs of an Ethernet-preferred
  image keep Wi-Fi alerts instead of reporting "Ethernet down".
- Record wifi.setup_complete only for Ethernet LAN onboarding, so
  Wi-Fi builds keep the SSID-based first-boot portal rule.
- Use seq_cst for the route-switch lock/mutation flag handshake.
- Docs: SNMP RSSI sentinel is -127; describe link-return vs medium-switch
  reconnect behavior accurately; note runtime credential pickup.
- Test: unknown keys inside a known /mqtt.json group are ignored, which
  keeps wifi.setup_complete downgrade-safe.
2026-09-19 10:14:16 -07:00
mikecarper 30067cb5cd Split MQTT and ESP-NOW controls in Full ESP32 builds 2026-09-17 01:13:10 -07:00
mikecarper 9a149eaea1 Combine ESP-NOW and MQTT in ESP32 Full builds 2026-09-17 00:37:32 -07:00
mikecarper 4ba26ff4af Add secure management reports and honor GPS off 2026-09-16 20:42:30 -07:00
mikecarper 8ef2d41fb7 Report CLI save failures and validate advert intervals
Roll back failed password and advert changes, propagate identity-store results, and report unsaved common and observer settings. Correct observer filter persistence and KISS OTA capability claims. Add fault-injection and packaging regressions.
2026-09-15 20:05:30 -07:00
mikecarper b847b46ef7 Retry identity startup and harden replay state and permissions
Read identities up to three times before allowing replacement, publish only complete reads, and require new startup keys to be saved with bounded retries.

Use authoritative replay-file metadata throughout persistence and clock recovery. Validate setperm input before narrowing or mutating roles, and add production-path regression coverage.
2026-09-15 17:33:50 -07:00
mikecarper b4315dabdb Harden storage transactions and deferred radio settings 2026-09-15 16:01:46 -07:00
mikecarper 8f09daaad8 Improve Companion, repeater, and OTA reliability 2026-09-15 15:15:45 -07:00
mikecarper e70f4f9030 Fix OTA resume, CLI identity, radio persistence, and network sessions
Distinguish OTA read failures from missing blocks and select unique raw-flash checkpoints by MID and target. Keep deferred CLI requests and replies bound to immutable command identity and full authenticated keys.

Gate radio mutations on their exact reply delivery, recover coding-rate restoration, and roll back failed Companion preference writes. Preserve accepted low duty cycles across reboot.

Retain partial network writes, cancel displaced client operations, and reject malformed or stalled framed input. Add regression and CI coverage, and size-optimize the RAK4631 Ethernet build without dropping features.
2026-09-15 14:32:40 -07:00
mikecarper a2a7d3b168 Order radio schedules and preserve temporary lease deadlines
Apply due radio2 settings by requested start time and expire every old temporary
window before starting its successor. Anchor temporary deadlines to command
acceptance so idle time before a command cannot shorten the new lease.

Keep failed radio2 saves in their original order and retry without rewriting
their scheduled timestamps. Give repeater persistence its own backoff and cache
the next actionable deadline, preventing repeated writes while allowing
temporary windows to start and expire on time.

Exercise all 24 slot insertion orders with punctual and delayed servicing, plus
adjacent windows, storage failures, command-time anchoring, and timer rollover.
2026-09-15 08:59:10 -07:00
mikecarper 95aca52b4e Add per-profile TX routing and bridge filtering
Persist Companion TX preferences per contact and channel. Default repeater,
room, and sensor replies to both active TX profiles, with a reply-only force
option for an RX-only secondary profile. Track both reply copies before a
temporary-radio handoff and account for OTA copies under queue backpressure.

Add bridge/crossover filter modes and the third built-in wardriving filter,
plus compact filter CLI syntax, documentation, and CI coverage.

Fix quoted target names being interpreted as slot/key selectors, partial
recovery of invalid saved reply settings, filter suspension during tempradio2,
and unnecessary packet allocation while OTA traffic is throttled.

Validation: 1,477 native tests, 52 Python integration tests, 29 filter UI tests,
and five firmware builds covering ESP32, nRF52, and STM32 passed.
2026-09-15 01:58:43 -07:00
mikecarper 0e0aa8e014 Add WiFi, ESP-NOW and combined wireless CLI controls 2026-09-15 00:01:45 -07:00
mikecarper 3b6c894ee2 Add two-profile LoRa operation and radio CLI regression coverage
Implement radio2, tempradio2, radioat2 and tempradioat2 across Mesh roles,
with RX-only/RX+TX operation, optional preambles and persistent cross-TX
policy. Keep temporary OTA traffic on its profile by default and maintain
independent retry ownership and backoff for each profile.

Use slow-first receive scanning with 4.8-symbol visits and automatic
preambles rounded up in steps of eight. Preserve pending RX, restore power
saving on exit, and discard work bound to changed or expired profiles.

Restore the infrastructure path.hash.mode setter and report unsupported
extra.sf settings consistently. Add CLI, scheduling, scan and retry tests,
setup documentation, and the V4/XIAO hardware validation results.

Validation: 1,422 native tests, eight KISS tests, 63 final focused profile
tests, 18 checks from the staged source, sanitizer-enabled OTA transfers,
and builds for V4 Mesh roles, Full XIAO Companion and nRF52 T1000-E.
Hardware checks cover reception, cross-TX policy, expiry, reboot and OTA
discovery while receiving main-channel adverts.
2026-09-13 08:25:00 -07:00
mikecarper 12a3a467e1 Make repeater RX watchdog MeshCore-aware and tune temporary radio
Use RX-only radio recovery and feed the repeater reboot watchdog after MeshCore parsing. Default to 24 hours, apply temporary watchdog and advert intervals in RAM, and report temporary durations as days, hours, and minutes. Add regression coverage and an ESP-NOW bridge setup guide.
2026-09-11 22:10:13 -07:00
mikecarper fa010196b1 Merge PR #7 with reviewed ExpressLRS power and memory fixes
Integrate ExpressLRS TX module support and its stacked ESP32 heap changes.
Use the approved Linkflow calibration (17-30 dBm), preserve the PA drive
and output path after radio recovery, and keep LoRa OTA enabled.

Preserve stored ACLs and filters on allocation failure, release owned
client/filter buffers, service heap OTA contexts on Companions, release
self-serving workspaces after TempRadio, and reset staged-resume state
when a context is released. Keep manual staging and active operations
alive. Account for all moved allocations in the runtime RAM gate.

Validation: 1,393 native cases; radio-power, heap-context, ACL persistence,
shared-queue transfer, display/inbox, radio receive, and memory regressions.
Firmware builds passed for Linkflow, Heltec V2 Companion, T-Beam MQTT
repeater, Heltec V4 R8 MQTT repeater, RAK4631 repeater, and Indicator Full.
Physical verification awaits access to the currently offline lab Pi.
2026-09-10 22:06:16 -07:00
mikecarper d83153e935 Restore clearing all neighbors with an empty prefix
Accept the upstream empty-prefix neighbor.remove command after whitespace normalization while preserving invalid-key checks. Cover repeater and room-server removal paths with regression tests.

Fixes #8
2026-09-10 19:33:54 -07:00
mikecarper d6869b0af9 Default Cascade profiles to CAD off 2026-09-10 17:44:55 -07:00
mikecarper 7dd48ff0e2 Allow authenticated LoRa admins to read paged ACLs 2026-09-10 07:40:34 -07:00
MUSTARDTIGERFPV c6d71b969a Enable the on-demand mOTA context for every classic ESP32 build
Move OTA_HEAP_CONTEXT from two hand-edited envs to a pre-script gated on
build.mcu == "esp32", so every classic ESP32 image gets it and none can drift.
S2/S3/C-series and nRF52 keep the .bss singleton: they have no equivalent
static-DRAM ceiling, so there a guaranteed-present workspace is the better
trade for what is ultimately a recovery path. It has to be a build flag rather
than a header default, because OTA_HEAP_CONTEXT must hold the same value in
every translation unit that sees OtaContext.h - OtaContext.cpp included - and a
header test on CONFIG_IDF_TARGET_ESP32 would depend on include order relative
to sdkconfig.h. The script defers to OTA_SHARED_COMPANION_QUEUE where a Full
Companion recipe already owns the storage.

Fix a gap this exposes in the roles that now use it. Only CLI entry points
acquire the context, so once it was released, a repeater with the temporary
radio profile up would no longer serve or announce its own firmware - LoRa OTA
would look enabled and silently do nothing. ota_service_temp_radio_context()
holds the workspace for exactly the temp-radio window and hands it back
outside it, which is where the saving was coming from anyway. Wired into the
repeater, room server and sensor loops; the Companion keeps its own
acquire-on-host-demand policy, since its context is borrowed from the offline
message queue and must not be taken speculatively.

Static DRAM, classic ESP32 (bytes occupied of a 124,580 region):
  Heltec_v2_repeater                   108,332 -> 70,732
  Heltec_v2_room_server                 83,716 -> 68,300
  Heltec_v2_companion_radio_ble        123,236 -> 107,772  (was failing on main)
  Tbeam_SX1262_repeater                 86,932 -> 71,468
  Tbeam_SX1262_repeater_observer_mqtt  121,132 -> 83,532   (was failing)
2026-09-09 01:24:00 -07:00
MUSTARDTIGERFPV 0d42d3c8e1 Move large repeater tables off classic ESP32 static DRAM
Classic ESP32 links into a dram0_0_seg of only 124,580 bytes: memory.ld
carves the BT controller's 0xdb5c (56,156) reservation off the 0x2c200
window before the application gets any. Three large objects dominated what
was left, and Tbeam_SX1262_repeater_observer_mqtt overran the build's 8 KiB
static reserve by 4,744 bytes.

Heap-allocate them instead, each with a defined behaviour when the
allocation fails:

- OtaContext: generalize the Companion's borrowed-storage path behind
  OTA_DYNAMIC_CONTEXT and add OTA_HEAP_CONTEXT, which allocates the context
  on first use and frees it once no transfer, apply or folder link needs it.
  A repeater is idle nearly all the time, so the workspace is usually absent.
  Exhaustion is reported to the caller and the operation declines.
- ClientACL: the client table becomes a pointer with a live capacity;
  capacity 0 refuses new clients rather than writing through a null table.
- MyMesh flood packet filters: likewise, with flood_packet_filter_slots as
  the live bound for every rule loop. At capacity 0 the node forwards
  unfiltered, and both save paths refuse to write so a stored ruleset is
  never replaced by an empty file.

Also override the Arduino SDK's weak btInUse() in simple_repeater so
initArduino() releases the BT controller memory to the heap. That grows the
runtime heap these tables now come from; it cannot recover the same
reservation from the linker's static window.

Static DRAM, classic ESP32:
  Heltec_v2_repeater                   108,332 -> 70,732
  Tbeam_SX1262_repeater_observer_mqtt  121,132 -> 83,532  (was failing)
2026-09-09 01:24:00 -07:00
mikecarper b9138c13b2 Allow local CLI maintenance across direct connections 2026-09-08 21:44:57 -07:00
mikecarper aa8caa4ed2 Unify device controls and restore the WiFi console 2026-09-08 18:36:23 -07:00
mikecarper 51ce1f8f0d Protect ESP32 HWCDC console sessions 2026-09-04 17:18:54 -07:00
agessaman 9b739a9b43 fix(network): harden Ethernet-preferred lifecycle 2026-09-04 08:42:46 -07:00