update documentation

This commit is contained in:
liquidraver
2026-07-04 09:41:09 +02:00
parent cb3d81535b
commit 487693f669
13 changed files with 573 additions and 111 deletions
-1
View File
@@ -93,7 +93,6 @@ WEST_UPDATE.md
THINKNODE_M1_HANDOVER.md
CLAUDE.md
zephcore/apc_checklist.md
zephcore/apc.md
RADIO_AUDIT_INDEX.md
RX_BUSY_LATCH_PLAN.md
zephcore/SX126X_LBT_RX_DEBUG_HANDOFF.md
+22 -11
View File
@@ -28,7 +28,8 @@ Other benefits:
| **ThinkNode M3** | LR1110 | GPS, buzzer, two buttons, RGB LEDs |
| **ThinkNode M6** | SX1262 | GPS (L76K), QSPI flash, RGB LEDs |
| **LilyGo T-Echo** | SX1262 (TCXO 1.8V) | GPS (L76K), 1.54" e-paper (SSD1681), BME280, QSPI flash, touch-button backlight |
| **Heltec Mesh Node T096** | SX1262 + KCT8103L PA/FEM | UC6580 GNSS, button, LED, battery ADC |
| **Heltec T114** | SX1262 | 1.14" TFT (ST7789V); screenless build via `no_display.conf` |
| **Heltec Mesh Node T096** | SX1262 + KCT8103L PA/FEM | UC6580 GNSS, ST7735S 160×80 TFT, button, LED, battery ADC |
| **Ikoka Nano 30dBm** | SX1262 (E22-900M30S, 30 dBm PA) | RGB LEDs |
| **GAT562 30S Mesh Kit** | SX1262 (30 dBm / 1 W PA) | RAK4631 core module. OLED (SSD1306), 5-way joystick, buzzer, GPS, BME280 pad, 2×18650 + solar |
| **SenseCAP Solar** | SX1262 | GPS (L76K), QSPI flash, battery monitor |
@@ -49,6 +50,7 @@ Other benefits:
| **Heltec V4.3** | ESP32-S3 | SX1262 + KCT8103L PA | OLED (SSD1306), 16MB flash, 2MB PSRAM |
| **Heltec Wireless Tracker** | ESP32-S3 | SX1262 | ST7735R 160×80 TFT, UC6580 GPS |
| **LilyGo T-Beam v1.2** | ESP32 (PICO-D4) | SX1262 | AXP2101 PMU, GNSS, USB-UART CLI |
| **TTGO LoRa32** | ESP32 (PICO-D4) | **SX1276** (loramac-node backend) | SX127x reference board, USB-UART CLI |
### Other
@@ -56,6 +58,9 @@ Other benefits:
|-------|-----|-------|--------|
| **XIAO nRF54L15 + Wio-SX1262** | nRF54L15 | SX1262 | FLPR multicore, RRAM storage |
| **XIAO MG24 + Wio-SX1262** | EFR32MG24 | SX1262 | BLE (SiLabs blob) |
| **Seeed LoRa-E5 mini** | STM32WL (STM32WLE5JC) | Integrated sub-GHz (SX1262-class) | No BLE/USB — companion + CLI over USART1 |
ZephCore also runs as a **native Linux process** on SBCs (Femtofox / Luckfox Pico Mini, Raspberry Pi + RAK6421 HAT) with a real SX1262 on SPI/GPIO and the companion app connecting over TCP — see [LINUX_NATIVE.md](zephcore/LINUX_NATIVE.md).
For exact `west build -b` board strings, flash methods, and special setup, see the [supported boards list](zephcore/boards/supported_boards.md) and the [Board Porting Guide](zephcore/boards/example_board/README.md).
@@ -112,8 +117,9 @@ west build -b xiao_esp32c3 zephcore --pristine -- \
# Formatter (factory-reset utility)
west build -b wio_tracker_l1 zephcore/tools/formatter --pristine
# BLE debug logging overlay
west build -b rak4631 zephcore --pristine -- -DCONFIG_ZEPHCORE_BLE_LOG_LEVEL_DBG=y
# BLE debug logging (debug.conf enables logging; the flag raises the BLE adapter to DBG)
west build -b rak4631 zephcore --pristine -- \
-DEXTRA_CONF_FILE="boards/common/debug.conf" -DCONFIG_ZEPHCORE_BLE_LOG_LEVEL_DBG=y
```
Output binaries are in `build/zephyr/` -- `.hex`, `.uf2`, and DFU `.zip` as applicable.
@@ -176,13 +182,13 @@ The old `txdelay`, `rxdelay`, and `direct.txdelay` commands are still accepted f
**CLI commands:**
- `get txdelay` -- shows adaptive status: contention estimate and current flood delay factor
- `get/set backoff.multiplier` -- reactive backoff cap (default 0.5, range 0.0-2.0). Set to 0 to disable reactive backoff (EMA window still works). Higher values allow more per-packet deferral in dense areas.
- `get/set backoff.multiplier` -- per-dupe reactive backoff multiplier (default 0.2, range 0.0-2.0). Set to 0 to disable reactive backoff (EMA window still works). Higher values allow more per-packet deferral in dense areas.
**Compatibility**: Purely local behavior, no wire protocol changes. Works alongside Arduino MeshCore repeaters -- their retransmits are counted as dupes just the same.
## Power Saving
- **LoRa RX duty cycle**: CAD-based receive windowing reduces LoRa RX current from ~10-15mA to ~3-5mA. Auto-enabled for SX1262 companion and repeater builds (toggleable at runtime via `set rxduty on/off`). Disabled for LR1110 (mid-preamble lock issue) and SX127x.
- **LoRa RX duty cycle**: chip-autonomous receive windowing (sniff mode) reduces LoRa RX current from ~10-15mA to ~3-5mA. Off by default; toggle at runtime with `set rxduty on/off` (SX126x only — unsupported on LR1110 due to a mid-preamble lock issue, and on SX127x). Window timing is auto-sized per SF/BW/preamble from the SX126x datasheet constraints.
- **Adaptive Power Control (APC)**: compiled in by default but disabled at runtime. Enable per-node with `set tx apc` -- automatically reduces TX power when echo SNR shows excess margin, ramping back up if echoes drop. See [apc.md](zephcore/apc.md).
- **Production by default**: No logging, no asserts, reboot-on-fatal. Add `debug.conf` to enable logging.
- **GPIO-gated GPS**: Powered on only during fix acquisition
@@ -200,14 +206,14 @@ Key Kconfig options (set in board configs or via `-D` flags):
| `CONFIG_ZEPHCORE_RADIO_LR1110` | n | LR1110/LR1120/LR1121 (custom driver) |
| `CONFIG_ZEPHCORE_RADIO_LR2021` | n | LR2021 (custom driver) |
| `CONFIG_ZEPHCORE_RADIO_SX127X` | n | SX1272/SX1276/SX1278 (loramac-node backend) |
| `CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE` | auto | CAD-based RX power saving (auto ON for companion+SX1262, OFF for LR1110/SX127x) |
| `CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE` | n | RX duty cycle (sniff mode) boot default; runtime toggle via `set rxduty on/off` (SX126x only) |
| `CONFIG_ZEPHCORE_APC` | y (compiled in, runtime OFF) | Adaptive Power Control — enable at runtime via CLI |
| `CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM` | 22 | Initial TX power; lower for boards with external PA |
| `CONFIG_ZEPHCORE_MAX_TX_POWER_DBM` | 22 | Hard cap (radio adapter clamps above this) |
| `CONFIG_ZEPHCORE_MAX_CONTACTS` | 350 | Contact storage slots (companion) |
| `CONFIG_ZEPHCORE_MAX_CHANNELS` | 40 | Channel slots (companion) |
| `CONFIG_ZEPHCORE_BLE_PASSKEY` | 123456 | BLE pairing PIN |
| `CONFIG_ZEPHCORE_GPS_POLL_INTERVAL_SEC` | 300 | Companion GPS duty interval between fixes (seconds); 0 = always-on |
| `CONFIG_ZEPHCORE_GPS_POLL_INTERVAL_SEC` | 300 | Companion GPS duty interval between fixes (seconds, 1086400); always-on is a runtime setting (`set gps duty 0`) |
| `CONFIG_ZEPHCORE_GPS_FIRST_FIX_TIMEOUT_SEC` | 300 | Cold-start window for the very first fix (longer to allow almanac download) |
| `CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC` | 172800 | Repeater/room-server GPS duty interval boot default (48 h); 0 = always-on |
| `CONFIG_ZEPHCORE_WIFI_OTA` | n | WiFi AP + HTTP OTA updates (ESP32 repeaters, requires `--sysbuild`) |
@@ -220,7 +226,7 @@ Key Kconfig options (set in board configs or via `-D` flags):
```
zephcore/
src/ Core mesh engine (Mesh, Dispatcher, Packet, Identity, ContentionTracker)
app/ Companion / Repeater / Observer role implementations
app/ Companion / Repeater / Room Server / Observer role implementations
adapters/
ble/ BLE NUS transport
board/ GPIO, LED, power management
@@ -232,17 +238,22 @@ zephcore/
radio/ LoRa radio drivers (SX126x, LR1110, LR2021, SX127x)
rng/ Random number generator
sensors/ I2C sensor auto-detection
transport/ TCP companion (native Linux) + serial companion (STM32WL)
usb/ USB serial transport (CDC-ACM, V3 framing)
wifi/ WiFi station client
helpers/
ui/ Display, buzzer, button input
ui/ Shared UI plumbing (display, buzzer, multi-tap input)
ui-button/ Single-button page UI
ui-joystick/ 5-way joystick UI (Wio Tracker L1)
boards/
nrf52840/ nRF52840 board overlays and configs
esp32/ ESP32-C3/C6/S3 board overlays and configs
esp32/ ESP32 (classic + C3/C6/S3) board overlays and configs
nrf54l/ nRF54L15 board overlay and config
mg24/ EFR32MG24 board overlay and config
stm32wl/ Seeed LoRa-E5 board overlay and config
linux_native/ native_sim presets (Femtofox, RAK6421)
common/ Shared Kconfig fragments and devicetree includes
lib/ ED25519 crypto library
lib/ Monocypher crypto library (Ed25519/X25519)
patches/ Auto-applied patches to the Zephyr tree
```
+1 -1
View File
@@ -2,6 +2,6 @@ nRF formatter tools
- QSPI is formatted for all supported boards
- Watch out for softdevice version! Flashing the wrong version can corrupt the node and you'll need a full bootloader reflash with adafruit-nrfutil!
- You can check what softdevice versio you use if you open INFO_UF2.TXT on the storage drive when in DFU mode. Bootloader should say "sxxx 6.x.x" for v6 and "sxxx 7.x.x" for v7
- You can check what softdevice version you use if you open INFO_UF2.TXT on the storage drive when in DFU mode. Bootloader should say "sxxx 6.x.x" for v6 and "sxxx 7.x.x" for v7
- Formatter output logs from the process over serial
- After format, it puts back the device to Mass Storage DFU mode
+191 -78
View File
@@ -32,11 +32,11 @@ ZephCore is a LoRa mesh networking firmware running on Zephyr RTOS. It supports
- **Room Server**: Headless store-and-forward shared message room (BBS). Reuses the repeater's ACL/region/CLI; pushes new posts to logged-in clients (per-client sync cursor + ACK).
- **Observer** (ESP32): Listen-only node that publishes received LoRa packets to MQTT over WiFi.
Supported hardware: nRF52840, nRF54L15, ESP32-C3/C6/S3, EFR32MG24 — all with SX1262 or LR1110 LoRa radios.
Supported hardware: nRF52840, nRF54L15, ESP32 (classic PICO-D4 and C3/C6/S3), EFR32MG24, and STM32WL (LoRa-E5). Radios: SX126x family (SX1261/62/68, LLCC68, STM32WL sub-GHz), LR1110, SX127x (SX1272/76/78, loramac-node backend), and LR2021 (experimental bring-up). A native Linux port runs the full stack on SBCs (Femtofox, Raspberry Pi) via Zephyr `native_sim` — see `LINUX_NATIVE.md`.
### Upstream Relationship
ZephCore is a port of [Arduino MeshCore](https://github.com/rmendes76/MeshCore). The core mesh protocol (Mesh.cpp, Dispatcher.cpp, Packet.cpp, Identity.cpp, Utils.cpp) is shared code. Adapters (`adapters/`) bridge MeshCore's HAL interfaces to Zephyr APIs. Binary file formats (prefs, contacts, channels) are byte-compatible with Arduino MeshCore.
ZephCore is a port of [Arduino MeshCore](https://github.com/meshcore-dev/MeshCore). The core mesh protocol (Mesh.cpp, Dispatcher.cpp, Packet.cpp, Identity.cpp, Utils.cpp) is shared code. Adapters (`adapters/`) bridge MeshCore's HAL interfaces to Zephyr APIs. Binary file formats (prefs, contacts, channels) are byte-compatible with Arduino MeshCore.
---
@@ -50,15 +50,20 @@ zephcore/
│ ├── Packet.cpp # Packet serialization, hash, wire format
│ ├── Identity.cpp # Ed25519 key management, ECDH shared secrets
│ ├── Utils.cpp # AES-ECB encrypt, HMAC-SHA256, MAC
│ ├── ContentionTracker.cpp # Adaptive contention window (EMA, backoff)
│ ├── PowerController.cpp # Adaptive Power Control (APC)
│ ├── StaticPoolPacketManager.cpp # Fixed-size packet pool (32 slots)
│ ├── main_companion.cpp # Companion mode entry point + event loop
── main_repeater.cpp # Repeater mode entry point + event loop
── main_repeater.cpp # Repeater mode entry point + event loop
│ └── main_room_server.cpp # Room server mode entry point + event loop
├── include/mesh/ # Core interfaces (shared with Arduino MeshCore)
│ ├── Mesh.h, Dispatcher.h, Packet.h, Identity.h, Utils.h
│ ├── MeshCore.h # Constants: key sizes, packet limits
│ ├── Radio.h # Abstract radio interface
│ ├── Board.h, Clock.h, RNG.h, RTC.h # HAL interfaces
│ ├── ContentionTracker.h # Adaptive contention window state
│ ├── PowerController.h # APC state machine
│ ├── LoRaConfig.h # Default radio parameters
│ ├── RadioIncludes.h # Compile-time radio driver selection
│ ├── SimpleMeshTables.h # Hash-based packet deduplication
@@ -67,52 +72,72 @@ zephcore/
├── adapters/ # Zephyr HAL implementations
│ ├── radio/ # LoRa radio drivers
│ │ ├── LoRaRadioBase.cpp/h # Shared TX/RX state machine, noise floor, AGC
│ │ ├── SX126xRadio.cpp/h # SX1262 adapter (native Zephyr driver)
│ │ ├── LR1110Radio.cpp/h # LR1110 adapter (patched Zephyr driver)
│ │ ├── SX126xRadio.cpp/h # SX126x adapter (native Zephyr driver, patched)
│ │ ├── SX127xRadio.cpp/h # SX127x adapter (loramac-node backend)
│ │ ├── LR1110Radio.cpp/h # LR1110 adapter (custom Zephyr driver)
│ │ ├── LR2021Radio.cpp/h # LR2021 adapter (custom driver, experimental)
│ │ ├── radio_common.h # Shared radio types and constants
│ │ ── lr11xx/ # LR11xx low-level HAL (SPI, GPIO, Semtech SDK)
│ │ ── lr11xx/ # LR11xx low-level HAL (SPI, GPIO, Semtech SDK)
│ │ └── lr20xx/ # LR20xx low-level HAL (Semtech SDK)
│ ├── ble/ZephyrBLE.cpp/h # BLE NUS service, pairing, TX congestion
│ ├── board/ZephyrBoard.cpp/h # Battery ADC, LEDs, reboot, bootloader
│ ├── clock/ # Millisecond uptime + software RTC
│ ├── clock/ # Millisecond uptime + software RTC + I2C RTC discovery
│ ├── datastore/ZephyrDataStore.cpp/h # LittleFS persistence
│ ├── gps/ZephyrGPSManager.cpp/h # GNSS state machine, power mgmt
│ ├── mqtt/ZephyrMQTTPublisher.c/h # MQTT packet publisher (observer / uplink)
│ ├── ota/wifi_ota.c/h # WiFi SoftAP + HTTP firmware upload
│ ├── rng/ZephyrRNG.cpp/h # Hardware CSPRNG with PRNG fallback
│ ├── sensors/ # I2C env sensors + power monitors
── usb/ # USB CDC for companion + repeater
── transport/ # TCP companion (native Linux) + serial companion (STM32WL)
│ ├── usb/ # USB CDC for companion + repeater
│ └── wifi/ZephyrWiFiStation.c/h # WiFi station client (ESP32)
├── app/ # Application layer
│ ├── CompanionMesh.cpp/h # Phone-connected companion logic
│ ├── RepeaterMesh.cpp/h # Autonomous repeater logic
── RepeaterDataStore.cpp/h # Repeater-specific persistence paths
── RepeaterRegionCLI.cpp # Repeater `region` CLI commands
│ ├── RepeaterUplink.cpp # Repeater WiFi+MQTT uplink (ESP32)
│ ├── RepeaterDataStore.cpp/h # Repeater-specific persistence paths
│ ├── RoomServerMesh.cpp/h # Store-and-forward room server (BBS)
│ ├── RoomServerRegionCLI.cpp # Room server `region` CLI commands
│ ├── ObserverMesh.cpp/h # Listen-only WiFi+MQTT observer (ESP32)
│ └── main_observer.cpp, observer_creds.cpp/h
├── helpers/ # Shared utilities
│ ├── BaseChatMesh.cpp/h # Contact/channel/message base class
│ ├── CommonCLI.cpp/h # Serial/mesh CLI command processor
│ ├── MeshTimeSync.cpp/h # Mesh clock-consensus estimator (§4.9)
│ ├── AdvertDataHelpers.cpp/h # Advertisement wire format encoder/decoder
│ ├── ClientACL.cpp/h # Authenticated client management
│ ├── TransportKeyStore.cpp/h # Region transport key cache
│ ├── RegionMap.cpp/h # Region-based flood filtering
│ ├── ContactInfo.h, ChannelDetails.h, NodePrefs.h # Data structures
│ ├── RateLimiter.h, IdentityStore.h, StatsFormatHelper.h
── ui/ # Display, buzzer, input, pages, Doom game
── battery_curve.c/h, fatal_reboot.c, oled_power.c/h
│ ├── ui/ # Shared UI plumbing: display, buzzer, multi-tap input, Doom
│ ├── ui-button/ # Single-button page UI (pages, task)
│ └── ui-joystick/ # 5-way joystick UI (Wio Tracker L1)
├── boards/ # Board definitions
│ ├── common/ # Shared configs, DTS includes, partition layouts
│ ├── nrf52840/ # RAK4631, WisMesh Tag, T1000-E, ThinkNode M1, etc.
│ ├── nrf52840/ # RAK4631, T1000-E, ThinkNode M1/M3/M6, T-Echo, T114, ...
│ ├── nrf54l/ # XIAO nRF54L15
│ ├── esp32/ # LilyGo TLoRa C6, Station G2, XIAO ESP32-C3/C6
── mg24/ # XIAO MG24
│ ├── esp32/ # XIAO C3/C6/S3, Heltec V3/V4.x, Station G2, T-Beam, ...
── mg24/ # XIAO MG24
│ ├── stm32wl/ # Seeed LoRa-E5 mini
│ └── linux_native/ # native_sim presets (Femtofox, RAK6421) — see LINUX_NATIVE.md
├── patches/ # Zephyr tree modifications
│ ├── zephyr/ # Unified diffs (SX126x extensions, GNSS, blobs)
│ └── zephyr-new/ # New files (LR11xx Zephyr driver, DTS bindings)
│ ├── zephyr/ # Unified diffs (SX126x extensions, GNSS, native Linux, ...)
│ └── zephyr-new/ # New files (LR11xx/LR20xx drivers, native Linux SPI/GPIO, DTS bindings)
├── lib/ed25519/ # Vendored Ed25519 crypto library
├── lib/monocypher/ # Vendored crypto library (Ed25519/X25519)
├── tools/ # Formatter (flash erase) + LR1110 firmware updater
├── CMakeLists.txt # Build orchestration
├── Kconfig # All ZephCore configuration options
├── Kconfig.psram # ESP32 PSRAM auto-enable from devicetree
├── prj.conf # Base project config
├── sysbuild.conf # Forces MCUboot when --sysbuild is used
└── west.yml # West manifest (Zephyr version pin)
```
@@ -122,9 +147,11 @@ zephcore/
```
┌─────────────────────────────────────────────────┐
│ Phone App (BLE NUS) or Serial CLI (USB CDC) │ External
│ Phone App (BLE NUS / USB CDC / TCP / UART) │ External
│ or Serial CLI (USB CDC / PTY) │
├─────────────────────────────────────────────────┤
│ CompanionMesh / RepeaterMesh │ App Layer
│ CompanionMesh / RepeaterMesh / │ App Layer
│ RoomServerMesh / ObserverMesh │
│ ├── BaseChatMesh (contacts, channels, msgs) │
│ ├── CommonCLI (command processor) │
│ ├── ClientACL, RegionMap, TransportKeyStore │
@@ -145,7 +172,9 @@ zephcore/
├─────────────────────────────────────────────────┤
│ LoRaRadioBase │ Radio HAL
│ ├── SX126xRadio ──► Zephyr SX126x driver │
── LR1110Radio ──► Custom LR11xx driver
── SX127xRadio ──► loramac-node backend
│ ├── LR1110Radio ──► Custom LR11xx driver │
│ └── LR2021Radio ──► Custom LR20xx driver │
├─────────────────────────────────────────────────┤
│ Zephyr RTOS (kernel, drivers, BLE, FS, USB) │ Platform
└─────────────────────────────────────────────────┘
@@ -335,10 +364,12 @@ All policy timers (6 h rate limit, 7-day suppression, tenure, sample age) anchor
mesh::Radio (abstract interface)
└── LoRaRadioBase (shared state machine, ring buffer, noise floor)
├── SX126xRadio → Zephyr native SX126x driver + sx126x_ext.h
── LR1110Radio → Custom lr11xx_lora.c driver + Semtech HAL
── SX127xRadio → Zephyr loramac-node backend (SX1272/76/78)
├── LR1110Radio → Custom lr11xx_lora.c driver + Semtech HAL
└── LR2021Radio → Custom lr20xx_lora.c driver + Semtech HAL (experimental)
```
Compile-time selection via `CONFIG_ZEPHCORE_RADIO_LR1110` in `RadioIncludes.h`.
Compile-time selection via the `CONFIG_ZEPHCORE_RADIO_NATIVE` / `_LR1110` / `_LR2021` / `_SX127X` Kconfig options, resolved in `RadioIncludes.h`. The native SX126x path is the default and covers SX1261/62/68, LLCC68, and the STM32WL integrated sub-GHz radio.
### 5.2 LoRaRadioBase State Machine
@@ -404,7 +435,12 @@ The custom `lr11xx_lora.c` driver handles several LR1110 firmware bugs:
- **DIO1 stuck HIGH**: 5-cycle detection → full hardware reset + recovery
- **RX duty cycle**: wired via `SetRxDutyCycle` MODE_RX, sized by the shared adapter math (same as SX126x). The earlier "broken, 23-40% loss" verdict was a window-sizing bug (over-sleep + no header budget), not a chip defect — default-off, HW-verify before production use.
### 5.5 Default Radio Parameters
### 5.5 SX127x and LR2021 Paths
- **SX127x** (`CONFIG_ZEPHCORE_RADIO_SX127X`): uses Zephyr's loramac-node LoRa backend instead of the native driver (`CONFIG_LORA_MODULE_BACKEND_LORAMAC_NODE`). Patch `0004-lora-sx127x-62k5-bandwidth` adds the 62.5 kHz bandwidth MeshCore defaults to. No RX duty cycle and no RX gain boost on this path. Reference board: TTGO LoRa32 (SX1276).
- **LR2021** (`CONFIG_ZEPHCORE_RADIO_LR2021`): custom driver in `patches/zephyr-new/drivers/lora/lr20xx/` (copied into the Zephyr tree at configure time, like LR11xx). Experimental — bring-up on the ProMicro LR2021 is still in progress; not listed as a supported board.
### 5.6 Default Radio Parameters
| Parameter | Default | Notes |
|-----------|---------|-------|
@@ -425,13 +461,15 @@ The custom `lr11xx_lora.c` driver handles several LR1110 firmware bugs:
mesh::Mesh
├── BaseChatMesh (contacts, channels, messages, connections)
│ └── CompanionMesh (BLE protocol, phone sync, offline queue, ACK tracking)
── RepeaterMesh (ClientACL, RegionMap, CLI, rate limiting, neighbor tracking)
── RepeaterMesh (ClientACL, RegionMap, CLI, rate limiting, neighbor tracking)
├── RoomServerMesh (store-and-forward BBS; reuses repeater ACL/region/CLI)
└── ObserverMesh (listen-only; publishes packets to MQTT over WiFi — ESP32)
```
### 6.2 CompanionMesh
Handles the binary BLE protocol with ~60 command opcodes. Key features:
- **Offline queue**: 16-frame circular buffer with peek/confirm pattern (survives BLE drops)
Handles the binary BLE protocol with ~50 command opcodes. Key features:
- **Offline queue**: circular buffer with peek/confirm pattern (survives BLE drops); `CONFIG_ZEPHCORE_OFFLINE_QUEUE_SIZE`, default 256 frames (lowered on RAM-bound boards)
- **ACK tracking**: 8-slot table, computes expected ACK = SHA256(secret + hash)[0:4]
- **Contact iteration**: Streaming protocol with `lastmod` filtering for incremental sync
- **Lazy write batching**: Dirty contacts/channels flush after 5-second delay
@@ -445,18 +483,29 @@ Autonomous operation features:
- **Authentication**: Password-based login with timestamp replay protection (120s window)
- **Permission levels**: GUEST(0), READ_ONLY(1), READ_WRITE(2), ADMIN(3)
- **Region filtering**: `RegionMap` with transport key matching per flood packet
- **Rate limiting**: 4 requests per 120s (discovery), 4 per 180s (anonymous)
- **Neighbor tracking**: 16-slot table with RSSI/SNR/name/timestamp
- **Rate limiting**: 4 requests per 120s (discovery), 4 per 180s (anonymous), 4 failed logins per 180s
- **Neighbor tracking**: RSSI/SNR/name/timestamp table (`CONFIG_ZEPHCORE_MAX_NEIGHBOURS`, default 50 slots)
- **Temporary radio params**: `tempradio` command applies freq/bw/sf/cr via `LoRaRadioBase::setRadioOverride()` (does not mutate `_prefs`); auto-revert timer calls `clearRadioOverride()` to fall back to saved prefs
- **WiFi+MQTT uplink** (ESP32, `CONFIG_ZEPHCORE_REPEATER_UPLINK`): `RepeaterUplink.cpp` reports packets observer-style while still repeating; configured via `set uplink.*` CLI
### 6.4 CommonCLI Commands
### 6.4 RoomServerMesh
Headless store-and-forward shared message room (BBS). Clients log in with the admin or guest password and post messages; the server pushes each new post to every other logged-in client (per-client sync cursor + ACK). Reuses the repeater's ACL, region filtering, and USB CLI. Entry point `main_room_server.cpp`; build with `boards/common/room_server.conf`.
### 6.5 ObserverMesh
Listen-only node (ESP32 only): receives LoRa packets and publishes them to an MQTT broker over WiFi STA (`adapters/mqtt/`, `adapters/wifi/`). Never transmits. Configured at runtime via serial CLI (credentials in `observer_creds.cpp`); build with `boards/common/observer.conf`.
### 6.6 CommonCLI Commands
System: `ver`, `board`, `reboot`, `start dfu`, `start ota`, `erase`
Config: `set name/freq/radio/tx/flood.max/password/...`, corresponding getters
GPS: `gps on/off/setloc/advert`
GPS: `gps on/off/setloc/advert`, `set gps duty <sec>`
Sensors: `sensor get/set/list`
Stats: `stats-core/stats-radio/stats-packets`, `clear stats`
Power: `powersaving on/off`
Time: `clock`, `clock sync`, `time <epoch>`, `set meshtimesync on/off`
Full command reference with constraints and remote-admin restrictions: `Repeater_CLI_commands.md`.
---
@@ -475,14 +524,14 @@ Power: `powersaving on/off`
- Fast/slow advertising switching with post-disconnect flap prevention
- DLE (Data Length Extension) to 251 bytes
- Interface coexistence: BLE vs USB, one active at a time
- Debug: `boards/common/ble_debug.conf` overlay enables DBG on bt_smp/att/gatt/conn
- Debug: build with `debug.conf` plus `-DCONFIG_ZEPHCORE_BLE_LOG_LEVEL_DBG=y` for adapter-level DBG logging
### 7.2 DataStore (`adapters/datastore/`)
- **Internal**: LittleFS on flash (`/lfs`), 256-byte cache for reduced flash I/O
- **External**: Optional LittleFS on QSPI (`/ext`) with auto-migration
- **BLE bonds**: NVS (`storage_partition`, 0xD0000 on nRF52) via Zephyr settings backend (≥1.16.2)
- **Prefs**: 292-byte binary format, Arduino-compatible, field-by-field I/O (see §13)
- **Prefs**: 152-byte binary (companion `new_prefs`), Arduino-compatible base + ZephCore extension fields, field-by-field I/O (see §13)
- **Contacts**: 152-byte records, stored on external flash if available
- **Channels**: 68-byte records (4 pad + 32 name + 32 secret)
- **Blobs**: Fixed-size records with LRU eviction by timestamp
@@ -538,6 +587,14 @@ Repeaters and room servers default to `CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC
- TX LED bracketing for LoRa transmissions
- Bootloader version detection via flash memory scan
### 7.6 WiFi / MQTT / TCP Transports
- **`adapters/wifi/ZephyrWiFiStation.c`**: WiFi STA client (ESP32) used by observer and repeater uplink
- **`adapters/mqtt/ZephyrMQTTPublisher.c`**: MQTT publisher for observed/uplinked packets
- **`adapters/ota/wifi_ota.c`**: WiFi SoftAP + HTTP firmware upload to MCUboot slot1 (ESP32, requires `--sysbuild`)
- **`adapters/transport/LinuxTCPTransport.c`**: TCP companion transport on native Linux (port 5000, MeshCore `SerialWifiInterface` framing)
- **`adapters/transport/SerialCompanionTransport.c`**: UART companion transport (STM32WL — drop-in `zephcore_ble_*` provider, auto-selected when `CONFIG_BT=n`)
---
## 8. UI Subsystem
@@ -546,21 +603,28 @@ Repeaters and room servers default to `CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC
Event-driven, no dedicated thread. All UI work on Zephyr work queues.
Two UI frontends share the same plumbing (`helpers/ui/`: display, buzzer, multi-tap input filter, mesh action queue):
- **Button UI** (`helpers/ui-button/`): single-button page cycler — most boards
- **Joystick UI** (`helpers/ui-joystick/`): 5-way joystick menu UI (Wio Tracker L1)
```
Hardware buttons → Zephyr input subsystem → Longpress filter → Multi-tap filter
→ ui_input_cb() → page navigation / action dispatch → schedule_render()
→ render_work (50ms OLED / 200ms EPD debounce) → CFB framebuffer → display
```
### 8.2 Pages
Color TFT panels (T114, T096, Wireless Tracker) are wrapped as 1bpp displays for CFB via the `zephcore,mono-tft` shim (`display_mono_tft.c`).
**Companion** (11 pages): Messages, Recent, Radio, Bluetooth, Advert, GPS, Buzzer, Sensors, Offgrid, DFU, Shutdown
### 8.2 Pages (Button UI)
**Companion** (up to 12 pages): Messages, Recent, Radio, Bluetooth, Advert, GPS, Buzzer (if buzzer present), LEDs, Sensors, Offgrid, DFU, Shutdown
**Repeater** (3 pages): Status, Radio, Shutdown
### 8.3 Multi-Tap Input
Single button, up to 4 taps within 400ms window:
Single button; tap-count → key-code mapping comes from the board's devicetree `tap-codes` (up to 5). Typical mapping:
- 1 tap → Page next
- 2 taps → Flood advert
- 3 taps → Buzzer toggle
@@ -590,56 +654,87 @@ prj.conf (base: console; production defaults — LOG=n, ASSERT=n)
### 9.2 Key Kconfig Choices
- **Role**: `ZEPHCORE_ROLE_COMPANION` (default) vs `ZEPHCORE_ROLE_REPEATER`
- **Radio**: `ZEPHCORE_RADIO_NATIVE` (SX126x, default) vs `ZEPHCORE_RADIO_LR1110`
- **Features**: Display, buzzer, buttons, multi-tap, Doom (auto-enabled from DT)
- **Role**: `ZEPHCORE_ROLE_COMPANION` (default) vs `ZEPHCORE_ROLE_REPEATER` vs `ZEPHCORE_ROLE_ROOM_SERVER` vs `ZEPHCORE_ROLE_OBSERVER` (selected via `repeater.conf` / `room_server.conf` / `observer.conf`)
- **Radio**: `ZEPHCORE_RADIO_NATIVE` (SX126x, default) vs `ZEPHCORE_RADIO_LR1110` vs `ZEPHCORE_RADIO_LR2021` vs `ZEPHCORE_RADIO_SX127X`
- **Features**: Display, buzzer, buttons, multi-tap, Doom (auto-enabled from DT); PSRAM auto-enable from DT (`Kconfig.psram`)
### 9.3 Platform Notes
- **nRF52840**: Zephyr open-source BLE controller, UF2 bootloader, partial flash erase for BLE coexistence
- **nRF54L15**: Same BLE controller as nRF52, CMSIS-DAP via SAMD11 bridge, no native USB
- **ESP32**: Espressif proprietary BLE blob, 32KB heap, asserts disabled (blob IRQ false positives)
- **ESP32-C3/C6/S3**: Espressif proprietary BLE blob, 32KB heap, asserts disabled (blob IRQ false positives); simple-boot by default, MCUboot only with `--sysbuild` (WiFi OTA)
- **ESP32 classic (PICO-D4)**: much smaller DRAM — contact/queue caps shrunk in `board.conf`; console/CLI on `uart0` (no native USB); DIO flash mode required (QIO bootloops)
- **EFR32MG24**: SiLabs proprietary BLE blob, 32KB heap, SEMAILBOX enabled for hardware TRNG/crypto entropy, ADC disabled (no battery divider), CMSIS-DAP via onboard SAMD11
- **STM32WL (LoRa-E5)**: no BLE, no USB device — companion protocol and CLI run over USART1; 64KB SRAM caps contacts/queues hard; TRNG entropy; single app partition, flash via SWD
- **Native Linux (`native_sim`)**: real SPI/GPIO via spidev + GPIO chardev; TCP companion transport; file-backed flash — see `LINUX_NATIVE.md`
### 9.4 Patches
Applied automatically at CMake configure time; a failed patch aborts the configure with the offending patch named.
| Patch | Risk | Purpose |
|-------|------|---------|
| 0001-lora-lr11xx-build | LOW | Integrates LR11xx driver into Zephyr LoRa build |
| 0003-lora-sx126x-native | **HIGH** | ~400 lines: DIO1 work queue, errata workarounds, extension API |
| 0002-lora-lr20xx-build | LOW | Integrates LR20xx driver into Zephyr LoRa build |
| 0003-lora-sx126x-native | **HIGH** | DIO1 work queue, duty cycle, RX-busy gating, extension API, errata workarounds |
| 0004-lora-sx127x-62k5-bandwidth | LOW | Adds 62.5 kHz bandwidth to the loramac-node backend |
| 0005-gnss-air530z-easy | MEDIUM | EASY ephemeris + removes PM (prevents deadlocks) |
| 0006-blobs-py | LOW | Fix `west blobs fetch` KeyError |
| 0007-spi-gpio-native-linux | LOW | Wires native-Linux SPI/GPIO drivers into the Zephyr build |
| 0008-flash-sim-per-node-file | LOW | Flash simulator defaults to per-node settings file (native Linux) |
| 0009-display-ssd16xx-fill-ram-white | LOW | E-paper full-refresh-to-white anti-ghosting helper |
New drivers in `patches/zephyr-new/` (LR11xx, LR20xx, native-Linux SPI/GPIO, DTS bindings) are copied — not patched — into the Zephyr tree at configure time.
### 9.5 Flash Partition Layouts
**nRF52840 SD v6**: SoftDevice 152KB → App 696KB → LFS 128KB → UF2 48KB
**nRF52840 SD v7**: SoftDevice 156KB → App 692KB → LFS 128KB → UF2 48KB
**ESP32 (4MB)**: Boot + App → LFS 192KB
**ESP32-S3 (16MB)**: Boot + App → LFS 384KB
**nRF52840 SD v6**: SoftDevice 152KB → App 680KB → NVS 16KB → LFS 128KB → UF2 48KB
**nRF52840 SD v7**: SoftDevice 156KB → App 676KB → NVS 16KB → LFS 128KB → UF2 48KB
**ESP32 (4MB)**: Boot + App → LFS 192KB + NVS 16KB
**ESP32-S3 (8/16MB)**: Boot + App → LFS 384KB + NVS 16KB
**nRF54L15**: MCUboot 64KB → App 1272KB → LFS 92KB
**EFR32MG24**: MCUboot 48KB → App 1344KB → LFS 144KB
**EFR32MG24**: MCUboot 48KB (reserved) → App 1344KB → LFS 144KB
**STM32WL**: App at flash origin → LFS (no bootloader)
---
## 10. Board Matrix
| Board | SoC | Radio | GPS | Display | Buzzer | Buttons | QSPI | Max Contacts |
|-------|-----|-------|-----|---------|--------|---------|------|-------------|
| RAK4631 | nRF52840 | SX1262 | gnss-nmea | - | - | - | - | 350 |
| RAK3401 1W | nRF52840 | SX1262+SKY66122 (30dBm) | gnss-nmea (opt) | - | - | - | - | 350 |
| WisMesh Tag | nRF52840 | SX1262 | Air530Z | - | Yes | 1+multitap | - | 350 |
| T1000-E | nRF52840 | **LR1110** | AG3335 | - | Yes | 1+multitap | - | 350 |
| ThinkNode M1 | nRF52840 | SX1262 | Air530Z | EPD 200x200 | Yes | 2+multitap | 2MB | 510 |
| Wio Tracker L1 | nRF52840 | SX1262 | L76K | OLED 128x64 | Yes | 5-way joy | 2MB | 510 |
| Ikoka Nano 30dBm | nRF52840 | SX1262+PA | - | - | - | - | - | 350 |
| XIAO nRF54L15 | nRF54L15 | SX1262 | - | - | - | - | - | 450 |
| XIAO ESP32-C3 | ESP32-C3 | SX1262 | - | - | - | - | - | 300 |
| XIAO ESP32-C6 | ESP32-C6 | SX1262 | - | - | - | - | - | 300 |
| LilyGo TLoRa C6 | ESP32-C6 | SX1262 | - | - | - | - | - | 300 |
| Station G2 | ESP32-S3 | SX1262+PA | UART1 | OLED 128x64 | - | 1 button | - | 350 |
| Heltec Wireless Tracker | ESP32-S3 | SX1262 | UC6580 | TFT 160x80 | - | - | - | 350 |
| LilyGo T-Beam v1.2 | ESP32 | SX1262 | gnss-nmea | - | - | - | - | 300 |
| XIAO MG24 | EFR32MG24 | SX1262 | - | - | - | - | - | 350 |
Build strings and flash methods: `boards/supported_boards.md` and `boards/example_board/README.md`.
| Board | SoC | Radio | GPS | Display | Notable extras |
|-------|-----|-------|-----|---------|----------------|
| RAK4631 / WisMesh Pocket | nRF52840 | SX1262 | u-blox MAX-7Q (opt) | WisBlock OLED (opt) | I2C sensors |
| RAK3401 1W | nRF52840 | SX1262+SKY66122 (30dBm) | u-blox MAX-7Q (opt) | - | I2C sensors |
| RAK WisMesh Tag | nRF52840 | SX1262 | AT6558R | - | Accelerometer, buzzer, multitap |
| T1000-E | nRF52840 | **LR1110** | AG3335 | - | Buzzer, button, multitap |
| ThinkNode M1 | nRF52840 | SX1262 | Air530Z | EPD 200x200 (SSD1681) | Buzzer, 2 buttons, QSPI 2MB, RGB LEDs |
| ThinkNode M3 | nRF52840 | **LR1110** | Yes | - | Buzzer, 2 buttons, RGB LEDs |
| ThinkNode M6 | nRF52840 | SX1262 | L76K | - | QSPI, RGB LEDs |
| Wio Tracker L1 | nRF52840 | SX1262 | L76K | OLED 128x64 (SH1106) | 5-way joystick UI, buzzer, QSPI 2MB |
| LilyGo T-Echo | nRF52840 | SX1262 (TCXO 1.8V) | L76K | EPD 1.54" (SSD1681) | BME280, QSPI, touch-button backlight |
| Heltec T114 | nRF52840 | SX1262 | - | TFT 240x135 (ST7789V) | Screenless build via `no_display.conf` |
| Heltec Mesh Node T096 | nRF52840 | SX1262+KCT8103L PA | UC6580 | TFT 160x80 (ST7735S) | Button, LED, battery ADC |
| Ikoka Nano 30dBm | nRF52840 | SX1262+PA (30dBm) | - | - | RGB LEDs |
| GAT562 30S Mesh Kit | nRF52840 | SX1262+PA (1W) | Yes | OLED (SSD1306) | 5-way joystick, buzzer, solar |
| SenseCAP Solar | nRF52840 | SX1262 | L76K | - | QSPI, battery monitor |
| XIAO nRF52840 + Wio-SX1262 | nRF52840 | SX1262 | - | - | - |
| ProMicro SX1262 | nRF52840 | SX1262 (E22-900M30S) | Yes | - | Button, LED, battery ADC |
| XIAO nRF54L15 | nRF54L15 | SX1262 | - | - | Contacts capped at 450 |
| XIAO ESP32-C3 | ESP32-C3 | SX1262 | - | - | Contacts capped at 300 |
| XIAO ESP32-C6 | ESP32-C6 | SX1262 | - | - | - |
| LilyGo TLoRa C6 | ESP32-C6 | SX1262 | - | - | - |
| XIAO ESP32-S3 | ESP32-S3 | SX1262 | - | - | 8MB flash, 8MB PSRAM |
| Station G2 | ESP32-S3 | SX1262+PA | UART GNSS | OLED (SH1106) | 16MB flash, 8MB PSRAM |
| Heltec V3 | ESP32-S3 | SX1262 | - | OLED (SSD1306) | Console on `uart0` |
| Heltec V4.2 / V4.3 | ESP32-S3 | SX1262+PA (GC1109 / KCT8103L) | - | OLED (SSD1306) | 16MB flash, 2MB PSRAM |
| Heltec Wireless Tracker | ESP32-S3 | SX1262 | UC6580 | TFT 160x80 (ST7735R) | - |
| LilyGo T-Beam v1.2 | ESP32 (PICO-D4) | SX1262 | Yes | - | AXP2101 PMU; contacts capped at 160 |
| TTGO LoRa32 | ESP32 (PICO-D4) | **SX1276** (loramac-node) | - | - | SX127x reference board |
| XIAO MG24 | EFR32MG24 | SX1262 | - | - | - |
| Seeed LoRa-E5 mini | STM32WL | STM32WL sub-GHz (SX1262-class) | - | - | UART companion/CLI; contacts capped at 24 |
Contact capacity is `CONFIG_ZEPHCORE_MAX_CONTACTS` (default 350) unless capped per-board as noted. Native-Linux presets (Femtofox, RAK6421) are `EXTRA_CONF_FILE` presets, not boards — see `LINUX_NATIVE.md`.
---
@@ -696,28 +791,33 @@ encrypted_payload (after AES-128-ECB decrypt):
### Frame Format
Raw binary over BLE NUS. Each frame: `[1B opcode] [payload...]`
Over USB CDC: V3 framing: `[2B LE length] [1B opcode] [payload...]`
Over USB CDC (and native-Linux TCP): framed with a length prefix — `[2B LE length] [1B opcode] [payload...]` (TCP additionally prefixes a `<`/`>` direction byte).
### Key Command Opcodes (phone → device)
The full set (~50 opcodes, `0x01``0x41`) is defined at the top of `app/CompanionMesh.cpp`; values match the Arduino MeshCore companion protocol. A sample:
| Opcode | Name | Payload |
|--------|------|---------|
| 0x01 | CMD_SEND_TXT_MSG | contact_idx + text |
| 0x03 | CMD_GET_CONTACTS | [optional 4B lastmod filter] |
| 0x06 | CMD_GET_SELF_INFO | (none) |
| 0x07 | CMD_SET_SELF_INFO | type + name + lat + lon |
| 0x0B | CMD_GET_MSG_WAITING | (none) |
| 0x0C | CMD_CONFIRM_MSG | (none) |
| 0x11 | CMD_SET_PREF | pref_key + value |
| 0x12 | CMD_DEVICE_QUERY | (none) |
| 0x15 | CMD_SEND_SELF_ADVERT | (none) |
| 0x20 | CMD_NEGOTIATE_VER | target_version |
| 0x01 | CMD_APP_START | app version + name (session start) |
| 0x02 | CMD_SEND_TXT_MSG | txt_type + attempt + timestamp + pubkey_prefix + text |
| 0x04 | CMD_GET_CONTACTS | [optional 4B `since` lastmod filter] |
| 0x05 / 0x06 | CMD_GET/SET_DEVICE_TIME | (none) / 4B epoch (forward-only) |
| 0x07 | CMD_SEND_SELF_ADVERT | [optional type byte: flood/zero-hop] |
| 0x08 | CMD_SET_ADVERT_NAME | name string |
| 0x0A | CMD_SYNC_NEXT_MESSAGE | (none) — offline queue peek/confirm |
| 0x0B | CMD_SET_RADIO_PARAMS | freq + bw + sf + cr |
| 0x16 | CMD_DEVICE_QUERY | app target version |
| 0x210x23 | CMD_SIGN_START / DATA / FINISH | 3-phase Ed25519 signing (up to 8KB) |
### Push Notifications (device → phone, async)
Codes `0x80``0x90` (`PUSH_CODE_*` in `app/CompanionMesh.h`). Most used:
| Code | Name |
|------|------|
| 0x80 | PUSH_CODE_ADVERT |
| 0x81 | PUSH_CODE_PATH_UPDATED |
| 0x82 | PUSH_CODE_SEND_CONFIRMED |
| 0x83 | PUSH_CODE_MSG_WAITING |
| 0x8A | PUSH_CODE_NEW_ADVERT |
@@ -731,21 +831,34 @@ Over USB CDC: V3 framing: `[2B LE length] [1B opcode] [payload...]`
| Path | Content | Format |
|------|---------|--------|
| `/lfs/_main.id` | Node identity | 64B private key + 32B public key |
| `/lfs/new_prefs` | Preferences | 93B binary (Arduino-compatible) |
| `/lfs/new_prefs` | Companion preferences | 152B binary, field-by-field (Arduino-compatible superset) |
| `/lfs/contacts3` or `/ext/contacts3` | Contacts | 152B × N records |
| `/lfs/channels2` or `/ext/channels2` | Channels | 68B × N records |
| `/lfs/adv_blobs` or `/ext/adv_blobs` | Advert cache | Fixed-size blob records |
| `/lfs/repeater/*` | Repeater/room-server identity + prefs | 297B prefs; atomic-replace writes |
| `/lfs/repeater/acl` | Client ACL | 136B × N records |
| `/lfs/repeater/regions2` | Region map | Header + 164B × N entries |
| `storage_partition` (NVS, 0xD0000 nRF52) | BLE bonds + Zephyr settings | NVS settings backend (≥1.16.2; old `/lfs/settings` file detected by self-heal) |
### Preferences Binary Layout (292 bytes)
### Preferences Binary Layouts
Field-by-field serialization (NOT raw struct dump). See `memory/prefs-format.md` for full layout,
or `zephcore/helpers/CommonCLI.cpp` `loadPrefs()` for the authoritative source.
Two distinct field-by-field serializations (NOT raw struct dumps), both Arduino-compatible
in their shared base fields:
**Companion `/lfs/new_prefs` (152 bytes)**`adapters/datastore/ZephyrDataStore.cpp`
`loadPrefs()`/`savePrefs()` (offset comments inline). Arduino companion layout (name, lat/lon,
radio params, telemetry modes, BLE pin, GPS, autoadd) plus ZephCore extensions from offset 92:
rx_boost(92), leds_disabled(93), apc(94-95), default flood scope name/key(96-142),
ble_disabled(143), display/wake/screen-off/auto-shutdown(144-149), rx_duty_cycle(150),
meshtimesync(151).
**Repeater/room-server `/lfs/repeater/prefs` (297 bytes)**`app/RepeaterDataStore.cpp`
`loadPrefs()`/`savePrefs()` (same field order as `helpers/CommonCLI.cpp`; offset comments inline).
Key ranges: name(4-36), radio(72-119), adaptive-delay(80-111, ignored at runtime),
Arduino-bridge(127-151, read+discarded), GPS(156-161), owner_info(170-290), rx_boost/duty(290-291).
Arduino-bridge(127-151, read+discarded), GPS(156-161), owner_info(170-290), rx_boost/duty(290-291),
apc(292-293), flood_max_unscoped/advert(294-295), meshtimesync(296). Older shorter files
load cleanly — reads past EOF are no-ops, so newer fields keep their defaults and a one-time
upgrade block migrates them.
---
+3 -3
View File
@@ -353,7 +353,7 @@ if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common" AND EXTRA_CONF_FILE MATCHES "re
endif()
# Append to EXTRA_CONF_FILE auto-generated configs first, user extras last.
# User-specified extras (repeater.conf, prod.conf) MUST come after the auto chain
# User-specified extras (repeater.conf, debug.conf) MUST come after the auto chain
# so they can override settings (e.g. CONFIG_BT=n in repeater.conf).
if(ZEPHCORE_CONF_FILES)
if(EXTRA_CONF_FILE)
@@ -377,7 +377,7 @@ if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common"
endif()
# Auto-pair user-supplied EXTRA_CONF_FILE entries with same-named .overlay files.
# This allows repeater.conf + repeater.overlay, prod.conf + prod.overlay, etc.
# This allows repeater.conf + repeater.overlay, wifi_ota.conf + wifi_ota.overlay, etc.
if(EXTRA_CONF_FILE)
foreach(_conf IN LISTS EXTRA_CONF_FILE)
zephcore_auto_pair_overlay("${_conf}")
@@ -605,7 +605,7 @@ if(CONFIG_ZEPHCORE_ROLE_REPEATER)
${CMAKE_CURRENT_SOURCE_DIR}/app
)
elseif(CONFIG_ZEPHCORE_REPEATER_UPLINK)
message(WARNING "ZephCore repeater uplink requested, but MQTT is disabled (likely prod.conf). Build will exclude uplink runtime.")
message(WARNING "ZephCore repeater uplink requested, but MQTT is disabled in this configuration. Build will exclude uplink runtime.")
endif()
target_compile_definitions(app PRIVATE ZEPHCORE_REPEATER=1)
elseif(CONFIG_ZEPHCORE_ROLE_ROOM_SERVER)
+1 -1
View File
@@ -582,7 +582,7 @@ All native-Linux board files live under **`boards/linux_native/`**:
End-to-end verified under WSL Ubuntu 24.04 (gcc 13.3):
- ✅ All 7 `patches/zephyr/*.patch` apply cleanly (including the new `0007-spi-gpio-native-linux.patch`).
- ✅ All `patches/zephyr/*.patch` apply cleanly (including the new `0007-spi-gpio-native-linux.patch`).
- ✅ All files in `patches/zephyr-new/` copy correctly into the Zephyr tree.
- ✅ Platform detection routes `BOARD=native_sim` to `boards/linux_native/linux_common.conf`.
- ✅ `native_sim/native/64` builds clean → `build/zephyr/zephcore_native_linux.exe` (~4.3 MB ELF).
+313
View File
@@ -0,0 +1,313 @@
# Adaptive Power Control (APC) for ZephCore
## Context
TX power is a static user setting (`NodePrefs.tx_power_dbm`, default 22 dBm). When neighbors are nearby and receiving with 20+ dB of excess SNR margin, we're wasting battery and adding unnecessary channel energy. APC automatically reduces TX power when echo packets (dupes of our own transmissions, heard back from neighbors who retransmitted them) indicate strong link margins, and ramps back up when data goes stale (neighbor offline/moving).
This is a novel "echo-based" approach — no published LoRa APC uses this technique. It's well-suited to flood mesh because every retransmit naturally produces echoes without any protocol overhead.
## Status
**Implemented and building** on all boards. **Compiled in by default** (`CONFIG_ZEPHCORE_APC=y`) but **disabled at runtime** — enable per-node with `set tx apc` (persisted in prefs, survives reboot). Works for both companion and repeater roles.
## Important: Link Asymmetry
APC measures the **return path** SNR (neighbor → us), not our outgoing SNR (us → neighbor). These differ when nodes have mismatched hardware — especially nodes with poor RX sensitivity ("bad ears").
**Path loss is reciprocal** (same frequency, same physical path), so echo SNR is a good proxy for link quality in most cases. The target margin provides a safety buffer for hardware asymmetry.
**If your network has nodes with poor RX hardware**, increase the target margin:
- Default: 16 dB (good for networks with similar hardware)
- 20-22 dB: recommended for mixed hardware networks
- 24-30 dB: very conservative, for networks with known bad receivers
See [CLI Commands](#cli-commands) for how to change the margin at runtime.
## Architecture
### Class: `mesh::PowerController`
Follows the `ContentionTracker` pattern: static ring buffer, EMA, `tick()` from maintenance loop.
**File:** `include/mesh/PowerController.h`, `src/PowerController.cpp`
```
PowerController
_ring[16] <- tracks recently sent packets (FNV-1a hash)
_margin_ema_x256 <- EMA of link margin (SNR - SF_threshold), fixed-point
_power_reduction_db <- current TX power reduction (0 to MAX_REDUCTION)
_target_margin_x4 <- configurable target margin (default 64 = 16 dB)
_enabled <- runtime enable/disable (object defaults true; each role
applies prefs.apc_enabled at begin() — prefs default 0 = off)
_last_echo_ms <- timestamp of most recent echo (for staleness)
_sf <- current spreading factor (for threshold lookup)
```
**Constants:**
| Parameter | Value | Rationale |
|-----------|-------|-----------|
| RING_SIZE | 16 | Match ContentionTracker; handles ~3 pkts/s with 5s window |
| ECHO_WINDOW_MS | 10,000 ms | 10s: covers SF12 2-hop echoes (~7s airtime + processing) |
| STALE_MS | 120,000 ms (2 min) | Mobile mesh — neighbors move/die fast |
| EMA_SHIFT | 2 (alpha=1/4) | More responsive than contention's 1/8 |
| WARMUP_COUNT | 3 | Need a few echoes before acting |
| MAX_SOURCES | 3 | Track up to 3 distinct first-hop echo sources per packet |
| STEP_DOWN_DB | 3 | ~halving power per step, conservative |
| STEP_UP_DB | 6 | Aggressive recovery when margin drops |
| MAX_REDUCTION_DB | 12 | Floor at 10 dBm (from 22 max) |
| CLUSTER_WIDTH_X4 | 24 | 6 dB in x4 units — echo SNRs within 6 dB of best are clustered |
| DEFAULT_TARGET_MARGIN_X4 | 64 | 16 dB above SF sensitivity (configurable at runtime) |
| HYSTERESIS_X4 | 4 | 1 dB (x4 units). Reduce at margin > target+1, increase at margin < target-1 |
| MIN_TX_POWER_DBM | -9 | SX1262 hardware minimum |
**Public API:**
- `setEnabled(bool en)` / `isEnabled()` — runtime enable/disable
- `setSF(uint8_t sf)` — set current SF for margin calculation
- `setTargetMargin(uint8_t margin_db)` / `getTargetMargin()` — configure target link margin (default 16 dB)
- `trackTransmit(uint32_t hash32, uint32_t now_ms)` — called when we send or retransmit a flood
- `recordEcho(uint32_t hash32, int8_t snr_x4, uint8_t first_hop_hash, uint32_t now_ms)` — called on flood dupe; updates best SNR and source diversity; returns true if matched
- `tick(uint32_t now_ms)` — finalize expired entries into EMA, adjust power, handle staleness
- `getPowerReduction() const` -> `int8_t` (0 to MAX_REDUCTION_DB; returns 0 when disabled)
- `getMarginEstimate() const` -> `float` (dB, for diagnostics)
- `getLastSourceCount() const` -> `uint8_t` (echo source count from most recent entry, for diagnostics)
- `isWarmedUp() const` / `isStale(uint32_t now_ms) const`
**Per-source SNR tracking in EchoEntry:**
```cpp
struct EchoEntry {
uint32_t hash32;
uint32_t timestamp_ms;
uint8_t source_count;
uint8_t sf_at_track; /* SF when packet was transmitted */
Source sources[MAX_SOURCES];
bool active;
};
```
Each entry stores the SF at track time (`sf_at_track`) so that margin calculation uses the correct threshold even if the radio SF changes while entries are in-flight.
**`recordEcho` logic:**
1. Find matching entry by hash32
2. Check if entry has expired (beyond ECHO_WINDOW_MS) — if so, finalize and reject
3. Check if `first_hop_hash` is already in `sources[]`:
- If yes: update its SNR if the new one is better
- If no: add new source with its SNR, increment `source_count`
4. Update `_last_echo_ms`
**Computing "robust SNR" when finalizing an entry:**
1. **0 sources** (no echo): return SF threshold (margin = 0, conservative)
2. **1 source**: use its SNR directly (no rogue detection possible, and no need)
3. **2-3 sources**: sort descending, cluster within CLUSTER_WIDTH (6 dB) of the best:
- If 2+ in cluster -> median the cluster values (2: average, 3: middle)
- If only 1 in cluster (top value is isolated = rogue) -> drop it, use next source(s)
**Power adjustment algorithm (in `tick()`):**
1. Finalize expired entries: `margin = robust_snr_x4 - sfThresholdX4(sf_at_track)`
2. Entries with **no echo heard**: count as margin=0 (conservative — assume the worst)
3. Feed margin into EMA (x256 fixed-point, warmup seeding for first 3 entries)
4. **Staleness takes priority** (mutually exclusive with margin-based adjustment):
- If `now - _last_echo_ms > STALE_MS (2 min)` -> ramp reduction toward 0 by STEP_DOWN_DB per tick
- When stale, **never increase reduction** — old EMA data is unreliable
5. Otherwise compare margin_ema vs target:
- margin > target + HYSTERESIS -> reduce by STEP_DOWN (3 dB), capped at MAX_REDUCTION_DB
- margin < target - HYSTERESIS -> increase by STEP_UP (6 dB)
- in between -> hold
### Integration points (all guarded by `#ifdef CONFIG_ZEPHCORE_APC`)
**1. Track originated packets** — `src/Mesh.cpp` `sendFlood()` (both overloads)
After `_tables->hasSeen(packet)`:
```cpp
uint32_t h = ContentionTracker::computePacketHash32(packet);
_power_ctrl.trackTransmit(h, (uint32_t)_ms->getMillis());
```
**2. Track retransmitted packets** — `src/Mesh.cpp` `routeRecvPacket()`
Alongside existing `_contention.trackRetransmit()`:
```cpp
_power_ctrl.trackTransmit(h, (uint32_t)_ms->getMillis());
```
**3. Record echoes** — `src/Mesh.cpp` `onRecvPacket()`
In the flood dupe detection block, extracting first path hash for diversity:
```cpp
uint8_t first_hop = (pkt->getPathHashCount() > 0) ? pkt->path[0] : 0;
_power_ctrl.recordEcho(h, pkt->_snr, first_hop, (uint32_t)_ms->getMillis());
```
**4. Tick + propagate to radio** — `src/Mesh.cpp` `maintenanceLoop()`
```cpp
_power_ctrl.tick(now);
_radio->setTxPowerReduction(_power_ctrl.getPowerReduction());
```
**5. Apply power reduction** — `adapters/radio/LoRaRadioBase.cpp` `buildModemConfig()`
After existing TX power clamps:
```cpp
cfg.tx_power -= _tx_power_reduction_db;
if (cfg.tx_power < -9) cfg.tx_power = -9;
```
The config cache (`configParamsEqual`) already compares `tx_power`, so a changed reduction naturally triggers `hwConfigure()` on next TX — no explicit reconfigure needed.
**6. Set SF** — `RepeaterMesh::begin()` and `CompanionMesh::begin()` / BLE radio param change callbacks.
### Radio interface
Virtual APC methods added to `mesh::Radio` (base class):
```cpp
virtual void setTxPowerReduction(int8_t reduction_db) { (void)reduction_db; }
virtual int8_t getTxPowerReduction() const { return 0; }
```
`LoRaRadioBase` overrides these with a stored `_tx_power_reduction_db` member.
### Kconfig
In `Kconfig` under "LoRa Power Saving" menu:
```kconfig
config ZEPHCORE_APC
bool "Adaptive Power Control (APC)"
default y
```
**Compiled in by default, disabled at runtime.** The `apc_enabled` pref (default 0)
gates it per-node; `set tx apc` enables and persists. Build with
`-DCONFIG_ZEPHCORE_APC=n` to exclude the code entirely (zero overhead). Works for
both companion and repeater roles.
### CLI commands
**`get tx`** — shows current APC state:
```
> 16dBm (apc=on max=22 reduction=6 margin=18.5 target=16) # APC enabled
> 22dBm (apc=off) # APC disabled
```
**`get tx apc`** — same data in a diagnostics-first form:
```
> apc=on effective=16dBm max=22 reduction=6 margin=18.5 target=16
> apc=off max=22dBm target=16
```
**`get apc.margin`** — shows current target margin:
```
> 16 dB
```
**`set tx apc`** — re-enables APC (default state). APC resumes with existing EMA data.
```
OK - tx power=22 dBm (apc=on)
```
**`set tx <number>`** — disables APC and sets fixed TX power:
```
OK - tx power=16 dBm (apc=off)
```
**`set apc.margin <dB>`** — set APC target link margin (range 6-30 dB):
```
OK - APC target margin=20 dB
```
The user's TX power setting (`NodePrefs.tx_power_dbm`) is always the ceiling — APC only subtracts from it. When APC is disabled, `getPowerReduction()` returns 0 but internal tracking continues, so re-enabling is seamless.
**Note:** Both the APC enable state (`apc_enabled`) and the target margin (`apc_margin`) are persisted in prefs and survive reboots.
### Target margin — what it means and how to choose
The target margin controls how much "extra" signal strength APC tries to maintain above the minimum required for reliable reception at the current SF.
**How it works (SF8 example, threshold = -10 dB):**
| Target margin | Reduce power when SNR > | Increase power when SNR < | Notes |
|--------------|------------------------|--------------------------|-------|
| 16 dB (default) | +7 dB | +5 dB | Good for networks with similar hardware |
| 20 dB | +11 dB | +9 dB | More conservative, safer with mixed hardware |
| 24 dB | +15 dB | +13 dB | Very conservative, for bad-ear networks |
| 10 dB | +1 dB | -1 dB | Aggressive, maximum power savings |
**The formula:**
- Reduce threshold = SF_threshold + target_margin + 1 dB hysteresis
- Increase threshold = SF_threshold + target_margin - 1 dB hysteresis
**Example scenarios:**
**Scenario 1: Two good radios on a rooftop, 500m apart**
Echo SNR = +15 dB. With default margin (16 dB), target SNR for SF8 = +6 dB.
Margin = 15 - (-10) = 25 dB. 25 > 17 -> APC reduces power.
After several ticks: reduction = 9 dB. Effective TX = 13 dBm.
Echo SNR drops to ~+6 dB. Margin = 16 dB. In the hysteresis band -> hold.
**Scenario 2: Good radio talking to a cheap node with -5 dB RX degradation**
You hear the echo at +15 dB, but the cheap node only hears you at +10 dB.
With default margin (16 dB): APC reduces to margin ~16. The cheap node sees ~11 dB.
Still above threshold (-10) by 21 dB. Safe.
With `set apc.margin 10`: APC reduces to margin ~10. Cheap node sees ~5 dB.
Only 15 dB above threshold. Might be marginal in fading conditions.
**Scenario 3: Your network has radios with 10+ dB RX variation**
Some nodes have external LNAs (+3 dB), others have bad antennas (-7 dB).
Total asymmetry up to 10 dB. Set `set apc.margin 22` to ensure the worst
receiver still gets 12 dB of real margin after APC reduces power.
**Rule of thumb:**
- Default (16 dB): most networks
- Add the worst-case RX asymmetry in your network to 16 dB
- If you don't know: 20 dB is a safe middle ground
## What APC does NOT see
- **Zero-hop packets** (advertisements): these are not retransmitted, so no echo is produced. APC only tracks flood packets.
- **Outgoing SNR**: APC measures return-path SNR. It cannot know the SNR at the receiving end without protocol changes (e.g., trace route responses include this, but would require active probing).
- **Per-neighbor granularity**: APC produces a single global power reduction. It does not adjust power per destination — the radio can only set one TX power at a time.
## Files created/modified
| File | Action |
|------|--------|
| `include/mesh/PowerController.h` | **CREATE** — PowerController class |
| `src/PowerController.cpp` | **CREATE** — implementation |
| `include/mesh/Radio.h` | EDIT — added virtual `setTxPowerReduction`/`getTxPowerReduction` |
| `include/mesh/Mesh.h` | EDIT — added `_power_ctrl` member + accessors |
| `src/Mesh.cpp` | EDIT — 4 integration points |
| `adapters/radio/LoRaRadioBase.h` | EDIT — added `_tx_power_reduction_db` + override methods |
| `adapters/radio/LoRaRadioBase.cpp` | EDIT — apply reduction in `buildModemConfig()`, init member |
| `Kconfig` | EDIT — added `ZEPHCORE_APC` option |
| `CMakeLists.txt` | EDIT — conditional compile of `PowerController.cpp` |
| `helpers/CommonCLI.h` | EDIT — added APC callbacks (`getAPCReduction`, `getAPCMargin`, `isAPCEnabled`, `setAPCEnabled`, `getAPCTargetMargin`, `setAPCTargetMargin`) |
| `helpers/CommonCLI.cpp` | EDIT — added `get txpower`, `get/set apc.margin`, modified `set tx` for APC enable/disable |
| `app/RepeaterMesh.h` | EDIT — added APC callback overrides |
| `app/RepeaterMesh.cpp` | EDIT — added `_power_ctrl.setSF()` in `begin()` |
| `app/CompanionMesh.cpp` | EDIT — added `_power_ctrl.setSF()` in `begin()` and BLE param change |
## Verification
1. **Build test**: `west build -b rak4631 zephcore --pristine` and `west build -b wio_tracker_l1 zephcore --pristine` — both pass
2. **Kconfig disable**: Build with `-DCONFIG_ZEPHCORE_APC=n` — zero overhead
3. **CLI**: Flash a repeater, run `get tx` — should show `> 22dBm (apc=off)` initially (runtime default is off); after `set tx apc`, `> 22dBm (apc=on max=22 reduction=0 margin=0.0 target=16)`
4. **Functional**: Two nodes in close proximity (high SNR). After a few message exchanges, APC should reduce power. Check with `get txpower`.
5. **Staleness**: Power off the neighbor. Within ~2 minutes, `get txpower` should show power ramping back to max.
6. **Override**: `set tx 16` disables APC and fixes power. `set tx apc` re-enables.
7. **Margin config**: `set apc.margin 20` changes the target. `get apc.margin` confirms.
8. **Logging**: Enable `CONFIG_ZEPHCORE_LORA_LOG_LEVEL_DBG` to see APC state changes in log output.
## Resolved decisions
1. **No-echo = poor**: Packets sent but never echoed back count as margin=0 (conservative). Prevents over-reduction in sparse networks.
2. **Staleness**: 2-minute timeout. Ramps back at STEP_DOWN_DB per tick. Full recovery from max reduction in ~20s once triggered. Staleness and margin-based adjustment are mutually exclusive — when stale, APC never increases reduction (old EMA data is unreliable).
3. **Max reduction**: 12 dB. With SF8/BW62.5 and the no-echo-as-poor policy, APC is naturally self-limiting.
4. **Primary target**: SF8/BW62.5 (SNR threshold -10.0 dB, effective sensitivity ~-130 dBm). Algorithm works for all SF/BW combos via the threshold lookup table.
5. **Both roles**: APC is active for both companions and repeaters.
6. **Rogue filtering via SNR clustering**: When 2+ distinct echo sources are seen per packet, cluster their SNRs within 6 dB of the best. If only 1 source is in the top cluster (potential rogue), drop it and use the next source. With 1 source only, use its SNR directly.
7. **Per-entry SF tracking**: Each EchoEntry records the SF at track time (`sf_at_track`). Margin calculation uses `sfThresholdX4(sf_at_track)` so SF changes mid-flight don't corrupt margins.
8. **Echo window**: 10s (increased from initial 5s design to cover SF12 2-hop echoes at ~7s).
9. **CLI override**: `set tx <number>` disables APC and sets fixed power. `set tx apc` re-enables. Internal tracking continues when disabled for seamless resume.
10. **Configurable target margin**: `set apc.margin <6-30>` adjusts how conservative APC is. Higher values are safer for networks with hardware asymmetry (nodes with poor RX sensitivity). Default 16 dB. Resets on reboot.
11. **Companion APC**: No app changes needed — the app's TX power setting changes the ceiling, APC subtracts from it. The companion applies `prefs.apc_enabled` / `prefs.apc_margin` at `begin()` (both default like the repeater: off / 16 dB, persisted in prefs).
+2 -2
View File
@@ -1,7 +1,7 @@
# Repeater uplink config (ESP32)
#
# Use with repeater + prod when WiFi+MQTT uplink is needed:
# -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/prod.conf;boards/common/repeater_uplink.conf"
# Use with repeater builds when WiFi+MQTT uplink is needed:
# -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/repeater_uplink.conf"
#
# This restores the minimum network/MQTT/TLS settings required by
# CONFIG_ZEPHCORE_REPEATER_UPLINK in production builds.
@@ -15,5 +15,5 @@ CONFIG_ZEPHCORE_BOARD_NAME="XIAO ESP32-C3"
# Device Information Service model name
CONFIG_BT_DIS_MODEL_NUMBER_STR="XIAO ESP32-C3"
# RAM-limited: override prod.conf MAX_CONTACTS (800 overflows DRAM)
# RAM-limited: cap MAX_CONTACTS below the 350 default (larger arrays overflow DRAM)
CONFIG_ZEPHCORE_MAX_CONTACTS=300
+14 -10
View File
@@ -193,16 +193,19 @@ Append `-- -DEXTRA_CONF_FILE="boards/common/room_server.conf"` to any build comm
west build -b rak4631 zephcore -- -DEXTRA_CONF_FILE="boards/common/room_server.conf"
```
### Production Build (logging disabled)
### Production vs Debug Builds
Production (no logging, no asserts, reboot-on-fatal) is the **default** — no
extra conf needed. To enable logging, add the debug overlay:
```
west build -b rak4631 zephcore -- -DEXTRA_CONF_FILE="boards/common/prod.conf"
west build -b rak4631 zephcore -- -DEXTRA_CONF_FILE="boards/common/debug.conf"
```
### Repeater + Production
### Repeater + Debug
```
west build -b rak4631 zephcore -- -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/prod.conf"
west build -b rak4631 zephcore -- -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/debug.conf"
```
All build commands should include `--pristine` when switching between roles or boards.
@@ -230,11 +233,11 @@ Directory structure:
Steps:
1. Create directory: `boards/<platform>/<board_name>/`
Platform folders: nrf52840, nrf54l, mg24, esp32
Platform folders: nrf52840, nrf54l, mg24, esp32, stm32wl
2. Copy board.conf and board.overlay from THIS directory
3. Uncomment the sections matching your platform
4. Fill in YOUR pin numbers and partition layout
5. Add board detection to CMakeLists.txt (~line 60-75):
5. Add board detection to CMakeLists.txt (platform detection block, ~line 270):
Add `BOARD MATCHES "your_board"` to the correct platform line
6. Build and iterate!
@@ -390,10 +393,11 @@ Config Inheritance
zephcore_common.conf BLE, storage, input, LoRa, crypto, sensors
|
<platform>_common.conf Platform-specific overrides only
| nrf52_common.conf — UF2, USB CDC, DLE, RTT
| nrf54l_common.conf — DLE, RTT
| mg24_common.conf — SiLabs blob stacks, heap
| esp32_common.conf — Espressif blob stacks, heap
| nrf52_common.conf — UF2, USB CDC, DLE, RTT
| nrf54l_common.conf — DLE, RTT
| mg24_common.conf — SiLabs blob stacks, heap
| esp32_common.conf — Espressif blob stacks, heap
| stm32wl_common.conf — BT off, UART companion/CLI
|
board.conf Board name, radio type, board-specific
@@ -4,10 +4,10 @@
# TFT panel VDD (P0.03) stays off for the entire session.
#
# no_display.overlay (long-press => deep sleep) is auto-included by CMakeLists.txt
# whenever this file is in EXTRA_CONF_FILE their is no need to pass it separately.
# whenever this file is in EXTRA_CONF_FILE; there is no need to pass it separately.
#
# west build -b heltec_t114 zephcore --pristine -- \
# -DEXTRA_CONF_FILE="boards/common/prod.conf;boards/nrf52840/heltec_t114/no_display.conf"
# -DEXTRA_CONF_FILE="boards/nrf52840/heltec_t114/no_display.conf"
CONFIG_ZEPHCORE_UI_DISPLAY=n
CONFIG_ZEPHCORE_DISPLAY_MONO_TFT=n
@@ -7,5 +7,5 @@ CONFIG_ZEPHCORE_BOARD_NAME="XIAO nRF54L15"
# Device Information Service model name
CONFIG_BT_DIS_MODEL_NUMBER_STR="XIAO nRF54L15"
# RAM-limited: FLPR core reserves 68KB, override prod.conf MAX_CONTACTS
# FLPR core reserves 68KB of RAM; 450 contacts is the ceiling that fits
CONFIG_ZEPHCORE_MAX_CONTACTS=450
+22
View File
@@ -42,6 +42,7 @@ heltec_wifi_lora32_v4/esp32s3/procpu
heltec_wifi_lora32_v43/esp32s3/procpu
heltec_wireless_tracker/esp32s3/procpu
ttgo_tbeam/esp32/procpu
ttgo_lora32/esp32/procpu
```
> ESP32 boards require `west blobs fetch hal_espressif` before first build.
@@ -55,6 +56,21 @@ ttgo_tbeam/esp32/procpu
> SX1262, AXP2101 PMU, and GNSS. Use this for the **v1.2 SX1262** variant — the
> upstream Zephyr DTS models the SX1276 radio, which ZephCore overrides to
> SX1262 in `board.overlay`. Console/CLI are on `uart0` (onboard USB-UART).
>
> **TTGO LoRa32** (`ttgo_lora32/esp32/procpu`): classic ESP32 (PICO-D4) with
> **SX1276** — the SX127x (loramac-node backend) reference board. Console/CLI
> on `uart0`.
## STM32WL
```
lora_e5_mini
```
> **Seeed LoRa-E5 mini** (`lora_e5_mini`): STM32WLE5JC with the integrated
> SX1262-class sub-GHz radio. No BLE and no USB device — the companion
> protocol and the CLI both run over USART1 (bridged to USB-C by the onboard
> USB-UART chip). Flash over SWD/ST-Link with `west flash`.
## MG24 (Silicon Labs)
@@ -71,3 +87,9 @@ xiao_nrf54l15/nrf54l15/cpuapp
```
> Requires `--no-sysbuild` flag: `west build -b xiao_nrf54l15/nrf54l15/cpuapp zephcore --no-sysbuild`
## Native Linux
Not `west build -b` boards — `EXTRA_CONF_FILE` presets on top of `native_sim`
for SBCs (Femtofox / Luckfox Pico Mini, Raspberry Pi + RAK6421 HAT). See
[LINUX_NATIVE.md](../LINUX_NATIVE.md) for build commands and wiring.