Warm tip: This article is reproduced from serverfault.com, please click

pymodbus: Issue reading String & multiple type of data from Modbus device

发布于 2019-02-15 12:41:11

I am trying to read String (Usecase-1) & multiple type of data in one request (Usecase-2) data from Modbus TCP device but, it failed to decode it correctly.

System Configuration:

Python 3.6.5
Pymodbus: 2.1.0
Platform: Windows 10 64-bit

Modbus TCP Server:

import logging

from pymodbus.constants import Endian
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.payload import BinaryPayloadBuilder
from pymodbus.server.sync import StartTcpServer

class ModbusTCPServer(object):
    # initialize your data store:
    hrBuilder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big)
    # Usecase-1
    hrBuilder.add_string("abcdefghij")

    #Uncomment below three lines for usecase-2
    # hrBuilder.add_32bit_float(20.5) 
    # hrBuilder.add_32bit_int(45) 
    # hrBuilder.add_bits([1, 0, 0, 0, 0, 0, 0, 0])

    hrBlock = ModbusSequentialDataBlock(0, hrBuilder.to_registers() * 100)
    store = ModbusSlaveContext(hr=hrBlock, ir=hrBlock, di=hrBlock, co=hrBlock)
    slaves = {
        1: store,
    }
    context = ModbusServerContext(slaves=slaves, single=False)

    # initialize the server information    
    identity = ModbusDeviceIdentification()

    modbusDeviceAddress = "127.0.0.1"
    modbusDevicePort = 501
    # run the TCP server

    # TCP:
    print("Modbus TCP Server started.")
    StartTcpServer(context, identity=identity, address=(modbusDeviceAddress, modbusDevicePort))


if __name__ == "__main__":
    print("Reading application configurations...")
    ModbusTCPServer();

Modbus TCP Client:

from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian
from pymodbus.compat import iteritems

if __name__ == '__main__':
    client = ModbusClient('127.0.0.1', port=501)
    client.connect()
    result  = client.read_holding_registers(0, 5,  unit=1)
    print("Result : ",result)
    decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big, wordorder=Endian.Big)
    # Usecase-1
    decoded = {
        'name': decoder.decode_string(10).decode(),
    }

    # Usecase-2
    # decoded = {
    #    'temp': decoder.decode_32bit_float(),
    #    'rpm': decoder.decode_32bit_int(),
    #    'status': decoder.decode_bits()
    #}

    for name, value in iteritems(decoded):
        print ("%s\t" % name, value)
    client.close()

Output Usecase-1:

Result :  ReadRegisterResponse (5)
name     cdefghijab

Modbus client should decode string as abcdefghij but, it's decoding it as cdefghijab.

Output Usecase-2:

Result :  ReadRegisterResponse (5)
temp     0.0
rpm  2949376
status   [True, False, False, False, False, False, True, False]

Look at the above output of reading multiple registers, the output values doesn't match with the one given in input to BinaryPayloadBuilder.

I've tried all the combination of byteorder & wordorder but, it's not working with any case.

Please help me in understanding why data is decoded like this ? Did I missed something to add while encoding or decoding this data ?

FYI: This solution was working fine with Pymodbus 1.5.1 version. Recently I've upgraded version & it failed to work as expected.

Any help would be appreciated.

Questioner
OO7
Viewed
0
Sanju 2019-02-16 14:57:37

Tl;dr. Use zero_mode=True in ModbusSlaveContext.

If you want to map registers [0..n] in your client reads to [0..n] in the server. By default pymodbus server maps register reads for address [0..n] to registers [1..n] in its internal store. This is to adhere to the modbus specs . Quoting from the pymodbus source code.

#The slave context can also be initialized in zero_mode which means that a
# request to address(0-7) will map to the address (0-7). The default is
# False which is based on section 4.4 of the specification, so address(0-7)
# will map to (1-8)::

So in your case you can either set the starting address of the ModbusSequentialDataBlock to 1 or initialize ModbusSlaveContext with zero_mode=True.

    hrBlock = ModbusSequentialDataBlock(1, hrBuilder.to_registers() * 100)
    # Or
    store = ModbusSlaveContext(hr=hrBlock, ir=hrBlock, di=hrBlock, co=hrBlock, zero_mode=True)