A structured namespace layout
Goal
Organize your address space in a clean, scalable layout using folders and objects. As with the previous pages, all of this code lives inside the onPopulate callback of the Boot Server Function, which the bootstrap helper guarantees runs exactly once per fresh build.
The Boot Function
const sterfive = global.get("sterfive");
if (!sterfive) {
node.error("global.get('sterfive') is not set — is the Sterfive OPC UA palette loaded?");
} else {
const { bootstrap, opcua } = sterfive;
const { bootstrapServer } = bootstrap;
const cfg = msg.config || {};
const handle = await bootstrapServer({
port: cfg.port ?? 4840,
endpoint: cfg.endpoint || "node-red-server",
nodesets: ["standard"],
onPopulate: (addressSpace, exposed) => {
const namespace = addressSpace.getOwnNamespace();
const devicesFolder = namespace.addFolder("ObjectsFolder", { browseName: "Devices" });
const deviceA = namespace.addObject({
organizedBy: devicesFolder,
browseName: "DeviceA",
});
const deviceB = namespace.addObject({
organizedBy: devicesFolder,
browseName: "DeviceB",
});
exposed.statusA = namespace.addVariable({
componentOf: deviceA,
browseName: "Status",
nodeId: "s=DeviceA.Status",
dataType: "String",
});
exposed.statusA.setValueFromSource({ dataType: opcua.DataType.String, value: "Running" });
exposed.speedA = namespace.addVariable({
componentOf: deviceA,
browseName: "Speed",
nodeId: "s=DeviceA.Speed",
dataType: "Double",
});
exposed.speedA.setValueFromSource({ dataType: opcua.DataType.Double, value: 0.0 });
exposed.statusB = namespace.addVariable({
componentOf: deviceB,
browseName: "Status",
nodeId: "s=DeviceB.Status",
dataType: "String",
});
exposed.statusB.setValueFromSource({ dataType: opcua.DataType.String, value: "Idle" });
},
});
// Downstream Function nodes can either look up by NodeId
// (handle.addressSpace.findNode("ns=1;s=DeviceA.Status")) or pull from flow context.
flow.set("$deviceA_status", handle.exposed.statusA);
flow.set("$deviceA_speed", handle.exposed.speedA);
flow.set("$deviceB_status", handle.exposed.statusB);
flow.set("$opcuaHandle", handle);
node.send({ payload: `OPC UA Server running at ${handle.server.getEndpointUrl()}` });
}
Routing many variables through one Update Function
A single Update Function can service many variables by routing on msg.topic:
const sterfive = global.get("sterfive");
if (!sterfive) {
node.error("global.get('sterfive') is not set — is the Sterfive OPC UA palette loaded?");
} else {
const { opcua } = sterfive;
// Topic-keyed lookup. Boot script publishes variables under
// flow["$<topic>"]; we use a `$` prefix to keep the keys unambiguous and flat
// (Node-RED treats dots as nested-path separators).
const target = flow.get(`$${msg.topic}`);
if (!target) {
node.warn(`unknown variable: ${msg.topic}`);
} else {
const value = msg.payload;
const variant = typeof value === "number"
? { dataType: opcua.DataType.Double, value }
: { dataType: opcua.DataType.String, value: String(value) };
target.setValueFromSource(variant);
}
}
Send messages with msg.topic = "deviceA_speed" and msg.payload = 42.0. This scales linearly to dozens or hundreds of variables without adding more Function nodes.
Two ways to share variables between Function nodes
In order of preference:
- NodeId lookup (recommended). Both Function nodes call
bootstrapServer({...})with the same config — the helper's idempotent adoption returns the same handle. The consumer resolves variables viahandle.addressSpace.findNode("ns=1;s=DeviceA.Speed"). NodeIds are stable identifiers chosen at populate time. No naming convention to invent, no shared state. - Flow context with a
$prefix. When NodeId lookup isn't convenient (or for non-OPC-UA objects), useflow.set("$<name>", ...). Avoid bare keys ("opcua"collides with other vendors), dotted keys (Node-RED treats them as nested paths), and colon-separated keys (need bracket notation in some UIs).
Tips
- Use folders for grouping (
Devices,Lines,Areas). Folders are organizational only; they do not appear as components. - Stable node IDs: prefer string-based node IDs (
nodeId: "s=DeviceA.Speed") for variables clients will subscribe to. Numeric IDs are auto-generated and can shift between deploys. - Keep names consistent: capitalisation, separators, and units in browse names are part of your contract with clients.
Related
Further reading
For more tips and examples, see the Sterfive book node-opcua by example.