autopause.c 8.1 KB

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