From 024850bf4886601273db0e013258fd0118694aac Mon Sep 17 00:00:00 2001 From: Seb3thehacker Date: Fri, 2 Oct 2026 15:51:15 -0700 Subject: [PATCH] fix car sensor channel, mark Metrolist working Sensors live in service field 2, not 5 (that's the mic), so the channel never opened. Message ids were also one off and the start request was missing min_update_period. --- .../seb3thehacker/gearslip/GearslipRunner.kt | 19 +++++++--- .../gearslip/ServiceDiscovery.kt | 8 ++-- .../seb3thehacker/gearslip/car/CarSensors.kt | 4 +- .../seb3thehacker/gearslip/host/KnownApps.kt | 1 + .../gearslip/ServiceDiscoverySensorTest.kt | 38 +++++++++++++++++++ 5 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 gearslip/src/test/java/app/seb3thehacker/gearslip/ServiceDiscoverySensorTest.kt diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt index 91f38a4..ae1e722 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt @@ -460,10 +460,12 @@ class GearslipRunner( log.w("head unit refused the sensor channel (status=$status)") return } - // SensorRequest { required int32 sensor_type = 1; } + // SensorRequest { required SensorType type = 1; required int64 min_update_period = 2; } + // A period of 0 asks for every update the car sends. sensorTypes.forEach { type -> send( - MSG_SENSOR_START_REQUEST, Protobuf.varintField(1, type.toLong()), + MSG_SENSOR_START_REQUEST, + Protobuf.varintField(1, type.toLong()) + Protobuf.varintField(2, 0L), encrypted = true, channel = sensorChannelId, ) log.i("-> SensorStartRequest(sensor_type=$type)") @@ -478,6 +480,11 @@ class GearslipRunner( log.i("<- SensorEvent:\n" + Protobuf.describe(body)) } + // SensorError { required SensorType sensor_type = 1; required SensorErrorType sensor_error_type = 2; } + MSG_SENSOR_ERROR -> log.w( + "<- SensorError: type=${Protobuf.readInt32Field(body, 1)} error=${Protobuf.readInt32Field(body, 2)}", + ) + else -> log.i("<- unhandled sensor message id=$messageId (${body.size} bytes)") } } @@ -988,9 +995,11 @@ class GearslipRunner( // aap_protobuf/service/sensor/... - same "first specific message on this channel type // starts at 0x8000" numbering as media/input above. - const val MSG_SENSOR_START_REQUEST = 32768 - const val MSG_SENSOR_START_RESPONSE = 32769 - const val MSG_SENSOR_EVENT_INDICATION = 32770 + // aasdk SensorMessageId: REQUEST, RESPONSE, BATCH, ERROR. + const val MSG_SENSOR_START_REQUEST = 32769 + const val MSG_SENSOR_START_RESPONSE = 32770 + const val MSG_SENSOR_EVENT_INDICATION = 32771 + const val MSG_SENSOR_ERROR = 32772 const val STREAM_MEDIA = 3 const val CODEC_H264_BP = 3 diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt index dfd20bb..8c5c0c9 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt @@ -196,9 +196,9 @@ object ServiceDiscovery { } /** - * The head unit's sensor feed, from SensorSourceService (Service field 5): - * SensorSourceService { repeated SensorType sensors = 1; } - * SensorType { required int32 type = 1; } + * The head unit's sensor feed, from SensorSourceService (Service field 2; field 5 is the car's mic): + * SensorSourceService { repeated Sensor sensors = 1; } + * Sensor { required SensorType sensor_type = 1; } * * Field numbers for what actually rides inside each SensorEvent once subscribed are not * pinned down here - unlike video/audio/input, there's no independent source to check them @@ -210,7 +210,7 @@ object ServiceDiscovery { for (channel in Wire.allBytes(Wire.fields(response), 1)) { val service = Wire.fields(channel) val id = Wire.varint(service, 1)?.toInt() ?: continue - val sensorSource = Wire.bytes(service, 5) ?: continue + val sensorSource = Wire.bytes(service, 2) ?: continue val types = Wire.allBytes(Wire.fields(sensorSource), 1).mapNotNull { Wire.varint(Wire.fields(it), 1)?.toInt() } diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSensors.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSensors.kt index 1126dfe..2b0e5cb 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSensors.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSensors.kt @@ -87,8 +87,8 @@ object SensorType { 18 to "Tire pressure", 19 to "Accelerometer", 20 to "Gyroscope", - 21 to "Gravity", - 22 to "Wheel tick / distance", + 21 to "GPS satellites", + 22 to "Toll card", ) fun name(id: Int): String = NAMES[id]?.let { "$it (#$id)" } ?: "Sensor #$id" diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/host/KnownApps.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/host/KnownApps.kt index 0d25797..005124a 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/host/KnownApps.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/host/KnownApps.kt @@ -16,6 +16,7 @@ object KnownApps { "in.krosbits.musicolet", // Musicolet: browse, play, seek, lyrics "com.kododake.aabrowser", // Aardvark: browser template, starts, draws, takes touches "com.vivi.vivimusic", // Vivi Music: browse, play, seek, works well + "com.metrolist.music", // Metrolist: browse, play, works "nl.flitsmeister", // Flitsmeister: map, routing, alerts - works well ) diff --git a/gearslip/src/test/java/app/seb3thehacker/gearslip/ServiceDiscoverySensorTest.kt b/gearslip/src/test/java/app/seb3thehacker/gearslip/ServiceDiscoverySensorTest.kt new file mode 100644 index 0000000..f28a51c --- /dev/null +++ b/gearslip/src/test/java/app/seb3thehacker/gearslip/ServiceDiscoverySensorTest.kt @@ -0,0 +1,38 @@ +package app.seb3thehacker.gearslip + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test + +/** + * The 2018 Uconnect's sensor and mic services, from `archive/spike/car-run-2026-09-16-run2.log`. + * Channel 7 offers sensors in Service field 2; channel 3 is the mic in field 5. Reading the wrong + * field finds nothing at all, so the sensor channel never opens. + */ +class ServiceDiscoverySensorTest { + + private fun varintField(field: Int, value: Long) = Protobuf.varintField(field, value) + + private fun lenField(field: Int, body: ByteArray): ByteArray = + Protobuf.varint(((field shl 3) or 2).toLong()) + Protobuf.varint(body.size.toLong()) + body + + private fun micSource() = lenField( + 1, + varintField(1, 3L) + lenField(5, varintField(1, 1L) + lenField(2, varintField(1, 16_000L))), + ) + + private fun sensorSource(types: List): ByteArray { + val sensors = types.fold(ByteArray(0)) { acc, t -> acc + lenField(1, varintField(1, t.toLong())) } + return lenField(1, varintField(1, 7L) + lenField(2, sensors + varintField(2, 256L))) + } + + @Test + fun findsUconnectSensorsInFieldTwo() { + val response = micSource() + sensorSource(listOf(1, 3, 10, 13, 20, 21)) + val sensors = ServiceDiscovery.findSensorService(response) + assertNotNull(sensors) + sensors!! + assertEquals(7, sensors.serviceId) + assertEquals(listOf(1, 3, 10, 13, 20, 21), sensors.types) + } +}