Commit Graph
765 Commits
Author SHA1 Message Date
ZephCore T096 0a15194eaa board: wire V4.3's existing PWM LED node into the brightness/mode system
heltec_wifi_lora32_v43_procpu.dts already declared a pwm-leds node for
its single onboard LED (pwm_led_white, LEDC0/GPIO35, generic pwm-led0
alias) -- it just never had the two project-specific aliases
(heartbeat-pwm-led / lora-tx-pwm-led) that helpers/ui/ui_common.c and
adapters/board/ZephyrBoard.cpp actually look for. T096 and Wireless
Tracker V2 have had this wired for a while; V4.3 was simply never
included in that earlier rollout, not blocked by anything in its
hardware -- the PWM path was sitting there unused this whole time,
confirmed by grepping for any other consumer of pwm-led0/pwm_led_white
before touching it (none).

Aliased both roles to the same node, same as T096/WT2: this board has
only one physical LED, shared between heartbeat and LoRa TX activity.
ZEPHCORE_LED_PIN_SHARED and the RX-pulse/pin-hold arbitration (extended
to PWM boards earlier today, commit a57d4c7) apply automatically since
both aliases point at the same node.

Gives V4.3 the full leds.radio/leds.hb/leds.brightness set for the first
time: leds.radio previously had no effect at all on this board (no
activity LED aliased whatsoever), leds.brightness was accepted but
inert (no PWM-capable LED reachable).

Verified building for heltec_wifi_lora32_v43/esp32s3 with
boards/common/esp32s3_pwm_led.conf (same flag already used for WT2).
2026-10-08 18:27:00 +02:00
ZephCore T096 50ca150243 board: extend TX/RX LED pin-share arbitration to PWM-shared boards
The RX-pulse/pin-hold interlock (s_tx_lit/s_rx_pulse_off/
zephcore_led_radio_hold_pin) was gated by HAS_TX_LED alone, so it never
compiled in for any PWM-capable board (HAS_TX_LED_PWM). Left two real
gaps:

- T096 and Wireless Tracker V2 alias heartbeat and TX activity to the
  SAME physical PWM LED (ZEPHCORE_LED_PIN_SHARED's intent exactly), but
  had none of the arbitration that protects every plain-GPIO board with
  the same wiring -- a transmit and a heartbeat tick could still clip
  each other on the shared PWM channel.
- All PWM boards, RAK3401 included, had no onPacketReceived() blink at
  all: "set leds.radio rx" (or "all") silently did nothing on the
  activity LED, since that whole code path only existed under
  HAS_TX_LED.

Fixed by widening every "#if HAS_TX_LED" guard around this machinery to
"#if HAS_TX_LED_PWM || HAS_TX_LED", and adding a small tx_led_write(bool)
helper (mirroring heartbeat_led_write() in helpers/ui/ui_common.c) so the
shared interlock logic doesn't need to know which path is compiled in --
only the final write differs.

ZEPHCORE_LED_PIN_SHARED itself gained a PWM branch: true when
heartbeat-pwm-led and lora-tx-pwm-led alias the same DT node (T096, WT2),
same as it already was for the GPIO led0/lora-tx-led case. RAK3401's two
PWM LEDs are on distinct nodes, so it correctly stays unshared -- no
arbitration overhead there, but it does now get the RX blink like every
other PWM board.

The heartbeat side (helpers/ui/ui_common.c) needed no change: it already
checks zephcore_led_radio_holds_pin() unconditionally through
heartbeat_led_write(), from the LED brightness persistence commit earlier
today.

Verified building for heltec_t096/nrf52840 (shared PWM LED),
heltec_wireless_tracker_v2/esp32s3 (shared PWM LED), and rak3401_1watt
(two independent PWM LEDs, confirms ZEPHCORE_LED_PIN_SHARED stays 0
there).
2026-10-08 18:27:00 +02:00
ZephCore T096 ec2a07dfa9 leds: persist brightness across reboots and firmware updates
leds.brightness (PWM-capable boards) was RAM-only since it was added: it
always reset to ZEPHCORE_LED_DEFAULT_BRIGHTNESS_PCT (10%) on every boot,
regardless of what a user had last set it to.

New field led_brightness on NodePrefs, following the exact same
append-only pattern as leds_radio_mode/leds_hb_mode added just before it:
offset 311 in RepeaterDataStore.cpp (repeater/room-server/observer,
shared file), offset 174 in ZephyrDataStore.cpp (companion). Both are
backward compatible by construction: a prefs file written before this
field existed simply leaves it at the initNodePrefs() default (10%),
which is exactly the behaviour every already-deployed node has today --
no special-casing needed to keep "10% on a new/not-yet-upgraded node" as
the effective default.

CommonCLI.cpp's "set leds.brightness" handler now writes the prefs field
and calls savePrefs(), same as the leds.radio/leds.hb handlers right
above it; "get leds.brightness" now reads from prefs instead of the RAM
getter directly, for the same reason those two already do (always in
sync, prefs is the source of truth). The four boot call sites
(main_companion.cpp, main_repeater.cpp, main_room_server.cpp,
main_observer.cpp) each gained a zephcore_led_set_brightness_pct() call
seeding the RAM cache from the persisted value, mirroring the existing
leds_radio_mode/leds_hb_mode lines right next to them.

Verified building for heltec_t096/nrf52840 (companion) and rak3401_1watt
(repeater), the two roles/datastores touched by this change.
2026-10-08 18:27:00 +02:00
ZephCore T096 4905191b1c wt2: shared PWM brightness for the status LED (heartbeat/TX/flashes)
Ports the T096 "Option 1" design (single LED, get/set leds on|off
persisted + get/set leds.brightness 0-100 RAM-only) to the Wireless
Tracker V2, which has the same single-LED-multi-purpose situation
(status_led doubles as heartbeat/message-flash/shutdown-flash/TX
indicator, same as T096's P0.28).

Adds a new ledc0 PWM channel on GPIO18 (same physical pin as the
existing plain-GPIO status_led) with heartbeat-pwm-led/lora-tx-pwm-led
aliases -- the shared C++ logic (helpers/ui/ui_common.c,
adapters/board/ZephyrBoard.cpp, helpers/led_gate.c/h,
helpers/CommonCLI.cpp) already existed from the T096 commits and needed
no changes, being board-agnostic behind DT_NODE_EXISTS checks.

CONFIG_PWM=y deliberately NOT in the board defconfig: WT2 builds via
--sysbuild (mcuboot + app as two images sharing that defconfig), and
mcuboot's minimal kernel config has no k_sem_give()/k_sem_take(), which
the ESP32 PWM LED driver needs -- CONFIG_PWM=y there links the app image
fine but fails mcuboot's link. New boards/common/esp32s3_pwm_led.conf
scopes it to the app image only via EXTRA_CONF_FILE, alongside the
existing esp32s3_usb.conf.
2026-10-08 18:26:59 +02:00
ZephCore T096 c8c456b64a t096: split leds.brightness from leds on|off (final Option 1 syntax)
Reverts the previous merge: "leds" now only ever parses on/off (a pure
word command again, no numeric fallback), and brightness moves to its
own "leds.brightness <0-100>" command instead of overloading "leds"
with a value that could be either a word or a number. Checked before
"leds " in the chain since it shares the same 4-byte prefix. Still
RAM-only, no savePrefs() call in the brightness branch.

This is the syntax settled on after comparing two options: reuse
"leds" via a namespaced brightness command (chosen), vs a full
per-LED led.hb/led.tx split like the RAK3401 branch (not chosen,
decided to apply the "leds"/"leds.brightness" shape everywhere without
exception, including any future RAK3401 rework).
2026-10-08 18:26:59 +02:00
ZephCore T096 bb49aa185c t096: fold LED brightness into set/get leds instead of a new command
Reuses the existing leds on|off switch for brightness too, same
"one command, wording vs number" convention as led.tx/led.hb on
_scratch_leds_test, rather than introducing a separate led command.
Drops the legacy '1'/'0' single-char aliases from set leds while at
it: same 1%/0% shadowing bug already found and fixed on led.tx/led.hb
on 2026-08-16, latent here until a numeric value became meaningful.

get leds now reports both: "on N%" / "off (N%)".
2026-10-08 18:26:59 +02:00
ZephCore T096 2eb8a8e5b7 t096: shared PWM brightness for the heartbeat/TX LED (unpersisted)
Single LED (P0.28) doubles as heartbeat, message flash, shutdown flash
and LoRa TX indicator, all sharing the same physical pin. Adds one PWM
channel on that pin (heartbeat-pwm-led/lora-tx-pwm-led aliases) and a
single 0-100 brightness knob (get/set led) covering every one of those
events at once, deliberately RAM-only: resets to a 10% default on every
reboot regardless of CLI changes made in a previous session. The
existing persisted on/off gate (set leds on|off) is untouched.

Boards without heartbeat-pwm-led/lora-tx-pwm-led aliases keep their
current plain-GPIO behavior unchanged.
2026-10-08 18:26:59 +02:00
liquidraver 290b0c26b0 guidelines for Giskard Reventlov vol.1 2026-10-07 21:06:39 +02:00
liquidraver 5dd330883a Merge pull request #105 from ptr727/rtc-year-range
rtc: write the RTC time only for years 2000-2099
2026-10-07 19:47:13 +02:00
liquidraver 069c974cc0 UI fixes 2026-10-07 17:55:14 +02:00
Pieter ViljoenandClaude Opus 5.5 9b68b7e1ae rtc: write the RTC time only for years 2000-2099
ZephCore writes the year as two BCD digits with no century, as y % 100.
A time before 2000, accepted by a node whose clock had no time yet, was
stored as a year near the end of the century and restored at the next
boot above the 2025 floor: `time 900000000` (1998) came back as 2098.

rtc_time_block() now fails outside 2000-2099, and zephcore_rtc_save()
then logs and writes no time, for every chip; the chip keeps the time it
has. An RV3028 write retry stops if its run-on time leaves the range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 07:09:40 -07:00
liquidraver a2e8d5f440 import / contact save fiddling 2026-10-06 22:50:14 +02:00
liquidraver 81d98abf2e HLD update 2026-10-06 21:54:36 +02:00
liquidraver 9d6b21a264 comment diet vol.2 2026-10-06 21:43:35 +02:00
liquidraver 8297d88bf7 formatter update, bump and contact iterator fix 2026-10-06 21:24:28 +02:00
liquidraver 42eea4f906 usb fix 2026-10-06 20:17:53 +02:00
liquidraver 9bb7ccfe53 upstream dev sync 2026-10-06 16:59:23 +02:00
liquidraver c9896d93e6 tune BT stack on ESP 2026-10-06 15:57:52 +02:00
liquidraver d90ae221cf bump zephyr 2026-10-06 14:12:30 +02:00
liquidraver 5cb319bbfe Merge pull request #101 from ptr727/rx8130ce-week-one-hot
rtc: write the RX8130CE and YSN8900 weekday one-hot
2026-10-06 13:33:00 +02:00
liquidraver e016fe1760 qspi power down on repeaters 2026-10-06 13:27:58 +02:00
liquidraver 108b96d36c rename cli commands and fix it 2026-10-05 22:35:28 +02:00
Pieter ViljoenandClaude Opus 5.5 26217825b3 rtc: write the RX8130CE and YSN8900 weekday one-hot
rtc_time_block() wrote the weekday as a number 0-6 (Sunday 0) for every
RTC. The RX8130CE's WEEK register (13h) is one-hot: Epson ETM50E-10
14.1.2, Table 15, p. 27, Sunday 01h to Saturday 40h, with no more than
one bit set. So each sync wrote Sunday as no day, Monday as Sunday, and
Wednesday, Friday and Saturday as two days at once. ZephCore never
reads the weekday back, so its time and date were unaffected.

A new boolean descriptor property, weekday-one-hot, is set on the
RX8130CE node in rtc-i2c.dtsi, and rtc_time_block() writes BIT(dow)
where it is set. That is the encoding of Zephyr's rtc_rx8130ce.c. The
DS3231, PCF8563 and RV3028 keep 0-6, as Zephyr's drivers for them
write it. The binding and comments no longer call the whole block BCD.

meshtracker_x1's YSN8900 sets it too. The board already uses the RX8900
register map for it, and the RX8900's WEEK register (03h) is one-hot:
Epson ETM45E-11 8.2.2, Table 15, p. 16. The YSN8900's own data sheet
has no register map, and its programming guide is not available.

The same defect and fix as meshcore-dev/MeshCore#3550.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:35:32 -07:00
liquidraver d1cc647293 Merge pull request #100 from ptr727/sensor-list-bounds
cli: reject a negative `sensor list` start index
2026-10-05 21:16:43 +02:00
Pieter ViljoenandClaude Opus 5.5 8190b4018e cli: reject a negative sensor list start index
`sensor list <start>` parsed start with _atoi(), whose uint32_t goes
negative as an int: a start of 4294967295 is -1 and 2147483648 is
INT_MIN. Both passed the `start >= end` guard, so the loop asked the
sensor manager for a negative index and passed the NULL it returned
to "%s=%s", undefined behaviour that picolibc happens to print as
"(null)". Rejecting a negative start closes it: ZephyrSensorManager
has one setting, so only index 0 is ever requested.

The same defect as meshcore-dev/MeshCore#3433.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:14:47 -07:00
liquidraver cdd6cc6300 Merge pull request #98 from ptr727/rak4631-rak12002-rtc
rak4631: use a fitted RAK12002 RTC, and keep its backup switchover enabled
2026-10-05 18:58:09 +02:00
Pieter ViljoenandClaude Opus 5.5 ad2df09dc5 rak4631: use a fitted RAK12002 RTC, and keep its backup switchover enabled
The RAK4631 overlay declared no zephcore,rtc-i2c candidate, so a fitted
RAK12002 (an RV3028 at 0x52) was never probed or written, and every
power cut started the node at 1970. Declaring it is not enough: the
RV3028 ships with backup switchover disabled (EEPROM 37h BSM = 00), so a
power cut still loses the time.

The overlay now declares the RV3028 with the zero-mask rtc-i2c.dtsi uses
for it, and a new optional rv3028-eeprom-config descriptor property
holds (register, mask, value) triplets for its EEPROM configuration
registers 35h-37h. The RAK4631 sets direct switchover with the trickle
charger on (TCE, BSM = 01, TCR 3 kOhm), as the RV-3028-C7 Application
Manual (Rev. 1.4) 7.3 prescribes for a capacitor backup, FEDE set as
3.15.6 says it always should be, and CLKOUT off. The configuration has
to live in EEPROM: the chip reloads its configuration RAM from EEPROM at
power-on and every midnight (4.6.2).

The store runs only when a read identified the chip (RTC_FOUND, not an
adoption on a failed second read) and a BSF-checked time read just
before it showed no switchover. It follows 3.15.6 (BSM 00 or 10 for any
EEPROM access): EERD held, switchover off in RAM, each configured byte
compared against the EEPROM with a single-byte read and written only if
it differs, and a closing Refresh that restores the stored switchover
mode, read back on every boot. 36h, the factory trim, is never written.
Switchover is turned back on only once EEbusy reads 0; otherwise it
stays off until a retry or the daily refresh. A failed store sets the
config in RAM (also only once EEbusy reads 0). A failed or skipped store
is retried every 10 minutes, at most 3 times per boot, each retry
identifying the chip again first.

A switchover disables the chip's I2C mid-transfer (4.2), so at a node
with rv3028-eeprom-config the time is read at boot and written on each
sync with BSF cleared before and checked after. A read that saw a
switchover gives no time. A write sets year 00h first and the real year
last, so a power loss cutting it between its first and last byte leaves
year 2000 and the next boot takes no time from it, and a write not
confirmed is repeated every 5 s, up to 12 times, with the time run on.

All of the RV3028 code is compiled only when some node carries
rv3028-eeprom-config, so other boards, thinknode_m1 among them, build
the same RTC object as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:22:14 -07:00
liquidraver 52644a80a4 make gps standby command universal 2026-10-05 14:00:13 +02:00
liquidraver d5bb275e01 update example board and porting guide 2026-10-05 10:24:36 +02:00
liquidraver 158df925ff Merge pull request #99 from ptr727/rtc-probe-identity
rtc: identify RTCs by documented always-zero bits; stop skipping real ones
2026-10-05 09:01:58 +02:00
liquidraver 448da06235 get rid of deprecated stuff 2026-10-05 08:39:04 +02:00
liquidraver 181fd24b22 readme housekeeping 2026-10-04 20:54:33 +02:00
liquidraver cb155a9449 buffer improvements 2026-10-04 20:49:11 +02:00
Pieter ViljoenandClaude Opus 5.5 03f306b5b3 rtc: identify RTCs by documented always-zero bits; stop skipping real ones
rtc_probe() decided from one read whether the device at a declared
address was the RTC its descriptor expects: a valid BCD block including
hours and year, or the power-loss flag set or unreadable. That skipped
real RTCs whose year byte or hours another firmware left out of range
(a year byte of A0 or more; 12-hour mode reads PM hours with bit 5 set),
ruled nothing out by identity, and adopted an erased EEPROM through its
0xFF "status".

- An optional zero-mask descriptor property holds the time-block bits a
  part's data sheet shows as 0. rtc-i2c.dtsi sets it for 0x68 (DS3231
  19-5170 Rev 10 and DS3232 19-5337 Rev 5, Figure 1, p. 11; DS1307 Rev
  3/15, Table 2, p. 8; 00h bit 7 left out, the DS1307's clock-halt bit),
  the RV3028 (App Manual Rev 1.4, 3.2, p. 12) and the RX8130CE
  (ETM50E-10, 13.2.1 Table 12, p. 22). The PCF8563 gets none: its data
  sheet (Rev 11.1, Table 4, p. 10) marks unused bits "not relevant".
- One read rules a device out if a masked bit is set, or if seconds,
  minutes, date or month are out of range while the power-loss flag does
  not read as set. The year and hours are no longer identity checks.
- A device is passed over only when two reads each rule it out. A failed
  first read means nothing is there; a failed second read does not count,
  and with no clean read the chip is adopted for write-back but gives no
  time. An all-0xFF second read rules the device out.
- An all-0xFF first read is skipped, and the first save probes once more,
  since a real RTC can power up that way.
- A time is restored only from a clean read whose fields, hours and year
  are all valid.

The decision is one function, rtc_identify(). The same rules, with a
few agreed differences, are in meshcore-dev/MeshCore#3544.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:48:55 -07:00
liquidraver f9d01cf33a upstream sync and gps stuff 2026-10-02 23:10:09 +02:00
liquidraver 5b779478b0 bump zephyr 2026-10-02 14:25:53 +02:00
liquidraver 3b886b861b add seeed lvk + nrf54lm20a 2026-10-02 13:27:21 +02:00
liquidraver 3c7a7c830a gps build warn 1.17.6-zephcore 2026-09-27 16:40:53 +02:00
liquidraver 2e0f765036 vanilla dev sync 2026-09-27 16:33:58 +02:00
liquidraver 7662decca5 bump west 2026-09-27 15:29:39 +02:00
liquidraver 0f870ce942 prepare for launch 2026-09-27 15:27:13 +02:00
liquidraver 514aa77fe8 QSPI-bringup stability enhancements 2026-09-27 13:59:33 +02:00
liquidraver 1f1e53d1df Make Packetlogging Great Again 2026-09-27 09:19:49 +02:00
liquidraver a7464b96f7 release: 1.17.6a-zephcore pre-release
Bump the firmware version to 1.17.6a-zephcore and move the release
notes to RELEASE_NOTES_1.17.6a-zephcore.md, which the release workflow
publishes as the release body. The workflow's prerelease switch is
already on, so the configurator keeps offering 1.17.5c.

Notes corrected against the commits since 1.17.5c: the light-sleep
section now names the boards that sleep by default, explains the
1.17.1 DIO1 regression correctly (the button's EXT1 polarity disarmed
DIO1 on the Heltecs; the XIAO and Station G2 never had light sleep),
describes the pad holds and the USB console behaviour after the
console window, and says what the powersaving migration really does.
Added what was missing: get pm, GPS UART suspend on boards without a
power line, the optional XIAO nRF54L15 GPS, poweroff/shutdown and
get pwrmgt.*, the flush before reboot, the XIAO ESP32-S3 and Station
G2 button fixes, and a pre-release notice asking Heltec users for
light-sleep reports.
1.17.6a-zephcore
2026-09-26 18:52:32 +02:00
liquidraver b0e38a7e98 gps: release the GPS UART on every nRF board; optional GPS on XIAO nRF54L15
An nRF UARTE receiver starts at boot and never stops. The GPS UART
suspend in standby/off was limited to boards with GPS power control,
because suspending into a module that is still streaming NMEA could
hang the main thread waiting for RXTO (RAK3401, 1.16.6). Patch 0010
bounds that wait (4 ms, then the peripheral is disabled and the events
cleared) and resume re-arms from a clean state, so gps_power.cpp drops
the requirement: gat562_30s, muziworks_r1neo, rak3401_1watt, rak4631
and xiao_nrf54l15 now release the UART too.

Stress-tested with a temporary hook on the T1000-E, GPS streaming NMEA
at 115200: 900 suspend/resume cycles at random points of the stream,
no failure, slowest suspend 640 us (the bounded fallback never ran),
NMEA callbacks back at 1 Hz after every round.

xiao_nrf54l15 gets an optional gnss-nmea-generic on uart21 (D6/D7,
9600), as the RAK4631 and Heltec V3 have: a user-fitted GPS works on
the release image, and with nothing fitted the port, enabled upstream
and unused until now, no longer keeps its receiver armed. On the bench
unit: boots, GPS acquiring, 10 x gps off/on and two reboots clean.

Builds: rak4631 repeater and companion, rak3401_1watt, gat562_30s,
muziworks_r1neo, xiao_nrf54l15 repeater and companion.
2026-09-26 18:33:16 +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
liquidraverandClaude Opus 5.5 ffced336fd west: bump Zephyr main to 3e7672a71bd
- retire 0018 (upstream 67151dfaf73 locks the shared multi-heap) and the
  hal_espressif PSRAM-realloc backport (in the new hal pin)
- 0001: add LR1110/LR2021 to LORA_MODULE_BACKEND_NATIVE's depends on;
  upstream 1263266b615 gated it on its own radios and LR boards silently
  fell to loramac-node
- ble: build the persisted gatt_peer settings key ourselves in the old
  format; bt_addr_le_to_str() changed to "P:AA:.." and would have orphaned
  every key and sent every bonded phone a Service Changed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:05:36 +02:00
liquidraver 187a44060d close out the restructure's open items
- boot_info: label the ESP32 reset reasons Zephyr's hwinfo driver maps to 0
  (USB-Serial-JTAG/JTAG reset -> DEBUG, power glitch -> BROWNOUT, CPU lockup,
  eFuse -> HARDWARE); get pwrmgt.bootreason no longer says Unknown after a flash
- mqtt: the uplink credentials struct moves to adapters/mqtt/uplink_creds.h,
  so the adapter no longer includes an app header (layout unchanged: the
  struct is persisted raw)
- gps: one off-levels routine for standby/off and System OFF; the sleep line
  stays asserted in standby and drops at System OFF, as upstream's
  sleep_gps/stop_gps/powerOff; System OFF now asserts reset before cutting
  GPS_EN
- doom: sprites solid with a one-pixel black halo, boss drawn 1.5x on the
  floor; the L2 boss was invisible from the spawn on 1-bit panels
2026-09-26 08:50:30 +02:00
liquidraverandClaude Opus 5.5 9fc1eaf18a docs: publish the system design and architecture decision records
DESIGN.md is the reconciled high-level design after the 2026-09 restructure:
system context, roles, build-time composition, layers and dependency rules
(with the known exceptions), threading and event loop, end-to-end flows,
cross-cutting concerns and the upstream alignment policy. docs/adr/ holds the
nine decision records (ESP32 boot layout, S3 native USB, overlay precedence,
BLE privacy, Zephyr main pin, SX126x duty cycle and RX-busy gating, adaptive
contention window, storage backend, WiFi companion boards).
ARCHITECTURE.md stays the detailed component reference and links both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:01:28 +02:00
liquidraver 159c086485 repo: publish the host regression suite; consolidate .gitignore
- tests/ (114 host cases over production sources), the CI workflow
  (ASan/UBSan on PRs and master pushes) and REGRESSION_TEST_PLAN.md
  are tracked
- .gitignore grouped and deduplicated (173 -> 96 lines, same ignored set)
2026-09-25 21:56:48 +02:00