autopause.c 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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. log_pause_event(0);
  36. beep(50, 4);
  37. }
  38. static void auto_pause_activate(void) {
  39. System.tracking_auto_paused = 1;
  40. /* Otherwise, speed-counter progress from just before this decision (e.g.
  41. * 2 of the 3 consecutive above-threshold samples the resume check below
  42. * wants) could carry over and complete on the very next sample,
  43. * instantly flip-flopping straight back out of the pause this just
  44. * activated. */
  45. auto_pause.speed_counter = 0;
  46. /* Anchor the resume-by-distance check on where the pause actually
  47. * started, then let it drift towards the running average of positions
  48. * seen while paused (steadier than any single fix - see
  49. * auto_pause_process()). */
  50. auto_pause.pause_anchor = location;
  51. auto_pause.anchor_sample_count = 1;
  52. auto_pause.resume_high_history = 0;
  53. log_pause_event(1);
  54. beep(50, 3);
  55. }
  56. void auto_pause_process(void) {
  57. if (System.tracking_paused || !get_flag(CONFFLAG_AUTO_PAUSE)) { /* remove auto-pause */
  58. System.tracking_auto_paused = 0;
  59. auto_pause.prev_distance = System.distance;
  60. auto_pause.point_counter = 0;
  61. auto_pause.speed_counter = 0;
  62. return;
  63. }
  64. /* A poor/invalid fix must not be read as "moving": a single noisy sample
  65. * (e.g. a low-satellite, high-HDOP fix briefly reporting tens of km/h,
  66. * as seen on a real hike where reception dropped for a few seconds)
  67. * could otherwise satisfy the "3 consecutive" debounce below on its own
  68. * and resume a logger that is actually sitting still. Such a sample is
  69. * simply ignored - neither counted towards, nor resetting, the
  70. * consecutive-samples debounce - so it can't itself trigger a resume,
  71. * but also can't erase real progress made by genuine movement. */
  72. if (gps_fix_trustworthy()) {
  73. if (System.speed >= System.conf.auto_pause_speed) { /* unpause when set speed is exceeded for 3 consecutive measurements */
  74. if (++auto_pause.speed_counter >= 3) {
  75. auto_pause.point_counter = 0;
  76. auto_pause.speed_counter = 0;
  77. auto_unpause();
  78. return;
  79. }
  80. } else {
  81. auto_pause.speed_counter = 0;
  82. }
  83. }
  84. /* Resume-by-distance: compared against an anchor updated to the running
  85. * average position seen while paused (steadier than any single fix),
  86. * scaled by HDOP (a poor fix needs to move further to count), and
  87. * required to persist for most of a short window rather than firing on
  88. * one sample - stationary GPS jitter (well under a meter per second) can
  89. * otherwise accumulate across enough net-movement windows to look like a
  90. * real departure. Evaluated every accepted point, not gated on the
  91. * auto_pause_time window below (that window is for the pause trigger
  92. * only), so the persistence history has one sample per point. */
  93. if (System.tracking_auto_paused && gps_fix_trustworthy()) {
  94. float resume_threshold_m = AUTO_PAUSE_RESUME_MIN_DIST_M;
  95. float hdop_threshold_m = (System.hdop_x100/100.0f) * AUTO_PAUSE_RESUME_HDOP_MULT;
  96. float dist_from_anchor_m;
  97. unsigned char resumed_this_sample;
  98. if (hdop_threshold_m > resume_threshold_m)
  99. resume_threshold_m = hdop_threshold_m;
  100. /* Capped well below the 16-bit wraparound point: once the running
  101. * average has this many samples behind it, one more changes it
  102. * negligibly anyway, and letting the count wrap to 0 would make the
  103. * very next sample overwrite the whole average with itself. */
  104. if (auto_pause.anchor_sample_count < 60000)
  105. auto_pause.anchor_sample_count++;
  106. auto_pause.pause_anchor.lat += (location.lat - auto_pause.pause_anchor.lat) / auto_pause.anchor_sample_count;
  107. auto_pause.pause_anchor.lon += (location.lon - auto_pause.pause_anchor.lon) / auto_pause.anchor_sample_count;
  108. dist_from_anchor_m = distance(&location, &auto_pause.pause_anchor);
  109. resumed_this_sample = dist_from_anchor_m > resume_threshold_m;
  110. auto_pause.resume_high_history = (auto_pause.resume_high_history << 1) | resumed_this_sample;
  111. if (__builtin_popcount(auto_pause.resume_high_history & AUTO_PAUSE_RESUME_HISTORY_MASK) >= AUTO_PAUSE_RESUME_MIN_COUNT) {
  112. auto_pause.point_counter = 0;
  113. auto_unpause();
  114. return;
  115. }
  116. }
  117. if (++auto_pause.point_counter < System.conf.auto_pause_time)
  118. return;
  119. auto_pause.point_counter = 0;
  120. if (!System.tracking_auto_paused && gps_fix_trustworthy()) {
  121. /* Net displacement over this window can be small even while genuinely
  122. * moving throughout it - a winding path folding back on itself, or a
  123. * short out-and-back - and pausing on that alone, only to have the
  124. * very next sample or two immediately satisfy the speed-based resume
  125. * above, is itself a spurious (if brief) pause. If the logger is
  126. * already moving fast enough to resume on its own right now, don't
  127. * pause it in the first place.
  128. *
  129. * Gated on gps_fix_trustworthy(), matching the resume checks above:
  130. * System.distance itself must stay ungated (it's the odometer), but
  131. * a run of untrustworthy fixes must not be allowed to decide a pause
  132. * transition either way here - it's simply skipped for this window. */
  133. if ((System.distance - auto_pause.prev_distance)/100 <= System.conf.auto_pause_dist
  134. && System.speed < System.conf.auto_pause_speed)
  135. auto_pause_activate(); /* pause: not enough net movement, and not currently moving fast */
  136. }
  137. auto_pause.prev_distance = System.distance;
  138. }