From 533a2a7e4e58ab6b7942f4243c71af2cfce4d57e Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:52:00 +0200 Subject: [PATCH] first fix 5 min timeout for GPS to prevent battery drain --- zephcore/Kconfig | 13 ++++ zephcore/adapters/gps/ZephyrGPSManager.cpp | 83 ++++++++++++---------- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/zephcore/Kconfig b/zephcore/Kconfig index 3f7ee1d..2b667e3 100644 --- a/zephcore/Kconfig +++ b/zephcore/Kconfig @@ -587,6 +587,19 @@ config ZEPHCORE_GPS_FIX_TIMEOUT_SEC 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. + endmenu menu "WiFi OTA Update" diff --git a/zephcore/adapters/gps/ZephyrGPSManager.cpp b/zephcore/adapters/gps/ZephyrGPSManager.cpp index 87d637b..d13023e 100644 --- a/zephcore/adapters/gps/ZephyrGPSManager.cpp +++ b/zephcore/adapters/gps/ZephyrGPSManager.cpp @@ -88,6 +88,7 @@ enum gps_state { static enum gps_state gps_current_state = GPS_STATE_OFF; static uint8_t consecutive_good_fixes = 0; static bool first_fix_acquired = false; /* True after first 3-good-fix cycle since enable. Cleared on gps_enable(false) and at boot. */ +static bool first_acquire_used = false; /* True once the one-time long cold-start window has ended (fix or timeout). Cleared on gps_enable(false) and at boot. */ static bool gps_time_synced = false; /* True after GPS syncs RTC. Starts false at boot (RTC reset), * set true after 3 good fixes, cleared when GPS disabled. */ static int64_t last_fix_uptime_ms = 0; /* k_uptime when last validated fix was acquired */ @@ -98,8 +99,9 @@ static uint64_t standby_interval_ms = 0; /* How long standby lasts (for next-wak #define GPS_MIN_SATELLITES 4 /* Minimum satellites for valid fix */ /* Runtime-configurable intervals, initialized from Kconfig defaults */ -static uint32_t gps_acquire_timeout_ms = CONFIG_ZEPHCORE_GPS_FIX_TIMEOUT_SEC * 1000U; -static uint32_t gps_wake_interval_ms = CONFIG_ZEPHCORE_GPS_POLL_INTERVAL_SEC * 1000U; +static uint32_t gps_acquire_timeout_ms = CONFIG_ZEPHCORE_GPS_FIX_TIMEOUT_SEC * 1000U; +static uint32_t gps_first_fix_timeout_ms = CONFIG_ZEPHCORE_GPS_FIRST_FIX_TIMEOUT_SEC * 1000U; +static uint32_t gps_wake_interval_ms = CONFIG_ZEPHCORE_GPS_POLL_INTERVAL_SEC * 1000U; /* Repeater mode intervals - GPS only for time sync */ #define GPS_REPEATER_SYNC_INTERVAL_MS (48ULL * 60 * 60 * 1000) /* 48 hours */ @@ -959,6 +961,25 @@ static void gps_software_wake(void) } #endif /* HAS_GPS_UART && !HAS_GPS_POWER_CONTROL && !HAS_GPS_POWER_REGULATOR */ +/* Acquire-window timeout (ms) for the current phase. + * - Repeater: fixed 5-min time-sync window. + * - First acquisition after enable (cold start, no fix yet): a generous but + * bounded window so almanac download has time, without pinning the module + * on forever when there's no sky. Spent once (first_acquire_used set on the + * first standby), after which the node uses the normal duty cycle regardless + * of whether a fix was obtained. + * - All later windows: the normal (warm) acquire timeout. */ +static uint32_t gps_acquire_window_ms(void) +{ + if (gps_repeater_mode) { + return GPS_REPEATER_SYNC_TIMEOUT_MS; + } + if (!first_fix_acquired && !first_acquire_used) { + return gps_first_fix_timeout_ms; + } + return gps_acquire_timeout_ms; +} + /* Go to standby and schedule next wake. * GPIO power control only — keep VRTC for warm start on T1000-E, * FORCE_ON pin LOW for L76K hardware standby. */ @@ -974,6 +995,9 @@ static void gps_go_to_standby(void) } gps_current_state = GPS_STATE_STANDBY; consecutive_good_fixes = 0; + /* The one-time long cold-start window (if any) is now spent — later + * wakes use the normal (warm) acquire timeout via gps_acquire_window_ms(). */ + first_acquire_used = true; /* Record standby timing for UI (next-wake calculation) */ standby_start_ms = k_uptime_get(); @@ -1023,16 +1047,11 @@ static void gps_start_acquiring(void) gps_software_wake(); #endif - /* Start timeout timer */ - if (gps_repeater_mode) { - /* Repeater mode: always use 5 min timeout for time sync */ - k_work_schedule(&gps_timeout_work, K_MSEC(GPS_REPEATER_SYNC_TIMEOUT_MS)); - } else if (first_fix_acquired) { - /* Companion mode: use short timeout after first fix acquired */ - k_work_schedule(&gps_timeout_work, K_MSEC(gps_acquire_timeout_ms)); - } else { - LOG_INF("GPS: First fix mode - no timeout"); - } + /* Start timeout timer — every window is now bounded (the first + * cold-start window is just longer; see gps_acquire_window_ms). */ + uint32_t timeout_ms = gps_acquire_window_ms(); + LOG_INF("GPS: Acquire window %u s", timeout_ms / 1000U); + k_work_schedule(&gps_timeout_work, K_MSEC(timeout_ms)); } /* Work handler: wake GPS for fix. @@ -1396,12 +1415,10 @@ void gps_enable(bool enable) * ~300ms to boot after GPIO power restore and calling it * immediately deadlocks the main thread. */ - /* No timeout for first fix after enable - wait as long as needed */ - if (first_fix_acquired) { - k_work_schedule(&gps_timeout_work, K_MSEC(gps_acquire_timeout_ms)); - } else { - LOG_INF("GPS: First fix mode - no timeout until first successful fix"); - } + /* Bounded first-acquisition window, then the normal duty cycle */ + uint32_t timeout_ms = gps_acquire_window_ms(); + LOG_INF("GPS: Acquire window %u s", timeout_ms / 1000U); + k_work_schedule(&gps_timeout_work, K_MSEC(timeout_ms)); } else { LOG_INF("GPS disabled - canceling timers and powering off"); @@ -1420,11 +1437,11 @@ void gps_enable(bool enable) gps_current_state = GPS_STATE_OFF; consecutive_good_fixes = 0; - /* Clear first-fix flag so the next enable gets the "no timeout" - * grace period again — in marginal signal, a 30–120s timeout may - * never be enough, and the user explicitly toggled GPS expecting - * it to try hard for a fix. */ + /* Clear first-fix flags so the next enable gets a fresh long + * first-acquisition window again — the user explicitly toggled GPS + * expecting it to try hard for a fix. */ first_fix_acquired = false; + first_acquire_used = false; /* Clear time sync flag - time will drift, allow phone sync again */ gps_time_synced = false; @@ -1574,22 +1591,12 @@ void gps_request_fresh_fix(void) * telemetry request that arrives 25s into a 30s acquire window * only has 5s left, which in marginal signal usually means the * chip goes to standby before producing a fix the requester - * could use. Repeater mode uses its own 5min timeout; companion - * mode pre-first-fix has no timeout (nothing to reschedule). */ - uint32_t timeout_ms = 0; - if (gps_repeater_mode) { - timeout_ms = GPS_REPEATER_SYNC_TIMEOUT_MS; - } else if (first_fix_acquired) { - timeout_ms = gps_acquire_timeout_ms; - } - if (timeout_ms > 0) { - LOG_INF("GPS: Fresh fix requested, extending acquire " - "timeout by %u ms", timeout_ms); - k_work_reschedule(&gps_timeout_work, K_MSEC(timeout_ms)); - } else { - LOG_DBG("GPS: Fresh fix requested, already acquiring " - "(no timeout active)"); - } + * could use. Every phase now has a bounded window + * (gps_acquire_window_ms), so always reschedule from now. */ + uint32_t timeout_ms = gps_acquire_window_ms(); + LOG_INF("GPS: Fresh fix requested, extending acquire timeout to %u s", + timeout_ms / 1000U); + k_work_reschedule(&gps_timeout_work, K_MSEC(timeout_ms)); } #endif }