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))
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")
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
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
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)
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)
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")
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)
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)
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")
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")
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', )
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)