New companion variant for the Heltec Wireless Tracker V1.1 (ESP32-S3-FN8 +
SX1262 + ST7735R 160x80 TFT + UC6580 GPS). Overlay + conf only; no driver,
CMake, or patch changes.
Board-specific fixes worth calling out:
- Battery ADC on &adc0 (ADC unit 1 -> GPIO1), NOT &adc1. In the upstream
esp32s3 DTS the node *labelled* adc1 is unit 2, whose channel 0 is GPIO11 =
the SX1262 MISO. adc_esp32_channel_setup() drives the channel pad to
GPIO_DISCONNECTED, clearing GPIO11's input buffer — which silently broke ALL
LoRa SPI reads (reads returned 0x00 -> every LBT CAD timed out) and also
sampled the wrong pin (battery read 0%). Removed the upstream 'vbatt'
voltage-divider (also on &adc1) and disabled adc1 so nothing claims GPIO11.
- Battery divider enable on GPIO2 active-HIGH (v1.1 ADC_CTRL), not the V3's
GPIO37/active-LOW (which never powered this board's divider).
- SX1262 on SPI2 with driver GPIO CS; DIO1 pull-down + rx-boosted.
- GPS run line (GPIO35) exposed via the gps-enable alias; GPS is powered from
the always-on Vext rail. GPIO36 (GPS PPS, an output) left unconfigured.
- Status LED on GPIO18 exposed as led0 (ZephCore heartbeat). UART1 — which the
upstream DTS parks on GPIO17/18 — disabled so the LED isn't stuck on.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a board-aware battery SOC system replacing the two independent
linear approximations that existed in the UI helpers.
New helpers/battery_curve.{h,c}: a 21-point (5% step) OCV lookup table
with integer linear interpolation. The default generic LiPo curve is a
weak symbol — any board can override it by dropping a battery_curve.c
into its board directory. CMakeLists.txt selects the board-specific file
when present, falling back to the generic.
Board.h gains getBattPercent() (default 0); ZephyrBoard implements it
via battery_curve_lookup().
Board-specific curves added for boards with measured cell data:
t1000_e, rak_wismesh_tag, thinknode_m6, sensecap_solar, wio_tracker_l1.
ADC multiplier corrections applied across all nRF52840 boards:
- Boards using a correctly-derived 3600×ratio formula get +0.5% to
compensate for nRF SAADC gain error (7200→7236, 6300→6332, etc.)
- rak_wismesh_tag, rak3401_1watt, gat562_30s had multipliers copied
from Arduino's 3.0V AREF formula; corrected to 3600×1.73×1.005=6259
- xiao_nrf52840 (10911) left unchanged — empirically calibrated value
above the theoretical, assumed already correct for that hardware
get adc.multiplier now reports current mV reading and the board's
curve 100% target, making field calibration self-guiding.
- README: add Room Server to "Device Roles" + a build command in "Building"
- ARCHITECTURE.md: role list two -> four (add Room Server + the
previously-undocumented Observer)
- Repeater_CLI_commands.md: note the room server shares this CLI
- boards/example_board/README.md: add the room_server.conf build snippet
Cleanups 1–3 (cosmetic):
Removed the dead uart1_default/uart1_sleep pinctrl groups + stale "GPS on UART1" comments → gat562_30s-pinctrl.dtsi
Removed the QSPI pinctrl groups (board has no QSPI; pins collide with the joystick) and replaced them with a comment explaining why
Untangled the GPS UART0-vs-async rationale in the DTS and the &uart1 comment
GPS driver switch (functional, you approved the full switch) → gat562_30s.dts, board.conf:
gnss-nmea-generic → luatos,air530z with on-off-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH> (matches the sibling rak_wismesh_tag, same AT6558R chip)
Deleted the always-on gps_power_en gpio-hog (the driver owns P1.02 now → GPS can be powered down)
Removed the CONFIG_UART_ASYNC_API=y override
Updated all related comments
Add the MeshCore Room Server role to ZephCore — a store-and-forward
shared message room. Clients log in with an admin or guest password
and post messages; the server pushes each new post to all other
logged-in clients (round-robin, per-client sync cursor, ACK + retry,
3-strike eviction).
Ported from upstream MeshCore's simple_room_server, structured as a
near-clone of RepeaterMesh so it reuses the proven ACL, region
filtering, CLI, adverts and telemetry; the post buffer + push engine
are the only net-new pieces:
- RoomServerMesh: PostInfo ring (MAX_UNSYNCED_POSTS=32), addPost,
pushPostToClient, getUnsyncedCount, processAck, onAckRecv, the
loop() push driver; room login (onAnonDataRecv parses the
sync_since cursor); posts/admin-CLI/keep-alive (onPeerDataRecv);
ADV_TYPE_ROOM advert; ServerStats wire layout; disable_fwd=1.
- main_room_server.cpp: event-loop entry (USB serial CLI, no BLE).
- Kconfig: ZEPHCORE_ROLE_ROOM_SERVER + ZEPHCORE_MAX_UNSYNCED_POSTS.
- CMakeLists role gating; boards/common/room_server.conf.
Post frame, SHA-256 ACK and login-reply layouts match upstream for
MeshCore app compatibility. Builds for gat562_30s
(FLASH 30.95% / RAM 32.46%). Not yet hardware-tested.
The GAT562 30S Mesh Kit is an nRF52840 + SX1262 board built on the
RAK4631 WisBlock core module with a 30 dBm (1 W) external PA, so it
reuses the proven rak4631 LoRa path. Deltas vs rak4631: 1.8 V TCXO
(per the MeshCore variant), battery multiplier 6232, buzzer on P1.01,
an AT6558R UART GPS, active-high LEDs, and a 5-way joystick that
selects the joystick companion menu UI.
A single board serves both the full kit and the screenless solar
repeater pod; absent peripherals (OLED, GPS, joystick, buzzer) are
optional in devicetree and skipped at runtime.
Board-specific notes:
- GPS: AT6558R NMEA on UART0 @ 9600 (P0.15 RX / P0.16 TX), powered by a
WB_IO2/P1.02 gpio-hog and parsed by gnss-nmea-generic. Uses
CONFIG_UART_ASYNC_API (DMA receive); nRF UARTE interrupt-driven RX did
not feed the modem backend.
- The 6 button/joystick inputs are routed to GPIO SENSE (sense-edge-mask
on &gpio0) so they don't consume the GPIOTE channel the SX1262 DIO1
"done" IRQ needs.
Registration: board definition, CMakeLists.txt platform-conf match,
build.sh (nRF_boards), supported_boards.md, the top-level README board
table, and the porting-guide board table.
Hardware-tested on the kit, companion and repeater: LoRa TX/RX with BLE
active (no CAD errors), BLE pairing (PIN 123456) and two-way messaging,
joystick menu UI, SSD1306 OLED, buzzer, battery %, UF2 + DFU-zip
flashing, and a 14-satellite GPS fix.
553b71c made production the default but broke configure+link:
- ZEPHCORE_COMPANION_USB sat inside the role choice (recursive dep)
- CONFIG_RESET_ON_FATAL_ERROR was an undefined, unimplemented symbol
- USB companion sources still gated on CONFIG_LOG, not ZEPHCORE_USB_STACK
Move the USB toggle out of the choice, add a real
ZEPHCORE_RESET_ON_FATAL_ERROR Kconfig + k_sys_fatal_error_handler that
cold-reboots, and match the CMake USB guard to ZEPHCORE_USB_STACK.
Production (LOG=n, ASSERT=n, RTT=n, reboot-on-fatal) is now the prj.conf
default; debug.conf is the opt-in bundle. Removed prod.conf and the
logging.conf auto-include; relocated RTT/ASSERT out of the always-on
platform confs so they no longer override the prod defaults.
Add CONFIG_ZEPHCORE_COMPANION_USB so the USB CDC companion transport
compiles independently of logging (default-y on USB-capable companions,
opt-in on ESP32-S3 via esp32s3_usb.conf). Gate all USB sites behind one
ZEPHCORE_USB_STACK macro.
Rework BLE/USB interface arbitration to first-come-first-served: neither
transport evicts a live session. Make active_iface mutation thread-safe
(mutex + atomic claim) across the BLE callback thread and USB workqueue.
Share the ESP32-S3 USB OTG / console DTS via common dtsi includes; enable
uart0 (GPIO43/44) on station_g2 and xiao so the console reroute works.
Single ZephyrUSBCDC module owns the usbd context, 1200-baud DFU
detection, and DTR transitions for both roles. The boot banner
now blocks on a k_event signalled by the usbd_msg_callback when
DTR transitions high — host attached → wakes immediately; no host
→ bounded timeout (2 s repeater, 1 s companion). Replaces the
fixed k_sleep delays in both mains.
Deletes the companion's 10 s DTR-polling work — line state changes
arrive as events now, same callback handles disconnect (resets V3
parser, flips active_iface) and DFU touch (reboots to bootloader).
Side effect: prod companion no longer enumerates a phantom CDC ACM
port (CONFIG_LOG=n skips the whole stack instead of auto-initing
an unused device).
1. CONFIG_BT_DEVICE_NAME_GATT_WRITABLE=y removed. The default GAP
Device Name write permission is plain BT_GATT_PERM_WRITE — no
bonding required (Zephyr gap_svc.c:158). Any connected peer
(bonded or not) could rename the device. Worse, a GAP write
updates bt_get_name() but NOT prefs.node_name, so the advertised
name wouldn't track the renamed value. Rename now flows
exclusively through CMD_SET_ADVERT_NAME, which is NUS-protected
(AUTHEN required) and properly propagates via
zephcore_ble_update_name() to prefs + GATT + adv data.
2. CONFIG_BT_DIS_FW_REV_STR synced from "1.13.0" to "v1.15.1-zephyr"
to match CompanionMesh.cpp CMD_DEVICE_QUERY's version string.
Comment added requiring the two to stay in sync.