|
|
@@ -31,6 +31,29 @@ struct auto_pause_s auto_pause;
|
|
|
#define AUTO_PAUSE_RESUME_HISTORY_MASK ((1U << AUTO_PAUSE_RESUME_HISTORY_LEN) - 1)
|
|
|
#define AUTO_PAUSE_RESUME_MIN_COUNT 5
|
|
|
|
|
|
+/* Raw NMEA-reported ground speed (System.speed_x10) is single-epoch Doppler
|
|
|
+ * output with no smoothing - real recorded hikes show it read a few km/h for
|
|
|
+ * several consecutive seconds even while the receiver is stationary and
|
|
|
+ * HDOP/sat count are excellent (multipath/receiver noise, not position
|
|
|
+ * error, and correlated over several seconds rather than independent from
|
|
|
+ * sample to sample). A strict "N consecutive samples over threshold"
|
|
|
+ * debounce is exactly the shape most exposed to this - and even a plain
|
|
|
+ * majority-of-a-window vote on the raw value isn't enough on its own, since
|
|
|
+ * this noise is persistent enough to occasionally dominate a short window
|
|
|
+ * outright. Smooth the speed first (exponential smoothing, same idea as
|
|
|
+ * elevation_process()'s altitude smoothing) to knock down the bulk of the
|
|
|
+ * noise, *then* still require the smoothed value to clear the threshold for
|
|
|
+ * most of a window before resuming - so a single lingering noisy patch
|
|
|
+ * cannot itself resume a genuinely stationary logger, but sustained real
|
|
|
+ * movement (which pushes the smoothed value well past the threshold, not
|
|
|
+ * just brushing it) still resumes within several seconds. */
|
|
|
+#define SPEED_SMOOTH_ALPHA 0.25 /* exponential smoothing factor per sample (~1s apart) */
|
|
|
+#define SPEED_RESUME_HISTORY_LEN 7 /* same window length as the other resume-history checks */
|
|
|
+#define SPEED_RESUME_HISTORY_MASK ((1U << SPEED_RESUME_HISTORY_LEN) - 1)
|
|
|
+#define SPEED_RESUME_MIN_COUNT 6 /* stricter majority than distance/heading: speed alone has no
|
|
|
+ * corroborating signal (no distance-from-anchor or heading check
|
|
|
+ * backs it up), so it needs to be almost the whole window */
|
|
|
+
|
|
|
/* 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
|
|
|
@@ -43,7 +66,7 @@ struct auto_pause_s auto_pause;
|
|
|
* 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_STEP_MIN_DIST_M 3 /* 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)
|
|
|
@@ -62,12 +85,11 @@ static void auto_unpause(void) {
|
|
|
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,
|
|
|
- * instantly flip-flopping straight back out of the pause this just
|
|
|
- * activated. */
|
|
|
- auto_pause.speed_counter = 0;
|
|
|
+ /* Otherwise, speed-history progress from just before this decision could
|
|
|
+ * carry over and complete on the very next few samples, instantly
|
|
|
+ * flip-flopping straight back out of the pause this just activated. */
|
|
|
+ auto_pause.speed_high_history = 0;
|
|
|
+ auto_pause.smoothed_speed_valid = 0;
|
|
|
/* Anchor the resume-by-distance check on where the pause actually
|
|
|
* started, then let it drift towards the running average of positions
|
|
|
* seen while paused (steadier than any single fix - see
|
|
|
@@ -96,7 +118,8 @@ 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;
|
|
|
+ auto_pause.speed_high_history = 0;
|
|
|
+ auto_pause.smoothed_speed_valid = 0;
|
|
|
}
|
|
|
|
|
|
/* Call once, whenever fix_gap_detected() reports a gap on the point about to
|
|
|
@@ -108,7 +131,8 @@ void auto_pause_clear_manual_resume(void) {
|
|
|
* 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.speed_high_history = 0;
|
|
|
+ auto_pause.smoothed_speed_valid = 0;
|
|
|
auto_pause.prev_distance = System.distance;
|
|
|
if (System.tracking_auto_paused) {
|
|
|
/* Same reasoning as auto_pause_activate()'s anchor init: re-anchor on
|
|
|
@@ -130,27 +154,33 @@ void auto_pause_process(void) {
|
|
|
System.tracking_auto_paused = 0;
|
|
|
auto_pause.prev_distance = System.distance;
|
|
|
auto_pause.point_counter = 0;
|
|
|
- auto_pause.speed_counter = 0;
|
|
|
+ auto_pause.speed_high_history = 0;
|
|
|
+ auto_pause.smoothed_speed_valid = 0;
|
|
|
return;
|
|
|
}
|
|
|
/* A poor/invalid fix must not be read as "moving": a single noisy sample
|
|
|
* (e.g. a low-satellite, high-HDOP fix briefly reporting tens of km/h,
|
|
|
* as seen on a real hike where reception dropped for a few seconds)
|
|
|
- * could otherwise satisfy the "3 consecutive" debounce below on its own
|
|
|
- * and resume a logger that is actually sitting still. Such a sample is
|
|
|
- * simply ignored - neither counted towards, nor resetting, the
|
|
|
- * consecutive-samples debounce - so it can't itself trigger a resume,
|
|
|
- * but also can't erase real progress made by genuine movement. */
|
|
|
+ * could otherwise satisfy the persistence check below on its own and
|
|
|
+ * resume a logger that is actually sitting still. Such a sample is
|
|
|
+ * simply ignored - it shifts nothing into the history below - so it
|
|
|
+ * can't itself trigger a resume, but also can't erase real progress
|
|
|
+ * made by genuine movement. */
|
|
|
if (gps_fix_trustworthy()) {
|
|
|
- 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) {
|
|
|
- auto_pause.point_counter = 0;
|
|
|
- auto_pause.speed_counter = 0;
|
|
|
- auto_unpause();
|
|
|
- return;
|
|
|
- }
|
|
|
+ unsigned char speed_high;
|
|
|
+ if (!auto_pause.smoothed_speed_valid) {
|
|
|
+ auto_pause.smoothed_speed_x10 = (float)System.speed_x10;
|
|
|
+ auto_pause.smoothed_speed_valid = 1;
|
|
|
} else {
|
|
|
- auto_pause.speed_counter = 0;
|
|
|
+ auto_pause.smoothed_speed_x10 += SPEED_SMOOTH_ALPHA * ((float)System.speed_x10 - auto_pause.smoothed_speed_x10);
|
|
|
+ }
|
|
|
+ speed_high = auto_pause.smoothed_speed_x10 >= (float)((unsigned int)System.conf.auto_pause_speed*10);
|
|
|
+ auto_pause.speed_high_history = (auto_pause.speed_high_history << 1) | speed_high;
|
|
|
+ if (__builtin_popcount(auto_pause.speed_high_history & SPEED_RESUME_HISTORY_MASK) >= SPEED_RESUME_MIN_COUNT) {
|
|
|
+ auto_pause.point_counter = 0;
|
|
|
+ auto_pause.speed_high_history = 0;
|
|
|
+ auto_unpause();
|
|
|
+ return;
|
|
|
}
|
|
|
}
|
|
|
/* Resume-by-distance: compared against an anchor updated to the running
|
|
|
@@ -203,13 +233,16 @@ void auto_pause_process(void) {
|
|
|
}
|
|
|
auto_pause.prev_bearing = this_bearing;
|
|
|
auto_pause.prev_bearing_valid = 1;
|
|
|
+ auto_pause.prev_location = location;
|
|
|
}
|
|
|
/* 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 (!auto_pause.prev_location_valid) {
|
|
|
+ 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) {
|
|
|
@@ -218,6 +251,8 @@ void auto_pause_process(void) {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
+ if (auto_pause.point_counter == System.conf.auto_pause_time - System.conf.auto_pause_time/4)
|
|
|
+ auto_pause.quarter_distance = System.distance;
|
|
|
if (++auto_pause.point_counter < System.conf.auto_pause_time)
|
|
|
return;
|
|
|
auto_pause.point_counter = 0;
|
|
|
@@ -235,6 +270,7 @@ void auto_pause_process(void) {
|
|
|
* a run of untrustworthy fixes must not be allowed to decide a pause
|
|
|
* transition either way here - it's simply skipped for this window. */
|
|
|
if ((System.distance - auto_pause.prev_distance)/100 <= System.conf.auto_pause_dist
|
|
|
+ && (System.distance - auto_pause.quarter_distance)/100 <= System.conf.auto_pause_dist/4
|
|
|
&& System.speed_x10 < (unsigned int)System.conf.auto_pause_speed*10)
|
|
|
auto_pause_activate(); /* pause: not enough net movement, and not currently moving fast */
|
|
|
}
|