# ECUmulator.py

> Local TCP note: For loopback adapter connections in this project, use `127.0.0.1` instead of `localhost`. `localhost` can resolve to IPv6 (`::1`) while current services listen on IPv4 only.

Python-based ECU emulation + adapter tooling.

This repository hosts the **Python** tooling formerly living in the Swift ECUmulator repo:

- **pymulator** — ECU emulator and CAN bus bridge
- **ELMpythonator** — ELM327/STN emulator
- **hsfzclient** — BMW HSFZ REPL client
- **doipclient** — DoIP (ISO 13400) REPL client
- **epy** — wrapper script to run server/client tools

> This repo is Python‑only. Swift sources live elsewhere.

## Installation

Homebrew (macOS, recommended for end users):

```sh
brew tap mickeyl/formulae
brew install ecumulator-py
```

Note: the Homebrew formula pulls source from a private GitLab repo over SSH.
You need access to that repo and a configured SSH key (or a tokenized HTTPS URL).

pipx (isolated CLI install, recommended for developers):

```sh
pipx install git+ssh://git@gitlab.com/a11086/ecumulator.py.git
```

pip (venv or local checkout):

```sh
python3.12 -m pip install -r requirements.txt
```

Or install as a package (local checkout):

```sh
python3.12 -m pip install .
```

Install directly from GitLab (private repo):

```sh
pip install git+ssh://git@gitlab.com/a11086/ecumulator.py.git
# or with token-based HTTPS:
pip install git+https://<TOKEN>@gitlab.com/a11086/ecumulator.py.git
```

Which installer should I use?
- Homebrew: most predictable for macOS end users and supportable fleet installs.
- pipx: clean CLI install for devs without touching system Python.
- pip/venv: best for local development or editable installs.

Optional dependencies are auto‑checked:
- `python-can` (required for `--attach`)
- `gs_usb` backend for `python-can` when using `gs_usb` (installed automatically on macOS via env marker; manual fallback: `pip install gs_usb`)
- `lz4` (required for `--ecuconnect`)

Non‑optional dependency:
- `zeroconf` (required for ECUconnect logger discovery; now a hard dependency for `--logger`)

## Tests

Recommended (PEP 668 friendly) workflow:

```sh
python3.14 -m venv .venv
.venv/bin/python -m pip install -U pip
.venv/bin/python -m pip install -r requirements-dev.txt
.venv/bin/python -m pip install -e .[dev]
.venv/bin/python -m pytest
```

## Quickstart

Run with a spec:
```sh
epy server ecu:./path/to/spec.json
```

Interactive REPL:
```sh
epy server -i ecu:./path/to/spec.json
```

Attach to real CAN:
```sh
epy server -a can0@500000 ecu:./path/to/spec.json
```

Backend selection for `--attach`:
- Linux defaults to `socketcan`.
- macOS defaults to `gs_usb`.
- Override explicitly with `socketcan:<channel>@<bitrate>`, `gs_usb:<index>@<bitrate>`, or `toucan:<index>@<bitrate>`.
- TouCAN on macOS supports classic CAN bitrates: `10000`, `20000`, `50000`, `100000`, `125000`, `250000`, `500000`, `800000`, `1000000`.

macOS `gs_usb` compatibility note:
- ECUmulator applies a runtime shim for Python `gs_usb` startup to avoid macOS permission issues seen with reset/detach in some setups.
- Disable this shim only for troubleshooting with `PYMULATOR_GS_USB_MACOS_SHIM=0`.

python-can-candle status note (February 2026):
- We tested switching macOS attach defaults to `candle`, but `pip install python-can-candle` failed due to a transitive `candle-api` build issue.
- Tracking issues: https://github.com/BIRLab/python-can-candle/issues/6 and https://github.com/BIRLab/candle_api/issues/2

## UI (ECUmulator Studio)

The desktop UI lives in `ui/` (Tauri + Vite). To run the dev build:

```sh
cd ui
npm install
npm run tauri dev
```

### Building for Linux

Standard build (requires internet on first launch for Python runtime setup):

```sh
make linux
```

Offline .deb (bundles Python runtime + all wheels — no internet needed on target):

```sh
make deb
```

Install:
```sh
sudo apt install ./dist/ecumulator-studio_*.deb
```

Uninstall:
```sh
sudo apt remove ecumulator-studio
```

UI source structure (for contributors):

- `ui/src/App.tsx` — root orchestration/composition
- `ui/src/app/defaults.ts` — default state/constants
- `ui/src/app/types.ts` — App-local types
- `ui/src/app/helpers.ts` — pure helpers and formatting logic
- `ui/src/app/serviceCatalog.tsx` — service/settings catalogs
- `ui/src/components/app/` — App-scoped components (modals, badges, quick-help window)
- `docs/ui-semantic-color-palette.md` — semantic UI color tokens/classes and usage rules

When adding features, prefer extracting focused modules/components rather than expanding `App.tsx`.

### Global Actions (Firewall ECUs)

In **ECU Detail**, the **Global Actions** firewall controls are only shown when the ECU role resolves to `FIREWALL`.

- Matching is spelling-tolerant and case-insensitive.
- Examples that match: `FIREWALL`, `firewall`, `Fire-Wall`, `fire wall`, `fire_wall`.
- These controls are shown for UDS/KWP ECUs and map to ECU `startupActions` (`lockAllOtherECUs` / `unlockAllOtherECUs`).

## Flow Control Testing (ISO-TP)

Use these CLI options to inject realistic flow-control stalls and overruns for tester validation.

Occasional short stall (10% of FCs, 2 WAIT frames, 25ms spacing):
```sh
epy server ecu:./path/to/spec.json --isotp-fc-wait-prob 0.1 --isotp-fc-wait-count 2 --isotp-fc-wait-ms 25
```

Rare overrun (1% of FCs), deterministic:
```sh
epy server ecu:./path/to/spec.json --isotp-fc-overflow-prob 0.01 --isotp-fc-rng-seed 42
```

Forced overrun (every FC is OVERFLOW):
```sh
epy server ecu:./path/to/spec.json --isotp-fc-overflow
```

## CAN FD + ISO-TP/FD

Transport profile is controlled per vehicle via `bus.transportProfile`:

- `"isotp"` (default): classic CAN framing.
- `"isotp_fd"`: CAN FD + ISO-TP/FD framing.

When `transportProfile` is `isotp_fd`, FD behavior is enabled for:
- Internal CAN runtime transport.
- External CAN bridge paths.
- ECUconnect FD channels (`open_fd_channel`, `raw_fd`, `isotp_fd`).

Compatibility note:
- `HSFZ`, `DoIP`, and `ELM327/STN2xxx` are classic-transport services and are unavailable with `transportProfile: "isotp_fd"`.
- Studio disables those service toggles automatically under `isotp_fd`.
- Sidecar also rejects invalid combinations at apply/runtime for safety.

## Supported Commands

`epy` command list:

- `server [pymulator args...]` — ECU emulator + CAN bus bridge.
- `import [specimport args...]` — Generate vehicle/ECU JSON specs from candump, CL1000, ECUconnect, or PCAP/PCAPNG logs.
- `spec compile <vehicle-or-project-dir> [-o out.vehicle] [--overwrite]` — Build canonical standalone `.vehicle`.
- `spec explode <vehicle-or-project-dir> [-o out-dir] [--overwrite]` — Create project layout (`vehicle.vehicle` + `ecus/*.json`).
- `client --doip [doipclient args...]` — DoIP REPL client.
- `client --hsfz [hsfzclient args...]` — HSFZ REPL client.
- `elm [elmpythonator args...]` / `elmpythonator [args...]` — deprecated; use `server --elm327`.
- `update [--force]` — Check Git tags and update the installed package.
- `version` — Print current version.
- `help` / `--help` — Show usage.

Example:
```sh
epy import ./capture.log -o ./specs
```

## Sample Specs

The `specs/can/` directory includes CAN-based OBD2 fixtures for app testing:

```sh
epy server specs/can/obd2-can29-vehicle.json
```

Recommended for new vehicle specs: use the `.vehicle` extension. Legacy `.json` vehicle wrappers are still supported.

That vehicle spec loads two ECUs:
- `specs/can/obd2-can29-ecu-a.json` (powertrain)
- `specs/can/obd2-can29-ecu-b.json` (transmission/secondary)

CAN FD demo fixture (11-bit arbitration + ISO-TP/FD):

```sh
epy server specs/can/canfd-can11-vehicle.json
```

## ECUconnect TCP adapter (optional)

Expose ECUconnect/CANyonero compatible TCP server:
```sh
epy server --ecuconnect ecu:./path/to/spec.json
```

Combine with a real bus:
```sh
epy server --ecuconnect -a can0@500000 ecu:./path/to/spec.json
```

Behavior notes:
- Adapter identity (`request_info` firmware/version + hardware revision) is configurable in ECUmulator Studio Settings.
- Virtual firmware update PDUs are supported (`prepare_for_update`, `send_update_data`, `commit_update`, `reset`) with real-world timing simulation (3s prepare, 50ms write chunk, 2s commit).
- On `commit_update`, the uploaded image is scanned for a firmware version string. On `reset`, the adapter applies it and restarts.
- The learned version is persisted in Studio service settings (`ecuconnectVersionFromUpdate` + `ecuconnectVersionUpdatedAt`) and survives Studio restarts.

Protocol coverage details: `docs/ecuconnect.md`.

## ECUconnect Logger (optional)

Stream all CAN traffic in the ECUconnect Logger binary format over TCP
and advertise via Zeroconf:

```sh
epy server --logger ecu:./path/to/spec.json
```

Custom service name:
```sh
epy server --logger --logger-name "ECUmulator.py Lab" ecu:./path/to/spec.json
```

## HSFZ TCP adapter + UDP discovery (optional)

```sh
epy server --hsfz ecu:./path/to/spec.json
```

Requires classic transport profile (`bus.transportProfile: "isotp"`).

Protocol coverage details: `docs/hsfz.md`.

## DoIP (ISO 13400) TCP adapter + UDP discovery (optional)

```sh
epy server --doip ecu:./path/to/spec.json
```

Requires classic transport profile (`bus.transportProfile: "isotp"`).

Protocol coverage details: `docs/doip.md`.

## ELM327/STN2xxx TCP adapter (optional)

```sh
epy server --elm327 ecu:./path/to/spec.json
```

Requires classic transport profile (`bus.transportProfile: "isotp"`).

Interpreter details: `docs/ELM327-STN-COMMANDS.md`.

## ENET Service Broker vehicle bridge (optional)

ECUmulator can register as the **vehicle** endpoint against an ENET Service Broker and forward CAN frames over the broker TCP stream:

```sh
epy server --enet-service-broker --enet-service-broker-vin WBADE6324VBW12345
```

Optional broker URL override:

```sh
epy server --enet-service-broker --enet-service-broker-vin WBADE6324VBW12345 --enet-service-broker-url https://esb.cornucopia.dev
```

ISO-TP mode:

```sh
epy server --enet-service-broker --enet-service-broker-vin WBADE6324VBW12345 --enet-service-broker-mode isotp
```

Notes:
- VIN is required (17 characters).
- By default, broker discovery uses Zeroconf service `enet-service-broker._enetbroker._tcp` on the local network.
- `--enet-service-broker-url` is only needed to force a specific broker endpoint.
- The broker response includes a tester port (`vehicle port + 1000`).
- Transport mode can be `isotp` (default) or `raw`.
- Studio shows the currently allocated tester port under the ENET service badge.

## Clients

HSFZ REPL client:
```sh
epy client --hsfz
```

DoIP REPL client:
```sh
epy client --doip
```

Both clients accept explicit URLs:
```sh
epy client --hsfz enet://127.0.0.1:6801/en0
epy client --doip doip://127.0.0.1:13400/en0
```

## ELM327/STN2xxx interpreter (ELMpythonator)

The ELM327/STN2xxx interpreter now runs inside the TCP adapter service (not as a standalone CLI).

```sh
epy server --elm327 ecu:./path/to/spec.json
```

Optional flags:

```sh
epy server --elm327 --elm327-host 0.0.0.0 --elm327-port 35000 --elm327-name "ELM327/STN2xxx"
```

Connect with a TCP client (telnet, netcat, or your app) and issue AT/ST commands.
See `docs/ELM327-STN-COMMANDS.md` for the full command list and behavior.

## Updates

`epy` checks once a day for newer Git tags and offers to update. You can force an update check:

```sh
epy update
```

If your Python is **externally managed** (PEP 668), auto‑updates are disabled to avoid breaking system packages. Use a virtualenv or pipx, or force a system update:

```sh
epy update --force
```

If you installed via Homebrew, `epy update` is disabled and will tell you to use:

```sh
brew update
brew upgrade ecumulator-py
```

## Notes

- Use `--bus-timing-factor` to simulate CAN timing for virtual buses.
- Sample OBD2 CAN29 specs live in `specs/`. For additional scenarios, point to your local specs repo.
- Broadcast discovery is minimal and answers first response received.

## Protocol Overviews

- `docs/ecuconnect.md` — ECUconnect adapter + logger support matrix.
- `docs/hsfz.md` — HSFZ TCP/UDP support notes.
- `docs/doip.md` — DoIP UDP discovery + TCP diagnostic coverage.

## Supported Services

Below is a quick reference for supported diagnostic services and subcommands in this Python implementation.

### UDS (ISO 14229)

| Service | Name | Supported subcommands / notes |
| --- | --- | --- |
| 0x10 | Diagnostic Session Control | Supports standard session types (by enum). |
| 0x11 | ECU Reset | Standard reset types (by enum). |
| 0x14 | Clear Diagnostic Information | Only `groupOfDTC = 0xFFFFFF`. |
| 0x19 | Read DTC Information | Only report DTC by status mask (subfunction 0x02). |
| 0x22 | Read Data By Identifier | Full DID list; supports multiple DIDs in one request. |
| 0x23 | Read Memory By Address | Reads from memory blocks when mapped; otherwise filler 0xFF. |
| 0x24 | Read Scaling Data By Identifier | Same data as Read DID (minimal implementation). |
| 0x27 | Security Access | Seed/key pairs from spec; odd/even and vendor levels. |
| 0x28 | Communication Control | Accepted; no-op response. |
| 0x2A | Read Data By Periodic Identifier | Acknowledges with transmissionMode + echo DIDs. |
| 0x2C | Dynamically Define Data Identifier | Define-by-identifier, define-by-memory, clear. |
| 0x2E | Write Data By Identifier | Updates DID content. |
| 0x2F | Input/Output Control By Identifier | Echoes control record; updates DID if provided. |
| 0x31 | Routine Control | Start/stop/results based on spec routines. |
| 0x34 | Request Download | Memory-block based. |
| 0x35 | Request Upload | Memory-block based. |
| 0x36 | Transfer Data | Block sequence handling. |
| 0x37 | Request Transfer Exit | Completes active transfer. |
| 0x3D | Write Memory By Address | Writes to memory blocks. |
| 0x3E | Tester Present | Standard 0x00/0x80 plus VehiCAL extensions (0x30/0x32/0x33). |
| 0x85 | Control DTC Setting | Accepted; no-op response. |
| Custom | `customServices` matcher | Spec-defined strict/prefix/suffix handlers with fixed/replay responses and optional actions. |

### KWP2000 (ISO 14230)

| Service | Name | Supported subcommands / notes |
| --- | --- | --- |
| 0x10 | Start Diagnostic Session | Standard session types (by enum). |
| 0x11 | ECU Reset | Standard reset types (by enum). |
| 0x12 | Read Freeze Frame Data | Returns empty positive response. |
| 0x13 | Read Diagnostic Trouble Codes | Returns all DTCs (count + 3-byte DTCs). |
| 0x14 | Clear Diagnostic Information | Only `groupOfDTC = 0xFFFF`. |
| 0x17 | Read Status Of DTCs | Status mask filtering, returns status + DTCs. |
| 0x18 | Read DTCs By Status | Only subfunction 0x02 with mask FFFF. |
| 0x1A | Read ECU Identification | ECU ID table via spec (incl. VIN if provided). |
| 0x20 | Stop Diagnostic Session | Resets to default session. |
| 0x21 | Read Data By Local Identifier | Local identifier table. |
| 0x22 | Read Data By Common Identifier | Common identifier table. |
| 0x23 | Read Memory By Address | 3-byte address + 1-byte length. |
| 0x24 | Read Scaling Data By Identifier | Same data as Read Common ID. |
| 0x27 | Security Access | Seed/key pairs from spec; odd/even and vendor levels. |
| 0x2C | Dynamically Define Data Identifier | Define-by-identifier, define-by-memory, clear. |
| 0x2E | Write Data By Identifier | Writes common identifiers. |
| 0x31 | Routine Control | Start/stop/results based on spec routines. |
| 0x34 | Request Download | Memory-block based. |
| 0x36 | Transfer Data | Block sequence handling. |
| 0x37 | Request Transfer Exit | Completes active transfer. |
| 0x3E | Tester Present | KWP-style (no subfunction). |
| 0x81 | Start Communication | Echoes payload. |
| 0x82 | Stop Communication | Echoes payload. |
| 0x83 | Access Timing Parameters | Echoes payload. |
| 0x84 | Network Configuration | Echoes payload. |
| Custom | `customServices` matcher | Spec-defined strict/prefix/suffix handlers with fixed/replay responses and optional actions. |

### OBD-II (SAE J1979, CAN/ISO-TP)

| Mode | Name | Supported subcommands / notes |
| --- | --- | --- |
| 0x01 | Show Current Data | PID support bitmaps + configured PIDs. |
| 0x02 | Show Freeze Frame Data | Uses frame index + PID. |
| 0x03 | Show Stored DTCs | Count + two-byte DTCs. |
| 0x04 | Clear DTCs | Clears stored + pending DTCs. |
| 0x05 | O2 Sensor Monitoring | PID support bitmaps + configured PIDs. |
| 0x06 | On-board Monitoring | PID support bitmaps + configured PIDs. |
| 0x07 | Show Pending DTCs | Count + two-byte DTCs. |
| 0x08 | Control On-board System/Test | PID support bitmaps + configured PIDs (`controlOperations`). |
| 0x09 | Vehicle Information | PID support bitmaps + configured PIDs. |
| 0x0A | Permanent DTCs | Count + two-byte DTCs. |

## License

Proprietary

## Contact

Dr. Michael 'Mickey' Lauer — mickey@vanille-media.de
