]> git.tnoah.ca Git - lora-radio.git/commitdiff
Library and example
authorMark Qvist <mark@adepta.io>
Fri, 22 Jun 2018 09:17:14 +0000 (11:17 +0200)
committerMark Qvist <mark@adepta.io>
Fri, 22 Jun 2018 09:17:14 +0000 (11:17 +0200)
.gitignore
Libraries/Example.py [new file with mode: 0644]
Libraries/RNode.py [new file with mode: 0644]
Libraries/RNodeInterface.py [deleted file]
README.md

index 2e61a39d81ac66c3a744dac9af6102db120662db..e16aab267ccc92145bf83f632b6dc608a191a066 100644 (file)
@@ -1,4 +1,5 @@
 .DS_Store
 *.hex
+*.pyc
 TODO
 
diff --git a/Libraries/Example.py b/Libraries/Example.py
new file mode 100644 (file)
index 0000000..45d9ba5
--- /dev/null
@@ -0,0 +1,37 @@
+# This is a short example program that
+# demonstrates the bare minimum of using
+# RNode in a Python program. First we'll
+# import the RNodeInterface class.
+from RNode import RNodeInterface
+
+# We'll also define which serial port the
+# RNode is attached to.
+serialPort = "/dev/ttyUSB0"
+
+# This function gets called every time a
+# packet is received
+def gotPacket(data, rnode):
+       print "Received a packet: "+data
+
+# Create an RNode instance. This configures
+# and powers up the radio.
+rnode = RNodeInterface(
+       callback = gotPacket,
+       name = "My RNode",
+       port = serialPort,
+       frequency = 868000000,
+       bandwidth = 125000,
+       txpower = 2,
+       sf = 7,
+       cr = 5,
+       loglevel = RNodeInterface.LOG_DEBUG)
+
+# Enter a loop waiting for user input.
+try:
+       print "Waiting for packets, hit enter to send a packet, Ctrl-C to exit"
+       while True:
+               raw_input()
+               rnode.send("Hello World!")
+except KeyboardInterrupt as e:
+       print ""
+       exit()
\ No newline at end of file
diff --git a/Libraries/RNode.py b/Libraries/RNode.py
new file mode 100644 (file)
index 0000000..7a53da0
--- /dev/null
@@ -0,0 +1,477 @@
+from __future__ import print_function
+from time import sleep
+import sys
+import serial
+import threading
+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_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
+       
+
+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
+
+       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_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):
+                                                       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):
+                                                               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+"]"
+
diff --git a/Libraries/RNodeInterface.py b/Libraries/RNodeInterface.py
deleted file mode 100644 (file)
index 72f227b..0000000
+++ /dev/null
@@ -1,472 +0,0 @@
-from __future__ import print_function
-from Interface import Interface
-from time import sleep
-import sys
-import serial
-import threading
-import time
-import math
-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_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_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
-       
-
-class RNodeInterface():
-       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
-
-       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_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):
-               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) < RNS.Reticulum.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):
-                                                       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):
-                                                               self.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
-                                                       elif (byte == KISS.ERROR_INITRADIO):
-                                                               self.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
-                                                       else:
-                                                               self.log(str(self)+" hardware error (code "+RNS.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(msg, level=3):
-               if self.loglevel >= level:
-                       timestamp = time.time()
-                       logstring = "["+time.strftime(logtimefmt)+"] ["+self.loglevelname(level)+"] "+msg
-
-                       if (logdest == LOG_STDOUT):
-                               print(logstring)
-
-       def loglevelname(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 __str__(self):
-               return "RNodeInterface["+self.name+"]"
-
index 067bde0537fbda429c605f24e2198045d4d39706..0e1fca471c08b6d6d1752af57995643986588082 100644 (file)
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ Have a look in the "Libraries" folder for includes to let you easily use RNode i
 Here's a Python example:
 
 ```python
-import RNodeInterface
+from RNode import RNodeInterface
 
 def gotPacket(data, rnode):
        print "Received a packet: "+data
@@ -76,7 +76,7 @@ rnode = RNodeInterface(
        txpower = 2,
        sf = 7,
        cr = 5,
-       loglevel = RnodeInterface.LOG_DEBUG)
+       loglevel = RNodeInterface.LOG_DEBUG)
 
 rnode.send("Hello World!")
 ```