LoPy1 to LoPy1 Nanogateway
-
Hi everyone,
I have been trying for quite sometime to get a LoPy1 to LoPy1 nanogateway setup. I've checked throughout the forums many times and I cannot seem to find what I am doing incorrectly I cannot get OTAA or ABP to show up. Please any suggestions would be greatly appreciated. My firmware for both is at the latest 1.17.3. I use one board attached to a pyTrack and powered on, and the other I power on through a breadboard with 5V, 3.3V and GND. Both have good working antennas on. (Also when using the OTAA for US915 there is a join data rate issue, none of the values are within range).
Below is my code for my LoPy1 Node for ABP_US915 which appears to run fine it says it sends a packet but only once even though it is supposed to be looping. It has never shown up on my TTN applications. With that code I also include my config.py in the libraries folder in the flash.
main.py of LoPy NODE
from network import LoRa import socket import binascii import struct import time import config import pycom import math # initialize LoRa in LORAWAN mode. # Please pick the region that matches where you are using the device: # Asia = LoRa.AS923 # Australia = LoRa.AU915 # Europe = LoRa.EU868 # United States = LoRa.US915 lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.US915) pycom.heartbeat(False) i = 97 # create an ABP authentication params dev_addr = struct.unpack(">l", binascii.unhexlify('26021B6A'.replace(' ','')))[0] nwk_swkey = binascii.unhexlify('6A9EC55DD96E7559C309725BA03073F1'.replace(' ','')) app_swkey = binascii.unhexlify('F0F2462EE9D24C97A047537E733DA1A4'.replace(' ','')) # remove all the channels for channel in range(0, 72): lora.remove_channel(channel) # set all channels to the same frequency (must be before sending the OTAA join request) for channel in range(0, 72): lora.add_channel(channel, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=3) pycom.rgbled(0x7f7f00) # yellow time.sleep(2.5) # join a network using ABP (Activation By Personalization) lora.join(activation=LoRa.ABP, auth=(dev_addr, nwk_swkey, app_swkey)) join = lora.has_joined() print("Join value %s" % join) while True: pycom.rgbled(0x7f7f00) # yellow time.sleep(2.5) if not lora.has_joined(): print('Not joined yet...') join_wait += 1 if join_wait == 5: lora.join(activation=LoRa.ABP, auth=(dev_addr, nwk_swkey, app_swkey)) join_wait = 0 else: break # create a LoRa socket s = socket.socket(socket.AF_LORA, socket.SOCK_RAW) # set the LoRaWAN data rate s.setsockopt(socket.SOL_LORA, socket.SO_DR, config.LORA_NODE_DR) # make the socket blocking s.setblocking(False) pycom.rgbled(0x007f00) # green while True: pkt = b'PKT #' + bytes([i]) print('Sending:', pkt) s.send(pkt) pycom.rgbled(0x0000ff) # blue time.sleep(5) i = i+1
import machine import ubinascii WIFI_MAC = ubinascii.hexlify(machine.unique_id()).lower() # Set the Gateway ID to be the first 3 bytes of MAC address + 'FFFE' + last 3 bytes of MAC address #GATEWAY_ID = WIFI_MAC[:6] + "ffee" + WIFI_MAC[6:12] GATEWAY_ID = "aabbcc1711004411" print("GID %s" % GATEWAY_ID) SERVER = 'router.us.thethings.network' PORT = 1700 NTP = "pool.ntp.org" NTP_PERIOD_S = 3600 WIFI_SSID = 'Removed' WIFI_PASS = 'Removed this' # for EU868 #LORA_FREQUENCY = 868100000 #LORA_GW_DR = "SF7BW125" # DR_5 #LORA_NODE_DR = 5 # for US915 LORA_FREQUENCY = 903900000 LORA_GW_DR = "SF7BW125" # DR_1 LORA_NODE_DR = 3 print("Setup Complete")
Next is my gateway lopy. I get push and pull ack as well as it appears on TTN but I never got any traffic or join requests, even though my node passes the join code. Note I am use robert-hh's code for the nanogateway due to a timing issue he informed me of, but changed to US915. It uses that same config file in the library
main.py of LoPy nanogateway
""" LoPy LoRaWAN Nano Gateway example usage """ import config from nanogateway import NanoGateway if __name__ == '__main__': nanogw = NanoGateway( id=config.GATEWAY_ID, frequency=config.LORA_FREQUENCY, datarate=config.LORA_GW_DR, ssid=config.WIFI_SSID, password=config.WIFI_PASS, server=config.SERVER, port=config.PORT, ntp_server=config.NTP, ntp_period=config.NTP_PERIOD_S ) nanogw.start() nanogw._log('You may now press ENTER to enter the REPL') input()
""" LoPy LoRaWAN Nano Gateway. Can be used for both EU868 and US915. """ import errno import machine import ubinascii import ujson import uos import usocket import utime import _thread import gc from micropython import const from network import LoRa from network import WLAN from machine import Timer from machine import WDT 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) WDT_TIMEOUT = const(120000) 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': '' } } 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, region=LoRa.US915, 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) if uos.uname()[0] == "LoPy": self.window_compensation = -1000 else: self.window_compensation = -6000 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, region=LoRa.US915, 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, region=LoRa.US915, 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_diff(utime.ticks_cpu(), tmst) > 0: pass self.lora_sock.settimeout(1) self.lora_sock.send(data) self.lora_sock.setblocking(False) self._log( 'Sent downlink packet scheduled on {:.3f}, at {:,d} Hz using {}: {}', tmst / 1000000, frequency, datarate, data ) def _udp_thread(self): """ UDP thread, reads data from the server and handles it. """ while not self.udp_stop: gc.collect() 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:]) 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)) if 1000 < t_us < 10000000: self.uplink_alarm = Timer.Alarm( handler=lambda x: self._send_down_link( payload, tmst, tx_pk["txpk"]["datr"], int(tx_pk["txpk"]["freq"] * 1000 + 0.0005) * 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) ))
-
I'm having a similar issue with mine - US, 915 mhz, on the dr=None allows the function to run but it never really joins.
-
Anyone with a LoPy to LoPy gateway got any suggestions?