Skip to content

Commit 8c0e870

Browse files
eriedclaude
andcommitted
Sources sheet: graphical Compare tab, more metrics, tab-inline live dots
UI refactor on the data-sources bottom sheet: - Drop the standalone "Live data sources" pill row above the tabs. Each tab label now carries its own live/offline dot (filled coloured circle vs hollow grey ring) so source state reads inline with the tab name. - Compare tab redesign: two side-by-side picker chip rows joined by a centered "VS" badge. No more "Pick two sources" / "A" / "B" labels. - Comparisons are now graphical line charts of a rolling 30-second time series per metric, per source. Both lines share an auto-fitted Y axis; the panel header carries the current Δ as a numeric badge so the value stays glanceable. - New comparison metrics: heading (deg), vertical speed (m/s), |G| horizontal. Phone tab now also shows heading from Location.bearing when speed > 0; satellites count + ±accuracy line for any source that reports them. RaceBox parser additions (both standard 0x01/0x07 and extended 0xFF/0x01 frames): - Heading of motion from offset 64 (int32 deg * 1e-5, wrapped to 0..360). - Vertical speed derived from NED velD at offset 56 (mm/s, +down -> +up). - Number of satellites from offset 23. ExternalGpsSample + SourceSnapshot gain matching nullable fields. Version bump for branch testing: 0.6.4-ex_gps (versionCode 43), so test builds are obvious vs the production 0.6.3 build on main. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4d044e6 commit 8c0e870

6 files changed

Lines changed: 516 additions & 161 deletions

File tree

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ android {
2727
applicationId = "com.eried.eucplanet"
2828
minSdk = 29
2929
targetSdk = 35
30-
versionCode = 42
31-
versionName = "0.6.3"
30+
versionCode = 43
31+
versionName = "0.6.4-ex_gps"
3232

3333
val buildStamp = SimpleDateFormat("yyMMdd.HHmm")
3434
.apply { timeZone = TimeZone.getTimeZone("UTC") }

app/src/main/java/com/eried/eucplanet/ble/gps/RaceBoxAdapter.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,17 @@ class RaceBoxAdapter @Inject constructor() : ExternalGpsAdapter {
101101
// are useful as a position sample. Drop and let the next frame land.
102102
if (fixType != 2 && fixType != 3 && fixType != 4) return null
103103

104+
val numSV = frame[payloadStart + 23].toInt() and 0xFF
104105
val lonRaw = readInt32LE(frame, payloadStart + 24)
105106
val latRaw = readInt32LE(frame, payloadStart + 28)
106107
val hMslRaw = readInt32LE(frame, payloadStart + 36)
107108
val hAccRaw = readUInt32LE(frame, payloadStart + 40)
109+
// NED vertical velocity, positive = down. Flip sign so the field
110+
// reads "+up" consistent with phone Location.verticalAccuracyMeters
111+
// semantics elsewhere.
112+
val velDRaw = readInt32LE(frame, payloadStart + 56)
108113
val gSpeedRaw = readInt32LE(frame, payloadStart + 60)
114+
val headMotRaw = readInt32LE(frame, payloadStart + 64)
109115

110116
val speedMmS = gSpeedRaw.coerceAtLeast(0)
111117
return ExternalGpsSample(
@@ -114,7 +120,14 @@ class RaceBoxAdapter @Inject constructor() : ExternalGpsAdapter {
114120
latitude = latRaw * 1e-7, // deg
115121
longitude = lonRaw * 1e-7, // deg
116122
altitudeMeters = hMslRaw / 1000f, // mm → m
117-
accuracyMeters = hAccRaw / 1000f // mm → m
123+
accuracyMeters = hAccRaw / 1000f, // mm → m
124+
headingDeg = (headMotRaw * 1e-5f).let { h ->
125+
// Wrap to [0, 360); the wire format can carry small negative
126+
// values when heading dithers near zero.
127+
((h % 360f) + 360f) % 360f
128+
},
129+
verticalSpeedMps = -velDRaw / 1000f, // mm/s down → m/s up
130+
numSatellites = numSV
118131
)
119132
}
120133

@@ -135,14 +148,17 @@ class RaceBoxAdapter @Inject constructor() : ExternalGpsAdapter {
135148
val fixType = frame[payloadStart + 20].toInt() and 0xFF
136149
if (fixType != 2 && fixType != 3 && fixType != 4) return null
137150

151+
val numSV = frame[payloadStart + 23].toInt() and 0xFF
138152
val lonRaw = readInt32LE(frame, payloadStart + 24)
139153
val latRaw = readInt32LE(frame, payloadStart + 28)
140154
val hMslRaw = readInt32LE(frame, payloadStart + 36)
141155
val hAccRaw = readUInt32LE(frame, payloadStart + 40)
156+
val velDRaw = readInt32LE(frame, payloadStart + 56)
142157
// Note: in the extended frame the speed field sits at offset 60 too,
143158
// same as standard PVT — RaceBox kept the layout compatible for the
144159
// shared fields.
145160
val gSpeedRaw = readInt32LE(frame, payloadStart + 60)
161+
val headMotRaw = readInt32LE(frame, payloadStart + 64)
146162

147163
// Accel: int16 LE milligees on each axis (positive Y = forward).
148164
// Divide by 1000 to get g.
@@ -160,7 +176,12 @@ class RaceBoxAdapter @Inject constructor() : ExternalGpsAdapter {
160176
accuracyMeters = hAccRaw / 1000f,
161177
accelXG = ax,
162178
accelYG = ay,
163-
accelZG = az
179+
accelZG = az,
180+
headingDeg = (headMotRaw * 1e-5f).let { h ->
181+
((h % 360f) + 360f) % 360f
182+
},
183+
verticalSpeedMps = -velDRaw / 1000f,
184+
numSatellites = numSV
164185
)
165186
}
166187

app/src/main/java/com/eried/eucplanet/data/model/ExternalGpsSample.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ data class ExternalGpsSample(
2121
val accelXG: Float? = null,
2222
val accelYG: Float? = null,
2323
val accelZG: Float? = null,
24+
/** Heading of motion in degrees [0..360), or null if unknown. Both
25+
* standard NAV-PVT and the extended frame report this at offset 64
26+
* as int32 deg × 1e-5. */
27+
val headingDeg: Float? = null,
28+
/** Vertical speed in m/s (positive up), or null if unknown. Derived
29+
* from the NED down-velocity (offset 56, int32 mm/s, +down → flip). */
30+
val verticalSpeedMps: Float? = null,
31+
/** Number of satellites used in the fix, or null if unknown. Useful as
32+
* a per-source quality indicator. */
33+
val numSatellites: Int? = null,
2434
val timestamp: Long = System.currentTimeMillis()
2535
)
2636

app/src/main/java/com/eried/eucplanet/ui/dashboard/sources/DataSource.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,16 @@ data class SourceSnapshot(
4545
/** Accel in g, axes aligned to the source's reference frame. */
4646
val accelXG: Float? = null,
4747
val accelYG: Float? = null,
48-
val accelZG: Float? = null
48+
val accelZG: Float? = null,
49+
/** Heading of motion in degrees [0..360), or null. Phone source fills
50+
* this in from [android.location.Location.getBearing] when speed > 0. */
51+
val headingDeg: Float? = null,
52+
/** Vertical speed in m/s (+up), or null. */
53+
val verticalSpeedMps: Float? = null,
54+
/** GPS quality indicator — only filled for sources with a GPS receiver. */
55+
val numSatellites: Int? = null,
56+
/** Horizontal positional accuracy in metres, or null. */
57+
val accuracyMeters: Float? = null
4958
) {
5059
/** Magnitude of horizontal G-force, useful as a single safety number. */
5160
val horizGMagnitude: Float?

0 commit comments

Comments
 (0)