mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-15 16:49:54 +00:00
1307 lines
52 KiB
Plaintext
1307 lines
52 KiB
Plaintext
# SPDX-License-Identifier: MIT
|
||
|
||
# WiFi companion on a PSRAM board: the WiFi heap and the network buffers go to
|
||
# PSRAM, so WiFi and BLE fit together (ZEPHCORE_COMPANION_WIFI). Ahead of
|
||
# Kconfig.zephyr, because a choice takes its first matching default and the
|
||
# driver's own ESP_WIFI_HEAP default is unconditional.
|
||
choice ESP_WIFI_HEAP
|
||
default ESP_WIFI_HEAP_SPIRAM if ZEPHCORE_COMPANION_WIFI && ESP_SPIRAM
|
||
endchoice
|
||
|
||
config ESP32_WIFI_NET_ALLOC_SPIRAM
|
||
default y if ZEPHCORE_COMPANION_WIFI && ESP_SPIRAM
|
||
|
||
# WiFi companion on an ESP32-S3 without PSRAM: the WiFi heap is the system heap,
|
||
# and Espressif's ADD_SIZE reservation (51 KB for WiFi alone) is sized for its
|
||
# default 10 static / 32 dynamic RX and 32 TX buffers. Bound the buffers and size
|
||
# the heap from measurement instead (72 KB, HEAP_MEM_POOL_SIZE in
|
||
# wifi_companion.conf): measured 2026-09-24 on a XIAO ESP32-S3 built without
|
||
# PSRAM, peak 61.6 KB under devdocs/tools/mem_load.py, and heap exhaustion under
|
||
# a ~10k frames/s flood recovers (devdocs/lld/08-connectivity.md §13). Saves
|
||
# ~12 KB of DRAM, spent on contacts. S3 only: C3/C6 and the classic ESP32 run
|
||
# different BLE controllers with different heap needs, not measured here.
|
||
config HEAP_MEM_POOL_IGNORE_MIN
|
||
default y if ZEPHCORE_COMPANION_WIFI && SOC_SERIES_ESP32S3 && !ESP_SPIRAM
|
||
|
||
config ESP32_WIFI_STATIC_RX_BUFFER_NUM
|
||
default 6 if ZEPHCORE_COMPANION_WIFI && SOC_SERIES_ESP32S3 && !ESP_SPIRAM
|
||
|
||
config ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM
|
||
default 16 if ZEPHCORE_COMPANION_WIFI && SOC_SERIES_ESP32S3 && !ESP_SPIRAM
|
||
|
||
config ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM
|
||
default 16 if ZEPHCORE_COMPANION_WIFI && SOC_SERIES_ESP32S3 && !ESP_SPIRAM
|
||
|
||
# Main thread stack. Measured 2026-09-24 (CONFIG_ZEPHCORE_MEM_STATS,
|
||
# devdocs/tools/mem_load.py: fresh BLE pairing + bonded reconnects, protocol
|
||
# golden over BLE and TCP, 8 KB sign, key export/import, flood advert, 120
|
||
# contacts added and dumped): companion peak 3008 B on nRF52 (T1000-E), 3828 B
|
||
# on ESP32-S3 with WiFi. 6144 for the companion (the LoRa-E5 companion already
|
||
# ran 6144); the servers keep 8192 (region CLI, uplink). A conf fragment still
|
||
# overrides (stm32wl_common 6144, observer 4096). Ahead of Kconfig.zephyr so it
|
||
# beats Zephyr's own default.
|
||
config MAIN_STACK_SIZE
|
||
default 6144 if ZEPHCORE_ROLE_COMPANION
|
||
default 8192
|
||
|
||
# The air530z driver's dynamic model (patch 0005) follows ours, so one knob
|
||
# sets it for every role, room servers included. Ahead of Kconfig.zephyr so
|
||
# it beats the driver's own default.
|
||
config GNSS_LUATOS_AIR530Z_NAV_MODE
|
||
int
|
||
default ZEPHCORE_GPS_NAV_MODE
|
||
depends on GNSS_LUATOS_AIR530Z
|
||
|
||
source "$(ZEPHYR_BASE)/Kconfig.zephyr"
|
||
|
||
# DT-driven PSRAM auto-enable for ESP32 boards (see Kconfig.psram).
|
||
rsource "Kconfig.psram"
|
||
|
||
module = ZEPHCORE_MAIN
|
||
module-str = zephcore_main
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
module = ZEPHCORE_BLE
|
||
module-str = zephcore_ble
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
module = ZEPHCORE_LORA
|
||
module-str = zephcore_lora
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
module = ZEPHCORE_BOARD
|
||
module-str = zephcore_board
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
module = ZEPHCORE_DATASTORE
|
||
module-str = zephcore_datastore
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
module = ZEPHCORE_GPS
|
||
module-str = zephcore_gps
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
module = ZEPHCORE_SENSORS
|
||
module-str = zephcore_sensors
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
module = ZEPHCORE_USB
|
||
module-str = zephcore_usb
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
module = ZEPHCORE_UI_ACTIONS
|
||
module-str = zephcore_ui_actions
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
module = ZEPHCORE_OBSERVER
|
||
module-str = zephcore_observer
|
||
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"
|
||
|
||
menu "ZephCore"
|
||
|
||
config ZEPHCORE_QSPI_RDID_PROBE
|
||
bool "QSPI flash bring-up probe"
|
||
help
|
||
Five seconds after boot, report whether the QSPI flash driver came
|
||
up. If it did not, print the init errno the driver discards and
|
||
re-read the JEDEC id by bit-banging the pins as single-lane SPI,
|
||
which distinguishes a driver-side fault from a part that is not
|
||
answering. Skipped entirely when the flash is working, so it is safe
|
||
to leave enabled. Bring-up diagnostic.
|
||
|
||
config ZEPHCORE_RESET_ON_FATAL_ERROR
|
||
bool "Reboot on fatal error"
|
||
default y
|
||
help
|
||
Cold-reboot instead of halting when a fatal error occurs (CPU
|
||
exception, stack overflow, k_panic, kernel oops). Production default;
|
||
turn it off (boards/common/debug.conf does) so a debugger can inspect
|
||
the halted core. Implemented via a k_sys_fatal_error_handler()
|
||
override in helpers/fatal_reboot.c — Zephyr has no built-in Kconfig
|
||
for this.
|
||
|
||
menu "Device Role"
|
||
|
||
choice ZEPHCORE_ROLE
|
||
prompt "Device Role"
|
||
default ZEPHCORE_ROLE_COMPANION
|
||
help
|
||
Select the device role. Companion devices connect to mobile apps via BLE.
|
||
Repeaters forward packets and are configured via USB serial CLI.
|
||
|
||
config ZEPHCORE_ROLE_COMPANION
|
||
bool "Companion Device"
|
||
help
|
||
BLE companion device that connects to ZephCore mobile app.
|
||
Includes BLE stack, contact management, offline queue, channels.
|
||
|
||
config ZEPHCORE_ROLE_REPEATER
|
||
bool "Repeater"
|
||
help
|
||
LoRa mesh repeater with USB serial CLI interface.
|
||
No BLE stack. Has ACL authentication, region filtering,
|
||
neighbor tracking, and full CLI command set.
|
||
|
||
config ZEPHCORE_ROLE_OBSERVER
|
||
bool "Observer (listen-only, WiFi+MQTT)"
|
||
help
|
||
ESP32-only listen-only node. Receives LoRa packets without
|
||
forwarding. Connects to WiFi (STA mode) and publishes received
|
||
packets to an MQTT broker in meshcoretomqtt-compatible format.
|
||
All parameters (WiFi SSID/PSK, MQTT host/port/TLS/user/pass,
|
||
IATA location code) are configured at runtime via serial CLI
|
||
and stored in LittleFS. No BLE, no advertising, no routing.
|
||
|
||
config ZEPHCORE_ROLE_ROOM_SERVER
|
||
bool "Room Server (shared BBS, USB serial CLI)"
|
||
help
|
||
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 all other logged-in clients and tracks a
|
||
per-client sync cursor. No BLE — configured via USB serial CLI.
|
||
Reuses the repeater's ACL, region filtering and CLI command set.
|
||
|
||
endchoice
|
||
|
||
config ZEPHCORE_COMPANION_USB
|
||
bool "Enable USB CDC companion transport"
|
||
depends on USB_DEVICE_STACK_NEXT
|
||
default y if ZEPHCORE_ROLE_COMPANION
|
||
help
|
||
Compile and init the USB CDC-ACM companion protocol stack
|
||
(ZephyrCompanionUSB / ZephyrUSBCDC) independently of CONFIG_LOG.
|
||
The full ~60-opcode binary protocol is available over USB even in
|
||
production builds (CONFIG_LOG=n). It is one interface in the
|
||
companion's MultiSerialInterface: USB, BLE (and TCP) clients can be
|
||
connected at once, and every connected one gets every frame.
|
||
|
||
Enabled by default for all companion builds on platforms that already
|
||
have CONFIG_USB_DEVICE_STACK_NEXT=y (all nRF52840 boards). On
|
||
platforms without USB hardware (MG24, ESP32 classic, C3/C6) the
|
||
depends forces this to n automatically — no conf change needed.
|
||
|
||
For ESP32-S3 boards, USB_DEVICE_STACK_NEXT must be enabled first via
|
||
boards/common/esp32s3_usb.conf before this becomes active. CMakeLists.txt
|
||
adds that conf automatically to companion builds on the S3 boards whose
|
||
board.overlay declares the USB OTG CDC-ACM node, so it is on by default
|
||
there too — but not on repeater/observer/room-server or debug builds,
|
||
which keep the port for the console.
|
||
|
||
DT_CHOSEN_ZEPHCORE_COMPANION_UART := zephcore,companion-uart
|
||
|
||
config ZEPHCORE_COMPANION_SERIAL
|
||
bool "Enable plain-UART companion transport"
|
||
depends on ZEPHCORE_ROLE_COMPANION && !ZEPHCORE_COMPANION_USB
|
||
default y if !BT && $(dt_chosen_enabled,$(DT_CHOSEN_ZEPHCORE_COMPANION_UART))
|
||
help
|
||
Run the same wired-companion stack (ZephyrCompanionUSB: frame parser,
|
||
TX ring, text CLI) over a plain UART instead of a native-USB CDC-ACM
|
||
endpoint. For boards whose USB-C is wired to a USB-UART bridge rather
|
||
than the SoC's native USB (e.g. Heltec V3 / CP2102), or that have no USB
|
||
device controller at all.
|
||
|
||
The board points the transport at its UART with the
|
||
`zephcore,companion-uart` chosen node (e.g. = &uart0); that UART should
|
||
be dedicated to the companion (console/shell moved off or disabled),
|
||
since the protocol stream and a text console cannot share one line.
|
||
It is one interface in the MultiSerialInterface, beside BLE and TCP.
|
||
|
||
On by default for a board without Bluetooth that names the chosen node
|
||
(the LoRa-E5 mini): the UART is then its only companion link.
|
||
|
||
if ZEPHCORE_ROLE_REPEATER || ZEPHCORE_ROLE_ROOM_SERVER
|
||
|
||
config ZEPHCORE_REPEATER_UPLINK
|
||
bool "Enable repeater WiFi+MQTT uplink"
|
||
depends on ZEPHCORE_ROLE_REPEATER && SOC_FAMILY_ESPRESSIF_ESP32
|
||
default n
|
||
help
|
||
Adds observer-style WiFi station and MQTT packet publishing to
|
||
repeater builds. Configuration is stored in LittleFS and applied
|
||
on reboot.
|
||
|
||
config ZEPHCORE_MAX_CLIENTS
|
||
int "Maximum ACL clients"
|
||
default 32
|
||
help
|
||
Maximum clients in the Access Control List.
|
||
Each client stores pubkey, permissions, and shared secret.
|
||
|
||
config ZEPHCORE_MAX_NEIGHBOURS
|
||
int "Maximum tracked neighbors"
|
||
default 50
|
||
help
|
||
Maximum number of neighboring repeaters to track.
|
||
Set to 0 to disable neighbor tracking.
|
||
Default 50 matches upstream Arduino MeshCore repeater variants.
|
||
|
||
config ZEPHCORE_MAX_REGION_ENTRIES
|
||
int "Maximum region entries"
|
||
default 32
|
||
help
|
||
Maximum entries in the region map for flood filtering.
|
||
Regions control which transport codes are allowed to flood.
|
||
|
||
config ZEPHCORE_ADMIN_PASSWORD
|
||
string "Default admin password"
|
||
default "password"
|
||
help
|
||
Default password for admin access to the repeater.
|
||
Should be changed on first configuration!
|
||
|
||
config ZEPHCORE_GUEST_PASSWORD
|
||
string "Default guest password"
|
||
default ""
|
||
help
|
||
Default password for guest access.
|
||
|
||
An empty string means different things per role, matching Arduino
|
||
MeshCore:
|
||
|
||
REPEATER - open guest access. A blank submitted password logs
|
||
in as PERM_ACL_GUEST, which cannot run CLI commands
|
||
or read the access list; it gets login plus
|
||
status/telemetry.
|
||
ROOM SERVER - guest access disabled, so a room is never
|
||
accidentally left open. Arduino reaches the same
|
||
place by defaulting the room server's guest password
|
||
to ROOM_PASSWORD instead of leaving it empty.
|
||
|
||
Set a non-empty value to require a password on either role.
|
||
|
||
config ZEPHCORE_MAX_UNSYNCED_POSTS
|
||
int "Room server: max buffered posts"
|
||
default 32
|
||
depends on ZEPHCORE_ROLE_ROOM_SERVER
|
||
help
|
||
Size of the room server's circular post buffer. When full, the
|
||
oldest unsynced post is overwritten. Matches upstream MeshCore (32).
|
||
|
||
endif # ZEPHCORE_ROLE_REPEATER || ZEPHCORE_ROLE_ROOM_SERVER
|
||
|
||
config ZEPHCORE_TIMESYNC_QUORUM
|
||
int "Mesh time sync: consensus quorum"
|
||
default 6
|
||
range 3 32
|
||
help
|
||
Minimum eligible advert senders required before a mesh time-sync
|
||
consensus may step the clock (a strict majority of them must also
|
||
agree). Each quorum unit costs a local attacker one more physical
|
||
radio sustained for tenure-hours, so lowering this trades eclipse
|
||
resistance for coverage on small meshes (e.g. 7 repeaters = exactly
|
||
6 potential voters). Floor 3 matches the bootstrap quorum — below
|
||
that the interval intersection degenerates. Deliberately a build-time
|
||
knob, not a runtime pref: this is a security parameter.
|
||
|
||
config ZEPHCORE_TIMESYNC_TABLE_SIZE
|
||
int "Mesh time sync: sample table slots"
|
||
default 16 if ZEPHCORE_ROLE_COMPANION
|
||
default 32
|
||
range 8 64
|
||
help
|
||
Per-sender advert sample slots (24 bytes each) for the mesh
|
||
time-sync consensus. 32 fits dense neighborhoods; companions
|
||
default to 16 (RAM-bound role). Must be at least the quorum for
|
||
normal-mode stepping to be reachable.
|
||
|
||
endmenu # Device Role
|
||
|
||
if ZEPHCORE_ROLE_COMPANION
|
||
|
||
config ZEPHCORE_MAX_CONTACTS
|
||
int "Maximum number of contacts"
|
||
default 350
|
||
help
|
||
Maximum number of contacts that can be stored.
|
||
|
||
config ZEPHCORE_MAX_CHANNELS
|
||
int "Maximum number of channels"
|
||
default 40
|
||
help
|
||
Maximum number of group channels that can be stored.
|
||
|
||
config ZEPHCORE_OFFLINE_QUEUE_SIZE
|
||
int "Offline message queue size"
|
||
default 256
|
||
help
|
||
Size of the offline message queue for incoming messages.
|
||
Arduino MeshCore uses 256 for both T1000-E and Wio Tracker.
|
||
|
||
config ZEPHCORE_ACK_TABLE_SIZE
|
||
int "ACK tracking table size"
|
||
default 16
|
||
help
|
||
Number of pending ACKs that can be tracked.
|
||
|
||
Sizing this below the number of messages that can be in flight at
|
||
once costs delivery confirmations: a peer working through a backlog
|
||
can answer seconds later, and an entry evicted before its ACK
|
||
arrives leaves the message unconfirmed, prompting a retry that adds
|
||
further load. Each entry costs 16 bytes.
|
||
|
||
config ZEPHCORE_MAX_CONNECTIONS
|
||
int "Maximum server connections"
|
||
default 16
|
||
help
|
||
Maximum number of simultaneous room server connections with keep-alive.
|
||
|
||
config ZEPHCORE_ADVERT_PATH_TABLE_SIZE
|
||
int "Advert path table size"
|
||
default 16
|
||
help
|
||
Size of recently-heard advert path table.
|
||
|
||
menu "BLE Configuration"
|
||
|
||
config ZEPHCORE_BLE_PASSKEY
|
||
int "BLE pairing passkey (6 digits)"
|
||
default 123456
|
||
range 0 999999
|
||
help
|
||
Fixed 6-digit passkey for BLE MITM pairing.
|
||
|
||
config ZEPHCORE_BLE_QUEUE_SIZE
|
||
int "BLE TX/RX message queue depth"
|
||
default 24
|
||
range 4 64
|
||
help
|
||
Number of frames that can be queued for BLE TX/RX.
|
||
Larger = better buffering for bursty traffic, more RAM.
|
||
24 absorbs the app's bursty CMD_GET_CHANNEL pipelining during
|
||
channel sync (it fires up to MAX_CHANNELS requests back-to-back);
|
||
at 12 the recv queue overran and dropped requests, stalling sync.
|
||
|
||
config ZEPHCORE_BLE_CONN_MIN_INTERVAL
|
||
int "BLE minimum connection interval (units: 1.25ms)"
|
||
default 12
|
||
range 6 3200
|
||
help
|
||
Minimum BLE connection interval in 1.25ms units.
|
||
12 = 15ms. Matches Arduino default. Lower = more responsive, higher power.
|
||
|
||
config ZEPHCORE_BLE_CONN_MAX_INTERVAL
|
||
int "BLE maximum connection interval (units: 1.25ms)"
|
||
default 36
|
||
range 6 3200
|
||
help
|
||
Maximum BLE connection interval in 1.25ms units.
|
||
36 = 45ms. Matches Arduino default. Higher = lower power when idle.
|
||
|
||
config ZEPHCORE_BLE_CONN_LATENCY
|
||
int "BLE connection latency (skip events)"
|
||
default 4
|
||
range 0 500
|
||
help
|
||
Skip up to N connection events when idle to save power.
|
||
4 = skip 4 events (effective interval up to 375ms idle).
|
||
|
||
config ZEPHCORE_BLE_CONN_TIMEOUT
|
||
int "BLE supervision timeout (units: 10ms)"
|
||
default 500
|
||
range 10 3200
|
||
help
|
||
Supervision timeout in 10ms units.
|
||
500 = 5000ms. Connection drops if no data for this duration.
|
||
|
||
config ZEPHCORE_BLE_ADV_SLOW_INTERVAL
|
||
int "BLE advertising interval (units: 0.625ms)"
|
||
default 338
|
||
range 244 3200
|
||
help
|
||
Advertising interval in 0.625ms units.
|
||
338 = 211.25ms (Apple-compliant, used after 60s fast window).
|
||
|
||
config ZEPHCORE_BLE_DFU
|
||
bool "Buttonless BLE DFU (legacy Nordic/Adafruit OTA jump)"
|
||
depends on SOC_SERIES_NRF52
|
||
default y
|
||
help
|
||
Register the legacy Nordic DFU service (00001530/00001531) in the
|
||
running companion app so a paired phone can trigger the jump into the
|
||
Adafruit bootloader's BLE OTA mode without pressing a button. On a write
|
||
of 0x01 to the control point the device sets the bootloader GPREGRET
|
||
magic (0xA8) and resets; the bootloader then advertises for DFU and the
|
||
phone's DFU tool reconnects to flash. nRF52 only.
|
||
|
||
endmenu # BLE Configuration
|
||
|
||
menu "Companion TCP Transport"
|
||
|
||
config ZEPHCORE_TRANSPORT_TCP
|
||
bool "Companion over a TCP socket"
|
||
default n
|
||
help
|
||
Serve the companion protocol over TCP (TcpCompanionTransport), as one
|
||
more interface in the MultiSerialInterface beside BLE and USB: the
|
||
native-Linux builds (linux_common.conf turns it on) and the WiFi
|
||
companion (ZEPHCORE_COMPANION_WIFI selects it).
|
||
|
||
The wire format is MeshCore's SerialWifiInterface framing:
|
||
['<'][len_LSB][len_MSB][payload] app->node and ['>']... node->app.
|
||
|
||
config ZEPHCORE_TCP_PORT
|
||
int "TCP listen port"
|
||
depends on ZEPHCORE_TRANSPORT_TCP
|
||
default 5000
|
||
range 1 65535
|
||
help
|
||
Port the companion TCP transport listens on. Default 5000 matches
|
||
upstream MeshCore's companion TCP_PORT and its Linux companion service,
|
||
so one mobile-app TCP configuration works against every backend.
|
||
|
||
config ZEPHCORE_TCP_QUEUE_SIZE
|
||
int "TCP TX/RX frame queue depth"
|
||
depends on ZEPHCORE_TRANSPORT_TCP
|
||
default 8
|
||
range 4 64
|
||
help
|
||
Frames queued between the TCP socket and the companion, each way.
|
||
Smaller than the BLE queues (ZEPHCORE_BLE_QUEUE_SIZE, 24): there is
|
||
one TCP client, the socket buffers on both ends absorb bursts, and
|
||
the RX side waits for room instead of dropping, so a full queue
|
||
pushes back through TCP flow control. 16 frames less each way is
|
||
~5.7 KB of DRAM, which the no-PSRAM WiFi boards spend on contacts.
|
||
|
||
config ZEPHCORE_COMPANION_WIFI
|
||
bool "WiFi companion (app over TCP on the saved network)"
|
||
depends on ZEPHCORE_ROLE_COMPANION && WIFI
|
||
select ZEPHCORE_TRANSPORT_TCP
|
||
help
|
||
Join the network saved with upstream's wifi.* CLI (set wifi.ssid,
|
||
set wifi.pwd, set wifi.enabled; applied on reboot) and serve the
|
||
companion protocol over TCP on ZEPHCORE_TCP_PORT, beside BLE and USB:
|
||
every connected transport is served (MultiSerialInterface).
|
||
|
||
Turned on by boards/common/wifi_companion.conf, which CMake adds to
|
||
companion builds of boards whose zephcore.yml declares
|
||
`capabilities: wifi: true` (the ones with the RAM for WiFi and BLE
|
||
together).
|
||
|
||
endmenu # Companion TCP Transport
|
||
|
||
endif # ZEPHCORE_ROLE_COMPANION
|
||
|
||
config ZEPHCORE_HOUSEKEEPING_INTERVAL_MS
|
||
int "Companion housekeeping interval (ms)"
|
||
depends on ZEPHCORE_ROLE_COMPANION
|
||
default 5000
|
||
range 1000 60000
|
||
help
|
||
Wake interval for the companion's periodic housekeeping: noise floor
|
||
calibration, battery reads, UI refresh, and loop()'s own deadlines.
|
||
Longer = lower power, less responsive monitoring.
|
||
|
||
Companion only. It keeps a fixed periodic tick because its block
|
||
contains genuine pollers of external state (contact-dump progress, BLE
|
||
advertising watchdog, battery) that have no deadline to arm. Repeaters
|
||
and room servers are deadline-driven instead — see
|
||
ZEPHCORE_MAINTENANCE_BACKSTOP_MS.
|
||
|
||
config ZEPHCORE_MAINTENANCE_BACKSTOP_MS
|
||
int "Repeater / room server maintenance backstop (ms)"
|
||
depends on ZEPHCORE_ROLE_REPEATER || ZEPHCORE_ROLE_ROOM_SERVER
|
||
default 60000
|
||
range 15000 600000
|
||
help
|
||
Hard ceiling on how long a repeater or room server will go without a
|
||
maintenance pass.
|
||
|
||
The event loop arms a one-shot wake at the soonest deadline reported
|
||
by msUntilNextMaintenance(), then clamps it to this value. The clamp is
|
||
UNCONDITIONAL, so this is a safety net against a deadline that is missed
|
||
or mis-reported — not a value that should ever bind in normal operation.
|
||
|
||
MUST stay above the radio measurement interval ("set probe.interval",
|
||
default 15 s; ZEPHCORE_NOISE_FLOOR_INTERVAL_MS when probing is off) —
|
||
the shortest recurring deadline on an idle node. Set at or below it
|
||
and this silently becomes the wake period, reproducing the old fixed 5 s
|
||
tick and making the deadline scheduling a no-op. That is exactly the bug
|
||
the original 5000 default shipped with.
|
||
|
||
Lowering it costs wakes without improving responsiveness — real work is
|
||
already scheduled at its own deadline. Raising it widens the window in
|
||
which a scheduling bug goes unnoticed.
|
||
|
||
config ZEPHCORE_NOISE_FLOOR_INTERVAL_MS
|
||
int "Default radio measurement interval (ms)"
|
||
default 15000
|
||
range 1000 120000
|
||
help
|
||
Fallback cadence for periodic radio measurements — the noise-floor RSSI
|
||
sample and the CAD probe that consumes it.
|
||
|
||
This is only the DEFAULT. At runtime the "probe.interval" pref governs
|
||
both (see set probe.interval); this value applies when that pref is 0,
|
||
i.e. CAD probing is switched off but the floor sampler still has to run.
|
||
|
||
This used to be implicit: the sampler ran once per housekeeping tick, so
|
||
it inherited that 5 s period. The sampler lives in shared radio code, so
|
||
roles that still tick periodically (companion, room server) keep 5000 —
|
||
raising it there would change their RF tracking for no power gain, since
|
||
their housekeeping timer wakes them at 5 s regardless.
|
||
|
||
Repeaters use 15 s, matching the default probe.interval pref (set in
|
||
initNodePrefs(), helpers/NodePrefs.h) — the only other recurring radio
|
||
deadline — so the two do not interleave into separate wakes any more
|
||
often than they have to.
|
||
|
||
On a repeater this is one of the two shortest recurring deadlines, so it
|
||
bounds how long the SoC can sleep between wakes. Raising it stretches
|
||
EMA warmup (8 samples) and the every-16th-sample unguarded bypass by the
|
||
same factor: at 15000 warmup is ~2 min and the bypass ~4 min apart; at
|
||
30000, ~4 min and ~8 min. Past 15000 the CAD probe becomes the binding
|
||
deadline instead, so raise both or neither.
|
||
|
||
config ZEPHCORE_PM_BOOT_AWAKE_MS
|
||
int "Console window: no light sleep for this long (ms)"
|
||
depends on PM && SOC_FAMILY_ESPRESSIF_ESP32
|
||
default 600000
|
||
range 0 3600000
|
||
help
|
||
How long the SoC refuses to light-sleep after boot, and again after
|
||
every press of the user button, so the USB console is reachable for a
|
||
configuration session.
|
||
|
||
This exists because nothing arms a UART wake source on ESP32 — see
|
||
helpers/pm_esp32_console.c. Once the window closes, characters typed at
|
||
a sleeping node are DROPPED, and on boards that console over the S3's
|
||
USB Serial/JTAG the USB device detaches on every sleep. Press the user
|
||
button, or send `powersaving off` over LoRa, to get it back.
|
||
|
||
0 disables the window entirely — the node may sleep immediately after
|
||
boot, and the USB console becomes unusable except in the instants it
|
||
happens to be awake. Do not set 0 on a board you still need to
|
||
configure.
|
||
|
||
Raising it costs only the extra awake time after each boot or press.
|
||
|
||
config ZEPHCORE_PM_CONSOLE_UART_NUM
|
||
int "Console UART index flushed before light sleep"
|
||
depends on PM && SOC_FAMILY_ESPRESSIF_ESP32
|
||
default 0
|
||
range 0 2
|
||
help
|
||
Hardware UART index (not a DT node) passed to
|
||
esp_rom_uart_tx_wait_idle() before entering light sleep, so a line in
|
||
the TX FIFO is not cut off mid-character by the clock stopping.
|
||
|
||
0 is correct for the boards whose console really is uart0: Heltec V3
|
||
(via a CP2102 bridge) and anything on the ROM-default pads. On Heltec
|
||
V4, V4.3 and Wireless Tracker V2 the console is usb_serial (their
|
||
board.overlay chooses it, and the uart0 reroute in
|
||
boards/common/esp32s3_usb.overlay is companion-only), so this flush
|
||
targets a peripheral that is not their console and does nothing. That
|
||
is harmless but not a fix: a USJ console has no wake source either way,
|
||
so on those boards the USB CLI is unavailable outside the console
|
||
window (ZEPHCORE_PM_BOOT_AWAKE_MS). Change it only if a board's console
|
||
is moved to another hardware UART — a wrong index does not fail the build, it
|
||
just silently flushes the wrong peripheral and lets the console
|
||
truncate again.
|
||
|
||
menu "Radio Configuration"
|
||
|
||
choice ZEPHCORE_RADIO_TYPE
|
||
prompt "LoRa Radio Type"
|
||
default ZEPHCORE_RADIO_NATIVE
|
||
help
|
||
Select the LoRa radio driver.
|
||
|
||
config ZEPHCORE_RADIO_NATIVE
|
||
bool "Zephyr native LoRa driver (SX126x, SX127x, LLCC68, STM32WL)"
|
||
help
|
||
Uses Zephyr's built-in LoRa driver. Supports all radios that Zephyr
|
||
has device tree bindings for:
|
||
SX126x: SX1261, SX1262, SX1268
|
||
SX127x: SX1272, SX1276, SX1278
|
||
LLCC68: Semtech LLCC68
|
||
STM32WL: STM32WL SUBGHZ radio (SX126x-based)
|
||
Used by: RAK4631, Wio Tracker L1, all XIAO + Wio-SX1262 boards
|
||
|
||
config ZEPHCORE_RADIO_LR1110
|
||
bool "LR1110/LR1120/LR1121 (custom driver)"
|
||
select LORA
|
||
select SPI
|
||
help
|
||
Semtech LR11xx via custom ZephCore driver (not upstream Zephyr).
|
||
Uses SPI mutex for thread safety + BUSY stuck recovery.
|
||
TCXO, RF switch, and PA config are in the device tree.
|
||
For any board with an LR1110, LR1120, or LR1121 radio.
|
||
|
||
config ZEPHCORE_RADIO_LR2021
|
||
bool "LR2021 (custom driver)"
|
||
select LORA
|
||
select SPI
|
||
help
|
||
Semtech LR2021 (LoRa Plus, 4th-gen) via custom ZephCore driver.
|
||
Supports sub-GHz + 2.4 GHz ISM + NTN/SATCOM.
|
||
TCXO, RF switch, and PA config are in the device tree.
|
||
|
||
config ZEPHCORE_RADIO_SX127X
|
||
bool "SX127x (SX1272/SX1276/SX1278) via Zephyr loramac-node driver"
|
||
help
|
||
Uses Zephyr's loramac-node-based LoRa driver for SX127x radios.
|
||
Supports SX1272, SX1276, and SX1278 chips via the standard Zephyr
|
||
LoRa API (lora_config, lora_send_async, lora_recv_async).
|
||
|
||
Chip-specific features not available via standard API are stubbed:
|
||
- Instantaneous RSSI (hwGetCurrentRSSI returns -80 dBm sentinel)
|
||
- Preamble detection (always false — TX won't abort mid-preamble)
|
||
- RX boost (no-op — SX127x has no dedicated boost register)
|
||
- AGC reset (no-op — loramac-node manages AGC internally)
|
||
- BUSY pin (false — SX127x has no BUSY signal)
|
||
|
||
Used by: TTGO LoRa32, Heltec LoRa32 V1/V2, and any board with
|
||
an SX1272/SX1276/SX1278 radio.
|
||
|
||
endchoice
|
||
|
||
config ZEPHCORE_DEFAULT_TX_POWER_DBM
|
||
int "Default TX power (dBm)"
|
||
default 22
|
||
range -9 22
|
||
help
|
||
Default SX1262 TX power in dBm for fresh devices (no saved prefs).
|
||
Boards with external PA should set this lower to avoid overdriving.
|
||
Example: Station G2 with 20dB PA gain uses 15 dBm (→ 35 dBm output).
|
||
|
||
config ZEPHCORE_MAX_TX_POWER_DBM
|
||
int "Maximum TX power (dBm)"
|
||
default 22
|
||
range -9 22
|
||
help
|
||
Hard cap on SX1262 TX power. The radio adapter clamps any value
|
||
above this, protecting external PAs from damage.
|
||
Example: Station G2 caps at 19 dBm to stay below PA P1dB (35 dBm).
|
||
|
||
config ZEPHCORE_SX126X_HELTEC_REG_PATCH
|
||
bool "Apply undocumented SX126x register 0x8B5 RX improvement (external PA/FEM)"
|
||
depends on ZEPHCORE_RADIO_NATIVE
|
||
default n
|
||
help
|
||
Sets the LSB of undocumented register 0x8B5 after the first lora_config().
|
||
Described by Heltec engineer @Quency-D in MeshCore PR#1398 — consistently
|
||
improves RX reception on boards with GC1109 or KCT8103L PA (Heltec V4/V4.3/V4-R8).
|
||
Upstream MeshCore also enables it for the RAK3401's SKY66122 FEM, so it is
|
||
not Heltec-specific despite the symbol name. Enable in board.conf for boards
|
||
with an external PA/FEM in the RX path.
|
||
|
||
endmenu # Radio Configuration
|
||
|
||
menu "Board Configuration"
|
||
|
||
config ZEPHCORE_BOARD_NAME
|
||
string "Board name for identification"
|
||
default "Zephyr Device"
|
||
help
|
||
Human-readable board name returned by the 'board' CLI command
|
||
and reported in device info. Should match Arduino variant names
|
||
for consistency (e.g., "RAK 4631", "Wio Tracker L1", "T1000-E").
|
||
|
||
config ZEPHCORE_SD_FWID
|
||
hex "SoftDevice firmware ID for DFU packages"
|
||
default 0x00B6
|
||
depends on SOC_SERIES_NRF52
|
||
help
|
||
SoftDevice firmware ID used by adafruit-nrfutil when generating
|
||
DFU zip packages (--sd-req parameter). Must match the SoftDevice
|
||
version flashed with the Adafruit bootloader on the target device.
|
||
Only applicable to nRF52 boards with Adafruit bootloader.
|
||
|
||
Common values:
|
||
0x00B6 - S140 v6.1.1 (RAK4631, Wio Tracker, T1000-E)
|
||
0x0123 - S140 v7.3.0 (XIAO nRF52840, Ikoka Nano)
|
||
|
||
config ZEPHCORE_VBAT_MV_MULTIPLIER
|
||
int "Battery voltage ADC multiplier (fallback)"
|
||
default 7200
|
||
help
|
||
Fallback multiplier if vbat-mv-multiplier is not defined in
|
||
devicetree zephyr,user node.
|
||
|
||
Prefer defining in devicetree alongside ADC channel:
|
||
zephyr,user {
|
||
io-channels = <&adc 7>;
|
||
vbat-mv-multiplier = <7200>;
|
||
};
|
||
|
||
Formula: mv = (raw * multiplier) / 4096
|
||
|
||
endmenu
|
||
|
||
menu "Debug & Logging"
|
||
|
||
config ZEPHCORE_PACKET_LOGGING
|
||
bool "Enable mesh packet logging"
|
||
default n
|
||
depends on PRINTK
|
||
help
|
||
Log all RX and TX mesh packets in Arduino-compatible format.
|
||
Output format matches Arduino MESH_PACKET_LOGGING exactly:
|
||
"HH:MM:SS - D/M/YYYY U: RX, len=N (type=N, route=D/F, payload_len=N) SNR=N RSSI=N score=N time=N hash=XXXX [XX -> XX]"
|
||
The lines are printk(), so they need no logging subsystem:
|
||
packet_logging.conf keeps CONFIG_LOG=n and the console carries
|
||
nothing else.
|
||
|
||
endmenu
|
||
|
||
menu "LoRa Power Saving"
|
||
|
||
config ZEPHCORE_LORA_RX_DUTY_CYCLE
|
||
bool "Enable LoRa RX duty cycle power saving"
|
||
default n
|
||
help
|
||
Enable RX duty cycling (chip-autonomous sniff mode) for power
|
||
saving on battery-powered devices. This sets the boot default;
|
||
it can be toggled at runtime via CLI "set rxduty on/off".
|
||
|
||
Window timing is computed per SF/BW/preamble from primary-source
|
||
constraints (SX1261/2 datasheet sec 13.1.7, AN1200.36): the deaf
|
||
time per cycle (sleep + TCXO/PLL wake transition) never exceeds
|
||
the budget needed to guarantee one full detection window inside
|
||
the sender's preamble, and the post-detect timer budget
|
||
(2*rxPeriod + sleepPeriod) always covers preamble + header.
|
||
With a 32-symbol sender preamble (current MeshCore firmware at
|
||
SF<=8) this yields ~40-55% radio-off time with zero loss against
|
||
updated senders. Presets whose preamble is too short for a
|
||
guaranteed catch (16 symbols at SF>=9) automatically fall back to
|
||
continuous RX — no fake duty cycling.
|
||
|
||
Caveat for mixed meshes: nodes still on pre-preamble-32 firmware
|
||
(16 symbols at SF<=8) are only caught ~50% worst-phase. Keep
|
||
this off on infrastructure until the local mesh has converted.
|
||
Floods are usually re-heard via updated repeaters; direct
|
||
messages from legacy nodes are the real exposure.
|
||
|
||
Duty cycle mode also resets AGC state on entry via
|
||
Calibrate(ALL), pins the boosted RX gain into the warm-start
|
||
retention list (datasheet sec 9.6, mandatory for SetRxDutyCycle),
|
||
and runs a parked-RX watchdog that re-arms the cycle after a
|
||
false preamble detect (inspect via CLI "get dc.restarts").
|
||
|
||
LR1110/LR20xx use the same chip-level SetRxDutyCycle MODE_RX
|
||
(identical preamble-detect-and-extend mechanism) sized by the same
|
||
adapter math; the LR1110's earlier "broken" status was a window-
|
||
sizing bug, not a chip defect. Both are wired but HW-unverified —
|
||
confirm no packet loss on a live board before trusting in
|
||
production. LR1110 has no infinite-park failure mode (its DC uses a
|
||
bounded 2*rx+sleep timeout), so it re-arms via the normal RX-timeout
|
||
path rather than the SX126x watchdog.
|
||
|
||
config ZEPHCORE_LORA_DC_MIN_SYMBOLS
|
||
int "RX duty cycle: preamble symbols required for detection"
|
||
range 4 16
|
||
default 8
|
||
help
|
||
Minimum LoRa preamble symbols that must land inside one open RX
|
||
window for detection to be considered guaranteed. 8 is Semtech's
|
||
own figure for sniff mode (AN1200.36 sec 4); their time-synced
|
||
LoRaWAN stacks budget 6. Raise to 10-12 to trade radio-off time
|
||
for extra detection margin on very noisy sites. SF5/6 adds +4
|
||
internally. Lowering below 8 risks missed packets at low SNR.
|
||
|
||
config ZEPHCORE_LORA_DC_MARGIN_PCT
|
||
int "RX duty cycle: sleep-budget safety margin (percent)"
|
||
range 0 40
|
||
default 5
|
||
help
|
||
Percentage the per-cycle deaf-time budget is derated before the
|
||
sleep period is programmed. The theoretical catch budget assumes
|
||
the sleep clock and wake transition are exact, but the chip's sleep
|
||
timer runs on an internal RC oscillator (RC64k on SX126x, RTC on
|
||
LR11xx) that drifts several percent over temperature, and the wake-
|
||
transition estimate is a datasheet figure the datasheet itself calls
|
||
"not accurate and may vary". Either overshoot silently pushes real
|
||
deaf time past the budget and drops the fraction of packets whose
|
||
preamble phase aligns with the window edge — signal-strength-
|
||
independent loss that presents as random DC packet drops. This
|
||
margin trades a little radio-off time for that robustness and is
|
||
shared by every radio (SX126x / LR11xx / LR20xx). Set to 0 to
|
||
restore the old zero-margin behaviour.
|
||
|
||
Lowered from 15% to 5% on 2026-08-21. What justifies it: the clock
|
||
half of the error is bounded, not open-ended. RC64k is recalibrated
|
||
by Calibrate(ALL) (SX126X_CALIBRATE_RC64K is bit 0 of 0x7F) whenever
|
||
agcMaintenance() sees the chip temperature move AGC_RECAL_TEMP_DELTA_C
|
||
(5 C), so the drift this must absorb is what accrues inside a 5 C
|
||
window, not across the whole operating range since boot.
|
||
|
||
What it costs, at SF7/BW62.5/P=32 on a 5 ms-TCXO board: absolute slack
|
||
against the catch budget falls from 4608 us (15%) to 1536 us (5%), and
|
||
the tolerated sleep-timer error falls from 23.5% to 6.8%. Off-time
|
||
goes 30.9% -> 34.9%. Note the tolerance always exceeds the margin
|
||
percentage, because the derate applies to the whole budget while the
|
||
clock error acts only on the sleep portion.
|
||
|
||
The part NOT bounded by the temperature recalibration is the
|
||
wake-transition estimate in hwWakeupTimeUs() -- a programmed TCXO delay
|
||
(deterministic) plus ~1 ms of context restore and PLL lock that the
|
||
datasheet itself calls "not accurate and may vary". At 5% that term
|
||
has 1536 us of headroom minus whatever RC64k drift consumes. If a
|
||
board shows unexplained duty-cycle packet loss, raise this first --
|
||
the loss is signal-strength-independent, so RSSI will look fine and
|
||
only a PDR comparison against rxduty 0 will show it.
|
||
|
||
config ZEPHCORE_MEM_STATS
|
||
bool "CLI: report thread stack and heap high-water marks"
|
||
select INIT_STACKS
|
||
select THREAD_STACK_INFO
|
||
select THREAD_MONITOR
|
||
select THREAD_NAME
|
||
select SYS_HEAP_RUNTIME_STATS
|
||
help
|
||
Adds `get mem <n>` to the CLI (all roles). Each call returns one line:
|
||
n = 0.. walks the threads (name, stack size, peak bytes used), then the
|
||
k_heaps (address, size, current and peak allocation), then "end".
|
||
|
||
For sizing stacks and heaps against measured use, e.g. after a Zephyr
|
||
bump. Stack watermarks need INIT_STACKS (stacks painted at thread
|
||
creation), which costs boot time, so leave this off in releases.
|
||
Resolve heap addresses to symbols with nm on zephyr.elf.
|
||
|
||
config ZEPHCORE_LOOP_WAKE_STATS
|
||
bool "CLI: count event-loop wakes"
|
||
help
|
||
Count every return from the mesh thread's k_event_wait(), in total and
|
||
per event bit, and report them with `get loop.wakes`. For measuring a
|
||
role's wake profile (wakes per second = wakes / up), e.g. before and
|
||
after a change to its scheduling. Measure on the release config with
|
||
this passed by -D, so debug logging does not add wakes of its own.
|
||
|
||
config ZEPHCORE_LORA_RETENTION_DEBUG
|
||
bool "SX126x: verify the warm-start retention list on air"
|
||
depends on ZEPHCORE_RADIO_NATIVE
|
||
help
|
||
Read back the SX126x register retention list (0x029F) and the Rx gain
|
||
register (0x08AC) and report whether they survived, at chip init and
|
||
after every duty-cycle packet.
|
||
|
||
This exists to answer one question: does installing the retention list
|
||
once at init actually hold? The list is what restores Rx gain across
|
||
the chip-internal sleep->RX wakes of SetRxDutyCycle, which the host
|
||
never sees. If it does not hold, every window after the first sleep
|
||
listens at power-saving gain -- a silent 3 dB sensitivity loss with
|
||
nothing in the log, which is precisely the failure this check makes
|
||
visible.
|
||
|
||
The check runs after RX_DONE and before sx126x_restart_rx() rewrites
|
||
the gain, so it observes the value the packet was actually received
|
||
with rather than the one we are about to reapply.
|
||
|
||
Costs two SPI register reads per received packet. Debug builds only;
|
||
enabled by boards/common/debug.conf.
|
||
|
||
endmenu
|
||
|
||
config ZEPHCORE_RTC_AUTODISCOVER
|
||
bool "Auto-discover a hardware I2C RTC at boot"
|
||
default y
|
||
depends on I2C
|
||
help
|
||
Probe I2C RTC chips declared with the "zephcore,rtc-i2c" binding
|
||
(DS3231 / PCF8563 / RV3028 / RX8130CE). Boards with a battery/cap-
|
||
backed RTC opt in by including boards/common/rtc-i2c.dtsi; if a chip
|
||
holds a valid time it is restored at boot — shown tagged "L" until the
|
||
next external sync. On every GPS/app/CLI sync the time is written back
|
||
so it survives power-off. Compact raw-I2C reader (no Zephyr RTC
|
||
subsystem). Safe to leave on: a board with no rtc-i2c.dtsi include has
|
||
no RTC nodes and compiles to nothing.
|
||
|
||
menu "GPS Configuration"
|
||
|
||
config ZEPHCORE_GPS_POLL_INTERVAL_SEC
|
||
int "GPS poll interval in seconds"
|
||
default 300
|
||
range 10 86400
|
||
help
|
||
How often to wake GPS and acquire a fix.
|
||
Default 300 seconds (5 minutes).
|
||
|
||
config ZEPHCORE_GPS_FIX_TIMEOUT_SEC
|
||
int "GPS fix acquisition timeout in seconds"
|
||
default 120
|
||
range 10 300
|
||
help
|
||
Maximum time to wait for GPS fix before giving up.
|
||
GPS will be powered off after this timeout.
|
||
Default 120s — 30s was too aggressive for Air530Z / MAX-7Q warm
|
||
starts in marginal sky conditions; the acquire loop would time out
|
||
before the 3-consecutive-good-fix gate could promote. Only applies
|
||
to wake cycles after the first successful fix since enable.
|
||
|
||
config ZEPHCORE_GPS_FIRST_FIX_TIMEOUT_SEC
|
||
int "GPS first-acquisition timeout in seconds"
|
||
default 300
|
||
range 30 1800
|
||
help
|
||
Bounded window for the FIRST position fix after GPS is enabled
|
||
(cold start). Longer than the normal fix timeout because a cold
|
||
start may need to download almanac data. If no fix is acquired in
|
||
this window the node drops into the normal duty cycle anyway, so
|
||
the GPS can never stay powered indefinitely with no sky view.
|
||
Default 300s (5 minutes). Companion mode only; repeaters use a
|
||
fixed 5-minute time-sync window.
|
||
|
||
config ZEPHCORE_REPEATER_GPS_INTERVAL_SEC
|
||
int "Repeater GPS duty interval in seconds (boot default)"
|
||
default 172800
|
||
range 0 604800
|
||
help
|
||
Default GPS standby interval for repeaters/room servers: how often
|
||
the GPS wakes for a time-sync fix. Default 172800s (48 hours).
|
||
0 = always-on (never sleeps). Applied at boot until the operator
|
||
overrides it with "set gps duty <sec>" (persisted to flash).
|
||
Companions default to ZEPHCORE_GPS_POLL_INTERVAL_SEC (300s) instead.
|
||
|
||
config ZEPHCORE_GPS_NMEA_DUMP
|
||
bool "Log every NMEA sentence received from the GNSS module"
|
||
depends on GNSS
|
||
help
|
||
Print each sentence the GNSS driver parses, plus everything else the
|
||
module emits, to the console. For diagnosing a receiver that reports
|
||
no satellites: it distinguishes a module that is talking normally
|
||
while it searches the sky from one that is silent, at the wrong baud,
|
||
or has had its NMEA output disabled — cases that are otherwise
|
||
indistinguishable from the application.
|
||
|
||
Verbose: at 1 Hz with multi-GNSS this is a continuous stream, and on
|
||
a repeater it shares the console with the CLI. Diagnostic builds only.
|
||
|
||
config ZEPHCORE_GPS_NAV_MODE
|
||
int "GNSS navigation dynamic model (CASIC $PCAS11)"
|
||
range -1 7
|
||
default 1 if ZEPHCORE_ROLE_REPEATER || ZEPHCORE_ROLE_ROOM_SERVER
|
||
default 3
|
||
help
|
||
Dynamic model sent to CASIC-family GNSS modules (Quectel L76K/L76KB,
|
||
Air530Z) on boards using the generic-NMEA driver: 0 = portable,
|
||
1 = stationary, 2 = pedestrian, 3 = automotive, 4 = sea,
|
||
5-7 = airborne. -1 sends nothing.
|
||
|
||
Defaults to stationary for fixed roles and automotive otherwise.
|
||
Worth setting rather than leaving alone: the model is stored IN THE
|
||
MODULE and survives reflashing the host, so a slot module that
|
||
previously lived in another device can arrive stuck in an airborne
|
||
model that quietly degrades fixes on a fixed site.
|
||
|
||
Boards that always carry a CASIC part use the air530z driver, whose
|
||
CONFIG_GNSS_LUATOS_AIR530Z_NAV_MODE follows this value.
|
||
|
||
config ZEPHCORE_GPS_SAT_DIAG
|
||
bool "GPS per-constellation satellite diagnostics"
|
||
# Must depend on GNSS, not just select GNSS_SATELLITES: on a board with
|
||
# no GNSS at all (native Linux, xiao_nrf52840) GNSS is n, and y-selecting
|
||
# a symbol whose dependencies are unmet is a Kconfig error that aborts the
|
||
# build — not a warning. The repeater default below would otherwise turn
|
||
# this on for every GNSS-less repeater target.
|
||
depends on GNSS
|
||
select GNSS_SATELLITES
|
||
default y if ZEPHCORE_ROLE_REPEATER
|
||
help
|
||
Tally tracked satellites per constellation from GSV sentences and
|
||
report them in "get gps" as sys=G<n>/R<n>/E<n>/B<n>/?<n>
|
||
(GPS / GLONASS / Galileo / BeiDou / other).
|
||
|
||
This is the ground truth for whether the boot-time multi-constellation
|
||
configuration (PMTK353 / UBX-CFG-GNSS) actually took: a module still in
|
||
its GPS-only default emits only $GPGSV, so every counter but G stays 0.
|
||
Satellite count alone cannot prove this — only the talker IDs can.
|
||
|
||
Costs RAM: CONFIG_GNSS_SATELLITES adds 512 bytes to the driver's UART
|
||
RX buffer plus a per-satellite array (~770 bytes total, measured on
|
||
RAK3401). Default on for repeaters, which have the headroom; off for
|
||
companions, which are RAM-bound. Enable it explicitly for a companion
|
||
diagnostic build.
|
||
|
||
Independent of "set gps diag", which is a runtime toggle and always
|
||
available — this option only adds the per-constellation evidence to
|
||
its report.
|
||
|
||
config ZEPHCORE_GPS_REAPPLY
|
||
bool "Re-send GNSS module settings after every GPS power-on"
|
||
depends on GNSS
|
||
default y
|
||
help
|
||
CASIC receivers (Quectel L76K/L76KB, Air530Z) keep the sentence
|
||
selection (PCAS03) and constellation set (PCAS04) in RAM only: nothing
|
||
here sends PCAS00 to save them, so a supply cut returns the module to
|
||
its factory GPS + BeiDou. Configuration otherwise runs once, at boot,
|
||
so a board whose gps off / duty-cycle standby really removes power
|
||
would lose GLONASS after its first sleep (upstream MeshCore #3201
|
||
re-sends PCAS04 on every start for the same reason).
|
||
|
||
With this on, about one second after each power-on the settings that
|
||
are lost with power are written again, blind, one sentence at a time
|
||
with the boot sequence's settle gaps, over the GNSS UART from the main
|
||
thread (never through modem_chat, whose blocking scripts are unsafe
|
||
right after a power restore): PCAS03, PCAS11 (the nav mode, when set)
|
||
and PCAS04 last; on generic-NMEA boards, whose module is unknown,
|
||
MediaTek's PMTK353 constellations, EASY (PMTK869) and AIC (PMTK286)
|
||
first. Both constellation commands restart their own chip's engine; on
|
||
a module that kept its settings that is one restart per wake, taken
|
||
over leaving one that lost them searching GPS-only. UBX stays
|
||
boot-only: CFG-CFG saves it to the module's flash, and repeating it
|
||
would wear that flash.
|
||
|
||
No board list: every GNSS board gets it, and it is inert where it is
|
||
not needed: receivers ignore the other families' sentences, and one
|
||
that only went to standby (Wio Tracker L1's WAKEUP pin) is simply told
|
||
what it already has. Costs under 130 UART bytes per wake.
|
||
|
||
Turn it off in a board.conf only with a reason, e.g. a CASIC board
|
||
proven to lose fixes to the navigation restart PCAS04 triggers.
|
||
|
||
endmenu
|
||
|
||
menu "WiFi OTA Update"
|
||
|
||
config ZEPHCORE_WIFI_OTA
|
||
bool "WiFi OTA firmware update"
|
||
depends on ZEPHCORE_ROLE_REPEATER
|
||
help
|
||
Enable WiFi AP + HTTP server for OTA firmware updates.
|
||
Auto-enabled for ESP32 repeater builds via wifi_ota.conf.
|
||
|
||
When "start ota" is issued, starts WiFi AP "ZephCore-OTA"
|
||
and HTTP server. User uploads firmware via browser at
|
||
http://192.168.100.1/update. Requires MCUboot (--sysbuild).
|
||
|
||
if ZEPHCORE_WIFI_OTA
|
||
|
||
config ZEPHCORE_OTA_AP_SSID
|
||
string "WiFi AP SSID for OTA"
|
||
default "ZephCore-OTA"
|
||
help
|
||
SSID of the WiFi access point started for OTA updates.
|
||
Open network (no password), matching Arduino behavior.
|
||
|
||
config ZEPHCORE_OTA_AP_IP
|
||
string "WiFi AP static IP address"
|
||
default "192.168.100.1"
|
||
help
|
||
Static IP address assigned to the WiFi AP interface.
|
||
DHCP server hands out addresses starting at .10.
|
||
|
||
endif # ZEPHCORE_WIFI_OTA
|
||
|
||
endmenu
|
||
|
||
menu "UI Configuration"
|
||
|
||
config ZEPHCORE_UI_BUTTONS
|
||
bool "Enable button input"
|
||
default y
|
||
depends on INPUT
|
||
help
|
||
Enable button/joystick input with long-press, double-click,
|
||
and triple-click detection using Zephyr's native input subsystem.
|
||
|
||
config ZEPHCORE_UI_MULTI_TAP
|
||
bool "Enable multi-tap input filter"
|
||
default y
|
||
depends on ZEPHCORE_UI_BUTTONS
|
||
help
|
||
Enable the multi-tap input filter that counts rapid taps
|
||
within a configurable window and emits different key codes
|
||
based on tap count. Supports 1-4 tap levels via devicetree.
|
||
Single-tap has ~400ms latency (waits for window to expire).
|
||
|
||
config ZEPHCORE_UI_BUZZER
|
||
bool "Enable buzzer"
|
||
default y if $(dt_nodelabel_enabled,buzzer)
|
||
select PWM
|
||
help
|
||
Enable PWM buzzer with RTTTL melody support.
|
||
Auto-enabled when a "buzzer" nodelabel exists in devicetree.
|
||
Pulls in CONFIG_PWM automatically.
|
||
|
||
DT_CHOSEN_ZEPHCORE_DISPLAY := zephyr,display
|
||
|
||
config ZEPHCORE_UI_DISPLAY
|
||
bool "Enable display (OLED, LCD, e-ink)"
|
||
default y if $(dt_chosen_enabled,$(DT_CHOSEN_ZEPHCORE_DISPLAY))
|
||
select DISPLAY
|
||
select CHARACTER_FRAMEBUFFER
|
||
help
|
||
Enable page-based UI on any Zephyr-supported display.
|
||
Auto-enabled when "zephyr,display" chosen node exists in DT.
|
||
Auto-detects display via chosen node or legacy nodelabels
|
||
(sh1106, ssd1306). Resolution and font are queried at runtime.
|
||
Supports OLED (SSD1306, SH1106), LCD, e-ink, etc.
|
||
|
||
config ZEPHCORE_UI_DISPLAY_AUTO_OFF_MS
|
||
int "Display auto-off timeout (ms)"
|
||
default 10000
|
||
depends on ZEPHCORE_UI_DISPLAY
|
||
help
|
||
Time in milliseconds before the display turns off
|
||
after the last user interaction. Set to 0 to disable.
|
||
|
||
config ZEPHCORE_UI_TIME_SOURCE_FRESH_HOURS
|
||
int "Clock source tag freshness window (hours)"
|
||
default 12
|
||
help
|
||
The top-bar clock shows a one-character source tag: G (GPS),
|
||
A (app), N (network/SNTP). If the most recent sync from one of
|
||
those sources is older than this many hours, the tag falls back
|
||
to L (local) — the RTC is free-running on its own oscillator.
|
||
Set to 0 to keep the last external source tag indefinitely
|
||
(no freshness expiry). Drift on a 50 ppm crystal is ~4 s/day,
|
||
so this is a trust/freshness signal, not an accuracy limit.
|
||
|
||
config ZEPHCORE_AUTO_SHUTDOWN_MILLIVOLTS
|
||
int "Low-battery auto-shutdown threshold (mV, 0 = disabled)"
|
||
default 3200 if SOC_SERIES_NRF52
|
||
default 0
|
||
help
|
||
Companion only. When the battery falls below this voltage the device
|
||
shows a brief "Low Battery / Shutting Down" notice (3 s on OLED;
|
||
e-paper persists without delay) and enters System OFF to protect the
|
||
cell from over-discharge. The check piggybacks the existing periodic
|
||
housekeeping tick (no extra polling) and is skipped while externally
|
||
powered (USB/charger) via MainBoard::isExternalPowered().
|
||
|
||
3300 mV is ~10% SoC on the default LiPo curve (battery_curve.c) — a
|
||
protective floor that still leaves headroom for the under-load voltage
|
||
sag during a TX burst. Requires CONFIG_POWEROFF to actually power down.
|
||
|
||
This is only the boot DEFAULT: the value is copied into prefs and is
|
||
tunable at runtime over the USB CLI ("set autoshutdown <mv>" /
|
||
"get autoshutdown", 0 = off or 1-5000 mV), persisted to flash.
|
||
|
||
Defaults ON (3300) for nRF52 only: that is where isExternalPowered()
|
||
reads VBUS, so the "plugged in" gate actually works. ESP32 / MG24 /
|
||
nRF54L stay at 0 (off) — isExternalPowered() is a stub there, so a
|
||
USB/mains-powered node could otherwise self-shutdown on a low ADC
|
||
reading. Set explicitly per board to enable once VBUS detect exists.
|
||
|
||
config ZEPHCORE_UI_CONFIRM_WINDOW_MS
|
||
int "Double-press confirmation window (ms)"
|
||
default 500
|
||
depends on ZEPHCORE_UI_DISPLAY
|
||
help
|
||
Maximum time between the two KEY_ENTER presses that confirm
|
||
destructive actions (shutdown, DFU, offgrid toggle). The default
|
||
500 ms suits boards where ENTER is a direct short tap. Boards
|
||
with only one button that emit ENTER via a zephyr,input-longpress
|
||
filter (T-Echo, RAK4631 Pocket) need a longer window — two
|
||
1 s holds plus the release gap do not fit in 500 ms. Set to
|
||
3000 on such boards.
|
||
|
||
config ZEPHCORE_DISPLAY_INSET
|
||
int "Display inset margin (pixels)"
|
||
default 0
|
||
depends on ZEPHCORE_UI_DISPLAY
|
||
help
|
||
Shrink the usable drawing area by this many pixels on each side.
|
||
mc_display_width/height() report the reduced size and all draw
|
||
primitives are offset by this amount. Useful on panels (e.g.
|
||
LilyGo T-Echo SSD1681) where the outermost rows/columns show
|
||
hardware artefacts — a small inset hides them behind a clean
|
||
background margin. Set to 0 on boards without edge artefacts.
|
||
|
||
config ZEPHCORE_DISPLAY_EPD_FULL_REFRESH_INTERVAL
|
||
int "E-paper full-refresh interval (partial refreshes)"
|
||
default 16
|
||
depends on ZEPHCORE_UI_DISPLAY
|
||
help
|
||
On e-paper panels, partial refreshes never fully clear the previous
|
||
image, so ghosting accumulates where text changes most (status bar
|
||
name/time/battery). After this many partial refreshes, perform one
|
||
full refresh to clear the panel. A full refresh is the ~2 s
|
||
black/white flash, so keep this large enough that it isn't constant
|
||
but small enough that ghosting never becomes unreadable. Set to 0 to
|
||
disable periodic full refreshes (partial-only — accumulates ghosting).
|
||
Ignored on non-EPD displays (OLED/TFT never ghost).
|
||
|
||
config ZEPHCORE_DISPLAY_LARGE_FONT
|
||
bool "Use a larger built-in CFB font"
|
||
default n
|
||
depends on ZEPHCORE_UI_DISPLAY && CHARACTER_FRAMEBUFFER_USE_DEFAULT_FONTS
|
||
help
|
||
Select the smallest Zephyr built-in CFB font whose height is
|
||
>= 16 pixels (typically the 10x16 font). Intended for larger
|
||
e-paper panels (T-Echo, ThinkNode M1) where the default 6x8
|
||
glyph is too small to read comfortably. The ui_pages.c layout
|
||
auto-centers its content rows using the active font height, so
|
||
no per-page changes are needed. Requires Zephyr's default CFB
|
||
fonts to be compiled in (CONFIG_CHARACTER_FRAMEBUFFER_USE_DEFAULT_FONTS).
|
||
|
||
config ZEPHCORE_DISPLAY_MONO_TFT
|
||
bool "Monochrome TFT wrapper (MONO01 => RGB565)"
|
||
default y if DT_HAS_ZEPHCORE_MONO_TFT_ENABLED
|
||
depends on DT_HAS_ZEPHCORE_MONO_TFT_ENABLED
|
||
help
|
||
Wraps an RGB TFT (e.g. ST7789V) as a PIXEL_FORMAT_MONO01 display.
|
||
CFB allocates x*y/8 bytes (~4 KB for 240×135) instead of ~64 KB RGB565,
|
||
making a colour TFT viable on RAM-constrained devices like the nRF52840.
|
||
On display_write(), converts 1bpp row-major MONO01 to RGB565 row by row.
|
||
Auto-enabled when a "zephcore,mono-tft" device exists in the devicetree.
|
||
|
||
config ZEPHCORE_EASTER_EGG_DOOM
|
||
bool "Enable Doom raycaster easter egg"
|
||
# Shipped on the Wio Tracker L1 companion. A Kconfig default rather than a
|
||
# build.sh flag, so a local build is the same firmware as the release; not
|
||
# in board.conf, which would also enable it in that board's repeater build.
|
||
default y if BOARD_WIO_TRACKER_L1 && ZEPHCORE_ROLE_COMPANION
|
||
depends on ZEPHCORE_UI_DISPLAY && ZEPHCORE_UI_BUTTONS
|
||
help
|
||
Hidden easter egg: Wolf3D-style raycaster on the OLED.
|
||
Button UI: triple-press ENTER on the Messages page.
|
||
Joystick UI: open the Tools menu and select "Doom".
|
||
Press BACK/ESC to exit.
|
||
Adds ~7KB flash and ~2KB RAM when enabled.
|
||
|
||
config ZEPHCORE_UI_JOYSTICK
|
||
bool
|
||
# In-tree Zephyr board (no board Kconfig of ours to select from)
|
||
default y if BOARD_WIO_TRACKER_L1
|
||
help
|
||
Set by board-level Kconfig when the hardware has a 5-way joystick or
|
||
D-pad. Not user-selectable; set by the board configuration.
|
||
Follows the same pattern as ZEPHCORE_UI_BUTTONS and ZEPHCORE_UI_DISPLAY.
|
||
|
||
config ZEPHCORE_UI_KEYBOARD
|
||
bool
|
||
help
|
||
Set by board-level Kconfig when the hardware has a full keyboard
|
||
(e.g. ThinkNode M9's STC8H matrix MCU on I2C). Not user-selectable.
|
||
|
||
A keyboard is a superset of a joystick — it supplies arrows, enter
|
||
and escape — so it drives the same full companion UI rather than a
|
||
separate one. Selecting this makes ZEPHCORE_UI_DESIGN_JOYSTICK
|
||
available on a board that has no 5-way stick.
|
||
|
||
Selected by ThinkNode M9. The STC8H driver is helpers/input/
|
||
stc8h_keypad.c; the keypad's arrow-key codes are still unknown, so
|
||
the driver logs any code it does not recognise (see its header).
|
||
The key-space reservation in helpers/ui-joystick/joystick_defs.h is
|
||
the groundwork that makes them droppable in later.
|
||
|
||
config ZEPHCORE_UI_DESIGN_BUTTON
|
||
bool "Button-based page UI"
|
||
default y if (ZEPHCORE_UI_BUTTONS || ZEPHCORE_UI_DISPLAY || ZEPHCORE_UI_BUZZER) && !ZEPHCORE_UI_DESIGN_JOYSTICK
|
||
depends on (ZEPHCORE_UI_BUTTONS || ZEPHCORE_UI_DISPLAY || ZEPHCORE_UI_BUZZER) && !ZEPHCORE_UI_DESIGN_JOYSTICK
|
||
help
|
||
Compile the default page-based button UI (helpers/ui-button/).
|
||
Auto-selected when any UI hardware is present and the joystick UI
|
||
design is not active. Disable to supply a custom ui_task.h
|
||
implementation without touching this Kconfig.
|
||
|
||
config ZEPHCORE_UI_DESIGN_JOYSTICK
|
||
bool "Full companion UI (joystick or keyboard driven)"
|
||
default y if ZEPHCORE_ROLE_COMPANION && ZEPHCORE_UI_DISPLAY && ZEPHCORE_UI_BUTTONS && (ZEPHCORE_UI_JOYSTICK || ZEPHCORE_UI_KEYBOARD)
|
||
depends on ZEPHCORE_ROLE_COMPANION && ZEPHCORE_UI_DISPLAY && ZEPHCORE_UI_BUTTONS && (ZEPHCORE_UI_JOYSTICK || ZEPHCORE_UI_KEYBOARD)
|
||
help
|
||
Full companion UI (helpers/ui-joystick/). Provides GPS, contacts,
|
||
channels, snake game, repeater admin, and all other companion screens.
|
||
Replaces ZEPHCORE_UI_DESIGN_BUTTON for companion builds on boards with
|
||
joystick or keyboard hardware.
|
||
|
||
Named "joystick" for the hardware that first drove it; the screens
|
||
themselves only consume the abstract key codes in joystick_defs.h, so
|
||
any input device supplying arrows/enter/escape can drive them.
|
||
|
||
endmenu
|
||
|
||
endmenu
|