From 3af781ee8799cc6bc47ea761f6cb522fa1719d10 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 2 Jul 2026 09:20:10 +0200 Subject: [PATCH] get rid of flash writes initiated by ZephyrGPSManager --- zephcore/adapters/gps/ZephyrGPSManager.cpp | 100 ++++----------------- zephcore/src/main_companion.cpp | 28 +++--- 2 files changed, 28 insertions(+), 100 deletions(-) diff --git a/zephcore/adapters/gps/ZephyrGPSManager.cpp b/zephcore/adapters/gps/ZephyrGPSManager.cpp index eff38e4..b0d473d 100644 --- a/zephcore/adapters/gps/ZephyrGPSManager.cpp +++ b/zephcore/adapters/gps/ZephyrGPSManager.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #if defined(CONFIG_SOC_NRF52840) #include @@ -111,9 +110,9 @@ static inline bool gps_duty_cycling(void) return gps_wake_interval_ms != 0; } -/* k_uptime of the last always-on position/clock refresh — rate-limits the - * flash write + RTC sync to gps_acquire_timeout_ms while streaming (see - * gnss_data_cb), so continuous operation doesn't hammer flash. */ +/* k_uptime of the last always-on fix-callback invocation — rate-limits the + * RTC sync / node-position update to gps_acquire_timeout_ms while streaming + * (see gnss_data_cb), so 1Hz fixes don't fire the callback continuously. */ static int64_t last_promote_ms = 0; /* Repeater acquire window — GPS only for time sync. The standby interval is @@ -133,59 +132,6 @@ 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); -/* ========== Last-known position persistence ========== */ -#define GPS_POS_FILE "/lfs/gps_pos" - -/* On-disk format: lat(8) + lon(8) + alt(4) = 20 bytes */ -struct gps_pos_record { - int64_t latitude_ndeg; - int64_t longitude_ndeg; - int32_t altitude_mm; -}; - -static void gps_save_position(const struct gps_position *pos) -{ - struct fs_file_t file; - struct gps_pos_record rec = { - .latitude_ndeg = pos->latitude_ndeg, - .longitude_ndeg = pos->longitude_ndeg, - .altitude_mm = pos->altitude_mm, - }; - - fs_file_t_init(&file); - if (fs_open(&file, GPS_POS_FILE, FS_O_CREATE | FS_O_WRITE) == 0) { - fs_write(&file, &rec, sizeof(rec)); - fs_close(&file); - } -} - -static bool gps_load_position(void) -{ - struct fs_file_t file; - struct gps_pos_record rec; - - fs_file_t_init(&file); - if (fs_open(&file, GPS_POS_FILE, FS_O_READ) < 0) { - return false; - } - ssize_t n = fs_read(&file, &rec, sizeof(rec)); - fs_close(&file); - - if (n != sizeof(rec)) { - return false; - } - - current_pos.latitude_ndeg = rec.latitude_ndeg; - current_pos.longitude_ndeg = rec.longitude_ndeg; - current_pos.altitude_mm = rec.altitude_mm; - current_pos.valid = true; - current_pos.satellites = 0; - current_pos.timestamp_ms = 0; /* unknown — loaded from flash */ - LOG_INF("GPS: Restored last position from flash lat=%lld lon=%lld", - rec.latitude_ndeg / 1000000, rec.longitude_ndeg / 1000000); - return true; -} - #else static gps_enable_callback_t gps_enable_cb = NULL; #endif @@ -301,26 +247,6 @@ static void gnss_data_cb(const struct device *dev, const struct gnss_data *data) gps_time_synced = true; last_fix_uptime_ms = k_uptime_get(); - /* Persist position to flash at most once per - * gps_acquire_timeout_ms (default 120s), always on the - * first-ever fix so a fresh device saves an initial - * position right away. This throttle applies uniformly - * to duty-cycled AND always-on mode: without it, a short - * `gps duty` interval (e.g. 10s) would write flash on - * every wake indefinitely — worse wear than always-on's - * throttled rate. The RTC sync / fix callback below still - * fires on every successful duty-cycle wake regardless - * (that's cheap, RAM + a hardware RTC write, not flash); - * only the flash persistence is throttled. */ - bool promote = first_ever || - (k_uptime_get() - last_promote_ms >= - (int64_t)gps_acquire_timeout_ms); - if (promote) { - last_promote_ms = k_uptime_get(); - /* Persist position to flash for reboot survival */ - gps_save_position(¤t_pos); - } - if (duty) { /* Cancel timeout */ k_work_cancel_delayable(&gps_timeout_work); @@ -345,11 +271,18 @@ static void gnss_data_cb(const struct device *dev, const struct gnss_data *data) return; } - /* Always-on: keep streaming (no standby). Reset the gate so - * we don't re-promote every fix; refresh persisted position - * + RTC on the cadence captured by `promote` above. */ + /* Always-on: keep streaming (no standby). Throttle the fix + * callback (RTC sync + node-position update in main) to + * once per gps_acquire_timeout_ms — at 1Hz fixes it would + * otherwise fire constantly. Always fire on the first-ever + * fix so the clock syncs right away. current_pos (the + * telemetry source) is updated on every fix above. */ consecutive_good_fixes = 0; + bool promote = first_ever || + (k_uptime_get() - last_promote_ms >= + (int64_t)gps_acquire_timeout_ms); if (promote && gps_fix_cb) { + last_promote_ms = k_uptime_get(); double lat = (double)data->nav_data.latitude / 1000000000.0; double lon = (double)data->nav_data.longitude / 1000000000.0; k_mutex_unlock(&gps_mutex); @@ -1363,9 +1296,6 @@ int gps_manager_init(void) #if HAS_GNSS gnss_init(); - /* Restore last known position from flash (survives reboot) */ - gps_load_position(); - /* Configure constellations + fix rate NOW while chip is powered * and the modem pipe is open (driver init already ran). * This is the ONLY safe place to call modem_chat_run_script() — @@ -1501,8 +1431,8 @@ void gps_enable(bool enable) * report a live fix (e.g. joystick UI showing "3D FIX") off old * data before any new NMEA sentence arrives. lat/lon/valid are * deliberately left alone — telemetry/UI "last known position" - * reads (gps_get_position/gps_load_position) intentionally survive - * an on/off toggle; only the live fix-quality indicator resets. */ + * reads (gps_get_position) intentionally survive an on/off + * toggle; only the live fix-quality indicator resets. */ k_mutex_lock(&gps_mutex, K_FOREVER); current_pos.satellites = 0; k_mutex_unlock(&gps_mutex); diff --git a/zephcore/src/main_companion.cpp b/zephcore/src/main_companion.cpp index 9d3e546..73ab951 100644 --- a/zephcore/src/main_companion.cpp +++ b/zephcore/src/main_companion.cpp @@ -531,11 +531,11 @@ static void mesh_event_loop(void) ui_auto_shutdown_check(); } - /* Off-main pref mutators (e.g. gps_fix_callback in modem_chat - * context) post this bit and update _prefs in memory; we flush - * to flash here so the synchronous LittleFS write doesn't block - * the originating thread. Multiple posts coalesce into one - * write of the latest _prefs values — desired behaviour. */ + /* Off-main pref mutators (e.g. the autoshutdown CLI handler on + * the sysworkq USB RX path) post this bit and update _prefs in + * memory; we flush to flash here so the synchronous LittleFS + * write doesn't block the originating thread. Multiple posts + * coalesce into one write of the latest _prefs values. */ if ((events & MESH_EVENT_PREFS_DIRTY) && companion_mesh_ptr) { save_prefs_to_flash(); } @@ -758,8 +758,8 @@ static bool handle_autoshutdown_cli(const char *line, char *reply) companion_mesh.prefs.auto_shutdown_mv = (uint16_t)v; /* Defer the flash write to the main thread (this runs on sysworkq via * the USB RX work handler). A synchronous savePrefs here could race a - * concurrent main-thread save (e.g. GPS-fix MESH_EVENT_PREFS_DIRTY) on - * the same prefs .tmp file. Posting the event coalesces both onto main. */ + * concurrent main-thread save (e.g. a companion-app command handler) + * on the same prefs .tmp file. Posting the event coalesces onto main. */ k_event_post(&mesh_events, MESH_EVENT_PREFS_DIRTY); ui_set_auto_shutdown_mv((uint16_t)v); if (v == 0) { @@ -847,7 +847,12 @@ static void gps_fix_callback(double lat, double lon, int64_t utc_time) } #ifdef ZEPHCORE_LORA - /* Update node position for mesh advertising */ + /* Update node position for mesh advertising — RAM only, no flash + * write. Matches upstream Arduino (sensors.node_lat/lon): the + * position reaches flash only piggybacked on the next savePrefs() + * triggered by an actual settings change. Persisting from here + * would rewrite the full prefs blob on nearly every promoted fix, + * since GPS jitter defeats the exact-double comparison below. */ if (lat != companion_mesh.prefs.node_lat || lon != companion_mesh.prefs.node_lon) { companion_mesh.prefs.node_lat = lat; companion_mesh.prefs.node_lon = lon; @@ -860,13 +865,6 @@ static void gps_fix_callback(double lat, double lon, int64_t utc_time) if (lon_frac < 0) lon_frac = -lon_frac; LOG_INF("GPS fix: position updated lat=%d.%06d lon=%d.%06d", lat_deg, lat_frac, lon_deg, lon_frac); - /* Defer flash write to main thread — gps_fix_callback runs in - * the GNSS modem_chat worker; a synchronous LittleFS write here - * (10-50 ms on nRF52 QSPI) would block NMEA ingest and risk - * UART buffer overflow / lost fixes. Main handles the save - * via MESH_EVENT_PREFS_DIRTY; multiple fixes coalesce into one - * write (the latest prefs values are written). */ - k_event_post(&mesh_events, MESH_EVENT_PREFS_DIRTY); } /* Update UI with GPS data */