diff --git a/RNS/Interfaces/KISSInterface.py b/RNS/Interfaces/KISSInterface.py index 4661e03..c877459 100644 --- a/RNS/Interfaces/KISSInterface.py +++ b/RNS/Interfaces/KISSInterface.py @@ -39,7 +39,7 @@ class KISSInterface(Interface): stopbits = None serial = None - def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control): + def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control, beacon_interval, beacon_data): self.serial = None self.owner = owner self.name = name @@ -50,10 +50,15 @@ class KISSInterface(Interface): self.stopbits = stopbits self.timeout = 100 self.online = False + self.beacon_i = beacon_interval + self.beacon_d = beacon_data.encode("utf-8") + self.first_tx = None self.packet_queue = [] self.flow_control = flow_control self.interface_ready = False + self.flow_control_timeout = 10 + self.flow_control_locked = time.time() self.preamble = preamble if preamble != None else 350; self.txtail = txtail if txtail != None else 20; @@ -174,12 +179,20 @@ class KISSInterface(Interface): if self.interface_ready: if self.flow_control: self.interface_ready = False + self.flow_control_locked = time.time() data = data.replace(bytes([0xdb]), bytes([0xdb])+bytes([0xdd])) data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc])) frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND]) written = self.serial.write(frame) + + if data == self.beacon_d: + self.first_tx = None + else: + if self.first_tx == None: + self.first_tx = time.time() + if written != len(frame): raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data))) @@ -235,8 +248,6 @@ class KISSInterface(Interface): escape = False data_buffer = data_buffer+bytes([byte]) elif (command == KISS.CMD_READY): - # TODO: add timeout and reset if ready - # command never arrives self.process_queue() else: time_since_last = int(time.time()*1000) - last_read_ms @@ -247,6 +258,19 @@ class KISSInterface(Interface): escape = False sleep(0.08) + if self.flow_control: + if not self.interface_ready: + if time.time() > self.flow_control_locked + self.flow_control_timeout: + RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command.", RNS.LOG_WARNING) + self.process_queue() + + if self.beacon_i != None and self.beacon_d != None: + if self.first_tx != None: + if time.time() > self.first_tx + self.beacon_i: + RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.beacon_d.decode("utf-8")), RNS.LOG_DEBUG) + self.first_tx = None + self.processOutgoing(self.beacon_d) + except Exception as e: self.online = False RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) diff --git a/RNS/Interfaces/RNodeInterface.py b/RNS/Interfaces/RNodeInterface.py index b434f03..13be2b9 100644 --- a/RNS/Interfaces/RNodeInterface.py +++ b/RNS/Interfaces/RNodeInterface.py @@ -92,6 +92,7 @@ class RNodeInterface(Interface): self.bitrate = 0 self.last_id = 0 + self.first_tx = None self.r_frequency = None self.r_bandwidth = None @@ -133,7 +134,7 @@ class RNodeInterface(Interface): if id_interval != None and id_callsign != None: if (len(id_callsign.encode("utf-8")) <= RNodeInterface.CALLSIGN_MAX_LEN): self.should_id = True - self.id_callsign = id_callsign + self.id_callsign = id_callsign.encode("utf-8") self.id_interval = id_interval else: RNS.log("The encoded ID callsign for "+str(self)+" exceeds the max length of "+str(RNodeInterface.CALLSIGN_MAX_LEN)+" bytes.", RNS.LOG_ERROR) @@ -286,15 +287,15 @@ class RNodeInterface(Interface): if self.flow_control: self.interface_ready = False - frame = b"" - - if self.id_interval != None and self.id_callsign != None: - if self.last_id + self.id_interval < time.time(): - self.last_id = time.time() - frame = bytes([0xc0])+bytes([0x00])+KISS.escape(self.id_callsign.encode("utf-8"))+bytes([0xc0]) + if data == self.id_callsign: + self.first_tx = None + else: + if self.first_tx == None: + self.first_tx = time.time() data = KISS.escape(data) - frame += bytes([0xc0])+bytes([0x00])+data+bytes([0xc0]) + frame = bytes([0xc0])+bytes([0x00])+data+bytes([0xc0]) + written = self.serial.write(frame) if written != len(frame): @@ -450,6 +451,13 @@ class RNodeInterface(Interface): in_frame = False command = KISS.CMD_UNKNOWN escape = False + + if self.id_interval != None and self.id_callsign != None: + if self.first_tx != None: + if time.time() > self.first_tx + self.id_interval: + RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.id_callsign.decode("utf-8")), RNS.LOG_DEBUG) + self.processOutgoing(self.id_callsign) + sleep(0.08) except Exception as e: diff --git a/RNS/Reticulum.py b/RNS/Reticulum.py index 21e844f..ea95f2e 100755 --- a/RNS/Reticulum.py +++ b/RNS/Reticulum.py @@ -256,6 +256,8 @@ class Reticulum: databits = int(c["databits"]) if "databits" in c else 8 parity = c["parity"] if "parity" in c else "N" stopbits = int(c["stopbits"]) if "stopbits" in c else 1 + beacon_interval = int(c["id_interval"]) if "id_interval" in c else None + beacon_data = c["id_callsign"] if "id_callsign" in c else None if port == None: raise ValueError("No port specified for serial interface") @@ -272,7 +274,9 @@ class Reticulum: txtail, persistence, slottime, - flow_control + flow_control, + beacon_interval, + beacon_data ) if "outgoing" in c and c["outgoing"].lower() == "true": @@ -542,16 +546,22 @@ loglevel = 4 # out identification on the channel with # a set interval by configuring the # following two parameters. The trans- - # ceiver will only ID before making an - # actual transmission, and if the set + # ceiver will only ID if the set # interval has elapsed since it's last - # ID. Interval is configured in seconds + # actual transmission. The interval is + # configured in seconds. # This option is commented out and not # used by default. # id_callsign = MYCALL-0 # id_interval = 600 - + # For certain homebrew RNode interfaces + # with low amounts of RAM, using packet + # flow control can be useful. By default + # it is disabled. + flow_control = False + + # An example KISS modem interface. Useful for running # Reticulum over packet radio hardware. @@ -574,11 +584,6 @@ loglevel = 4 parity = none stopbits = 1 - # Whether to use KISS flow-control. - # This is useful for modems with a - # small internal packet buffer. - flow_control = false - # Set the modem preamble. A 150ms # preamble should be a reasonable # default, but may need to be @@ -597,6 +602,25 @@ loglevel = 4 persistence = 200 slottime = 20 + # You can configure the interface to send + # out identification on the channel with + # a set interval by configuring the + # following two parameters. The KISS + # interface will only ID if the set + # interval has elapsed since it's last + # actual transmission. The interval is + # configured in seconds. + # This option is commented out and not + # used by default. + # id_callsign = MYCALL-0 + # id_interval = 600 + + # Whether to use KISS flow-control. + # This is useful for modems that have + # a small internal packet buffer, but + # support packet flow control instead. + flow_control = false + # If you're using Reticulum on amateur radio spectrum, # you might want to use the AX.25 KISS interface. This @@ -607,6 +631,9 @@ loglevel = 4 # Only do this if you really need to! Reticulum doesn't # need the AX.25 layer for anything, and it incurs extra # overhead on every packet to encapsulate in AX.25. + # + # A more efficient way is to use the plain KISS interface + # with the beaconing functionality described above. [[Packet Radio AX.25 KISS Interface]] type = AX25KISSInterface diff --git a/setup.py b/setup.py index 699eb04..59db174 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open("README.md", "r") as fh: setuptools.setup( name="rns", - version="0.1.7", + version="0.1.8", author="Mark Qvist", author_email="mark@unsigned.io", description="Self-configuring, encrypted and resilient mesh networking stack for LoRa, packet radio, WiFi and everything in between",