Add Station G2

This commit is contained in:
liquidraver
2026-02-20 22:40:56 +01:00
parent 1587a8bcdf
commit dcd8a03c5b
16 changed files with 542 additions and 32 deletions
+20 -6
View File
@@ -84,12 +84,18 @@ if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "wio_tr
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf")
elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*nrf52.*")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf")
elseif(BOARD MATCHES ".*esp32.*")
elseif(BOARD MATCHES ".*esp32.*" OR BOARD MATCHES "station_g2")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_common.conf")
elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*esp32.*")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_common.conf")
elseif(BOARD MATCHES ".*nrf54l.*")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf54l_common.conf")
elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*nrf54l.*")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf54l_common.conf")
elseif(BOARD MATCHES ".*mg24.*" OR BOARD MATCHES ".*efr32.*")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/mg24_common.conf")
elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*mg24.*")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/mg24_common.conf")
else()
set(ZEPHCORE_PLATFORM_CONF "")
endif()
@@ -137,11 +143,12 @@ else()
endif()
endif()
# Append to EXTRA_CONF_FILE (preserves any user-specified extras)
# Note: repeater.conf is passed via EXTRA_CONF_FILE on the command line when building repeater role
# Append to EXTRA_CONF_FILE — auto-generated configs first, user extras last.
# User-specified extras (repeater.conf, prod.conf) MUST come after the auto chain
# so they can override settings (e.g. CONFIG_BT=n in repeater.conf).
if(ZEPHCORE_CONF_FILES)
if(EXTRA_CONF_FILE)
set(EXTRA_CONF_FILE "${EXTRA_CONF_FILE};${ZEPHCORE_CONF_FILES}" CACHE STRING "" FORCE)
set(EXTRA_CONF_FILE "${ZEPHCORE_CONF_FILES};${EXTRA_CONF_FILE}" CACHE STRING "" FORCE)
else()
set(EXTRA_CONF_FILE "${ZEPHCORE_CONF_FILES}" CACHE STRING "" FORCE)
endif()
@@ -260,8 +267,9 @@ if(CONFIG_ZEPHCORE_ROLE_REPEATER)
helpers/TransportKeyStore.cpp
helpers/CommonCLI.cpp
)
# USB CDC with 1200 baud touch detection (when not using auto-init)
if(NOT CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT)
# USB CDC with 1200 baud touch detection (nRF boards with USB CDC ACM only).
# ESP32 boards use usb_serial (no UDC/CDC ACM), so skip custom USB init.
if(NOT CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT AND (CONFIG_USB_CDC_ACM OR CONFIG_USBD_CDC_ACM_CLASS))
target_sources(app PRIVATE
adapters/usb/ZephyrRepeaterUSB.cpp
)
@@ -321,6 +329,12 @@ if(CONFIG_ZEPHCORE_UI_BUTTONS OR CONFIG_ZEPHCORE_UI_BUZZER OR CONFIG_ZEPHCORE_UI
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/helpers/ui
)
# Repeater builds need weak stubs for companion-only UI mesh actions
if(CONFIG_ZEPHCORE_ROLE_REPEATER)
target_sources(app PRIVATE
helpers/ui/ui_mesh_actions_stubs.c
)
endif()
endif()
if(CONFIG_ZEPHCORE_EASTER_EGG_DOOM)
+24 -2
View File
@@ -2,6 +2,28 @@
#
# Build with: -DEXTRA_CONF_FILE="boards/common/repeater.conf"
#
# Disable Zephyr's auto USB init - we do it ourselves with 1200 baud touch detection
# for entering UF2 bootloader mode (like Arduino's behavior).
# Repeaters use USB serial CLI for configuration — no BLE, no bonding, no NUS.
# This overrides the BLE settings in zephcore_common.conf.
# ========== Role Selection ==========
CONFIG_ZEPHCORE_ROLE_REPEATER=y
# ========== Disable BLE entirely ==========
# Repeaters have zero BLE functionality — all config via USB serial CLI.
# Saves ~100KB flash and ~30KB RAM on nRF52.
CONFIG_BT=n
# ========== Entropy (required when BT is disabled) ==========
# BT normally pulls in the entropy subsystem. Without BT, we need it explicitly
# for Ed25519 key generation, LoRa random delays, etc.
CONFIG_ENTROPY_GENERATOR=y
# ========== Display ==========
# Fast sleep — repeater display is rarely needed, save power.
# Only user button wakes it (no LoRa RX wake, no flashing).
CONFIG_ZEPHCORE_UI_DISPLAY_AUTO_OFF_MS=5000
# ========== USB CDC ==========
# Custom USB init with 1200 baud touch detection for UF2 bootloader (nRF52 only).
# ESP32 boards use usb_serial — this setting is ignored there.
CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n
+3
View File
@@ -0,0 +1,3 @@
config HEAP_MEM_POOL_ADD_SIZE_BOARD
int
default 4096
@@ -0,0 +1,3 @@
config BOARD_STATION_G2
select SOC_ESP32S3_WROOM_N16R8
select SOC_ESP32S3_PROCPU if BOARD_STATION_G2_ESP32S3_PROCPU
@@ -0,0 +1,7 @@
if(NOT "${OPENOCD}" MATCHES "^${ESPRESSIF_TOOLCHAIN_PATH}/.*")
set(OPENOCD OPENOCD-NOTFOUND)
endif()
find_program(OPENOCD openocd PATHS ${ESPRESSIF_TOOLCHAIN_PATH}/openocd-esp32/bin NO_DEFAULT_PATH)
include(${ZEPHYR_BASE}/boards/common/esp32.board.cmake)
include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake)
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: Apache-2.0
# BQ Station G2 — ZephCore board configuration
#
# Hardware:
# ESP32-S3 (16MB flash, 8MB PSRAM)
# SX1262 + PA on SPI2 (TX power 7 dBm → ~27 dBm with PA)
# SH1106 128x64 OLED on I2C0
# GPS on UART1
# User button on GPIO38
#
# Include order: prj.conf → zephcore_common.conf → esp32_common.conf → board.conf
# Board identification
CONFIG_ZEPHCORE_BOARD_NAME="Station G2"
CONFIG_BT_DIS_MODEL_NUMBER_STR="BQ Station G2"
# Radio type — SX1262 via Zephyr native LoRa driver
CONFIG_ZEPHCORE_RADIO_NATIVE=y
# Flash size — 16MB (override Zephyr default)
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
@@ -0,0 +1,119 @@
/*
* SPDX-License-Identifier: Apache-2.0
* BQ Station G2 — ZephCore overlay
*
* Adds LoRa radio, display, GPS, button, and filesystem on top of the
* base board DTS. Pin mapping from Arduino MeshCore variants/station_g2.
*
* LoRa SPI2 pins: SCLK=12, MOSI=13, MISO=14, NSS=11
* LoRa control: DIO1=48, RESET=21, BUSY=47
* Display I2C0: SDA=5, SCL=6 (SH1106 @ 0x3C)
* GPS UART1: TX=7, RX=15
* Button: GPIO38
*/
/ {
aliases {
lora0 = &lora;
sw0 = &user_button;
};
chosen {
zephyr,settings-partition = &storage_partition;
zephyr,display = &oled;
};
buttons: buttons {
compatible = "gpio-keys";
user_button: user_button {
gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>; /* GPIO38 = gpio1 pin 6 */
label = "User Button";
zephyr,code = <INPUT_BTN_0>;
};
};
};
/* SX1262 + PA on SPI2 */
&spi2 {
cs-gpios = <&gpio0 11 GPIO_ACTIVE_LOW>; /* NSS = GPIO11 */
lora: lora@0 {
compatible = "semtech,sx1262";
reg = <0>;
spi-max-frequency = <8000000>;
reset-gpios = <&gpio0 21 GPIO_ACTIVE_LOW>; /* RESET = GPIO21 */
busy-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>; /* BUSY = GPIO47 = gpio1 pin 15 */
dio1-gpios = <&gpio1 16 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; /* DIO1 = GPIO48 = gpio1 pin 16 */
/* DIO2 controls RF switch for TX */
dio2-tx-enable;
/* TCXO 1.8 V via DIO3 (value 2 = 1.8 V in Zephyr SX126x driver) */
dio3-tcxo-voltage = <2>;
tcxo-power-startup-delay-ms = <10>;
/*
* RX boosted mode DISABLED — Station G2 wiki warns against it:
* "Impact of LoRa node dense areas/high noise environments on RF performance"
* https://wiki.uniteng.com/en/meshtastic/station-g2
*/
};
};
/* SH1106 128x64 OLED on I2C0 */
&i2c0 {
oled: sh1106@3c {
compatible = "sinowealth,sh1106";
reg = <0x3c>;
width = <128>;
height = <64>;
segment-offset = <2>;
page-offset = <0>;
display-offset = <0>;
multiplex-ratio = <63>;
prechargep = <0x22>;
segment-remap;
com-invdir;
};
#include "../../common/sensors-i2c.dtsi"
};
/* GPS on UART1 (already enabled in DTS with 9600 baud) */
&uart1 {
gnss: gnss-nmea-generic {
compatible = "gnss-nmea-generic";
};
};
/*
* Flash partitions — repartition default ESP32-S3 16 MB layout.
* Default storage_partition is 192 KB at 0xFB0000.
* Delete unused partitions (lpcore, scratch, coredump) to reclaim space.
* Split into: NVS 8 KB (BLE bonds) + LittleFS 376 KB (DataStore).
*/
/delete-node/ &slot0_lpcore_partition;
/delete-node/ &slot1_lpcore_partition;
/delete-node/ &storage_partition;
/delete-node/ &scratch_partition;
/delete-node/ &coredump_partition;
&flash0 {
partitions {
/* NVS 8 KB for BLE bonds + settings */
storage_partition: partition@fa0000 {
label = "storage";
reg = <0xFA0000 0x2000>;
};
/* LittleFS 376 KB for DataStore (identity, contacts, channels, etc.) */
lfs_partition: partition@fa2000 {
label = "lfs";
reg = <0xFA2000 0x5E000>;
};
};
};
/* LittleFS auto-mount — standard /lfs mount point */
#include "../../common/filesystem.dtsi"
@@ -0,0 +1,6 @@
board:
name: station_g2
full_name: BQ Station G2
vendor: bq
socs:
- name: esp32s3
@@ -0,0 +1,51 @@
/*
* SPDX-License-Identifier: Apache-2.0
* BQ Station G2 — Pin Control Definitions
*
* Pin mapping (from Arduino MeshCore variants/station_g2):
* LoRa SPI2: SCLK=GPIO12, MOSI=GPIO13, MISO=GPIO14, NSS=GPIO11
* I2C0: SDA=GPIO5, SCL=GPIO6 (SH1106 OLED + sensors)
* UART1: TX=GPIO7, RX=GPIO15 (GPS)
* Console: USB Serial (native ESP32-S3 USB)
*/
#include <zephyr/dt-bindings/pinctrl/esp-pinctrl-common.h>
#include <zephyr/dt-bindings/pinctrl/esp32s3-pinctrl.h>
#include <zephyr/dt-bindings/pinctrl/esp32s3-gpio-sigmap.h>
&pinctrl {
/* SPI2 — LoRa SX1262 (CS handled via cs-gpios, not pinctrl) */
spim2_default: spim2_default {
group1 {
pinmux = <SPIM2_MISO_GPIO14>,
<SPIM2_SCLK_GPIO12>;
};
group2 {
pinmux = <SPIM2_MOSI_GPIO13>;
output-low;
};
};
/* I2C0 — SH1106 OLED display + optional I2C sensors */
i2c0_default: i2c0_default {
group1 {
pinmux = <I2C0_SDA_GPIO5>,
<I2C0_SCL_GPIO6>;
bias-pull-up;
drive-open-drain;
output-high;
};
};
/* UART1 — GPS module */
uart1_default: uart1_default {
group1 {
pinmux = <UART1_TX_GPIO7>;
output-high;
};
group2 {
pinmux = <UART1_RX_GPIO15>;
bias-pull-up;
};
};
};
@@ -0,0 +1,89 @@
/*
* SPDX-License-Identifier: Apache-2.0
* BQ Station G2 — ESP32-S3, 16MB flash, 8MB PSRAM
*
* Hardware:
* MCU: ESP32-S3 (Xtensa LX7, dual-core @ 80 MHz)
* Flash: 16 MB (QIO)
* PSRAM: 8 MB (OPI)
* Radio: SX1262 + PA (~27 dBm output) on SPI2
* Display: SH1106 128x64 OLED on I2C0
* GPS: NMEA on UART1
* Button: GPIO38
* Console: Native USB Serial
*/
/dts-v1/;
#include <espressif/esp32s3/esp32s3_wroom_n16r8.dtsi>
#include "station_g2-pinctrl.dtsi"
#include <espressif/partitions_0x0_default_16M.dtsi>
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
model = "BQ Station G2";
compatible = "bq,station-g2";
chosen {
zephyr,sram = &sram1;
zephyr,console = &usb_serial;
zephyr,shell-uart = &usb_serial;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
zephyr,bt-hci = &esp32_bt_hci;
};
aliases {
uart-0 = &uart0;
i2c-0 = &i2c0;
watchdog0 = &wdt0;
};
};
/* Reduce CPU clock from 240 MHz to 80 MHz — plenty for mesh node, saves power */
&cpu0 {
clock-frequency = <DT_FREQ_M(80)>;
};
&cpu1 {
clock-frequency = <DT_FREQ_M(80)>;
};
&usb_serial {
status = "okay";
};
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
pinctrl-0 = <&i2c0_default>;
pinctrl-names = "default";
};
&spi2 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-0 = <&spim2_default>;
pinctrl-names = "default";
};
&uart1 {
status = "okay";
current-speed = <9600>;
pinctrl-0 = <&uart1_default>;
pinctrl-names = "default";
};
&gpio0 { status = "okay"; };
&gpio1 { status = "okay"; };
&wdt0 { status = "okay"; };
&trng0 { status = "okay"; };
&esp32_bt_hci {
status = "okay";
};
&wifi {
status = "okay";
};
@@ -0,0 +1,12 @@
# BQ Station G2 — minimal Zephyr defconfig (procpu)
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y
CONFIG_GPIO=y
CONFIG_CLOCK_CONTROL=y
CONFIG_PINCTRL=y
CONFIG_SPI=y
CONFIG_I2C=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
@@ -0,0 +1,25 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Weak stubs for companion-only UI mesh actions.
*
* Repeater builds with a display compile ui_task.c which references these
* functions. The real implementations live in ui_mesh_actions.cpp and are
* linked only for companion builds. These weak stubs allow repeater+display
* builds to link cleanly — button actions that require BLE/GPS/companion
* features simply do nothing.
*/
#include <stdbool.h>
#include <stdint.h>
#include <zephyr/kernel.h>
__attribute__((weak)) void mesh_send_flood_advert(void) {}
__attribute__((weak)) void mesh_send_zerohop_advert(void) {}
__attribute__((weak)) void mesh_gps_set_enabled(bool enable) { ARG_UNUSED(enable); }
__attribute__((weak)) void mesh_ble_set_enabled(bool enable) { ARG_UNUSED(enable); }
__attribute__((weak)) void mesh_set_buzzer_quiet(bool quiet) { ARG_UNUSED(quiet); }
__attribute__((weak)) void mesh_set_offgrid_mode(bool enable) { ARG_UNUSED(enable); }
__attribute__((weak)) void mesh_disable_power_regulators(void) {}
__attribute__((weak)) void mesh_reboot_to_ota_dfu(void) {}
__attribute__((weak)) void mesh_handle_ui_actions(void) {}
__attribute__((weak)) void mesh_housekeeping_ui_refresh(void) {}
+103 -17
View File
@@ -145,6 +145,38 @@ void utf8_to_latin1(char *dst, const char *src, size_t dst_size)
/* ========== Global State ========== */
static struct ui_state state;
/* ========== Active Pages (role-dependent) ========== */
/* Repeater: minimal pages — status, radio, shutdown.
* Companion: full page set matching Arduino UI.
* Navigation walks this array instead of the raw enum. */
#ifdef ZEPHCORE_REPEATER
static const enum ui_page active_pages[] = {
UI_PAGE_STATUS,
UI_PAGE_RADIO,
UI_PAGE_SHUTDOWN,
};
#else
static const enum ui_page active_pages[] = {
UI_PAGE_MESSAGES,
UI_PAGE_RECENT,
UI_PAGE_RADIO,
UI_PAGE_BLUETOOTH,
UI_PAGE_ADVERT,
UI_PAGE_GPS,
UI_PAGE_BUZZER,
UI_PAGE_SENSORS,
UI_PAGE_OFFGRID,
UI_PAGE_DFU,
UI_PAGE_SHUTDOWN,
};
#endif
#define ACTIVE_PAGE_COUNT ((int)(sizeof(active_pages) / sizeof(active_pages[0])))
/* Current index into active_pages[] */
static int current_page_idx;
/* ========== Helper: Battery Percentage from mV ========== */
static uint8_t calc_battery_pct(uint16_t mv)
@@ -213,7 +245,7 @@ static void render_page_indicator(void)
/* Centered dots matching Arduino layout:
* Each dot spaced 10px apart, centered on screen.
* Current page = filled 3x3 block, others = single pixel. */
int total = UI_PAGE_COUNT;
int total = ACTIVE_PAGE_COUNT;
int dot_spacing = 10;
int total_width = (total - 1) * dot_spacing;
int start_x = (DISP_W - total_width) / 2;
@@ -221,7 +253,7 @@ static void render_page_indicator(void)
for (int i = 0; i < total; i++) {
int x = start_x + i * dot_spacing;
if (i == (int)state.current_page) {
if (i == current_page_idx) {
/* Current page: filled 3x3 block */
mc_display_fill_rect(x - 1, DOTS_Y, 3, 3);
} else {
@@ -582,6 +614,51 @@ static void render_shutdown(void)
}
}
static void render_status(void)
{
char buf[28];
int y = CONTENT_Y;
/* Role label */
draw_centered(y, "REPEATER");
y += LINE_H;
/* Uptime */
uint32_t up_s = (uint32_t)(k_uptime_get() / 1000);
uint32_t days = up_s / 86400;
uint32_t hours = (up_s % 86400) / 3600;
uint32_t mins = (up_s % 3600) / 60;
snprintf(buf, sizeof(buf), "Up: %ud %uh %um", days, hours, mins);
mc_display_text(0, y, buf, false);
y += LINE_H;
/* Clock — only if RTC has been synced (after Jan 1 2025) */
if (state.rtc_epoch > 1735689600) {
uint32_t day_sec = state.rtc_epoch % 86400;
uint8_t hh = day_sec / 3600;
uint8_t mm = (day_sec % 3600) / 60;
uint8_t ss = day_sec % 60;
snprintf(buf, sizeof(buf), "Time: %02u:%02u:%02u UTC", hh, mm, ss);
mc_display_text(0, y, buf, false);
} else {
mc_display_text(0, y, "Time: not synced", false);
}
y += LINE_H;
/* Battery */
if (state.battery_mv > 0) {
uint8_t pct = state.battery_pct;
if (pct == 0) {
pct = calc_battery_pct(state.battery_mv);
}
snprintf(buf, sizeof(buf), "Batt: %u%% (%umV)", pct, state.battery_mv);
mc_display_text(0, y, buf, false);
}
}
/* ========== Page render dispatch ========== */
typedef void (*page_render_fn)(void);
@@ -598,6 +675,7 @@ static const page_render_fn renderers[] = {
[UI_PAGE_OFFGRID] = render_offgrid,
[UI_PAGE_DFU] = render_dfu,
[UI_PAGE_SHUTDOWN] = render_shutdown,
[UI_PAGE_STATUS] = render_status,
};
/* ========== Public API ========== */
@@ -613,8 +691,10 @@ void ui_pages_render(void)
render_top_bar();
render_page_indicator();
if (state.current_page < UI_PAGE_COUNT && renderers[state.current_page]) {
renderers[state.current_page]();
enum ui_page page = active_pages[current_page_idx];
if (page < UI_PAGE_COUNT && renderers[page]) {
renderers[page]();
}
mc_display_finalize();
@@ -648,35 +728,41 @@ void ui_pages_render_splash(void)
void ui_pages_next(void)
{
state.shutdown_confirm_time = 0; /* Reset confirmation on navigate */
int page = (int)state.current_page + 1;
if (page >= UI_PAGE_COUNT) {
page = 0;
current_page_idx++;
if (current_page_idx >= ACTIVE_PAGE_COUNT) {
current_page_idx = 0;
}
state.current_page = (enum ui_page)page;
state.current_page = active_pages[current_page_idx];
}
void ui_pages_prev(void)
{
state.shutdown_confirm_time = 0; /* Reset confirmation on navigate */
int page = (int)state.current_page - 1;
if (page < 0) {
page = UI_PAGE_COUNT - 1;
current_page_idx--;
if (current_page_idx < 0) {
current_page_idx = ACTIVE_PAGE_COUNT - 1;
}
state.current_page = (enum ui_page)page;
state.current_page = active_pages[current_page_idx];
}
void ui_pages_set(enum ui_page page)
{
if (page < UI_PAGE_COUNT) {
state.current_page = page;
/* Find page in active list, fall back to first active page */
for (int i = 0; i < ACTIVE_PAGE_COUNT; i++) {
if (active_pages[i] == page) {
current_page_idx = i;
state.current_page = page;
return;
}
}
/* Page not in active list — go to first active page */
current_page_idx = 0;
state.current_page = active_pages[0];
}
enum ui_page ui_pages_current(void)
{
return state.current_page;
return active_pages[current_page_idx];
}
void ui_pages_set_node_name(const char *name)
+1
View File
@@ -31,6 +31,7 @@ enum ui_page {
UI_PAGE_OFFGRID, /* Offgrid mode (client repeat) toggle */
UI_PAGE_DFU, /* BLE DFU bootloader entry */
UI_PAGE_SHUTDOWN, /* Hibernate / power off */
UI_PAGE_STATUS, /* Repeater status (uptime, time, packets) */
UI_PAGE_COUNT
};
+17 -3
View File
@@ -213,8 +213,12 @@ static void splash_work_handler(struct k_work *work)
splash_active = false;
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
/* Transition from splash to home page */
/* Transition from splash to home page (first active page for this role) */
#ifdef ZEPHCORE_REPEATER
ui_pages_set(UI_PAGE_STATUS);
#else
ui_pages_set(UI_PAGE_MESSAGES);
#endif
k_work_reschedule(&render_work, K_NO_WAIT);
#endif
}
@@ -611,7 +615,11 @@ static void ui_input_cb(struct input_event *evt, void *user_data)
k_work_cancel_delayable(&splash_work);
splash_active = false;
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
#ifdef ZEPHCORE_REPEATER
ui_pages_set(UI_PAGE_STATUS);
#else
ui_pages_set(UI_PAGE_MESSAGES);
#endif
#endif
schedule_render();
return;
@@ -718,6 +726,9 @@ int ui_init(void)
splash_active = true;
ui_pages_render_splash();
/* Start auto-off timer so display sleeps after timeout */
mc_display_reset_auto_off();
/* Schedule transition to home page */
k_work_reschedule(&splash_work, K_MSEC(SPLASH_DURATION_MS));
} else if (ret == -ENODEV) {
@@ -817,14 +828,17 @@ void ui_notify(enum ui_event event)
break;
}
/* Wake display on notifications — but skip for incoming messages
* when a phone is connected (phone handles its own notifications). */
/* Wake display on notifications.
* Repeater: never wake display on events — only user button wakes it.
* Companion: wake unless phone is connected (phone handles its own). */
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
#ifndef ZEPHCORE_REPEATER
if (!(is_msg_event && get_state()->ble_connected)) {
mc_display_on();
schedule_render();
}
#endif
#endif
}
void ui_set_msg_count(uint16_t count)
+41 -4
View File
@@ -20,8 +20,19 @@ LOG_MODULE_REGISTER(zephcore_repeater_main, CONFIG_ZEPHCORE_LORA_LOG_LEVEL);
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/sys/reboot.h>
#include "oled_power.h"
/* BLE controller assert handler — BT is compiled even for repeater (via zephcore_common.conf) */
#if IS_ENABLED(CONFIG_BT_CTLR_ASSERT_HANDLER)
extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line)
{
LOG_ERR("!!! BLE CONTROLLER ASSERT: %s:%u !!!", file ? file : "?", line);
k_sleep(K_MSEC(100));
sys_reboot(SYS_REBOOT_COLD);
}
#endif
/* USB CDC with 1200 baud touch detection (when not using auto-init) */
#if !IS_ENABLED(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT)
#include <ZephyrRepeaterUSB.h>
@@ -32,6 +43,9 @@ LOG_MODULE_REGISTER(zephcore_repeater_main, CONFIG_ZEPHCORE_LORA_LOG_LEVEL);
#include <adapters/clock/ZephyrRTCClock.h>
#include <ZephyrSensorManager.h>
/* UI subsystem (display, buttons, buzzer) */
#include "ui_task.h"
/* Radio + mesh includes (shared header selects LR1110 or SX126x) */
#include <mesh/RadioIncludes.h>
@@ -240,6 +254,12 @@ static void repeater_event_loop(void)
repeater_mesh_ptr->loop();
}
#endif
/* Update display clock on housekeeping tick (if display is on) */
if (events & MESH_EVENT_HOUSEKEEPING) {
ui_set_clock(rtc_clock.getCurrentTime());
ui_refresh_display();
}
}
}
@@ -279,8 +299,10 @@ int main(void)
gps_set_repeater_mode(true);
}
/* Put OLED display to sleep */
oled_sleep();
/* Initialize UI (display + buttons). Shows splash screen, then auto-
* transitions to STATUS page. Display sleeps after auto-off timeout;
* user button is the only wake source for repeater. */
ui_init();
/* Log environment sensor availability */
if (env_sensors_available()) {
@@ -337,16 +359,31 @@ int main(void)
/* Apply RX boost setting from prefs (overrides radio default if user changed it) */
lora_radio.setRxBoost(prefs->rx_boost != 0);
/* Feed initial UI state from loaded prefs */
ui_set_node_name(prefs->node_name);
ui_set_radio_params(
(uint32_t)(prefs->freq * 1000000.0f), /* MHz → Hz */
prefs->sf,
(uint16_t)(prefs->bw * 10.0f), /* kHz → 0.1 kHz */
prefs->cr,
prefs->tx_power_dbm,
lora_radio.getNoiseFloor());
/* Send initial zero-hop advertisement after boot */
LOG_INF("Sending initial advertisement...");
repeater_mesh.sendSelfAdvertisement(500, false); /* 500ms delay, zero-hop */
#endif
/* Initialize USB CDC for CLI with 1200 baud touch detection */
#if !IS_ENABLED(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT)
/* Initialize USB serial for CLI */
#if !IS_ENABLED(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT) && DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart)
zephcore_usbd_init();
#endif
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart)
usb_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
#else
/* No CDC ACM (e.g. ESP32 usb_serial) — use chosen console UART */
usb_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));
#endif
if (device_is_ready(usb_dev)) {
LOG_INF("USB CDC device ready: %s", usb_dev->name);
ring_buf_init(&usb_ring_buf, sizeof(usb_ring_buf_data), usb_ring_buf_data);