# hart-protocol
[![PyPI](https://img.shields.io/pypi/v/hart-protocol)](https://pypi.org/project/hart-protocol)
[![Conda](https://img.shields.io/conda/vn/conda-forge/hart-protocol)](https://anaconda.org/conda-forge/hart-protocol)
[![black](https://img.shields.io/badge/code--style-black-black)](https://black.readthedocs.io/)
[![ver](https://img.shields.io/badge/calver-YYYY.M.MICRO-blue)](https://calver.org/)
[![log](https://img.shields.io/badge/change-log-informational)](https://github.com/yaq-project/hart-protocol/-/blob/main/CHANGELOG.md)
A sans I/O Python implementation of the [Highway Adressable Remote Transducer Protocol](https://en.wikipedia.org/wiki/Highway_Addressable_Remote_Transducer_Protocol).
## Introduction
This Python package contains tooling for encoding and decoding bytestrings for communication with HART peripherals.
HART has been implemented using a variety of transport layers---Bell 202, RS485, Ethernet, etc.
In persuit of simplicity and reusability, this package does not contain any interface capabilities.
Use something like [pySerial](https://pyserial.readthedocs.io) for transport.
Read the [sans I/O manifesto](https://sans-io.readthedocs.io/) for more motivation regarding this design pattern.
Briefly, HART is an open protocol for industrial automation supported by multiple device manufacturers.
HART has a concept of "address", so that many peripherals can share the same communication channel.
HART has limited support for multiple controllers, and generic handheld controllers exist.
HART peripherals respond to numbered commands, which can be thought of as primative [remote procedure calls](https://en.wikipedia.org/wiki/Remote_procedure_call).
The standard specifies a number of universal commands which should be supported by any peripheral, and there are also so-called "common" commands which many peripherals implement.
It's strongly recommended that you check the documentation of your own peripheral---implementations may be inconsistent.
In addition to universal and common commands, it's likely that your peripheral implements many device-specific commands.
This package aims to have complete and accurate support for all universal and common commands.
In addition, this package has tooling for packing and unpacking generic command data for device-specific commands.
This package is intentionally simple and narrowly scoped.
There is no documentation beyond this README.
Please open an issue or PR to the GitHub repository if you find any errors or missing functionality.
## Sending Commands
The following functions return bytestrings that can be fed to your transport layer.
Universal Commands
| command | function |
| ------- | ----------------------------------------------------------- |
| 0 | `read_unique_identifier(address)` |
| 1 | `read_primary_variable(address)` |
| 2 | `read_loop_current_and_percent(address)` |
| 3 | `read_dynamic_variables_and_loop_current(address)` |
| 6 | `write_polling_address(address, new_short_address)` |
| 11 | `read_unique_identifier_associated_with_tag(tag)` |
| 12 | `read_message(address)` |
| 13 | `read_tag_descriptor_date(address)` |
| 14 | `read_primary_variable_information(address)` |
| 15 | `read_output_information(address)` |
| 16 | `read_final_assembly_number(address)` |
| 17 | `write_message(address, message)` |
| 18 | `write_tag_descriptor_date(address, tag, descriptor, date)` |
| 19 | `write_final_assembly_number(address, number)` |
Common-Practice Commands
| command | function |
| ------- | -------------------------------------------------------- |
| 37 | `set_primary_variable_lower_range_value(address, value)` |
| 38 | `reset_configuration_changed_flag(address)` |
| 42 | `perform_master_reset(address)` |
| 48 | `read_additional_transmitter_status(address)` |
| 50 | `read_dynamic_variable_assignments(address)` |
| 59 | `write_number_of_response_preambles(address, number)` |
| 66 | `toggle_analog_output_mode(address)` |
| 67 | `trim_analog_output_zero(address)` |
| 68 | `trim_analog_output_span(address)` |
| 123 | `select_baud_rate(address, rate)` |
Arbitrary additional command bytestrings can also be generated as shown below.
This is a device-specific command for Brooks GF40 Mass Flow Controllers, which takes an IEE-754 floating point number as well as a unique code.
```python
import struct
import hart_protocol
code = 0
value = 32.1
data = struct.pack(">Bf", code, value)
command = hart_protocol.pack_command(address=123, command_id=236, data=data)
```
## Parsing Responses
All responses are parsed into named tuples.
Every single response will have the following keys.
Generic Response
| key | value |
| --------------- | --------- |
| `address` | `<int>` |
| `bytecount` | `<int>` |
| `command` | `<int>` |
| `command_name` | `<str>` |
| `data` | `<bytes>` |
| `full_response` | `<bytes>` |
| `device_status` | `<bytes>` |
| `response_code` | `<bytes>` |
You can parse the raw `data` according to the particulars of your peripheral.
Certain standard responses are parsed further as shown below.
Response 0
| key | value |
| --------------------------------------------- | -------------------------- |
| `command_name` | `"read_unique_identifier"` |
| `command` | `0` |
| `device_id` | `<bytes>` |
| `hardware_revision_level` | `<int>` |
| `manufacturer_device_type` | `<bytes>` |
| `manufacturer_id` | `<int>` |
| `number_response_preamble_charachters` | `<int>` |
| `software_revision_level` | `<int>` |
| `transmitter_specific_command_revision_level` | `<int>` |
| `universal_command_revision_level` | `<int>` |
Response 1
| key | value |
| ------------------ | ------------------------- |
| `command_name` | `"read_primary_variable"` |
| `command` | `1` |
| `primary_variable` | `<float>` |
Response 11
| key | value |
| --------------------------------------------- | -------------------------- |
| `command_name` | `"read_unique_identifier"` |
| `command` | `11` |
| `device_id` | `<bytes>` |
| `hardware_revision_level` | `<int>` |
| `manufacturer_device_type` | `<bytes>` |
| `manufacturer_id` | `<int>` |
| `number_response_preamble_charachters` | `<int>` |
| `software_revision_level` | `<int>` |
| `transmitter_specific_command_revision_level` | `<int>` |
| `universal_command_revision_level` | `<int>` |
Many other universal and common responses are also parsed..., give it a try!
## Integration Example
```python
>>> import hart_protocol
>>> import serial
>>>
>>> port = serial.Serial("/dev/ttyUSB0", 19200, timeout=0.1)
>>> port.parity = "O"
>>> port.stopbits = 1
>>> tag = hart_protocol.tools.pack_ascii("06C22300517"[-8:])
>>> port.write(hart_protocol.universal.read_unique_identifier_associated_with_tag(tag))
>>>
>>> unpacker = hart_protocol.Unpacker(port)
>>> for msg in unpacker:
... print(msg)
...
>>>
```
## Maintainers
- [Blaise Thompson](https://github.com/untzag)
Raw data
{
"_id": null,
"home_page": "https://github.com/yaq-project/hart-protocol",
"name": "hart-protocol",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": null,
"keywords": null,
"author": "Blaise Thompson",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/7f/62/e9c9c9c391fbde0ac198c7f0c522bb2f9853e55703ed5bc198e11df149ad/hart_protocol-2023.6.0.tar.gz",
"platform": null,
"description": "# hart-protocol\n\n[![PyPI](https://img.shields.io/pypi/v/hart-protocol)](https://pypi.org/project/hart-protocol)\n[![Conda](https://img.shields.io/conda/vn/conda-forge/hart-protocol)](https://anaconda.org/conda-forge/hart-protocol)\n[![black](https://img.shields.io/badge/code--style-black-black)](https://black.readthedocs.io/)\n[![ver](https://img.shields.io/badge/calver-YYYY.M.MICRO-blue)](https://calver.org/)\n[![log](https://img.shields.io/badge/change-log-informational)](https://github.com/yaq-project/hart-protocol/-/blob/main/CHANGELOG.md)\n\nA sans I/O Python implementation of the [Highway Adressable Remote Transducer Protocol](https://en.wikipedia.org/wiki/Highway_Addressable_Remote_Transducer_Protocol).\n\n## Introduction\n\nThis Python package contains tooling for encoding and decoding bytestrings for communication with HART peripherals.\nHART has been implemented using a variety of transport layers---Bell 202, RS485, Ethernet, etc.\nIn persuit of simplicity and reusability, this package does not contain any interface capabilities.\nUse something like [pySerial](https://pyserial.readthedocs.io) for transport.\nRead the [sans I/O manifesto](https://sans-io.readthedocs.io/) for more motivation regarding this design pattern.\n\nBriefly, HART is an open protocol for industrial automation supported by multiple device manufacturers.\nHART has a concept of \"address\", so that many peripherals can share the same communication channel.\nHART has limited support for multiple controllers, and generic handheld controllers exist.\nHART peripherals respond to numbered commands, which can be thought of as primative [remote procedure calls](https://en.wikipedia.org/wiki/Remote_procedure_call).\nThe standard specifies a number of universal commands which should be supported by any peripheral, and there are also so-called \"common\" commands which many peripherals implement.\nIt's strongly recommended that you check the documentation of your own peripheral---implementations may be inconsistent.\nIn addition to universal and common commands, it's likely that your peripheral implements many device-specific commands.\n\nThis package aims to have complete and accurate support for all universal and common commands.\nIn addition, this package has tooling for packing and unpacking generic command data for device-specific commands.\nThis package is intentionally simple and narrowly scoped.\nThere is no documentation beyond this README.\nPlease open an issue or PR to the GitHub repository if you find any errors or missing functionality.\n\n## Sending Commands\n\nThe following functions return bytestrings that can be fed to your transport layer.\n\nUniversal Commands\n| command | function |\n| ------- | ----------------------------------------------------------- |\n| 0 | `read_unique_identifier(address)` |\n| 1 | `read_primary_variable(address)` |\n| 2 | `read_loop_current_and_percent(address)` |\n| 3 | `read_dynamic_variables_and_loop_current(address)` |\n| 6 | `write_polling_address(address, new_short_address)` |\n| 11 | `read_unique_identifier_associated_with_tag(tag)` |\n| 12 | `read_message(address)` |\n| 13 | `read_tag_descriptor_date(address)` |\n| 14 | `read_primary_variable_information(address)` |\n| 15 | `read_output_information(address)` |\n| 16 | `read_final_assembly_number(address)` |\n| 17 | `write_message(address, message)` |\n| 18 | `write_tag_descriptor_date(address, tag, descriptor, date)` |\n| 19 | `write_final_assembly_number(address, number)` |\n\nCommon-Practice Commands\n| command | function |\n| ------- | -------------------------------------------------------- |\n| 37 | `set_primary_variable_lower_range_value(address, value)` |\n| 38 | `reset_configuration_changed_flag(address)` |\n| 42 | `perform_master_reset(address)` |\n| 48 | `read_additional_transmitter_status(address)` |\n| 50 | `read_dynamic_variable_assignments(address)` |\n| 59 | `write_number_of_response_preambles(address, number)` |\n| 66 | `toggle_analog_output_mode(address)` |\n| 67 | `trim_analog_output_zero(address)` |\n| 68 | `trim_analog_output_span(address)` |\n| 123 | `select_baud_rate(address, rate)` |\n\nArbitrary additional command bytestrings can also be generated as shown below.\nThis is a device-specific command for Brooks GF40 Mass Flow Controllers, which takes an IEE-754 floating point number as well as a unique code.\n\n```python\nimport struct\nimport hart_protocol\ncode = 0\nvalue = 32.1\ndata = struct.pack(\">Bf\", code, value)\ncommand = hart_protocol.pack_command(address=123, command_id=236, data=data)\n```\n\n## Parsing Responses\n\nAll responses are parsed into named tuples.\nEvery single response will have the following keys.\n\nGeneric Response\n| key | value |\n| --------------- | --------- |\n| `address` | `<int>` |\n| `bytecount` | `<int>` |\n| `command` | `<int>` |\n| `command_name` | `<str>` |\n| `data` | `<bytes>` |\n| `full_response` | `<bytes>` |\n| `device_status` | `<bytes>` |\n| `response_code` | `<bytes>` |\n\nYou can parse the raw `data` according to the particulars of your peripheral.\nCertain standard responses are parsed further as shown below.\n\nResponse 0\n| key | value |\n| --------------------------------------------- | -------------------------- |\n| `command_name` | `\"read_unique_identifier\"` |\n| `command` | `0` |\n| `device_id` | `<bytes>` |\n| `hardware_revision_level` | `<int>` |\n| `manufacturer_device_type` | `<bytes>` |\n| `manufacturer_id` | `<int>` |\n| `number_response_preamble_charachters` | `<int>` |\n| `software_revision_level` | `<int>` |\n| `transmitter_specific_command_revision_level` | `<int>` |\n| `universal_command_revision_level` | `<int>` |\n\nResponse 1\n| key | value |\n| ------------------ | ------------------------- |\n| `command_name` | `\"read_primary_variable\"` |\n| `command` | `1` |\n| `primary_variable` | `<float>` |\n\nResponse 11\n| key | value |\n| --------------------------------------------- | -------------------------- |\n| `command_name` | `\"read_unique_identifier\"` |\n| `command` | `11` |\n| `device_id` | `<bytes>` |\n| `hardware_revision_level` | `<int>` |\n| `manufacturer_device_type` | `<bytes>` |\n| `manufacturer_id` | `<int>` |\n| `number_response_preamble_charachters` | `<int>` |\n| `software_revision_level` | `<int>` |\n| `transmitter_specific_command_revision_level` | `<int>` |\n| `universal_command_revision_level` | `<int>` |\n\nMany other universal and common responses are also parsed..., give it a try!\n\n## Integration Example\n\n```python\n>>> import hart_protocol\n>>> import serial\n>>>\n>>> port = serial.Serial(\"/dev/ttyUSB0\", 19200, timeout=0.1)\n>>> port.parity = \"O\"\n>>> port.stopbits = 1\n>>> tag = hart_protocol.tools.pack_ascii(\"06C22300517\"[-8:])\n>>> port.write(hart_protocol.universal.read_unique_identifier_associated_with_tag(tag))\n>>>\n>>> unpacker = hart_protocol.Unpacker(port)\n>>> for msg in unpacker:\n... print(msg)\n...\n>>>\n```\n\n## Maintainers\n\n- [Blaise Thompson](https://github.com/untzag)\n",
"bugtrack_url": null,
"license": null,
"summary": "A sans-io python implementation of the Highway Addressable Remote Transducer Protocol.",
"version": "2023.6.0",
"project_urls": {
"Homepage": "https://github.com/yaq-project/hart-protocol",
"Issues": "https://github.com/yaq-project/hart-protocol/issues",
"Source": "https://github.com/yaq-project/hart-protocol"
},
"split_keywords": [],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "8f6c4ef3ba0cf146beec139d335bf863ff4bea6f41fe3cae0f056fdffd1f681a",
"md5": "a4623c8f50a5b1fa988c3a1fae81df30",
"sha256": "7027fd64f7260af63986abf18a3ee0b5564a0c86f7963b6afa079083a85abf78"
},
"downloads": -1,
"filename": "hart_protocol-2023.6.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a4623c8f50a5b1fa988c3a1fae81df30",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 10883,
"upload_time": "2023-06-02T20:02:53",
"upload_time_iso_8601": "2023-06-02T20:02:53.136690Z",
"url": "https://files.pythonhosted.org/packages/8f/6c/4ef3ba0cf146beec139d335bf863ff4bea6f41fe3cae0f056fdffd1f681a/hart_protocol-2023.6.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "7f62e9c9c9c391fbde0ac198c7f0c522bb2f9853e55703ed5bc198e11df149ad",
"md5": "cc200053f90a6b9b166444fd1484cc21",
"sha256": "1c97810c91cabb18947f7a0bb25749dce0984645c3647d8f294d2e2abc8f9c80"
},
"downloads": -1,
"filename": "hart_protocol-2023.6.0.tar.gz",
"has_sig": false,
"md5_digest": "cc200053f90a6b9b166444fd1484cc21",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 12230,
"upload_time": "2023-06-02T20:02:54",
"upload_time_iso_8601": "2023-06-02T20:02:54.678964Z",
"url": "https://files.pythonhosted.org/packages/7f/62/e9c9c9c391fbde0ac198c7f0c522bb2f9853e55703ed5bc198e11df149ad/hart_protocol-2023.6.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-06-02 20:02:54",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "yaq-project",
"github_project": "hart-protocol",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "hart-protocol"
}