This is Part 2 of a 3-part series on i3X and OPC UA. Part 1: When REST Meets Real-Time | Part 3: The Missing Safety Layer
Editor's note (June 14, 2026): We have reworded the monitoring section for precision following feedback from Matthew Parris. i3X keeping subscription tuning away from the client is a deliberate design choice, not an oversight, so we now separate what the spec requires from what a naive polling implementation does. The bridge carries this responsibility, and we are glad to have it pointed out.
One API Call to Rule Them All — and Its Limits
In Part 1, we saw that i3X and OPC UA share the same information-modeling DNA. But i3X doesn't just mirror OPC UA through a REST interface — it does something genuinely clever with the data.
The OPC UA → i3X mapping
When node-i3x translates an OPC UA address space into i3X, the mapping is precise:
| OPC UA Node Class | i3X Kind | Role |
|---|---|---|
| Object | asset | A container — a machine, a sensor, a subsystem |
| Variable | property | A data point — temperature, speed, state |
| Method | action | An operation — Start, Stop, Brew |
This mapping looks simple. The magic lies in what i3X does next.
The OPC UA way: browse, then read, then reassemble
Consider a temperature sensor in OPC UA. It's modeled as an Object with four child Variables:
OPC UA Address Space:
└─ TemperatureSensor (Object)
├─ Value (Variable) → 72.5
├─ EngineeringUnit (Variable) → "°C"
├─ EURange (Variable) → {low: 0, high: 200}
└─ Accuracy (Variable) → 0.5
To get the full picture with a native OPC UA client, you must:
- Browse the TemperatureSensor to discover its children
- Read with 4 separate
ReadValueIds - Parse 4 individual
DataValueresponses - Reassemble them into a coherent sensor object in your code
For a CNC machine with 50 variables, that's a browse call plus a read call with 50 items. You're doing the data integration in your application layer.
The i3X way: maxDepth + isComposition
i3X flips the model. Every OPC UA Object becomes an i3X asset, and all its child Variables become properties — directly addressable internal components of that asset. A single API call with the maxDepth parameter retrieves everything:
// POST /v1/objects/value
{
"elementIds": ["nsu=http://example.org/:TemperatureSensor"],
"maxDepth": 0
}
The response arrives pre-structured:
{
"success": true,
"results": [{
"elementId": "nsu=http://example.org/:TemperatureSensor",
"result": {
"isComposition": true,
"value": null,
"quality": "Good",
"timestamp": "2026-06-09T07:00:00Z",
"components": {
"Value": { "value": 72.5, "quality": "Good", "timestamp": "..." },
"EngineeringUnit": { "value": "°C", "quality": "Good", "timestamp": "..." },
"EURange": { "value": {"low": 0, "high": 200}, "quality": "Good", "timestamp": "..." },
"Accuracy": { "value": 0.5, "quality": "Good", "timestamp": "..." }
}
}
}]
}
The isComposition: true flag tells the client this asset has internal structure. The components map gives you every child's value, quality, and timestamp. No browsing, no reassembly, no OPC UA expertise required.
The "Digital Twin in a single call" effect
Scale this up. A CNC machine with 50 variables — spindle speed, feed rate, tool ID, axis positions, temperatures, coolant flow. With OPC UA: browse + 50-item read + manual reassembly. With i3X:
POST /v1/objects/value
{ "elementIds": ["CNCMachine"], "maxDepth": 0 }
→ One JSON object, 50 properties nested under components
For an AI/ML pipeline, a React dashboard, or a digital twin, this is transformative. The data arrives in the shape your application needs — no OPC UA SDK, no browse logic, no data mapping code.
Subscriptions inherit the same superpower
The decomposition model extends to real-time monitoring. When you register a parent element with maxDepth > 1, i3X delivers composite updates through the SSE stream:
SSE Event:
elementId: "CNCMachine"
isComposition: true
components:
SpindleSpeed: { value: 12000, quality: "Good", ... }
FeedRate: { value: 500, quality: "Good", ... }
ToolId: { value: 7, quality: "Good", ... }
...47 more properties...
One subscription registration monitors an entire sub-tree. Far simpler than managing 50 individual OPC UA MonitoredItems.
But wait — the first cracks appear
The asset decomposition model is elegant for data consumption. But industrial systems aren't just about reading data. Consider these scenarios:
A safety gate opens for 200ms. The PLC records the transition. If the bridge behind i3X polls at a fixed rate (say, 1000ms), the 200ms opening falls between two samples and is never delivered. If the bridge maps that signal to event-driven OPC UA monitoring instead, it is caught. The i3X client sees neither choice.
A part counter increments every 800ms. Polled at 1000ms, the counter jumps by 2 or 3 and individual parts are lost. Monitored event-driven with a queue, every count survives.
Here is the precise point, and it is a fair one the community raised after this series first went out. i3X deliberately keeps subscription tuning away from the client. A consumer should not have to set samplingInterval, queueSize or a deadband. It just wants meaningful changes, and deciding what that means is the platform's job. We agree with that. The real gap is not that the client lacks knobs. It is that the spec gives the platform no way to declare what its on-change actually guarantees, so two conformant servers can behave very differently and the client cannot tell which one it is talking to. The responsibility lands on the bridge, and getting it right, which signal is fast-discrete, what deadband on an analog value, takes real domain knowledge.
On a real bottling line with 200 monitored points:
| Signal Type | Count | Optimal Mode | Naive polling bridge |
|---|---|---|---|
| Analog (temperature, pressure) | 40 | Sampling + 1% deadband | ⚠️ No deadband, wastes bandwidth |
| Valve states (booleans) | 80 | Event-driven | ❌ Sampling, misses fast transitions |
| Part counters | 20 | Event-driven + buffer | ❌ Sampling, loses counts |
| Alarm booleans | 20 | Event-driven | ❌ Sampling, misses short alarms |
A naive polling bridge monitors roughly 60% of signals sub-optimally. A bridge that selects the monitoring mode per signal, as node-i3x does, recovers them. The point is that the spec leaves this entirely to the implementation and gives the client no way to know which it got.
What's next
Missing a valve transition is bad. Missing a 200°C reactor alarm is catastrophic. But it gets worse — what about nodes that appear after startup and are invisible to i3X? Or audit trails that are legally required but completely empty? In Part 3, we'll see why the entire safety layer — alarms, events, dynamic models, and the ability to command machines — is invisible to i3X today, and what Sterfive is doing about it.
Sterfive's node-i3x implements smart defaults to mitigate these monitoring gaps — detecting boolean and counter nodes and automatically using event-driven monitoring under the hood. Available as open source (AGPL-3.0) or with a commercial license. Talk to us to discuss your deployment.