Skip to main content

Injecting standard companion specifications (RFID example)

Goal

Load a standard companion specification (RFID) into your scripted server and instantiate a complex object type. The pattern generalises to any OPC UA companion spec: AutoID, DI, Machinery, ISA-95, and so on.

Prerequisites

  • The basic scripted server is working — see Creating an OPC UA server.
  • The nodesets: field accepts the built-in names that ship with the palette — "standard", "di", "autoId", "machinery", "ia", ... — or absolute paths to your own .NodeSet2.xml files. Use the latter when you have a downloaded companion spec that isn't bundled.

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", "di", "autoId"],
onPopulate: (addressSpace, exposed) => {
const namespace = addressSpace.getOwnNamespace();

const nsAutoId = addressSpace.getNamespaceIndex("http://opcfoundation.org/UA/AutoID/");
const rfidReaderDeviceType = addressSpace.findObjectType("RfidReaderDeviceType", nsAutoId);
if (!rfidReaderDeviceType) {
throw new Error("RfidReaderDeviceType not found — is the AutoID nodeset loaded?");
}

const devicesFolder = namespace.addFolder("ObjectsFolder", { browseName: "Devices" });

const rfidDevice = rfidReaderDeviceType.instantiate({
organizedBy: devicesFolder,
browseName: "RFID_Reader_1",
nodeId: "s=RFID_Reader_1",
optionals: ["DeviceHealth", "DeviceStatus", "ScanActive", "LastScanData"],
});

const deviceStatus = rfidDevice.getComponentByName("DeviceStatus", nsAutoId);
if (deviceStatus) {
deviceStatus.writeEnumValue("Idle");
exposed.deviceStatus = deviceStatus;
}
const scanActive = rfidDevice.getComponentByName("ScanActive", nsAutoId);
if (scanActive) {
scanActive.setValueFromSource({ dataType: opcua.DataType.Boolean, value: false });
exposed.scanActive = scanActive;
}
},
});

flow.set("$rfid_deviceStatus", handle.exposed.deviceStatus);
flow.set("$rfid_scanActive", handle.exposed.scanActive);
flow.set("$opcuaHandle", handle);

node.send({ payload: `OPC UA Server running at ${handle.server.getEndpointUrl()}` });
}

If you have an XML file you've downloaded yourself, mix paths and built-ins:

nodesets: [
"standard",
"/srv/nodesets/Opc.Ua.Di.NodeSet2.xml",
"/srv/nodesets/Opc.Ua.AutoID.NodeSet2.xml",
],

Paths must be absolute. The bootstrap helper validates each entry up-front and throws a helpful error listing built-in names if you misspell one — that pre-empts node-opcua's [NODE-OPCUA-E02] generateAddressSpace : cannot find nodeset2 xml file crash, which would otherwise leave the server in a partially-initialized state.

caution

Nodesets are construction-time only — nodesets: is part of the config-identity hash. Adding a new nodeset to the boot Function and redeploying triggers an automatic teardown-then-rebuild.

Update values dynamically

In a separate Update 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 { opcua } = sterfive;
const scanActive = flow.get("$rfid_scanActive");

if (!scanActive) {
node.warn("server not booted");
} else {
scanActive.setValueFromSource({
dataType: opcua.DataType.Boolean,
value: Boolean(msg.payload),
});
}
}

Tips

  • The companion specs define many optional components. optionals: [...] controls which ones are materialised on instantiation. Be deliberate; instantiating everything inflates the address space.
  • getComponentByName(name, namespaceIndex) requires the namespace index because the same browse name might exist in several namespaces.
  • For composite extension-object values (e.g., RfidScanResult), use addressSpace.constructExtensionObject(dataTypeNode, fields) to build the value, then setValueFromSource({ dataType: DataType.ExtensionObject, value: extObj }).
  • Several companion specs depend on others (AutoID requires DI, MachineTool requires Machinery, etc.). Load the prerequisites in nodesets: order.

Further reading

For more tips and examples, see the Sterfive book node-opcua by example.