Service
LoRaWAN devices, networks, and the points they become
Most of this field stops at a dashboard. A building system needs the other half: a point with one type for life, a history that adds up, and a downlink that is acknowledged — which means the device, the network server and the station all have to be somebody's problem.
- Payload decoders
- ChirpStack 4
- STM32WL firmware
- Class C downlinks
- Niagara points
What this covers
Decoders that survive a station
A vendor's JavaScript codec is written for a dashboard, and a dashboard tolerates a
key that is a number one minute and the word Error the next. A point
does not: it has one type for life and a history that will not take text. We port
the codec — usually into Java, sometimes into a normalising service in front
of the station — keeping enum ordinals rather than English display strings,
turning invalid markers into point status instead of into a value, and making
missing keys a fault rather than silence. Six patterns we keep finding, with a test
harness, are written up in
reading a LoRaWAN payload
decoder before you trust it.
The network server
We run ChirpStack 4 ourselves — server, gateway bridge, REST API and MQTT integration, in containers — so a private network on site is a configuration exercise rather than a research project: device profiles, join handling, downlink queues, retained topics that a station can subscribe to. Where a public network is already in use, the work happens against your existing tenant instead. Either way the boundary we care about is the one where a frame becomes a message somebody else has to consume.
The write direction
Reading sensors is the easy half. Controlling a device from a building system means downlinks, and that means a device that is listening: Class C, OTAA, EU868 end-node firmware on the STM32WL under FreeRTOS with ST's LoRaWAN stack, an application protocol carried on those downlinks as a register map rather than ad-hoc opcodes, and a two-MCU split where the radio MCU and the application MCU talk over a framed serial link. That is firmware we have written, and it is why a command from a station can be something other than a hope.
Proving it without the hardware
Devices arrive late and arrive in ones. A simulator that speaks to the network server as a fleet does — joins, uplinks on a schedule, downlink acknowledgement — lets the station work, the alarms and the histories be built and tested before a single unit is mounted. The same rig is how a protocol gets audited: derive the byte layout from the firmware, then run the backend against it and list every place the two disagree. We have done that and the list was not short.
What we hold to across that boundary
Each of these is a rule we adopted after watching the alternative fail somewhere real.
| Rule | Why it is held |
|---|---|
| Time crosses as epoch or with an offset | Decoders that format a device clock with local-time getters emit a string that reads differently on every server it runs on, and once it is in the JSON there is no recovering which zone it meant. The station has a time zone; the decoder does not need one. |
| One type per point, for its whole life | A numeric point fed a string goes to fault or stops updating, and if the first frame after commissioning was the invalid one, the point may have been created as the wrong type and will stay that way. |
| Ordinals survive, display strings are extra | An enum point needs the number the device sent to be alarmable, trendable and translatable. Text that arrives instead breaks the day the vendor improves their wording. |
| A missing value is a fault, not silence | An unmapped byte that decodes to nothing leaves the key out of the payload, which downstream is indistinguishable from a message that never arrived — so the one frame telling you something new is the one that leaves no trace. |
| Downlinks are confirmed and re-queued | A command written into a queue is not a command delivered. Class C keeps the receive window open, but the acknowledgement is still what the station should believe, and a failed write should look like a failed write. |
| Station-side code stays pure Java | Whatever happens on the network-server side, the part installed on a controller is a signed, pure-Java module with no native library, because a JACE is an ARM/QNX box and a native dependency will not load on it at all. |
Whose work this is. The device and network-server experience above comes from product engineering done under other companies' names, so there are no logos and no case studies on this page — those are theirs to publish, not ours. What is ours to describe is the part that transfers: the stack, the device class, the shape of the protocol, and the failure modes we have already paid for once. On the Niagara side the work is our own and the tools are published.
Deliverables
What you actually receive
Fixed price per deliverable, quoted against a written specification. No hourly billing, and no price before the scope is in writing.
| Deliverable | Detail |
|---|---|
| A written boundary | Agreed before a price exists: which side decodes, what the JSON looks like, what happens to an invalid reading, and what a failed downlink does. One page, in language both sides can hold each other to. |
| The decoder | In Java for the station, or as a normalising service in front of it — with the three test frames that prove it: nominal, every-invalid-marker, and one value below zero. |
| The network-server configuration | Device profiles, integrations and downlink behaviour, as files rather than as clicks somebody has to remember, so the same network can be stood up twice. |
| Firmware, where that is the job | End-node work on the STM32WL: join behaviour, the register map, and the downlink handling — delivered with the serial protocol written down, because that document is what outlives the developer. |
| A simulator | So the station, the alarms and the histories can be built and tested before hardware arrives, and regression-tested after it does. |
Also
Other services
Custom modules & drivers
Bespoke Niagara rt, wb and ux modules and drivers written against the Baja API: pure Java, signed, and stamped to install across a mixed estate.
bajaux widgets
Custom Niagara web widgets in bajaux and BajaScript: HTML5 BMS dashboards, HVAC plant views, navigation and charts, bound to live station ORDs and BQL.
PX graphics
Niagara PX graphics as a reusable template set: standard HVAC plant sheets, relative-ORD binding, navigation hierarchy and a written graphics standard.
Station engineering
JACE controller and Niagara station setup end to end: platform commissioning, TLS, users, roles, BACnet and Modbus, tagging, histories, alarms, backups.
Workbench tooling
Custom Workbench views and tools for repetitive Niagara work: bulk renaming and retagging, station audits, provisioning helpers and module inventories.
Niagara 5 migration
Niagara 5 readiness audits and migration, including JACE 8000 to JACE 9000: which third-party modules survive the new runtime and mandatory signing.
Notes
Written up in more detail
The engineering behind this service, in public, with no pitch attached. All notes.
Bringing LoRaWAN and MQTT data into a Niagara station
Where abstractMqttDriver and jsonToolkit stop, and what LoRaWAN decoding, topic design, and store-and-forward buffering add on top.
- Integration
- LoRaWAN
- MQTT
Reading an unfamiliar MQTT broker in a building
A subscriber cannot list topics, the retain flag is how you tell a stored value from a live one, and a duplicate client ID disconnects someone else.
- MQTT
- Integration
- Commissioning
Reading a LoRaWAN payload decoder before you trust it
Eight things found by executing vendors' published JavaScript codecs, and what each one does to a Niagara point once the data lands.
- LoRaWAN
- Integration
- Commissioning
Next step
Send one frame and the decoder that is meant to read it.
That is enough to say whether this is a decoder job, a network-server job, or a firmware job — and which of the three you do not need. The read is free and the answer is usually a paragraph.