Skip to content

devdash-project/haltech-mock

Repository files navigation

devdash-haltech-mock

PyPI version Python 3.11+ License: MIT CI codecov

Note: This package was renamed from haltech-mock to devdash-haltech-mock in v0.2.0. The CLI command haltech-mock still works for backwards compatibility.

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 devdash-haltech-mock

From Source

git clone https://github.com/devdash-project/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/devdash-project/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

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

0 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors

Languages