Search code examples
pythonmodbusrs485pymodbus

Modbus Python Schneider PM5300


I'm trying to get voltage or current from PM5300 meter, but always come 32768 only. The code:

from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
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.DEBUG)

UNIT = 0x01


def run_sync_client():
    client = ModbusClient(method='rtu', port='COM15', timeout=1, baudrate=19200, parity='E', stopbits=1, bytesize=8)
    client.connect()

    request = client.read_holding_registers(address=43010, count=2, unit=UNIT)
    result = request.registers
    print(result)
    decoder = BinaryPayloadDecoder.fromRegisters(result, Endian.Little, wordorder=Endian.Big)
    #dc2 = (decoder.decode_32bit_float() + 32768 /1000)
    #print(dc2)
    print(decoder.decode_32bit_float())


    client.close()


if __name__ == "__main__":
    run_sync_client()

The address 3010 in register list matches to current (4 is the holding register).

The log and print with Endian.Little:

2020-06-16 20:11:24,349 MainThread      DEBUG    transaction    :115      Current transaction state - IDLE
2020-06-16 20:11:24,349 MainThread      DEBUG    transaction    :120      Running transaction 1
2020-06-16 20:11:24,350 MainThread      DEBUG    transaction    :219      SEND: 0x1 0x3 0xa8 0x2 0x0 0x2 0x45 0xab
2020-06-16 20:11:24,350 MainThread      DEBUG    sync           :75       New Transaction state 'SENDING'
2020-06-16 20:11:24,350 MainThread      DEBUG    transaction    :228      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2020-06-16 20:11:24,364 MainThread      DEBUG    transaction    :304      Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
2020-06-16 20:11:24,365 MainThread      DEBUG    transaction    :233      RECV: 0x1 0x3 0x4 0x80 0x0 0x80 0x0 0xb2 0x33
2020-06-16 20:11:24,365 MainThread      DEBUG    rtu_framer     :180      Getting Frame - 0x3 0x4 0x80 0x0 0x80 0x0
2020-06-16 20:11:24,365 MainThread      DEBUG    factory        :266      Factory Response[ReadHoldingRegistersResponse: 3]
2020-06-16 20:11:24,365 MainThread      DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
2020-06-16 20:11:24,366 MainThread      DEBUG    transaction    :383      Adding transaction 1
2020-06-16 20:11:24,366 MainThread      DEBUG    transaction    :394      Getting transaction 1
2020-06-16 20:11:24,366 MainThread      DEBUG    transaction    :193      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
[32768, 32768]
**2020-06-16 20:11:24,366 MainThread      DEBUG    payload        :312      [32768, 32768]**
2020-06-16 20:11:24,367 MainThread      DEBUG    payload        :368      [b'\x00\x80', b'\x00\x80']
1.1755122874426309e-38

With Endian.Big the result: -4.591774807899561e-41

Thanks a lot!


Solution

  • Your problem is in the register number you are trying to read. On pymodbus you have to indicate registers as absolute address numbers; you don't have to add 40000 to the Modbus map as you did.

    Just change this line:

    request = client.read_holding_registers(address=43010, count=2, unit=UNIT)
    

    to:

    request = client.read_holding_registers(address=3010, count=2, unit=UNIT)
    

    That should give you the current average according to the map of your meter.

    I don't have access to a meter right now but if my notes are correct the endianness should be byteorder=Endian.Big, wordorder=Endian.Little so you might want to fiddle with that in your code too.

    Reading this question might be worth your while, it has some code that I think targets the same family of Schneider Electric devices.

    I would argue it would be better if instead of an apparently correct reading you'd get a wrong address error, which is what you should be getting in this case. I imagine they wanted to keep open the option to add more registers to the map and they kept it unbounded.

    EDIT: As discussed in the comments below the correct register to read for the current average is 3009, so the request should be:

    request = client.read_holding_registers(address=3009, count=2, unit=UNIT)
    

    Some devices give you holding register numbers starting from 40001. For pymodbus and others the first holding register is number 0, so if the Modbus map of the device you should read register number 45125, for instance, you need to subtract 40001 to get the address for pymodbus:

    address=45125-40001=5124
    

    The case you are dealing with here is even more annoying: the map starts with register 1 so you need to subtract 1.

    As you can see on the following screenshot:

    ModbusPoll screenshot

    ModbusPoll follows the same logic: by default you have to enter 0 for register 40001 (or 10 for register 40011. If you click the check box PLC addresses you will have to enter 1 to read from address 40001.

    Yes, I know, confusing!

    Note that the question in the link above does not mention the -1 offset. Some devices (like yours!) will not let you read from "the wrong" register. Such that, if you want to read current average (which is a FLOAT32 and hence occupies two registers) you need to start reading from register 3009 and read two registers (yes, the map says 3010 but keep in mind the -1 offset). If instead, you start from register 3010 you are actually trying to read the upper half part of the current average and the lower half of the next variable (current imbalance). Since you will not be able to get any useful data because you are reading two mixed up variables you get an error. Again, not the best error; it should at least give you some hint of what you are doing wrong, but alas!