Commit Graph
2021 Commits
Author SHA1 Message Date
mikecarper 7a8bddda53 Advertise exact nRF52 DFU name and service 2026-09-25 08:38:39 -07:00
mikecarper 4362acf00b Bound nRF52 UF2 images and reduce repeater BLE DFU memory 2026-09-25 08:09:38 -07:00
mikecarper 51ed608951 Fix logging matrix firmware build failures 2026-09-25 00:37:38 -07:00
mikecarper 40a9843c24 Merge branch 'keymindCascade' of github.com:mikecarper/MeshCore into keymindCascade
# Conflicts:
#	test/test_nrf52_ble_startup.py
2026-09-24 17:36:09 -07:00
mikecarper 307e7bec02 Complete ESP32 partition expander and adaptive LoRa OTA pacing 2026-09-24 17:13:22 -07:00
mikecarper 640facd5a2 Safely hand off expanded ESP32 LoRa bridge without resume record 2026-09-24 12:17:49 -07:00
mikecarper c0ef07a968 Add LoRa partition migration recipes for ESP32 infrastructure boards 2026-09-24 10:40:32 -07:00
mikecarper e7f6d529a0 Expand ESP32 OTA partition migration across infrastructure roles 2026-09-24 10:01:14 -07:00
mikecarper 1e8ef78a2e Merge observer firmware branch into keymindCascade
Integrate compatible observer board, preset, WebConfig, display, CI, and policy updates. Preserve the checkout's newer MQTT binary preferences and WiFi bridge runtime where the forked implementations conflict. Keep ThinkNode observer networking WiFi-based until the Ethernet transport is wired into that runtime.
2026-09-24 01:20:56 -07:00
mikecarper 4176c92462 Merge PowerSaving-v17 into keymindCascade 2026-09-24 00:56:05 -07:00
mikecarper c31a0e5ed5 Merge upstream dev into keymindCascade 2026-09-24 00:51:07 -07:00
mikecarper c55022b001 Add in-place ESP32 Wi-Fi and LoRa partition migration packages 2026-09-23 22:12:45 -07:00
mikecarper 69c2e4855f Use nRF52 profile clock only for dual-radio scanning 2026-09-23 15:42:12 -07:00
mikecarper 1e1ee2618a Use precise T1000-E dual-profile timing 2026-09-23 15:31:43 -07:00
mikecarper dd0ce36176 Refresh nRF52 Companion BLE name without reboot 2026-09-23 14:12:37 -07:00
mikecarper 7de7fcf675 fix(nrf52): recover idle secured BLE links 2026-09-23 13:11:09 -07:00
mikecarper 5c4394edd8 Keep nRF52 Full Companion BLE subscriptions across reconnects 2026-09-23 03:19:58 -07:00
mikecarper ff50589b69 feat(cli): support relative minutes in radio schedules 2026-09-23 00:19:12 -07:00
mikecarper 47af95b4b2 Harden nRF52 filesystem recovery and flash writes 2026-09-22 15:17:39 -07:00
mikecarper 7e1f87dfbc Fix nRF52 flash completion and preserve recoverable ExtraFS 2026-09-22 09:29:05 -07:00
mikecarper 3282b7898e fix(nrf52): stabilize full companion bluetooth 2026-09-22 05:54:46 -07:00
ripplebiz 140178728b Merge pull request #3427 from Dom4n/feat/lr1110-dcdc-dev
Enable DC/DC for LR1110 radios
2026-09-22 17:57:09 +10:00
mikecarper 872e557db0 fix(esp32): remove case-colliding buffered radio HAL header 2026-09-22 00:35:03 -07:00
mikecarper 04697db82e feat(radio): calibrate dual-profile preambles from live switch timing 2026-09-22 00:09:37 -07:00
mikecarper 8c7f4df1cd feat(radio): enable fast profile switching across direct SX1262 boards
Add acknowledged SetFs and packet-cache shortcuts for SX1262, extend the LR1110 FS-mode fast path with buffered SPI, and keep the expander-backed Indicator on its previous path. Include HIL fixtures, validation notes, and regression tests.
2026-09-21 22:57:22 -07:00
ripplebiz 5e9d80df38 Merge pull request #3464 from lbibass/lr1121_dcdc
LR1121 DC-DC & Misc Updates
2026-09-22 15:12:56 +10:00
mikecarper 90d8a7f3c4 fix(ota): recover corrupt companion settings stores 2026-09-21 18:35:57 -07:00
mikecarper 7bad5f9b30 fix(companion): recover radio profile settings after flash 2026-09-21 18:21:36 -07:00
mikecarper b089b745fa feat(ui): page radio and system status 2026-09-21 15:35:46 -07:00
Blake Latchford 0c3fe356d0 Constrain agc.reset.interval and allow intervals > 255s 2026-09-21 12:26:36 -04:00
fdlamotte f4ae72dc12 Merge pull request #3436 from Blake-Latchford/wio-e5-fix
WIO-E5 Fix: Pin Radiolab for stm32 and replace ltoa
2026-09-21 05:50:12 -04:00
ripplebiz 65737d48f4 Merge pull request #3402 from meshcore-dev/sensor-improvements
Sensor improvements
2026-09-21 19:03:18 +10:00
Ev Lbibass 84f1e11d29 update structure of LR1121 to match LR1110 files. Implement DC-DC on LR1121 radio. saves about 30% power consumption at idle. 2026-09-20 20:38:05 -04:00
Blake Latchford 12a8c8d4d4 Add missing include, and revert removal of lota 2026-09-20 15:16:33 -04:00
Liam Cottle 0886259701 Merge pull request #3108 from tjdownes/muzi-base
Add Muzi Base board support (Duo + Uno + SuperIO)
2026-09-20 22:56:32 +12:00
Adam Gessaman 1025128622 Merge pull request #59 from agessaman/feat/webconfig-board-commands
feat(webconfig): let the portal discover board-specific CLI commands
2026-09-19 21:38:09 -07:00
Adam Gessaman 76a770d8f1 Merge pull request #58 from agessaman/fix/observer-hostname-on-rename
fix(network): rebuild the DHCP hostname when the node is renamed
2026-09-19 21:37:32 -07:00
agessaman fdf299c3d0 fix(webconfig): publish the board probe safely and test the gap
Codex review of the previous commit found two real problems.

The routes go live in startLanMode()/startSetupMode(), one tick before
probeBoardCommands() first runs, so a status request can land in between.
That made _board_cmds a genuine cross-task race despite the comment
claiming otherwise, and — worse — let /api/status answer "no board
commands" before the board had been asked. The page reads status once,
so a browser that loaded in that window hid working FEM controls for the
whole session.

The probe result is now published and read under _mux, and /api/status
OMITS board_cmds until the probe has run, which is not the same as
reporting an empty list. The page treats the absent field as "not known
yet" and re-polls with backoff; the audit waits for it the same way,
rather than auditing a board it only half knows.

Probing from the start path instead would close the window, but
Callbacks::execCommand is documented as loop-task-only and `start
webconfig` is itself mid-handleCommand, so that trades a race for
re-entrancy.

The audit never executed fan.lo or fan.hi, which have no getter and so no
round-trip: they were only ever matched as strings. They now run for real,
including the four rejection paths, and the mock grew --probe-delay so the
retry path is exercised rather than assumed.
2026-09-19 21:08:54 -07:00
agessaman a1f3e2c3d3 feat(webconfig): let the portal discover board-specific CLI commands
The FEM commands moved out of CommonCLI into Board::handleCommand(), and
the T-Beam 1W fan control was added there too, but the portal never caught
up: `radio.fem.txgain` and the fan commands were missing from the terminal
table entirely, while `radio.fem.rxgain` was offered on every board even
though a board with no hook now answers "??:" rather than "unsupported".

Whether a node answers these is a property of the board, not of the build,
so the page cannot know from the firmware version. Ask the board instead:
probeBoardCommands() runs each candidate getter once on the loop task at
startup and keeps the ones that answer, which needs no per-variant list
because Board::handleCommand() already reports whether it handled a
command. /api/status names the survivors and the page hides everything
else, so adding a command to a variant means one entry in WC_BOARD_CMDS
rather than an edit per board.

The two FEM keys also become Radio-panel toggles, gated the same way;
their `set` reaches the board hook through the existing config batch, so
only the allowlist and /api/config needed to grow.

webconfig_cli_audit.py now scans variants/*/*Board.cpp for the boards that
actually build the portal, checks set-only keys in the reverse direction,
and verifies every gate is a command some board answers; the mock gained
--board-cmds so both shapes of board are testable. `stop ota` joins
NOT_OFFERED: `start ota` cannot run from the portal, so it has nothing to
stop.
2026-09-19 20:51:25 -07:00
mikecarper 66b605a49d Add Wi-Fi OTA partition migration support 2026-09-19 20:14:37 -07:00
MarekWoandClaude Opus 5 c32730ea33 Add marwoj MQTT broker preset
Adds a built-in preset for the Polish MeshCore community broker at
mqtt.marwoj.net: MQTT over TLS on port 8883, username/password auth
with credentials embedded in firmware, and the standard MeshCore topic
layout. The Let's Encrypt chain anchors at the existing ISRG_ROOT_X1
constant, so no new CA certificate is needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 17:01:23 -07:00
agessaman 7af901a576 build(mqtt): make the preset table byte-identical across channels
The preset table is fleet state, not a build detail. A slot's preset is stored
in /mqtt.json by name, and firmware that does not know a name does not merely
ignore it: MQTTPrefsSerializer repairs it to "none" and CommonCLI writes the
repaired file back to flash. A node that rolls back to a channel missing a
preset therefore loses that slot permanently, and re-upgrading does not bring
it back.

The parity gate compared preset names only, deliberately allowing URL, CA and
credential drift. It now requires src/helpers/MQTTPresets.h to be byte-identical
between the channels, which also catches that drift — the two channels are meant
to dial the same brokers.

That is only workable if the file holds no channel-specific code, so
mqttPresetEnforcesTokenExp() moves to the new MQTTPresetPolicy.h. It was the
sole difference between the two channels' copies, and with it moved they match
exactly today. Policy keyed off the table belongs there from now on; the table
itself stays pure data.

The older name-only comparison stays available without --exact for ad-hoc use,
and the checker's self-test now covers both modes, including that --exact
rejects a config-only change the name check waves through.
2026-09-19 16:46:55 -07:00
agessaman 7523ac742d 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 4dd438f499 fix(network): keep the DNS snapshot typed and on the lwIP thread
Follow-up to the DNS restore, from a second review round.

lwIP's DNS functions belong to the TCP/IP thread, and both halves were called
from elsewhere: the Ethernet snapshot from the Arduino event task, the Wi-Fi
snapshot and every restore from the MQTT task. Both now go through
esp_netif_tcpip_exec(), which is also what serializes the snapshot itself, so
it no longer needs atomics. Neither caller is the TCP/IP thread, so the call
cannot deadlock on itself.

The snapshot keeps the whole ip_addr_t instead of an IPv4 word. These builds
compile lwIP with IPv6 enabled, so reinterpreting a v6 server as v4 would have
installed four meaningless bytes as a resolver. RDNSS is disabled here, so a v6
server should not arise today; storing the tagged value means it stays correct
if that changes.

Restore now writes every slot, empty ones included, so returning to a
one-server network cannot leave the other medium's second server behind as a
fallback that only fails slowly. A lease that carried no resolver at all is not
recorded, so a medium that has never had DNS leaves the current resolver alone
instead of wiping it.
2026-09-19 15:45:39 -07:00
agessaman ee41da85ec fix(network): bound link transitions and restore each medium's DNS
Two defects found by an adversarial review of the merge.

A link transition no longer stops a slot whose connect attempt is in flight.
Stopping one means esp_mqtt_client_stop(), which waits on the SDK's API mutex
and its task's stopped event with no bound; the client task notices only when
it returns from whatever transport call it is in. During teardown the bridge's
StopUnproven timeout contains that, but during a transition nothing does, so a
routine WiFi flap could freeze the sole MQTT worker for far longer than the 2 s
this path advertises, with the bridge's own stop handshake queued behind it.
The attempt is left to resolve instead: unlike a reconfigure, a transition
changes neither endpoint nor credentials, so an attempt that completes is
credited to the broker it actually reached and drops with the old route.

Failing back to Ethernet left the node using the WiFi network's DNS server.
lwIP keeps one global server list and IDF 4.4 has no per-interface retention,
so the medium that leased last owns DNS for every socket. Each link now
remembers the resolver its own DHCP lease installed and puts it back when it is
selected again; a medium that never held a lease leaves the current resolver
alone. Where the two networks are on different subnets this was a silent
outage: the link read as connected while every broker and NTP hostname failed
to resolve, until Ethernet's own DHCP renewal happened to fix it.
2026-09-19 15:31:51 -07:00
agessaman 7f28231b25 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 172ef36843 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 0b75cb9314 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 109d7d6128 fix(mqtt): record the renewed token as applied after a successful bounce
The renewal bounce called reconnect()/connect() directly, so a successful
bounce never advanced applied_token_expires_at. The renewal decision
reads that value, so a renewed slot stayed due and, on a broker that
enforces exp, minted and reconnected every minute for the rest of the
token's life. Both the renewal bounce and the corrected-clock bounce now
go through reconnectSlotClient(), which starts a stopped client, refuses a
quarantined one, moves the slot to Starting and records the token in use.
The corrected-clock no-bounce case settles the renewal as the renewal
path already does.
2026-09-19 11:48:43 -07:00
agessaman 3c03254aa2 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