Skip to content

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

Reviewed

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.

RuleWhy 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.

DeliverableDetail
A written boundaryAgreed 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 decoderIn 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 configurationDevice 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 jobEnd-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 simulatorSo 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

Notes

Written up in more detail

The engineering behind this service, in public, with no pitch attached. All notes.

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.