bashwork/pymodbus — pull request #55 by Cougar (2014-08-15 21:45:55+00:00)
Title: PATCH: boost serial speed 3x or more

pymodbus over serial port is slow. The reason is that once serial port is opened, the initial timeout values is an indication how long it takes to get Modbus response. If one knows exactly how long it takes for controller to respond to the request, it is possible to manually tune timeout with quite good precision. But when different requests are used where responses have different latency and different length, then timeout should be set based on the slowest response. Otherwise some data just remains in buffer and both this and next response will be incorrect.

To overcome this, we need to get rid of timer and know how big is the response of any request. When ModbusSerialClient reads the response, it should know how many bytes to read and not wait for any timeout. For that I implemented getResponseSize() method in ModbusRequest class (and its derived classes). If response size is known, then ModbusTransactionManager.execute() reads only this amount of bytes. If size is not known, then the old timeout based behavior is used. Latter happens also with exception responses which are usually shorter than full response frames. Because most operations do not depend on timeout any more, much longer timeout can be used.

This is implemented only for ModbusRtuFramer and serial port right now.

Using this technique alone could cause a new kind of problem: the next request might happen too quickly after previous one. Modbus standard requires that there is silent interval of at least 3.5 character times between message serial RTU frames. For that _silentinterval is calculated based on serial port baud rate in ModbusSerialClient.connect(), and serial port write times are always saved to the _lastsend variable. Before every send it is checked that there has been at least _silentinterval delay between consecutive requests. However, I'm not sure if serial port writes are always synchronous. This may need some more work if not.

I also added serial port input buffer flushing before sending request. This makes sure that any old or incorrect data can't mess up with the next response.

Some words about performance. I'm testing with Modbus RTU slave at 19200 bps. This controller responds to holding register reads usually within 0.4 … 14 ms. However sometimes the response takes around 20 ms. 7 byte response for one holding register request takes 4.5 ms. So, the minimum timeout that can be used should be at least 25 ms if only such requests are used. And results are:

pymodbus with timeout: 24…26 req/sec
pymodbus with getResponseSize(): 76…78 req/sec

Enjoy,

--
Cougar