HandsOn

This section provides setup instructions for HART basic command tests.

First: You will need to have a HART device and a HART modem connected as below

Second: Below software will provide you a concept of how HART communication works. The execution result is provided first.

python hart1.py COM12
Connected to COM12
Type help for usage or cmd x directly: cmd 0
ffffffffff0280000082
Response: b'\xff\xff\xff\xff\x06\x80\x00\x0e\x00@\xfe&\x06\x05\x05\x03\xa3\x08\x00\x0e\x1e\x979\xaa'
Processed input: cmd 0
Type help for usage or cmd x directly: cmd 1
ffffffffff82a6060e1e970100a4
Response: ff 0xff 0xff 0xff 0xff 0x86 0xa6 0x06 0x0e 0x1e 0x97 0x01 0x07 0x00 0x40 0x06 0x3d 0x58 0x47 0xce 0x0d 0xaa
Processed input: cmd 1
Type help for usage or cmd x directly: cmd 2
ffffffffff82a6060e1e970200a7
Response: ff 0xff 0xff 0xff 0x86 0xa6 0x06 0x0e 0x1e 0x97 0x02 0x0a 0x00 0x40 0x40 0x81 0x4f 0xe1 0x3e 0x83 0x33 0x34 0x3c 0xaa
Processed input: cmd 2
Type help for usage or cmd x directly:
Input cancelled by user.

Command 0 is the command to be executed first, this command is guaranteed to be replied from the device. The response will be the base for further communication because further command requires manufacturer information which is the result of command 0.

0280000082 is the bytes sent out. 02: Master to slave, 80: Polling address 0 00: Command 0 00: Count 82: Checksum

x06\x80\x00\x0e\x00@\xfe&\x06\x05\x05\x03\xa3\x08\x00\x0e\x1e\x979 is the response from device, please check the meaning of each byte online

After command 0, we get manufacturer ID, device identification and device type. Those will be the base for further commands. As seen above for command 1.

82a6060e1e970100a4 a6: Manufacturing ID 06: Device type 0e1e97: Device identification number

Code below is tested on a rosemount device, user can open a cmd window and type “python hart1.py COM12”, if you don’t have python on your PC, windows will prompt you to install python from windows market place. If you don’t have serial module installed. You can type “pip install pySerial”

'''
Created on Dec 15, 2025

'''

#!/usr/bin/env python3
import sys
import serial.tools.list_ports
from typing import Union
import math

def print_help():
    print("address = 12 - this will set target address to be 12")
    print("cmd 0 - this will issue command 0 to target")
    print("cmd 3 - this will issue command 3 to target")
    print("cmd 33 - this will issue command 33 to target")
    print("cmd 19 234 - this will issue command 19 to write final addesmbly number 234")

def calculate_checksum(command: Union[int, bytes]) -> bytes:
    if type(command) == int:
        command = command.to_bytes(64, "big")  # type: ignore
    lrc = 0
    for byte in command:  # type: ignore
        lrc ^= byte
    out = lrc.to_bytes(1, "big")
    return out

def calculate_long_address(manufacturer_id: int, manufacturer_device_type: int, device_id: bytes):
    out = int.from_bytes(device_id, "big")
    out |= manufacturer_device_type << 24
    out |= (manufacturer_id & 0x3f) << 32
    return out.to_bytes(5, "big")

def pack_command(address, command_id, bIsLong=True, data=None):
    if type(address) == bytes:
        address = int.from_bytes(address, "big")
    if type(command_id) == int:
        command_id = command_id.to_bytes(1, "big")
    command = b"\xFF\xFF\xFF\xFF\xFF"  # preamble
    if (bIsLong == True):
        command += b"\x82"  # start charachter
        command += (549755813888 | address).to_bytes(5, "big")
    else:
        command += b"\x02"  # start charachter
        command += (address|0x80).to_bytes(1, "big")
    
    command += command_id
    if data is None:
        command += b"\x00"  # byte count
    else:
        command += len(data).to_bytes(1, "big")  # byte count
        command += data  # data
    command += calculate_checksum(command[5:])
    print(command.hex())
    return command

def pack_ascii(string: Union[str, bytes]) -> bytes:
    if type(string) == str:
        chars = [c.encode() for c in string]  # type: ignore
    else:
        chars = [c for c in string]  # type: ignore
    out = 0
    for i, c in zip(range(8), [ord(c) & 0b0011_1111 for c in chars][::-1]):
        out |= c << (i * 6)
    return out.to_bytes(math.ceil((len(string) * 6) / 8), "big")

def main():
    # sys.argv is a list of command-line arguments
    # sys.argv[0] is the script name
    argc = len(sys.argv)  # Number of arguments (like argc in C)
    argv = sys.argv       # List of arguments (like argv in C)

    if (argc == 1):
        print("usage: python script.py ""COMxx""")
        # List all available serial ports
        ports = [port.device for port in serial.tools.list_ports.comports()]
        print("Available Serial Ports:", ports)
        exit(0)

    # with serial.Serial(port=argv[1], baudrate=115200, parity=serial.PARITY_ODD, timeout=1) as ser:
    # with serial.Serial(port=argv[1], baudrate=115200, timeout=1) as ser:
    ser = serial.Serial(
        port=argv[1], # Replace with your port (e.g., '/dev/ttyUSB0' on Linux)
        baudrate=1200,
        parity=serial.PARITY_ODD,
        stopbits=serial.STOPBITS_ONE,
        bytesize=serial.EIGHTBITS,
        timeout=1 # Optional: Set a timeout for reading
        )
    print(f"Connected to {ser.name}")
        #hex_data = b'\x41\x42\x43\x44\x48\x65\x6C\x6C\x6F'
        #ser.write(hex_data)

    target_address = 0
    while True:
        try:
            user_input = input("Type help for usage or cmd x directly: ").strip()  # Remove leading/trailing spaces
            inputstr = user_input.split(" ")
            #num_items = len(inputstr)
            #print(f"split is {inputstr}")
            #print(f"number of items {num_items}")
            if(inputstr[0]=="help"):
                print_help()
            elif(inputstr[0]=="cmd" and inputstr[1]=="0"):
                cmd = pack_command(int(target_address), int(inputstr[1]), False)
                ser.write(cmd)
                response = ser.read(100) # Adjust the number of bytes to read as needed
                byte_array = bytearray(100)
                byte_array[0:len(response)] = response
                hex_string = ' 0x'.join(format(x, '02x') for x in byte_array)
                #print("Response:", hex_string)
                print("Response:", response)
                position = byte_array.find(b'\x0e\x00')
                if (position != -1):
                    vendor_id = byte_array[position+4]
                    device_type = byte_array[position+5]
                    device_id = bytes(byte_array[position+12:position+15])
                    long_addr =  calculate_long_address(vendor_id, device_type, device_id)
            elif(inputstr[0]=="cmd" and inputstr[1]!="0" and len(inputstr)==2):
                cmd = pack_command(long_addr, int(inputstr[1]))
                ser.write(cmd)
                response = ser.read(100) # Adjust the number of bytes to read as needed
                hex_string = ' 0x'.join(format(x, '02x') for x in response)
                print("Response:", hex_string)
            elif(inputstr[0]=="cmd" and inputstr[1]!="0" and len(inputstr)>=2):
                byte_data = inputstr[2].encode("utf-8")
                cmd = pack_command(long_addr, int(inputstr[1]), True, byte_data)
                ser.write(cmd)
                response = ser.read(100) # Adjust the number of bytes to read as needed
                hex_string = ' 0x'.join(format(x, '02x') for x in response)
                print("Response:", hex_string)
                #print("Response:", response)
                
            elif(inputstr[0]=="address" and inputstr[1]=="="):
                target_address = inputstr[2]
                if (not target_address.isdigit()):
                    print("address is not a digital number")
                print(f"addr {target_address}")
            
            if not user_input:
                print("You entered an empty line.")
            else:
                print(f"Processed input: {user_input}")

        except EOFError:
            print("No input received (EOF).")
        except KeyboardInterrupt:
            print("\nInput cancelled by user.")
            exit(1)


if __name__ == '__main__':
    main()