Welcome to the Pycom forum Forum
    • \[\[global:header.categories\]\]
    • \[\[global:header.recent\]\]
    • \[\[global:header.tags\]\]
    • \[\[global:header.popular\]\]
    • Login

    Explore Pybytes | Official Documentation | Report a Firmware Bug/Issue | GitHub

    LoPy1 to LoPy1 Nanogateway

    Scheduled Pinned Locked Moved LoPy
    lopylorawanttnttn lopyhelp
    3 Posts 2 Posters 881 Views 2 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • B Offline
      Burgeh
      last edited by Burgeh

      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
      

      0_1527871865761_3e362b46-f630-44fa-a199-c7b1aa1edfcc-image.png
      0_1527872633256_b34235f9-d409-4d9b-862d-084d3852d63b-image.png

      CONFIG.PY

      
      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()
      

      nanogateway.py

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

      0_1527872476272_b93a6844-3774-4b61-9535-35fed3028c9b-image.png

      0_1527872706415_8c706136-c99e-4cb7-bbc3-fd2ffd5ce2be-image.png

      1 Reply Last reply Reply Quote 0
      • B Offline
        Burgeh
        last edited by

        Anyone with a LoPy to LoPy gateway got any suggestions?

        1 Reply Last reply Reply Quote 1
        • G Offline
          gcapilot
          last edited by

          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.

          1 Reply Last reply Reply Quote 0

          Hello! It looks like you're interested in this conversation, but you don't have an account yet.

          Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

          With your input, this post could be even better 💗

          Register Login
          • 1 / 1
          • First post
            Last post

          Pycom on Twitter

          Pycom