浏览代码

Elevation dead-band/smoothing, NMEA-average speed, and a restructured system log

Elevation:
- Add a 5m dead-band + 30s exponential smoothing pass (gpx.c: elevation_process()),
  always computed regardless of the "disable filters" setting. Ascent/descent
  totals (System.elevation_gain/loss) now come from this filtered pipeline; the
  previous unfiltered per-point accumulation is kept as elevation_gain_raw/loss_raw
  for comparison in the system log only.
- The averaged GPX point now carries the smoothed altitude (nloc.alt was
  previously never set).

Speed:
- Average speed is now a fixed-point accumulation of instantaneous NMEA (VTG)
  speed samples while not paused (System.speed_accum_x100), instead of
  distance/time. get_avg_speed_x100() feeds the display.

System log (soft/syslog.c, new):
- Every line gets a UTC ISO-8601 timestamp plus millisecond uptime; NOTIME is
  used before the GPS has provided real time (cold boot).
- Full configuration is printed once at boot and again on any change, instead
  of per-write. Per-write Kalman/accept/reject debug text moved behind a new
  "verbose log" setting; a merged status line (satellites, HDOP, fix type,
  DGPS, filtered+raw distance/elevation, write/reject counters, battery) is
  logged every 60s and rejections are coalesced instead of logged individually.
- New events: boot record (fw version+hash, reset cause, config), fix
  acquired/lost (with time-to-first-fix), SBAS on/off transitions, auto-pause
  start/end, large position/altitude jumps, and an hourly + shutdown session
  summary.
- New UART log mode setting: NMEA only / mixed (default) / system only.

NMEA parsing:
- Add GSA parsing for HDOP and 2D/3D fix type, used by the new status line and
  reject/fix events.

Notes/limitations (flagged for follow-up, not implemented here):
- No new HDOP/satellite-count based write gate was added; the requested
  "REJ hdop=.. sats=.. fix=.." format is used as diagnostic context on the
  existing rejection reasons (Kalman error, distance-diff, too-small-change),
  not a new quality gate.
- Receiver model/firmware version is not queried live from the GPS module
  (would need new PMTK605/705 handling); the boot record reports the
  configured GNSS mode instead.
- Free disk space is not logged: FatFs f_getfree() is disabled via
  FF_FS_MINIMIZE in this build and enabling it costs flash.
  Battery percentage in the status line is a rough linear 3.3-4.2V estimate;
  there's no calibrated full-scale reference in this codebase.
k4be 1 周之前
父节点
当前提交
ef4eccf117
共有 12 个文件被更改,包括 597 次插入 和 55 次删除
  1. 33 2
      gps-test-tool/main.h
  2. 6 1
      soft/Makefile
  3. 4 4
      soft/display.c
  4. 115 13
      soft/gpx.c
  5. 81 26
      soft/main.c
  6. 31 3
      soft/main.h
  7. 2 0
      soft/menu.c
  8. 36 4
      soft/nmea.c
  9. 31 0
      soft/settings.c
  10. 12 2
      soft/settings.h
  11. 223 0
      soft/syslog.c
  12. 23 0
      soft/syslog.h

+ 33 - 2
gps-test-tool/main.h

@@ -161,6 +161,22 @@
 #define CONFFLAG_DISABLE_FILTERS 0x01
 #define CONFFLAG_ENABLE_SBAS     0x02
 #define CONFFLAG_AUTO_PAUSE      0x04
+#define CONFFLAG_VERBOSE_LOG     0x08
+
+/* 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
@@ -186,14 +202,24 @@ struct config_s {
     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) */
-    unsigned long int elevation_loss;   /* dm (decimeters, 0.1m resolution) */
+    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 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 */
     time_t time_start;
     time_t current_pause_start;
@@ -238,6 +264,11 @@ void gps_initialize(void);
 void add_distance(float dist);
 void add_elevation(float ele_change);
 
+/* System log stubs for the PC build: the structured system log (soft/syslog.c)
+ * is not part of this test tool, but gpx.c calls into it. */
+static inline void log_reject(unsigned char reason) { System.rejected_count++; (void)reason; }
+static inline void log_jump(unsigned char is_alt, float meters) { (void)is_alt; (void)meters; }
+
 /* Stub functions for PC build */
 #ifdef PC_BUILD
 static inline void set_timer(int timer, int val) { (void)timer; (void)val; }

+ 6 - 1
soft/Makefile

@@ -1,8 +1,12 @@
 ### Project name (also used for output file name)
 PROJECT	= glg
 
+### Firmware version string, embedded in the boot log record
+FW_VERSION_NUM = 1.0.0
+GIT_HASH := $(shell git describe --always --dirty --abbrev=8 2>/dev/null || echo unknown)
+
 ### Source files and search directory
-CSRC    = main.c uart0.c uart1.c ff.c mmc.c 1wire.c ds18b20.c expander.c I2C.c xprintf.c gpx.c ffunicode.c display.c working_modes.o timec.o nmea.o settings.o menu.o
+CSRC    = main.c uart0.c uart1.c ff.c mmc.c 1wire.c ds18b20.c expander.c I2C.c xprintf.c gpx.c ffunicode.c display.c working_modes.o timec.o nmea.o settings.o menu.o syslog.o
 ASRC    = stime.S
 VPATH   =
 
@@ -23,6 +27,7 @@ LIBS	=
 LIBDIRS	=
 INCDIRS	=
 DEFS	= F_CPU=7372800
+DEFS	+= 'FW_VERSION="$(FW_VERSION_NUM)+$(GIT_HASH)"'
 # DEFS	+= LEDR_UART
 ADEFS	=
 

+ 4 - 4
soft/display.c

@@ -277,10 +277,10 @@ void disp_pause_time(void) {
 }
 
 void disp_speed(void) {
-	unsigned int time = get_logging_time();
-	disp_line1(PSTR("Predkosc:"));
-	if (time) {
-		xsprintf(disp.line2, PSTR("%.2f km/h"), (float)System.distance / (float)(time) * 0.036); /* convert seconds to hours */
+	unsigned int avg = get_avg_speed_x100();
+	disp_line1(PSTR("Sr. predkosc:"));
+	if (avg) {
+		xsprintf(disp.line2, PSTR("%.2f km/h"), (float)avg / 100.0);
 	} else {
 		disp_line2(PSTR("nieznana"));
 	}

+ 115 - 13
soft/gpx.c

@@ -31,6 +31,14 @@ struct kalman_s {
 #define AVG_COUNT	3
 #define MIN_DIST_DELTA	2.0
 
+/* Elevation dead-band + smoothing, applied to the filtered ascent/descent totals */
+#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) */
+#define POS_JUMP_THRESHOLD	100.0	/* meters between consecutive raw fixes */
+#define ALT_JUMP_THRESHOLD	50.0	/* meters between consecutive raw fixes */
+
 struct prev_points_s {
 	struct location_s data[PREV_POINTS_LENGTH];
 	unsigned char start;
@@ -43,6 +51,13 @@ struct avg_store_s {
 	time_t time;
 };
 
+struct elevation_s {
+	unsigned char initialized;
+	float smoothed_alt;
+	float baseline;
+	time_t last_time;
+};
+
 static struct gpx_s {
 	struct prev_points_s prev_points;
 	unsigned char avg_count;
@@ -52,11 +67,14 @@ static struct gpx_s {
 	struct location_s last_saved;
 	struct location_s last_distance_point; /* Last accepted point for distance calculation */
 	struct kalman_s kalman[2];
+	struct elevation_s elevation;
 } gpx;
 
 float kalman_predict(struct kalman_s *k, float data);
 void kalman_init(struct kalman_s *k);
 float distance(struct location_s *pos1, struct location_s *pos2);
+float elevation_process(float alt, time_t time);
+void add_elevation_filtered(float amount, unsigned char is_gain);
 
 void prev_points_append(struct location_s *new){
 	gpx.prev_points.data[(gpx.prev_points.start + gpx.prev_points.count)%PREV_POINTS_LENGTH] = *new;
@@ -90,6 +108,7 @@ unsigned char gpx_init(FIL *file) {
 	gpx.last_distance_point.lon = 0;
 	gpx.last_distance_point.lat = 0;
 	gpx.last_distance_point.time = 0;
+	gpx.elevation.initialized = 0;
 
 	gpx.paused = 1; /* make it add a <trkseg> tag */
 
@@ -187,19 +206,26 @@ void gpx_process_point(struct location_s *loc, FIL *file){
 	filtered_loc.alt = loc->alt;
 
 	if (get_flag(CONFFLAG_DISABLE_FILTERS)) {
-		/* Write unfiltered data to GPX, but calculate distance from filtered data */
-		xputs_P(PSTR("Write with filters disabled\r\n"));
+		/* Write unfiltered data to GPX, but always calculate distance/elevation from filtered data */
+		if (get_flag(CONFFLAG_VERBOSE_LOG))
+			xputs_P(PSTR("Write with filters disabled\r\n"));
 
 		gpx_write(loc, file);
+		System.points_written++;
 
 		/* Calculate distance and elevation from filtered points */
 		if (gpx.last_distance_point.lat != 0) {
 			float ele_change;
 			dist = distance(&gpx.last_distance_point, &filtered_loc);
+			if (dist > POS_JUMP_THRESHOLD)
+				log_jump(0, dist);
 			add_distance(dist);
 			ele_change = filtered_loc.alt - gpx.last_distance_point.alt;
+			if (fabs(ele_change) > ALT_JUMP_THRESHOLD)
+				log_jump(1, ele_change);
 			add_elevation(ele_change);
 		}
+		elevation_process(filtered_loc.alt, filtered_loc.time);
 		gpx.last_distance_point = filtered_loc;
 
 	} else {
@@ -208,7 +234,9 @@ void gpx_process_point(struct location_s *loc, FIL *file){
 		lon_err = fabs(loc->lon - lon_est);
 	//	xprintf(PSTR("lat_err: %e, lon_err: %e, limit: %e\r\n"), lat_err, lon_err, (float)KALMAN_ERR_MAX);
 		if(lat_err > KALMAN_ERR_MAX || lon_err > KALMAN_ERR_MAX){
-			xputs_P(PSTR("KALMAN REJECT\r\n"));
+			if (get_flag(CONFFLAG_VERBOSE_LOG))
+				xputs_P(PSTR("KALMAN REJECT\r\n"));
+			log_reject(REJECT_REASON_KALMAN);
 			return;
 		}
 
@@ -218,27 +246,36 @@ 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));
-			xprintf(PSTR("New distance: %fm\r\n"), dist32);
+			if (get_flag(CONFFLAG_VERBOSE_LOG))
+				xprintf(PSTR("New distance: %fm\r\n"), dist32);
 			if(dist34 > dist12 && dist32 > dist12){
-				xputs_P(PSTR("DISTANCE DIFF REJECT\r\n"));
+				if (get_flag(CONFFLAG_VERBOSE_LOG))
+					xputs_P(PSTR("DISTANCE DIFF REJECT\r\n"));
+				log_reject(REJECT_REASON_DISTDIFF);
 				return;
 			}
+			if (dist32 > POS_JUMP_THRESHOLD)
+				log_jump(0, dist32);
 			ptr = prev_points_get(PREV_POINTS_LENGTH - 2);
 		} else {
 			if(gpx.prev_points.count >= PREV_POINTS_LENGTH-2){
 				ptr = prev_points_get(gpx.prev_points.count - 2);
-				xputs_P(PSTR("NEW\r\n"));
+				if (get_flag(CONFFLAG_VERBOSE_LOG))
+					xputs_P(PSTR("NEW\r\n"));
 			} else {
 				return;
 			}
 		}
 
 		if(distance(&gpx.last_saved, ptr) < MIN_DIST_DELTA){
-			xputs_P(PSTR("Too small position change REJECT\r\n"));
+			if (get_flag(CONFFLAG_VERBOSE_LOG))
+				xputs_P(PSTR("Too small position change REJECT\r\n"));
+			log_reject(REJECT_REASON_MINDIST);
 			return;
 		}
 
-		xputs_P(PSTR("ACCEPT\r\n"));
+		if (get_flag(CONFFLAG_VERBOSE_LOG))
+			xputs_P(PSTR("ACCEPT\r\n"));
 
 		/* Calculate distance and elevation for accepted point */
 		if (gpx.last_distance_point.lat != 0) {
@@ -246,8 +283,11 @@ void gpx_process_point(struct location_s *loc, FIL *file){
 			dist = distance(&gpx.last_distance_point, ptr);
 			add_distance(dist);
 			ele_change = ptr->alt - gpx.last_distance_point.alt;
+			if (fabs(ele_change) > ALT_JUMP_THRESHOLD)
+				log_jump(1, ele_change);
 			add_elevation(ele_change);
 		}
+		elevation_process(ptr->alt, ptr->time);
 		gpx.last_distance_point = *ptr;
 
 		gpx.avg_store.lat += ptr->lat;
@@ -259,12 +299,14 @@ void gpx_process_point(struct location_s *loc, FIL *file){
 			nloc.lat = gpx.avg_store.lat / AVG_COUNT;
 			nloc.lon = gpx.avg_store.lon / AVG_COUNT;
 			nloc.time = gpx.avg_store.time;
+			nloc.alt = gpx.elevation.smoothed_alt; /* filtered (smoothed) altitude, for the filtered GPX write */
 			gpx.avg_count = 0;
 			gpx.avg_store.lat = 0;
 			gpx.avg_store.lon = 0;
 			gpx.avg_store.time = 0;
 			gpx.last_saved = nloc;
 			gpx_write(&nloc, file);
+			System.points_written++;
 		}
 	}
 	if (System.time_start == 0)
@@ -316,19 +358,79 @@ void add_distance(float dist) {
 	unsigned char paused = System.tracking_paused || System.tracking_auto_paused;
 	if (!paused)
 		System.distance += (dist+0.005)*100.0;
-	xprintf(PSTR("Distance: %f m; sum: %f m\r\n"), dist, System.distance/100.0);
+	if (get_flag(CONFFLAG_VERBOSE_LOG))
+		xprintf(PSTR("Distance: %.2f m; sum: %.2f m\r\n"), (double)dist, (double)System.distance/100.0);
 }
 
+/* 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;
 	if (!paused) {
 		if (ele_change > 0) {
-			System.elevation_gain += (ele_change+0.05)*10.0;
+			System.elevation_gain_raw += (ele_change+0.05)*10.0;
 		} else if (ele_change < 0) {
-			System.elevation_loss += (-ele_change+0.05)*10.0;
+			System.elevation_loss_raw += (-ele_change+0.05)*10.0;
+		}
+	}
+	if (get_flag(CONFFLAG_VERBOSE_LOG))
+		xprintf(PSTR("Elevation change: %.1f m; raw gain: %.1f m, raw loss: %.1f m\r\n"),
+			(double)ele_change, (double)System.elevation_gain_raw/10.0, (double)System.elevation_loss_raw/10.0);
+}
+
+/* Filtered (dead-band + smoothed) gain/loss, used for display, GPX ascent stats
+ * and the periodic status line/session summary. */
+void add_elevation_filtered(float amount, unsigned char is_gain) {
+	unsigned long int dm = (unsigned long int)(amount*10.0 + 0.5);
+	if (is_gain)
+		System.elevation_gain += dm;
+	else
+		System.elevation_loss += dm;
+}
+
+/* Exponential smoothing (~30s time constant) followed by a 5m dead-band on the
+ * 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 long int alt_dm;
+	float delta;
+
+	if (!gpx.elevation.initialized) {
+		gpx.elevation.smoothed_alt = alt;
+		gpx.elevation.baseline = alt;
+		gpx.elevation.last_time = time;
+		gpx.elevation.initialized = 1;
+	} else {
+		float dt = (float)(time - gpx.elevation.last_time);
+		float alpha;
+		if (dt <= 0)
+			dt = 1.0;
+		gpx.elevation.last_time = time;
+		alpha = dt / (ELEV_SMOOTH_TAU + dt);
+		gpx.elevation.smoothed_alt += alpha * (alt - gpx.elevation.smoothed_alt);
+	}
+
+	if (gpx.elevation.smoothed_alt > 0) {
+		alt_dm = (unsigned long int)(gpx.elevation.smoothed_alt*10.0 + 0.5);
+		if (alt_dm > System.alt_max)
+			System.alt_max = alt_dm;
+	}
+
+	if (paused) {
+		/* Discard the dead-band reference drift accumulated while paused,
+		 * the same way distance/raw elevation data is discarded when paused. */
+		gpx.elevation.baseline = gpx.elevation.smoothed_alt;
+	} else {
+		delta = gpx.elevation.smoothed_alt - gpx.elevation.baseline;
+		if (delta > ELEV_DEADBAND) {
+			add_elevation_filtered(delta - ELEV_DEADBAND, 1);
+			gpx.elevation.baseline = gpx.elevation.smoothed_alt - ELEV_DEADBAND;
+		} else if (delta < -ELEV_DEADBAND) {
+			add_elevation_filtered(-delta - ELEV_DEADBAND, 0);
+			gpx.elevation.baseline = gpx.elevation.smoothed_alt + ELEV_DEADBAND;
 		}
 	}
-	xprintf(PSTR("Elevation change: %f m; gain: %f m, loss: %f m\r\n"),
-		ele_change, System.elevation_gain/10.0, System.elevation_loss/10.0);
+	return gpx.elevation.smoothed_alt;
 }
 

+ 81 - 26
soft/main.c

@@ -19,6 +19,18 @@ char Line[100];				/* Line buffer */
 time_t utc;					/* current time */
 struct location_s location;
 struct auto_pause_s auto_pause;
+volatile unsigned long int uptime_ms; /* milliseconds since MCU startup, for the system log */
+
+/* Capture the reset cause before it is cleared by ioinit()/wdt_disable(),
+ * and disable the watchdog immediately: otherwise a watchdog-triggered
+ * reset with a short timeout could loop forever before main() re-enables it. */
+volatile unsigned char reset_cause __attribute__((section(".noinit")));
+void capture_reset_cause(void) __attribute__((naked, used, section(".init3")));
+void capture_reset_cause(void) {
+	reset_cause = MCUSR;
+	MCUSR = 0;
+	wdt_disable();
+}
 
 void start_bootloader(void) {
 	typedef void (*do_reboot_t)(void);
@@ -51,7 +63,9 @@ ISR(TIMER1_COMPA_vect)
 	unsigned char i;
 	unsigned char k;
 	static unsigned char oldk;
-	
+
+	uptime_ms += 10; /* this ISR fires at 100Hz */
+
 	for(i=0; i<sizeof(System.timers)/sizeof(unsigned int); i++){ // decrement every variable from timers struct unless it's already zero
 		ctimer = ((unsigned int *)&System.timers) + i;
 		if(*ctimer)
@@ -176,7 +190,8 @@ struct {
 
 void log_put(int c){
 	UINT bw;
-	uart1_put(c);
+	if (System.conf.uart_log_mode != UART_LOG_MODE_NMEA)
+		uart1_put(c);
 	logbuf.buf[logbuf.len++] = c;
 	if (logbuf.len >= LOG_SIZE && (FLAGS & F_FILEOPEN)) {
 		if(!f_write(&system_log, logbuf.buf, logbuf.len, &bw))
@@ -235,6 +250,8 @@ void ioinit (void)
 void close_files(unsigned char flush_logs) {
 	UINT bw;
 	if (FLAGS & F_FILEOPEN) {
+		if (flush_logs)
+			log_session_summary(1);
 		if (f_close(&gps_log)) {
 			System.status = STATUS_FILE_CLOSE_ERROR;
 			System.global_error |= ERROR_FILE_CLOSE;
@@ -261,11 +278,13 @@ static inline void auto_unpause(void) {
 	if (!System.tracking_auto_paused)
 		return;
 	System.tracking_auto_paused = 0;
+	log_pause_event(0);
 	beep(50, 4);
 }
 
 static inline void auto_pause_activate(void) {
 	System.tracking_auto_paused = 1;
+	log_pause_event(1);
 	beep(50, 3);
 }
 
@@ -304,11 +323,25 @@ void reset_counters(void) {
 	System.distance = 0;
 	System.elevation_gain = 0;
 	System.elevation_loss = 0;
+	System.elevation_gain_raw = 0;
+	System.elevation_loss_raw = 0;
+	System.alt_max = 0;
+	System.speed_accum_x100 = 0;
+	System.speed_sample_count = 0;
+	System.points_written = 0;
+	System.rejected_count = 0;
+	System.bat_volt_min = 99.0;
 	System.time_start = 0;
 	System.current_pause_start = 0;
 	System.pause_time = 0;
 }
 
+unsigned int get_avg_speed_x100(void) {
+	if (!System.speed_sample_count)
+		return 0;
+	return System.speed_accum_x100 / System.speed_sample_count;
+}
+
 time_t get_pause_time(void) {
 	time_t res = System.pause_time;
 	if (System.current_pause_start < System.time_start)
@@ -333,11 +366,13 @@ __flash const char __open_msg[] = "Open %s\r\n";
 int main (void)
 {
 	UINT bw, len;
-	static struct tm ct;
 	time_t tmp_utc, localtime;
 	FRESULT res;
 	unsigned char prev_status;
 	unsigned char already_logging = 0;
+	static unsigned char fix_ok_prev = 0;
+	static unsigned char sbas_prev = 0xFF; /* 0xFF: not yet known, suppress the first (non-)transition */
+	static unsigned long int last_summary_uptime = 0;
 
 	ioinit();
 	xdev_out(log_put);
@@ -345,9 +380,11 @@ int main (void)
 	disp_init();
 	display_event(DISPLAY_EVENT_STARTUP);
 	settings_load();
-	
+	reset_counters();
+	log_boot_record();
+
 	menu_push(default_menu);
-	
+
 	for (;;) {
 		wdt_reset();
 		if (FLAGS & (F_POWEROFF | F_LVD)) {
@@ -366,7 +403,7 @@ int main (void)
 		}
 
 		display_event(DISPLAY_EVENT_INITIALIZED);
-		xprintf(PSTR("LOOP err=%u\r\n"), (unsigned int)System.status);
+		log_loop_status(System.status);
 		utc = 0;
 		localtime = 0;
 		prev_status = System.status;
@@ -405,7 +442,9 @@ int main (void)
 		uart0_init();	/* Enable UART */
 		_delay_ms(300);	/* Delay */
 		System.gps_initialized = 0;
-		
+		gps_powered_on();
+
+
 		if (!already_logging && !get_flag(CONFFLAG_LOGGING_AFTER_BOOT)) {
 			tracking_pause(TRACKING_PAUSE_CMD_PAUSE, 0);
 		}
@@ -430,8 +469,6 @@ int main (void)
 			else
 				LEDW_OFF();
 
-			if (!(FLAGS & F_GPSOK))
-				xputs_P(PSTR("Waiting for GPS\r\n"));
 			len = get_line(Line, sizeof Line);	/* Receive a line from GPS receiver */
 			if (!len){
 				if (FLAGS & (F_LVD | F_POWEROFF))
@@ -442,28 +479,44 @@ int main (void)
 			tmp_utc = gps_parse(Line);
 			if (tmp_utc) {
 				localtime = tmp_utc + local_time_diff(tmp_utc) * 3600L;	/* Local time */
-				ct = *gmtime(&localtime);
-				if (timer_expired(system_log)) {
-					set_timer(system_log, 5000);
-					xprintf(PSTR("Time: %u.%02u.%04u %u:%02u:%02u\r\n"), ct.tm_mday, ct.tm_mon + 1, ct.tm_year+1900, ct.tm_hour, ct.tm_min, ct.tm_sec);
-					xprintf(PSTR("Bat volt: %.3f\r\n"), System.bat_volt);
-					if (System.temperature_ok)
-						xprintf(PSTR("Temp: %.2f\r\n"), System.temperature);
-					else
-						xputs_P(PSTR("Temperature unknown\r\n"));
-					if (System.sbas)
-						xputs_P(PSTR("SBAS (DGPS) active\r\n"));
-					else
-						xputs_P(PSTR("SBAS inactive\r\n"));
-					xputs_P(PSTR("Using GNSS: "));
-					xputs_P(gnss_names[System.conf.gnss_mode]);
-					xputs_P(PSTR("\r\n"));
-				}
 				LEDG_ON();
 				_delay_ms(2);
 				LEDG_OFF();
 			}
 
+			/* Battery low-water mark, for the session summary */
+			if (System.bat_volt > 0 && System.bat_volt < System.bat_volt_min)
+				System.bat_volt_min = System.bat_volt;
+
+			/* Fix acquired/lost events (a "fix" requires both a valid GPS status and enough satellites) */
+			{
+				unsigned char fix_ok_now = (FLAGS & F_GPSOK) && !System.sat_count_low;
+				if (fix_ok_now != fix_ok_prev) {
+					if (fix_ok_now)
+						log_fix_event();
+					else
+						log_fix_lost();
+					fix_ok_prev = fix_ok_now;
+				}
+			}
+
+			/* SBAS/DGPS on-off transitions */
+			if (System.sbas != sbas_prev) {
+				if (sbas_prev != 0xFF)
+					log_sbas_transition();
+				sbas_prev = System.sbas;
+			}
+
+			if (timer_expired(status_log)) {
+				set_timer(status_log, 60000);
+				log_status_line();
+			}
+
+			if (get_uptime_ms() - last_summary_uptime >= 3600000UL) {
+				last_summary_uptime = get_uptime_ms();
+				log_session_summary(0);
+			}
+
 			if (FLAGS & F_FILEOPEN) {
 				f_write(&gps_log, Line, len-1, &bw);
 				if (bw != len-1) {
@@ -541,6 +594,8 @@ int main (void)
 				wdt_enable(WDTO_4S);
 				FLAGS |= F_FILEOPEN;
 				System.status = STATUS_OK;
+				set_timer(status_log, 60000);
+				last_summary_uptime = get_uptime_ms();
 				beep(50, System.tracking_paused?8:2);		/* Two beeps. Start logging. */
 				display_event(DISPLAY_EVENT_FILE_OPEN);
 				continue;

+ 31 - 3
soft/main.h

@@ -167,11 +167,16 @@ struct timers {
 	unsigned int owire;
 	unsigned int beep;
 	unsigned int recv_timeout;
-	unsigned int system_log;
+	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;
@@ -183,12 +188,22 @@ struct system_s {
 	unsigned char keypress;
 	unsigned char working_mode;
 	unsigned long int distance; // cm
-	unsigned long int elevation_gain; // dm (decimeters, 0.1m resolution)
-	unsigned long int elevation_loss; // dm (decimeters, 0.1m resolution)
+	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 int rejected_count; // count of rejected candidate points this session
+	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;
@@ -217,6 +232,8 @@ struct auto_pause_s {
 extern volatile struct system_s System;
 extern struct location_s location;
 extern time_t utc;
+extern volatile unsigned long int uptime_ms;
+extern volatile unsigned char reset_cause;
 
 /* Project includes - headers that depend on struct definitions above */
 #include "gpx.h"
@@ -225,6 +242,7 @@ extern time_t utc;
 #include "timec.h"
 #include "nmea.h"
 #include "menu.h"
+#include "syslog.h"
 
 static inline void atomic_set_uint(volatile unsigned int *volatile data, unsigned int value) __attribute__((always_inline));
 static inline void atomic_set_uint(volatile unsigned int *volatile data, unsigned int value){
@@ -240,6 +258,15 @@ static inline unsigned int atomic_get_uint(volatile unsigned int *volatile data)
 	sei();
 	return ret;
 }
+static inline unsigned long int atomic_get_ulong(volatile unsigned long int *volatile data) __attribute__((always_inline));
+static inline unsigned long int atomic_get_ulong(volatile unsigned long int *volatile data){
+	unsigned long int ret;
+	cli();
+	ret = *data;
+	sei();
+	return ret;
+}
+#define get_uptime_ms() atomic_get_ulong(&uptime_ms)
 
 #define set_timer(timer,val) set_timer_counts(timer, ms(val))
 #define set_timer_counts(timer,val) atomic_set_uint(&System.timers.timer, val)
@@ -254,4 +281,5 @@ void sleep(void);
 void reset_counters(void);
 time_t get_pause_time(void);
 unsigned int get_logging_time(void);
+unsigned int get_avg_speed_x100(void);
 

+ 2 - 0
soft/menu.c

@@ -62,6 +62,7 @@ void settings_change_bool(struct menu_pos pos, unsigned char k) {
 		set_flag(index, val);
 		if (pos.changed != NULL)
 			pos.changed();
+		log_config();
 	}
 }
 
@@ -83,6 +84,7 @@ void settings_change_u8(struct menu_pos pos, unsigned char k) {
 		System.conf.conf_u8[index] = val;
 		if (pos.changed != NULL)
 			pos.changed();
+		log_config();
 	}
 }
 

+ 36 - 4
soft/nmea.c

@@ -41,13 +41,14 @@ uint16_t get_line (		/* 0:line incomplete or timed out, >0: Number of bytes rece
 			break;	/* EOL */
 		}
 		buff[i++] = c;
-		uart1_put(c);
+		if (System.conf.uart_log_mode != UART_LOG_MODE_SYSTEM)
+			uart1_put(c);
 		if (i >= sz_buf - 3) /* keep 3 bytes for terminating character */
 			i = 0;	/* Buffer overflow (abort this line) */
 	}
 	ret_len = i;
 	i = 0;
-	if (ret_len > 0) {
+	if (ret_len > 0 && System.conf.uart_log_mode != UART_LOG_MODE_SYSTEM) {
 		uart1_put('\r');
 		uart1_put('\n');
 	}
@@ -202,14 +203,41 @@ static void gp_gga_parse(const char *str) {
 static void gp_vtg_parse(const char *str) {
 	const char *p;
 	double speed;
-	
+	unsigned char paused = System.tracking_paused || System.tracking_auto_paused;
+
 	p = gp_col(str, 9);
 	if (*p == 'N') /* Not valid */
 		return;
-	
+
 	p = gp_col(str, 7); /* speed in km/h */
 	xatof(&p, &speed);
 	System.speed = speed+0.5;
+
+	/* Average speed: fixed-point accumulation of instantaneous samples,
+	 * excluding time spent paused, instead of dividing distance by time. */
+	if (!paused) {
+		System.speed_accum_x100 += (unsigned long int)(speed*100.0 + 0.5);
+		System.speed_sample_count++;
+	}
+}
+
+static void gp_gsa_parse(const char *str) {
+	const char *p;
+	double hdop;
+
+	p = gp_col(str, 2); /* fix type: 1 no fix, 2 2D, 3 3D */
+	if (*p == '2')
+		System.fix_type = FIX_TYPE_2D;
+	else if (*p == '3')
+		System.fix_type = FIX_TYPE_3D;
+	else
+		System.fix_type = FIX_TYPE_UNKNOWN;
+
+	p = gp_col(str, 16); /* HDOP */
+	if (p && *p) {
+		xatof(&p, &hdop);
+		System.hdop_x100 = (unsigned int)(hdop*100.0 + 0.5);
+	}
 }
 
 /*$PMTK355*31<CR><LF>
@@ -292,6 +320,10 @@ time_t gps_parse(const char *str) {	/* Get all required data from NMEA sentences
 		gp_vtg_parse(str);
 		return 0;
 	}
+	if (!gp_comp(str, PSTR("GPGSA")) || !gp_comp(str, PSTR("GNGSA")) || !gp_comp(str, PSTR("BDGSA")) || !gp_comp(str, PSTR("GAGSA"))) {
+		gp_gsa_parse(str);
+		return 0;
+	}
 	if (!System.gps_initialized && !gp_comp(str, PSTR("PMTK011"))) {
 		gps_initialize();
 		return 0;

+ 31 - 0
soft/settings.c

@@ -10,6 +10,7 @@ const __flash unsigned char limits_max_u8[] = {
 	[CONF_U8_AUTO_PAUSE_DIST] = 100,
 	[CONF_U8_MIN_SATS] = 12,
 	[CONF_U8_AUTO_PAUSE_SPEED] = 20,
+	[CONF_U8_UART_LOG_MODE] = UART_LOG_MODE_SYSTEM,
 };
 
 const __flash unsigned char limits_min_u8[] = {
@@ -19,6 +20,7 @@ const __flash unsigned char limits_min_u8[] = {
 	[CONF_U8_AUTO_PAUSE_DIST] = 2,
 	[CONF_U8_MIN_SATS] = 4,
 	[CONF_U8_AUTO_PAUSE_SPEED] = 0,
+	[CONF_U8_UART_LOG_MODE] = UART_LOG_MODE_MIXED,
 };
 
 const __flash unsigned char defaults_u8[] = {
@@ -28,6 +30,7 @@ const __flash unsigned char defaults_u8[] = {
 	[CONF_U8_AUTO_PAUSE_DIST] = 10,
 	[CONF_U8_MIN_SATS] = 5,
 	[CONF_U8_AUTO_PAUSE_SPEED] = 3,
+	[CONF_U8_UART_LOG_MODE] = UART_LOG_MODE_MIXED,
 };
 
 unsigned char settings_load(void) { /* 0 - ok, 1 - error */
@@ -99,6 +102,20 @@ void display_current_gnss_mode(void) {
 	display_gnss_mode(System.conf.gnss_mode);
 }
 
+__flash const char uart_log_mode_mixed[] = "NMEA+system";
+__flash const char uart_log_mode_nmea[] = "Tylko NMEA";
+__flash const char uart_log_mode_system[] = "Tylko system";
+
+__flash const char *uart_log_mode_names[] = {
+	uart_log_mode_mixed,
+	uart_log_mode_nmea,
+	uart_log_mode_system,
+};
+
+void display_current_uart_log_mode(void) {
+	strcpy_P(disp.line2, uart_log_mode_names[System.conf.uart_log_mode]);
+}
+
 /* SETTINGS ITEMS */
 
 __flash const char _msg_disable_filters[] = "Nie filtruj";
@@ -114,6 +131,8 @@ __flash const char _msg_auto_pause_dist[] = "Autopauza odleg";
 __flash const char _msg_auto_pause_speed[] = "Autopau. predk.";
 __flash const char _msg_min_sats[] = "Minimum satelit";
 __flash const char _msg_reset_on_new_file[] = "Zeruj dystans";
+__flash const char _msg_uart_log_mode[] = "Log na UART";
+__flash const char _msg_verbose_log[] = "Log szczegolowy";
 
 __flash const struct menu_pos settings_menu_list[] = {
 	{
@@ -186,6 +205,18 @@ __flash const struct menu_pos settings_menu_list[] = {
 		.name = _msg_logging_after_boot,
 		.index = CONFFLAG_LOGGING_AFTER_BOOT,
 	},
+	{
+		.type = MENU_TYPE_SETTING_U8,
+		.display_type = MENU_DISPLAY_TYPE_NAME_FUNCTION,
+		.name = _msg_uart_log_mode,
+		.index = CONF_U8_UART_LOG_MODE,
+		.display = display_current_uart_log_mode,
+	},
+	{
+		.type = MENU_TYPE_SETTING_BOOL,
+		.name = _msg_verbose_log,
+		.index = CONFFLAG_VERBOSE_LOG,
+	},
 };
 
 __flash const struct menu_struct settings_menu = {

+ 12 - 2
soft/settings.h

@@ -9,8 +9,14 @@
 #define CONF_U8_AUTO_PAUSE_DIST	3
 #define CONF_U8_MIN_SATS	4
 #define CONF_U8_AUTO_PAUSE_SPEED	5
+#define CONF_U8_UART_LOG_MODE	6
 
-#define CONF_U8_LAST		5
+#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 */
 
 /* flags list - max 31 */
 #define CONFFLAG_DISABLE_FILTERS	0
@@ -18,8 +24,9 @@
 #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				4
+#define CONFFLAG_LAST				5
 
 /* GNSS modes */
 #define GNSS_MODE_GPS_GLONASS_GALILEO	0
@@ -39,6 +46,7 @@ struct config_s {
 			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];
@@ -47,6 +55,7 @@ struct config_s {
 extern const __flash unsigned char limits_max_u8[];
 extern const __flash unsigned char limits_min_u8[];
 extern __flash const char *gnss_names[];
+extern __flash const char *uart_log_mode_names[];
 extern __flash const struct menu_struct settings_menu;
 
 unsigned char settings_load(void); /* 0 - ok, 1 - error */
@@ -57,4 +66,5 @@ void settings_display_and_modify_u8(unsigned char mindex, unsigned char k);
 unsigned char get_flag(unsigned char index);
 void set_flag(unsigned char index, unsigned char val);
 void settings_bool_disp_default(unsigned char val);
+void display_current_uart_log_mode(void);
 

+ 223 - 0
soft/syslog.c

@@ -0,0 +1,223 @@
+#include "main.h"
+
+#ifndef FW_VERSION
+#define FW_VERSION "unknown"
+#endif
+
+__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_names[] = {
+	[REJECT_REASON_KALMAN] = reject_reason_kalman,
+	[REJECT_REASON_DISTDIFF] = reject_reason_distdiff,
+	[REJECT_REASON_MINDIST] = reject_reason_mindist,
+};
+
+__flash const char status_no_power[] = "no power";
+__flash const char status_no_disk[] = "no card";
+__flash const char status_no_gps[] = "waiting for GPS";
+__flash const char status_ok[] = "ok";
+__flash const char status_disk_error[] = "card mount failed";
+__flash const char status_file_write_error[] = "write failed";
+__flash const char status_file_sync_error[] = "sync failed";
+__flash const char status_file_close_error[] = "close failed";
+__flash const char status_file_open_error[] = "open failed";
+__flash const char status_unknown[] = "unknown";
+
+__flash const char *status_names[] = {
+	status_no_power,
+	status_no_disk,
+	status_no_gps,
+	status_ok,
+	status_disk_error,
+	status_file_write_error,
+	status_file_sync_error,
+	status_file_close_error,
+	status_file_open_error,
+};
+
+static struct {
+	unsigned char reason;
+	unsigned char count;
+	unsigned int hdop_x100;
+	unsigned char sats;
+	unsigned char fix_type;
+} reject_coalesce;
+
+static unsigned char fix_had_first;
+static unsigned long int gps_on_uptime;
+
+void log_prefix(void) {
+	if (utc)
+		xprintf(PSTR("%s up=%lu "), get_iso_time(utc, 0), get_uptime_ms());
+	else
+		xprintf(PSTR("NOTIME up=%lu "), get_uptime_ms());
+}
+
+void log_config(void) {
+	log_prefix();
+	xputs_P(PSTR("CFG filt="));
+	xputs_P(get_flag(CONFFLAG_DISABLE_FILTERS) ? PSTR("off") : PSTR("on"));
+	xprintf(PSTR(" skip=%u gate_sats=%u pause_t=%us pause_d=%um pause_v=%ukmh sbas_search="),
+		(unsigned int)System.conf.skip_points, (unsigned int)System.conf.min_sats,
+		(unsigned int)System.conf.auto_pause_time, (unsigned int)System.conf.auto_pause_dist,
+		(unsigned int)System.conf.auto_pause_speed);
+	xputs_P(get_flag(CONFFLAG_ENABLE_SBAS) ? PSTR("on") : PSTR("off"));
+	xputs_P(PSTR(" auto_pause="));
+	xputs_P(get_flag(CONFFLAG_AUTO_PAUSE) ? PSTR("on") : PSTR("off"));
+	xputs_P(PSTR(" gnss="));
+	xputs_P(gnss_names[System.conf.gnss_mode]);
+	xputs_P(PSTR(" uart_log="));
+	xputs_P(uart_log_mode_names[System.conf.uart_log_mode]);
+	xputs_P(PSTR(" verbose="));
+	xputs_P(get_flag(CONFFLAG_VERBOSE_LOG) ? PSTR("on") : PSTR("off"));
+	xputs_P(PSTR(" reset_new_file="));
+	xputs_P(get_flag(CONFFLAG_RESET_ON_NEW_FILE) ? PSTR("on") : PSTR("off"));
+	xputs_P(PSTR("\r\n"));
+}
+
+void log_boot_record(void) {
+	log_prefix();
+	xputs_P(PSTR("BOOT fw="));
+	xputs_P(PSTR(FW_VERSION));
+	xputs_P(PSTR(" reset="));
+	if (reset_cause & _BV(WDRF))
+		xputs_P(PSTR("WDT"));
+	else if (reset_cause & _BV(BORF))
+		xputs_P(PSTR("BOR"));
+	else if (reset_cause & _BV(EXTRF))
+		xputs_P(PSTR("EXT"));
+	else if (reset_cause & _BV(PORF))
+		xputs_P(PSTR("POR"));
+	else
+		xputs_P(PSTR("UNKNOWN"));
+	xputs_P(PSTR(" rx="));
+	xputs_P(gnss_names[System.conf.gnss_mode]);
+	xputs_P(PSTR("\r\n"));
+	log_config();
+}
+
+void log_loop_status(unsigned char status) {
+	log_prefix();
+	xprintf(PSTR("LOOP err=%u ("), (unsigned int)status);
+	if (status < sizeof(status_names)/sizeof(status_names[0]))
+		xputs_P(status_names[status]);
+	else
+		xputs_P(status_unknown);
+	xputs_P(PSTR(")\r\n"));
+}
+
+void gps_powered_on(void) {
+	gps_on_uptime = get_uptime_ms();
+	fix_had_first = 0;
+}
+
+void log_fix_event(void) {
+	log_prefix();
+	xprintf(PSTR("FIX %uD"), (unsigned int)System.fix_type);
+	xputs_P(System.sbas ? 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);
+		fix_had_first = 1;
+	} else {
+		xputs_P(PSTR(" regained"));
+	}
+	xprintf(PSTR(" sats=%u hdop=%.2f\r\n"), (unsigned int)System.satellites_used, (double)System.hdop_x100/100.0);
+}
+
+void log_fix_lost(void) {
+	log_prefix();
+	xprintf(PSTR("FIX LOST sats=%u\r\n"), (unsigned int)System.satellites_used);
+}
+
+void log_sbas_transition(void) {
+	log_prefix();
+	xputs_P(PSTR("SBAS "));
+	xputs_P(System.sbas ? PSTR("on") : PSTR("off"));
+	xputs_P(PSTR("\r\n"));
+}
+
+void log_pause_event(unsigned char started) {
+	log_prefix();
+	xputs_P(started ? PSTR("PAUSE\r\n") : PSTR("RESUME\r\n"));
+}
+
+void log_reject_flush(void) {
+	if (!reject_coalesce.count)
+		return;
+	log_prefix();
+	xputs_P(PSTR("REJ reason="));
+	xputs_P(reject_reason_names[reject_coalesce.reason]);
+	xprintf(PSTR(" hdop=%.2f sats=%u fix=%uD"), (double)reject_coalesce.hdop_x100/100.0,
+		(unsigned int)reject_coalesce.sats, (unsigned int)reject_coalesce.fix_type);
+	if (reject_coalesce.count > 1)
+		xprintf(PSTR(" x%u"), (unsigned int)reject_coalesce.count);
+	xputs_P(PSTR("\r\n"));
+	reject_coalesce.count = 0;
+}
+
+void log_reject(unsigned char reason) {
+	System.rejected_count++;
+	if (reject_coalesce.count && reject_coalesce.reason == reason && reject_coalesce.count < 255) {
+		reject_coalesce.count++;
+		reject_coalesce.hdop_x100 = System.hdop_x100;
+		reject_coalesce.sats = System.satellites_used;
+		reject_coalesce.fix_type = System.fix_type;
+		return;
+	}
+	log_reject_flush();
+	reject_coalesce.reason = reason;
+	reject_coalesce.count = 1;
+	reject_coalesce.hdop_x100 = System.hdop_x100;
+	reject_coalesce.sats = System.satellites_used;
+	reject_coalesce.fix_type = System.fix_type;
+}
+
+void log_jump(unsigned char is_alt, float meters) {
+	log_prefix();
+	xputs_P(PSTR("JUMP "));
+	xputs_P(is_alt ? PSTR("alt=") : PSTR("pos="));
+	xprintf(PSTR("%.1fm\r\n"), (double)meters);
+}
+
+void log_status_line(void) {
+	signed int batpct = (signed int)((System.bat_volt - 3.3) / (4.2 - 3.3) * 100.0 + 0.5);
+	if (batpct < 0)
+		batpct = 0;
+	if (batpct > 100)
+		batpct = 100;
+
+	log_prefix();
+	xprintf(PSTR("ST v=%.3f bat%%=%d"), (double)System.bat_volt, batpct);
+	if (System.temperature_ok)
+		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(""));
+	xprintf(PSTR(" dist=%.2f gain=%.1f/raw%.1f loss=%.1f/raw%.1f wr=%lu rej=%u"),
+		(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,
+		System.points_written, System.rejected_count);
+	if (utc)
+		xprintf(PSTR(" loc=%s"), get_iso_time(utc, 1));
+	xputs_P(PSTR("\r\n"));
+}
+
+void log_session_summary(unsigned char final) {
+	unsigned int moving = get_logging_time();
+	unsigned long int stopped = get_pause_time();
+	unsigned int h, m;
+
+	log_reject_flush();
+	log_prefix();
+	xputs_P(final ? PSTR("SUMMARY final ") : PSTR("SUMMARY "));
+	xprintf(PSTR("dist=%.2f gain=%.1f loss=%.1f moving="), (double)System.distance/100.0,
+		(double)System.elevation_gain/10.0, (double)System.elevation_loss/10.0);
+	h = moving/3600; m = (moving/60)%60;
+	xprintf(PSTR("%uh%02um stopped="), h, m);
+	h = stopped/3600; m = (stopped/60)%60;
+	xprintf(PSTR("%uh%02um altmax=%.1f vmin=%.3f rej=%u\r\n"), h, m,
+		(double)System.alt_max/10.0, (double)System.bat_volt_min, System.rejected_count);
+}

+ 23 - 0
soft/syslog.h

@@ -0,0 +1,23 @@
+#pragma once
+
+/* Reasons a candidate point can be rejected before being written */
+#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 */
+
+extern __flash const char *reject_reason_names[];
+
+void log_prefix(void);
+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_pause_event(unsigned char started);
+void log_reject(unsigned char reason);
+void log_reject_flush(void);
+void log_jump(unsigned char is_alt, float meters);
+void log_status_line(void);
+void log_session_summary(unsigned char final);
+void gps_powered_on(void);
+void log_loop_status(unsigned char status);