You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by jf...@apache.org on 2019/05/19 19:39:00 UTC
[plc4x] 01/07: Add initial OPC UA project. This is a raw version of
the project without any tests or refactoring in a first fully functional
state.
This is an automated email from the ASF dual-hosted git repository.
jfeinauer pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 3d8a7e6f31b549ff5b4c85a28cbaff69eca281ef
Author: Matthias Milan Strljic <Ma...@gmail.com>
AuthorDate: Mon May 13 19:17:11 2019 +0200
Add initial OPC UA project. This is a raw version of the project without any tests or refactoring in a first fully functional state.
---
plc4j/drivers/opcua/pom.xml | 83 ++++
.../apache/plc4x/java/opcua/OPCUAPlcDriver.java | 99 +++++
.../opcua/connection/BaseOPCUAPlcConnection.java | 89 ++++
.../opcua/connection/OPCUAConnectionFactory.java | 36 ++
.../opcua/connection/OPCUATcpPlcConnection.java | 474 +++++++++++++++++++++
.../plc4x/java/opcua/protocol/OpcuaField.java | 115 +++++
.../java/opcua/protocol/model/OpcuaDataTypes.java | 43 ++
.../opcua/protocol/model/OpcuaIdentifierType.java | 26 ++
.../opcua/protocol/model/OpcuaPlcFieldHandler.java | 146 +++++++
.../protocol/model/OpcuaSubsriptionHandle.java | 76 ++++
.../services/org.apache.plc4x.java.spi.PlcDriver | 19 +
.../java/opcua/connection/ManualPLC4XOPCUA.java | 125 ++++++
plc4j/drivers/pom.xml | 1 +
13 files changed, 1332 insertions(+)
diff --git a/plc4j/drivers/opcua/pom.xml b/plc4j/drivers/opcua/pom.xml
new file mode 100644
index 0000000..69a36cd
--- /dev/null
+++ b/plc4j/drivers/opcua/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.plc4x</groupId>
+ <artifactId>plc4j-drivers</artifactId>
+ <version>0.4.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>plc4j-driver-opcua</artifactId>
+ <name>PLC4J: Driver: OPC UA</name>
+ <description>Implementation of a PLC4X driver able to speak with devices using the OPC UA protocol.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.plc4x</groupId>
+ <artifactId>plc4j-api</artifactId>
+ <version>0.4.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.plc4x</groupId>
+ <artifactId>plc4j-protocol-driver-base</artifactId>
+ <version>0.4.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.milo</groupId>
+ <artifactId>sdk-client</artifactId>
+ <version>0.3.0-M1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.milo</groupId>
+ <artifactId>stack-core</artifactId>
+ <version>0.3.0-M1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.milo</groupId>
+ <artifactId>stack-client</artifactId>
+ <version>0.3.0-M1</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ </dependencies>
+ <!--
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <configuration>
+ <usedDependencies combine.children="append">
+ <usedDependency>org.eclipse.milo:sdk-client</usedDependency>
+ </usedDependencies>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ -->
+</project>
\ No newline at end of file
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OPCUAPlcDriver.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OPCUAPlcDriver.java
new file mode 100644
index 0000000..68f3886
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OPCUAPlcDriver.java
@@ -0,0 +1,99 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.opcua;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.authentication.PlcAuthentication;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.messages.PlcReadRequest;
+import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.messages.PlcWriteRequest;
+import org.apache.plc4x.java.api.messages.PlcWriteResponse;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.opcua.connection.OPCUAConnectionFactory;
+import org.apache.plc4x.java.opcua.connection.OPCUATcpPlcConnection;
+import org.apache.plc4x.java.opcua.protocol.model.OpcuaPlcFieldHandler;
+import org.apache.plc4x.java.spi.PlcDriver;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Implementation of the OPC UA protocol, based on:
+ * - Eclipse Milo (https://github.com/eclipse/milo)
+ */
+public class OPCUAPlcDriver implements PlcDriver {
+
+
+
+ public static final Pattern INET_ADDRESS_PATTERN = Pattern.compile("tcp://(?<host>[\\w.-]+)(:(?<port>\\d*))?");
+ public static final Pattern OPCUA_URI_PATTERN = Pattern.compile("^opcua:(" + INET_ADDRESS_PATTERN + ")?" + "(?<params>/[\\w/]+)?");
+
+ private OPCUAConnectionFactory opcuaConnectionFactory;
+
+ public OPCUAPlcDriver() {
+ this.opcuaConnectionFactory = new OPCUAConnectionFactory();
+ }
+
+ public OPCUAPlcDriver(OPCUAConnectionFactory opcuaConnectionFactory) {
+ this.opcuaConnectionFactory = opcuaConnectionFactory;
+ }
+
+ @Override
+ public String getProtocolCode() {
+ return "opcua";
+ }
+
+ @Override
+ public String getProtocolName() {
+ return "OPC UA (TCP)";
+ }
+
+ @Override
+ public PlcConnection connect(String url) throws PlcConnectionException {
+ Matcher matcher = OPCUA_URI_PATTERN.matcher(url);
+
+ if (!matcher.matches()) {
+ throw new PlcConnectionException(
+ "Connection url doesn't match the format 'opcua:{type}//{port|host}'");
+ }
+
+ String host = matcher.group("host");
+ String portString = matcher.group("port");
+ Integer port = StringUtils.isNotBlank(portString) ? Integer.parseInt(portString) : null;
+ String params = matcher.group("params") != null ? matcher.group("params").substring(1) : null;
+
+ try {
+ return opcuaConnectionFactory.opcuaTcpPlcConnectionOf(InetAddress.getByName(host), port, params);
+ } catch (UnknownHostException e) {
+ throw new PlcConnectionException(e);
+ }
+ }
+
+ @Override
+ public PlcConnection connect(String url, PlcAuthentication authentication) throws PlcConnectionException {
+ throw new PlcConnectionException("opcua does not support Auth at this state");
+ }
+
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/BaseOPCUAPlcConnection.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/BaseOPCUAPlcConnection.java
new file mode 100644
index 0000000..b7221e2
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/BaseOPCUAPlcConnection.java
@@ -0,0 +1,89 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.opcua.connection;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.base.connection.AbstractPlcConnection;
+import org.apache.plc4x.java.base.messages.*;
+import org.apache.plc4x.java.opcua.protocol.model.OpcuaPlcFieldHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public abstract class BaseOPCUAPlcConnection extends AbstractPlcConnection implements PlcReader, PlcWriter, PlcSubscriber {
+
+ private static final Logger logger = LoggerFactory.getLogger(BaseOPCUAPlcConnection.class);
+
+ BaseOPCUAPlcConnection(String params) {
+
+ if (!StringUtils.isEmpty(params)) {
+ for (String param : params.split("&")) {
+ String[] paramElements = param.split("=");
+ String paramName = paramElements[0];
+ if (paramElements.length == 2) {
+ String paramValue = paramElements[1];
+ switch (paramName) {
+ default:
+ logger.debug("Unknown parameter {} with value {}", paramName, paramValue);
+ }
+ } else {
+ logger.debug("Unknown no-value parameter {}", paramName);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean canRead() {
+ return true;
+ }
+
+ @Override
+ public boolean canWrite() {
+ return true;
+ }
+
+ @Override
+ public PlcReadRequest.Builder readRequestBuilder() {
+ return new DefaultPlcReadRequest.Builder(this, new OpcuaPlcFieldHandler());
+ }
+
+ @Override
+ public PlcWriteRequest.Builder writeRequestBuilder() {
+ return new DefaultPlcWriteRequest.Builder(this, new OpcuaPlcFieldHandler());
+ }
+
+ @Override
+ public boolean canSubscribe() {
+ return true;
+ }
+
+ @Override
+ public PlcSubscriptionRequest.Builder subscriptionRequestBuilder() {
+ return new DefaultPlcSubscriptionRequest.Builder(this, new OpcuaPlcFieldHandler());
+ }
+
+ @Override
+ public PlcUnsubscriptionRequest.Builder unsubscriptionRequestBuilder() {
+ return new DefaultPlcUnsubscriptionRequest.Builder(this);
+ }
+
+
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OPCUAConnectionFactory.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OPCUAConnectionFactory.java
new file mode 100644
index 0000000..1bf0e60
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OPCUAConnectionFactory.java
@@ -0,0 +1,36 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.opcua.connection;
+
+import java.net.InetAddress;
+import java.util.Objects;
+
+public class OPCUAConnectionFactory {
+
+ public OPCUATcpPlcConnection opcuaTcpPlcConnectionOf(InetAddress address, Integer port, String params) {
+ Objects.requireNonNull(address);
+
+ if (port == null) {
+ return OPCUATcpPlcConnection.of(address, params);
+ } else {
+ return OPCUATcpPlcConnection.of(address, port, params);
+ }
+ }
+
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OPCUATcpPlcConnection.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OPCUATcpPlcConnection.java
new file mode 100644
index 0000000..7235226
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OPCUATcpPlcConnection.java
@@ -0,0 +1,474 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.opcua.connection;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
+
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.base.messages.*;
+import org.apache.plc4x.java.base.messages.items.*;
+import org.apache.plc4x.java.base.model.SubscriptionPlcField;
+import org.apache.plc4x.java.opcua.protocol.OpcuaField;
+import org.apache.plc4x.java.opcua.protocol.model.OpcuaSubsriptionHandle;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
+import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
+import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider;
+import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
+import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
+import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
+import org.eclipse.milo.opcua.stack.core.AttributeId;
+import org.eclipse.milo.opcua.stack.core.Identifiers;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.types.builtin.*;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
+import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
+import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
+import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
+import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.time.Duration;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
+
+public class OPCUATcpPlcConnection extends BaseOPCUAPlcConnection {
+
+ private static final int OPCUA_DEFAULT_TCP_PORT = 4840;
+
+ private static final Logger logger = LoggerFactory.getLogger(OPCUATcpPlcConnection.class);
+ private InetAddress address;
+ private int port;
+ private String params;
+ private OpcUaClient client;
+ private boolean isConnected = false;
+ private final AtomicLong clientHandles = new AtomicLong(1L);
+ private OPCUATcpPlcConnection(InetAddress address, String params) {
+ this( address, OPCUA_DEFAULT_TCP_PORT, params);
+ logger.info("Configured OPCUATcpPlcConnection with: host-name {}", address.getHostAddress());
+ }
+
+ public OPCUATcpPlcConnection(InetAddress address, int port, String params) {
+ this(params);
+ logger.info("Configured OPCUATcpPlcConnection with: host-name {}", address.getHostAddress());
+ this.address = address;
+ this.port = port;
+ this.params = params;
+ }
+
+ public OPCUATcpPlcConnection(String params) {
+ super(params);
+ }
+
+ public static OPCUATcpPlcConnection of(InetAddress address, String params) {
+ return new OPCUATcpPlcConnection(address, params);
+ }
+
+ public static OPCUATcpPlcConnection of(InetAddress address, int port, String params) {
+ return new OPCUATcpPlcConnection(address, port, params);
+ }
+
+
+
+ public InetAddress getRemoteAddress() {
+ return null;
+ }
+
+ @Override
+ public void connect() throws PlcConnectionException {
+ List<EndpointDescription> endpoints = null;
+
+ try {
+ endpoints = DiscoveryClient.getEndpoints(getEndpointUrl(address, port, params)).get();
+ } catch (Throwable ex) {
+ // try the explicit discovery endpoint as well
+ String discoveryUrl = getEndpointUrl(address, port, params);
+
+ if (!discoveryUrl.endsWith("/")) {
+ discoveryUrl += "/";
+ }
+ discoveryUrl += "discovery";
+
+ logger.info("Trying explicit discovery URL: {}", discoveryUrl);
+ try {
+ endpoints = DiscoveryClient.getEndpoints(discoveryUrl).get();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ EndpointDescription endpoint = null;
+ try {
+ endpoint = endpoints.stream()
+ .filter(e -> e.getSecurityPolicyUri().equals(getSecurityPolicy().getUri()))
+ .filter(endpointFilter())
+ .findFirst()
+ .orElseThrow(() -> new Exception("no desired endpoints returned"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ OpcUaClientConfig config = OpcUaClientConfig.builder()
+ .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
+ .setApplicationUri("urn:eclipse:milo:examples:client")
+ .setEndpoint(endpoint)
+ .setIdentityProvider(getIdentityProvider())
+ .setRequestTimeout(UInteger.valueOf(5000))
+ .build();
+
+ try {
+ this.client = OpcUaClient.create(config);
+ this.client.connect().get();
+ isConnected = true;
+ } catch (UaException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ isConnected = false;
+ } catch (ExecutionException e) {
+ isConnected = false;
+ }
+ }
+
+ @Override
+ public boolean isConnected() {
+ return client != null && isConnected;
+ }
+
+ @Override
+ public void close() throws Exception {
+ if(client != null){
+ client.disconnect().get();
+ isConnected = false;
+ }
+ }
+
+ @Override
+ public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest subscriptionRequest) {
+ InternalPlcSubscriptionRequest internalPlcSubscriptionRequest = checkInternal(subscriptionRequest, InternalPlcSubscriptionRequest.class);
+ CompletableFuture<PlcSubscriptionResponse> future = CompletableFuture.supplyAsync(() ->{
+ Map<String, Pair<PlcResponseCode, PlcSubscriptionHandle>> responseItems = internalPlcSubscriptionRequest.getSubscriptionPlcFieldMap().entrySet().stream()
+ .map(subscriptionPlcFieldEntry -> {
+ final String plcFieldName = subscriptionPlcFieldEntry.getKey();
+ final SubscriptionPlcField subscriptionPlcField = subscriptionPlcFieldEntry.getValue();
+ final OpcuaField field = (OpcuaField)Objects.requireNonNull(subscriptionPlcField.getPlcField());
+ long cycleTime = subscriptionPlcField.getDuration().orElse(Duration.ofSeconds(1)).toMillis();
+ NodeId idNode = generateNodeId(field);
+ ReadValueId readValueId = new ReadValueId(
+ idNode,
+ AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE);
+ UInteger clientHandle = uint(clientHandles.getAndIncrement());
+
+ MonitoringParameters parameters = new MonitoringParameters(
+ clientHandle,
+ (double) cycleTime, // sampling interval
+ null, // filter, null means use default
+ uint(1), // queue size
+ true // discard oldest
+ );
+ MonitoringMode monitoringMode;
+ switch (subscriptionPlcField.getPlcSubscriptionType()) {
+ case CYCLIC:
+ monitoringMode = MonitoringMode.Sampling;
+ break;
+ case CHANGE_OF_STATE:
+ monitoringMode = MonitoringMode.Reporting;
+ break;
+ case EVENT:
+ monitoringMode = MonitoringMode.Reporting;
+ break;
+ default: monitoringMode = MonitoringMode.Reporting;
+ }
+
+ PlcSubscriptionHandle subHandle = null;
+ PlcResponseCode responseCode = PlcResponseCode.ACCESS_DENIED;
+ try {
+ UaSubscription subscription = client.getSubscriptionManager().createSubscription(1000.0).get();
+
+ MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(
+ readValueId, monitoringMode, parameters);
+ List<MonitoredItemCreateRequest> requestList = new LinkedList<>();
+ requestList.add(request);
+ OpcuaSubsriptionHandle subsriptionHandle = new OpcuaSubsriptionHandle(plcFieldName, clientHandle);
+ BiConsumer<UaMonitoredItem, Integer> onItemCreated =
+ (item, id) -> item.setValueConsumer(subsriptionHandle::onSubscriptionValue);
+
+ List<UaMonitoredItem> items = subscription.createMonitoredItems(
+ TimestampsToReturn.Both,
+ requestList,
+ onItemCreated
+ ).get();
+
+ subHandle = subsriptionHandle;
+ responseCode = PlcResponseCode.OK;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+
+
+ return Pair.of(plcFieldName, Pair.of(responseCode, subHandle));
+ })
+ .collect(Collectors.toMap(Pair::getKey, Pair::getValue));
+ PlcSubscriptionResponse result = new DefaultPlcSubscriptionResponse(internalPlcSubscriptionRequest, responseItems);
+ return result;
+ });
+
+ return future;
+ }
+
+ @Override
+ public CompletableFuture<PlcUnsubscriptionResponse> unsubscribe(PlcUnsubscriptionRequest unsubscriptionRequest) {
+ InternalPlcUnsubscriptionRequest internalPlcUnsubscriptionRequest = checkInternal(unsubscriptionRequest, InternalPlcUnsubscriptionRequest.class);
+ internalPlcUnsubscriptionRequest.getInternalPlcSubscriptionHandles().forEach(o -> {
+ OpcuaSubsriptionHandle opcSubHandle = (OpcuaSubsriptionHandle) o;
+ try {
+ client.getSubscriptionManager().deleteSubscription(opcSubHandle.getClientHandle()).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ });
+
+ return null;
+ }
+
+ @Override
+ public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer, Collection<PlcSubscriptionHandle> handles) {
+ List<PlcConsumerRegistration> unregisters = new LinkedList<>();
+ handles.forEach(plcSubscriptionHandle -> unregisters.add(plcSubscriptionHandle.register(consumer)));
+
+ return () -> unregisters.forEach(PlcConsumerRegistration::unregister);
+ }
+
+ @Override
+ public void unregister(PlcConsumerRegistration registration) {
+ registration.unregister();
+ }
+
+ @Override
+ public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
+ CompletableFuture<PlcReadResponse> future = CompletableFuture.supplyAsync(() -> {
+ readRequest.getFields();
+ Map<String, Pair<PlcResponseCode, BaseDefaultFieldItem>> fields = new HashMap<>();
+ List<NodeId> readValueIds = new LinkedList<>();
+ List<PlcField> readPLCValues = readRequest.getFields();
+ for (PlcField field: readPLCValues) {
+ NodeId idNode = generateNodeId((OpcuaField) field);
+ readValueIds.add(idNode);
+ }
+
+ CompletableFuture<List<DataValue>> dataValueCompletableFuture = client.readValues(0.0, TimestampsToReturn.Both, readValueIds);
+ List<DataValue> readValues = null;
+ try {
+ readValues = dataValueCompletableFuture.get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ readValues = new LinkedList<>();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ readValues = new LinkedList<>();
+ }
+ for(int counter = 0; counter < readValues.size(); counter++){
+ PlcResponseCode resultCode = PlcResponseCode.OK;
+ BaseDefaultFieldItem stringItem = null;
+ if(readValues.get(counter).getStatusCode() != StatusCode.GOOD){
+ resultCode = PlcResponseCode.NOT_FOUND;
+ }else{
+ stringItem = encodeFieldItem(readValues.get(counter));
+
+ }
+ Pair<PlcResponseCode, BaseDefaultFieldItem> newPair = new ImmutablePair<>(resultCode, stringItem);
+ fields.put((String) readRequest.getFieldNames().toArray()[counter], newPair);
+
+
+ }
+ InternalPlcReadRequest internalPlcReadRequest = checkInternal(readRequest, InternalPlcReadRequest.class);
+ PlcReadResponse response = new DefaultPlcReadResponse(internalPlcReadRequest, fields );
+ return response;
+ });
+
+
+ return future;
+ }
+
+ private NodeId generateNodeId(OpcuaField uaField){
+ NodeId idNode = null;
+ switch (uaField.getIdentifierType()) {
+ case s:
+ idNode = new NodeId(uaField.getNamespace(), uaField.getIdentifier());
+ break;
+ case i:
+ idNode = new NodeId(uaField.getNamespace(), UInteger.valueOf(uaField.getIdentifier()));
+ break;
+ case g:
+ idNode = new NodeId(uaField.getNamespace(), UUID.fromString(uaField.getIdentifier()));
+ break;
+ case b:
+ idNode = new NodeId(uaField.getNamespace(), new ByteString(uaField.getIdentifier().getBytes()));
+ break;
+
+ default: idNode = new NodeId(uaField.getNamespace(), uaField.getIdentifier());
+ }
+
+ return idNode;
+ }
+
+ public static BaseDefaultFieldItem encodeFieldItem(DataValue value){
+ NodeId typeNode = value.getValue().getDataType().get();
+ Object objValue = value.getValue().getValue();
+
+ if(typeNode.equals(Identifiers.Boolean)){
+ return new DefaultBooleanFieldItem((Boolean)objValue);
+ }else if (typeNode.equals(Identifiers.ByteString)){
+ byte[] array = ((ByteString)objValue).bytes();
+ Byte[] byteArry = new Byte[array.length];
+ int counter = 0;
+ for (byte bytie: array
+ ) {
+ byteArry[counter] = bytie;
+ counter++;
+ }
+ return new DefaultByteArrayFieldItem(byteArry);
+ }else if (typeNode.equals(Identifiers.Integer)){
+ return new DefaultIntegerFieldItem((Integer)objValue);
+ }else if (typeNode.equals(Identifiers.Int16)){
+ return new DefaultShortFieldItem((Short)objValue);
+ }else if (typeNode.equals(Identifiers.Int32)){
+ return new DefaultIntegerFieldItem((Integer)objValue);
+ }else if (typeNode.equals(Identifiers.Int64)){
+ return new DefaultLongFieldItem((Long)objValue);
+ }else if (typeNode.equals(Identifiers.UInteger)){
+ return new DefaultLongFieldItem((Long)objValue);
+ }else if (typeNode.equals(Identifiers.UInt16)){
+ return new DefaultIntegerFieldItem(((UShort)objValue).intValue());
+ }else if (typeNode.equals(Identifiers.UInt32)){
+ return new DefaultLongFieldItem(((UInteger)objValue).longValue());
+ }else if (typeNode.equals(Identifiers.UInt64)){
+ return new DefaultBigIntegerFieldItem(new BigInteger(objValue.toString()));
+ }else if (typeNode.equals(Identifiers.Byte)){
+ return new DefaultShortFieldItem(Short.valueOf(objValue.toString()));
+ }else if (typeNode.equals(Identifiers.Float)){
+ return new DefaultFloatFieldItem((Float)objValue);
+ }else if (typeNode.equals(Identifiers.Double)){
+ return new DefaultDoubleFieldItem((Double)objValue);
+ }else if (typeNode.equals(Identifiers.SByte)){
+ return new DefaultByteFieldItem((Byte)objValue);
+ }else {
+ return new DefaultStringFieldItem(objValue.toString());
+ }
+
+ }
+
+ @Override
+ public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
+ CompletableFuture<PlcWriteResponse> future;
+ future = CompletableFuture.supplyAsync(() -> {
+
+ InternalPlcWriteRequest internalPlcWriteRequest = (InternalPlcWriteRequest) writeRequest;
+
+ List<PlcField> writePLCValues = writeRequest.getFields();
+ LinkedList<DataValue> values = new LinkedList<>();
+ LinkedList<NodeId> ids = new LinkedList<>();
+ LinkedList<String> names = new LinkedList<>();
+ Map<String, PlcResponseCode> fieldResponse = new HashMap<>();
+ for (String fieldName: writeRequest.getFieldNames()) {
+ OpcuaField uaField = (OpcuaField) writeRequest.getField(fieldName);
+ NodeId idNode = generateNodeId(uaField);
+ Variant var = new Variant(internalPlcWriteRequest.getFieldItem(fieldName).getObject(0));
+ DataValue value = new DataValue(var, null, null);
+ ids.add(idNode);
+ names.add(fieldName);
+ values.add(value);
+ }
+ CompletableFuture<List<StatusCode>> opcRequest =
+ client.writeValues(ids, values);
+ List<StatusCode> statusCodes = null;
+ try {
+ statusCodes = opcRequest.get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+
+ for(int counter = 0; counter < names.size(); counter++){
+ PlcResponseCode resultCode = PlcResponseCode.OK;
+ BaseDefaultFieldItem stringItem = null;
+ if(statusCodes != null && statusCodes.size() > counter){
+ if(statusCodes.get(counter).isGood()){
+ resultCode = PlcResponseCode.OK;
+ }else if(statusCodes.get(counter).isUncertain()){
+ resultCode = PlcResponseCode.NOT_FOUND;
+ }else {
+ resultCode = PlcResponseCode.ACCESS_DENIED;
+ }
+ }else{
+ resultCode = PlcResponseCode.ACCESS_DENIED;
+ }
+ fieldResponse.put(names.get(counter), resultCode);
+ }
+ InternalPlcWriteRequest internalPlcReadRequest = checkInternal(writeRequest, InternalPlcWriteRequest.class);
+ PlcWriteResponse response = new DefaultPlcWriteResponse(internalPlcReadRequest, fieldResponse);
+ return response;
+ });
+
+
+ return future;
+ }
+
+
+ private String getEndpointUrl(InetAddress address, Integer port, String params) {
+ return "opc.tcp://" + address.getHostAddress() +":" + port + "/" + params;
+ }
+
+ private Predicate<EndpointDescription> endpointFilter() {
+ return e -> true;
+ }
+
+ private SecurityPolicy getSecurityPolicy() {
+ return SecurityPolicy.None;
+ }
+
+ private IdentityProvider getIdentityProvider() {
+ return new AnonymousProvider();
+ }
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaField.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaField.java
new file mode 100644
index 0000000..317d6ad
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaField.java
@@ -0,0 +1,115 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+package org.apache.plc4x.java.opcua.protocol;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.opcua.protocol.model.OpcuaDataTypes;
+import org.apache.plc4x.java.opcua.protocol.model.OpcuaIdentifierType;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class OpcuaField implements PlcField {
+
+ private static final Pattern ADDRESS_PATTERN = Pattern.compile("^ns=(?<namespace>\\d+);(?<identifierType>[isgb])=((?<identifier>\\w+))?");
+
+ private final OpcuaIdentifierType identifierType;
+
+ private final int namespace;
+
+ private final String identifier;
+
+ protected OpcuaField(int namespace, OpcuaIdentifierType identifierType, String identifier) {
+ this.namespace = namespace;
+ this.identifier = identifier;
+ this.identifierType = identifierType;
+ if (this.identifier == null || this.namespace < 0) {
+ throw new IllegalArgumentException("Identifier can not be null or Namespace can not be lower then 0.");
+ }
+ }
+
+ private OpcuaField(Integer namespace, String identifier, OpcuaIdentifierType identifierType) {
+ this.identifier = Objects.requireNonNull(identifier);
+ this.identifierType = Objects.requireNonNull(identifierType);
+ this.namespace = namespace != null ? namespace : 0;
+ if (this.namespace < 0) {
+ throw new IllegalArgumentException("namespace must be greater then zero. Was " + this.namespace);
+ }
+ }
+
+ public static OpcuaField of(String address) {
+ Matcher matcher = ADDRESS_PATTERN.matcher(address);
+ if (!matcher.matches()) {
+ throw new PlcInvalidFieldException(address, ADDRESS_PATTERN, "{address}");
+ }
+ String identifier = matcher.group("identifier");
+
+ String identifierTypeString = matcher.group("identifierType");
+ OpcuaIdentifierType identifierType = OpcuaIdentifierType.valueOf(identifierTypeString);
+
+ String namespaceString = matcher.group("namespace");
+ Integer namespace = namespaceString != null ? Integer.valueOf(namespaceString) : 0;
+
+ return new OpcuaField(namespace, identifier, identifierType);
+ }
+
+
+ public static boolean matches(String address) {
+ return ADDRESS_PATTERN.matcher(address).matches();
+ }
+
+ public int getNamespace() {
+ return namespace;
+ }
+
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ public OpcuaIdentifierType getIdentifierType() {
+ return identifierType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof OpcuaField)) {
+ return false;
+ }
+ OpcuaField that = (OpcuaField) o;
+ return namespace == that.namespace && identifier.equals(that.identifier);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(namespace);
+ }
+
+ @Override
+ public String toString() {
+ return "OpcuaField{" +
+ "namespace=" + namespace +
+ "identifier=" + identifier +
+ '}';
+ }
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaDataTypes.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaDataTypes.java
new file mode 100644
index 0000000..e3f056e
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaDataTypes.java
@@ -0,0 +1,43 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.opcua.protocol.model;
+
+public enum OpcuaDataTypes {
+ BOOL,
+ SByte,
+ Byte,
+ Int16,
+ UInt16,
+ Int32,
+ UInt32,
+ Int64,
+ Uint64,
+ Float,
+ Double,
+ StatusCode,
+ String,
+ DateTime,
+ Guid,
+ ByteString,
+ XMLElement,
+ NodeId,
+ ExpandedNodeId,
+ QualifiedName
+
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaIdentifierType.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaIdentifierType.java
new file mode 100644
index 0000000..afcef2a
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaIdentifierType.java
@@ -0,0 +1,26 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.opcua.protocol.model;
+
+public enum OpcuaIdentifierType {
+ s,
+ i,
+ g,
+ b
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaPlcFieldHandler.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaPlcFieldHandler.java
new file mode 100644
index 0000000..81fe4f2
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaPlcFieldHandler.java
@@ -0,0 +1,146 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.opcua.protocol.model;
+
+
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.base.connection.DefaultPlcFieldHandler;
+import org.apache.plc4x.java.base.messages.items.*;
+import org.apache.plc4x.java.opcua.protocol.OpcuaField;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+public class OpcuaPlcFieldHandler extends DefaultPlcFieldHandler {
+
+ @Override
+ public PlcField createField(String fieldQuery) throws PlcInvalidFieldException {
+ if (OpcuaField.matches(fieldQuery)) {
+ return OpcuaField.of(fieldQuery);
+ }
+ throw new PlcInvalidFieldException(fieldQuery);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeString(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<String> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add(item.toString());
+ }
+ return new DefaultStringFieldItem(resultSet.toArray(new String[0]));
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeBoolean(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<Boolean> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add((Boolean) item);
+ }
+ return new DefaultBooleanFieldItem(resultSet.toArray(new Boolean[0]));
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeByte(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<Byte> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add((Byte) item);
+ }
+ return new DefaultByteFieldItem(resultSet.toArray(new Byte[0]));
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeShort(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<Short> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add((Short) item);
+ }
+ return new DefaultShortFieldItem(resultSet.toArray(new Short[0]));
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeInteger(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<Integer> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add((Integer) item);
+ }
+ return new DefaultIntegerFieldItem(resultSet.toArray(new Integer[0]));
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeBigInteger(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<BigInteger> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add((BigInteger) item);
+ }
+ return new DefaultBigIntegerFieldItem(resultSet.toArray(new BigInteger[0]));
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeLong(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<Long> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add((Long) item);
+ }
+ return new DefaultLongFieldItem(resultSet.toArray(new Long[0]));
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeFloat(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<Float> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add((Float) item);
+ }
+ return new DefaultFloatFieldItem(resultSet.toArray(new Float[0]));
+ }
+
+
+
+ @Override
+ public BaseDefaultFieldItem encodeDouble(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<Double> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add((Double) item);
+ }
+ return new DefaultDoubleFieldItem(resultSet.toArray(new Double[0]));
+ }
+
+
+ @Override
+ public BaseDefaultFieldItem encodeByteArray(PlcField field, Object[] values) {
+ OpcuaField adsField = (OpcuaField) field;
+ ArrayList<Byte[]> resultSet = new ArrayList<>();
+ for(Object item : values){
+ resultSet.add((Byte[]) item);
+ }
+ return new DefaultByteArrayFieldItem(resultSet.toArray(new Byte[0][0]));
+ }
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaSubsriptionHandle.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaSubsriptionHandle.java
new file mode 100644
index 0000000..4a03345
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/model/OpcuaSubsriptionHandle.java
@@ -0,0 +1,76 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.opcua.protocol.model;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
+import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
+import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.base.messages.DefaultPlcSubscriptionEvent;
+import org.apache.plc4x.java.base.messages.items.BaseDefaultFieldItem;
+import org.apache.plc4x.java.opcua.connection.OPCUATcpPlcConnection;
+import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+
+import java.awt.*;
+import java.time.Instant;
+import java.util.*;
+import java.util.function.Consumer;
+
+public class OpcuaSubsriptionHandle implements PlcSubscriptionHandle {
+ Set< Consumer<PlcSubscriptionEvent>> consumers = new HashSet<>();
+ String fieldName;
+ public UInteger getClientHandle() {
+ return clientHandle;
+ }
+
+ UInteger clientHandle;
+
+ public OpcuaSubsriptionHandle(String fieldName, UInteger clientHandle){
+ this.clientHandle = clientHandle;
+ }
+ @Override
+ public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer) {
+ consumers.add(consumer);
+ return () -> {consumers.remove(consumer);};
+ }
+
+ public void onSubscriptionValue(UaMonitoredItem item, DataValue value) {
+ consumers.forEach(plcSubscriptionEventConsumer -> {
+ PlcResponseCode resultCode = PlcResponseCode.OK;
+ BaseDefaultFieldItem stringItem = null;
+ if(value.getStatusCode() != StatusCode.GOOD){
+ resultCode = PlcResponseCode.NOT_FOUND;
+ }else{
+ stringItem = OPCUATcpPlcConnection.encodeFieldItem(value);
+
+ }
+ Map<String, Pair<PlcResponseCode, BaseDefaultFieldItem>> fields = new HashMap<>();
+ Pair<PlcResponseCode, BaseDefaultFieldItem> newPair = new ImmutablePair<>(resultCode, stringItem);
+ fields.put(fieldName, newPair);
+ PlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent(Instant.now(), fields);
+ plcSubscriptionEventConsumer.accept(event);
+ });
+ }
+
+}
diff --git a/plc4j/drivers/opcua/src/main/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver b/plc4j/drivers/opcua/src/main/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver
new file mode 100644
index 0000000..860acdc
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+org.apache.plc4x.java.opcua.OPCUAPlcDriver
diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/connection/ManualPLC4XOPCUA.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/connection/ManualPLC4XOPCUA.java
new file mode 100644
index 0000000..eb29494
--- /dev/null
+++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/connection/ManualPLC4XOPCUA.java
@@ -0,0 +1,125 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.plc4x.java.opcua.connection;
+
+import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.api.types.PlcSubscriptionType;
+import org.apache.plc4x.java.base.messages.DefaultPlcSubscriptionRequest;
+import org.apache.plc4x.java.base.model.SubscriptionPlcField;
+import org.apache.plc4x.java.opcua.protocol.OpcuaField;
+import org.apache.plc4x.java.opcua.protocol.model.OpcuaPlcFieldHandler;
+
+import java.math.BigInteger;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+public class ManualPLC4XOPCUA {
+ public static void main(String args[]){
+
+
+
+
+ OPCUATcpPlcConnection adsConnection = null;
+ OpcuaPlcFieldHandler fieldH = new OpcuaPlcFieldHandler();
+ PlcField field = fieldH.createField("ns=2;i=10855");
+ try {
+ adsConnection = (OPCUATcpPlcConnection)
+ new PlcDriverManager().getConnection("opcua:tcp://opcua.demo-this.com:51210/UA/SampleServer");
+
+ } catch (PlcConnectionException e) {
+ e.printStackTrace();
+ }
+ try {
+ PlcReadRequest.Builder builder = adsConnection.readRequestBuilder();
+ //builder.addItem("String", "ns=2;i=10855");
+ builder.addItem("Bool", "ns=2;i=10844");
+ builder.addItem("ByteString", "ns=2;i=10858");
+ builder.addItem("Byte", "ns=2;i=10846");
+ builder.addItem("Double", "ns=2;i=10854");
+ builder.addItem("Float", "ns=2;i=10853");
+ builder.addItem("Int16", "ns=2;i=10847");
+ builder.addItem("Int32", "ns=2;i=10849");
+ builder.addItem("Int64", "ns=2;i=10851");
+ builder.addItem("Integer", "ns=2;i=10869");
+ builder.addItem("SByte", "ns=2;i=10845");
+ builder.addItem("String", "ns=2;i=10855");
+ builder.addItem("UInt16", "ns=2;i=10848");
+ builder.addItem("UInt32", "ns=2;i=10850");
+ builder.addItem("UInt64", "ns=2;i=10852");
+ builder.addItem("UInteger", "ns=2;i=10870");
+
+ builder.addItem("DoesNotExists", "ns=2;i=12512623");
+
+ PlcReadRequest request = builder.build();
+ PlcReadResponse response = adsConnection.read(request).get();
+ Collection coll = response.getAllStrings("String");
+
+ PlcWriteRequest.Builder wBuilder = adsConnection.writeRequestBuilder();
+ //wBuilder.addItem("w-Bool", "ns=2;i=10844", "TEST");
+ //wBuilder.addItem("w-Bool", "ns=2;i=11012", true);
+ //--->> wBuilder.addItem("w-ByteString", "ns=2;i=10858", "TEST".getBytes());
+ wBuilder.addItem("w-String", "ns=2;i=10855", "TEST");
+
+ /*
+ wBuilder.addItem("w-Byte", "ns=2;i=10846", (byte)1);
+ wBuilder.addItem("w-Double", "ns=2;i=10854", (double)0.25);
+ wBuilder.addItem("w-Float", "ns=2;i=10853", (float)0.25);
+ wBuilder.addItem("w-Int16", "ns=2;i=10847", (short)12);
+ wBuilder.addItem("w-Int32", "ns=2;i=10849", (int)314);
+ wBuilder.addItem("w-Int64", "ns=2;i=10851", (long)123125);
+ wBuilder.addItem("w-Integer", "ns=2;i=10869", (int)314);
+ wBuilder.addItem("w-SByte", "ns=2;i=10845", (short)23);
+ wBuilder.addItem("w-String", "ns=2;i=10855", "TEST");
+ wBuilder.addItem("w-UInt16", "ns=2;i=10848", (int)222);
+ wBuilder.addItem("w-UInt32", "ns=2;i=10850", (long)21412);
+ wBuilder.addItem("w-UInt64", "ns=2;i=10852", new BigInteger("1245152"));
+ wBuilder.addItem("w-UInteger", "ns=2;i=10870", new BigInteger("1245152"));
+
+ */
+ PlcWriteRequest writeRequest = wBuilder.build();
+ PlcWriteResponse wResponse = adsConnection.write(writeRequest).get();
+
+ PlcSubscriptionResponse subResp = adsConnection.subscribe(new DefaultPlcSubscriptionRequest(
+ adsConnection,
+ new LinkedHashMap<>(
+ Collections.singletonMap("field1",
+ new SubscriptionPlcField(PlcSubscriptionType.CHANGE_OF_STATE, OpcuaField.of("ns=2;i=10855"), Duration.of(1, ChronoUnit.SECONDS)))
+ )
+ )).get();
+
+ Consumer<PlcSubscriptionEvent> consumer = plcSubscriptionEvent -> System.out.println(plcSubscriptionEvent.toString());
+ PlcConsumerRegistration registration = adsConnection.register(consumer, subResp.getSubscriptionHandles());
+ Thread.sleep(7000);
+ registration.unregister();
+ Thread.sleep(200000);
+ adsConnection.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/plc4j/drivers/pom.xml b/plc4j/drivers/pom.xml
index 34f60a2..cf47c07 100644
--- a/plc4j/drivers/pom.xml
+++ b/plc4j/drivers/pom.xml
@@ -39,6 +39,7 @@
<module>modbus</module>
<module>s7</module>
<module>simulated</module>
+ <module>opcua</module>
</modules>
</project>