hartip-py
Pure-Python HART-IP client library. Server functionality is not included.
Supports HART-IP v1 (plaintext, TCP/UDP) and v2 (TLS, Direct PDU, Audit Log). DTLS is not supported — Python's ssl module does not provide DTLS.
Features
- HART-IP v1: UDP (default) and TCP on port 5094, with session initiate/close, keep-alive and sequence numbers
- HART-IP v2 TLS: STARTTLS upgrade (HCF_SPEC-085) or TLS-first, TLS-PSK, CA-verified and mutual TLS, certificate validation callbacks, custom
SSLContext; TLS 1.2 minimum - v2 messages: Direct PDU (several commands per request) and Read Audit Log
- Command parsers: 42 commands in the registry (
parse_command), including extended commands above 253 via Command 31 - Robust parsing: byte count and checksum validation, delayed-response retries, communication-error decoding
- Lookup tables: 178 engineering unit codes, 551 manufacturer IDs, status and classification decoders
- High-level API:
Devicecaches identity (Commands 0/13/20) and reads process values on access - Multidrop: address each device on a loop by poll address or unique address
- Declarative parsing: wire structs via the construct library
- Typed: ships
py.typed
Requirements
- Python 3.10+
- Python 3.13+ for TLS-PSK (
sslPSK callbacks) construct>=2.10- No root privileges: HART-IP is plain UDP/TCP
Installation
pip install hartip-py
Runnable scripts live in examples/.
Quick start
from hartip import HARTIPClient, parse_cmd0, parse_cmd1
# v1 over UDP (default)
with HARTIPClient("192.168.1.100") as client:
resp = client.read_unique_id()
info = parse_cmd0(resp.payload)
print(info.manufacturer_name, info.device_id)
resp = client.read_primary_variable()
pv = parse_cmd1(resp.payload)
print(f"{pv.value} (unit {pv.unit_code})")
TLS (HART-IP v2)
from hartip import HARTIPClient, HARTIP_V2_PSK_CIPHERS
# PSK authentication
with HARTIPClient(
"192.168.1.100",
protocol="tcp",
version=2,
psk_identity="client1",
psk_key=b"\x00" * 16,
ciphers=HARTIP_V2_PSK_CIPHERS,
) as client:
resp = client.read_unique_id()
# CA-verified + mutual TLS
with HARTIPClient(
"192.168.1.100",
protocol="tcp",
version=2,
ca_certs="/path/to/ca.pem",
certfile="/path/to/client.pem",
keyfile="/path/to/client.key",
) as client:
resp = client.read_unique_id()
# Custom certificate validation callback
def pin_cert(cert_dict):
subject = dict(x[0] for x in cert_dict.get("subject", ()))
return subject.get("commonName") == "my-device.local"
with HARTIPClient(
"192.168.1.100",
protocol="tcp",
version=2,
cert_validator=pin_cert,
) as client:
resp = client.read_unique_id()
# Full custom SSLContext
import ssl
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.load_verify_locations("/path/to/ca.pem")
with HARTIPClient("192.168.1.100", protocol="tcp", version=2, ssl_context=ctx) as client:
resp = client.read_unique_id()
Publish (burst) frames
Devices in burst mode, and gateways with an active subscription, send unsolicited Publish frames on the client's session. The client skips them while it waits for the reply to a request. To receive them, pass a callback:
from hartip import HARTIPClient
def on_publish(resp):
print(resp.command_number, resp.parsed)
with HARTIPClient("192.168.1.100", protocol="tcp", on_publish=on_publish) as client:
client.read_primary_variable()
Publish frames are only read while a request is outstanding; there is no background reader. Frames that fail validation are dropped.
Error handling
from hartip import HARTIPClient
from hartip.exceptions import (
HARTIPConnectionError,
HARTIPTimeoutError,
HARTIPTLSError,
HARTResponseError,
)
with HARTIPClient("192.168.1.100") as client:
resp = client.read_unique_id()
# Check manually
if not resp.success:
print(resp.error_message)
# Or raise
resp.raise_for_error() # raises HARTResponseError on failure
Exception hierarchy:
HARTError
├── HARTIPError
│ ├── HARTIPTimeoutError
│ ├── HARTIPConnectionError
│ │ └── HARTIPTLSError
│ └── HARTIPStatusError
└── HARTProtocolError
├── HARTChecksumError
├── HARTFrameError (also a ValueError)
├── HARTResponseError
└── HARTCommunicationError
Convenience methods
| Method | HART Command |
|---|---|
read_unique_id() |
0 |
read_primary_variable() |
1 |
read_current_and_percent() |
2 |
read_dynamic_variables() |
3 |
read_tag_descriptor_date() |
13 |
read_output_info() |
15 |
read_long_tag() |
20 |
read_additional_status() |
48 |
send_command(cmd, ...) |
any |
send_direct_pdu(commands) |
v2 Direct PDU |
read_audit_log() |
v2 Audit Log |
Commands above 253 go out wrapped in Command 31. The reply is unwrapped for you:
command_number reports the expanded number (e.g. 534), payload no longer
starts with its 2 bytes, parsed uses the parser for 534, and pdu.command
stays 31. A reply that carries a different expanded number raises HARTFrameError.
Testing
# Unit tests (no server needed)
pytest
# Integration tests (requires HART-IP server)
pytest -m integration
Support
If you find this project useful, consider supporting development:
License
Dual-licensed under GPL-3.0 and a commercial license.
Free for open source use under GPL-3.0. If you want to use hartip-py in proprietary products without GPL obligations, a commercial license is available — see LICENSE-COMMERCIAL.md for details.
References
- HART specifications (FieldComm Group)
- FieldComm hipserver (reference HART-IP server, used by the integration tests)
- Wireshark HART-IP dissector
- CISA ICSNPP HART-IP parser
- construct library
Trademarks
All marks belong to their owners; used nominatively to describe interoperability; no endorsement implied.
Metadata
Release files for hartip-py 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| hartip_py-0.4.0.tar.gz | 130.4 kB | With provenance | Details for hartip_py-0.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| hartip_py-0.4.0-py3-none-any.whl | Python 3 | none | any | Details for hartip_py-0.4.0-py3-none-any.whl |
Total release size: 208.6 kB
Release files / hartip_py-0.4.0.tar.gz
| Download URL | hartip_py-0.4.0.tar.gz |
|---|---|
| Size | 130.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
86268aa0bafc824d57a73f46561232345b5a8709154ab0fcfdb27c505206444c
|
|
BLAKE2b-256 checksum How to use checksums |
1edf8c6eb39db917fc6795a0e4d9f4410108b03bed7290072845bc342583b352
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 19, 2026.
Transparency logRelease files / hartip_py-0.4.0-py3-none-any.whl
| Download URL | hartip_py-0.4.0-py3-none-any.whl |
|---|---|
| Size | 78.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7954619d095e8bf44d6ba2b1611fd14f0de21e8cb732a3159cb8fc6a02086ce8
|
|
BLAKE2b-256 checksum How to use checksums |
c5648f73cfa86dd17aea13d5de6c1fd450a60ca5275588a3c946e86144311242
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 19, 2026.
Transparency logSecurity for hartip-py 0.4.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for hartip_py-0.4.0.tar.gz
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 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.