]> git.tnoah.ca Git - lora-radio.git/commitdiff
Cleaned up whitespace
authorMark Qvist <mark@adepta.io>
Wed, 27 May 2020 14:17:40 +0000 (16:17 +0200)
committerMark Qvist <mark@adepta.io>
Wed, 27 May 2020 14:17:40 +0000 (16:17 +0200)
Libraries/Example.py
Libraries/RNode.py

index 24cea49713e78d2b88cc75d100ce6050051aff35..bc4613e4eb9330b012c1719809807e2c75782c39 100644 (file)
@@ -7,13 +7,15 @@ from RNode import RNodeInterface
 # We'll also define which serial port the
 # RNode is attached to.
 serialPort = "/dev/ttyUSB0"
+# TODO: Remove
+serialPort = "/dev/tty.usbserial-DN03E0FQ"
 
 # This function gets called every time a
 # packet is received
 def gotPacket(data, rnode):
-       print "Received a packet: "+data
-       print "RSSI: "+str(rnode.r_stat_rssi)+" dBm"
-       print "SNR:  "+str(rnode.r_stat_snr)+" dB"
+       print("Received a packet: "+data)
+       print("RSSI: "+str(rnode.r_stat_rssi)+" dBm")
+       print("SNR:  "+str(rnode.r_stat_snr)+" dB")
 
 # Create an RNode instance. This configures
 # and powers up the radio.
@@ -30,10 +32,10 @@ rnode = RNodeInterface(
 
 # Enter a loop waiting for user input.
 try:
-       print "Waiting for packets, hit enter to send a packet, Ctrl-C to exit"
+       print("Waiting for packets, hit enter to send a packet, Ctrl-C to exit")
        while True:
-               raw_input()
+               input()
                rnode.send("Hello World!")
 except KeyboardInterrupt as e:
-       print ""
+       print("")
        exit()
\ No newline at end of file
index 408661e6425f4e7b5dd7ed9407e6716603722c45..3f238b08ccc9ebf31ded2be4bd78f1a3fef545f8 100644 (file)
@@ -1,4 +1,3 @@
-from __future__ import print_function
 from time import sleep
 import sys
 import serial
@@ -7,499 +6,498 @@ import time
 import math
 
 class KISS():
-       FEND                    = chr(0xC0)
-       FESC                    = chr(0xDB)
-       TFEND                   = chr(0xDC)
-       TFESC                   = chr(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_PROMISC             = chr(0x0E)
-       CMD_READY       = chr(0x0F)
-       CMD_STAT_RX             = chr(0x21)
-       CMD_STAT_TX             = chr(0x22)
-       CMD_STAT_RSSI   = chr(0x23)
-       CMD_STAT_SNR    = chr(0x24)
-       CMD_BLINK               = chr(0x30)
-       CMD_RANDOM              = chr(0x40)
-       CMD_FW_VERSION  = chr(0x50)
-       CMD_ROM_READ    = chr(0x51)
-
-       DETECT_REQ      = chr(0x73)
-       DETECT_RESP     = chr(0x46)
-       
-       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)
-       ERROR_EEPROM_LOCKED     = chr(0x03)
-
-       @staticmethod
-       def escape(data):
-               data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd))
-               data = data.replace(chr(0xc0), chr(0xdb)+chr(0xdc))
-               return data
-       
+    FEND            = 0xC0
+    FESC            = 0xDB
+    TFEND           = 0xDC
+    TFESC           = 0xDD
+    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_PROMISC     = 0x0E
+    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      = 0x73
+    DETECT_RESP     = 0x46
+
+    RADIO_STATE_OFF = 0x00
+    RADIO_STATE_ON  = 0x01
+    RADIO_STATE_ASK = 0xFF
+    
+    CMD_ERROR           = chr0x90
+    ERROR_INITRADIO     = chr0x01
+    ERROR_TXFAILED      = chr0x02
+    ERROR_EEPROM_LOCKED = chr0x03
+
+    @staticmethod
+    def escape(data):
+        data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
+        data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
+        return data
+    
 
 class RNodeInterface():
-       MTU = 500
-       MAX_CHUNK = 32768
-       FREQ_MIN  = 137000000
-       FREQ_MAX  = 1020000000
-
-       LOG_CRITICAL = 0
-       LOG_ERROR    = 1
-       LOG_WARNING  = 2
-       LOG_NOTICE   = 3
-       LOG_INFO     = 4
-       LOG_VERBOSE  = 5
-       LOG_DEBUG    = 6
-       LOG_EXTREME  = 7
-
-       RSSI_OFFSET  = 157
-       SNR_OFFSET  = 128
-
-       def __init__(self, callback, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, loglevel = -1, flow_control = True):
-               self.serial      = None
-               self.loglevel    = loglevel
-               self.callback    = callback
-               self.name        = name
-               self.port        = port
-               self.speed       = 115200
-               self.databits    = 8
-               self.parity      = serial.PARITY_NONE
-               self.stopbits    = 1
-               self.timeout     = 100
-               self.online      = False
-
-               self.frequency   = frequency
-               self.bandwidth   = bandwidth
-               self.txpower     = txpower
-               self.sf          = sf
-               self.cr          = cr
-               self.state       = KISS.RADIO_STATE_OFF
-               self.bitrate     = 0
-
-               self.r_frequency = None
-               self.r_bandwidth = None
-               self.r_txpower   = None
-               self.r_sf        = None
-               self.r_cr        = 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_stat_snr  = None
-               self.r_random    = None
-
-               self.packet_queue    = []
-               self.flow_control    = flow_control
-               self.interface_ready = False
-
-               self.validcfg  = True
-               if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
-                       self.log("Invalid frequency configured for "+str(self), RNodeInterface.LOG_ERROR)
-                       self.validcfg = False
-
-               if (self.txpower < 0 or self.txpower > 17):
-                       self.log("Invalid TX power configured for "+str(self), RNodeInterface.LOG_ERROR)
-                       self.validcfg = False
-
-               if (self.bandwidth < 7800 or self.bandwidth > 500000):
-                       self.log("Invalid bandwidth configured for "+str(self), RNodeInterface.LOG_ERROR)
-                       self.validcfg = False
-
-               if (self.sf < 7 or self.sf > 12):
-                       self.log("Invalid spreading factor configured for "+str(self), RNodeInterface.LOG_ERROR)
-                       self.validcfg = False
-
-               if (not self.validcfg):
-                       raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
-
-               try:
-                       self.log("Opening serial port "+self.port+"...")
-                       self.serial = serial.Serial(
-                               port = self.port,
-                               baudrate = self.speed,
-                               bytesize = self.databits,
-                               parity = self.parity,
-                               stopbits = self.stopbits,
-                               xonxoff = False,
-                               rtscts = False,
-                               timeout = 0,
-                               inter_byte_timeout = None,
-                               write_timeout = None,
-                               dsrdtr = False,
-                       )
-               except Exception as e:
-                       self.log("Could not open serial port for interface "+str(self), RNodeInterface.LOG_ERROR)
-                       raise e
-
-               if self.serial.is_open:
-                       sleep(2.0)
-                       thread = threading.Thread(target=self.readLoop)
-                       thread.setDaemon(True)
-                       thread.start()
-                       self.online = True
-                       self.log("Serial port "+self.port+" is now open")
-                       self.log("Configuring RNode interface...", RNodeInterface.LOG_VERBOSE)
-                       self.initRadio()
-                       if (self.validateRadioState()):
-                               self.interface_ready = True
-                               self.log(str(self)+" is configured and powered up")
-                               sleep(1.0)
-                       else:
-                               self.log("After configuring "+str(self)+", the actual radio parameters did not match your configuration.", RNodeInterface.LOG_ERROR)
-                               self.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNodeInterface.LOG_ERROR)
-                               self.log("Aborting RNode startup", RNodeInterface.LOG_ERROR)
-                               self.serial.close()
-                               raise IOError("RNode interface did not pass validation")
-               else:
-                       raise IOError("Could not open serial port")
-
-       def log(self, message, level):
-               pass
-
-       def initRadio(self):
-               self.setFrequency()
-               self.setBandwidth()
-               self.setTXPower()
-               self.setSpreadingFactor()
-               self.setRadioState(KISS.RADIO_STATE_ON)
-
-       def setFrequency(self):
-               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)
-               if written != len(kiss_command):
-                       raise IOError("An IO error occurred while configuring frequency for "+self(str))
-
-       def setBandwidth(self):
-               c1 = self.bandwidth >> 24
-               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))
-
-               kiss_command = KISS.FEND+KISS.CMD_BANDWIDTH+data+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
-               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
-               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
-               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
-               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):
-               self.log("Validating radio configuration for "+str(self)+"...", RNodeInterface.LOG_VERBOSE)
-               sleep(0.25);
-               if (self.frequency != self.r_frequency):
-                       self.log("Frequency mismatch", RNodeInterface.LOG_ERROR)
-                       self.validcfg = False
-               if (self.bandwidth != self.r_bandwidth):
-                       self.log("Bandwidth mismatch", RNodeInterface.LOG_ERROR)
-                       self.validcfg = False
-               if (self.txpower != self.r_txpower):
-                       self.log("TX power mismatch", RNodeInterface.LOG_ERROR)
-                       self.validcfg = False
-               if (self.sf != self.r_sf):
-                       self.log("Spreading factor mismatch", RNodeInterface.LOG_ERROR)
-                       self.validcfg = False
-
-               if (self.validcfg):
-                       return True
-               else:
-                       return False
-
-       def setPromiscuousMode(self, state):
-               if state == True:
-                       kiss_command = KISS.FEND+KISS.CMD_PROMISC+chr(0x01)+KISS.FEND
-               else:
-                       kiss_command = KISS.FEND+KISS.CMD_PROMISC+chr(0x00)+KISS.FEND
-
-               written = self.serial.write(kiss_command)
-               if written != len(kiss_command):
-                       raise IOError("An IO error occurred while configuring promiscuous mode for "+self(str))
-
-
-       def updateBitrate(self):
-               try:
-                       self.bitrate = self.r_sf * ( (4.0/self.cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
-                       self.bitrate_kbps = round(self.bitrate/1000.0, 2)
-                       self.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNodeInterface.LOG_DEBUG)
-               except:
-                       self.bitrate = 0
-
-       def processIncoming(self, data):
-               self.callback(data, self)
-
-       def send(self, data):
-               self.processOutgoing(data)
-
-       def processOutgoing(self,data):
-               if self.online:
-                       if self.interface_ready:
-                               if self.flow_control:
-                                       self.interface_ready = False
-
-                               data = KISS.escape(data)
-                               frame = chr(0xc0)+chr(0x00)+data+chr(0xc0)
-                               written = self.serial.write(frame)
-                               if written != len(frame):
-                                       raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
-                       else:
-                               self.queue(data)
-
-       def queue(self, data):
-               self.packet_queue.append(data)
-
-       def process_queue(self):
-               if len(self.packet_queue) > 0:
-                       data = self.packet_queue.pop(0)
-                       self.interface_ready = True
-                       self.processOutgoing(data)
-               elif len(self.packet_queue) == 0:
-                       self.interface_ready = True
-
-       def readLoop(self):
-               try:
-                       in_frame = False
-                       escape = False
-                       command = KISS.CMD_UNKNOWN
-                       data_buffer = ""
-                       command_buffer = ""
-                       last_read_ms = int(time.time()*1000)
-
-                       while self.serial.is_open:
-                               if self.serial.in_waiting:
-                                       byte = 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 = ""
-                                       elif (byte == KISS.FEND):
-                                               in_frame = True
-                                               command = KISS.CMD_UNKNOWN
-                                               data_buffer = ""
-                                               command_buffer = ""
-                                       elif (in_frame and len(data_buffer) < RNodeInterface.MTU):
-                                               if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
-                                                       command = byte
-                                               elif (command == KISS.CMD_DATA):
-                                                       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
-                                                               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])
-                                                                       self.log(str(self)+" Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz", RNodeInterface.LOG_DEBUG)
-                                                                       self.updateBitrate()
-
-                                               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])
-                                                                       self.log(str(self)+" Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz", RNodeInterface.LOG_DEBUG)
-                                                                       self.updateBitrate()
-
-                                               elif (command == KISS.CMD_TXPOWER):
-                                                       self.r_txpower = ord(byte)
-                                                       self.log(str(self)+" Radio reporting TX power is "+str(self.r_txpower)+" dBm", RNodeInterface.LOG_DEBUG)
-                                               elif (command == KISS.CMD_SF):
-                                                       self.r_sf = ord(byte)
-                                                       self.log(str(self)+" Radio reporting spreading factor is "+str(self.r_sf), RNodeInterface.LOG_DEBUG)
-                                                       self.updateBitrate()
-                                               elif (command == KISS.CMD_CR):
-                                                       self.r_cr = ord(byte)
-                                                       self.log(str(self)+" Radio reporting coding rate is "+str(self.r_cr), RNodeInterface.LOG_DEBUG)
-                                                       self.updateBitrate()
-                                               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):
-                                                       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
-                                                               self.r_stat_rssi = ord(byte)-self.RSSI_OFFSET
-
-                                               elif (command == KISS.CMD_STAT_SNR):
-                                                       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
-                                                               self.r_stat_snr  = ord(byte)-self.SNR_OFFSET
-
-                                               elif (command == KISS.CMD_RANDOM):
-                                                       self.r_random = ord(byte)
-                                               elif (command == KISS.CMD_ERROR):
-                                                       if (byte == KISS.ERROR_INITRADIO):
-                                                               self.log(str(self)+" hardware initialisation error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
-                                                       elif (byte == KISS.ERROR_INITRADIO):
-                                                               self.log(str(self)+" hardware TX error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
-                                                       else:
-                                                               self.log(str(self)+" hardware error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
-                                               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
-                                       if len(data_buffer) > 0 and time_since_last > self.timeout:
-                                               self.log(str(self)+" serial read timeout", RNodeInterface.LOG_DEBUG)
-                                               data_buffer = ""
-                                               in_frame = False
-                                               command = KISS.CMD_UNKNOWN
-                                               escape = False
-                                       sleep(0.08)
-
-               except Exception as e:
-                       self.online = False
-                       self.log("A serial port error occurred, the contained exception was: "+str(e), RNodeInterface.LOG_ERROR)
-                       self.log("The interface "+str(self.name)+" is now offline.", RNodeInterface.LOG_ERROR)
-
-       def log(self, msg, level=3):
-               logtimefmt   = "%Y-%m-%d %H:%M:%S"
-               if self.loglevel >= level:
-                       timestamp = time.time()
-                       logstring = "["+time.strftime(logtimefmt)+"] ["+self.loglevelname(level)+"] "+msg
-                       print(logstring)
-
-       def loglevelname(self, level):
-               if (level == RNodeInterface.LOG_CRITICAL):
-                       return "Critical"
-               if (level == RNodeInterface.LOG_ERROR):
-                       return "Error"
-               if (level == RNodeInterface.LOG_WARNING):
-                       return "Warning"
-               if (level == RNodeInterface.LOG_NOTICE):
-                       return "Notice"
-               if (level == RNodeInterface.LOG_INFO):
-                       return "Info"
-               if (level == RNodeInterface.LOG_VERBOSE):
-                       return "Verbose"
-               if (level == RNodeInterface.LOG_DEBUG):
-                       return "Debug"
-               if (level == RNodeInterface.LOG_EXTREME):
-                       return "Extra"
-
-       def hexrep(data, delimit=True):
-               delimiter = ":"
-               if not delimit:
-                       delimiter = ""
-               hexrep = delimiter.join("{:02x}".format(ord(c)) for c in data)
-               return hexrep
-
-       def __str__(self):
-               return "RNodeInterface["+self.name+"]"
+    MTU = 500
+    MAX_CHUNK = 32768
+    FREQ_MIN  = 137000000
+    FREQ_MAX  = 1020000000
+
+    LOG_CRITICAL = 0
+    LOG_ERROR    = 1
+    LOG_WARNING  = 2
+    LOG_NOTICE   = 3
+    LOG_INFO     = 4
+    LOG_VERBOSE  = 5
+    LOG_DEBUG    = 6
+    LOG_EXTREME  = 7
+
+    RSSI_OFFSET  = 157
+    SNR_OFFSET  = 128
+
+    def __init__(self, callback, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, loglevel = -1, flow_control = True):
+        self.serial      = None
+        self.loglevel    = loglevel
+        self.callback    = callback
+        self.name        = name
+        self.port        = port
+        self.speed       = 115200
+        self.databits    = 8
+        self.parity      = serial.PARITY_NONE
+        self.stopbits    = 1
+        self.timeout     = 100
+        self.online      = False
+
+        self.frequency   = frequency
+        self.bandwidth   = bandwidth
+        self.txpower     = txpower
+        self.sf          = sf
+        self.cr          = cr
+        self.state       = KISS.RADIO_STATE_OFF
+        self.bitrate     = 0
+
+        self.r_frequency = None
+        self.r_bandwidth = None
+        self.r_txpower   = None
+        self.r_sf        = None
+        self.r_cr        = 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_stat_snr  = None
+        self.r_random    = None
+
+        self.packet_queue    = []
+        self.flow_control    = flow_control
+        self.interface_ready = False
+
+        self.validcfg  = True
+        if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
+            self.log("Invalid frequency configured for "+str(self), RNodeInterface.LOG_ERROR)
+            self.validcfg = False
+
+        if (self.txpower < 0 or self.txpower > 17):
+            self.log("Invalid TX power configured for "+str(self), RNodeInterface.LOG_ERROR)
+            self.validcfg = False
+
+        if (self.bandwidth < 7800 or self.bandwidth > 500000):
+            self.log("Invalid bandwidth configured for "+str(self), RNodeInterface.LOG_ERROR)
+            self.validcfg = False
+
+        if (self.sf < 7 or self.sf > 12):
+            self.log("Invalid spreading factor configured for "+str(self), RNodeInterface.LOG_ERROR)
+            self.validcfg = False
+
+        if (not self.validcfg):
+            raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
+
+        try:
+            self.log("Opening serial port "+self.port+"...")
+            self.serial = serial.Serial(
+                port = self.port,
+                baudrate = self.speed,
+                bytesize = self.databits,
+                parity = self.parity,
+                stopbits = self.stopbits,
+                xonxoff = False,
+                rtscts = False,
+                timeout = 0,
+                inter_byte_timeout = None,
+                write_timeout = None,
+                dsrdtr = False,
+            )
+        except Exception as e:
+            self.log("Could not open serial port for interface "+str(self), RNodeInterface.LOG_ERROR)
+            raise e
+
+        if self.serial.is_open:
+            sleep(2.0)
+            thread = threading.Thread(target=self.readLoop)
+            thread.setDaemon(True)
+            thread.start()
+            self.online = True
+            self.log("Serial port "+self.port+" is now open")
+            self.log("Configuring RNode interface...", RNodeInterface.LOG_VERBOSE)
+            self.initRadio()
+            if (self.validateRadioState()):
+                self.interface_ready = True
+                self.log(str(self)+" is configured and powered up")
+                sleep(1.0)
+            else:
+                self.log("After configuring "+str(self)+", the actual radio parameters did not match your configuration.", RNodeInterface.LOG_ERROR)
+                self.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNodeInterface.LOG_ERROR)
+                self.log("Aborting RNode startup", RNodeInterface.LOG_ERROR)
+                self.serial.close()
+                raise IOError("RNode interface did not pass validation")
+        else:
+            raise IOError("Could not open serial port")
+
+    def log(self, message, level):
+        pass
+
+    def initRadio(self):
+        self.setFrequency()
+        self.setBandwidth()
+        self.setTXPower()
+        self.setSpreadingFactor()
+        self.setRadioState(KISS.RADIO_STATE_ON)
+
+    def setFrequency(self):
+        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)
+        if written != len(kiss_command):
+            raise IOError("An IO error occurred while configuring frequency for "+self(str))
+
+    def setBandwidth(self):
+        c1 = self.bandwidth >> 24
+        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))
+
+        kiss_command = KISS.FEND+KISS.CMD_BANDWIDTH+data+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
+        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
+        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
+        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
+        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):
+        self.log("Validating radio configuration for "+str(self)+"...", RNodeInterface.LOG_VERBOSE)
+        sleep(0.25);
+        if (self.frequency != self.r_frequency):
+            self.log("Frequency mismatch", RNodeInterface.LOG_ERROR)
+            self.validcfg = False
+        if (self.bandwidth != self.r_bandwidth):
+            self.log("Bandwidth mismatch", RNodeInterface.LOG_ERROR)
+            self.validcfg = False
+        if (self.txpower != self.r_txpower):
+            self.log("TX power mismatch", RNodeInterface.LOG_ERROR)
+            self.validcfg = False
+        if (self.sf != self.r_sf):
+            self.log("Spreading factor mismatch", RNodeInterface.LOG_ERROR)
+            self.validcfg = False
+
+        if (self.validcfg):
+            return True
+        else:
+            return False
+
+    def setPromiscuousMode(self, state):
+        if state == True:
+            kiss_command = KISS.FEND+KISS.CMD_PROMISC+chr(0x01)+KISS.FEND
+        else:
+            kiss_command = KISS.FEND+KISS.CMD_PROMISC+chr(0x00)+KISS.FEND
+
+        written = self.serial.write(kiss_command)
+        if written != len(kiss_command):
+            raise IOError("An IO error occurred while configuring promiscuous mode for "+self(str))
+
+
+    def updateBitrate(self):
+        try:
+            self.bitrate = self.r_sf * ( (4.0/self.cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
+            self.bitrate_kbps = round(self.bitrate/1000.0, 2)
+            self.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNodeInterface.LOG_DEBUG)
+        except:
+            self.bitrate = 0
+
+    def processIncoming(self, data):
+        self.callback(data, self)
+
+    def send(self, data):
+        self.processOutgoing(data)
+
+    def processOutgoing(self,data):
+        if self.online:
+            if self.interface_ready:
+                if self.flow_control:
+                    self.interface_ready = False
+
+                data = KISS.escape(data)
+                frame = chr(0xc0)+chr(0x00)+data+chr(0xc0)
+                written = self.serial.write(frame)
+                if written != len(frame):
+                    raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
+            else:
+                self.queue(data)
+
+    def queue(self, data):
+        self.packet_queue.append(data)
+
+    def process_queue(self):
+        if len(self.packet_queue) > 0:
+            data = self.packet_queue.pop(0)
+            self.interface_ready = True
+            self.processOutgoing(data)
+        elif len(self.packet_queue) == 0:
+            self.interface_ready = True
+
+    def readLoop(self):
+        try:
+            in_frame = False
+            escape = False
+            command = KISS.CMD_UNKNOWN
+            data_buffer = ""
+            command_buffer = ""
+            last_read_ms = int(time.time()*1000)
+
+            while self.serial.is_open:
+                if self.serial.in_waiting:
+                    byte = 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 = ""
+                    elif (byte == KISS.FEND):
+                        in_frame = True
+                        command = KISS.CMD_UNKNOWN
+                        data_buffer = ""
+                        command_buffer = ""
+                    elif (in_frame and len(data_buffer) < RNodeInterface.MTU):
+                        if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
+                            command = byte
+                        elif (command == KISS.CMD_DATA):
+                            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
+                                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])
+                                    self.log(str(self)+" Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz", RNodeInterface.LOG_DEBUG)
+                                    self.updateBitrate()
+
+                        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])
+                                    self.log(str(self)+" Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz", RNodeInterface.LOG_DEBUG)
+                                    self.updateBitrate()
+
+                        elif (command == KISS.CMD_TXPOWER):
+                            self.r_txpower = ord(byte)
+                            self.log(str(self)+" Radio reporting TX power is "+str(self.r_txpower)+" dBm", RNodeInterface.LOG_DEBUG)
+                        elif (command == KISS.CMD_SF):
+                            self.r_sf = ord(byte)
+                            self.log(str(self)+" Radio reporting spreading factor is "+str(self.r_sf), RNodeInterface.LOG_DEBUG)
+                            self.updateBitrate()
+                        elif (command == KISS.CMD_CR):
+                            self.r_cr = ord(byte)
+                            self.log(str(self)+" Radio reporting coding rate is "+str(self.r_cr), RNodeInterface.LOG_DEBUG)
+                            self.updateBitrate()
+                        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):
+                            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
+                                self.r_stat_rssi = ord(byte)-self.RSSI_OFFSET
+
+                        elif (command == KISS.CMD_STAT_SNR):
+                            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
+                                self.r_stat_snr  = ord(byte)-self.SNR_OFFSET
+
+                        elif (command == KISS.CMD_RANDOM):
+                            self.r_random = ord(byte)
+                        elif (command == KISS.CMD_ERROR):
+                            if (byte == KISS.ERROR_INITRADIO):
+                                self.log(str(self)+" hardware initialisation error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
+                            elif (byte == KISS.ERROR_INITRADIO):
+                                self.log(str(self)+" hardware TX error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
+                            else:
+                                self.log(str(self)+" hardware error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
+                        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
+                    if len(data_buffer) > 0 and time_since_last > self.timeout:
+                        self.log(str(self)+" serial read timeout", RNodeInterface.LOG_DEBUG)
+                        data_buffer = ""
+                        in_frame = False
+                        command = KISS.CMD_UNKNOWN
+                        escape = False
+                    sleep(0.08)
+
+        except Exception as e:
+            self.online = False
+            self.log("A serial port error occurred, the contained exception was: "+str(e), RNodeInterface.LOG_ERROR)
+            self.log("The interface "+str(self.name)+" is now offline.", RNodeInterface.LOG_ERROR)
+
+    def log(self, msg, level=3):
+        logtimefmt   = "%Y-%m-%d %H:%M:%S"
+        if self.loglevel >= level:
+            timestamp = time.time()
+            logstring = "["+time.strftime(logtimefmt)+"] ["+self.loglevelname(level)+"] "+msg
+            print(logstring)
+
+    def loglevelname(self, level):
+        if (level == RNodeInterface.LOG_CRITICAL):
+            return "Critical"
+        if (level == RNodeInterface.LOG_ERROR):
+            return "Error"
+        if (level == RNodeInterface.LOG_WARNING):
+            return "Warning"
+        if (level == RNodeInterface.LOG_NOTICE):
+            return "Notice"
+        if (level == RNodeInterface.LOG_INFO):
+            return "Info"
+        if (level == RNodeInterface.LOG_VERBOSE):
+            return "Verbose"
+        if (level == RNodeInterface.LOG_DEBUG):
+            return "Debug"
+        if (level == RNodeInterface.LOG_EXTREME):
+            return "Extra"
+
+    def hexrep(data, delimit=True):
+        delimiter = ":"
+        if not delimit:
+            delimiter = ""
+        hexrep = delimiter.join("{:02x}".format(ord(c)) for c in data)
+        return hexrep
+
+    def __str__(self):
+        return "RNodeInterface["+self.name+"]"