Skip to main content

hilspy

HILS (hardware-in-the-loop simulation) I/O for Python on a Rust core, built to be embedded in a scenario library (such as autoware_carla_scenario).

One call opens a channel: a transport (how packets travel) composed with a codec (what they mean). Both are pluggable, in Rust or in Python.

import hilspy

with hilspy.open("can://vcan0", "dbc:vehicle.dbc") as ecu:
    ecu.send("VehicleSpeed", {"Speed": 12.5, "Accel": 0.0, "Gear": "D", "Counter": 0})
    for msg in ecu:  # until ecu.close()
        print(msg.name, msg.fields)  # "SteeringCmd" {"SteeringAngle": -3.5, ...}
Transport URL
can://vcan0 Linux SocketCAN (?fd, ?echo, ?filter=0x100:0x700,...)
udp://host:port UDP to a peer (?bind=addr:port); udp://addr:port?listen replies to the last sender
tcp://host:port TCP client; tcp://addr:port?listen serves every connected client
mem://name in-process bus (tests, simulated ECUs in one process)
file://path replay a recording; ?write / ?append records one
Codec
raw (default) none: messages carry data (and id for CAN)
dbc:FILE CAN frames <-> DBC messages, via can-dbc (options strict, scaling, decode_choices)
sbf Septentrio Binary Format stream <-> blocks, via libsbf; encodes PVTGeodetic and AttEuler

Install

Python 3.10+. One abi3 wheel per platform serves every Python version; wheels are built for Linux (x86_64, aarch64), macOS (arm64, x86_64) and Windows x64. can:// (SocketCAN) is Linux-only; every other transport and codec works everywhere.

pip install hilspy
uv add hilspy
pip install git+https://github.com/hakuturu583/hilspy   # from source (needs Rust)

From a checkout:

uv sync                             # builds the extension with maturin
uv run pytest

Versions follow semver and are released by labelling the PR (bump patch / bump minor / bump major); see docs/releasing.md.

API

The whole public API:

Name
hilspy.open(url, codec="raw", **codec_options) -> Channel open a channel
Channel.send(message) / send(name, fields) encode and send
Channel.recv(timeout=None) -> Message | None next message (iterate the channel for all)
Channel.every(period, message_or_name, fields=None, *, duration=None) -> Periodic cyclic send from a Rust thread; update(), stop(), period, sent_count
Channel.close() also via with / async with
await Channel.asend(...), await Channel.arecv(timeout=None), async for msg in channel asyncio
hilspy.merge(*channels) (channel, message) from several buses as they arrive (for / async for)
hilspy.Message(name=None, fields=None, *, data=None, id=None, extended=None, fd=False) .name, .fields, msg["field"], .data, .id, .timestamp
hilspy.register_transport(scheme, factory) / register_codec(name, factory) plugins
hilspy.Error bad URL/codec/option, encode/decode failure, closed channel

asyncio and several buses at once

Every channel also has await ch.asend(...), await ch.arecv(timeout), async for msg in ch and async with. hilspy.merge(*channels) receives from many buses at once, yielding (channel, message) as messages arrive, with for or async for:

async with (
    hilspy.open("can://can0", "dbc:powertrain.dbc") as can0,
    hilspy.open("can://can1", "dbc:chassis.dbc") as can1,
    hilspy.open("tcp://192.168.3.1:28784", "sbf") as gnss,
    hilspy.merge(can0, can1, gnss) as buses,
):
    async for bus, msg in buses:
        if bus is gnss and msg.name == "PVTGeodetic":
            await can1.asend("GnssPosition", {"Lat": msg["latitude"], "Lon": msg["longitude"]})

Each bus is received on its own Rust thread, so awaiting never blocks the event loop and a quiet or slow bus does not delay the others. A channel has one consumer at a time: once received with arecv/async for/merge, its synchronous recv refuses.

A message with a name goes through the codec; a nameless one (Message(data=b"...", id=0x123)) is sent as is. Messages the codec does not know (a CAN id missing from the DBC) arrive nameless.

GNSS receiver simulation (SBF over TCP)

with hilspy.open("tcp://0.0.0.0:28784?listen", "sbf") as receiver:
    receiver.send(
        "PVTGeodetic", {"mode": 4, "latitude": lat_rad, "longitude": lon_rad, "height": 40.0}
    )
    receiver.send("AttEuler", {"heading": 90.0, "pitch": 0.0, "roll": 0.0})
    # tow/wnc default to the current GPS time

Plugins

A transport is any object with send(packet) and recv(timeout) (and optionally close()); a codec has encode(message) and decode(packet). Packets are nameless Messages.

class SerialTransport:
    def __init__(self, address: str, options: dict[str, str]):
        self.port = serial.Serial(address, int(options.get("baud", 115200)))

    def send(self, packet):
        self.port.write(packet.data)

    def recv(self, timeout):
        self.port.timeout = timeout
        data = self.port.read(self.port.in_waiting or 1)
        return hilspy.Message(data=data) if data else None


hilspy.register_transport("serial", SerialTransport)
gnss = hilspy.open("serial:///dev/ttyACM0?baud=921600", "sbf")

Rust crates register through hilspy_core::transport::register and hilspy_core::codec::register.

A virtual CAN interface for local testing:

sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set vcan0 mtu 72        # CAN FD
sudo ip link set up vcan0

Examples

uv run python examples/virtual_ecu.py                              # cyclic DBC message, in-process
uv run python examples/virtual_ecu.py --url can://vcan0
uv run python examples/gnss_simulator.py "tcp://127.0.0.1:28784?listen"
uv run python examples/gnss_simulator.py "file://sim.sbf?write" --seconds 5
uv run python examples/sbf_dump.py file://sim.sbf
uv run python examples/multi_bus.py                                # asyncio: CAN + GNSS at once

Development

uv sync                                 # build + dev dependencies (cantools for cross-checks)
cargo test -p hilspy-core               # Rust unit tests
uv run pytest                           # Python tests
HILSPY_TEST_VCAN=vcan0 uv run pytest tests/test_socketcan.py   # needs a vcan interface
uv run pre-commit run --all-files       # ruff, mypy, cargo fmt, clippy

Design notes: docs/design.md.

License

Apache-2.0

Metadata

Release files for hilspy 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hilspy 0.4.0
File Size Uploaded
hilspy-0.4.0.tar.gz 67.0 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for hilspy 0.4.0
File
hilspy-0.4.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
hilspy-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
hilspy-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
hilspy-0.4.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
hilspy-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 4.3 MB

Release files / hilspy-0.4.0.tar.gz

Download URL hilspy-0.4.0.tar.gz
Size 67.0 kB
Tags Source
SHA-256 checksum
How to use checksums
080ebb5ee00c73ce5e0f63554ca3a02e7e0ad18de633c188baa6ea208c47289d
BLAKE2b-256 checksum
How to use checksums
c767f17d0427c43495f256f38b06031e06ebf84dab5ecd323428711014e471b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / hilspy-0.4.0-cp310-abi3-win_amd64.whl

Download URL hilspy-0.4.0-cp310-abi3-win_amd64.whl
Size 777.8 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
8df9d86bc77fa734455d41fec92c82217a4e023dc1e0c2b32c0c0a1606e3c31b
BLAKE2b-256 checksum
How to use checksums
3e7db310d999087d7f35c1340f2d65c678ca2f3d494deffcc73f5c1130b93ff6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / hilspy-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL hilspy-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 906.0 kB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
563509ea0f9171ad1543713aab3903b44ae3f3b65438dfc218503db77c321ba6
BLAKE2b-256 checksum
How to use checksums
58d595a9e22fcfdda44c86f4ea59fd396b70df7f06cf33dbec770e77f00c03fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / hilspy-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL hilspy-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 890.0 kB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
90986ed139667e60b79b47521d8f3f3e40edaa7d824425b36fe8235710784aa3
BLAKE2b-256 checksum
How to use checksums
f6529b75d35a4943f7fc82b346d1f2e31379170dde17131617e8ae60d433fff7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / hilspy-0.4.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL hilspy-0.4.0-cp310-abi3-macosx_11_0_arm64.whl
Size 817.6 kB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
d1a246bde429376c851c746837a1f3e91b5f79ac5872ffcfb56242608ec5eee1
BLAKE2b-256 checksum
How to use checksums
80978798fbe74e346e528fe8175d26ab20f51acaa35a30c9287be2c020493df1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / hilspy-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL hilspy-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 837.2 kB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
11c2af91064abb46494839cebe12dd3beccaf306240dbe038e504e1c92ff8882
BLAKE2b-256 checksum
How to use checksums
e59a1f12e4b157244e8a7e76568562df64a3db29738d7b93b895e118876666ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release history Release notifications | RSS feed

0.12.0

6 release files

0.11.0

6 release files

0.10.0

6 release files

0.9.0

6 release files

0.8.0

6 release files

0.7.0

6 release files

0.6.0

6 release files

0.5.0

6 release files

0.4.1

6 release files

This release

0.4.0 This release

6 release files

0.3.0

6 release files

0.2.0

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page