Commit Graph
3822 Commits
Author SHA1 Message Date
agessaman daec2e4edd fix(mqtt): address review — stopped clients, late allocation, fail-open map check
Three defects found reviewing the preceding commits.

1. reconnectSlotClient() stranded a STOPPED client, reintroducing the very bug
   this branch fixes. It only rebuilt when isStarted() was true and otherwise
   fell through to reconnect(), which is a documented no-op on a stopped client
   — so nothing restarted it, at any rung, including the breaker probe. The
   WiFi-transition teardown reaches exactly this state: it calls the hard
   disconnect(), clearing _started while initial_connect_done stays set, so
   after WiFi returned the slot could never come back. Now a stopped client is
   started with connect() before the rebuild/reuse decision is considered.

   The post-NTP credential refresh had the same exposure — it called
   reconnect() directly — so it now goes through the helper too, still reusing
   the transport since its fault is stale credentials, not the transport.

2. Allocating the neighbors buffer on first use let a stopped bridge allocate.
   A neighbour discovery started before a stop can complete after it, and
   neither caller rechecks bridge state, so requestPublishNeighbors() would
   allocate 4 KB after releaseRuntimeBuffers() had already run and strand
   _neighbors_publish_pending with no task to consume it. end() then returns
   early on !_initialized, retaining the buffer until a later begin/end or a
   reboot. Guarded on isRunning(), the same flag end() checks.

   The release/acquire handoff itself was confirmed sound: the allocation and
   copy precede the release store, and the task loop reads the pointer only
   after its acquire load, so a half-published pointer is not observable.

3. The post-link map check failed open, contradicting the fail-closed claim in
   its own commit message. A missing map, an unrecognised map format, or a
   partial archive list each warned and passed; and it hardcoded firmware.map
   while the post-action target used ${PROGNAME}, so a renamed program could
   inspect a stale or absent file and still succeed. All four now fail the
   build, and it requires every one of the four archives to appear rather than
   at least one.

Rebuilt Heltec_v3_repeater_observer_mqtt, Heltec_v3_repeater and
heltec_v4_repeater_observer_mqtt; the opt-in path still reports all 4 archives
linked from .mbedtls-4k/.

(cherry picked from commit 5b5f076e5e165997e8050f2be061c7c67340fcf7)
2026-08-14 09:47:40 -07:00
agessaman 8275512964 build(mqtt): make the reduced-TLS mbedTLS archives shippable, opt-in and verified
The reduced-TLS work was validated on hardware but only reachable through
PLATFORMIO_BUILD_FLAGS pointing at an absolute path in a developer's home
directory, so nothing outside that machine could reproduce it.

Distribute the archives as a release asset instead of committing them: ~6 MB
per architecture, and they must be rebuilt for every espressif32 bump, so
committing would grow history permanently and go stale without any signal.

  scripts/mbedtls_4k_manifest.txt  per-arch sha256 of each archive
  scripts/fetch_mbedtls_4k.sh      fetch into .mbedtls-4k/<arch>/, verify
  scripts/mbedtls_4k.py            pre-build wiring and post-link proof

Off by default. The script is attached to esp32_base but returns immediately
unless MESHCORE_REDUCED_TLS=1, so ordinary builds need no artifact and are
byte-for-byte unaffected — confirmed by building with it absent.

Both ways this can fail silently produce a firmware that looks fine and lacks
the change, so the opt-in path refuses to guess:

  - a -L at a missing or partial directory: the linker ignores an unusable
    search path and resolves mbedTLS from the framework. Now a hard error.
  - archives left over from an earlier platform version: now a sha256
    mismatch against the manifest, naming both hashes.
  - a -L that is present but outranked, leaving the flag inert: after the
    link, firmware.map must resolve every libmbed*.a into .mbedtls-4k/, or
    the build fails and prints the offending paths.

That last check earned its place immediately — it caught its own first
implementation comparing a relative map path against an absolute one, and an
earlier build flag in this investigation was accepted by the compiler while
no source read it. A flag reaching the compiler proves nothing about the link.

Verified all four paths on Heltec_v3_repeater_observer_mqtt: default build
unaffected; opted in with archives present links all four from .mbedtls-4k/
and says so; archives absent fails with a fetch hint; a single appended byte
fails on sha256.

Also removes platformio.local.ini.hold, which held the superseded approach of
pointing platform_packages at a whole custom framework. That installs over the
shared framework package and changes mbedTLS for every other ESP32 project on
the machine; the -L path keeps the change scoped to one env.

Note the inbound record buffer stays at 16 KiB, so this lowers per-connection
footprint by ~12 KiB but does not move the contiguous allocation a handshake
needs. It buys headroom, not a lower floor.

(cherry picked from commit a87faff6ff170c328fdd0550f4b4dd9089aa2ea0)
2026-08-14 09:47:40 -07:00
agessaman 8ee21d2c0f fix(mqtt): allocate the neighbors JSON buffer on first use, not at bridge start
allocateRuntimeBuffers() took NEIGHBORS_JSON_BUFFER_SIZE unconditionally on
every board built WITH_MQTT_NEIGHBORS, whether or not mqtt.neighbors was ever
turned on. On a non-PSRAM board that is 4 KB of internal DRAM held for the
bridge's lifetime by a node that may never publish a neighbours snapshot.

Gating the existing allocation on the pref would not work: mqtt.neighbors is
read live by the mesh loop with no bridge restart, so enabling it at runtime
would find no buffer and silently publish nothing. Allocate on first use
instead, in requestPublishNeighbors(), which is reached only when something
actually wants to publish — periodic or a manual discovery.

Publishing the pointer across cores is safe with the existing handshake: the
allocation precedes the release store on _neighbors_publish_pending, and the
task loop reads the pointer only after its matching acquire load, so the
pointer cannot be observed half-published. A failed allocation drops that one
snapshot and retries on the next, rather than disabling neighbours for the
bridge's lifetime as the eager path did.

(cherry picked from commit e6da052a93f8765824d0fb4bd0c704ca3ed3d294)
2026-08-14 09:47:40 -07:00
agessaman 4c90db2199 fix(mqtt): renew JWT credentials without stopping the esp-mqtt client
The scheduled JWT bounce called PsychicMqttClient::disconnect(), which ends
with esp_mqtt_client_stop(). That ends the client task and returns its 6 KiB
stack to the heap at the moment the TLS teardown vacates two 16 KiB mbedTLS
record buffers, so the stack lands in that hole and the next handshake cannot
reuse it. On non-PSRAM boards the largest free block then ratchets down 16 KiB
at a time while total free heap stays flat.

Soak evidence from a Heltec V3 on 8d1a0eb3: 43 of 60 disconnects had no
preceding transport error, i.e. they were this proactive bounce rather than a
broker FIN, and two of the three max_alloc steps landed within 5 s of one.
Losing a whole TLS session later returned exactly 16,384 bytes of contiguity.

softDisconnect() closes the transport without the stop, so the task and its
stack stay put across the handshake. The bounce uses it plus reconnect(), and
falls back to connect() when the client really is stopped, since reconnect()
is a silent no-op in that state.

Also corrects a comment claiming the mbedTLS context survives a transport
close: only the esp-mqtt client object does.

(cherry picked from commit 10cf5cf48fb009e751e25b37fcc1f3d1256ddbbc)
2026-08-14 09:47:40 -07:00
agessaman 40635a53c4 test(native): declare stdlib in the Arduino mock so ConfigSerializer builds
The real Arduino.h includes stdlib.h, so ConfigSerializer.cpp reaches atoi,
atol and atof through it and compiles on device. The mock supplied only
cstdint, cmath and Stream.h, leaving those undeclared — and since the native
env compiles ConfigSerializer.cpp into every suite via build_src_filter, all
21 suites errored rather than just its own.

Fixing the mock keeps src/ identical to upstream and covers any other source
relying on the same transitive include.

pio test -e native: 297 test cases, 297 succeeded.
2026-08-14 09:05:05 -07:00
agessaman d489159c99 docs: refresh firmware-notes.html for v1.17.1
The rolling-release body is what the flasher dropdown serves as changelog.
Drop the stale v1.16.0 experimental blurb and describe the current observer
surface: MeshCore 1.17.1, web config, in-channel OTA, and neighbors.
2026-08-14 08:19:34 -07:00
agessaman 47c80fecf3 ci: bump observer channel base version to v1.17.1
Both channels move together so a node comparing its embedded base against
its own channel's manifest does not read as permanently behind.

Resets the per-base build counter to N=1 on both channels. Nodes still on
v1.17.0.N take the diff_base branch in ESP32Board's OTA comparison, so the
counter going backwards does not strand them.
2026-08-14 08:10:42 -07:00
agessaman 6c0ae4d249 merge: upstream/dev into observer-firmware-dev (v1.17.1)
Picks up upstream MeshCore 1.17.1.

Notable upstream content:
- 1.17.1 version/build-date bump in the example MyMesh headers.
- nRF52: combine radio entropy with CC310 RNG.
- Companion FEM prefs: load/save of fem_ properties commented out until they
  can be set from the client.
- Scoped reply routing: replies no longer dropped when flood.max.unscoped is
  low (RoutingPolicy + unit tests).
- nRF52 unused-pin sweep (T1, T-Echo Lite, MeshPocket).

No conflicts.
2026-08-14 08:09:07 -07:00
Scott Powell d929643524 * version 1.17.1 mbedtls-4k 2026-08-14 22:19:19 +10:00
Huw Duddy 24bdc24b39 Merge pull request #3206 from meshcore-dev/nrf-combine-rng-entropy
combine radio's entropy with CC310 RNG
2026-08-14 22:09:26 +10:00
Scott Powell fcb9bdf209 * combine radio's entropy with CC310 RNG 2026-08-14 17:46:43 +10:00
Huw Duddy 50a51a6479 Merge pull request #3203 from meshcore-dev/companion-fem-pref-fix
commenting out the load/save of the fem_ properties
2026-08-14 16:44:59 +10:00
Scott Powell e78bff0041 * unit test no longer valid 2026-08-14 16:42:54 +10:00
Scott Powell 890a2e2cff * commenting out the load/save of the fem_ properties, until they can be set 2026-08-14 16:30:12 +10:00
ripplebiz b09cb27a1f Merge pull request #3106 from ViezeVingertjes/fix/scoped-reply-routing
Fix replies dropped when flood.max.unscoped is low
2026-08-13 15:00:37 +10:00
ripplebiz abd3546d75 Merge pull request #3189 from oltaco/nrf52-unused-pin-sweep
Fix unused pin definitions on some NRF52 boards
2026-08-13 13:48:59 +10:00
taco 7cd1d207af Fix LilyGo T-Echo Lite SPI pins 2026-08-13 00:53:58 +10:00
agessaman ced0eb3780 merge: upstream/dev into observer-firmware-dev
Brings in the external FEM gain preferences (fem_txgain, PR #3137 plus the
companion-side port), the AGC reset rxgain fix, the LR2021 preamble/IRQ
timeout logic, and assorted variant fixes (T096, T-Echo Card TCXO, promicro
pinmap, minewsemi, R1 Neo).

Conflict resolutions:

- SH1106Display: both sides fixed T-Beam Supreme startup independently. Kept
  our _initialized guard and DISPLAY_ADDRESS_ALT override, took upstream's
  SA0-pair fallback and its unconditional display.begin() so the frame buffer
  is allocated even when no panel answers.
- MyMesh/SensorMesh/CommonCLI: took upstream's fem_txgain default and wiring,
  kept our comments and the observer-side prefs layout.

Also fixes CustomLLCC68Wrapper, which upstream missed when sx126xResetAGC
gained its rx_boost_gain parameter. No variant builds that wrapper today, so
neither tree failed to compile.
2026-08-12 07:19:19 -07:00
taco c2a4ef082f Fix Heltec MeshPocket SPI pins 2026-08-13 00:15:34 +10:00
taco 3ea541f1f2 Fix Heltec T1 pin definitions
SPI pins that are set to -1 cause OOB reads on NRF52. The unused Serial2 pin definitions were removed to avoid potential issues with the Uart framework.
2026-08-13 00:12:37 +10:00
ripplebiz e9edfc8e1a Merge pull request #3188 from oltaco/heltec-t096-pin-fix
Fix Heltec T096 Tx issue
2026-08-12 23:00:31 +10:00
taco 49d9f9a839 Heltec T096: fix PIN_SPI1_MISO
Setting SPI pins to values that are OOB of the g_ADigitalPinMap[] array causes OOB reads. This variant.h has pin 0 as 0xFF which passes through as NRFX_SPIM_PIN_NOT_USED.
2026-08-12 22:31:57 +10:00
taco f43e1cde89 Revert "t096 compiler layout workaround"
This reverts commit 9c60f9696e.
2026-08-12 22:28:36 +10:00
taco 264d778168 Revert "T096 PA_CTX pin direction workaround"
This reverts commit 82de18fb0e.
2026-08-12 22:28:05 +10:00
ripplebiz 956094b271 Merge pull request #3185 from oltaco/fix-techo-card-txco-voltage
Set T-Echo Card TCXO voltage to 3.0
2026-08-12 18:13:44 +10:00
ripplebiz a4ab7f0e59 Merge pull request #3137 from agessaman/feat/station-g3-fem-prefs
Station G3: Expose, persist, and apply FEM gain preferences
2026-08-12 16:11:23 +10:00
taco 9d6dcc673e set T-Echo Card TCXO voltage to 3.0
as per https://github.com/meshcore-dev/MeshCore/pull/3140 and https://github.com/Xinyuan-LilyGO/T-Echo-Lite/issues/14
2026-08-12 16:08:05 +10:00
ripplebiz 1cb308b694 Merge pull request #3182 from oltaco/t096-tx-workaround
Workaround for Heltec T096 transmit issue
2026-08-12 14:49:32 +10:00
taco 82de18fb0e T096 PA_CTX pin direction workaround 2026-08-12 14:07:31 +10:00
taco 9c60f9696e t096 compiler layout workaround
see https://github.com/meshcore-dev/MeshCore/issues/3151
2026-08-12 14:04:50 +10:00
ripplebiz e027263d1e Merge pull request #3154 from fdlamotte/nrf52_hwcrypto_init
nrf52: call nRFCrypto.begin()/end() only once
2026-08-12 12:14:21 +10:00
Huw Duddy 34ad059afb Merge pull request #3170 from recrof/promicro-pin-tidy-up
[promicro] make pinmap more tidy, move the LoRa pins to variant.h
2026-08-11 18:43:20 +10:00
Rastislav Vysoky 5cce5cfe97 removed misleading 'GPIO' from pin numbers 2026-08-11 10:37:20 +02:00
ripplebiz fbef2ff0dc Merge pull request #3164 from weebl2000/fix-tbeam-s3-display
Fix display issues on T-beam Supreme S3
2026-08-11 14:39:39 +10:00
agessaman c5bf3e990a docs(mqtt): record the non-PSRAM TLS footprint investigation
Documents why the largest allocatable block on a non-PSRAM observer walks down in
16 KiB steps at every TLS reconnect, and what to do about it.

The framework builds mbedTLS with the symmetric buffer configuration
(CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN 16384), so each broker slot holds two 16 KiB
record buffers in internal DRAM and two slots cost 64 KiB on a board with about
80 KiB free. Confirmed against the sdkconfig the Heltec env actually links, not
the IDF 5.3 libs package that belongs to the esp32c6 env.

Rebuilding only the mbedTLS archives with an asymmetric outbound buffer of 4 KiB
is ABI-safe, because in_buf/out_buf are heap pointers sized in ssl_setup() and no
public struct embeds a CONTENT_LEN-sized array. Dropping KEEP_PEER_CERTIFICATE is
not, since it changes mbedtls_ssl_session layout, so it is excluded.

Also records two traps worth not rediscovering: esp32-arduino-lib-builder's
release/v4.4 clones its dependencies at master and no longer resolves, and
pointing platform_packages at a file:// framework copy overwrites the shared
package for every other project on the machine.
2026-08-10 15:47:44 -07:00
agessaman 28dd599fa8 fix(observer): recover T-Beam Supreme display startup 2026-08-10 12:59:02 -07:00
agessaman e2aa7b98f9 feat(companion_radio): add external FEM gain preferences for RX and TX for companions
Introduced consistent preferences for external LoRa FEM RX and TX gain settings in NodePrefs. Updated companion MyMesh to apply these settings during initialization and transmission. Added unit tests to verify the round-trip serialization of these new preferences.
2026-08-10 11:59:26 -07:00
Rastislav Vysoky a82dc1b9da make promicro pinmap more tidy, move the LoRa pins to variant.h 2026-08-10 20:13:59 +02:00
Liam Cottle 9d8894e166 Merge pull request #3166 from recrof/debug-noise-floor-flag
move RadioLibWrapper noise_floor level message under special debug flag
2026-08-11 03:07:31 +12:00
Rastislav Vysoky 3138ad99ba move RadioLibWrapper noise_floor level message under special debug flag 2026-08-10 16:56:56 +02:00
Wessel Nieboer 32bd6d48d6 Fix T-beam Supreme S3 display 2026-08-10 13:39:16 +02:00
Huw Duddy f6c25e6a25 Merge pull request #3158 from mepholic/bug/fix-rxgain-setting-post-agc-reset
Fix RX Boosted Gain setting after AGC reset is triggered
2026-08-10 17:31:38 +10:00
ripplebiz 9941669fd5 Merge pull request #3142 from oltaco/buildfix-userbtn
Build fix for R1 Neo and Minewsemi repeater
2026-08-10 13:30:24 +10:00
ripplebiz 0d60de6fa6 Merge pull request #3146 from oltaco/lr2021-preamble-and-irq-timeout
LR2021: add PREAMBLE_DETECTED bit and IRQ timeout logic
2026-08-10 13:29:12 +10:00
agessaman 8204004959 fix(observer): leave AGC reset user-configurable 2026-08-09 19:07:25 -07:00
Dan Theisen 6f491f3033 RX Boosted Gain was set back to the compiled in value rather than the current/flash value set by the user when the ACG reset is triggered. This commit fixes this. 2026-08-09 18:11:35 -07:00
Florent ecb8c94560 nrf52: call nRFCrypto.begin()/end() only once 2026-08-09 14:46:23 -04:00
agessaman 95f326954b merge: perf/memory-savings into observer-firmware-dev
Nine commits reducing the MQTT bridge's internal-DRAM footprint, plus four
fixes that rode with them (invalid path encodings, stale-JWT scan after a
clock correction, setup-retry interval measured from the failure, retried
setup consuming the reconnect allowance).

Touches no prefs surface -- nothing in NodePrefs, MQTTPrefs, or
ConfigSerializer -- so /prefs.json layout is unaffected and there is no
fleet config risk.

Soak evidence: every soak branch already contained this work in full. Device 1
has run it 69.6 h with 325,852 publishes, 0 errors and 0 reboots. The caveat
worth carrying: that long-duration evidence is all on the reduced-TLS
framework (OUT_CONTENT_LEN 4096). Device 3 is now soaking it on the stock
framework, which is where the allocation-ordering interaction with the full
16 KiB record buffers actually gets exercised.
2026-08-09 09:33:20 -07:00
agessaman 8f12b89d6d ci: bump observer channel base version to v1.17.0
Both channels move together: the beta workflow's FIRMWARE_VERSION must equal
production's, since a node compares its embedded base against its own channel's
manifest and a mismatch would read as permanently behind.

Resets the per-base build counter to N=1 on both channels. Nodes still on
v1.16.0.N take the diff_base branch in ESP32Board's OTA comparison ('different
base version is always an update'), so the counter going backwards does not
strand them; the build-number arithmetic only runs when the base matches.

The flasher repo's config.json version key still needs the matching bump.
2026-08-09 06:57:43 -07:00
agessaman f9968ac1a5 merge: upstream/dev into observer-firmware-dev (v1.17.0)
Picks up upstream MeshCore 1.17.0 and the dev commits that followed it.

Notable upstream content:
- 1.17.0 version/build-date bump in the example MyMesh headers.
- anon_req hardening: reply_path_len is now uint8_t with an isValidPathLen()
  bounds check and a 0xFF sentinel; reply_path_hash_size is gone.
- LR2021 support (Meshnology W12, Seeed MeshTracker X1) incl. side detectors,
  multi-SF and the new `extra.sf` get/set CLI.
- CustomLFS 0.2.2 -> 0.2.3 (GD25Q64C support).
- kiss_modem envs for several nRF52 variants; ThinkNode M6 GPS/flash fixes.

Conflicts resolved:
- platformio.ini: took upstream's CustomLFS 0.2.3, kept our lib_ignore for
  the vendored PsychicMqttClient.
- CommonCLI.h: kept both upstream's USE_LR2021 configSideDetectors() hook and
  our fault-alert callbacks.

NodePrefs gains upstream's extra_sf[4], which is not registered with
ConfigSerializer, so /prefs.json layout is unchanged.

Verified: 275/275 native host tests pass; Heltec_v3_repeater_observer_mqtt,
Heltec_v3_repeater and heltec_v4_repeater_observer_mqtt all build.
2026-08-09 06:32:53 -07:00