Deploying OPC UA Nodeset XML Models on Siemens IOT2000

David Krause11 min read
OPC / OPC UASiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

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.

Confirmed runtime baseline. The example image V2.1.3 or above is required. Earlier images do not include the 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:

  1. Modelling tool (SiOME or FreeOpcUaModeler) - generates the address space and serialises it to a Nodeset XML conformant with the OPC Foundation UA-Nodeset schema.
  2. Nodeset XML file - portable container that follows the OPC UA UANodeSet XSD. It contains UAObject, UAVariable, UAReference, and UAType elements with namespace URIs.
  3. Node.js OPC UA server (app.js) - boots a OPCUAServer instance, registers the XML, and binds variables to a data source.
  4. 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.

  1. Install SiOME on a Windows engineering station.
  2. Launch the editor and create a new project; SiOME prompts for a target namespace URI (e.g. urn:siemens:iot2000:demo).
  3. Open the Information Model view to author the address space.
Alternative tooling. FreeOpcUaModeler (FlamencoSoft / Eclipse Paschim) is a free, cross-platform alternative that produces the same Nodeset XML. Use either tool, but keep the namespace URI identical between design and deployment so 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:

  1. Add Object types for equipment (e.g. PumpType, MotorType).
  2. Instantiate objects of those types under a folder such as Devices.
  3. Add variables with the engineering properties you want to expose (RPM, temperature, current, setpoint).
  4. Select the data type for each variable. Valid types in this pipeline are Float, Double, Boolean, and String. See Section 9 - Datatype Compatibility for the integer caveat.
  5. 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.ttcn3 artefact; rename it to mymodel.xml or 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.js when 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.

  1. Open WinSCP and connect to the IOT2000 (default user root, password defined during image flashing).
  2. Drag the .zip archive to the IOT2000 working directory.
  3. Open an SSH session (PuTTY or WinSCP terminal) and unzip the file: unzip opcua_server.zip -d opcua_server
  4. 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:

  1. Use Float / Double in the model. The simplest fix. Coerce any downstream integer to Math.round(value) in the SCADA tag.
  2. Bind values in app.js explicitly. Locate the imported variable node and call node.setValueFromSource(new opcua.DataValue({value: new opcua.Variant({dataType: opcua.DataType.Int32, value: 0})})) to push an initial typed value into the address space.
  3. Upgrade node-opcua to a release that includes the install_optional_variable_and_datatype patch for integer nodes. Run npm update node-opcua inside the program folder, then re-test.
Integer read/write fix (community-confirmed). A documented workaround uses 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.

  1. Generate a 2048-bit RSA key: openssl genrsa -out private_key.pem 2048
  2. Create a CSR: openssl req -new -key private_key.pem -out iot.csr -subj "/CN=iot2000/O=Plant/OU=Automation"
  3. 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
  4. Place both files in a certs/ subfolder of the application and update the serverCertificate and privateKey paths in app.js accordingly.

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

  1. Open UaExpert and add a new server. Use the discovery URL: opc.tcp://iot2000:4840/iot2000/opcua (or replace iot2000 with the IP address).
  2. Select None - None (Anonymous) for the first connection test, or use a username/password if enabled in app.js.
  3. Accept the self-signed certificate and add it to the trusted store.
  4. Connect. The custom namespace appears under Objects > [YourNamespace].
  5. Drag a variable onto the Data Access view. The Status column should read Good; the value reflects the seed value (Float/Double) or your bound setValueFromSource integer.
  6. 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.

Back to blog