This is part of a larger project where we are implementing a network of wireless low-power air quality sensors to deploy in the city of Magurele, Romania. The other projects are:

Disclaimer

This may not be a very good idea. For most purposes you're much better off using something like The Things Network, as you're helping the community and vastly increasing your coverage by leveraging on said community. However, based on your coverage needs and future deployment plans this could actually make sense, as you gain guranteed quality of service and you won't run into costs as you scale. In this case I just wanted to see how feasible it is to run an isolated pocket of LoRaWAN.

LoRaWAN network architecture

A LoRaWAN network consists a multitude of interconnected hardware and software components, promising large coverage for low-power end-devices, leveraging on existing infrastructure wherever this is available.

Our local LoRaWAN implementation

The infrastructure consists of a gateway and a server, using the ChirpStack software for implementing the LoRaWAN protocol. For testing things out the server also runs basic persistency and visualisation with InfluxDB and Grafana.

Gateway

Kerlink Wigrid station, placed on top of the tallest building in Magurele.

Network coverage, gathered on a drive around Magurele and Bucharest. Furthest packet received from 9.3km.

Gateway firmware

Of substantial help in updating the firmware and getting it connected to ChirpStack was the The Things Network help page. Gateway can load up new firmware from a USB drive at boot. Copies of the files used for this project are in the project's files section.

Gateway configuration

The gateway uses the Semtech Packet Forwarder (SPF) to interact with its LoRa modem. This software exposes a UDP interface to a server that runs the LoRaWAN stack. Configuration files for the SPF are stored in /mnt/fsuser-1/spf/etc and are global_conf.json and local_conf.json.

global_conf.json contains the channel plan and I am not sure if these channel indices need to be the same as the ones configured in ChirpStack. I aligned them just in case:

{
        "SX1301_conf": {
                "lorawan_public": true,
                "antenna_gain": 1,
                "clksrc": 1,
                "radio_0": {
                        "enable": true,
                        "type": "SX1257",
                        "freq": 868500000,
                        "tx_enable": true,
                        "tx_notch_freq": 129000,
                        "tx_freq_min": 863000000,
                        "tx_freq_max": 870000000,
                        "rssi_offset": -160
                },
                "radio_1": {
                        "enable": true,
                        "type": "SX1257",
                        "freq": 867500000,
                        "tx_enable": false,
                        "rssi_offset": -160
                },
                "chan_multiSF_0": { "enable": true, "radio": 0, "if": -400000 },
                "chan_multiSF_1": { "enable": true, "radio": 0, "if": -200000 },
                "chan_multiSF_2": { "enable": true, "radio": 0, "if": 0 },
                "chan_multiSF_3": { "enable": true, "radio": 1, "if": 400000 },
                "chan_multiSF_4": { "enable": true, "radio": 1, "if": 200000 },
                "chan_multiSF_5": { "enable": true, "radio": 1, "if": 0 },
                "chan_multiSF_6": { "enable": true, "radio": 1, "if": -200000 },
                "chan_multiSF_7": { "enable": true, "radio": 1, "if": -400000 },
                "chan_Lora_std": {
                        "enable": true,
                        "radio": 0,
                        "if": -200000,
                        "bandwidth": 250000,
                        "spread_factor": 7
                },
                "chan_FSK": {
                        "enable": true,
                        "radio": 0,
                        "if": 300000,
                        "bandwidth": 125000,
                        "datarate": 50000
                },
                "tx_lut_0": {"dig_gain": 0, "pa_gain": 0, "mix_gain": 8, "rf_power": 0}
        },
        "gateway_conf": {
                "server_address": "localhost",
                "serv_port_up": 1700,
                "serv_port_down": 1700,
                "keepalive_interval": 10,
                "stat_interval": 30,
                "push_timeout_ms": 100,
                "forward_crc_valid": true,
                "forward_crc_error": false,
                "forward_crc_disabled": false,
                "autoquit_threshold": 3,
                "gps_tty_path": "/dev/nmea"
        }
}

I ran into issues with local_conf.json as it kept being updated by some startup scripts with values that caused issues with my ChirpStack installation. Specifically, the "rssi_offset" value was set to -nan and that resulted in the SPF sending to the server NaN for RSSI measurements. The ChirpStack Gateway Bridge couldn't parse that value and rejected all communication. I provided my own local_conf.json with the following contents:

{
        "SX1301_conf": {
                "radio_0": { "rssi_offset": -160},
                "radio_1": { "rssi_offset": -160 },
                "tx_lut_0": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_1": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_2": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_3": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_4": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_5": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_6": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_7": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_8": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_9": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_10": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_11": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_12": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_13": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_14": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 },
                "tx_lut_15": {"dig_gain": 3, "pa_gain": 3, "mix_gain": 15, "rf_power": -1 }
        },

        "gateway_conf": {
                "gateway_ID": "XXXXXXXXXXXXXXXX"
        }
}

LoRaWAN server stack

The gateway just relays uplink packets to the server and expects any downlink packets from the server to broadcast to end nodes. It does not understand LoRaWAN, as the intricacies of the protocol are handled by LoRaWAN Network Servers and Application Servers. One instance of these is the ChirpStack open source implementation.

For testing this out without going through the hassle of installing ChirpStack, Semtech kindly provides an instance of ChirpStack that you can use within their LoRa developer portal.

ChirpStack itself comprises of 3 pieces of software:

These are well documented on the ChirpStack web page. Below are the (nearly default) configurations used in this setup.

ChirpStack Gateway Bridge

This converts the somewhat rudimentary UDP protocol used by the Semtech Packet Forwarder into a more flexible one (e.g. MQTT, with optional encryption) used to communicate to the Network Server. The Gateway Bridge can run on your server and do this conversion there, or it can be installed on the gateway itself, to not need to deal with UDP over the internet.

Running this on the Kerlink gateway, it keeps its configuration file under /var/config/chirpstack-gateway-bridge.toml. It listens to UDP port 1700 and connects to the MQTT server on TCP port 1883.

[general]
log_level=4
log_to_syslog=true

[backend]
type="semtech_udp"

  [backend.semtech_udp]
  udp_bind = "0.0.0.0:1700"

[integration]
marshaler="protobuf"
  [integration.mqtt]
  event_topic_template="gateway/{{ .GatewayID }}/event/{{ .EventType }}"
  command_topic_template="gateway/{{ .GatewayID }}/command/#"
  [integration.mqtt.auth]
  type="generic"
    [integration.mqtt.auth.generic]
    server="tcp://<SERVER_IP_OR_NAME>:1883"
    username=""
    password=""

ChirpStack Network Server

In this instance most of the configuration is the default one. Make sure to install prerequisites and configure postgresql as per the documentation. We have the channel frequency plan identical to the one on the gateway. The contents of the /etc/chirpstack-network-server/chirpstack-network-server.toml configuration file are:

[postgresql]
dsn="postgres://<DB_USER_NS>:<DB_PASSWORD_NS>@localhost/<DB_NAME_NS>?sslmode=disable"
[redis]
url="redis://localhost:6379"
[network_server]
net_id="000000"
  [network_server.band]
  name="EU868"
  [network_server.network_settings]
    [[network_server.network_settings.extra_channels]]
    frequency=867900000
    min_dr=0
    max_dr=5
    [[network_server.network_settings.extra_channels]]
    frequency=867700000
    min_dr=0
    max_dr=5
    [[network_server.network_settings.extra_channels]]
    frequency=867500000
    min_dr=0
    max_dr=5
    [[network_server.network_settings.extra_channels]]
    frequency=867300000
    min_dr=0
    max_dr=5
    [[network_server.network_settings.extra_channels]]
    frequency=867100000
    min_dr=0
    max_dr=5
    [network_server.network_settings.class_b]
    ping_slot_dr=0
    ping_slot_frequency=0
  [network_server.api]
  bind="0.0.0.0:8000"
  [network_server.gateway.backend]
  type="mqtt"
    [network_server.gateway.backend.mqtt]
    event_topic="gateway/+/event/+"
    server="tcp://localhost:1883"
    username=""
    password=""
[metrics]
timezone="Local"
[join_server]
  [join_server.default]
  server="http://localhost:8003"

ChirpStack Application Server

Make sure to install prerequisites and configure postrgresql as per the documentation. Configuration for the application server is in /etc/chirpstack-application-server/chirpstack-application-server.toml. Again, this instance uses a mostly default setup.

[postgresql]
dsn="postgres://<DB_USER_AS>:<DB_PASSWORD_AS>@localhost/<DB_NAME_AS>?sslmode=disable"
automigrate=true
[redis]
url="redis://localhost:6379"
[application_server]
  [application_server.integration]
  marshaler="json_v3"
  enabled=["mqtt"]
    [application_server.integration.mqtt]
    event_topic_template="application/{{ .ApplicationID }}/device/{{ .DevEUI }}/event/{{ .EventType }}"
    command_topic_template="application/{{ .ApplicationID }}/device/{{ .DevEUI }}/command/{{ .CommandType }}"
    server="tcp://localhost:1883"
    username=""
    password=""
    [application_server.api]
    bind="0.0.0.0:8001"
    public_host="localhost:8001"
    [application_server.external_api]
    bind="0.0.0.0:8080"
    tls_cert=""
    tls_key=""
    # You could generate this by executing 'openssl rand -base64 32' for example
    jwt_secret="<YOUR_SECRET>"
[join_server]
bind="0.0.0.0:8003"