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.1

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.1
File Size Uploaded
hilspy-0.4.1.tar.gz 67.0 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for hilspy 0.4.1
File
hilspy-0.4.1-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
hilspy-0.4.1-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.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
hilspy-0.4.1-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
hilspy-0.4.1-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.1.tar.gz

Download URL hilspy-0.4.1.tar.gz
Size 67.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a77a68fd07b9de9303b33f1f6a46a8ca7cad547463a79bde2dbf5bb64d713651
BLAKE2b-256 checksum
How to use checksums
a5dad8a224fc00778c862fbe7ad21d5689ff43f75f632cf8f9d05e2cd6d16973
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.1-cp310-abi3-win_amd64.whl

Download URL hilspy-0.4.1-cp310-abi3-win_amd64.whl
Size 794.9 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
8cddfcba29cb2cef883734f73f7894f09d52b595367b3a2ab32a768cfb055bd9
BLAKE2b-256 checksum
How to use checksums
31c88e10ab42eb0866a7cbfa77efb9a0f496e173e94d153338c056b86f81a338
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.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL hilspy-0.4.1-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
b05e9782978871bbdd069f3822f23405071ecb73964af9b74794856cd5629782
BLAKE2b-256 checksum
How to use checksums
c044fb59c6877dc43d83d29ee533c121ba40989f7d3c47b6dc19d51a6737ea73
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.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL hilspy-0.4.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 889.9 kB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
a72abfcc223dfe191b2c3e086707ca71a0827e09c5351e20c76294b3ab47c52f
BLAKE2b-256 checksum
How to use checksums
a93574c60b2ad569935df35993950e8e24758db77b179912a6d7db78fc09d894
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.1-cp310-abi3-macosx_11_0_arm64.whl

Download URL hilspy-0.4.1-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
829ab6071af2c17f0ab0b9db08d27812408af42a429fc5dc5c62a88c0a48936c
BLAKE2b-256 checksum
How to use checksums
e8943fd129bc13e8fb10855d800a7b665610bf01fc816a7e49451483f7e57af6
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.1-cp310-abi3-macosx_10_12_x86_64.whl

Download URL hilspy-0.4.1-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
64ea58387dafedea15fca4420e6c9302da04fd041e7a07ec144c154d33089df7
BLAKE2b-256 checksum
How to use checksums
5ff3ae096b613ae8758f53daee3a20de7a091a3b717f92c03de4c4378d90dcf6
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

This release

0.4.1 This release

6 release files

0.4.0

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