mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-10-26 10:51:49 -04:00
Prepared serial-based interfaces for live detach/attach
This commit is contained in:
parent
2c30a88e85
commit
7283cb417a
6 changed files with 144 additions and 110 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -130,6 +130,7 @@ class AX25KISSInterface(Interface):
|
|||
self.flow_control = flow_control
|
||||
self.shared_medium = True
|
||||
self.interface_ready = False
|
||||
self.reconnecting = False
|
||||
self.flow_control_timeout = 5
|
||||
self.flow_control_locked = time.time()
|
||||
|
||||
|
|
@ -152,14 +153,18 @@ class AX25KISSInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial port")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
raise e
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
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)
|
||||
|
|
@ -177,6 +182,12 @@ class AX25KISSInterface(Interface):
|
|||
dsrdtr = False,
|
||||
)
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def configure_device(self):
|
||||
# Allow time for interface to initialise before config
|
||||
sleep(2.0)
|
||||
|
|
@ -373,30 +384,28 @@ class AX25KISSInterface(Interface):
|
|||
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 now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
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()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
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)
|
||||
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))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "AX25KISSInterface["+self.name+"]"
|
||||
def __str__(self): return "AX25KISSInterface["+self.name+"]"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -128,6 +128,7 @@ class KISSInterface(Interface):
|
|||
self.beacon_d = beacon_data.encode("utf-8")
|
||||
self.first_tx = None
|
||||
self.bitrate = KISSInterface.BITRATE_GUESS
|
||||
self.reconnecting = False
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
|
|
@ -149,15 +150,23 @@ class KISSInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial port")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port "+self.port, RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
|
|
@ -417,24 +426,23 @@ class KISSInterface(Interface):
|
|||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
self.reconnecting = True
|
||||
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)
|
||||
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))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
def __str__(self): return "KISSInterface["+self.name+"]"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -109,6 +109,7 @@ class SerialInterface(Interface):
|
|||
self.timeout = 100
|
||||
self.online = False
|
||||
self.bitrate = self.speed
|
||||
self.reconnecting = False
|
||||
|
||||
if parity.lower() == "e" or parity.lower() == "even":
|
||||
self.parity = "E"
|
||||
|
|
@ -118,15 +119,23 @@ class SerialInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial 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")
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
|
|
@ -258,24 +267,23 @@ class SerialInterface(Interface):
|
|||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
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)
|
||||
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))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "SerialInterface["+self.name+"]"
|
||||
def __str__(self): return "SerialInterface["+self.name+"]"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -124,6 +124,7 @@ class KISSInterface(Interface):
|
|||
self.flow_control = flow_control
|
||||
self.shared_medium = True
|
||||
self.interface_ready = False
|
||||
self.reconnecting = False
|
||||
self.flow_control_timeout = 5
|
||||
self.flow_control_locked = time.time()
|
||||
|
||||
|
|
@ -140,15 +141,17 @@ class KISSInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial port")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port "+self.port, RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
|
||||
|
|
@ -166,6 +169,11 @@ class KISSInterface(Interface):
|
|||
dsrdtr = False,
|
||||
)
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def configure_device(self):
|
||||
# Allow time for interface to initialise before config
|
||||
|
|
@ -361,30 +369,28 @@ class KISSInterface(Interface):
|
|||
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 now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
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()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
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)
|
||||
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))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
def __str__(self): return "KISSInterface["+self.name+"]"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -97,6 +97,7 @@ class SerialInterface(Interface):
|
|||
self.timeout = 100
|
||||
self.online = False
|
||||
self.bitrate = self.speed
|
||||
self.reconnecting = False
|
||||
|
||||
if parity.lower() == "e" or parity.lower() == "even":
|
||||
self.parity = serial.PARITY_EVEN
|
||||
|
|
@ -106,15 +107,17 @@ class SerialInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial 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")
|
||||
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
|
||||
|
|
@ -132,6 +135,11 @@ class SerialInterface(Interface):
|
|||
dsrdtr = False,
|
||||
)
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def configure_device(self):
|
||||
sleep(0.5)
|
||||
|
|
@ -141,13 +149,11 @@ class SerialInterface(Interface):
|
|||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open", RNS.LOG_VERBOSE)
|
||||
|
||||
|
||||
def process_incoming(self, data):
|
||||
if not data: return
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
||||
def process_outgoing(self,data):
|
||||
if self.online:
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
|
|
@ -156,7 +162,6 @@ class SerialInterface(Interface):
|
|||
if written != len(data):
|
||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||
|
||||
|
||||
def readLoop(self):
|
||||
try:
|
||||
in_frame = False
|
||||
|
|
@ -200,30 +205,28 @@ class SerialInterface(Interface):
|
|||
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 now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
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()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
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)
|
||||
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))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "SerialInterface["+self.name+"]"
|
||||
def __str__(self): return "SerialInterface["+self.name+"]"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue