Deploying OPC UA Nodeset XML Models on Siemens IOT2000
The SIMATIC IOT2000 family (IOT2020, IOT2040) is a Siemens industrial IoT gateway based on Intel Quark / x86 silicon running a Yocto Linux example image. The shipped image bundles Node.js, the node-opcua server stack, and a sample application that loads a user-defined OPC UA information model from a Nodeset XML file at startup. This reference shows the complete flow: design a custom address space in SiOME, export it to XML, deploy it to the gateway, regenerate the application certificate, and verify the running server with UaExpert.
node-opcua Nodeset loader and will fail with Cannot find module 'node-opcua' or a missing nodeset.xml import path.1. System Architecture
The IOT2000 OPC UA pipeline consists of four logical components that exchange data through a single XML file:
- Modelling tool (SiOME or FreeOpcUaModeler) - generates the address space and serialises it to a Nodeset XML conformant with the OPC Foundation UA-Nodeset schema.
-
Nodeset XML file - portable container that follows the OPC UA UANodeSet XSD. It contains
UAObject,UAVariable,UAReference, andUATypeelements with namespace URIs. -
Node.js OPC UA server (
app.js) - boots aOPCUAServerinstance, registers the XML, and binds variables to a data source. - OPC UA client (UaExpert) - validates the address space, reads/writes values, and inspects the status codes.
Because the model is exchanged as XML, the same model can be authored once in SiOME and consumed by the IOT2000, an S7-1500 CPU, or a third-party server. The OPC Foundation overview of Nodeset files describes the serialisation format and import/export semantics; the companion specification approach is also documented for SIMATIC controllers in the TIA Portal export of the S7-1500 server XML page, which applies the same .xml extension convention used here.
2. Prerequisites
| Component | Required version / detail |
|---|---|
| SIMATIC IOT2000 | IOT2020 or IOT2040 with example image V2.1.3+ |
| Node.js | Bundled with the example image (8.x or 10.x) |
| SiOME | Siemens OPC UA Modelling Editor (current release) |
| WinSCP | For SFTP transfer of the model and the application archive |
| UaExpert | Unified Automation OPC UA Client (1.5+) |
| OpenSSL | For regenerating private_key.pem and certificate.pem
|
| pm2 (optional) | Process manager for auto-restart of the server |
3. Installing and Configuring SiOME
SiOME (Siemens OPC UA Modelling Editor) is the tool of choice for designing a custom namespace. It is delivered as a Windows installer and embeds an Eclipse-based GUI. The editor stores each project as a .ttcn3-style bundle plus a generated .xml Nodeset output.
- Install SiOME on a Windows engineering station.
- Launch the editor and create a new project; SiOME prompts for a target namespace URI (e.g.
urn:siemens:iot2000:demo). - Open the Information Model view to author the address space.
app.js can resolve references.4. Building the Custom Namespace
Inside the Information Model editor, build the address space following standard OPC UA composition rules:
-
Add Object types for equipment (e.g.
PumpType,MotorType). -
Instantiate objects of those types under a folder such as
Devices. - Add variables with the engineering properties you want to expose (RPM, temperature, current, setpoint).
-
Select the data type for each variable. Valid types in this pipeline are
Float,Double,Boolean, andString. See Section 9 - Datatype Compatibility for the integer caveat. - Add references (HasComponent, HasProperty) to connect variables to their parent object.
Validate the model inside SiOME before exporting. SiOME will flag references to non-existent types, missing data type nodes, and invalid browse names.
5. Exporting the Nodeset XML
SiOME writes the model in its native project format, but the Export menu produces a portable .xml file that follows the OPC Foundation UANodeSet schema. Confirm the following before export:
- The file extension is
.xml. Some SiOME versions emit a.xml.ttcn3artefact; rename it tomymodel.xmlor any filename you intend to use on the IOT2000. - The exported root element is
<UANodeSet xmlns=\"http://opcfoundation.org/UA/2011/03/UANodeSet.xsd\">or the 2017 revision. - The namespace URI matches the URI used in
app.jswhen binding variables.
6. Transferring the Application to the IOT2000
Download the OPC UA server attachment that ships with the IOT2000 example image, then transfer it via WinSCP over SFTP to a working directory such as /home/root/opcua.
- Open WinSCP and connect to the IOT2000 (default user
root, password defined during image flashing). - Drag the
.ziparchive to the IOT2000 working directory. - Open an SSH session (PuTTY or WinSCP terminal) and unzip the file:
unzip opcua_server.zip -d opcua_server - Copy your exported model into the program folder:
cp mymodel.xml opcua_server/mymodel.xml
7. Configuring the Server (app.js)
The shipped app.js is a small Node.js script that uses node-opcua to construct the server, load the XML, and expose the variables. The minimal structure is:
const opcua = require("node-opcua");
const os = require("os");
const path = require("path");
const fs = require("fs");
(async () => {
const hostname = os.hostname();
const server = new opcua.OPCUAServer({
port: 4840,
resourcePath: "/iot2000/opcua",
buildInfo: {
productName: "IOT2000-OPCUA-Server",
buildNumber: "1",
buildDate: new Date(),
},
serverCertificate: fs.readFileSync("certs/certificate.pem"),
privateKey: fs.readFileSync("certs/private_key.pem"),
applicationUri: "urn:siemens:iot2000:" + hostname,
});
await server.initialize();
// Line 12: replace 'mymodel.xml' if you renamed the export
const nodesetFile = path.join(__dirname, "mymodel.xml");
await server.addressSpace.importNodeset(nodesetFile);
await server.start();
const endpoint = server.endpoints[0].endpointDescriptions()[0].endpointUrl;
console.log("OPC UA server listening at:", endpoint);
})();
Key parameters to verify on Line 12 and adjacent lines:
| Parameter | Default | Notes |
|---|---|---|
port |
4840 | Standard OPC UA TCP port. Open in the IOT2000 firewall if accessing across subnets. |
resourcePath |
/iot2000/opcua |
Appended to the discovery URL. |
applicationUri |
hostname-based | Set explicitly to urn:siemens:iot2000:<hostname> to avoid UaExpert unknown host errors. |
| XML filename | mymodel.xml |
Edit Line 12 (or keep the filename) to match the exported model. |
8. Starting the Server
From the program folder, launch the application with Node.js:
cd /home/root/opcua/opcua_server
node app.js
On a successful boot, the console prints the discovery endpoint URL, typically:
OPC UA server listening at: opc.tcp://iot2000:4840/iot2000/opcua
Leave the SSH session open during verification, or use pm2 for background execution (Section 11).
9. Datatype Compatibility Constraints
Several field reports confirm that the node-opcua Nodeset XML loader has historically initialised only a subset of primitive types. The observed behaviour, derived from the deployment history, is:
| SiOME type | Resulting OPC UA DataType | Variable status after import |
|---|---|---|
Float |
Float (single) | Good, value present |
Double |
Double | Good, value present |
Boolean |
Boolean | Good |
String |
String | Good |
Int32 / UInt32
|
Int32 / UInt32 |
BadWaitingForInitialData on first read |
Int16 / UInt16
|
Int16 / UInt16 | Same failure pattern as Int32 |
The root cause is that addressSpace.importNodeset creates variable nodes without seeding a typed value, and the automatic install_optional_variable logic inside the older node-opcua build shipped with image V2.1.3 only seeds Float / Double defaults. Three field-proven mitigations exist:
-
Use Float / Double in the model. The simplest fix. Coerce any downstream integer to
Math.round(value)in the SCADA tag. -
Bind values in
app.jsexplicitly. Locate the imported variable node and callnode.setValueFromSource(new opcua.DataValue({value: new opcua.Variant({dataType: opcua.DataType.Int32, value: 0})}))to push an initial typed value into the address space. -
Upgrade
node-opcuato a release that includes theinstall_optional_variable_and_datatypepatch for integer nodes. Runnpm update node-opcuainside the program folder, then re-test.
node.readValue() followed by Math.trunc() coercion on the application side, plus an explicit setValueFromSource binding of an Int32 variant in app.js. This is the most reliable way to get true integer I/O without leaving the XML loader path.10. Certificate and Security Configuration
The example image ships with a self-signed certificate.pem and private_key.pem. Field deployments should regenerate them so the certificate subject matches the IOT2000 hostname, and so UaExpert can trust the server on first connect.
- Generate a 2048-bit RSA key:
openssl genrsa -out private_key.pem 2048 - Create a CSR:
openssl req -new -key private_key.pem -out iot.csr -subj "/CN=iot2000/O=Plant/OU=Automation" - Self-sign a certificate valid 3650 days:
openssl x509 -req -in iot.csr -signkey private_key.pem -out certificate.pem -days 3650 -sha256 -extfile v3.ext - Place both files in a
certs/subfolder of the application and update theserverCertificateandprivateKeypaths inapp.jsaccordingly.
Update v3.ext with the OPC UA application URI and the Subject Alternative Name containing the IOT2000 hostname. UaExpert will still raise an untrusted-certificate warning - accept it once and add the certificate to the trusted peers store.
11. Process Management with pm2
Running node app.js interactively ties the server to the SSH session. Use pm2 for supervised background execution and automatic reboot on crash:
npm install -g pm2
pm2 start app.js --name opcua-server
pm2 startup
pm2 save
pm2 startup emits a single shell command that registers the process supervisor as a systemd service. pm2 save snapshots the current process list so the server restarts on every IOT2000 reboot.
12. Verification with UaExpert
- Open UaExpert and add a new server. Use the discovery URL:
opc.tcp://iot2000:4840/iot2000/opcua(or replaceiot2000with the IP address). - Select None - None (Anonymous) for the first connection test, or use a username/password if enabled in
app.js. - Accept the self-signed certificate and add it to the trusted store.
- Connect. The custom namespace appears under Objects > [YourNamespace].
- Drag a variable onto the Data Access view. The Status column should read
Good; the value reflects the seed value (Float/Double) or your boundsetValueFromSourceinteger. - Write a test value from UaExpert and confirm the read-back matches.
13. Adding User Authentication (Optional)
To require a username and password, extend the server configuration with the userManager option:
const userManager = {
isValidUser: (username, password) => (username === "operator" && password === "changeme")
};
const server = new opcua.OPCUAServer({
userManager,
allowAnonymous: false,
// ...rest of config
});
Use a hashed credential store in production. UaExpert prompts for the credentials when allowAnonymous: false is set.
14. Verification Checklist
| Check | Pass criterion |
|---|---|
| Node.js process running |
pm2 list shows opcua-server as online
|
| Port listening |
netstat -ln | grep 4840 shows TCP LISTEN on 4840 |
| Endpoint discoverable | UaExpert lists the server under Local Discovery or custom URL |
| Custom namespace visible | Address space shows the SiOME objects and variables |
| Float/Double variable | Status Good, value updates on write |
| Integer variable | Status Good after explicit setValueFromSource or coercion fix |
| Process restart |
pm2 restart opcua-server cleanly stops and re-binds port 4840 |
| Boot persistence | Reboot IOT2000; server comes back within 30 s |
15. Troubleshooting Matrix
| Symptom | Likely cause | Resolution |
|---|---|---|
Cannot find module 'node-opcua' |
Wrong image version | Flash example image V2.1.3 or above |
| UaExpert error Unknown host iot2000 | Missing applicationUri / DNS resolution |
Set applicationUri in OPCUAServer and use the IOT2000 IP if DNS is missing |
Variable status BadWaitingForInitialData on Int32 / UInt32 |
Nodeset loader does not seed integer defaults | Apply Float/Double in the model, or bind with setValueFromSource and an explicit opcua.DataType.Int32 variant |
| UaExpert rejects the certificate | Hostname mismatch or expired self-signed cert | Regenerate with OpenSSL, set the SAN to the IOT2000 hostname, accept and trust in UaExpert |
| Endpoint URL not printed at boot | XML filename mismatch on Line 12 of app.js
|
Verify the path passed to addressSpace.importNodeset matches the deployed file |
| Server crashes silently after a few minutes | SSH session closing the Node.js process | Use pm2 start instead of running node app.js interactively |
Variable value is always null
|
XML type binding missing or wrong namespace URI | Re-export from SiOME and confirm namespace URIs match between XML and app.js
|
| Node-RED OPC UA server is sluggish | Not the recommended stack for the IOT2000 | Use the node-opcua direct path documented here; it is significantly faster |
16. Performance and Scaling Notes
Direct node-opcua outperforms the Node-RED OPC UA wrapper significantly on the IOT2040. The Quark CPU comfortably handles thousands of polled variables at a 1 Hz update rate, provided subscriptions are batched and the data source (Modbus, S7, MQTT) feeds the variables in setValueFromSource rather than rebuilding the address space on every change.
For larger models, increase the IOT2000 swap file before starting the server, and use a recent Node.js LTS for better garbage collection under sustained load.
17. Notes on Standards Conformance
The Nodeset XML format is defined by the OPC Foundation in the OPC UA Specification Part 6 - Mappings, with the normative schema maintained in the UA-Nodeset GitHub repository. Exporters from different vendors (SiOME, UaModeler, the TIA Portal S7-1500 exporter documented in the SIMATIC OPC UA documentation) all produce UANodeSet-compliant XML, which is why the same .xml can be re-imported into the IOT2000 with no transformation. Always validate the namespace URI before importing - the loader trusts the document and will not flag mismatches against the applicationUri.
Which image version is required on the SIMATIC IOT2000 for the OPC UA server to work?
Use example image V2.1.3 or above. Earlier images do not include the node-opcua library or the Nodeset XML loader.
Why do my Int32 and UInt32 variables show BadWaitingForInitialData after import?
The node-opcua Nodeset loader shipped with image V2.1.3 only seeds Float and Double defaults. Use Float/Double in SiOME, upgrade node-opcua, or bind the integer variable explicitly with setValueFromSource using an opcua.DataType.Int32 variant.
How do I regenerate the OPC UA server certificate for the IOT2000?
Use OpenSSL: generate a 2048-bit RSA key, create a CSR with the OPC UA application URI and hostname in the SAN, self-sign with a 10-year validity, and load the resulting certificate.pem and private_key.pem into the OPCUAServer constructor in app.js.
Can I require a username and password instead of anonymous access?
Yes. Add a userManager object with an isValidUser function and set allowAnonymous: false on the OPCUAServer instance. UaExpert will then prompt for credentials on connect.
How do I keep the OPC UA server running after the SSH session closes?
Install pm2, run pm2 start app.js --name opcua-server, then pm2 startup and pm2 save to enable supervised background execution and automatic restart on reboot.
Where can I obtain a normative Nodeset XML schema?
The OPC Foundation publishes the official UANodeSet XSD and reference nodesets in the UA-Nodeset GitHub repository. Validate any exported model against the current schema before deploying to the IOT2000.