Skip to main content

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.

One-time setup only

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 Device001 object lives under ObjectsFolder.
  • It carries a SerialNumber (constant) and a Temperature (live).
  • A Reset method on the device resets Temperature to 20.0.
  • exposed.temperature is the standard way to surface the variable for downstream nodes — anything written into exposed inside onPopulate reappears on handle.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.addObject and namespace.addMethod will throw if called twice with the same browseName in the same namespace — another reason all of this must live in onPopulate, which the bootstrap helper guarantees runs only on a fresh build.
  • A method's bindMethod callback 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.