Skip to content

Commit 7be953b

Browse files
malysclaude
andcommitted
feat(diagnostics): show the navigation transaction census
A vehicle bundle reports firmware 1300R67; the transaction map behind the probe was read from R69. Two revisions of the same generation are not the same binary, and an inserted interface method would shift every later code — producing wrong distances rather than no distances. The probe now prints every transaction code the adapter sent, marking the ones this build does not decode, and its report says plainly that the census must be read before any number above it is trusted. A capture that shows traffic on undecoded codes, or silence on the decoded ones while guidance is running, has caught the shift. Adds the status the probe previously could not express: armed, receiving traffic, decoding none of it. Bumps EVHardware to e8ebc8a. Refs: CP-040 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 9599fe8 commit 7be953b

5 files changed

Lines changed: 47 additions & 2 deletions

File tree

‎analysis/CP-040_route_source.md‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,35 @@ listener on its own, so this displaces neither the navigation app nor the cluste
7777
- **Behaviour with no guidance running.** If the navigation app publishes nothing when idle,
7878
the feature only exists for drivers who navigate with the OEM app. This is the single most
7979
decision-relevant unknown and the probe is built to record it.
80+
- **Whether R67 numbers the interface as R69 does.** See the section below; the probe is
81+
built to answer it in the same capture.
8082
- **Whether a third-party navigation app feeds the same path.** TomTom broadcasts
8183
(`com.tomtom.navigation.GUIDE_INFO`) reach `MapService`, so at least one non-SAIC app does.
8284

8385
None of these can be settled off-vehicle, which is why CP-040 carries `on_vehicle_required`.
8486

87+
## Firmware revision risk, and how the probe catches it
88+
89+
A diagnostic bundle captured on the vehicle reports `detected_firmware=SWI68-29958-1300R67`.
90+
The transaction map above was read from an R69 build of the same generation — two revisions
91+
apart, not a different family, but not the same binary either.
92+
93+
This matters more than a missing signal would. If SAIC inserted a method into
94+
`IGeneralNotificationListener` between R67 and R69, every code after the insertion point
95+
shifts by one. A shifted map does not throw and does not read as unavailable: it decodes
96+
whatever callback now occupies code 13 and reports it as a remaining distance. Silent wrong
97+
numbers are the one outcome this project's provenance rules exist to prevent.
98+
99+
`TransactionCensus` therefore counts every code the adapter sends, decoded or not, and the
100+
probe prints it. Two patterns condemn the map:
101+
102+
- traffic on codes the build does not decode, marked `?` in the trace header; or
103+
- silence on the decoded codes while guidance is visibly running on the head unit.
104+
105+
Either one means this firmware numbers the interface differently, and no reading from the
106+
capture may be used until the map is re-derived from an R67 build. Confirming the map is
107+
therefore part of the vehicle run, not an assumption it rests on.
108+
85109
## Scored comparison
86110

87111
Scores are against CP-040's four criteria. "Boundary" means the read-only, offline,

‎app/src/unstable/java/com/evsuite/chargepilot/NavGuidanceProbeActivity.kt‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.os.SystemClock
99
import androidx.appcompat.app.AppCompatActivity
1010
import com.evsuite.chargepilot.databinding.ActivityNavGuidanceProbeBinding
1111
import com.evsuite.hardware.saic.NavGuidance
12+
import com.evsuite.hardware.saic.NavGuidanceReducer
1213
import com.evsuite.hardware.saic.SaicNavGuidance
1314
import com.evsuite.hardware.telemetry.EnergyTelemetryReader
1415
import com.google.android.material.snackbar.Snackbar
@@ -146,14 +147,30 @@ class NavGuidanceProbeActivity : AppCompatActivity() {
146147
append("- adapter bound: ${SaicNavGuidance.isAvailable}\n")
147148
append("- listener registered: ${SaicNavGuidance.isListening}\n")
148149
append("- callbacks seen: ${SaicNavGuidance.latest().events}\n")
150+
append("- transaction census: ${census()}\n")
151+
append("- codes past the census ceiling: ${SaicNavGuidance.censusBeyondCeiling()}\n")
149152
append("- trace lines: ${trace.size}${if (trace.size >= TRACE_LINES) " (oldest dropped)" else ""}\n\n")
150153
append("`dist` and `turn` are raw callback values. Their unit is unproven — state it\n")
151154
append("by comparing against a distance known independently before using any of this.\n\n")
155+
append("The transaction map was read from an R69 build. Check the census before\n")
156+
append("trusting any number above: traffic on codes marked `?`, or silence on the\n")
157+
append("decoded ones while guidance is clearly running, means this firmware numbers\n")
158+
append("the interface differently and every reading here is off by the same shift.\n\n")
152159
append("```\n")
153160
trace.forEach { append(it).append('\n') }
154161
append("```\n")
155162
}
156163

164+
/** Decoded codes are named; anything else is flagged, because that is the shift signal. */
165+
private fun census(): String {
166+
val seen = SaicNavGuidance.census()
167+
if (seen.isEmpty()) return "none"
168+
return seen.entries.sortedBy { it.key }.joinToString(" ") { (code, count) ->
169+
val mark = if (code in NavGuidanceReducer.KNOWN_TRANSACTIONS) "" else "?"
170+
"$code$mark=$count"
171+
}
172+
}
173+
157174
private fun render() {
158175
val guidance = SaicNavGuidance.latest()
159176
val listening = SaicNavGuidance.isListening
@@ -163,7 +180,9 @@ class NavGuidanceProbeActivity : AppCompatActivity() {
163180
binding.probeStatus.text = when {
164181
!SaicNavGuidance.isAvailable -> getString(R.string.nav_probe_unavailable)
165182
!listening -> getString(R.string.nav_probe_idle)
166-
guidance.events == 0 -> getString(R.string.nav_probe_silent)
183+
guidance.events == 0 && SaicNavGuidance.census().isEmpty() ->
184+
getString(R.string.nav_probe_silent)
185+
guidance.events == 0 -> getString(R.string.nav_probe_undecoded)
167186
else -> getString(R.string.nav_probe_receiving, guidance.events)
168187
}
169188
binding.probeHint.setText(

‎app/src/unstable/res/values-fr/strings.xml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@
3030
<string name="nav_probe_refused">Mettez-vous à l’arrêt et attendez une vitesse lisible avant d’armer l’écoute.</string>
3131
<string name="nav_probe_copied">Trace copiée dans le presse-papiers</string>
3232
<string name="nav_probe_open">Sonde de guidage</string>
33+
<string name="nav_probe_undecoded">Armée · trafic vu, rien de décodé</string>
3334
</resources>

‎app/src/unstable/res/values/strings.xml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@
3030
<string name="nav_probe_refused">Park and wait for a readable speed before arming the listener.</string>
3131
<string name="nav_probe_copied">Trace copied to the clipboard</string>
3232
<string name="nav_probe_open">Guidance probe</string>
33+
<string name="nav_probe_undecoded">Armed · traffic seen, none decoded</string>
3334
</resources>

0 commit comments

Comments
 (0)