Skip to content

Commit 14162ab

Browse files
eriedclaude
andcommitted
fix(eucstats-meta): split wheel serial out of the model name
The InMotion P6 adapter was concatenating its serial into the model name ("InMotion P6 (SN12345)") as a temporary workaround until a proper P6 parser landed. The KingSong 0xB3 sub-cmd was doing the same thing — emitting a ModelName whose `name` was the bare serial, which overwrote the model set by the earlier 0xBB sub-cmd. Both made the eucstats upload meta non-uniform across riders: V14 uploads as `model = "InMotion V14"`, P6 was uploading as `model = "InMotion P6 (SN12345)"`, KingSong as `model = "SN98765432"`. Add a proper DecodeResult.Serial slot, lift the serial out of both adapters into it, route it through WheelRepository.wheelSerial, and populate the eucstats meta's existing `serial` JSON field that was previously hardcoded to null. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 4e99868 commit 14162ab

5 files changed

Lines changed: 32 additions & 9 deletions

File tree

app/src/main/java/com/eried/eucplanet/ble/InMotionV2Adapter.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -421,15 +421,16 @@ class InMotionV2Adapter @Inject constructor() : WheelAdapter {
421421
} ?: DecodeResult.Unknown
422422
}
423423
0x06 -> {
424-
// info bundle: skip `02 86 01 00`, then ASCII serial follows the
425-
// 0x01 record marker. We surface the serial as the model name so
426-
// the dashboard has *something* to identify the wheel until a
427-
// proper P6 parser lands.
424+
// info bundle: skip `02 86 01 00`, then ASCII serial follows
425+
// the 0x01 record marker. Emit the serial in its own decode
426+
// result so the eucstats upload meta gets `model = "InMotion
427+
// P6"` (uniform with V14 / V12) and `serial = "..."` in a
428+
// separate JSON field. The dashboard label was already set
429+
// earlier from notifyConnectingTo (line 72), so we don't need
430+
// to re-emit a ModelName here.
428431
if (data.size < 4) return DecodeResult.Unknown
429432
val serial = InMotionV2Parser.parseP6Serial(data.copyOfRange(4, data.size))
430-
if (serial != null) {
431-
DecodeResult.ModelName("InMotion P6 ($serial)", InMotionV2Model.P6)
432-
} else DecodeResult.Unknown
433+
if (serial != null) DecodeResult.Serial(serial) else DecodeResult.Unknown
433434
}
434435
0x20 -> {
435436
// settings page A: `02 a0 [body]`. The body starts with a 0x20

app/src/main/java/com/eried/eucplanet/ble/KingsongAdapter.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,13 @@ class KingsongAdapter @Inject constructor() : WheelAdapter {
191191
results
192192
}
193193
0xB3 -> {
194+
// Serial reply from the 0xB3 sub-cmd. Was previously
195+
// overwriting the model name (set earlier by the 0xBB
196+
// sub-cmd) so the dashboard label and eucstats upload meta
197+
// ended up showing the bare serial instead of "KingSong
198+
// S22" / etc. Emit it in its own slot so model stays clean.
194199
val serial = KingsongParser.parseSerial(rawBytes) ?: return emptyList()
195-
listOf(DecodeResult.ModelName(serial, detectedModel))
200+
listOf(DecodeResult.Serial(serial))
196201
}
197202
0xF5 -> {
198203
val cpu = KingsongParser.parseCpuPwm(rawBytes) ?: return emptyList()

app/src/main/java/com/eried/eucplanet/ble/WheelAdapter.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ sealed class DecodeResult {
308308
* for display.
309309
*/
310310
data class ModelName(val name: String, val model: Any? = null) : DecodeResult()
311+
/** Wheel-reported serial number. Stays out of [ModelName.name] so the
312+
* eucstats upload meta keeps `model` uniform across families (e.g.
313+
* "InMotion P6", never "InMotion P6 (SN12345)") and the `serial` slot
314+
* carries the per-unit identifier separately. */
315+
data class Serial(val serial: String) : DecodeResult()
311316
data class Firmware(val display: String, val mainBoard: String, val driverBoard: String, val ble: String) : DecodeResult()
312317
data class TotalDistance(val km: Float) : DecodeResult()
313318
data class AuthKey(val encryptedKey: ByteArray) : DecodeResult() {

app/src/main/java/com/eried/eucplanet/data/repository/TripRepository.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ class TripRepository @Inject constructor(
393393
val wheelMeta = buildWheelMetaJson(
394394
brand = wheelRepository.connectedBrand.value,
395395
model = wheelRepository.modelName.value,
396-
serial = null,
396+
serial = wheelRepository.wheelSerial.value,
397397
bleMac = settingsRepository.get().lastDeviceAddress,
398398
bleName = wheelRepository.connectedDeviceName.value,
399399
firmware = wheelRepository.firmwareVersion.value,

app/src/main/java/com/eried/eucplanet/data/repository/WheelRepository.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,13 @@ class WheelRepository @Inject constructor(
248248
private val _modelName = MutableStateFlow<String?>(null)
249249
val modelName: StateFlow<String?> = _modelName.asStateFlow()
250250

251+
// Wheel serial number reported by the firmware (currently emitted by the
252+
// KingSong 0xB3 sub-cmd and the InMotion P6 0x06 info bundle). Separate
253+
// from modelName so eucstats meta carries them in different JSON fields
254+
// and the dashboard's model label stays uniform across riders.
255+
private val _wheelSerial = MutableStateFlow<String?>(null)
256+
val wheelSerial: StateFlow<String?> = _wheelSerial.asStateFlow()
257+
251258
private val _firmwareVersion = MutableStateFlow<String?>(null)
252259
val firmwareVersion: StateFlow<String?> = _firmwareVersion.asStateFlow()
253260

@@ -557,6 +564,7 @@ class WheelRepository @Inject constructor(
557564
chargeTempHist.clear()
558565
_chargingSnapshot.value = ChargingSnapshot()
559566
_modelName.value = null
567+
_wheelSerial.value = null
560568
_firmwareVersion.value = null
561569
_maxSpeedCap.value = DEFAULT_MAX_SPEED_KMH
562570
_wheelData.value =
@@ -1302,6 +1310,10 @@ class WheelRepository @Inject constructor(
13021310
_firmwareVersion.value = result.display
13031311
Log.i(TAG, "Firmware: Main=${result.mainBoard} Drv=${result.driverBoard} BLE=${result.ble}")
13041312
}
1313+
is DecodeResult.Serial -> {
1314+
_wheelSerial.value = result.serial
1315+
Log.i(TAG, "Wheel serial: ${result.serial}")
1316+
}
13051317
is DecodeResult.AuthKey -> {
13061318
Log.i(TAG, "Auth key received: ${result.encryptedKey.joinToString(" ") { "%02X".format(it) }}")
13071319
pendingAuthKeyDeferred?.complete(result.encryptedKey)

0 commit comments

Comments
 (0)