mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-06 14:24:20 -05:00
ExternalNotifications
1598
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8ebb6988b5 |
fix(gps): match Airoha idle_int sleep to the documented sequence (#12167)
* fix(gps): match Airoha idle_int sleep to the documented sequence For boards that wake the AG3352 through idle_int (ThinkNode M8 and M9): - Drop $PAIR382,1 before the sleep command. It locks the CPU out of sleep, which works against both $PAIR003 and $PAIR650. - Hold idle_int low 50 ms before sending, and wait 50 ms after the command takes before EN cuts the core, as Elecrow's reference does. - A successful $PAIR650 need not reply, so also accept NMEA going quiet for a full cycle as success; resend once otherwise. Boards with RTC_INT are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ThinkNode M8: reset the GPS out of backup mode at boot If the MCU resets while the AG3352 is in backup mode, the GPS stays there and the cached-probe check gets no answer, forcing a full re-probe. Pulse RESETB in earlyInitVariant(), as the M5 does with its wake pulse in #12113. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9c119da2e3 |
AG3352 GPS update for ThinkNode M5 (#12113)
* initial commit for L76K/AG3352 dual GNSS detection * GPIO fixes according firmware demo * resolve rabbits * define PIN_GPS_SWITCH * fix wake button and power pin * clear response to avoid logging junk data * increase IO07 samples to more reliably detect version * ThinkNode M5: read the v1.0 GPS switch on GPIO10 On a v1.0 (L76K) board the GPS switch is sensed on GPIO10, not GPIO18. Watching GPIO18 there meant flipping the switch did nothing. Pick the pin by revision, as the GPS UART pins already are; m5HasAg3352 is set in earlyInitVariant() before createGps() configures and starts watching it. Tested on a v1.0 board: switch off and on now turns the GPS off and on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ThinkNode M5: trunk fmt variant.h Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b33c337a89 |
feat(variants): add RAK WisMesh Dongle (USB Type-C) (#12009)
* feat(variants): add RAK WisMesh Dongle. USB-powered headless adapter on the RAK4631 module. Hardware model stays RAK4631 until protobufs add WISMESH_DONGLE. The boot VDD gate is disabled because phone VBUS makes the SAADC sample look unsafe. * add board_level * fix(rak_wismesh_dongle): set VDD gate to 1 mV so cppcheck passes --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> |
||
|
|
c4b08e82a8 |
CI: build no firmware in the merge queue; trim repeated CI work (#12162)
* CI: run pio check on PRs only, not in the merge queue The merge queue re-ran cppcheck on the same --level pr boards the PR run had already checked. Restrict the check job to pull_request; ci-gate already treats a skipped check as passing, so merge_group runs gate on the builds alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L * CI: cancel superseded push runs on the same branch Push runs fell back to github.run_id for their concurrency group, so every merge to develop ran the full 156-board matrix to completion even when a newer merge had already landed. Group pushes by github.ref so the newer push cancels the older run. PRs keep grouping by head_ref, and merge_group, schedule and workflow_dispatch runs keep a group of their own, so the queue, the nightly and releases are never cancelled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L * CI: move axiometa-genesis-mini out of the PR board set Set its board_level to release. PRs and the merge queue now build 17 boards instead of 18; the board is still built in the full matrix on push, nightly and release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L * CI: reuse the merge queue's PR-level builds in the post-merge run A post-merge push to master/develop lands the exact commit its merge_group run already built, so the PR-level boards were compiled twice. When the push's merge_group run is found and its ci-gate passed, setup marks those boards 'reuse' and build_firmware.yml copies their firmware and manifest artifacts from that run instead of building. The artifacts keep their names, so the size report baseline, the RAM/flash budget gate and MemBrowse are unchanged. If no such run is found (event/* and feature/* pushes, a push that bypassed the queue, an API failure), every board builds as before. ci-gate is read through the check-runs API: the run's jobs endpoint returns 502 for a run this size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L * CI: skip macOS, Windows and debian-src on reused post-merge pushes When setup finds the merge_group run that already built this commit (reuse_run_id), these jobs ran on that exact commit there and nothing on push consumes their output: the debian-src artifact is only read by release-artifacts on workflow_dispatch. test-native, WASM and Docker still run, because only pushes to develop save their caches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L * CI: only skip OS builds on push when the merge queue run passed them ci-gate needs only build and check, so a merge_group run whose macOS, Windows or debian-src job failed still merges, and skipping those jobs on the push hid the failure. setup now sets skip_os_builds only when the latest attempt of every MacOS, Windows and build-debian-src check run in that merge_group run concluded success and all three are present; the jobs skip on that flag instead of reuse_run_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L * CI: harden the post-merge reuse lookup (review fixes) - Find the merge_group run at any status. The queue merges once ci-gate passes, often while jobs outside it still run, so requiring a completed run usually found nothing and rebuilt every board. - Mark a board 'reuse' only if its firmware and manifest artifacts are still present and unexpired in that run; any other board compiles. setup now needs version to build the exact artifact names. - Never skip MacOS, Windows or build-debian-src on the default branch: those jobs save their caches only on its pushes. The skip stays for master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L * CI: drop the macOS/Windows/debian-src skip on post-merge pushes Those jobs save their caches only on default-branch pushes, so the skip was already limited to master, where it saved too little to justify the check-run lookup. They now run on every push exactly as before this PR. Reuse of the PR-level firmware builds is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L * CI: look up the merge_group run by workflow file, not display name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L * CI: build no firmware in the merge queue; drop post-merge artifact reuse Per review: the merge queue now only batches PRs into develop/master. setup is skipped on merge_group, which skips the build and check matrices that depend on it, and ci-gate accepts a skipped build on merge_group only; a PR must still build. Post-merge pushes build the full matrix as before. With no firmware built in the queue there is nothing to reuse, so the post-merge artifact copy (reuse-build, the merge_group run lookup and setup's dependency on version) is removed and build_firmware.yml is back to develop's version. The merge_group branches of setup's matrix generation are removed with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DdE8uBENCwUMApqYdut2L --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
200aa4270e |
Stop treating BATTERY_PIN -1 as a battery ADC (#12091)
* Stop treating BATTERY_PIN -1 as a battery ADC Six variants defined BATTERY_PIN -1 to mean "no battery sense", but every guard is `#ifdef BATTERY_PIN`, so the analog battery read compiled in anyway. On the Seeed Xiao S3 it samples an unwired GPIO1 and reports a battery that is not there: 0% at 3112 mV on one board, charging at 6310 mV on another. On nRF52, analogRead(-1) indexes past the pin map. Leave BATTERY_PIN undefined on those boards, which takes the existing external-power path, and fail the build when a variant defines a negative pin. * Keep the INA battery path on boards without a battery ADC pin AnalogBatteryLevel is the only reader of a battery INA configured through power.device_battery_ina_address, and analogInit() only installed it when BATTERY_PIN was defined. Dropping the -1 placeholder from six variants therefore also dropped their INA path. Install AnalogBatteryLevel on any board without a pin when the environmental sensors are compiled in, and let the PowerFSM treat such a board as externally powered only while no battery source has been found. |
||
|
|
38ae9aab86 |
Renovate: Fix lookup errors / tag formatting (#12176)
Also unpins actions/checkout... no need for this on official Actions (needless renovate PR churn). |
||
|
|
a2a27f4f99 |
Track meshtastic/Crypto with git-refs on rp2040/rp2350 and bump to 9ceb213 (#12157)
The other platforms moved their Crypto comment to datasource=git-refs in #11065 and Renovate has kept them current since (#11879, 1c817c2). rp2040 and rp2350 still say datasource=github-tags, and meshtastic/Crypto has no tags, so Renovate never updates them: both are still on 591ff9a. Switch the comment to the same git-refs form and bump the pin to the current main (9ceb213), which brings in meshtastic/Crypto #4, #5, #7 and #8. |
||
|
|
7579bfe4d3 |
InkHUD: Feature/sdcard eink maps (#12118)
* InkHUD: load offline map tiles from SD cards InkHUD map backgrounds previously came only from MapTile.h compiled into the firmware image. This commit expands that to read map tiles from an SD Card. * Firmware-compiled tiles remain the first choice, with SD Card secondary. * SD Card MapTiles' header position table and payload descriptors loaded into PSRAM. * The index is cached in PSRAM when allocation succeeds; otherwise streamed index access. * Validates the file magic, layout, counts and index/data bounds before use. * Added an MTB1 reader for /Map/MapTile.bin * Supports sparse and grid indexes together with white, black and raw LZ4 block entries. * Moved the tile format constants and LZ4 decoder out of MapApplet. * Reuses the applet's existing decoded-tile buffer. Compressed tile imagery remains on the card. Rendering resolves coordinates from the cached index, takes spiLock for a bounded open/seek/read/close operation, releases the bus, then decompresses the tile. Memory use therefore stays bounded by the index and one compressed and decoded tile rather than the size of the map file. The T5S3 E-Paper V2 connects its SD card and LoRa radio to the same SPI pins. Drop SDCARD_USE_SPI1 only from the InkHUD environment so both use the default SPI instance and the existing shared lock. Keep SD map enablement and preload setup in the T5S3 variant; the non-InkHUD V2 environment retains its existing SPI1 configuration. Update MapApplet to walk only the visible tile-coordinate range, wrap tile X coordinates across the antimeridian and check embedded tiles before requesting an SD tile. A missing, invalid or temporarily busy SD source simply leaves that background tile undrawn without changing the existing map applets or their compiled-in tile behavior. * PowerFSM: service non-input GPIO wakeups after light sleep Before |
||
|
|
4964a0c6a5 |
feat(raksensorhub): SensorHub uplink for WisMesh Hub (RAK2560) (#11817)
* feat(raksensorhub): add SensorHub uplink driver for WisMesh Hub Add HAS_RAKHUB env rak_wismesh_sensorhub with 1-Wire bus + IPSO cache. Ship standard environment metrics only; soil/water chemistry stays gated until SoilWaterMetrics lands in firmware. * fix(raksensorhub): harden RX length, MODBUS log, and power TTL Reject oversized frames without uint16 wrap, validate MODBUS before head[] access, TTL hub power like other scalars, and set board_level for CI matrix. * fix(raksensorhub): track HubPower freshness per field Use separate timestamps for voltage, current, and capacity so one IPSO cannot keep stale siblings fresh, and percent no longer depends on volMv. * fix(raksensorhub): stamp 0-unset cache times with Time::skipZero A wrap or boot 0 tick must not look like never-updated; freshness reads use Time::stampMillis() too. --------- Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com> |
||
|
|
ed072eb4b5 |
ThinkNode M8: switch the GPS to the AG3352Q (#12151)
* ThinkNode M8: switch the GPS to the AG3352Q Later M8 units replace the ATGM336H with an Airoha AG3352Q. Drop the ATGM336H standby pin and wire up idle_int (P1.07) so the existing GPS_SLEEP_INT path stops the engine with $PAIR003 before GPS_EN cuts the 0.8 V core, as on the ThinkNode M9. Default baud is now 115200. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ThinkNode M8: put the AG3352 into backup mode for sleep $PAIR003 only stops the GNSS engine, so with the 1.8 V rail always on the receiver never reached low power (1.5 mA measured by Elecrow). Add GPS_AIROHA_RESET_WAKE for boards without RTC_INT that have RESETB: sleep sends $PAIR650,0 (with idle_int low) before EN goes low, and wake pulses RESETB once EN is back up. Enable it on the M8. At shutdown, hold idle_int high and RESETB released instead of letting the variant's pin sweep float them through System OFF. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a2984a5d28 |
ThinkNode M8: make button E the cancel button (#12150)
Button E was PIN_BUTTON1 with a hand-rolled FunctionButton map in InputBroker (single press: ping, double: privacy toggle, 5 s hold: shutdown). The rotary encoder already drives the UI, so define the pin as CANCEL_BUTTON_PIN instead and drop the M8 special case: a press now cancels and a 4 s hold shuts down, through the generic cancel button. variant_shutdown() follows the rename so the wake-on-press pin is still parked and sensed. Claude-Session: https://claude.ai/code/session_01B7bFRZ9bBMRr7KPBF8rmzu Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
afdaccc25f |
Makerfabs nomad terminal (#12112)
* Add Makerfabs Nomad Terminal variant * Configure external power detect pins before probing battery gauges * TFTDisplay: add ILI9488 reset pin and colour inversion options * Makerfabs Nomad Terminal: add BaseUI env, early power latch, soft-start LCD/GPS rail, GPS wake * I2C scan: tell FocalTech touch from AHT10 at 0x38 and BMM150 from VEML7700 at 0x10 * Run a BMM150 next to an IMU on the magnetometer thread * Makerfabs Nomad Terminal: BaseUI touch * BMM150: add hard/soft-iron calibration, read at 10 Hz * Makerfabs Nomad Terminal: fix MUI build, restart Wire after MUI touch init * BMM150: seed calibration from the first valid sample * Makerfabs Nomad Terminal: combined BaseUI+MUI -tft firmware, SD defines in build flags * Makerfabs Nomad Terminal: reset touch controller at init, wake touch thread on INT * BMM150: poll every 200 ms * Makerfabs Nomad Terminal: scale CW2015 SOC to the on-USB full reading * BMM150: end an expired calibration when the sample is rejected |
||
|
|
f7f66b4731 |
Initial Elecrow ThinkNode M5 InkHUD port (#12097)
Adds an InkHUD driver (nicheGraphics.h) and build target (platformio.ini) for InkHUD on the M5 |
||
|
|
04cb888511 |
Wio Tracker L2 fixes (#12133)
* fix WakeKey not responding when pressed during boot * fix I2C error address 0x48 during scan * enable GT911 gesture recognition in MUI * shutdown_variant() * disable io expander INT during light sleep * fix BaseUI wakeup * prevent bootloop in case DIO1 fires * fix ADS1X15 scan type * fix power down sequence * external keyboard support * fix I2C port 1 define; skip false positive |
||
|
|
b48e1e0a2f |
Update RAKwireless RAK12034 library git ref (#12147)
These three variants were referencing the older (broken) library, where other RAKs had already updated to this fixed version. This PR aligns everything on the fixed version. |
||
|
|
2e69aecf19 |
nRF52: Update gcc to v12.3.1 (same as nRF54) (#12027)
Align the two nRF platforms on v12.3.1 Switch to forked RAK12035_SoilMoisture library for now |
||
|
|
1e3a38fc8a |
Add native Portduino mDNS discovery with Avahi and Bonjour (#12032)
* Add native Portduino mDNS discovery via Avahi and Bonjour * Guard native discovery in Portduino WASM builds * Add linux dependencies in packaging, support "buildroot" Adds libavahi dependencies for Debian, RPM, Docker and CI. Moves the libavahi definition to the shared `build_flags` (which is only consumed on Linux targets) * Fix platformio formatting * fix(portduino): disable Avahi when daemon unavailable --------- Co-authored-by: Jake-B <jake-b@users.noreply.github.com> Co-authored-by: vidplace7 <vidplace7@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9015b2213f |
Update portduino to gnu++20 (#12069)
Upstream dependencies in the newest distros are beginning to require c++20. This PR bumps all portduino-based targets to c++20, removes the shared c++17 build_flag, and adds it to each variant family where it is needed. |
||
|
|
bd951500b7 |
Select InkHUD font code page with the OLED_* language flags (#11869)
OLED_GR selects Win1253, OLED_RU/OLED_UA Win1251, OLED_PL/OLED_CS Win1250, no flag Win1252. heltec_mesh_pocket returns to the Win1252 default. t5s3_epaper stays on Win1253 (18pt/24pt exist only there). Co-authored-by: Jason P <applewiz@mac.com> |
||
|
|
e09cec6ada |
Update pioarduino to v3.3.12-1 (#12052)
* Update pioarduino to v3.3.12-1 Updates pioarduino platform and arduino-esp32 to v3.3.12, a minor bump from current v3.3.11 https://github.com/espressif/arduino-esp32/releases/tag/3.3.12 Meshtastic's fixes have been integrated upstream, so move off of our fork and back to official releases. * update indicator to 3.3.12 --------- Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com> |
||
|
|
bce6f4fb4d |
nRF52840: run X25519 and Ed25519 verify on the CC310 (#12098)
CryptoEngine gains two virtual hooks, x25519 (Curve25519::eval semantics) and ed25519Verify, which setDHPublicKey, regeneratePublicKey and xeddsa_verify now call. setDHPublicKey keeps dh2's weak-point checks around the hook. Other platforms are unchanged. NRF52CryptoEngine overrides both with the CryptoCell CC310 through nRFCrypto_X25519 / nRFCrypto_Ed25519. Only clamped scalars go to the hardware, since the CC310 clamps and eval does not; non-canonical peer keys return false as eval does. A verify result is final only when the CC310 accepts or rejects; any other CC310 error falls back to software. nRFCrypto is bumped to Kongduino/Adafruit_nRFCrypto@6d4d369, whose wrappers survive the firmware's per-call nRFCrypto.begin()/end() and follow RFC 7748. On a RAK4631: X25519 11.7 ms vs 62.3 ms, Ed25519 verify 17.9 ms vs 173.6 ms. Co-authored-by: Kongduino <Kongduino@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
07008058c0 |
nrf52 promicro: LR2021 LF PA table from the LR20xx datasheet (#11980)
* nrf52 promicro: LR2021 LF PA table from the LR20xx datasheet RadioLib's default LR2021 LF PA table never programs tx_power above 20.5 dBm, so output stops rising above 14 dBm. This board table takes 10-22 dBm from LR20xx DS rev 2.2 Table 7-19 and keeps RadioLib's rows below 10 dBm, where that table stops. * LR2021: install the board LF PA table only at 500-1000 MHz The datasheet table is the 915 MHz reference design, but applyCustomLfPaTable() installed it for every sub-GHz frequency, so 433 and 490 MHz regions used it too. Outside 500-1000 MHz the LF table now goes back to RadioLib's default. RadioLib keeps the table pointer, so the same-band reconfigure path, which retunes without a full begin(), also reselects the table before setOutputPower(): a 915 -> 433 MHz region change would otherwise have kept the 915 MHz table. isLr20x0CustomLfPaBand() carries the range and is covered in test_radio. * fix(lr2021): restore receive metadata after develop merge Restore the virtual method lost in the merge; CI native and LR2021 links otherwise fail. --------- Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com> |
||
|
|
5ced5ee4f7 |
Nrf promicro hf (#11966)
* define txen pin to allow sx128x testing * nrf52 promicro: gate the P1.06 TXEN pin, and add an optional SX1280 build P1.06 was wired to SX126X_TXEN unconditionally earlier in this series, to drive an external PA on a bench unit. That is not safe to ship: three envs share this variant.h, and P1.06 is PIN_WIRE_SCL on the EasyProMicro pinout and PIN_EINK_BUSY on the InkHUD one. The default builds go back to RADIOLIB_NC. Put it behind -D PROMICRO_SX126X_TXEN, refused wherever the pin is already claimed. The EasyProMicro arrangement has its own SX126X_TXEN on P1.00 in the #else branch and never reaches this, so only the e-ink exclusion is needed. The pin was wanted for SX1280 in the first place - every other SX128X line is already assigned on this board and TXEN was the one missing - so add that too, behind -D PROMICRO_SX1280 and a new nrf52_promicro_diy-sx1280 env. Both opt-ins claim P1.06, so asking for both is an #error rather than a silent pick. Costs about 10.7 KB: the image ends at 0xE5140, 19 KB clear of the warm region, against 0xE2710 and 30 KB for the base env. * nrf52 promicro: keep P1.06 off TXEN on a MESHTASTIC_USE_EINK_UI build configuration.h includes variant.h before it derives USE_EINK from MESHTASTIC_USE_EINK_UI, so a build enabling the e-ink UI that way passed the guard and could give the panel's BUSY line to TXEN. Test both. --------- Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz> |
||
|
|
0fc168d875 |
Improved LBT behaviour - CAD-to-RX, correct symbol times, better thresholds (#11137)
* LoRa LBT: fix SX128x/SX126x CAD encoding + slot model, ESP32 sleep race Three fixes from the "are we really listening?" LBT audit: - SX128x CAD: pass RADIOLIB_SX128X_CAD_ON_4_SYMB instead of the raw count 4. The raw value set bits 7:5 = 000, i.e. a 1-symbol scan, leaving LBT nearly blind on 2.4 GHz. Now runs the intended 4-symbol CAD. - SX126x CAD / slot time: name the symNum value explicitly as CAD_ON_4_SYMB (identical 0x02 raw value, so no on-air change) and add a virtual getCadSymbolCount() so computeSlotTimeMsec() models the real ~4.5-symbol scan for the SX126x/LLCC68/STM32WL family only, leaving LR11x0/SX127x at their correct 2.5 symbols. Fixes the too-short contention-window slots. - ESP32 light sleep: if the DIO1/RF95_IRQ line is already asserted just before esp_light_sleep_start(), skip the sleep so the main loop services the pending packet instead of stranding it for up to a full sleep chunk (the detachInterrupt-clears-the-GPIO-wake disarm race). clod helped too * LoRa LBT: keep software-poll preamble flags latched while idle (anti-stomp) Finding #10 from the "are we really listening?" audit, LORA_DIO1_SOFTWARE_POLL boards (meshnology-w10). The 1 ms poll cleared latched PREAMBLE_DETECTED|HEADER_VALID whenever they were the only bits set, so for the rest of a reception isActivelyReceiving() read idle. A TX queued during the payload phase then passed the send gate and startTransmit overwrote the shared chip buffer, losing the inbound packet. That clear only exists to stop a noise preamble from permanently blocking our own TX (starvation). Starvation can only happen when we actually have something to send, so gate the clear on a non-empty txQueue. While the queue is empty the bits stay latched, isActivelyReceiving() reports the in-progress packet, and a later TX defers instead of stomping it. A queued TX still gets the escape hatch. The poll cadence is a fixed 1 ms reschedule either way, so latched bits don't spin the loop; readData() clears everything once a real RX_DONE arrives. clod helped * LoRa LBT: optional RSSI energy second-gate before TX (default off) Findings #4/#5 of the "are we really listening?" audit. CAD only detects a preamble. A neighbour's packet whose preamble already aired - during our own TX, a CAD scan, or a standby gap - is mid-payload and invisible to CAD, so we key up on top of it. Add an RSSI energy check as a second listen-before- talk gate in the TX-decision path: channelBusyByRSSI() compares the instantaneous in-RX RSSI against the rolling noise floor (getNoiseFloor() + margin) and defers if the channel is hot. It reads RSSI before isChannelActive()'s setStandby() via the || short-circuit, so the sample is taken while still in RX where GET_RSSI_INST is valid - no mode juggling. Orthogonal to CAD: CAD has processing gain and catches weak preambles below the noise floor; RSSI catches strong energy at any packet phase, including the mid- payload case CAD is blind to. Neither subsumes the other. DEFAULT OFF. A mis-set threshold makes a node read "busy" on noise and stop transmitting entirely, which is worse than the bug, so it must be tuned on hardware before it ships live. Compiled out unless LORA_RSSI_LBT_MARGIN_DB is defined at build time; when off it is a compile-time `return false` (zero behaviour change). Fails open: returns not-busy unless in RX with a full noise-floor window and a sane dBm reading, so it can never persistently silence a node. Native build (gate off) links clean. clod helped * LoRa LBT: RSSI margin profiler to tune RSSI_LBT_MARGIN_DB (default off) Instrumentation to pick the #4/#5 RSSI second-gate margin from measured data instead of guessing. Build with -DLORA_RSSI_LBT_PROFILE; compiled out otherwise. It accumulates (RSSI - rolling noise floor) in two channel states: - idle (reuses updateNoiseFloor()'s existing RX sample, no extra SPI): the noise- jitter distribution. Its high tail is the floor the margin must clear. - signal (decoded-packet RSSI, hooked in handleReceiveInterrupt): the real-traffic rise. Its low tail is the ceiling the margin must stay under. A throttled log dump prints both distributions (min/mean/max + percentiles) and the derived usable window (idle p99, signal p05). Placement: the stats/histogram engine is a dependency-free, radio-agnostic header (RssiProfiler.h) so it stays out of the core file and is testable on its own; the two hooks and the RSSI/log I/O live on RadioLibInterface where the state is. Independent of the gate flag (LORA_RSSI_LBT_MARGIN_DB) on purpose: profile first to choose the margin, then build the gate on. Native build (profiler on) compiles clean. clod helped * docs: describe the RSSI LBT margin profiler Feature doc for the RSSI listen-before-talk margin profiler (-DLORA_RSSI_LBT_PROFILE): what it measures (idle noise-jitter vs decoded-signal distributions of RSSI-minus-noise-floor), how it's wired (RssiProfiler.h engine + RadioLibInterface hooks, idle sample reused from updateNoiseFloor), how to build and run it, and how to read the log output to choose RSSI_LBT_MARGIN_DB. clod helped * LoRa LBT: draft #3/#6 fixes behind MESHTASTIC_RADIOLIB_HAS_RESUME_RECEIVE (inactive) Firmware side of the CAD->RX handoff (#3) and ISR_RX re-arm (#6) from the LBT audit. Both need to re-arm RX WITHOUT the standby() that startReceive() forces (which aborts an in-flight reception) - unavailable in stock RadioLib, provided by resumeReceive() in NomDeTom/RadioLib@rxResume. Guarded by MESHTASTIC_RADIOLIB_HAS_RESUME_RECEIVE, left UNDEFINED: against stock RadioLib these paths compile out and behaviour is unchanged. Define it (via build_flags) only when linking a RadioLib that has resumeReceive(). - resumeReceiveInPlace(bool startRx): base-class helper - iface->resumeReceive() (remap RX IRQ, optional SetRx, no standby) + re-attach the MCU ISR + base RX bookkeeping. One place, chip-agnostic via iface. - #3: SX126x/LR11x0 isChannelActive() CAD exitMode -> GOTO_RX; the TX-decision path splits the busy check into rssi/cad flags and, on a CAD detect, hands the packet to RX via resumeReceiveInPlace(false) instead of startReceive(). Macro-off, the split is exactly the old `channelBusyByRSSI() || isChannelActive()` short-circuit. - #6: ISR_RX re-arms via resumeReceiveInPlace(true). Open (flagged in-code, for review + HW validation): whether re-issuing SetRx (#6) disturbs an on-chip in-flight reception, and the #3 handoff's interaction with a beacon-target packet on the home config. clod helped * LoRa LBT #4: synthesize GPIO wake cause when light sleep is skipped doLightSleep() skips esp_light_sleep_start() when a LoRa IRQ is already asserted, but still read esp_sleep_get_wakeup_cause() unconditionally - so the skip path returned the previous real sleep's cause (e.g. TIMER) and missed the ESP_SLEEP_WAKEUP_GPIO branch that sets runASAP. Capture whether we skipped and return a synthetic GPIO cause in that case so PowerFSM takes the packet-service path. Reads the ESP-IDF cause only after an actual sleep. clod helped too * LoRa LBT #10: keep HEADER_VALID latched in the software IRQ poll The LORA_DIO1_SOFTWARE_POLL anti-starvation escape hatch cleared both PREAMBLE_DETECTED and HEADER_VALID whenever a TX was queued. HEADER_VALID is evidence of a real inbound frame, so clearing it made isActivelyReceiving() read idle mid-payload and let the queued TX stomp the frame. Gate the hatch on a bare-preamble IRQ (PREAMBLE set, HEADER_VALID clear) and clear only PREAMBLE_DETECTED. A stuck header still can't block TX forever - receiveDetected() ages it out after maxPacketTimeMsec - and readData() clears everything on RX_DONE. clod helped too * LoRa LBT #3/#6: CAD->RX handoff + continuous-RX re-arm on stock RadioLib Reimplements the two audit fixes that previously needed the NomDeTom/RadioLib @rxResume fork (MESHTASTIC_RADIOLIB_HAS_RESUME_RECEIVE), using only stock RadioLib, behind a new default-off flag MESHTASTIC_LBT_CAD_TO_RX. Per the RadioLib maintainer's feedback, no new RX API is needed. (GOTO_RX) with the RX IRQs pre-mapped in the scan config, so the chip's own CAD->RX transition delivers RX_DONE with no library call in between. irqFlags (status-enable) carries the CAD verdict + full RX set incl. PREAMBLE/HEADER so isActivelyReceiving() can read them; irqMask (DIO trigger) carries the CAD verdict + terminal RX events only - NOT PREAMBLE/HEADER, which would fire the ISR mid-frame and run readData() on an incomplete packet. On detection it hands off in place: clear the latched CAD verdict, re-attach the MCU ISR, mark receiving - no standby (which would abort the packet just detected). standby), so re-attach the MCU ISR instead of re-issuing SetRx (which could disturb a second packet already arriving on-chip). Removes resumeReceiveInPlace() and the fork macro. SX126x-only: LR11x0's startChannelScan conflates irqFlags/irqMask (drives the DIO from irqFlags), so it keeps the safe GOTO_STDBY path, re-arming on detect to stay coherent with onNotify when the flag is on. Default-off: needs on-hardware validation and assumes continuous RX (the default preamble=16/minSymbols=8 degenerates startReceiveDutyCycleAuto to continuous). Native builds pass both flag-off and flag-on. clod helped too * LoRa LBT #1: re-arm RX unconditionally when deferring a busy channel The post-CAD/RSSI defer path wrapped its startReceive() re-arm in a !hasTargetRadioSettings(txp) guard, so a beacon-target packet that hit a busy channel took setTransmitDelay() with the radio left in standby. When the beacon's target config coincides with the home config the radio is on the home channel, so the node went deaf on home traffic until the next TX cycle - and kept skipping the re-arm every cycle the channel stayed busy. Drop the guard: re-arm unconditionally and keep listening while we wait to key up. A beacon's job is to get a packet out, but it should still mirror the main RX path. (No effect under MESHTASTIC_LBT_CAD_TO_RX, where the chip is already left in RX.) clod helped too * docs: fix RSSI profiler fences (MD040) + RAM figure, note negative-bin limit - Tag the formula and sample-output fenced blocks as `text` so markdownlint MD040 passes. - Correct the RAM figure: RssiDeltaStats is 148 bytes (12 scalar + 34*4 histogram), so two instances are ~296 bytes, not ~280. - Add a limitations note that all negative deltas collapse into bin[0], so a percentile landing there returns the minimum sample. This does not affect the margin (keyed off the always-positive idle p99) but can understate signal p05 when a node decodes below-floor packets; never biases toward a smaller margin. clod helped too * docs: RSSI profiler - record histogram bucketing design choices Add a "Histogram bucketing" section laying out why a fixed histogram is used over exact percentiles, what the axis has to resolve (the margin window sits at roughly -a few..+20 dB), and the arguments for five bucketing schemes: the current positive-only layout (A), a cost-neutral re-centred layout with negative-range resolution (B, recommended if signal p05 must be trusted), wider full-range bins (C), more bins (D), and exact/off-box (E). Explains the current choice and when to switch. clod helped too * LoRa LBT: move RSSI profiler + energy second-gate to listening-now-rssi Split the RSSI work off this branch so the active CAD/sleep fixes and the #3/#6 CAD->RX rework can land without the tuning-dependent RSSI pieces. Removed here (preserved intact on branch listening-now-rssi): - RSSI margin profiler: RssiProfiler.h, the LORA_RSSI_LBT_PROFILE hooks, and docs/rssi_lbt_margin_profiler.md. - RSSI energy second-gate: channelBusyByRSSI() and the LORA_RSSI_LBT_MARGIN_DB gate. The pre-existing noise-floor sampler (updateNoiseFloor/getNoiseFloor, already on develop and consumed by telemetry) is untouched. The TX-decision block reverts to a plain CAD scan; #3/#6 (behind MESHTASTIC_LBT_CAD_TO_RX) and #1 unchanged. Native build green. clod helped too * reduce to bare essentials * LoRa LBT: calibrate CAD symbol count and detPeak against vendor data NUM_SYM_CAD carried two incompatible meanings: computeSlotTimeMsec() read it as a literal symbol count, while the drivers passed it into scanChannel(), where symNum is a register encoding on SX126x/SX128x. On SX126x the raw 2 encodes CAD_ON_4_SYMB, so the chip has scanned 4 symbols since #5749/#5772 while the slot time was priced at 2.5 - a 1.8x mis-scaling of the CSMA backoff unit, live since January 2025. Give the constant one meaning (a literal count of 4) and have each driver translate at its own call site. getCadSymbolCount() reports the real scan length so slotTimeMsec matches it; RF95 overrides it because SX127x CAD is fixed in hardware and its legacy scanChannel() takes no symNum. AN1200.48 Table 1 recommends 4 symbols for SF9-SF12, which is where most shipped presets sit, so 4 is the right target. The comment claiming 2 was reading that note's SF7/SF8 row. detPeak is calibrated per (SF, symbol count), and on LR11x0 per bandwidth as well. Both LR drivers ran thresholds meant for a 2-symbol scan, leaving them less sensitive than measured-best and biased toward missed detections. Now sourced explicitly: LR20x0 LR20xx datasheet rev 2.2 Table 6-19, indexed by symbols x SF LR11x0 Semtech SWSD003 lr11xx/apps/cad/main_cad.c optimized_parameters, indexed by symbols x bandwidth x SF SX126x RadioLib's SF+13, which sits at or below every AN1200.48 value and so errs toward deferring rather than transmitting Combinations the vendor data does not characterise fall back to the RadioLib default rather than guessing. LR2021 also pins fastCad, which scanChannel() reads off the radio object rather than the config struct. Also fix the 2.4 GHz slot term: (2 * sf + 3) / 32 is integer division that truncates to 0 for every legal SF, so the AN1200.22 fractional term never contributed anything. Not hardware-verified. * LoRa LBT: drop audit-doc pointers and cross-chip asides from comments The LBT comments referenced an external audit document by item number (#1, #3, #3/#6, #10, and a see-.notes/lbt pointer), which means nothing to anyone reading the tree on its own. They also described what sibling drivers do, which goes stale silently the moment one of them changes and belongs in that driver's own file anyway. Keep the reasoning, drop the pointer and the comparison. Where a comment explained a rule that genuinely spans drivers - NUM_SYM_CAD's register- encoding caveat - state it as a rule rather than a roll-call of chips. No behaviour change; comments only. * LoRa LBT: don't index the LR11x0 CAD table with a 2.4 GHz bandwidth The detPeak lookup picked its bandwidth row by range, so wideLora's 406.25 kHz fell into the sub-GHz 500 kHz row - SWSD003's table is the sub-GHz LoRa modem (BW enums 62.5-500), and 2.4 GHz is a separate modem with its own calibration. Match the four bandwidths exactly instead, so anything unmatched falls back to the RadioLib default rather than borrowing a row it has no claim to. Those four are the whole sub-GHz set today - LR11x0::setBandwidth() rejects anything else - so note that a newly unlocked bandwidth, or a symbol count outside 2/4/8/16, needs a row adding here and fails silently if it doesn't get one. Same note on LR20x0 for symbol counts above 4. Also correct two citations. The 2.4 GHz slot term is the SX1280 datasheet's own CAD duration formula, and the CAD symbol-count guidance is AN1200.77, not AN1200.22 - which is "LoRa Modulation Basics" from 2015 and predates the part. Record that SX1280 ignores detPeak/detMin/exitMode entirely: SetCadParams carries only cadSymbolNum, and the PNR threshold lives in register 0x942. * LoRa LBT: set the SX1280 CAD PNR threshold and scan 8 symbols SetCadParams on this part carries only cadSymbolNum - detPeak/detMin in the scan config are inert - so CAD sensitivity is governed entirely by register 0x942, which nothing was writing. The scan therefore ran at the chip's reset value of 0x32, against the 19-36 that AN1200.77 Table 1 calibrates per SF and bandwidth: at LONG_FAST wideLora the recommendation is 27 and we were using 50. A threshold that high needs a much stronger correlation peak, so 2.4 GHz LBT was under-detecting on top of the symbol-count bug already fixed. Write the table value before each scan, indexed by bandwidth x SF. Unmatched bandwidths write nothing and leave the chip's default, as on the LR parts. This goes through our own Module rather than lora.writeRegister(), which RadioLib keeps protected unless RADIOLIB_LOW_LEVEL unlocks raw register access across every driver. SX128x::writeRegister() is a straight SPIwriteRegisterBurst on the same Module we already own, and begin() has pointed spiConfig at the 0x18 WriteRegister opcode long before any scan runs, so the transaction is identical - no subclass needed. Also raise the CAD window from 4 to 8 symbols, matching RadioLib's own SX128x default. AN1200.77 measures a longer window as moving P(Detection) further from P(False Detection); its thresholds are stated to hold for any window duration, so the table above is unaffected. NUM_SYM_CAD_24GHZ moves in step so the slot time still reflects the real scan. Not hardware-verified. * LoRa LBT: hand CAD straight to RX on the LR parts CAD exited to standby, and the busy-channel path then issued a full startReceive() - which standbys the radio and re-enters RX from scratch, throwing away the reception CAD had just detected. Both LR families expose a CAD-to-RX exit mode that avoids the round trip, and it reaches the chip through the public startChannelScan(cfg), so nothing here needs a library patch. A note in an earlier commit claimed this was impossible because RadioLib collapses irqFlags and irqMask into one set. That reasoning is SX126x's, where irqMask is a status gate and an event omitted from it never latches at all. The LR parts have no such gate: SetDioIrqParams takes a mask per interrupt pin and SetDioIrqConfig maps IRQs to one DIO, while getIrqStatus() reads the status bytes returned on every SPI transaction and consults no pin mapping. So the CAD IRQ set now carries the verdict plus terminal RX events only - preamble and header stay off the pin, where they would fire the ISR mid-frame and run readData() on a partial packet - and isActivelyReceiving() still sees them. rearmReceive() cannot be unconditionally standby-free the way SX126x's is. cad_timeout 0 means "RX until one packet arrives, then standby", so the two re-arm paths leave the chip in different states: already listening after the handoff, back in standby after that packet's RX_DONE. It branches on a flag set at detection and cleared on use, so the first re-arm skips the standby and the next restores continuous RX. Forcing continuous RX through the timeout instead is not reliable - RadioLib converts it to RTC ticks through a float divide, so the 0xFFFFFF "no timeout" encoding is not addressable. On a CAD false positive the chip now waits in single-shot RX rather than returning to standby. Still listening, but a state the old code never entered. Not hardware-verified. * LoRa LBT: size the 2.4 GHz CAD slot per chip, not per band computeSlotTimeMsec() branches on myRegion->wideLora, which is a region rather than a part. LR1120 and LR2021 both run there, and both scan NUM_SYM_CAD symbols - but that branch read the flat NUM_SYM_CAD_24GHZ, so raising it to 8 for SX128x moved the slot for two families whose scan never changed. At LONG_FAST wideLora that priced ~22 ms of CAD against a ~10 ms scan. getCadSymbolCount() exists precisely so the slot tracks the real scan, but only the sub-GHz branch consulted it. Use it in both, and give SX128x an override returning NUM_SYM_CAD_24GHZ. That constant stops being a fleet-wide 2.4 GHz value and becomes what one driver reports; SX128x is unchanged at 8.78 symbols, while the LR parts on 2.4 GHz drop from a wrong 8.78 to 4.78. The error was conservative - slots too long, so throughput lost rather than collisions caused - but it is the same scan/slot mismatch the rest of this work removes. * LoRa LBT: declare CAD symbols per chip and per band, 8 on 2.4 GHz A 2.4 GHz region is shared: an LR1120 and an SX1280 can sit on the same mesh. Leaving the LR parts at 4 symbols there while SX1280 scanned 8 gave them different slot times, and so a different CSMA backoff scale between neighbours - the cross-family asymmetry this branch removed for sub-GHz, reintroduced one band up. Both LR families now scan 8 on 2.4 GHz, matching SX1280. Rather than switch on the band inside a shared constant, each driver states its own count for each band it works in, and derives the symNum it passes from the same accessor - so the scan and the slot cannot drift apart: SX126x 4 sub-GHz SX128x 8 on 2.4 GHz LR11x0 4 sub-GHz, 8 on 2.4 GHz LR20x0 4 sub-GHz, 8 on 2.4 GHz RF95 2 sub-GHz, its CAD being fixed in hardware and not settable NUM_SYM_CAD and NUM_SYM_CAD_24GHZ become base-class defaults nothing falls through to; they now record the fleet convention rather than any part's behaviour. LR20xx Table 6-19 stops at 4 symbols, so an 8-symbol scan reuses that row - the last measured point, erring high and so slightly less sensitive than a true 8-symbol value. Noted at the table. Also a comment pass over the branch: drop retrospective phrasing that describes what the code used to do, cut trailing justification clauses, and fix comments the per-band change left stale - one still claimed the LR parts scan fewer symbols than SX1280 on 2.4 GHz, which is now the opposite of the truth. * LoRa LBT: sweep up a CAD->RX handoff that delivers nothing A handoff leaves the chip listening by its own exit mode, so nothing in the driver arms the radio and no return value says whether it worked. If the detection was false, or the exit mode was not honoured, the node waits in a state we never verified - and on the LR parts that is single-shot RX, which does not return to continuous listening on its own. Stamp the handoff and let the existing ~1s poll re-arm if a whole max-length airtime passes with no packet, the same bound receiveDetected() uses. Clearing it on a real RX_DONE is obvious; clearing it in startSend() is not, but a TX can intervene before any packet arrives and its completion re-arms RX anyway, so leaving the marker set would have the poll re-arm a radio already listening. Reads the clock through Time::getMillis(), the same source Throttle compares against, so the site stays testable across the millis() wrap. 0 is the "none outstanding" sentinel and is tested before the elapsed compare. Also document on rearmReceive() that an override must never call the chip's own startReceive() - its standby+SetRx aborts the packet CAD just detected, which is the entire reason the override exists. * define txen pin to allow sx128x testing * LoRa LBT: bound the CAD->RX handoff in hardware, and re-arm after it delivers cadTimeout only applies to the CAD->RX exit mode. Every driver on develop exited CAD to standby, so the field was ignored and its value - 0, which no datasheet defines - was dead data. Switching the exit mode to RX made it load-bearing while the line itself never changed, so the diff showed nothing. All three handoff parts now bound that RX at one max-length airtime, taken from getPacketTime() so it tracks the preset. A detection that delivers nothing expires to standby and raises TIMEOUT, already mapped to the pin, so the existing ISR re-arms and the queued TX retries on its own backoff. handleReceiveInterrupt() returns early on TIMEOUT without RX_DONE; otherwise each dud detection would reach readData() and be logged as a bad packet. That RX is single-shot, so the arm after it delivers must be a full one. SX126x re-armed unconditionally without a standby, which left the chip in standby while the interface believed it was listening - and nothing swept it, since RX_DONE has already cleared the handoff stamp. A node that received a handoff packet then went deaf until its next transmit. It now carries the same cadHandedToRx flag the LR drivers already had. Sourcing, since the parts disagree and RadioLib's uniform ChannelScanConfig_t hides it: SX126x DS_SX1261-2 p98 (15.625us steps), LR11x0 UM Table 8-8 (31.25us), LR2021 lr20xx_driver cad_timeout_in_pll_step (31.25us, contra the datasheet's "32 MHz periods"). RadioLib scales both LR parts as 30.52us, so the programmed value lands ~2% long - harmless, and left for upstream. Also marks the three SWSD003 cells at 2 symbols/BW500/SF10-12 as that SDK's fallback rather than measurements. * LoRa LBT: own the CAD->RX handoff in one place, and void it on standby The handoff state was split across two classes: cadHandoffRxStart in RadioLibInterface, cadHandedToRx in each of SX126x/LR11x0/LR20x0, whose rearmReceive() overrides were byte-identical. Neither field was cleared by setStandby(), so a standby taken while a handoff was outstanding could strand cadHandedToRx set - and the next rearmReceive() would then re-attach the ISR to a chip sitting in standby, marking the interface as receiving with no RX_DONE ever to come and no recovery path. A stale cadHandoffRxStart was milder but still re-armed the radio over whatever was arriving at the time. Hold both in the base, arm them together in noteCadHandoffToRx(), clear both in setStandby(), and collapse the three overrides into one non-virtual base rearmReceive(). SX128x and RF95 never arm the flag, so they keep taking the plain startReceive() branch. Document on isChannelActive() that a true return must be followed by rearmReceive() - the invariant the whole scheme rests on. Two fixes fall out of the shape: - The handoff re-arm attaches a rising-edge ISR to a line that is not known to be low, unlike startReceive() which clears the IRQ status first. Follow it with checkRxDoneIrqFlag(), as startReceive() already does. - checkCadHandoffTimeout() used one max-length airtime, the same figure the chip's own cadTimeout now uses, so the two expired together. Losing that race left a queued ISR_RX to run against flags the software path had just cleared, falling past the TIMEOUT guard into readData() on an empty buffer. Double it so the hardware always expires first; the backstop then only covers an RX whose timer stopped on a header that never completed. * LoRa LBT: re-arm RX when a queued packet is dropped for tx_enabled=false Pre-existing on develop, not introduced by this branch, but it is the same failure this series exists to remove. TRANSMIT_DELAY_COMPLETED reaches startSend() only through isChannelActive(), which leaves the chip in standby for the transmit about to follow. When that transmit is then dropped because LoRa TX is disabled, the early return frees the packet and returns without arming anything: isReceiving stays false, so pollMissedIrqs() skips the radio too, and the node is deaf until something else re-arms it. Narrow - tx_enabled has to change between enqueue and dequeue, since send() checks it as well - but the fix is one call on a path that already exists. * LoRa LBT: say what the CAD slot time actually does per chip Two comments in RadioInterface.h described intentions the code does not carry out. NUM_SYM_CAD/NUM_SYM_CAD_24GHZ claimed the per-band values keep the CW backoff scale equal across a mesh. They do not: the slot has to match the scan each part really runs, and RF95's CAD is fixed in hardware at 2.25 symbols while SX126x/LR scan 4. A mixed sub-GHz mesh therefore runs two slot lengths, which is correct per part and worth stating rather than implying otherwise. The slotTimeMsec initializer runs during RadioInterface's own construction, so the virtual getCadSymbolCount() it calls always resolves to the fallbacks here, never a driver override. That is harmless only because applyModemConfig() recomputes it before anything reads it - left as-is deliberately, since initializing to 0 would make random(0, 2^CW) * slotTimeMsec fire an immediate transmit if anything ever did read it early. * LoRa LBT: set the SX128x CAD threshold at config time, not per scan isChannelActive() looked up the AN1200.77 PNR row and wrote register 0x0942 on every scan, but sf and bw only change in applyModemConfig(). That is an SPI round trip between the channel scan and the transmit that follows it, on the one path this file's own comments ask to keep short. Move it to applyCadDetPeak(), called from reconfigure() after the sf/bw setters it depends on, and from init() as well - init() does not route through reconfigure(), so it needs its own call. * ESP32: stop skipping light sleep forever on a stuck LoRa IRQ The skip added earlier in this series returns instead of sleeping whenever DIO1 already reads high, so the main loop can service the packet rather than strand it for the sleep chunk. It had no bound: a line that latches high with nothing clearing it makes every subsequent call skip too, and the node quietly stops sleeping at all, announced only by a LOG_INFO each time. Count consecutive skips - warn at 3, sleep through at 5, reset on any real sleep. If the IRQ has survived five attempts the main loop is not going to clear it, and never sleeping costs more than one stranded packet. Also drop the LORA_DIO1_EXTENDED_IO branch in loraWakeIrqAsserted(): those boards return from doLightSleep() before it is ever called. * nrf52 promicro: gate the P1.06 TXEN pin, and add an optional SX1280 build P1.06 was wired to SX126X_TXEN unconditionally earlier in this series, to drive an external PA on a bench unit. That is not safe to ship: three envs share this variant.h, and P1.06 is PIN_WIRE_SCL on the EasyProMicro pinout and PIN_EINK_BUSY on the InkHUD one. The default builds go back to RADIOLIB_NC. Put it behind -D PROMICRO_SX126X_TXEN, refused wherever the pin is already claimed. The EasyProMicro arrangement has its own SX126X_TXEN on P1.00 in the #else branch and never reaches this, so only the e-ink exclusion is needed. The pin was wanted for SX1280 in the first place - every other SX128X line is already assigned on this board and TXEN was the one missing - so add that too, behind -D PROMICRO_SX1280 and a new nrf52_promicro_diy-sx1280 env. Both opt-ins claim P1.06, so asking for both is an #error rather than a silent pick. Costs about 10.7 KB: the image ends at 0xE5140, 19 KB clear of the warm region, against 0xE2710 and 30 KB for the base env. * LoRa LBT: fix two defects in the CAD->RX handoff found in review Both reported by Copilot against 1fcd219af, both confirmed against the code as it stands after the handoff was hoisted into RadioLibInterface. rearmReceive() armed the MCU ISR before marking the interface as receiving. An ISR firing in that window queues ISR_RX while isReceiving is still false, so handleReceiveInterrupt() bails with "called while not in rx mode" and the packet is dropped - the exact packet the CAD handoff existed to catch. Reversed, which also makes this path match the order the drivers' own startReceive() has always used: base state, then arm, then checkRxDoneIrqFlag(). noteCadHandoffToRx() stored Time::getMillis() raw, but 0 is the "no handoff outstanding" sentinel that checkCadHandoffTimeout() tests before its elapsed comparison. A handoff armed on the one millisecond per wrap where getMillis() returns 0 reads as "none outstanding" and the backstop never fires. Step past it to 1, the same hand-remap Throttle.h's own TODO records GPS.cpp using for fixHoldEnds. One millisecond of error, once per 49.7 days. * ESP32: wake on SX128X_DIO1, which five SX1280 boards define instead of LORA_DIO1 enableLoraInterrupt() only ever looked at LORA_DIO1 and RF95_IRQ. RadioInterface builds an SX1280Interface from SX128X_DIO1, and several variants name the IRQ pin only that way, or put it on a different pin from LORA_DIO1: tlora_v2_1_18 LORA_DIO1 undefined SX128X_DIO1 26 betafpv_2400_tx_micro LORA_DIO1 undefined SX128X_DIO1 4 t-eth-elite LORA_DIO1 16 SX128X_DIO1 8 tlora_t3s3_v1 LORA_DIO1 33 // TCXO_EN? SX128X_DIO1 9 tlora_t3s3_epaper LORA_DIO1 33 // TCXO_EN? SX128X_DIO1 9 Every branch of the function is gated on defined(LORA_DIO1), so on the first two it preprocessed to an empty function: esp_sleep_enable_gpio_wakeup() still enabled GPIO as a wake class, but no pin was ever armed for the radio. On the other three it armed the wrong pin - GPIO33 there being the line the variant's own comment questions as TCXO_EN. Those boards could not be woken from light sleep by an incoming packet; they waited out the 30s chunk and pollMissedIrqs() recovered the latched RX_DONE about a second after waking. Delayed, not lost, but deaf for the duration. loraWakeIrqAsserted(), added earlier in this series, inherited the same blind spot and was dead code on the two boards with no LORA_DIO1 at all - so the skip-the-sleep fix never once fired on an SX1280. Resolve the pin per radio in loraWakeDio1Pin() and use it for arming, polling and teardown alike, so the three cannot drift apart again, and widen their guards to accept either macro. Behaviour is unchanged wherever it already worked: for any non-SX1280 radio the helper returns LORA_DIO1, including on RF95 boards where the SOC_PM_SUPPORT_EXT_WAKEUP branch arms it regardless of radio type. LORA_DIO1_EXTENDED_IO still short-circuits first. Note this is a power-profile change for those five boards, not only a fix: they gain a LoRa wake source they have never had, so on a busy channel their light sleep will now be cut short by traffic like every other board's. * LoRa LBT: trim three-line comments to the repo's two-line limit copilot-instructions.md:340 says one or two lines; the P1.06 availability note and the LR2021 DIO routing note were both three. CodeRabbit flagged both. * LoRa LBT: trace the CAD scan, its exits and the handoff outcome Eight short lines, all in the base class so they cost one set of strings rather than one per chip driver. The scan logs arm/busy/free; the in-chip GOTO_RX handoff logs started, then exactly one of pkt, empty, timeout or void - so a dropped or double-counted handoff is visible by inspection. void and timeout are LOG_WARN because both mean an invariant broke: a standby discarded a live handoff, or the chip's own timer failed to fire and the software backstop had to re-arm. The rest are LOG_DEBUG, so a node at normal log level stays quiet unless something is actually wrong. The two pre-existing handoff strings were 44 and 41 bytes of flash; cut to "CAD>RX timeout" and "CAD>RX empty", they pay for most of the new set. The shared "CAD" prefix does not dedup - only string suffixes merge - and splitting it out behind a %s measured 8 bytes worse on Cortex-M once the extra pointer argument per call site is counted. Also fixes the delivered-packet trace, which sat above the TIMEOUT check and so claimed a packet on a window that expired empty. * LoRa LBT: restore the SX1280 CAD threshold after a chip-state recovery The calibrated AN1200.77 PNR threshold moved from a per-scan register write to a config-time one, so it now has to be re-applied wherever the chip's state is rebuilt. recoverChipStateLoss() hardware-resets the chip and reprograms only the modem params, which left a recovered SX1280 doing CAD against the vendor default threshold until the next reconfigure() - and on a node whose config never changes, that is the rest of its uptime. * LoRa LBT: retire RX flags that plain RX never completes Plain RX runs with no chip timeout (startReceiveDutyCycleAuto(16, 8) falls back to RX_TIMEOUT_INF) and the DIO mask carries RX_DONE only, so a PREAMBLE_DETECTED, HEADER_VALID or HEADER_ERR that never completes stays latched until something re-arms RX. On a node with nothing to send, nothing does. The CAD->RX handoff already has this recovery (cadTimeout plus checkCadHandoffTimeout()); plain RX had none. checkStaleRxFlags() is its twin on the ~1 s pollMissedIrqs() tick. It notes when flags are first seen, and if no RX_DONE has consumed them one max-length packet later: - a header (valid or errored) re-arms RX. RX_DONE clears every flag in readData(), so a header still latched has no frame behind it. SX1280 DS rev 3.3 section 16.2: a header error leaves RX wedged with no RX_DONE or timeout, and only a re-arm recovers it. - a bare preamble is only cleared. ClearIrqStatus/ClearIrq changes the IRQ register alone on SX126x, SX1280, LR11x0 and LR20x0, so it never aborts a reception; startReceive() goes through standby and would. HEADER_VALID is never cleared on its own: RadioLib's readData() reads it at RX_DONE, and on LR2021 its absence reports the packet as header damaged. Skipped while a CAD handoff is outstanding (its own timeout owns that RX) and while RX_DONE is pending. RF95 opts out: its carrier sense reads modem status rather than latched flags, and SX127x maps neither PREAMBLE_DETECTED nor HEADER_ERR. The decision is staleRxFlagAction() in RadioInterface.h, pinned in test_radio at both sides of the window and for each action. * LoRa LBT: hold TX on a seen preamble instead of judging it by a deadline receiveDetected() declared a bare PREAMBLE_DETECTED false once 2 * preambleTimeMsec passed without HEADER_VALID - about 8 ms at SF7/BW500. Nothing reads the flags on that cadence: they are latched status bits, never routed to DIO, and the only reader is the TX path, once per CSMA backoff (0-56 ms at SF7/BW500). The verdict was usually reached by a read older than its own deadline, and the queued TX went out over the frame. Reported in #11933 by @jp-bennett, whose two-node bench measured own TX a median 43 ms after the verdict, inside a ~102 ms frame. This branch's longer CAD slot widens it to LONG_FAST: 0-308 ms of backoff at CWmin against a 262 ms deadline. A read that sees a bare preamble now clears PREAMBLE_DETECTED and holds TX for one max-length airtime. The clear touches only the IRQ register (ClearIrqStatus/ClearIrq on SX126x, SX1280, LR11x0, LR20x0), so it never aborts a reception. Reads during the hold report busy, so the normal random backoff still spreads retries, and any RX interrupt ends the hold early. A preamble that fires again during the hold stays latched, and the first read after it holds again: a source that keeps triggering keeps deferring TX, one airtime at a time, while a single stale flag cannot block it. HEADER_VALID is never cleared here - RadioLib's readData() reads it at RX_DONE, and on LR2021 its absence reports the packet as header-damaged - and keeps its one-airtime ageing. The SX126x software IRQ poll routes its bare-preamble case through the same hold instead of clearing on every tick while a TX is queued, which would have released TX before the next read could see a refire. That poll read the private txQueue directly, so meshnology_w10 - the only env that defines LORA_DIO1_SOFTWARE_POLL - did not compile. hasQueuedTx() exposes the one fact it needs. #11937 addresses the same defect by deferring the verdict when the previous read was stale; the two conflict in receiveDetected(). * LoRa: re-arm RX before logging the packet that just went out After TX_DONE the chip sits in standby, deaf, until startReceive(). The ISR_TX path used to log airtime ("Packet TX") and printPacket() the sent packet first, which kept it deaf for ~2-5 ms more depending on where the log goes. Measured on an nRF52 SX1262 bench node: the TX_DONE-to-RX gap drops from ~8.8 ms to ~3.7 ms with USB serial logging (~11.3 to ~6.2 ms with the log also going to a phone over BLE). completeSending() is split into detachSentPacket(), which clears sendingPacket and runs packetReleased() so RX re-arms on the home config, and finishSentPacket(), which does the airtime, counters, log and pool release. setStandby() callers keep the combined completeSending(). * SX126x: keep the TCXO powered in standby RadioLib leaves standbyXOSC off, so standby and the post-TX/RX fallback both use STDBY_RC, which powers a DIO3-controlled TCXO down. Every SetRx and SetTx after that first waits out the TCXO start-up delay programmed into the chip (RadioLib's setTCXO() default, 5000 us). On an nRF52 SX1262 bench node the RX start after TX_DONE took ~6.0 ms at SF7, SF9 and SF11 alike; with setStandbyXOSC(true) it takes ~0.95 ms, and the TX_DONE-to-RX gap falls to ~1.1 ms together with the previous commit. The result held across the periodic AGC reset. Only boards that drive a TCXO from DIO3 change. The cost is the TCXO and XOSC current while the chip sits in standby, which the firmware keeps brief; duty-cycled RX still sleeps, and powers the TCXO down, between windows. * LoRa: skip the periodic AGC reset while a packet is queued resetAGC() ends in startReceive(), and trySetStandby() first runs checkNotification() to drain pending events. With a packet queued that can be TRANSMIT_DELAY_COMPLETED: CAD passes, startSend() keys up, and trySetStandby() then puts the chip in standby over the transmission it just started. completeSending() still reports the packet as sent and counts its airtime. A bench node logged this twice in 20 minutes; one was a packet's last retry, so it never went on air. Skip the reset for this tick when a TX is queued. The queue drains within seconds, and the reset comes round again on the next interval. * GPS: re-enable on a LoRa config save only when the region was unset #10386 keeps the GPS off until a region is set and turns it on when a LoRa config save sets one. The check never looked at the old region, so a GPS the probe had disabled ("GPS not detected; not present this boot") was re-enabled by every LoRa config save, preset changes included, and the probe ran again. The probe blocks the cooperative main loop: on a bench node with no GPS fitted, TX_DONE handling stalled by 0.34-0.89 s on 4 of 9 packets after a preset change, leaving the radio deaf in standby for that long. * LR20x0: CAD exit to RX is 0x01 on the LR2021 RadioLib (510e00cf) defines RADIOLIB_LR2021_CAD_EXIT_MODE_RX as 0x02. The LR2021 takes SetLoraCadParams exit modes 0x00 CAD only, 0x01 RX on detection and 0x10 TX when clear, as the LR11x0 does, and refuses 0x02 with a processing error (-706). With the CAD>RX handoff passing 0x02, every scan failed before the CAD ran and isChannelActive() reported the channel clear: on a Seeed Mesh-Tracker X1, 539 scans against live traffic gave no busy verdict, so the node transmitted with no carrier sense. Pass 0x01, and warn when a scan fails rather than reporting it clear in silence. * LoRa LBT: take out the preamble hold on a seen preamble Revert |
||
|
|
dee945cd6a |
CH341: fewer waits on the USB bus per radio command (#12013)
* CH341: fewer waits on the USB bus per radio command Every RadioLib command to a radio behind a CH341 waits on the USB bus about five times: the BUSY read before it, CS low, the command, CS high, and the BUSY read after, with a 1 us settle between the last two that usleep() turns into ~50 us of timer slack. - A BUSY read that follows the read that found BUSY low after a command is answered from it, once, within 1 ms and only if nothing else has been sent to the adapter in between: RadioLib's wait before a command then costs nothing after the wait that ended the last one. - Delays under 100 us spin on the steady clock instead of sleeping. - With a libch341 that has pinedio_transceive_select(), and where Lora.CS is D0, the CS levels travel with the SPI transfer and RadioLib's separate CS writes are dropped. Against an older libch341 this part compiles out. The HAL learns the radio's CS and BUSY pins from initLoRa(), since USBHal.h is included by PortduinoGlue.h ahead of portduino_config. Measured on an SX1262 over a CH341 (SHORT_TURBO, 6 v 6 runs of 300 s): the three-command CAD setup fell 1.01 -> 0.62 ms, CAD arm to air 6.60 -> 5.23 ms, and total packet loss 1.51 -> 1.13% (p=0.048), with collisions 4.83 -> 0.67 per run (p=0.002). Co-Authored-By: Jonathan Bennett <jbennett@incomsystems.biz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REkPVFh6kvG4AZJ5A8AtM9 * Bump libch341-spi-userspace to 027cde5 for pinedio_transceive_select() Picks up libch341#6 (1 ms interrupt poll, poll thread woken on detach) and #7 (pinedio_transceive_select()), so the packed-CS path in USBHal.h builds in rather than compiling out. Co-Authored-By: Jonathan Bennett <jbennett@incomsystems.biz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REkPVFh6kvG4AZJ5A8AtM9 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> |
||
|
|
e0c670a65d |
Meshnology W12: report MESHNOLOGY_W12 hardware model (#12037)
The MESHNOLOGY_W12 enum (145) was added in meshtastic/protobufs#1042, but the W12 variant predates it and still reports PRIVATE_HW (255). Add the HW_VENDOR mapping in architecture.h, set custom_meshtastic_hw_model to 145, and drop the outdated "no hardware model yet" comment. Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
8c0abbd522 |
feat(portduino): BLE peripheral support via BlueZ for meshtasticd (Raspberry Pi) (#11396)
* feat(portduino): BLE peripheral support via BlueZ for meshtasticd on Linux Adds the standard Meshtastic BLE service (toRadio/fromRadio/fromNum/logRadio) to the Linux native target, so a Raspberry Pi running meshtasticd can be paired and used over BLE like any other Meshtastic device. Implementation: a new LinuxBluetooth backend registers a GATT application, LE advertisement and pairing agent with bluetoothd over the org.bluez D-Bus APIs, using sdbus-c++ (both the 1.x and 2.x major versions, via a small compat shim - Debian bookworm/Ubuntu 24.04 ship 1.x, trixie/Fedora ship 2.x). When the sdbus-c++ dev package is absent the whole backend compiles out via __has_include, the same optional-dependency idiom as the ulfius webserver. Threading follows the NimbleBluetooth model, simplified: the sdbus event loop runs its own thread, and all PhoneAPI calls happen on the main thread. Writes queue to the main loop; reads park the D-Bus reply and are completed from the main thread after queued writes, so write-then-read clients see their answer without any busy-waiting. Enablement is a double opt-in: a new `Bluetooth:` config.yaml section (Enabled, default false; AdapterId, default hci0) must turn BLE on for the host, and the regular device config bluetooth.enabled must be on. The config-check schema and fixtures cover the new section. Pairing honors config.bluetooth.mode: NO_PIN maps to a NoInputNoOutput just-works agent; RANDOM_PIN to DisplayOnly with the kernel-generated passkey shown on screen/log via the existing BluetoothStatus plumbing. FIXED_PIN falls back to random-passkey semantics with a warning - BlueZ does not support forcing a passkey. PIN modes enforce encrypt-authenticated-read/write on all mesh characteristics. Packaging: install a D-Bus system policy so the meshtasticd user may talk to org.bluez, add it to the bluetooth group, order the unit after bluetooth.service, and add libsdbus-c++-dev to debian/rpm/docker/CI deps. Verified in-container against a mock bluetoothd: registration flow, GATT tree enumeration, advertisement properties, and a full config download (ToRadio wantConfig -> 47 FromRadio packets) through the D-Bus bridge. Real-hardware pairing/notify testing on a Pi still pending. Known limitations (v1): meshtasticd must be restarted if bluetoothd restarts; FIXED_PIN degrades to a random passkey; getRssi() returns 0 (same as nRF52). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ * fix(portduino): BLE fixes from first real-hardware pass (Pi CM5 + RAK6421) Findings from testing PR #11396 on a Raspberry Pi CM5 (Pi OS trixie, BlueZ/sdbus-c++ 2.1 - the v2 compat path) with a RAK6421 HAT and an Android phone: - getMacAddr() leaked its HCI socket on every call and never closed it, and on failure returned without touching the caller's buffer - which getDeviceName() passed in uninitialized. Close the socket on all paths and cache the MAC after the first successful read; it cannot change at runtime and this now runs on every bluetoothd property read. - getDeviceName() zero-initializes its MAC buffer, and LinuxBluetooth snapshots the name once at setup() on the main thread: the advertisement's LocalName getter runs on the D-Bus event-loop thread and getDeviceName()'s static buffer is not thread-safe. - Restore NimBLE-style config-phase packet prefetch (depth 3). The initial port answered every FromRadio read with a D-Bus -> main-loop round trip, which made the config download noticeably slow; ReadValue now answers straight from the prefetch queue on the event-loop thread, with NimBLE's safety rules (never in STATE_SEND_PACKETS, writes always observed before reads, queue cleared on disconnect). - Set advertising MinInterval/MaxInterval to 20-100ms (BlueZ >= 5.71; older versions ignore the properties). btmon showed the kernel default of 1.28s otherwise, and Android's background-connect scan windows are sparse enough that tap-to-connect took 8-14s; 20ms is the same floor NimBLE uses on ESP32. Verified on hardware: scan, passkey pairing, connect, config download, reconnect after bond wipe. Also diagnosed (no code change): the node identity MAC comes from the RAK HAT EEPROM by design, so the BLE name suffix follows the HAT rather than the BT adapter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ * fix(portduino): address CodeRabbit review on BLE support - setBluetoothEnable: handle disable before the config gate, so a running BLE stack is always stoppable even after the device config turns Bluetooth off underneath it - getMacAddr: read the adapter configured as Bluetooth.AdapterId instead of hardcoding hci0, falling back to hci0 for unparseable names - systemd unit: Wants=bluetooth.service so bluetoothd is pulled up when present (After= only orders, it does not start it) - debian/rpm: Recommends: bluez as the runtime contract for BLE - dbus policy: document why the org.bluez rule is destination-wide rather than a per-interface allowlist Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ * fix(portduino): show the BLE pairing code on BaseUI screens onDisplayPasskey published the passkey to bluetoothStatus and triggered PowerFSM, but never called screen->startAlert(), so on BaseUI the code only ever reached the log. BluetoothStatus has no BaseUI consumer -- only InkHUD's PairingApplet and StatusLEDModule read it -- so a Pi driving a HUB75/OLED panel showed nothing while BlueZ sat waiting for the user to type a code they could not see. NimBLE and nRF52 draw it via startAlert(); this adds the missing half for Linux. The agent callbacks run on the sdbus event-loop thread while the screen is owned by the main thread, so the passkey is handed over as a pending flag and drawn from runOnce(), matching the existing disconnectCleanupPending pattern rather than reaching into the screen from the event loop. Dismissed on all four exits, so a stale code cannot stick on an always-on panel: Paired -> true (newly watched in PropertiesChanged, which previously only looked at Connected), agent Cancel, peer disconnect (moved out of the lastGone branch so a peer leaving mid-pairing clears the code even when another device is still connected), and doDeinit() -- applied inline there because runOnce() may never be scheduled again after teardown. Verified on a Pi 5 + BlueZ 5.66 in RANDOM_PIN mode: the code renders on a HUB75 panel and clears once the phone completes pairing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: install libsdbus-c++-dev for the native test build setup-native-test landed on develop while this branch was adding libsdbus-c++-dev to setup-native, so the new action's "full setup-native list" of C libraries is missing it. Without the package the test job builds with HAS_BLUETOOTH 0 and never compiles LinuxBluetooth.cpp. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ * fix(portduino): warn when a factory reset cannot clear BLE bonds factoryReset(eraseBleBonds) silently did nothing on Linux when the BLE backend was not running, so the reset reported success while the host's pairings stayed. Removing them needs a live connection to bluetoothd that a disabled backend never opened, so say so rather than imply they went. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ * fix(portduino): gate the factory-reset bond clear on an enabled backend setup() leaves linuxBluetooth allocated with its bus torn down when it throws, so a pointer check alone let factoryReset log "Clear bluetooth bonds" for a clear that clearBonds() then declined to perform. isEnabled() is only true after setup() completes, which routes that case to the warning that says the bonds were left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ * style: reformat under clang-format 20 #11909 moved trunk from clang-format 16 to 20, which spaces C-style casts differently and reindents the comment above the HAS_WIFI block. Both files are ones this branch already touches, and trunk's fmt linter grades whole files, so its check fails until they are reformatted. No behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ * fix(portduino): three BLE config and lifecycle fixes from review Bluetooth config keys are now assigned individually rather than per section. loadConfig() runs once for every file in config.d, so reading an absent key as its default let a later file that named only one of them silently reset the other: `AdapterId: hci1` alone turned Bluetooth off, and `Enabled: true` alone dragged the adapter back to hci0. Only what a file actually states should override what an earlier one set. A backend that failed to come up is now retried. setup() can leave linuxBluetooth non-null but disabled - bluetoothd not ready, adapter missing, policy refusing - and every later enable then called resumeAdvertising(), which returns immediately while disabled. A transient failure at boot kept BLE off until the process restarted. doSetup() already opens with `if (enabled) return` and tears the bus down on every failure path, so calling it again is safe. Bluetooth.AdapterId is now checked for the hci<digits> form. LinuxBluetooth uses the value verbatim as the BlueZ object path while the MAC fallback reads only the leading hciN, so "hci1junk" looks plausible, yields a MAC, then finds no adapter and BLE never comes up. Covered by a new fixture and suite case, which is the kind of silent no-op that directory exists to catalogue. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f5940ab5dd |
Meshtasticd notifications (#10216)
* First attempt at sending message notifications on Linux
* Add files via upload
* Notifications: Use libnotify
Use libnotify for meshtasticd desktop notifications.
Add "HAS_LIBNOTIFY" macro to guard for builds where libnotify is expected.
This is not included in buildroot/openwrt builds. (no libnotify except when extra repos are added).
Install desktop icon in the correct location on Debian and Fedora packages.
Update dependencies in packaging and dockerfiles.
* Add libnotify to setup-native (GitHub Actions)
* Address review feedback on the meshtasticd notification path
- platformio.ini: only define HAS_LIBNOTIFY when pkg-config actually finds
libnotify. The probe previously ran unguarded and the macro was defined
unconditionally, so a native build without libnotify-dev both hard-failed at
config time and claimed the feature was available.
- Fix the sender lookup for develop's flattened NodeInfoLite: has_user/user.*
are gone, replaced by nodeInfoLiteHasUser() and direct long_name/short_name.
This is a silent semantic conflict - it merges cleanly but does not compile.
- Move the desktop notification off the packet path. notify_notification_show()
is a synchronous DBus round trip and meshtasticd's packet handling is
single-threaded, so a slow or wedged notification daemon could stall the
radio. handleReceived() now resolves the strings and queues them (bounded at
16); a dedicated worker owns every libnotify call.
- Stop retrying forever: a failed notify_init(), or three consecutive failed
shows, latches desktop notifications off instead of re-logging per message.
- NodeDB: the ARCH_PORTDUINO default-enable block was nested inside
#ifdef HAS_I2S, which Portduino never defines, so it never ran. Hoist it out.
* fix(portduino): close input-broker guard before the libnotify block
The libnotify implementation was inserted ahead of the #endif that closed
#if !MESHTASTIC_EXCLUDE_INPUTBROKER, so the file's trailing #endif closed
#if HAS_LIBNOTIFY instead and the input-broker conditional was never closed.
Every build target failed to preprocess:
src/modules/ExternalNotificationModule.cpp:630: error: unterminated #if
Close the guard immediately after handleInputEvent(), as develop does, so the
two conditionals stay independent and HAS_LIBNOTIFY still resolves when the
input broker is excluded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs
* fix(portduino): back off desktop notifications instead of latching them off
notifyDisabled was doing two jobs: "libnotify looks unusable" and "the
destructor wants the worker to exit". Because the worker's only exit path was
also its only failure path, three failed shows turned notifications off for the
process lifetime with no way back. The packaged daemon is exactly that case:
bin/meshtasticd.service runs as User=meshtasticd and the rpm spec creates that
account with /sbin/nologin, so there is no session bus and every show() fails.
A stock install logged three warnings and then went silent forever, while
NodeDB now enables the module by default on Portduino.
Split the flag. notifyShutdown is destructor-only and remains the worker's one
exit; a retry window replaces the latch. After maxNotifyFailures the worker
arms a backoff (30s, doubling to a 15min cap), drops the queue rather than
holding stale popups, and keeps looping. The producer refuses to queue while
the window is open, so the retry is driven by the next message after it expires
rather than by a timer - no idle wakeups, no probe notifications. Any success
resets the backoff. notify_init() moved inside the loop so a retry can pick up
a session bus that was absent at startup.
The worker also no longer calls the LOG_ macros. RedirectablePrint formats into
a shared static buffer that nothing guards and every other writer to it is on
the main thread, so the worker now records the state change under the mutex it
already holds and reportNotifyStatus() emits it from portduinoNotify() and
runOnce(). Per-message failure spam becomes one line per transition: the reason
and retry interval when it goes down, and a line when it comes back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs
* fix(portduino): sanitize mesh text before it reaches libnotify
The notification body was the raw decoded payload and the summary was a node
name, both attacker-controlled, and neither was checked before being handed to
libnotify. g_variant_new_string() rejects invalid UTF-8: a GLib CRITICAL and an
"[Invalid UTF-8]" body by default, and a hard abort under G_DEBUG=fatal-criticals,
so an unauthenticated mesh packet could terminate meshtasticd on any install
running with that flag. An embedded NUL separately truncated the body at the
first one, hiding the rest of the message.
Route both strings through sanitizedMeshText(), which replaces embedded NULs and
then applies the existing sanitizeUtf8() helper. TypeConversions already
sanitizes names on the way into NodeDB, but an abort is too sharp an edge to
leave resting on an invariant owned by another file.
Escape the body for Pango markup as well. Servers advertising "body-markup"
parse a markup subset there, so a message can inject formatting to dress itself
up as trusted UI, and where the server also advertises body-images or
body-hyperlinks it can inject tags that make the notification daemon fetch a
remote URL. The escaping is unconditional rather than gated on
notify_get_server_caps(): a caps query that fails, or goes stale across a daemon
restart, fails open, while on a server without body-markup the only cost is
entities rendering literally, and mesh text carries no markup worth preserving.
The summary is not escaped - the spec gives it no markup, so escaping it would
only ever show entities.
Also call notify_uninit() in the destructor, after the join: the worker owns
every libnotify call, so tearing down while it is still running would be a
use-after-uninit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs
* fix(portduino): gate the notification default-on behind HAS_LIBNOTIFY
The default-enable block was gated on ARCH_PORTDUINO, so a native build where
pkg-config could not find libnotify still shipped external_notification enabled.
portduinoNotify() is not compiled into that build, and native defines
EXT_NOTIFICATION_MODULE_OUTPUT as 0 with setup() guarding the pin writes behind
output > 0, so nothing was driven - it only exposed a config surface that can
do nothing. Gate it on the same macro that governs the code it exists to feed.
HAS_LIBNOTIFY is a build flag for the whole env and is simply absent when the
probe fails, so this reads as 0 on every other target, matching how
ExternalNotificationModule.cpp already tests it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs
* fix(portduino): fail closed when markup escaping returns NULL
escapedNotificationBody() returned its input unescaped if g_markup_escape_text()
gave back NULL, which would hand libnotify the exact attacker-controlled string
the function exists to neutralize. The branch is unreachable for the input we
pass - already sanitized to valid UTF-8, and GLib documents no NULL return for
it - but an error path whose fallback is the unsafe action is the wrong shape
for a helper on this boundary. Drop the body instead.
Also note in the doc comment why running this on the caller's thread does not
weaken the rule that the worker owns every libnotify call: it is a pure GLib
string function that touches no libnotify or DBus state.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs
* style: format ExternalNotificationModule.h per the pinned clang-format
Trunk Check Runner failed on
|
||
|
|
17cc649a64 |
fix(gps): stop the AIROHA engine before power-down on boards with GPS_SLEEP_INT (ThinkNode M9) (#12004)
* fix AIROHA GPS and M9 sleep/wake * fix nrf compile error * make coderabbit happy as well |
||
|
|
09e514602e |
feat(variants): add L1 Pro 1W battery curve and brightness levels (#11972)
- Add OCV_ARRAY for the L1 Pro 1W battery.
- Let variants override the brightness menu levels via
SCREEN_BRIGHTNESS_LEVEL_{MEDIUM,HIGH,VERY_HIGH}; defaults unchanged.
- Set L1 Pro 1W brightness levels to 100/160/255.
- Drop USE_SSD1306 from the L1 Pro 1W variant; USE_SH1106 is set in
configuration.h.
|
||
|
|
f90b48ea6c |
chore: trunk fmt --all (#11938)
Whitespace and comment-alignment only: the output of `trunk fmt --all` on
develop @
|
||
|
|
608ff51c86 | Update L2 TCXO value (#11960) | ||
|
|
12db3b6dc1 |
Declare custom_meshtastic_has_mui for Heltec V4 R8 (#11957)
Matches the heltec-v4 env. Fixes #11956. |
||
|
|
b1470cd719 |
fix(heltec): sleep the T1 and T096 panels on screen-off to stop image retention (#11894)
* fix(heltec-t1): sleep the panel on screen-off to stop image retention The T1 is excluded from the LovyanGFX sleep()/wakeup() calls because it uses TFT_eSPI, so DISPLAYOFF only dropped the backlight and the ST7735 kept driving the last frame unlit for the whole screen-off timeout. That constant static image is what burns ghost pixels into the panel. Add opt-in TFT_SLEEP_WHEN_OFF for the TFT_eSPI path: DISPOFF + SLPIN on screen-off, SLPOUT (120 ms) + DISPON on wake, issued as raw MIPI DCS since TFT_eSPI exposes no sleep API. Frame memory survives sleep-in, so the previous frame reappears and the dirty-window diff continues unchanged. The sleep flag keeps the double displayOn() in Screen::handleSetOn() from paying the delay twice. * fix(heltec-t096): sleep the panel on screen-off to stop image retention The T096 sits behind the same TFT_eSPI exclusion as the T1, so DISPLAYOFF only dropped its backlight while the ST7735S kept driving the last frame unlit for the whole screen-off timeout. Same panel, same bus, same burn-in. Opt in to TFT_SLEEP_WHEN_OFF; the TFTDisplay side of the fix is already generic. * fix(tft): harden the TFT_SLEEP_WHEN_OFF wake/sleep sequence Drive VTFT_CTRL LOW before SLPOUT, so the rail is up before the panel is addressed. Wait out the remainder of the 120 ms the controller needs after SLPIN before sending SLPOUT, so a wake landing as the screen timeout fires is not dropped. Guard DISPLAYOFF on panelAsleep to match DISPLAYON. Correct the T1 VTFT_CTRL comment: LOW enables the rail, not HIGH. * fix(tft): wait out only what is left of the sleep-in window before SLPOUT Throttle::isWithinTimespanMs() is true for the whole 120 ms after SLPIN, so the wake path paid a fresh 120 ms on top of however much had already elapsed. A wake 119 ms after the SLPIN waited ~120 ms rather than ~1 ms, up to 119 ms of avoidable latency on every quick off/on. Add Throttle::remainingMs(), which returns what is left of the interval and saturates at 0 instead of underflowing to a ~49 day wait. It reads the clock once, so a caller that tests and then waits cannot be preempted between the two and land on that underflow - which a separate isWithinTimespanMs() plus subtraction at the call site could. heltec-mesh-node-t1 and -t096 both build; neither is board_level = pr, so CI does not compile this path. Docker native suite green, 1515/1515. * trunk --------- Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz> |
||
|
|
d971d507c4 |
Thinknode M9 V2: keyboard and GPS (#11905)
* thinknode v2 keyboard and GPS * update device-ui commit reference |
||
|
|
fa87e7d29c |
chore(deps): update libch341-spi-userspace digest to d85aceb (#11907)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
60f82b4488 |
Fix Mesh Node T1 device image filename (#11893)
custom_meshtastic_images pointed at heltec-mesh-node-t1.svg, which does not exist in web-flasher. The file there is heltec-meshnode-t1.svg. The wrong path returns HTTP 200 with a 7 KB HTML error page rather than the 203 KB SVG, so it does not surface as a 404. |
||
|
|
d4166d0426 |
chore(deps): update libch341-spi-userspace digest to eaaef01 (#11895)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
54334ff936 |
Initial firmware support for Axiometa Genesis Mini (#11852)
* Initial firmware support for Axiometa Genesis Mini * Report the real AXIOMETA_GENESIS_MINI hardware model The protobufs now carry AXIOMETA_GENESIS_MINI = 148, so the board no longer has to masquerade as private hardware. On ESP32 the -D PRIVATE_HW flag never selected the model on its own - architecture.h has no arm for it, so the board fell through to the PRIVATE_HW default at the end of the chain. Give it its own arm and drop the flag. * fix(input): sample the encoder button after light-sleep wake The edge that wakes the device lands while beforeLightSleep() has the interrupts detached, and a button that is still held produces no further edge until it is released. The thread stayed parked at INT32_MAX, so the press was never sampled - no event was emitted, PowerFSM's GPIO-wake branch reads BUTTON_PIN rather than the encoder pin, and the node dropped straight back into light sleep with the press swallowed entirely. The second press worked, the first did not. Sample once on wake, and only when the button is asserted, so a timer or radio wake leaves the thread alone. The press then follows the ordinary path and InputBroker drops the event because the screen was off, so it wakes the screen and does nothing more - the same behaviour every other input device has. Rotation stays deliberately non-waking: only the button pin is armed in doLightSleep(), and the abState re-seed discards a shaft moved during sleep rather than replaying it as detents. Reported by CodeRabbit on #11852. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: rcarteraz <robert.l.carter2@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7bfa062f27 | fix(esp32): raise T-Watch S3 to support level 1 (#11884) | ||
|
|
dbbd42687b |
chore(deps): update meshtastic/crypto digest to 1c817c2 (#11879)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
f36a1ea821 |
nRF52: reclaim flash to bring rak4631 back under its size budget (#11873)
* build(nrf52): drop unused TinyUSB classes and assert function-name strings Only the CDC class is used on nRF52. Disable the MSC, HID, MIDI, vendor and video class drivers in the Adafruit TinyUSB config, and pass an empty __ASSERT_FUNC so assert() no longer embeds __PRETTY_FUNCTION__ strings. File and line are still reported. rak4631 estimate: ~7.4 KB flash, ~2.3 KB RAM. * fix(nrf52): link only the secp256r1 cc310 curve domain CRYS_ECPKI_GetEcDomain indexes ecDomainsFuncP, which references the parameter tables of all eleven cc310 curves. Bluefruit LESC pairing only requests secp256r1, so override the lookup to return that domain alone. rak4631 estimate: ~7.4 KB flash. * fix(airtime): replace powf in the channel-utilization EMA fold foldChannelUtil was the only powf caller on nRF52. The exponent is an integer step count, so raise the EMA factor by squaring instead; a multi-day sleep still folds in at most 32 multiplications. rak4631 estimate: ~1.9 KB flash. * fix(graphics): use double sin/cos in the compass renderers The compass renderers were the only sinf/cosf callers on nRF52 screen builds, pulling in the float trig kernels next to the double ones GeoCoord already links. Call the double variants instead. rak4631 estimate: ~3.2 KB flash. * fix(motion): use double atan2 for magnetometer heading fallbacks MMC5983MA, QMC6309 and the InkHUD map centre were the remaining application atan2f callers. The double atan2 is already linked, so the float variant only added atan2f, __ieee754_atan2f and atanf. The saving lands once meshtastic/Fusion#1 removes the library's atan2f as well. rak4631 estimate: ~0.8 KB flash with Fusion#1. * fix(hopscale): trim diagnostic logging to state changes and anomalies Drop the save/restore confirmations, the hourly histogram and trend dumps, the denominator step logs and the per-packet hop_limit log (printPacket already reports HopLim). Keep the save-failure and histogram-full warnings, the congestion on/off transition and a single periodic status line, and remove lastScaledPerHop, which only fed the logs. * fix(hopscale): silence cppcheck uselessAssignmentArg on restored count * perf(crypto): use full-schedule AES128/AES256 for AES-CCM aesSetKey used AESSmall128/AESSmall256, which re-derive round keys for every block. AES128/AES256 precompute the schedule, encrypt faster and are already linked by encryptAESCtr, so the AESSmall*/AESTiny* code drops out. No change on ESP32, where AESSmall* already aliases AES128/AES256. rak4631 estimate: ~3.9 KB flash; cipher object up to 184 bytes larger. * perf(nrf52): use the shared software CTR for AES-256 and remove tiny-aes CryptoCell only accelerates AES-128, which stays on hardware. AES-256 CTR now calls CryptoEngine::encryptAESCtr (rweather CTR<AES256>, already linked) instead of the in-tree tiny-aes copy, whose sources were removed in the previous commit. Output is identical. rak4631 estimate: ~0.8 KB flash. * perf(mesh): use std::map for pending retransmissions and API port timestamps NextHopRouter::pending and PhoneAPI::lastPortNumToRadio were the only unordered_map instances linked on nRF52. Switching them to std::map, which is already linked, drops the libstdc++ hashtable, rehash policy and prime table. GlobalPacketId gains operator<; the unused hash functor is removed. rak4631 estimate: ~2.1 KB flash. * perf: parse sensor decimals without strtod The WS85 serial parser (strtof) and DFRobotLarkSensor (String::toFloat) were the only callers of newlib's strtod. Add parseDecimalFloat to meshUtils for plain [+-]digits[.digits] fields and use it at both sites. Covered by test_type_conversions against strtof. rak4631 estimate: ~4.5 KB flash. * perf(gps): compute tan from sin/cos in UTM and OSGR conversion latLongToUTM and latLongToOSGR were the only tan callers. sin and cos are already linked, so deriving tan from them drops tan and __kernel_tan. rak4631 estimate: ~1.1 KB flash. * fix(graphics): only dispatch the theme menu when TFT coloring is enabled The Theme option is only offered with GRAPHICS_TFT_COLORING_ENABLED, but handleMenuSwitch dispatched ThemeMenu unconditionally, linking kThemes and the theme accessors into monochrome builds where the menu is unreachable. rak4631 estimate: ~1 KB flash. * fix(senxx): trim diagnostic logging to errors and user-visible actions Keep all errors and warnings and a single version line; shorten the admin action messages; drop progress chatter, state save/restore confirmations and the per-reading and VOC-state debug dumps. The nested VOC restore branch collapses to one condition with the same behaviour. rak4631 estimate: ~2 KB flash. * build(nrf52): define CRYPTO_AES_NO_DECRYPT CTR and CCM only encrypt, so the AES inverse tables and round helpers are dead code on nRF52. Takes effect once the Crypto dependency includes meshtastic/Crypto#5. rak4631 estimate: ~1.0 KB flash. |
||
|
|
ce7e6e448d |
Separate the nRF54 platform code from src/platform/nrf52 (#11867)
* Move the nRF54L platform code into src/platform/nrf54l15 and drop the ARCH_NRF54L branches from src/platform/nrf52 * Name the platform directory nrf54 so future nRF54 variants can share it * Rename the nRF54 platform base to nrf54_base in variants/nrf54l15/nrf54.ini * Leave the SoftDevice random seed to the Bluefruit core, which seeds in begin() and answers NRF_EVT_RAND_SEED_REQUEST * Seed the SoftDevice from checkSDEvents() when it pops NRF_EVT_RAND_SEED_REQUEST |
||
|
|
ff9a2b1b86 |
fix(http): give a TLS session the contiguous heap it actually needs (#6960) (#11838)
* fix(http): give a TLS session the contiguous heap it actually needs (#6960) An HTTPS connection to a no-PSRAM ESP32-S3 fails its handshake with MBEDTLS_ERR_SSL_ALLOC_FAILED and the client gets a TCP reset. Four things were in the way, and the first two are the bug. mbedtls_ssl_setup() makes two independent calloc() calls, one inbound record buffer and one outbound, each needing its own contiguous block of 16717 bytes. The admission gate compared ESP.getFreeHeap(), which is a sum over every free block and says nothing about the largest one, so on a fragmented heap it waved the connection through and let the handshake be the thing that discovered there was no room. Size the two record buffers separately. Inbound stays at the RFC maximum because browsers and our own MQTT and OTA clients receive full-size records; outbound only ever carries what this device sends, 4 kB at a time at most. That is 12288 bytes of contiguous heap back per session, which is the difference between one usable HTTPS connection and two. These are the ESP-IDF defaults - Espressif's arduino-lib-builder is what turns the asymmetric split off. It replaces CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN, which no longer applies: that symbol is `depends on !MBEDTLS_ASYMMETRIC_CONTENT_LEN`, and mbedTLS dropped it in 3.0. Then ask the allocator the question mbedTLS is about to ask it, rather than a different one. The probe holds both record buffers at once, as setup() does, through the same heap that CONFIG_MBEDTLS_*_MEM_ALLOC pins mbedTLS to - a plain malloc would be served from PSRAM that mbedTLS never touches. Not heap_caps_get_largest_free_block(), which walks every block under the allocator lock and tripped the interrupt watchdog in #11666. Closed connections are reaped before the measurement, so the library cannot free a slot behind it and accept into it unmeasured, and so the probe sees the memory a finished session just returned. The answer is advisory: the heap can still turn between the probe and setup(), which makes the failure rare rather than routine. A smaller outgoing record makes an existing bug much easier to hit. mbedtls_ssl_write() accepts at most one record per call and returns a short count for the rest, and neither the server library nor Print::print() loops on that, so any response body larger than one record was quietly cut off - /json/nodes has been truncating at 16 kB already. Loop at the bulk write sites. Looping on the returned count also covers a client that negotiates a smaller maximum fragment length, which a fixed-size chunker would not. /json/nodes also built its whole body in one std::string. A full node DB is 200 entries of ~240 bytes, and std::string grows by doubling, so it asked for a 64 kB contiguous block while still holding a 32 kB one, on a board with ~50 kB of heap, through operator new, which aborts rather than throws here. Stream it a node at a time instead. Finally, stop every node in the fleet paying for geofencing. The crossing table reserved all 256 slots at construction, 4 kB, though a node with no geofenced waypoints never tracks a crossing at all. Allocate on the first one instead, stepping the capacity explicitly so there is a size to probe for, and hand the block back once the last waypoint is gone. The probe matters because this allocation has moved off the pristine boot heap onto a live one, where a reserve() that cannot find the block would abort; a refusal now degrades into the bounded-drop path that already exists. heltec-v3 builds clean from scratch with the IDF recompiled, and comes out 20,656 bytes smaller in flash and 172 bytes smaller in static RAM. Full native suite is green at 1414 cases. * style(http,geofence): cut the added comments to the repo's one-or-two-line rule The rationale belongs in the commit message and the PR, not in block comments above every new symbol. No behaviour change; the binary is byte-identical. * fix(geofence): index the capacity probe write to clear cppcheck uninitdata * fix(http): stop the /json/nodes body at the first failed write * fix(geofence): grow crossing state with realloc so a refused allocation drops instead of aborting * refactor(http): probe through esp_mbedtls_mem_calloc and fold the free-slot scan into reaping * refactor(geofence): allocate the crossing table once on first use and free it on store changes * fix(http): probe through mbedtls_calloc so the calls keep C linkage * fix(http): name the check that refused a TLS session, and sum the right heap Both failure modes logged the same line, so a refusal on the free-heap threshold read as fragmentation. The probe returns a verdict now and the warning names the check that failed. That threshold also read ESP.getFreeHeap(), the internal heap, while the probe below it goes through mbedtls_calloc() - PSRAM wherever EXTERNAL_MEM_ALLOC sends mbedTLS, which [device-ui_base] sets. There is no free-size query on the allocator, and the handshake's remaining small blocks want a sum rather than a block to probe for, so the sum asks heap_caps for the same heap. * fix(http): probe the TLS heap only when a client is waiting for a slot The probe holds ~21 kB across two callocs and ran whenever a slot was free, which on a board with nobody connecting is every 50 ms while the web server is active and every second when it is idle - on exactly the low-heap boards this branch is for. It now runs only when a zero-timeout select() on the listen socket says a client is already in the backlog, which is the question loop() asks a few microseconds later. With nothing knocking the server drives its open connections and accepts nothing, so a connection is never accepted past an unmeasured gate; one that lands between the two select()s waits a tick. * perf(http): flush /json/nodes a couple of kB at a time One writeAll() per node is one mbedtls_ssl_write() per node, about 200 of them for a full node DB. Filling a 2 kB buffer first cuts that by roughly 9x and still bounds the allocation, which is why the whole-body string went away in the first place. * fix(geofence): keep the table-full warning for a full table A refused malloc shared the one-shot table-full warning, so a momentary heap dip spent the message that means the 256-entry cap was reached, and the real thing then never printed. They are separate now, and the allocation failure throttles rather than latching, because the next position may well find the 4 kB the table needs. * fix(http): close a waiting client the TLS heap check refuses * fix(http): hold spiLock only for filesystem calls in the browse and delete handlers * refactor(http): leave the browse and delete spiLock scoping to #11870 --------- Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com> |
||
|
|
e0cf782131 | Build xiao_nrf54l15_lr2021 on every PR as the nrf54l15 canary and mark it community supported (#11856) | ||
|
|
2fe711115e |
feat(nrf52): add RAK3401 + LR2021 (RAK13700) variant (#11819)
* feat(nrf52): add RAK3401 + LR2021 (RAK13700) variant WisBlock core with IO-slot LR2021: DIO RF switch, board LF PA table, and 1.6 V TCXO. Keep extra/unsupported until it has its own hw_model. * fix(lr2021): log custom PA setOutputPower in fullBegin Match init(): a calibration miss stays a warning so band-hop keeps the begin() PA config. * refactor(lr2021): share custom LF PA table helper init() and fullBegin() both re-install the board table after begin(); keep the warn-only setOutputPower miss. |
||
|
|
ea7d4aa410 |
Port nRF54L15 to the s145 SoftDevice Arduino core (#11842)
* Remove the Zephyr based nRF54L15 port * Add nRF54L15 port on the s145 Arduino core: nrf54l15dk and xiao_nrf54l15 variants * nRF54L: errno-style nrfx results, flush console before assert reset * nRF54L: log the SoftDevice status on Bluefruit failure, ignore the seed request event * Support the Wio-LR2021 LoRa Plus expansion board with OLED and K1 on the XIAO nRF54L15 variant * Consume the nRF54L15 platform, core and bootloader from their repositories * Pin the nRF54L15 platform to v0.2.0 * nRF52: forward SoftDevice flash events taken by the main loop to the flash driver, log the pairing failure status * Pin the nRF54L15 platform to v0.2.1 * Pin the nRF54L15 platform to v0.3.0 * Split the XIAO nRF54L15 variant into SX1262 and LoRa Plus environments, seed the SoftDevice on request, add the nrf54l15 CI build script * Pin the nRF54L15 platform to meshtastic/platform-nordicnrf54 v0.3.1 * NRF54: Fix mtjson generation --------- Co-authored-by: vidplace7 <vidplace7@gmail.com> |
||
|
|
9d27b276aa |
NRF54: Update to toolchain-gccarmnoneeabi@1.90301.200702, align NRF52840 (#11850)
Version 1.90201.191206 supports arm64 MacOS, but not arm64 Linux (1.90301.200702 supports both) Also change the fuzzy match for gccarmnoneeabi to an exact match for nRF52840 (for reproducible builds), this is effectively a no-op change, today. |
||
|
|
2826c6712b | Pin nrf54 platform, update toolchain-gccarmnoneeabi for arm64 build hosts (#11848) | ||
|
|
f5158f50be |
feat(rp2040/rp2350): Update earlephilhower/arduino-pico to 6.1.0, bump maxgerhardt/platform-raspberrypi to latest (#11814)
* Update earlephilhower/arduino-pico to 6.1.0 * feat(rp2040/rp2350) bump platform-raspberrypi * fix(rp2040/rp2350) recover printf and scanf |