From 8be1acee0a455eec695176276e3e2591a5a8bafc Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sun, 5 Dec 2021 14:35:25 +0100 Subject: [PATCH] Added auto reconnection for disconnected serial-based devices --- RNS/Interfaces/AX25KISSInterface.py | 88 ++++++++++++++++---------- RNS/Interfaces/KISSInterface.py | 12 ++-- RNS/Interfaces/RNodeInterface.py | 95 ++++++++++++++++++----------- RNS/Interfaces/SerialInterface.py | 73 +++++++++++++++------- 4 files changed, 176 insertions(+), 92 deletions(-) diff --git a/RNS/Interfaces/AX25KISSInterface.py b/RNS/Interfaces/AX25KISSInterface.py index ec9a24c..350a1a1 100644 --- a/RNS/Interfaces/AX25KISSInterface.py +++ b/RNS/Interfaces/AX25KISSInterface.py @@ -58,6 +58,7 @@ class AX25KISSInterface(Interface): self.rxb = 0 self.txb = 0 + self.pyserial = serial self.serial = None self.owner = owner self.name = name @@ -97,44 +98,48 @@ class AX25KISSInterface(Interface): self.parity = serial.PARITY_ODD try: - RNS.log("Opening serial port "+self.port+"...") - self.serial = serial.Serial( - port = self.port, - baudrate = self.speed, - bytesize = self.databits, - parity = self.parity, - stopbits = self.stopbits, - xonxoff = False, - rtscts = False, - timeout = 0, - inter_byte_timeout = None, - write_timeout = None, - dsrdtr = False, - ) + self.open_port() except Exception as e: RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR) raise e if self.serial.is_open: - # Allow time for interface to initialise before config - sleep(2.0) - thread = threading.Thread(target=self.readLoop) - thread.setDaemon(True) - thread.start() - self.online = True - RNS.log("Serial port "+self.port+" is now open") - RNS.log("Configuring AX.25 KISS interface parameters...") - self.setPreamble(self.preamble) - self.setTxTail(self.txtail) - self.setPersistence(self.persistence) - self.setSlotTime(self.slottime) - self.setFlowControl(self.flow_control) - self.interface_ready = True - RNS.log("AX.25 KISS interface configured") - sleep(2) + self.configure_device() else: raise IOError("Could not open serial port") + def open_port(self): + RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE) + self.serial = self.pyserial.Serial( + port = self.port, + baudrate = self.speed, + bytesize = self.databits, + parity = self.parity, + stopbits = self.stopbits, + xonxoff = False, + rtscts = False, + timeout = 0, + inter_byte_timeout = None, + write_timeout = None, + dsrdtr = False, + ) + + def configure_device(self): + # Allow time for interface to initialise before config + sleep(2.0) + thread = threading.Thread(target=self.readLoop) + thread.setDaemon(True) + thread.start() + self.online = True + RNS.log("Serial port "+self.port+" is now open") + RNS.log("Configuring AX.25 KISS interface parameters...") + self.setPreamble(self.preamble) + self.setTxTail(self.txtail) + self.setPersistence(self.persistence) + self.setSlotTime(self.slottime) + self.setFlowControl(self.flow_control) + self.interface_ready = True + RNS.log("AX.25 KISS interface configured") def setPreamble(self, preamble): preamble_ms = preamble @@ -315,10 +320,29 @@ class AX25KISSInterface(Interface): except Exception as e: self.online = False RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) - RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR) - + RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR) + if RNS.Reticulum.panic_on_interface_error: RNS.panic() + RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR) + + self.online = False + self.serial.close() + self.reconnect_port() + + def reconnect_port(self): + while not self.online: + try: + time.sleep(5) + RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE) + self.open_port() + if self.serial.is_open: + self.configure_device() + except Exception as e: + RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR) + + RNS.log("Reconnected serial port for "+str(self)) + def __str__(self): return "AX25KISSInterface["+self.name+"]" \ No newline at end of file diff --git a/RNS/Interfaces/KISSInterface.py b/RNS/Interfaces/KISSInterface.py index f7ded2e..447b42c 100644 --- a/RNS/Interfaces/KISSInterface.py +++ b/RNS/Interfaces/KISSInterface.py @@ -98,7 +98,7 @@ class KISSInterface(Interface): def open_port(self): - RNS.log("Opening serial port "+self.port+"...") + RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE) self.serial = self.pyserial.Serial( port = self.port, baudrate = self.speed, @@ -299,11 +299,13 @@ class KISSInterface(Interface): except Exception as e: self.online = False RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) - RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR) - + RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR) + if RNS.Reticulum.panic_on_interface_error: RNS.panic() + RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR) + self.online = False self.serial.close() self.reconnect_port() @@ -312,12 +314,14 @@ class KISSInterface(Interface): while not self.online: try: time.sleep(5) - RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...") + RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE) self.open_port() if self.serial.is_open: self.configure_device() except Exception as e: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR) + RNS.log("Reconnected serial port for "+str(self)) + def __str__(self): return "KISSInterface["+self.name+"]" \ No newline at end of file diff --git a/RNS/Interfaces/RNodeInterface.py b/RNS/Interfaces/RNodeInterface.py index 79e41ba..cbe126b 100644 --- a/RNS/Interfaces/RNodeInterface.py +++ b/RNS/Interfaces/RNodeInterface.py @@ -82,6 +82,7 @@ class RNodeInterface(Interface): self.rxb = 0 self.txb = 0 + self.pyserial = serial self.serial = None self.owner = owner self.name = name @@ -157,46 +158,53 @@ class RNodeInterface(Interface): raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline") try: - RNS.log("Opening serial port "+self.port+"...") - self.serial = serial.Serial( - port = self.port, - baudrate = self.speed, - bytesize = self.databits, - parity = self.parity, - stopbits = self.stopbits, - xonxoff = False, - rtscts = False, - timeout = 0, - inter_byte_timeout = None, - write_timeout = None, - dsrdtr = False, - ) + self.open_port() except Exception as e: RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR) raise e if self.serial.is_open: - sleep(2.0) - thread = threading.Thread(target=self.readLoop) - thread.setDaemon(True) - thread.start() - self.online = True - RNS.log("Serial port "+self.port+" is now open") - RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE) - self.initRadio() - if (self.validateRadioState()): - self.interface_ready = True - RNS.log(str(self)+" is configured and powered up") - sleep(1.0) - else: - RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR) - RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR) - RNS.log("Aborting RNode startup", RNS.LOG_ERROR) - self.serial.close() - raise IOError("RNode interface did not pass validation") + self.configure_device() else: raise IOError("Could not open serial port") + def open_port(self): + RNS.log("Opening serial port "+self.port+"...") + self.serial = self.pyserial.Serial( + port = self.port, + baudrate = self.speed, + bytesize = self.databits, + parity = self.parity, + stopbits = self.stopbits, + xonxoff = False, + rtscts = False, + timeout = 0, + inter_byte_timeout = None, + write_timeout = None, + dsrdtr = False, + ) + + + def configure_device(self): + sleep(2.0) + thread = threading.Thread(target=self.readLoop) + thread.setDaemon(True) + thread.start() + self.online = True + RNS.log("Serial port "+self.port+" is now open") + RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE) + self.initRadio() + if (self.validateRadioState()): + self.interface_ready = True + RNS.log(str(self)+" is configured and powered up") + sleep(1.0) + else: + RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR) + RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR) + RNS.log("Aborting RNode startup", RNS.LOG_ERROR) + self.serial.close() + raise IOError("RNode interface did not pass validation") + def initRadio(self): self.setFrequency() @@ -478,11 +486,30 @@ class RNodeInterface(Interface): except Exception as e: self.online = False RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) - RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR) - + RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR) + if RNS.Reticulum.panic_on_interface_error: RNS.panic() + RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR) + + self.online = False + self.serial.close() + self.reconnect_port() + + def reconnect_port(self): + while not self.online: + try: + time.sleep(5) + RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE) + self.open_port() + if self.serial.is_open: + self.configure_device() + except Exception as e: + RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR) + + RNS.log("Reconnected serial port for "+str(self)) + def __str__(self): return "RNodeInterface["+self.name+"]" diff --git a/RNS/Interfaces/SerialInterface.py b/RNS/Interfaces/SerialInterface.py index 7fa699f..f26006a 100755 --- a/RNS/Interfaces/SerialInterface.py +++ b/RNS/Interfaces/SerialInterface.py @@ -41,6 +41,7 @@ class SerialInterface(Interface): self.rxb = 0 self.txb = 0 + self.pyserial = serial self.serial = None self.owner = owner self.name = name @@ -59,35 +60,43 @@ class SerialInterface(Interface): self.parity = serial.PARITY_ODD try: - RNS.log("Opening serial port "+self.port+"...") - self.serial = serial.Serial( - port = self.port, - baudrate = self.speed, - bytesize = self.databits, - parity = self.parity, - stopbits = self.stopbits, - xonxoff = False, - rtscts = False, - timeout = 0, - inter_byte_timeout = None, - write_timeout = None, - dsrdtr = False, - ) + self.open_port() except Exception as e: RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR) raise e if self.serial.is_open: - sleep(0.5) - thread = threading.Thread(target=self.readLoop) - thread.setDaemon(True) - thread.start() - self.online = True - RNS.log("Serial port "+self.port+" is now open") + self.configure_device() else: raise IOError("Could not open serial port") + def open_port(self): + RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE) + self.serial = self.pyserial.Serial( + port = self.port, + baudrate = self.speed, + bytesize = self.databits, + parity = self.parity, + stopbits = self.stopbits, + xonxoff = False, + rtscts = False, + timeout = 0, + inter_byte_timeout = None, + write_timeout = None, + dsrdtr = False, + ) + + + def configure_device(self): + sleep(0.5) + thread = threading.Thread(target=self.readLoop) + thread.setDaemon(True) + thread.start() + self.online = True + RNS.log("Serial port "+self.port+" is now open") + + def processIncoming(self, data): self.rxb += len(data) self.owner.inbound(data, self) @@ -139,13 +148,33 @@ class SerialInterface(Interface): in_frame = False escape = False sleep(0.08) + except Exception as e: self.online = False RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) - RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR) - + RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR) + if RNS.Reticulum.panic_on_interface_error: RNS.panic() + RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR) + + self.online = False + self.serial.close() + self.reconnect_port() + + def reconnect_port(self): + while not self.online: + try: + time.sleep(5) + RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE) + self.open_port() + if self.serial.is_open: + self.configure_device() + except Exception as e: + RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR) + + RNS.log("Reconnected serial port for "+str(self)) + def __str__(self): return "SerialInterface["+self.name+"]"