Exemple #1
0
    def post(self):
        """Method to create a device"""
        deveui = self.args['deveui']
        name = self.args['name']
        devclass = self.args['devclass']
        enabled = self.args['enabled']
        otaa = self.args['otaa']
        devaddr = self.args['devaddr']
        appeui = self.args['appeui']
        nwkskey = self.args['nwkskey']
        appskey = self.args['appskey']
        
        message = {}

        # Check for required args
        required = {'deveui', 'name', 'devclass', 'enabled',
                    'otaa', 'appeui', 'devaddr', 'nwkskey', 'appskey'}
        for r in required:
            if otaa is True:
                if r in {'devaddr', 'nwkskey', 'appskey'}:
                    continue
            if self.args[r] is None:
                message[r] = "Missing the {} parameter.".format(r)
        if message:
            abort(400, message=message)

        # Check this device does not currently exist
        exists = yield Device.exists(where=['deveui = ?', deveui])
        if exists:
            message[r] = "Device {} currently exists.".format(euiString(deveui))
            abort(400, message=message)

        # Set devaddr to None if this is an otaa device
        if otaa:
            devaddr = None
        
        # Create and validate
        device = Device(deveui=deveui, name=name, devclass=devclass,
                        enabled=enabled, otaa=otaa, devaddr=devaddr,
                        appeui=appeui, nwkskey=nwkskey, appskey=appskey,
                        fcntup=0, fcntdown=0, fcnterror=False)
        (valid, message) = yield device.valid(self.server)
        if not valid:
            abort(400, message=message)

        try:
            d = yield device.save()
            if d is None:
                abort(500, message={'error': "Error saving device"})
            location = self.restapi.api.prefix + '/device/' + str(device.deveui)
            returnValue(({}, 201, {'Location': location}))
        except TimeoutError:
            # Exception returns 500 to client
            log.error("REST API timeout for device POST request")
    def delete(self, appeui):
        """Method to handle application DELETE requests
        
        Args:
            appeui (int): Application EUI
        """

        try:
            # Check that no devices exist with this AppEUI.
            devices = yield Device.find(where=['appeui = ?', appeui], limit=1)
            if devices is not None:
                abort(400, message={'error': "Cannot delete - devices exist " \
                    "with Application EUI {}".format(euiString(appeui))})

            # Return a 404 if not found.
            app = yield Application.find(where=['appeui = ?', appeui], limit=1)
            if app is None:
                abort(404,
                      message={
                          'error':
                          "Application {} doesn't exist.".format(
                              euiString(appeui))
                      })
            yield app.delete()
            returnValue(({}, 200))

        except TimeoutError:
            log.error("REST API timeout retrieving application {appeui}",
                      appeui=euiString(appeui))
Exemple #3
0
    def put(self, deveui):
        """Method to handle device PUT requests
        
        Args:
            deveui (int): Device deveui
        """
        try:
            device = yield Device.find(where=['deveui = ?', deveui], limit=1)
            # Return a 404 if not found.
            if device is None:
                abort(404, message={'error': "Device {} doesn't exist".
                                    format(euiString(deveui))})
            
            kwargs = {}
            for a,v in self.args.items():
                if v is not None and v != getattr(device, a):
                    kwargs [a] = v
                    setattr(device, a, v)
            (valid, message) = yield device.valid(self.server)
            if not valid:
                abort(400, message=message)
            
            # Update the device with the new attributes
            if kwargs:
                device.update(**kwargs)
            returnValue(({}, 200))

        except TimeoutError:
            log.error("REST API timeout for device PUT request")
Exemple #4
0
    def _processADRRequests(self):
        """Updates devices with target data rate, and sends ADR requests.
        
        This method is called every adrcycletime seconds as a looping task.
        
        """
        # If we are running, return
        if self.adrprocessing is True:
            returnValue(None)

        self.adrprocessing = True

        devices = yield Device.all()
        sendtime = time.time()

        for device in devices:
            # Check this device is enabled
            if not device.enabled:
                continue

            # Check ADR is enabled
            if not device.adr:
                continue

            # Set the target data rate
            target = device.getADRDatarate(self.band, self.config.adrmargin)

            # If no target, or the current data rate is the target, continue
            if target is None:
                continue

            # Set the device adr_datr
            yield device.update(adr_datr=target)

            # Only send a request if we need to change
            if device.tx_datr == device.adr_datr:
                continue

            # If we are queueing commands, create the command and add to the queue.
            # Replace any existing requests.
            if self.config.macqueueing:
                log.info("Queuing ADR MAC Command")
                command = self._createLinkADRRequest(device)
                self._dequeueMACCommand(device.deveui, command)
                self._queueMACCommand(device.deveui, command)
                continue

            # Check we have reached the next scheduled ADR message time
            scheduled = sendtime + self.config.adrmessagetime
            current = time.time()
            if current < scheduled:
                yield txsleep(scheduled - current)

            # Refresh and send the LinkADRRequest
            sendtime = time.time()
            yield self._sendLinkADRRequest(device)

        self.adrprocessing = False
Exemple #5
0
    def _pollInboundMessages(self):
        """Poll Azure IOT hub for inbound messages and forward
        them to the Network Server"""
        
        # If we are running, return
        if self.polling is True:
            returnValue(None)
        
        log.info("Azure IoT HTTPS interface '{name}' commencing "
                 "polling loop", name=self.name)
        self.polling = True

        # Get the applications associated with this interface. 
        apps = yield Application.find(where=['appinterface_id = ?', self.appinterface.id]) 
        if apps is None:
            self.polling = False
            returnValue(None)
            
        # Loop through the applications 
        for app in apps:
            
            # Poll all devices associated with this app
            devices = yield Device.find(where=['appeui = ?', app.appeui])   
            if devices is None:
                returnValue(None)
                
            for device in devices:
                # Use the device appname property for the Azure devid,
                # if it exists. Otherwise, use the device name property
                devid = device.appname if device.appname else device.name
                
                # Form the url, headers and parameters
                url = 'https://{}/devices/{}/messages/devicebound'.format(
                    self.iothost, devid)
                resuri = '{}/devices/{}'.format(self.iothost, devid)
                headers = {'Authorization': self._iotHubSasToken(resuri)}
                params = {'api-version': self.API_VERSION}
                
                # Make the request, catch any exceptions
                try:
                    r = requests.get(url, headers=headers,
                              params=params, timeout=self.TIMEOUT)
                except requests.exceptions.RequestException:
                    log.debug("Application interface {name} could not poll "
                          "Azure IOT Hub {host} for device ID {device}",
                          name=self.name, host=self.iothost, device=devid)
                    continue
                
                # Response code 204 indicates there is no data to be sent.
                if r.status_code == 204:
                    continue
                # Response code 200 means we have data to send to the device
                elif r.status_code == 200:
                    appdata = r.content
                    self.netserver.inboundAppMessage(device.devaddr, appdata)

        self.polling = False
Exemple #6
0
 def _getActiveDevice(self, devaddr):
     """Searches active devices for the given devaddr.
     
     Args:
         devaddr (int): A 32 bit end device network address (DevAddr).
     
     Returns:
         A device object if successful, None otherwise.
     """
     # Search active device for devaddr
     device = yield Device.find(where=['devaddr = ?', devaddr], limit=1)
     returnValue(device)
Exemple #7
0
    def _test_device(self):
        """Create a test device object """

        return Device(deveui=int('0x0F0E0E0D00010209', 16),
                      devaddr=int('0x06000001', 16),
                      appeui=int('0x0A0B0C0D0A0B0C0D', 16),
                      nwkskey=int('0xAEB48D4C6E9EA5C48C37E4F132AA8516', 16),
                      appskey=int('0x7987A96F267F0A86B739EED480FC2B3C', 16),
                      adr=True,
                      tx_chan=3,
                      tx_datr='SF7BW125',
                      gw_addr='192.168.1.125',
                      enabled=True)
Exemple #8
0
 def get(self):
     """Method to get all devices"""
     try:
         devices = yield Device.all()
         if devices is None:
             returnValue({})
         data = {}
         for i,d in enumerate(devices):
             data[i] = marshal(d, self.fields)
         returnValue(data)
         
     except TimeoutError:
         # Exception returns 500 to client
         log.error("REST API timeout retrieving all devices")
Exemple #9
0
 def _getOTAADevAddrs(self):
     """Get all devaddrs for currently assigned Over the Air Activation (OTAA) devices.
     
     Returns:
         A list of devaddrs.
     """
     devices = yield Device.find(where=[
         'devaddr >= ? AND devaddr <= ?', self.config.otaastart,
         self.config.otaaend
     ],
                                 orderby='devaddr')
     if devices is None:
         returnValue([])
     devaddrs = [d.devaddr for d in devices]
     returnValue(devaddrs)
Exemple #10
0
    def setUp(self):

        # Bootstrap the database
        fpath = os.path.realpath(__file__)
        config = os.path.dirname(fpath) + '/database.cfg'

        db = Database()
        db.parseConfig(config)
        db.start()
        db.register()

        self.device = yield Device.find(where=['appname = ?', 'azuredevice02'],
                                        limit=1)
        self.app = yield Application.find(
            where=['appeui = ?', self.device.appeui], limit=1)
Exemple #11
0
    def get(self, deveui):
        """Method to handle device GET request
        
        Args:
            deveui (int): Device deveui
        """
        try:
            d = yield Device.find(where=['deveui = ?', deveui], limit=1)
            # Return a 404 if not found.
            if d is None:
               abort(404, message={'error': "Device {} doesn't exist".
                                   format(euiString(deveui))})
            returnValue(marshal(d, self.fields))

        except TimeoutError:
            log.error("REST API timeout for device GET request")
Exemple #12
0
    def delete(self, deveui):
        """Method to handle device DELETE requests
        
        Args:
            deveui (int): Device deveui
        """
        try:
            d = yield Device.find(where=['deveui = ?', deveui], limit=1)
            # Return a 404 if not found.
            if d is None:
                abort(404, message={'error': "Device {} doesn't exist".
                                    format(euiString(deveui))})
            deleted = yield d.delete()
            returnValue(({}, 200))

        except TimeoutError:
            log.error("REST API timeout for device DELETE request")
Exemple #13
0
    def _test_device(self):
        """Create a test device object. We must load the device
        dynamically as it depends on the adbapi intialisation"""

        return Device(
            deveui=int('0x0F0E0E0D00010209', 16),
            name='device',
            devclass='c',
            enabled=True,
            otaa=False,
            devaddr=int('0x06000001', 16),
            appeui=int('0x0A0B0C0D0A0B0C0D', 16),
            nwkskey=int('0xAEB48D4C6E9EA5C48C37E4F132AA8516', 16),
            appskey=int('0x7987A96F267F0A86B739EED480FC2B3C', 16),
            tx_chan=3,
            tx_datr='SF7BW125',
            gw_addr='192.168.1.125',
        )
Exemple #14
0
    def processPushDataMessage(self, request, gateway):
        """Process a PUSH_DATA message from a LoraWAN gateway
        
        Args:
            request (GatewayMessage): the received gateway message object
            gateway (Gateway): the gateway that sent the message
        
        Returns:
            True on success, otherwise False
        """
        if not request.rxpk:
            request.rxpk = []
        for rxpk in request.rxpk:
            # Decode the MAC message
            message = MACMessage.decode(rxpk.data)
            if message is None:
                log.info(
                    "MAC message decode error for gateway {gateway}: message "
                    "timestamp {timestamp}",
                    gateway=gateway.host,
                    timestamp=str(rxpk.time))
                returnValue(False)

            # Check if thisis a duplicate message
            if self._checkDuplicateMessage(message):
                returnValue(False)

            # Join Request
            if message.isJoinRequest():
                # Get the application using appeui
                app = yield Application.find(
                    where=['appeui = ?', message.appeui], limit=1)
                #app = next((a for a in self.applications if
                #            a.appeui == message.appeui), None)
                if app is None:
                    log.info(
                        "Message from {deveui} - AppEUI {appeui} "
                        "does not match any configured applications.",
                        deveui=euiString(message.deveui),
                        appeui=message.appeui)
                    returnValue(False)

                # Find the Device
                device = yield Device.find(
                    where=['deveui = ?', message.deveui], limit=1)
                if device is None:
                    #log.info("Message from unregistered device {deveui}",
                    #     deveui=euiString(message.deveui))
                    #returnValue(False)
                    # TODO save device to database (cheng)
                    device = Device(deveui=message.deveui,
                                    name='smk_node',
                                    devclass='A',
                                    enabled=True,
                                    otaa=True,
                                    devaddr=None,
                                    devnonce=[],
                                    appeui=message.appeui,
                                    nwkskey='',
                                    appskey='',
                                    fcntup=0,
                                    fcntdown=0,
                                    fcnterror=False,
                                    snr=[],
                                    snr_average=0)
                    #device.save()
                else:
                    # Check the device is enabled
                    if not device.enabled:
                        log.info("Join request for disabled device {deveui}.",
                                 deveui=euiString(device.deveui))
                        returnValue(False)

                # Process join request
                joined = yield self._processJoinRequest(message, app, device)
                if joined:
                    # Update the ADR measures
                    if self.config.adrenable:
                        device.updateSNR(rxpk.lsnr)

                    yield device.update(tx_chan=rxpk.chan,
                                        tx_datr=rxpk.datr,
                                        devaddr=device.devaddr,
                                        nwkskey=device.nwkskey,
                                        appskey=device.appskey,
                                        time=rxpk.time,
                                        tmst=rxpk.tmst,
                                        gw_addr=gateway.host,
                                        fcntup=0,
                                        fcntdown=0,
                                        fcnterror=False,
                                        devnonce=device.devnonce,
                                        snr=device.snr,
                                        snr_average=device.snr_average)

                    log.info(
                        "Successful Join request from DevEUI {deveui} "
                        "for AppEUI {appeui} | Assigned address {devaddr}",
                        deveui=euiString(device.deveui),
                        appeui=euiString(app.appeui),
                        devaddr=devaddrString(device.devaddr))

                    # Send the join response
                    device.save()
                    self._sendJoinResponse(request, rxpk, gateway, app, device)
                    returnValue(True)
                else:
                    log.info(
                        "Could not process join request from device "
                        "{deveui}.",
                        deveui=euiString(device.deveui))
                    returnValue(False)

            # LoRa message. Check this is a registered device
            device = yield self._getActiveDevice(message.payload.fhdr.devaddr)
            if device is None:
                log.info(
                    "Message from device using unregistered address "
                    "{devaddr}",
                    devaddr=devaddrString(message.payload.fhdr.devaddr))
                returnValue(False)

            # Check the device is enabled
            if not device.enabled:
                log.info("Message from disabled device {devaddr}",
                         devaddr=devaddrString(message.payload.fhdr.devaddr))
                returnValue(False)

            # Check frame counter
            if not device.checkFrameCount(message.payload.fhdr.fcnt,
                                          self.band.max_fcnt_gap,
                                          self.config.fcrelaxed):
                log.info("Message from {devaddr} failed frame count check.",
                         devaddr=devaddrString(message.payload.fhdr.devaddr))
                log.debug(
                    "Received frame count {fcnt}, device frame count {dfcnt}",
                    fcnt=message.payload.fhdr.fcnt,
                    dfcnt=device.fcntup)
                yield device.update(fcntup=device.fcntup,
                                    fcntdown=device.fcntdown,
                                    fcnterror=device.fcnterror)
                returnValue(False)

            # Perform message integrity check.
            if not message.checkMIC(device.nwkskey):
                log.info(
                    "Message from {devaddr} failed message "
                    "integrity check.",
                    devaddr=devaddrString(message.payload.fhdr.devaddr))
                returnValue(False)

            # Update SNR reading and device
            device.updateSNR(rxpk.lsnr)
            yield device.update(tx_chan=rxpk.chan,
                                tx_datr=rxpk.datr,
                                fcntup=device.fcntup,
                                fcntdown=device.fcntdown,
                                fcnterror=device.fcnterror,
                                time=rxpk.time,
                                tmst=rxpk.tmst,
                                adr=bool(message.payload.fhdr.adr),
                                snr=device.snr,
                                snr_average=device.snr_average,
                                gw_addr=gateway.host)

            # Set the device rx window parameters
            device.rx = self.band.rxparams((device.tx_chan, device.tx_datr),
                                           join=False)

            # Process MAC Commands
            commands = []
            # Standalone MAC command
            if message.isMACCommand():
                message.decrypt(device.nwkskey)
                commands = [MACCommand.decode(message.payload.frmpayload)]
            # Contains piggybacked MAC command(s)
            elif message.hasMACCommands():
                commands = message.commands

            for command in commands:
                if command.isLinkCheckReq():
                    self._processLinkCheckReq(device, command, request,
                                              rxpk.lsnr)
                elif command.isLinkADRAns():
                    self._processLinkADRAns(device, command)
                # TODO: add other MAC commands

            # Process application data message
            if message.isUnconfirmedDataUp() or message.isConfirmedDataUp():
                # Find the app
                app = yield Application.find(
                    where=['appeui = ?', device.appeui], limit=1)
                if app is None:
                    log.info(
                        "Message from {devaddr} - AppEUI {appeui} "
                        "does not match any configured applications.",
                        devaddr=euiString(device.devaddr),
                        appeui=device.appeui)
                    returnValue(False)

                # Decrypt frmpayload
                message.decrypt(device.appskey)
                appdata = str(message.payload.frmpayload)
                port = message.payload.fport

                # Route the data to an application server via the configured interface
                log.info("Outbound message from devaddr {devaddr}",
                         devaddr=devaddrString(device.devaddr))
                interface = interfaceManager.getInterface(app.appinterface_id)
                if interface is None:
                    log.error(
                        "No outbound interface found for application "
                        "{app}",
                        app=app.name)
                elif not interface.started:
                    log.error(
                        "Outbound interface for application "
                        "{app} is not started",
                        app=app.name)
                else:
                    self._outboundAppMessage(interface, device, app, port,
                                             appdata)

                # Send an ACK if required
                if message.isConfirmedDataUp():
                    yield self.inboundAppMessage(device.devaddr,
                                                 '',
                                                 acknowledge=True)