mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 18:38:17 +00:00
235 lines
14 KiB
Markdown
235 lines
14 KiB
Markdown
<h1>ZephCore — MeshCore for Zephyr RTOS <img src="img/kite-network-logo-thick-bright.svg" alt="ZephCore logo" width="85" align="middle"></h1>
|
||
|
||
A port of [MeshCore](https://github.com/meshcore-dev/MeshCore/) LoRa mesh firmware from Arduino to [Zephyr RTOS](https://zephyrproject.org/). Aiming for full protocol compatibility with the original Arduino firmware and the MeshCore mobile apps.
|
||
|
||
## Why Zephyr?
|
||
|
||
The Arduino version uses a `loop()`. This port replaces that with Zephyr's event-driven primitives (`k_event_wait`, `k_poll`, `k_msgq`), so the CPU sleeps in WFI (Wait For Interrupt) between events.
|
||
|
||
Other benefits:
|
||
|
||
- **Proper driver model** -- LoRa, GNSS, display, sensors, and BLE all use Zephyr subsystem drivers rather than Arduino libraries
|
||
- **Hierarchical build configuration** -- board-specific settings compose cleanly via Kconfig and devicetree overlays
|
||
- **DFU support** -- generates Arduino compatible zip packages for OTA updates and UF2 binaries for drag-and-drop flashing
|
||
- **Back and forth compatible** -- Adapted to softdevice and adafruit's bootloader, so no bootloader re-flashing required.
|
||
|
||
## Supported Boards
|
||
|
||
nRF52840, ESP32, nRF54L15, MG24, and STM32WL boards, covering SX126x, LR1110,
|
||
LR2021, and SX127x radios. 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](docs/LINUX_NATIVE.md).
|
||
|
||
For the full board list with exact `west build -b` strings, radios, and
|
||
hardware notes, see the [supported boards list](docs/supported_boards.md) and
|
||
the [Board Porting Guide](zephcore/boards/example_board/README.md).
|
||
|
||
## Device Roles
|
||
|
||
- **Companion** (default) -- connects to MeshCore mobile apps via BLE/USB. Contacts, channels, offline message queue.
|
||
- **Repeater** -- forwards packets, configured via USB serial CLI. See the [Repeater CLI Command Reference](docs/Repeater_CLI_commands.md) for all available commands.
|
||
- **Room Server** -- store-and-forward shared message room (a "BBS"). Clients log in with an admin or guest password and post messages; the server pushes each new post to every other logged-in client. No BLE; configured via the same USB serial CLI as the repeater.
|
||
- **Observer** (ESP32 only) -- listen-only node that publishes received LoRa packets to MQTT over WiFi STA. Configured at runtime via serial CLI.
|
||
|
||
## Building
|
||
|
||
Prerequisites: [Zephyr SDK >=1.0.1 (!)](https://docs.zephyrproject.org/latest/develop/getting_started/index.html) and `west` installed (required by Zephyr 4.4.0, which is pinned in `west.yml`).
|
||
|
||
Optional: [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil) to allow DFU zip generation for OTA updates on nRF52.
|
||
|
||
```bash
|
||
# Initialize workspace (first time only)
|
||
cd <cloned folder>
|
||
west init -l zephcore
|
||
west update
|
||
|
||
# Companion (production — default, no extra conf needed)
|
||
west build -b wio_tracker_l1 zephcore --pristine
|
||
|
||
# Companion (debug logging)
|
||
west build -b wio_tracker_l1 zephcore --pristine -- \
|
||
-DEXTRA_CONF_FILE="boards/common/debug.conf"
|
||
|
||
# Repeater
|
||
west build -b rak4631 zephcore --pristine -- \
|
||
-DEXTRA_CONF_FILE="boards/common/repeater.conf"
|
||
|
||
# Repeater (debug logging)
|
||
west build -b rak4631 zephcore --pristine -- \
|
||
-DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/debug.conf"
|
||
|
||
# Repeater with packet logging (clean RAW/RX/TX lines only, no debug spam)
|
||
west build -b rak4631 zephcore --pristine -- \
|
||
-DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/packet_logging.conf"
|
||
|
||
# ESP32 repeater + WiFi AP HTTP OTA (requires sysbuild for MCUboot)
|
||
west build -b xiao_esp32s3/esp32s3/procpu zephcore --pristine --sysbuild -- \
|
||
-DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/wifi_ota.conf"
|
||
|
||
# Room Server (store-and-forward BBS, USB CLI)
|
||
west build -b rak4631 zephcore --pristine -- \
|
||
-DEXTRA_CONF_FILE="boards/common/room_server.conf"
|
||
|
||
# Observer (ESP32, listen-only WiFi+MQTT)
|
||
west build -b xiao_esp32c3 zephcore --pristine -- \
|
||
-DEXTRA_CONF_FILE="boards/common/observer.conf"
|
||
|
||
# Formatter (factory-reset utility)
|
||
west build -b wio_tracker_l1 zephcore/tools/formatter --pristine
|
||
|
||
# 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.
|
||
|
||
**Platform notes:**
|
||
- ESP32 boards require `west blobs fetch hal_espressif` once before the first build.
|
||
- ESP32 default builds use `CONFIG_ESP_SIMPLE_BOOT` (no MCUboot) -- `west flash` writes a self-contained `zephyr.bin`. The `wifi_ota.conf` overlay is the exception: it requires MCUboot, so add `--sysbuild`.
|
||
- MG24 requires `west blobs fetch hal_silabs` and `pyocd` (`west flash --runner pyocd`).
|
||
- nRF54L15 requires `--no-sysbuild` (no MCUboot support yet).
|
||
- Heltec V3 routes console/shell to `uart0` -- use the UART serial port for logs/CLI.
|
||
|
||
Always use `--pristine` when switching boards or roles.
|
||
|
||
## Architecture Overview
|
||
|
||
```
|
||
Mobile App <--BLE (NUS)--> [ Companion ] <--LoRa--> Mesh Network
|
||
|
|
||
k_event_wait()
|
||
/ | \
|
||
LORA_RX LORA_TX_DONE BLE_RX
|
||
```
|
||
|
||
All code paths are event-driven. The CPU sleeps in WFI between events.
|
||
|
||
- **LoRa RX**: Zephyr driver callback enqueues to a ring buffer and signals the mesh event loop
|
||
- **LoRa TX**: A dedicated thread blocks on `k_poll()`, restarts RX on completion, then notifies the mesh loop
|
||
- **BLE**: NUS write handler enqueues to `k_msgq` and signals the mesh loop; TX uses `bt_gatt_notify_cb()` chaining
|
||
- **USB**: CDC-ACM with V3 binary framing protocol, frame timeout recovery
|
||
- **Main loop**: `k_event_wait()` blocks until work arrives; housekeeping runs every 5s
|
||
|
||
### Key Differences from Arduino
|
||
|
||
| | Arduino | Zephyr |
|
||
|---|---------|--------|
|
||
| Idle behavior | Cooperative loop; CPU busy-waits unless `board.sleep()` called explicitly | `k_event_wait(K_FOREVER)` yields to idle thread → WFI between events |
|
||
| LoRa TX completion | ISR sets flag, polled in `loop()` via `isSendComplete()` | ISR signals `k_poll_signal`, dedicated thread blocks on `k_poll()` |
|
||
| BLE transport | Platform-specific (ESP-IDF BLE, Adafruit nRF52 lib) | Unified `bt_gatt` API across all SoCs |
|
||
| LoRa driver | RadioLib (userspace SPI bit-bang) | Zephyr subsystem driver (DTS-configured, kernel-managed SPI) |
|
||
| Configuration | `platformio.ini` + `variant.h` per board | Kconfig + devicetree overlays, hierarchical config inheritance |
|
||
| Threading | Single `loop()` + ISRs | Explicit threads (main mesh, TX wait) + system work queue |
|
||
|
||
### Adaptive Contention Window (ZephCore-only)
|
||
|
||
Arduino MeshCore uses three static delay knobs (`txdelay`, `rxdelay`, `direct.txdelay`) that add the same retransmit jitter regardless of local conditions. In a linear chain of repeaters where each only hears its neighbor, this adds latency for zero benefit. In dense areas with 50+ neighbors, the same value may be too low to avoid collisions.
|
||
|
||
ZephCore replaces all three with a self-tuning system based on **observed retransmit contention**:
|
||
|
||
1. **Dupe counting**: When a node retransmits a flood packet, it counts how many times it hears that same packet retransmitted by neighbors within a 10-second window. This is a direct measurement of local contention -- 0 dupes means a quiet linear chain, 15+ means a dense cluster.
|
||
|
||
2. **EMA-based delay sizing**: Dupe counts feed into a rolling exponential moving average. This drives a sqrt-curve delay factor for future retransmits: near-zero delay in sparse areas, scaling up in dense ones. At ~15 dupes (moderate density), the factor matches the old Arduino default of 0.5. Jitter is double-capped at `min(2000ms, 6·airtime)` for repeaters.
|
||
|
||
3. **Reactive per-packet backoff**: When a node is waiting to retransmit and hears a neighbor retransmit the same packet, it pushes its own TX back by a random amount (up to `backoff.multiplier × airtime`). This is real-time CSMA -- you hear the channel being used for your packet, so you defer. Capped at `min(2000ms, 12·airtime)` total reactive extension per packet.
|
||
|
||
**Companion-originated floods** use a smaller spread (up to `min(1000ms, 3·airtime)`) so user messages feel responsive while still avoiding collisions with nearby repeaters that may be in TX/RX.
|
||
|
||
**Direct packets** (routed, single next-hop) use minimal fixed jitter instead of adaptive delay, since only the next hop retransmits them.
|
||
|
||
The old `txdelay`, `rxdelay`, and `direct.txdelay` commands are still accepted for binary compatibility with Arduino prefs but are ignored -- the system is fully adaptive.
|
||
|
||
**CLI commands:**
|
||
- `get txdelay` -- shows adaptive status: contention estimate and current flood delay factor
|
||
- `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**: 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.
|
||
- **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
|
||
|
||
## Configuration
|
||
|
||
Key Kconfig options (set in board configs or via `-D` flags):
|
||
|
||
| Option | Default | Description |
|
||
|--------|---------|-------------|
|
||
| `CONFIG_ZEPHCORE_ROLE_COMPANION` | y | BLE companion mode |
|
||
| `CONFIG_ZEPHCORE_ROLE_REPEATER` | n | USB CLI repeater mode |
|
||
| `CONFIG_ZEPHCORE_ROLE_OBSERVER` | n | Listen-only WiFi+MQTT mode (ESP32) |
|
||
| `CONFIG_ZEPHCORE_RADIO_NATIVE` | y | SX1261/SX1262/SX1268, LLCC68, STM32WL (Zephyr native sx126x driver) |
|
||
| `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` | 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, 10–86400); 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`) |
|
||
| `CONFIG_ZEPHCORE_REPEATER_UPLINK` | n | Repeater WiFi+MQTT uplink (ESP32) |
|
||
| `CONFIG_ZEPHCORE_PACKET_LOGGING` | n | Arduino-compatible mesh packet logging |
|
||
| `CONFIG_ZEPHCORE_HOUSEKEEPING_INTERVAL_MS` | 5000 | Periodic maintenance interval |
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
zephcore/
|
||
src/ Core mesh engine (Mesh, Dispatcher, Packet, Identity, ContentionTracker)
|
||
app/ Companion / Repeater / Room Server / Observer role implementations
|
||
adapters/
|
||
ble/ BLE NUS transport
|
||
board/ GPIO, LED, power management
|
||
clock/ Millisecond and RTC clocks
|
||
datastore/ LittleFS filesystem wrapper
|
||
gps/ GPS/GNSS drivers
|
||
mqtt/ MQTT publisher (observer / repeater uplink)
|
||
ota/ WiFi AP + HTTP firmware update server
|
||
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/ 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 (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/ Monocypher crypto library (Ed25519/X25519)
|
||
patches/ Auto-applied patches to the Zephyr tree
|
||
```
|
||
|
||
## License
|
||
|
||
MIT License — see [`LICENSE`](LICENSE). Same license as the
|
||
upstream MeshCore project, which this work relies heavily on (see the
|
||
[official meshcore repo](https://github.com/meshcore-dev/MeshCore/)).
|
||
|
||
A few vendored dependencies carry their own (compatible) licenses — see the
|
||
notice at the bottom of `LICENSE` for details (Monocypher, Zephyr patches).
|
||
|
||
Logo by [recrof](https://github.com/recrof), licensed under the
|
||
[WTFPL](https://en.wikipedia.org/wiki/WTFPL).
|
||
|
||

|
||
|
||
(FYI the whole project is 99,9% claude and cursor backed, relying heavily on the [official meshcore repo](https://github.com/meshcore-dev/MeshCore/) and the work they do in it)
|