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>