Diagnostics tools for Tesla Model 3/Y ECUs over CAN
  • Python 91.7%
  • HTML 7.7%
  • JavaScript 0.5%
  • Shell 0.1%
Find a file
2026-09-23 15:46:23 -05:00
.github/workflows ci: add GitHub Actions workflow to lint and test PRs 2026-05-11 19:35:02 -05:00
docs Merge private/main into feat/vapi-layout 2026-09-09 20:16:46 -04:00
flash_scripts fix(flash): route CP PLC subcomponents to their own flash regions 2026-09-06 13:12:11 -04:00
githooks feat(sim): firmware-versioned CAN sets for 2020 vs 2022 DIR (+ RE-driven fixes) 2026-08-18 22:32:24 -05:00
scenarios feat(sim): BMS pack voltage always mirrors the measured bus (DIR, else PCS) 2026-09-23 11:31:45 -05:00
scripts feat(sim): BMS pack voltage always mirrors the measured bus (DIR, else PCS) 2026-09-23 11:31:45 -05:00
tests test: dir_learn alerts/state/vBat/park and the UDS stale-reply echo check 2026-09-23 13:43:00 -05:00
tm3web_ui feat(web): show metric metadata in the ODIN log and results table 2026-09-13 14:06:00 -04:00
uds_local fix(uds): drop stale positive responses whose DID/routine echo doesn't match 2026-09-23 10:47:20 -05:00
.env.example refactor: rename can_live → tm3web; trim + refresh public docs 2026-09-09 09:18:48 -04:00
.gitattributes feat(sim): firmware-versioned CAN sets for 2020 vs 2022 DIR (+ RE-driven fixes) 2026-08-18 22:32:24 -05:00
.gitignore feat(sim): enable DIR rotor-offset/resolver learning on the sim bench 2026-09-11 14:50:43 -04:00
alert_log.py Merge private/main into feat/vapi-layout 2026-09-09 20:16:46 -04:00
bhx.py chore: apply remaining ruff auto-fixes (import ordering, unused imports) 2026-05-11 19:33:33 -05:00
can_decoder.py feat(vapi): the catalog is the database, layouts are an optional overlay 2026-08-31 23:07:48 -05:00
candata_to_dbc.py fix(config): the root that produced an artifact names it, not TM3_FW 2026-08-31 23:20:51 -05:00
clog.py Add clog.py: gateway cluster-log (CLH/CLB) parser 2026-06-02 11:51:12 -05:00
compact_to_dbc.py feat(alerts): extract Tesla's alert + CAN catalogs from the MCU .so files 2026-08-26 22:44:07 -05:00
config.py feat(odin): run the procedures that are scripts, not graphs 2026-09-01 00:57:51 -05:00
decode_bin.py chore: apply remaining ruff auto-fixes (import ordering, unused imports) 2026-05-11 19:33:33 -05:00
dfu.py feat(odin): offer the RAM apps as a three-way choice, and fix dfu's 2026-09-01 08:30:51 -05:00
dump_alerts.py feat(alerts): extract Tesla's alert + CAN catalogs from the MCU .so files 2026-08-26 22:44:07 -05:00
dump_odin.py feat(di): vehicle-bus liveness sim to clear bench DIR/PMR MIAs 2026-07-30 21:39:50 -05:00
ecu_bench.py feat(config): default every tool to the generated DBC, not compact.json 2026-08-31 15:11:44 -05:00
firmware_report.py Add Highland service-card fixtures and dual-bank gateway HEX support 2026-06-02 10:40:56 -05:00
ihex.py Add Highland service-card fixtures and dual-bank gateway HEX support 2026-06-02 10:40:56 -05:00
pyproject.toml chore: add ruff linter and fix all violations 2026-05-11 19:29:12 -05:00
README.md Merge private/main into feat/vapi-layout 2026-09-09 20:16:46 -04:00
requirements.txt build: unicorn is a runtime dependency now that vapi_emu decodes with it 2026-08-31 21:56:32 -05:00
sim.toml.example refactor(vehicle_sim): rename bench config to sim.toml 2026-08-08 14:55:54 -05:00
so_alerts.py fix(config): the root that produced an artifact names it, not TM3_FW 2026-08-31 23:20:51 -05:00
so_candata.py feat(alerts): extract Tesla's alert + CAN catalogs from the MCU .so files 2026-08-26 22:44:07 -05:00
tm3cli.py refactor: rename tm3diag.py → tm3cli.py 2026-09-09 10:26:17 -04:00
tm3uds.py feat(identity): shared 0xF180 decode + tm3uds identity lookup-key utility 2026-06-24 06:46:35 -05:00
tm3web.py feat(uds): surface TesterPresent keep-alive stats to diagnose FAIL_NO_TESTER_PRESENT 2026-09-22 21:21:47 -05:00
unsquash_firmware.py Add unsquash_firmware.py: extract firmware + nested .dirsquashed parts 2026-06-03 00:17:46 -05:00
uv.lock feat(sim): firmware-versioned CAN sets for 2020 vs 2022 DIR (+ RE-driven fixes) 2026-08-18 22:32:24 -05:00
vapi_emu.py Merge private/main into feat/vapi-layout 2026-09-09 20:16:46 -04:00
vapi_layout.py feat(vapi): read a selector assembled from two payload pieces 2026-08-31 21:58:26 -05:00
vapi_registry.py feat(sim): enable DIR rotor-offset/resolver learning on the sim bench 2026-09-11 14:50:43 -04:00

tm3diag

Tesla Model 3 diagnostics tools for CAN

Use at your own risk This is unofficial, open-source software with no affiliation to Tesla. Flashing ECU firmware carries real risk — a failed or interrupted flash can leave an ECU in an unrecoverable state, potentially disabling safety-critical vehicle systems. By using these tools you accept full responsibility for any damage to your vehicle, its components, or any third parties. The authors provide no warranty and assume no liability.

Requirements

  • Python 3.10 or later
  • A CAN interface connected to any of the Tesla ECUs — either a real USB adapter (e.g. PEAK, Kvaser, CANable) or a virtual interface (vcan) for offline testing
  • Linux is recommended; SocketCAN is the default interface driver

Setup

1. Clone and install dependencies

git clone https://github.com/outlandnish/tm3diag.git
cd tm3diag
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

The source line activates the virtual environment. You'll need to run it again in each new terminal session before using any of the tools:

source .venv/bin/activate

2. Configure your CAN interface

Copy the example config and open it in a text editor:

cp .env.example .env

Set TM3_VEHICLE_CHANNEL to your CAN interface name and TM3_INTERFACE to your adapter's driver. The defaults work for a standard Linux SocketCAN setup:

TM3_VEHICLE_CHANNEL=can0       # your interface name — check with: ip link show type can
TM3_INTERFACE=socketcan

Bring the interface up before running any tool (replace can0 and 500000 with your interface and bitrate):

sudo ip link set can0 type can bitrate 500000
sudo ip link set can0 up

To use a virtual interface for testing without hardware:

sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set vcan0 up
# then set TM3_VEHICLE_CHANNEL=vcan0 in .env

3. Firmware dump (optional)

Some tools (tm3cli.py, dfu.py, tm3uds.py) can decode signal names and validate routines when pointed at an extracted Tesla firmware squashfs. This is not required for the immobilizer handshake script.

If you have a firmware image, extract it with unsquash_firmware.py, then set TM3_ROOT in .env to the resulting squashfs-root directory:

TM3_ROOT=/path/to/squashfs-root

If your firmware's .compact.json and ODJ files are encrypted .bin files, you'll also need the decryption key — see .env.example for how to extract it.

The ODIN diagnostic graphs ship as a zip that nothing unpacks for you. Unzip it in place, or the ODIN panel reports no ODIN bundle:

cd "$TM3_ROOT/opt/odin" && unzip -q odin_bundle.zip     # -> opt/odin/odin_bundle/networks

4. Signal database

With TM3_ROOT set, CAN frames are decoded by running the MCU's own decoder — GUICanCracker::crackMessage out of libQtCarVAPI.so, emulated, with the signal catalog from libQtCarCANData.so naming what it stores. Nothing is modelled, so nothing can be modelled wrong, and there is no build step: point TM3_ROOT at an extraction and every tool has the full database.

default_db() resolves three sources, best first:

Source Covers
1 The firmware's own decoder (vapi_emu) the whole catalog, exactly as the car decodes it
2 A generated DBC (candata_to_dbc.py) the whole catalog, from bit layouts recovered out of that same decoder
3 Model3_ETH.compact.json only the subset Tesla ships to the diagnostic tool, and it shrinks every release

Set TM3_VAPI=0 to force the layout path — the A/B for a suspected layout bug.

Decoding needs no DBC. Encoding does: crackMessage only runs one way, so vehicle_sim.py, ecu_bench.py and the frame builders need bit layouts. Build one once per firmware revision:

python candata_to_dbc.py dbc        # writes Model3_ETH.<rev>.dbc, ~1-2 min

The revision is taken from the TM3_ROOT directory name — a trailing .ice, .extracted or .ice.extracted is stripped — and that same name is how config finds the DBC again, so the two cannot drift. Without a DBC, encoding falls back to whatever layouts compact.json carries.

To check the recovered layouts against the firmware itself:

python vapi_emu.py parity           # same / different / only-emu / only-dbc

Tools

Tool Description
tm3cli.py Interactive diagnostic terminal — read DIDs, run routines, trigger firmware updates
tm3uds.py General-purpose UDS CLI for reading/writing DIDs, routines, and session management
dfu.py Firmware flash CLI — identity discovery, file selection, and ECU-specific flash sequence
scripts/di/di.py Drive Inverter bench emulator — gear/system control + immobilizer responder
scripts/di/immobilizer_handshake.py Pair a KEY/SALT with the Drive Inverter and run the runtime 0x276/0x3D9 responder
scripts/pcs/pcs.py PCS bench emulator — operating modes, precharge, DC-DC and charge control
bhx.py BHX firmware image parser and builder
ihex.py Intel HEX / .hgz parser — decode dual-bank gateway images to canonical Intel HEX
clog.py Gateway cluster-log parser — decode CL/DATA/*.CLH+*.CLB signal logs
compact_to_dbc.py Convert Model3_ETH.compact.json to DBC
dump_odin.py Extract + decompile the odin PyInstaller binary from a firmware squashfs
dump_alerts.py Dump the per-bus alert catalogue a firmware revision exposes (recipe supplied privately)
unsquash_firmware.py Unsquash a firmware image and expand its nested .dirsquashed parts
tm3web.py Local web console — live CAN signals, alerts, DB explorer, raw frames, ODIN/DID

Reference

Tests

source .venv/bin/activate
pytest tests/ -v