Examples as a serial client using twisted architecture

1,189 views
Skip to first unread message

Darryl

unread,
May 23, 2011, 2:59:04 AM5/23/11
to pymodbus
G'Day

I have some code running in modbus_tk, but I think I want to move this
into pymodbus so that I can use the twisted architecture and gain the
advantages of such an architecture. I have other 'device drivers'
under Twisted, and I think pymodbus will allow me to do what I need.
The thing is that I cannot work out how to get pymodbus to do what I
want.

Basically, I want to poll a modbus device for several Holding
Registers in one case, and several coils in another case, and send
this data out a TCP connection via Twisted. The second part with TCP
is easy. I cannot work out how to do the pymodbus bit of this. Later I
will need to also write coils based on TCP input and coil value
changes.

Any assistance with this would be really appreciated. The issue is
that most of the examples assume a standalone program, and also assume
TCP, which makes things harder to work out

Thanks

Darryl VK2TDS
Sydney, Australia

Albert Brandl

unread,
May 24, 2011, 4:20:18 AM5/24/11
to pymo...@googlegroups.com
Hi!

On Sun, May 22, 2011 at 11:59:04PM -0700, Darryl wrote:
> Basically, I want to poll a modbus device for several Holding
> Registers in one case, and several coils in another case, and send
> this data out a TCP connection via Twisted.

Will this polling be done in a cyclic fashion?

> The second part with TCP is easy. I cannot work out how to do the pymodbus
> bit of this. Later I will need to also write coils based on TCP input and
> coil value changes.

You'll have to connect the Modbus clien and the TCP part in some way.

> Any assistance with this would be really appreciated. The issue is
> that most of the examples assume a standalone program, and also assume
> TCP, which makes things harder to work out

Have a look at the attached example script. I am not sure if it works
out of the box, since I have no serial RTU server available right now.
If it does not work, the errors are most likely in the definition of
the SerialModbusClient or the main() function.

The logic of MyProtocol might be simplified. The protocol fetches the
holding registers first, and waits for the response before fetching
the coils. You might be able to collect the methods into one, but
some devices don't like too quick polling and start reacting in a
strange way.

The LineReader class is a dummy that just prints the values received
from the device. You can replace it with your instance of the TCP
protocol where you want to send the data (and use the appropriate
method instead of write()).

If you have any questions, feel free to ask them.

Best regards,
--
Albert Brandl
Weiermayer Solutions GmbH | Abteistra�e 12, A-4813 Altm�nster
phone: +43 (0) 720 70 30 14 | fax: +43 (0) 7612 20 3 56
web: http://www.weiermayer.com

modbus_client.py

Darryl

unread,
May 24, 2011, 6:05:17 AM5/24/11
to pymodbus
Thanks for this Albert. I have started going though it, and I am
starting to work things out. I am using modbus devices from Trycom in
Taiwan. They are nice solid units, and are reasonably priced. I have
not yet had any blow up, although early on there was a firmware bug
that did not let any inputs get read following power on until at least
one input had changed state. Thankfully, this was fixed pretty
quickly.

> Will this polling be done in a cyclic fashion?

Your code seems to do this sort of thing, many thanks.

> The logic of MyProtocol might be simplified. The protocol fetches the
> holding registers first, and waits for the response before fetching
> the coils. You might be able to collect the methods into one, but
> some devices don't like too quick polling and start reacting in a
> strange way.

I have two main questions. First, lets say I request some coils with d
= self.read_coils(18, 16) followed by the callback to the next step.
How can I see the values that I am getting back. Also, to change the
speed of the serial port, is the syntax something like
SerialModbusClient(factory, SERIAL_PORT, reactor, baudrate=57600)?

I probably have more questions, but it is now 8pm and time for dinner
and I have been attempting to debug code much of the day and it is
definitely time for a break.

Thanks

Darryl

Bashwork

unread,
May 24, 2011, 10:50:40 AM5/24/11
to pymodbus

> I have two main questions. First, lets say I request some coils with d
> = self.read_coils(18, 16) followed by the callback to the next step.
> How can I see the values that I am getting back.

The response that is returned is a ReadCoilsResponse instance and the
resulting values are stored in the bits field (instance.bits). There
is also a helper methods to get each bit of the response message
(instance.getBit(n)). You can simply write this value to method you
want to use for visualization (the python logging facility say).

> Also, to change the
> speed of the serial port, is the syntax something like
> SerialModbusClient(factory, SERIAL_PORT, reactor,  baudrate=57600)?

That is correct, here is the documentation for the remaining
arguments:
http://twistedmatrix.com/documents/current/api/twisted.internet.serialport.SerialPort.html#__init__

Darryl

unread,
May 24, 2011, 7:54:14 PM5/24/11
to pymodbus
Thanks Albert

Thanks are starting to make sense. The important thing is the
following statement :
d = self.read_coils(0, count=16, unit=19)
This reads 16 coils starting at coil 0 from a unit with address 19. By
default, looking at the source, the unit will be 0. Printing
response.bits will show me the state of all the 16 coils that I
retrieved.

At the moment, if I try to do a self.read from a unit that is not
there (temporarily or permanently), the code hangs. Some idea on how
to deal with that would definitely help. But this has helped so much -
so much that I think I will be able to soon ditch modbus-tk :-)

Darryl

Albert Brandl

unread,
May 25, 2011, 7:25:39 AM5/25/11
to pymo...@googlegroups.com
On Tue, May 24, 2011 at 04:54:14PM -0700, Darryl wrote:
> This reads 16 coils starting at coil 0 from a unit with address 19. By
> default, looking at the source, the unit will be 0.

That is right. Some units only work if addressed with the correct ID;
others are less picky.

> Printing response.bits will show me the state of all the 16 coils that
> I retrieved.

You should probably try not depend on the existence of this attribute
and use the getBit() method instead.

> At the moment, if I try to do a self.read from a unit that is not
> there (temporarily or permanently), the code hangs. Some idea on how
> to deal with that would definitely help.

Right now, the callback is never called, since the method for fetching
holding registers or coils does not finish.

How about updating a timestamp each time you receive data from the unit?
You could then add a "watchdog" that is called periodically and restarts
the connection if the timestamp is too old.

> But this has helped so much - so much that I think I will be able to
> soon ditch modbus-tk :-)

The Twisted integration of pymodbus is definitely better...

Regards,

Darryl

unread,
May 25, 2011, 9:46:26 PM5/25/11
to pymodbus
Dear Albert

I have managed to get everything working on my solar inverter, at
least for the simple case where I am only looking at one set of
datapoints. The timeout code can be improved for some predictors based
on the line speed and the amount of data being returned. Next I will
work on the mode complex case of controlling my lighting, which
involves polling four devices with 16 coils, reading a number of
registers on one of those devices, and writing coils to any of four
other devices. And reading registers from another device once a
second. Thankfully the code just needs to be moved from modbus-tk and
is fairly clean.

Anyway, my code fragment appears below

Darryl


class MyProtocol(async.ModbusClientProtocol):
"A Modbus client that fetches holding registers and coils."

def __init__(self, context, framer, tcp):
async.ModbusClientProtocol.__init__(self, framer)
self.context = context
self.tcp = tcp
reactor.callLater(0.1, self.interact)

def timeout(self):
print "Timeout!!!"
reactor.callLater(10, self.fetch)

def interact(self):
"Trigger first cycle."
run_later(self.fetch, 1)

def fetch(self):
"Defer fetching holding registers."
d = self.read_holding_registers(49184, count=24, unit=1)
d.addCallback(self.fetch2)
self.timeoutLoop = reactor.callLater (1, self.timeout)

def fetch2(self, response):
"Defer fetching holding registers."
self.timeoutLoop.cancel()

self.factory.gotResponse(response)
reactor.callLater (10, self.fetch)

Albert Brandl

unread,
May 26, 2011, 3:21:23 AM5/26/11
to pymo...@googlegroups.com
Hi!

On Wed, May 25, 2011 at 06:46:26PM -0700, Darryl wrote:

> Anyway, my code fragment appears below

Thanks for sharing :-).

> def timeout(self):
> print "Timeout!!!"
> reactor.callLater(10, self.fetch)

I'm not sure that this will work. We had a similar case, where our SPS
went offline and later reappeared. But Twisted didn't manage to use the
previously broken serial connection again. You can easily check this by
unplugging the serial line, waiting for some seconds and plugging it in
again.

The only way I found to reestablish communication was to terminate the
client and restart it again. This should be triggered from outside the
client.

Maybe you can add a timestamp that is updated each time you read data.
If you have a method that checks the up-to-dateness of the timestamps
(one for every client), you can create a function that calls this
method, and restarts the client if necessary - similar to your timeout
method above.

Best regards,

Reply all
Reply to author
Forward
0 new messages