コード例 #1
0
ファイル: netserver.py プロジェクト: chengzhongkai/floranet
    def _sendJoinResponse(self, request, rxpk, gateway, app, device):
        """Send a join response message
        
        Called if a join response message is to be sent.
        
        Args:
            request: request (GatewayMessage): Received gateway message object
            app (Application): The requested application object
            device (Device): The requesting device object
        """
        # Get receive window parameters and
        # set dlsettings field
        device.rx = self.band.rxparams((device.tx_chan, device.tx_datr),
                                       join=True)
        dlsettings = 0 | self.band.rx1droffset << 4 | device.rx[2]['index']

        # Create the Join Response message
        log.info("Sending join response for devaddr {devaddr}",
                 devaddr=devaddrString(device.devaddr))
        response = JoinAcceptMessage(app.appkey, app.appnonce,
                                     self.config.netid, device.devaddr,
                                     dlsettings, device.rx[1]['delay'])
        data = response.encode()
        txpk = self._txpkResponse(device, data, gateway, rxpk.tmst)

        # Send the RX1 window messages
        self.lora.sendPullResponse(request, txpk[1])
        # Send the RX2 window message
        self.lora.sendPullResponse(request, txpk[2])
コード例 #2
0
ファイル: netserver.py プロジェクト: chengzhongkai/floranet
    def _processLinkCheckReq(self, device, command, request, lsnr):
        """Process a link check request
        
        Args:
            device (Device): Sending device
            command (LinkCheckReq): LinkCheckReq object
        """
        # We assume 'margin' corresponds to the
        # absolute value of LNSR, as an integer.
        # Set to zero if negative.
        margin = max(0, round(lsnr))
        # If we are processing the first request,
        # gateway count must be one, we guess.
        gwcnt = 1

        # Create the LinkCheckAns response and encode. Set fcntdown
        command = LinkCheckAns(margin=margin, gwcnt=gwcnt)

        # Queue the command if required
        if self.config.macqueueing:
            self._queueMACCommand(device.deveui, command)
            return

        frmpayload = command.encode()
        fcntdown = device.fcntdown + 1

        # Create the downlink message. Set fport=0,
        # encrypt with NwkSKey and encode
        message = MACDataDownlinkMessage(device.devaddr,
                                         device.nwkskey,
                                         fcntdown,
                                         self.config.adrenable,
                                         '',
                                         0,
                                         frmpayload,
                                         acknowledge=True)
        message.encrypt(device.nwkskey)
        data = message.encode()

        gateway = self.lora.gateway(device.gw_addr)
        if gateway is None:
            log.info(
                "Could not find gateway for gateway {gw_addr} for device "
                "{devaddr}",
                gw_addr=device.gw_addr,
                devaddr=devaddrString(device.devaddr))
            return

        # Create GatewayMessage and Txpk objects, send immediately
        request = GatewayMessage(version=1,
                                 token=0,
                                 remote=(gateway.host, gateway.port))
        device.rx = self.band.rxparams((device.tx_chan, device.tx_datr))
        txpk = self._txpkResponse(device, data, gateway, immediate=True)

        # Update the device fcntdown
        device.update(fcntdown=fcntdown)

        # Send the RX2 window message
        self.lora.sendPullResponse(request, txpk[2])
コード例 #3
0
ファイル: test_util.py プロジェクト: ckycst/floranet
 def test_devaddrString(self):
     eui = int('0x06100000', 16)
     
     expected = "06:10:00:00"
     result = util.devaddrString(eui)
     
     self.assertEqual(expected, result)
コード例 #4
0
ファイル: test_util.py プロジェクト: uicm-mas/floranet
    def test_devaddrString(self):
        """Test devaddrString"""
        expected = "0610.0000"

        eui = int('0x06100000', 16)        
        result = util.devaddrString(eui)
        
        self.assertEqual(expected, result)
コード例 #5
0
    def valid(self, server):
        """Validate a device object.
        
        Args:
            server (NetServer): Network server object
            
        Returns:
            valid (bool), message(dict): (True, empty) on success,
            (False, error message dict) otherwise.
        """
        messages = {}
        
        # Check we have a valid class
        if not self.devclass in {'a', 'c'}:
            messages['class'] = "Invalid device class"
        
        # For ABP device, check for unique devaddr and keys, and devaddr
        # validity
        if not self.otaa:
            check = {'devaddr': self.devaddr, 'nwkskey': self.nwkskey,
                     'appskey': self.appskey}
            for attr,v in check.items():
                exists = yield Device.exists(where=[attr + ' = ? AND deveui != ?',
                                                    v, self.deveui])
                if exists:
                    messages[attr] = "Device {} {} ".format(attr, v) + \
                                     "currently exists. Must be unique."

            # Check devaddr is correctly defined within the network range
            if not 'devaddr' in messages:
                if not server.checkDevaddr(self.devaddr):
                    messages['devaddr'] = "Device devaddr " + \
                            "{} ".format(devaddrString(self.devaddr)) + \
                            "is not within the configured network address range"

            # Check devaddr is not within the OTAA range
            if not 'devaddr' in messages:
                if self.devaddr >= server.config.otaastart and \
                 self.devaddr <= server.config.otaaend:
                    messages['devaddr'] = "Device devaddr " \
                            "{} ".format(devaddrString(self.devaddr)) + \
                             "is within configured OTAA address range"

        valid = not any(messages)
        returnValue((valid, messages))
コード例 #6
0
def show(ctx):
    """show the system configuration.
    
    Args:
        ctx (Context): Click context
    """

    # Form the url and payload
    server = ctx.obj['server']
    payload = {'token': ctx.obj['token']}
    url = 'http://{}/api/v{}/system'.format(server, str(version))

    # Make the request
    data = restRequest(server, url, 'get', payload, 200)
    if data is None:
        return

    # Single application
    c = data
    indent = ' ' * 10
    click.echo('System: {} at {}'.format(c['name'], server))
    click.echo('{}Network interface: {}'.format(indent, c['listen']))
    click.echo('{}LoraWAN port: {}'.format(indent, c['port']))
    click.echo('{}Web server port: {}'.format(indent, c['webport']))
    click.echo('{}Frequency band: {}'.format(indent, c['freqband']))
    click.echo('{}Network ID: 0x{}'.format(indent,
                                           intHexString(c['netid'], 3, sep=2)))
    click.echo('{}OTAA Address Range: 0x{} - 0x{}'.format(
        indent, devaddrString(c['otaastart']), devaddrString(c['otaaend'])))
    t = 'Yes' if c['adrenable'] else 'No'
    click.echo('{}ADR enabled: {}'.format(indent, t))
    if c['adrenable']:
        click.echo('{}ADR margin: {} dB'.format(indent, c['adrmargin']))
        click.echo('{}ADR cycle time: {} s'.format(indent, c['adrcycletime']))
        click.echo('{}ADR message time: {} s'.format(indent,
                                                     c['adrmessagetime']))
    t = 'Yes' if c['fcrelaxed'] else 'No'
    click.echo('{}Relaxed frame count: {}'.format(indent, t))
    t = 'Yes' if c['macqueueing'] else 'No'
    click.echo('{}MAC queueing: {}'.format(indent, t))
    if c['macqueueing']:
        click.echo('{}MAC queue limit: {} s'.format(indent,
                                                    c['macqueuelimit']))
    return
コード例 #7
0
ファイル: netserver.py プロジェクト: chengzhongkai/floranet
    def _sendLinkADRRequest(self, device, command):
        """Send a Link ADR Request message
        
        Called if an ADR change is required for this device.
        
        Args:
            device: (Device): Target device
            command (LinkADRReq): Link ADR Request object
        """
        frmpayload = command.encode()

        # Create the downlink message. Increment fcntdown, set fport=0,
        # encrypt with NwkSKey and encode
        fcntdown = device.fcntdown + 1
        log.info("Sending ADR Request to devaddr {devaddr}",
                 devaddr=devaddrString(device.devaddr))
        message = MACDataDownlinkMessage(device.devaddr, device.nwkskey,
                                         fcntdown, self.config.adrenable, '',
                                         0, frmpayload)
        message.encrypt(device.nwkskey)
        data = message.encode()

        gateway = self.lora.gateway(device.gw_addr)
        if gateway is None:
            log.info(
                "Could not find gateway for gateway {gw_addr} for device "
                "{devaddr}",
                gw_addr=device.gw_addr,
                devaddr=devaddrString(device.devaddr))
            returnValue(None)

        # Create GatewayMessage and Txpk objects, send immediately
        request = GatewayMessage(version=1,
                                 token=0,
                                 remote=(gateway.host, gateway.port))
        device.rx = self.band.rxparams((device.tx_chan, device.tx_datr))
        txpk = self._txpkResponse(device, data, gateway, immediate=True)

        # Update the device fcntdown
        device.update(fcntdown=fcntdown)

        # Send the RX2 window message
        self.lora.sendPullResponse(request, txpk[2])
コード例 #8
0
ファイル: netserver.py プロジェクト: chengzhongkai/floranet
 def _processLinkADRAns(self, device, command):
     """Process a link ADR answer
     
     Returns three ACKS: power_ack, datarate_ack, channelmask_ack
     
     Args:
         device (Device): Sending device
         command (LinkADRAns): LinkADRAns object
     """
     # Not much to do here - we will know if the device had changed datarate via
     # the rxpk field.
     log.info("Received LinkADRAns from device {devaddr}",
              devaddr=devaddrString(device.devaddr))
コード例 #9
0
def show(ctx, deveui):
    """show a device, or all devices.
    
    Args:
        ctx (Context): Click context
        deveui (str): Device EUI string
    """
    if '.' in deveui:
        deveui = str(hexStringInt(str(deveui)))
    
    # Form the url and payload
    server = ctx.obj['server']
    payload = {'token': ctx.obj['token']}
    url = 'http://{}/api/v{}'.format(server, version)
    url += '/devices' if deveui == 'all' else '/device/{}'.format(deveui)
    
    # Make the request
    data = restRequest(server, url, 'get', payload, 200)
    if data is None:
        return
    
    # Single device
    if deveui != 'all':
        d = data
        indent = ' ' * 10
        enable = 'enabled' if d['enabled'] else 'disabled'
        drate = d['tx_datr'] if d['tx_datr'] else 'N/A'
        nwkid = hex(d['devaddr'] >> 25)
        snrav = '{0:.2f} dBm'.format(d['snr_average']) if d['snr_average'] else 'N/A'
        appname = d['appname'] if d['appname'] else 'N/A'
        lat = '{0:.4f}'.format(d['latitude']) if d['latitude'] else 'N/A'
        lon = '{0:.4f}'.format(d['longitude']) if d['longitude'] else 'N/A'
        activ = 'Over the air (OTAA)' if d['otaa'] else 'Personalization (ABP)'
        click.echo('Device EUI: ' + euiString(d['deveui']))
        click.echo(indent + 'device address ' + devaddrString(d['devaddr']) + \
                   ' nwkID ' + nwkid + ' ' + enable)
        click.echo(indent + 'name: ' + d['name'])
        click.echo(indent + 'class: ' + d['devclass'].upper())
        click.echo(indent + 'application EUI: ' + euiString(d['appeui']))
        click.echo(indent + 'activation: ' + activ)
        click.echo(indent + 'appname: ' + appname)
        click.echo(indent + 'latitude: ' + lat)
        click.echo(indent + 'longitude: ' + lon)
        if not d['otaa']:
            click.echo(indent + 'appskey: ' + intHexString(d['appskey'], 16))
            click.echo(indent + 'nwkskey: ' + intHexString(d['nwkskey'], 16))
        click.echo(indent + 'data rate: ' + drate)
        click.echo(indent + 'average SNR: ' + snrav)
        return
        
    # All devices
    click.echo('{:15}'.format('Device') + \
               '{:24}'.format('DeviceEUI') + \
               '{:12}'.format('DevAddr') + \
               '{:9}'.format('Enabled') + \
               '{:5}'.format('Act') + \
               '{:12}'.format('Average-SNR'))
    for i,d in data.iteritems():
        enable = 'Yes' if d['enabled'] else 'No'
        active = 'OTA' if d['otaa'] else 'ABP'
        snravg = '{0:.2f} dBm'.format(d['snr_average']) if d['snr_average'] else 'N/A'
        click.echo('{:14.14}'.format(d['name']) + ' ' + \
                   '{:23}'.format(euiString(d['deveui'])) +  ' ' + \
                   '{:12}'.format(devaddrString(d['devaddr'])) + \
                   '{:9}'.format(enable) + \
                   '{:5}'.format(active) + \
                   '{:12}'.format(snravg))
コード例 #10
0
ファイル: netserver.py プロジェクト: chengzhongkai/floranet
    def inboundAppMessage(self, devaddr, appdata, acknowledge=False):
        """Sends inbound data from the application interface to the device
        
        Args:
            devaddr (int): 32 bit device address (DevAddr)
            appdata (str): packed application data
            acknowledge (bool): Acknowledged message
        """

        log.info("Inbound message to devaddr {devaddr}",
                 devaddr=devaddrString(devaddr))

        # Retrieve the active device
        device = yield self._getActiveDevice(devaddr)
        if device is None:
            log.error("Cannot send to unregistered device address {devaddr}",
                      devaddr=devaddrString(devaddr))
            returnValue(None)

        # Check the device is enabled
        if not device.enabled:
            log.error(
                "Inbound application message for disabled device "
                "{deveui}",
                deveui=euiString(device.deveui))
            returnValue(None)

        # Get the associated application
        app = yield Application.find(where=['appeui = ?', device.appeui],
                                     limit=1)
        if app is None:
            log.error(
                "Inbound application message for {deveui} - "
                "AppEUI {appeui} does not match any configured applications.",
                deveui=euiString(device.deveui),
                appeui=device.appeui)
            returnValue(None)

        # Find the gateway
        gateway = self.lora.gateway(device.gw_addr)
        if gateway is None:
            log.error(
                "Could not find gateway for inbound message to "
                "{devaddr}.",
                devaddr=devaddrString(device.devaddr))
            returnValue(None)

        # Increment fcntdown
        fcntdown = device.fcntdown + 1

        # Piggyback any queued MAC messages in fopts
        fopts = ''
        device.rx = self.band.rxparams((device.tx_chan, device.tx_datr),
                                       join=False)
        if self.config.macqueueing:
            # Get all of this device's queued commands: this returns a list of tuples (index, command)
            commands = [(i, c[2]) for i, c in enumerate(self.commands)
                        if device.deveui == c[1]]
            for (index, command) in commands:
                # Check if we can accommodate the command. If so, encode and remove from the queue
                if self.band.checkAppPayloadLen(device.rx[1]['datr'],
                                                len(fopts) + len(appdata)):
                    fopts += command.encode()
                    del self.commands[index]
                else:
                    break

        # Create the downlink message, encrypt with AppSKey and encode
        response = MACDataDownlinkMessage(device.devaddr,
                                          device.nwkskey,
                                          device.fcntdown,
                                          self.config.adrenable,
                                          fopts,
                                          int(app.fport),
                                          appdata,
                                          acknowledge=acknowledge)
        response.encrypt(device.appskey)
        data = response.encode()

        # Create Txpk objects
        txpk = self._txpkResponse(device,
                                  data,
                                  gateway,
                                  itmst=int(device.tmst),
                                  immediate=False)
        request = GatewayMessage(version=2,
                                 gatewayEUI=gateway.eui,
                                 remote=(gateway.host, gateway.port))

        # Save the frame count down
        device.update(fcntdown=fcntdown)

        # Send RX1 window message
        self.lora.sendPullResponse(request, txpk[1])
        # If Class A, send the RX2 window message
        self.lora.sendPullResponse(request, txpk[2])
コード例 #11
0
ファイル: netserver.py プロジェクト: chengzhongkai/floranet
    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)