peripherals & power: GPS fixes, poweroff/pwrmgt, upstream SensorManager telemetry

- gps: servers honour gps_enabled at boot (was lost on reboot; zc.gps_set
  upgrades older prefs to on once); sleep lock only while acquiring;
  gps off works on boards without a power line (UART sleep); UI toggles
  and fixes handled on the main thread; configure only after a good init;
  observer parks its GPS; one interval clamp (clampGpsInterval); one
  nav-mode knob (room servers on Air530Z now stationary); GNSS UARTE from DT
- gps: split into ZephyrGPSManager / gps_power / gps_module_cfg
  (+gps_internal.h); dead driver branches, gps_get_diag, sats_* removed
- clock: ZephyrRTCClock writes the hardware RTC itself (clkreboot now
  resets it); MESH_EVENT_RTC_SAVE gone
- power: one power-off path (zephcore_power_off, ZephyrBoard::powerOff),
  poweroff/shutdown CLI, get pwrmgt.*, crash record kept across reboot,
  battery reading cached 10 s, fuel gauge on the current API
- telemetry: upstream SensorManager + CayenneLPP (rounded to nearest),
  one builder for all roles; per-sensor channels, MCU temp always,
  guest gating and inverse mask on servers; sensor list/get/set work;
  BME280/BMP280 also probed at 0x77
- CLI: gps in upstream's form, get gps detail, gps sync
This commit is contained in:
liquidraver
2026-09-25 17:56:34 +02:00
parent e6d6ecef80
commit d5bd805710
59 changed files with 3090 additions and 3077 deletions
+25 -35
View File
@@ -66,6 +66,7 @@ LOG_MODULE_REGISTER(zephcore_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#include <helpers/StatsFormatHelper.h>
#include <app/CompanionCLI.h>
#include <helpers/battery_curve.h>
#include <zephyr_poweroff.h>
#endif
/* Without this a BLE controller assert freezes the CPU at top IRQ priority
@@ -92,16 +93,10 @@ static void save_prefs_to_flash(void);
static void vcontact_battery_alert_check(void);
#endif
/* Epoch for a deferred hardware-RTC write. gps_fix_callback runs on the GNSS
* modem_chat thread, where several ms of blocking I2C would stall NMEA ingest,
* so the main thread does the write (MESH_EVENT_RTC_SAVE); posts coalesce to
* the latest time. */
static atomic_t pending_rtc_epoch = ATOMIC_INIT(0);
#define MESH_EVENT_BASE (MESH_EVENT_LORA_RX | MESH_EVENT_LORA_TX_DONE | \
MESH_EVENT_BLE_RX | MESH_EVENT_HOUSEKEEPING | MESH_EVENT_UI_ACTION | \
MESH_EVENT_GPS_ACTION | MESH_EVENT_TX_DRAIN | MESH_EVENT_PREFS_DIRTY | \
MESH_EVENT_RTC_SAVE | MESH_EVENT_CONTACT_ITER | MESH_EVENT_LINK)
MESH_EVENT_CONTACT_ITER | MESH_EVENT_LINK)
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK)
#define MESH_EVENT_ALL (MESH_EVENT_BASE | MESH_EVENT_JOYSTICK_LOOP)
#else
@@ -112,13 +107,6 @@ static atomic_t pending_rtc_epoch = ATOMIC_INIT(0);
static struct k_event mesh_events;
/* See pending_rtc_epoch. */
static void request_rtc_save(uint32_t epoch)
{
atomic_set(&pending_rtc_epoch, (atomic_val_t)epoch);
k_event_post(&mesh_events, MESH_EVENT_RTC_SAVE);
}
static void process_companion_rx(void); /* runs on MAIN thread (see link_on_rx) */
static void run_contact_iteration(void); /* runs on MAIN thread (see MESH_EVENT_CONTACT_ITER) */
static void housekeeping_timer_fn(struct k_timer *timer);
@@ -434,19 +422,6 @@ static void mesh_event_loop(void)
}
#endif
/* See pending_rtc_epoch. */
if (events & MESH_EVENT_RTC_SAVE) {
zephcore_rtc_save((uint32_t)atomic_get(&pending_rtc_epoch));
#ifdef ZEPHCORE_LORA
/* GPS set the clock: arm the time-sync drift envelope and
* activate a deferred v-contact. */
if (companion_mesh_ptr) {
companion_mesh_ptr->noteGPSTimeSync();
companion_mesh_ptr->vcontactClockSynced();
}
#endif
}
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK)
if (events & MESH_EVENT_JOYSTICK_LOOP) {
joystick_ui_task.loop();
@@ -562,7 +537,7 @@ static bool companion_transport_tx_idle(void)
static CompanionCLICallbacks companion_cli_cbs(data_store, companion_mesh, lora_radio, zephyr_board,
ms_clock, packet_mgr, companion_transport_tx_idle);
/* No region map and no client ACL: a companion has neither (nullptr both). */
static CommonCLI companion_cli(zephyr_board, rtc_clock, nullptr, nullptr,
static CommonCLI companion_cli(zephyr_board, rtc_clock, sensors, nullptr, nullptr,
&companion_mesh.prefs, &companion_cli_cbs);
/* CMD_SET_RADIO_PARAMS / CMD_SET_RADIO_TX_POWER (needs companion_cli, hence
@@ -754,7 +729,7 @@ static bool companion_shutdown_hook(int reason)
return true; /* deliver live — ask the UI for the grace delay */
}
data_store.saveShutdownReason((uint8_t)reason);
zephcore_shutdown_reason_save(ZC_SHUTDOWN_LOW_VOLTAGE);
return false; /* nobody listening — flash marker, power off now */
}
@@ -885,16 +860,24 @@ static void gps_event_callback(void)
k_event_post(&mesh_events, MESH_EVENT_GPS_ACTION);
}
/* A valid fix (GNSS modem_chat thread): set the clock and the node position. */
/* A validated fix (main thread, from gps_process_event()): set the clock and
* the node position. */
static void gps_fix_callback(double lat, double lon, int64_t utc_time)
{
if (utc_time > 0) {
LOG_INF("GPS fix: RTC sync time=%lld", utc_time);
rtc_clock.setCurrentTime((uint32_t)utc_time);
time_sync_report(TIME_SYNC_GPS);
request_rtc_save((uint32_t)utc_time);
#ifdef ZEPHCORE_LORA
/* Arm the time-sync drift envelope; activate a deferred v-contact. */
companion_mesh.noteGPSTimeSync();
companion_mesh.vcontactClockSynced();
#endif
}
sensors.node_lat = lat;
sensors.node_lon = lon;
#ifdef ZEPHCORE_LORA
/* RAM only, as upstream: GPS jitter would otherwise rewrite prefs on
* nearly every fix. It reaches flash with the next real settings save. */
@@ -985,8 +968,8 @@ int main(void)
/* A low-battery shutdown with no app connected left its reason in flash
* (see companion_shutdown_hook); the hardware cause cannot tell. */
{
uint8_t sdr = data_store.takeShutdownReason();
if (sdr == UI_SHUTDOWN_LOW_BATTERY) {
uint8_t sdr = zephcore_shutdown_reason();
if (sdr == ZC_SHUTDOWN_LOW_VOLTAGE) {
size_t l = strlen(boot_cause_msg);
snprintf(boot_cause_msg + l, sizeof(boot_cause_msg) - l,
"%sLast shutdown: low battery",
@@ -997,13 +980,16 @@ int main(void)
/* Before bt_enable(): a first boot may have to erase the BLE bond store. */
data_store.adoptVolume();
sensor_manager_init();
sensors.begin();
#ifdef ZEPHCORE_LORA
zephyr_board.captureBootVoltage();
#endif
/* Wall-clock time from a battery-backed RTC, if one answers on I2C. */
{
uint32_t rtc_epoch;
if (zephcore_rtc_restore(&rtc_epoch)) {
rtc_clock.setCurrentTime(rtc_epoch);
rtc_clock.seedCurrentTime(rtc_epoch);
}
}
@@ -1036,6 +1022,9 @@ int main(void)
companion_mesh.prefs.gps_interval = CONFIG_ZEPHCORE_GPS_POLL_INTERVAL_SEC; /* 0 = always on */
data_store.loadPrefs(companion_mesh.prefs);
/* Until the first fix, the position is the configured one. */
sensors.node_lat = companion_mesh.prefs.node_lat;
sensors.node_lon = companion_mesh.prefs.node_lon;
/* Saved BLE PIN (0 = Kconfig default) */
#if IS_ENABLED(CONFIG_BT)
@@ -1080,6 +1069,7 @@ int main(void)
}
companion_mesh.setBatteryCallback(get_battery_mv);
companion_mesh.setBoard(&zephyr_board);
ui_set_battery_provider(get_battery_mv);
ui_set_power_source_provider([]() { return zephyr_board.isExternalPowered(); });
ui_set_auto_shutdown_mv(companion_mesh.prefs.auto_shutdown_mv);
+2 -2
View File
@@ -23,8 +23,8 @@
#define MESH_EVENT_HOUSEKEEPING BIT(3) /* companion: periodic housekeeping */
#define MESH_EVENT_MAINTENANCE BIT(3) /* servers: a maintenance deadline came due */
#define MESH_EVENT_GPS_ACTION BIT(4) /* GPS state change (must run on the main thread) */
#define MESH_EVENT_GPS_ACTION BIT(4) /* GPS state change or fix (must run on the main thread) */
#define MESH_EVENT_TX_DRAIN BIT(5) /* outbound packet delay expired, run checkSend */
#define MESH_EVENT_RTC_SAVE BIT(6) /* hardware-RTC write requested off-main */
/* BIT(6) free: was MESH_EVENT_RTC_SAVE, until ZephyrRTCClock wrote the hardware RTC itself */
#define MESH_EVENT_ROLE_BASE 7 /* first role-only bit */
+31 -34
View File
@@ -57,6 +57,7 @@ extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line)
#include <Serial.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
#include <ZephyrSensorManager.h>
#include <zephyr_poweroff.h>
/* UI subsystem (display, buttons, buzzer) */
#include "ui_task.h"
@@ -98,7 +99,7 @@ static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(LED1_NODE, gpios);
/* Event bits: the shared ones are in mesh_events.h. */
#define MESH_EVENT_INIT_ADVERT BIT(MESH_EVENT_ROLE_BASE) /* Deferred boot advert — send on main thread */
#define MESH_EVENT_WAKE BIT(MESH_EVENT_ROLE_BASE + 1) /* Off-main state set; run loop() promptly */
#define MESH_EVENT_ALL (MESH_EVENT_LORA_RX | MESH_EVENT_LORA_TX_DONE | MESH_EVENT_CLI_RX | MESH_EVENT_MAINTENANCE | MESH_EVENT_GPS_ACTION | MESH_EVENT_TX_DRAIN | MESH_EVENT_RTC_SAVE | MESH_EVENT_INIT_ADVERT | MESH_EVENT_WAKE)
#define MESH_EVENT_ALL (MESH_EVENT_LORA_RX | MESH_EVENT_LORA_TX_DONE | MESH_EVENT_CLI_RX | MESH_EVENT_MAINTENANCE | MESH_EVENT_GPS_ACTION | MESH_EVENT_TX_DRAIN | MESH_EVENT_INIT_ADVERT | MESH_EVENT_WAKE)
/* Maintenance is deadline-driven, not periodic: after every pass the loop asks
* the mesh when its soonest pending deadline is (msUntilNextMaintenance) and
@@ -117,18 +118,6 @@ static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(LED1_NODE, gpios);
/* Event object for mesh loop */
static struct k_event mesh_events;
/* Pending epoch for a deferred zephcore_rtc_save() — gps_fix_callback runs on
* the GNSS modem_chat worker thread, where the RTC's blocking I2C transactions
* would stall NMEA ingest. Stash the latest epoch and let the main thread
* perform the write; concurrent posts coalesce into one save. */
static atomic_t pending_rtc_epoch = ATOMIC_INIT(0);
static void request_rtc_save(uint32_t epoch)
{
atomic_set(&pending_rtc_epoch, (atomic_val_t)epoch);
k_event_post(&mesh_events, MESH_EVENT_RTC_SAVE);
}
/* USB CDC state */
static const struct device *usb_dev;
static uint8_t usb_ring_buf_data[USB_RING_BUF_SIZE];
@@ -411,7 +400,8 @@ static void gps_event_callback(void)
static RepeaterDataStore data_store;
/* GPS fix callback - syncs RTC from GPS time.
/* GPS fix callback (main thread, from gps_process_event()) - syncs RTC from
* GPS time.
* Servers do NOT update prefs lat/lon from GPS — prefs coordinates are the
* user's manually-set position used for adverts. Precise GPS position is
* served only via telemetry requests (gps_get_last_known_position). */
@@ -420,19 +410,25 @@ static void gps_fix_callback(double lat, double lon, int64_t utc_time)
if (utc_time > 0) {
LOG_INF("GPS fix: RTC sync time=%lld", utc_time);
rtc_clock.setCurrentTime((uint32_t)utc_time);
/* Defer the hardware-RTC write to the main thread — blocking I2C
* here would stall NMEA ingest (gps_fix_callback runs in the
* GNSS modem_chat worker context). */
request_rtc_save((uint32_t)utc_time);
#ifdef ZEPHCORE_LORA
/* Arm the mesh time-sync drift envelope. */
if (server_role) {
server_role->note_gps_time_sync();
}
#endif
}
/* The live position: `gps setloc` and `gps advert share` read it. */
sensors.node_lat = lat;
sensors.node_lon = lon;
int lat_deg = (int)lat;
int lon_deg = (int)lon;
int lat_frac = (int)((lat - lat_deg) * 1000000);
int lon_frac = (int)((lon - lon_deg) * 1000000);
if (lat_frac < 0) lat_frac = -lat_frac;
if (lon_frac < 0) lon_frac = -lon_frac;
LOG_INF("GPS fix: lat=%d.%06d lon=%d.%06d (telemetry only)",
LOG_INF("GPS fix: lat=%d.%06d lon=%d.%06d",
lat_deg, lat_frac, lon_deg, lon_frac);
}
@@ -596,19 +592,6 @@ static void server_event_loop(void)
#endif /* ZEPHCORE_HAS_UI */
}
/* Off-main RTC write request (gps_fix_callback runs in modem_chat
* context — see request_rtc_save()). Perform the blocking I2C
* write here on the main thread instead. */
if (events & MESH_EVENT_RTC_SAVE) {
zephcore_rtc_save((uint32_t)atomic_get(&pending_rtc_epoch));
#ifdef ZEPHCORE_LORA
/* GPS just set the clock — arm the mesh time-sync drift envelope. */
if (server_role) {
server_role->note_gps_time_sync();
}
#endif
}
/* Re-arm for the soonest deadline this pass left behind. Done for
* every wake, not just maintenance ones: a CLI command, an inbound
* packet or a GPS fix can all create or clear a deadline. */
@@ -678,14 +661,25 @@ int server_main(const ServerRole &role)
}
/* Initialize sensor manager */
sensor_manager_init();
sensors.begin();
#ifdef ZEPHCORE_LORA
zephyr_board.captureBootVoltage();
#endif
/* Take the shutdown marker now, so it describes this boot only. */
{
uint8_t sdr = zephcore_shutdown_reason();
if (sdr != ZC_SHUTDOWN_NONE) {
LOG_INF("Last shutdown: %s", zephcore_shutdown_reason_str(sdr));
}
}
/* Restore wall-clock time from a battery-backed hardware RTC if present
* (shown tagged "L" until the next GPS/CLI sync; no-op if no RTC). */
{
uint32_t rtc_epoch;
if (zephcore_rtc_restore(&rtc_epoch)) {
rtc_clock.setCurrentTime(rtc_epoch);
rtc_clock.seedCurrentTime(rtc_epoch);
}
}
@@ -752,6 +746,9 @@ int server_main(const ServerRole &role)
/* LED gate and GPS duty from prefs. After loadPrefs() and ui_init(),
* before begin() -- see boot_prefs.h. */
apply_boot_prefs(role.prefs, true);
/* Until the first fix, the position is the configured one. */
sensors.node_lat = role.prefs->node_lat;
sensors.node_lon = role.prefs->node_lon;
/* Start mesh with data store - loads ACL, regions */
role.begin(&data_store);