Repository navigation
OPC UA Concepts
Applies to Milo 1.2.0-RC1. Source baseline 69cb25087, 2026-10-12.
An OPC UA server exposes its data as an address space, a graph of Nodes connected by references, and a client works with that address space through services such as Browse, Read, Write, and Call. Before you write code in the project from Getting started, this page explains the ideas the tutorials and the rest of the guide rely on. Its examples come from the thermostat you build next in Your first server. You do not need to implement the wire protocol to use Milo's SDKs.
A Node represents an Object, Variable, Method, type, or other modeled entity, and its attributes describe it. A Variable's Value attribute carries the process value, while its DataType, ValueRank, and ArrayDimensions describe the value's shape. A display name is a label for people, not an identifier.
References connect Nodes, and each reference has a type that says what the connection means. Organizes commonly groups Nodes into folders, HasComponent says that one Node is part of another, and HasTypeDefinition connects an instance to its type. The tutorial thermostat uses all three.
Objects (folder)
└── Organizes → Thermostat (Object, HasTypeDefinition BaseObjectType)
├── HasComponent → Temperature (Variable, Double)
├── HasComponent → Setpoint (Variable, Double)
└── HasComponent → AdjustSetpoint (Method)
Browsing follows these references, which makes the address space look like a tree in a client. It is a graph, though, so a folder is not a filesystem directory, and browsing a folder's children does not read their Values. See Address space for the server side and Browsing for client navigation.
A Method is a Node too. A client invokes it with the Call service by naming both the Object it acts on and the Method, which is why AdjustSetpoint is a component of Thermostat. The call returns a status and the Method's output arguments, as Methods shows.
A NodeId consists of a namespace index and a numeric, string, GUID, or opaque identifier. Written as text, ns=2;s=Temperature means string identifier Temperature in namespace index 2. It is an illustration, not a Node every server has. Namespace 0 contains the standard OPC UA definitions, which Milo exposes as NodeIds constants such as NodeIds.ObjectsFolder.
Indexes belong to a server's current namespace table. The tutorial program, for example, registers the URI urn:eclipse:milo:wiki and lets the server choose its index. Persist the namespace URI and identifier in application configuration, then resolve the URI through the connected client's NamespaceTable. Do not copy an index from another server or assume it survives a server reconfiguration. If the URI is absent, fail with a useful configuration error instead of silently using namespace 0.
An ExpandedNodeId can carry a namespace URI and a server index. ExpandedNodeId.toNodeId(NamespaceTable) returns the local NodeId as an Optional, which is empty when its namespace URI is unknown. A reference to another server may require a separate connection.
A QualifiedName is a namespace-qualified browse name. A browse path follows reference and browse-name steps, for example from Objects through Organizes to Thermostat and then through HasComponent to Setpoint. A browse path is neither a NodeId string nor a filesystem path, and translating one can return multiple or partial matches.
A SecureChannel carries messages between client and server. With a secured policy, Sign authenticates the applications and protects message integrity, and SignAndEncrypt also protects confidentiality. Security policy and message security mode are separate choices. See Policy, mode, and identity.
A Session represents a client interaction and carries its user identity. An application certificate identifies the application, and an anonymous, username, or X.509 user identity authenticates the user. These are separate decisions. An ApplicationUri is the persistent identity of an application instance and must match the URI in its application certificate.
Before it connects, a client discovers endpoints. Discovery returns endpoint descriptions, which pair server connection URLs with the server identity, transport, security policy and mode, and supported user-token policies. A user-token policy identifies an accepted identity-token type and its protection policy. The client selects a transport, security policy, message security mode, and compatible user-token policy, then connects. In Milo, connect() blocks and connectAsync() returns a future. Read, Write, Browse, Call, and subscription services then use that connection. See Connecting and Security policies.
A service can fail as a whole, or succeed while individual operations in it fail. A completed future therefore tells you the service returned, not that every requested operation succeeded. Preserve per-operation status, input order, and diagnostics when reporting errors.
A Variant holds a typed scalar, array, or matrix. A DataValue combines a Variant, a StatusCode, and optional source and server timestamps. Reading the thermostat's Temperature, for example, returns a DataValue with a Good StatusCode and a Variant that holds the Double 21.5.
Check the StatusCode before using the payload. Good, Uncertain, and Bad have different meanings, and each application must decide whether an Uncertain value suits its purpose. Null payloads and absent timestamps are legal in some results.
The source timestamp describes the value's time at the source, and the server timestamp describes when the server handled the value. A successful Read or a new server timestamp alone does not prove that the device delivered a fresh value. See Reading, Data types, and Data access.
When a client wants changes instead of single reads, it creates a Subscription and adds MonitoredItems to it. A MonitoredItem watches an attribute or events. For data items, the sampling interval governs acquisition, and the Subscription's publishing interval governs when the server sends queued notifications. The server may revise requested intervals and queue sizes, so read the revised values rather than treating requests as guarantees. A subscription on the thermostat's Setpoint, for example, reports the current value and then each change.
Filters and queue policies affect which changes are delivered, so a notification stream is not automatically a lossless event log or historian. Check each item's status. StatusCode.isOverflowSet() identifies the overflow bit on data-item values, and your application should also handle subscription notifications of unrecovered data loss.
Creating an item does not guarantee permission to read its value. Delete items and subscriptions you no longer need, and stop clients during shutdown. See Subscriptions, Sampling, and Historical access.
- Your first server: you create the Thermostat Object, its two Variables, and its Method, and connect them with references.
- Your first client: you resolve the tutorial namespace URI, read Temperature, and check the DataValue's status and type.
- Client SDK: you apply each service to the thermostat, from browsing and reading to subscriptions and Method calls.
- Server SDK: you see how each part of the thermostat is built and extend it with write validation, events, and alarms.
Build the thermostat next in Your first server, where these Nodes and references become code.
Related: Getting started · Client SDK · Server SDK · Data types
User guide for Milo 1.2.0-RC1 at source 69cb25087, 2026-10-12. Home · Getting started · Examples · Troubleshooting · Release notes
- Home
- Start here
- Client guide
- Server guide
- Feature guides
- Reference
- Operations
- Examples
- Release notes
- Contributing