How-tos

Building a LoRa Sensor Gateway on a Raspberry Pi

· 6 min

Radio in, JSON out, one reading at a time.

In 2016 I needed something that could hear sensors over LoRa and forward what they said to a server on the internet. The result was Lora_GW, a handful of Python files that run on a Raspberry Pi. Its git history runs from August 2016 to March 2017. This post walks through what the code actually does, including the parts I would write differently today.

What LoRa gives you, and what it doesn't

LoRa is a long-range, low-power radio. A sensor can send a few bytes a long way on very little battery, which is perfect for things like a water tank or an energy meter. What it does not give you is the internet. Something has to receive those packets and pass them on. That something is the gateway.

Two kinds of radio, one config file

The README says the target is a Raspberry Pi running Raspbian, and the code is Python 2. The script supports two very different radios, and you choose between them in lora.conf:

The config file has three sections the code reads: Connection (the serial device or the port number), Configuration (MODULE, MODE and AUX_PIN) and Testing (how many test packets to send). MODE picks one of three jobs: LISTEN, PING or CONFIG.

Step 1: Talk to the radio

SerialConnection.py wraps the pyserial library. For the DORJI module it opens the port at 9600 baud and offers two ways to read: whole lines for data, and single bytes for command replies.

RF_TestBed.py then watches the AUX pin with RPi.GPIO. Instead of polling the serial port in a loop, it registers a callback on the pin's falling edge. When the module pulls AUX low, the callback reads a line. The Pi does nothing until the radio has something to say.

Step 2: Read the radio's settings

In CONFIG mode the script sends the DORJI module a fixed 25-byte command and reads back a 25-byte reply. Each byte position means something: baud rate, parity, frequency, spreading factor, mode, bandwidth, device ID, network ID and transmit power. The code keeps small lookup tables to turn those codes into readable values, so spreading factor 7 to 12 and bandwidth codes become numbers you can check at a glance.

The frequency is the nice bit. It arrives as three bytes, which the code joins into one number and multiplies by 61.035 Hz. That figure matches the frequency step of Semtech's SX1276 radio chip (a 32 MHz crystal divided by 2^19), so the result is the carrier frequency in MHz.

There is also a setValues function that patches the device ID and network ID into that reply, recomputes a checksum and writes it back as a write command.

Step 3: Listen to the concentrator

With the iC880A, the Pi does not touch the radio at all. SocketListener.py opens a TCP server on the configured port and starts a new thread for every connection. Each thread reads up to 256 bytes and hands them to a cleaning function.

That function pulls out the first {...} block with a regular expression and parses it as JSON. Each packet carries a type field and numbered values D1, D2 and so on. A table maps each type to the names of its readings:

Every reading goes out as its own small JSON document, one per sensor stream, through an HTTP PUT in HttpService.py.

Step 4: Turn raw numbers into physics

The water sensor got special treatment, and the code admits it with a log line that calls it a dirty hack. The temperature arrives as a raw ADC count. The code converts it to a voltage on a 3.3 V scale over 16384 counts, works out a thermistor's resistance from a divider with a 10 kΩ resistor, and then aims for the thermistor's beta equation, with B = 3950 and 25 °C as the reference, to get degrees Celsius.

Then it uses that temperature to correct the water level. The speed of sound in air depends on temperature, roughly 20.05 times the square root of the absolute temperature in metres per second, and the code uses exactly that to convert the level sensor's raw reading. It is a small touch, but it is what separates a reading from a measurement.

Step 5: Test the link

PING mode is meant to measure the radio link. It sends a test message, waits up to ten seconds for a reply, and repeats for the number of packets in the config, then logs how many came back and the percentage lost.

What I would fix today

Reading old code honestly is the most useful part of writing about it.

None of this stopped it from doing its job: hear a sensor, make sense of its bytes, and get the numbers onto a server. That is most of what a gateway is.

Share