microrobotics icon

Huabang DDS228 Modbus Parser

microrobotics | PRO | 06/10/24 08:17:08 AM UTC (Edited) | 0 ⭐ | 2179 👁️ | Never ⏰ | []
Python |

3.99 KB

|

None

|

0 👍

/

0 👎

import serial
import struct
import time
 
# CRC-16 calculation function (Modbus flavor)
def crc16(data):
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc
 
# Function to convert IEEE 754 floating point bytes to a float value
def ieee754_to_float(bytes):
    return struct.unpack('>f', struct.pack('>L', bytes))[0]
 
# Function to parse a Modbus response
def parse_modbus_response(response_bytes):
    # Slave ID
    slave_id = response_bytes[0]
   
    # Function code
    function_code = response_bytes[1]
   
    # Byte count
    byte_count = response_bytes[2]
   
    # Data bytes
    data_bytes = response_bytes[3:-2]
   
    # CRC bytes
    crc_bytes = response_bytes[-2:]
   
    # Calculate CRC and check
    calculated_crc = crc16(response_bytes[:-2])
    received_crc = int.from_bytes(crc_bytes, byteorder='little')
    if calculated_crc != received_crc:
        print("CRC error in the response")
        return
   
    # Check for error response
    if function_code == 0x83:
        exception_code = data_bytes[0]
        print(f"Error response: Exception code {exception_code}")
        return
   
    # Parse data based on expected data type
    if function_code == 0x03:  # Read holding registers
        if byte_count == 4:  # 32-bit float
            value = ieee754_to_float(int.from_bytes(data_bytes, byteorder='big'))
            return value
        elif byte_count == 2:  # 16-bit unsigned int
            value = int.from_bytes(data_bytes, byteorder='big')
            return value
        else:
            print("Unexpected byte count for read holding registers response")
            return None
    else:
        print(f"Unhandled function code: {function_code}")
        return None
 
# Function to construct a Modbus request
def construct_modbus_request(register_choice, slave_id=0x01):
    requests = {
        1: bytes.fromhex(f'01 03 00 00 00 02'),  # Total active energy (kWh)
        7: bytes.fromhex(f'01 03 00 90 00 02'),  # Grid frequency (Hz)
    }
   
    if register_choice in requests:
        request_bytes = requests[register_choice]
        crc = crc16(request_bytes)
        crc_bytes = crc.to_bytes(2, byteorder='little')
        return request_bytes + crc_bytes
    else:
        print("Invalid choice, please try again.")
        return None
 
# Main program
# Get the COM port from the user
com_port = input("Enter the COM port (e.g., COM1, /dev/ttyUSB0): ")
 
# Device ID is fixed to 26
device_id = 26
 
# Open the serial port
try:
    ser = serial.Serial(com_port, baudrate=9600, bytesize=8, parity=serial.PARITY_EVEN, stopbits=1)
except serial.SerialException as e:
    print(f"Error opening serial port: {e}")
    exit(1)
 
while True:
    print("\nSelect the register you want to request:")
    print("1. Total active energy (kWh)")
    print("7. Grid frequency (Hz)")
    print("0. Exit")
   
    choice = int(input("Enter your choice (0, 1, or 7): "))
   
    if choice == 0:
        break
   
    request_bytes = construct_modbus_request(choice, device_id)
   
    if request_bytes:
        print(f"Modbus request: {request_bytes.hex()}")
       
        # Send the request over the serial port
        ser.write(request_bytes)
       
        # Wait for the response
        response_bytes = ser.read(size=9)
       
        if len(response_bytes) == 9:
            value = parse_modbus_response(response_bytes)
            if value is not None:
                if choice == 1:
                    print(f"Total active energy (kWh): {value}")
                elif choice == 7:
                    print(f"Grid frequency (Hz): {value}")
        else:
            print("Invalid response length")
           
        # Add a delay to avoid flooding the meter with requests
        time.sleep(0.1)
 
print("Exiting program...")
ser.close()
 

Comments

  • microrobotics icon
    04/12/24 09:27:34 AM UTC
    text |

    0 B

    |

    0 👍

    /

    0 👎

    Install Dependencies:
    Ensure that you have the pyserial library installed. You can install it using pip:
     pip install pyserial
     Connect the Energy Meter:
    Connect the energy meter to the device (e.g., a computer or a Raspberry Pi) running the Python code using a serial cable (e.g., RS-485 or RS-232).
    Make sure the serial connection settings (baud rate, data bits, parity, and stop bits) on both the energy meter and the device match. According to the documentation, the settings should be:
    Baud rate: 9600
    Data bits: 8
    Parity: Even
    Stop bits: 1
     Configure the Code:
    Open the Python script in a text editor or an IDE.
    Locate the construct_modbus_request function and ensure that the slave ID (device address) is set correctly. In the provided code, the slave ID is set to 0x01 (decimal 1). If your energy meter has a different slave ID, update the line:
     def construct_modbus_request(register_choice, slave_id=0x01):
     Replace 0x01 with the hexadecimal representation of your energy meter's slave ID.
     Run the Code:
    Save the Python script.
    Open a terminal or command prompt and navigate to the directory containing the Python script.
    Run the script by executing the following command:
     python script_name.py
     Replace script_name.py with the actual name of your Python script file.
     Interact with the Script:
    The script will prompt you to enter the COM port (e.g., COM1, /dev/ttyUSB0) to which the energy meter is connected. Enter the appropriate COM port and press Enter.
    The script will then display a menu with options to request different registers from the energy meter:
     Select the register you want to request:
    1. Total active energy (kWh)
    2. Reverse active energy (kWh)
    3. Voltage (V)
    4. Current (A)
    5. Active power (kW)
    6. Power factor
    7. Grid frequency (Hz)
    0. Exit
     Enter the corresponding number (1-7) for the register you want to read, and press Enter.
     The script will send the Modbus request to the energy meter and display the received response value.
     After displaying the response, the script will wait for a short period (0.1 seconds) before prompting you for the next register choice. This delay is to avoid flooding the energy meter with requests.
    To exit the script, enter 0 and press Enter.
     Interpreting the Output:
    The script will print the received value from the energy meter for the requested register.
    For 32-bit float values (e.g., voltage, current, active power), the script will display the value with the appropriate number of decimal places based on the expected accuracy.
    For 16-bit unsigned integer values (if any), the script will display the integer value directly.