fix(position): ground_speed is km/h, not m/s — fixes ~3.6× speed inflation (#3797)#3800
Conversation
…ation (#3797) Meshtastic's Position.ground_speed is km/h on the wire: the firmware writes it from TinyGPS++ reader.speed.kmph() (GPS::lookForLocation), despite the mesh.proto comment saying "m/s". MeshMonitor treated it as m/s and multiplied by 3.6 in convertSpeed(), inflating every reported speed ~3.6x — a wire value of 90 displayed as 324 km/h. (The ticket's premise that the field is cm/s / ~100x is also wrong; the real factor is 3.6x and the unit is km/h. Verified against the firmware GPS path.) - convertSpeed() now treats its input as km/h: metric passes through, imperial applies only the km/h->mph factor (drops the bogus x3.6). - Stored ground_speed telemetry unit relabeled 'm/s' -> 'km/h' (the value was always km/h, so legacy rows display correctly via convertSpeed — no migration). - Telemetry page unit label and code comments corrected. - convertSpeed tests rewritten for km/h semantics + a #3797 regression (90 -> 90). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review: fix(position): ground_speed is km/h, not m/s
This is a clean, well-researched fix. The root cause analysis (tracing to Core fix —
|
| Area | Status |
|---|---|
| Root cause analysis | ✅ Thorough, backed by firmware source |
convertSpeed logic |
✅ Correct |
| Storage unit labels | ✅ Fixed |
| Display unit labels | ✅ Fixed |
| Test coverage | ✅ Good, with regression test |
| Migration impact | ✅ None needed |
generateHeadingAwarePath JSDoc @param speed |
Recommend merging. The JSDoc @param speed in generateHeadingAwarePath is a nit worth fixing in a follow-up; it doesn't affect behavior.
…3797 review) Addresses Claude review note: the @param speed JSDoc still said m/s. The value passed (item.groundSpeed) has always been the raw km/h wire value, so this is a doc-only correction with no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review: fix(position): ground_speed is km/h, not m/s
This is a well-researched, clean fix. The root cause analysis (tracing to Core fix —
|
| Test | Status |
|---|---|
Metric pass-through (36 → 36 km/h) |
✅ |
km/h → mph conversion (100 → 62.1) |
✅ |
| Zero speed | ✅ |
Regression #3797 (90 → 90, not 324) |
✅ |
| High speed / highway | ✅ |
| Walking speed | ✅ |
| Metric/imperial consistency | ✅ |
One optional addition (non-blocking): An explicit test for an unrecognized distanceUnit (e.g. '' or 'ft') would document that it falls through to the metric pass-through. The behavior is correct today since the only branch is === 'mi', but a test would pin the contract.
No migration needed — correct
The bug was entirely at display time: convertSpeed applied ×3.6 at render. Stored values are the raw wire km/h values and are correct. No backfill required.
Summary
| Area | Status |
|---|---|
| Root cause analysis | ✅ Thorough, backed by firmware source |
convertSpeed logic |
✅ Correct |
| Storage unit labels | ✅ Fixed |
| Display unit labels | ✅ Fixed |
generateHeadingAwarePath JSDoc |
✅ Fixed (resolved from previous review) |
| Test coverage | ✅ Good, with regression test |
| Migration impact | ✅ None needed |
Recommend merging. All previous review nits have been addressed.
Summary
Position speeds (history popups, node popups, telemetry) were over-reported by ~3.6×: a node reporting
ground_speed = 90displayed as 324 km/h. The root cause is a unit mismatch — Meshtastic'sground_speedis km/h on the wire, but MeshMonitor treated it as m/s and multiplied by 3.6.Verification — the field is km/h, not m/s
The protobuf comment says m/s, but that comment is wrong; the firmware transmits km/h. The GPS path sets the field in exactly one place, using the TinyGPS++ km/h accessor:
https://github.com/meshtastic/firmware/blob/a4b2021782d3e323b8fdc6da166f4305c53c6181/src/gps/GPS.cpp#L1824
PositionModulecopies it through with no conversion:https://github.com/meshtastic/firmware/blob/a4b2021782d3e323b8fdc6da166f4305c53c6181/src/modules/PositionModule.cpp#L260
So
90on the wire = 90 km/h. OurconvertSpeed()didvalue * 3.6(m/s→km/h) ⇒ 324 km/h.Changes
src/utils/speedConversion.ts—convertSpeed()now treats its input as km/h: metric passes through, imperial applies only the km/h→mph factor (drops the bogus ×3.6).src/server/meshtasticManager.ts(×2) — storedground_speedtelemetry unit relabeledm/s→km/h(the value was always km/h, so existing rows display correctly viaconvertSpeed— no migration needed).src/pages/UnifiedTelemetryPage.tsx— telemetry unit labelm/s→km/h.src/utils/mapHelpers.tsx,src/contexts/MapContext.tsx— corrected misleading "m/s" comments.src/utils/mapHelpers.test.tsx— rewrote theconvertSpeedsuite for km/h semantics + a [BUG] Position history velocity inflated ~100× — ground_speed protobuf is cm/s, not m/s #3797 regression (90 → 90, not 324).Issues Resolved
Fixes #3797. Relates to #3791 (Bug 3, inflated position-segment velocity).
Documentation Updates
CHANGELOG.md— Fixed entry under [Unreleased].Testing
convertSpeedregression:90 → 90 km/h(metric),100 → 62.1 mph(imperial),0 → 0🤖 Generated with Claude Code