Implemented RNode interface driver

This commit is contained in:
Mark Qvist 2018-04-05 19:12:21 +02:00
parent 0ce144c760
commit 9bfb7ae2d7
6 changed files with 272 additions and 86 deletions

View File

@ -34,9 +34,10 @@ class AX25Interface(Interface):
stopbits = None stopbits = None
serial = None serial = None
def __init__(self, owner, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime): def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime):
self.serial = None self.serial = None
self.owner = owner self.owner = owner
self.name = name
self.port = port self.port = port
self.speed = speed self.speed = speed
self.databits = databits self.databits = databits
@ -72,7 +73,7 @@ class AX25Interface(Interface):
dsrdtr = False, dsrdtr = False,
) )
except Exception as e: except Exception as e:
RNS.log("Could not create serial port", RNS.LOG_ERROR) RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
raise e raise e
if self.serial.is_open: if self.serial.is_open:

View File

@ -33,9 +33,10 @@ class KISSInterface(Interface):
stopbits = None stopbits = None
serial = None serial = None
def __init__(self, owner, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime): def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime):
self.serial = None self.serial = None
self.owner = owner self.owner = owner
self.name = name
self.port = port self.port = port
self.speed = speed self.speed = speed
self.databits = databits self.databits = databits
@ -71,7 +72,7 @@ class KISSInterface(Interface):
dsrdtr = False, dsrdtr = False,
) )
except Exception as e: except Exception as e:
RNS.log("Could not create serial port", RNS.LOG_ERROR) RNS.log("Could not open serial port "+self.port, RNS.LOG_ERROR)
raise e raise e
if self.serial.is_open: if self.serial.is_open:
@ -88,7 +89,6 @@ class KISSInterface(Interface):
self.setPersistence(self.persistence) self.setPersistence(self.persistence)
self.setSlotTime(self.slottime) self.setSlotTime(self.slottime)
RNS.log("KISS interface configured") RNS.log("KISS interface configured")
sleep(2)
else: else:
raise IOError("Could not open serial port") raise IOError("Could not open serial port")

View File

@ -12,17 +12,36 @@ class KISS():
FESC = chr(0xDB) FESC = chr(0xDB)
TFEND = chr(0xDC) TFEND = chr(0xDC)
TFESC = chr(0xDD) TFESC = chr(0xDD)
CMD_UNKNOWN = chr(0xFE) CMD_UNKNOWN = chr(0xFE)
CMD_DATA = chr(0x00) CMD_DATA = chr(0x00)
CMD_TXDELAY = chr(0x01) CMD_FREQUENCY = chr(0x01)
CMD_P = chr(0x02) CMD_BANDWIDTH = chr(0x02)
CMD_SLOTTIME = chr(0x03) CMD_TXPOWER = chr(0x03)
CMD_TXTAIL = chr(0x04) CMD_SF = chr(0x04)
CMD_FULLDUPLEX = chr(0x05) CMD_RADIO_STATE = chr(0x05)
CMD_SETHARDWARE = chr(0x06) CMD_RADIO_LOCK = chr(0x06)
CMD_RETURN = chr(0xFF) CMD_STAT_RX = chr(0x21)
CMD_STAT_TX = chr(0x22)
CMD_STAT_RSSI = chr(0x23)
CMD_BLINK = chr(0x30)
CMD_RANDOM = chr(0x40)
RADIO_STATE_OFF = chr(0x00)
RADIO_STATE_ON = chr(0x01)
RADIO_STATE_ASK = chr(0xFF)
CMD_ERROR = chr(0x90)
ERROR_INITRADIO = chr(0x01)
ERROR_TXFAILED = chr(0x02)
@staticmethod
def escape(data):
data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd))
data = data.replace(chr(0xc0), chr(0xdb)+chr(0xdc))
return data
# TODO: THIS CLASS IS NOT YET IMPLEMENTED --- PLACEHOLDER ONLY ---
class RNodeInterface(Interface): class RNodeInterface(Interface):
MAX_CHUNK = 32768 MAX_CHUNK = 32768
@ -34,27 +53,57 @@ class RNodeInterface(Interface):
stopbits = None stopbits = None
serial = None serial = None
def __init__(self, owner, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime): FREQ_MIN = 137000000
FREQ_MAX = 1020000000
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None):
self.serial = None self.serial = None
self.owner = owner self.owner = owner
self.name = name
self.port = port self.port = port
self.speed = speed self.speed = 115200
self.databits = databits self.databits = 8
self.parity = serial.PARITY_NONE self.parity = serial.PARITY_NONE
self.stopbits = stopbits self.stopbits = 1
self.timeout = 100 self.timeout = 100
self.online = False self.online = False
self.preamble = preamble if preamble != None else 350; self.frequency = frequency
self.txtail = txtail if txtail != None else 20; self.bandwidth = bandwidth
self.persistence = persistence if persistence != None else 64; self.txpower = txpower
self.slottime = slottime if slottime != None else 20; self.sf = sf
self.state = KISS.RADIO_STATE_OFF
if parity.lower() == "e" or parity.lower() == "even": self.r_frequency = None
self.parity = serial.PARITY_EVEN self.r_bandwidth = None
self.r_txpower = None
self.r_sf = None
self.r_state = None
self.r_lock = None
self.r_stat_rx = None
self.r_stat_tx = None
self.r_stat_rssi = None
self.r_random = None
if parity.lower() == "o" or parity.lower() == "odd": self.validcfg = True
self.parity = serial.PARITY_ODD if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
RNS.log("Invalid frequency configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
if (self.txpower < 0 or self.txpower > 17):
RNS.log("Invalid TX power configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
if (self.bandwidth < 7800 or self.bandwidth > 500000):
RNS.log("Invalid bandwidth configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
if (self.sf < 7 or self.sf > 12):
RNS.log("Invalid spreading factor configured for "+str(self), RNS.LOG_ERROR)
self.validcfg = False
if (not self.validcfg):
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
try: try:
RNS.log("Opening serial port "+self.port+"...") RNS.log("Opening serial port "+self.port+"...")
@ -72,78 +121,102 @@ class RNodeInterface(Interface):
dsrdtr = False, dsrdtr = False,
) )
except Exception as e: except Exception as e:
RNS.log("Could not create serial port", RNS.LOG_ERROR) RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
raise e raise e
if self.serial.is_open: if self.serial.is_open:
# 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)
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")
RNS.log("Configuring RNode interface parameters...") RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
self.setPreamble(self.preamble) self.initRadio()
self.setTxTail(self.txtail) if (self.validateRadioState()):
self.setPersistence(self.persistence) RNS.log(str(self)+" is configured and powered up")
self.setSlotTime(self.slottime) else:
RNS.log("RNode interface configured") RNS.log("After configuring "+str(self)+", the actual radio parameters did not match your configuration.", RNS.LOG_ERROR)
sleep(2) 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")
else: else:
raise IOError("Could not open serial port") raise IOError("Could not open serial port")
def setPreamble(self, preamble): def initRadio(self):
preamble_ms = preamble self.setFrequency()
preamble = int(preamble_ms / 10) self.setBandwidth()
if preamble < 0: self.setTXPower()
preamble = 0 self.setSpreadingFactor()
if preamble > 255: self.setRadioState(KISS.RADIO_STATE_ON)
preamble = 255
RNS.log("Setting preamble to "+str(preamble)+" "+chr(preamble)) def setFrequency(self):
kiss_command = KISS.FEND+KISS.CMD_TXDELAY+chr(preamble)+KISS.FEND c1 = self.frequency >> 24
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))
kiss_command = KISS.FEND+KISS.CMD_FREQUENCY+data+KISS.FEND
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(kiss_command): if written != len(kiss_command):
raise IOError("Could not configure KISS interface preamble to "+str(preamble_ms)+" (command value "+str(preamble)+")") raise IOError("An IO error occurred while configuring frequency for "+self(str))
def setTxTail(self, txtail): def setBandwidth(self):
txtail_ms = txtail c1 = self.bandwidth >> 24
txtail = int(txtail_ms / 10) c2 = self.bandwidth >> 16 & 0xFF
if txtail < 0: c3 = self.bandwidth >> 8 & 0xFF
txtail = 0 c4 = self.bandwidth & 0xFF
if txtail > 255: data = KISS.escape(chr(c1)+chr(c2)+chr(c3)+chr(c4))
txtail = 255
kiss_command = KISS.FEND+KISS.CMD_TXTAIL+chr(txtail)+KISS.FEND kiss_command = KISS.FEND+KISS.CMD_BANDWIDTH+data+KISS.FEND
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(kiss_command): if written != len(kiss_command):
raise IOError("Could not configure KISS interface TX tail to "+str(txtail_ms)+" (command value "+str(txtail)+")") raise IOError("An IO error occurred while configuring bandwidth for "+self(str))
def setPersistence(self, persistence): def setTXPower(self):
if persistence < 0: txp = chr(self.txpower)
persistence = 0 kiss_command = KISS.FEND+KISS.CMD_TXPOWER+txp+KISS.FEND
if persistence > 255:
persistence = 255
kiss_command = KISS.FEND+KISS.CMD_P+chr(persistence)+KISS.FEND
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(kiss_command): if written != len(kiss_command):
raise IOError("Could not configure KISS interface persistence to "+str(persistence)) raise IOError("An IO error occurred while configuring TX power for "+self(str))
def setSlotTime(self, slottime): def setSpreadingFactor(self):
slottime_ms = slottime sf = chr(self.sf)
slottime = int(slottime_ms / 10) kiss_command = KISS.FEND+KISS.CMD_SF+sf+KISS.FEND
if slottime < 0:
slottime = 0
if slottime > 255:
slottime = 255
kiss_command = KISS.FEND+KISS.CMD_SLOTTIME+chr(slottime)+KISS.FEND
written = self.serial.write(kiss_command) written = self.serial.write(kiss_command)
if written != len(kiss_command): if written != len(kiss_command):
raise IOError("Could not configure KISS interface slot time to "+str(slottime_ms)+" (command value "+str(slottime)+")") raise IOError("An IO error occurred while configuring spreading factor for "+self(str))
def setRadioState(self, state):
kiss_command = KISS.FEND+KISS.CMD_RADIO_STATE+state+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))
def validateRadioState(self):
RNS.log("Validating radio configuration for "+str(self)+"...", RNS.LOG_VERBOSE)
sleep(0.25);
if (self.frequency != self.r_frequency):
RNS.log("Frequency mismatch", RNS.LOG_ERROR)
self.validcfg = False
if (self.bandwidth != self.r_bandwidth):
RNS.log("Bandwidth mismatch", RNS.LOG_ERROR)
self.validcfg = False
if (self.txpower != self.r_txpower):
RNS.log("TX power mismatch", RNS.LOG_ERROR)
self.validcfg = False
if (self.sf != self.r_sf):
RNS.log("Spreading factor mismatch", RNS.LOG_ERROR)
self.validcfg = False
if (self.validcfg):
return True
else:
return False
def processIncoming(self, data): def processIncoming(self, data):
@ -152,8 +225,7 @@ class RNodeInterface(Interface):
def processOutgoing(self,data): def processOutgoing(self,data):
if self.online: if self.online:
data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd)) data = KISS.escape(data)
data = data.replace(chr(0xc0), chr(0xdb)+chr(0xdc))
frame = chr(0xc0)+chr(0x00)+data+chr(0xc0) frame = chr(0xc0)+chr(0x00)+data+chr(0xc0)
written = self.serial.write(frame) written = self.serial.write(frame)
if written != len(frame): if written != len(frame):
@ -166,6 +238,7 @@ class RNodeInterface(Interface):
escape = False escape = False
command = KISS.CMD_UNKNOWN command = KISS.CMD_UNKNOWN
data_buffer = "" data_buffer = ""
command_buffer = ""
last_read_ms = int(time.time()*1000) last_read_ms = int(time.time()*1000)
while self.serial.is_open: while self.serial.is_open:
@ -180,6 +253,7 @@ class RNodeInterface(Interface):
in_frame = True in_frame = True
command = KISS.CMD_UNKNOWN command = KISS.CMD_UNKNOWN
data_buffer = "" data_buffer = ""
command_buffer = ""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU): elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN): if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
# We only support one HDLC port for now, so # We only support one HDLC port for now, so
@ -197,6 +271,86 @@ class RNodeInterface(Interface):
byte = KISS.FESC byte = KISS.FESC
escape = False escape = False
data_buffer = data_buffer+byte data_buffer = data_buffer+byte
elif (command == KISS.CMD_FREQUENCY):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+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])
RNS.log("Radio reporting frequency is "+str(self.r_frequency), RNS.LOG_DEBUG)
elif (command == KISS.CMD_BANDWIDTH):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+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])
RNS.log("Radio reporting bandwidth is "+str(self.r_bandwidth), RNS.LOG_DEBUG)
elif (command == KISS.CMD_TXPOWER):
self.r_txpower = ord(byte)
RNS.log("Radio reporting TX power is "+str(self.r_txpower), RNS.LOG_DEBUG)
elif (command == KISS.CMD_SF):
self.r_sf = ord(byte)
RNS.log("Radio reporting spreading factor is "+str(self.r_sf), RNS.LOG_DEBUG)
elif (command == KISS.CMD_RADIO_STATE):
self.r_state = ord(byte)
elif (command == KISS.CMD_RADIO_LOCK):
self.r_lock = ord(byte)
elif (command == KISS.CMD_STAT_RX):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+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])
elif (command == KISS.CMD_STAT_TX):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+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)
elif (command == KISS.CMD_RANDOM):
self.r_random = ord(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)
elif (byte == KISS.ERROR_INITRADIO):
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
else:
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
else: else:
time_since_last = int(time.time()*1000) - last_read_ms time_since_last = int(time.time()*1000) - last_read_ms
if len(data_buffer) > 0 and time_since_last > self.timeout: if len(data_buffer) > 0 and time_since_last > self.timeout:

View File

@ -18,9 +18,10 @@ class SerialInterface(Interface):
stopbits = None stopbits = None
serial = None serial = None
def __init__(self, owner, port, speed, databits, parity, stopbits): def __init__(self, owner, name, port, speed, databits, parity, stopbits):
self.serial = None self.serial = None
self.owner = owner self.owner = owner
self.name = name
self.port = port self.port = port
self.speed = speed self.speed = speed
self.databits = databits self.databits = databits
@ -51,7 +52,7 @@ class SerialInterface(Interface):
dsrdtr = False, dsrdtr = False,
) )
except Exception as e: except Exception as e:
RNS.log("Could not create serial port", RNS.LOG_ERROR) RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
raise e raise e
if self.serial.is_open: if self.serial.is_open:

View File

@ -12,10 +12,12 @@ class UdpInterface(Interface):
forward_port = None forward_port = None
owner = None owner = None
def __init__(self, owner, bindip=None, bindport=None, forwardip=None, forwardport=None): def __init__(self, owner, name, bindip=None, bindport=None, forwardip=None, forwardport=None):
self.IN = True self.IN = True
self.OUT = False self.OUT = False
self.name = name
if (bindip != None and bindport != None): if (bindip != None and bindport != None):
self.receives = True self.receives = True
self.bind_ip = bindip self.bind_ip = bindip

View File

@ -63,6 +63,7 @@ class Reticulum:
if c["type"] == "UdpInterface": if c["type"] == "UdpInterface":
interface = UdpInterface.UdpInterface( interface = UdpInterface.UdpInterface(
RNS.Transport, RNS.Transport,
name,
c["listen_ip"], c["listen_ip"],
int(c["listen_port"]), int(c["listen_port"]),
c["forward_ip"], c["forward_ip"],
@ -74,7 +75,6 @@ class Reticulum:
else: else:
interface.OUT = False interface.OUT = False
interface.name = name
RNS.Transport.interfaces.append(interface) RNS.Transport.interfaces.append(interface)
if c["type"] == "SerialInterface": if c["type"] == "SerialInterface":
@ -89,6 +89,7 @@ class Reticulum:
interface = SerialInterface.SerialInterface( interface = SerialInterface.SerialInterface(
RNS.Transport, RNS.Transport,
name,
port, port,
speed, speed,
databits, databits,
@ -101,7 +102,6 @@ class Reticulum:
else: else:
interface.OUT = False interface.OUT = False
interface.name = name
RNS.Transport.interfaces.append(interface) RNS.Transport.interfaces.append(interface)
if c["type"] == "KISSInterface": if c["type"] == "KISSInterface":
@ -121,6 +121,7 @@ class Reticulum:
interface = KISSInterface.KISSInterface( interface = KISSInterface.KISSInterface(
RNS.Transport, RNS.Transport,
name,
port, port,
speed, speed,
databits, databits,
@ -137,13 +138,40 @@ class Reticulum:
else: else:
interface.OUT = False interface.OUT = False
interface.name = name RNS.Transport.interfaces.append(interface)
if c["type"] == "RNodeInterface":
frequency = int(c["frequency"]) if "frequency" in c else None
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
port = c["port"] if "port" in c else None
if port == None:
raise ValueError("No port specified for RNode interface")
interface = RNodeInterface.RNodeInterface(
RNS.Transport,
name,
port,
frequency,
bandwidth,
txpower,
spreadingfactor
)
if "outgoing" in c and c["outgoing"].lower() == "true":
interface.OUT = True
else:
interface.OUT = False
RNS.Transport.interfaces.append(interface) RNS.Transport.interfaces.append(interface)
except Exception as e: 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 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) RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
traceback.print_exc() #traceback.print_exc()