Custom objects and methods
Goal
Extend the persistent server with custom objects and methods. Everything on this page goes inside the onPopulate callback of the Boot Server Function from Creating an OPC UA server. onPopulate runs exactly once, only when a server is built fresh — perfect for one-time address-space construction.
Adding objects and methods is a construction-time activity. Do not put addObject / addMethod calls in nodes that handle every message — they would either fail (browseName collision) or pile up duplicates each redeploy.
The full Boot Function with a Device object and a Reset method
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();
exposed.myVariable = namespace.addVariable({
organizedBy: "RootFolder",
nodeId: "s=MyDynamicVariable",
browseName: "MyDynamicVariable",
dataType: "Double",
value: { dataType: opcua.DataType.Double, value: 0.0 },
});
const device = namespace.addObject({
organizedBy: "ObjectsFolder",
browseName: "Device001",
});
const serialNumber = namespace.addVariable({
componentOf: device,
browseName: "SerialNumber",
dataType: "String",
});
serialNumber.setValueFromSource({ dataType: opcua.DataType.String, value: "SN-001" });
exposed.temperature = namespace.addVariable({
componentOf: device,
browseName: "Temperature",
dataType: "Double",
});
exposed.temperature.setValueFromSource({ dataType: opcua.DataType.Double, value: 20.0 });
const resetMethod = namespace.addMethod(device, {
browseName: "Reset",
inputArguments: [],
outputArguments: [],
});
resetMethod.bindMethod(async (_inputArguments, _context) => {
exposed.temperature.setValueFromSource({ dataType: opcua.DataType.Double, value: 20.0 });
return { statusCode: opcua.StatusCodes.Good, outputArguments: [] };
});
},
});
flow.set("$temperature", handle.exposed.temperature);
flow.set("$opcuaHandle", handle);
node.send({ payload: `OPC UA Server running at ${handle.server.getEndpointUrl()}` });
}
What's happening:
- A new
Device001object lives underObjectsFolder. - It carries a
SerialNumber(constant) and aTemperature(live). - A
Resetmethod on the device resetsTemperatureto20.0. exposed.temperatureis the standard way to surface the variable for downstream nodes — anything written intoexposedinsideonPopulatereappears onhandle.exposed.
Updating the temperature from another flow
In a separate Update Temperature Function node:
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;
const temperature = flow.get("$temperature");
if (!temperature) {
node.warn("server not booted");
} else {
temperature.setValueFromSource({
dataType: opcua.DataType.Double,
value: Number(msg.value),
});
}
}
This is identical in shape to the basic Update Function — every live variable gets the same treatment.
Test the method
Use any OPC UA client to call Device001.Reset. You should receive Good and observe Temperature reset to 20.0.
Pitfalls
namespace.addObjectandnamespace.addMethodwill throw if called twice with the samebrowseNamein the same namespace — another reason all of this must live inonPopulate, which the bootstrap helper guarantees runs only on a fresh build.- A method's
bindMethodcallback runs in the server's request thread; do not perform blocking I/O without bounding it. Long-running work should set the variable from a timer instead. addressSpace.registerShutdownTask(fn)is the right place to clean up timers, sockets, or other resources you created while building the address space — they will be invoked on shutdown.
Next step
Continue with user authentication.
Further reading
For more tips and examples, see the Sterfive book node-opcua by example.