Service
Custom IoT builds, from the LoRaWAN frame to the point on the screen
Devices, gateways, a network server, a broker, and whatever has to read the result — a building system, a dashboard, or a database that already exists. Most of this field stops at the dashboard. The half that is left is a value with one type for life, a history that adds up, and a downlink that is acknowledged, and that half is the work.
- Payload decoders
- ChirpStack 4
- MQTT
- STM32WL firmware
- Class C downlinks
- Niagara points
A worked example of the whole chain, with the authorship of every component stated: six repositories, four benches, one system that had to behave as one.
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.
When there is no BMS at all
Half of the enquiries that reach us are not Niagara jobs. A grower, a landlord, a water utility, a manufacturer with a hundred machines — they have devices and a question, and no building system to hang them on. That work is the same work minus one hop, and we take it.
The stack, end to end
Devices and gateways selected against the range you actually need rather than the range on the datasheet, a network server you own, an MQTT broker with a topic tree designed rather than accumulated, a time-series database, and a dashboard people can read on a phone. One party responsible from the radio to the screen, which is the part that usually has three owners and therefore none.
On your infrastructure or ours
ChirpStack 4, Mosquitto or EMQX, InfluxDB or TimescaleDB, Grafana — all of it open source, all of it installable on a small server you own, with the configuration handed over as files rather than as screenshots of a wizard. If you would rather not run it, we will; but you should be able to take it back, and the handover pack is written so you can.
The integration nobody quoted for
Readings have to land somewhere that already exists — an ERP, a CMMS, a billing run, a spreadsheet somebody actually opens. That is usually a small service with a queue in front of it, and it is usually the piece left out of the quote. We would rather it were in ours.
What you can actually buy
Each of these is a named deliverable with an end, not an open engagement. Scope is agreed in writing first and the price is fixed against it — there is no hourly billing here and no price before the scope exists.
A read of one codec — free
Send one decoder and a handful of frames. What comes back is the list of places a building system would see a value change type, a key vanish or an error code read as a measurement, with the payload that shows each one. It obliges nothing, and the tool is published so you can run it first.
Typically two working days.
A device integration pack
One device family, taken from its frame specification to something a station can use: a dependency-free Java codec, a generated vector set with a self-test that proves it matches the vendor's own decoder frame for frame, a point template with the register types and word order already right, and a one-page data contract naming what every field means when it is invalid.
Fixed price. Usually two to three weeks.
A private network, built and handed over
A network server you own, gateways joined and their coverage measured rather than assumed, device profiles and payload codecs loaded, an MQTT topic tree designed for the consumers it will have, and a handover document that lets somebody else run it.
Fixed price against a site count. Usually three to six weeks.
The bridge into the building system
The station half: points that hold one type for life, histories that add up, alarms that fire on the condition rather than on the gap in the data, and downlink control that reports a failed write as a failed write. Niagara if that is what you run; BACnet, Modbus or a REST endpoint if it is not.
Fixed price per point count and protocol.
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. |
If you make the device
Half of what breaks a BMS integration was decided before the device shipped, in a codec and a register document. That side is work we take on too.
A read of your codec
We run it rather than skim it: every frame type, both product variants, the error codes, the top of each range and the malformed payloads, then the derived set — all-zero, all-ones, single-bit flips, one byte too many. What comes back is the list of places a building system would see a number change type, a key disappear, or an error code read as a measurement. The tool that does it is published and free, so you can run it before you talk to us. Findings go to you privately and nobody else, with a patch where one is possible.
A Java codec for Niagara
The same decoder, ported to a self-contained Java class with no dependencies and no
framework imports, so it compiles with plain javac and runs on a
JACE‑8000 — ARM, QNX, Java 8 — as well as on a Supervisor. Ported
from your frame specification rather than transliterated from the JavaScript, and
shipped with a self-test that replays thousands of generated frames through both
yours and ours and requires the output to be identical, character for character. You
can rerun that count yourself; it is the whole point.
A read of your register map
Modbus documents fail in a small number of repeatable ways: a function-code list that the worked example contradicts, an address column that is one-based in one table and zero-based in the next, a float area published in two word orders when a station has one byte-order setting for the whole device, and a totaliser whose rollover is documented nowhere. We check yours row by row and send the arithmetic, not the opinion. The generic version is in Modbus register addressing.
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. |
What it costs
A first job is a fixed 750 USD
That buys one to two days: a decoder rewritten against the payload specification and proved frame by frame against your own uplinks, or one network-server integration made to keep its join keys where they belong. The scope is written down before anything starts and it says what is not included. Past about three days it is not a first job any more: it gets its own written scope and its own number, quoted before work begins. No hourly billing, nothing to sign monthly, and no introductory rate. Scope a job.
After the job
Support afterwards is part of the offer
Devices change faster than stations do. A decoder updated when a vendor revises the payload, a new sensor type added to the ones already flowing, and a look at the broker when a reading stops arriving. How support works.
Also
Other services
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.
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.
Operator graphics
Niagara operator screens built once and reused: standard PX plant sheets bound by relative ORD, bajaux web widgets and dashboards, and a written 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.
Integration audit
A fixed-scope paid audit of an existing integration: what talks to what, where it breaks, and what fixing it would take.
Protocols & data
Field protocol integration and the data behind it: BACnet, Modbus, M-Bus and MQTT, into a building system, a database or a dashboard.
Web, apps & DevOps
Web and app builds with the DevOps around them: front end, backend, the database under it, and the build, deploy and monitoring work that keeps it up.
Website design
Remote website design services for small firms: a few real pages, fixed price, and a measured handover — HTTPS, broken links, Lighthouse, page weight.
Spreadsheets & internal tools
An audit of the spreadsheet a team runs on — every rule it encodes, and which ones contradict each other — then one bounded slice of it as a web app.
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
When the datasheet link in the device registry is dead
Every product and datasheet link in the public LoRaWAN device registry, requested: 213 of 1,568 are gone, and a wall is not a break.
- LoRaWAN
- Measurement
- 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.