Python-based ECU emulation and automotive diagnostic tooling.
Project description
ECUmulator.py
Local TCP note: For loopback adapter connections in this project, use
127.0.0.1instead oflocalhost.localhostcan 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):
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):
pipx install git+ssh://git@gitlab.com/a11086/ecumulator.py.git
pip (venv or local checkout):
python3.12 -m pip install -r requirements.txt
Or install as a package (local checkout):
python3.12 -m pip install .
Install directly from GitLab (private repo):
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_usbbackend forpython-canwhen usinggs_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:
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:
epy server ecu:./path/to/spec.json
Interactive REPL:
epy server -i ecu:./path/to/spec.json
Attach to real CAN:
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>, ortoucan:<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_usbstartup 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, butpip install python-can-candlefailed due to a transitivecandle-apibuild 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:
cd ui
npm install
npm run tauri dev
Building for Linux
Standard build (requires internet on first launch for Python runtime setup):
make linux
Offline .deb (bundles Python runtime + all wheels — no internet needed on target):
make deb
Install:
sudo apt install ./dist/ecumulator-studio_*.deb
Uninstall:
sudo apt remove ecumulator-studio
UI source structure (for contributors):
ui/src/App.tsx— root orchestration/compositionui/src/app/defaults.ts— default state/constantsui/src/app/types.ts— App-local typesui/src/app/helpers.ts— pure helpers and formatting logicui/src/app/serviceCatalog.tsx— service/settings catalogsui/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):
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:
epy server ecu:./path/to/spec.json --isotp-fc-overflow-prob 0.01 --isotp-fc-rng-seed 42
Forced overrun (every FC is OVERFLOW):
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, andELM327/STN2xxxare classic-transport services and are unavailable withtransportProfile: "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; useserver --elm327.update [--force]— Check Git tags and update the installed package.version— Print current version.help/--help— Show usage.
Example:
epy import ./capture.log -o ./specs
Sample Specs
The specs/can/ directory includes CAN-based OBD2 fixtures for app testing:
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):
epy server specs/can/canfd-can11-vehicle.json
ECUconnect TCP adapter (optional)
Expose ECUconnect/CANyonero compatible TCP server:
epy server --ecuconnect ecu:./path/to/spec.json
Combine with a real bus:
epy server --ecuconnect -a can0@500000 ecu:./path/to/spec.json
Behavior notes:
- Adapter identity (
request_infofirmware/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. Onreset, 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:
epy server --logger ecu:./path/to/spec.json
Custom service name:
epy server --logger --logger-name "ECUmulator.py Lab" ecu:./path/to/spec.json
HSFZ TCP adapter + UDP discovery (optional)
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)
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)
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:
epy server --enet-service-broker --enet-service-broker-vin WBADE6324VBW12345
Optional broker URL override:
epy server --enet-service-broker --enet-service-broker-vin WBADE6324VBW12345 --enet-service-broker-url https://esb.cornucopia.dev
ISO-TP mode:
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._tcpon the local network. --enet-service-broker-urlis only needed to force a specific broker endpoint.- The broker response includes a tester port (
vehicle port + 1000). - Transport mode can be
isotp(default) orraw. - Studio shows the currently allocated tester port under the ENET service badge.
Clients
HSFZ REPL client:
epy client --hsfz
DoIP REPL client:
epy client --doip
Both clients accept explicit URLs:
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).
epy server --elm327 ecu:./path/to/spec.json
Optional flags:
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:
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:
epy update --force
If you installed via Homebrew, epy update is disabled and will tell you to use:
brew update
brew upgrade ecumulator-py
Notes
- Use
--bus-timing-factorto 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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ecumulator_py-0.9.211.tar.gz.
File metadata
- Download URL: ecumulator_py-0.9.211.tar.gz
- Upload date:
- Size: 348.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df5bdde782a3dfa59ac5082ce50bcf55531d61a55e4ce894fd72bd7276c2d47c
|
|
| MD5 |
5f9d90f48ffdb863883d07cf9a281377
|
|
| BLAKE2b-256 |
eccaaa80f23ef50e6c5a273c18eab4ccd2332aaabebd9818f3d901171d017aaf
|
File details
Details for the file ecumulator_py-0.9.211-py3-none-any.whl.
File metadata
- Download URL: ecumulator_py-0.9.211-py3-none-any.whl
- Upload date:
- Size: 412.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d95b3dc18234ca82f2b4937a71c1b68c8060173d96fb45a71e6388d32ca6504b
|
|
| MD5 |
e2387fcaee50f2f33150c953034c3916
|
|
| BLAKE2b-256 |
d86cdadce466821a3b9b117f3804f4b88a55d585828f72f260cb12dedd0292a3
|