async or sync clients and solar inverters

501 views
Skip to first unread message

Simon Kranz

unread,
Aug 27, 2020, 7:06:17 PM8/27/20
to pymodbus
Hello All,

I have been experimenting with pymodbus for the past 6 months ever since i installed a solar PV system in my home.
I wanted to communicate with the solar inverter and it's smart meter via RS485 to obtain power, voltage and current information.
So far, i have been successful using the pymodbus library (using the TCP sync client).  The system is a Fronius system.  

Anyhow, i have also experimented with a RS485 enabled power meter (Sinotimer DDS548MR).  Again, i used the sync client successfully.  

I also have a family member with a solar system with KStar inverter system.  So far, i have only been able to do limited experimentation with this.  The KStar documentation i obtained says it uses asynchronous mode.  I first tried using the sync client and didn' seem to have any luck connecting.  So i began to try to research the async client.
I have not been able to experiment further due to COVID travel restrictions.

I have tried to understand when and where the use of sync and async is needed and if both or only one option works?
I seem to be only be able to communicate to the Sinotimer via the sync client, not
 the async client. (via RS485 USB dongle)
On the other hand, i can successfully communicate withe Fronius system via TCP via both sync and async.

Can anyone explain in more detail why one is preferred over the other and if any modbus slave can respond to either a sync client or async client (master)?

Has anyone out there had experience connecting to KStar Solar inverters using pymodbus?

thanks,
Simon

Galen Collins

unread,
Aug 27, 2020, 9:00:01 PM8/27/20
to pymo...@googlegroups.com
Summary: if you are not sure which one you need, then the sync is probably fine. The details is that the sync uses simple python sockets which are fine but the client isn't designed for high performance. The async was designed to handle thousands of reads per second using the twisted framework and now also the python asio framework.

Th sync client should work with all your modbus hardware and can be thought of as useful for reading from a few devices without a high polling rate. The async client should be used more for say doing data center monitoring of hundreds of devices with a very high polling rate.

--
You received this message because you are subscribed to the Google Groups "pymodbus" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pymodbus+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pymodbus/591cbccc-000b-48fd-a62b-3cf3611b93f5n%40googlegroups.com.

Simon Kranz

unread,
Aug 29, 2020, 2:45:14 AM8/29/20
to pymodbus
Hello,
thanks for the response.
So, if i understand you correctly, then a sync client or async client should both work on any modbus, but it is a matter of performance.

However, i am trying to make async client using asyncio and cannot make it work with my Sinotimer device, whilst the sync does work.
It seems to connect, then send the request (read_input_register) to the device, but it just hangs and goes nowhere.
I am using debian buster with pymodbus 2.1.0
I then tried the twisted async serial client example and managed to get it to work.  So is something going wrong with asyncio?

Code snippet for asyncio:


METER = 0x1a # decimal 26

## start
async def start_async_test(client):
    # ----------------------------------------------------------------------- #
    # specify slave to query
    # ----------------------------------------------------------------------- #
    # The slave to query is specified in an optional parameter for each
    # individual request. This can be done by specifying the `unit` parameter
    # which defaults to `0x00`
    # ----------------------------------------------------------------------- #
    log.debug("Reading Meter Energy")
    myresponse = await client.read_input_registers(0x0100, 2, unit=METER)

    await asyncio.sleep(1)

if __name__ == '__main__':
    # ----------------------------------------------------------------------- #
    # For testing on linux based systems you can use socat to create serial
    # ports
    # ----------------------------------------------------------------------- #
    # socat -d -d PTY,link=/tmp/ptyp0,raw,echo=0,ispeed=9600 PTY,
    # link=/tmp/ttyp0,raw,echo=0,ospeed=9600

    log.debug("First connect")
    loop, client = ModbusClient(schedulers.ASYNC_IO, port="/dev/ttyUSB0",baudrate=9600, method="rtu", parity = 'E')
    log.debug("After connect")
    log.debug("start loop")
    loop.run_until_complete(start_async_test(client.protocol))
    log.debug("close loop")
    loop.close()

###
Here is output:

2020-08-29 16:39:19,930 MainThread      DEBUG    async_test_simo:84       First connect
2020-08-29 16:39:19,937 MainThread      DEBUG    selector_events:53       Using selector: EpollSelector
2020-08-29 16:39:19,939 MainThread      DEBUG    __init__       :711      Connecting.
2020-08-29 16:39:19,958 MainThread      DEBUG    __init__       :94       Client connected to modbus server
2020-08-29 16:39:19,959 MainThread      INFO     __init__       :728      Protocol made connection.
2020-08-29 16:39:19,959 MainThread      INFO     __init__       :720      Connected to /dev/ttyUSB0
2020-08-29 16:39:19,959 MainThread      DEBUG    async_test_simo:86       After connect
2020-08-29 16:39:19,959 MainThread      DEBUG    async_test_simo:87       start loop
2020-08-29 16:39:19,960 MainThread      DEBUG    async_test_simo:59       Reading Meter Energy
2020-08-29 16:39:19,960 MainThread      DEBUG    __init__       :125      send: 0x1a 0x4 0x1 0x0 0x0 0x2 0x73 0xdc
2020-08-29 16:39:19,960 MainThread      DEBUG    transaction    :418      Adding transaction 1

I then have to Ctrl+C to exit
Now the twisted snippet:

#####
SERIAL_PORT = "/dev/ttyUSB0"
STATUS_REGS = (0, 2)
STATUS_COILS = (1, 3)
CLIENT_DELAY = 1
UNIT = 0x1a

class ExampleProtocol(ModbusClientProtocol):

    def __init__(self, framer):
        """ Initializes our custom protocol

        :param framer: The decoder to use to process messages
        :param endpoint: The endpoint to send results to
        """
        ModbusClientProtocol.__init__(self, framer)
        log.debug("Beginning the processing loop")
        reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers)

    def fetch_holding_registers(self):
        """ Defer fetching holding registers
        """
        log.debug("Starting the next cycle")
        #d = self.read_holding_registers(*STATUS_REGS, unit=UNIT)
        d = self.read_input_registers(0x100,2, unit=UNIT)
        #d.addCallbacks(self.send_holding_registers, self.error_handler)
        d.addCallbacks(self.start_next_cycle, self.error_handler)       

    def start_next_cycle(self, response):
        """ Write values of coils, trigger next cycle

        :param response: The response to process
        """
   
        log.info(response.getRegister(0))
        log.info(response.getRegister(1))
        decoder = BinaryPayloadDecoder.fromRegisters(response.registers,byteorder=Endian.Big)
        # round to 2 decimals
        log.info(round(decoder.decode_32bit_float(),2))
       
        # repeat reads
        #reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers)
       
        # stop
        #reactor.callLater(1, client.transport.loseConnection) # find serial closing?
        reactor.callLater(2, reactor.stop)

  
if __name__ == "__main__":
    proto, client = AsyncModbusSerialClient(schedulers.REACTOR,
                                            method="rtu",
                                            port=SERIAL_PORT,
                                            timeout=2, parity = 'E',
                                            proto_cls=ExampleProtocol)
    proto.start()
    #proto.stop()

#####

And the output:

DEBUG:pymodbus.client.asynchronous.serial:SK pre-factory
DEBUG:pymodbus.client.asynchronous.serial:SK after-factory
DEBUG:pymodbus:Beginning the processing loop
DEBUG:pymodbus.client.asynchronous.twisted:Client connected to modbus server
DEBUG:pymodbus.client.asynchronous.serial:SK after yieldable
INFO:pymodbus.client.asynchronous.thread:Starting Event Loop: 'PyModbus_reactor'
DEBUG:pymodbus:Starting the next cycle
DEBUG:pymodbus.client.asynchronous.twisted:send: 0x1a 0x4 0x1 0x0 0x0 0x2 0x73 0xdc
DEBUG:pymodbus.transaction:Adding transaction 1
DEBUG:pymodbus.framer.rtu_framer:Getting Frame - 0x4 0x4 0x41 0xf1 0xbd 0x71
DEBUG:pymodbus.factory:Factory Response[ReadInputRegistersResponse: 4]
DEBUG:pymodbus.framer.rtu_framer:Frame advanced, resetting header!!
INFO:pymodbus:16881
INFO:pymodbus:48497
DEBUG:pymodbus.payload:[16881, 48497]
DEBUG:pymodbus.payload:[b'A\xf1', b'\xbdq']
INFO:pymodbus:30.22


###

As you can see, i get the two bytes back and i convert to float and get correct value "30.22".
You can see the hex string sent in both examples is identical.

Any ideas what is wrong with asyncio version?

thanks,
Simon

Sanjay KV

unread,
Aug 29, 2020, 3:14:44 AM8/29/20
to pymo...@googlegroups.com
Please  use pymodbus 2.4.0rc1 . It has fixes for some known asyncio client issues.

Simon Kranz

unread,
Aug 29, 2020, 5:53:28 AM8/29/20
to pymodbus
Hello,

yes, i just figured it out just before your post.
v 2.1.0 asyncio "create_serial_connection" function call is missing "parity" option.
So as my device is parity "E", not "N", which i believe is the default, it wasn't responding.
I added the parity keyword arg into the function call in my pymodbus v2.1.0 code and it now works.

Simon
Reply all
Reply to author
Forward
0 new messages