Example #1
0
    def _is_fwrev_ge(self, val):
        ''' check if current fw release match is Greater or Equal to the one specified as a parameter. '''
        _cur_fwrev = self._fw_rev[:]
        # extract integers
        try:
            _expected_fwrev = [int(x) for x in val.split('.')]
        except Exception as ex:
            log.error("unable to parse fw_rev '%s' " + str(ex))
            raise ex

        # extends list to match each others
        difflen = len(_cur_fwrev) - len(_expected_fwrev)
        if (difflen > 0):
            _expected_fwrev.extend([0] * abs(difflen))
        elif (difflen < 0):
            _cur_fwrev.extend([0] * abs(difflen))

        # check fw revisions
        for cur, expected in zip(_cur_fwrev, _expected_fwrev):
            if (cur > expected):
                return True
            elif (cur < expected):
                return False
        # revisions are the same
        return True
Example #2
0
    def connect(self, mode=None, **kwargs):
        ''' join either in OTAA or ABP mode '''

        # SF12 for join mode
        log.debug("[join] setting dr to 0 as default for join mode ...")
        self._command("mac set dr 0", True)
        self._command("radio set sf sf12", True)

        _res = None
        if (mode.lower() == 'otaa'):
            _res = self._loraOTAAjoin(**kwargs)
        elif (mode.lower() == 'abp'):
            _res = self._loraABPjoin(**kwargs)
        else:
            log.error("unknwon join mode '%s' ?!?!, ought to be one of %s" %
                      (str(mode), str(__class__.LORA_JOIN_MODES)))
            return False
        if _res is not True: return False

        # set channels status and data rates
        if (mode.lower() == 'abp' or self._command("mac get adr") == 'off'):
            log.debug("\t... setting channels DataRates (DR) and status ...")
            # set data rates (useless in OTAA with ADR enabled ?)
            # 0 --> SF12/125kHz
            # 1 --> SF11/125kHz
            # 2 --> SF10/125kHz
            # 3 --> SF9/125kHz
            # 4 --> SF8/125kHz
            # 5 --> SF7/125kHz
            self._command("mac set ch drrange 3 0 5", True)
            self._command("mac set ch drrange 4 0 5", True)
            self._command("mac set ch drrange 5 0 5", True)
            self._command("mac set ch drrange 6 0 5", True)
            self._command("mac set ch drrange 7 0 5", True)
            # enable channels (useless in OTAA with ADR enabled ?)
            self._command("mac set ch status 3 on", True)
            self._command("mac set ch status 4 on", True)
            self._command("mac set ch status 5 on", True)
            self._command("mac set ch status 6 on", True)
            self._command("mac set ch status 7 on", True)

        # set duty cycle
        log.debug("\t... setting duty-cycle to '%d' ..." % self._dcycle)
        for _ch in range(8):
            self._command("mac set ch dcycle %d %d" % (_ch, self._dcycle),
                          True)

        # is there specified a DR for data exchange ?
        if self._data_rate is not None:
            log.info("set data_rate = '%d' for DATA exchanges ..." %
                     self._data_rate)
            self._command("mac set dr %d" % self._data_rate, True)

        # is there specified a SF for data exchange ?
        if self._data_sf is not None and self._data_sf.lower(
        ) in __class__.LORA_SPREAD_FACTOR:
            log.info("set data_sf = '%s' for DATA exchanges ..." %
                     self._data_sf.lower())
            self._command("radio set sf %s" % self._data_sf.lower(), True)
Example #3
0
 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
Example #4
0
 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)
Example #5
0
 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
Example #6
0
 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
Example #7
0
 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))
Example #8
0
    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()
Example #9
0
 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
Example #10
0
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()
Example #11
0
    def __init__(self,
                 port=None,
                 speed=0,
                 shutdown_event=None,
                 *args,
                 **kwargs):

        log.info("Initializing '%s' module ..." % __class__.__name__)
        self._addons = None

        self._link = None
        self.serial_lock = None

        self._appKEY = None
        self._batlvl = None

        self._data_rate = None

        self._shutdown_event = shutdown_event

        if kwargs is not None:
            self._addons = kwargs

        if port is not None:
            self.serial_port = port
            try:
                self.serial_speed = int(speed) if int(
                    speed) != 0 else __class__.DEFAULT_SERIAL_SPEED
            except ValueError as ex:
                log.error("link_speed '%s' is not an integer ... aborting" %
                          (str(speed)))
                raise ex

        # reset_pin option ?
        if kwargs.get('reset_pin', None) is not None:
            self._gpio_rst = int(kwargs['reset_pin'])
            GPIO.setup(
                self._gpio_rst, GPIO.HIGH
            )  # set HIGH before OUT mode to avoid spurious RST on RN2483
            GPIO.setup(self._gpio_rst, GPIO.OUT)
            log.info("\tsetting GPIO '%d' as reset pin ..." % self._gpio_rst)
            # ... and remove from dict
            kwargs.pop('reset_pin', None)

        # thread related initialization
        self._condition = threading.Condition()
        self._thread = None

        # check link to test presence of serial adapter
        # and presence of a RN2483 module
        self.start_serial()
        if (self._fw_rev is None):
            raise Exception("unable to read FW revision of RN2483 module ?!?!")

        # radioModule factory reset settings
        self.loraFactoryReset()

        # LoRaWAN setup
        self._command("mac reset 868", True)  # configure radio for 868MHz ops
        self._command(
            "mac set retx 2", True
        )  # for confirmed type messages, number of retransmissions until gateway ack
        self._command(
            "mac set dr 0", True
        )  # set data rate to 0:SF12/125kHz (slowest) 5:SF7/125kHz (fastest)
        self._command("mac set linkchk 600",
                      True)  # link check process interval
        #self._command("mac set ar on", True)    # automatic reply on downlink reception

        # check for extra parameters
        if (self._addons):
            for key, val in self._addons.items():
                # check for 'ADR'
                if (key.lower() == "adr"):
                    if val is True:
                        log.info("Enabling Adaptative Data Rate (ADR) ...")
                        self._command("mac set adr on")
                    else:
                        log.info("Disabling Adaptative Data Rate (ADR) ...")
                        self._command("mac set adr off")
                    continue
                # check for 'DR' (i.e data rate)
                if (key.lower() == "dr"):
                    log.info("Set DataRate ...")
                    #self._command("mac set dr %d" % int(val))
                    self._data_rate = int(val)
                    continue
                # check for 'duty_cycle'
                if (key.lower() == "duty_cycle"):
                    log.info("Override duty cycle default value ...")
                    self._dcycle = int(100 / int(val)) - 1
                    continue
                # check for 'SF' (i.e data spreading factor)
                if (key.lower() == "sf"):
                    if val.lower() not in __class__.LORA_SPREAD_FACTOR:
                        log.error("Specified SF '%s' not supported" %
                                  val.lower())
                        continue
                    log.info("Set SpreadingFactor ...")
                    #self._command("radio set sf %s" % val)
                    self._data_sf = val.lower()
                    continue
                log.debug("unknwon _addon parameter '%s' ?!?!" % str(key))

        # end of setup
        time.sleep(0.5)