Join request from lopy4 to a chirpstack server

Hi guys,
i’m newbie in LoRaWAN network configuration.
I need to transmit data from a lopy4 board to a backend through the use of a second lopy4 board acting as a nanogateway.

The chirpstack LoRaWAN network stack has been implemented on a dedicated server.

The nanogateway is configured and visible on the chirpstack server grapgical interface, i created a profile for the device associated to the second board, but it transmits continuous join requests without ever being accepted. As authentication method I tried both ABP and OTAA, I also tried to reverse the order of credentials (MSB to LSB) but on the board I always read “Not yet joined” and on the nanogatewy I read the following log :
“[ 11644.731] Received packet: {“rxpk”: [{“data”: “AAAAAAAAAA/iKYkNnVs3DqV+8M2mA=”, “time”: “2022-05-03T12:38:26. 263245Z”, “chan”: 0, “tmst”: 196355297, “stat”: 1, “modu”: “LORA”, “lsnr”: 6.0, “rssi”: -28, “rfch”: 0, “codr”: “4/5”, “freq”: 868.1, “datr”: “SF7BW125”, “size”: 23}]}”

On the second board I’m using the following codes:

config.py

#configuration
from network import LoRa

#application-level device identifier
DEVICE_ID = '4'

# authentication credentials (specific for each device, generated by LoRa backend)
# LORA_CRED_ABP = ('01e1a5f3', 'f71c36d3499d4962f8966b4d7abe271f', '7f9d74a6966264fbf6e7d13ec0e70509')
#LORA_CRED_ABP = ('00750889', 'f56e40b52f0162a9b9a68fc131a07a34', '5d015e33eaf54140ca1b69f238da1fdd')
# initialized (hard-coded) sampling/sending interval (will be modified and adapted by the synchronization mechanism)
INTERVAL_PRE = 120000 # (milliseconds)

# led activation (for testing only)
LED_ON = True

### It is suggested to not modify the following configurations

# authentication method
# ABP (Authentication By Personalization) suggested.
# ABP = False  enables OTA (Over The Air authentication)
ABP = False
# For OTA specific OTA-credentials must be used
LORA_CRED_OTAA = ubinascii.unhexlify('70b3d5d9909822fe','0000000000000000', '6ac1f02766915f0cc4ad8e2d04042303')


# LoRa payload format
# (2 Bytes for device ID, 8 float numbers: temp humid ph1 ph2 ph3 volt1 volt2 volt3)
LORA_PKG_FORMAT = "!Hffffffff"

# waiting time (for receive windows to expire)
WAITING_SLEEP = 8 #seconds

# LoRa PHY settings
LORA_FREQUENCY = 868100000
LORA_NODE_DR = 0 # LoRa datarate DR=0 sets SF = 12, DR=5 sets SF = 7 (DR=0 suggested for maximum range)
LORA_DEVICE_CLASS = LoRa.CLASS_C
LORA_REGION = LoRa.EU868 # LORAWAN region (Europe)
# alternatives:
# Asia = LoRa.AS923
# Australia = LoRa.AU915
# United States = LoRa.US915

loranet.py

#!/usr/bin/env python

import config
from network import LoRa
import socket
import binascii
import struct
import time
import _thread
import pycom
import time

class LoraNet:

    def __init__(self, sleep_time, check_rx, frequency, dr, region, activation, device_class=LoRa.CLASS_C, auth = None):
        self.sleep_time = sleep_time
        self.check_rx = check_rx
        self.frequency = frequency
        self.dr = dr
        self.region = region
        self.device_class = device_class
        self.activation = activation
        self.auth = auth
        self.sock = None
        self._exit = False
        self.s_lock = _thread.allocate_lock()
        self.lora = LoRa(mode=LoRa.LORAWAN, region = self.region, device_class = self.device_class)
        self._process_ota_msg = None

    def stop(self):
        self._exit = True

    def init(self, process_msg_callback):
        self._process_ota_msg = process_msg_callback

    def read_sleep_time(self):
        return self.sleep_time

    def read_check_rx(self):
        return self.check_rx

    def receive_callback(self, lora):
        #callback on reception ack-sync
        events = lora.events()
        if events & LoRa.RX_PACKET_EVENT:
            rx, port = self.sock.recvfrom(256)
            #decode received meassage and extract sleep time
            rx_data = rx.decode()
            rx_sleep_time = int(rx_data.split(',')[1].split('.')[0])
            print('Ack/sync received. Decoded received sleep time', rx_sleep_time)
            self.sleep_time = rx_sleep_time
            self.check_rx = True
            if config.LED_ON:
                #toggle blue led, message received
                pycom.rgbled(0x0000FF) #blue
                print('Received, blue led on')

    def connect(self):
        if self.activation != LoRa.OTAA and self.activation != LoRa.ABP:
            raise ValueError("Invalid Lora activation method")
        if len(self.auth) < 3:
            raise ValueError("Invalid authentication parameters")

        self.lora.callback(trigger=LoRa.RX_PACKET_EVENT, handler=self.receive_callback)

        if self.activation == LoRa.OTAA:
            # set the 3 default channels to the same frequency
            self.lora.add_channel(0, frequency=self.frequency, dr_min=0, dr_max=5)
            self.lora.add_channel(1, frequency=self.frequency, dr_min=0, dr_max=5)
            self.lora.add_channel(2, frequency=self.frequency, dr_min=0, dr_max=5)
        else:
            # set the 3 default channels as per LoRaWAN specification
            self.lora.add_channel(0, frequency=868100000, dr_min=0, dr_max=5)
            self.lora.add_channel(1, frequency=868300000, dr_min=0, dr_max=5)
            self.lora.add_channel(2, frequency=868500000, dr_min=0, dr_max=5)

        # remove all the non-default channels
        for i in range(3, 16):
            self.lora.remove_channel(i)

        # authenticate with abp or ota
        if self.activation == LoRa.OTAA:
            self._authenticate_otaa(self.auth)
        else:
            self._authenticate_abp(self.auth)

        # create socket to server
        self._create_socket()


    def has_joined(self):
        return self.lora.has_joined()

main.py

from network import LoRa
import socket
import time
import ubinascii

# Initialise 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.EU868)

# create an OTAA authentication parameters, change them to the provided credentials
app_eui = ubinascii.unhexlify('0000000000000000')
app_key = ubinascii.unhexlify('6ac1f02766915f0cc4ad8e2d04042303')
#uncomment to use LoRaWAN application provided dev_eui
dev_eui = ubinascii.unhexlify('70b3d5d9909822fe')

# Uncomment for US915 / AU915 & Pygate
# for i in range(0,8):
#     lora.remove_channel(i)
# for i in range(16,65):
#     lora.remove_channel(i)
# for i in range(66,72):
#     lora.remove_channel(i)

# join a network using OTAA (Over the Air Activation)
#uncomment below to use LoRaWAN application provided dev_eui
lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), timeout=0)
#lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
    time.sleep(2.5)
    print('Not yet joined...')

print('Joined')
# 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, 5)

# make the socket blocking
# (waits for the data to be sent and for the 2 receive windows to expire)
s.setblocking(True)

# send some data
s.send(bytes([0x01, 0x02, 0x03]))

# make the socket non-blocking
# (because if there's no data received it will block forever...)
s.setblocking(False)

# get any data received (if any...)
data = s.recv(64)
print(data)

otaa_node.py

#!/usr/bin/env python
#
# Copyright (c) 2019, Pycom Limited.
#
# This software is licensed under the GNU GPL version 3 or any
# later version, with permitted additional terms. For more information
# see the Pycom Licence v1.0 document supplied with this file, or
# available at https://www.pycom.io/opensource/licensing
#

""" OTAA Node example compatible with the LoPy Nano Gateway """

from network import LoRa
import socket
import binascii
import struct
import time
import config

# 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.EU868)

# create an OTA authentication params
dev_eui = binascii.unhexlify('70b3d5d9909822fe')
app_eui = binascii.unhexlify('0000000000000000')
app_key = binascii.unhexlify('6ac1f02766915f0cc4ad8e2d04042303')

# set the 3 default channels to the same frequency (must be before sending the OTAA join request)
lora.add_channel(0, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=5)
lora.add_channel(1, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=5)
lora.add_channel(2, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=5)

# join a network using OTAA
lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), timeout=0, dr=config.LORA_NODE_DR)

# wait until the module has joined the network
while not lora.has_joined():
    time.sleep(2.5)
    print('Not joined yet...')

# remove all the non-default channels
for i in range(3, 16):
    lora.remove_channel(i)

# 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 non-blocking
s.setblocking(False)

time.sleep(5.0)

for i in range (200):
    pkt = b'PKT #' + bytes([i])
    print('Sending:', pkt)
    s.send(pkt)
    time.sleep(4)
    rx, port = s.recvfrom(256)
    if rx:
        print('Received: {}, on port: {}'.format(rx, port))
    time.sleep(6)

I attach an image of the join frame

Can someone help me ?

We are having the exact same issue with a FiPy and ChirpStack server with EU868 configuration.

Pycom’s version

>>> os.uname()
(sysname='FiPy', nodename='FiPy', release='1.20.2.r6', version='v1.11-c5a0a97 on 2021-10-28', machine='FiPy with ESP32', lorawan='1.0.2', sigfox='1.0.1', pybytes='1.7.1')

What’s wrong?

The JoinRequest and JoinAccept are displayed in the device LoRaWAN Frames log section in ChirpStack, so the issue does not reside in the LoRa server not authenticating the device properly. The most likely scenario is the JoinAccept not being properly sent to the device, due to an unexpected frequency, DR, bandwidth, etc. by the device.

We have not had any related problems with other devices (e.g. CubeCell HTCC-AB01 @ EU868) therefor there must be some sort of misconfiguration/issue with the device’s code. Also, we have tried different LoRaWAN code samples by the Pycom community, all seemingly working with EU868 out of the box, with no luck.

Our next steps

  • Track the LoRa traffic from a gateway to further debug the issue.
  • Is there a way to debug this sort of behavior on the end device?

If you have any idea on how this issue may be resolved, please respond.

Thank you.

try defining ABP or OTAA (whichever is the opposite to what you have configured now)

Our problem was caused by impossibilities of the nano gateway to transmit on 6 channels as LoRaWAN stack expects it to be by default. So you have to configure only one frequency that will work for you for both Gateway and end device and in the code defining connection (loranet.py file in our case) for Device comment out all the rest of the frequencies defined. BTW, we used the ABP join option. Hope this help!

On ABP mode the FiPy connects and sends data without any issues, but does not work with downlinks.
In order to join the network through OTAA the server must send the end device a JoinAccept, in the same way any downlink message on ABP would be sent, which fails.
Therefore, ABP and OTAA are irrelevant in this scenario.

Would it be possible for you to provide the code with which you’ve managed to send & receive LoRaWAN frames? Or the example code your code may be based on?

Also, would you mind specifying the region & frequency as well as the uplink/downlink ports used?

Thank you

In config.py for the device configuration we have this settings:

LORA_FREQUENCY = 868100000
LORA_NODE_DR = 5
LORA_DEVICE_CLASS = LoRa.CLASS_C
LORA_REGION = LoRa.EU868 # LORAWAN region (Europe)

in the loranet.py the following code:

 else:
            # set the 3 default channels as per LoRaWAN specification
            self.lora.add_channel(0, frequency=868100000, dr_min=0, dr_max=5)
            #self.lora.add_channel(1, frequency=868300000, dr_min=0, dr_max=5)
            #self.lora.add_channel(2, frequency=868500000, dr_min=0, dr_max=5)

in main.py

if config.ABP:
    LORA_ACTIVATION = LoRa.ABP
    print('ABP mode')
    LORA_CRED = config.LORA_CRED_ABP
else:
    LORA_ACTIVATION = LoRa.OTAA
    print('OTA mode')
    LORA_CRED = config.LORA_CRED_OTA

# setting up and connecting to LoRa network
lora = LoraNet(config.INTERVAL_PRE, False, config.LORA_FREQUENCY, config.LORA_NODE_DR, config.LORA_REGION, LORA_ACTIVATION, config.LORA_DEVICE_CLASS, LORA_CRED)
lora.connect()

the credentials are generated by LoRaWAN backend using MAC retrieved from the device using this code:

import ubinascii
from network import LoRa

lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)
lora_mac = ubinascii.hexlify(lora.mac()).decode('ascii')
print('Device eui (LORA MAC): ', lora_mac)


import machine
import ubinascii

WIFI_MAC = ubinascii.hexlify(machine.unique_id()).upper()
# Set  the Gateway ID to be the first 3 bytes of MAC address + 'FFFE' + last 3 bytes of MAC address
DEVICE_ID = WIFI_MAC[:6] + "FFFE" + WIFI_MAC[6:12]
print('Device eui (LORA MAC): ', DEVICE_ID)


from network import WLAN
import binascii
wl = WLAN()
print("Gateway EUI: {}".format(binascii.hexlify(wl.mac())[:6] + 'fffe' + binascii.hexlify(wl.mac())[6:]))

For nanoagteway in config.py we also specify explicitly:

for EU868

LORA_FREQUENCY = 868100000;