Advertisement
microrobotics

Huabang DDS228 Modbus Parser

Apr 12th, 2024 (edited)
602
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.27 KB | None | 0 0
  1. import serial
  2. import struct
  3. import time
  4.  
  5. # CRC-16 calculation function (Modbus flavor)
  6. def crc16(data):
  7.     crc = 0xFFFF
  8.     for byte in data:
  9.         crc ^= byte
  10.         for _ in range(8):
  11.             if crc & 0x0001:
  12.                 crc = (crc >> 1) ^ 0xA001
  13.             else:
  14.                 crc >>= 1
  15.     return crc
  16.  
  17. # Function to convert IEEE 754 floating point bytes to a float value
  18. def ieee754_to_float(bytes):
  19.     return struct.unpack('>f', struct.pack('>L', bytes))[0]
  20.  
  21. # Function to parse a Modbus response
  22. def parse_modbus_response(response_bytes):
  23.     # Slave ID
  24.     slave_id = response_bytes[0]
  25.    
  26.     # Function code
  27.     function_code = response_bytes[1]
  28.    
  29.     # Byte count
  30.     byte_count = response_bytes[2]
  31.    
  32.     # Data bytes
  33.     data_bytes = response_bytes[3:-2]
  34.    
  35.     # CRC bytes
  36.     crc_bytes = response_bytes[-2:]
  37.    
  38.     # Calculate CRC and check
  39.     calculated_crc = crc16(response_bytes[:-2])
  40.     received_crc = int.from_bytes(crc_bytes, byteorder='little')
  41.     if calculated_crc != received_crc:
  42.         print("CRC error in the response")
  43.         return
  44.    
  45.     # Check for error response
  46.     if function_code == 0x83:
  47.         exception_code = data_bytes[0]
  48.         print(f"Error response: Exception code {exception_code}")
  49.         return
  50.    
  51.     # Parse data based on expected data type
  52.     if function_code == 0x03:  # Read holding registers
  53.         if byte_count == 4:  # 32-bit float
  54.             value = ieee754_to_float(int.from_bytes(data_bytes, byteorder='big'))
  55.             print(f"32-bit float value: {value}")
  56.         elif byte_count == 2:  # 16-bit unsigned int
  57.             value = int.from_bytes(data_bytes, byteorder='big')
  58.             print(f"16-bit unsigned int value: {value}")
  59.         else:
  60.             print("Unexpected byte count for read holding registers response")
  61.     else:
  62.         print(f"Unhandled function code: {function_code}")
  63.  
  64. # Function to construct a Modbus request
  65. def construct_modbus_request(register_choice, slave_id=0x01):
  66.     requests = {
  67.         1: bytes.fromhex(f'01 03 00 00 00 02'),  # Total active energy (kWh)
  68.         2: bytes.fromhex(f'01 03 00 0A 00 02'),  # Reverse active energy (kWh)
  69.         3: bytes.fromhex(f'01 03 00 64 00 02'),  # Voltage (V)
  70.         4: bytes.fromhex(f'01 03 00 6A 00 02'),  # Current (A)
  71.         5: bytes.fromhex(f'01 03 00 76 00 02'),  # Active power (kW)
  72.         6: bytes.fromhex(f'01 03 00 8E 00 02'),  # Power factor
  73.         7: bytes.fromhex(f'01 03 00 90 00 02'),  # Grid frequency (Hz)
  74.     }
  75.    
  76.     if register_choice in requests:
  77.         request_bytes = requests[register_choice]
  78.         crc = crc16(request_bytes)
  79.         crc_bytes = crc.to_bytes(2, byteorder='little')
  80.         return request_bytes + crc_bytes
  81.     else:
  82.         print("Invalid choice, please try again.")
  83.         return None
  84.  
  85. # Main program
  86. # Get the COM port from the user
  87. com_port = input("Enter the COM port (e.g., COM1, /dev/ttyUSB0): ")
  88.  
  89. # Device ID is fixed to 26
  90. device_id = 26
  91.  
  92. # Open the serial port
  93. try:
  94.     ser = serial.Serial(com_port, baudrate=9600, bytesize=8, parity=serial.PARITY_EVEN, stopbits=1)
  95. except serial.SerialException as e:
  96.     print(f"Error opening serial port: {e}")
  97.     exit(1)
  98.  
  99. while True:
  100.     print("\nSelect the register you want to request:")
  101.     print("1. Total active energy (kWh)")
  102.     print("2. Reverse active energy (kWh)")
  103.     print("3. Voltage (V)")
  104.     print("4. Current (A)")
  105.     print("5. Active power (kW)")
  106.     print("6. Power factor")
  107.     print("7. Grid frequency (Hz)")
  108.     print("0. Exit")
  109.    
  110.     choice = int(input("Enter your choice (0-7): "))
  111.    
  112.     if choice == 0:
  113.         break
  114.    
  115.     request_bytes = construct_modbus_request(choice, device_id)
  116.    
  117.     if request_bytes:
  118.         print(f"Modbus request: {request_bytes.hex()}")
  119.        
  120.         # Send the request over the serial port
  121.         ser.write(request_bytes)
  122.        
  123.         # Wait for the response
  124.         response_bytes = ser.read(size=9)
  125.        
  126.         if len(response_bytes) == 9:
  127.             parse_modbus_response(response_bytes)
  128.         else:
  129.             print("Invalid response length")
  130.            
  131.         # Add a delay to avoid flooding the meter with requests
  132.         time.sleep(0.1)
  133.  
  134. print("Exiting program...")
  135. ser.close()
Advertisement
Comments
  • microrobotics
    50 days
    # text 2.59 KB | 0 0
    1. Install Dependencies:
    2. Ensure that you have the pyserial library installed. You can install it using pip:
    3.  
    4. pip install pyserial
    5.  
    6. Connect the Energy Meter:
    7. 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).
    8. 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:
    9. Baud rate: 9600
    10. Data bits: 8
    11. Parity: Even
    12. Stop bits: 1
    13.  
    14. Configure the Code:
    15. Open the Python script in a text editor or an IDE.
    16. 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:
    17.  
    18. def construct_modbus_request(register_choice, slave_id=0x01):
    19.  
    20. Replace 0x01 with the hexadecimal representation of your energy meter's slave ID.
    21.  
    22. Run the Code:
    23. Save the Python script.
    24. Open a terminal or command prompt and navigate to the directory containing the Python script.
    25. Run the script by executing the following command:
    26.  
    27. python script_name.py
    28.  
    29. Replace script_name.py with the actual name of your Python script file.
    30.  
    31. Interact with the Script:
    32. 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.
    33. The script will then display a menu with options to request different registers from the energy meter:
    34.  
    35. Select the register you want to request:
    36. 1. Total active energy (kWh)
    37. 2. Reverse active energy (kWh)
    38. 3. Voltage (V)
    39. 4. Current (A)
    40. 5. Active power (kW)
    41. 6. Power factor
    42. 7. Grid frequency (Hz)
    43. 0. Exit
    44.  
    45. Enter the corresponding number (1-7) for the register you want to read, and press Enter.
    46.  
    47. The script will send the Modbus request to the energy meter and display the received response value.
    48.  
    49. 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.
    50. To exit the script, enter 0 and press Enter.
    51.  
    52. Interpreting the Output:
    53. The script will print the received value from the energy meter for the requested register.
    54. 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.
    55. For 16-bit unsigned integer values (if any), the script will display the integer value directly.
Add Comment
Please, Sign In to add comment
Advertisement