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.

- End devices use the LoRa RF modulation to talk to gateways. Each device may be heard by multiple gateways and all copies of the messages are forwarded to the network server. Devices further away from a gateway can choose a lower data rate to increase their range
- The network server handles deduplication of messages, forwards them to application servers and selects which gateway to use for downlink transmissions
- Application servers decrypt and use the data from end devices. The same gateways and network server can service multiple applications.
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:
- ChirpStack Gateway Bridge
- ChirpStack Network Server
- ChirpStack Application Server
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"