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.
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.
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)
Add durable TempRadio, ACL, clock, bridge, GPS/I2C, USB logging, and Indicator recovery safeguards together with expanded automation, build/profile contracts, release metadata, and the hardware validation ledger.
Add selectable shared ESP-NOW channels, raw/wrapped bridge compatibility, and bounded fragmentation for maximum-size packets.\n\nGive ESP32 Full profiles WiFi coexistence, stabilize WebConfig and USB terminal lifecycles, and keep disabled bridges stopped during observer updates.\n\nUpdate generated UI, firmware selection, documentation, and regression coverage.
The manifest said "rebuild these for every espressif32 platform bump" and nothing
enforced it. mbedtls_4k.py verified the staged archives against the manifest's own
hashes, which proves the pair agrees with itself and nothing more: bump the
platform without rebuilding and every check still passes while the link takes
mbedTLS built against a different IDF. That fails at runtime on struct-layout
drift, not at the link, which is the failure the mechanism claimed to prevent.
Fingerprint the framework's own mbedTLS archives — the ones ours displace — as
stock: lines in the manifest and check them before the build. If the framework
moves, the staged pair is stale by construction and the build stops with the
replacement hashes printed ready to paste. Stronger than comparing a version
string: framework-arduinoespressif32 versions independently of the platform, and
its archives are what actually has to match.
The lib directory is resolved by trying the layouts espressif32 has used rather
than hardcoding one, and failing closed if none holds all four archives. The fetch
script ignores the new lines; its known-arches hint skips them so they cannot be
reported as architectures.
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)