Skip to main content

Bulletproof Hardware-in-the-Loop testing for firmware teams

Project description

CruciHiL

Bulletproof, easy-to-use Hardware-in-the-Loop (HiL) testing for firmware teams.

Write a test in Python. Run it against simulation before hardware exists. Deploy to real hardware with zero test changes. See results in CI/CD automatically. Ask AI what broke and why.


Why CruciHiL

Legacy HiL tools (dSPACE, NI, Vector) are expensive, slow to configure, and hostile to modern dev workflows. CruciHiL is built for teams that move fast:

  • Python-first — no proprietary scripting languages, full IDE support
  • Simulation-to-hardware parity — same test file, swap a TOML config
  • CI/CD native — runs headless, produces JUnit XML, integrates with GitHub Actions
  • AI-powered analysis — MCP server connects Claude/GPT directly to test results and signal traces

Architecture

Layer 6 — Interfaces        Web Dashboard · CLI · CI/CD webhooks
Layer 5 — AI Interface      MCP Server (FastMCP) — 11 tools, vendor-agnostic
Layer 4 — Cloud Control     FastAPI + PostgreSQL — orchestration and history
Layer 3 — Local Agent       Test runner · YAML executor · result reporter
Layer 2 — Rig HAL           rig.can / rig.sim / rig.someip / rig.doip / rig.ecu
Layer 1 — Hardware          CAN · Ethernet · GPIO · Power · ECUs

Test code only ever touches Layer 2. Hardware details live in TOML config, never in test code.


Installation

pip install crucihil

Or from source:

git clone <repo>
cd crucihil
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

CLI Reference

crucihil --help

Commands:
  version   Show CruciHiL version
  run       Run a test suite against a rig
  agent     Start the persistent local agent daemon
  init      Interactive wizard — create a rig TOML and register with cloud
  discover  AI-assisted rig setup (probes hardware, generates TOML)

crucihil run — run tests locally

# Run against the virtual simulation rig (no hardware needed)
crucihil run --suite tests/suites/engine_validation.yaml --rig rigs/virtual.toml

# Run against real hardware
crucihil run --suite tests/suites/engine_validation.yaml --rig rigs/my_bench.toml

# With JUnit XML + HTML output
crucihil run --suite tests/suites/engine_validation.yaml --rig rigs/virtual.toml \
  --output results.xml --html results.html

crucihil init — set up a new rig

The interactive wizard for configuring a new rig. Run this on the bench machine:

crucihil init

It will:

  1. Auto-detect CAN and Ethernet interfaces (ip link)
  2. Prompt for bitrate, FD mode, power backend, DBC path
  3. Write a validated rigs/<name>.toml
  4. Optionally register the rig with the cloud control plane (self-registers, saves API key)

After crucihil init, start the agent with no further config:

crucihil agent --rig rigs/<name>.toml

crucihil agent — persistent agent daemon

Runs on the bench machine. Connects to the cloud via WebSocket, receives test run commands, streams results back.

crucihil agent --rig rigs/my_bench.toml

First boot auto-registration: if [rig.cloud] has a registration_token but no api_key, the agent registers itself, saves the key to ~/.crucihil/credentials.toml, and connects — no manual steps needed.

# rigs/my_bench.toml
[rig.cloud]
url                = "https://crucihil-server.fly.dev"
registration_token = "your-REGISTRATION_TOKEN-here"
# api_key is saved automatically after first boot

Options:

--rig, -r   PATH   Path to rig TOML config (required)
--cache     PATH   SQLite result cache path (default: ~/.crucihil/results.db)
--verbose   -v     Enable debug logging

Writing Tests

async def test_engine_startup(rig: Rig):
    await rig.can.send(message="EngineControl", fields={"Throttle": 50.0, "Mode": 1.0})
    result = await rig.can.expect(
        signal="EngineData.RPM",
        condition=lambda v: v > 800,
        timeout=2.0,
    )
    assert result.passed, result.fail_msg

Switch from virtual to real hardware: change --rig rigs/virtual.toml to --rig rigs/my_bench.toml. The test is unchanged.

Fault injection

async def test_can_dropout_recovery(rig: Rig):
    async with rig.fault.inject(rig.fault.can_dropout(arb_id=0x100, duration=1.0)):
        await asyncio.sleep(1.0)
    result = await rig.can.expect("EngineData.RPM", lambda v: v > 0, timeout=3.0)
    assert result.passed, result.fail_msg

ECU firmware flash

async def test_firmware_update(rig: Rig):
    result = await rig.ecu.ecu_main.flash("builds/firmware_v2.1.bin")
    assert result.success, result.error

Complete Rig Setup Workflow

This is the end-to-end process for bringing a new bench machine online.

Step 1 — Install CruciHiL on the bench machine

pip install crucihil

Step 2 — Run the setup wizard

crucihil init

Follow the prompts. At the end it will ask:

Connect this rig to a CruciHiL cloud server? [y/N]:

Answer y and provide:

  • Server URLhttps://crucihil-server.fly.dev (or your self-hosted URL)
  • Registration token — the REGISTRATION_TOKEN from your server setup

The wizard registers the rig, saves the API key locally, and writes the [rig.cloud] section into the TOML. You won't need to handle the key manually.

Step 3 — Start the agent

crucihil agent --rig rigs/<name>.toml

The rig appears as connected in the dashboard within seconds. You can now trigger test runs from the dashboard, CLI, or via AI through the MCP server.

Step 4 (optional) — Install as a systemd service for production

sudo ./scripts/install-agent.sh \
  --rig rigs/my_bench.toml \
  --server https://crucihil-server.fly.dev \
  --key <api-key>
systemctl status crucihil-agent@my-bench
journalctl -u crucihil-agent@my-bench -f
systemctl restart crucihil-agent@my-bench

Cloud Dashboard

The web dashboard is available at https://app.crucihil.io.

First-time setup (self-hosted)

Bootstrap your first org and admin account via the setup API (one-time only):

curl -X POST https://your-server/api/v1/setup \
  -H 'Content-Type: application/json' \
  -d '{"org_name":"Acme Corp","admin_email":"you@company.com","admin_password":"strong-password"}'

Returns a JWT. Log in at the dashboard with the same email and password.

Inviting team members

Admins can invite members from Settings → Team → Invite member. An email is sent via Resend with a link to set their password. Members can view results and trigger runs; admins can also manage rigs and users.

Connecting an AI client (MCP)

Add to Claude Desktop claude_desktop_config.json:

{
  "mcpServers": {
    "crucihil": {
      "url": "https://crucihil-mcp.fly.dev/sse",
      "headers": {
        "Authorization": "Bearer <your-CRUCIHIL_API_KEY>"
      }
    }
  }
}
MCP Tool What it does
list_rigs List rigs with online/offline status
get_rig_config Hardware summary for one rig
list_runs Query run history
get_run_summary Pass/fail counts and status for one run
run_test_suite Trigger a test suite on a connected rig
cancel_run Cancel an active run
get_results Per-test results (filterable by status)
get_signal_trace Signal telemetry recorded during a run
describe_failure Full failure context in one call — errors + signals + logs
list_signals Parse a DBC and return all signal names
list_tests Parse a YAML manifest and return test metadata

Self-Hosting with Docker

# Clone and configure
cp .env.example .env
# Edit .env — set POSTGRES_PASSWORD and SECRET_KEY

# Bootstrap everything in one command
./setup.sh

# Start the dashboard dev server (hot reload)
./dev.sh

Dashboard at http://localhost:5173.

./setup.sh --status    # service health
./setup.sh --restart   # restart containers
./dev.sh --rig rigs/my_bench.toml   # register and start a native rig agent

Deploy to Fly.io

fly deploy --config fly.server.toml   # control plane
fly deploy --config fly.mcp.toml      # MCP server

Set secrets (not in toml files):

fly secrets set SECRET_KEY="..." REGISTRATION_TOKEN="..." RESEND_API_KEY="re_..." --app crucihil-server

Dashboard deploys automatically to Vercel on push to main.


Supported Hardware Backends

Bus Backends
CAN socketcan (Linux), peak (PEAK PCAN-USB), virtual
SOME/IP vsomeip, virtual_someip
DoIP python-doip, virtual
Power gpio_relay, bench_psu, virtual_power
GPIO linux_gpio, virtual_gpio
Custom backend = "myorg.module:ClassName" via importlib

Project Structure

crucihil/
├── hal/          Layer 2: Rig HAL (backends, BSE, namespaces, config)
├── agent/        Layer 3: Test runner, agent daemon, SQLite cache, init wizard
├── server/       Layer 4: FastAPI control plane + PostgreSQL
├── mcp/          Layer 5: MCP server (11 tools, FastMCP 3.x)
└── cli/          Layer 6: CLI entry point

rigs/             Rig TOML configs (hardware details — never in test code)
tests/
├── unit/         Unit tests (361 passing)
└── integration/  Integration tests against virtual rig
scripts/
├── release.sh    Cut a new release: ./scripts/release.sh 0.2.0
└── install-agent.sh  Install agent as systemd service on a bench machine

Cutting a Release

./scripts/release.sh 0.2.0

Bumps pyproject.toml, commits, tags v0.2.0, pushes. GitHub Actions publishes to PyPI and creates a GitHub Release automatically.


Requirements

  • Python 3.11+
  • Real hardware additionally requires: Linux (for SocketCAN/GPIO), vsomeip, python-doip, and relevant drivers

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

crucihil-0.6.0.tar.gz (283.2 kB view details)

Uploaded Source

Built Distribution

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

crucihil-0.6.0-py3-none-any.whl (152.5 kB view details)

Uploaded Python 3

File details

Details for the file crucihil-0.6.0.tar.gz.

File metadata

  • Download URL: crucihil-0.6.0.tar.gz
  • Upload date:
  • Size: 283.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for crucihil-0.6.0.tar.gz
Algorithm Hash digest
SHA256 fa8d55f27622c6daa487942809a5c9aa834377da3435533f01c8f73f1b5fdcac
MD5 af5de56d3a73dd0e7dee3e31f7b1b6d8
BLAKE2b-256 dc24dab83cab91f3f04ac1775c5dd8c84f722253e8d50ca63c3bfa449d418b5d

See more details on using hashes here.

File details

Details for the file crucihil-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: crucihil-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 152.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for crucihil-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f21cbe35fc051b1f4ed74a29871378111ccdb53e49ac3e7cf5ec785f0c6cb2f1
MD5 99bf08de4863575cbff9a282c0ec9562
BLAKE2b-256 a8aa98053775d61fc8e7eb1803a6286f55fbb40ad0b4d8fefb47bda528e20511

See more details on using hashes here.

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