vanilla dev sync

This commit is contained in:
liquidraver committed 2026-09-27 16:33:58 +02:00
1 parent 7662decca5
commit 2e0f765036
12 files changed
+301 -13

No files matched your search

+34
View File
@@ -1016,6 +1016,40 @@ config ZEPHCORE_GPS_SAT_DIAG
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"
+10
View File
@@ -641,4 +641,14 @@ bool ZephyrBoard::isExternalPowered()
#endif
}
bool ZephyrBoard::hasUsbPowerDetect() const
{
return IS_ENABLED(CONFIG_SOC_SERIES_NRF52);
}
bool ZephyrBoard::isUsbPowered()
{
return hasUsbPowerDetect() && isExternalPowered();
}
} /* namespace mesh */
+4
View File
@@ -33,6 +33,10 @@ public:
void clearBootloaderMagic(); /* Clear stale GPREGRET values at startup */
uint8_t getStartupReason() const override;
bool isExternalPowered() override; /* nRF52: VBUS present (USB/charger); else false */
/* `power`: nRF52's VBUS detect is a USB-power detect; other platforms
* keep upstream's "n/a". No board reports a charger line yet. */
bool hasUsbPowerDetect() const override;
bool isUsbPowered() override;
/* get pwrmgt.*: upstream's power-management getters. */
uint16_t getBootVoltage() override { return _boot_mv; }
+113 -9
View File
@@ -56,6 +56,7 @@ static gps_event_callback_t gps_event_cb = NULL;
#define GPS_ACTION_TIMEOUT BIT(1) /* Acquisition timeout → go to standby */
#define GPS_ACTION_FIX_DONE BIT(2) /* Got enough good fixes → go to standby */
#define GPS_ACTION_FIX BIT(3) /* A validated fix for gps_fix_cb (fix_pending) */
#define GPS_ACTION_REAPPLY BIT(4) /* Next module re-send step (gps_reapply_step) */
static atomic_t pending_gps_actions;
/* GPS Power Management State Machine */
@@ -122,6 +123,42 @@ static void gps_start_acquiring(void);
static K_WORK_DELAYABLE_DEFINE(gps_wake_work, gps_wake_work_fn);
static K_WORK_DELAYABLE_DEFINE(gps_timeout_work, gps_timeout_work_fn);
#if HAS_GPS_UART && defined(CONFIG_ZEPHCORE_GPS_REAPPLY)
/* CONFIG_ZEPHCORE_GPS_REAPPLY: after each power-on, wait for the module
* to boot, then hand the main thread one sentence per step. */
#define GPS_REAPPLY_BOOT_MS 1000 /* module boot is ~300 ms; margin for slow rails */
static uint8_t gps_reapply_step;
static uint32_t gps_reapply_count; /* completed re-sends, `gps diag` ra: */
static void gps_reapply_work_fn(struct k_work *work)
{
ARG_UNUSED(work);
if (gps_current_state != GPS_STATE_ACQUIRING) {
return;
}
atomic_or(&pending_gps_actions, GPS_ACTION_REAPPLY);
if (gps_event_cb) {
gps_event_cb();
}
}
static K_WORK_DELAYABLE_DEFINE(gps_reapply_work, gps_reapply_work_fn);
static void gps_reapply_start(void)
{
gps_reapply_step = 0;
k_work_reschedule(&gps_reapply_work, K_MSEC(GPS_REAPPLY_BOOT_MS));
}
static void gps_reapply_cancel(void)
{
k_work_cancel_delayable(&gps_reapply_work);
atomic_and(&pending_gps_actions, ~GPS_ACTION_REAPPLY);
}
#else
static inline void gps_reapply_start(void) { }
static inline void gps_reapply_cancel(void) { }
#endif
#else
static gps_enable_callback_t gps_enable_cb = NULL;
#endif
@@ -490,6 +527,7 @@ static void gps_go_to_standby(void)
* VBACKUP charger keeps the receiver's V_BCKP domain alive, so ephemeris/
* RTC survive the cut and re-acquisition is a warm/hot start, not cold.
* Other non-GPIO boards: software sleep via UART commands (PMTK + UBX). */
gps_reapply_cancel();
gps_module_power(false);
/* Module is off/asleep — release the UART until the next wake
@@ -497,10 +535,10 @@ static void gps_go_to_standby(void)
gps_uart_set_power(false);
gps_hold_sleep_lock(false);
/* NOTE: gnss_configured stays true — L76K retains PCAS settings in
* flash across power cycles. Re-running gps_module_configure() after GPIO
* wake would call modem_chat_run_script() before the chip has booted,
* risking a deadlock (modem_chat blocks on system work queue). */
/* NOTE: gnss_configured stays true, gps_module_configure() is boot-only:
* after a power restore modem_chat_run_script() would reach a chip that
* has not booted yet (deadlock risk, see gps_wake_work_fn). What a power
* cut loses on CASIC parts is re-sent by gps_reapply_start() instead. */
/* Schedule next wake (event-driven, no polling!) */
k_work_schedule(&gps_wake_work, K_MSEC(wake_interval));
@@ -509,11 +547,12 @@ static void gps_go_to_standby(void)
/* Wake GPS and start acquiring.
* GPIO boards: hardware power-on.
* Non-GPIO boards: UART wake byte (wakes L76K from standby, ZOE-M8Q from backup).
* Does NOT call gps_module_configure() — constellation/fix-rate settings persist
* in L76K flash across power cycles. Calling modem_chat_run_script() here
* Does NOT call gps_module_configure(). Calling modem_chat_run_script() here
* would deadlock: the chip needs ~300ms to boot after GPIO power restore,
* but modem_chat blocks the calling thread waiting for the system work
* queue which may be processing stale UART data. */
* queue which may be processing stale UART data. CASIC settings are NOT
* persisted (no PCAS00, see the air530z driver), so they are re-sent blind
* a second after power-on (CONFIG_ZEPHCORE_GPS_REAPPLY). */
static void gps_start_acquiring(void)
{
LOG_INF("GPS: Waking for %s", gps_repeater_mode ? "time sync" : "position fix");
@@ -526,6 +565,7 @@ static void gps_start_acquiring(void)
* goes out, so the first NMEA sentences aren't lost. */
gps_uart_set_power(true);
gps_module_power(true);
gps_reapply_start();
/* Schedule the standby timeout — unless always-on (interval 0), where the
* GPS stays in continuous acquisition and never sleeps. Every duty window
@@ -824,8 +864,8 @@ void gps_enable(bool enable)
* lock, and the first (longer) acquire window unless always-on. */
gps_start_acquiring();
/* gps_module_configure() runs once at boot (see gps_manager_init path).
* L76K retains PCAS settings in flash across power cycles.
/* gps_module_configure() runs once at boot (see gps_manager_init path);
* gps_start_acquiring() re-sends the CASIC settings a power cut loses.
* Do NOT call modem_chat_run_script() here — the chip needs
* ~300ms to boot after GPIO power restore and calling it
* immediately deadlocks the main thread. */
@@ -836,6 +876,7 @@ void gps_enable(bool enable)
/* Cancel any pending work */
k_work_cancel_delayable(&gps_wake_work);
k_work_cancel_delayable(&gps_timeout_work);
gps_reapply_cancel();
/* Power off GPS — warm standby if VRTC available (Arduino sleep_gps),
* full power off otherwise. Warm standby preserves ephemeris/RTC
@@ -1046,6 +1087,56 @@ void gps_get_state_info(struct gps_state_info *info)
#endif
}
#if HAS_GNSS
/* Parsed-sentence count from the GNSS driver (see gps_module_cfg.cpp). */
extern "C" uint32_t zephcore_gnss_rx_count(void) __attribute__((weak));
#endif
void gps_format_diagnostics(char *out, size_t out_size)
{
if (out_size == 0) {
return;
}
#if HAS_GNSS
/* Upstream's keys, where this port has the same quantity: en = module
* powered and searching (standby is off/asleep), ok = checksum-valid
* sentences (the driver parses nothing else), fa = age of the last
* validated fix. */
k_mutex_lock(&gps_mutex, K_FOREVER);
bool fix = current_pos.valid;
unsigned sats = current_pos.satellites;
k_mutex_unlock(&gps_mutex);
char fix_age[11];
if (last_fix_uptime_ms > 0) {
snprintf(fix_age, sizeof(fix_age), "%lu",
(unsigned long)(uint32_t)(k_uptime_get() - last_fix_uptime_ms));
} else {
snprintf(fix_age, sizeof(fix_age), "never");
}
char ok[16] = "";
if (zephcore_gnss_rx_count != NULL) {
snprintf(ok, sizeof(ok), " ok:%lu", (unsigned long)zephcore_gnss_rx_count());
}
/* ZephCore addition: completed CASIC re-sends (CONFIG_ZEPHCORE_GPS_REAPPLY),
* one per power-on that stayed up past GPS_REAPPLY_BOOT_MS. */
char ra[16] = "";
#if HAS_GPS_UART && defined(CONFIG_ZEPHCORE_GPS_REAPPLY)
snprintf(ra, sizeof(ra), " ra:%lu", (unsigned long)gps_reapply_count);
#endif
snprintf(out, out_size, "en:%u%s sat:%u fix:%u fa:%s bc:%lu sc:%lu%s",
gps_current_state == GPS_STATE_ACQUIRING ? 1U : 0U,
ok, sats, fix ? 1U : 0U, fix_age,
(unsigned long)gps_power_on_count,
(unsigned long)gps_power_off_count, ra);
#else
snprintf(out, out_size, "en:0 sat:0 fix:0");
#endif
}
/* Process pending GPS state transitions — called from main thread.
* Work handlers on the system work queue set flags + signal the main
* thread via gps_event_cb(). The main thread then calls this function,
@@ -1089,5 +1180,18 @@ void gps_process_event(void)
gps_go_to_standby();
}
}
#if HAS_GPS_UART && defined(CONFIG_ZEPHCORE_GPS_REAPPLY)
/* After the transitions: a standby above has already cancelled it. */
if ((actions & GPS_ACTION_REAPPLY) && gps_current_state == GPS_STATE_ACQUIRING) {
uint32_t gap_ms = gps_module_reapply_step(gps_reapply_step++);
if (gap_ms > 0) {
k_work_reschedule(&gps_reapply_work, K_MSEC(gap_ms));
} else {
gps_reapply_count++;
}
}
#endif
#endif
}
+6
View File
@@ -53,6 +53,12 @@ void gps_set_diag(bool on);
* succeeds; reports "never run" if configuration has not happened yet. */
void gps_get_diag_report(char *buf, size_t len);
/* `gps diag` (upstream's LocationProvider::formatDiagnostics): module on,
* NMEA sentences parsed, satellites, fix, fix age in ms, module power-on /
* power-off counts. Upstream's UART byte and bad-checksum counters have no
* source behind the GNSS API and are left out. */
void gps_format_diagnostics(char *out, size_t out_size);
/* GPS enable callback - called when GPS is enabled/disabled (for power management) */
typedef void (*gps_enable_callback_t)(bool enabled);
void gps_set_enable_callback(gps_enable_callback_t cb);
+9
View File
@@ -84,6 +84,9 @@ void gps_sat_tally(uint8_t out[5]);
void gps_power_control(bool on, bool keep_vrtc = false);
/* The power line or rail if the board has one, else the UART sleep commands. */
void gps_module_power(bool on, bool keep_vrtc = true);
/* gps_module_power() calls, for `gps diag` (upstream's bc/sc). */
extern uint32_t gps_power_on_count;
extern uint32_t gps_power_off_count;
/* Suspend/resume the GNSS UART (nRF UARTE: releases HFCLK). */
void gps_uart_set_power(bool on);
#if HAS_GPS_POWER_CONTROL
@@ -95,6 +98,12 @@ void gps_dump_gpio_states(void);
void gps_module_configure(void);
/* Re-run the UART configuration when `set gps diag 1` asked for it. */
void gps_diag_maybe_reconfigure(void);
#if HAS_GPS_UART && defined(CONFIG_ZEPHCORE_GPS_REAPPLY)
/* CONFIG_ZEPHCORE_GPS_REAPPLY: write the step-th re-sent setting blind
* (see the list in gps_module_cfg.cpp). Returns the ms to wait before the
* next step, 0 when that was the last. */
uint32_t gps_module_reapply_step(uint8_t step);
#endif
void gps_uart_dump_hw_state(void);
#if HAS_GPS_UART
extern const struct device *const gps_uart_dev;
+62 -3
View File
@@ -29,9 +29,9 @@ static bool gps_diag_on = false;
* 3. The script completion callback competes with NMEA processing
*
* Safe to call at boot because the driver init already ran and the chip
* is powered and outputting NMEA. After power cycles, the L76K retains
* constellation + fix rate settings in internal flash (PCAS commands
* persist). So we only need to configure once. */
* is powered and outputting NMEA. PCAS settings are NOT persisted (nothing
* sends PCAS00), so what a power cut loses is re-sent blind after every
* power-on instead (gps_module_reapply_step, CONFIG_ZEPHCORE_GPS_REAPPLY). */
static bool gnss_configured = false;
/* ========== Configuration Diagnostics ==========
@@ -567,6 +567,65 @@ void gps_module_configure(void)
gnss_configured = true;
}
#if HAS_GPS_UART && defined(CONFIG_ZEPHCORE_GPS_REAPPLY)
/* The boot-time settings a power cut loses (see the Kconfig help). One
* sentence per step, written with gps_uart_send() directly and no settle
* sleep: the caller waits each entry's gap with a work item, so the main
* thread only spends the UART time.
*
* Kept boot-only on purpose:
* - UBX: already saved to BBR + flash by CFG-CFG at boot, and repeating it
* would rewrite the module's flash on every wake;
* - PCAS02 (1 Hz is the default) and PCAS06 (a query, no state).
*
* Both constellation commands restart their chip's navigation engine, and a
* sentence written into a restarting engine is lost, so each is followed by
* the boot sequence's restart settle: PMTK353 first, PCAS04 last. Each only
* restarts its own family's chip (the other ignores it). On a MediaTek part
* that merely slept this costs one engine restart per wake, accepted
* (architect, 2026-09-27): one that lost power is otherwise left searching
* GPS-only, which is slower than a restart with every constellation. The
* PMTK sentences go only where the module is unknown (the generic-NMEA
* path); an API-driver board's module is known CASIC. */
struct reapply_cmd {
const char *sentence;
uint16_t gap_ms; /* wait before the next one */
};
static const struct reapply_cmd reapply_casic[] = {
{ pcas_sentences, GPS_CFG_SETTLE_MS },
#if CONFIG_ZEPHCORE_GPS_NAV_MODE >= 0
/* The driver's GNSS_LUATOS_AIR530Z_NAV_MODE mirrors this value, so
* this matches what either path set at boot. */
{ pcas_nav_mode, GPS_CFG_SETTLE_MS },
#endif
{ pcas_constellations, 0 },
};
static const struct reapply_cmd reapply_generic[] = {
{ pmtk_constellations, GPS_CFG_RESTART_MS },
{ pmtk_easy, GPS_CFG_SETTLE_MS },
{ pmtk_aic, GPS_CFG_SETTLE_MS },
{ pcas_sentences, GPS_CFG_SETTLE_MS },
#if CONFIG_ZEPHCORE_GPS_NAV_MODE >= 0
{ pcas_nav_mode, GPS_CFG_SETTLE_MS },
#endif
{ pcas_constellations, 0 },
};
uint32_t gps_module_reapply_step(uint8_t step)
{
bool generic = (gps_cfg_diag.path == GPS_CFG_UART);
const struct reapply_cmd *list = generic ? reapply_generic : reapply_casic;
size_t n = generic ? ARRAY_SIZE(reapply_generic) : ARRAY_SIZE(reapply_casic);
if (step >= n) {
return 0;
}
gps_uart_send((const uint8_t *)list[step].sentence, strlen(list[step].sentence));
return (step + 1 < n) ? list[step].gap_ms : 0;
}
#endif
/* ========== GPS UART Diagnostics ========== */
/**
+8
View File
@@ -532,8 +532,16 @@ static void gps_software_wake(void)
/* Module on or off with whatever this board has: the power GPIO or PMU rail,
* else the UART sleep commands. keep_vrtc: T1000-E warm standby (off only). */
uint32_t gps_power_on_count;
uint32_t gps_power_off_count;
void gps_module_power(bool on, bool keep_vrtc)
{
if (on) {
gps_power_on_count++;
} else {
gps_power_off_count++;
}
#if HAS_GPS_POWER_CONTROL || HAS_GPS_POWER_REGULATOR
gps_power_control(on, keep_vrtc);
#elif HAS_GPS_UART
+14
View File
@@ -1453,6 +1453,20 @@ void CompanionMesh::onContactResponse(const ContactInfo &contact, const uint8_t
};
ui_notify_joystick_event(UI_JOYSTICK_REQ_RESPONSE, contact.id.pub_key, &rr);
}
/* Arduino b204f9a1: let the app know about unknown/unhandled responses
* (eg. push telemetry from a sensor subscription): tag + payload as-is. */
{
uint8_t rsp[MAX_FRAME_SIZE];
int i = 0;
rsp[i++] = PUSH_CODE_BINARY_RESPONSE;
rsp[i++] = 0; // reserved
int data_len = len;
if (data_len > (int)sizeof(rsp) - i) data_len = sizeof(rsp) - i;
memcpy(&rsp[i], data, data_len);
i += data_len;
sendPush(rsp[0], &rsp[1], i - 1);
}
}
/* Raw packet logging - sends all RX packets to app for "heard X repeats" etc */
+28
View File
@@ -550,6 +550,19 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
snprintf(reply, CLI_REPLY_SIZE, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate());
} else if (memcmp(command, "board", 5) == 0) {
snprintf(reply, CLI_REPLY_SIZE, "%s", _board->getManufacturerName());
} else if (strcmp(command, "power") == 0) {
const char* usb_state = _board->hasUsbPowerDetect()
? (_board->isUsbPowered() ? "yes" : "no")
: "n/a";
const char* solar_state = _board->hasSolarChargerDetect()
? (_board->isSolarChargerActive() ? "yes" : "no")
: "n/a";
snprintf(reply, 160, "batt:%u mV usb:%s solar_chg:%s ext:%s charger:%s",
(unsigned)_board->getBattMilliVolts(),
usb_state,
solar_state,
_board->isExternalPowered() ? "yes" : "no",
_board->isChargerActive() ? "yes" : "no");
} else if (memcmp(command, "sensor get ", 11) == 0) {
const char* key = command + 11;
const char* val = _sensors->getSettingByKey(key);
@@ -621,6 +634,21 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
gps_request_fresh_fix();
strcpy(reply, "ok");
}
} else if (strcmp(command, "gps diag") == 0) {
/* ZEPHCORE: upstream asks the LocationProvider; here the GPS
* manager formats the fields it has (see gps_format_diagnostics). */
if (_sensors->isGPSDetected()) {
const char* setting = _sensors->getSettingByKey("gps");
bool requested = setting != NULL && strcmp(setting, "1") == 0;
int prefix_len = snprintf(reply, 160, "req:%u ", requested ? 1U : 0U);
if (prefix_len < 0 || prefix_len >= 160) {
reply[159] = 0;
} else {
gps_format_diagnostics(reply + prefix_len, 160 - prefix_len);
}
} else {
strcpy(reply, "gps provider not found");
}
} else if (memcmp(command, "gps setloc", 10) == 0) {
_prefs->node_lat = _sensors->node_lat;
_prefs->node_lon = _sensors->node_lon;
+10
View File
@@ -81,6 +81,16 @@ public:
// ZEPHCORE: no isPwrMgtInitialised() / getWakeLpcompSupported() -- upstream's
// nRF52 Arduino power manager; Zephyr PM covers this.
virtual bool isExternalPowered() { return false; }
// True while a board can identify an active battery-charging source.
virtual bool isChargerActive() { return false; }
// Optional, source-specific power detection. Boards that can distinguish a USB
// supply from a solar charger override these; defaults keep every other board
// unaffected (the generic CLI prints "n/a" when a capability is absent).
virtual bool hasUsbPowerDetect() const { return false; }
virtual bool isUsbPowered() { return false; }
virtual bool hasSolarChargerDetect() const { return false; }
virtual bool isSolarChargerActive() { return false; }
virtual uint16_t getBootVoltage() { return 0; }
virtual uint32_t getResetReason() const { return 0; }
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }