Communication between end device to nano gateway

Hi guys,
i’m newbie in LoRaWAN network configuration.
I need to get an end device to communicate with a nanogateway, both are lopy4 boards.
The chirpstack LoRaWAN network stack has been implemented on a dedicated server.
On the Chirpstack server I have registered both the end device and the nanogateway. The nanogateway is correctly registered and online but the end device is not online.
If I use the abp authentication method for the end device and try to send packets, on the nanogateway and chirpsack server I don’t see received packet logs or lorawan frames.
If I use the otaa authentication method for the end device, I see join requests (lorawam frames) from the end device in loop but they are never accepted.
With both authentication methods I tried to change the order of the credentials (from MSB to LSB), but without success.
Can you help me understand if the problem is the code on the boards (end device and nanogateway) or the configuration of the Chirpstack server ?

These are the codes used for the nanogateway:

  • config.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
#

""" LoPy LoRaWAN Nano Gateway configuration options """

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
GATEWAY_ID = WIFI_MAC[:6] + "FFFE" + WIFI_MAC[6:12]

SERVER = '******'
PORT = 1700

NTP = "pool.ntp.org"
NTP_PERIOD_S = 3600

WIFI_SSID = '*****'
WIFI_PASS = '******'

# for EU868
LORA_FREQUENCY = 868100000
LORA_GW_DR = "SF7BW125" # DR_5
LORA_NODE_DR = 5

# for US915
# LORA_FREQUENCY = 903900000
# LORA_GW_DR = "SF10BW125" # DR_0
# LORA_NODE_DR = 0

  • main
""" LoPy LoRaWAN Nano Gateway example usage """
# Acknowledgement:
# Thanks to robert-hh for providing us with an updated, more stable example for the nanogateway 

# disable heartbeat
from pycom import heartbeat
heartbeat(False)
print ("Heartbeat off")


import utime
from machine import RTC
rtc = RTC()
rtc.ntp_sync("pool.ntp.org")
utime.sleep_ms(750)
t = rtc.now()
with open("bootlog.txt", "a") as f:
   f.write(repr(utime.time()) + " " + repr(t) + "\n")

def reload(mod):
    import sys
    mod_name = mod.__name__
    del sys.modules[mod_name]
    return __import__(mod_name)

from machine import reset

""" LoPy LoRaWAN Nano Gateway example usage """

import config
from nanogateway import NanoGateway

if True: #__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
""" 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(120)


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

        self.watchdog = WDT(timeout=10000)

    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()
        self.watchdog.feed()
        # 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.watchdog.feed()
        self._log("RTC NTP sync complete")
        self.watchdog.feed()
        # 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 watchdog
        self.watchdog.feed()
        utime.sleep(1)
        self._log("Watchdog started")

        # start the UDP receive thread
        self.udp_stop = False
        _thread.start_new_thread(self._udp_thread, ())
        self.watchdog.feed()
        # 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.EU868,
            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.watchdog.feed()
        self.lora.callback(trigger=(LoRa.RX_PACKET_EVENT | LoRa.TX_PACKET_EVENT), handler=self._lora_cb)
        self.watchdog.feed()
        if uos.uname()[0] == "LoPy":
            self.window_compensation = -1000
        else:
            self.window_compensation = -6000
        self.watchdog.feed()
        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.watchdog.feed()
        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.EU868,
                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.EU868,
            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:
                    if -10000000 < t_us < 10000000000:    
                        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.args[0] != errno.EAGAIN:
                    self._log('UDP recv OSError Exception: {}', ex)
            except Exception as ex:
                self._log('UDP recv Exception: {}', ex)

            self.watchdog.feed()
            # self._log("Feeding the dog")

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

Instead for the final device the codes are these:

  • config
#configuration
from network import LoRa

#application-level device identifier
DEVICE_ID = 1

# authentication credentials (specific for each device, generated by LoRa backend)
LORA_CRED_ABP = ('01afa676', '68ad4544d96ac404337ccaf70233283a', '27a77837058db6f75b326ba3733c05f0')

# 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 = True
# For OTA specific OTA-credentials must be used
#LORA_CRED_OTA = ('fe229890d9d5b370', '0000000000000000', '6f604d539b2dc058e7d5513e9116d3ec')

# 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
#!/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 _authenticate_otaa(self, auth_params):
        # create an OTAA authentication params
        self.dev_eui = binascii.unhexlify(auth_params[0])
        self.app_eui = binascii.unhexlify(auth_params[1])
        self.app_key = binascii.unhexlify(auth_params[2])

        self.lora.join(activation=LoRa.OTAA, auth=(self.dev_eui, self.app_eui, self.app_key), timeout=0, dr=self.dr)

        while not self.lora.has_joined():
            time.sleep(2.5)
            print('Not joined yet...')

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

    def _authenticate_abp(self, auth_params):
        # create an ABP authentication params
        self.dev_addr = struct.unpack(">l", binascii.unhexlify(auth_params[0]))[0]
        self.nwk_swkey = binascii.unhexlify(auth_params[1])
        self.app_swkey = binascii.unhexlify(auth_params[2])

        self.lora.join(activation=LoRa.ABP, auth=(self.dev_addr, self.nwk_swkey, self.app_swkey))

    def _create_socket(self):
        # create a LoRa socket
        self.sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
        # set the LoRaWAN data rate
        self.sock.setsockopt(socket.SOL_LORA, socket.SO_DR, self.dr)
        # make the socket non blocking
        self.sock.setblocking(False)

    def send(self, packet):
        with self.s_lock:
            #self.lora.nvram_restore()
            self.sock.send(packet)
            #self.lora.nvram_save()
            print('Sent data', packet)
            if config.LED_ON:
                # green led toggle on message sent
                pycom.rgbled(0x00FF00) #green
                print('Sent data, green led on')

  • main
import config
from network import LoRa
from loranet import LoraNet
import socket
import time
import ubinascii
import struct
import machine
import pycom
#sensor drivers
#import sys
#sys.path.append('/flash/TH_Sensor')
#sys.path.append('/flash/PH_Sensor')
#TH sensors
#from HTS221 import HTS221
#from machine import I2C
#PH sensors
#from pH_driver import PH_DRIVER
##import urequests
#from motor_driver import MOTOR_DRIVER

if config.LED_ON:
    #wake-up toggle, red led
    pycom.heartbeat(False)
    print('Wake-up, red led on')
    pycom.rgbled(0xFF0000) #red

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

# get sensor measurements
# read temperature humidity
#i2c = I2C(0, I2C.MASTER, baudrate=400000)
#t_h_sensor= HTS221(i2c)
#t_h_sensor.powerUp()
#t_h_sensor.calibrate()

#humidity = t_h_sensor.getHumidity()
humidity = 80.6
#temperature = t_h_sensor.getTemperature()
#print ('Humidity:{0:2f}'.format(humidity))
#print('Temperature:{0:.2f}'.format(temperature))
# formatting for LoRa packet
humid_read = float('{0:2f}'.format(humidity))
#temp_read = float('{0:.2f}'.format(temperature))

# #hard-coded sensor readings (for testing without sensors) humidity = 3.4444
temperature = 20.5
humid_read = float('{0:2f}'.format(humidity))
temp_read = float('{0:.2f}'.format(temperature))

# read PH
#motor = MOTOR_DRIVER()
##sens = PH_DRIVER(coeff1 = None, coeff2 = None, coeff3 = None, DCycle = None)
##values = []
#motor.off()

#values = sens.getpH()
#ph1 = values[0]
#ph2 = values[1]
#ph3 = values[2]

# ensure right format for LoRa packet
#ph1_read = float(ph1)
#ph2_read = float(ph2)
#ph3_read = float(ph3)

# hard-coded PH readings (for testing without sensors)
ph1 = 9.499279390327938
ph2 = 11.467829390355589
ph3 = 3.467279392427777
# # ensure right format for LoRa packet
ph1_read = float(ph1)
ph2_read = float(ph2)
ph3_read = float(ph3)

# read voltage (TO BE DEFINED)
# temporarily hard-coded
volt1_read = 0.13338137397813754
volt2_read = 0.13338137397813754
volt3_read = 0.13338137397813754

msg = struct.pack(config.LORA_PKG_FORMAT, config.DEVICE_ID, temp_read, humid_read, ph1_read, ph2_read, ph3_read, volt1_read, volt2_read, volt3_read)
#check msg to send
print('Message to send', struct.unpack(config.LORA_PKG_FORMAT, msg))
# send LoRa packet
lora.send(msg)

# wait for reception windows to expire
print('Wait for reception windows to expire')
time.sleep(config.WAITING_SLEEP)
# check ack-sync reception
check_rx = lora.read_check_rx()
if check_rx:
    deepsleep_time = lora.read_sleep_time()
    print('Ack/sync received. Sleep time updated to ', deepsleep_time)
else:
    print('Nothing received, retry in few seconds')
    # missed ack-sync (or send unsuccessful): restart and retry to send
    deepsleep_time = 0

if config.LED_ON:
    #turn off led, going to sleep
    pycom.rgbled(0x000000) #off
    print('Going to sleep led off')

machine.deepsleep(int(deepsleep_time))

Your nano gateway is not a LoRaWAN compatible gateway and won’t work with LoRaWAN. The hardware is not capable of performing required data acquisition at 8 frequencies in parallel.

You need to disconnect the ‘gateway’ from TTN as it will cause issues for other users and get a LoRaWAN compatible gateway if you want to use TTN.

In fact i’m connecting to my Chirpstack Stack Service, created using docker-compose. The question is, how i need to change configuration of the server o Gateway to access it from the end-device?

As @jalbert48 already mentioned, the nano gateway is not a real gateway. It can only operate on a single frequency / data-rate at a time. It might work with ChirpStack with the right configuration, but I have never tried this and I think it is better to invest in a proper gateway.