Once the sensors were covered and the radio was working, the next question arrived: how many devices can we attach?

The first structure was the simple one. Each device measures on its own timer and transmits. It is the easiest thing to build. It is also pure ALOHA — nobody knows when anybody else is talking, so there is no way to prevent collisions. To keep the collision rate under 1% with 30 devices, the transmit interval had to stretch to five minutes.

Once every five minutes is too slow. So we inverted it.

Instead of letting devices speak on their own, the server calls them one at a time. With a single caller, two devices never talk at once in the first place.

Wait-for-reply polling was the natural way to write it: call, receive the reply, call the next device. And 30 devices took 42 seconds.

Measurement explained why.

SegmentTime
Radio round trip itself466 ms
From the call to the reply1,444 ms
Differenceabout 1 second

That second is not propagation time. It is the wait created by the gateway emitting downlinks on a one-second scheduler. In a wait-for-reply design, that second accumulates once per device.

So we stopped waiting for replies. We keep calling at a fixed interval and pair up the replies as they arrive. That turns the second into a constant applied equally to every device, and a full round is determined only by the calling interval.

The interval cannot shrink without limit. Overlapping replies are collisions again, so it has to sit comfortably above the uplink airtime. For 30 devices we settled on a 1-second interval, which is a 30-second round.

LoRa devices that do not answer get a rest

Skipping devices that do not answer was one more condition we added: a device that is not answering must not be called forever.

A device that has been power-cycled needs to reconnect to the gateway, and if we keep calling it in the meantime, the gateway transmitter stays busy and cannot take the join request. The more we call, the later it comes back.

So after a few missed calls, a device is left alone for a while and the poller moves on. In exchange, when word arrives that it has reconnected, we call it again immediately.

The gateway’s downlink slots have a minimum re-call interval — something shrinking the interval taught us. Calling the same device again after 6 seconds failed; 10 seconds went through.

We did not find that in any document — we found it by changing the value and observing. Details like this are usually undocumented, and the longer part is realizing that they are.

The uplink monitor window exists to show what is going back and forth while polling runs.

Uplink monitor

Received uplinks stack up in time order. Device, SEQ, RSSI, SNR, reason, measured value, slave, and CRC all sit on one line together with the raw payload, and per-device totals run in a separate panel on the right. (Device identifiers and the server address are masked.)

This screen caught a lot. The reason column records whether a frame is a poll response or an autonomous transmission, and the slave column shows which sensor answered. Even in the screenshot above, one device is reporting normal values while another shows sensor disconnected — the node is alive, but the sensor beneath it is not answering. You can separate a radio problem from a sensor problem by eye.

We implemented the broker ourselves as well. We did not want this one window to force the installation of a separate MQTT broker, and since all we actually needed was connect, publish, subscribe, and ping, doing it ourselves was the simpler path.

The node configuration tool — AT commands on a screen

LoRa node settings originally went in one AT command at a time: sensor count, baud rate, poll interval, timeout, and the keys used to attach to the gateway. Typing all of that by hand in the field is not realistic.

Configuration tool

Connection, settings, gateway attachment, and firmware update on a single screen. (Shown with no device connected.)

Building it taught us three things.

One. Opening the port reboots the module. Opening the serial port toggles the control lines and resets the module. Which means that if you enter values and do not save them, they are gone on the next connection. Not knowing this leads to an endless loop of “I definitely entered that, why is it not there?”

Two. The slave ID is discovered, not configured. At first we had the user type in the slave ID. But in the field, nobody knows what ID a sensor is set to — the node scans and finds out. So we removed the input box and replaced it with a “detected sensors (1–5)” readout. Never ask the user for a value the user cannot determine.

Three. Flashing firmware does not need a button press. Normally you have to double-tap the reset button on the module to enter the bootloader. But this chip family drops into the bootloader when a port is opened and closed at a particular baud rate. Putting that into the tool means nobody has to open the enclosure on site.

The gateway management tool — why login kept failing

The gateway API management tool exists because clicking through the web UI for every change is also work, so we built a separate tool that drives it through the API. And login failed, over and over.

The username and password were correct, but we could not extract the auth token. Only after picking the response apart did we see it: this firmware returns the token under its own field name, not one of the common ones. We had only been looking for the common ones.

One thing to be careful about here: while groping around the API, accumulated login failures lock the gateway. The number of remaining attempts comes back in the response, and only after seeing it did we understand that this is a door you knock on carefully.

We held to one design rule. Read the whole radio configuration, replace only the fields you intend to change, and write it back. That is the only way settings this tool knows nothing about survive. For a tool that touches a gateway in production, this is not optional.

Calling at a fixed interval instead of waiting for each reply stops the one-second downlink wait from accumulating per device, and a 30-device round drops from 42 seconds to 30.


▶️ Next in this series — zero interference, and still impossible to assemble: Turning a working node into a product needed an enclosure. The interference check kept passing while assembly failed five times.

🔧 Previously: building a sensor without the hardware.

Contact