# Modbus 1. Test simulation program:[modsim32](https://www.win-tech.com/html/modsim32.htm) 2. Python library used: https://github.com/pymodbus-dev/pymodbus?tab=readme-ov-file ## Usage - Modsim32 1. File > New ![image](https://hackmd.io/_uploads/BkHT-y4Op.png) ![image](https://hackmd.io/_uploads/BysCZ1NuT.png) 2. Connection > Connect > Modbus/TCP Svr ![image](https://hackmd.io/_uploads/ByZefkNup.png) Set the Port `502` ![image](https://hackmd.io/_uploads/BkDZMJV_a.png) 3. Choose **03: HOLDING REGISTER** ![image](https://hackmd.io/_uploads/BkDSzyN_T.png) ## Python Code ```python= """ Maintainer: Chieh Work with Modsim3. Address: 0100 Length: 100 Device ID: 1 03: Holding register """ from pymodbus.client.sync import ModbusTcpClient import random SERVER_IP = '10.1.2.190' # aka modbus slave SERVER_PORT = 502 ADDRESS_START = 100 ADDRESS_LENGTH = 10 DEVICE_ID = 1 client = ModbusTcpClient(host=SERVER_IP, port=SERVER_PORT) if not client.connect(): # .connect() returns True if connection established print("[Error] Fail to connect to modbus slave %s:%d." % (SERVER_IP, SERVER_PORT)) exit() res = client.read_holding_registers(address=99, count=10, unit=1) results = res.registers # shows the result, type: list print(results) # --------------------------- ## write an random value print("[INFO] Now writing a random boolean value to address= {}.".format(ADDRESS_START)) rand_value = random.randint(10, 19) print(rand_value) write_registers_response = client.write_register( address=ADDRESS_START, value=rand_value, unit=DEVICE_ID) write_registers_response.isError() res = client.read_holding_registers(address=99, count=10, unit=1) results = res.registers # shows the result, type: list print(results) client.close() ``` Output: ![image](https://hackmd.io/_uploads/ByUf4y4up.png) ## Reference - https://hackmd.io/@quency/most_pymodbus - https://pymodbus.readthedocs.io/en/latest/source/client.html --- # Send the string ```python #!/usr/bin/env python """ Pymodbus Payload Building/Decoding Example -------------------------------------------------------------------------- # Run modbus_payload_server.py or synchronous_server.py to check the behavior """ from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder from pymodbus.payload import BinaryPayloadBuilder from pymodbus.client.sync import ModbusTcpClient as ModbusClient from pymodbus.compat import iteritems from collections import OrderedDict # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s' ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.INFO) ORDER_DICT = { "<": "LITTLE", ">": "BIG" } def run_binary_payload_ex(): # ----------------------------------------------------------------------- # # We are going to use a simple client to send our requests # ----------------------------------------------------------------------- # client = ModbusClient('192.168.100.217', port=502) client.connect() # ----------------------------------------------------------------------- # # If you need to build a complex message to send, you can use the payload # builder to simplify the packing logic. # # Here we demonstrate packing a random payload layout, unpacked it looks # like the following: # # - an 8 byte string "abcdefgh" # - an 8 bit bitstring [0,1,0,1,1,0,1,0] # - an 8 bit int -0x12 # - an 8 bit unsigned int 0x12 # - a 16 bit int -0x5678 # - a 16 bit unsigned int 0x1234 # - a 32 bit int -0x1234 # - a 32 bit unsigned int 0x12345678 # - a 16 bit float 12.34 # - a 16 bit float -12.34 # - a 32 bit float 22.34 # - a 32 bit float -22.34 # - a 64 bit int -0xDEADBEEF # - a 64 bit unsigned int 0x12345678DEADBEEF # - another 64 bit unsigned int 0x12345678DEADBEEF # - a 64 bit float 123.45 # - a 64 bit float -123.45 # The packing can also be applied to the word (wordorder) and bytes in each # word (byteorder) # The wordorder is applicable only for 32 and 64 bit values # Lets say we need to write a value 0x12345678 to a 32 bit register # The following combinations could be used to write the register # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # Word Order - Big Byte Order - Big # word1 =0x1234 word2 = 0x5678 # Word Order - Big Byte Order - Little # word1 =0x3412 word2 = 0x7856 # Word Order - Little Byte Order - Big # word1 = 0x5678 word2 = 0x1234 # Word Order - Little Byte Order - Little # word1 =0x7856 word2 = 0x3412 # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # ----------------------------------------------------------------------- # combos = [(wo, bo) for wo in [Endian.Big, Endian.Little] for bo in [Endian.Big, Endian.Little]] for wo, bo in combos: print("-" * 60) print("Word Order: {}".format(ORDER_DICT[wo])) print("Byte Order: {}".format(ORDER_DICT[bo])) print() builder = BinaryPayloadBuilder(byteorder=bo, wordorder=wo) strng = "abcdefgh" builder.add_string(strng) builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0]) builder.add_8bit_int(-0x12) builder.add_8bit_uint(0x12) builder.add_16bit_int(-0x5678) builder.add_16bit_uint(0x1234) builder.add_32bit_int(-0x1234) builder.add_32bit_uint(0x12345678) builder.add_16bit_float(12.34) builder.add_16bit_float(-12.34) builder.add_32bit_float(22.34) builder.add_32bit_float(-22.34) builder.add_64bit_int(-0xDEADBEEF) builder.add_64bit_uint(0x12345678DEADBEEF) builder.add_64bit_uint(0x12345678DEADBEEF) builder.add_64bit_float(123.45) builder.add_64bit_float(-123.45) payload = builder.to_registers() print("-" * 60) print("Writing Registers") print("-" * 60) print(payload) print("\n") payload = builder.build() address = 100 # Can write registers # registers = builder.to_registers() # client.write_registers(address, registers, unit=1) # Or can write encoded binary string client.write_registers(address, payload, skip_encode=True, unit=1) # ----------------------------------------------------------------------- # # If you need to decode a collection of registers in a weird layout, the # payload decoder can help you as well. # # Here we demonstrate decoding a random register layout, unpacked it looks # like the following: # # - an 8 byte string "abcdefgh" # - an 8 bit bitstring [0,1,0,1,1,0,1,0] # - an 8 bit int -0x12 # - an 8 bit unsigned int 0x12 # - a 16 bit int -0x5678 # - a 16 bit unsigned int 0x1234 # - a 32 bit int -0x1234 # - a 32 bit unsigned int 0x12345678 # - a 16 bit float 12.34 # - a 16 bit float -12.34 # - a 32 bit float 22.34 # - a 32 bit float -22.34 # - a 64 bit int -0xDEADBEEF # - a 64 bit unsigned int 0x12345678DEADBEEF # - another 64 bit unsigned int which we will ignore # - a 64 bit float 123.45 # - a 64 bit float -123.45 # ----------------------------------------------------------------------- # # address = 0x0 count = len(payload) result = client.read_holding_registers(address, count, unit=1) print("-" * 60) print("Registers") print("-" * 60) print(result) print(result.registers) print("\n") decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=bo, wordorder=wo) assert decoder._byteorder == builder._byteorder, \ "Make sure byteorder is consistent between BinaryPayloadBuilder and BinaryPayloadDecoder" assert decoder._wordorder == builder._wordorder, \ "Make sure wordorder is consistent between BinaryPayloadBuilder and BinaryPayloadDecoder" decoded = OrderedDict([ ('string', decoder.decode_string(len(strng))), ('bits', decoder.decode_bits()), ('8int', decoder.decode_8bit_int()), ('8uint', decoder.decode_8bit_uint()), ('16int', decoder.decode_16bit_int()), ('16uint', decoder.decode_16bit_uint()), ('32int', decoder.decode_32bit_int()), ('32uint', decoder.decode_32bit_uint()), ('16float', decoder.decode_16bit_float()), ('16float2', decoder.decode_16bit_float()), ('32float', decoder.decode_32bit_float()), ('32float2', decoder.decode_32bit_float()), ('64int', decoder.decode_64bit_int()), ('64uint', decoder.decode_64bit_uint()), ('ignore', decoder.skip_bytes(8)), ('64float', decoder.decode_64bit_float()), ('64float2', decoder.decode_64bit_float()), ]) print("-" * 60) print("Decoded Data") print("-" * 60) for name, value in iteritems(decoded): print("%s\t" % name, hex(value) if isinstance(value, int) else value) # ----------------------------------------------------------------------- # # close the client # ----------------------------------------------------------------------- # client.close() if __name__ == "__main__": run_binary_payload_ex() ```