mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-02 00:38:50 +00:00
enable PM on ESP repeaters that support it
This commit is contained in:
@@ -9,6 +9,24 @@ env:
|
||||
python_version: 3.12
|
||||
toolchains: arm-zephyr-eabi:xtensa-espressif_esp32s3_zephyr-elf:xtensa-espressif_esp32_zephyr-elf:riscv64-zephyr-elf
|
||||
|
||||
# Mark the GitHub release as a pre-release AND skip the Mesh America catalog
|
||||
# publish. Set to "true" for a release that should not reach ordinary users
|
||||
# through apps.meshamerica.com's browser flasher; set back to "false" for a
|
||||
# normal release.
|
||||
#
|
||||
# Two separate effects, both driven from here so they cannot drift apart:
|
||||
# - the release is created with prerelease: true, so GitHub does not mark it
|
||||
# "Latest" and it is excluded from the /releases/latest API.
|
||||
# - publish-catalog is skipped entirely, so the firmware-dist branch and its
|
||||
# catalog.json are left ALONE — the configurator keeps offering whatever
|
||||
# the last non-prerelease build published. Nothing is unpublished or
|
||||
# rolled back; that branch is simply not force-pushed this time.
|
||||
# Release artifacts are still built and attached, so a manual download and
|
||||
# flash works exactly as usual.
|
||||
#
|
||||
# 1.16.8: pre-release while ESP32 light sleep is unvalidated on hardware.
|
||||
prerelease: "true"
|
||||
|
||||
jobs:
|
||||
setup-zephyr:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -228,6 +246,10 @@ jobs:
|
||||
contents: write
|
||||
outputs:
|
||||
release_tag: ${{ steps.tag.outputs.release_tag }}
|
||||
# Re-exported as a job output because the `env` context is NOT available
|
||||
# in a job-level `if:` — publish-catalog cannot read env.prerelease
|
||||
# directly and has to gate on this instead.
|
||||
prerelease: ${{ env.prerelease }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -326,6 +348,7 @@ jobs:
|
||||
body_path: ${{ steps.notes.outputs.notes_path }}
|
||||
target_commitish: ${{ github.sha }}
|
||||
draft: false
|
||||
prerelease: ${{ env.prerelease }}
|
||||
files: firmware/*
|
||||
|
||||
# Publish firmware + provider catalog for the Mesh America configurator
|
||||
@@ -335,8 +358,14 @@ jobs:
|
||||
# CORS-clean hosts (firmware via jsDelivr, catalog via raw.githubusercontent).
|
||||
# The stable catalog URL registered with Mesh America is:
|
||||
# https://raw.githubusercontent.com/<owner>/<repo>/firmware-dist/catalog.json
|
||||
#
|
||||
# Skipped entirely on a pre-release (see the `prerelease` env at the top).
|
||||
# This branch is force-pushed, so skipping is the whole mechanism: the
|
||||
# previous release's firmware and catalog.json stay exactly as they are and
|
||||
# the configurator keeps offering that version.
|
||||
publish-catalog:
|
||||
needs: create-release
|
||||
if: needs.create-release.outputs.prerelease != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
# ZephCore 1.16.8-zephcore
|
||||
|
||||
> [!CAUTION]
|
||||
> **This is a pre-release.** ESP32 light sleep is enabled by default on Heltec repeaters and has not
|
||||
> been validated on hardware yet — see the light-sleep section for what to check and how to build it
|
||||
> out. It is deliberately **not published to the apps.meshamerica.com configurator**, which keeps
|
||||
> offering v1.16.7; flash this one manually from the assets below if you want to help test it.
|
||||
> The nRF based boards should be fine.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> ## Before you upgrade
|
||||
>
|
||||
> **From v1.16.5 / v1.16.6 / v1.16.7** — clean flash, no re-bond, bonds and data survive.
|
||||
>
|
||||
> **From v1.16.2 / v1.16.3 / v1.16.4** — clean flash, bonds and data survive. If you are on an
|
||||
> **ESP32-S3 / ESP32-C board** and have *not* yet taken the v1.16.5 update, you still owe the one-time
|
||||
> serial reflash: the app moved to flash offset `0x10000` in v1.16.5, and a board on the old layout
|
||||
> cannot cross that update over WiFi-OTA or the browser flasher — **flash the `-merged.bin` once over
|
||||
> USB/serial.** Affected boards: *Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO
|
||||
> ESP32-S3 / C3 / C6, LilyGo T-Lora C6, ThinkNode M9.* Identity, contacts, channels, prefs, and BLE
|
||||
> bonds are preserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native
|
||||
> Linux are unaffected.
|
||||
>
|
||||
> **From v1.16.1 or older** — flash it; on first boot it clears BLE bonds automatically (identity,
|
||||
> contacts, channels, prefs preserved). Re-bond your phone/desktop once.
|
||||
>
|
||||
> **Coming from Arduino MeshCore** — flash it; auto-formats on first boot (new identity, clean storage).
|
||||
>
|
||||
> **Heltec ESP32 repeaters now light-sleep**, and their USB CLI stops answering 10 minutes after boot
|
||||
> as a result. Reconnecting your terminal normally resets the node and gives you another 10 minutes;
|
||||
> if it doesn't, power-cycle. Details in the light-sleep section below.
|
||||
>
|
||||
> **One CLI rename:** `set/get cad.probe.interval` is now `set/get probe.interval` — the same setting,
|
||||
> renamed because one measurement now feeds both the noise floor and the CAD probe. Your stored value
|
||||
> carries over; only the command name changed. `set/get agc.reset.interval` is gone (see below).
|
||||
>
|
||||
> Take this with a grain of salt — try the formatters if anything anomalous happens with your node.
|
||||
|
||||
---
|
||||
|
||||
A **power and radio-measurement release**. Repeaters no longer wake on a fixed 5 s tick — they sleep
|
||||
until their next real deadline. Every ESP32 build drops to 80 MHz, and Heltec repeaters now light-sleep
|
||||
between packets. The noise-floor sampler and CAD probe were unified into one measurement, the
|
||||
SX126x RSSI/AGC calibration is now band-correct, and the observer role got a batch of fixes for settings
|
||||
that silently never reached the hardware.
|
||||
|
||||
## Highlights
|
||||
|
||||
### Repeaters are now deadline-driven — the periodic housekeeping tick is gone
|
||||
|
||||
Every repeater woke five times a second-and-a-bit forever: a fixed 5 s "housekeeping" timer that ran the
|
||||
noise-floor sample, the CAD probe, advert timers and the tempradio revert. None of that actually needs a
|
||||
poll — each item is either a deadline or a sampler with its own interval.
|
||||
|
||||
The new `mesh/Maintenance.h` contract lets the event loop ask *"when does maintenance next have work?"*
|
||||
and arm **one one-shot wake for exactly that moment**. An idle repeater now sleeps until its soonest real
|
||||
deadline instead of on a fixed cadence. A hard ceiling (`CONFIG_ZEPHCORE_MAINTENANCE_BACKSTOP_MS`,
|
||||
default **60 s**) clamps the wake as a safety net against a missed deadline — it should never bind in
|
||||
normal operation.
|
||||
|
||||
Companions and room servers keep their periodic tick deliberately: the companion block contains genuine
|
||||
pollers (contact-dump progress, BLE advertising watchdog) with no deadline to arm, and the room server is
|
||||
paced by its own 500 ms push timer regardless.
|
||||
|
||||
### ESP32 power: 80 MHz everywhere, and light sleep on Heltec repeaters
|
||||
|
||||
Espressif parts have no equivalent of the nRF52's "idle at microamps in WFI" — `WAITI` gates the core
|
||||
clock but leaves PLL, peripherals and RAM powered, so an idle ESP32 sits in the **tens of mA**. CPU
|
||||
frequency is therefore a first-order term in a battery node's draw, and light sleep (sub-mA) is what
|
||||
removes the rest.
|
||||
|
||||
- **Every ESP32 build is now pulled to 80 MHz**, matching what Arduino MeshCore ships (new
|
||||
hal-espressif patch `0002` — upstream omits 80 from its DT map). **Observers are the exception** and
|
||||
keep the SoC maximum (240 MHz on S3/classic, 160 MHz on C3/C6): they run a full WiFi + TLS + MQTT
|
||||
stack and are mains- or big-panel powered, so throughput matters more than current.
|
||||
- **Light sleep is now enabled automatically** for **repeater** builds on **Heltec V3, V4, V4.3,
|
||||
Wireless Tracker and Wireless Tracker V2**. Patch `0012` marks the LoRa DIO1 line as a wakeup source,
|
||||
so a received packet wakes the SoC — the node sleeps between packets and deadlines and wakes for real
|
||||
work. Combined with the deadline-driven change above, an idle repeater now spends most of its life
|
||||
asleep instead of waking five times a second at 240 MHz.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **The USB CLI stops answering 10 minutes after boot on these boards.** Nothing arms a UART wake
|
||||
> source on ESP32, so characters typed at a sleeping node are dropped. To make configuration reliable,
|
||||
> light sleep is **blocked for the first 10 minutes after every boot**
|
||||
> (`CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS`) — and because these boards' USB bridge drives EN from DTR,
|
||||
> **connecting a terminal normally resets the node and re-arms that window.** If your terminal doesn't
|
||||
> toggle DTR, power-cycle the node to get a console. Remote admin over LoRa is unaffected.
|
||||
|
||||
Console output itself is safe: a new `pm_notifier` flushes the console UART to idle before every sleep,
|
||||
which Zephyr's PM path does not do — without it, lines would be cut mid-character. Packet-logging and
|
||||
debug builds are therefore fine to run with sleep enabled.
|
||||
|
||||
Anything that can't tolerate sleeping holds a `pm_policy` lock instead of being excluded by hand: a
|
||||
**WiFi OTA session** holds one for its duration, an **uplink/observer WiFi station** holds one
|
||||
permanently (the association would drop), and **GPS holds one while the module is powered**, since the
|
||||
GNSS UART is not a wake source and inbound NMEA would be lost. Under a GPS duty cycle the lock is only
|
||||
held during the awake phase.
|
||||
|
||||
> [!WARNING]
|
||||
> **Not yet validated on hardware**, and now on by default for the boards above — so this is the first
|
||||
> thing to suspect if an allowlisted board misbehaves. The failure mode to rule out is a **deaf
|
||||
> repeater**: check the boot log for `Pin N is not wakeup capable`, confirm the node still receives
|
||||
> packets, and only then measure current. Rebuild with `-DCONFIG_PM=n` to take it back out.
|
||||
>
|
||||
> Boards **deliberately excluded** because DIO1 can't wake the SoC: XIAO ESP32-S3 (GPIO39), Station G2
|
||||
> (GPIO48), ThinkNode M9 (GPIO42), LilyGo T-Lora C6 (GPIO23, outside the C6's LP range), and
|
||||
> TTGO LoRa32 (SX127x — patch 0012 only arms the sx126x driver). **Companion builds never get it** on
|
||||
> any board: the Espressif HCI driver takes no `pm_policy` locks, so the SoC could sleep mid-advertising
|
||||
> or mid-connection. Passing `pm_esp32.conf` by hand still works for testing another board.
|
||||
|
||||
### One measurement now serves both the noise floor and CAD — `set probe.interval`
|
||||
|
||||
The noise-floor sampler and the CAD calibration probe used to be two separate things riding the same
|
||||
5 s tick. They are now a **single periodic radio measurement**: one median-of-8 RSSI reading that the
|
||||
CAD probe consumes rather than measuring again.
|
||||
|
||||
- `set cad.probe.interval <sec>` → **`set probe.interval <sec>`** (default still **15**, `0` still
|
||||
disables CAD probing). It is now also the noise-floor sampling rate, and it sets how often an idle
|
||||
repeater wakes.
|
||||
- New `CONFIG_ZEPHCORE_NOISE_FLOOR_INTERVAL_MS` is the build-time fallback used when probing is off —
|
||||
15 s on repeaters, 5 s on companions/room servers (which wake on their tick anyway).
|
||||
- A sample blocked by TX, mid-RX or a duty-cycle sleep window retries on a 5 s deadline (max 2 retries)
|
||||
instead of losing a full interval — deliberately reproducing the old retry grid, which matters under
|
||||
RX duty cycle where blocked attempts are the norm.
|
||||
|
||||
**RSSI reads are now correctly spaced.** A median only rejects outliers if the reads are independent,
|
||||
and the chip refreshes RSSI once per averaging window (~16 µs at BW 62.5, ~134 µs at BW 7.8) — reads
|
||||
issued faster return the *same* underlying sample and the median collapses to a single read. The spacing
|
||||
and the RX-entry settle delay are now derived from the SX1261/2 datasheet (rev 2.2, Table 13-82).
|
||||
|
||||
`get cad` reports the result so the assumption stays falsifiable on real hardware: the old `iv:` field is
|
||||
replaced by **`sp:<mean-spread>/<zero-spread %>(<count>)`**. A **non-zero mean proves the reads are
|
||||
independent**, however high the zero-spread share climbs — only mean `0.0` with a high share indicts the
|
||||
sampler. Measured on air at BW 62.5: `0.6/90%` quiet, `0.9/84%` with the floor at −103 dBm.
|
||||
|
||||
### Fixed: SX126x RSSI/AGC calibration used the wrong band on sub-GHz builds
|
||||
|
||||
Datasheet rev 2.2 (§6.1.6, new in Dec 2024) documents that the SX126x RSSI/AGC calibration registers are
|
||||
**band-specific**. ZephCore applied the 868–915 MHz set unconditionally, so every 433 MHz build was
|
||||
running the wrong AGC gain selection — that is **packet loss, not just wrong telemetry**. New patch
|
||||
`0011` adds the per-band table plus the shared-register `0x08AC` / `AgcSensiAdjust` fix.
|
||||
|
||||
### RAK3401 1-watt: external-PA RX register patch enabled
|
||||
|
||||
The undocumented register `0x8B5` RX improvement (`CONFIG_ZEPHCORE_SX126X_HELTEC_REG_PATCH`) is not
|
||||
Heltec-specific despite its name — upstream MeshCore also enables it for the RAK3401's SKY66122 FEM. It
|
||||
is now on for `rak3401_1watt`, and the Kconfig help was corrected to say "boards with an external PA/FEM
|
||||
in the RX path."
|
||||
|
||||
### The AGC-reset workaround is gone
|
||||
|
||||
`set/get agc.reset.interval` has been **removed** — it now replies `use rxduty instead`, and `set` is
|
||||
accepted-and-ignored for prefs compatibility. It was a periodic blind AGC kick that only existed because
|
||||
of the fixed tick; the in-driver RX restore and duty-cycle re-arm paths handle the real cases.
|
||||
|
||||
### Observer role fixes
|
||||
|
||||
Several observer settings looked accepted and then never reached the hardware:
|
||||
|
||||
- **`set freq/sf/bw/cr` never reached the radio.** The radio was bound to a static placeholder
|
||||
`NodePrefs` that is never loaded from flash, so it stayed on compiled-in defaults forever — the value
|
||||
was written to flash and echoed back by the CLI and MQTT, then "reverted" on reboot. The radio is now
|
||||
bound to the mesh's live prefs before `begin()`, matching repeater and room server.
|
||||
- **First boot discarded the observer defaults.** The store is shared with the repeater, and its no-file
|
||||
branch re-applies *repeater* defaults over whatever the caller passed in. The observer now detects
|
||||
first boot itself and re-applies (and persists) its own `cr` / `tx_power` once.
|
||||
- **`set name Observer` reverted on every reboot** — "Observer" was in the regenerate-if-unset list. It
|
||||
is a name a user can legitimately set, so it is no longer treated as unset.
|
||||
- **`set freq` / `set bw` now refuse values the loader will reject** (300–1000 MHz, 7–500 kHz). Out-of-
|
||||
range values used to save fine, then get silently reset to defaults on the next boot — taking
|
||||
`bw`/`sf`/`cr`/`tx_power` with them.
|
||||
- **USB-console routing fixed** on Station G2 and XIAO ESP32-S3, so non-companion roles keep their
|
||||
console and serial CLI.
|
||||
- The observer `help` banner was reorganised (network / node / radio / query) and now lists the query
|
||||
commands that already existed.
|
||||
|
||||
## Upstream sync
|
||||
|
||||
Synced with Arduino MeshCore `dev`:
|
||||
|
||||
- **Room server: `room.post <message>`** — post to the shared room as the server itself. Pushed to
|
||||
clients like any other post.
|
||||
- **Fixed: an empty guest password granted read+write to anyone.** An unset `guest.password` (the
|
||||
default) now *disables* guest login instead of matching an empty submitted password. To run an open
|
||||
room server use `set allow.read.only on`, which grants read-only. Both password compares still run
|
||||
unconditionally, so timing behaviour is unchanged.
|
||||
- **Companion telemetry: MCU die temperature is now reported under base permission**, matching Arduino
|
||||
MeshCore and ZephCore's own repeater/room-server telemetry. It is still suppressed when an external
|
||||
sensor already supplied a temperature, so a node never emits two.
|
||||
- **Advert names are truncated on a UTF-8 code-point boundary** — a name whose multi-byte character
|
||||
straddled the advert size limit used to go out mangled.
|
||||
- **ThinkNode M3: GPS reset pin left floating.** Driving the REINIT pin (P0.25) stops the module
|
||||
producing NMEA on this hardware; upstream fixed it by never driving it. The `gps-reset` alias is gone,
|
||||
which compiles out every reset path — `GPS_EN` alone handles wake/standby.
|
||||
- **New patch `0013` bounds the SX126x RX-busy latch with a payload deadline.** Continuous RX has no
|
||||
symbol timer, so a `HEADER_VALID` whose packet never completes would pin the TX gate open forever and
|
||||
silently mute the node. The bound is max-length airtime at the current SF/BW +25% +100 ms —
|
||||
deliberately generous, a stuck-state safety net rather than a timing mechanism.
|
||||
|
||||
## Other changes
|
||||
|
||||
- **west manifest bumped** (routine pinned-tree advance).
|
||||
- `ARCHITECTURE.md`, `ADAPTIVE_CAD.md` and `Repeater_CLI_commands.md` updated for the probe unification
|
||||
and the removed commands.
|
||||
|
||||
## Recommended upgrade checklist
|
||||
|
||||
1. **Heltec V3 / V4 / V4.3 / Wireless Tracker repeater?** You get 80 MHz *and* light sleep
|
||||
automatically. Do your configuration in the first 10 minutes after boot, or reconnect your terminal
|
||||
(which resets the node) to get the window back.
|
||||
2. **Running a 433 MHz SX126x node?** Update — the RSSI/AGC calibration band fix affects reception, not
|
||||
just readings.
|
||||
3. **Running an observer?** Update, then re-apply your radio settings once — they may never have reached
|
||||
the hardware before.
|
||||
4. **Scripts or notes using `cad.probe.interval` / `agc.reset.interval`?** Update them to
|
||||
`probe.interval`; the AGC command is gone.
|
||||
5. **ESP32-S3 / ESP32-C boards that skipped v1.16.5:** flash `-merged.bin` once over USB/serial. Data survives.
|
||||
6. **Everything else, from v1.16.2 onward:** just flash — bonds and data survive.
|
||||
7. **From v1.16.1 or older:** just flash — self-migrates on first boot; re-bond once.
|
||||
8. **From Official MeshCore:** just flash — auto-formats on first boot.
|
||||
9. **Anything odd?** Format first.
|
||||
+41
-1
@@ -369,6 +369,40 @@ if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common" AND NOT EXTRA_CONF_FILE MATCHES
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ========== ESP32 light sleep (allowlisted repeater boards) ==========
|
||||
# 80 MHz above cuts the idle draw; light sleep is what removes it. See
|
||||
# boards/common/pm_esp32.conf for the mechanism and its requirements — this
|
||||
# block is only the policy that decides who gets it automatically.
|
||||
#
|
||||
# REPEATERS ONLY, and the gate is deliberately the same one WiFi OTA uses
|
||||
# below: the Espressif HCI driver takes no pm_policy locks, so a companion
|
||||
# build would light-sleep mid-advertising or mid-connection. repeater.conf
|
||||
# sets CONFIG_BT=n, which removes the problem instead of racing it.
|
||||
#
|
||||
# BOARD ALLOWLIST, not a platform check. Light sleep is only safe where DIO1
|
||||
# can wake the SoC, i.e. an RTC-capable pin (GPIO 0-21 on the S3). Every board
|
||||
# below wires DIO1 to GPIO14 and consoles on uart0 through a USB-serial bridge,
|
||||
# so both the packet wake and the console flush land correctly. Deliberately
|
||||
# ABSENT and must stay absent unless their wiring changes:
|
||||
# xiao_esp32s3 (DIO1=GPIO39), station_g2 (GPIO48), thinknode_m9 (GPIO42),
|
||||
# lilygo_tlora_c6 (GPIO23, outside the C6's LP range 0-7)
|
||||
# -> cannot wake on a received packet; the node would go deaf while
|
||||
# looking excellent on a current meter.
|
||||
# ttgo_lora32 -> SX127x; patches/zephyr/0012 arms DIO1 in the sx126x driver
|
||||
# only, so nothing marks the pin as a wake source.
|
||||
# Boards with a native USB Serial/JTAG console are also unsuitable: the USB
|
||||
# peripheral drops off the bus in light sleep and there is no USJ wake source
|
||||
# in the HAL (only UART0/1/2 and LP-UART).
|
||||
if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common" AND EXTRA_CONF_FILE MATCHES "repeater"
|
||||
AND BOARD MATCHES "heltec_wifi_lora32_v3|heltec_wifi_lora32_v4|heltec_wireless_tracker")
|
||||
set(ZEPHCORE_PM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/pm_esp32.conf")
|
||||
if(EXISTS ${ZEPHCORE_PM_CONF})
|
||||
list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_PM_CONF})
|
||||
zephcore_auto_pair_overlay("${ZEPHCORE_PM_CONF}")
|
||||
message(STATUS " ESP32 light sleep: auto-enabled (DIO1 wake-capable repeater board)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common" AND EXTRA_CONF_FILE MATCHES "repeater"
|
||||
AND NOT BOARD MATCHES "/esp32/")
|
||||
set(ZEPHCORE_WIFI_OTA_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/wifi_ota.conf")
|
||||
@@ -491,7 +525,7 @@ add_definitions(-DFIRMWARE_BUILD_EPOCH=${ZEPHCORE_BUILD_EPOCH}u)
|
||||
# release is tagged/named, and what the Mesh America catalog uses as its version
|
||||
# key — so the configurator can match a running device against the catalog. Keep
|
||||
# all four identical; the release workflow reads this value directly.
|
||||
set(ZEPHCORE_FIRMWARE_VERSION "1.16.7-zephcore")
|
||||
set(ZEPHCORE_FIRMWARE_VERSION "1.16.8-zephcore")
|
||||
add_definitions(-DFIRMWARE_VERSION="${ZEPHCORE_FIRMWARE_VERSION}")
|
||||
|
||||
add_subdirectory(lib/monocypher)
|
||||
@@ -607,6 +641,12 @@ if(CONFIG_SOC_FAMILY_ESPRESSIF_ESP32)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ESP32 light-sleep console guards — TX flush before sleep + post-boot awake
|
||||
# window. Only meaningful when CONFIG_PM is on (boards/common/pm_esp32.conf).
|
||||
if(CONFIG_SOC_FAMILY_ESPRESSIF_ESP32 AND CONFIG_PM)
|
||||
target_sources(app PRIVATE helpers/pm_esp32_console.c)
|
||||
endif()
|
||||
|
||||
# Native-Linux (native_sim) runtime setup: force real-time clock mode so the
|
||||
# simulated clock tracks wall time (required for the real SX126x radio's
|
||||
# BUSY/DIO1 timing). Bakes in the equivalent of the --rt command-line flag.
|
||||
|
||||
@@ -436,6 +436,45 @@ config ZEPHCORE_NOISE_FLOOR_INTERVAL_MS
|
||||
30000, ~4 min and ~8 min. Past 15000 the CAD probe becomes the binding
|
||||
deadline instead, so raise both or neither.
|
||||
|
||||
config ZEPHCORE_PM_BOOT_AWAKE_MS
|
||||
int "Block light sleep for this long after boot (ms)"
|
||||
depends on PM && SOC_FAMILY_ESPRESSIF_ESP32
|
||||
default 600000
|
||||
range 0 3600000
|
||||
help
|
||||
How long after boot the SoC refuses to light-sleep, so the USB console
|
||||
is guaranteed reachable for a configuration session.
|
||||
|
||||
This exists because nothing arms a UART wake source on ESP32 — see
|
||||
helpers/pm_esp32_console.c. Once the window closes, characters typed at
|
||||
a sleeping node are DROPPED and the CLI stops answering over USB until
|
||||
the board resets. In practice connecting a terminal usually does reset
|
||||
it (the USB bridge drives EN from DTR), which re-arms the window.
|
||||
|
||||
0 disables the window entirely — the node may sleep immediately after
|
||||
boot, and the USB console becomes unusable except in the instants it
|
||||
happens to be awake. Do not set 0 on a board you still need to
|
||||
configure.
|
||||
|
||||
Raising it costs only the extra awake time at the start of each boot.
|
||||
|
||||
config ZEPHCORE_PM_CONSOLE_UART_NUM
|
||||
int "Console UART index flushed before light sleep"
|
||||
depends on PM && SOC_FAMILY_ESPRESSIF_ESP32
|
||||
default 0
|
||||
range 0 2
|
||||
help
|
||||
Hardware UART index (not a DT node) passed to
|
||||
esp_rom_uart_tx_wait_idle() before entering light sleep, so a line in
|
||||
the TX FIFO is not cut off mid-character by the clock stopping.
|
||||
|
||||
0 is correct for every board this is currently enabled on: their
|
||||
console is uart0, whether via a USB-serial bridge (Heltec V3/V4/V4.3)
|
||||
or the ROM-default pads. Change it only if a board's console is moved
|
||||
to another hardware UART — a wrong index does not fail the build, it
|
||||
just silently flushes the wrong peripheral and lets the console
|
||||
truncate again.
|
||||
|
||||
menu "Radio Configuration"
|
||||
|
||||
choice ZEPHCORE_RADIO_TYPE
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "ZephyrGPSManager.h"
|
||||
#include "../../helpers/pm_sleep_guard.h"
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
@@ -1513,6 +1514,15 @@ void gps_enable(bool enable)
|
||||
if (enable) {
|
||||
LOG_INF("GPS enabled - starting acquisition");
|
||||
|
||||
/* Block SoC light sleep for as long as the module is powered.
|
||||
* The GNSS UART is not a wake source, so a sleeping SoC drops
|
||||
* inbound NMEA outright — sentences would be lost mid-stream and
|
||||
* a fix would never converge. Balanced by the put in the disable
|
||||
* branch; the early return above keeps the pair 1:1, and under a
|
||||
* GPS duty cycle the lock is only held during the awake phase.
|
||||
* Compiles to nothing without CONFIG_PM. */
|
||||
zc_pm_block_sleep();
|
||||
|
||||
/* Start acquiring immediately (no delay for first wake) */
|
||||
gps_current_state = GPS_STATE_ACQUIRING;
|
||||
consecutive_good_fixes = 0;
|
||||
@@ -1543,6 +1553,9 @@ void gps_enable(bool enable)
|
||||
} else {
|
||||
LOG_INF("GPS disabled - canceling timers and powering off");
|
||||
|
||||
/* Matches the block taken in the enable branch. */
|
||||
zc_pm_unblock_sleep();
|
||||
|
||||
/* Cancel any pending work */
|
||||
k_work_cancel_delayable(&gps_wake_work);
|
||||
k_work_cancel_delayable(&gps_timeout_work);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "wifi_ota.h"
|
||||
#include "ota_page.h"
|
||||
#include "../../helpers/pm_sleep_guard.h"
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/net/wifi_mgmt.h>
|
||||
@@ -459,10 +460,18 @@ int wifi_ota_start(const char *node_name, const char *board_name)
|
||||
node_name ? node_name : "Unknown",
|
||||
board_name ? board_name : "Unknown");
|
||||
|
||||
/* Hold off light sleep for the whole session. The WiFi stack and the
|
||||
* HTTP upload have no PM coordination here, so a node that slept
|
||||
* mid-transfer would drop the client's connection. Released in
|
||||
* wifi_ota_stop(); the post-upload reboot path also passes through
|
||||
* there, and a reboot would clear the lock regardless. */
|
||||
zc_pm_block_sleep();
|
||||
|
||||
/* Start WiFi AP */
|
||||
int ret = wifi_ap_start();
|
||||
|
||||
if (ret) {
|
||||
zc_pm_unblock_sleep();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -472,6 +481,7 @@ int wifi_ota_start(const char *node_name, const char *board_name)
|
||||
if (ret) {
|
||||
LOG_ERR("HTTP server start failed: %d", ret);
|
||||
wifi_ap_stop();
|
||||
zc_pm_unblock_sleep();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -505,6 +515,11 @@ int wifi_ota_stop(void)
|
||||
flash_ctx_initialized = false;
|
||||
total_bytes_received = 0;
|
||||
|
||||
/* Matches the lock taken in wifi_ota_start(). The early return above
|
||||
* (!ota_active) is why this sits after the flag check — an unbalanced
|
||||
* put would drop someone else's lock. */
|
||||
zc_pm_unblock_sleep();
|
||||
|
||||
LOG_INF("OTA server stopped");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "ZephyrWiFiStation.h"
|
||||
#include "observer_creds.h"
|
||||
#include "../../helpers/pm_sleep_guard.h"
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/net/net_if.h>
|
||||
@@ -182,6 +183,13 @@ void zc_wifi_station_start(const struct ObserverCreds *creds,
|
||||
s_wifi_link_up = false;
|
||||
s_wifi_ready = false;
|
||||
|
||||
/* Permanent, and intentionally never released: there is no
|
||||
* zc_wifi_station_stop() — once an uplink or observer node associates it
|
||||
* stays associated for the life of the boot. Light sleep would drop the
|
||||
* association (nothing here coordinates with the WiFi modem's own sleep),
|
||||
* so a node built with WiFi simply does not light-sleep. */
|
||||
zc_pm_block_sleep();
|
||||
|
||||
/* Register WiFi event callback */
|
||||
net_mgmt_init_event_callback(&wifi_cb, wifi_event_handler,
|
||||
NET_EVENT_WIFI_CONNECT_RESULT |
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
# ESP32 system power management — light sleep between events.
|
||||
#
|
||||
# AUTO-ENABLED for repeater builds on the DIO1-wake-capable Heltec boards; see
|
||||
# the allowlist block in CMakeLists.txt for who qualifies and why the others
|
||||
# must not. Passing it by hand is still supported for testing another board:
|
||||
#
|
||||
# west build -b heltec_wifi_lora32_v3/esp32s3/procpu zephcore --pristine -- \
|
||||
# -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/pm_esp32.conf"
|
||||
#
|
||||
# OPT-IN, and deliberately not enabled by default. Read this before shipping it.
|
||||
# CONSOLE BEHAVIOUR — helpers/pm_esp32_console.c does two things without which
|
||||
# light sleep makes a node unusable: it flushes the console UART before every
|
||||
# sleep (Zephyr's PM path does not, so lines would be cut mid-character), and
|
||||
# it blocks sleep for ZEPHCORE_PM_BOOT_AWAKE_MS (default 10 min) after boot so
|
||||
# there is a guaranteed window to configure the node. AFTER THAT WINDOW THE USB
|
||||
# CLI STOPS ANSWERING: nothing arms a UART wake source, so typed characters are
|
||||
# dropped. Connecting a terminal usually resets the board (the USB bridge
|
||||
# drives EN from DTR) which re-arms the window — but a terminal that does not
|
||||
# toggle DTR needs a manual power cycle. Remote admin over LoRa is unaffected.
|
||||
#
|
||||
# WiFi builds hold a pm_policy lock and never light-sleep at all — the OTA
|
||||
# session takes one for its duration (adapters/ota/wifi_ota.c) and an uplink or
|
||||
# observer station takes one permanently (adapters/wifi/ZephyrWiFiStation.c).
|
||||
#
|
||||
# What it buys: Espressif parts have no equivalent of the nRF52's "idle at
|
||||
# microamps in WFI" — WAITI gates the core clock but leaves PLL, peripherals
|
||||
@@ -23,7 +39,10 @@
|
||||
#
|
||||
# OK heltec_wifi_lora32_v3 / v4 / v43, heltec_wireless_tracker(_v2)
|
||||
# -> DIO1 = GPIO14
|
||||
# NOT OK xiao_esp32s3 (GPIO39), station_g2 (GPIO48), thinknode_m9 (GPIO42)
|
||||
# NOT OK xiao_esp32s3 (GPIO39), station_g2 (GPIO48), thinknode_m9 (GPIO42),
|
||||
# lilygo_tlora_c6 (GPIO23, outside the C6's LP range 0-7)
|
||||
# N/A ttgo_lora32 -> SX127x; patch 0012 arms DIO1 in the sx126x driver
|
||||
# only, so nothing marks the pin as a wake source at all
|
||||
#
|
||||
# On a NOT-OK board the driver logs "Pin N is not wakeup capable" at boot and
|
||||
# the node will light-sleep straight through inbound packets while looking
|
||||
@@ -43,9 +62,12 @@
|
||||
# and announce elapsed ticks, so k_uptime_get() and every maintenance deadline
|
||||
# stay correct.
|
||||
#
|
||||
# UNVALIDATED ON HARDWARE. Verify in this order: (1) no "not wakeup capable"
|
||||
# line at boot, (2) the node still receives packets, (3) only then measure
|
||||
# current. A deaf repeater is the expected failure and it is not obvious.
|
||||
# NOT YET VALIDATED ON HARDWARE — and it is now auto-enabled, so this is the
|
||||
# first thing to check if an allowlisted board misbehaves. Verify in this
|
||||
# order: (1) no "not wakeup capable" line at boot, (2) the node still receives
|
||||
# packets, (3) only then measure current. A deaf repeater is the expected
|
||||
# failure and it is not obvious from the outside. To rule it out on a suspect
|
||||
# unit, rebuild with -DCONFIG_PM=n.
|
||||
|
||||
CONFIG_PM=y
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ CONFIG_BT_DIS_FW_REV=y
|
||||
# of truth for the C side, injected as -DFIRMWARE_VERSION). This Kconfig value can't
|
||||
# read a C macro, so it must be bumped here too. Phones that read DIS and phones
|
||||
# that query CMD_DEVICE_QUERY should see the same version.
|
||||
CONFIG_BT_DIS_FW_REV_STR="1.16.7-zephcore"
|
||||
CONFIG_BT_DIS_FW_REV_STR="1.16.8-zephcore"
|
||||
CONFIG_BT_DIS_SW_REV=y
|
||||
CONFIG_BT_DIS_SW_REV_STR="Zephyr"
|
||||
CONFIG_BT_DIS_PNP=n
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* ESP32 light-sleep console guards.
|
||||
*
|
||||
* Two independent problems sit between ESP32 light sleep and a usable serial
|
||||
* console. This file solves both; neither fix substitutes for the other.
|
||||
*
|
||||
* 1. TRUNCATED OUTPUT. soc/espressif/common/power.c calls
|
||||
* esp_light_sleep_start() with no wait for the console UART to drain — the
|
||||
* TX-idle flush that ESP-IDF performs lives behind its own console Kconfig,
|
||||
* which is not part of a Zephyr build. Sleeping with bytes still in the
|
||||
* FIFO cuts the line mid-character. A pm_notifier's state_entry callback
|
||||
* runs immediately before pm_state_set() (subsys/pm/pm.c), which is exactly
|
||||
* the right moment to poll the UART to idle.
|
||||
*
|
||||
* 2. UNREACHABLE INPUT. Nothing arms a UART wake source — Zephyr's PM path
|
||||
* never calls esp_sleep_enable_uart_wakeup(), and on the boards this runs on
|
||||
* the console RX pad (GPIO44 on an S3 uart0) is outside the RTC range that
|
||||
* could carry a GPIO wake instead. Characters typed at a sleeping node are
|
||||
* therefore dropped, and no amount of TX handling changes that.
|
||||
*
|
||||
* The answer here is a boot window rather than a wake source: sleep is
|
||||
* blocked outright for ZEPHCORE_PM_BOOT_AWAKE_MS after boot, so a node is
|
||||
* always reachable for that long. This works better in practice than it
|
||||
* sounds, because the USB bridge on these boards drives EN from DTR — a
|
||||
* terminal that asserts DTR on open resets the board, which re-arms the
|
||||
* window at the moment someone connects. A terminal that does not toggle
|
||||
* DTR gets no window, and the node has to be power-cycled to answer over
|
||||
* USB; that is the known limitation of this approach. Arming
|
||||
* esp_sleep_enable_uart_wakeup() and re-taking the lock on console activity
|
||||
* would remove it, at the cost of the first keystroke (the hardware
|
||||
* consumes it as the wake trigger).
|
||||
*
|
||||
* Remote admin over LoRa is unaffected by any of this: DIO1 is armed as a wake
|
||||
* source by patches/zephyr/0012 and wakes the SoC on a received packet.
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/pm/pm.h>
|
||||
|
||||
#include <esp_rom_uart.h>
|
||||
|
||||
#include "pm_sleep_guard.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(zephcore_pm, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
|
||||
|
||||
/* ========== 1. Flush the console before every sleep ========== */
|
||||
|
||||
static void pm_console_flush(enum pm_state state)
|
||||
{
|
||||
if (state != PM_STATE_STANDBY) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Polls a status register until the shifter empties — no locking, so it
|
||||
* is safe in this context (called with the scheduler locked). Bounded
|
||||
* by the FIFO depth at the configured baud: ~11 ms worst case for a full
|
||||
* 128-byte FIFO at 115200, and normally microseconds. */
|
||||
esp_rom_uart_tx_wait_idle(CONFIG_ZEPHCORE_PM_CONSOLE_UART_NUM);
|
||||
}
|
||||
|
||||
static struct pm_notifier console_notifier = {
|
||||
.state_entry = pm_console_flush,
|
||||
};
|
||||
|
||||
/* ========== 2. Keep the node awake for a window after boot ========== */
|
||||
|
||||
#if CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS > 0
|
||||
|
||||
static void boot_window_expired(struct k_work *work)
|
||||
{
|
||||
ARG_UNUSED(work);
|
||||
|
||||
zc_pm_unblock_sleep();
|
||||
LOG_INF("boot window elapsed — light sleep enabled "
|
||||
"(USB console answers again after a reset)");
|
||||
}
|
||||
|
||||
static K_WORK_DELAYABLE_DEFINE(boot_window_work, boot_window_expired);
|
||||
|
||||
#endif /* CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS > 0 */
|
||||
|
||||
static int pm_console_init(void)
|
||||
{
|
||||
pm_notifier_register(&console_notifier);
|
||||
|
||||
#if CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS > 0
|
||||
/* Taken here rather than released here: the lock is held from init and
|
||||
* dropped by the work item, so there is no window at startup in which
|
||||
* the node could sleep before the guard is in place. */
|
||||
zc_pm_block_sleep();
|
||||
k_work_schedule(&boot_window_work,
|
||||
K_MSEC(CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS));
|
||||
|
||||
LOG_INF("light sleep deferred %d ms (console configuration window)",
|
||||
CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* POST_KERNEL: needs the kernel work queue, and must be in place before the
|
||||
* application can idle long enough to sleep. */
|
||||
SYS_INIT(pm_console_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Sleep guard — block SoC light sleep across a critical stretch.
|
||||
*
|
||||
* Zephyr's policy locks are reference-counted, so every zc_pm_block_sleep()
|
||||
* needs exactly one matching zc_pm_unblock_sleep(); nested holders compose.
|
||||
* While at least one lock is held the idle path skips PM_STATE_STANDBY and the
|
||||
* SoC idles normally instead — device power management is untouched either way.
|
||||
*
|
||||
* Both calls compile to nothing without CONFIG_PM, so callers do not need to
|
||||
* guard their own use sites.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(CONFIG_PM)
|
||||
|
||||
#include <zephyr/pm/policy.h>
|
||||
|
||||
static inline void zc_pm_block_sleep(void)
|
||||
{
|
||||
pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
|
||||
}
|
||||
|
||||
static inline void zc_pm_unblock_sleep(void)
|
||||
{
|
||||
pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
|
||||
}
|
||||
|
||||
#else /* !CONFIG_PM */
|
||||
|
||||
static inline void zc_pm_block_sleep(void) { }
|
||||
static inline void zc_pm_unblock_sleep(void) { }
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
Reference in New Issue
Block a user