Files
samplez/tunie/STATUS.md
uhryniuk 4aa5da53d2 Commit tune maps and research so tunes are reachable from the phone
Removes the *.hex/maps_cache gitignore rule (explicit user call, reversing
the earlier no-redistribution stance) so the official TuneECU catalogue
maps, derived SAI/O2-delete composites, and the checksum/composition
tooling are actually available to pull up on a phone browser when using
the real TuneECU app. Also folds in tonight's KWP2000 fixes (TesterPresent
keep-alive, connect-failure cleanup, slow-init StartCommunication fix) and
the accumulated research docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FP2GaxS9HkUdL5sLBnjKje
2026-08-27 01:34:05 -05:00

273 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Triumph Bonneville tuning project — status & context
**Bike:** 2010 Triumph Bonneville T100, 865cc air-cooled parallel twin, Keihin
ECU (Renesas SH7054), **mechanical/analog odometer**.
**Goal:** an open-source Python toolchain to read and (eventually) tune the ECU,
replacing the closed TuneECU app. Ultimate hardware path: SAI removal, O2 delete,
airbox removal, full exhaust — each needs a matching recalibration.
**Last updated:** 2026-08-10. Not getting to actual tuning this week — this doc
is the cold-start reference to pick it back up.
---
## TL;DR — where we are
- **Phase 1 (read-only comms) is built, not yet run against the bike.** Nothing
has touched the ECU. No hardware connected yet.
- **Next real step:** wire the cable to the Triumph connector correctly, then run
`tunie info` to read the ECU's identity, current map ID, and fault codes.
- **Blocker:** the wired cable is a *VAG* KKL — right cable type, wrong plug for
this bike. Needs an adapter/re-pin to the Triumph diagnostic connector. This is
the one genuine (electrical) risk and must be confirmed against a wiring
diagram before plugging in.
- **Big win:** the ECU's security-access seed/key algorithm was fully recovered
from the TuneECU APK — it's **AES-128** with three embedded keys. Documented,
reference-implemented, and verified. This is write-path work, quarantined
outside the read-only tool.
---
## Directory map
```
/Users/dylan/dojo/tuner/
tunie/ the read-only Python tool (installed, working)
src/tunie/ package source
tests/ protocol verification (passes)
maps.json 1811 extracted TuneECU maps
README.md tool-level docs
research/ WRITE-PATH reverse engineering (kept OUT of the tool)
keihin_seedkey.py AES-128 seed/key reference impl (self-testing)
FINDINGS.md seed/key writeup
work/ decompiled TuneECU
jadx_out/ Java decompile (read-only, best for reading logic)
apktool_out/ smali decompile (rebuildable)
TuneECU.apk original
TuneECU-logging.apk a clean rebuild (no real instrumentation yet)
samplez/ pristine original TuneECU.apk (git repo)
STATUS.md this file
RESEARCH.md the original (LLM-written, partly wrong) research brief
```
---
## What has been built
### `tunie` — read-only KWP2000 diagnostic tool
Installed and working. Commands:
```sh
tunie ports # list serial devices
tunie info --port /dev/cu.usbserial-XXXX # interrogate ECU (KKL cable)
tunie info --port /dev/cu.OBDII --adapter elm327
tunie dtc --port ... # fault codes only
tunie maps Bonneville --ecu 0 --odometer mechanical
tunie extract-maps <arrays.xml> # rebuild maps.json
tunie -v info --port ... # verbose: log every frame
```
Supports both a wired K-Line KKL cable (`--adapter kline`) and a Bluetooth
ELM327 (`--adapter elm327`) behind one interface.
**Read-only by construction.** `src/tunie/safety.py::assert_read_only()` runs on
every outbound request inside `Transport.request()`, before any byte reaches the
serial port. It allows only an allowlist of query services and refuses all
write/flash services (`0x27` SecurityAccess, `0x31` erase, `0x34`/`0x36` write,
`0x35` upload, `0x37`, `0x14`, `0x11`, `0x2E`) and all programming diagnostic
sessions. A bug cannot brick the ECU because the write path is not implemented.
Module layout:
```
safety.py read-only allowlist, enforced before transmit
kwp2000.py ISO 14230-3 framing, checksums, negative-response decoding
triumph.py Triumph constants recovered from the APK
identify.py the read-only interrogation sweep
maps.py TuneECU map catalogue extraction + search
cli.py command-line entry point
transport/
base.py Transport ABC; routes every request through safety
kline.py FTDI/KKL cable, raw serial, break-condition fast init
elm327.py ELM327 adapter, using TuneECU's own AT init sequence
```
`tests/verify_protocol.py` passes: it asserts our frames are byte-identical to
TuneECU's and that every write service / programming session is blocked.
---
## What was learned (all recovered from the APK, not guessed)
### Protocol facts (source: `com/tuneecu/m.java` + `MainActivity.java`)
| Parameter | Value | Notes |
|---|---|---|
| ECU address | `0xD5` | from `Ld()` framing + `ATSH81D5F5` |
| Tester address (K-Line) | `0xF5` | **not** 0xF1 — that's another brand |
| Format byte | `0x80 \| length` | long form (0x80 + length byte) when >120 B |
| Checksum | additive sum mod 256 | `Ub()` in m.java |
| Init | fast (`ATTP5`) or 5-baud (`ATTP4`+`ATIIAD5`) | address 0xD5 |
| ECU type code | `0`=Keihin, `1`=Sagem, `P`=Bosch | `ecu` string-array |
Reference frames (verified in tests):
```
81 d5 f5 81 cc StartCommunication
82 d5 f5 1a 80 e6 ReadEcuIdentification 0x80
84 d5 f5 18 00 ff 00 65 ReadDtcByStatus
```
> RESEARCH.md guessed target `0x10` / source `0xF1` and a 16-pin OBD-II port.
> Both wrong for this bike.
### Map database
Extracted TuneECU's full catalogue: **1811 entries** in `tunie/maps.json`.
TuneECU's own database distinguishes **"Mechanical odometer"** from **"LCD
odometer"** Bonnevilles — they are not interchangeable, and this is the ECU
generation split. For a 2010 mechanical-odo 865:
- **Stock baselines (`:0:` Keihin, mechanical odo):**
- `20187` production silencers, `20188` aftermarket silencers
- `20191`/`20192` same, up to VIN 739050, E25 fuel
- **Exhaust maps:** `20262`–`20265`, `20313`–`20316` (Arrow 2-in-1 / 2-in-2)
> RESEARCH.md recommended `20498` as an "OEM Arrow, safe rich baseline." It is
> actually a **Thruxton, LCD-odometer** map — wrong model AND wrong ECU
> generation. Do not use it.
### Security Access seed/key — RECOVERED, it's AES-128
Source: `com/tuneecu/m.java` method `Vb()`. Full writeup in
`research/FINDINGS.md`; working reference in `research/keihin_seedkey.py`
(`python3 research/keihin_seedkey.py` → all self-tests pass).
- The algorithm is **standard AES-128** (verified: all 256 T-table entries match
AES Te0; reference passes the FIPS-197 known-answer vector). It is **not** the
Honda XOR/bit-shift scheme RESEARCH.md predicted.
- `Vb()` AES-encrypts a 128-bit seed block with one of **three** embedded keys
(`iArr4`, m.java:5351), selected by `MainActivity.h7 ∈ {0,1,2}`, and returns
the first ciphertext word as the 4-byte key (`Gd()` sends `27 02 <key>`).
- The three keys (little-endian):
```
h7=0: ef704ca051b800cc9287df6a3511a978
h7=1: d4b15ff4c92ab7f098316e7a5b11ac39
h7=2: dc9fdba46f2fad18a4b8e1123c7183c2
```
- **Still open (framing, not crypto):** which h7 applies to the mechanical-odo
865, and the exact seed-block padding (`Ie[1..3]` from the response + fixed
`0x03`/`0x01`; `Ie[0]` set on an earlier path). **One captured (seed, key) pair
resolves both.**
### The logging APK
`work/TuneECU-logging.apk` is a clean apktool rebuild of the original (~17 KB
delta = re-signing/recompression). No custom instrumentation; all `Log` calls are
TuneECU's own, gated behind its debug boolean; no `debuggable` flag. It does not
currently capture seed/key at runtime — but it's the right vehicle for the
validation step (see next steps).
---
## The cable (VAG KKL) — what to do
A "VAG KKL" 409.1 cable is the **correct cable class** (K-Line). Confirmed
in hand and working: `/dev/cu.usbserial-A50285BI`, genuine FTDI FT232R, no
driver needed.
**Step 1 — identify the chip.** `tunie ports`:
- `/dev/cu.usbserial-XXXX` → FTDI, ideal, no driver. **(this is what we have)**
- `/dev/cu.wchusbserialXXXX` → CH340; works but install the CH340 macOS driver.
- nothing → driver missing.
**Step 2 — the connector, revised (Aug 2026).** Originally assumed this bike
uses a fully proprietary, non-OBD-II connector requiring a hand-built
adapter. **Real-world evidence from actual TuneECU users on this exact ECU
family says otherwise** — multiple independent accounts (triumphrat.net,
"TuneECU For Dummies" thread) describe a standard **16-pin OBD-II-style
plug** that a genuine-FTDI cable connects to directly, no re-pinning:
> "The KL-1 interface has a 16 pin plug (OBD2 style) that will plug
> directly into Triumph models." — "The cable connects to the dedicated
> 16-pin diagnostic plug of the ECM." — a real install log on a **2010
> Speed Triple 1050 (same-era Keihin ECU family)**: *"Remove your seat and
> the ECU cable connector is just sitting in front of the fusebox (behind
> the tank). You don't need any tools."*
**New, previously-undocumented, important step from the same source:**
**pull the headlight AND taillight fuses before connecting** — both to
protect the battery ("Keihin ECUs don't like a voltage drop") and as a
built-in interlock (the bike won't start with the headlight fuse pulled).
Fuse numbers are bike-specific (the Speed Triple account used #8; check
this bike's own fusebox cover, which has the numbering printed on it) —
don't assume the same number without checking.
**Still not 100% settled** — this is strong, multi-source evidence, not a
verified fact for this specific 2010 T100 mechanical-odo bike (the primary
sources are Speed Triple owners, a related but different model in the same
ECU family). **Before plugging in: visually confirm the connector really is
16-pin OBD-II-shaped** at the location described (front of fusebox, behind
tank) — if it matches, plug in directly; if it looks different, the
re-pinning caution below still applies.
> **Confirm the connector shape and pull the relevant fuses before plugging
> in.** If it turns out not to be OBD-II shaped after all, do not force a
> connection — that's when the re-pinning risk below is real. Wrong pins
> damage the transceiver.
---
## Next steps (in order)
1. **Identify the cable chip** — `tunie ports`, install CH340 driver if needed.
2. **Confirm the Triumph diagnostic connector pinout** — service manual / wiring
diagram. Build the adapter (K-Line, switched +12V, ground).
3. **First scan (safe):** ignition on / engine off, `tunie info --port …`.
Yields ECU ID, currently-flashed map ID, DTCs. If fast init times out, try
`--init slow`. This resolves which stock map is actually on the bike.
4. *(Later, Phase 2)* ROM dump for a recovery image — requires deliberately
enabling `0x35` upload; blocked in the read-only tool by design.
5. *(Later, Phase 3)* Checksum patching — not yet investigated; needed before any
modified map can boot.
6. *(Later, Phase 4)* Write path — validate the AES seed/key against a captured
pair first; ideally against a spare ECU, not the bike's only one.
### Optional now: capture a real seed/key pair
Write a minimal smali patch to `work/apktool_out` that logs every KWP frame
(patch the BT send/receive in `d.smali`) to logcat, rebuild, run one real TuneECU
Security Access against the bike, and capture the `27 01` seed + `27 02` key.
Then check `compute_key(seed, h7)` reproduces it — validates the whole AES port
and pins down h7 + padding.
---
## Risk register
| Area | Status |
|---|---|
| Software bricking | **Eliminated** — write services structurally unreachable in `tunie`. |
| Electrical / wiring | **LIVE** — VAG cable ≠ Triumph connector; confirm pinout before plugging in. |
| Seed/key correctness | Algorithm recovered + AES-verified; which-key + padding need one captured pair. Phase 4 only. |
| Firmware checksum | Not yet investigated. Needed before flashing a modified map. Phase 3. |
| Single ECU, no spare | Read-only-first mandatory; validate any write path against TuneECU before trusting ours. |
---
## Tuning theory (from RESEARCH.md §9 — this part is sound)
- **SAI removal:** flip the software SAI flag or the ECU throws a DTC; SAI air
also corrupts AFR readings on a dyno, so disable it before any fuel tuning.
- **O2 delete / open loop:** removing narrowband O2 + disabling closed-loop lets
you command a richer light-load AFR (~13.5–13.8:1 vs 14.7) — cools the
air-cooled top end and fixes the snatchy off-idle response.
- **Airbox removal & full exhaust:** both raise cylinder fill (VE / scavenging);
fuel tables must be enriched or it runs lean under load. Community airbox maps
derived from `20188` provide pre-calculated enrichment.
XDF definition files (to edit the .bin in TunerPro) are sold by OldSkullTuning
and Tuniverse (~€70) for the SH7054 — a later purchase, only once we're editing.
```