-
Notifications
You must be signed in to change notification settings - Fork 433
Client
The entry point for access to a remote server is via an instance of OpcUaClient
. An instance can be created by providing an OpcUaClientConfig
to the static OpcUaClient#create()
function.
Creating an OpcUaClientConfig
requires, at a minimum, a valid EndpointDescription
for the server this client will connect to. An EndpointDescription
can be obtained by calling the GetEndpoints service and selecting an endpoint that matches the criteria of the client application.
Each EndpointDescription
contains details about an endpoint such as the endpoint URL, SecurityPolicy
, MessageSecurityMode
, and UserTokenPolicy
s supported by that endpoint.
Putting this together, the process looks like:
- Given an endpoint URL, call the GetEndpoints service to get a list of endpoints.
- Select an endpoint that meets your application criteria.
- Create and configure an
OpcUaClientConfig
using the selected endpoint. - Create an
OpcUaClient
instance and callconnect()
.
OpcUaClient#create
has an overload that combines the above steps into a single call. The following example creates an OpcUaClient
after selecting the first endpoint that uses SecurityPolicy.None
.
OpcUaClient client = OpcUaClient.create(
"opc.tcp://milo.digitalpetri.com:62541/milo",
endpoints ->
endpoints.stream()
.filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
.findFirst(),
configBuilder ->
configBuilder.build()
);
Now the OpcUaClient
just needs to be connected:
client.connect().get();
From this call forward the OpcUaClient
instance will automatically attempt to reconnect any time the connection is lost, including if the initial connect()
call failed. This behavior persists until disconnect()
is called.
Endpoints can be discovered by calling the GetEndpoints service on a remote server.
The client SDK provides a DiscoveryClient
with convenient static helper methods to make this easier.
A list of EndpointDescriptions
can be obtained from DiscoveryClient#getEndpoints
:
List<EndpointDescription> endpoints =
DiscoveryClient.getEndpoints(endpointUrl).get();
The above example showed an extremely minimal configuriation for a client, but properly configured client applications need a little more configuration to identify them.
- Application Name
- Application URI
- Product URI
In order to connect to an endpoint that uses security additional configuration is needed.
- Public+Private Key Pair
- Certificate or Certificate Chain
- Certificate Validator
Authentication is configured by setting an IdentityProvider
when building an OpcUaClientConfig
. You must configure an IdentityProvider
that the selected endpoint indicates it supports in its UserTokenPolicy
array.
No additional configuration is necessary to connect anonymously as AnonymousProvider
is the default IdentityProvider
when one is not explicitly configured.
The selected endpoint must contain a UserTokenPolicy
with a UserTokenType
of UserTokenType.Anonymous
.
To connect with a username and password set an instance of UsernameProvider
when building the OpcUaClientConfig
.
The selected endpoint must contain a UserTokenPolicy
with a UserTokenType
of UserTokenType.UserName
.
To connect using an X509Certificate
as the identity set an instance of X509IdentityProvider
The selected endpoint must contain a UserTokenPolicy
with a UserTokenType
of UserTokenType.Certificate
.
Browsing can be done in three ways:
-
AddressSpace#browse
, a higher level API that provides blocking or non-blocking options and a reasonable set of default parameters.Using the default
BrowseOptions
:AddressSpace addressSpace = client.getAddressSpace(); UaNode serverNode = addressSpace.getNode(Identifiers.Server); List<? extends UaNode> nodes = addressSpace.browseNodes(serverNode);
Using a custom
BrowseOptions
:AddressSpace addressSpace = client.getAddressSpace(); UaNode serverNode = addressSpace.getNode(Identifiers.Server); BrowseOptions browseOptions = addressSpace.getBrowseOptions().copy( b -> b.setReferenceType(BuiltinReferenceType.HasProperty) ); // just UaNodes referenced by HasProperty references List<? extends UaNode> nodes = addressSpace.browseNodes(serverNode, browseOptions);
-
UaNode#browse
andUaNode#browseNodes
, a higher level API that makes gettingReference
s or otherUaNode
instances referenced by aUaNode
instance easy.BrowseOptions browseOptions = BrowseOptions.builder() .setReferenceType(Identifiers.HasProperty) .build(); // Browse for HasProperty references List<ReferenceDescription> references = node.browse(browseOptions); // Browse for HasProperty references and get the UaNodes they target List<? extends UaNode> nodes = node.browseNodes(browseOptions);
-
OpcUaClient#browse
, a lower level API that takes parameters corresponding directly to those defined for the Browse service in the OPC UA spec.// Browse for forward hierarchal references from the Objects folder // that lead to other Object and Variable nodes. BrowseDescription browse = new BrowseDescription( Identifiers.ObjectsFolder, BrowseDirection.Forward, Identifiers.References, true, uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()), uint(BrowseResultMask.All.getValue()) ); BrowseResult browseResult = client.browse(browse).get();
Unlike the previous browse methods, this one does not check whether a continuation point was returned and follow up with calls to the BrowseNext service until all
Reference
s have been retrieved. Subsequent calls to BrowseNext are the responsibility of the caller.
Attributes can be read using either the high or low level APIs:
-
Using
UaNode
, which provides a variety of high level calls to read each attribute directly or provide anAttributeId
to read.UaVariableNode testNode = (UaVariableNode) addressSpace.getNode( new NodeId(2, "TestInt32") ); // Read the Value attribute DataValue value = testNode.readValue(); // Read the BrowseName attribute QualifiedName browseName = testNode.readBrowseName(); // Read the Description attribute, with timestamps and quality intact DataValue descriptionValue = testNode.readAttribute(AttributeId.Description);
-
Using
OpcUaClient#read
, a lower level API that takes parameters corresponding directly to those defined for the Read service in the OPC UA spec.List<ReadValueId> readValueIds = new ArrayList<>(); readValueIds.add( new ReadValueId( new NodeId(2, "TestInt32"), AttributeId.Value.uid(), null, // indexRange QualifiedName.NULL_VALUE ) ); ReadResponse readResponse = client.read( 0.0, // maxAge TimestampsToReturn.Both, readValueIds ).get();
Attributes can be written using either the high or low level APIs:
-
Using
UaNode
, which provides a variety of high level calls to write each attribute directly or provide anAttributeId
andDataValue
to write.UaVariableNode testNode = (UaVariableNode) addressSpace.getNode( new NodeId(2, "TestInt32") ); // Write the Value attribute; throws UaException if the write fails testNode.writeValue(new Variant(42)); // Most servers don't allow quality or timestamps to be written, // hence the use of DataValue.valueOnly(), but this method could // be used to write to a server that did support it if necessary StatusCode statusCode = testNode.writeAttribute( AttributeId.Value, DataValue.valueOnly(new Variant(42)) );
-
Using
OpcUaClient#write
, a lower level API that takes parameters corresponding directly to those defined for the Write service in the OPC UA spec.List<WriteValue> writeValues = new ArrayList<>(); writeValues.add( new WriteValue( new NodeId(2, "TestInt32"), AttributeId.Value.uid(), null, // indexRange DataValue.valueOnly(new Variant(42)) ) ); WriteResponse writeResponse = client.write(writeValues).get();
Methods can be called using either the high or low level APIs:
-
Using
UaObjectNode#callMethod
:UaObjectNode serverNode = addressSpace.getObjectNode(Identifiers.Server); Variant[] outputs = serverNode.callMethod( "GetMonitoredItems", new Variant[]{ new Variant(subscription.getSubscription().getSubscriptionId()) } );
-
Finding a
UaMethod
and invokingUaMethod#call
:UaObjectNode serverNode = addressSpace.getObjectNode(Identifiers.Server); UaMethod getMonitoredItems = serverNode.getMethod("GetMonitoredItems"); Variant[] outputs = getMonitoredItems.call( new Variant[]{ new Variant(subscription.getSubscription().getSubscriptionId()) } );
The
UaMethod
object also has a copy of the input and output defined by the method:Argument[] inputArguments = getMonitoredItems.getInputArguments(); Argument[] outputArguments = getMonitoredItems.getOutputArguments();
When a method does not define input or output arguments the corresponding
Argument[]
will be empty. -
OpcUaClient#call
, a lower level API That takes parameters corresponding directly to those defined by the Call service in the OPC UA spec:NodeId objectId = NodeId.parse("ns=2;s=HelloWorld"); NodeId methodId = NodeId.parse("ns=2;s=HelloWorld/sqrt(x)"); CallMethodRequest request = new CallMethodRequest( objectId, methodId, new Variant[]{new Variant(input)} ); return client.call(request).thenCompose(result -> { StatusCode statusCode = result.getStatusCode(); if (statusCode.isGood()) { Double value = (Double) l(result.getOutputArguments()).get(0).getValue(); return CompletableFuture.completedFuture(value); } else { StatusCode[] inputArgumentResults = result.getInputArgumentResults(); for (int i = 0; i < inputArgumentResults.length; i++) { logger.error("inputArgumentResults[{}]={}", i, inputArgumentResults[i]); } CompletableFuture<Double> f = new CompletableFuture<>(); f.completeExceptionally(new UaException(statusCode)); return f; } });
See
MethodExample.java
in theclient-examples
Maven module for the full source code.
ManagedSubscription
is the entry point in the higher level API for creating Subscriptions and MonitoredItems.
Create a ManagedSubscription
using an OpcUaClient
instance like this:
ManagedSubscription subscription = ManagedSubscription.create(client);
Optionally, specify a publishing interval when creating the subscription:
ManagedSubscription subscription = ManagedSubscription.create(client, 250.0);
Once the Subscription is created you can add a data or event listener to it. This listener will receive all data changes for all items belonging to the subscription.
subscription.addChangeListener(new ChangeListener() {
@Override
public void onDataReceived(List<ManagedDataItem> dataItems, List<DataValue> dataValues) {
// Each item in the dataItems list has a corresponding value at
// the same index in the dataValues list.
// Some items may appear multiple times if the item has a queue
// size greater than 1 and the value changed more than once within
// the publishing interval of the subscription.
// The items and values appear in the order of the changes.
}
});
A ManagedDataItem
represents a MonitoredItem monitoring an attribute value (as opposed to an Event).
Create a ManagedDataItem
using a ManagedSubscription
instance like this:
ManagedDataItem dataItem = subscription.createDataItem(Identifiers.Server_ServerStatus_CurrentTime);
if (!dataItem.getStatusCode.isGood()) {
throw new RuntimeException("uh oh!")
}
Always check the StatusCode after creating or modifying a ManagedDataItem
.
A ManagedEventItem
represents a MonitoredItem that will receive Events from an Objects EventNotifier attribute.
When subscribing to an Event a filter is required that specifies the types of events and fields that will be reported. Create a ManagedEventItem
using a ManagedSubscription
instance like this:
EventFilter eventFilter = new EventFilter(
new SimpleAttributeOperand[]{
new SimpleAttributeOperand(
Identifiers.BaseEventType,
new QualifiedName[]{new QualifiedName(0, "EventId")},
AttributeId.Value.uid(),
null),
new SimpleAttributeOperand(
Identifiers.BaseEventType,
new QualifiedName[]{new QualifiedName(0, "Time")},
AttributeId.Value.uid(),
null),
new SimpleAttributeOperand(
Identifiers.BaseEventType,
new QualifiedName[]{new QualifiedName(0, "Message")},
AttributeId.Value.uid(),
null)
},
new ContentFilter(null)
);
// Subscribe for Events from the Server Node's EventNotifier attribute.
ManagedEventItem eventItem = subscription.createEventItem(Identifiers.Server, eventFilter);
Events are reported to any ChangeListeners registered on the ManagedSubscription
:
subscription.addChangeListener(new ChangeListener() {
@Override
public void onEventReceived(List<ManagedEventItem> eventItems, List<Variant[]> eventFields) {
// Each item in the eventItems list has a corresponding set of
// event field values at the same index in the eventFields list.
// The number of fields and their meaning depend on the filter.
}
});
Because ManagedEventItem
s can be created with different filters, it may be easier to register an EventValueListener
directly on each event item instead:
eventItem.addEventValueListener(new ManagedEventItem.EventValueListener() {
@Override
public void onEventValueReceived(ManagedEventItem item, Variant[] value) {
// A new event arrived, do something with it.
}
});
OpcUaSubscriptionManager
is the entry point into the lower level API for creating Subscriptions and MonitoredItems.
OpcUaSubscriptionManager#createSubscription
is used to create a UaSubscription
object. MonitoredItems can then be created using UaSubscription#createMonitoredItems
. The parameters and data structures used in these API calls correspond directly to those defined by the Subscription and MonitoredItems services in the OPC UA spec.
See SubscriptionExample.java
in the client-examples
Maven module for an example.
Instances of UaNode
can be obtained from the AddressSpace
. When these instances are created for the first time all of the Node's attributes are read.
Every UaNode
contains a local copy of those attributes that can be accessed without causing a round-trip communication to the server. Use the get<attribute>()
methods to access these values.
Fresh attribute values can be obtained using read<attribute>()
methods and written using the write<attribute>()
methods. When a read or a write succeeds it also updates the local attribute value.
Local attribute values can be set using the set<attribute>()
methods, which affects only the local value. See #synchronize()
to write these values to the server.
UaNode#refresh(Set<AttributeId>)
will do a bulk read for the identified set of attributes and update the local attribute values if it succeeds.
UaNode#synchronize(Set<AttributeId>)
will do a bulk write for the identified set of attributes using the local attribute values.