Skip to content

Commit

Permalink
- removes the optional delay on reads from serial MODBUS
Browse files Browse the repository at this point in the history
- adds optional delay after connect before sending requests to serial MODBUS to allow to wait for Arduino slaves to complete reboot (Issue #1694)
  • Loading branch information
MAKOMO committed Sep 23, 2024
1 parent cc305f1 commit 601ffed
Show file tree
Hide file tree
Showing 65 changed files with 26,853 additions and 26,812 deletions.
9 changes: 3 additions & 6 deletions src/artisanlib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17115,7 +17115,7 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
self.modbus.parity = s2a(toString(settings.value('parity',self.modbus.parity)))
self.modbus.timeout = max(0.3, float2float(toFloat(settings.value('timeout',self.modbus.timeout)))) # min serial MODBUS timeout is 300ms
self.modbus.serial_strict_timing = bool(toBool(settings.value('serial_strict_timing',self.modbus.serial_strict_timing)))
self.modbus.modbus_serial_extra_read_delay = toFloat(settings.value('modbus_serial_extra_read_delay',self.modbus.modbus_serial_extra_read_delay))
self.modbus.modbus_serial_connect_delay = toFloat(settings.value('modbus_serial_connect_delay',self.modbus.modbus_serial_connect_delay))
self.modbus.serial_readRetries = toInt(settings.value('serial_readRetries',self.modbus.serial_readRetries))
self.modbus.IP_timeout = float2float(toFloat(settings.value('IP_timeout',self.modbus.IP_timeout)))
self.modbus.IP_retries = toInt(settings.value('IP_retries',self.modbus.IP_retries))
Expand Down Expand Up @@ -17147,7 +17147,6 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
self.modbus.type = toInt(settings.value('type',self.modbus.type))
self.modbus.host = toString(settings.value('host',self.modbus.host))
self.modbus.port = toInt(settings.value('port',self.modbus.port))
self.modbus.reset_socket = bool(toBool(settings.value('reset_socket',self.modbus.reset_socket)))
settings.endGroup()
#--- END GROUP Modbus

Expand Down Expand Up @@ -18841,7 +18840,7 @@ def saveAllSettings(self, settings:QSettings, default_settings:Optional[Dict[str
self.settingsSetValue(settings, default_settings, 'parity',self.modbus.parity, read_defaults)
self.settingsSetValue(settings, default_settings, 'timeout',self.modbus.timeout, read_defaults)
self.settingsSetValue(settings, default_settings, 'serial_strict_timing',self.modbus.serial_strict_timing, read_defaults)
self.settingsSetValue(settings, default_settings, 'modbus_serial_extra_read_delay',self.modbus.modbus_serial_extra_read_delay, read_defaults)
self.settingsSetValue(settings, default_settings, 'modbus_serial_connect_delay',self.modbus.modbus_serial_connect_delay, read_defaults)
self.settingsSetValue(settings, default_settings, 'serial_readRetries',self.modbus.serial_readRetries, read_defaults)
self.settingsSetValue(settings, default_settings, 'IP_timeout',self.modbus.IP_timeout, read_defaults)
self.settingsSetValue(settings, default_settings, 'IP_retries',self.modbus.IP_retries, read_defaults)
Expand Down Expand Up @@ -18872,7 +18871,6 @@ def saveAllSettings(self, settings:QSettings, default_settings:Optional[Dict[str
self.settingsSetValue(settings, default_settings, 'type',self.modbus.type, read_defaults)
self.settingsSetValue(settings, default_settings, 'host',self.modbus.host, read_defaults)
self.settingsSetValue(settings, default_settings, 'port',self.modbus.port, read_defaults)
self.settingsSetValue(settings, default_settings, 'reset_socket',self.modbus.reset_socket, read_defaults)
settings.endGroup()
#--- END GROUP Modbus

Expand Down Expand Up @@ -22814,7 +22812,7 @@ def setcommport(self, _:bool = False) -> None:
self.modbus.parity = str(dialog.modbus_parityComboBox.currentText())
self.modbus.timeout = max(0.3, float2float(toFloat(str(dialog.modbus_timeoutEdit.text())))) # minimum serial timeout should be 300ms
try:
self.modbus.modbus_serial_extra_read_delay = toInt(dialog.modbus_Serial_delayEdit.text()) / 1000
self.modbus.modbus_serial_connect_delay = float2float(toFloat(dialog.modbus_Serial_delayEdit.text()))
except Exception: # pylint: disable=broad-except
pass
self.modbus.serial_readRetries = dialog.modbus_Serial_retriesComboBox.currentIndex()
Expand Down Expand Up @@ -22914,7 +22912,6 @@ def setcommport(self, _:bool = False) -> None:
self.modbus.wordorderLittle = bool(dialog.modbus_littleEndianWords.isChecked())
self.modbus.optimizer = bool(dialog.modbus_optimize.isChecked())
self.modbus.fetch_max_blocks = bool(dialog.modbus_full_block.isChecked())
self.modbus.reset_socket = bool(dialog.modbus_reset.isChecked())
self.modbus.type = int(dialog.modbus_type.currentIndex())
self.modbus.host = str(dialog.modbus_hostEdit.text())
try:
Expand Down
43 changes: 15 additions & 28 deletions src/artisanlib/modbusport.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,19 @@ def getBinaryPayloadDecoderFromRegisters(registers:List[int], byteorderLittle:bo
class modbusport:
""" this class handles the communications with all the modbus devices"""

__slots__ = [ 'aw', 'modbus_serial_read_delay', 'modbus_serial_extra_read_delay', 'modbus_serial_write_delay', 'maxCount', 'readRetries', 'serial_strict_timing', 'default_comport', 'comport', 'baudrate', 'bytesize', 'parity', 'stopbits',
__slots__ = [ 'aw', 'modbus_serial_read_delay', 'modbus_serial_connect_delay', 'modbus_serial_write_delay', 'maxCount', 'readRetries', 'serial_strict_timing', 'default_comport', 'comport', 'baudrate', 'bytesize', 'parity', 'stopbits',
'timeout', 'IP_timeout', 'IP_retries', 'serial_readRetries', 'PID_slave_ID', 'PID_SV_register', 'PID_p_register', 'PID_i_register', 'PID_d_register', 'PID_ON_action', 'PID_OFF_action',
'channels', 'inputSlaves', 'inputRegisters', 'inputFloats', 'inputBCDs', 'inputFloatsAsInt', 'inputBCDsAsInt', 'inputSigned', 'inputCodes', 'inputDivs',
'inputModes', 'optimizer', 'fetch_max_blocks', 'fail_on_cache_miss', 'disconnect_on_error', 'acceptable_errors', 'reset_socket', 'activeRegisters', 'readingsCache', 'SVmultiplier', 'PIDmultiplier',
'inputModes', 'optimizer', 'fetch_max_blocks', 'fail_on_cache_miss', 'disconnect_on_error', 'acceptable_errors', 'activeRegisters', 'readingsCache', 'SVmultiplier', 'PIDmultiplier',
'byteorderLittle', 'wordorderLittle', 'master', 'COMsemaphore', 'default_host', 'host', 'port', 'type', 'lastReadResult', 'commError' ]

def __init__(self, aw:'ApplicationWindow') -> None:
self.aw = aw

self.modbus_serial_read_delay :Final[float] = 0.035 # in seconds
self.modbus_serial_extra_read_delay :float = 0.0 # in seconds (user configurable)
# self.modbus_serial_extra_read_delay :float = 0.0 # in seconds (user configurable) # retired
self.modbus_serial_write_delay :Final[float] = 0.080 # in seconds
self.modbus_serial_connect_delay :float = 0.0 # in seconds (user configurable delay after serial connect; important for Arduino based slaves that reboot on connect)

self.maxCount:Final[int] = 125 # the maximum number of registers that can be fetched in one request according to the MODBUS spec
self.readRetries:int = 0 # retries
Expand Down Expand Up @@ -161,8 +162,6 @@ def __init__(self, aw:'ApplicationWindow') -> None:
self.disconnect_on_error:bool = True # if True we explicitly disconnect the MODBUS connection on IO errors (was: if on MODBUS serial and restart it on next request)
self.acceptable_errors = 3 # the number of errors that are acceptable without a disconnect/reconnect. If set to 0 every error triggers a reconnect if disconnect_on_error is True

self.reset_socket:bool = False # reset socket connection on error (True by default in pymodbus>v2.5.2, False by default in pymodbus v2.3)

self.activeRegisters:Dict[int, Dict[int, List[int]]] = {}
# the readings cache that is filled by requesting sequences of values of activeRegisters in blocks
self.readingsCache:Dict[int, Dict[int, Dict[int, int]]] = {}
Expand Down Expand Up @@ -197,10 +196,11 @@ def sleepBetween(self, write:bool = False) -> None:
# pass
# else:
# time.sleep(self.modbus_serial_write_delay)
elif self.type in {3, 4}: # delay between writes only on serial connections
elif self.type in {3, 4}: # delay between reads only on serial connections
pass
else:
time.sleep(self.modbus_serial_read_delay + self.modbus_serial_extra_read_delay)
# time.sleep(self.modbus_serial_read_delay + self.modbus_serial_extra_read_delay)
time.sleep(self.modbus_serial_read_delay)

@staticmethod
def address2register(addr:Union[float, int], code:int = 3) -> int:
Expand Down Expand Up @@ -269,8 +269,6 @@ def connect(self) -> None:
retries=1, # number of send retries
# retry_on_empty=True, # retry on empty response, by default False for faster speed # removed in pymodbus 3.7
# retry_on_invalid=True, # retry on invalid response, by default False for faster speed # retired
# close_comm_on_error=self.reset_socket,
# reset_socket=self.reset_socket,
reconnect_delay=0, # avoid automatic reconnection
# on_reconnect_callback=self.reconnect, # removed in pymodbus 3.7
# timeout is in seconds and defaults to 3
Expand All @@ -291,7 +289,6 @@ def connect(self) -> None:
# retries=0, # number of send retries
## retry_on_empty=True, # retry on empty response, by default False for faster speed # removed in pymodbus 3.7
# retry_on_invalid=True, # retry on invalid response, by default False for faster speed # retired
# reset_socket=self.reset_socket,
# reconnect_delay=0, # avoid automatic reconnection
## on_reconnect_callback=self.reconnect, # removed in pymodbus 3.7
# # timeout is in seconds and defaults to 3
Expand All @@ -306,9 +303,7 @@ def connect(self) -> None:
retries=1, # number of send retries
# retry_on_empty=True, # retry on empty response # removed in pymodbus 3.7
# retry_on_invalid=True, # retry on invalid response # retired
# close_comm_on_error=self.reset_socket,
# reset_socket=self.reset_socket,
reconnect_delay=0, # avoid automatic reconnection
# reconnect_delay=0, # not used by sync client
# on_reconnect_callback=self.reconnect, # removed in pymodbus 3.7
# timeout is in seconds (int) and defaults to 3
timeout=min((self.aw.qmc.delay/2000), self.IP_timeout) # the timeout should not be larger than half of the sampling interval
Expand All @@ -328,9 +323,7 @@ def connect(self) -> None:
retries=0, # number of send retries (if set to n>0 each requests is sent n-types on MODBUS UDP!)
# retry_on_empty=True, # retry on empty response # removed in pymodbus 3.7
# retry_on_invalid=True, # retry on invalid response # retired
# close_comm_on_error=self.reset_socket,
# reset_socket=self.reset_socket, # retired
reconnect_delay=0, # avoid automatic reconnection
# reconnect_delay=0, # not used by sync client
# on_reconnect_callback=self.reconnect, # removed in pymodbus 3.7
# timeout is in seconds (int) and defaults to 3
timeout=min((self.aw.qmc.delay/2000), self.IP_timeout) # the timeout should not be larger than half of the sampling interval
Expand Down Expand Up @@ -359,27 +352,21 @@ def connect(self) -> None:
bytesize=self.bytesize,
parity=self.parity,
stopbits=self.stopbits,
retries=1, # number of send retries
# retry_on_empty=True, # retry on empty response; by default False for faster speed # removed in pymodbus 3.7
# retry_on_invalid=True, # retry on invalid response; by default False # retired
# close_comm_on_error=self.reset_socket,
# reset_socket=self.reset_socket, # retired
# strict=False, # settings this to False disables the inter char timeout restriction # retired and replaced by self.serial_strict_timing
reconnect_delay=0, # avoid automatic reconnection
# on_reconnect_callback=self.reconnect, # removed in pymodbus 3.7
retries=1, # number of send retries (ignored on sync client pymodbus 3.6.9, but not on 3.7.2); NOTE: disconnects between retries!
timeout=min((self.aw.qmc.delay/2000), self.timeout)) # the timeout should not be larger than half of the sampling interval
self.readRetries = self.serial_readRetries
_log.debug('connect(): connecting')
time.sleep(.2) # avoid possible hickups on startup
if self.master is not None:
self.master.connect() # type:ignore[no-untyped-call,unused-ignore]
# # on connect() of serial connections we reset the inter_byte_timeout of the serial socket if serial_strict_timing is False
# if not self.serial_strict_timing and self.type in {0,1} and self.master.socket is not None and isinstance(self.master.socket, serial.Serial):
# self.master.socket.inter_byte_timeout = None
if self.isConnected():
self.updateActiveRegisters()
self.clearReadingsCache()
time.sleep(.3) # avoid possible hickups on startup
if self.type in {0,1}:
# respect the user defined connect delay on serial connections
time.sleep(self.modbus_serial_connect_delay)
else:
time.sleep(.3) # avoid possible hickups on startup
self.aw.sendmessage(QApplication.translate('Message', 'Connected via MODBUS'))
else:
self.aw.qmc.adderror(QApplication.translate('Error Message','Modbus Error: failed to connect'))
Expand Down
14 changes: 3 additions & 11 deletions src/artisanlib/ports.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,9 +681,9 @@ def __init__(self, parent:'QWidget', aw:'ApplicationWindow') -> None:


modbus_Serial_delaylabel = QLabel(QApplication.translate('Label', 'Delay'))
modbus_Serial_delaylabel.setToolTip(QApplication.translate('Tooltip', 'Extra delay in Milliseconds between MODBUS Serial commands'))
self.modbus_Serial_delayEdit = QLineEdit(str(int(self.aw.modbus.modbus_serial_extra_read_delay*1000)))
self.modbus_Serial_delayEdit.setValidator(self.aw.createCLocaleDoubleValidator(0,99,0,self.modbus_Serial_delayEdit))
modbus_Serial_delaylabel.setToolTip(QApplication.translate('Tooltip', 'Extra delay after connect in seconds before sending requests (needed by Arduino devices restarting on connect)'))
self.modbus_Serial_delayEdit = QLineEdit(str(self.aw.modbus.modbus_serial_connect_delay))
self.modbus_Serial_delayEdit.setValidator(self.aw.createCLocaleDoubleValidator(0,3,1,self.modbus_Serial_delayEdit))
self.modbus_Serial_delayEdit.setFixedWidth(50)
self.modbus_Serial_delayEdit.setAlignment(Qt.AlignmentFlag.AlignRight)
self.modbus_Serial_delayEdit.setToolTip(QApplication.translate('Tooltip', 'Extra delay in Milliseconds between MODBUS Serial commands'))
Expand Down Expand Up @@ -760,12 +760,6 @@ def __init__(self, parent:'QWidget', aw:'ApplicationWindow') -> None:
self.modbus_full_block.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.modbus_full_block.setEnabled(bool(self.aw.modbus.optimizer))

self.modbus_reset = QCheckBox(QApplication.translate('ComboBox','reset'))
self.modbus_reset.setChecked(self.aw.modbus.reset_socket)
self.modbus_reset.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.modbus_reset.setToolTip(QApplication.translate('Tooltip','Reset socket connection on error'))
self.modbus_reset.setEnabled(False)

########################## TAB 4 WIDGETS SCALE
scale_devicelabel = QLabel(QApplication.translate('Label', 'Device'))
self.scale_deviceEdit = QComboBox()
Expand Down Expand Up @@ -990,8 +984,6 @@ def __init__(self, parent:'QWidget', aw:'ApplicationWindow') -> None:
modbus_setup.addWidget(self.modbus_optimize)
modbus_setup.addSpacing(5)
modbus_setup.addWidget(self.modbus_full_block)
modbus_setup.addSpacing(5)
modbus_setup.addWidget(self.modbus_reset)
modbus_setup.addSpacing(7)
modbus_setup.addStretch()
modbus_setup.addWidget(modbus_typelabel)
Expand Down
1 change: 1 addition & 0 deletions src/artisanlib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ def setDeviceDebugLogLevel(state: bool) -> None:
if state:
# debug logging on
logging.getLogger('pymodbus.logging').setLevel(logging.DEBUG)
logging.getLogger('pymodbus.client').setLevel(logging.DEBUG)
_log.info('device debug logging ON')
else:
# debug logging off
Expand Down
Binary file modified src/translations/artisan_ar.qm
Binary file not shown.
Loading

0 comments on commit 601ffed

Please sign in to comment.