Commit Graph
170 Commits
Author SHA1 Message Date
liquidraver c60b5e1bae pwm cleanup 2026-10-09 08:38:37 +02:00
ZephCore T096andClaude Sonnet 5 6b9d205b00 leds: do not reclaim a PWM-shared pin as plain GPIO in main()
server_main_common.cpp and main_observer.cpp both configured led0 as
a plain GPIO output at boot, unconditionally. On ESP32 that calls
esp_rom_gpio_matrix_out() under the hood, which routes the pin's
output to the GPIO signal and takes it away from whatever peripheral
had it before, including the LEDC channel a heartbeat-pwm-led or
lora-tx-pwm-led alias had already claimed during the PWM driver's own
init. The PWM write then succeeds (no error, no warning) but never
reaches the pin: confirmed on V4.3 hardware, where the same pin lights
up fine over plain GPIO, and over PWM in a different (non-Zephyr)
firmware, but stays dark over Zephyr's PWM once this GPIO configure
has run.

Companion builds never hit this (they don't use server_main_common.cpp)
and nRF52 isn't affected (no shared GPIO matrix), which is why T096
tested fine while V4.3 didn't.

Fix is alias-based, not a board list, so any future board with either
PWM alias is covered without extra wiring.

Also: CLI_HAS_RADIO_LED only checked the plain lora-tx-led alias, so
"set leds.radio" replied "(no radio LED on this board)" on boards
whose radio LED exists only via lora-tx-pwm-led (V4.3). Check both.

Documents the two PWM aliases in the board porting guide, and
leds.brightness plus the corrected LED topology counts in
docs/CLI_commands.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-08 18:27:42 +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
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 f9d01cf33a upstream sync and gps stuff 2026-10-02 23:10:09 +02:00
liquidraver 1f1e53d1df Make Packetlogging Great Again 2026-09-27 09:19:49 +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 059022403b ui: admin telemetry decoder, battery % from board curves, PowerPolicy
- admin screen: telemetry via upstream LPPDataHelpers (ported; fixed its
  1-byte humidity size), every field, per-channel, signed current
  (helpers/ui/telemetry_text)
- battery %: both UIs show ZephyrBoard::getBattPercent() (discharge
  curve / fuel gauge) instead of a linear 3.0-4.2 V estimate
- app/PowerPolicy: low-battery auto-shutdown + v-contact alert +
  autoshutdown CLI on every companion (headless too); under 2000 mV
  counts as no battery; default 3200 mV, stored 3300 migrated once
- one time_sync.c for both UIs; ui_push_radio_state() for all roles
- repeater admin screen in its own file; handleInput split per mode
- dead UI code removed; companion help fits one reply frame
2026-09-25 20:49:48 +02:00
liquidraver d5bd805710 peripherals & power: GPS fixes, poweroff/pwrmgt, upstream SensorManager telemetry
- gps: servers honour gps_enabled at boot (was lost on reboot; zc.gps_set
  upgrades older prefs to on once); sleep lock only while acquiring;
  gps off works on boards without a power line (UART sleep); UI toggles
  and fixes handled on the main thread; configure only after a good init;
  observer parks its GPS; one interval clamp (clampGpsInterval); one
  nav-mode knob (room servers on Air530Z now stationary); GNSS UARTE from DT
- gps: split into ZephyrGPSManager / gps_power / gps_module_cfg
  (+gps_internal.h); dead driver branches, gps_get_diag, sats_* removed
- clock: ZephyrRTCClock writes the hardware RTC itself (clkreboot now
  resets it); MESH_EVENT_RTC_SAVE gone
- power: one power-off path (zephcore_power_off, ZephyrBoard::powerOff),
  poweroff/shutdown CLI, get pwrmgt.*, crash record kept across reboot,
  battery reading cached 10 s, fuel gauge on the current API
- telemetry: upstream SensorManager + CayenneLPP (rounded to nearest),
  one builder for all roles; per-sensor channels, MCU temp always,
  guest gating and inverse mask on servers; sensor list/get/set work;
  BME280/BMP280 also probed at 0x77
- CLI: gps in upstream's form, get gps detail, gps sync
2026-09-25 17:56:34 +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 b361c33901 companion: upstream-shaped BaseChatMesh, per-opcode handlers, companion CLI
- BaseChatMesh/ContactInfo/ChannelDetails restored to upstream text, ZephCore
  changes fenced; added to the Shared-file list
- upstream names: handleCmdFrame, writeOKFrame/writeErrFrame,
  addToOfflineQueue, queueMessage; dead dequeue removed
- handleCmdFrame split into handleCmd<Name>() in upstream order;
  CMD_SEND_PATH_DISCOVERY -> _REQ
- CompanionMesh::handleCommand as the single CLI entry point; over-the-air
  TXT_TYPE_CLI_COMMAND from flag-0x10 contacts (upstream command set only)
- V-contact moved to app/VContact.cpp, protocol constants to
  app/CompanionProtocol.h; comment diet (image-identical)
2026-09-24 07:15:44 +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 fd2cbe9d62 radio: extract CadController and NoiseFloorEstimator, one prefs source
- CadController (stats, window, re-anchor, visits, staircase, safety,
  starvation relax) and NoiseFloorEstimator split out of LoRaRadioBase
  behind a 4-call CadHw interface; Zephyr-free, host unit tests added
- prefs mandatory: setPrefs() before begin(), LoRaConfig fallbacks and the
  companion/observer placeholder prefs removed; LoRaConfig is the one
  defaults source (invalid-prefs fallback now uses the board TX default)
- TX-wait thread stack owned by the base; reconfigureWithParams removed
- driver extension headers on the public include path
  (zephyr/drivers/lora/*.h); patch 0003 include updated
- comment diet (image-identical); rationale moved to devdocs LLD 03
2026-09-23 14:33:29 +02:00
liquidraver a7d073900e core: restore upstream shape for the shared mesh core
- Mesh, Dispatcher, Packet, packet pool and the core headers rebuilt from
  Arduino MeshCore's text; ZephCore changes re-applied, each fenced
  `// ZEPHCORE:` (diff vs upstream: 4540 -> 1147 lines)
- upstream header layout; MainBoard gains handleCommand/loop and the other
  upstream members; RNG::nextInt back in Utils.cpp
- packet logging moved to the roles' log hooks (helpers/PacketLog.h)
- zero-hop direct ACKs deduplicated and length-checked as upstream
- RadioIncludes.h -> src/, LoRaConfig.h -> helpers/
- .editorconfig: shared files keep upstream formatting

Verified: 10 role builds, per-function disassembly review, bench DM/ACK
regression (T1000-E, Wio L1, XIAO S3 on 1.17.4) incl. via live repeater.
2026-09-23 13:39:48 +02:00
liquidraver d7cc4cbe1d Return DIO1 to the digital GPIO block after every light sleep 2026-09-22 22:03:47 +02:00
liquidraver 4bf5ccd397 length guards for dispatcher 2026-09-21 10:06:59 +02:00
liquidraver c005a0cc67 vcontact race fix 2026-09-20 21:23:17 +02:00
liquidraver 3a2f9f5a0a fix 1byte/2byte weirdnesses 2026-09-20 11:13:59 +02:00
liquidraver 946b871d46 boot reset cause: plain-English hints in the restart notice, fix nrf52840 vbus wake docs 2026-09-19 21:35:22 +02:00
Pieter ViljoenandClaude Opus 5 6ec31e7b7f capture the reset cause at boot instead of consuming it in main()
hwinfo_get_reset_cause() reads a latched register. It was read inline in
main_companion.cpp and cleared there, so the repeater, room server and
observer never read it and never cleared it: their flags carried into the next
boot, and whichever role read first defined what any later reader would see.

helpers/boot_info.c reads it once in a SYS_INIT at POST_KERNEL, logs it,
stores it and clears the register. That runs before main() on every role.
zephcore_boot_reset_cause() hands out the stored copy and reports false where
the platform has no support, which a caller can distinguish from a cause of 0.

The clear's return is discarded deliberately: ESP32 has no
hwinfo_clear_reset_cause() implementation, so the weak stub returns -ENOSYS.
The capture is still correct there; only the next boot's reading is not.

Logging in boot_info_init() rather than only in the consuming role matters
because the clear now happens everywhere. Without it a repeater that rebooted
on WATCHDOG or LOCKUP would have the evidence cleared and recorded nowhere,
where before it survived in the register for whoever read it over SWD.

main_companion.cpp reads the stored copy and renders through a table with one
row per RESET_* bit, so a cause with no label is no longer logged as bare hex.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 11:58:01 -07:00
liquidraver a5a67e9ea6 boot_prefs shared helper 2026-09-19 20:42:47 +02:00
liquidraver 3055ae9269 Merge pull request #90 from mepholic/fix/gps-duty-boot-ordering
Fix GPS duty interval read before loadPrefs() on repeater/room server
2026-09-19 20:15:09 +02: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
Dan TheisenandClaude Opus 5 3d5fb9461c Fix GPS duty interval read before loadPrefs() on repeater/room server
main_repeater.cpp and main_room_server.cpp applied the GPS duty interval
~55 lines before data_store.loadPrefs() ran, so getNodePrefs()->gps_interval
still held the initNodePrefs() value of 300 s.

That 300 is the *companion* default. loadPrefs() is not a pure read on these
roles: its first-boot path is the only place the 48 h repeater/room-server
default is established, and it persists that to flash so later code can treat
flash as authoritative. A stored value is likewise only in the struct after
loadPrefs() returns. Reading gps_interval before that call therefore cannot
see either the role default or the operator's setting -- only the companion
constant.

Two consequences, on every boot:

  - A stock GPS-equipped repeater duty-cycled its GPS every 5 minutes instead
    of every 48 hours, ~576x more wakeups than intended. On battery nodes this
    is a straight power regression.

  - Any operator value never reached the hardware. "set gps duty 0" (always-on)
    persisted to flash correctly, and `get gps duty` reports the manager's
    now-effective value, so it answered 300 -- readable as a stale default
    rather than as the setting being ignored.

The GPS manager itself is correct: gps_start_acquiring(), the fix-done
callback, gps_enable() and the fresh-fix request all guard standby on
gps_duty_cycling(), so the always-on invariant holds once the manager is
actually told the interval is zero.

Same ordering class as the two bugs already documented in these files -- radio
frequency binding and the LED master switch -- where CLI readback looked right
and the hardware disagreed. main_companion.cpp has always had the correct
order (loadPrefs() at 1461, gps_set_poll_interval_sec() at 1616) and is
unaffected.

Move the interval and the mode switch that starts acquisition to just after
loadPrefs(). Callback registration stays where it was: it has no prefs
dependency and must precede acquisition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 10:29:54 +00:00
liquidraver 39b9584b20 .editorconfig and .gitattributes 2026-09-18 21:25:19 +02:00
liquidraver 1c2c942849 command reply buffer and tcp oversize fix 2026-09-17 10:59:13 +02:00
liquidraver da0450e511 upstream sync + wio tracker 1w 2026-09-16 11:29:45 +02:00
liquidraver 69cc148fbe properly reset CAD offset when needed 2026-09-10 21:56:52 +02:00
liquidraver bdf7348208 CAD offset safety net 2026-09-03 22:47:26 +02:00
liquidraver 7ae467305a transmit airtime fix 2026-09-03 19:02:51 +02:00
liquidraver 1633274e4f leds refactor/improvement 2026-09-03 14:34:12 +02:00
liquidraver 355ce04b21 numerous more cad fixes and improvements 2026-09-02 22:39:40 +02:00
liquidraver ba33cebf6a cad improvements 2026-09-01 22:05:29 +02:00
liquidraver ccb7fc748d command fixes 2026-08-30 09:27:20 +02:00
liquidraver 7331ca6c58 sync with upstream but held off the companion CLI 2026-08-29 22:08:59 +02:00
liquidraver 6258b2264c refactor CRLF de-duplicator 2026-08-28 11:11:26 +02:00
liquidraver 27ea7e41a5 flash cleaning improvements 2026-08-28 09:41:43 +02:00
liquidraver 2e3f5f3f59 display/input rotate 2026-08-25 15:19:11 +02:00
liquidraver dc5b5c2594 radio driver fixes v1 2026-08-23 22:49:17 +02:00
liquidraver 7d695872d4 implement radio.fem.rxgain 2026-08-22 17:32:41 +02:00
liquidraver 9ba23afbe2 vcontact improvements 2026-08-22 10:50:02 +02:00
liquidraver 9fc3ab7f7e PR70 fix 2026-08-20 21:13:15 +02:00
liquidraver 5c4ac21bd4 refactor patches and bump to 1.17.2 2026-08-20 17:50:48 +02:00
liquidraver 9ab9a9b950 lr2021 cleanup 2026-08-16 22:56:28 +02:00
liquidraver 4473b261ee rx path fixes, dc.restart wired in to lr chips,
TX timeout magic number is gone
2026-08-14 13:27:54 +02:00
liquidraver aac97f7ade vcontact and cad fixes 2026-08-12 15:36:51 +02:00
liquidraver ee90693c89 lr20xx: add freq-error summary log and get freqerr CLI 2026-08-12 15:12:43 +02:00
liquidraver 3494411f83 x1 debug 2026-08-10 19:15:26 +02:00
liquidraver 41cc786496 the repo is radiating seeed X1 vibes 2026-08-08 15:19:20 +02:00