Esempio n. 1
0
def wifi():
    print('wifi init')
    pycom.rgbled(0x009999)  # blue
    wlan = WLAN(mode=WLAN.AP,
                ssid='waterLevel',
                auth=(WLAN.WPA2, 'ucuenca1234'),
                channel=7,
                antenna=WLAN.INT_ANT)
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        serversocket.bind(socket.getaddrinfo(
            host, port)[0][-1])  #ipServer 192.168.4.1
    except Exception as e:
        print('bind failed, error code: ', str(e[0]))
        sys.exit()
    serversocket.listen(1)
    print('socket is now listening over port: ', port)

    wifiSocket = True
    while (wifiSocket):
        print('socket init')
        sc, addr = serversocket.accept()
        print('sc: ', sc, ' addr: ', addr)
        recibido = sc.recv(16)
        print('valor recibido :', recibido)
        print('dato[0]: ', recibido[0])
        wifiSocket, msg = calibrationType(recibido)
        sc.send(msg)
    print('closing wifi and socket')
    sc.close()
    serversocket.close()
    wlan.deinit()
Esempio n. 2
0
def find_wifi(testCase=None):
    wlan = WLAN(mode=WLAN.STA)
    try:
        if isinstance(testCase, Exception):
            raise testCase
        wlan.disconnect()
        nets = wlan.scan()
        for net in nets:
            if net.ssid == WIFI_AP['name']:
                #print(net, net[4])
                if not testCase == 'Not found':
                    rssi = net[4]
                    break
        else:
            rssi = -10000
        wlan.deinit()
    except Exception as e:
        return -10000

    if testCase is not None and not testCase == 'Not found':
        rssi = testCase
    if rssi >= 0:
        return -10000

    return rssi
Esempio n. 3
0
def get_WLAN():
    global wlan
    if wlan == None:
        wlan = WLAN(mode=WLAN.STA)
    if wlan.isconnected():
        return True
    nets = wlan.scan()
    for net in nets:
        if net.ssid == config.WLAN_SSID:
            debugprint('Network found!')
            wlan.connect(net.ssid,
                         auth=(net.sec, config.WLAN_WPA),
                         timeout=5000)
            attempts = 0
            while (not wlan.isconnected()) and (attempts < 10):
                debugprint('Connecting...')
                attempts = attempts + 1
                time.sleep(1)
            if wlan.isconnected():
                debugprint('WLAN connection succeeded')
                return True
            else:
                debugprint('WLAN connection failed')
                end_WLAN()
                return False
    debugprint('No known WLAN SSID found')
    wlan.deinit()
    wlan = None
    return False
Esempio n. 4
0
def setup_new_config(logger):
    """
    Start a WiFi AP that provides a configuration page.
    The device automatically reboots and applies modifications upon successful configuration.
    :param logger: status logger
    :type logger: LoggerFactory
    """

    #  Only one of this thread is allowed to run at a time
    if not wifi_lock.locked():
        with wifi_lock:

            logger.info("New configuration setup started")

            # Config uses LED colours to indicate the state of the connection - lock is necessary to disable error pings
            led_lock.acquire(1)
            unique_id = ubinascii.hexlify(machine.unique_id()).decode()
            # set pycom up as access point
            wlan = WLAN(mode=WLAN.AP,
                        ssid=config.get_config("device_name") + unique_id)
            # Connect to PmSensor using password set by the user
            wlan.init(
                mode=WLAN.AP,
                ssid=config.get_config("device_name") + unique_id,
                auth=(WLAN.WPA2, config.get_config("password")),
                channel=1,
                antenna=WLAN.INT_ANT,
            )
            # Load HTML via entering 192,168.4.10 to browser
            wlan.ifconfig(
                id=1,
                config=("192.168.4.10", "255.255.255.0", "192.168.4.1",
                        "192.168.4.1"),
            )

            logger.info("Access point turned on as {}".format(
                config.get_config("device_name") + unique_id))
            logger.info(
                "Configuration website can be accessed at 192.168.4.10")

            address = socket.getaddrinfo(
                "0.0.0.0", 80)[0][-1]  # Accept stations from all addresses
            sct = socket.socket()  # Create socket for communication
            sct.settimeout(int(
                float(config.get_config("config_timeout")) *
                60))  # session times out after x seconds
            gc.collect(
            )  # frees up unused memory if there was a previous connection
            sct.bind(address)  # Bind address to socket
            sct.listen(1)  # Allow one station to connect to socket

            pycom.rgbled(0x000055)  # Blue LED - waiting for connection

            get_new_config(sct, logger)

            wlan.deinit()  # turn off wifi
            gc.collect()

            logger.info("rebooting...")
            machine.reset()
Esempio n. 5
0
    def preSetup(self):
        wlan = WLAN()
        wlan.deinit()  #Wlan uitschakelen
        # Uitschakelen van LTE en Bluetooth zorgt voor een grote vertraging. Normaal zijn deze niet actief dus moeten ze niet uitgeschakeld worden.
        # lte = LTE()
        # lte.deinit(reset=True) #lte (4G) uitschakelen

        # ble = Bluetooth()
        # ble.deinit() #Disable Bluetooth
        print("preSetup completed")
Esempio n. 6
0
def bringup_wlan(ext_ant=False):
    wlan = WLAN()
    wlan.deinit()
    init_kwargs = {
        "mode": WLAN.STA,
        "antenna": WLAN.INT_ANT if not ext_ant else WLAN.EXT_ANT
    }
    wlan.init(**init_kwargs)

    return wlan
Esempio n. 7
0
    def __cli_activation_over_wifi(self, activation_info):
        print('Please wait while we try to connect to {}'.format(
            activation_info.get('s')))
        from network import WLAN
        wlan = WLAN(mode=WLAN.STA)
        attempt = 0
        known_nets = [((activation_info['s'], activation_info['p']))]  # noqa

        print_debug(3, 'WLAN connected? {}'.format(wlan.isconnected()))
        while not wlan.isconnected() and attempt < 10:
            attempt += 1
            print_debug(3, "Wifi connection attempt: {}".format(attempt))
            print_debug(3, 'WLAN connected? {}'.format(wlan.isconnected()))
            available_nets = None
            while available_nets is None:
                try:
                    available_nets = wlan.scan()
                    for x in available_nets:
                        print_debug(5, x)
                    time.sleep(3)
                except:
                    pass

            nets = frozenset([e.ssid for e in available_nets])
            known_nets_names = frozenset([e[0] for e in known_nets])
            net_to_use = list(nets & known_nets_names)
            try:
                net_to_use = net_to_use[0]
                pwd = dict(known_nets)[net_to_use]
                sec = [e.sec for e in available_nets
                       if e.ssid == net_to_use][0]  # noqa
                print_debug(
                    99, "Connecting with {} and {}".format(net_to_use, pwd))
                if sec == 0:
                    wlan.connect(net_to_use, timeout=10000)
                else:
                    wlan.connect(net_to_use, (sec, pwd), timeout=10000)
                start_time = time.time()
                while not wlan.isconnected():
                    if time.time() - start_time > timeout:
                        raise TimeoutError(
                            'Timeout trying to connect via WiFi')
                    time.sleep(0.1)
            except Exception as e:
                if str(e) == "list index out of range" and attempt == 3:
                    print("Please review Wifi SSID and password!")
                    wlan.deinit()
                    return None
                elif attempt == 3:
                    print("Error connecting using WIFI: %s" % e)
                    return None
Esempio n. 8
0
class ConnectWIFI:
    def __init__(self):
        self.wlan = WLAN(mode=WLAN.STA)

    def connectwifi(self):
        if self.isConnected():
            print("Wifi Connected")
        else:
            self.SSID = config.wifissid
            self.PASSWORD = config.wifipassword
            timeout = time.time() + 10
            self.wlan.connect(self.SSID,
                              auth=(WLAN.WPA, self.PASSWORD),
                              channel=9)
            while not self.wlan.isconnected():
                if time.time() > timeout:
                    pycom.rgbled(config.LED_ERROR)
                    time.sleep(0.5)
                    pycom.rgbled(config.LED_ERROR)
                    machine.reset()
                print(".", end="")
                pycom.rgbled(config.LED_blink_WIFI)
                time.sleep(0.5)
                pycom.rgbled(0x000000)
                time.sleep(0.5)
                machine.idle()  # save power while waiting
            print('.........WiFi connected........')
            print("Wifi Config --> ", self.wlan.ifconfig())

    def isConnected(self):
        if self.wlan.isconnected():
            return True
        else:
            return False

    def deinit(self):
        print('Deinitializing wifi radio')
        self.wlan.deinit()

    def disconnect(self):
        print("disconnecting wifi")
        self.wlan.disconnect()

    def reconnect(self):
        if self.isConnected():
            print("wifi connected")
        else:
            print("Trying to reconnect")
            self.__init__()
            self.connectwifi()
Esempio n. 9
0
def set_wlan_to_access_point(ssid="wipy_https_server",
                             password="******",
                             host_ip="192.168.4.1",
                             log=lambda msg: None):
    log("Creating Access Point {} with password {}".format(ssid, password))
    wlan = WLAN()
    wlan.deinit()
    wlan.ifconfig(config=(host_ip, '255.255.255.0', '0.0.0.0', '8.8.8.8'))
    wlan.init(mode=WLAN.AP,
              ssid=ssid,
              auth=(WLAN.WPA2, password),
              channel=5,
              antenna=WLAN.INT_ANT)

    return wlan
Esempio n. 10
0
def software_update(logger):

    with wifi_lock:
        try:
            logger.info("Over the Air update started")

            led_lock.acquire(1)  # disable all other indicator LEDs
            pycom.rgbled(0x555500)  # Yellow LED

            # Get credentials from configuration
            ssid = config.get_config("SSID")
            password = config.get_config("wifi_password")
            server_ip = config.get_config("server")
            port = int(config.get_config("port"))

            print(ssid, password, server_ip, port)

            # Perform OTA update
            ota = WiFiOTA(ssid, password, server_ip, port)

            # Turn off WiFi to save power
            w = WLAN()
            w.deinit()

            ota.connect()
            ota.update()

            time.sleep(5)

        except Exception as e:
            logger.exception("Failed to update the device")
            pycom.rgbled(0x550000)
            time.sleep(3)

        finally:
            # Turn off update mode
            config.save_configuration({"update": False})

            pycom.rgbled(0x000000)
            led_lock.release()

            # Reboot the device to apply patches
            machine.reset()
Esempio n. 11
0
def connect_wifi(network, password, oled):
    wlan = WLAN()
    wlan.deinit()
    wlan.init(mode=WLAN.STA)
    wlan.ifconfig(config=('dhcp'))

    wlan.scan()  # scan for available networks
    wlan.connect(ssid=network, auth=(WLAN.WPA2, password))

    for _ in range(15):
        if wlan.isconnected():
            print("Joining Success")
            # Boot Text with version
            oled.fill(0)
            oled.text("Connected to:", 0, 0)
            oled.text(network, 0, 15)
            oled.text("IP Address:", 0, 40)
            oled.text(wlan.ifconfig()[0], 0, 55)
            oled.show()
            utime.sleep_ms(1000)
            return
        else:
            # Boot Text with version
            oled.fill(0)
            oled.text("Connecting to:", 0, 0)
            oled.text(network, 0, 15)
            oled.show()
            utime.sleep_ms(1000)

    # WiFi Creds probably broken
    print("Can't Join Clearing Credentials")
    kv.set("AP_PWD", False)
    kv.set("AP_SSID", False)

    oled.fill(0)
    oled.text("Failed to", 0, 0)
    oled.text("connect to:", 0, 15)
    oled.text(network, 0, 30)
    oled.text("Rebooting", 0, 55)
    oled.show()
    utime.sleep_ms(3000)
    machine.reset()
Esempio n. 12
0
def wifiTask():
    global wifi_wdt_lock, wifi_stop_flag

    logger = Logger(name='WIFI ' + __version__,
                    level=logging.INFO,
                    filename=None)
    logger.info('** WIFI Task started **')

    wlan = WLAN()
    wlan.deinit()
    uid = str(binascii.hexlify(machine.unique_id())[6:])
    Ssid = 'ENS-PP-' + uid[2:8]
    logger.info('Using Hidden AP SSID:' + Ssid)

    wlan.init(mode=WLAN.AP,
              ssid=Ssid,
              auth=(WLAN.WPA2, 'Li56#fd0gertQQ'),
              channel=8,
              antenna=WLAN.INT_ANT,
              hidden=True)

    server = network.Server()
    server.deinit()  # disable the server
    # enable the server again with new settings
    server.init(login=('ensuser', 'n0ty0urbu5ine55'), timeout=120)

    wifi_ap_init_done = True

    while not wifi_stop_flag.locked():
        if wifi_wdt_lock.locked():
            wifi_wdt_lock.release()
        time.sleep(1)
        if fota_wifi_release_sem.locked() and wifi_ap_init_done == True:
            wlan.deinit()
            wifi_ap_init_done = False
            wlan = None
            logger.info('Deinit AP mode for FOTA')

    try:
        wlan.deinit()
    except:
        pass

    logger.error('** WIFI Task ended **')
def software_update(logger):
    """
    Connects to the wlan and fetches updates from a server. After having applied the patches successfully, it reboots
    the device.
    :param logger: status logger
    :type logger: LoggerFactory object
    """

    with wifi_lock:
        try:
            logger.info("Over the Air update started")

            led_lock.acquire(1)  # disable all other indicator LEDs
            pycom.rgbled(0x555500)  # Yellow LED

            # Get credentials from configuration
            ssid = config.get_config("SSID")
            password = config.get_config("wifi_password")
            server_ip = config.get_config("server")
            port = int(config.get_config("port"))

            logger.info("SSID: " + str(ssid))
            logger.info("server_ip: " + str(server_ip))
            logger.info("port: " + str(port))

            version = config.get_config("code_version")

            # Perform OTA update
            ota = WiFiOTA(logger, ssid, password, server_ip, port, version)

            # Turn off WiFi to save power
            w = WLAN()
            w.deinit()

            logger.info("connecting...")
            ota.connect()
            if ota.update():
                new_version = str(
                    ota.get_current_version())  # get updated version
                config.save_config({"code_version": str(new_version)
                                    })  # save new version to config on SD
                logger.info(
                    "Successfully updated the device from version {} to version {}"
                    .format(version, new_version))

        except Exception as e:
            logger.exception("Failed to update the device")
            pycom.rgbled(0x550000)  # turn LED to RED
            time.sleep(3)

        finally:
            # Turn off update mode
            config.save_config({"update": False})

            # Turn off indicator LED
            pycom.rgbled(0x000000)
            led_lock.release()

            # Reboot the device to apply patches
            logger.info("rebooting...")
            machine.reset()
Esempio n. 14
0
class NanoGateway:
    """
    Nano gateway class, set up by default for use with TTN, but can be configured
    for any other network supporting the Semtech Packet Forwarder.

    Only required configuration is wifi_ssid and wifi_password which are used for
    connecting to the Internet.
    """

    PROTOCOL_VERSION = const(2)

    PUSH_DATA = const(0)
    PUSH_ACK = const(1)
    PULL_DATA = const(2)
    PULL_ACK = const(4)
    PULL_RESP = const(3)

    TX_ERR_NONE = 'NONE'
    TX_ERR_TOO_LATE = 'TOO_LATE'
    TX_ERR_TOO_EARLY = 'TOO_EARLY'
    TX_ERR_COLLISION_PACKET = 'COLLISION_PACKET'
    TX_ERR_COLLISION_BEACON = 'COLLISION_BEACON'
    TX_ERR_TX_FREQ = 'TX_FREQ'
    TX_ERR_TX_POWER = 'TX_POWER'
    TX_ERR_GPS_UNLOCKED = 'GPS_UNLOCKED'

    UDP_THREAD_CYCLE_MS = const(10)

    STAT_PK = {
        'stat': {
            'time': '',
            'lati': 0,
            'long': 0,
            'alti': 0,
            'rxnb': 0,
            'rxok': 0,
            'rxfw': 0,
            'ackr': 100.0,
            'dwnb': 0,
            'txnb': 0
        }
    }

    RX_PK = {
        'rxpk': [{
            'time': '',
            'tmst': 0,
            'chan': 0,
            'rfch': 0,
            'freq': 0,
            'stat': 1,
            'modu': 'LORA',
            'datr': '',
            'codr': '4/5',
            'rssi': 0,
            'lsnr': 0,
            'size': 0,
            'data': ''
        }]
    }

    TX_ACK_PK = {'txpk_ack': {'error': ''}}

    def __init__(self,
                 wifi_ssid,
                 wifi_password,
                 gateway_id=None,
                 server='router.eu.thethings.network',
                 port=1700,
                 frequency=868100000,
                 datarate='SF7BW125',
                 ntp_server='pool.ntp.org',
                 ntp_period=3600):
        # If unset, set the Gateway ID to be the first 3 bytes
        # of MAC address + 'FFFE' + last 3 bytes of MAC address
        if gateway_id is None:
            gateway_id = ubinascii.hexlify(machine.unique_id()).upper()
            gateway_id = gateway_id[:6] + 'FFFE' + gateway_id[6:12]
        self.gateway_id = gateway_id

        self.server = server
        self.port = port

        self.frequency = frequency
        self.datarate = datarate

        self.wifi_ssid = wifi_ssid
        self.wifi_password = wifi_password

        self.ntp_server = ntp_server
        self.ntp_period = ntp_period

        self.server_ip = None

        self.rxnb = 0
        self.rxok = 0
        self.rxfw = 0
        self.dwnb = 0
        self.txnb = 0

        self.sf = self._dr_to_sf(self.datarate)
        self.bw = self._dr_to_bw(self.datarate)

        self.stat_alarm = None
        self.pull_alarm = None
        self.uplink_alarm = None

        self.wlan = None
        self.sock = None
        self.udp_stop = False
        self.udp_lock = _thread.allocate_lock()

        self.lora = None
        self.lora_sock = None

        self.rtc = machine.RTC()

    def start(self):
        """
        Starts the nano gateway.
        """

        self.log('Starting nano gateway with id {}', self.gateway_id)

        # Change WiFi to STA mode and connect
        self.wlan = WLAN(mode=WLAN.STA)
        self._connect_to_wifi()

        # Get a time sync
        self.log('Syncing time with {} ...', self.ntp_server)
        self.rtc.ntp_sync(self.ntp_server, update_period=self.ntp_period)
        while not self.rtc.synced():
            utime.sleep_ms(50)
        self.log('RTC NTP sync complete')

        # Get the server IP and create an UDP socket
        self.server_ip = usocket.getaddrinfo(self.server, self.port)[0][-1]
        self.log('Opening UDP socket to {} ({}) port {}...', self.server,
                 self.server_ip[0], self.server_ip[1])
        self.sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM,
                                   usocket.IPPROTO_UDP)
        self.sock.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)
        self.sock.setblocking(False)

        # Push the first time stat immediately
        self._push_data(self._make_stat_packet())

        # Create the alarms
        self.stat_alarm = machine.Timer.Alarm(
            handler=lambda t: self._push_data(self._make_stat_packet()),
            s=60,
            periodic=True)
        self.pull_alarm = machine.Timer.Alarm(
            handler=lambda u: self._pull_data(), s=25, periodic=True)

        # Start the UDP receive thread
        self.udp_stop = False
        _thread.start_new_thread(self._udp_thread, ())

        # Initialize the LoRa radio in LORA mode
        self.log('Setting up LoRa socket on {:.1f} Mhz using {}',
                 self._freq_to_float(self.frequency), self.datarate)
        self.lora = LoRa(mode=LoRa.LORA,
                         frequency=self.frequency,
                         bandwidth=self.bw,
                         sf=self.sf,
                         preamble=8,
                         coding_rate=LoRa.CODING_4_5,
                         tx_iq=True)

        # Create a raw LoRa socket
        self.lora_sock = usocket.socket(usocket.AF_LORA, usocket.SOCK_RAW)
        self.lora_sock.setblocking(False)

        self.lora.callback(trigger=(LoRa.RX_PACKET_EVENT
                                    | LoRa.TX_PACKET_EVENT),
                           handler=self._lora_cb)
        self.log('Nano gateway online')

    def stop(self):
        """
        Stops the nano gateway.
        """

        self.log('Stopping...')

        # Send the LoRa radio to sleep
        self.lora.callback(trigger=None, handler=None)
        self.lora.power_mode(LoRa.SLEEP)

        # Stop the NTP sync
        self.rtc.ntp_sync(None)

        # Cancel all the alarms
        self.stat_alarm.cancel()
        self.pull_alarm.cancel()

        # Signal the UDP thread to stop
        self.udp_stop = True
        while self.udp_stop:
            utime.sleep_ms(50)

        # Disable WLAN
        self.wlan.disconnect()
        self.wlan.deinit()

    def _connect_to_wifi(self):
        self.wlan.connect(self.wifi_ssid, auth=(None, self.wifi_password))
        while not self.wlan.isconnected():
            utime.sleep_ms(50)
        self.log('WiFi connected: {}', self.wifi_ssid)

    def _dr_to_sf(self, dr):
        sf = dr[2:4]
        if sf[1] not in '0123456789':
            sf = sf[:1]
        return int(sf)

    def _dr_to_bw(self, dr):
        bw = dr[-5:]
        if bw == 'BW125':
            return LoRa.BW_125KHZ
        elif bw == 'BW250':
            return LoRa.BW_250KHZ
        else:
            return LoRa.BW_500KHZ

    def _sf_bw_to_dr(self, sf, bw):
        dr = 'SF' + str(sf)
        if bw == LoRa.BW_125KHZ:
            return dr + 'BW125'
        elif bw == LoRa.BW_250KHZ:
            return dr + 'BW250'
        else:
            return dr + 'BW500'

    def _lora_cb(self, lora):
        """
        Event listener for LoRa radio events.
        """

        events = lora.events()
        if events & LoRa.RX_PACKET_EVENT:
            self.rxnb += 1
            self.rxok += 1
            rx_data = self.lora_sock.recv(256)
            stats = lora.stats()
            packet = self._make_node_packet(rx_data, self.rtc.now(),
                                            stats.rx_timestamp, stats.sfrx,
                                            self.bw, stats.rssi, stats.snr)
            self.log('Received packet: {}', packet)
            self._push_data(packet)
            self.rxfw += 1
        if events & LoRa.TX_PACKET_EVENT:
            self.log('Re-initing LoRa radio after transmission')
            self.txnb += 1
            lora.init(mode=LoRa.LORA,
                      frequency=self.frequency,
                      bandwidth=self.bw,
                      sf=self.sf,
                      preamble=8,
                      coding_rate=LoRa.CODING_4_5,
                      tx_iq=True)

    def _freq_to_float(self, frequency):
        """
        MicroPython has some inprecision when doing large float division.

        To counter this, this method first does integer division until we
        reach the decimal breaking point. This doesn't completely elimate
        the issue in all cases, but it does help for a number of commonly
        used frequencies.
        """

        divider = 6
        while divider > 0 and frequency % 10 == 0:
            frequency = frequency // 10
            divider -= 1
        if divider > 0:
            frequency = frequency / (10**divider)
        return frequency

    def _make_stat_packet(self):
        now = self.rtc.now()
        self.STAT_PK['stat']['time'] = '%d-%02d-%02d %02d:%02d:%02d GMT' % (
            now[0], now[1], now[2], now[3], now[4], now[5])
        self.STAT_PK['stat']['rxnb'] = self.rxnb
        self.STAT_PK['stat']['rxok'] = self.rxok
        self.STAT_PK['stat']['rxfw'] = self.rxfw
        self.STAT_PK['stat']['dwnb'] = self.dwnb
        self.STAT_PK['stat']['txnb'] = self.txnb
        return ujson.dumps(self.STAT_PK)

    def _make_node_packet(self, rx_data, rx_time, tmst, sf, bw, rssi, snr):
        self.RX_PK['rxpk'][0]['time'] = '%d-%02d-%02dT%02d:%02d:%02d.%dZ' % (
            rx_time[0], rx_time[1], rx_time[2], rx_time[3], rx_time[4],
            rx_time[5], rx_time[6])
        self.RX_PK['rxpk'][0]['tmst'] = tmst
        self.RX_PK['rxpk'][0]['freq'] = self._freq_to_float(self.frequency)
        self.RX_PK['rxpk'][0]['datr'] = self._sf_bw_to_dr(sf, bw)
        self.RX_PK['rxpk'][0]['rssi'] = rssi
        self.RX_PK['rxpk'][0]['lsnr'] = float(snr)
        self.RX_PK['rxpk'][0]['data'] = ubinascii.b2a_base64(rx_data)[:-1]
        self.RX_PK['rxpk'][0]['size'] = len(rx_data)
        return ujson.dumps(self.RX_PK)

    def _push_data(self, data):
        token = uos.urandom(2)
        packet = bytes([self.PROTOCOL_VERSION]) + token + bytes(
            [self.PUSH_DATA]) + ubinascii.unhexlify(self.gateway_id) + data
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except BaseException as ex:
                self.log('Failed to push uplink packet to server: {}', ex)

    def _pull_data(self):
        token = uos.urandom(2)
        packet = bytes([self.PROTOCOL_VERSION]) + token + bytes(
            [self.PULL_DATA]) + ubinascii.unhexlify(self.gateway_id)
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except BaseException as ex:
                self.log('Failed to pull downlink packets from server: {}', ex)

    def _ack_pull_rsp(self, token, error):
        self.TX_ACK_PK['txpk_ack']['error'] = error
        resp = ujson.dumps(self.TX_ACK_PK)
        packet = bytes([self.PROTOCOL_VERSION]) + token + bytes(
            [self.PULL_ACK]) + ubinascii.unhexlify(self.gateway_id) + resp
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except BaseException as ex:
                self.log('PULL RSP ACK exception: {}', ex)

    def _send_down_link(self, data, tmst, datarate, frequency):
        """
        Transmits a downlink message over LoRa.
        """

        self.lora.init(mode=LoRa.LORA,
                       frequency=frequency,
                       bandwidth=self._dr_to_bw(datarate),
                       sf=self._dr_to_sf(datarate),
                       preamble=8,
                       coding_rate=LoRa.CODING_4_5,
                       tx_iq=True)
        while utime.ticks_us() < tmst:
            pass
        self.lora_sock.send(data)
        self.log(
            'Sent downlink packet scheduled for {:.3f}, at {:.1f} Mhz using {}: {}',
            tmst / 1000000, self._freq_to_float(frequency), datarate, data)

    def _udp_thread(self):
        """
        UDP thread, reads data from the server and handles it.
        """

        while not self.udp_stop:
            try:
                data, src = self.sock.recvfrom(1024)
                _token = data[1:3]
                _type = data[3]
                if _type == self.PUSH_ACK:
                    self.log('Push ack')
                elif _type == self.PULL_ACK:
                    self.log('Pull ack')
                elif _type == self.PULL_RESP:
                    self.dwnb += 1
                    ack_error = self.TX_ERR_NONE
                    tx_pk = ujson.loads(data[4:])
                    tmst = tx_pk['txpk']['tmst']
                    t_us = tmst - utime.ticks_us() - 12500
                    if t_us < 0:
                        t_us += 0xFFFFFFFF
                    if t_us < 20000000:
                        self.uplink_alarm = machine.Timer.Alarm(
                            handler=lambda x: self._send_down_link(
                                ubinascii.a2b_base64(tx_pk['txpk']['data']),
                                tx_pk['txpk']['tmst'] - 50, tx_pk['txpk'][
                                    'datr'],
                                int(tx_pk['txpk']['freq'] * 1000000)),
                            us=t_us)
                    else:
                        ack_error = self.TX_ERR_TOO_LATE
                        self.log('Downlink timestamp error!, t_us: {}', t_us)
                    self._ack_pull_rsp(_token, ack_error)
                    self.log('Pull rsp')
                else:
                    self.log('Unknown message type from server: {}', _type)
            except usocket.timeout:
                pass
            except OSError as ex:
                if ex.errno != errno.EAGAIN:
                    self.log('UDP recv OSError Exception: {}', ex)
            except BaseException as ex:
                self.log('UDP recv Exception: {}', ex)

            # Wait before trying to receive again
            utime.sleep_ms(self.UDP_THREAD_CYCLE_MS)

        self.sock.close()
        self.udp_stop = False
        self.log('UDP thread stopped')

    def log(self, message, *args):
        """
        Prints a log message to the stdout.
        """

        print('[{:>10.3f}] {}'.format(utime.ticks_ms() / 1000,
                                      str(message).format(*args)))
Esempio n. 15
0
def http_daemon(ssid="OpenScale", password="******", path_to_handler={}):
    host_ip = "192.168.4.1"
    print("Creating Access Point {} with password {}".format(ssid, password))
    print("User will need to connect to the webpage at {}".format(host_ip))

    wlan = WLAN()
    wlan.deinit()
    wlan.ifconfig(config=(host_ip, '255.255.255.0', '0.0.0.0', '8.8.8.8'))
    wlan.init(mode=WLAN.AP,
              ssid=ssid,
              auth=(WLAN.WPA2, password),
              channel=5,
              antenna=WLAN.INT_ANT)

    # The address of the board at port 80
    address = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

    s = socket.socket()
    s.bind(address)
    s.listen(5)

    print('listening on ', address)

    while True:
        connection_socket, address = s.accept()
        connection_socket.setblocking(False)
        print('New connection from', address)

        msg = connection_socket.recv(MAX_HTTP_MESSAGE_LENGTH).decode()
        msg = msg.replace("\r\n", "\n")

        blank_line_split = msg.split('\n\n')
        if len(blank_line_split) != 2:
            raise Exception("Malformated HTTP request.")

        preamble = blank_line_split[0].split("\n")
        request = preamble[0]
        request_keys = ["method", "path", "version"]
        request_key_value = zip(request_keys, request.split(" "))
        request = {key: value for key, value in request_key_value}

        headers = preamble[1:]
        headers = {
            line.split(":")[0].strip(): line.split(":")[1].strip()
            for line in headers
        }

        for key, value in headers.items():
            request[key] = value

        print("Received Request:\n{}".format(request))

        request['body'] = blank_line_split[1]
        if 'Content-Length' in request:
            content_length = int(request['Content-Length'])

            if len(request['body']) < content_length:
                print(
                    "Attempting to retrieve {} ({} remaining) bytes of content"
                    .format(content_length,
                            content_length - len(request['body'])))
                while len(request['body']) != content_length:
                    new_segment = connection_socket.recv(
                        MAX_HTTP_MESSAGE_LENGTH).decode()
                    request['body'] += new_segment

        if request['path'] not in path_to_handler:
            print("{} not found in path_to_handler".format(request['path']))
            response = not_configured_response
        else:
            endpoint_handler = path_to_handler[request['path']]
            print("Path found. Passing to {}".format(endpoint_handler))
            response = endpoint_handler(**request)

        print("Sending response")

        connection_socket.send(response)
        connection_socket.close()
Esempio n. 16
0
class Scanner:
    def __init__(self):
        # Initialize wlan
        self.wlan = WLAN(mode=WLAN.STA)
        # Internal (WLAN.INT_ANT) or external (WLAN.EXT_ANT) antenna
        self.wlan.antenna(WLAN.INT_ANT)
        self.nets = []
        # Define the named tuple that contains the WiFi access point information
        self.net = namedtuple('net',
                              ('ssid', 'bssid', 'sec', 'channel', 'rssi'))

    def get(self, testNetworksValid=False, testNetworks=[]):
        if testNetworksValid:
            self.nets = testNetworks
        else:
            # Scan available networks
            self.nets = self.wlan.scan(type=WLAN.SCAN_PASSIVE, scantime=120)
            # Switch WiFi off
            self.wlan.deinit()

        # Nets is defined None if no networks are found
        if self.nets is None:
            self.nets = []

        # If more than one network, order the list by RSSI (strongest first)
        if len(self.nets) > 1:
            sorted(self.nets, key=self.__byRssi_key)

        # Flash file storage not used because it is slow
        #self.storeNets(self.convertNetList(self.nets))
        #print(self.readNets())
        return self.nets

    # Convert from list of named tuples to list of dictionaries
    # ssid', 'bssid', 'sec', 'channel', 'rssi'
    def convertNetList(self, tupleNetList):
        dictNetList = []
        for tupleNet in tupleNetList:
            dictNet = {
                "ssid": tupleNet.ssid,
                "bssid": tupleNet.bssid,
                "sec": tupleNet.sec,
                "channel": tupleNet.channel,
                "rssi": tupleNet.rssi
            }
            dictNetList.append(dictNet)
        return dictNetList

    def writeLongList(self, name, longVars, maxLen):
        i = 0
        while i < len(longVars) and i < maxLen:
            pycom.nvs_set(name + str(i) + 'l',
                          struct.unpack(">I", longVars[i][-4:])[0])
            pycom.nvs_set(name + str(i) + 'm',
                          struct.unpack(">H", longVars[i][0:2])[0])
            i = i + 1
        try:
            pycom.nvs_erase(name + str(i) + 'l')
            pycom.nvs_erase(name + str(i) + 'm')
        except:
            return
        else:
            return

    def readLongList(self, name):
        longVars = []
        i = 0
        while True:
            try:
                lsb = pycom.nvs_get(name + str(i) + 'l')
                msb = pycom.nvs_get(name + str(i) + 'm')
            except:
                break
            else:
                longVars.append(struct.pack(">HI", msb, lsb))
                i = i + 1
        return longVars

    def writeStore(self):
        macList = []
        i = 0
        while i < len(self.nets):
            macList.append(self.nets[i].bssid)
            i = i + 1
        self.writeLongList('net', macList, 10)
        return

    def readStore(self):
        macList = self.readLongList('net')

        self.storedNets = []
        for mac in macList:
            self.storedNets.append(
                self.net(ssid='', bssid=mac, sec=0, channel=1, rssi=-100))
        return self.storedNets

    def storeNets(self, nets):
        f = open('data.txt', 'w+')
        for net in nets:
            f.write(repr(net) + '\n')
        f.close()

    def readNets(self):
        dictNetList = []
        f = open('data.txt')
        nets = f.readlines()
        for net in nets:
            dictNetList.append(eval(net))
        f.close()
        return dictNetList

    def createMessage(self):
        message = bytearray()
        i = 0
        while i < len(self.nets) and i < 3:
            # Add BSSID to the LoRa message
            message = message + self.nets[i].bssid
            # Add RSSI (without minus) to the LoRa message
            message.append(self.nets[i].rssi * -1)
            i = i + 1
        return message

    def netDiff(self, nets1=[], nets2=[]):
        matchCnt = 0
        # Define the named tuple that contains the result of the matching
        diffRes = namedtuple('diffRes', ('net1', 'net2', 'match', 'noMatch'))
        for net1 in nets1:
            for net2 in nets2:
                if net1.bssid == net2.bssid:
                    matchCnt = matchCnt + 1
        noMatchCnt = len(nets1) + len(nets2) - (2 * matchCnt)
        return diffRes(net1=len(nets1),
                       net2=len(nets2),
                       match=matchCnt,
                       noMatch=noMatchCnt)

    def __byRssi_key(self, net):
        return net.rssi
class PybytesConnection:
    def __init__(self, config, message_callback):
        if config is not None:
            self.__conf = config
            try:
                self.__host = pycom.nvs_get('pybytes_server')
            except:
                self.__host = config.get('server')
            self.__ssl = config.get('ssl', False)
            self.__ssl_params = config.get('ssl_params', {})
            self.__user_name = config.get('username')
            self.__device_id = config.get('device_id')
            self.__mqtt_download_topic = "d" + self.__device_id
            self.__mqtt_upload_topic = "u" + self.__device_id
            self.__pybytes_protocol = PybytesProtocol(
                config, message_callback, pybytes_connection=self
            )
        self.__connection = None
        self.__connection_status = constants.__CONNECTION_STATUS_DISCONNECTED
        self.__lora_socket = None
        self.lora = None
        self.lora_lock = _thread.allocate_lock()
        self.__sigfox_socket = None
        self.lte = None
        self.wlan = None
        self.__network_type = None
        self.__wifi_lte_watchdog = None

    def lte_ping_routine(self, delay):
        while True:
            self.send_ping_message()
            time.sleep(delay)

    def print_pretty_response(self, rsp):
        lines = rsp.split('\r\n')
        for line in lines:
            if line:
                if line not in ['OK']:
                    print(line)

    def __initialise_watchdog(self):
        if self.__conf.get('connection_watchdog', True):
            self.__wifi_lte_watchdog = WDT(
                timeout=constants.__WDT_TIMEOUT_MILLISECONDS
            )
            print('Initialized watchdog for WiFi and LTE connection with timeout {} ms'.format(constants.__WDT_TIMEOUT_MILLISECONDS)) # noqa
        else:
            print('Watchdog for WiFi and LTE was disabled, enable with "connection_watchdog": true in pybytes_config.json') # noqa

    # Establish a connection through WIFI before connecting to mqtt server
    def connect_wifi(self, reconnect=True, check_interval=0.5, timeout=120):
        self.__initialise_watchdog()

        if self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED: # noqa
            print("Error connect_wifi: Connection already exists. Disconnect First") # noqa
            return False
        try:
            from network import WLAN
            antenna = self.__conf.get('wlan_antenna', WLAN.INT_ANT)
            known_nets = [((self.__conf['wifi']['ssid'], self.__conf['wifi']['password']))] # noqa
            if antenna == WLAN.EXT_ANT:
                print("WARNING! Using external WiFi antenna.")

            '''to connect it to an existing network,
            the WiFi class must be configured as a station'''

            self.wlan = WLAN(mode=WLAN.STA, antenna=antenna)

            attempt = 0

            print_debug(3, 'WLAN connected? {}'.format(self.wlan.isconnected()))

            while not self.wlan.isconnected() and attempt < 3:
                attempt += 1
                print_debug(3, "Wifi connection attempt: {}".format(attempt))
                print_debug(3, 'WLAN connected? {}'.format(self.wlan.isconnected()))
                available_nets = None
                while available_nets is None:
                    try:
                        available_nets = self.wlan.scan()
                        for x in available_nets:
                            print_debug(5, x)
                        time.sleep(1)
                    except:
                        pass

                nets = frozenset([e.ssid for e in available_nets])
                known_nets_names = frozenset([e[0]for e in known_nets])
                net_to_use = list(nets & known_nets_names)
                try:
                    net_to_use = net_to_use[0]
                    pwd = dict(known_nets)[net_to_use]
                    sec = [e.sec for e in available_nets if e.ssid == net_to_use][0] # noqa
                    print_debug(99, "Connecting with {} and {}".format(net_to_use, pwd))
                    if  sec == 0:
                        self.wlan.connect(net_to_use, timeout=10000)
                    else:
                        self.wlan.connect(net_to_use, (sec, pwd), timeout=10000)
                    start_time = time.time()
                    while not self.wlan.isconnected():
                        if time.time() - start_time > timeout:
                            raise TimeoutError('Timeout trying to connect via WiFi')
                        time.sleep(0.1)
                except Exception as e:
                    if str(e) == "list index out of range" and attempt == 3:
                        print("Please review Wifi SSID and password inside config")
                        self.wlan.deinit()
                        return False
                    elif attempt == 3:
                        print("Error connecting using WIFI: %s" % e)
                        return False

            self.__network_type = constants.__NETWORK_TYPE_WIFI
            print("WiFi connection established")
            try:
                self.__connection = MQTTClient(
                    self.__device_id,
                    self.__host,
                    self.__mqtt_download_topic,
                    self.__pybytes_protocol,
                    user=self.__user_name,
                    password=self.__device_id
                )
                self.__connection.connect()
                self.__connection_status = constants.__CONNECTION_STATUS_CONNECTED_MQTT_WIFI # noqa
                self.__pybytes_protocol.start_MQTT(
                    self,
                    constants.__NETWORK_TYPE_WIFI
                )
                return True
            except Exception as ex:
                if '{}'.format(ex) == '4':
                    print('MQTT ERROR! Bad credentials when connecting to server: "{}"'.format(self.__host)) # noqa
                else:
                    print("MQTT ERROR! {}".format(ex))
                return False
        except Exception as ex:
            print("Exception connect_wifi: {}".format(ex))
            return False

    # Establish a connection through LTE before connecting to mqtt server
    def connect_lte(self, activation_info=False, start_mqtt=True):
        if activation_info:
            lte_cfg = activation_info
        else:
            lte_cfg = self.__conf.get('lte')
            self.__initialise_watchdog()

        if lte_cfg is not None:
            if (os.uname()[0] not in ['FiPy', 'GPy']):
                print("You need a device with FiPy or GPy firmware to connect via LTE") # noqa
                return False
            try:
                from network import LTE
                time.sleep(3)
                if lte_cfg.get('carrier', 'standard') == 'standard':
                    carrier = None
                else:
                    carrier = lte_cfg.get('carrier')
                print_debug(1, 'LTE init(carrier={}, cid={})'.format(carrier, lte_cfg.get('cid', 1))) # noqa
                # instantiate the LTE object
                self.lte = LTE(carrier=carrier, cid=lte_cfg.get('cid', 1))
                try:
                    lte_type = lte_cfg.get('type') if len(lte_cfg.get('type')) > 0 else None
                except:
                    lte_type = None
                try:
                    lte_apn = lte_cfg.get('apn') if len(lte_cfg.get('type')) > 0 else None
                except:
                    lte_apn = None
                try:
                    lte_band = int(lte_cfg.get('band'))
                except:
                    lte_band = None
                print_debug(
                    1,
                    'LTE attach(band={}, apn={}, type={})'.format(
                        lte_band,
                        lte_apn,
                        lte_type
                    )
                )

                self.lte.attach(band=lte_band, apn=lte_apn, type=lte_type)  # noqa   # attach the cellular modem to a base station
                while not self.lte.isattached():
                    time.sleep(0.25)
                time.sleep(1)
                print_debug(1, 'LTE connect()')
                # start a data session and obtain an IP address
                self.lte.connect()
                print_debug(1, 'LTE is_connected()')
                while not self.lte.isconnected():
                    time.sleep(0.25)
                print("LTE connection established")
                self.__network_type = constants.__NETWORK_TYPE_LTE

                if start_mqtt:
                    try:
                        self.__connection = MQTTClient(
                            self.__device_id,
                            self.__host,
                            self.__mqtt_download_topic,
                            self.__pybytes_protocol,
                            user=self.__user_name,
                            password=self.__device_id
                        )
                        self.__connection.connect()
                        self.__connection_status = constants.__CONNECTION_STATUS_CONNECTED_MQTT_LTE # noqa
                        self.__pybytes_protocol.start_MQTT(
                            self,
                            constants.__NETWORK_TYPE_LTE
                        )
                        print("Connected to MQTT {}".format(self.__host))
                        return True
                    except Exception as ex:
                        if '{}'.format(ex) == '4':
                            print('MQTT ERROR! Bad credentials when connecting to server: "{}"'.format(self.__host)) # noqa
                        else:
                            print("MQTT ERROR! {}".format(ex))
                        return False
            except Exception as ex:
                print("Exception connect_lte: {}".format(ex))
                sys.print_exception(ex)
            return False
        else:
            print("Error... missing configuration!")
            return False

    # LORA
    def connect_lora_abp(self, lora_timeout, nanogateway):
        print_debug(1,'Attempting to connect via LoRa')
        if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): # noqa
            print("Error connect_lora_abp: Connection already exists. Disconnect First") # noqa
            return False
        try:
            from network import LoRa
        except Exception as ex:
            print("This device does not support LoRa connections: %s" % ex)
            return False

        lora_class = self.__conf.get('lora', {}).get('class', 0)
        if self.__conf.get('lora', {}).get('region') is not None:
            self.lora = LoRa(mode=LoRa.LORAWAN, region=self.__conf.get('lora').get('region'), device_class=lora_class)
        else:
            self.lora = LoRa(mode=LoRa.LORAWAN, device_class=lora_class)
        self.lora.nvram_restore()

        try:
            dev_addr = self.__conf['lora']['abp']['dev_addr']
            nwk_swkey = self.__conf['lora']['abp']['nwk_skey']
            app_swkey = self.__conf['lora']['abp']['app_skey']
        except Exception as ex:
            print("Invalid LoRaWAN ABP configuration!")
            print_debug(1, ex)
            return False
        timeout_ms = self.__conf.get('lora_timeout', lora_timeout) * 1000

        dev_addr = struct.unpack(">l", binascii.unhexlify(dev_addr.replace(' ', '')))[0] # noqa
        nwk_swkey = binascii.unhexlify(nwk_swkey.replace(' ', ''))
        app_swkey = binascii.unhexlify(app_swkey.replace(' ', ''))

        try:
            print("Trying to join LoRa.ABP for %d seconds..." % self.__conf.get('lora_timeout', lora_timeout))
            self.lora.join(
                activation=LoRa.ABP,
                auth=(dev_addr, nwk_swkey, app_swkey),
                timeout=timeout_ms
            )

            # if you want, uncomment this code, but timeout must be 0
            # while not self.lora.has_joined():
            #     print("Joining...")
            #     time.sleep(5)

            self.__open_lora_socket(nanogateway)
#            print_debug(5, 'Stack size: {}'.format(self.__thread_stack_size))
#            _thread.stack_size(self.__thread_stack_size)
#            _thread.start_new_thread(self.__check_lora_messages, ())
            return True
        except Exception as e:
            message = str(e)
            if message == 'timed out':
                print("LoRa connection timeout: %d seconds" % self.__conf.get('lora_timeout', lora_timeout))
            else:
                print_debug(3, 'Exception in LoRa connect: {}'.format(e))
            return False

    def connect_lora_otaa(self, lora_timeout, nanogateway):
        print_debug(1,'Attempting to connect via LoRa')
        if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): # noqa
            print("Error connect_lora_otaa: Connection already exists. Disconnect First") # noqa
            return False
        try:
            from network import LoRa
        except Exception as ex:
            print("This device does not support LoRa connections: %s" % ex)
            return False

        try:
            dev_eui = self.__conf['lora']['otaa']['app_device_eui']
            app_eui = self.__conf['lora']['otaa']['app_eui']
            app_key = self.__conf['lora']['otaa']['app_key']
        except Exception as ex:
            print("Invalid LoRaWAN OTAA configuration!")
            print_debug(1, ex)
            return False

        timeout_ms = self.__conf.get('lora_timeout', lora_timeout) * 1000

        lora_class = self.__conf.get('lora', {}).get('class', 0)
        if self.__conf.get('lora', {}).get('region') is not None:
            self.lora = LoRa(mode=LoRa.LORAWAN, region=self.__conf.get('lora', {}).get('region'), device_class=lora_class)
        else:
            self.lora = LoRa(mode=LoRa.LORAWAN, device_class=lora_class)
        self.lora.nvram_restore()

        dev_eui = binascii.unhexlify(dev_eui.replace(' ', ''))
        app_eui = binascii.unhexlify(app_eui.replace(' ', ''))
        app_key = binascii.unhexlify(app_key.replace(' ', ''))
        try:
            if not self.lora.has_joined():
                print("Trying to join LoRa.OTAA for %d seconds..." % self.__conf.get('lora_timeout', lora_timeout))
                self.lora.join(
                    activation=LoRa.OTAA,
                    auth=(dev_eui, app_eui, app_key),
                    timeout=timeout_ms
                )

            # if you want, uncomment this code, but timeout must be 0
            # while not self.lora.has_joined():
            #     print("Joining...")
            #     time.sleep(5)

            self.__open_lora_socket(nanogateway)
#            print_debug(5, 'Stack size: {}'.format(self.__thread_stack_size))
#            _thread.stack_size(self.__thread_stack_size)
#            _thread.start_new_thread(self.__check_lora_messages, ())
            return True
        except Exception as e:
            message = str(e)
            if message == 'timed out':
                print("LoRa connection timeout: %d seconds" % self.__conf.get('lora_timeout', lora_timeout))
            else:
                print_debug(3, 'Exception in LoRa connect: {}'.format(e))
            return False

    def __open_lora_socket(self, nanogateway):
        if (nanogateway):
            for i in range(3, 16):
                self.lora.remove_channel(i)

            self.lora.add_channel(0, frequency=868100000, dr_min=0, dr_max=5)
            self.lora.add_channel(1, frequency=868100000, dr_min=0, dr_max=5)
            self.lora.add_channel(2, frequency=868100000, dr_min=0, dr_max=5)

        print("Setting up LoRa socket...")
        self.__lora_socket = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
        self.__lora_socket.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

        self.__connection_status = constants.__CONNECTION_STATUS_CONNECTED_LORA

        self.__pybytes_protocol.start_Lora(self)

        print("Connected using LoRa")

    # SIGFOX
    def connect_sigfox(self):
        if (self.__connection_status != constants.__CONNECTION_STATUS_DISCONNECTED): # noqa
            print("Error: Connection already exists. Disconnect First")
            pass
        try:
            from network import Sigfox
        except Exception:
            print("This device does not support Sigfox connections")
            return
        sigfox_config = self.__conf.get('sigfox', {})
        if sigfox_config is None or sigfox_config.get('RCZ') is None:
            print(constants.__SIGFOX_WARNING)
        try:
            sf_rcz = int(sigfox_config.get('RCZ', 1)) - 1
            if sf_rcz >= 0 and sf_rcz <= 3:
                Sigfox(mode=Sigfox.SIGFOX, rcz=sf_rcz)
                self.__sigfox_socket = socket.socket(socket.AF_SIGFOX, socket.SOCK_RAW) # noqa
                self.__sigfox_socket.setblocking(True)
                self.__sigfox_socket.setsockopt(socket.SOL_SIGFOX, socket.SO_RX, False) # noqa
                self.__network_type = constants.__NETWORK_TYPE_SIGFOX
                self.__connection_status = constants.__CONNECTION_STATUS_CONNECTED_SIGFOX # noqa
                self.__pybytes_protocol.start_Sigfox(self)
                print(
                    "Connected using Sigfox. Only upload stream is supported"
                )
                return True
            else:
                print('Invalid Sigfox RCZ specified in config!')
                return False
        except Exception as e:
            print('Exception in connect_sigfox: {}'.format(e))
            return False

    # COMMON
    def disconnect(self, keep_wifi=False, force=False):

        if self.__wifi_lte_watchdog is not None:
            self.__wifi_lte_watchdog = WDT(timeout=constants.__WDT_MAX_TIMEOUT_MILLISECONDS)
            print('Watchdog timeout has been increased to {} ms'.format(constants.__WDT_MAX_TIMEOUT_MILLISECONDS)) # noqa

        print_debug(
            1,
            'self.__connection_status={} | self.__network_type={}'.format(
                self.__connection_status, self.__network_type
            )
        )

        if (self.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED): # noqa
            print_debug(3, "Already disconnected")

        if (constants.__CONNECTION_STATUS_CONNECTED_MQTT_WIFI <= self.__connection_status <= constants.__CONNECTION_STATUS_CONNECTED_MQTT_LTE): # noqa
            print_debug(1, 'MQTT over WIFI||LTE... disconnecting MQTT')
            try:
                self.__connection.disconnect(force=force)
                self.__connection_status = constants.__CONNECTION_STATUS_DISCONNECTED # noqa
            except Exception as e:
                print("Error disconnecting: {}".format(e))

        if (self.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_LORA): # noqa
            print_debug(1, 'Connected over LORA... closing socket and saving nvram') # noqa
            try:
                self.__lora_socket.close()
                self.lora.nvram_save()
            except Exception as e:
                print("Error disconnecting: {}".format(e))

        if (self.__connection_status == constants.__CONNECTION_STATUS_CONNECTED_SIGFOX): # noqa
            print_debug(1, 'Connected over SIGFOX... closing socket')
            try:
                self.__sigfox_socket.close()
            except Exception as e:
                print("Error disconnecting: {}".format(e))

        if (self.__network_type == constants.__NETWORK_TYPE_WIFI and not keep_wifi):
            print_debug(1, 'Connected over WIFI... disconnecting')
            try:
                self.wlan.deinit()
            except Exception as e:
                print("Error disconnecting: {}".format(e))

        if (self.__network_type == constants.__NETWORK_TYPE_LTE):
            print_debug(1, 'Connected over LTE... disconnecting')
            try:
                lte_cfg = self.__conf.get('lte')
                print_debug(1, 'lte.deinit(reset={})'.format(lte_cfg.get('reset', False))) # noqa
                self.lte.deinit(reset=lte_cfg.get('reset', False))
            except Exception as e:
                print("Error disconnecting: {}".format(e))

        self.__network_type = None
        self.__connection_status = constants.__CONNECTION_STATUS_DISCONNECTED

    def is_connected(self):
        return not (self.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED) # noqa

    # Added for convention with other connectivity classes
    def isconnected(self):
        return not (self.__connection_status == constants.__CONNECTION_STATUS_DISCONNECTED) # noqa
Esempio n. 18
0
def disable():
    wlan = WLAN()

    logger.info("disabling wlan.")
    wlan.deinit()
Esempio n. 19
0
class NanoGateway:
    """
    Nano gateway class, set up by default for use with TTN, but can be configured
    for any other network supporting the Semtech Packet Forwarder.
    Only required configuration is wifi_ssid and wifi_password which are used for
    connecting to the Internet.
    """

    def __init__(self, id, frequency, datarate, ssid, password, server, port, ntp_server='pool.ntp.org', ntp_period=3600):
        self.id = id
        self.server = server
        self.port = port

        self.frequency = frequency
        self.datarate = datarate

        self.ssid = ssid
        self.password = password

        self.ntp_server = ntp_server
        self.ntp_period = ntp_period

        self.server_ip = None

        self.rxnb = 0
        self.rxok = 0
        self.rxfw = 0
        self.dwnb = 0
        self.txnb = 0

        self.sf = self._dr_to_sf(self.datarate)
        self.bw = self._dr_to_bw(self.datarate)
        self.region = LoRa.AU915

        self.stat_alarm = None
        self.pull_alarm = None
        self.uplink_alarm = None

        self.wlan = None
        self.sock = None
        self.udp_stop = False
        self.udp_lock = _thread.allocate_lock()

        self.lora = None
        self.lora_sock = None

        self.rtc = machine.RTC()

    def start(self):
        """
        Starts the LoRaWAN nano gateway.
        """

        self._log('Starting LoRaWAN nano gateway with id: {}', self.id)

        # setup WiFi as a station and connect
        self.wlan = WLAN(mode=WLAN.STA)
        self._connect_to_wifi()

        # get a time sync
        self._log('Syncing time with {} ...', self.ntp_server)
        self.rtc.ntp_sync(self.ntp_server, update_period=self.ntp_period)
        while not self.rtc.synced():
            utime.sleep_ms(50)
        self._log("RTC NTP sync complete")

        # get the server IP and create an UDP socket
        self.server_ip = usocket.getaddrinfo(self.server, self.port)[0][-1]
        self._log('Opening UDP socket to {} ({}) port {}...', self.server, self.server_ip[0], self.server_ip[1])
        self.sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM, usocket.IPPROTO_UDP)
        self.sock.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)
        self.sock.setblocking(False)

        # push the first time immediatelly
        self._push_data(self._make_stat_packet())

        # create the alarms
        self.stat_alarm = Timer.Alarm(handler=lambda t: self._push_data(self._make_stat_packet()), s=60, periodic=True)
        self.pull_alarm = Timer.Alarm(handler=lambda u: self._pull_data(), s=25, periodic=True)

        # start the UDP receive thread
        self.udp_stop = False
        _thread.start_new_thread(self._udp_thread, ())

        # initialize the LoRa radio in LORA mode
        self._log('Setting up the LoRa radio at {} Mhz using {}', self._freq_to_float(self.frequency), self.datarate)
        self.lora = LoRa(
            mode=LoRa.LORA,
            region=self.region,
            frequency=self.frequency,
            bandwidth=self.bw,
            sf=self.sf,
            preamble=8,
            coding_rate=LoRa.CODING_4_5,
            power_mode=LoRa.ALWAYS_ON,
            #tx_iq=True
        )

        # create a raw LoRa socket
        self.lora_sock = usocket.socket(usocket.AF_LORA, usocket.SOCK_RAW)
        self.lora_sock.setblocking(False)
        self.lora_tx_done = False

        self.lora.callback(trigger=(LoRa.RX_PACKET_EVENT | LoRa.TX_PACKET_EVENT), handler=self._lora_cb)
        
        if uos.uname()[0] == "LoPy":
            self.window_compensation = -1000
        else:
            self.window_compensation = -6000
        self.downlink_count = 0
        
        self._log('LoRaWAN nano gateway online')

    def stop(self):
        """
        Stops the LoRaWAN nano gateway.
        """

        self._log('Stopping...')

        # send the LoRa radio to sleep
        self.lora.callback(trigger=None, handler=None)
        self.lora.power_mode(LoRa.SLEEP)

        # stop the NTP sync
        self.rtc.ntp_sync(None)

        # cancel all the alarms
        self.stat_alarm.cancel()
        self.pull_alarm.cancel()

        # signal the UDP thread to stop
        self.udp_stop = True
        while self.udp_stop:
            utime.sleep_ms(50)

        # disable WLAN
        self.wlan.disconnect()
        self.wlan.deinit()

    def _connect_to_wifi(self):
        self.wlan.connect(self.ssid, auth=(None, self.password))
        while not self.wlan.isconnected():
            utime.sleep_ms(50)
        self._log('WiFi connected to: {}', self.ssid)

    def _dr_to_sf(self, dr):
        sf = dr[2:4]
        if sf[1] not in '0123456789':
            sf = sf[:1]
        return int(sf)

    def _dr_to_bw(self, dr):
        bw = dr[-5:]
        if bw == 'BW125':
            return LoRa.BW_125KHZ
        elif bw == 'BW250':
            return LoRa.BW_250KHZ
        else:
            return LoRa.BW_500KHZ

    def _sf_bw_to_dr(self, sf, bw):
        dr = 'SF' + str(sf)
        if bw == LoRa.BW_125KHZ:
            return dr + 'BW125'
        elif bw == LoRa.BW_250KHZ:
            return dr + 'BW250'
        else:
            return dr + 'BW500'

    def _lora_cb(self, lora):
        """
        LoRa radio events callback handler.
        """

        events = lora.events()
        if events & LoRa.RX_PACKET_EVENT:
            self.rxnb += 1
            self.rxok += 1
            rx_data = self.lora_sock.recv(256)
            stats = lora.stats()
            if DEBUG:
                self._log("stats "+ujson.dumps(stats))
                self._log('rx_timestamp diff: {}', utime.ticks_diff(stats.rx_timestamp,utime.ticks_cpu()))
            packet = self._make_node_packet(rx_data, self.rtc.now(), stats.rx_timestamp, stats.sfrx, self.bw, stats.rssi, stats.snr)
            packet = self.frequency_rounding_fix(packet, self.frequency)
            self._log('Received and uploading packet: {}', packet)
            self._push_data(packet)
            self._log('after _push_data')
            self.rxfw += 1
            
        if events & LoRa.TX_PACKET_EVENT:
            self.txnb += 1
            lora.init(
                mode=LoRa.LORA,
                region=self.region,
                frequency=self.frequency,
                bandwidth=self.bw,
                sf=self.sf,
                preamble=8,
                coding_rate=LoRa.CODING_4_5,
                power_mode=LoRa.ALWAYS_ON,
                #tx_iq=True
                )

    def _freq_to_float(self, frequency):
        """
        MicroPython has some inprecision when doing large float division.
        To counter this, this method first does integer division until we
        reach the decimal breaking point. This doesn't completely elimate
        the issue in all cases, but it does help for a number of commonly
        used frequencies.
        """

        divider = 6
        while divider > 0 and frequency % 10 == 0:
            frequency = frequency // 10
            divider -= 1
        if divider > 0:
            frequency = frequency / (10 ** divider)
        return frequency

    def frequency_rounding_fix(self, packet, frequency):
        freq = str(frequency)[0:3] + '.' + str(frequency)[3]

        start = packet.find("freq\":")
        end = packet.find(",", start)

        packet = packet[:start + 7] + freq + packet[end:]

        return packet

    def _make_stat_packet(self):
        now = self.rtc.now()
        STAT_PK["stat"]["time"] = "%d-%02d-%02d %02d:%02d:%02d GMT" % (now[0], now[1], now[2], now[3], now[4], now[5])
        STAT_PK["stat"]["rxnb"] = self.rxnb
        STAT_PK["stat"]["rxok"] = self.rxok
        STAT_PK["stat"]["rxfw"] = self.rxfw
        STAT_PK["stat"]["dwnb"] = self.dwnb
        STAT_PK["stat"]["txnb"] = self.txnb
        return ujson.dumps(STAT_PK)

    def _make_node_packet(self, rx_data, rx_time, tmst, sf, bw, rssi, snr):
        RX_PK["rxpk"][0]["time"] = "%d-%02d-%02dT%02d:%02d:%02d.%dZ" % (rx_time[0], rx_time[1], rx_time[2], rx_time[3], rx_time[4], rx_time[5], rx_time[6])
        RX_PK["rxpk"][0]["tmst"] = tmst
        RX_PK["rxpk"][0]["freq"] = self._freq_to_float(self.frequency)
        RX_PK["rxpk"][0]["datr"] = self._sf_bw_to_dr(sf, bw)
        RX_PK["rxpk"][0]["rssi"] = rssi
        RX_PK["rxpk"][0]["lsnr"] = snr
        RX_PK["rxpk"][0]["data"] = ubinascii.b2a_base64(rx_data)[:-1]
        RX_PK["rxpk"][0]["size"] = len(rx_data)
        return ujson.dumps(RX_PK)

    def _push_data(self, data):
        token = uos.urandom(2)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes([PUSH_DATA]) + ubinascii.unhexlify(self.id) + data
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception as ex:
                self._log('Failed to push uplink packet to server: {}', ex)

    def _pull_data(self):
        token = uos.urandom(2)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes([PULL_DATA]) + ubinascii.unhexlify(self.id)
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception as ex:
                self._log('Failed to pull downlink packets from server: {}', ex)

    def _ack_pull_rsp(self, token, error):
        TX_ACK_PK["txpk_ack"]["error"] = error
        resp = ujson.dumps(TX_ACK_PK)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes([PULL_ACK]) + ubinascii.unhexlify(self.id) + resp
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception as ex:
                self._log('PULL RSP ACK exception: {}', ex)

    def _send_down_link(self, data, tmst, datarate, frequency):
        """
        Transmits a downlink message over LoRa.
        """
        self.lora.init(
            mode=LoRa.LORA,
            region=self.region,
            frequency=frequency,
            bandwidth=self._dr_to_bw(datarate),     # LoRa.BW_125KHZ
            sf=self._dr_to_sf(datarate),
            preamble=8,
            coding_rate=LoRa.CODING_4_5,
            power_mode=LoRa.ALWAYS_ON,
            #tx_iq=True
        )

        if WINDOW_COMPENSATION=='cycle':
            self.window_compensation = -((self.downlink_count % 25) * 1000)
        else:
            self.window_compensation = WINDOW_COMPENSATION

        t_adj = utime.ticks_add(tmst, self.window_compensation)
        self.lora_sock.settimeout(1)
        t_cpu = utime.ticks_cpu()
        self._log("BEFORE spin wait at {} late {}",t_cpu,t_cpu-tmst)
        while utime.ticks_diff(t_adj, utime.ticks_cpu()) > 0:
            pass
        t_cpu = utime.ticks_cpu()
        self._log("BEFORE lora_sock.send at {} late {} window_compensation {}",t_cpu,t_cpu-tmst,self.window_compensation)
        self.lora_sock.send(data)
        self._log("AFTER lora_sock.send late {}",utime.ticks_cpu()-tmst)
        self.lora_sock.setblocking(False)
        self._log(
            'Sent downlink packet scheduled on {}, at {:,d} Hz using {}: {}',
            tmst,
            frequency,
            datarate,
            data
        )
        self.downlink_count += 1

    def _send_down_link_class_c(self, data, datarate, frequency):
        self.lora.init(
            mode=LoRa.LORA,
            frequency=frequency,
            bandwidth=self._dr_to_bw(datarate),
            sf=self._dr_to_sf(datarate),
            preamble=8,
            coding_rate=LoRa.CODING_4_5,
            region=self.region,
            power_mode=LoRa.ALWAYS_ON,
            #tx_iq=True,
            device_class=LoRa.CLASS_C
            )

        self.lora_sock.send(data)
        self._log(
            'Sent downlink packet scheduled on {}, at {:.3f} Mhz using {}: {}',
            utime.ticks_cpu(),
            self._freq_to_float(frequency),
            datarate,
            data
        )

    def _udp_thread(self):
        """
        UDP thread, reads data from the server and handles it.
        """
        loops = 0
        while not self.udp_stop:
            if loops % 20 == 19:
                b4 = utime.ticks_cpu()
                gc.collect()
                self._log("gc.collect for {} us",utime.ticks_diff(utime.ticks_cpu(),b4))
            b4 = utime.ticks_cpu()
            utime.sleep_ms(UDP_THREAD_CYCLE_MS)
            t_diff = utime.ticks_diff(utime.ticks_cpu(),b4)
            if t_diff > (UDP_THREAD_CYCLE_MS*1000*1.5):
                self._log("overslept! for {} us",t_diff)
            try:
                b4 = utime.ticks_cpu()
                data, src = self.sock.recvfrom(1024)
                self._log("sock.recvfrom for {} us",utime.ticks_diff(utime.ticks_cpu(),b4))
                _token = data[1:3]
                _type = data[3]
                if _type == PUSH_ACK:
                    self._log("Push ack")
                elif _type == PULL_ACK:
                    self._log("Pull ack")
                elif _type == PULL_RESP:
                    self._log("Pull resp")
                    self.dwnb += 1
                    ack_error = TX_ERR_NONE
                    tx_pk = ujson.loads(data[4:])
                    if DEBUG:
                       self._log("tx data "+ujson.dumps(tx_pk))
                    payload = ubinascii.a2b_base64(tx_pk["txpk"]["data"])
                    # depending on the board, pull the downlink message 1 or 6 ms upfronnt
                    
                    # tmst = utime.ticks_add(tx_pk["txpk"]["tmst"], self.window_compensation)
                    # t_us = utime.ticks_diff(utime.ticks_cpu(), utime.ticks_add(tmst, -15000))
                    tmst = tx_pk["txpk"]["tmst"]
                    t_req = utime.ticks_add(tmst, -RX_DELAY_TIMER_EARLY)
                    t_cpu = utime.ticks_cpu()
                    self._log("t_cpu {}",t_cpu)
                    t_us = utime.ticks_diff(t_req, t_cpu)
                    self._log("t_us {}",t_us)
                    if 1000 < t_us < 10000000:
                        self._log("Delaying for {} at {}, so should fire at t_req {}, compensated early_by {}",t_us,t_cpu,t_req,RX_DELAY_TIMER_EARLY)
                        def handler(x):
                            t_cpu = utime.ticks_cpu()
                            self._log("_send_down_link alarm fired at {} late {}us",t_cpu,t_cpu-t_req)
                            self._send_down_link(
                                payload,
                                tmst, tx_pk["txpk"]["datr"],
                                int(tx_pk["txpk"]["freq"] * 1000 + 0.0005) * 1000
                            )
                        self.uplink_alarm = Timer.Alarm(handler=handler, us=t_us)
                    else:
                        ack_error = TX_ERR_TOO_LATE
                        self._log('Downlink timestamp error!, t_us: {}', t_us)
                    self._ack_pull_rsp(_token, ack_error)
                    self._log("Pull rsp")
            except usocket.timeout:
                pass
            except OSError as ex:
                if ex.args[0] != errno.EAGAIN:
                    self._log('UDP recv OSError Exception: {}', ex)
            except Exception as ex:
                self._log('UDP recv Exception: {}', ex)

        # we are to close the socket
        self.sock.close()
        self.udp_stop = False
        self._log('UDP thread stopped')

    def _log(self, message, *args):
        """
        Outputs a log message to stdout.
        """
        if len(args)==0:
            print('[{}] '.format(utime.ticks_cpu()) + str(message))
        else:
            print('[{}] {}'.format(
                utime.ticks_cpu(),
                str(message).format(*args)
                ))
class NanoGateway:
    def __init__(self,
                 id,
                 frequency,
                 datarate,
                 ssid,
                 password,
                 server,
                 port,
                 ntp='pool.ntp.org',
                 ntp_period=3600):
        self.id = id
        self.frequency = frequency
        self.datarate = datarate
        self.sf = self._dr_to_sf(datarate)
        self.ssid = ssid
        self.password = password
        self.server = server
        self.port = port
        self.ntp = ntp
        self.ntp_period = ntp_period
        self.rtc = machine.RTC()

        self.rxnb = 0
        self.rxok = 0
        self.rxfw = 0
        self.dwnb = 0
        self.txnb = 0

        self.stat_alarm = None
        self.pull_alarm = None
        self.uplink_alarm = None

        self.udp_stop = False
        self.udp_lock = _thread.allocate_lock()

        self.lora = None
        self.lora_sock = None

    def start(self):
        # setup WiFi as a station and connect
        self.wlan = WLAN(mode=WLAN.STA)
        self._connect_to_wifi()

        # get a time sync
        self.rtc.ntp_sync(self.ntp, update_period=self.ntp_period)
        while not self.rtc.synced():
            time.sleep_ms(50)
        print("RTC NTP sync complete")

        # get the server IP and create an UDP socket
        self.server_ip = socket.getaddrinfo(self.server, self.port)[0][-1]
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
                                  socket.IPPROTO_UDP)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.setblocking(False)

        # push the first time immediatelly
        self._push_data(self._make_stat_packet())

        # create the alarms
        self.stat_alarm = Timer.Alarm(
            handler=lambda t: self._push_data(self._make_stat_packet()),
            s=60,
            periodic=True)
        self.pull_alarm = Timer.Alarm(handler=lambda u: self._pull_data(),
                                      s=25,
                                      periodic=True)

        # start the UDP receive thread
        self.udp_stop = False
        _thread.start_new_thread(self._udp_thread, ())

        # initialize the LoRa radio in LORA mode
        self.lora = LoRa(mode=LoRa.LORA,
                         frequency=self.frequency,
                         bandwidth=LoRa.BW_125KHZ,
                         sf=self.sf,
                         preamble=8,
                         coding_rate=LoRa.CODING_4_5,
                         tx_iq=True)
        # create a raw LoRa socket
        self.lora_sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
        self.lora_sock.setblocking(False)
        self.lora_tx_done = False

        self.lora.callback(trigger=(LoRa.RX_PACKET_EVENT
                                    | LoRa.TX_PACKET_EVENT),
                           handler=self._lora_cb)

    def stop(self):
        # send the LoRa radio to sleep, stop the NTP sync, cancel the alarms and kill the UDP thread
        self.lora.callback(trigger=None, handler=None)
        self.lora.power_mode(LoRa.SLEEP)
        self.rtc.ntp_sync(None)
        self.stat_alarm.cancel()
        self.pull_alarm.cancel()
        self.udp_stop = True
        while self.udp_stop:
            time.sleep_ms(50)
        # disable WLAN
        self.wlan.disconnect()
        self.wlan.deinit()

    def _connect_to_wifi(self):
        self.wlan.connect(self.ssid, auth=(None, self.password))
        while not self.wlan.isconnected():
            time.sleep_ms(50)
        print("WiFi connected")

    def _dr_to_sf(self, dr):
        sf = dr[2:4]
        if sf[1] not in '0123456789':
            sf = sf[:1]
        return int(sf)

    def _sf_to_dr(self, sf):
        return self.datarate

    def _make_stat_packet(self):
        now = self.rtc.now()
        STAT_PK["stat"]["time"] = "%d-%02d-%02d %02d:%02d:%02d GMT" % (
            now[0], now[1], now[2], now[3], now[4], now[5])
        STAT_PK["stat"]["rxnb"] = self.rxnb
        STAT_PK["stat"]["rxok"] = self.rxok
        STAT_PK["stat"]["rxfw"] = self.rxfw
        STAT_PK["stat"]["dwnb"] = self.dwnb
        STAT_PK["stat"]["txnb"] = self.txnb
        return json.dumps(STAT_PK)

    def _make_node_packet(self, rx_data, rx_time, tmst, sf, rssi, snr):
        RX_PK["rxpk"][0]["time"] = "%d-%02d-%02dT%02d:%02d:%02d.%dZ" % (
            rx_time[0], rx_time[1], rx_time[2], rx_time[3], rx_time[4],
            rx_time[5], rx_time[6])
        RX_PK["rxpk"][0]["tmst"] = tmst
        RX_PK["rxpk"][0]["datr"] = self._sf_to_dr(sf)
        RX_PK["rxpk"][0]["rssi"] = rssi
        RX_PK["rxpk"][0]["lsnr"] = float(snr)
        RX_PK["rxpk"][0]["data"] = binascii.b2a_base64(rx_data)[:-1]
        RX_PK["rxpk"][0]["size"] = len(rx_data)
        return json.dumps(RX_PK)

    def _push_data(self, data):
        token = os.urandom(2)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes(
            [PUSH_DATA]) + binascii.unhexlify(self.id) + data
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception:
                print("PUSH exception")

    def _pull_data(self):
        token = os.urandom(2)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes(
            [PULL_DATA]) + binascii.unhexlify(self.id)
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception:
                print("PULL exception")

    def _ack_pull_rsp(self, token, error):
        TX_ACK_PK["txpk_ack"]["error"] = error
        resp = json.dumps(TX_ACK_PK)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes(
            [PULL_ACK]) + binascii.unhexlify(self.id) + resp
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception:
                print("PULL RSP ACK exception")

    def _lora_cb(self, lora):
        events = lora.events()
        if events & LoRa.RX_PACKET_EVENT:
            self.rxnb += 1
            self.rxok += 1
            rx_data = self.lora_sock.recv(256)
            stats = lora.stats()
            self._push_data(
                self._make_node_packet(rx_data, self.rtc.now(),
                                       stats.rx_timestamp, stats.sfrx,
                                       stats.rssi, stats.snr))
            self.rxfw += 1
        if events & LoRa.TX_PACKET_EVENT:
            self.txnb += 1
            lora.init(mode=LoRa.LORA,
                      frequency=self.frequency,
                      bandwidth=LoRa.BW_125KHZ,
                      sf=self.sf,
                      preamble=8,
                      coding_rate=LoRa.CODING_4_5,
                      tx_iq=True)

    def _send_down_link(self, data, tmst, datarate, frequency):
        self.lora.init(mode=LoRa.LORA,
                       frequency=frequency,
                       bandwidth=LoRa.BW_125KHZ,
                       sf=self._dr_to_sf(datarate),
                       preamble=8,
                       coding_rate=LoRa.CODING_4_5,
                       tx_iq=True)
        while time.ticks_us() < tmst:
            pass
        self.lora_sock.send(data)

    def _udp_thread(self):
        while not self.udp_stop:
            try:
                data, src = self.sock.recvfrom(1024)
                _token = data[1:3]
                _type = data[3]
                if _type == PUSH_ACK:
                    print("Push ack")
                elif _type == PULL_ACK:
                    print("Pull ack")
                elif _type == PULL_RESP:
                    self.dwnb += 1
                    ack_error = TX_ERR_NONE
                    tx_pk = json.loads(data[4:])
                    tmst = tx_pk["txpk"]["tmst"]
                    t_us = tmst - time.ticks_us() - 12500
                    if t_us < 0:
                        t_us += 0xFFFFFFFF
                    if t_us < 20000000:
                        self.uplink_alarm = Timer.Alarm(
                            handler=lambda x: self._send_down_link(
                                binascii.a2b_base64(tx_pk["txpk"]["data"]),
                                tx_pk["txpk"]["tmst"] - 50, tx_pk["txpk"][
                                    "datr"],
                                int(tx_pk["txpk"]["freq"] * 1000000)),
                            us=t_us)
                    else:
                        ack_error = TX_ERR_TOO_LATE
                        print("Downlink timestamp error!, t_us:", t_us)
                    self._ack_pull_rsp(_token, ack_error)
                    print("Pull rsp")
            except socket.timeout:
                pass
            except OSError as e:
                if e.errno == errno.EAGAIN:
                    pass
                else:
                    print("UDP recv OSError Exception")
            except Exception:
                print("UDP recv Exception")
            # wait before trying to receive again
            time.sleep(0.025)

        # we are to close the socket
        self.sock.close()
        self.udp_stop = False
Esempio n. 21
0
# if mac > 10:
#     pymesh.end_device(True)
# if mac == 20:
#      pymesh.leader_priority(255)
# elif mac == 15:
#      pymesh.leader_priority(250)

while not pymesh.is_connected():
    print(pymesh.status_str())
    time.sleep(3)

print("Current available memory after pymesh load: %d" % gc.mem_free())
gc.collect()

wlan = WLAN()
wlan.deinit()
wlan = WLAN(mode=WLAN.AP,
            ssid=node.node_ssid,
            auth=(WLAN.WPA2, node.node_pass),
            channel=11,
            antenna=WLAN.INT_ANT)
wlan.ifconfig(id=1,
              config=('192.168.1.1', '255.255.255.0', '192.168.1.1',
                      '8.8.8.8'))

print("====================================================================")

print("AP setting up")

node.first_time_set()
Esempio n. 22
0
from network import WLAN
w = WLAN()
w.deinit()  ##Switch Off WLAN
Esempio n. 23
0
class NanoGateway:
    """
    Nano gateway class, set up by default for use with TTN, but can be configured
    for any other network supporting the Semtech Packet Forwarder.
    Only required configuration is wifi_ssid and wifi_password which are used for
    connecting to the Internet.
    """

    def __init__(self, id, frequency, datarate, ssid, password, server, port, ntp_server='pool.ntp.org', ntp_period=3600):
        self.id = id
        self.server = server
        self.port = port

        self.frequency = frequency
        self.datarate = datarate

        self.ssid = ssid
        self.password = password

        self.ntp_server = ntp_server
        self.ntp_period = ntp_period

        self.server_ip = None

        self.rxnb = 0
        self.rxok = 0
        self.rxfw = 0
        self.dwnb = 0
        self.txnb = 0

        self.sf = self._dr_to_sf(self.datarate)
        self.bw = self._dr_to_bw(self.datarate)

        self.stat_alarm = None
        self.pull_alarm = None
        self.uplink_alarm = None

        self.wlan = None
        self.sock = None
        self.udp_stop = False
        self.udp_lock = _thread.allocate_lock()

        self.lora = None
        self.lora_sock = None

        self.rtc = machine.RTC()

    def start(self):
        """
        Starts the LoRaWAN nano gateway.
        """

        self._log('Starting LoRaWAN nano gateway with id: {}', self.id)

        # setup WiFi as a station and connect
        self.wlan = WLAN(mode=WLAN.STA)
        self._connect_to_wifi()

        # get a time sync
        self._log('Syncing time with {} ...', self.ntp_server)
        self.rtc.ntp_sync(self.ntp_server, update_period=self.ntp_period)
        while not self.rtc.synced():
            utime.sleep_ms(50)
        self._log("RTC NTP sync complete")

        # get the server IP and create an UDP socket
        self.server_ip = usocket.getaddrinfo(self.server, self.port)[0][-1]
        self._log('Opening UDP socket to {} ({}) port {}...', self.server, self.server_ip[0], self.server_ip[1])
        self.sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM, usocket.IPPROTO_UDP)
        self.sock.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)
        self.sock.setblocking(False)

        # push the first time immediatelly
        self._push_data(self._make_stat_packet())

        # create the alarms
        self.stat_alarm = Timer.Alarm(handler=lambda t: self._push_data(self._make_stat_packet()), s=60, periodic=True)
        self.pull_alarm = Timer.Alarm(handler=lambda u: self._pull_data(), s=25, periodic=True)

        # start the UDP receive thread
        self.udp_stop = False
        _thread.start_new_thread(self._udp_thread, ())

        # initialize the LoRa radio in LORA mode
        self._log('Setting up the LoRa radio at {:.1f} Mhz using {}', self._freq_to_float(self.frequency), self.datarate)
        self.lora = LoRa(
            mode=LoRa.LORA,
            frequency=self.frequency,
            bandwidth=self.bw,
            sf=self.sf,
            preamble=8,
            coding_rate=LoRa.CODING_4_5,
            tx_iq=True
        )

        # create a raw LoRa socket
        self.lora_sock = usocket.socket(usocket.AF_LORA, usocket.SOCK_RAW)
        self.lora_sock.setblocking(False)
        self.lora_tx_done = False

        self.lora.callback(trigger=(LoRa.RX_PACKET_EVENT | LoRa.TX_PACKET_EVENT), handler=self._lora_cb)
        self._log('LoRaWAN nano gateway online')

    def stop(self):
        """
        Stops the LoRaWAN nano gateway.
        """

        self._log('Stopping...')

        # send the LoRa radio to sleep
        self.lora.callback(trigger=None, handler=None)
        self.lora.power_mode(LoRa.SLEEP)

        # stop the NTP sync
        self.rtc.ntp_sync(None)

        # cancel all the alarms
        self.stat_alarm.cancel()
        self.pull_alarm.cancel()

        # signal the UDP thread to stop
        self.udp_stop = True
        while self.udp_stop:
            utime.sleep_ms(50)

        # disable WLAN
        self.wlan.disconnect()
        self.wlan.deinit()

    def _connect_to_wifi(self):
        self.wlan.connect(self.ssid, auth=(None, self.password))
        while not self.wlan.isconnected():
            utime.sleep_ms(50)
        self._log('WiFi connected to: {}', self.ssid)

    def _dr_to_sf(self, dr):
        sf = dr[2:4]
        if sf[1] not in '0123456789':
            sf = sf[:1]
        return int(sf)

    def _dr_to_bw(self, dr):
        bw = dr[-5:]
        if bw == 'BW125':
            return LoRa.BW_125KHZ
        elif bw == 'BW250':
            return LoRa.BW_250KHZ
        else:
            return LoRa.BW_500KHZ

    def _sf_bw_to_dr(self, sf, bw):
        dr = 'SF' + str(sf)
        if bw == LoRa.BW_125KHZ:
            return dr + 'BW125'
        elif bw == LoRa.BW_250KHZ:
            return dr + 'BW250'
        else:
            return dr + 'BW500'

    def _lora_cb(self, lora):
        """
        LoRa radio events callback handler.
        """

        events = lora.events()
        if events & LoRa.RX_PACKET_EVENT:
            self.rxnb += 1
            self.rxok += 1
            rx_data = self.lora_sock.recv(256)
            stats = lora.stats()
            packet = self._make_node_packet(rx_data, self.rtc.now(), stats.rx_timestamp, stats.sfrx, self.bw, stats.rssi, stats.snr)
            self._push_data(packet)
            self._log('Received packet: {}', packet)
            self.rxfw += 1
        if events & LoRa.TX_PACKET_EVENT:
            self.txnb += 1
            lora.init(
                mode=LoRa.LORA,
                frequency=self.frequency,
                bandwidth=self.bw,
                sf=self.sf,
                preamble=8,
                coding_rate=LoRa.CODING_4_5,
                tx_iq=True
                )

    def _freq_to_float(self, frequency):
        """
        MicroPython has some inprecision when doing large float division.
        To counter this, this method first does integer division until we
        reach the decimal breaking point. This doesn't completely elimate
        the issue in all cases, but it does help for a number of commonly
        used frequencies.
        """

        divider = 6
        while divider > 0 and frequency % 10 == 0:
            frequency = frequency // 10
            divider -= 1
        if divider > 0:
            frequency = frequency / (10 ** divider)
        return frequency

    def _make_stat_packet(self):
        now = self.rtc.now()
        STAT_PK["stat"]["time"] = "%d-%02d-%02d %02d:%02d:%02d GMT" % (now[0], now[1], now[2], now[3], now[4], now[5])
        STAT_PK["stat"]["rxnb"] = self.rxnb
        STAT_PK["stat"]["rxok"] = self.rxok
        STAT_PK["stat"]["rxfw"] = self.rxfw
        STAT_PK["stat"]["dwnb"] = self.dwnb
        STAT_PK["stat"]["txnb"] = self.txnb
        return ujson.dumps(STAT_PK)

    def _make_node_packet(self, rx_data, rx_time, tmst, sf, bw, rssi, snr):
        RX_PK["rxpk"][0]["time"] = "%d-%02d-%02dT%02d:%02d:%02d.%dZ" % (rx_time[0], rx_time[1], rx_time[2], rx_time[3], rx_time[4], rx_time[5], rx_time[6])
        RX_PK["rxpk"][0]["tmst"] = tmst
        RX_PK["rxpk"][0]["freq"] = self._freq_to_float(self.frequency)
        RX_PK["rxpk"][0]["datr"] = self._sf_bw_to_dr(sf, bw)
        RX_PK["rxpk"][0]["rssi"] = rssi
        RX_PK["rxpk"][0]["lsnr"] = snr
        RX_PK["rxpk"][0]["data"] = ubinascii.b2a_base64(rx_data)[:-1]
        RX_PK["rxpk"][0]["size"] = len(rx_data)
        return ujson.dumps(RX_PK)

    def _push_data(self, data):
        token = uos.urandom(2)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes([PUSH_DATA]) + ubinascii.unhexlify(self.id) + data
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception as ex:
                self._log('Failed to push uplink packet to server: {}', ex)

    def _pull_data(self):
        token = uos.urandom(2)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes([PULL_DATA]) + ubinascii.unhexlify(self.id)
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception as ex:
                self._log('Failed to pull downlink packets from server: {}', ex)

    def _ack_pull_rsp(self, token, error):
        TX_ACK_PK["txpk_ack"]["error"] = error
        resp = ujson.dumps(TX_ACK_PK)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes([PULL_ACK]) + ubinascii.unhexlify(self.id) + resp
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception as ex:
                self._log('PULL RSP ACK exception: {}', ex)

    def _send_down_link(self, data, tmst, datarate, frequency):
        """
        Transmits a downlink message over LoRa.
        """

        self.lora.init(
            mode=LoRa.LORA,
            frequency=frequency,
            bandwidth=self._dr_to_bw(datarate),
            sf=self._dr_to_sf(datarate),
            preamble=8,
            coding_rate=LoRa.CODING_4_5,
            tx_iq=True
            )
        while utime.ticks_us() < tmst:
            pass
        self.lora_sock.send(data)
        self._log(
            'Sent downlink packet scheduled on {:.3f}, at {:.1f} Mhz using {}: {}',
            tmst / 1000000,
            self._freq_to_float(frequency),
            datarate,
            data
        )

    def _udp_thread(self):
        """
        UDP thread, reads data from the server and handles it.
        """

        while not self.udp_stop:
            try:
                data, src = self.sock.recvfrom(1024)
                _token = data[1:3]
                _type = data[3]
                if _type == PUSH_ACK:
                    self._log("Push ack")
                elif _type == PULL_ACK:
                    self._log("Pull ack")
                elif _type == PULL_RESP:
                    self.dwnb += 1
                    ack_error = TX_ERR_NONE
                    tx_pk = ujson.loads(data[4:])
                    tmst = tx_pk["txpk"]["tmst"]
                    t_us = tmst - utime.ticks_us() - 12500
                    if t_us < 0:
                        t_us += 0xFFFFFFFF
                    if t_us < 20000000:
                        self.uplink_alarm = Timer.Alarm(
                            handler=lambda x: self._send_down_link(
                                ubinascii.a2b_base64(tx_pk["txpk"]["data"]),
                                tx_pk["txpk"]["tmst"] - 50, tx_pk["txpk"]["datr"],
                                int(tx_pk["txpk"]["freq"] * 1000000)
                            ), 
                            us=t_us
                        )
                    else:
                        ack_error = TX_ERR_TOO_LATE
                        self._log('Downlink timestamp error!, t_us: {}', t_us)
                    self._ack_pull_rsp(_token, ack_error)
                    self._log("Pull rsp")
            except usocket.timeout:
                pass
            except OSError as ex:
                if ex.errno != errno.EAGAIN:
                    self._log('UDP recv OSError Exception: {}', ex)
            except Exception as ex:
                self._log('UDP recv Exception: {}', ex)

            # wait before trying to receive again
            utime.sleep_ms(UDP_THREAD_CYCLE_MS)

        # we are to close the socket
        self.sock.close()
        self.udp_stop = False
        self._log('UDP thread stopped')

    def _log(self, message, *args):
        """
        Outputs a log message to stdout.
        """

        print('[{:>10.3f}] {}'.format(
            utime.ticks_ms() / 1000,
            str(message).format(*args)
            ))
Esempio n. 24
0
import pycom
import machine
from machine import Pin

pycom.heartbeat(False)

from network import WLAN
from network import Bluetooth
import os

pycom.wifi_on_boot(False)
wlan = WLAN()  # Instantiates WLAN
wlan.deinit()  # Turns off wifi
bluetooth = Bluetooth()  # Instantiates Bluetooth
bluetooth.deinit()  # Turns off Bluetooth

counter = pycom.nvs_get('count')  # Get ID value from memory

if type(counter) != int:  # If the value is not in memory
    pycom.nvs_set('count', 0)  # ID = 0
Esempio n. 25
0
class WiFiManager:
    def __init__(self, manager, settings):
        self.manager = manager
        self.settings = settings

        # WIFI settings.
        self.stations = self.settings.get('networking.wifi.stations')
        self.station = None

    def start(self):
        """
        https://docs.pycom.io/tutorials/all/wlan.html
        https://github.com/pycom/pydocs/blob/master/firmwareapi/pycom/network/wlan.md
        """

        # Todo: Propagate more parameters here, e.g. for using an external antenna.
        self.station = WLAN()

        #if machine.reset_cause() == machine.SOFT_RESET:
        #   print("WiFi STA: Network connection after SOFT_RESET.")
        #    self.print_short_status()
        #    # Inform about networking status.
        #    self.print_address_status()
        #    return True

        # Save the default ssid and auth for restoring AP mode later
        original_ssid = self.station.ssid()
        original_auth = self.station.auth()

        # Inform about networking status.
        self.print_address_status()

        # Setup network interface.
        self.station.init()

        # Check WiFi connectivity.
        if self.station.isconnected():

            log.info(
                "WiFi STA: Network connection already established, will skip scanning and resume connectivity."
            )
            self.print_short_status()

            # Give system some breath.
            time.sleep(0.25)

            # Inform about networking status.
            self.print_address_status()

            return True

        # Prepare information about known WiFi networks.
        networks_known = frozenset(
            [station['ssid'] for station in self.stations])

        log.info("WiFi STA: Starting interface")
        self.station.mode(WLAN.STA)

        # Attempt to connect to known/configured networks.
        log.info("WiFi STA: Directly connecting to configured networks: %s",
                 list(networks_known))
        try:
            self.connect_stations(networks_known)

        except:
            log.warning('WiFi: Switching to AP mode not implemented yet')

        # Todo: Reenable WiFi AP mode in the context of an "initial configuration" mode.
        """
        log.info('WiFi: Switching to AP mode')
        # WLAN.AP, original_ssid, original_auth, WLAN.INT_ANT
        # TOOD: Make default channel configurable
        self.station.init(mode=WLAN.AP, ssid=original_ssid, auth=original_auth, channel=6, antenna=WLAN.INT_ANT)
        """

    def power_off(self):
        """
        Power off all radio peripherals.

        - https://forum.pycom.io/topic/563/disabling-wifi-on-lopy
        - https://github.com/Hiverize/FiPy/commit/b6b15677
        """

        # WiFi
        if self.station:
            try:
                log.info('Turning off WiFi')
                self.station.deinit()
            except:
                log.exception('Turning off WiFi failed')

    def connect_stations(self, network_names):

        # Prepare information about known WiFi networks.
        network_map = {station['ssid']: station for station in self.stations}

        for network_name in network_names:
            try:
                # All the configuration details for this network.
                # {
                #    'ssid': 'FooBar',
                #    'password': '******',
                #    'ifconfig': ('192.168.42.42', '255.255.255.0', '192.168.42.1', '192.168.42.1'),
                # }
                network_selected = network_map[network_name]
                if self.connect_station(network_selected):
                    break

            except Exception:
                log.exception(
                    'WiFi STA: Connecting to "{}" failed'.format(network_name))

        if not self.station.isconnected():

            self.forget_network(network_name)

            message = 'WiFi STA: Connecting to any network candidate failed'
            description = 'Please check your WiFi configuration for one of the ' \
                          'station candidates {}.'.format(len(network_names))
            log.error('{}. {}'.format(message, description))
            log.warning(
                'Todo: We might want to switch to AP mode here or alternatively '
                'buffer telemetry data to flash to be scheduled for transmission later.'
            )
            raise WiFiException(message)

    def connect_station(self, network):

        network_name = network['ssid']

        log.info('WiFi STA: Prepare connecting to network "{}"'.format(
            network_name))

        auth_mode = self.get_auth_mode(network_name)

        log.info(
            'WiFi STA: Attempt connecting to network "{}" with auth mode "{}"'.
            format(network_name, auth_mode))

        password = network['password']

        # TODO: Optionally, configure hostname.
        # https://docs.micropython.org/en/latest/library/network.WLAN.html
        # https://github.com/pycom/pycom-micropython-sigfox/pull/165
        # https://forum.pycom.io/topic/3326/new-firmware-release-v1-18-0
        if 'dhcp_hostname' in network:
            if hasattr(self.station, 'config'):
                log.ingo('WiFi STA: Using dhcp_hostname "{}"'.format(
                    network['dhcp_hostname']))
                self.station.config(dhcp_hostname=network['dhcp_hostname'])
            else:
                log.error('Could not set hostname on older MicroPython')

        # Optionally, configure static IP address.
        if 'ifconfig' in network:
            log.info(
                'WiFi STA: Using static network configuration "{}"'.format(
                    network_name))
            self.station.ifconfig(config=network['ifconfig'])

        # Obtain timeout value.
        network_timeout = network.get('timeout', 15.0)

        # Set interval how often to poll for WiFi connectivity.
        network_poll_interval = 800

        # Connect to WiFi station.
        log.info(
            'WiFi STA: Starting connection to "{}" with timeout of {} seconds'.
            format(network_name, network_timeout))
        self.station.connect(network_name, (auth_mode, password),
                             timeout=int(network_timeout * 1000))

        # Wait for station network to arrive.
        # ``isconnected()`` returns True when connected to a WiFi access point *and* having a valid IP address.
        retries = int(network_timeout * network_poll_interval)
        while not self.station.isconnected() and retries > 0:

            log.info(
                'WiFi STA: Waiting for network "{}".'.format(network_name))
            retries -= 1

            # Save power while waiting.
            machine.idle()

            # Feed watchdog.
            self.manager.device.feed_watchdog()

            # Don't busy-wait.
            time.sleep_ms(network_poll_interval)

        if not self.station.isconnected():
            raise WiFiException(
                'WiFi STA: Unable to connect to "{}"'.format(network_name))

        # Inform about networking status.
        self.print_short_status()
        self.print_address_status()

        return True

    def scan_stations(self):

        self.manager.device.feed_watchdog()

        # Inquire visible networks.
        log.info("WiFi STA: Scanning for networks")
        stations_available = self.station.scan()
        networks_found = frozenset([e.ssid for e in stations_available])

        # Print names/SSIDs of networks found.
        log.info("WiFi STA: Networks available: %s", list(networks_found))

        return stations_available

        # Compute set of effective networks by intersecting known with found ones.
        #network_candidates = list(networks_found & networks_known)
        #log.info("WiFi STA: Network candidates: %s", network_candidates)

    def get_ssid(self):
        return self.station.ssid()

    def get_ip_address(self):
        try:
            return self.station.ifconfig()[0]
        except:
            pass

    def get_auth_mode(self, network_name):

        # NVRAM key for storing auth mode per network. Maximum of 15 characters.
        auth_mode_nvs_key = self.auth_mode_nvs_key(network_name)

        # Get WiFi STA auth mode from NVRAM.
        try:
            import pycom
            auth_mode = pycom.nvs_get(auth_mode_nvs_key)
            log.info('WiFi STA: Auth mode from NVRAM with key=%s, value=%s',
                     auth_mode_nvs_key, auth_mode)
        except:
            auth_mode = None

        # Fall back to find out WiFi STA auth mode by network scan.
        if auth_mode is None:
            log.info(
                'WiFi STA: Unknown auth mode for network "%s", invoking WiFi scan',
                network_name)
            wifi_neighbourhood = self.scan_stations()

            #log.info('WiFi STA: Neighbourhood is %s', wifi_neighbourhood)
            for e in wifi_neighbourhood:
                if e.ssid == network_name:
                    auth_mode = e.sec
                    break

            if not auth_mode:
                message = 'WiFi STA: Unable to inquire auth mode for network "{}"'.format(
                    network_name)
                log.warning(message)
                raise WiFiException(message)

            log.info(
                'WiFi STA: Storing auth mode into NVRAM with key=%s, value=%s',
                auth_mode_nvs_key, auth_mode)
            try:
                import pycom
                pycom.nvs_set(auth_mode_nvs_key, auth_mode)
            except:
                log.exception('WiFi STA: Storing auth mode into NVRAM failed')

        return auth_mode

    def auth_mode_nvs_key(self, ssid):
        """
        Hack to get a short representation of a WiFi SSID in order to
        squeeze it into a NVRAM key with a maximum length of 15 characters.

        Fixme: Review this.
        """
        import hashlib
        import ubinascii
        digest = ubinascii.hexlify(hashlib.sha512(ssid).digest()).decode()
        identifier = 'wa.{}'.format(digest[15:27])
        return identifier

    def forget_network(self, network_name):
        log.info('WiFi STA: Forgetting NVRAM data for network "{}"'.format(
            network_name))
        auth_mode_nvs_key = self.auth_mode_nvs_key(network_name)
        try:
            import pycom
            pycom.nvs_erase(auth_mode_nvs_key)
        except:
            pass

    def print_short_status(self):
        log.info('WiFi STA: Connected to "{}" with IP address "{}"'.format(
            self.get_ssid(), self.get_ip_address()))

    def print_address_status(self):
        mac_address = self.humanize_mac_addresses(self.station.mac())
        ifconfig = self.station.ifconfig()
        log.info('WiFi STA: Networking address (MAC): %s', mac_address)
        log.info('WiFi STA: Networking address (IP):  %s', ifconfig)

    def humanize_mac_addresses(self, mac):
        info = {}
        if hasattr(mac, 'sta_mac'):
            info['sta_mac'] = format_mac_address(
                binascii.hexlify(mac.sta_mac).decode())
        if hasattr(mac, 'ap_mac'):
            info['ap_mac'] = format_mac_address(
                binascii.hexlify(mac.ap_mac).decode())
        return info

    def print_metrics(self):
        metrics = SystemWiFiMetrics(self.station).read()
        log.info('WiFi STA: Metrics: %s', metrics)
Esempio n. 26
0
time.sleep_ms(5000)

tft.fill(TFT.BLACK)
print("Sleep: ")
v = 0
fft_print = "Enter Sleep"
tft.text((0, v), fft_print, TFT.WHITE, sysfont, 1)
time.sleep_ms(1000)

#======================================
# Garbage Collector
#======================================

gc.collect()
gc.mem_free()

#sigfox = Sigfox(mode=Sigfox.SIGFOX, rcz=Sigfox.RCZ1)
#sigfox.deinit()

#bt = Bluetooth()
#bt.deinit()

w = WLAN()
w.deinit()

lte = LTE()
lte.deinit()

time_ms = 60000
machine.deepsleep(time_ms)
Esempio n. 27
0
        lora.power_mode(LoRa.SLEEP)
        random_sleep(10)
    pycom.rgbled(off)


### MAIN ###

MY_ID = 0x00 # to be filled in get_id
pycom.heartbeat(False)
get_id()
bt = Bluetooth()
bt.deinit()
server = Server()
server.deinit()
wlan = WLAN(mode=WLAN.STA)
wlan.deinit() # remove these two lines if you activate OTA
pybytes.smart_config(False) # disable provisioning
# py = Pytrack()
# ANSELC_ADDR = const(0x18E)
# py.poke_memory(ANSELC_ADDR, ~(1 << 7))

_LORA_PKG_FORMAT = "!BB%ds"
_LORA_RCV_PKG_FORMAT = "!BB%ds"
(my_sf, my_bw_index, my_bw_plain, guard, my_slot, packet_size) = (7, 0, 0, 15000, -1, 16) # default values
index = 0
S = 1000
active_rx = 0.0
active_tx = 0.0
proc_gw = 4000 # gw default (minimum) processing time (us)
(sack_rcv, sack_bytes) = (0, 0) # will be filled later
msg = crypto.getrandbits(packet_size*8) # just a random packet
Esempio n. 28
0
class MicroWifi:

    # ============================================================================
    # ===( Constants )============================================================
    # ============================================================================

    _ETH_AP = 1
    _ETH_STA = 0
    _IP_NONE = '0.0.0.0'
    _DEFAULT_AUTH_TYPE = WLAN.WPA2
    _AP_MASK = '255.255.255.0'
    _DEFAULT_TIMEOUT_SEC = 10

    # ============================================================================
    # ===( Utils  )===============================================================
    # ============================================================================

    @staticmethod
    def _mac2Str(binMac):
        return hexlify(binMac, ':').decode().upper()

    # ----------------------------------------------------------------------------

    def _setAPInfos(self,
                    ssid=None,
                    key=None,
                    ip=None,
                    mask=None,
                    gateway=None,
                    dns=None):
        self._apInfos = {
            'ssid': ssid,
            'key': key,
            'ip': ip,
            'mask': mask,
            'gateway': gateway,
            'dns': dns
        }

    # ----------------------------------------------------------------------------

    def _setConnectionInfos(self,
                            bssid=None,
                            ssid=None,
                            key=None,
                            ip=None,
                            mask=None,
                            gateway=None,
                            dns=None):
        self._connInfos = {
            'macBssid': bssid,
            'ssid': ssid,
            'key': key,
            'ip': ip,
            'mask': mask,
            'gateway': gateway,
            'dns': dns
        }

    # ----------------------------------------------------------------------------

    def _openConf(self):
        try:
            with open(self._filePath, 'r') as jsonFile:
                self._confObj = load(jsonFile)
        except:
            self._confObj = {}
        if self._confObj.get('STA', None) is None:
            self._confObj['STA'] = {}

    # ----------------------------------------------------------------------------

    def _writeConf(self):
        try:
            jsonStr = dumps(self._confObj)
            try:
                mkdir(self._confPath)
            except:
                pass
            jsonFile = open(self._filePath, 'wb')
            jsonFile.write(jsonStr)
            jsonFile.close()
            return True
        except:
            return False

    # ============================================================================
    # ===( Constructor )==========================================================
    # ============================================================================

    def __init__(self,
                 confName="wifi",
                 confPath="/flash/conf",
                 useExtAntenna=False):
        self._confPath = confPath
        self._filePath = '%s/%s.json' % (confPath, confName)
        self._wlan = WLAN()
        self._antenna = WLAN.EXT_ANT if useExtAntenna else WLAN.INT_ANT
        self._openConf()
        self._setAPInfos()
        self._setConnectionInfos()
        self._wlan.init(antenna=self._antenna)
        self.DisableRadio()

    # ============================================================================
    # ===( Functions )============================================================
    # ============================================================================

    def DisableRadio(self):
        self.CloseAccessPoint()
        self.CloseConnectionToAP()
        self._wlan.deinit()

    # ----------------------------------------------------------------------------

    def GetMACAddr(self):
        return self._mac2Str(self._wlan.mac())

    # ----------------------------------------------------------------------------

    def GetAPInfos(self):
        if not self.IsAccessPointOpened():
            self._setAPInfos()
        return self._apInfos

    # ----------------------------------------------------------------------------

    def GetConnectionInfos(self):
        if not self.IsConnectedToAP():
            self._setConnectionInfos()
        return self._connInfos

    # ----------------------------------------------------------------------------

    def ScanAP(self):
        try:
            if self._wlan.mode() == WLAN.STA:
                self._wlan.init(antenna=self._antenna)
            return self._wlan.scan()
        except:
            return ()

    # ----------------------------------------------------------------------------

    def OpenAccessPoint(self,
                        ssid,
                        key=None,
                        ip='192.168.0.254',
                        autoSave=True):
        if ssid and ip:
            try:
                self._wlan.ifconfig(id=self._ETH_AP,
                                    config=(ip, self._AP_MASK, ip, ip))
                auth = (self._DEFAULT_AUTH_TYPE, key) if key else None
                self._wlan.init(mode=WLAN.STA_AP,
                                ssid=ssid,
                                auth=auth,
                                antenna=self._antenna)
                print("WIFI ACCESS POINT OPENED :")
                print("  - MAC address  : %s" % self.GetMACAddr())
                print("  - Network SSID : %s" % ssid)
                print("  - IP address   : %s" % ip)
                print("  - Mask         : %s" % self._AP_MASK)
                print("  - Gateway IP   : %s" % ip)
                print("  - DNS server   : %s" % ip)
                if autoSave:
                    self._confObj['AP'] = {'ssid': ssid, 'key': key, 'ip': ip}
                    self._writeConf()
                self._setAPInfos(ssid, key, ip, self._AP_MASK, ip, ip)
                return True
            except:
                self.CloseAccessPoint()
        return False

    # ----------------------------------------------------------------------------

    def OpenAccessPointFromConf(self):
        try:
            ssid = self._confObj['AP']['ssid']
            key = self._confObj['AP']['key']
            ip = self._confObj['AP']['ip']
            return self.OpenAccessPoint(ssid, key, ip, False)
        except:
            return False

    # ----------------------------------------------------------------------------

    def RemoveAccessPointFromConf(self):
        try:
            del self._confObj['AP']
            return self._writeConf()
        except:
            return False

    # ----------------------------------------------------------------------------

    def CloseAccessPoint(self):
        try:
            ip = self._IP_NONE
            self._wlan.mode(WLAN.STA)
            self._wlan.ifconfig(id=self._ETH_AP, config=(ip, ip, ip, ip))
            return True
        except:
            return False

    # ----------------------------------------------------------------------------

    def IsAccessPointOpened(self):
        return self._wlan.ifconfig(self._ETH_AP)[0] != self._IP_NONE

    # ----------------------------------------------------------------------------

    def ConnectToAP(self,
                    ssid,
                    key=None,
                    macBssid=None,
                    timeoutSec=None,
                    autoSave=True):
        if ssid:
            if not key:
                key = ''
            if not timeoutSec:
                timeoutSec = self._DEFAULT_TIMEOUT_SEC
            timeout = timeoutSec * 1000
            if self._wlan.mode() == WLAN.STA:
                self._wlan.init(antenna=self._antenna)
            print("TRYING TO CONNECT WIFI TO AP %s..." % ssid)
            for ap in self.ScanAP():
                if ap.ssid == ssid and \
                   ( not macBssid or self._mac2Str(ap.bssid) == macBssid ) :
                    self._wlan.connect(ssid=ap.ssid,
                                       bssid=ap.bssid,
                                       auth=(self._DEFAULT_AUTH_TYPE, key),
                                       timeout=timeout)
                    t = ticks_ms()
                    while ticks_diff(t, ticks_ms()) < timeout:
                        sleep(0.100)
                        if self.IsConnectedToAP():
                            bssid = self._mac2Str(ap.bssid)
                            staCfg = self._wlan.ifconfig(id=self._ETH_STA)
                            ip = staCfg[0]
                            mask = staCfg[1]
                            gateway = staCfg[2]
                            dns = staCfg[3]
                            print("WIFI CONNECTED TO AP :")
                            print("  - MAC address   : %s" % self.GetMACAddr())
                            print("  - Network BSSID : %s" % bssid)
                            print("  - Network SSID  : %s" % ssid)
                            print("  - IP address    : %s" % ip)
                            print("  - Mask          : %s" % mask)
                            print("  - Gateway IP    : %s" % gateway)
                            print("  - DNS server    : %s" % dns)
                            if autoSave:
                                sta = {
                                    'ssid': ssid,
                                    'key': key,
                                }
                                self._confObj['STA'][bssid] = sta
                                self._writeConf()
                            self._setConnectionInfos(bssid, ssid, key, ip,
                                                     mask, gateway, dns)
                            return True
                    self.CloseConnectionToAP()
                    break
            print("FAILED TO CONNECT WIFI TO AP %s" % ssid)
        return False

    # ----------------------------------------------------------------------------

    def ConnectToAPFromConf(self, bssidMustBeSame=False, timeoutSec=None):
        if self._wlan.mode() == WLAN.STA:
            self._wlan.init(antenna=self._antenna)
        for ap in self.ScanAP():
            for bssid in self._confObj['STA']:
                macBssid = self._mac2Str(ap.bssid) if bssidMustBeSame else None
                if self._confObj['STA'][bssid]['ssid'] == ap.ssid and \
                   ( not macBssid or bssid == macBssid ) :
                    if self.ConnectToAP(ap.ssid,
                                        self._confObj['STA'][bssid]['key'],
                                        macBssid, timeoutSec, False):
                        return True
                    break
        return False

    # ----------------------------------------------------------------------------

    def RemoveConnectionToAPFromConf(self, ssid, macBssid=None):
        try:
            changed = False
            for bssid in list(self._confObj['STA']):
                if self._confObj['STA'][bssid]['ssid'] == ssid and \
                   ( not macBssid or bssid == macBssid ) :
                    del self._confObj['STA'][bssid]
                    changed = True
            if changed:
                return self._writeConf()
        except:
            pass
        return False

    # ----------------------------------------------------------------------------

    def CloseConnectionToAP(self):
        try:
            self._wlan.disconnect()
            self._wlan.ifconfig(id=self._ETH_STA, config='dhcp')
            return True
        except:
            return False

    # ----------------------------------------------------------------------------

    def IsConnectedToAP(self):
        return self._wlan.ifconfig(self._ETH_STA)[0] != self._IP_NONE

    # ----------------------------------------------------------------------------

    def ResolveIPFromHostname(self, hostname):
        originalMode = self._wlan.mode()
        if originalMode == WLAN.STA_AP:
            self._wlan.mode(WLAN.STA)
        try:
            ipResolved = getaddrinfo(hostname, 0)[0][-1][0]
        except:
            ipResolved = None
        if originalMode == WLAN.STA_AP:
            self._wlan.mode(WLAN.STA_AP)
        return ipResolved if ipResolved != self._IP_NONE else None

    # ----------------------------------------------------------------------------

    def InternetAccessIsPresent(self):
        return (self.ResolveIPFromHostname('iana.org') is not None)

    # ----------------------------------------------------------------------------

    def WaitForInternetAccess(self, timeoutSec=None):
        if not timeoutSec:
            timeoutSec = self._DEFAULT_TIMEOUT_SEC
        timeout = timeoutSec * 1000
        t = ticks_ms()
        while ticks_diff(t, ticks_ms()) < timeout:
            sleep(0.100)
            if self.InternetAccessIsPresent():
                return True
        return False
Esempio n. 29
0
try:
    sd = SD()
    os.mount(sd, SD_MOUNT_DIR)
    sd_en = True
    rgb.green(0x88)
    rgb.red(0x88)
    time.sleep(1)
    rgb.green_off()
    rgb.red_off()
except OSError:
    sd_en = False  #Make sure SD card access is disabled
print("SD Card enabled: " + str(sd_en))

wlan = WLAN()
wlan.deinit()  #Disable the WiFi access point

#Enable GPS here red on
rgb.red_on()
py = Pytrack()
gps = L76GNSS(py, timeout=config.GPS_TIMEOUT)
fix = False  #Initially we don't have a fix

#Join Lora blue on
lora = LoRa(mode=LoRa.LORAWAN)
dev_addr = struct.unpack(">l", binascii.unhexlify(config.DEV_ADDR))[0]
nwk_swkey = binascii.unhexlify(config.NWS_KEY)
app_swkey = binascii.unhexlify(config.APPS_KEY)
lora.join(activation=LoRa.ABP, auth=(dev_addr, nwk_swkey, app_swkey))
prev_lat = 0
prev_lon = 0
class NanoGateway:
    """
    Nano gateway class, set up by default for use with TTN, but can be configured
    for any other network supporting the Semtech Packet Forwarder.
    Only required configuration is wifi_ssid and wifi_password which are used for
    connecting to the Internet.
    """
    def __init__(self,
                 id,
                 frequency,
                 datarate,
                 ssid,
                 password,
                 server,
                 port,
                 ntp_server='pool.ntp.org',
                 ntp_period=3600):
        self.id = id
        self.server = server
        self.port = port

        self.frequency = frequency
        self.datarate = datarate

        self.ssid = ssid
        self.password = password

        self.ntp_server = ntp_server
        self.ntp_period = ntp_period

        self.server_ip = None

        self.rxnb = 0
        self.rxok = 0
        self.rxfw = 0
        self.dwnb = 0
        self.txnb = 0

        self.sf = self._dr_to_sf(self.datarate)
        self.bw = self._dr_to_bw(self.datarate)

        self.stat_alarm = None
        self.pull_alarm = None
        self.uplink_alarm = None

        self.wlan = None
        self.sock = None
        self.udp_stop = False
        self.udp_lock = _thread.allocate_lock()

        self.lora = None
        self.lora_sock = None

        self.rtc = machine.RTC()

    def start(self):
        """
        Starts the LoRaWAN nano gateway.
        """

        self._log('Starting LoRaWAN nano gateway with id: {}', self.id)

        # setup WiFi as a station and connect
        self.wlan = WLAN(mode=WLAN.STA)
        self._connect_to_wifi()

        # get a time sync
        self._log('Syncing time with {} ...', self.ntp_server)
        self.rtc.ntp_sync(self.ntp_server, update_period=self.ntp_period)
        while not self.rtc.synced():
            utime.sleep_ms(50)
        self._log("RTC NTP sync complete")

        # get the server IP and create an UDP socket
        self.server_ip = usocket.getaddrinfo(self.server, self.port)[0][-1]
        self._log('Opening UDP socket to {} ({}) port {}...', self.server,
                  self.server_ip[0], self.server_ip[1])
        self.sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM,
                                   usocket.IPPROTO_UDP)
        self.sock.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)
        self.sock.setblocking(False)

        # push the first time immediatelly
        self._push_data(self._make_stat_packet())

        # create the alarms
        self.stat_alarm = Timer.Alarm(
            handler=lambda t: self._push_data(self._make_stat_packet()),
            s=60,
            periodic=True)
        self.pull_alarm = Timer.Alarm(handler=lambda u: self._pull_data(),
                                      s=25,
                                      periodic=True)

        # start the UDP receive thread
        self.udp_stop = False
        _thread.start_new_thread(self._udp_thread, ())

        # initialize the LoRa radio in LORA mode
        self._log('Setting up the LoRa radio at {} Mhz using {}',
                  self._freq_to_float(self.frequency), self.datarate)
        self.lora = LoRa(mode=LoRa.LORA,
                         frequency=self.frequency,
                         bandwidth=self.bw,
                         sf=self.sf,
                         preamble=8,
                         coding_rate=LoRa.CODING_4_5,
                         tx_iq=True)

        # create a raw LoRa socket
        self.lora_sock = usocket.socket(usocket.AF_LORA, usocket.SOCK_RAW)
        self.lora_sock.setblocking(False)
        self.lora_tx_done = False

        self.lora.callback(trigger=(LoRa.RX_PACKET_EVENT
                                    | LoRa.TX_PACKET_EVENT),
                           handler=self._lora_cb)
        self._log('LoRaWAN nano gateway online')

    def stop(self):
        """
        Stops the LoRaWAN nano gateway.
        """

        self._log('Stopping...')

        # send the LoRa radio to sleep
        self.lora.callback(trigger=None, handler=None)
        self.lora.power_mode(LoRa.SLEEP)

        # stop the NTP sync
        self.rtc.ntp_sync(None)

        # cancel all the alarms
        self.stat_alarm.cancel()
        self.pull_alarm.cancel()

        # signal the UDP thread to stop
        self.udp_stop = True
        while self.udp_stop:
            utime.sleep_ms(50)

        # disable WLAN
        self.wlan.disconnect()
        self.wlan.deinit()

    def _connect_to_wifi(self):
        self.wlan.connect(self.ssid, auth=(None, self.password))
        while not self.wlan.isconnected():
            utime.sleep_ms(50)
        self._log('WiFi connected to: {}', self.ssid)

    def _dr_to_sf(self, dr):
        sf = dr[2:4]
        if sf[1] not in '0123456789':
            sf = sf[:1]
        return int(sf)

    def _dr_to_bw(self, dr):
        bw = dr[-5:]
        if bw == 'BW125':
            return LoRa.BW_125KHZ
        elif bw == 'BW250':
            return LoRa.BW_250KHZ
        else:
            return LoRa.BW_500KHZ

    def _sf_bw_to_dr(self, sf, bw):
        dr = 'SF' + str(sf)
        if bw == LoRa.BW_125KHZ:
            return dr + 'BW125'
        elif bw == LoRa.BW_250KHZ:
            return dr + 'BW250'
        else:
            return dr + 'BW500'

    def _lora_cb(self, lora):
        """
        LoRa radio events callback handler.
        """

        events = lora.events()
        if events & LoRa.RX_PACKET_EVENT:
            self.rxnb += 1
            self.rxok += 1
            rx_data = self.lora_sock.recv(256)
            stats = lora.stats()
            packet = self._make_node_packet(rx_data, self.rtc.now(),
                                            stats.rx_timestamp, stats.sfrx,
                                            self.bw, stats.rssi, stats.snr)
            self._push_data(packet)
            self._log('Received packet: {}', packet)
            self.rxfw += 1
        if events & LoRa.TX_PACKET_EVENT:
            self.txnb += 1
            lora.init(mode=LoRa.LORA,
                      frequency=self.frequency,
                      bandwidth=self.bw,
                      sf=self.sf,
                      preamble=8,
                      coding_rate=LoRa.CODING_4_5,
                      tx_iq=True)

    def _freq_to_float(self, frequency):
        """
        MicroPython has some inprecision when doing large float division.
        To counter this, this method first does integer division until we
        reach the decimal breaking point. This doesn't completely elimate
        the issue in all cases, but it does help for a number of commonly
        used frequencies.
        """

        divider = 6
        while divider > 0 and frequency % 10 == 0:
            frequency = frequency // 10
            divider -= 1
        if divider > 0:
            frequency = frequency / (10**divider)
        return frequency

    def _make_stat_packet(self):
        now = self.rtc.now()
        STAT_PK["stat"]["time"] = "%d-%02d-%02d %02d:%02d:%02d GMT" % (
            now[0], now[1], now[2], now[3], now[4], now[5])
        STAT_PK["stat"]["rxnb"] = self.rxnb
        STAT_PK["stat"]["rxok"] = self.rxok
        STAT_PK["stat"]["rxfw"] = self.rxfw
        STAT_PK["stat"]["dwnb"] = self.dwnb
        STAT_PK["stat"]["txnb"] = self.txnb
        return ujson.dumps(STAT_PK)

    def _make_node_packet(self, rx_data, rx_time, tmst, sf, bw, rssi, snr):
        RX_PK["rxpk"][0]["time"] = "%d-%02d-%02dT%02d:%02d:%02d.%dZ" % (
            rx_time[0], rx_time[1], rx_time[2], rx_time[3], rx_time[4],
            rx_time[5], rx_time[6])
        RX_PK["rxpk"][0]["tmst"] = tmst
        RX_PK["rxpk"][0]["freq"] = self._freq_to_float(self.frequency)
        RX_PK["rxpk"][0]["datr"] = self._sf_bw_to_dr(sf, bw)
        RX_PK["rxpk"][0]["rssi"] = rssi
        RX_PK["rxpk"][0]["lsnr"] = snr
        RX_PK["rxpk"][0]["data"] = ubinascii.b2a_base64(rx_data)[:-1]
        RX_PK["rxpk"][0]["size"] = len(rx_data)
        return ujson.dumps(RX_PK)

    def _push_data(self, data):
        token = uos.urandom(2)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes(
            [PUSH_DATA]) + ubinascii.unhexlify(self.id) + data
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception as ex:
                self._log('Failed to push uplink packet to server: {}', ex)

    def _pull_data(self):
        token = uos.urandom(2)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes(
            [PULL_DATA]) + ubinascii.unhexlify(self.id)
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception as ex:
                self._log('Failed to pull downlink packets from server: {}',
                          ex)

    def _ack_pull_rsp(self, token, error):
        TX_ACK_PK["txpk_ack"]["error"] = error
        resp = ujson.dumps(TX_ACK_PK)
        packet = bytes([PROTOCOL_VERSION]) + token + bytes(
            [PULL_ACK]) + ubinascii.unhexlify(self.id) + resp
        with self.udp_lock:
            try:
                self.sock.sendto(packet, self.server_ip)
            except Exception as ex:
                self._log('PULL RSP ACK exception: {}', ex)

    def _send_down_link(self, data, tmst, datarate, frequency):
        """
        Transmits a downlink message over LoRa.
        """

        self.lora.init(mode=LoRa.LORA,
                       frequency=frequency,
                       bandwidth=self._dr_to_bw(datarate),
                       sf=self._dr_to_sf(datarate),
                       preamble=8,
                       coding_rate=LoRa.CODING_4_5,
                       tx_iq=True)
        while utime.ticks_cpu() < tmst:
            pass
        self.lora_sock.send(data)
        self._log(
            'Sent downlink packet scheduled on {:.3f}, at {:.3f} Mhz using {}: {}',
            tmst / 1000000, self._freq_to_float(frequency), datarate, data)

    def _udp_thread(self):
        """
        UDP thread, reads data from the server and handles it.
        """

        while not self.udp_stop:
            try:
                data, src = self.sock.recvfrom(1024)
                _token = data[1:3]
                _type = data[3]
                if _type == PUSH_ACK:
                    self._log("Push ack")
                elif _type == PULL_ACK:
                    self._log("Pull ack")
                elif _type == PULL_RESP:
                    self.dwnb += 1
                    ack_error = TX_ERR_NONE
                    tx_pk = ujson.loads(data[4:])
                    tmst = tx_pk["txpk"]["tmst"]
                    t_us = tmst - utime.ticks_cpu() - 15000
                    if t_us < 0:
                        t_us += 0xFFFFFFFF
                    if t_us < 20000000:
                        self.uplink_alarm = Timer.Alarm(
                            handler=lambda x: self._send_down_link(
                                ubinascii.a2b_base64(tx_pk["txpk"]["data"]),
                                tx_pk["txpk"]["tmst"] - 50, tx_pk["txpk"][
                                    "datr"],
                                int(tx_pk["txpk"]["freq"] * 1000) * 1000),
                            us=t_us)
                    else:
                        ack_error = TX_ERR_TOO_LATE
                        self._log('Downlink timestamp error!, t_us: {}', t_us)
                    self._ack_pull_rsp(_token, ack_error)
                    self._log("Pull rsp")
            except usocket.timeout:
                pass
            except OSError as ex:
                if ex.errno != errno.EAGAIN:
                    self._log('UDP recv OSError Exception: {}', ex)
            except Exception as ex:
                self._log('UDP recv Exception: {}', ex)

            # wait before trying to receive again
            utime.sleep_ms(UDP_THREAD_CYCLE_MS)

        # we are to close the socket
        self.sock.close()
        self.udp_stop = False
        self._log('UDP thread stopped')

    def _log(self, message, *args):
        """
        Outputs a log message to stdout.
        """

        print('[{:>10.3f}] {}'.format(utime.ticks_ms() / 1000,
                                      str(message).format(*args)))
class WLANAgent:
    def __init__(self, ap_config=None, sta_config=None):
        self._wlan = None
        self.networks = {}
        self._sta_config = sta_config
        self._ap_config = ap_config

    def isActive(self):
        return self._wlan != None

    def isconnected(self):
        return self._wlan.isconnected()

    def activate_ap_mode(self, ssid, password):
        if self._ap_config is None:
            log('No config to init AP Mode.')
            return

        try:
            ssid = self._ap_config['ssid']
            password = self._ap_config['password']
        except KeyError:
            log('Need password or ssid to init Access Point.')
            return

        if self._wlan:
            self._wlan.deinit()
            self._wlan = None

        self._wlan = WLAN(
            mode=WLAN.AP,
            ssid=ssid,
            auth=(WLAN.WPA2, password),
        )

    def stop(self):
        if self._wlan:
            self._wlan.deinit()
            self._wlan = None

    def activate_sta_mode(self):
        if self._sta_config is None:
            log('No config to init STA Mode.')
            return

        try:
            self.networks = self._sta_config['wifi_networks']
        except KeyError:
            log('Need networks to init Wifi Station.')
            return

        if self._wlan:
            self._wlan.deinit()
            self._wlan = None

        self._wlan = WLAN(mode=WLAN.STA, )
        self._wlan.ifconfig(config="dhcp")
        self.connect_to_ap()

    def connect_to_ap(self):
        while not self._wlan.isconnected():
            try:
                if len(list(self.networks.items())) < 1:
                    raise Exception("Need networks to connect to Wifi AP.")

                if self._wlan.mode() != WLAN.STA:
                    raise Exception(
                        "Must in Station Mode to connect to Wifi AP.")

                existing_networks = self._wlan.scan()
                log('Found {}. existing wlan networks.'.format(
                    len(existing_networks)))

                for network in existing_networks:
                    (ssid, bssid, sec, channel, rssi) = network
                    log('Try to connect to network {}'.format(ssid))
                    if ssid in self.networks:
                        password = self.networks[ssid]
                        self._wlan.connect(ssid, auth=(WLAN.WPA2, password))
                        break

                now = time()
                while not self._wlan.isconnected():
                    if (time() - now > 10):
                        raise Exception(
                            'Failed to connect to wlan {}. Timeout.'.format(
                                self._wlan.ssid()))
                    sleep(0.3)

                (ip, subnet_mask, gateway, DNS_server) = self._wlan.ifconfig()
                log('Connect to wifi {}'.format(self._wlan.ssid()))
                log('IP: {}'.format(ip))
            except Exception as err:
                log(err)
                sleep(2)