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.
This commit is contained in:
Seb3thehacker committed 2026-10-02 15:51:15 -07:00
1 parent 96d16d048c
commit 024850bf48
5 files changed
+59 -11

No files matched your search

@@ -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
@@ -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()
}
@@ -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"
@@ -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
)
@@ -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<Int>): 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)
}
}