Added auto reconnection for disconnected serial-based devices

This commit is contained in:
Mark Qvist 2021-12-05 14:35:25 +01:00
parent ba39a69175
commit 8be1acee0a
4 changed files with 176 additions and 92 deletions

View File

@ -58,6 +58,7 @@ class AX25KISSInterface(Interface):
self.rxb = 0 self.rxb = 0
self.txb = 0 self.txb = 0
self.pyserial = serial
self.serial = None self.serial = None
self.owner = owner self.owner = owner
self.name = name self.name = name
@ -97,8 +98,19 @@ class AX25KISSInterface(Interface):
self.parity = serial.PARITY_ODD self.parity = serial.PARITY_ODD
try: try:
RNS.log("Opening serial port "+self.port+"...") self.open_port()
self.serial = serial.Serial( 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:
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, port = self.port,
baudrate = self.speed, baudrate = self.speed,
bytesize = self.databits, bytesize = self.databits,
@ -111,11 +123,8 @@ class AX25KISSInterface(Interface):
write_timeout = None, write_timeout = None,
dsrdtr = False, dsrdtr = False,
) )
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: def configure_device(self):
# Allow time for interface to initialise before config # Allow time for interface to initialise before config
sleep(2.0) sleep(2.0)
thread = threading.Thread(target=self.readLoop) thread = threading.Thread(target=self.readLoop)
@ -131,10 +140,6 @@ class AX25KISSInterface(Interface):
self.setFlowControl(self.flow_control) self.setFlowControl(self.flow_control)
self.interface_ready = True self.interface_ready = True
RNS.log("AX.25 KISS interface configured") RNS.log("AX.25 KISS interface configured")
sleep(2)
else:
raise IOError("Could not open serial port")
def setPreamble(self, preamble): def setPreamble(self, preamble):
preamble_ms = preamble preamble_ms = preamble
@ -315,10 +320,29 @@ class AX25KISSInterface(Interface):
except Exception as e: except Exception as e:
self.online = False self.online = False
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) 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: if RNS.Reticulum.panic_on_interface_error:
RNS.panic() 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): def __str__(self):
return "AX25KISSInterface["+self.name+"]" return "AX25KISSInterface["+self.name+"]"

View File

@ -98,7 +98,7 @@ class KISSInterface(Interface):
def open_port(self): 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( self.serial = self.pyserial.Serial(
port = self.port, port = self.port,
baudrate = self.speed, baudrate = self.speed,
@ -299,11 +299,13 @@ class KISSInterface(Interface):
except Exception as e: except Exception as e:
self.online = False self.online = False
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) 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: if RNS.Reticulum.panic_on_interface_error:
RNS.panic() RNS.panic()
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
self.online = False self.online = False
self.serial.close() self.serial.close()
self.reconnect_port() self.reconnect_port()
@ -312,12 +314,14 @@ class KISSInterface(Interface):
while not self.online: while not self.online:
try: try:
time.sleep(5) 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() self.open_port()
if self.serial.is_open: if self.serial.is_open:
self.configure_device() self.configure_device()
except Exception as e: except Exception as e:
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR) 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): def __str__(self):
return "KISSInterface["+self.name+"]" return "KISSInterface["+self.name+"]"

View File

@ -82,6 +82,7 @@ class RNodeInterface(Interface):
self.rxb = 0 self.rxb = 0
self.txb = 0 self.txb = 0
self.pyserial = serial
self.serial = None self.serial = None
self.owner = owner self.owner = owner
self.name = name self.name = name
@ -157,8 +158,19 @@ class RNodeInterface(Interface):
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline") raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
try: try:
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:
self.configure_device()
else:
raise IOError("Could not open serial port")
def open_port(self):
RNS.log("Opening serial port "+self.port+"...") RNS.log("Opening serial port "+self.port+"...")
self.serial = serial.Serial( self.serial = self.pyserial.Serial(
port = self.port, port = self.port,
baudrate = self.speed, baudrate = self.speed,
bytesize = self.databits, bytesize = self.databits,
@ -171,11 +183,9 @@ class RNodeInterface(Interface):
write_timeout = None, write_timeout = None,
dsrdtr = False, dsrdtr = False,
) )
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:
def configure_device(self):
sleep(2.0) sleep(2.0)
thread = threading.Thread(target=self.readLoop) thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True) thread.setDaemon(True)
@ -194,8 +204,6 @@ class RNodeInterface(Interface):
RNS.log("Aborting RNode startup", RNS.LOG_ERROR) RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
self.serial.close() self.serial.close()
raise IOError("RNode interface did not pass validation") raise IOError("RNode interface did not pass validation")
else:
raise IOError("Could not open serial port")
def initRadio(self): def initRadio(self):
@ -478,11 +486,30 @@ class RNodeInterface(Interface):
except Exception as e: except Exception as e:
self.online = False self.online = False
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) 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: if RNS.Reticulum.panic_on_interface_error:
RNS.panic() 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): def __str__(self):
return "RNodeInterface["+self.name+"]" return "RNodeInterface["+self.name+"]"

View File

@ -41,6 +41,7 @@ class SerialInterface(Interface):
self.rxb = 0 self.rxb = 0
self.txb = 0 self.txb = 0
self.pyserial = serial
self.serial = None self.serial = None
self.owner = owner self.owner = owner
self.name = name self.name = name
@ -59,8 +60,20 @@ class SerialInterface(Interface):
self.parity = serial.PARITY_ODD self.parity = serial.PARITY_ODD
try: try:
RNS.log("Opening serial port "+self.port+"...") self.open_port()
self.serial = serial.Serial( 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:
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, port = self.port,
baudrate = self.speed, baudrate = self.speed,
bytesize = self.databits, bytesize = self.databits,
@ -73,19 +86,15 @@ class SerialInterface(Interface):
write_timeout = None, write_timeout = None,
dsrdtr = False, dsrdtr = False,
) )
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:
def configure_device(self):
sleep(0.5) sleep(0.5)
thread = threading.Thread(target=self.readLoop) thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
self.online = True self.online = True
RNS.log("Serial port "+self.port+" is now open") RNS.log("Serial port "+self.port+" is now open")
else:
raise IOError("Could not open serial port")
def processIncoming(self, data): def processIncoming(self, data):
@ -139,13 +148,33 @@ class SerialInterface(Interface):
in_frame = False in_frame = False
escape = False escape = False
sleep(0.08) sleep(0.08)
except Exception as e: except Exception as e:
self.online = False self.online = False
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) 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: if RNS.Reticulum.panic_on_interface_error:
RNS.panic() 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): def __str__(self):
return "SerialInterface["+self.name+"]" return "SerialInterface["+self.name+"]"