In its Simple view the cluster draws the turn card itself, from fields an app pushes over the vendor cluster service (paper #7), with no Android surface anywhere in the picture (paper #8). Finding which endpoint takes those fields cost a working implementation, and the wrong answer is the useful half of the story.
Question#
Which endpoint actually feeds the turn card the Sealion 7’s cluster draws for itself?
Environment#
Decompiled read-only with jadx 1.5.5 from ClusterCommService.apk, ts-framework.jar, ts-androidauto-adapter.jar and the unit’s CarPlay and Android Auto applications. Device work was over adb: settings get global, dumpsys window windows, and the app’s own on-device ring log. That log matters — this unit’s logcat keeps warning and above and drops Log.d, so anything an app logs at debug level is invisible from the host. Two parked runs (27 and 29 August) and one drive (29 August).
Observation#
The first implementation drove the gauge with the two DataTypes that look like they were built for it: DataType 15 (NaviSource, NaviTbtSwitch) to turn the cluster navi feed on, then DataType 16, the navi-info command whose keys read like a turn card — NaviIconTbt, NaviNextRoadDistance, NaviDistanceToDest, NaviToDestTime, NaviNextRoadName.
Nothing ever rendered. Not a wrong icon, not a stale card: nothing.
The commands were accepted, so the next question was who else sends them. Grepping the whole export firmware — ClusterCommService.apk, ts-framework.jar, ts-androidauto-adapter.jar, the CarPlay application and the Android Auto application — finds no sender of DataType 15 or 16 at all. They exist, they are plumbed, and on this unit nobody uses them.
What the projection apps here actually do is something else: they push an already-formatted turn card through sendTbtSimpleNaviInfo, which on the wire is DataType 37, msgType 3, subtype 20.
Evidence#
The wire form comes from an odd place, and that is worth following. On domestic units the transport is BydAutoNaviDevice.sendSimpleNaviInfo(...) into android.hardware.bydauto.instrument.BYDAutoInstrumentDevice.sendTbtSimpleNaviInfo(...), fed from the OneCore engine’s NotifyGuidanceInfoUpdate and gated on the app already holding map focus:
sendTbtSimpleNaviInfo(mapType, nextTbtIcon, nextRoadName,
nextRoadDistData, nextRoadDistUnit, nextNextTbtIcon,
nextNextDistData, nextNextDistUnit,
distToDestDisData, distToDestDisUnit, distToDestTime,
toDestDays, toDestTime, multipleText, multipleText1, multipleText2)On the export unit the android.hardware.bydauto.* classes are not on the boot classpath. But the firmware ships its own reimplementation of that API on top of the ts cluster service — android.hardware.bydauto.instrument.tsimpl.InstrumentMediaManagerImpl, inside ts-androidauto-adapter.jar, which every projection app here links against. Decompiling it gives the exact bundle.
sendCommand(16, { // ClusterCommManager command bit 16
DataType : 37, msgType : 3, subtype : 20, mapType : 2,
nextTbtIcon : int, // BYD maneuver id (Utils.BydManeuver)
nextRoadName : String,
nextRoadDistData : String, // "300" — display value
nextRoadDistUnit : String, // "m" | "km"
nextNextTbtIcon : int, // 0 / nulls when not modelled
nextNextDistData : String, nextNextDistAndUnit : String,
distToDestDisData : String, distToDestDisUnit : String,
distToDestTime : String, // "1H20Min" | "20Min" | "1H"
arriveToDestTimeDay: String, // "+1D" past midnight, else null
arriveToDestTime : String, // "14:35"
multipleText, multipleText1, multipleText2 : String
})The parameter names of the domestic signature and the bundle keys on the wire are not identical: toDestDays / toDestTime become arriveToDestTimeDay / arriveToDestTime, and nextNextDistUnit becomes nextNextDistAndUnit. Since DataType 37 is forwarded to QNX without repackaging, the bundle keys are what the cluster reads; the signature is only a guide to their order and meaning.
Field names are half the contract. The other half is the string formats, taken from the vendor’s own formatters in NavigationProxy:
| Field | Form | Vendor formatter |
|---|---|---|
nextRoadDistData + nextRoadDistUnit |
display value plus a unit word, m or km |
the vendor’s data/unit pair |
distToDestDisData + distToDestDisUnit |
display value plus m or km |
the vendor’s data/unit pair |
distToDestTime |
%dH%dMin / %dMin / %dH |
calculateArrivalTime |
arriveToDestTime |
H:MM, hour unpadded, minute zero-padded |
calculateToDestTime |
arriveToDestTimeDay |
+nD, else null |
convertSecondToDays |
nextTbtIcon is an id from com.ts.carplay.app.service.proxy.navigation.Utils.BydManeuver, cross-checked against BYDAutoInstrumentDevice.TBT_ICON001_TURN_LEFT:
1 TURN_LEFT
2 TURN_RIGHT
3 LEFT_FRONT 4 RIGHT_FRONT
5 LEFT_REAR 6 RIGHT_REAR
7 LEFT_TURNAROUND
8 STRAIGHT_AHEAD
12 DESTINATION
16..23, 44..47, 59..64 ROUNDABOUT_EXIT_1..18The roundabout ids are a trap: they are named as ordinals and they are not ordinals. That is paper #10, not this one.
The device runs#
Two independent reasons made those sends invisible, and either one alone will waste an afternoon. The gauge is composited by QNX, so screencap -d 1 captures only the Android layer — empty by design in Simple — and can never show the card. And the client was logging its send results at debug level, which this unit’s logcat drops.
Interpretation#
Reachable is not the same as used. DataType 15 and 16 are implemented, documented by their own key names, and dead on this unit. The negative grep across five binaries is what turned a plausible endpoint into a disproved one, and it is the step the first pass skipped.
Because SomeIpClusterAdapter forwards DataType 37 to QNX untouched, there is no translation layer to be forgiving. The bundle that leaves the app is the bundle the cluster parses, so the key names, the types and the string formats are all contract, equally. Sending 1h20m where the vendor sends 1H20Min, or an integer where it sends a string, is not a near miss.
The failure mode deserves naming too: on this path a wrong endpoint and a wrong payload look identical from the host. Nothing errors, nothing logs, and the one surface that would show the difference is not an Android surface. The 29 August fixes were found by reading what the app itself recorded, not by watching the car.
What this does not establish: that DataType 15 and 16 do nothing anywhere. They were sent on this unit and produced no rendering, and nothing in this firmware sends them — but a domestic unit, or another export build, was not tested.
Result#
Confirmed: the native turn card on this unit is fed by DataType 37 on msgType 3, subtype 20, and by nothing else. DataType 15 and 16 have no sender in the export firmware and produced no rendering when we sent them.
Three things follow for anyone writing to this gauge:
- Send display strings, in the vendor’s formats, under the bundle keys above. The gauge does no formatting for you.
- Open the session first. The vendor’s own CarPlay proxy will not even send a presentation state from an app that does not own map focus —
NavigationProxy.sendNaviPresentationState()returns early, “no focus, fail” — so the order is DataType 33, then 35, then 30 mode 2, then 31 state 7, and only then the frames. - Do not index the roundabout ids by exit number, however they are named (paper #10).
Limitations#
One car, one market, one head-unit build, and one app doing the sending. Nothing here was tested on a domestic unit; the OneCore side of the story — NotifyGuidanceInfoUpdate feeding sendSimpleNaviInfo — is read from the decompile of the export firmware’s own reimplementation and from the domestic API’s shape, not observed on a domestic car.
Only part of the contract has been exercised. nextNextTbtIcon, nextNextDistData, nextNextDistAndUnit, multipleText, multipleText1 and multipleText2 went out as 0 and null on every frame — we have never sent a populated second maneuver or a free-text line, and we do not know how the gauge draws them. We have no record of arriveToDestTimeDay carrying a non-null value on a drive.
The rendering evidence is a single drive by one observer. We have confirmed that the card appears and shows instructions; we have not photographed it, measured its refresh behaviour on a moving car, or checked what it does when frames stop.
The ~3.2 s frame spacing is the app’s own keep-alive on a static card, not a limit the protocol imposes. We have not established a maximum rate, a minimum rate, or what the cluster does with a frame it cannot parse — result=true is the binder call returning, not an acknowledgement from QNX.
The ADAS display fault seen once after a mode change was not reproduced and not explained. It is recorded because it happened, not because we know it will happen again.
Reproduction#
The decompile half needs no car beyond pulling the binaries once. Decompile ClusterCommService.apk, ts-framework.jar, ts-androidauto-adapter.jar and the two projection applications with jadx 1.5.5, then ask the two questions in order:
grep -rn 'sendTbtSimpleNaviInfo' <jadx-output>
grep -rn 'DataType' <jadx-output> | grep -E '\b1[56]\b'The first lands in InstrumentMediaManagerImpl and gives the bundle. The second should return the legacy SomeIpClusterAdapter / SomeIpPackageUtils handling and no sender — read the hits rather than counting them, because the finding here is an absence.
On the car, with adb reachable (our guide covers getting there), this is the state to watch while an app drives the gauge:
adb shell settings get global moet_extension_navi_display_mode
adb shell settings get global moet_extension_navi_focus_map
adb shell dumpsys window windowsSimple is display mode 2 with focus 2 and no Presentation of yours on display 1. Do not expect screencap -d 1 to show the card — in Simple the Android layer is empty by design, and the card is composited by QNX. Confirming it needs eyes on the instrument panel.
Log your own send results at warning level or above, or you will not see them: this unit’s logcat drops debug. A result=false means the manager is not bound; a missing line means the feed is not running; a line with result=true and no card on the cluster means the payload, not the endpoint.