Commit Graph
78 Commits
Author SHA1 Message Date
liquidraver 514aa77fe8 QSPI-bringup stability enhancements 2026-09-27 13:59:33 +02:00
liquidraver d2d786517b power & storage: ESP32 light sleep that sleeps and wakes; less flash wear
ESP32 light sleep (devdocs/lld/13-power-management.md)

Light sleep on the S3 repeaters never worked as designed, for two
independent reasons:

- No S3 repeater ever slept. Every one carries WiFi OTA, the WiFi
  driver starts WiFi at boot (NULL mode), and a started WiFi library
  holds its modem PM lock. Measured on the XIAO: zero sleep entries
  with WiFi compiled in. helpers/pm_esp32_wake.c now stops WiFi at
  boot on PM builds; `start ota` and a station connect start it again
  (verified: AP up, sleep blocked for the session, sleeping after
  `stop ota`).
- DIO1 was not a wake source. GPIO_INT_WAKEUP means EXT1 in Zephyr's
  ESP32 driver: RTC pads only, one trigger polarity for all pins on
  the S3, and pads left on the RTC mux after every sleep. Since 1.17.1
  pm_esp32.overlay also armed the active-low user button; gpio-keys
  configures first, so the driver refused the active-high DIO1.

The new helper arms the radio IRQ line and the user button (sw0) as
per-pin level GPIO wakes (ESP-IDF gpio_wakeup_enable, any pad) for the
length of each sleep, masks their CPU interrupt while in level mode,
and re-raises the interrupt a sleep swallowed through status_w1ts, so
the radio's dio1_isr and gpio-keys run as usual. EXT1 is no longer
used: patch 0003 configures DIO1 as a plain input again and drops the
d7cc4cb pad-restore notifier (preamble section rewritten). Non-RTC
DIO1 boards (XIAO GPIO39, Station G2 GPIO48) can now light-sleep.
Each light sleep is also bounded by the next esp_timer alarm
(pm_policy_next_custom_ticks, CONFIG_PM_CUSTOM_TICKS_HOOK), as ESP-IDF
does; the BLE controller's modem-sleep wake timer lives there.

Light sleep isolates every pad that is not held, RTC or digital, while
the radio keeps receiving. Holds (ESP32_GPIO_SLEEP_HOLD_EN, inert
without PM) now live in each board's own files: NSS and RESET on all
PM-capable boards, VFEM_Ctrl (GPIO7) and Vext on V4/V4.3/V4 R8/WTv2,
CPS on V4, RXEN on the XIAO. The shared overlay keeps only &rtc_timer
and the GPIO wake source; the wrong RTC_PERIPH explanations are gone.

Also:
- `powersaving` (upstream's command and replies) gates light sleep;
  light-sleep builds default to on, and a stored 0 without the new
  pwr_sav_set marker (the field did nothing before) becomes on once.
  `powersaving off` persists and keeps the node (and its USB) awake.
- `get pm`: asleep %, sleeps, wake causes, per-pin wake counts.
- The console window reopens on every user-button press.
- XIAO ESP32-S3: GPIO21 is both the kit's user button and the board's
  led0; the heartbeat drove it low and every sleep woke as a button
  press. led0 is deleted, the button gets a pull-up.
- Station G2: button active-low with pull-up, as upstream reads it
  (was active-high). Untested on hardware.

Verified on the XIAO ESP32-S3 as a light-sleep repeater on the live
mesh (repeat off, 2 dBm) against a T1000-E on the same channel: awake
20 min, ratio of floods heard 0.95; asleep 30.5 min (96.6 % of
uptime), 119 vs 119, ratio 1.00. TX: sent == flood + direct. Heltec
boards: build-only (no unit on the bench).

Companion light sleep stays off by default; the esp_timer bound makes
it possible. Spike on the XIAO (pm_esp32.conf by hand, BLE only): a
held connection survives, 35 min with a poll every minute and no
miss, the SoC asleep ~91 % between connection events, LoRa RX 130 vs
~130 on the T1000-E. Pairing while asleep fails; USB CDC and UI still
need locks before companions can ship with it.

Flash wear (storage)

The companion marked contacts dirty on is_new, but upstream's is_new
means "not in contacts[]": auto-add declined the node (type filter,
hop limit, full table). Every advert from such a node forced a 5 s
full rewrite of an unchanged contacts3, while a contact that really
was just added only got the liveness deadline. onAdvertRecv now
snapshots the stored record first. is_new never dirties. An addition
or a changed name, type or position flushes in 5 s, and a plain
re-advert waits the liveness deadline, now 1 h instead of 10 min.

A new weak hook, zephcore_persist_before_off(), flushes the deferred
writes before every clean reboot and power-off. It is called from
zephcore_power_off(), the ZephyrBoard reboot, bootloader and BLE-OTA
paths, the UI save-and-restart, the joystick restart and wifi_ota.
Off the main thread it posts MESH_EVENT_PERSIST and waits up to 5 s.
Before this, only CMD_REBOOT flushed.

The repeater saved its ACL on every admin login. It now saves only
when the role or secret changed; the secret is the ECDH of two fixed
keys, so re-logins store nothing new. The room server likewise skips
the lazy write unless the role, sync_since or secret changed.
zephcore_prefs_json_save() compares the serialised JSON with the file
and skips an identical rewrite. Not tested on hardware.

Builds and tests: role_builds.sh 10/10; the ESP32 PM set on the
combined tree (five Heltec repeaters, V4 R8, Station G2, XIAO repeater
and companion) 9/9, repeaters at 69-71 % dram0; board_manifest check
clean; host tests quick 114/114. heltec_companion dram0 at 99.80 %
(776 B left, +88 B for both changes together).
2026-09-26 16:49:17 +02:00
liquidraver 38f815a0a1 server roles, event loops, companion transports, storage and prefs.json
Server roles (slice 7)
- CommonCLI split into upstream's shape; region CLI moved into CommonCLI;
  ClientACL, RegionMap, TransportKeyStore and both server meshes in
  upstream shape over an Arduino compat shim (helpers/compat: Arduino.h,
  Stream/Print, Serial, SHA256, FILESYSTEM/File over Zephyr fs)
- room server processAck port; `get acl` prints as upstream
- room: default name Room-XXXX, display/input rotation, live radio UI refresh

Event loops (slice 7b)
- repeater and room server share server_main_common with a ServerRole
  descriptor; shared event bits in src/mesh_events.h
- room server deadline-driven (idle wakes 2.2/s -> 0.08/s); companion
  loop() runs on housekeeping (closes the lazy contact-flush gap)
- `get loop.wakes` in debug builds (CONFIG_ZEPHCORE_LOOP_WAKE_STATS)

Companion transports and WiFi (slice 8)
- upstream MultiSerialInterface: BLE, USB/UART and TCP are each a
  BaseSerialInterface, every connected transport gets every frame;
  CompanionSerial holds a lossless reply; shared frame_txq for BLE/TCP
- SerialCompanionTransport and LinuxTCPTransport replaced by
  TcpCompanionTransport; ZephyrBLE split (gatt layout, DFU)
- WiFi companion (upstream wifi.ssid/pwd/enabled CLI, TCP port 5000) on
  boards declaring `capabilities: wifi` in zephcore.yml: the PSRAM
  ESP32-S3 boards, plus Heltec V3 (140 contacts), Wireless Tracker (130)
  and both C6 boards (280); ESP32-S3 without PSRAM gets bounded WiFi
  buffers and a measured 72 KB heap; PSRAM boards' internal heap 72 KB
- TCP queues: own depth (ZEPHCORE_TCP_QUEUE_SIZE=8), RX waits instead
  of dropping
- RAM: companion ClientACL dropped; main/BT-TX/TCP stacks sized from
  measured peaks (-7 KB nRF companions, -9 KB ESP32 companions)
- ESP32 WiFi stopped working under ~200 frames/s unicast. Fixed by:
  patches/zephyr/0017 never block the WiFi task in the RX callback;
  patches/zephyr/0018 lock shared_multi_heap (heap corruption with the
  WiFi heap in PSRAM); patches/modules/hal_espressif/0001 backport of
  hal_espressif af97fb02df (PSRAM realloc)

Storage and prefs (slice 9)
- prefs are now prefs.json through upstream's ConfigSerializer (ported
  verbatim), with upstream's key names for shared fields and ZephCore's
  own under "zc"; on first boot the binary new_prefs / repeater prefs is
  migrated and kept, so a downgrade still boots with the old settings
- helpers/PrefsCodec: both legacy binary layouts (companion 272 B,
  server 311 B) as Zephyr-free codecs; sanitizeNodePrefs is the only
  validator
- fix: server prefs could be replaced by a truncated file when a write
  failed; identity, prefs and channel writes go through one checked
  atomic replace (ZephyrFsUtil)
- fix: a corrupt cad_auto byte turned adaptive CAD off instead of the
  default on
- fix: a deliberate 300 s GPS duty on a repeater was rewritten to 48 h
  on every boot (that migration now runs on legacy files only)
- one identity loader for all roles (IdentityFile); an unusable key
  file is always parked as .bad before regenerating
- server store no longer stamps repeater defaults on room server and
  observer; companion first-boot volume policy moved into the store
  (adoptVolume)
- ZephyrDataStore 1435 -> ~850 lines
2026-09-25 08:34:21 +02:00
liquidraver 10b36094ae radio: one LoRaRadio adapter over per-family ops tables; lr20xx cleanup
- LoRaRadioBase -> LoRaRadio (final); SX126x/SX127x/LR1110/LR2021Radio
  replaced by const LoRaRadioOps tables (radio_ops_<family>.cpp), one
  linked per build; mains construct the radio in one line
- lr20xx: vendored Semtech SDK + HAL moved beside the driver (no app-tree
  sources), DIO1 handler split (CCN 52 -> 10), zc_lora_timing.h adopted,
  dead exports removed, header rewritten, comment diet (image-identical)
- semtech,lr2021 uses the lr1110 property names (irq-gpios, tcxo-voltage,
  rfsw-*), compiled config byte-identical
- correct the stale "LR2021 is not CAD-probed" claim
2026-09-23 19:51:08 +02:00
liquidraver 0c485b2230 lr11xx: SDK from LoRa Basics Modem, split DIO1 handler, shared LoRa timing
- compile Semtech lr11xx_driver v2.7.0 from modules/lib/lora-basics-modem
  (driver layer only) instead of the vendored fork; HAL moves next to the
  driver (+abort_blocking_cmd); driver no longer builds app-tree sources
- split lr11xx_dio1_work_handler into per-IRQ helpers (CCN 46 -> 12)
- zc_lora_timing.h: preamble grace, payload deadline, 24-bit saturation,
  shared with sx126x (patch 0003) and host-tested; sx126x CAD_RX Rx bound
  now saturates instead of wrapping (SF12/BW7.81)
- semtech,lr1110 binding includes upstream lr11xx-base; boards use
  upstream property names (irq-gpios, tcxo-voltage, rfsw-*)
- drop dead lr11xx_get_random/get_chip_temp_c; correct the IRQ-clear
  invariant wording; comment diet (image-identical)
2026-09-23 19:04:41 +02:00
liquidraver 2e2d852f29 build: board manifests, shared board resolver, manifest-driven release matrix
- boards/**/zephcore.yml: one manifest per board/preset (release roles,
  variants, catalog metadata, capabilities); scripts/board_manifest.py
  validates and serves them to build.sh, gen_provider_catalog.py and
  docs/supported_boards.md
- cmake/zephcore_board.cmake: board dir -> platform, shared by app and
  sysbuild; removes the board-name regex lists and 7 separate globs
- conf chain: exact-name role/debug classification checked against
  Kconfig, idempotent across non-pristine reconfigures, version string
  single-sourced (BT DIS rev generated), day-granular build epoch
- Doom easter egg: Kconfig default for the wio_tracker_l1 companion
  instead of a build.sh-only flag
- catalog: maker for the LR2021 EVK (KeyError before); CI installs PyYAML
- user -DEXTRA_DTC_OVERLAY_FILE overlays now take precedence over derived overlays

Verified: identical .config, DTS and source lists for 97 board/role
configs; identical images for 4 reference builds; identical build.sh
command/artifact set and catalog content.
2026-09-23 10:24:40 +02:00
liquidraver 251269d102 heltec v4/v4.3: swap gps uart to tx=38 rx=39 (arduino setPins is rx-first) 2026-09-19 22:03:09 +02:00
liquidraver c5ab279c32 Merge pull request #93 from ptr727/heltec-wifi-lora32-v4-r8
Add the Heltec WiFi LoRa 32 V4-R8 board
2026-09-19 20:58:43 +02:00
Pieter ViljoenandClaude Opus 5 bb84f54191 drive every gps control line with logical gpio semantics
gps_power_control() mixed the two GPIO conventions on the same pins. It
configured lines with GPIO_OUTPUT_HIGH / GPIO_OUTPUT_LOW, which Zephyr defines
as physical levels, then drove the same lines with gpio_pin_set_dt(), which is
logical. On a line declared GPIO_ACTIVE_LOW those are opposite, so the first
call asserted the reverse of every call after it.

Two lines are actually broken by this.

gps-enable, on the boards declaring it active-low. Most of them reach the block
named for the T1000-E rather than the "simple boards" path, because
HAS_T1000_GPS_CONTROL is (HAS_GPS_VRTC || HAS_GPS_RESET || HAS_GPS_SLEEP) and a
bare gps-reset alias is enough to route a board there. That block drives the
enable physical HIGH unconditionally on every power-on, and power-off lands on
physical HIGH too, so the line is de-asserted in both states and the module is
never powered at all.

gps-reset, on the six boards declaring it active-low: heltec_t096, heltec_t1,
heltec_t114, thinknode_m1, heltec_wifi_lora32_v4 and _v43. The documented
power-on sequence is "assert reset, hold 10 ms, release", written as
GPIO_OUTPUT_HIGH then gpio_pin_set_dt(0). On an active-low line both of those
are physical HIGH, so the pulse never asserts and the module starts
uncontrolled -- the condition the power-off comment says this code prevents.
heltec_wifi_lora32_v4's overlay documented the no-op rather than fixing it;
that comment is updated here, with _v43's copy.

vrtc, sleep, rtcint and resetb are converted too. Every board declaring them is
active-high, so those change nothing today; they are converted so the root
cause does not survive in four more places.

Converting is safe by construction. gpio_pin_configure() flips the init level
only when both GPIO_OUTPUT_INIT_LOGICAL and GPIO_ACTIVE_LOW are set, then
strips INIT_LOGICAL, so on an active-high pin GPIO_OUTPUT_ACTIVE produces a
flags word byte-identical to GPIO_OUTPUT_HIGH. Behaviour changes only where a
line is declared active-low, which is only where it is currently wrong.

gps_power_off_for_shutdown() had the same inversion and it mattered most there:
on the active-low boards the old physical LOW asserted the enable, powering the
GPS on as the device entered System OFF rather than powering it down. Its
header comment also described a narrower scope than the function has, naming
only the power enable where it configures vrtc, reset, sleep, rtcint and
resetb as well; that is corrected here.

Comments that described a level this change now drives logically are reworded
to asserted/de-asserted. Comments stating genuine electrical facts, such as a
pin floating LOW without its pull-up, are left as they are, since rewording
those would make them wrong.

Verified on hardware on a Heltec V4-R8, whose gps-enable is active-low: with
this change the boot log reports "GPS power GPIO configured, set ACTIVE" and
the L76K streams NMEA (210 sentences in 35 s), where without it the log reports
"set HIGH" and the module never powers on. Also built for heltec_wifi_lora32_v4
and _v43 (active-low enable and reset), thinknode_m1 and heltec_t114
(active-low reset) and t1000_e (active-high throughout).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 10:10:30 -07:00
Pieter ViljoenandClaude Opus 5 43e9db5823 add heltec wifi lora32 v4-r8 board + anchor the esp32 light-sleep allowlist
The V4-R8 is an ESP32-S3R8 with 8 MB octal PSRAM, where V4 and V4.3 are S3R2
with 2 MB quad. Octal PSRAM claims GPIO33-37, which moves Vext 36->40, the LED
35->46 and VGNSS_Ctrl 34->42, and drops ADC_Ctrl and GNSS_RST. CTX binds as
lna-bypass-gpios rather than tx-enable-gpios, so the board is a fourth one
where set radio.fem.rxgain works; the CLI reference and the register-patch help
text say so.

The allowlist change is needed for the board to be added correctly. CMake
MATCHES is a substring search, so the existing heltec_wifi_lora32_v4 entry also
matches heltec_wifi_lora32_v4_r8 and the new board would get CONFIG_PM=y
without anyone deciding it should, on a path pm_esp32.conf marks as not yet
validated on hardware. Anchoring preserves the five boards that were intended
to match, including heltec_wireless_tracker_v2, which previously qualified only
by substring and is now listed explicitly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 09:52:37 -07:00
liquidraver edcc40a884 track zephyr main 3e8f38faf93: rebase patches, lora 0.9 api, nrf clock split, s3 heap, lr11xx/lr20xx dc preamble guard, usb kick loop fix 2026-09-19 16:46:14 +02:00
liquidraver 39b9584b20 .editorconfig and .gitattributes 2026-09-18 21:25:19 +02:00
liquidraver 54e333532e PR #80 followup 2026-09-08 23:09:33 +02:00
liquidraver ddfadb9125 Merge pull request #80 from bisbille/pr3-confirm-window
board: fix double-press confirm window on WT2 and V4.3
2026-09-08 23:04:44 +02:00
liquidraver 53cf183a5c esp gps and BT to 9dBm 2026-09-07 22:29:10 +02:00
ZephCore T096 be5e647c6a board: fix double-press confirm window on WT2 and V4.3
Both emit ENTER via a zephyr,input-longpress filter (single button),
same category the Kconfig help text already documents as needing
CONFIG_ZEPHCORE_UI_CONFIRM_WINDOW_MS=3000 instead of the 500ms
default -- two ~1s holds don't fit in 500ms, so the second long press
on shutdown/DFU/offgrid always arrives after the window closed and
just re-arms instead of confirming. T096 already had this set
(board.conf:38); WT2 and V4.3 didn't, apparently missed when their
board.conf was written.
2026-09-05 14:30:20 +02:00
liquidraver 81a43f6dd1 west update and upstream dev sync 2026-09-02 10:01:34 +02:00
liquidraver efa5240b69 add meshnology w12 2026-08-30 22:29:21 +02:00
liquidraver 5372f1d8a2 auto-enable esp32s3 USB CDC companion in CMakeLists; board.overlay first in overlay order; esp32s3 download-mode reboot 2026-08-28 10:32:49 +02:00
liquidraver ea86815c95 fix LNA/FEM control 2026-08-25 14:04:32 +02:00
liquidraver 6b5e892b38 wirelesstracker improvements 2026-08-24 10:44:28 +02:00
liquidraver 174bce80d9 cleanup/bugfix run 2026-08-23 09:45:14 +02:00
liquidraver 5302d21801 wireless tracker display inversion and button wake fixes 2026-08-22 22:48:27 +02:00
liquidraver 2c4c3a07c3 t3s3 fixes 2026-08-22 11:10:05 +02:00
Max Beffert c4672ce85e T3S3 support 2026-08-18 16:33:39 +02:00
liquidraver 216012574e X1 barometrification 2026-08-14 14:01:18 +02:00
liquidraver 788e4dd48b PM debug v2 2026-07-31 17:19:55 +02:00
liquidraver cceb6606e1 fix observer role 2026-07-30 09:55:16 +02:00
liquidraver ab4ea6d87c ESP power saving p1 2026-07-30 09:33:03 +02:00
liquidraver 63155b4d44 thinknode m9 stuff + temp remove from build.sh 2026-07-23 08:40:31 +02:00
liquidraver 076fbb7ff3 m9 and rng polishing 2026-07-21 22:49:48 +02:00
liquidraver 24936cc4e5 fix M9 debug build 2026-07-21 19:02:17 +02:00
liquidraver 16041b0f3a M9 keys P1 2026-07-21 15:55:51 +02:00
liquidraver 47f24a0b1c M9 SD card settle 2026-07-21 15:29:05 +02:00
liquidraver 13b5e2fcf3 scrub APC and bump to 1.16.6 2026-07-20 09:55:32 +02:00
liquidraver 16c7ae506a port thinknode m9 2026-07-18 20:30:38 +02:00
liquidraver 28e2126dcc add GPS to heltecv3 2026-07-16 13:02:00 +02:00
liquidraver dc1239537e SmartCAD p1 2026-07-12 10:48:37 +02:00
liquidraver 603f0e15f6 refactor all ESP partitioning 2026-07-09 22:16:54 +02:00
liquidraver 4bcefbcda7 mirror PR #51 onto V4.3 2026-07-07 22:36:26 +02:00
liquidraverandClaude Opus 4.8 4246f94ae1 Merge pull request #51 from etienn01/fix-heltec-v4-display-and-boot
Fix Heltec V4 display (Vext PMOS polarity) and Xtensa TX-wait stack overflow.

Conflict in heltec_wifi_lora32_v4_procpu.dts resolved: kept the corrected
BatADC header line from 247ca65 (GPIO1 = ADC1_CH0 / &adc0, GPIO37 active-HIGH)
and took the PR Vext change (GPIO36 active-LOW PMOS rail switch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:18:09 +02:00
liquidraver 247ca655a8 fix heltec v3/4/4.3 0% battery 2026-07-07 21:25:59 +02:00
Étienne Fesser 2a26e830e2 Fix Heltec V4 Vext polarity (OLED never powered)
The V4's Vext rail switch is a PMOS: GPIO36 LOW turns the rail ON.
The DTS drove it ACTIVE_HIGH, leaving the OLED (and its I2C pull-ups)
unpowered — the rail floats at ~1.6V and SSD1306 init always fails
with -ETIMEDOUT.

Verified on hardware with a multimeter: GPIO36 high = 1.6V on Vext,
GPIO36 low = 3.3V, display works.

This matches what the working Arduino MeshCore firmware actually does
with the pin, despite its PIN_VEXT_EN_ACTIVE=HIGH define:
RefCountedDigitalPin::begin() initialises the pin to its *inactive*
level (LOW), and the V4 OLED build constructs the display with a NULL
periph_power (variants/heltec_v4/target.cpp) so nothing ever claims
it HIGH. GPIO36 is therefore driven LOW continuously under Arduino —
the exact state this fix reproduces via an ACTIVE_LOW boot-on
regulator.
2026-07-06 19:26:17 +02:00
Étienne Fesser b62507f68b Fix Heltec Wireless Tracker v1/v2 display offset 2026-07-04 22:30:43 +02:00
liquidraver 74aa3810ee Merge pull request #47 from matthew73210/master
Reworked the screen pages
2026-07-04 12:51:15 +02:00
liquidraver e12f5fce8c heltec v4 battery reading fix 2026-07-04 11:26:01 +02:00
liquidraver 487693f669 update documentation 2026-07-04 09:41:09 +02:00
Matthew Burton 955acca8b0 Added the WTv2 2026-07-04 07:28:17 +02:00
liquidraver bd363781ac companion hybrid refactor v1 2026-06-23 22:45:25 +02:00
liquidraver 550b77fd63 set default tx to around 22dBm for all PA boards 2026-06-18 09:32:49 +02:00