Skip to content

Getting started

Ralf Steinert edited this page Nov 9, 2026 · 3 revisions

Install

Python 3.10 or newer:

pip install hart-sim
hart-sim --version
hart-sim 0.1.2

Run a bench

Save this as plant.yaml:

devices:
  - {profile: pressure, polling_address: 1, device_id: 0x000001, tag: PT-101}
  - {profile: temperature, polling_address: 2, device_id: 0x000002, tag: TT-201}
hart-sim serve --bench plant.yaml
hart-sim: 2 devices on HART-IP 127.0.0.1:5094 (tcp+udp), control on 127.0.0.1:5095

Leave it running and use a second terminal. For a single device, hart-sim serve pressure is enough.

Or with Docker

The image runs the same two devices without a local Python, with no bench file needed:

docker run --rm --name sim -p 5094:5094 -p 5094:5094/udp -p 127.0.0.1:5095:5095 ghcr.io/steitec/hart-sim

The commands below work against it as they are if hart-sim is installed. Without it, run them inside the container, for example docker exec sim hart-sim poll. The control channel has no authentication, hence 127.0.0.1: in front of it. For CI, see Testing in CI.

Talk to it

$ hart-sim poll
addr 1  PT-101  pressure-transmitter  id 0x000001
addr 2  TT-201  temperature-transmitter  id 0x000002
$ hart-sim cmd --tag PT-101 3
loop 10.70 mA | PV 418.9 kPa | SV 23.5 degC

hart-sim poll finds the devices with commands 0 and 13, and hart-sim cmd sends one command. The output is decoded for commands 1, 2, 3, 13, 15 and 48, and every other command prints the response code, the status and the data as hex. Add --udp for UDP.

Break it

$ hart-sim inject PT-101 sensor_failure
$ hart-sim cmd --tag PT-101 3
status: device malfunction, more status available, PV out of limits | loop 21.00 mA | PV 416.9 kPa (bad) | SV 23.5 degC
$ hart-sim clear PT-101

The loop current goes to the NE 43 high alarm of 21 mA, and command 48 has the details.

From Python

The package has a small HART-IP master, which also works on its own against real gateways:

from hart_sim.client import HartIPClient

with HartIPClient("127.0.0.1") as client:        # transport="udp" for UDP
    device = client.find("PT-101")                # command 11, broadcast by tag
    variables = device.read_variables()           # command 3
    print(f"{variables.pv:.1f} kPa, {variables.current_ma:.2f} mA")
    print(device.write_range(0, 600))             # command 35; 0 means accepted

Next: Device profiles, Testing in CI or Fault injection recipes.

Clone this wiki locally