cleanup/bugfix run

This commit is contained in:
liquidraver
2026-08-23 09:45:14 +02:00
parent 5302d21801
commit 174bce80d9
10 changed files with 109 additions and 35 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ Other benefits:
| **Heltec Wireless Tracker V2** | ESP32-S3FN8 | SX1262 + KCT8103L PA/FEM | ST7735R 160x80 TFT, UC6580 GNSS, battery ADC |
| **ThinkNode M9** | ESP32-S3 | LR1110 | ST7789 320x240 TFT, CC1167Q GPS, PCF8563 RTC, buzzer, 16MB flash, 8MB PSRAM |
| **LilyGo T-Beam v1.2** | ESP32 (PICO-D4) | SX1262 | AXP2101 PMU, GNSS, USB-UART CLI |
| **TTGO LoRa32** | ESP32 (PICO-D4) | **SX1276** (loramac-node backend) | SX127x reference board, USB-UART CLI |
| **TTGO LoRa32** | ESP32 (PICO-D4) | **SX1276** (loramac-node backend) | SX127x reference board**source-only, no published firmware** |
### Other
@@ -245,11 +245,15 @@ Proposed by **bisbille** ([@bisbille](https://github.com/bisbille)) —
Housekeeping, listed for completeness — nothing here changes how a node behaves day to day.
- **The XIAO nRF54L15 now ships firmware with every release.** The board has been supported for a
while but was never part of the published build, so it had to be compiled by hand. Companion and
repeater images are now built and listed alongside every other board. Like the ME25LS02 it is a
`.hex` flashed over SWD — though the XIAO's own USB cable is enough, since it has a debug bridge
built into the board.
- **Three boards that were supported but never published now ship firmware with every release.** All
three have been buildable for a while but were missing from the published build, so they had to be
compiled by hand. Companion and repeater images are now built and listed alongside every other
board:
- **XIAO nRF54L15** — like the ME25LS02, a `.hex` flashed over SWD, though the XIAO's own USB cable
is enough since it has a debug bridge built into the board.
- **XIAO MG24** — also SWD-only, and the first Silicon Labs board to get published firmware.
- **LoRa-E5 mini** — SWD-only. No Bluetooth on this chip, so the companion talks to the app over
the USB serial port instead.
- **The Zephyr operating system underneath was updated** to a newer snapshot, and our radio patches were
reorganised on top of it: five separate SX126x patches are now one. Same behaviour, fewer things to go
wrong the next time Zephyr moves.
+2 -2
View File
@@ -1221,10 +1221,10 @@ Timeouts and deadlines that are watchdogs in everything but name:
| Mechanism | Location | Bounds |
|-----------|----------|--------|
| RX-latch payload deadline | all three custom radio paths: SX126x `patch 0013`, `lr11xx_lora.c`, `lr20xx_lora.c` (`header_seen_at_ms` + `*_max_payload_ms()`) | A `HEADER_VALID` whose packet never completes would pin the TX gate closed and silently mute the node — continuous RX has no symbol timer. Released at 255-byte airtime +25% +100 ms. See [5.2.1](#521-rx-busy-gate-tx-during-rx-prevention). |
| RX-latch payload deadline | all three custom radio paths: SX126x `patch 0003` ("Bound the lifetime of the RX-busy latch"), `lr11xx_lora.c`, `lr20xx_lora.c` (`header_seen_at_ms` + `*_max_payload_ms()`) | A `HEADER_VALID` whose packet never completes would pin the TX gate closed and silently mute the node — continuous RX has no symbol timer. Released at 255-byte airtime +25% +100 ms. See [5.2.1](#521-rx-busy-gate-tx-during-rx-prevention). |
| Stuck-DIO1 counter | `lr11xx_lora.c` and `lr20xx_lora.c` | 5 empty DIO1 cycles → hardware reset. Counting rather than timing; on the LR11xx it complements the wedge watchdog rather than replacing it. |
| CAD timeout | `Dispatcher::checkSend()` | 4 s (~20 retry attempts) → `ERR_EVENT_CAD_TIMEOUT` + `recoverRxState()`, rather than falling through to TX. See [5.2.2](#522-cad-timeout-recovery). |
| Chip-side TX timeout | SX126x `SetTx` deadline (`patch 0014`, airtime +25% +500 ms, floored at 10 s, clamped 262143 ms); LR2021 `TIMEOUT` IRQ handler | The chip stops the transmission when this fires, so a fixed value is a truncation, not a safeguard — at SF12/BW62.5 the old flat 10 s cut every packet from 76 bytes up. |
| Chip-side TX timeout | SX126x `SetTx` deadline (`patch 0003`, "Scale the chip-side Tx timeout from airtime instead of a fixed 10 s"; airtime +25% +500 ms, floored at 10 s, clamped 262143 ms); LR2021 `TIMEOUT` IRQ handler | The chip stops the transmission when this fires, so a fixed value is a truncation, not a safeguard — at SF12/BW62.5 the old flat 10 s cut every packet from 76 bytes up. |
| Serial partial-frame resync | `SerialCompanionTransport.c` (`FRAME_PARTIAL_TIMEOUT_MS`) | 2 s. Parser-level only — deliberately **not** a session or idle timeout; an idle-but-connected companion sits in `RX_IDLE` indefinitely. |
| TCP send timeout | `LinuxTCPTransport.c` | Native sim only. A peer that can't accept a frame in the window is wedged → close it, rather than hang the whole queue. |
| Bounded RXTO wait | `patches/zephyr/0010-uarte-pm-suspend-bounded-rxto-wait.patch` | `uarte_pm_suspend()` busy-waits for RXTO with no timeout upstream. Landing in the STOPRX race with bytes in flight spins forever on the main thread and wedges the entire mesh (observed: RAK3401 1W repeater on 1.16.6, CLI answering only `-> busy`). Backstop for the GPS UART PM path in [7.3](#73-gps-adaptersgps). |
+9 -2
View File
@@ -1170,8 +1170,15 @@ bool LoRaRadioBase::isReceiving()
void LoRaRadioBase::agcMaintenance()
{
/* Never mid-transmit: both operations sleep the chip. */
if (atomic_get(&_tx_active)) {
/* Never mid-transmit or mid-receive: both operations warm-sleep the
* chip, which aborts a TX and destroys an in-flight packet. The RX
* half cannot be inferred from the activity counters below those
* only move at RX_DONE/CRC_ERR, so a packet whose preamble is landing
* right now still reads as silence. isReceiving() is the latch that
* knows (HEADER_VALID promotion + preamble grace); it is the same gate
* checkSend() and the noise-floor sampler use before touching the chip.
* Bailing here just defers the work to the next maintenance pass. */
if (atomic_get(&_tx_active) || isReceiving()) {
return;
}
+17 -5
View File
@@ -63,10 +63,12 @@ static const uint32_t ntc_res[NTC_TABLE_LEN] = {
* Photocell
*
* Seeed maps the divider voltage onto 0-100 with a dead band at each end.
* LIGHT_SPAN_MV is deliberately not (LIGHT_MAX_MV - LIGHT_MIN_MV): the
* stock firmware divides the 80..2480 mV range by 2400 while subtracting
* only the 80 mV floor, and the top of the range is clamped rather than
* reached. Reproduced as-is so readings match.
* LIGHT_SPAN_MV happens to equal LIGHT_MAX_MV - LIGHT_MIN_MV today, but it is
* kept as its own constant rather than derived from them: the stock firmware
* treats the divisor as an independent number (subtract the 80 mV floor,
* divide by 2400, clamp at the top rather than reach it). Deriving it would
* let a future tweak to either endpoint silently move the scale factor away
* from what the stock firmware uses, and the readings would stop matching.
* ================================================================ */
#define LIGHT_MIN_MV 80
@@ -198,6 +200,14 @@ static int t1000e_power(const struct t1000e_config *cfg, bool on)
int rc = 0;
if (on) {
/* The rail is shared with the battery divider (one devicetree node,
* two labels: sensor_power / vbat_enable). The regulator core
* refcounts, so if another holder already has it up there is no
* off->on transition to wait out settling only costs time when we
* are the one turning it on. */
bool already_on = (cfg->power_supply != NULL) &&
regulator_is_enabled(cfg->power_supply);
if (cfg->power_supply != NULL) {
rc = regulator_enable(cfg->power_supply);
if (rc < 0) {
@@ -210,7 +220,9 @@ static int t1000e_power(const struct t1000e_config *cfg, bool on)
return rc;
}
}
k_msleep(cfg->settle_time_ms);
if (!already_on) {
k_msleep(cfg->settle_time_ms);
}
return 0;
}
+12
View File
@@ -1117,6 +1117,13 @@ bool CompanionMesh::vcontactHandleFrame(const uint8_t *data, size_t len)
if (ack == 0) ack = 1;
sendPacketSent(MSG_SEND_SENT_DIRECT, ack, 3000);
/* One slot, not a queue: a second v-contact message inside
* VCONTACT_CONFIRM_DELAY_MS would overwrite the first ack before
* its work ever ran, so that message stayed un-confirmed until the
* app hit est_timeout and resent it. Flush the pending one first
* it is already past the sub-millisecond window that made deferring
* necessary, so emitting it now is safe. */
vcontactFlushConfirm();
_vcontact_confirm_ack = ack;
k_work_reschedule(&_vcontact_confirm_work.work,
K_MSEC(VCONTACT_CONFIRM_DELAY_MS));
@@ -2169,6 +2176,11 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
_contact_iter_active = false;
cancelSyncPending();
cleanupSignState();
/* Drop a deferred v-contact confirm from the previous session: its ack
* matches an outbound message the new session never sent, so delivering
* it here would push an unmatched SEND_CONFIRMED into a fresh app. */
_vcontact_confirm_ack = 0;
k_work_cancel_delayable(&_vcontact_confirm_work.work);
/* New session: suppress v-contact notice MSG_WAITING until the initial
* sync (contacts + messages) completes at PACKET_NO_MORE_MSGS. */
+23 -10
View File
@@ -7,9 +7,15 @@
#
# Include order: prj.conf → zephcore_common.conf → esp32_common.conf → board.conf
# Board identification
CONFIG_ZEPHCORE_BOARD_NAME="TTGO LoRa32"
CONFIG_BT_DIS_MODEL_NUMBER_STR="TTGO LoRa32"
# Board identification.
# "ttgo_lora32" is the upstream Zephyr board name and stays as-is, but the name
# the node reports follows Arduino MeshCore: LilyGoTLoraBoard::getManufacturerName()
# in variants/lilygo_tlora_v2_1 returns exactly this string, so a ZephCore node
# and a stock MeshCore node identify themselves identically on the mesh.
# (MeshCore's web flasher lists the same hardware under a third spelling,
# "LilyGo LoRa32 V2.1_1.6" — irrelevant here, we publish no firmware for it.)
CONFIG_ZEPHCORE_BOARD_NAME="LILYGO T-LoRa V2.1-1.6"
CONFIG_BT_DIS_MODEL_NUMBER_STR="LILYGO T-LoRa V2.1-1.6"
# Radio: SX1276 via Zephyr loramac-node driver (not the native SX126x driver)
# zephcore_common.conf sets CONFIG_LORA_MODULE_BACKEND_NATIVE=y for SX126x boards.
@@ -26,14 +32,21 @@ CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=17
# lora_recv_async, but disable it to avoid the spurious attempt)
CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE=n
# Companion RAM budget — same constraint as the T-Beam: the classic ESP32's
# contiguous DRAM segment can't fit the default contact/queue arrays (350/256)
# alongside the BT controller heap. The T-Beam sizes (160/8/128) still
# overflow here by ~1.2 KB — the SX1276 loramac-node backend carries more
# static DRAM than the native SX126x driver — so contacts go to 150.
CONFIG_ZEPHCORE_MAX_CONTACTS=150
# Companion RAM budget — the binding constraint on this board, and tighter than
# the T-Beam's. The classic ESP32's contiguous DRAM segment cannot fit the
# default contact/queue arrays (350/256) alongside the BT controller heap, and
# this board pays for an SSD1306 display and the SX1276 loramac-node backend on
# top, neither of which the T-Beam carries.
#
# Measured, not guessed (ttgo_lora32/esp32/procpu companion, dram0_0_seg is
# 140452 B): 150/128 overflows by 32016 B. A contact costs ~188 B and an
# offline-queue slot 173 B (QueuedFrame: 1 + 172), so the two arrays are the
# whole lever. 50/32 lands at ~97% with a few KB to spare; raising either one
# spends that margin at those rates. Do not restore 150/128 — it has never
# linked on this board.
CONFIG_ZEPHCORE_MAX_CONTACTS=50
CONFIG_ZEPHCORE_MAX_CHANNELS=8
CONFIG_ZEPHCORE_OFFLINE_QUEUE_SIZE=128
CONFIG_ZEPHCORE_OFFLINE_QUEUE_SIZE=32
# Flash mode: ESP32-PICO-D4 (rev 1.0) — use DIO, not QIO.
# esp32_common.conf enables QIO for the ESP32-S3/C3/C6 boards in this repo.
+3 -1
View File
@@ -98,7 +98,9 @@ for `0x1000`, since they use simple-boot in the release build too.
### SX127x Boards (loramac-node backend)
ZephCore supports SX1272/SX1276/SX1278 via the loramac-node backend — a separate radio path from the native SX126x driver used by all other boards. The TTGO LoRa32 is the reference implementation:
ZephCore can drive SX1272/SX1276/SX1278 via the loramac-node backend — a separate radio path from the native SX126x driver used by all other boards. The TTGO LoRa32 is the only board exercising it.
**This path is source-only and unsupported.** No release publishes firmware for it, it is not in `build.sh` or the Mesh America catalog, and it has no RX duty cycle and no RX gain boost. Treat it as a starting point to maintain yourself, not as a supported target.
| Board | Build string | Flash |
|----------------|------------------------------------------------|--------------|
+16 -3
View File
@@ -49,7 +49,7 @@ heltec_wireless_tracker/esp32s3/procpu
heltec_wireless_tracker_v2/esp32s3/procpu
thinknode_m9/esp32s3/procpu
ttgo_tbeam/esp32/procpu
ttgo_lora32/esp32/procpu
ttgo_lora32/esp32/procpu # source-only, no published firmware
```
> ESP32 boards require `west blobs fetch hal_espressif` before first build.
@@ -76,8 +76,21 @@ ttgo_lora32/esp32/procpu
> SX1262 in `board.overlay`. Console/CLI are on `uart0` (onboard USB-UART).
>
> **TTGO LoRa32** (`ttgo_lora32/esp32/procpu`): classic ESP32 (PICO-D4) with
> **SX1276** — the SX127x (loramac-node backend) reference board. Console/CLI
> on `uart0`.
> **SX1276**. Console/CLI on `uart0`.
>
> **Source-only — no published firmware, and not a supported configuration.**
> It is deliberately absent from `build.sh`, the release workflow and the Mesh
> America catalog, so no release ever carries a binary for it. It is kept in the
> tree as the one exercise of the SX127x loramac-node radio path, which is old,
> lacks RX duty cycle and RX gain boost, and gets none of the work the native
> SX126x/LR11xx/LR20xx drivers do. Build it yourself if you want it; expect to
> maintain it yourself too.
>
> If you do build the companion, note it is DRAM-bound (~97% of `dram0_0_seg`)
> and capped at **50 contacts / 32 queued offline messages** — lower than any
> other companion board. The repeater has no such limit. At the 150/128 this
> board's config used to declare, it did not link at all. See `board.conf` for
> the measured budget before raising either number.
## STM32WL
+17 -6
View File
@@ -550,10 +550,14 @@ static void action_deep_sleep(void)
* because the multi-tap filter passes an unhandled raw KEY_A through per tap
* before it resolves; swallowing that would leave a double tap still acting.
*
* No expiry is needed: on every board using this UI each switch case below is
* fed by the longpress or multi-tap filter, so a wake is always followed by an
* action code that clears the flag. (The two boards with direct joystick codes
* select ZEPHCORE_UI_JOYSTICK and never reach this callback.)
* No expiry is needed, but the flag must only be armed when the wake press was
* a raw code that still has an action code coming. ZEPHCORE_UI_DESIGN_JOYSTICK
* depends on ZEPHCORE_ROLE_COMPANION, so the repeater build of a joystick board
* (wio_tracker_l1, gat562_30s both shipped as repeater artifacts) falls back
* to ZEPHCORE_UI_DESIGN_BUTTON and *does* reach this callback, with its
* joystick GPIOs wired straight to INPUT_KEY_UP/DOWN/LEFT/RIGHT/ENTER and no
* filter in between. There the wake press is already an action code and is
* consumed at the wake, so the flag stays clear and the next press acts.
*/
static bool display_woken_pending;
@@ -636,10 +640,17 @@ static void ui_input_cb(struct input_event *evt, void *user_data)
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
/* If display is off, wake it and consume the event. The action this
* press resolves to arrives later from the longpress / multi-tap filter;
* display_woken_pending makes the switch below swallow it. */
* display_woken_pending makes the switch below swallow it.
*
* Only arm that flag when this press really does resolve into a *later*
* action code, i.e. when the code we just consumed is a raw one. Boards
* that wire an action code straight to the GPIO (joystick lines on
* wio_tracker_l1 / gat562_30s in repeater builds) already consumed their
* action here arming the flag would make the next, genuinely separate
* press get swallowed instead. */
if (!mc_display_is_on()) {
mc_display_on();
display_woken_pending = true;
display_woken_pending = !is_ui_action_code(evt->code);
schedule_render();
return;
}