def cr(self, val): ''' set coding rate ''' if (val not in __class__.LORA_CODING_RATE): log.warning("invalid coding rate '%s', ought to be one of %s" % (val, str(__class__.LORA_CODING_RATE))) return self._command("radio set cr " + val, True)
def crc(self, val): ''' set crc parameter in ['on','off'] ''' if (val not in __class__.LORA_ON_OFF): log.warning("invalid CRC parameter '%s', ought to be one of %s" % (val, str(__class__.LORA_ON_OFF))) return self._command("radio set crc " + val, True)
def modulation(self, val): ''' set modulation mode in ['lora','fsk'] ''' if (val not in __class__.LORA_RADIO_MODULATION): log.warning( "invalid radio modulation '%s', ought to be one of %s" % (val, str(__class__.LORA_RADIO_MODULATION))) return self._command("radio set mod " + val, True)
def sf(self, val): ''' set spreading factor ''' if (val not in __class__.LORA_SPREAD_FACTOR): log.warning( "invalid spreading factor '%s', ought to be one of %s" % (val, str(__class__.LORA_SPREAD_FACTOR))) return self._command("radio set sf " + val, True)
def wdt(self, val): ''' set rx watchdog ''' try: if (0 <= int(val) <= __class__.LORA_RX_WATCHDOG): self._command("radio set wdt " + str(val), True) return log.warning("out of limits rx watchdof '%s' ?!?!" % str(val)) except Exception as ex: log.error("error in watchdog timeout value '%s' ?!?!" % str(val)) raise ex
def pwr(self, val): ''' set TX power ''' try: if (int(val) not in __class__.LORA_RADIO_TX_POWER): log.warning("invalid radio tx pwer provided") return except ValueError as ex: log.error("tx PWR '%s' ought to be an integer ... aborting" % (str(val))) raise ex self._command("radio set pwr " + str(val), True)
def freq(self, val): ''' set radio frequency ''' try: for f in __class__.LORA_RADIO_FREQUENCIES: if (int(val) == int(f * 1000000)): self._command("radio set freq " + str(val), True) return log.warning("unknwon frequency '%s' ?!?!" % str(val)) except Exception as ex: log.error("error in frequency value '%s' ?!?!" % str(val)) raise ex
def classDevice(self, val): ''' set end-device class ''' if (str(val)[0] not in __class__.LORA_DEVICES_CLASSES): log.warning( "invalid end-device class '%c', ought to be one of %s" % (str(val)[0], str(__class__.LORA_DEVICES_CLASSES))) return if (self._is_fwrev_ge('1.0.5')): self._command("mac set class " + str(val)[0], True) else: log.warning("device FW_rev '%s' is < to '1.0.5'" % str(self._fw_rev))
def bw(self, val): ''' set bandwidth ''' try: for _bw in __class__.LORA_BANDWIDTH: if (int(val) == _bw): self._command("radio set bw " + str(val), True) return log.warning("unknwon bandwidth '%s', ought to be one of %s" % (str(val), str(__class__.LORA_BANDWIDTH))) except Exception as ex: log.error("error in bandwidth value '%s' ?!?!" % str(val)) raise ex
def loraSleep(self, val): ''' activate lora system sleep ''' try: if (__class__.LORA_SYS_SLEEP[0] <= int(val) <= __class__.LORA_SYS_SLEEP[1]): log.info("LoRa module will enter in sleep mode for %dms ..." % int(val)) self._command("sys sleep " + str(val)) # respond 'ok' after wakeup return log.warning("out of range sleep value '%s' !" % str(val)) except Exception as ex: log.error("invalid sleep timeout (ms) '%s' !" % str(val))
def start_serial(self): ''' verify serial link is valid ... and a RN2483 is connected to ''' if (self._link is not None): log.warning("# serial link is already active !") return try: # warning: port is immediately opened on link creation log.info("Initialise serial '%s' at speed '%d' ..." % (self.serial_port, self.serial_speed)) self._link = serial.Serial( port=self.serial_port, baudrate=self.serial_speed, bytesize=__class__.DEFAULT_SERIAL_BITS, parity=__class__.DEFAULT_SERIAL_PARITY, stopbits=__class__.DEFAULT_SERIAL_STOP, timeout=__class__.DEFAULT_SERIAL_TIMEOUT, exclusive=True) # hardware reset of the radio module self.radioModuleHWreset() # on reset, RN2483 will send its firmware release # TODO: launch thread waiting for serial data _answer = str() _answer = self._link.read_until().decode( "utf-8") # serial timeout applies here if (len(_answer)): log.debug("[init] recveived str: %s" % (_answer)) if (not ("rn2483" in _answer.lower())): raise Exception( "RN2483 module not found because answer was: " + str(_answer)) self._fw_rev = [ int(x) for x in _answer.lower().split()[1].split('.') ] else: raise Exception("Lora module didn't answered!") except ValueError as ex: log.error("Firmware revision number are not integers: '%s'" % _answer.lower() + str(ex)) raise ex except Exception as ex: log.error("while opening serial port '%s' with baudrate=%d " % (self.serial_port, self.serial_speed) + str(ex)) raise ex log.debug("link '%s' @ '%d'bauds is validated as a serial port :)" % (self.serial_port, self.serial_speed)) # activate locking mechanism self._serial_lock = threading.Lock()
def _loraOTAAjoin(self, deveui=None, appeui=None, appkey=None): ''' join with OTAA protocol returns True, False or raise exception ''' log.info("Start OTAA join procedure ...") if (deveui is not None): self.devEUI = deveui if (appeui is not None): self.appEUI = appeui if (appkey is not None): self.appKEY = appkey # try to join _retry = 5 _sleep = 2 while (self._shutdown_event.is_set() is not True and _retry > 0): _res = self._command( "mac join otaa" ) # check=false because we don't want an exception if answer differs from 'ok' print("(1) join res = %s" % _res) # check for acceptance of command if (_res != "ok"): log.info( "[join][devEUI=0x%s, appEUI=0x%s, appKEY=0x%s] command failed: '%s'" % (self.devEUI, self.appEUI, self.appKEY, _res)) log.debug("... sleeping a bit before retrying join ...") time.sleep(_sleep) _sleep *= 2 _retry -= 1 continue # ... and now wating for the join result print("(2) join ...") _res = self.receiveData(timeout=10000) # wait up to ten seconds print("(3) join _res = %s" % str(_res)) if (_res is None or _res.endswith('\r\n') is not True): log.warning("# WARNING: partial retrieval from serial link !!") raise Eception("partial data from serial received :(") _res = _res.rstrip('\r\n') log.debug("\t received join answer: '%s'" % _res) if (_res == 'accepted'): log.info("\t JOIN successful :)") return True log.info( "[join][devEUI=0x%s, appEUI=0x%s, appKEY=0x%s] connection failed with code '%s'" % (self.devEUI, self.appEUI, self.appKEY, str(_res))) log.debug("... sleeping a bit before retrying join ...") time.sleep(_sleep) _sleep *= 2 _retry -= 1 return False
def batlvl(self, val): ''' set battery-level ''' if (val is None): self._batlvl = None return try: if (int(val) in __class__.LORA_BAT_LVL_RNG): _res = self._command("mac set bat %d" % val, True) if (_res is None or _res != 'ok'): raise Exception("error while setting battery level!") return log.warning("out-of-range battery-level '%s'" % str(val)) except Exception as ex: log.error("error in battery-level value '%s' ?!?!" % str(val)) raise ex
def main(): # Global variables global _shutdownEvent # print("\n###\nLoRaWAN powermeter demo based on RN2483") print("###") # create threading.event _shutdownEvent = threading.Event() # Trap CTRL+C (kill -2) signal.signal(signal.SIGINT, ctrlc_handler) # Parse arguments parser = argparse.ArgumentParser( description="LoRaWAN powermeter based on RN2483 \ \n Hit <ENTER> to terminate program.") parser.add_argument('-p', '--port', type=str, dest="serial_link", nargs='?', help="Serial port to use, eg. /dev/ttyAMA0.") parser.add_argument('-s', '--speed', type=int, dest="serial_link_speed", nargs='?', help="Absolute path to labels file.") parser.add_argument('--reset-pin', type=int, dest="reset_pin", nargs='?', help="RPi's GPIO connected to RST pin of RN2483.") parser.add_argument( '--set-dcycle', type=int, dest="duty_cycle", nargs='?', help="Set duty cycle percent from 0 to 100 on all channels.") # debug mode parser.add_argument('-d', '--debug', action="store_true", help="Toggle debug mode (True as default).") ARGS = parser.parse_args() #print(ARGS) # logging if (ARGS.debug is True): print("\n[DBG] DEBUG mode activated ... ") setLogLevel(logging.DEBUG) else: print("\n[LOG] level set to %d" % int(settings.log_level)) setLogLevel(settings.log_level) # Setup GPIOs GPIO.setmode(GPIO.BCM) ''' # # Modbus initialisation log.info("Instantiate and enable modbus backend ...") try: kwargs = dict() kwargs['shutdown_event'] = _shutdownEvent if( hasattr(settings, 'modbus_debug') is True ): kwargs['modbus_debug'] = settings.modbus_debug _backend = ModbusBackend(settings.modbus_link, settings.modbus_link_speed, **kwargs) _backend.enable() except Exception as ex: log.error("unable to initialise Modbus backend: " + str(ex) ) raise ex time.sleep(1) ''' # # LoRaWAN # # RN2483 serial initialisation log.info("Starting RN2483 initialisation ...") try: # instantiate device with optional specific configuration _deviceConfig = dict() # register shurdownEvent _deviceConfig['shutdown_event'] = _shutdownEvent # enable/disable ADR mode _deviceConfig['adr'] = True if (hasattr(settings, 'disable_adr') is True): if settings.disable_adr is True: _deviceConfig['adr'] = False # set data exchange DataRate mode 0:SF12/125kHz to 5:SF7/125kHz _deviceConfig['dr'] = 0 # SF12/125kHz if (hasattr(settings, 'data_rate') is True): _deviceConfig['dr'] = settings.data_rate # set data exchange DataRate spreading factor (default is SF12) if (hasattr(settings, 'data_sf') is True): _deviceConfig['sf'] = settings.data_sf # optional reset pin if (ARGS.reset_pin is not None): _deviceConfig['reset_pin'] = ARGS.reset_pin elif (hasattr(settings, 'reset_pin') is True): _deviceConfig['reset_pin'] = settings.reset_pin # optional duty cycle if (ARGS.duty_cycle is not None): _deviceConfig['duty_cycle'] = ARGS.duty_cycle elif (hasattr(settings, 'duty_cycle') is True): _deviceConfig['duty_cycle'] = settings.duty_cycle # # instantiate LoRaWAN device device = RN2483( ARGS.serial_link if ARGS.serial_link is not None else settings.serial_link, ARGS.serial_link_speed if ARGS.serial_link_speed is not None else settings.serial_link_speed, **_deviceConfig) # tell we're on external power supply device.batlvl = 0 except Exception as ex: log.error("### ERROR on RN2483 instantiation: " + str(ex)) raise ex log.info("RN2483 successfully instantiated :)") # RN2483 LoRaWAN initialisation log.info("Starting LoRaWAN initialisation ...") try: # [RADIO] LoRa parameters #device.mod = 'lora' # default modulation: LoRa #device.freq = int(868.1*1000000) # default to 868.1MHz device.pwr = 14 #device.sf = 'sf7' # default sf12 #device.cr = '4/8' # default 4/5 #device.bw = 250 # default 125kHz #device.crc = 'off' # default on # [MAC] LoRaWAN parameters device.devEUI = settings.deveui device.appEUI = settings.appeui device.appKEY = settings.appkey # save parameters #device.loraMacSave() except Exception as ex: log.error("### LoRaWAN initialization error: " + str(ex)) raise ex log.info("LoRaWAN setup successful :)") print(device) time.sleep(2) # # main loop txData = None while (not _shutdownEvent.is_set()): try: # need to join ? if (device.isConnected() is not True): # activate OTAA join device.connect(mode='otaa') # are we connected ? if (device.isConnected() is not True): time.sleep(5) continue # print device detailed status print(device) log.debug("sleeping a bit before continuing ...") time.sleep(5) _startTime = datetime.now() # # TX some data # TODO: make use of optional port :) log.info("Sending some data ...") txData = "hello from RN2483!" print("MyData = %s" % str(txData)) # # TX messages with or without acknowledge # Note: if 1% duty-cycle is disabled, best is to ask for acknowledge _ask4cnf = True if (hasattr(settings, 'ask_cnf') is True): _ask4cnf = settings.ask_cnf if (device.transmitData(str(txData), ack=_ask4cnf) is not True): log.debug("tx command not accepted by LoRaWAN stack :(") log.debug("... sleeping a bit before retrying ...") time.sleep(7) continue _txAnswer = None # LoRaWAN Class A | wait for some data in return from previous transmit log.info("Waiting for some data ...") while ((datetime.now() - _startTime).total_seconds() < settings.loraTimer and not _shutdownEvent.is_set()): print("_*_", end='', flush=True) rxData = device.receiveData() if (rxData is None): continue if (not rxData.endswith('\r\n')): log.warning("Partial data received !!\n\t'%s'" % str(rxData)) time.sleep(1) continue rxData = rxData.rstrip('\r\n') if (_txAnswer is not None): # this RX1 or RX2 or class C device ? log.info("Received data = '%s'" % str(rxData)) time.sleep(1) continue # TX answer received log.info("TX answer received: '%s'" % rxData) if (rxData == 'mac_tx_ok'): _txAnswer = rxData continue elif (rxData.startswith('mac_rx')): # RX1 or RX2 slots received data _rxFields = rxData.split() print("\n\tPort[%d] answer received: '%s'" % (int(_rxFields[1]), str(_rxFields[2:]))) # TODO: process incomming data print("\tTODO: process incoming data!") _txAnswer = rxData time.sleep(1) continue elif (rxData == 'mac_err'): # failure to send log.warning( "'mac_err' ... failed to transmit data ... mac status = 0x%08X ... try to resend :|" % (device.macStatus)) time.sleep(4) break elif (rxData.startswith('invalid_data_len')): # payload too large log.warning( "oversized payload ... (ADR mode??) ... try to resend :|" ) time.sleep(4) break elif (rxData.startswith('invalid')): # [aug.19] let's consider this as a warning ... log.warning( "there's something wrong in the TX process ... ... mac status = 0x%08X ... try to resend" % (device.macStatus)) time.sleep(4) else: log.warning("unknwown TX answer '%s' ?!?! ... continuing" % rxData) time.sleep(1) except Exception as ex: exc_type, exc_obj, exc_tb = sys.exc_info() log.error("exception '%s'(line %d): " % (exc_type, exc_tb.tb_lineno) + str(ex)) time.sleep(3) continue # destroy instance log.info("ending LoRaWAN ops ...") del (device) # GPIO cleanup GPIO.cleanup()
def loraMacPause(self): ''' pause LoRaWAN stack to enable RADIO modifications when a join has already been activated ''' log.warning("#WARNING: pausing LoRaWAN stack ...") self._command("mac pause")
def appEUI(self, val): ''' set appEUI ''' if (val is None): log.warning("unsupported appEUI '%s'" % str(val)) return self._command("mac set appeui " + str(val), True)
def appKEY(self, val): if (val is None): log.warning("unsupported appKEY '%s'" % str(val)) return if (self._command("mac set appkey " + str(val), True) == 'ok'): self._appKEY = str(val)