autopause.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. * Auto-pause detection: shared between the firmware and gps-test-tool (see
  3. * autopause_wrapper.c), since it directly gates what ends up in
  4. * System.distance/elevation_gain/elevation_loss (excluded while paused) and
  5. * is a major part of what those tools' output actually looks like.
  6. */
  7. #ifdef PC_BUILD
  8. #include "../gps-test-tool/main.h"
  9. #else
  10. #include "main.h"
  11. #endif
  12. struct auto_pause_s auto_pause;
  13. /* Resuming from a period of auto-pause because of apparent distance needs
  14. * more apparent movement than triggering the pause did in the first place:
  15. * position noise while genuinely stationary (multipath, poor sky view) can
  16. * still occasionally clear the plain auto_pause_dist threshold over enough
  17. * auto_pause_time windows, and unlike a false pause (which just costs one
  18. * quiet window before the speed-based check or the next window catches the
  19. * resume), a false resume pollutes the moving/stopped split and the
  20. * odometer for as long as it isn't caught. */
  21. #define AUTO_PAUSE_RESUME_MIN_DIST_M 15 /* resume distance-from-anchor floor, regardless of HDOP */
  22. #define AUTO_PAUSE_RESUME_HDOP_MULT 5 /* ...and scale up from there by this many meters per HDOP unit -
  23. * a standard rule-of-thumb for GPS position error (HDOP x a typical
  24. * few-meter UERE), so a poor fix needs to move further to count */
  25. #define AUTO_PAUSE_RESUME_HISTORY_LEN 7 /* the resume condition must hold for most (see MIN_COUNT below) of
  26. * the last this-many samples, not just instantaneously - the same
  27. * persistence idea used for the speed cap, so a few seconds of
  28. * genuine drift-free movement are needed, not one lucky sample */
  29. #define AUTO_PAUSE_RESUME_HISTORY_MASK ((1U << AUTO_PAUSE_RESUME_HISTORY_LEN) - 1)
  30. #define AUTO_PAUSE_RESUME_MIN_COUNT 5
  31. /* Distance-from-anchor alone can't tell a real departure from a smooth
  32. * position drift with no real movement behind it: a satellite geometry
  33. * change (a few satellites dropping out/back in) can walk the computed fix
  34. * steadily in one direction for tens of seconds while the receiver doesn't
  35. * move at all - the exact "persist over several samples" shape the resume
  36. * check above is trying to accept. Real walking keeps a fairly stable
  37. * step-to-step bearing; a geometry-driven drift has no reason to, so require
  38. * the bearing between consecutive paused samples to also stay consistent
  39. * before counting a sample as a real resume signal. Skipped below
  40. * HEADING_STEP_MIN_DIST_M: bearing is meaningless noise over a sub-meter step
  41. * (the two fixes are within GPS jitter of each other), and would otherwise
  42. * make genuinely stationary jitter look "inconsistent" for the wrong reason. */
  43. #define HEADING_STEP_MIN_DIST_M 1.5 /* ignore bearing between two samples closer than this */
  44. #define HEADING_CONSISTENT_MAX_DEG 60 /* max angle from the previous step's bearing to still agree */
  45. #define HEADING_HISTORY_LEN 7 /* same window length as the distance resume history above */
  46. #define HEADING_HISTORY_MASK ((1U << HEADING_HISTORY_LEN) - 1)
  47. #define HEADING_MIN_COUNT 5 /* same majority-of-window requirement as the distance resume history */
  48. static void auto_unpause(void) {
  49. if (!System.tracking_auto_paused)
  50. return;
  51. System.tracking_auto_paused = 0;
  52. if (!System.tracking_paused)
  53. pause_time_end();
  54. log_pause_event(0);
  55. beep(50, 4);
  56. }
  57. static void auto_pause_activate(void) {
  58. System.tracking_auto_paused = 1;
  59. pause_time_start();
  60. /* Otherwise, speed-counter progress from just before this decision (e.g.
  61. * 2 of the 3 consecutive above-threshold samples the resume check below
  62. * wants) could carry over and complete on the very next sample,
  63. * instantly flip-flopping straight back out of the pause this just
  64. * activated. */
  65. auto_pause.speed_counter = 0;
  66. /* Anchor the resume-by-distance check on where the pause actually
  67. * started, then let it drift towards the running average of positions
  68. * seen while paused (steadier than any single fix - see
  69. * auto_pause_process()). */
  70. auto_pause.pause_anchor = location;
  71. auto_pause.anchor_sample_count = 1;
  72. auto_pause.resume_high_history = 0;
  73. auto_pause.prev_location_valid = 0;
  74. auto_pause.prev_bearing_valid = 0;
  75. auto_pause.heading_consistent_history = 0;
  76. log_pause_event(1);
  77. beep(50, 3);
  78. }
  79. /* Call once, when tracking_pause() (working_modes.c) transitions
  80. * tracking_paused from set to clear (a manual resume): auto_pause_process()
  81. * only clears tracking_auto_paused while tracking_paused is *true* (to force
  82. * an active auto-pause off the instant a manual pause starts), so without
  83. * this, a manual resume while auto-paused would leave tracking_auto_paused
  84. * set - tracking_is_paused() (tracking_paused || tracking_auto_paused) would
  85. * still report paused, silently keeping the logger paused until the
  86. * speed/distance-based auto-resume condition happened to also be met, well
  87. * after the user asked to resume. No log/beep here (unlike auto_unpause()) -
  88. * the manual resume already has its own distinct feedback. */
  89. void auto_pause_clear_manual_resume(void) {
  90. System.tracking_auto_paused = 0;
  91. auto_pause.prev_distance = System.distance;
  92. auto_pause.point_counter = 0;
  93. auto_pause.speed_counter = 0;
  94. }
  95. /* Call once, whenever fix_gap_detected() reports a gap on the point about to
  96. * be passed to auto_pause_process(). Without this, point_counter keeps
  97. * counting straight through the gap with no idea how long it was or whether
  98. * the device kept moving during it, so reacquiring a fix that was lost while
  99. * genuinely moving could satisfy the stationary-time threshold within
  100. * moments, using mostly pre-gap progress - the opposite of what the
  101. * threshold is meant to require (persistent time actually stationary). */
  102. void auto_pause_reset_gap(void) {
  103. auto_pause.point_counter = 0;
  104. auto_pause.speed_counter = 0;
  105. auto_pause.prev_distance = System.distance;
  106. if (System.tracking_auto_paused) {
  107. /* Same reasoning as auto_pause_activate()'s anchor init: re-anchor on
  108. * the fix regained after the gap, not wherever the device was when
  109. * the fix was lost - it may have moved (or been carried) an unknown
  110. * distance in between, which resume-by-distance must not read as an
  111. * instant departure from a now-stale anchor. */
  112. auto_pause.pause_anchor = location;
  113. auto_pause.anchor_sample_count = 1;
  114. auto_pause.resume_high_history = 0;
  115. auto_pause.prev_location_valid = 0;
  116. auto_pause.prev_bearing_valid = 0;
  117. auto_pause.heading_consistent_history = 0;
  118. }
  119. }
  120. void auto_pause_process(void) {
  121. if (System.tracking_paused || !get_flag(CONFFLAG_AUTO_PAUSE)) { /* remove auto-pause */
  122. System.tracking_auto_paused = 0;
  123. auto_pause.prev_distance = System.distance;
  124. auto_pause.point_counter = 0;
  125. auto_pause.speed_counter = 0;
  126. return;
  127. }
  128. /* A poor/invalid fix must not be read as "moving": a single noisy sample
  129. * (e.g. a low-satellite, high-HDOP fix briefly reporting tens of km/h,
  130. * as seen on a real hike where reception dropped for a few seconds)
  131. * could otherwise satisfy the "3 consecutive" debounce below on its own
  132. * and resume a logger that is actually sitting still. Such a sample is
  133. * simply ignored - neither counted towards, nor resetting, the
  134. * consecutive-samples debounce - so it can't itself trigger a resume,
  135. * but also can't erase real progress made by genuine movement. */
  136. if (gps_fix_trustworthy()) {
  137. if (System.speed_x10 >= (unsigned int)System.conf.auto_pause_speed*10) { /* unpause when set speed is exceeded for 3 consecutive measurements */
  138. if (++auto_pause.speed_counter >= 3) {
  139. auto_pause.point_counter = 0;
  140. auto_pause.speed_counter = 0;
  141. auto_unpause();
  142. return;
  143. }
  144. } else {
  145. auto_pause.speed_counter = 0;
  146. }
  147. }
  148. /* Resume-by-distance: compared against an anchor updated to the running
  149. * average position seen while paused (steadier than any single fix),
  150. * scaled by HDOP (a poor fix needs to move further to count), and
  151. * required to persist for most of a short window rather than firing on
  152. * one sample - stationary GPS jitter (well under a meter per second) can
  153. * otherwise accumulate across enough net-movement windows to look like a
  154. * real departure. Evaluated every accepted point, not gated on the
  155. * auto_pause_time window below (that window is for the pause trigger
  156. * only), so the persistence history has one sample per point. Also
  157. * requires a consistent step-to-step bearing (see HEADING_* above) - a
  158. * satellite geometry change can drift the fix in a way that also
  159. * persists across this many samples, but has no reason to keep the same
  160. * heading step to step the way real walking does. */
  161. if (System.tracking_auto_paused && gps_fix_trustworthy()) {
  162. float resume_threshold_m = AUTO_PAUSE_RESUME_MIN_DIST_M;
  163. float hdop_threshold_m = (System.hdop_x100/100.0f) * AUTO_PAUSE_RESUME_HDOP_MULT;
  164. float dist_from_anchor_m;
  165. unsigned char resumed_this_sample;
  166. if (hdop_threshold_m > resume_threshold_m)
  167. resume_threshold_m = hdop_threshold_m;
  168. /* Capped well below the 16-bit wraparound point: once the running
  169. * average has this many samples behind it, one more changes it
  170. * negligibly anyway, and letting the count wrap to 0 would make the
  171. * very next sample overwrite the whole average with itself. */
  172. if (auto_pause.anchor_sample_count < 60000)
  173. auto_pause.anchor_sample_count++;
  174. auto_pause.pause_anchor.lat += (location.lat - auto_pause.pause_anchor.lat) / auto_pause.anchor_sample_count;
  175. auto_pause.pause_anchor.lon += (location.lon - auto_pause.pause_anchor.lon) / auto_pause.anchor_sample_count;
  176. dist_from_anchor_m = distance(&location, &auto_pause.pause_anchor);
  177. resumed_this_sample = dist_from_anchor_m > resume_threshold_m;
  178. auto_pause.resume_high_history = (auto_pause.resume_high_history << 1) | resumed_this_sample;
  179. if (auto_pause.prev_location_valid) {
  180. float step_dist_m = distance(&auto_pause.prev_location, &location);
  181. if (step_dist_m >= HEADING_STEP_MIN_DIST_M) {
  182. float this_bearing = bearing(&auto_pause.prev_location, &location);
  183. if (auto_pause.prev_bearing_valid) {
  184. float diff = fabs(this_bearing - auto_pause.prev_bearing);
  185. unsigned char heading_ok;
  186. if (diff > 180.0)
  187. diff = 360.0 - diff;
  188. heading_ok = diff <= HEADING_CONSISTENT_MAX_DEG;
  189. auto_pause.heading_consistent_history =
  190. (auto_pause.heading_consistent_history << 1) | heading_ok;
  191. }
  192. auto_pause.prev_bearing = this_bearing;
  193. auto_pause.prev_bearing_valid = 1;
  194. }
  195. /* else: too small a step to trust its bearing - leave prev_bearing
  196. * and heading_consistent_history untouched, neither confirming nor
  197. * breaking the run of consistent headings. */
  198. }
  199. auto_pause.prev_location = location;
  200. auto_pause.prev_location_valid = 1;
  201. if (__builtin_popcount(auto_pause.resume_high_history & AUTO_PAUSE_RESUME_HISTORY_MASK) >= AUTO_PAUSE_RESUME_MIN_COUNT
  202. && __builtin_popcount(auto_pause.heading_consistent_history & HEADING_HISTORY_MASK) >= HEADING_MIN_COUNT) {
  203. auto_pause.point_counter = 0;
  204. auto_unpause();
  205. return;
  206. }
  207. }
  208. if (++auto_pause.point_counter < System.conf.auto_pause_time)
  209. return;
  210. auto_pause.point_counter = 0;
  211. if (!System.tracking_auto_paused && gps_fix_trustworthy()) {
  212. /* Net displacement over this window can be small even while genuinely
  213. * moving throughout it - a winding path folding back on itself, or a
  214. * short out-and-back - and pausing on that alone, only to have the
  215. * very next sample or two immediately satisfy the speed-based resume
  216. * above, is itself a spurious (if brief) pause. If the logger is
  217. * already moving fast enough to resume on its own right now, don't
  218. * pause it in the first place.
  219. *
  220. * Gated on gps_fix_trustworthy(), matching the resume checks above:
  221. * System.distance itself must stay ungated (it's the odometer), but
  222. * a run of untrustworthy fixes must not be allowed to decide a pause
  223. * transition either way here - it's simply skipped for this window. */
  224. if ((System.distance - auto_pause.prev_distance)/100 <= System.conf.auto_pause_dist
  225. && System.speed_x10 < (unsigned int)System.conf.auto_pause_speed*10)
  226. auto_pause_activate(); /* pause: not enough net movement, and not currently moving fast */
  227. }
  228. auto_pause.prev_distance = System.distance;
  229. }