13 Commity f23a51be3c ... 05050f4e7e

Autor SHA1 Wiadomość Data
  k4be 05050f4e7e ds18b20: re-arm poll timer even with no sensor present 3 tygodni temu
  k4be a8bbc3b3f4 add soft/common.h to stop duplicating structs between builds 3 tygodni temu
  k4be b3ac3670d7 autopause: manual resume didn't clear an active auto-pause 3 tygodni temu
  k4be 949903cef6 autopause: reset stationary-time counter on a fix gap 3 tygodni temu
  k4be e201aacb8a add paused/skipped/accepted point counters to summaries 3 tygodni temu
  k4be 254a59481a rename System.sbas to dgps, log event SBAS->DGPS 3 tygodni temu
  k4be ea60116417 main: fix stale current_pause_start corrupting stopped/moving time 3 tygodni temu
  k4be a1866c92cb gpx: reject altitude spikes and re-warm-up after a fix gap 3 tygodni temu
  k4be cf913880b8 nmea: reject stale-valid fixes on poor HDOP or non-3D type 3 tygodni temu
  k4be 5a76b3c919 gps-test-tool: fix mktime() applying local TZ to UTC NMEA timestamps 3 tygodni temu
  k4be b0aa218bdb Fix optiboot link failure with GCC 15 / newer binutils 3 tygodni temu
  k4be 2f0f12fa0f Ignore optiboot build artifacts 3 tygodni temu
  k4be e65ed23194 Remove outdated info from README 3 tygodni temu

+ 7 - 0
.gitignore

@@ -1,2 +1,9 @@
 /*.md
 !/README.md
+
+# optiboot build artifacts
+optiboot/*.o
+optiboot/*.elf
+optiboot/*.hex
+optiboot/*.lst
+optiboot/baudcheck.tmp.sh

+ 1 - 1
gps-test-tool/Makefile

@@ -27,7 +27,7 @@ $(TEST_MALFORMED): $(TEST_MALFORMED_OBJECTS)
 run-test-malformed: $(TEST_MALFORMED)
 	./$(TEST_MALFORMED) malformed_test.nmea
 
-%.o: %.c avr_compat.h
+%.o: %.c
 	$(CC) $(CFLAGS) -c $< -o $@
 
 clean:

+ 0 - 8
gps-test-tool/README.md

@@ -34,14 +34,6 @@ make            # builds ./gps-test-tool
 make clean      # removes build artifacts and generated output/debug 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.
-
 ## Running
 
 ```

+ 0 - 117
gps-test-tool/avr_compat.h

@@ -1,117 +0,0 @@
-#pragma once
-/*
- * AVR Compatibility Layer for PC compilation
- * Provides AVR types, macros, and functions for x86/x64 hosts
- */
-
-/* Prevent AVR-specific headers from being included */
-#define _AVR_PGMSPACE_H_ 1
-#define _AVR_IO_H_ 1
-#define _AVR_WDT_H_ 1
-#define _AVR_SLEEP_H_ 1
-
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
-#include <time.h>
-#include <math.h>
-
-/* AVR type definitions - on PC, these are already correct */
-/* No need to redefine built-in types */
-
-/* PROGMEM/Flash support - on PC everything is in RAM */
-#define PROGMEM
-#define __flash
-#define PSTR(s) (s)
-#define strcpy_P strcpy
-#define strcat_P strcat
-/* xprintf functions are implemented in gpx_wrapper.c and nmea_wrapper.c */
-
-/* FatFS types stub */
-typedef struct {
-    FILE *fp;
-    char filename[256];
-} FIL;
-
-typedef uint32_t DWORD;
-typedef unsigned int UINT;
-
-/* FatFS function stubs */
-#define FA_WRITE 0x02
-#define FA_OPEN_ALWAYS 0x10
-
-static inline unsigned char f_open(FIL *file, const char *path, unsigned char mode) {
-    (void)mode;
-    file->fp = fopen(path, "w");
-    if (file->fp) {
-        strncpy(file->filename, path, sizeof(file->filename) - 1);
-        return 0;
-    }
-    return 1;
-}
-
-static inline unsigned char f_write(FIL *file, const void *buf, unsigned int len, unsigned int *written) {
-    if (!file->fp) return 1;
-    *written = fwrite(buf, 1, len, file->fp);
-    return (*written == len) ? 0 : 1;
-}
-
-static inline unsigned char f_close(FIL *file) {
-    if (file->fp) {
-        fclose(file->fp);
-        file->fp = NULL;
-        return 0;
-    }
-    return 1;
-}
-
-/* Math functions */
-#ifndef M_PI
-#define M_PI 3.14159265358979323846
-#endif
-
-/* AVR interrupt stubs */
-#define cli()
-#define sei()
-
-/* System structure - simplified for PC testing */
-struct config_s {
-    unsigned char skip_points;
-    unsigned char auto_pause_dist;
-};
-
-struct system_s {
-    struct config_s conf;
-    unsigned long int distance;
-    unsigned long int elevation_gain;
-    unsigned long int elevation_loss;
-    time_t time_start;
-    time_t current_pause_start;
-    time_t pause_time;
-    unsigned tracking_paused:1;
-    unsigned tracking_auto_paused:1;
-};
-
-struct location_s {
-    float lon;
-    float lat;
-    float alt;
-    time_t time;
-};
-
-extern volatile struct system_s System;
-extern struct location_s location;
-extern time_t utc;
-
-/* Settings flags */
-#define CONFFLAG_DISABLE_FILTERS 0x01
-
-extern unsigned char config_flags;
-
-static inline unsigned char get_flag(unsigned char flag) {
-    return (config_flags & flag) ? 1 : 0;
-}
-
-/* Time functions */
-char *get_iso_time(time_t time, unsigned char local);
-void iso_time_to_filename(char *time);

+ 25 - 9
gps-test-tool/main.c

@@ -18,7 +18,6 @@
 volatile struct system_s System;
 struct location_s location;
 time_t utc;
-unsigned char config_flags = 0;
 unsigned char FLAGS = 0;
 volatile unsigned char reset_cause = 0; /* no real MCU reset happened; log_boot_record() reports "UNKNOWN" */
 
@@ -41,6 +40,7 @@ __flash const char *uart_log_mode_names[] = {
 unsigned char gpx_init(FIL *file);
 unsigned char gpx_close(FIL *file);
 void gpx_process_point(struct location_s *loc, FIL *file);
+void gpx_reset_gap(void);
 time_t gps_parse(const char *nmea);
 
 /* Time function implementation */
@@ -104,11 +104,13 @@ int process_nmea_file(const char *input_file, const char *output_gpx,
         return 1;
     }
 
-    /* Configure filters */
-    config_flags = (filters_enabled ? 0 : CONFFLAG_DISABLE_FILTERS) | CONFFLAG_AUTO_PAUSE;
-
     /* Reset system state */
     reset_system();
+
+    /* Configure filters */
+    set_flag(CONFFLAG_DISABLE_FILTERS, !filters_enabled);
+    set_flag(CONFFLAG_AUTO_PAUSE, 1);
+
     gps_powered_on();
     log_boot_record();
 
@@ -139,7 +141,7 @@ int process_nmea_file(const char *input_file, const char *output_gpx,
     #define FIX_HOLD_EPOCHS 5
     unsigned char fix_ok_prev = 0;
     unsigned char fix_hold_counter = 0;
-    unsigned char sbas_prev = 0xFF; /* 0xFF: not yet known, suppress the first (non-)transition */
+    unsigned char dgps_prev = 0xFF; /* 0xFF: not yet known, suppress the first (non-)transition */
     time_t next_status_utc = 0;
     time_t next_summary_utc = 0;
     while (fgets(line, sizeof(line), input)) {
@@ -169,6 +171,10 @@ 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) {
+                if (fix_gap_detected(location.time)) {
+                    auto_pause_reset_gap();
+                    gpx_reset_gap();
+                }
                 auto_pause_process();
                 gpx_process_point(&location, &gpx_file);
                 point_count++;
@@ -194,10 +200,10 @@ int process_nmea_file(const char *input_file, const char *output_gpx,
                         log_fix_lost();
                 }
             }
-            if (System.sbas != sbas_prev) {
-                if (sbas_prev != 0xFF)
-                    log_sbas_transition();
-                sbas_prev = System.sbas;
+            if (System.dgps != dgps_prev) {
+                if (dgps_prev != 0xFF)
+                    log_dgps_transition();
+                dgps_prev = System.dgps;
             }
             if (!next_status_utc)
                 next_status_utc = utc + 60;
@@ -222,8 +228,18 @@ int process_nmea_file(const char *input_file, const char *output_gpx,
     {
         unsigned int moving = get_logging_time();
         unsigned long int stopped = get_pause_time();
+        /* points_accepted counts every point past all reject checks, at
+         * AVG_COUNT=3 samples per written trkpt with filters enabled (1:1
+         * with filters disabled, so this is 0 in that mode). */
+        unsigned long int averaged_out = System.points_accepted - System.points_written *
+            (filters_enabled ? 3UL : 1UL);
         fprintf(debug_output, "\n=== Summary ===\n");
         fprintf(debug_output, "Points processed: %d\n", point_count);
+        fprintf(debug_output, "Points during pauses: %lu\n", System.points_paused);
+        fprintf(debug_output, "Points skipped (warm-up): %lu\n", System.points_skipped);
+        fprintf(debug_output, "Points rejected: %lu\n", System.rejected_count);
+        fprintf(debug_output, "Points averaged out: %lu\n", averaged_out);
+        fprintf(debug_output, "Points saved: %lu\n", System.points_written);
         fprintf(debug_output, "Total distance: %.2f km\n", System.distance / 100000.0);
         fprintf(debug_output, "Elevation gain: %.1f m\n", System.elevation_gain / 10.0);
         fprintf(debug_output, "Elevation loss: %.1f m\n", System.elevation_loss / 10.0);

+ 56 - 108
gps-test-tool/main.h

@@ -126,6 +126,12 @@
     #define M_PI 3.14159265358979323846
     #endif
 
+    /* NMEA time fields are UTC. The embedded build's stime.S mktime() is a
+     * UTC-only conversion (no TZ/DST concept), but libc's mktime() treats
+     * struct tm as local time and applies TZ - redirect to timegm() (POSIX/
+     * glibc/BSD extension, ignores TZ) so the PC build matches. */
+    #define mktime timegm
+
 #else
     /* ========== EMBEDDED BUILD CONFIGURATION ========== */
 
@@ -157,109 +163,17 @@
 #define VI_LVH          3.4
 #define VI_MULT         (3.3 / 6.6 / 2.495 * 1024)
 
-/* Configuration flags */
-#define CONFFLAG_DISABLE_FILTERS 0x01
-#define CONFFLAG_ENABLE_SBAS     0x02
-#define CONFFLAG_AUTO_PAUSE      0x04
-#define CONFFLAG_VERBOSE_LOG     0x08
-#define CONFFLAG_RESET_ON_NEW_FILE 0x10
-
-/* Reject reasons (see soft/syslog.h) */
-#define REJECT_REASON_KALMAN     0
-#define REJECT_REASON_DISTDIFF   1
-#define REJECT_REASON_MINDIST    2
-
-/* UART log mode (see soft/settings.h) */
-#define UART_LOG_MODE_MIXED     0
-#define UART_LOG_MODE_NMEA      1
-#define UART_LOG_MODE_SYSTEM    2
-
-/* Fix type (see soft/main.h) */
-#define FIX_TYPE_UNKNOWN        0
-#define FIX_TYPE_2D             2
-#define FIX_TYPE_3D             3
-
-/* GNSS modes */
-#define GNSS_MODE_GPS_GLONASS_GALILEO 0
-#define GNSS_MODE_GPS                 1
-#define GNSS_MODE_GPS_GALILEO         2
-#define GNSS_MODE_GALILEO             3
-#define GNSS_MODE_GPS_BEIDOU          4
-#define GNSS_MODE_BEIDOU              5
-
-/* GPS initialization states */
+/* GPS initialization states (PC-build value only - GPS_INIT_INITIALIZED
+ * doesn't exist here, this tool has no real GPS module init sequence) */
 #define GPS_INIT_NOT_INITIALIZED 0
 #define GPS_INIT_QUERY_SENT      1
 
-/* Location validity states */
-#define LOC_INVALID     0
-#define LOC_VALID_NEW   1
-#define LOC_VALID       2
-
-/* Configuration structure */
-struct config_s {
-    unsigned char skip_points;
-    unsigned char auto_pause_dist;
-    unsigned char auto_pause_time;
-    unsigned char auto_pause_speed;
-    unsigned char gnss_mode;
-    unsigned char min_sat;
-    unsigned char min_sats;  /* Alias for compatibility */
-    unsigned char uart_log_mode;
-};
-
-/* System structure */
-struct system_s {
-    struct config_s conf;
-    unsigned long int distance;         /* cm */
-    unsigned long int elevation_gain;   /* dm (decimeters, 0.1m resolution); filtered */
-    unsigned long int elevation_loss;   /* dm; filtered */
-    unsigned long int elevation_gain_raw; /* dm; unfiltered, for comparison only */
-    unsigned long int elevation_loss_raw; /* dm; unfiltered, for comparison only */
-    unsigned long int alt_max;          /* dm; highest smoothed altitude seen */
-    unsigned long int points_written;
-    unsigned long int rejected_count;
-    unsigned long int speed_accum_x100;
-    unsigned long int speed_sample_count;
-    unsigned int hdop_x100;
-    unsigned char fix_type;
-    unsigned char speed;                /* km/h */
-    float bat_volt;
-    float bat_volt_min;
-    float temperature;
-    unsigned char temperature_ok;
-    time_t time_start;
-    time_t current_pause_start;
-    time_t pause_time;
-    unsigned tracking_paused:1;
-    unsigned tracking_auto_paused:1;
-    unsigned gps_initialized:2;
-    unsigned gps_only:1;
-    unsigned keypress:1;
-    unsigned location_valid:2;
-    unsigned sat_count_low:1;
-    unsigned sbas:1;
-    unsigned satellites_used:5;
-};
-
-/* Location structure */
-struct location_s {
-    float lon;
-    float lat;
-    float alt;
-    time_t time;
-};
-
-/* Must match soft/autopause.h exactly: soft/autopause.c is compiled into
- * this tool too (see autopause_wrapper.c). */
-struct auto_pause_s {
-    unsigned char point_counter;
-    unsigned long int prev_distance;
-    unsigned char speed_counter;
-    struct location_s pause_anchor;
-    unsigned int anchor_sample_count;
-    unsigned char resume_high_history;
-};
+/* struct system_s/location_s/config_s/auto_pause_s and the value constants
+ * their fields hold are in soft/common.h - shared byte-for-byte with the
+ * embedded build, since gpx.c/nmea.c/syslog.c/autopause.c (soft/) compile
+ * unmodified into this tool too (see the *_wrapper.c files). */
+#include "../soft/common.h"
+
 extern struct auto_pause_s auto_pause;
 
 /* Global variables */
@@ -267,11 +181,22 @@ extern volatile struct system_s System;
 extern struct location_s location;
 extern time_t utc;
 
-/* Configuration flags */
-extern unsigned char config_flags;
+/* Kept in sync with soft/settings.c's get_flag()/set_flag(): System.conf.flags
+ * is part of struct config_s in common.h, and CONFFLAG_* there is a bit
+ * index (not a pre-shifted mask), so this must index the same way - not the
+ * config_flags-bitmask scheme this used to have, which numbered its flags
+ * differently (and didn't have all of them), a silent mismatch from
+ * embedded's real indices that a shared header can't paper over unless both
+ * sides actually agree on what a CONFFLAG_* value means. */
+static inline unsigned char get_flag(unsigned char index) {
+    return System.conf.flags[index/8] & (1 << (index % 8));
+}
 
-static inline unsigned char get_flag(unsigned char flag) {
-    return (config_flags & flag) ? 1 : 0;
+static inline void set_flag(unsigned char index, unsigned char val) {
+    if (val)
+        System.conf.flags[index/8] |= (1 << (index % 8));
+    else
+        System.conf.flags[index/8] &= ~(1 << (index % 8));
 }
 
 /* soft/main.c's helpers, reimplemented here: soft/main.c itself isn't part
@@ -283,11 +208,33 @@ static inline unsigned char tracking_is_paused(void) {
     return System.tracking_paused || System.tracking_auto_paused;
 }
 
+/* Kept in sync with soft/main.c's pause_time_start()/pause_time_end()/
+ * get_pause_time(): call the start/end function exactly once, at the moment
+ * a pause actually begins/ends (not deferred to the next accepted point,
+ * which may never come before the pause ends, or may come from a much
+ * later, unrelated pause) - see soft/main.c for the full rationale. */
+static inline void pause_time_start(void) {
+    System.current_pause_start = utc;
+}
+
+static inline void pause_time_end(void) {
+    if (System.current_pause_start && System.current_pause_start >= System.time_start)
+        System.pause_time += utc - System.current_pause_start;
+    System.current_pause_start = 0;
+}
+
+/* Kept in sync with soft/main.c's fix_gap_detected() - see there for the
+ * full rationale. */
+static inline unsigned char fix_gap_detected(time_t point_time) {
+    static time_t last_point_time = 0;
+    unsigned char gap = last_point_time && point_time - last_point_time > 3;
+    last_point_time = point_time;
+    return gap;
+}
+
 static inline time_t get_pause_time(void) {
     time_t res = System.pause_time;
-    if (System.current_pause_start < System.time_start)
-        System.current_pause_start = System.time_start;
-    if (is_paused() && System.current_pause_start)
+    if (is_paused() && System.current_pause_start && System.current_pause_start >= System.time_start)
         res += utc - System.current_pause_start;
     return res;
 }
@@ -335,7 +282,7 @@ void log_boot_record(void);
 void log_status_line(void);
 void log_fix_event(void);
 void log_fix_lost(void);
-void log_sbas_transition(void);
+void log_dgps_transition(void);
 void log_session_summary(unsigned char final);
 void log_pause_event(unsigned char started);
 void gps_powered_on(void);
@@ -345,6 +292,7 @@ void gps_powered_on(void);
  * of what this tool's output looks like. It calls beep(), which is
  * meaningless on PC and stubbed out here. */
 void auto_pause_process(void);
+void auto_pause_reset_gap(void);
 unsigned char gps_fix_trustworthy(void);
 static inline void beep(unsigned int len, unsigned char cnt) { (void)len; (void)cnt; }
 

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

@@ -24,7 +24,6 @@
 volatile struct system_s System;
 struct location_s location;
 time_t utc;
-unsigned char config_flags = 0;
 unsigned char FLAGS = 0;
 volatile unsigned char reset_cause = 0;
 FILE *debug_output = NULL;

+ 8 - 0
optiboot/Makefile

@@ -138,6 +138,14 @@ STK500-2 = $(STK500) -d$(MCU_TARGET) -ms -q -lCF -LCF -cUSB -I200kHz -s -wt
 OBJ        = $(PROGRAM).o
 OPTIMIZE = -Os -fno-split-wide-types -mrelax
 
+# GCC 15+ needs -mno-call-main, otherwise linking fails against newer
+# binutils: main is placed directly in .init9 (optiboot never returns from
+# it), so the libc __call_main/exit startup sequence this flag suppresses
+# is neither needed nor safely relocatable here.
+ifeq ($(shell echo 'int main(void){return 0;}' | $(GCCROOT)avr-gcc -mmcu=atmega328p -mno-call-main -x c -c -o /dev/null - 2>/dev/null; echo $$?),0)
+OPTIMIZE += -mno-call-main
+endif
+
 DEFS       = 
 
 CC         = $(GCCROOT)avr-gcc

+ 43 - 0
soft/autopause.c

@@ -35,12 +35,15 @@ static void auto_unpause(void) {
 	if (!System.tracking_auto_paused)
 		return;
 	System.tracking_auto_paused = 0;
+	if (!System.tracking_paused)
+		pause_time_end();
 	log_pause_event(0);
 	beep(50, 4);
 }
 
 static void auto_pause_activate(void) {
 	System.tracking_auto_paused = 1;
+	pause_time_start();
 	/* Otherwise, speed-counter progress from just before this decision (e.g.
 	 * 2 of the 3 consecutive above-threshold samples the resume check below
 	 * wants) could carry over and complete on the very next sample,
@@ -58,6 +61,46 @@ static void auto_pause_activate(void) {
 	beep(50, 3);
 }
 
+/* Call once, when tracking_pause() (working_modes.c) transitions
+ * tracking_paused from set to clear (a manual resume): auto_pause_process()
+ * only clears tracking_auto_paused while tracking_paused is *true* (to force
+ * an active auto-pause off the instant a manual pause starts), so without
+ * this, a manual resume while auto-paused would leave tracking_auto_paused
+ * set - tracking_is_paused() (tracking_paused || tracking_auto_paused) would
+ * still report paused, silently keeping the logger paused until the
+ * speed/distance-based auto-resume condition happened to also be met, well
+ * after the user asked to resume. No log/beep here (unlike auto_unpause()) -
+ * the manual resume already has its own distinct feedback. */
+void auto_pause_clear_manual_resume(void) {
+	System.tracking_auto_paused = 0;
+	auto_pause.prev_distance = System.distance;
+	auto_pause.point_counter = 0;
+	auto_pause.speed_counter = 0;
+}
+
+/* Call once, whenever fix_gap_detected() reports a gap on the point about to
+ * be passed to auto_pause_process(). Without this, point_counter keeps
+ * counting straight through the gap with no idea how long it was or whether
+ * the device kept moving during it, so reacquiring a fix that was lost while
+ * genuinely moving could satisfy the stationary-time threshold within
+ * moments, using mostly pre-gap progress - the opposite of what the
+ * threshold is meant to require (persistent time actually stationary). */
+void auto_pause_reset_gap(void) {
+	auto_pause.point_counter = 0;
+	auto_pause.speed_counter = 0;
+	auto_pause.prev_distance = System.distance;
+	if (System.tracking_auto_paused) {
+		/* Same reasoning as auto_pause_activate()'s anchor init: re-anchor on
+		 * the fix regained after the gap, not wherever the device was when
+		 * the fix was lost - it may have moved (or been carried) an unknown
+		 * distance in between, which resume-by-distance must not read as an
+		 * instant departure from a now-stale anchor. */
+		auto_pause.pause_anchor = location;
+		auto_pause.anchor_sample_count = 1;
+		auto_pause.resume_high_history = 0;
+	}
+}
+
 void auto_pause_process(void) {
 	if (System.tracking_paused || !get_flag(CONFFLAG_AUTO_PAUSE)) { /* remove auto-pause */
 		System.tracking_auto_paused = 0;

+ 4 - 8
soft/autopause.h

@@ -1,14 +1,10 @@
 #pragma once
 
-struct auto_pause_s {
-	unsigned char point_counter;
-	unsigned long int prev_distance;
-	unsigned char speed_counter;
-	struct location_s pause_anchor; /* running average position while paused */
-	unsigned int anchor_sample_count;
-	unsigned char resume_high_history; /* bit history: was the anchor-distance/speed resume condition met */
-};
+/* struct auto_pause_s is in common.h - shared byte-for-byte with the PC test
+ * tool, since this module compiles unmodified into both. */
 
 extern struct auto_pause_s auto_pause;
 
 void auto_pause_process(void);
+void auto_pause_reset_gap(void);
+void auto_pause_clear_manual_resume(void);

+ 157 - 0
soft/common.h

@@ -0,0 +1,157 @@
+#pragma once
+
+/* Needs time_t already defined (embedded: stime.h's typedef; PC: <time.h>) -
+ * include this only after whichever of those this build already includes. */
+
+/*
+ * Definitions shared byte-for-byte between the embedded build (soft/) and
+ * the PC test tool (gps-test-tool/): struct layouts that cross the boundary
+ * between translation units compiled for each side (System/location/
+ * auto_pause, all real logic - gpx.c/nmea.c/syslog.c/autopause.c compile
+ * unmodified into both), and the value constants those structs' fields hold.
+ *
+ * Kept deliberately minimal: this is not "the" shared header, just the part
+ * that must never drift, because a mismatch here is a silent ABI mismatch
+ * between translation units rather than a compile error. Includes, pin/
+ * hardware definitions, and anything embedded-only or PC-only stay in each
+ * side's own main.h.
+ */
+
+/* System.location_valid values */
+#define LOC_INVALID	0
+#define LOC_VALID	1
+#define LOC_VALID_NEW	2
+
+/* System.fix_type values (from GSA) */
+#define FIX_TYPE_UNKNOWN	0
+#define FIX_TYPE_2D		2
+#define FIX_TYPE_3D		3
+
+/* GNSS modes (System.conf.gnss_mode) */
+#define GNSS_MODE_GPS_GLONASS_GALILEO	0
+#define GNSS_MODE_GPS			1
+#define GNSS_MODE_GPS_GALILEO		2
+#define GNSS_MODE_GALILEO		3
+#define GNSS_MODE_GPS_BEIDOU		4
+#define GNSS_MODE_BEIDOU		5
+
+/* UART log mode values (System.conf.uart_log_mode) */
+#define UART_LOG_MODE_MIXED	0	/* NMEA and system log interleaved (default) */
+#define UART_LOG_MODE_NMEA	1	/* NMEA only */
+#define UART_LOG_MODE_SYSTEM	2	/* system log only */
+
+/* Reasons a candidate point can be rejected before being written (see
+ * soft/syslog.h's reject_reason_names[], indexed by these) */
+#define REJECT_REASON_KALMAN	0	/* Kalman-filtered position error too large */
+#define REJECT_REASON_DISTDIFF	1	/* distance jump inconsistent with recent history */
+#define REJECT_REASON_MINDIST	2	/* too small a position change since last saved point */
+#define REJECT_REASON_ALTDIFF	3	/* altitude jump inconsistent with recent history */
+
+/* System.conf.flags bit indices, read/set via get_flag(index)/set_flag(index,
+ * val) - both sides index the same way (a bit position within a flags byte
+ * array), not a pre-shifted bitmask, so these constants and the get_flag()
+ * call sites are interchangeable between builds. */
+#define CONFFLAG_DISABLE_FILTERS	0
+#define CONFFLAG_ENABLE_SBAS		1
+#define CONFFLAG_LOGGING_AFTER_BOOT	2
+#define CONFFLAG_AUTO_PAUSE		3
+#define CONFFLAG_RESET_ON_NEW_FILE	4
+#define CONFFLAG_VERBOSE_LOG		5
+
+#define CONFFLAG_LAST			5
+
+struct config_s {
+	union {
+		unsigned char conf_u8[16];
+		struct {
+			unsigned char gnss_mode;	// 0
+			unsigned char skip_points;	// 1
+			unsigned char auto_pause_time;	// 2
+			unsigned char auto_pause_dist;	// 3
+			unsigned char min_sats;		// 4
+			unsigned char auto_pause_speed;	// 5
+			unsigned char uart_log_mode;	// 6
+		};
+	};
+	unsigned char flags[4];
+};
+
+struct location_s {
+	float lon;
+	float lat;
+	float alt;
+	time_t time;
+};
+
+#ifndef PC_BUILD
+/* Real hardware-timer-backed countdowns (set_timer()/timer_expired() in
+ * soft/main.h), driven by an ISR via atomic_set_uint()/atomic_get_uint().
+ * Embedded-only: the PC build's set_timer()/timer_expired() are plain no-op
+ * functions that never reference System.timers, so struct system_s doesn't
+ * carry it there - dead weight, and a stray cli()/sei() dependency, for a
+ * build with no such ISR. */
+struct timers {
+	unsigned int owire;
+	unsigned int beep;
+	unsigned int recv_timeout;
+	unsigned int status_log;
+	unsigned int lcd;
+	unsigned int backlight;
+};
+#endif
+
+struct system_s {
+#ifndef PC_BUILD
+	struct timers timers;
+#endif
+	struct config_s conf;
+	unsigned long int distance; // cm
+	unsigned long int elevation_gain; // dm (decimeters, 0.1m resolution); filtered (dead-band+smoothed)
+	unsigned long int elevation_loss; // dm; filtered (dead-band+smoothed)
+	unsigned long int elevation_gain_raw; // dm; unfiltered, for log comparison only
+	unsigned long int elevation_loss_raw; // dm; unfiltered, for log comparison only
+	unsigned long int alt_max; // dm; highest smoothed altitude seen this session
+	unsigned long int speed_accum_x100; // sum of instantaneous NMEA speed samples (km/h * 100) while not paused
+	unsigned long int speed_sample_count; // number of samples in speed_accum_x100
+	unsigned long int points_written; // count of accepted/written track points this session
+	unsigned long int rejected_count; // count of rejected candidate points this session (unsigned long: a poor-fix session can reject far more than 65535 times)
+	unsigned long int points_paused; // count of candidate points seen while tracking_is_paused(), this session
+	unsigned long int points_skipped; // count of candidate points dropped by the skip_points warm-up (after boot or a fix gap), this session
+	unsigned long int points_accepted; // count of candidate points that passed every reject check, this session; with filters enabled, points_accepted - points_written*AVG_COUNT is "averaged out, not yet flushed".
+		// points_paused + points_skipped + rejected_count + points_accepted may fall a couple short of the true candidate-point total: the sliding window used for the distance/altitude spike checks needs a couple of points to refill after each reset (boot, or a fix gap) before it can even decide reject-vs-accept, and those few points aren't tallied anywhere.
+	unsigned int hdop_x100; // HDOP * 100, from GSA
+	unsigned char fix_type; // FIX_TYPE_* from GSA
+	unsigned char speed; // km/h
+	time_t time_start;
+	time_t current_pause_start;
+	time_t pause_time;
+	float bat_volt;
+	float bat_volt_min; // lowest battery voltage seen this session
+	float temperature;
+	unsigned temperature_ok:1;
+	unsigned satellites_used:5;
+	unsigned location_valid:2;
+	unsigned dgps:1; /* GGA reports a DGPS-quality fix - the receiver's own doing, not CONFFLAG_ENABLE_SBAS (our SBAS-search request) */
+	unsigned gps_initialized:2;
+	unsigned gps_only:1;
+	unsigned keypress:1;
+	unsigned tracking_paused:1;
+	unsigned tracking_auto_paused:1;
+	unsigned open_new_file:1;
+	unsigned sat_count_low:1;
+	unsigned int global_error;
+	unsigned char status;
+	unsigned char display_state;
+	unsigned char working_mode;
+};
+
+/* Auto-pause state (soft/autopause.c, compiled unmodified into both builds -
+ * see soft/autopause.h). */
+struct auto_pause_s {
+	unsigned char point_counter;
+	unsigned long int prev_distance;
+	unsigned char speed_counter;
+	struct location_s pause_anchor; /* running average position while paused */
+	unsigned int anchor_sample_count;
+	unsigned char resume_high_history; /* bit history: was the anchor-distance/speed resume condition met */
+};

+ 1 - 1
soft/display.c

@@ -227,7 +227,7 @@ void disp_func_ele_sat(void) {
 		xsprintf(disp.line1, PSTR("ele = %.1fm"), location.alt);
 	}
 	xsprintf(disp.line2, PSTR("%2d satelit"), System.satellites_used);
-	if (System.sbas)
+	if (System.dgps)
 		strcat_P(disp.line2, PSTR(", DGPS"));
 }
 

+ 27 - 21
soft/ds18b20.c

@@ -28,24 +28,29 @@ void gettemp(void){
 
 	_delay_ms(1);
 
+	/* No presence pulse (bus short, or no sensor at all): skip straight to
+	 * the error accounting below instead of returning here - an early
+	 * return would skip re-arming System.timers.owire at the end of this
+	 * function, so with no sensor ever connected, this whole function would
+	 * run on every single main loop pass (never gated by the timer again)
+	 * instead of the intended once-per-4s poll. */
+	if(_1WireInit() == 1){
+		_1WireWriteByte(0xcc);
+		_1WireWriteByte(0xbe);
 
-	if(_1WireInit() != 1){
-		return;
-	}
-
-	_1WireWriteByte(0xcc);
-	_1WireWriteByte(0xbe);
-
-	for(i=0;i<9;i++){
-		tmp = _1WireReadByte();
-		t.owbuffer[i] = tmp;
-		crc = _crc_ibutton_update(crc, tmp);
-	}
-	if(!crc){
-		if(t.owbuffer[0] != 0x50 || t.owbuffer[1] != 0x05 || t.owbuffer[5] != 0xff || t.owbuffer[7] != 0x10){
-			temp = (float)t.t*0.0625;
-			System.temperature = temp;
-			temp_ok_out = 1;
+		for(i=0;i<9;i++){
+			tmp = _1WireReadByte();
+			t.owbuffer[i] = tmp;
+			crc = _crc_ibutton_update(crc, tmp);
+		}
+		if(!crc){
+			/* Reject the power-on-reset scratchpad default (85.0C) still in
+			 * place if a conversion never actually completed. */
+			if(t.owbuffer[0] != 0x50 || t.owbuffer[1] != 0x05 || t.owbuffer[5] != 0xff || t.owbuffer[7] != 0x10){
+				temp = (float)t.t*0.0625;
+				System.temperature = temp;
+				temp_ok_out = 1;
+			}
 		}
 	}
 
@@ -59,9 +64,10 @@ void gettemp(void){
 		error_cnt = 0;
 		System.temperature_ok = 1;
 	}
-	
-	_1WireInit();
-	_1WireWriteByte(0xcc);
-	_1WireWriteBytePR(0x44);
+
+	if(_1WireInit() == 1){
+		_1WireWriteByte(0xcc);
+		_1WireWriteBytePR(0x44);
+	}
 	atomic_set_uint(&System.timers.owire, ms(4000));
 }

+ 57 - 4
soft/gpx.c

@@ -35,7 +35,14 @@ struct kalman_s {
 #define ELEV_SMOOTH_TAU		30.0	/* seconds */
 #define ELEV_DEADBAND		5.0		/* meters */
 
-/* Large-jump detection thresholds (informational logging only, points are not rejected because of these) */
+/* Large-jump detection thresholds. POS_JUMP_THRESHOLD is informational
+ * logging only - the distance-diff spike check above already rejects
+ * inconsistent position jumps regardless of size. ALT_JUMP_THRESHOLD both
+ * logs and (combined with the same spike shape) rejects an inconsistent
+ * altitude jump - unlike position there was previously no altitude
+ * consistency check at all, letting a poor-geometry fix's bogus altitude
+ * (right sats/HDOP, wrong solution - e.g. just after reacquiring signal)
+ * straight into the accepted track. */
 #define POS_JUMP_THRESHOLD	100.0	/* meters between consecutive raw fixes */
 #define ALT_JUMP_THRESHOLD	50.0	/* meters between consecutive raw fixes */
 
@@ -108,6 +115,7 @@ unsigned char gpx_init(FIL *file) {
 	gpx.last_distance_point.lat = 0;
 	gpx.last_distance_point.time = 0;
 	gpx.elevation.initialized = 0;
+	gpx.point_count = 0;
 
 	gpx.paused = 1; /* make it add a <trkseg> tag */
 
@@ -154,7 +162,6 @@ unsigned char gpx_write(struct location_s *loc, FIL *file) {
 			strcpy_P(buf, xml_trkseg_end);
 			gpx.paused = 1;
 			gpx.point_count = 0;
-			System.current_pause_start = utc;
 		} else {
 			return 0; /* nothing to store */
 		}
@@ -163,8 +170,6 @@ unsigned char gpx_write(struct location_s *loc, FIL *file) {
 			strcpy_P(buf, xml_trkseg_start);
 			f_write(file, buf, strlen(buf), &bw);
 			gpx.paused = 0;
-			if (System.current_pause_start)
-				System.pause_time += utc - System.current_pause_start;
 		}
 		time = get_iso_time(loc->time, 0);
 		xsprintf(buf, PSTR("\t\t\t<trkpt lat=\"%.8f\" lon=\"%.8f\">\n\t\t\t\t<ele>%.2f</ele>\n\t\t\t\t<time>%s</time>\n"), loc->lat, loc->lon, loc->alt, time);
@@ -190,14 +195,41 @@ unsigned char gpx_close(FIL *file) {
 	return f_close(file);
 }
 
+/* Call once, whenever fix_gap_detected() reports a gap on the point about to
+ * be passed to gpx_process_point() - see fix_gap_detected()'s comment for why
+ * a gap (regardless of cause: fix loss, a 2D fix, poor HDOP, or the receiver
+ * simply not producing lines) needs handling at all. Reset the Kalman/window
+ * state that would otherwise splice the old and new fixes together, and
+ * re-run the existing skip_points warm-up: a freshly reacquired fix can carry
+ * a wildly wrong position/altitude for a few seconds while it converges (seen
+ * after a real signal loss - a nominally-3D, in-range-HDOP fix still a couple
+ * hundred meters off in altitude until more satellites came in), the same way
+ * a fix has never been trusted right after power-on. */
+void gpx_reset_gap(void) {
+	if (get_flag(CONFFLAG_VERBOSE_LOG))
+		xputs_P(PSTR("FIX GAP: resetting warm-up/filter state\r\n"));
+	kalman_init(&gpx.kalman[0]);
+	kalman_init(&gpx.kalman[1]);
+	gpx.prev_points.count = 0;
+	gpx.avg_count = 0;
+	gpx.avg_store.lat = 0;
+	gpx.avg_store.lon = 0;
+	gpx.avg_store.time = 0;
+	gpx.point_count = 0;
+}
+
 void gpx_process_point(struct location_s *loc, FIL *file){
 	float lon_est, lon_err, lat_est, lat_err, dist;
 	struct location_s *ptr;
 	static struct location_s nloc;
 	static struct location_s filtered_loc;
 
+	if (tracking_is_paused())
+		System.points_paused++;
+
 	if (gpx.point_count < System.conf.skip_points) { /* Skipping initial points */
 		gpx.point_count++;
+		System.points_skipped++;
 		return;
 	}
 
@@ -217,6 +249,7 @@ void gpx_process_point(struct location_s *loc, FIL *file){
 
 		gpx_write(loc, file);
 		System.points_written++;
+		System.points_accepted++;
 
 		/* Calculate distance and elevation from filtered points */
 		if (gpx.last_distance_point.lat != 0) {
@@ -251,6 +284,19 @@ void gpx_process_point(struct location_s *loc, FIL *file){
 			float dist12 = distance(prev_points_get(0), prev_points_get(1));
 			float dist34 = distance(prev_points_get(2), prev_points_get(3));
 			float dist32 = distance(prev_points_get(2), prev_points_get(1));
+			/* Same spike test as the distance check below, applied to altitude:
+			 * a poor-geometry fix (e.g. right after reacquiring signal) can pass
+			 * the satellite-count/HDOP gate in nmea.c yet still carry a wildly
+			 * wrong altitude, so catch it here the same way a position spike is
+			 * caught - a jump surrounded by two much smaller ones on both sides.
+			 * TODO: GSA's VDOP would be a more direct altitude-quality gate than
+			 * this shape heuristic, but VDOP is only in GSA, which is parsed
+			 * after GGA within the epoch (see nmea.c's gp_gga_parse HDOP
+			 * comment) - using it here would need buffering a point across the
+			 * epoch boundary until VDOP arrives. Left as a future refinement. */
+			float alt12 = fabs(prev_points_get(0)->alt - prev_points_get(1)->alt);
+			float alt34 = fabs(prev_points_get(2)->alt - prev_points_get(3)->alt);
+			float alt32 = fabs(prev_points_get(2)->alt - prev_points_get(1)->alt);
 			if (get_flag(CONFFLAG_VERBOSE_LOG))
 				xprintf(PSTR("New distance: %fm\r\n"), dist32);
 			if(dist34 > dist12 && dist32 > dist12){
@@ -259,6 +305,12 @@ void gpx_process_point(struct location_s *loc, FIL *file){
 				log_reject(REJECT_REASON_DISTDIFF);
 				return;
 			}
+			if(alt32 > ALT_JUMP_THRESHOLD && alt34 > alt12 && alt32 > alt12){
+				if (get_flag(CONFFLAG_VERBOSE_LOG))
+					xputs_P(PSTR("ALTITUDE DIFF REJECT\r\n"));
+				log_reject(REJECT_REASON_ALTDIFF);
+				return;
+			}
 			if (dist32 > POS_JUMP_THRESHOLD)
 				log_jump(0, dist32);
 			ptr = prev_points_get(PREV_POINTS_LENGTH - 2);
@@ -282,6 +334,7 @@ void gpx_process_point(struct location_s *loc, FIL *file){
 		if (get_flag(CONFFLAG_VERBOSE_LOG))
 			xputs_P(PSTR("ACCEPT\r\n"));
 		log_reject_flush(); /* close out any pending reject burst now that good data has resumed */
+		System.points_accepted++;
 
 		/* Calculate distance and elevation for accepted point */
 		if (gpx.last_distance_point.lat != 0) {

+ 1 - 0
soft/gpx.h

@@ -8,6 +8,7 @@ unsigned char gpx_init(FIL *file);
 unsigned char gpx_write(struct location_s *loc, FIL *file);
 unsigned char gpx_close(FIL *file);
 void gpx_process_point(struct location_s *loc, FIL *file);
+void gpx_reset_gap(void);
 void gpx_save_single_point(struct location_s *loc);
 float distance(struct location_s *pos1, struct location_s *pos2);
 void add_distance(float dist);

+ 73 - 9
soft/main.c

@@ -377,6 +377,9 @@ void reset_counters(void) {
 	System.speed_sample_count = 0;
 	System.points_written = 0;
 	System.rejected_count = 0;
+	System.points_paused = 0;
+	System.points_skipped = 0;
+	System.points_accepted = 0;
 	System.bat_volt_min = 99.0;
 	System.time_start = 0;
 	System.current_pause_start = 0;
@@ -400,11 +403,66 @@ unsigned int get_dist_avg_speed_x100(void) {
 	return (unsigned int)((unsigned long int)System.distance * 36UL / (10UL * moving));
 }
 
+/* A fix normally arrives about once a second; treat a longer gap between
+ * accepted points as a fix having been lost and reacquired (regardless of
+ * why the epochs in between were rejected/absent - fix loss, a 2D fix, poor
+ * HDOP, or the receiver simply not producing lines). Shared by
+ * gpx_process_point() (resets the Kalman/averaging warm-up, the same as
+ * after power-on - a freshly reacquired fix can carry a wildly wrong
+ * position/altitude for a few seconds) and auto_pause_process() (resets the
+ * stationary-time counter - otherwise it keeps counting through the gap
+ * with no visibility into how long the gap was or whether the device kept
+ * moving during it, and can trip a pause within moments of reacquiring a
+ * fix that was actually lost while the device was genuinely still moving).
+ * Called at most once per accepted point (the main loop below calls it
+ * before both), so a single static last-seen time is enough - two callers
+ * checking independently would each need their own copy and could disagree
+ * about where the gap boundary was. */
+#define FIX_GAP_SECONDS	3
+
+unsigned char fix_gap_detected(time_t point_time) {
+	static time_t last_point_time = 0;
+	unsigned char gap = last_point_time && point_time - last_point_time > FIX_GAP_SECONDS;
+	last_point_time = point_time;
+	return gap;
+}
+
+/* Call exactly once, at the moment tracking_paused/tracking_auto_paused
+ * actually transitions to true (auto_pause_activate(), tracking_pause()) -
+ * not deferred until the next accepted point, which may never come before
+ * the pause ends (a near-stationary point, which is exactly what triggers a
+ * pause, is also exactly what a min-distance/Kalman/distdiff reject in
+ * gpx_process_point() would drop) or may come from a much later, unrelated
+ * pause, corrupting current_pause_start into a stale value that then leaks
+ * into get_pause_time() and the pause_time carried forward on resume. */
+void pause_time_start(void) {
+	System.current_pause_start = utc;
+}
+
+/* Call exactly once, at the moment tracking_paused/tracking_auto_paused
+ * actually transitions to false (auto_unpause(), tracking_pause()) - folds
+ * this pause's duration into the permanent accumulator and clears the
+ * start, so a later summary/query while not paused never reuses it. */
+void pause_time_end(void) {
+	/* Same time_start floor as get_pause_time() - a pause that started (and
+	 * possibly also ended) before the first fix contributes no pause time,
+	 * since logging itself hadn't started yet either. */
+	if (System.current_pause_start && System.current_pause_start >= System.time_start)
+		System.pause_time += utc - System.current_pause_start;
+	System.current_pause_start = 0;
+}
+
 time_t get_pause_time(void) {
 	time_t res = System.pause_time;
-	if (System.current_pause_start < System.time_start)
-		System.current_pause_start = System.time_start; /* disallow negative pause time */
-	if (is_paused() && System.current_pause_start)
+	/* current_pause_start is 0 whenever there's no in-progress pause to add -
+	 * either genuinely not paused, or paused but reset_counters() cleared it
+	 * (along with time_start) after pause_time_start() ran for this pause.
+	 * The time_start floor guards the one remaining case reset_counters()
+	 * doesn't cover: pause_time_start() ran before the first fix (e.g. the
+	 * boot-time "don't log until moving" pause), leaving current_pause_start
+	 * at an earlier (possibly 0) utc than time_start - only time since
+	 * logging actually started counts as pause time. */
+	if (is_paused() && System.current_pause_start && System.current_pause_start >= System.time_start)
 		res += utc - System.current_pause_start;
 	return res;
 }
@@ -434,7 +492,7 @@ int main (void)
 	unsigned char already_logging = 0;
 	static unsigned char fix_ok_prev = 0;
 	static unsigned char fix_hold_counter = 0;
-	static unsigned char sbas_prev = 0xFF; /* 0xFF: not yet known, suppress the first (non-)transition */
+	static unsigned char dgps_prev = 0xFF; /* 0xFF: not yet known, suppress the first (non-)transition */
 	static unsigned long int last_summary_uptime = 0;
 
 	ioinit();
@@ -573,11 +631,13 @@ int main (void)
 				}
 			}
 
-			/* SBAS/DGPS on-off transitions */
-			if (System.sbas != sbas_prev) {
-				if (sbas_prev != 0xFF)
-					log_sbas_transition();
-				sbas_prev = System.sbas;
+			/* DGPS-quality-fix on-off transitions (the receiver's own doing,
+			 * e.g. from SBAS - not necessarily caused by our own SBAS-search
+			 * request, CONFFLAG_ENABLE_SBAS) */
+			if (System.dgps != dgps_prev) {
+				if (dgps_prev != 0xFF)
+					log_dgps_transition();
+				dgps_prev = System.dgps;
 			}
 
 			if (timer_expired(status_log)) {
@@ -602,6 +662,10 @@ int main (void)
 					break;
 				}
 				if (System.location_valid == LOC_VALID_NEW) { /* a new point */
+					if (fix_gap_detected(location.time)) {
+						auto_pause_reset_gap();
+						gpx_reset_gap();
+					}
 					auto_pause_process();
 					gpx_process_point(&location, &gpx_file);
 				}

+ 10 - 65
soft/main.h

@@ -21,6 +21,13 @@
 
 /* Project includes - basic headers without struct dependencies */
 #include "stime.h"
+
+/* struct system_s/location_s/config_s/auto_pause_s and the value constants
+ * their fields hold are in common.h - shared byte-for-byte with the PC test
+ * tool, since gpx.c/nmea.c/syslog.c/autopause.c compile unmodified into both.
+ * Needs time_t (stime.h, just above) already defined. */
+#include "common.h"
+
 #include "expander.h"
 #include "settings.h"
 #include "ff.h"
@@ -151,11 +158,6 @@
 #define K_RIGHT	_BV(0)
 #define K_POWER	_BV(4)
 
-/* System.location_valid values */
-#define LOC_INVALID	0
-#define LOC_VALID	1
-#define LOC_VALID_NEW	2
-
 /* System.gps_initialized values */
 #define GPS_INIT_NOT_INITIALIZED	0
 #define GPS_INIT_QUERY_SENT			1
@@ -163,66 +165,6 @@
 
 #define ms(x) (x/10)
 
-struct timers {
-	unsigned int owire;
-	unsigned int beep;
-	unsigned int recv_timeout;
-	unsigned int status_log;
-	unsigned int lcd;
-	unsigned int backlight;
-};
-
-/* System.fix_type values (from GSA) */
-#define FIX_TYPE_UNKNOWN	0
-#define FIX_TYPE_2D			2
-#define FIX_TYPE_3D			3
-
-struct system_s {
-	struct timers timers;
-	struct config_s conf;
-	unsigned int global_error;
-	unsigned char status;
-	float bat_volt;
-	float temperature;
-	unsigned char display_state;
-	unsigned char keypress;
-	unsigned char working_mode;
-	unsigned long int distance; // cm
-	unsigned long int elevation_gain; // dm (decimeters, 0.1m resolution); filtered (dead-band+smoothed)
-	unsigned long int elevation_loss; // dm; filtered (dead-band+smoothed)
-	unsigned long int elevation_gain_raw; // dm; unfiltered, for log comparison only
-	unsigned long int elevation_loss_raw; // dm; unfiltered, for log comparison only
-	unsigned long int alt_max; // dm; highest smoothed altitude seen this session
-	unsigned long int speed_accum_x100; // sum of instantaneous NMEA speed samples (km/h * 100) while not paused
-	unsigned long int speed_sample_count; // number of samples in speed_accum_x100
-	unsigned long int points_written; // count of accepted/written track points this session
-	unsigned long int rejected_count; // count of rejected candidate points this session (unsigned long: a poor-fix session can reject far more than 65535 times)
-	unsigned int hdop_x100; // HDOP * 100, from GSA
-	unsigned char fix_type; // FIX_TYPE_* from GSA
-	unsigned char speed; // km/h
-	time_t time_start;
-	time_t current_pause_start;
-	time_t pause_time;
-	float bat_volt_min; // lowest battery voltage seen this session
-	unsigned temperature_ok:1;
-	unsigned satellites_used:5;
-	unsigned location_valid:2;
-	unsigned sbas:1;
-	unsigned gps_initialized:2;
-	unsigned gps_only:1;
-	unsigned tracking_paused:1;
-	unsigned tracking_auto_paused:1;
-	unsigned open_new_file:1;
-	unsigned sat_count_low:1;
-};
-
-struct location_s {
-	float lon;
-	float lat;
-	float alt;
-	time_t time;
-};
-
 extern volatile struct system_s System;
 extern struct location_s location;
 extern time_t utc;
@@ -279,6 +221,9 @@ void reset_counters(void);
 time_t get_pause_time(void);
 unsigned int get_logging_time(void);
 unsigned char tracking_is_paused(void);
+void pause_time_start(void);
+void pause_time_end(void);
+unsigned char fix_gap_detected(time_t point_time);
 unsigned int get_avg_speed_x100(void);
 unsigned int get_dist_avg_speed_x100(void);
 

+ 40 - 12
soft/nmea.c

@@ -209,9 +209,19 @@ static time_t gp_rmc_parse(const char *str) {
 	return utc;
 }
 
+/* A fix is only trusted (for acceptance into gpx_process_point(), the speed
+ * average, and the auto-pause speed-based unpause check) when it is valid,
+ * of a known type, and reasonably accurate: a single sample from a poor fix
+ * (e.g. HDOP 16 with only a few satellites) would otherwise distort the
+ * whole-session average, corrupt the track with a wild position/altitude
+ * outlier, or - worse - talk the auto-pause logic into resuming a stationary
+ * logger. */
+#define FIX_QUALITY_HDOP_MAX_X100	500	/* a fix is untrustworthy once HDOP > 5.00 */
+
 static void gp_gga_parse(const char *str) {
 	const char *p;
-	double tmp;
+	double tmp, hdop;
+	unsigned int hdop_x100;
 
 	p = gp_col(str, 7); /* satellites used */
 	if (!p)
@@ -225,6 +235,15 @@ static void gp_gga_parse(const char *str) {
 		System.sat_count_low = 1;
 	}
 
+	/* HDOP, from this same sentence: GSA (which also reports HDOP, into
+	 * System.hdop_x100) is parsed after GGA within the epoch, so its value
+	 * is still last epoch's here - read GGA's own HDOP field instead. */
+	p = gp_col(str, 8);
+	if (!p)
+		return;
+	xatof(&p, &hdop);
+	hdop_x100 = (unsigned int)(hdop*100.0 + 0.5);
+
 	/* check validity */
 	p = gp_col(str, 6);
 	if (!p)
@@ -234,8 +253,24 @@ static void gp_gga_parse(const char *str) {
 		return;
 	}
 
-	if (!System.sat_count_low)
-		System.location_valid = LOC_VALID_NEW; /* don't accept the coordinates otherwise, even if reported valid */
+	/* Reject (rather than just not-promote) on a low satellite count, poor
+	 * HDOP, or anything short of a 3D fix, so a bad fix can't coast on
+	 * LOC_VALID left over from the last good epoch - otherwise a
+	 * low-quality point (wrong position and/or altitude) would still be
+	 * accepted downstream. A 2D fix in particular can't solve for altitude
+	 * at all (only 3 satellites: 2 horizontal DOF + time, no vertical) and
+	 * reports a held-over or drifting one instead - seen after a real
+	 * reacquisition, where a 2D fix reported an altitude a few hundred
+	 * meters off before enough satellites came in for a real 3D fix.
+	 * System.fix_type is set by GSA, parsed after GGA within the epoch, so
+	 * it's last epoch's value here - same staleness as HDOP above, but a
+	 * bad 2D fix reliably spans several consecutive epochs, so a one-epoch
+	 * lag isn't a practical gap here (unlike a true point fix). */
+	if (!System.sat_count_low && hdop_x100 <= FIX_QUALITY_HDOP_MAX_X100
+			&& System.fix_type == FIX_TYPE_3D)
+		System.location_valid = LOC_VALID_NEW;
+	else
+		System.location_valid = LOC_INVALID;
 
 	/* parse location */
 	p = gp_col(str, 2);		/* latitude */
@@ -272,9 +307,9 @@ static void gp_gga_parse(const char *str) {
 	if (!p)
 		return;
 	if (*p == '2')
-		System.sbas = 1;
+		System.dgps = 1;
 	else
-		System.sbas = 0;
+		System.dgps = 0;
 
 	p = gp_col(str, 9); /* MSL altitude */
 	if (!p)
@@ -285,13 +320,6 @@ static void gp_gga_parse(const char *str) {
 	location.time = utc; /* parsed from RMC */
 }
 
-/* A fix is only trusted (for the speed average, and for the auto-pause
- * speed-based unpause check below) when it is valid, of a known type, and
- * reasonably accurate: a single sample from a poor fix (e.g. HDOP 16 with
- * only a few satellites) would otherwise distort the whole-session average,
- * or - worse - talk the auto-pause logic into resuming a stationary logger. */
-#define FIX_QUALITY_HDOP_MAX_X100	500	/* a fix is untrustworthy once HDOP > 5.00 */
-
 unsigned char gps_fix_trustworthy(void) {
 	return (FLAGS & F_GPSOK) && System.fix_type != FIX_TYPE_UNKNOWN
 		&& System.hdop_x100 <= FIX_QUALITY_HDOP_MAX_X100;

+ 4 - 37
soft/settings.h

@@ -13,44 +13,11 @@
 
 #define CONF_U8_LAST		6
 
-/* UART log mode values (CONF_U8_UART_LOG_MODE) */
-#define UART_LOG_MODE_MIXED	0	/* NMEA and system log interleaved (default) */
-#define UART_LOG_MODE_NMEA	1	/* NMEA only */
-#define UART_LOG_MODE_SYSTEM	2	/* system log only */
+#define CONFFLAG_LAST		CONFFLAG_VERBOSE_LOG
 
-/* flags list - max 31 */
-#define CONFFLAG_DISABLE_FILTERS	0
-#define CONFFLAG_ENABLE_SBAS		1
-#define CONFFLAG_LOGGING_AFTER_BOOT	2
-#define CONFFLAG_AUTO_PAUSE			3
-#define CONFFLAG_RESET_ON_NEW_FILE	4
-#define CONFFLAG_VERBOSE_LOG		5
-
-#define CONFFLAG_LAST				5
-
-/* GNSS modes */
-#define GNSS_MODE_GPS_GLONASS_GALILEO	0
-#define GNSS_MODE_GPS					1
-#define GNSS_MODE_GPS_GALILEO			2
-#define GNSS_MODE_GALILEO				3
-#define GNSS_MODE_GPS_BEIDOU			4
-#define GNSS_MODE_BEIDOU				5
-
-struct config_s {
-	union {
-		unsigned char conf_u8[16];
-		struct {
-			unsigned char gnss_mode;	// 0
-			unsigned char skip_points;	// 1
-			unsigned char auto_pause_time;	// 2
-			unsigned char auto_pause_dist;	// 3
-			unsigned char min_sats;		// 4
-			unsigned char auto_pause_speed;	// 5
-			unsigned char uart_log_mode;	// 6
-		};
-	};
-	unsigned char flags[4];
-};
+/* UART_LOG_MODE_*, CONFFLAG_*, GNSS_MODE_*, and struct config_s are in
+ * common.h (included via main.h, before this file - see main.h's include
+ * order) - shared byte-for-byte with the PC test tool. */
 
 extern const __flash unsigned char limits_max_u8[];
 extern const __flash unsigned char limits_min_u8[];

+ 15 - 6
soft/syslog.c

@@ -11,11 +11,13 @@
 __flash const char reject_reason_kalman[] = "kalman";
 __flash const char reject_reason_distdiff[] = "distdiff";
 __flash const char reject_reason_mindist[] = "mindist";
+__flash const char reject_reason_altdiff[] = "altdiff";
 
 __flash const char *reject_reason_names[] = {
 	[REJECT_REASON_KALMAN] = reject_reason_kalman,
 	[REJECT_REASON_DISTDIFF] = reject_reason_distdiff,
 	[REJECT_REASON_MINDIST] = reject_reason_mindist,
+	[REJECT_REASON_ALTDIFF] = reject_reason_altdiff,
 };
 
 __flash const char status_no_power[] = "no power";
@@ -120,7 +122,7 @@ void gps_powered_on(void) {
 void log_fix_event(void) {
 	log_prefix();
 	xprintf(PSTR("FIX %uD"), (unsigned int)System.fix_type);
-	xputs_P(System.sbas ? PSTR("/D") : PSTR(""));
+	xputs_P(System.dgps ? PSTR("/D") : PSTR(""));
 	if (!fix_had_first) {
 		unsigned long int ttff = get_uptime_ms() - gps_on_uptime;
 		xprintf(PSTR(" ttff=%lu.%01lus"), ttff/1000, (ttff%1000)/100);
@@ -136,10 +138,10 @@ void log_fix_lost(void) {
 	xprintf(PSTR("FIX LOST sats=%u\r\n"), (unsigned int)System.satellites_used);
 }
 
-void log_sbas_transition(void) {
+void log_dgps_transition(void) {
 	log_prefix();
-	xputs_P(PSTR("SBAS "));
-	xputs_P(System.sbas ? PSTR("on") : PSTR("off"));
+	xputs_P(PSTR("DGPS "));
+	xputs_P(System.dgps ? PSTR("on") : PSTR("off"));
 	xputs_P(PSTR("\r\n"));
 }
 
@@ -224,7 +226,7 @@ void log_status_line(void) {
 		xprintf(PSTR(" t=%.1f"), (double)System.temperature);
 	xprintf(PSTR(" sats=%u hdop=%.2f fix=%u"), (unsigned int)System.satellites_used,
 		(double)System.hdop_x100/100.0, (unsigned int)System.fix_type);
-	xputs_P(System.sbas ? PSTR("D") : PSTR(""));
+	xputs_P(System.dgps ? PSTR("D") : PSTR(""));
 	xprintf(PSTR(" dist=%.2f gain=%.1f/raw%.1f loss=%.1f/raw%.1f wr=%lu rej=%lu"),
 		(double)System.distance/100.0, (double)System.elevation_gain/10.0, (double)System.elevation_gain_raw/10.0,
 		(double)System.elevation_loss/10.0, (double)System.elevation_loss_raw/10.0,
@@ -251,6 +253,13 @@ void log_session_summary(unsigned char final) {
 	xprintf(PSTR("%uh%02um altmax=%.1f vmin=%.3f rej=%lu"), h, m,
 		(double)System.alt_max/10.0, (double)System.bat_volt_min, System.rejected_count);
 	/* Both averages should track each other; a growing gap is worth noticing. */
-	xprintf(PSTR(" avg_speed=%.2f avg_dist=%.2f\r\n"),
+	xprintf(PSTR(" avg_speed=%.2f avg_dist=%.2f"),
 		(double)get_avg_speed_x100()/100.0, (double)get_dist_avg_speed_x100()/100.0);
+	/* points_accepted is pre-averaging: with filters enabled (AVG_COUNT
+	 * samples per written trkpt), points_accepted - points_written*AVG_COUNT
+	 * is however many accepted points are sitting in the averaging window,
+	 * not yet flushed to a trkpt - "averaged out" here, since it never will
+	 * be on its own (the group only flushes once it reaches AVG_COUNT). */
+	xprintf(PSTR(" paused=%lu skipped=%lu accepted=%lu wr=%lu\r\n"),
+		System.points_paused, System.points_skipped, System.points_accepted, System.points_written);
 }

+ 2 - 1
soft/syslog.h

@@ -4,6 +4,7 @@
 #define REJECT_REASON_KALMAN	0	/* Kalman-filtered position error too large */
 #define REJECT_REASON_DISTDIFF	1	/* distance jump inconsistent with recent history */
 #define REJECT_REASON_MINDIST	2	/* too small a position change since last saved point */
+#define REJECT_REASON_ALTDIFF	3	/* altitude jump inconsistent with recent history */
 
 extern __flash const char *reject_reason_names[];
 
@@ -12,7 +13,7 @@ void log_boot_record(void);
 void log_config(void);
 void log_fix_event(void);
 void log_fix_lost(void);
-void log_sbas_transition(void);
+void log_dgps_transition(void);
 void log_pause_event(unsigned char started);
 void log_reject(unsigned char reason);
 void log_reject_flush(void);

+ 14 - 0
soft/working_modes.c

@@ -1,6 +1,9 @@
 #include "main.h"
 
 void tracking_pause(unsigned char cmd, unsigned char display) {
+	unsigned char was_paused = System.tracking_paused;
+	unsigned char was_auto_paused = System.tracking_auto_paused;
+
 	switch (cmd) {
 		case TRACKING_PAUSE_CMD_TOGGLE:
 			System.tracking_paused = !System.tracking_paused;
@@ -13,10 +16,21 @@ void tracking_pause(unsigned char cmd, unsigned char display) {
 			break;
 	}
 	if (System.tracking_paused) {
+		if (!was_paused && !was_auto_paused)
+			pause_time_start();
 		LEDB_ON();
 		if (display)
 			display_event(DISPLAY_EVENT_TRACKING_PAUSED);
 	} else {
+		/* A manual resume must actually resume, even if auto-pause is also
+		 * active: auto_pause_process() only clears tracking_auto_paused
+		 * while tracking_paused is *true*, so left alone here it would
+		 * survive this transition and tracking_is_paused() would still
+		 * report paused. */
+		if (was_auto_paused)
+			auto_pause_clear_manual_resume();
+		if (was_paused && (was_auto_paused || !System.tracking_auto_paused))
+			pause_time_end();
 		LEDB_OFF();
 		if (display)
 			display_event(DISPLAY_EVENT_TRACKING_RESUMED);