فهرست منبع

gpx: reject altitude spikes and re-warm-up after a fix gap

A poor-geometry fix right after reacquiring signal can pass the
satellite-count/HDOP/fix-type gates in nmea.c yet still carry a wildly
wrong altitude for several seconds while the solution converges.

Add an altitude equivalent of the existing position distance-diff
spike check (new REJECT_REASON_ALTDIFF), and treat a >3s gap between
accepted points as a fix having been lost and reacquired: reset the
Kalman filters and averaging window, and re-run the existing
skip_points warm-up, the same as after power-on.
k4be 1 هفته پیش
والد
کامیت
a1866c92cb
3فایلهای تغییر یافته به همراه59 افزوده شده و 1 حذف شده
  1. 56 1
      soft/gpx.c
  2. 2 0
      soft/syslog.c
  3. 1 0
      soft/syslog.h

+ 56 - 1
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 */
 
@@ -63,6 +70,7 @@ static struct gpx_s {
 	unsigned char avg_count;
 	unsigned char paused;
 	unsigned char point_count;
+	time_t last_point_time; /* time of the last point seen by gpx_process_point(), to detect a fix gap */
 	struct avg_store_s avg_store;
 	struct location_s last_saved;
 	struct location_s last_distance_point; /* Last accepted point for distance calculation */
@@ -108,6 +116,8 @@ unsigned char gpx_init(FIL *file) {
 	gpx.last_distance_point.lat = 0;
 	gpx.last_distance_point.time = 0;
 	gpx.elevation.initialized = 0;
+	gpx.last_point_time = 0;
+	gpx.point_count = 0;
 
 	gpx.paused = 1; /* make it add a <trkseg> tag */
 
@@ -190,12 +200,38 @@ unsigned char gpx_close(FIL *file) {
 	return f_close(file);
 }
 
+/* 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), even without an
+ * explicit auto-pause. A freshly reacquired fix can take a few seconds to
+ * converge (seen after a real signal loss: a nominally-3D, in-range-HDOP fix
+ * still a couple hundred meters off in altitude until more satellites come
+ * in), the same way a fix has never been trusted right after power-on -
+ * so re-run the existing skip_points warm-up, and reset the Kalman/window
+ * state that would otherwise splice the old and new fixes together. */
+#define FIX_GAP_SECONDS	3
+
 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 (gpx.last_point_time && loc->time - gpx.last_point_time > FIX_GAP_SECONDS) {
+		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;
+	}
+	gpx.last_point_time = loc->time;
+
 	if (gpx.point_count < System.conf.skip_points) { /* Skipping initial points */
 		gpx.point_count++;
 		return;
@@ -251,6 +287,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 +308,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);

+ 2 - 0
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";

+ 1 - 0
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[];