diff --git a/README.md b/README.md index 8166c83..6cfbad3 100755 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Reticulum is a cryptography-based networking stack for low-bandwidth, high-laten Reticulum is a complete networking stack, and does not use IP or higher layers, although it can be easily tunnelled through conventional IP networks. This frees up a lot of overhead, that has been utilised to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks. -Reticulum runs completely in userland, and can run on practically any system that runs Python. +Reticulum runs completely in userland, and can run on practically any system that runs Python 3. For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects/reticulum/) @@ -47,9 +47,9 @@ An API- and wireformat-stable alpha release is coming in the near future. Until Some countries still ban the use of encryption when operating under an amateur radio license. Reticulum offers several encryptionless modes, while still using cryptographic principles for station verification, link establishment, data integrity verification, acknowledgements and routing. It is therefore perfectly possible to include Reticulum in amateur radio use, even if your country bans encryption. ## Dependencies: - - Python 2.7 + - Python 3 - cryptography.io - - pyserial 3.1 + - pyserial ## How do I get started? Full documentation and video tutorials are coming with the stable alpha release. Until then, you are mostly on your own. If you really want to experiment already, you could take a look in the "Examples" folder, for some well-documented example programs. Be sure to also read the [Reticulum Overview Document](http://unsigned.io/wp-content/uploads/2018/04/Reticulum_Overview_v0.4.pdf). @@ -58,7 +58,7 @@ To install dependencies and get started: ``` # Install dependencies -pip install cryptography pyserial +pip3 install cryptography pyserial # Clone repository git clone https://github.com/markqvist/Reticulum.git diff --git a/RNS/Interfaces/AX25KISSInterface.py b/RNS/Interfaces/AX25KISSInterface.py index 54bedb3..ca13a96 100644 --- a/RNS/Interfaces/AX25KISSInterface.py +++ b/RNS/Interfaces/AX25KISSInterface.py @@ -8,25 +8,25 @@ import time import RNS class KISS(): - FEND = chr(0xC0) - FESC = chr(0xDB) - TFEND = chr(0xDC) - TFESC = chr(0xDD) - CMD_UNKNOWN = chr(0xFE) - CMD_DATA = chr(0x00) - CMD_TXDELAY = chr(0x01) - CMD_P = chr(0x02) - CMD_SLOTTIME = chr(0x03) - CMD_TXTAIL = chr(0x04) - CMD_FULLDUPLEX = chr(0x05) - CMD_SETHARDWARE = chr(0x06) - CMD_READY = chr(0x0F) - CMD_RETURN = chr(0xFF) + FEND = 0xC0 + FESC = 0xDB + TFEND = 0xDC + TFESC = 0xDD + CMD_UNKNOWN = 0xFE + CMD_DATA = 0x00 + CMD_TXDELAY = 0x01 + CMD_P = 0x02 + CMD_SLOTTIME = 0x03 + CMD_TXTAIL = 0x04 + CMD_FULLDUPLEX = 0x05 + CMD_SETHARDWARE = 0x06 + CMD_READY = 0x0F + CMD_RETURN = 0xFF class AX25(): - PID_NOLAYER3 = chr(0xF0) - CTRL_UI = chr(0x03) - CRC_CORRECT = chr(0xF0)+chr(0xB8) + PID_NOLAYER3 = 0xF0 + CTRL_UI = 0x03 + CRC_CORRECT = bytes([0xF0])+bytes([0xB8]) HEADER_SIZE = 16 @@ -45,9 +45,9 @@ class AX25KISSInterface(Interface): self.serial = None self.owner = owner self.name = name - self.src_call = callsign.upper() + self.src_call = callsign.upper().encode("ascii") self.src_ssid = ssid - self.dst_call = "APZRNS" + self.dst_call = "APZRNS".encode("ascii") self.dst_ssid = 0 self.port = port self.speed = speed @@ -57,8 +57,8 @@ class AX25KISSInterface(Interface): self.timeout = 100 self.online = False # TODO: Sane default and make this configurable - # TODO: Changed to 1ms instead of 100ms, check it - self.txdelay = 0.001 + # TODO: Changed to 25ms instead of 100ms, check it + self.txdelay = 0.025 self.packet_queue = [] self.flow_control = flow_control @@ -129,7 +129,7 @@ class AX25KISSInterface(Interface): if preamble > 255: preamble = 255 - kiss_command = KISS.FEND+KISS.CMD_TXDELAY+chr(preamble)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXDELAY])+bytes([preamble])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("Could not configure AX.25 KISS interface preamble to "+str(preamble_ms)+" (command value "+str(preamble)+")") @@ -142,7 +142,7 @@ class AX25KISSInterface(Interface): if txtail > 255: txtail = 255 - kiss_command = KISS.FEND+KISS.CMD_TXTAIL+chr(txtail)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXTAIL])+bytes([txtail])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("Could not configure AX.25 KISS interface TX tail to "+str(txtail_ms)+" (command value "+str(txtail)+")") @@ -153,7 +153,7 @@ class AX25KISSInterface(Interface): if persistence > 255: persistence = 255 - kiss_command = KISS.FEND+KISS.CMD_P+chr(persistence)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_P])+bytes([persistence])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("Could not configure AX.25 KISS interface persistence to "+str(persistence)) @@ -166,13 +166,13 @@ class AX25KISSInterface(Interface): if slottime > 255: slottime = 255 - kiss_command = KISS.FEND+KISS.CMD_SLOTTIME+chr(slottime)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SLOTTIME])+bytes([slottime])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("Could not configure AX.25 KISS interface slot time to "+str(slottime_ms)+" (command value "+str(slottime)+")") def setFlowControl(self, flow_control): - kiss_command = KISS.FEND+KISS.CMD_READY+chr(0x01)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_READY])+bytes([0x01])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): if (flow_control): @@ -192,30 +192,30 @@ class AX25KISSInterface(Interface): if self.flow_control: self.interface_ready = False - encoded_dst_ssid = 0x60 | (self.dst_ssid << 1) - encoded_src_ssid = 0x60 | (self.src_ssid << 1) | 0x01 + encoded_dst_ssid = bytes([0x60 | (self.dst_ssid << 1)]) + encoded_src_ssid = bytes([0x60 | (self.src_ssid << 1) | 0x01]) - addr = "" + addr = b"" for i in range(0,6): if (i < len(self.dst_call)): - addr += chr(ord(self.dst_call[i])<<1) + addr += bytes([self.dst_call[i]<<1]) else: - addr += chr(0x20) - addr += chr(encoded_dst_ssid) + addr += bytes([0x20]) + addr += encoded_dst_ssid for i in range(0,6): if (i < len(self.src_call)): - addr += chr(ord(self.src_call[i])<<1) + addr += bytes([self.src_call[i]<<1]) else: - addr += chr(0x20) - addr += chr(encoded_src_ssid) + addr += bytes([0x20]) + addr += encoded_src_ssid - data = addr+AX25.CTRL_UI+AX25.PID_NOLAYER3+data + data = addr+bytes([AX25.CTRL_UI])+bytes([AX25.PID_NOLAYER3])+data - data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd)) - data = data.replace(chr(0xc0), chr(0xdb)+chr(0xdc)) - kiss_frame = chr(0xc0)+chr(0x00)+data+chr(0xc0) + data = data.replace(bytes([0xdb]), bytes([0xdb])+bytes([0xdd])) + data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc])) + kiss_frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND]) if (self.txdelay > 0): RNS.log(str(self.name)+" delaying TX for "+str(self.txdelay)+" seconds", RNS.LOG_EXTREME) @@ -250,7 +250,7 @@ class AX25KISSInterface(Interface): while self.serial.is_open: if self.serial.in_waiting: - byte = self.serial.read(1) + byte = ord(self.serial.read(1)) last_read_ms = int(time.time()*1000) if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA): @@ -259,12 +259,12 @@ class AX25KISSInterface(Interface): elif (byte == KISS.FEND): in_frame = True command = KISS.CMD_UNKNOWN - data_buffer = "" + data_buffer = b"" elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU+AX25.HEADER_SIZE): if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN): # We only support one HDLC port for now, so # strip off the port nibble - byte = chr(ord(byte) & 0x0F) + byte = byte & 0x0F command = byte elif (command == KISS.CMD_DATA): if (byte == KISS.FESC): @@ -276,7 +276,7 @@ class AX25KISSInterface(Interface): if (byte == KISS.TFESC): byte = KISS.FESC escape = False - data_buffer = data_buffer+byte + data_buffer = data_buffer+bytes([byte]) elif (command == KISS.CMD_READY): # TODO: add timeout and reset if ready # command never arrives @@ -296,5 +296,4 @@ class AX25KISSInterface(Interface): RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR) def __str__(self): - return "AX25KISSInterface["+self.name+"]" - + return "AX25KISSInterface["+self.name+"]" \ No newline at end of file diff --git a/RNS/Interfaces/KISSInterface.py b/RNS/Interfaces/KISSInterface.py index 49cc57d..e2fef24 100644 --- a/RNS/Interfaces/KISSInterface.py +++ b/RNS/Interfaces/KISSInterface.py @@ -8,20 +8,20 @@ import time import RNS class KISS(): - FEND = chr(0xC0) - FESC = chr(0xDB) - TFEND = chr(0xDC) - TFESC = chr(0xDD) - CMD_UNKNOWN = chr(0xFE) - CMD_DATA = chr(0x00) - CMD_TXDELAY = chr(0x01) - CMD_P = chr(0x02) - CMD_SLOTTIME = chr(0x03) - CMD_TXTAIL = chr(0x04) - CMD_FULLDUPLEX = chr(0x05) - CMD_SETHARDWARE = chr(0x06) - CMD_READY = chr(0x0F) - CMD_RETURN = chr(0xFF) + FEND = 0xC0 + FESC = 0xDB + TFEND = 0xDC + TFESC = 0xDD + CMD_UNKNOWN = 0xFE + CMD_DATA = 0x00 + CMD_TXDELAY = 0x01 + CMD_P = 0x02 + CMD_SLOTTIME = 0x03 + CMD_TXTAIL = 0x04 + CMD_FULLDUPLEX = 0x05 + CMD_SETHARDWARE = 0x06 + CMD_READY = 0x0F + CMD_RETURN = 0xFF class KISSInterface(Interface): MAX_CHUNK = 32768 @@ -108,8 +108,7 @@ class KISSInterface(Interface): if preamble > 255: preamble = 255 - RNS.log("Setting preamble to "+str(preamble)+" "+chr(preamble)) - kiss_command = KISS.FEND+KISS.CMD_TXDELAY+chr(preamble)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXDELAY])+bytes([preamble])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("Could not configure KISS interface preamble to "+str(preamble_ms)+" (command value "+str(preamble)+")") @@ -122,7 +121,7 @@ class KISSInterface(Interface): if txtail > 255: txtail = 255 - kiss_command = KISS.FEND+KISS.CMD_TXTAIL+chr(txtail)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXTAIL])+bytes([txtail])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("Could not configure KISS interface TX tail to "+str(txtail_ms)+" (command value "+str(txtail)+")") @@ -133,7 +132,7 @@ class KISSInterface(Interface): if persistence > 255: persistence = 255 - kiss_command = KISS.FEND+KISS.CMD_P+chr(persistence)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_P])+bytes([persistence])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("Could not configure KISS interface persistence to "+str(persistence)) @@ -146,13 +145,13 @@ class KISSInterface(Interface): if slottime > 255: slottime = 255 - kiss_command = KISS.FEND+KISS.CMD_SLOTTIME+chr(slottime)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SLOTTIME])+bytes([slottime])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("Could not configure KISS interface slot time to "+str(slottime_ms)+" (command value "+str(slottime)+")") def setFlowControl(self, flow_control): - kiss_command = KISS.FEND+KISS.CMD_READY+chr(0x01)+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_READY])+bytes([0x01])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): if (flow_control): @@ -171,9 +170,10 @@ class KISSInterface(Interface): if self.flow_control: self.interface_ready = False - data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd)) - data = data.replace(chr(0xc0), chr(0xdb)+chr(0xdc)) - frame = chr(0xc0)+chr(0x00)+data+chr(0xc0) + 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 written != len(frame): raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data))) @@ -202,7 +202,7 @@ class KISSInterface(Interface): while self.serial.is_open: if self.serial.in_waiting: - byte = self.serial.read(1) + byte = ord(self.serial.read(1)) last_read_ms = int(time.time()*1000) if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA): @@ -211,12 +211,12 @@ class KISSInterface(Interface): elif (byte == KISS.FEND): in_frame = True command = KISS.CMD_UNKNOWN - data_buffer = "" + data_buffer = b"" elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU): if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN): # We only support one HDLC port for now, so - # strip off port nibble - byte = chr(ord(byte) & 0x0F) + # strip off the port nibble + byte = byte & 0x0F command = byte elif (command == KISS.CMD_DATA): if (byte == KISS.FESC): @@ -228,7 +228,7 @@ class KISSInterface(Interface): if (byte == KISS.TFESC): byte = KISS.FESC escape = False - data_buffer = data_buffer+byte + data_buffer = data_buffer+bytes([byte]) elif (command == KISS.CMD_READY): # TODO: add timeout and reset if ready # command never arrives @@ -248,5 +248,4 @@ class KISSInterface(Interface): RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR) def __str__(self): - return "KISSInterface["+self.name+"]" - + return "KISSInterface["+self.name+"]" \ No newline at end of file diff --git a/RNS/Interfaces/RNodeInterface.py b/RNS/Interfaces/RNodeInterface.py index 3ac782e..4cb7c54 100644 --- a/RNS/Interfaces/RNodeInterface.py +++ b/RNS/Interfaces/RNodeInterface.py @@ -9,46 +9,47 @@ import math import RNS class KISS(): - FEND = chr(0xC0) - FESC = chr(0xDB) - TFEND = chr(0xDC) - TFESC = chr(0xDD) + FEND = 0xC0 + FESC = 0xDB + TFEND = 0xDC + TFESC = 0xDD - CMD_UNKNOWN = chr(0xFE) - CMD_DATA = chr(0x00) - CMD_FREQUENCY = chr(0x01) - CMD_BANDWIDTH = chr(0x02) - CMD_TXPOWER = chr(0x03) - CMD_SF = chr(0x04) - CMD_CR = chr(0x05) - CMD_RADIO_STATE = chr(0x06) - CMD_RADIO_LOCK = chr(0x07) - CMD_DETECT = chr(0x08) - CMD_READY = chr(0x0F) - CMD_STAT_RX = chr(0x21) - CMD_STAT_TX = chr(0x22) - CMD_STAT_RSSI = chr(0x23) - CMD_BLINK = chr(0x30) - CMD_RANDOM = chr(0x40) - CMD_FW_VERSION = chr(0x50) - CMD_ROM_READ = chr(0x51) + CMD_UNKNOWN = 0xFE + CMD_DATA = 0x00 + CMD_FREQUENCY = 0x01 + CMD_BANDWIDTH = 0x02 + CMD_TXPOWER = 0x03 + CMD_SF = 0x04 + CMD_CR = 0x05 + CMD_RADIO_STATE = 0x06 + CMD_RADIO_LOCK = 0x07 + CMD_DETECT = 0x08 + CMD_READY = 0x0F + CMD_STAT_RX = 0x21 + CMD_STAT_TX = 0x22 + CMD_STAT_RSSI = 0x23 + CMD_STAT_SNR = 0x24 + CMD_BLINK = 0x30 + CMD_RANDOM = 0x40 + CMD_FW_VERSION = 0x50 + CMD_ROM_READ = 0x51 - DETECT_REQ = chr(0x73) - DETECT_RESP = chr(0x46) + DETECT_REQ = 0x73 + DETECT_RESP = 0x46 - RADIO_STATE_OFF = chr(0x00) - RADIO_STATE_ON = chr(0x01) - RADIO_STATE_ASK = chr(0xFF) + RADIO_STATE_OFF = 0x00 + RADIO_STATE_ON = 0x01 + RADIO_STATE_ASK = 0xFF - CMD_ERROR = chr(0x90) - ERROR_INITRADIO = chr(0x01) - ERROR_TXFAILED = chr(0x02) - ERROR_EEPROM_LOCKED = chr(0x03) + CMD_ERROR = 0x90 + ERROR_INITRADIO = 0x01 + ERROR_TXFAILED = 0x02 + ERROR_EEPROM_LOCKED = 0x03 @staticmethod def escape(data): - data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd)) - data = data.replace(chr(0xc0), chr(0xdb)+chr(0xdc)) + data = data.replace(bytes([0xdb]), bytes([0xdb])+bytes([0xdd])) + data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc])) return data @@ -66,9 +67,10 @@ class RNodeInterface(Interface): FREQ_MIN = 137000000 FREQ_MAX = 1020000000 - RSSI_OFFSET = 292 + RSSI_OFFSET = 157 + SNR_OFFSET = 128 - def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, flow_control = True): + def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, flow_control = False): self.serial = None self.owner = owner self.name = name @@ -157,7 +159,7 @@ class RNodeInterface(Interface): RNS.log(str(self)+" is configured and powered up") sleep(1.0) else: - RNS.log("After configuring "+str(self)+", the actual radio parameters did not match your configuration.", RNS.LOG_ERROR) + 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() @@ -178,9 +180,9 @@ class RNodeInterface(Interface): c2 = self.frequency >> 16 & 0xFF c3 = self.frequency >> 8 & 0xFF c4 = self.frequency & 0xFF - data = KISS.escape(chr(c1)+chr(c2)+chr(c3)+chr(c4)) + data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4])) - kiss_command = KISS.FEND+KISS.CMD_FREQUENCY+data+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("An IO error occurred while configuring frequency for "+self(str)) @@ -190,36 +192,39 @@ class RNodeInterface(Interface): c2 = self.bandwidth >> 16 & 0xFF c3 = self.bandwidth >> 8 & 0xFF c4 = self.bandwidth & 0xFF - data = KISS.escape(chr(c1)+chr(c2)+chr(c3)+chr(c4)) + data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4])) - kiss_command = KISS.FEND+KISS.CMD_BANDWIDTH+data+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("An IO error occurred while configuring bandwidth for "+self(str)) def setTXPower(self): - txp = chr(self.txpower) - kiss_command = KISS.FEND+KISS.CMD_TXPOWER+txp+KISS.FEND + txp = bytes([self.txpower]) + + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("An IO error occurred while configuring TX power for "+self(str)) def setSpreadingFactor(self): - sf = chr(self.sf) - kiss_command = KISS.FEND+KISS.CMD_SF+sf+KISS.FEND + sf = bytes([self.sf]) + + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("An IO error occurred while configuring spreading factor for "+self(str)) def setCodingRate(self): - cr = chr(self.cr) - kiss_command = KISS.FEND+KISS.CMD_CR+cr+KISS.FEND + cr = bytes([self.cr]) + + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("An IO error occurred while configuring coding rate for "+self(str)) def setRadioState(self, state): - kiss_command = KISS.FEND+KISS.CMD_RADIO_STATE+state+KISS.FEND + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND]) written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("An IO error occurred while configuring radio state for "+self(str)) @@ -265,7 +270,7 @@ class RNodeInterface(Interface): self.interface_ready = False data = KISS.escape(data) - frame = chr(0xc0)+chr(0x00)+data+chr(0xc0) + frame = bytes([0xc0])+bytes([0x00])+data+bytes([0xc0]) written = self.serial.write(frame) if written != len(frame): raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data))) @@ -288,25 +293,25 @@ class RNodeInterface(Interface): in_frame = False escape = False command = KISS.CMD_UNKNOWN - data_buffer = "" - command_buffer = "" + data_buffer = b"" + command_buffer = b"" last_read_ms = int(time.time()*1000) while self.serial.is_open: if self.serial.in_waiting: - byte = self.serial.read(1) + byte = ord(self.serial.read(1)) last_read_ms = int(time.time()*1000) if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA): in_frame = False self.processIncoming(data_buffer) - data_buffer = "" - command_buffer = "" + data_buffer = b"" + command_buffer = b"" elif (byte == KISS.FEND): in_frame = True command = KISS.CMD_UNKNOWN - data_buffer = "" - command_buffer = "" + data_buffer = b"" + command_buffer = b"" elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU): if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN): command = byte @@ -320,7 +325,7 @@ class RNodeInterface(Interface): if (byte == KISS.TFESC): byte = KISS.FESC escape = False - data_buffer = data_buffer+byte + data_buffer = data_buffer+bytes([byte]) elif (command == KISS.CMD_FREQUENCY): if (byte == KISS.FESC): escape = True @@ -331,9 +336,9 @@ class RNodeInterface(Interface): if (byte == KISS.TFESC): byte = KISS.FESC escape = False - command_buffer = command_buffer+byte + command_buffer = command_buffer+bytes([byte]) if (len(command_buffer) == 4): - self.r_frequency = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3]) + self.r_frequency = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3] RNS.log(str(self)+" Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz", RNS.LOG_DEBUG) self.updateBitrate() @@ -347,27 +352,27 @@ class RNodeInterface(Interface): if (byte == KISS.TFESC): byte = KISS.FESC escape = False - command_buffer = command_buffer+byte + command_buffer = command_buffer+bytes([byte]) if (len(command_buffer) == 4): - self.r_bandwidth = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3]) + self.r_bandwidth = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3] RNS.log(str(self)+" Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz", RNS.LOG_DEBUG) self.updateBitrate() elif (command == KISS.CMD_TXPOWER): - self.r_txpower = ord(byte) + self.r_txpower = byte RNS.log(str(self)+" Radio reporting TX power is "+str(self.r_txpower)+" dBm", RNS.LOG_DEBUG) elif (command == KISS.CMD_SF): - self.r_sf = ord(byte) + self.r_sf = byte RNS.log(str(self)+" Radio reporting spreading factor is "+str(self.r_sf), RNS.LOG_DEBUG) self.updateBitrate() elif (command == KISS.CMD_CR): - self.r_cr = ord(byte) + self.r_cr = byte RNS.log(str(self)+" Radio reporting coding rate is "+str(self.r_cr), RNS.LOG_DEBUG) self.updateBitrate() elif (command == KISS.CMD_RADIO_STATE): - self.r_state = ord(byte) + self.r_state = byte elif (command == KISS.CMD_RADIO_LOCK): - self.r_lock = ord(byte) + self.r_lock = byte elif (command == KISS.CMD_STAT_RX): if (byte == KISS.FESC): escape = True @@ -378,7 +383,7 @@ class RNodeInterface(Interface): if (byte == KISS.TFESC): byte = KISS.FESC escape = False - command_buffer = command_buffer+byte + command_buffer = command_buffer+bytes([byte]) if (len(command_buffer) == 4): self.r_stat_rx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3]) @@ -392,14 +397,16 @@ class RNodeInterface(Interface): if (byte == KISS.TFESC): byte = KISS.FESC escape = False - command_buffer = command_buffer+byte + command_buffer = command_buffer+bytes([byte]) if (len(command_buffer) == 4): self.r_stat_tx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3]) elif (command == KISS.CMD_STAT_RSSI): - self.r_stat_rssi = ord(byte)-RNodeInterface.RSSI_OFFSET + self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET + elif (command == KISS.CMD_STAT_SNR): + self.r_stat_snr = byte-RNodeInterface.SNR_OFFSET elif (command == KISS.CMD_RANDOM): - self.r_random = ord(byte) + self.r_random = byte elif (command == KISS.CMD_ERROR): if (byte == KISS.ERROR_INITRADIO): RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR) @@ -408,8 +415,10 @@ class RNodeInterface(Interface): else: RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR) elif (command == KISS.CMD_READY): - # TODO: add timeout and reset if ready - # command never arrives + # TODO: Flow control is disabled by default now. + # Add timed flow control ready-inidication to the + # RNode firmware to make sure flow control doesn't + # hang if it is enabled self.process_queue() else: diff --git a/RNS/Interfaces/SerialInterface.py b/RNS/Interfaces/SerialInterface.py index 42697c8..0761f38 100755 --- a/RNS/Interfaces/SerialInterface.py +++ b/RNS/Interfaces/SerialInterface.py @@ -1,3 +1,8 @@ +# TODO: This should be reworked for Python3 support, +# and maybe framing should be introduced to improve +# performance. The current 100ms wait is a bit stupid. +# Probably also need to add queue support like the +# other interfaces. from .Interface import Interface from time import sleep diff --git a/RNS/Link.py b/RNS/Link.py index 364c793..6eb2496 100644 --- a/RNS/Link.py +++ b/RNS/Link.py @@ -31,7 +31,7 @@ class Link: # first-hop RTT latency and distance DEFAULT_TIMEOUT = 15.0 TIMEOUT_FACTOR = 3 - KEEPALIVE = 120 + KEEPALIVE = 180 PENDING = 0x00 HANDSHAKE = 0x01 @@ -330,12 +330,12 @@ class Link: def send_keepalive(self): - keepalive_packet = RNS.Packet(self, chr(0xFF), context=RNS.Packet.KEEPALIVE) + keepalive_packet = RNS.Packet(self, bytes([0xFF]), context=RNS.Packet.KEEPALIVE) keepalive_packet.send() def receive(self, packet): self.watchdog_lock = True - if not self.status == Link.CLOSED and not (self.initiator and packet.context == RNS.Packet.KEEPALIVE and packet.data == chr(0xFF)): + if not self.status == Link.CLOSED and not (self.initiator and packet.context == RNS.Packet.KEEPALIVE and packet.data == bytes([0xFF])): if packet.receiving_interface != self.attached_interface: RNS.log("Link-associated packet received on unexpected interface! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR) else: @@ -400,8 +400,8 @@ class Link: resource.cancel() elif packet.context == RNS.Packet.KEEPALIVE: - if not self.initiator and packet.data == chr(0xFF): - keepalive_packet = RNS.Packet(self, chr(0xFE), context=RNS.Packet.KEEPALIVE) + if not self.initiator and packet.data == bytes([0xFF]): + keepalive_packet = RNS.Packet(self, bytes([0xFE]), context=RNS.Packet.KEEPALIVE) keepalive_packet.send() diff --git a/RNS/Reticulum.py b/RNS/Reticulum.py index 4e574f2..32e2db0 100755 --- a/RNS/Reticulum.py +++ b/RNS/Reticulum.py @@ -233,6 +233,7 @@ class Reticulum: bandwidth = int(c["bandwidth"]) if "bandwidth" in c else None txpower = int(c["txpower"]) if "txpower" in c else None spreadingfactor = int(c["spreadingfactor"]) if "spreadingfactor" in c else None + flow_control = (True if c["flow_control"] == "true" else False) if "flow_control" in c else False port = c["port"] if "port" in c else None @@ -246,7 +247,8 @@ class Reticulum: frequency, bandwidth, txpower, - spreadingfactor + spreadingfactor, + flow_control ) if "outgoing" in c and c["outgoing"].lower() == "true": @@ -259,6 +261,7 @@ class Reticulum: except Exception as e: RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR) RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) + raise e def createDefaultConfig(self):