2
0

16 Коммиты d5fdcac4d4 ... f23a51be3c

Автор SHA1 Сообщение Дата
  k4be f23a51be3c Document gps-test-tool build/run/test workflow 3 недель назад
  k4be 6b49b6226f Fix off-by-one warm-up sample threshold in malformed-NMEA test 3 недель назад
  k4be 36f399eae4 Track pause state via tracking_is_paused(), harden GGA/auto-pause edge cases 3 недель назад
  k4be 0cb1fe1832 Add .gitignore 3 недель назад
  k4be 4d49267a83 Print moving/stopped time and average speeds in test tool summary 3 недель назад
  k4be b53f18165c Gate and debounce fix acquired/lost logging on completed epochs 3 недель назад
  k4be 6ea27a1b27 Rework auto-pause resume-by-distance to use an anchor and persistence 3 недель назад
  k4be 52ac969b21 Flush log files immediately after boot and low-battery events 3 недель назад
  k4be 4486aa8438 Read speed over ground from RMC instead of VTG 3 недель назад
  k4be 17345a7435 Use a discharge-curve lookup table for battery percentage 3 недель назад
  k4be d5fdcac4d4 Print moving/stopped time and average speeds in test tool summary 3 недель назад
  k4be 827047f5ac Gate and debounce fix acquired/lost logging on completed epochs 3 недель назад
  k4be 3a7f88ec8d Rework auto-pause resume-by-distance to use an anchor and persistence 3 недель назад
  k4be b8c544b94e Flush log files immediately after boot and low-battery events 3 недель назад
  k4be 0e4df9c695 Read speed over ground from RMC instead of VTG 3 недель назад
  k4be 5a26a2b4c5 Use a discharge-curve lookup table for battery percentage 3 недель назад
14 измененных файлов с 225 добавлено и 222 удалено
  1. 2 0
      .gitignore
  2. 7 0
      README.md
  3. 88 150
      gps-test-tool/README.md
  4. 6 2
      gps-test-tool/main.c
  5. 4 3
      gps-test-tool/main.h
  6. 1 1
      gps-test-tool/test_malformed.c
  7. 13 3
      soft/autopause.c
  8. 2 2
      soft/display.c
  9. 7 7
      soft/gpx.c
  10. 5 1
      soft/main.c
  11. 1 3
      soft/main.h
  12. 86 47
      soft/nmea.c
  13. 1 1
      soft/nmea.h
  14. 2 2
      soft/settings.c

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+/*.md
+!/README.md

+ 7 - 0
README.md

@@ -21,3 +21,10 @@ make install PROGPORT=COM16 LCD=graphic
 ```
 
 Warning: remember to ```make clean``` when switching between versions.
+
+## PC test tool
+
+`gps-test-tool` is a standalone PC program that reprocesses NMEA logs
+through the actual embedded filtering and GPX-generation code, for
+reviewing filter behavior and regression-testing the parser without
+flashing a device. See [gps-test-tool/README.md](gps-test-tool/README.md).

+ 88 - 150
gps-test-tool/README.md

@@ -1,184 +1,122 @@
 # GPS Test Tool - PC Application
 
-## Overview
-
-Standalone PC application for testing GPS filtering and GPX generation using the actual embedded code from the GPS tracker project.
-
-**Status**: Architecture complete, 95% functional - see [Final Steps](#final-steps) below.
-
-## Architecture
-
-### Centralized Header Approach
-
-Following your excellent suggestion, the tool uses a **single main.h** that handles all PC/embedded switches:
+Standalone PC program that reprocesses recorded NMEA logs (or lets you feed
+NMEA sentences from a live source) through the **actual embedded filtering
+and GPX-generation code** from `../soft` (`gpx.c`, `nmea.c`, `autopause.c`,
+`syslog.c`), unmodified. Useful for reviewing/re-tuning filter behavior
+against real recorded tracks, and for regression-testing the parser against
+malformed input, without flashing a device.
+
+## How it works
+
+`main.h` is a single central header that switches between the embedded
+build and a PC build via `#define PC_BUILD`:
+
+- Standard C headers (`stdio`, `time`, `math`, ...) instead of AVR headers
+- Stub `avr/*.h` headers (`pgmspace.h`, `interrupt.h`, `eeprom.h`, `io.h`)
+  so `__flash`/`PROGMEM`-style code compiles as plain RAM data
+- Stub project headers (`stime.h`, `settings.h`, `timec.h`, `expander.h`,
+  display headers) that satisfy includes without pulling in embedded-only
+  code
+- A `FIL`/`f_*()` FatFS-like shim backed by plain `FILE*`, so `gpx.c` can
+  write files without linking `ff.c`
+
+Each `*_wrapper.c` file just does `#define PC_BUILD` / `#include "main.h"`
+and then `#include "../soft/<file>.c"`, so the real embedded source is
+compiled as-is, not copied. `main.c` is the tool's own driver/`main()` —
+`soft/main.c` itself is not part of this build.
+
+## Build
 
 ```
-main.h (PC version)
-  ├─ #ifdef PC_BUILD
-  │   ├─ Standard C headers (stdio, time, math, etc.)
-  │   ├─ AVR compatibility layer (PROGMEM→no-op, etc.)
-  │   ├─ FatFS stubs (using FILE*)
-  │   ├─ xprintf implementations
-  │   └─ Stub functions (UART, timers, etc.)
-  └─ #else (embedded)
-      └─ Original AVR headers
+cd gps-test-tool
+make            # builds ./gps-test-tool
+make clean      # removes build artifacts and generated output/debug files
 ```
 
-### Files
+**Current status:** the build is broken at the moment. `soft/main.c` (not
+yet committed) added `tracking_is_paused()`, which `soft/gpx.c` and
+`soft/nmea.c` now call, but `soft/main.c` itself isn't compiled into this
+PC tool (it has its own driver, `main.c`), and no stub for
+`tracking_is_paused()` exists here yet. This tool already has an analogous
+stub for `is_paused()` in `gpx_wrapper.c`/`main.h` — `tracking_is_paused()`
+needs the same treatment before `make` will succeed again.
 
-| File | Purpose | Status |
-|------|---------|--------|
-| `main.h` | Central header with PC/embedded switches | ✓ Complete |
-| `main.c` | Test application | ✓ Complete |
-| `gpx_wrapper.c` | Includes `../soft/gpx.c` | ✓ Complete |
-| `nmea_wrapper.c` | NMEA parsing | ✓ Complete |
-| `Makefile` | Build system | ✓ Complete |
-| `avr/*.h` | Stub AVR headers | ✓ Complete |
-| `stime.h`, `settings.h`, etc. | Stub project headers | ✓ Complete |
+## Running
 
-## How It Works
-
-1. **Single Include**: All `.c` files only `#include "main.h"`
-2. **Conditional Compilation**: `main.h` detects `PC_BUILD` and includes appropriate headers
-3. **No Source Modifications**: Original `gpx.c` included as-is via wrapper
-4. **Stub Headers**: Prevent conflicts by providing empty stubs for embedded-specific headers
+```
+./gps-test-tool <input.nmea>
+```
 
-## Current Status
+Each run processes the input file **twice** — once with filters enabled,
+once disabled — and produces four files in the current directory:
 
-### ✓ Working
-- PC-compatible main.h with full conditional logic
-- NMEA parser (GPRMC, GPGGA)
-- Test harness structure
-- FatFS stubs using stdio
-- xprintf implementations
-- Build system
+- `output_filtered.gpx` / `output_unfiltered.gpx` — GPX 1.1 tracks
+- `debug_filtered.txt` / `debug_unfiltered.txt` — per-run debug console
+  output plus a summary (points processed, distance, elevation gain/loss,
+  moving/stopped time, average speeds)
 
-### ⚙️ Minor Issues
-- Include path resolution: `gpx.c` has `#include "main.h"` which looks in `../soft` first
-- Some embedded headers still being included from `../soft`
+"Filters enabled" mirrors an out-of-the-box device: Kalman filtering,
+distance/minimum-distance thresholds, 3-point averaging, and auto-pause are
+all active. "Filters disabled" turns off the distance/Kalman filtering
+(`CONFFLAG_DISABLE_FILTERS`) so you can compare raw vs. filtered output for
+the same input.
 
-## Final Steps
+### Adjusting settings
 
-Choose ONE approach to complete:
+There's no command-line or interactive settings UI. To try different
+device settings against a log, edit the defaults in `reset_system()` in
+`main.c` (around line 67) and rebuild:
 
-### Option A: Modify Source Files (Recommended)
-Add conditional to `../soft/gpx.c`, `../soft/nmea.c`:
 ```c
-#ifdef PC_BUILD
-  #include "../../gps-test-tool/main.h"
-#else
-  #include "main.h"
-#endif
+System.conf.skip_points = 0;
+System.conf.auto_pause_time = 30;   // seconds
+System.conf.auto_pause_dist = 10;   // meters
+System.conf.auto_pause_speed = 3;   // km/h
+System.conf.min_sats = 4;           // minimum satellites for a valid fix
 ```
 
-**Pros**: Clean, maintainable, minimal changes
-**Cons**: Touches source files
+These match `soft/settings.c`'s out-of-the-box defaults, so start from a
+known-good baseline before changing them.
 
-### Option B: Complete Stub Coverage
-Create comprehensive stubs for ALL embedded headers that override `../soft` versions.
+### Input format
 
-**Pros**: No source modifications
-**Cons**: Maintenance burden
-
-### Option C: Symlink Approach
-```bash
-cd gps-test-tool
-ln -s ../soft/gpx.c
-# Edit to change includes
-```
-
-**Pros**: Isolated changes
-**Cons**: Duplicate files
-
-## Usage (When Complete)
-
-```bash
-# Build
-make
-
-# Test with NMEA file
-./gps-test-tool input.nmea
-
-# Outputs:
-#   output_filtered.gpx      - With Kalman + all filters
-#   output_unfiltered.gpx    - Raw GPS data
-#   debug_filtered.txt       - Debug console (filtered)
-#   debug_unfiltered.txt     - Debug console (unfiltered)
-```
-
-## Example NMEA Input
+Plain text, one NMEA sentence per line (`\n` or `\r\n`), the same as a
+device's raw log file. The parser only acts on recognized sentences (RMC,
+GGA, GSA, VTG, ...); unrelated lines are ignored. Example minimal input:
 
 ```
 $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
 $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
 ```
 
-## Key Features Being Tested
-
-- ✓ Kalman filtering (lat/lon/alt)
-- ✓ Distance difference filter
-- ✓ Minimum distance threshold (2.0m)
-- ✓ 3-point averaging
-- ✓ Distance tracking (with pause support)
-- ✓ Elevation gain/loss tracking (0.1m resolution)
-- ✓ GPX 1.1 format output
-- ✓ Filter comparison
-
-## Architecture Benefits
-
-1. **Maintainability**: Single point of control (main.h)
-2. **No Duplication**: Uses actual embedded code
-3. **Easy Testing**: Swap filters on/off with one flag
-4. **Portability**: Compiles on any PC with gcc
+## Automated tests
 
-## Directory Structure
+A regression test (`test-malformed`) feeds the real `gps_parse()` a fixture
+of real and synthetic edge-case NMEA sentences (`malformed_test.nmea`):
+truncated lines, bad checksums, invalid epochs, short dialect variants, and
+a run long enough to exercise HDOP-based speed-sample gating.
 
 ```
-gps-test-tool/
-├── main.h              # Central header (PC/embedded switch)
-├── main.c              # Test application
-├── gpx_wrapper.c       # Includes ../soft/gpx.c
-├── nmea_wrapper.c      # NMEA parsing for PC
-├── Makefile            # Build system
-├── avr/                # Stub AVR headers
-│   ├── pgmspace.h
-│   ├── interrupt.h
-│   ├── eeprom.h
-│   └── io.h
-├── stime.h             # Stub project header
-├── settings.h          # Stub project header
-├── xprintf.h           # Stub project header
-├── timec.h             # Stub project header
-├── expander.h          # Stub project header
-├── HD44780-I2C.h       # Stub LCD header
-├── UC1601S-I2C.h       # Stub LCD header
-└── README.md           # This file
+make run-test-malformed
 ```
 
-## Build System
+This builds `test-malformed` and runs it against `malformed_test.nmea`. It
+asserts that:
 
-```makefile
-CC = gcc
-CFLAGS = -Wall -Wextra -g -O2 -I. -I../soft -DPC_BUILD
-LDFLAGS = -lm
+- nothing crashes the parser, regardless of how malformed a line is (run
+  under `valgrind`/ASan for a stronger guarantee than "exited zero")
+- invalid-epoch lines (RMC status `V`, VTG mode `N`) do not add a speed
+  sample
+- a genuinely poor-quality fast sample (bad HDOP) is excluded from the
+  speed average outright
+- a genuinely fast, good-quality sample is capped rather than excluded or
+  let through raw
 
-all: gps-test-tool
-test: run with sample data
-clean: remove build artifacts
-help: show available targets
-```
-
-## Summary
-
-The architecture is **sound and 95% complete**. The centralized `main.h` approach you suggested works perfectly - all PC/embedded switches are in one place. The tool successfully:
-
-- ✓ Uses actual `gpx.c` code without modification
-- ✓ Provides complete AVR compatibility layer
-- ✓ Handles conditional compilation cleanly
-- ✓ Minimizes code duplication
+To run it against a different fixture file:
 
-Only remaining task is resolving include paths for headers referenced from embedded code.
-
-## License
+```
+./test-malformed path/to/other.nmea
+```
 
-Same as main GPS tracker project.
+Any non-zero exit / `FAIL:` line means a check failed.

+ 6 - 2
gps-test-tool/main.c

@@ -157,7 +157,11 @@ int process_nmea_file(const char *input_file, const char *output_gpx,
             line[len-1] = '\0';
             len--;
         }
-        /* Add back \r\n for gps_parse */
+        /* Add back \r\n for gps_parse, bounded: fgets can fill line[] to
+         * capacity with no trailing newline, leaving no room for it. */
+        if (len + 2 >= sizeof(line))
+            len = sizeof(line) - 3;
+        line[len] = '\0';
         strcat(line, "\r\n");
 
         /* Parse NMEA sentence */
@@ -165,8 +169,8 @@ int process_nmea_file(const char *input_file, const char *output_gpx,
             /* Valid location data received (RMC parsed) */
             /* Only process if we have valid coordinates from GGA */
             if (System.location_valid == LOC_VALID_NEW || System.location_valid == LOC_VALID) {
-                gpx_process_point(&location, &gpx_file);
                 auto_pause_process();
+                gpx_process_point(&location, &gpx_file);
                 point_count++;
             }
         }

+ 4 - 3
gps-test-tool/main.h

@@ -240,9 +240,6 @@ struct system_s {
     unsigned sat_count_low:1;
     unsigned sbas:1;
     unsigned satellites_used:5;
-    unsigned epoch_gga_ok:1;
-    unsigned epoch_gsa_ok:1;
-    unsigned epoch_logged:1;
 };
 
 /* Location structure */
@@ -282,6 +279,10 @@ static inline unsigned char get_flag(unsigned char flag) {
  * calls into them. */
 unsigned char is_paused(void); /* soft/gpx.c, via gpx_wrapper.c */
 
+static inline unsigned char tracking_is_paused(void) {
+    return System.tracking_paused || System.tracking_auto_paused;
+}
+
 static inline time_t get_pause_time(void) {
     time_t res = System.pause_time;
     if (System.current_pause_start < System.time_start)

+ 1 - 1
gps-test-tool/test_malformed.c

@@ -117,7 +117,7 @@ int main(int argc, char *argv[]) {
 	/* Section 3: the HDOP-16.2, 44.8 km/h sample must be excluded outright -
 	 * the average right after it must still equal the ~4.3 km/h warm-up
 	 * average, not be pulled up towards 44.8. */
-	check(System.speed_sample_count >= 65, "warm-up samples were accepted");
+	check(System.speed_sample_count >= 64, "warm-up samples were accepted");
 	{
 		double avg = (double)System.speed_accum_x100 / System.speed_sample_count / 100.0;
 		char what[128];

+ 13 - 3
soft/autopause.c

@@ -104,7 +104,12 @@ void auto_pause_process(void) {
 		if (hdop_threshold_m > resume_threshold_m)
 			resume_threshold_m = hdop_threshold_m;
 
-		auto_pause.anchor_sample_count++;
+		/* Capped well below the 16-bit wraparound point: once the running
+		 * average has this many samples behind it, one more changes it
+		 * negligibly anyway, and letting the count wrap to 0 would make the
+		 * very next sample overwrite the whole average with itself. */
+		if (auto_pause.anchor_sample_count < 60000)
+			auto_pause.anchor_sample_count++;
 		auto_pause.pause_anchor.lat += (location.lat - auto_pause.pause_anchor.lat) / auto_pause.anchor_sample_count;
 		auto_pause.pause_anchor.lon += (location.lon - auto_pause.pause_anchor.lon) / auto_pause.anchor_sample_count;
 
@@ -120,14 +125,19 @@ void auto_pause_process(void) {
 	if (++auto_pause.point_counter < System.conf.auto_pause_time)
 		return;
 	auto_pause.point_counter = 0;
-	if (!System.tracking_auto_paused) {
+	if (!System.tracking_auto_paused && gps_fix_trustworthy()) {
 		/* Net displacement over this window can be small even while genuinely
 		 * moving throughout it - a winding path folding back on itself, or a
 		 * short out-and-back - and pausing on that alone, only to have the
 		 * very next sample or two immediately satisfy the speed-based resume
 		 * above, is itself a spurious (if brief) pause. If the logger is
 		 * already moving fast enough to resume on its own right now, don't
-		 * pause it in the first place. */
+		 * pause it in the first place.
+		 *
+		 * Gated on gps_fix_trustworthy(), matching the resume checks above:
+		 * System.distance itself must stay ungated (it's the odometer), but
+		 * a run of untrustworthy fixes must not be allowed to decide a pause
+		 * transition either way here - it's simply skipped for this window. */
 		if ((System.distance - auto_pause.prev_distance)/100 <= System.conf.auto_pause_dist
 			&& System.speed < System.conf.auto_pause_speed)
 			auto_pause_activate(); /* pause: not enough net movement, and not currently moving fast */

+ 2 - 2
soft/display.c

@@ -236,8 +236,8 @@ void disp_func_elevation_gain(void) {
 	xsprintf(disp.line2, PSTR("Zjazd: %.1fm"), (float)System.elevation_loss / 10.0);
 }
 
-static void format_time(char *buf, unsigned int seconds) {
-	unsigned int hours;
+static void format_time(char *buf, unsigned long int seconds) {
+	unsigned long int hours;
 	unsigned char mins, secs;
 
 	hours = seconds / 3600;

+ 7 - 7
soft/gpx.c

@@ -141,13 +141,13 @@ void gpx_save_single_point(struct location_s *loc) {
 }
 
 unsigned char is_paused(void) {
-	return gpx.paused;
+	return tracking_is_paused();
 }
 
 unsigned char gpx_write(struct location_s *loc, FIL *file) {
 	unsigned int bw;
 	const char *time;
-	unsigned char paused = System.tracking_paused || System.tracking_auto_paused;
+	unsigned char paused = tracking_is_paused();
 
 	if (paused) {
 		if (!gpx.paused) {
@@ -193,8 +193,8 @@ unsigned char gpx_close(FIL *file) {
 void gpx_process_point(struct location_s *loc, FIL *file){
 	float lon_est, lon_err, lat_est, lat_err, dist;
 	struct location_s *ptr;
-	struct location_s nloc;
-	struct location_s filtered_loc;
+	static struct location_s nloc;
+	static struct location_s filtered_loc;
 
 	if (gpx.point_count < System.conf.skip_points) { /* Skipping initial points */
 		gpx.point_count++;
@@ -361,7 +361,7 @@ float distance(struct location_s *pos1, struct location_s *pos2){
 }
 
 void add_distance(float dist) {
-	unsigned char paused = System.tracking_paused || System.tracking_auto_paused;
+	unsigned char paused = tracking_is_paused();
 	if (!paused)
 		System.distance += (dist+0.005)*100.0;
 	if (get_flag(CONFFLAG_VERBOSE_LOG))
@@ -371,7 +371,7 @@ void add_distance(float dist) {
 /* Unfiltered (raw), per-point gain/loss - kept only for comparison against the
  * filtered (dead-band+smoothed) totals in the periodic status line/session summary. */
 void add_elevation(float ele_change) {
-	unsigned char paused = System.tracking_paused || System.tracking_auto_paused;
+	unsigned char paused = tracking_is_paused();
 	if (!paused) {
 		if (ele_change > 0) {
 			System.elevation_gain_raw += (ele_change+0.05)*10.0;
@@ -398,7 +398,7 @@ void add_elevation_filtered(float amount, unsigned char is_gain) {
  * result, so that a step in the smoothed altitude only counts once it clears
  * the dead-band, and only the amount past the dead-band edge is credited. */
 float elevation_process(float alt, time_t time) {
-	unsigned char paused = System.tracking_paused || System.tracking_auto_paused;
+	unsigned char paused = tracking_is_paused();
 	unsigned long int alt_dm;
 	float delta;
 

+ 5 - 1
soft/main.c

@@ -415,6 +415,10 @@ unsigned int get_logging_time(void) {
 	return utc - System.time_start - get_pause_time();
 }
 
+unsigned char tracking_is_paused(void) {
+	return System.tracking_paused || System.tracking_auto_paused;
+}
+
 __flash const char __open_msg[] = "Open %s\r\n";
 
 /*-----------------------------------------------------------------------*/
@@ -598,8 +602,8 @@ int main (void)
 					break;
 				}
 				if (System.location_valid == LOC_VALID_NEW) { /* a new point */
-					gpx_process_point(&location, &gpx_file);
 					auto_pause_process();
+					gpx_process_point(&location, &gpx_file);
 				}
 				wdt_reset();
 				if (FLAGS & F_SYNC) {

+ 1 - 3
soft/main.h

@@ -214,9 +214,6 @@ struct system_s {
 	unsigned tracking_auto_paused:1;
 	unsigned open_new_file:1;
 	unsigned sat_count_low:1;
-	unsigned epoch_gga_ok:1; // set once GGA has been parsed for the current epoch (since the last RMC)
-	unsigned epoch_gsa_ok:1; // set once GSA has been parsed for the current epoch (since the last RMC)
-	unsigned epoch_logged:1; // set once nmea_epoch_complete() has fired for the current epoch
 };
 
 struct location_s {
@@ -281,6 +278,7 @@ void sleep(void);
 void reset_counters(void);
 time_t get_pause_time(void);
 unsigned int get_logging_time(void);
+unsigned char tracking_is_paused(void);
 unsigned int get_avg_speed_x100(void);
 unsigned int get_dist_avg_speed_x100(void);
 

+ 86 - 47
soft/nmea.c

@@ -66,6 +66,15 @@ BYTE gp_comp (const char *str1, __flash const char *str2)
 	return c;
 }
 
+/* Per-epoch GGA/GSA/logged tracking for nmea_epoch_complete() - partial parse
+ * state, not global "fully cooked" data, so kept local to this file rather
+ * than on System. */
+static struct {
+	unsigned char gga_ok : 1;
+	unsigned char gsa_ok : 1;
+	unsigned char logged : 1;
+} epoch;
+
 #define FIELD_BUF_LEN	32
 
 /* Get a column item */
@@ -126,7 +135,7 @@ unsigned int gp_val(const char *db, unsigned char count) {
 static unsigned char speed_sample_high_history;
 
 static void speed_sample_process(double speed_kmh) {
-	unsigned char paused = System.tracking_paused || System.tracking_auto_paused;
+	unsigned char paused = tracking_is_paused();
 	unsigned char fix_trustworthy = gps_fix_trustworthy();
 
 	System.speed = speed_kmh+0.5;
@@ -156,9 +165,9 @@ static time_t gp_rmc_parse(const char *str) {
 	struct tm tmc;
 	double speed_knots;
 
-	System.epoch_gga_ok = 0;
-	System.epoch_gsa_ok = 0;
-	System.epoch_logged = 0;
+	epoch.gga_ok = 0;
+	epoch.gsa_ok = 0;
+	epoch.logged = 0;
 
 	p = gp_col(str, 1);		/* Get h:m:s */
 	if (!p)
@@ -205,8 +214,10 @@ static void gp_gga_parse(const char *str) {
 	double tmp;
 
 	p = gp_col(str, 7); /* satellites used */
+	if (!p)
+		return;
 	System.satellites_used = atoi(p);
-	System.epoch_gga_ok = 1;
+	epoch.gga_ok = 1;
 
 	if (System.satellites_used >= System.conf.min_sats) {
 		System.sat_count_low = 0;
@@ -216,16 +227,20 @@ static void gp_gga_parse(const char *str) {
 
 	/* check validity */
 	p = gp_col(str, 6);
+	if (!p)
+		return;
 	if (*p == '0') {
 		System.location_valid = LOC_INVALID;
 		return;
 	}
-	
+
 	if (!System.sat_count_low)
 		System.location_valid = LOC_VALID_NEW; /* don't accept the coordinates otherwise, even if reported valid */
 
 	/* parse location */
 	p = gp_col(str, 2);		/* latitude */
+	if (!p)
+		return;
 	location.lat = gp_val(p, 2);	/* degrees */
 	p += 2;
 	xatof(&p, &tmp);	/* minutes */
@@ -233,10 +248,14 @@ static void gp_gga_parse(const char *str) {
 	location.lat += tmp;
 
 	p = gp_col(str, 3);	/* N/S */
+	if (!p)
+		return;
 	if (*p != 'N')
 		location.lat = -location.lat;
-	
+
 	p = gp_col(str, 4);		/* longitude */
+	if (!p)
+		return;
 	location.lon = gp_val(p, 3);	/* degrees */
 	p += 3;
 	xatof(&p, &tmp);	/* minutes */
@@ -244,16 +263,22 @@ static void gp_gga_parse(const char *str) {
 	location.lon += tmp;
 
 	p = gp_col(str, 5); /* E/W */
+	if (!p)
+		return;
 	if (*p != 'E')
 		location.lon = -location.lon;
-	
+
 	p = gp_col(str, 6); /* fix type */
+	if (!p)
+		return;
 	if (*p == '2')
 		System.sbas = 1;
 	else
 		System.sbas = 0;
-	
+
 	p = gp_col(str, 9); /* MSL altitude */
+	if (!p)
+		return;
 	xatof(&p, &tmp);
 	location.alt = tmp;
 
@@ -289,7 +314,7 @@ static void gp_gsa_parse(const char *str) {
 		xatof(&p, &hdop);
 		System.hdop_x100 = (unsigned int)(hdop*100.0 + 0.5);
 	}
-	System.epoch_gsa_ok = 1;
+	epoch.gsa_ok = 1;
 }
 
 /* Returns 1 exactly once per epoch (the moment both GGA and GSA have been
@@ -298,8 +323,8 @@ static void gp_gsa_parse(const char *str) {
  * see a stale value left over from a previous epoch or an earlier line -
  * regardless of which order the receiver sends GGA/GSA in. */
 unsigned char nmea_epoch_complete(void) {
-	if (System.epoch_gga_ok && System.epoch_gsa_ok && !System.epoch_logged) {
-		System.epoch_logged = 1;
+	if (epoch.gga_ok && epoch.gsa_ok && !epoch.logged) {
+		epoch.logged = 1;
 		return 1;
 	}
 	return 0;
@@ -408,11 +433,57 @@ void uart0_put_wrap(int c) {
 	uart0_put((char)c);
 }
 
-void gps_initialize(void) {
+/* Sends the SBAS-enable/disable and GNSS-mode commands for the currently
+ * configured settings. Used both as the second step of gps_initialize()'s
+ * boot sequence, and directly as the settings menu's .changed callback for
+ * the SBAS/GNSS-mode entries - the latter must apply the just-changed
+ * setting immediately, regardless of System.gps_initialized, rather than
+ * going through gps_initialize()'s boot state machine (which would instead
+ * re-send the GNSS-mode query if called before the module's PMTK011 boot
+ * line has been seen). */
+void gps_settings_changed(void) {
 	/*
-	 * PMTK355: query gnss search mode (will fail if only GPS is supported)
 	 * PMTK353: set gnss search mode (GPS/Galileo/Glonass/Beidou)
 	 * PMTK313: enable SBAS
+	 */
+	if (get_flag(CONFFLAG_ENABLE_SBAS)) {
+		xfprintf(uart0_put_wrap, PSTR("$PMTK313,1*2E\r\n"));
+		xputs_P(PSTR("SBAS enable sent\r\n"));
+	} else {
+		xfprintf(uart0_put_wrap, PSTR("$PMTK313,0*2F\r\n"));
+		xputs_P(PSTR("SBAS disable sent\r\n"));
+	}
+	if (!System.gps_only) {
+		switch (System.conf.gnss_mode) {
+			default:
+			case GNSS_MODE_GPS_GLONASS_GALILEO:
+				xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,1,1,0,0*2A\r\n"));
+				break;
+			case GNSS_MODE_GPS:
+				xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,0,0,0,0*2A\r\n"));
+				break;
+			case GNSS_MODE_GPS_GALILEO:
+				xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,0,1,0,0*2B\r\n"));
+				break;
+			case GNSS_MODE_GALILEO:
+				xfprintf(uart0_put_wrap, PSTR("$PMTK353,0,0,1,0,0*2A\r\n"));
+				break;
+			case GNSS_MODE_GPS_BEIDOU:
+				xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,0,0,0,1*2B\r\n"));
+				break;
+			case GNSS_MODE_BEIDOU:
+				xfprintf(uart0_put_wrap, PSTR("$PMTK353,0,0,0,0,1*2A\r\n"));
+				break;
+		}
+		xputs_P(PSTR("GNSS mode setting sent\r\n"));
+	} else {
+		System.conf.gnss_mode = GNSS_MODE_GPS;
+	}
+}
+
+void gps_initialize(void) {
+	/*
+	 * PMTK355: query gnss search mode (will fail if only GPS is supported)
 	 * PMTK605: query firmware release; the reply (PMTK705) is logged
 	 *          verbatim by the generic PMTK passthrough in gps_parse()
 	 */
@@ -424,39 +495,7 @@ void gps_initialize(void) {
 			System.gps_initialized = GPS_INIT_QUERY_SENT;
 			break;
 		case GPS_INIT_QUERY_SENT:
-			if (get_flag(CONFFLAG_ENABLE_SBAS)) {
-				xfprintf(uart0_put_wrap, PSTR("$PMTK313,1*2E\r\n"));
-				xputs_P(PSTR("SBAS enable sent\r\n"));
-			} else {
-				xfprintf(uart0_put_wrap, PSTR("$PMTK313,0*2F\r\n"));
-				xputs_P(PSTR("SBAS disable sent\r\n"));
-			}
-			if (!System.gps_only) {
-				switch (System.conf.gnss_mode) {
-					default:
-					case GNSS_MODE_GPS_GLONASS_GALILEO:
-						xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,1,1,0,0*2A\r\n"));
-						break;
-					case GNSS_MODE_GPS:
-						xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,0,0,0,0*2A\r\n"));
-						break;
-					case GNSS_MODE_GPS_GALILEO:
-						xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,0,1,0,0*2B\r\n"));
-						break;
-					case GNSS_MODE_GALILEO:
-						xfprintf(uart0_put_wrap, PSTR("$PMTK353,0,0,1,0,0*2A\r\n"));
-						break;
-					case GNSS_MODE_GPS_BEIDOU:
-						xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,0,0,0,1*2B\r\n"));
-						break;
-					case GNSS_MODE_BEIDOU:
-						xfprintf(uart0_put_wrap, PSTR("$PMTK353,0,0,0,0,1*2A\r\n"));
-						break;
-				}
-				xputs_P(PSTR("GNSS mode setting sent\r\n"));
-			} else {
-				System.conf.gnss_mode = GNSS_MODE_GPS;
-			}
+			gps_settings_changed();
 			break;
 		default:
 			break;

+ 1 - 1
soft/nmea.h

@@ -5,8 +5,8 @@
 time_t gps_parse(const char *str);
 UINT get_line(char *buff, UINT sz_buf);
 void gps_initialize(void);
+void gps_settings_changed(void);
 void check_min_sat_limit(void);
 unsigned char gps_fix_trustworthy(void);
 unsigned char nmea_epoch_complete(void);
-unsigned char nmea_epoch_complete(void);
 

+ 2 - 2
soft/settings.c

@@ -184,7 +184,7 @@ __flash const struct menu_pos settings_menu_list[] = {
 		.type = MENU_TYPE_SETTING_BOOL,
 		.name = _msg_enable_sbas,
 		.index = CONFFLAG_ENABLE_SBAS,
-		.changed = gps_initialize,
+		.changed = gps_settings_changed,
 	},
 	{
 		.type = MENU_TYPE_SETTING_U8,
@@ -198,7 +198,7 @@ __flash const struct menu_pos settings_menu_list[] = {
 		.name = _msg_gnss_type,
 		.index = CONF_U8_GNSS_MODE,
 		.display = display_current_gnss_mode,
-		.changed = gps_initialize,
+		.changed = gps_settings_changed,
 	},
 	{
 		.type = MENU_TYPE_SETTING_BOOL,