Most plants we talk to are not short on data — they are short on a path off the machine. A CNC line, a packaging cell, an energy meter on the incoming feeder: the PLC or meter has been faithfully counting cycles, current, and temperature for years, and none of it has ever left the panel. The instinct is to assume connecting it means new hardware, a controls-engineering project, and downtime to install it. It doesn’t. It means reading Modbus registers the device already exposes, and getting the decoding right.
The real hazard isn’t the wire — it’s the decode
Modbus is a simple, decades-old protocol: a client polls a server (the PLC, meter, or VFD) for blocks of 16-bit registers or single-bit coils, over TCP or an RS-485 serial line. Wiring it up is the easy part. The part that actually breaks industrial IoT integrations is turning those raw unsigned 16-bit words into a correct engineering value — a kW figure, a temperature, an RPM — without silently corrupting it.
That is where the Barquecon platform’s @bqt/modbus-adapter starts: not as a socket library, but as a decoder built specifically to survive the classic field-device hazard surface, with a test suite covering every path.
How the platform decodes Modbus data
- Pure decode core, unit-tested.
decodeRegistersandcreateModbusAdapter().map()map already-read registers plus a poll timestamp through a declared register map into a contract-valid telemetry event. Deterministic and replay-safe, matching the sibling adapters used elsewhere on the platform. - Signedness handled correctly.
int16sign-extension andint32two’s-complement, so a negative reading (a reversed current flow, a sub-zero temperature) doesn’t wrap into a nonsense large positive number. - 32-bit width and word order.
uint32/int32/float32values span two registers;wordOrderselects ABCD (big, the default) versus CDAB (little) register ordering — a notorious source of "reads exactly double or exactly backwards" bugs on real meters. - IEEE-754 floats.
float32bit reinterpretation, with NaN/Infinity dropped rather than ever emitted as a metric. - Fixed-point scaling. Per-field
scale(e.g. 0.1 for a one-decimal register), so a device that reports "215" for 21.5°C comes out correctly. - All four Modbus data banks, kept as separate address spaces. A field declares its bank —
holding,input,coil, ordiscrete— mapping to read function codes 03, 04, 01 and 02 respectively. Coils and discrete inputs decode viatype: "bool"to a clean 0/1 metric, with an optionalinvertflag for active-low signals (a normally-closed e-stop loop, for example). Because the four Modbus tables are numbered independently on the wire, the adapter keeps them independent internally too — coil 0 never aliases holding register 0.
Never fabricate a reading
The adapter’s discipline is the same one every Barquecon telemetry adapter follows: a missing or short register read, an out-of-range raw value, or a non-0/1 bit drops that specific metric as a structured skip — it does not get invented, interpolated, or defaulted to zero. An unprovisioned device is skipped rather than assigned a fabricated deviceId. For anything feeding a maintenance alert or a compliance record, a metric that silently goes missing is far safer than one that silently goes wrong.
A multi-bank example, as it looks in code
A single machine can mix register and bit banks in one map — a spindle RPM from an input register, a running state from a coil, and an inverted e-stop status from a discrete input:
A field’s bank defaults to holding, so simpler pre-existing register maps are unaffected, and the bank/type pairing is validated at construction — a boolean declared on a register bank, or a numeric type declared on a bit bank, fails fast rather than silently misreading.
How we wire a specific PLC or meter into your rollout
Everything above happens once the registers are in hand. Reading them — opening a Modbus-TCP socket or an RS-485 serial line and polling register blocks on an interval — is the last mile, and it’s a build step our engineers own per device model as part of your rollout: we confirm the transport (TCP or serial), confirm the register map for your controller or meter model, and the same decode core takes it from there. We wire the live feed from your PLC as part of getting you connected — it isn’t something you have to solve yourself first.
Two things worth knowing up front: pulling individual bits out of a packed status or alarm word (bitmask decoding on a holding register) isn’t supported today — we use the coil/discrete banks for genuine bit tables. And the Modbus PDU’s maximum registers per read (roughly 125 for TCP, 123 for RTU) is enforced by the polling layer, not the decode core itself.
Where this leaves a plant that wants to connect a PLC today
| Piece | Status |
|---|---|
| Register decoding (signedness, word order, scaling, IEEE-754, all four banks) | Built in |
| Register map authoring per device model | Standard process |
| Modbus-TCP / RTU transport (opening the socket, polling on an interval) | We build this for your rollout |
| Live data flow from your specific PLC or meter | We wire this during onboarding |
The hardest and most bug-prone part of connecting an existing PLC — correctly decoding what it already sends — is built, tested, and reusable across device models by authoring one register map per model. Getting a specific device on your site talking to the platform is a scoped engineering task our team owns against that same decoder, not an open design question. If you run Siemens, Mitsubishi, Delta or Schneider controllers, or a Modbus-capable energy meter or VFD, that is the conversation worth having next.