Files
ZephCore/zephcore/Kconfig
T
2026-08-30 09:27:20 +02:00

1166 lines
46 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPDX-License-Identifier: MIT
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_WIFI_OTA
module-str = zephcore_wifi_ota
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). USB and BLE arbitrate the active
interface first-come-first-served: CMD_APP_START claims it for USB only
if no BLE session is already active (and is ignored while BLE is live);
on USB unplug the interface is handed back to BLE or released to idle.
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.
config ZEPHCORE_COMPANION_SERIAL
bool "Enable plain-UART companion transport"
depends on ZEPHCORE_ROLE_COMPANION && !ZEPHCORE_COMPANION_USB
help
Run the same wired-companion stack (ZephyrCompanionUSB: frame parser,
TX ring, text CLI, BLE-arbitrated interface) 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 must be
dedicated to the companion (console/shell moved off or disabled), since
the protocol stream and a text console cannot share one line. Coexists
with BLE exactly like the USB backend — same first-come arbitration.
Distinct from the legacy SerialCompanionTransport (no-BLE STM32WL path):
this one keeps BLE and adds the text CLI.
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 "Linux Companion Transport (native_sim)"
config ZEPHCORE_TRANSPORT_TCP
bool "Use TCP socket transport instead of BLE NUS"
default n
help
Replaces the ZephyrBLE adapter with LinuxTCPTransport, exposing
the companion NUS protocol over a TCP socket on Linux SBC builds
(BOARD=native_sim). Auto-enabled by boards/linux_native/linux_common.conf.
The wire format is MeshCore's SerialWifiInterface framing:
['<'][len_LSB][len_MSB][NUS payload] app->node and ['>']... node->app.
config ZEPHCORE_LINUX_TCP_PORT
int "TCP listen port"
depends on ZEPHCORE_TRANSPORT_TCP
default 5000
range 1 65535
help
Port the companion TCP transport binds to. Default 5000 matches
the upstream MeshCore Linux companion service so a single mobile
app TCP configuration works against both backends.
endmenu # Linux Companion Transport
endif # ZEPHCORE_ROLE_COMPANION
config ZEPHCORE_HOUSEKEEPING_INTERVAL_MS
int "Periodic housekeeping interval (ms)"
depends on ZEPHCORE_ROLE_COMPANION || ZEPHCORE_ROLE_ROOM_SERVER
default 5000
range 1000 60000
help
Wake interval for noise floor calibration, battery reads, and UI refresh.
Longer = lower power, less responsive monitoring.
Companion and room server only. Both still run a fixed periodic tick:
the companion's block contains genuine pollers (contact-dump progress,
BLE advertising watchdog) that have no deadline to arm, and the room
server is paced by its own 500 ms push timer regardless. Repeaters are
deadline-driven instead — see ZEPHCORE_MAINTENANCE_BACKSTOP_MS.
config ZEPHCORE_MAINTENANCE_BACKSTOP_MS
int "Repeater maintenance backstop (ms)"
depends on ZEPHCORE_ROLE_REPEATER
default 60000
range 15000 600000
help
Hard ceiling on how long a repeater will go without a maintenance pass.
main_repeater.cpp 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 repeater. 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 "Block light sleep for this long after boot (ms)"
depends on PM && SOC_FAMILY_ESPRESSIF_ESP32
default 600000
range 0 3600000
help
How long after boot the SoC refuses to light-sleep, so the USB console
is guaranteed 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 the CLI stops answering over USB until
the board resets. In practice connecting a terminal usually does reset
it (the USB bridge drives EN from DTR), which re-arms the window.
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 at the start of each boot.
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 simply unavailable once
ZEPHCORE_PM_BOOT_AWAKE_MS expires. 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).
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 LOG
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]"
config ZEPHCORE_PACKET_LOGGING_ONLY
bool "Quiet mode - only packet logs, no debug spam"
default n
depends on ZEPHCORE_PACKET_LOGGING
help
When enabled, suppresses all other log messages and only outputs
packet logging (RAW, RX, TX lines). Uses printk() to bypass
log level filtering.
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_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 instead
— see CONFIG_GNSS_LUATOS_AIR530Z_NAV_MODE for those.
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.
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 3300 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"
default n
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 ~5KB flash and ~1.7KB RAM when enabled.
config ZEPHCORE_UI_JOYSTICK
bool
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