autopause.c 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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. static void auto_unpause(void) {
  32. if (!System.tracking_auto_paused)
  33. return;
  34. System.tracking_auto_paused = 0;
  35. if (!System.tracking_paused)
  36. pause_time_end();
  37. log_pause_event(0);
  38. beep(50, 4);
  39. }
  40. static void auto_pause_activate(void) {
  41. System.tracking_auto_paused = 1;
  42. pause_time_start();
  43. /* Otherwise, speed-counter progress from just before this decision (e.g.
  44. * 2 of the 3 consecutive above-threshold samples the resume check below
  45. * wants) could carry over and complete on the very next sample,
  46. * instantly flip-flopping straight back out of the pause this just
  47. * activated. */
  48. auto_pause.speed_counter = 0;
  49. /* Anchor the resume-by-distance check on where the pause actually
  50. * started, then let it drift towards the running average of positions
  51. * seen while paused (steadier than any single fix - see
  52. * auto_pause_process()). */
  53. auto_pause.pause_anchor = location;
  54. auto_pause.anchor_sample_count = 1;
  55. auto_pause.resume_high_history = 0;
  56. log_pause_event(1);
  57. beep(50, 3);
  58. }
  59. /* Call once, when tracking_pause() (working_modes.c) transitions
  60. * tracking_paused from set to clear (a manual resume): auto_pause_process()
  61. * only clears tracking_auto_paused while tracking_paused is *true* (to force
  62. * an active auto-pause off the instant a manual pause starts), so without
  63. * this, a manual resume while auto-paused would leave tracking_auto_paused
  64. * set - tracking_is_paused() (tracking_paused || tracking_auto_paused) would
  65. * still report paused, silently keeping the logger paused until the
  66. * speed/distance-based auto-resume condition happened to also be met, well
  67. * after the user asked to resume. No log/beep here (unlike auto_unpause()) -
  68. * the manual resume already has its own distinct feedback. */
  69. void auto_pause_clear_manual_resume(void) {
  70. System.tracking_auto_paused = 0;
  71. auto_pause.prev_distance = System.distance;
  72. auto_pause.point_counter = 0;
  73. auto_pause.speed_counter = 0;
  74. }
  75. /* Call once, whenever fix_gap_detected() reports a gap on the point about to
  76. * be passed to auto_pause_process(). Without this, point_counter keeps
  77. * counting straight through the gap with no idea how long it was or whether
  78. * the device kept moving during it, so reacquiring a fix that was lost while
  79. * genuinely moving could satisfy the stationary-time threshold within
  80. * moments, using mostly pre-gap progress - the opposite of what the
  81. * threshold is meant to require (persistent time actually stationary). */
  82. void auto_pause_reset_gap(void) {
  83. auto_pause.point_counter = 0;
  84. auto_pause.speed_counter = 0;
  85. auto_pause.prev_distance = System.distance;
  86. if (System.tracking_auto_paused) {
  87. /* Same reasoning as auto_pause_activate()'s anchor init: re-anchor on
  88. * the fix regained after the gap, not wherever the device was when
  89. * the fix was lost - it may have moved (or been carried) an unknown
  90. * distance in between, which resume-by-distance must not read as an
  91. * instant departure from a now-stale anchor. */
  92. auto_pause.pause_anchor = location;
  93. auto_pause.anchor_sample_count = 1;
  94. auto_pause.resume_high_history = 0;
  95. }
  96. }
  97. void auto_pause_process(void) {
  98. if (System.tracking_paused || !get_flag(CONFFLAG_AUTO_PAUSE)) { /* remove auto-pause */
  99. System.tracking_auto_paused = 0;
  100. auto_pause.prev_distance = System.distance;
  101. auto_pause.point_counter = 0;
  102. auto_pause.speed_counter = 0;
  103. return;
  104. }
  105. /* A poor/invalid fix must not be read as "moving": a single noisy sample
  106. * (e.g. a low-satellite, high-HDOP fix briefly reporting tens of km/h,
  107. * as seen on a real hike where reception dropped for a few seconds)
  108. * could otherwise satisfy the "3 consecutive" debounce below on its own
  109. * and resume a logger that is actually sitting still. Such a sample is
  110. * simply ignored - neither counted towards, nor resetting, the
  111. * consecutive-samples debounce - so it can't itself trigger a resume,
  112. * but also can't erase real progress made by genuine movement. */
  113. if (gps_fix_trustworthy()) {
  114. if (System.speed >= System.conf.auto_pause_speed) { /* unpause when set speed is exceeded for 3 consecutive measurements */
  115. if (++auto_pause.speed_counter >= 3) {
  116. auto_pause.point_counter = 0;
  117. auto_pause.speed_counter = 0;
  118. auto_unpause();
  119. return;
  120. }
  121. } else {
  122. auto_pause.speed_counter = 0;
  123. }
  124. }
  125. /* Resume-by-distance: compared against an anchor updated to the running
  126. * average position seen while paused (steadier than any single fix),
  127. * scaled by HDOP (a poor fix needs to move further to count), and
  128. * required to persist for most of a short window rather than firing on
  129. * one sample - stationary GPS jitter (well under a meter per second) can
  130. * otherwise accumulate across enough net-movement windows to look like a
  131. * real departure. Evaluated every accepted point, not gated on the
  132. * auto_pause_time window below (that window is for the pause trigger
  133. * only), so the persistence history has one sample per point. */
  134. if (System.tracking_auto_paused && gps_fix_trustworthy()) {
  135. float resume_threshold_m = AUTO_PAUSE_RESUME_MIN_DIST_M;
  136. float hdop_threshold_m = (System.hdop_x100/100.0f) * AUTO_PAUSE_RESUME_HDOP_MULT;
  137. float dist_from_anchor_m;
  138. unsigned char resumed_this_sample;
  139. if (hdop_threshold_m > resume_threshold_m)
  140. resume_threshold_m = hdop_threshold_m;
  141. /* Capped well below the 16-bit wraparound point: once the running
  142. * average has this many samples behind it, one more changes it
  143. * negligibly anyway, and letting the count wrap to 0 would make the
  144. * very next sample overwrite the whole average with itself. */
  145. if (auto_pause.anchor_sample_count < 60000)
  146. auto_pause.anchor_sample_count++;
  147. auto_pause.pause_anchor.lat += (location.lat - auto_pause.pause_anchor.lat) / auto_pause.anchor_sample_count;
  148. auto_pause.pause_anchor.lon += (location.lon - auto_pause.pause_anchor.lon) / auto_pause.anchor_sample_count;
  149. dist_from_anchor_m = distance(&location, &auto_pause.pause_anchor);
  150. resumed_this_sample = dist_from_anchor_m > resume_threshold_m;
  151. auto_pause.resume_high_history = (auto_pause.resume_high_history << 1) | resumed_this_sample;
  152. if (__builtin_popcount(auto_pause.resume_high_history & AUTO_PAUSE_RESUME_HISTORY_MASK) >= AUTO_PAUSE_RESUME_MIN_COUNT) {
  153. auto_pause.point_counter = 0;
  154. auto_unpause();
  155. return;
  156. }
  157. }
  158. if (++auto_pause.point_counter < System.conf.auto_pause_time)
  159. return;
  160. auto_pause.point_counter = 0;
  161. if (!System.tracking_auto_paused && gps_fix_trustworthy()) {
  162. /* Net displacement over this window can be small even while genuinely
  163. * moving throughout it - a winding path folding back on itself, or a
  164. * short out-and-back - and pausing on that alone, only to have the
  165. * very next sample or two immediately satisfy the speed-based resume
  166. * above, is itself a spurious (if brief) pause. If the logger is
  167. * already moving fast enough to resume on its own right now, don't
  168. * pause it in the first place.
  169. *
  170. * Gated on gps_fix_trustworthy(), matching the resume checks above:
  171. * System.distance itself must stay ungated (it's the odometer), but
  172. * a run of untrustworthy fixes must not be allowed to decide a pause
  173. * transition either way here - it's simply skipped for this window. */
  174. if ((System.distance - auto_pause.prev_distance)/100 <= System.conf.auto_pause_dist
  175. && System.speed < System.conf.auto_pause_speed)
  176. auto_pause_activate(); /* pause: not enough net movement, and not currently moving fast */
  177. }
  178. auto_pause.prev_distance = System.distance;
  179. }