2
0

3 Коммиты 05050f4e7e ... 5f47d3146d

Автор SHA1 Сообщение Дата
  k4be 5f47d3146d autopause: fix speed-rounding false resume, add heading-consistency check 3 недель назад
  k4be c3378005c5 nmea: count low-quality-fix rejections toward rejected_count 3 недель назад
  k4be 522095585d fix: keypress bitfield truncated multi-key mask to 1 bit 3 недель назад
8 измененных файлов с 112 добавлено и 15 удалено
  1. 1 0
      gps-test-tool/main.h
  2. 57 4
      soft/autopause.c
  3. 9 2
      soft/common.h
  4. 16 0
      soft/gpx.c
  5. 1 0
      soft/gpx.h
  6. 25 4
      soft/nmea.c
  7. 2 0
      soft/syslog.c
  8. 1 5
      soft/syslog.h

+ 1 - 0
gps-test-tool/main.h

@@ -267,6 +267,7 @@ void gps_initialize(void);
 void add_distance(float dist);
 void add_distance(float dist);
 void add_elevation(float ele_change);
 void add_elevation(float ele_change);
 float distance(struct location_s *pos1, struct location_s *pos2);
 float distance(struct location_s *pos1, struct location_s *pos2);
+float bearing(struct location_s *pos1, struct location_s *pos2);
 unsigned char nmea_epoch_complete(void);
 unsigned char nmea_epoch_complete(void);
 
 
 /* The structured system log (soft/syslog.c) is compiled into this test tool
 /* The structured system log (soft/syslog.c) is compiled into this test tool

+ 57 - 4
soft/autopause.c

@@ -31,6 +31,24 @@ struct auto_pause_s auto_pause;
 #define AUTO_PAUSE_RESUME_HISTORY_MASK	((1U << AUTO_PAUSE_RESUME_HISTORY_LEN) - 1)
 #define AUTO_PAUSE_RESUME_HISTORY_MASK	((1U << AUTO_PAUSE_RESUME_HISTORY_LEN) - 1)
 #define AUTO_PAUSE_RESUME_MIN_COUNT	5
 #define AUTO_PAUSE_RESUME_MIN_COUNT	5
 
 
+/* Distance-from-anchor alone can't tell a real departure from a smooth
+ * position drift with no real movement behind it: a satellite geometry
+ * change (a few satellites dropping out/back in) can walk the computed fix
+ * steadily in one direction for tens of seconds while the receiver doesn't
+ * move at all - the exact "persist over several samples" shape the resume
+ * check above is trying to accept. Real walking keeps a fairly stable
+ * step-to-step bearing; a geometry-driven drift has no reason to, so require
+ * the bearing between consecutive paused samples to also stay consistent
+ * before counting a sample as a real resume signal. Skipped below
+ * HEADING_STEP_MIN_DIST_M: bearing is meaningless noise over a sub-meter step
+ * (the two fixes are within GPS jitter of each other), and would otherwise
+ * make genuinely stationary jitter look "inconsistent" for the wrong reason. */
+#define HEADING_STEP_MIN_DIST_M		1.5	/* ignore bearing between two samples closer than this */
+#define HEADING_CONSISTENT_MAX_DEG	60	/* max angle from the previous step's bearing to still agree */
+#define HEADING_HISTORY_LEN		7	/* same window length as the distance resume history above */
+#define HEADING_HISTORY_MASK		((1U << HEADING_HISTORY_LEN) - 1)
+#define HEADING_MIN_COUNT		5	/* same majority-of-window requirement as the distance resume history */
+
 static void auto_unpause(void) {
 static void auto_unpause(void) {
 	if (!System.tracking_auto_paused)
 	if (!System.tracking_auto_paused)
 		return;
 		return;
@@ -57,6 +75,9 @@ static void auto_pause_activate(void) {
 	auto_pause.pause_anchor = location;
 	auto_pause.pause_anchor = location;
 	auto_pause.anchor_sample_count = 1;
 	auto_pause.anchor_sample_count = 1;
 	auto_pause.resume_high_history = 0;
 	auto_pause.resume_high_history = 0;
+	auto_pause.prev_location_valid = 0;
+	auto_pause.prev_bearing_valid = 0;
+	auto_pause.heading_consistent_history = 0;
 	log_pause_event(1);
 	log_pause_event(1);
 	beep(50, 3);
 	beep(50, 3);
 }
 }
@@ -98,6 +119,9 @@ void auto_pause_reset_gap(void) {
 		auto_pause.pause_anchor = location;
 		auto_pause.pause_anchor = location;
 		auto_pause.anchor_sample_count = 1;
 		auto_pause.anchor_sample_count = 1;
 		auto_pause.resume_high_history = 0;
 		auto_pause.resume_high_history = 0;
+		auto_pause.prev_location_valid = 0;
+		auto_pause.prev_bearing_valid = 0;
+		auto_pause.heading_consistent_history = 0;
 	}
 	}
 }
 }
 
 
@@ -118,7 +142,7 @@ void auto_pause_process(void) {
 	 * consecutive-samples debounce - so it can't itself trigger a resume,
 	 * consecutive-samples debounce - so it can't itself trigger a resume,
 	 * but also can't erase real progress made by genuine movement. */
 	 * but also can't erase real progress made by genuine movement. */
 	if (gps_fix_trustworthy()) {
 	if (gps_fix_trustworthy()) {
-		if (System.speed >= System.conf.auto_pause_speed) { /* unpause when set speed is exceeded for 3 consecutive measurements */
+		if (System.speed_x10 >= (unsigned int)System.conf.auto_pause_speed*10) { /* unpause when set speed is exceeded for 3 consecutive measurements */
 			if (++auto_pause.speed_counter >= 3) {
 			if (++auto_pause.speed_counter >= 3) {
 				auto_pause.point_counter = 0;
 				auto_pause.point_counter = 0;
 				auto_pause.speed_counter = 0;
 				auto_pause.speed_counter = 0;
@@ -137,7 +161,11 @@ void auto_pause_process(void) {
 	 * otherwise accumulate across enough net-movement windows to look like a
 	 * otherwise accumulate across enough net-movement windows to look like a
 	 * real departure. Evaluated every accepted point, not gated on the
 	 * real departure. Evaluated every accepted point, not gated on the
 	 * auto_pause_time window below (that window is for the pause trigger
 	 * auto_pause_time window below (that window is for the pause trigger
-	 * only), so the persistence history has one sample per point. */
+	 * only), so the persistence history has one sample per point. Also
+	 * requires a consistent step-to-step bearing (see HEADING_* above) - a
+	 * satellite geometry change can drift the fix in a way that also
+	 * persists across this many samples, but has no reason to keep the same
+	 * heading step to step the way real walking does. */
 	if (System.tracking_auto_paused && gps_fix_trustworthy()) {
 	if (System.tracking_auto_paused && gps_fix_trustworthy()) {
 		float resume_threshold_m = AUTO_PAUSE_RESUME_MIN_DIST_M;
 		float resume_threshold_m = AUTO_PAUSE_RESUME_MIN_DIST_M;
 		float hdop_threshold_m = (System.hdop_x100/100.0f) * AUTO_PAUSE_RESUME_HDOP_MULT;
 		float hdop_threshold_m = (System.hdop_x100/100.0f) * AUTO_PAUSE_RESUME_HDOP_MULT;
@@ -159,7 +187,32 @@ void auto_pause_process(void) {
 		dist_from_anchor_m = distance(&location, &auto_pause.pause_anchor);
 		dist_from_anchor_m = distance(&location, &auto_pause.pause_anchor);
 		resumed_this_sample = dist_from_anchor_m > resume_threshold_m;
 		resumed_this_sample = dist_from_anchor_m > resume_threshold_m;
 		auto_pause.resume_high_history = (auto_pause.resume_high_history << 1) | resumed_this_sample;
 		auto_pause.resume_high_history = (auto_pause.resume_high_history << 1) | resumed_this_sample;
-		if (__builtin_popcount(auto_pause.resume_high_history & AUTO_PAUSE_RESUME_HISTORY_MASK) >= AUTO_PAUSE_RESUME_MIN_COUNT) {
+
+		if (auto_pause.prev_location_valid) {
+			float step_dist_m = distance(&auto_pause.prev_location, &location);
+			if (step_dist_m >= HEADING_STEP_MIN_DIST_M) {
+				float this_bearing = bearing(&auto_pause.prev_location, &location);
+				if (auto_pause.prev_bearing_valid) {
+					float diff = fabs(this_bearing - auto_pause.prev_bearing);
+					unsigned char heading_ok;
+					if (diff > 180.0)
+						diff = 360.0 - diff;
+					heading_ok = diff <= HEADING_CONSISTENT_MAX_DEG;
+					auto_pause.heading_consistent_history =
+						(auto_pause.heading_consistent_history << 1) | heading_ok;
+				}
+				auto_pause.prev_bearing = this_bearing;
+				auto_pause.prev_bearing_valid = 1;
+			}
+			/* else: too small a step to trust its bearing - leave prev_bearing
+			 * and heading_consistent_history untouched, neither confirming nor
+			 * breaking the run of consistent headings. */
+		}
+		auto_pause.prev_location = location;
+		auto_pause.prev_location_valid = 1;
+
+		if (__builtin_popcount(auto_pause.resume_high_history & AUTO_PAUSE_RESUME_HISTORY_MASK) >= AUTO_PAUSE_RESUME_MIN_COUNT
+			&& __builtin_popcount(auto_pause.heading_consistent_history & HEADING_HISTORY_MASK) >= HEADING_MIN_COUNT) {
 			auto_pause.point_counter = 0;
 			auto_pause.point_counter = 0;
 			auto_unpause();
 			auto_unpause();
 			return;
 			return;
@@ -182,7 +235,7 @@ void auto_pause_process(void) {
 		 * a run of untrustworthy fixes must not be allowed to decide a pause
 		 * a run of untrustworthy fixes must not be allowed to decide a pause
 		 * transition either way here - it's simply skipped for this window. */
 		 * transition either way here - it's simply skipped for this window. */
 		if ((System.distance - auto_pause.prev_distance)/100 <= System.conf.auto_pause_dist
 		if ((System.distance - auto_pause.prev_distance)/100 <= System.conf.auto_pause_dist
-			&& System.speed < System.conf.auto_pause_speed)
+			&& System.speed_x10 < (unsigned int)System.conf.auto_pause_speed*10)
 			auto_pause_activate(); /* pause: not enough net movement, and not currently moving fast */
 			auto_pause_activate(); /* pause: not enough net movement, and not currently moving fast */
 	}
 	}
 	auto_pause.prev_distance = System.distance;
 	auto_pause.prev_distance = System.distance;

+ 9 - 2
soft/common.h

@@ -46,6 +46,7 @@
 #define REJECT_REASON_DISTDIFF	1	/* distance jump inconsistent with recent history */
 #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_MINDIST	2	/* too small a position change since last saved point */
 #define REJECT_REASON_ALTDIFF	3	/* altitude jump inconsistent with recent history */
 #define REJECT_REASON_ALTDIFF	3	/* altitude jump inconsistent with recent history */
+#define REJECT_REASON_QUALITY	4	/* low satellite count, poor HDOP, or non-3D fix */
 
 
 /* System.conf.flags bit indices, read/set via get_flag(index)/set_flag(index,
 /* 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
  * val) - both sides index the same way (a bit position within a flags byte
@@ -121,7 +122,8 @@ struct system_s {
 		// 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.
 		// 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 int hdop_x100; // HDOP * 100, from GSA
 	unsigned char fix_type; // FIX_TYPE_* from GSA
 	unsigned char fix_type; // FIX_TYPE_* from GSA
-	unsigned char speed; // km/h
+	unsigned int speed_x10; // instantaneous NMEA speed, km/h * 10 - see nmea.c's speed_sample_process()
+		// for why whole km/h is too coarse for the auto-pause speed threshold
 	time_t time_start;
 	time_t time_start;
 	time_t current_pause_start;
 	time_t current_pause_start;
 	time_t pause_time;
 	time_t pause_time;
@@ -134,7 +136,6 @@ struct system_s {
 	unsigned dgps:1; /* GGA reports a DGPS-quality fix - the receiver's own doing, not CONFFLAG_ENABLE_SBAS (our SBAS-search request) */
 	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_initialized:2;
 	unsigned gps_only:1;
 	unsigned gps_only:1;
-	unsigned keypress:1;
 	unsigned tracking_paused:1;
 	unsigned tracking_paused:1;
 	unsigned tracking_auto_paused:1;
 	unsigned tracking_auto_paused:1;
 	unsigned open_new_file:1;
 	unsigned open_new_file:1;
@@ -143,6 +144,7 @@ struct system_s {
 	unsigned char status;
 	unsigned char status;
 	unsigned char display_state;
 	unsigned char display_state;
 	unsigned char working_mode;
 	unsigned char working_mode;
+	unsigned char keypress; // bitmask of K_* (main.h); needs full width, not a bitfield bit
 };
 };
 
 
 /* Auto-pause state (soft/autopause.c, compiled unmodified into both builds -
 /* Auto-pause state (soft/autopause.c, compiled unmodified into both builds -
@@ -154,4 +156,9 @@ struct auto_pause_s {
 	struct location_s pause_anchor; /* running average position while paused */
 	struct location_s pause_anchor; /* running average position while paused */
 	unsigned int anchor_sample_count;
 	unsigned int anchor_sample_count;
 	unsigned char resume_high_history; /* bit history: was the anchor-distance/speed resume condition met */
 	unsigned char resume_high_history; /* bit history: was the anchor-distance/speed resume condition met */
+	struct location_s prev_location; /* last sample seen while paused, for step-to-step bearing */
+	unsigned char prev_location_valid:1;
+	unsigned char prev_bearing_valid:1;
+	float prev_bearing;
+	unsigned char heading_consistent_history; /* bit history: did this step's bearing agree with the last */
 };
 };

+ 16 - 0
soft/gpx.c

@@ -413,6 +413,22 @@ float distance(struct location_s *pos1, struct location_s *pos2){
 	return ret;
 	return ret;
 }
 }
 
 
+/* Initial bearing (degrees, 0-360, 0=north) from pos1 to pos2 - used by the
+ * auto-pause resume-by-distance check (autopause.c) to tell a real, directed
+ * departure from a smooth-but-directionless position drift (e.g. a satellite
+ * geometry change reshaping the fix while the receiver doesn't move at all). */
+float bearing(struct location_s *pos1, struct location_s *pos2){
+	float lat1 = pos1->lat * M_PI / 180.0;
+	float lat2 = pos2->lat * M_PI / 180.0;
+	float dlon = (pos2->lon - pos1->lon) * M_PI / 180.0;
+	float y = sinf(dlon) * cosf(lat2);
+	float x = cosf(lat1) * sinf(lat2) - sinf(lat1) * cosf(lat2) * cosf(dlon);
+	float deg = atan2f(y, x) * 180.0 / M_PI;
+	if (deg < 0)
+		deg += 360.0;
+	return deg;
+}
+
 void add_distance(float dist) {
 void add_distance(float dist) {
 	unsigned char paused = tracking_is_paused();
 	unsigned char paused = tracking_is_paused();
 	if (!paused)
 	if (!paused)

+ 1 - 0
soft/gpx.h

@@ -11,6 +11,7 @@ void gpx_process_point(struct location_s *loc, FIL *file);
 void gpx_reset_gap(void);
 void gpx_reset_gap(void);
 void gpx_save_single_point(struct location_s *loc);
 void gpx_save_single_point(struct location_s *loc);
 float distance(struct location_s *pos1, struct location_s *pos2);
 float distance(struct location_s *pos1, struct location_s *pos2);
+float bearing(struct location_s *pos1, struct location_s *pos2);
 void add_distance(float dist);
 void add_distance(float dist);
 void add_elevation(float ele_change);
 void add_elevation(float ele_change);
 unsigned char is_paused(void);
 unsigned char is_paused(void);

+ 25 - 4
soft/nmea.c

@@ -73,6 +73,9 @@ static struct {
 	unsigned char gga_ok : 1;
 	unsigned char gga_ok : 1;
 	unsigned char gsa_ok : 1;
 	unsigned char gsa_ok : 1;
 	unsigned char logged : 1;
 	unsigned char logged : 1;
+	unsigned char quality_rejected : 1; /* REJECT_REASON_QUALITY already
+		counted this epoch - RMC status=V and GGA quality=0 both fire on the
+		same no-fix epoch, and would otherwise double-count one rejection */
 } epoch;
 } epoch;
 
 
 #define FIELD_BUF_LEN	32
 #define FIELD_BUF_LEN	32
@@ -138,7 +141,7 @@ static void speed_sample_process(double speed_kmh) {
 	unsigned char paused = tracking_is_paused();
 	unsigned char paused = tracking_is_paused();
 	unsigned char fix_trustworthy = gps_fix_trustworthy();
 	unsigned char fix_trustworthy = gps_fix_trustworthy();
 
 
-	System.speed = speed_kmh+0.5;
+	System.speed_x10 = (unsigned int)(speed_kmh*10.0 + 0.5);
 
 
 	/* Average speed: fixed-point accumulation of instantaneous samples,
 	/* Average speed: fixed-point accumulation of instantaneous samples,
 	 * excluding time spent paused, instead of dividing distance by time. */
 	 * excluding time spent paused, instead of dividing distance by time. */
@@ -168,6 +171,7 @@ static time_t gp_rmc_parse(const char *str) {
 	epoch.gga_ok = 0;
 	epoch.gga_ok = 0;
 	epoch.gsa_ok = 0;
 	epoch.gsa_ok = 0;
 	epoch.logged = 0;
 	epoch.logged = 0;
+	epoch.quality_rejected = 0;
 
 
 	p = gp_col(str, 1);		/* Get h:m:s */
 	p = gp_col(str, 1);		/* Get h:m:s */
 	if (!p)
 	if (!p)
@@ -191,6 +195,10 @@ static time_t gp_rmc_parse(const char *str) {
 	if (!p || *p != 'A') {
 	if (!p || *p != 'A') {
 		System.location_valid = LOC_INVALID;
 		System.location_valid = LOC_INVALID;
 		FLAGS &= ~F_GPSOK;
 		FLAGS &= ~F_GPSOK;
+		if (!epoch.quality_rejected) {
+			epoch.quality_rejected = 1;
+			log_reject(REJECT_REASON_QUALITY);
+		}
 		return 0;			/* Return 0 even is time is valid (comes from module's internal RTC) */
 		return 0;			/* Return 0 even is time is valid (comes from module's internal RTC) */
 	}
 	}
 
 
@@ -250,6 +258,10 @@ static void gp_gga_parse(const char *str) {
 		return;
 		return;
 	if (*p == '0') {
 	if (*p == '0') {
 		System.location_valid = LOC_INVALID;
 		System.location_valid = LOC_INVALID;
+		if (!epoch.quality_rejected) {
+			epoch.quality_rejected = 1;
+			log_reject(REJECT_REASON_QUALITY);
+		}
 		return;
 		return;
 	}
 	}
 
 
@@ -265,12 +277,21 @@ static void gp_gga_parse(const char *str) {
 	 * System.fix_type is set by GSA, parsed after GGA within the epoch, so
 	 * 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
 	 * 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
 	 * bad 2D fix reliably spans several consecutive epochs, so a one-epoch
-	 * lag isn't a practical gap here (unlike a true point fix). */
+	 * lag isn't a practical gap here (unlike a true point fix).
+	 * log_reject() logs System.hdop_x100 (GSA's, same one-epoch-stale value
+	 * checked above), not this epoch's GGA hdop_x100 just computed here -
+	 * close enough for the coalesced diagnostic line, and keeps every
+	 * REJECT_REASON logging the same field, sourced the same way. */
 	if (!System.sat_count_low && hdop_x100 <= FIX_QUALITY_HDOP_MAX_X100
 	if (!System.sat_count_low && hdop_x100 <= FIX_QUALITY_HDOP_MAX_X100
-			&& System.fix_type == FIX_TYPE_3D)
+			&& System.fix_type == FIX_TYPE_3D) {
 		System.location_valid = LOC_VALID_NEW;
 		System.location_valid = LOC_VALID_NEW;
-	else
+	} else {
 		System.location_valid = LOC_INVALID;
 		System.location_valid = LOC_INVALID;
+		if (!epoch.quality_rejected) {
+			epoch.quality_rejected = 1;
+			log_reject(REJECT_REASON_QUALITY);
+		}
+	}
 
 
 	/* parse location */
 	/* parse location */
 	p = gp_col(str, 2);		/* latitude */
 	p = gp_col(str, 2);		/* latitude */

+ 2 - 0
soft/syslog.c

@@ -12,12 +12,14 @@ __flash const char reject_reason_kalman[] = "kalman";
 __flash const char reject_reason_distdiff[] = "distdiff";
 __flash const char reject_reason_distdiff[] = "distdiff";
 __flash const char reject_reason_mindist[] = "mindist";
 __flash const char reject_reason_mindist[] = "mindist";
 __flash const char reject_reason_altdiff[] = "altdiff";
 __flash const char reject_reason_altdiff[] = "altdiff";
+__flash const char reject_reason_quality[] = "quality";
 
 
 __flash const char *reject_reason_names[] = {
 __flash const char *reject_reason_names[] = {
 	[REJECT_REASON_KALMAN] = reject_reason_kalman,
 	[REJECT_REASON_KALMAN] = reject_reason_kalman,
 	[REJECT_REASON_DISTDIFF] = reject_reason_distdiff,
 	[REJECT_REASON_DISTDIFF] = reject_reason_distdiff,
 	[REJECT_REASON_MINDIST] = reject_reason_mindist,
 	[REJECT_REASON_MINDIST] = reject_reason_mindist,
 	[REJECT_REASON_ALTDIFF] = reject_reason_altdiff,
 	[REJECT_REASON_ALTDIFF] = reject_reason_altdiff,
+	[REJECT_REASON_QUALITY] = reject_reason_quality,
 };
 };
 
 
 __flash const char status_no_power[] = "no power";
 __flash const char status_no_power[] = "no power";

+ 1 - 5
soft/syslog.h

@@ -1,10 +1,6 @@
 #pragma once
 #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 */
-#define REJECT_REASON_ALTDIFF	3	/* altitude jump inconsistent with recent history */
+#include "common.h"
 
 
 extern __flash const char *reject_reason_names[];
 extern __flash const char *reject_reason_names[];