Speed pipeline: smoothing, stop clamping, distance, moving average #17

Closed
opened 2026-08-31 17:14:40 +02:00 by robert · 1 comment
robert commented 2026-08-31 17:14:40 +02:00 (Migrated from git.butzei.de)

Goal

Turn raw fixes into numbers that look right on the handlebars.

Acceptance criteria

  • FusedLocationProviderClient at 1 s, high accuracy
  • Location.getSpeed() used where available, distance/dt as fallback
  • Speed smoothed over roughly 3 s
  • Speed clamped to zero below about 0.8 m/s so the display does not drift at traffic lights
  • Cumulative distance accumulated with an accuracy gate to reject bad fixes
  • Moving average excludes stopped time
  • Fixes with poor accuracy rejected rather than folded into distance

Files

  • companion/.../location/SpeedPipeline.kt

Update — 2026-09-01: "stopped" is defined in #69, not here

Moving time and the moving average both need stop detection, and FR-M8 (auto-pause) sat two phases
away in Phase 5 as "behaviour not yet specified". Two phases apart, that becomes two definitions of
stopped and rides where the average speed and the ride timer disagree.

  • Consume the shared StopDetector from #69 rather than defining stopped locally
  • State explicitly whether the 0.8 m/s display clamp and the stop threshold are the same number,
    and why

See D35.

## Goal Turn raw fixes into numbers that look right on the handlebars. ## Acceptance criteria - [ ] `FusedLocationProviderClient` at 1 s, high accuracy - [ ] `Location.getSpeed()` used where available, distance/dt as fallback - [ ] Speed smoothed over roughly 3 s - [ ] Speed clamped to zero below about 0.8 m/s so the display does not drift at traffic lights - [ ] Cumulative distance accumulated with an accuracy gate to reject bad fixes - [ ] Moving average excludes stopped time - [ ] Fixes with poor accuracy rejected rather than folded into distance ## Files - `companion/.../location/SpeedPipeline.kt` ## Update — 2026-09-01: "stopped" is defined in #69, not here Moving time and the moving average both need stop detection, and FR-M8 (auto-pause) sat two phases away in Phase 5 as "behaviour not yet specified". Two phases apart, that becomes two definitions of stopped and rides where the average speed and the ride timer disagree. - [ ] Consume the shared `StopDetector` from #69 rather than defining stopped locally - [ ] State explicitly whether the 0.8 m/s display clamp and the stop threshold are the same number, and why See D35.
Owner

Closed by PR #101 (merged): extends #69's SpeedPipeline.kt (not a fork) with a real onGpsFix() \u2014 30m accuracy gate (with a genuinely-caught NaN edge case: Location.hasAccuracy()==false surfaces as NaN, and NaN > threshold is false under IEEE 754, so a naive check would have let unknown-accuracy fixes through undetected), has-speed vs. position-delta/dt fallback, 3-sample/~3s moving-average smoothing, real haversine distance between accepted fixes, and a display clamp to exactly 0.0 once StopDetector confirms STOPPED (the 0.8 m/s number itself still lives in exactly one place). Rejected fixes are counted and surfaced (rejectedFixCount/lastRejectedFix), never silently dropped.\n\nCorrectly did not add FusedLocationProviderClient despite the issue's literal wording \u2014 found that #15 (merged earlier tonight) already deliberately uses LocationManager's modern LocationRequest.Builder instead, a documented D19 decision enabled by minSdk 31. Respected the existing architecture rather than blindly implementing stale issue text.\n\nVerified for real: new SpeedPipelineTest cases (accuracy gate incl. the NaN case, has-speed precedence, dt-fallback smoothing convergence, wheel-live drops not counted as rejections, display clamp) all green via ./gradlew :companion:core:test; ./gradlew :companion:assembleDebug clean. No GPS hardware in this sandbox \u2014 the live location callback is compiled, not exercised.

Closed by PR #101 (merged): extends #69's SpeedPipeline.kt (not a fork) with a real onGpsFix() \u2014 30m accuracy gate (with a genuinely-caught NaN edge case: Location.hasAccuracy()==false surfaces as NaN, and NaN > threshold is false under IEEE 754, so a naive check would have let unknown-accuracy fixes through undetected), has-speed vs. position-delta/dt fallback, 3-sample/~3s moving-average smoothing, real haversine distance between accepted fixes, and a display clamp to exactly 0.0 once StopDetector confirms STOPPED (the 0.8 m/s number itself still lives in exactly one place). Rejected fixes are counted and surfaced (rejectedFixCount/lastRejectedFix), never silently dropped.\n\n**Correctly did not add FusedLocationProviderClient** despite the issue's literal wording \u2014 found that #15 (merged earlier tonight) already deliberately uses LocationManager's modern LocationRequest.Builder instead, a documented D19 decision enabled by minSdk 31. Respected the existing architecture rather than blindly implementing stale issue text.\n\nVerified for real: new SpeedPipelineTest cases (accuracy gate incl. the NaN case, has-speed precedence, dt-fallback smoothing convergence, wheel-live drops not counted as rejections, display clamp) all green via `./gradlew :companion:core:test`; `./gradlew :companion:assembleDebug` clean. No GPS hardware in this sandbox \u2014 the live location callback is compiled, not exercised.
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
robert/PedalPebble#17
No description provided.