diff --git a/sunspec/core/modbus/client.py b/sunspec/core/modbus/client.py index c05bdf2..9f01134 100755 --- a/sunspec/core/modbus/client.py +++ b/sunspec/core/modbus/client.py @@ -26,6 +26,7 @@ import struct import serial import sys +import time try: import xml.etree.ElementTree as ET @@ -34,6 +35,10 @@ import sunspec.core.modbus.mbmap as mbmap + +deadtime_clock = getattr(time, 'monotonic', time.clock) + + PARITY_NONE = 'N' PARITY_EVEN = 'E' @@ -56,6 +61,16 @@ class ModbusClientTimeout(ModbusClientError): class ModbusClientException(ModbusClientError): pass + +def wait_until(target, clock): + while True: + remaining = target - clock() + if remaining > 0: + time.sleep(remaining) + else: + break + + def modbus_rtu_client(name=None, baudrate=None, parity=None): global modbus_rtu_clients @@ -148,6 +163,8 @@ def __init__(self, name='/dev/ttyUSB0', baudrate=9600, parity=None): self.timeout = .5 self.write_timeout = .5 self.devices = {} + self.deadtime = 44 / int(self.baudrate) + self.last_receive_completion = -self.deadtime self.open() @@ -235,32 +252,40 @@ def _read(self, slave_id, addr, count, op=FUNC_READ_HOLDING, trace_func=None): s += '%02X' % (ord(c)) trace_func(s) + wait_until( + target=self.last_receive_completion + self.deadtime, + clock=deadtime_clock, + ) + self.serial.flushInput() try: self.serial.write(req) except Exception as e: raise ModbusClientError('Serial write error: %s' % str(e)) - while len_remaining > 0: - c = self.serial.read(len_remaining) - if type(c) == bytes and sys.version_info > (3,): - temp = "" - for i in c: - temp += chr(i) - c = temp + try: + while len_remaining > 0: + c = self.serial.read(len_remaining) + if type(c) == bytes and sys.version_info > (3,): + temp = "" + for i in c: + temp += chr(i) + c = temp - len_read = len(c); - if len_read > 0: - resp += c - len_remaining -= len_read - if len_found is False and len(resp) >= 5: - if not (ord(resp[1]) & 0x80): - len_remaining = (ord(resp[2]) + 5) - len(resp) - len_found = True - else: - except_code = ord(resp[2]) - else: - raise ModbusClientTimeout('Response timeout') + len_read = len(c); + if len_read > 0: + resp += c + len_remaining -= len_read + if len_found is False and len(resp) >= 5: + if not (ord(resp[1]) & 0x80): + len_remaining = (ord(resp[2]) + 5) - len(resp) + len_found = True + else: + except_code = ord(resp[2]) + else: + raise ModbusClientTimeout('Response timeout') + finally: + self.last_receive_completion = deadtime_clock() if trace_func: s = '%s:%s[addr=%s] <--' % (self.name, str(slave_id), addr) @@ -354,6 +379,11 @@ def _write(self, slave_id, addr, data, trace_func=None): s += '%02X' % (ord(c)) trace_func(s) + wait_until( + target=self.last_receive_completion + self.deadtime, + clock=deadtime_clock, + ) + self.serial.flushInput() try: if sys.version_info > (3,): @@ -362,25 +392,28 @@ def _write(self, slave_id, addr, data, trace_func=None): except Exception as e: raise ModbusClientError('Serial write error: %s' % str(e)) - while len_remaining > 0: - c = self.serial.read(len_remaining) - if type(c) == bytes and sys.version_info > (3,): - temp = "" - for i in c: - temp += chr(i) - c = temp - len_read = len(c); - if len_read > 0: - resp += c - len_remaining -= len_read - if len_found is False and len(resp) >= 5: - if not (ord(resp[1]) & 0x80): - len_remaining = 8 - len(resp) - len_found = True - else: - except_code = ord(resp[2]) - else: - raise ModbusClientTimeout('Response timeout') + try: + while len_remaining > 0: + c = self.serial.read(len_remaining) + if type(c) == bytes and sys.version_info > (3,): + temp = "" + for i in c: + temp += chr(i) + c = temp + len_read = len(c); + if len_read > 0: + resp += c + len_remaining -= len_read + if len_found is False and len(resp) >= 5: + if not (ord(resp[1]) & 0x80): + len_remaining = 8 - len(resp) + len_found = True + else: + except_code = ord(resp[2]) + else: + raise ModbusClientTimeout('Response timeout') + finally: + self.last_receive_completion = deadtime_clock() if trace_func: s = '%s:%s[addr=%s] <--' % (self.name, str(slave_id), addr)