Skip to main content

A Python library and CLI tool for simulating Haltech ECU CAN bus data

Project description

haltech-mock

PyPI version Python 3.11+ License: MIT CI codecov

A Python library and CLI tool for simulating Haltech ECU CAN bus data. Perfect for developing and testing dashboards, data loggers, and other automotive tools that interface with Haltech engine management systems—without requiring a running engine.

Features

  • Full V2.35 Protocol Support — 38 frames, 167 channels including RPM, temperatures, pressures, lambda, and status flags
  • PD16 Power Distribution Module — Simulate the 16-output PDM with fault injection, current simulation, and 5 built-in scenarios
  • Physics-Based Simulation — Realistic engine behavior with RPM response, temperature modeling, and vehicle dynamics
  • 9 Built-in ECU Scenarios — Idle, acceleration, warmup, track laps, and warning conditions (overheating, low oil, redline, battery)
  • Custom JSON Scenarios — Define your own scenarios with transitions, variations, and multi-stage sequences
  • Generic Sensor Support — 10 user-configurable generic sensor channels for custom data
  • Multiple Output Modes — Virtual CAN (vcan), SocketCAN interfaces, or stdout in candump format
  • Flexible Python API — Use as a library in your own projects
  • Rich CLI — Beautiful terminal output with channel/frame inspection tools

Installation

pip install haltech-mock

From Source

git clone https://github.com/YeeP79/haltech-mock.git
cd haltech-mock
pip install -e ".[dev]"

Linux vcan Setup (Optional)

To use virtual CAN on Linux:

sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

Quick Start

CLI Usage

# Run with physics simulation (outputs to stdout)
haltech-mock run --physics

# Run idle scenario for 30 seconds
haltech-mock scenario idle --duration 30

# Run a warning scenario to test dashboard alerts
haltech-mock scenario overheat --duration 60

# Run a track lap scenario
haltech-mock scenario track

# Run a custom JSON scenario
haltech-mock scenario my_scenario.json

# List all available channels
haltech-mock channels

# List all CAN frames
haltech-mock frames

# Decode a raw CAN frame
haltech-mock decode 0x360 0DAC03E800000000

Python API

from haltech_mock import HaltechSimulator
from haltech_mock.interfaces.stdout import StdoutInterface

# Create simulator with stdout output
interface = StdoutInterface()
sim = HaltechSimulator(interface=interface)

# Set channel values directly
sim.set_channel("RPM", 3500)
sim.set_channel("Coolant Temperature", 85, units="C")  # Auto-converts to Kelvin
sim.set_channel("Oil Pressure", 45, units="psi")       # Auto-converts to kPa

# Use generic sensors for custom data
sim.set_channel("Generic Sensor 1", 42.5)   # Your custom sensor
sim.set_channel("Generic Sensor 2", 100.0)  # Another custom value

# Send all frames once
sim.send_all_frames()

# Or run continuously
sim.start()  # Blocks until Ctrl+C

Physics-Based Simulation

from haltech_mock import HaltechSimulator, create_physics_simulator
from haltech_mock.interfaces.stdout import StdoutInterface

interface = StdoutInterface()
sim = HaltechSimulator(interface=interface)

# Create physics engine with "sport" preset
physics = create_physics_simulator(sim, performance="sport")

# Control the simulated vehicle
physics.set_throttle(0.75)  # 75% throttle
physics.set_gear(3)

# Run simulation loop
import time
while True:
    physics.update(0.02)  # 50Hz update
    sim.send_all_frames()
    time.sleep(0.02)

Scenario Playback

import asyncio
from haltech_mock import HaltechSimulator, ScenarioRunner, create_idle_scenario
from haltech_mock.interfaces.stdout import StdoutInterface

interface = StdoutInterface()
sim = HaltechSimulator(interface=interface)
runner = ScenarioRunner(sim)

# Run built-in idle scenario
scenario = create_idle_scenario(duration=60.0)

async def main():
    await sim.start_async()
    await runner.run(scenario)
    sim.stop()

asyncio.run(main())

PD16 Power Distribution Module

The PD16 simulator supports the full bidirectional protocol with 16 outputs and 8 inputs.

CLI:

# Run PD16 simulator (device A)
haltech-mock pd16 run --device A

# Run a PD16 scenario
haltech-mock pd16 scenario lights

# List available PD16 scenarios
haltech-mock pd16 scenarios

# Show PD16 output/input information
haltech-mock pd16 outputs

Python API:

from haltech_mock.simulators.pd16 import PD16Simulator
from haltech_mock.protocols.pd16 import DeviceID
from haltech_mock.interfaces.stdout import StdoutInterface

interface = StdoutInterface()
sim = PD16Simulator(interface=interface, device_id=DeviceID.A)

# Set output states
sim.set_output_25a(0, duty_cycle=75.0)  # Fan motor at 75%
sim.set_output_8a(0, duty_cycle=100.0)  # Headlights full on

# Inject a fault for testing
from haltech_mock.models.pd16.enums import MuxType, PinState
sim.inject_fault(MuxType.OUTPUT_8A, 5, PinState.SHORT_CIRCUIT)

# Start simulator (transmits status frames at protocol rates)
sim.start()  # Blocks until Ctrl+C

Async with scenarios:

import asyncio
from haltech_mock.simulators.pd16 import PD16Simulator
from haltech_mock.scenarios.pd16 import PD16ScenarioRunner, create_lights_scenario

sim = PD16Simulator(interface=interface)
runner = PD16ScenarioRunner(sim)
scenario = create_lights_scenario()

async def main():
    await sim.start_async()
    await runner.run(scenario)
    sim.stop()

asyncio.run(main())

Built-in Scenarios

Scenario Description Default Duration
idle Engine idling with realistic RPM and temperature variations 30s
accel Smooth acceleration from low to high RPM 5s
warmup Cold start with high idle, rich mixture, gradual temperature rise 120s
track Single track lap with straights, braking zones, and corners 90s
track-session Multi-lap track session (loops until stopped) 90s/lap
overheat Engine overheating with fan activation and warning lights 30s
low-oil Oil pressure dropping with warning light activation 20s
redline Hitting the rev limiter with limiting flags active 15s
battery Alternator failure with dropping voltage and warning light 20s
# Run any built-in scenario
haltech-mock scenario idle
haltech-mock scenario overheat --duration 60
haltech-mock scenario track --loop

PD16 Scenarios

The PD16 simulator includes 5 built-in scenarios for testing power distribution:

Scenario Description Outputs Used
lights Parking, low beam, high beam, turn signals 8A: 0-4
cooling Temperature-based fan control (0% → 30% → 80%) 25A: 0
fuel-pump Priming sequence and steady operation 25A: 1
fault Cycle through fault conditions (overcurrent, short, open) 8A: 5
startup Boot sequence with output self-test Multiple
# Run PD16 scenarios
haltech-mock pd16 scenario lights
haltech-mock pd16 scenario cooling --loop
haltech-mock pd16 scenario fault --device B

Custom JSON Scenarios

Create your own scenarios using JSON files. This is perfect for:

  • Testing specific dashboard behaviors
  • Simulating custom warning conditions
  • Replaying recorded data patterns
  • Creating test sequences for your application

JSON Scenario Format

{
  "name": "My Custom Scenario",
  "description": "A custom test scenario",
  "loop": false,
  "stages": [
    {
      "name": "Stage 1",
      "duration": 10.0,
      "channels": {
        "RPM": 2500,
        "Coolant Temperature": {"value": 358.15},
        "Oil Pressure": {
          "range": [300, 400],
          "variation": "noise"
        },
        "Generic Sensor 1": {"value": 50.0}
      },
      "transitions": {
        "Throttle Position": {"from": 0, "to": 50, "curve": "ease_in"}
      }
    }
  ]
}

Channel Value Options

Channels can be specified in several ways:

{
  "channels": {
    "RPM": 3500,

    "Coolant Temperature": {"value": 358.15},

    "Oil Pressure": {
      "range": [300, 400],
      "variation": "sine",
      "period": 2.0
    },

    "Wideband Lambda 1": {
      "range": [0.95, 1.05],
      "variation": "random_walk"
    }
  }
}
Property Description
value Static value
range [min, max] for variations
variation "none", "sine", "noise", or "random_walk"
period Period in seconds for sine variation
units Unit conversion: "C", "F", "psi", "bar"

Transitions

Transitions smoothly change values over the stage duration:

{
  "transitions": {
    "RPM": {
      "from": 1000,
      "to": 7000,
      "curve": "ease_in"
    }
  }
}

Available curves: "linear", "exponential", "ease_in", "ease_out", "ease_in_out"

Complete Example: Custom Warning Test

Save as warning_test.json:

{
  "name": "Dashboard Warning Test",
  "description": "Test all warning lights and conditions",
  "stages": [
    {
      "name": "Normal Operation",
      "duration": 5.0,
      "channels": {
        "RPM": {"range": [800, 900], "variation": "sine", "period": 2.0},
        "Coolant Temperature": {"value": 358.15},
        "Oil Pressure": {"value": 350},
        "Battery Voltage": {"value": 14.2},
        "Check Engine Light": {"value": 0},
        "Oil Pressure Light": {"value": 0},
        "Battery Light": {"value": 0}
      }
    },
    {
      "name": "Check Engine Warning",
      "duration": 5.0,
      "channels": {
        "RPM": {"value": 850},
        "Check Engine Light": {"value": 1},
        "Oil Pressure Light": {"value": 0},
        "Battery Light": {"value": 0}
      }
    },
    {
      "name": "Oil Pressure Warning",
      "duration": 5.0,
      "channels": {
        "RPM": {"value": 850},
        "Oil Pressure": {"value": 80},
        "Check Engine Light": {"value": 0},
        "Oil Pressure Light": {"value": 1},
        "Battery Light": {"value": 0}
      }
    },
    {
      "name": "Battery Warning",
      "duration": 5.0,
      "channels": {
        "RPM": {"value": 850},
        "Battery Voltage": {"value": 11.5},
        "Check Engine Light": {"value": 0},
        "Oil Pressure Light": {"value": 0},
        "Battery Light": {"value": 1}
      }
    },
    {
      "name": "All Warnings",
      "duration": 5.0,
      "channels": {
        "RPM": {"value": 850},
        "Coolant Temperature": {"value": 393.15},
        "Oil Pressure": {"value": 80},
        "Battery Voltage": {"value": 11.5},
        "Check Engine Light": {"value": 1},
        "Oil Pressure Light": {"value": 1},
        "Battery Light": {"value": 1},
        "Thermo Fan 1": {"value": 1},
        "Thermo Fan 2": {"value": 1}
      }
    }
  ]
}

Run it:

haltech-mock scenario warning_test.json --loop

Generic Sensors

Haltech ECUs support 10 generic sensor channels that can be configured for any purpose. These are perfect for:

  • Custom sensors not covered by standard channels
  • Testing dashboard elements with arbitrary data
  • Simulating auxiliary inputs

Using Generic Sensors

from haltech_mock import HaltechSimulator
from haltech_mock.interfaces.stdout import StdoutInterface

sim = HaltechSimulator(interface=StdoutInterface())

# Set generic sensor values
sim.set_channel("Generic Sensor 1", 42.5)    # Boost controller setpoint
sim.set_channel("Generic Sensor 2", 100.0)   # Nitrous bottle pressure
sim.set_channel("Generic Sensor 3", 75.0)    # Intercooler water temp
sim.set_channel("Generic Sensor 4", 1.0)     # Custom switch state
# ... up to Generic Sensor 10

sim.send_all_frames()

In JSON Scenarios

{
  "name": "Custom Sensors Demo",
  "stages": [
    {
      "name": "Sensor Test",
      "duration": 30.0,
      "channels": {
        "RPM": 3000,
        "Generic Sensor 1": {"range": [0, 100], "variation": "sine", "period": 5.0},
        "Generic Sensor 2": {"value": 50.0},
        "Generic Sensor 3": {"range": [40, 60], "variation": "noise"}
      }
    }
  ]
}

CLI Reference

haltech-mock run

Run the simulator with manual or physics-based control.

haltech-mock run [OPTIONS]

Options:
  -i, --interface TEXT     CAN interface (stdout, null, vcan0, etc.)
  -p, --physics            Enable physics-based simulation
  --performance TEXT       Physics preset: street, sport, race
  -t, --throttle FLOAT     Initial throttle position (0-100)
  -g, --gear INTEGER       Initial gear (0=neutral, 1-6)
  --rpm FLOAT              Initial RPM (manual mode only)
  -d, --duration FLOAT     Run duration in seconds
  --no-timestamp           Disable timestamps in output

haltech-mock scenario

Run a predefined or custom scenario.

haltech-mock scenario NAME [OPTIONS]

Arguments:
  NAME                     Scenario name or path to JSON file
                          Built-in: idle, accel, warmup, track, track-session,
                                   overheat, low-oil, redline, battery

Options:
  -i, --interface TEXT     CAN interface
  -d, --duration FLOAT     Override scenario duration
  -l, --loop               Loop the scenario
  --no-timestamp           Disable timestamps in output

haltech-mock channels

List all available channels in the protocol.

haltech-mock channels [FILTER]

Arguments:
  FILTER                   Filter channels by name (optional)

Examples:
  haltech-mock channels              # List all channels
  haltech-mock channels temp         # Filter by "temp"
  haltech-mock channels generic      # Show generic sensors

haltech-mock frames

List all CAN frames or show details for a specific frame.

haltech-mock frames [FRAME_ID]

Arguments:
  FRAME_ID                 Show details for specific frame (e.g., 0x360)

haltech-mock decode

Decode a CAN frame and display channel values.

haltech-mock decode FRAME_ID DATA

Arguments:
  FRAME_ID                 CAN frame ID (e.g., 0x360)
  DATA                     Hex data string (e.g., 0DAC03E800000000)

haltech-mock pd16

PD16 Power Distribution Module simulator commands.

haltech-mock pd16 run

Run the PD16 simulator.

haltech-mock pd16 run [OPTIONS]

Options:
  -i, --interface TEXT     CAN interface (stdout, null, vcan0, etc.)
  -d, --device TEXT        Device ID: A, B, C, or D (default: A)
  --duration FLOAT         Run duration in seconds
  --no-timestamp           Disable timestamps in output

haltech-mock pd16 scenario

Run a PD16 scenario.

haltech-mock pd16 scenario NAME [OPTIONS]

Arguments:
  NAME                     Scenario name (lights, cooling, fuel-pump, fault, startup)

Options:
  -i, --interface TEXT     CAN interface
  -d, --device TEXT        Device ID
  -l, --loop               Loop the scenario
  --no-timestamp           Disable timestamps in output

haltech-mock pd16 scenarios

List available PD16 scenarios with descriptions.

haltech-mock pd16 outputs

Show PD16 output and input information (types, counts, index ranges).

Protocol Support

Based on Haltech CAN Broadcast Protocol V2.35:

Frame Range Description Rate
0x360-0x362 Engine Core (RPM, pressures, injection) 50 Hz
0x363-0x370 Sensors (lambda, knock, wheel speeds) 20 Hz
0x371-0x376 Fuel, battery, EGT, ambient 10 Hz
0x3E0-0x3EF Temperatures, status flags, generic sensors 5-50 Hz
0x470-0x472 Wideband, pedal, cruise control 20-50 Hz

Supported Channels (167 Total)

  • Engine: RPM, Throttle Position, Engine Demand, Manifold Pressure
  • Temperatures: Coolant, Oil, Air, Fuel, Ambient, Gearbox, Diff, EGT 1-12
  • Pressures: Oil, Fuel, Coolant, Manifold, Barometric, Brake, NOS 1-4
  • Lambda: Wideband 1-12, Target Lambda, Bank averages
  • Drivetrain: Vehicle Speed, Gear, Wheel Speeds (FL/FR/RL/RR), Driveshaft RPM
  • Electrical: Battery Voltage, Injector Duty Cycle (4 stages)
  • Status Flags: 23 boolean indicators (Check Engine, Oil Warning, Fans, etc.)
  • Generic Sensors: 10 user-configurable channels
  • And more: Knock, Cam angles, Turbo speed, G-forces, Cruise control, etc.

PD16 Protocol Support

The PD16 Power Distribution Module uses multiplexed CAN messages with bidirectional communication.

Device IDs (up to 4 PD16 units on one CAN bus):

Device Base CAN ID Frame Range
A 0x6D0 0x6D0-0x6D7
B 0x6D8 0x6D8-0x6DF
C 0x6E0 0x6E0-0x6E7
D 0x6E8 0x6E8-0x6EF

Outputs:

  • 4 × 25A High Current outputs (index 0-3) — for fans, fuel pumps, high-draw loads
  • 10 × 8A High Side outputs (index 0-9) — for lights, relays, low-draw loads
  • 2 × Half-Bridge outputs (index 0-1) — for DC motor control

Inputs:

  • 4 × SPI (Speed/Pulse) inputs — for wheel speed sensors, etc.
  • 4 × AVI (Analog Voltage) inputs — for temperature sensors, pressure sensors

Transmission Rates:

Frame Type Direction Rate
Input Status PD16→ECU 20 Hz
Output Status PD16→ECU 5 Hz
Device Status PD16→ECU 2 Hz
Diagnostics PD16→ECU 2 Hz

Output Formats

stdout (candump format)

(1234567890.123456) stdout 360#0DAC03E800000000
(1234567890.143456) stdout 361#00C8012C00000000

SocketCAN

Connect directly to vcan0, can0, or any SocketCAN interface on Linux.

Development

# Clone and install
git clone https://github.com/YeeP79/haltech-mock.git
cd haltech-mock
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

# Run tests
pytest

# Run linter
ruff check src/ tests/

# Run type checker
mypy src/

Contributing

Contributions are welcome! Please ensure:

  1. All tests pass (pytest)
  2. Code is formatted (ruff format src/ tests/)
  3. No lint errors (ruff check src/ tests/)
  4. Type checks pass (mypy src/)
  5. New features include tests

License

MIT License - see LICENSE for details.

Acknowledgments

  • Protocol specification based on Haltech CAN Broadcast Protocol V2.35
  • Built for the automotive tuning and dashboard development community

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

haltech_mock-0.1.1.tar.gz (92.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

haltech_mock-0.1.1-py3-none-any.whl (83.2 kB view details)

Uploaded Python 3

File details

Details for the file haltech_mock-0.1.1.tar.gz.

File metadata

  • Download URL: haltech_mock-0.1.1.tar.gz
  • Upload date:
  • Size: 92.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for haltech_mock-0.1.1.tar.gz
Algorithm Hash digest
SHA256 9a0dfa08bb0b8cb85bcc717e0d70d853085737787433f82ba700b86174aa7dfb
MD5 bbc905b7176184846193e6378229b01e
BLAKE2b-256 62dc4d9d8a13c76c832aa2a5b775f83a35fa2e28cd60ba983e94e817935a4506

See more details on using hashes here.

Provenance

The following attestation bundles were made for haltech_mock-0.1.1.tar.gz:

Publisher: ci.yml on YeeP79/haltech-mock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file haltech_mock-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: haltech_mock-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 83.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for haltech_mock-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 891684545a1ba6b36890c83021a1641d3103e8fc1592f45d52474892cc03b979
MD5 ca366e1735b2134b830105cbc059df2b
BLAKE2b-256 b2df56a0bff270dcc95a2395e1fc91da61eb3d7d7a4994f5b422f6eb4b1bc104

See more details on using hashes here.

Provenance

The following attestation bundles were made for haltech_mock-0.1.1-py3-none-any.whl:

Publisher: ci.yml on YeeP79/haltech-mock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page