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 2018/11/24 18:36:59 UTC

[incubator-plc4x] branch feature/plc4j-scraper updated (18f1fd8 -> 57b707b)

This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a change to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git.


    from 18f1fd8  [plc4j-scraper] Improvements.
     new f6c1572  [plc4j-opm] enable write support (PLC4X-70)
     new fafbc79  [OPM] Added OPM Documentation.
     new 760c78e  [plc4j-pool] Fixed visibility of Interface.
     new b16cc9b  [plc4j-opm] fixed issue with detached entity.
     new 73f9b08  [plc4j-opm] deactivate caching by default and fixed test
     new eebf860  [plc4j-opm] fixed some sonar issues.
     new 7da0521  [plc4j-opm] fixed tests
     new c91a4cd  [plc4j-scraper] Added tests.
     new 2fcd8bd  [plc4j-scraper] Current state.
     new 6b8bddf  [plc4j-scraper] Fix for S7 Connection. Further implementation.
     new 57b707b  [plc4j-scraper] Working state.

The 11 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../plc4x/java/s7/connection/S7PlcConnection.java  |   2 +-
 .../java/base/connection/NettyPlcConnection.java   |   2 +-
 .../org/apache/plc4x/java/mock/MockDevice.java     |  19 +-
 .../java/org/apache/plc4x/java/mock/MockField.java |  13 +
 .../apache/plc4x/java/mock/MockFieldHandler.java   |  70 +++++
 .../org/apache/plc4x/java/mock/MockFieldItem.java} |   9 +-
 .../apache/plc4x/java/mock/PlcMockConnection.java  |  94 ++++--
 .../connectionpool/PooledPlcDriverManager.java     |   2 +-
 .../plc4x/java/opm/PlcEntityInterceptor.java       | 265 ++++++++++++++---
 .../apache/plc4x/java/opm/PlcEntityManager.java    | 115 ++++----
 .../java/org/apache/plc4x/java/opm/PlcField.java   |   4 +-
 .../apache/plc4x/java/opm/ConnectedEntityTest.java |  70 ++---
 .../org/apache/plc4x/java/opm/OpmUtilsTest.java    |  10 +-
 .../plc4x/java/opm/PlcEntityInterceptorTest.java   | 111 +++----
 .../java/opm/PlcEntityManagerComplexTest.java      |  41 ++-
 .../plc4x/java/opm/PlcEntityManagerTest.java       | 324 +++++++++++++--------
 .../plc4x/java/opm/SimpleAliasRegistryTest.java    |  11 +-
 plc4j/utils/scraper/pom.xml                        |  19 ++
 .../org/apache/plc4x/java/scraper/Scraper.java     | 153 ++++++++++
 .../org/apache/plc4x/java/scraper/ScraperTask.java | 195 +++++++++++++
 .../apache/plc4x/java/s7/ManualS7PlcDriverMT.java  |  52 +++-
 .../apache/plc4x/java/scraper/ScraperTaskTest.java | 111 +++++++
 .../org/apache/plc4x/java/scraper/ScraperTest.java | 107 +++++++
 .../resources/{logback.xml => logback-test.xml}    |   2 +-
 src/site/asciidoc/users/opm.adoc                   |  73 +++++
 src/site/site.xml                                  |   1 +
 26 files changed, 1501 insertions(+), 374 deletions(-)
 copy plc4j/{drivers/simulated/src/main/java/org/apache/plc4x/java/simulated/connection/TestFieldItem.java => protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldItem.java} (80%)
 create mode 100644 plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/Scraper.java
 create mode 100644 plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/ScraperTask.java
 create mode 100644 plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTaskTest.java
 create mode 100644 plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
 rename plc4j/utils/scraper/src/test/resources/{logback.xml => logback-test.xml} (97%)
 create mode 100644 src/site/asciidoc/users/opm.adoc


[incubator-plc4x] 11/11: [plc4j-scraper] Working state.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 57b707b92bb4620be5af56240e7aaa9b76e2d087
Author: Julian Feinauer <j....@pragmaticminds.de>
AuthorDate: Sat Nov 24 19:35:45 2018 +0100

    [plc4j-scraper] Working state.
---
 .../src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java       | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
index bb33c1a..a3107ea 100644
--- a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
+++ b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
@@ -60,6 +60,7 @@ class ScraperTest {
     void real_stuff() throws InterruptedException {
         PlcDriverManager driverManager = new PooledPlcDriverManager(pooledPlcConnectionFactory -> {
             GenericKeyedObjectPoolConfig<PlcConnection> config = new GenericKeyedObjectPoolConfig<>();
+            config.setJmxEnabled(true);
             config.setMaxWaitMillis(-1);
             config.setMaxTotal(3);
             config.setMinIdlePerKey(0);
@@ -82,7 +83,7 @@ class ScraperTest {
             )
         ));
 
-        Thread.sleep(300_000);
+        Thread.sleep(30_000_000);
     }
 
     @Test


[incubator-plc4x] 02/11: [OPM] Added OPM Documentation.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit fafbc79ce56eea8d8ab0d04c520dc2274f12cbb9
Author: Julian Feinauer <j....@pragmaticminds.de>
AuthorDate: Fri Nov 23 14:22:01 2018 +0100

    [OPM] Added OPM Documentation.
---
 src/site/asciidoc/users/opm.adoc | 73 ++++++++++++++++++++++++++++++++++++++++
 src/site/site.xml                |  1 +
 2 files changed, 74 insertions(+)

diff --git a/src/site/asciidoc/users/opm.adoc b/src/site/asciidoc/users/opm.adoc
new file mode 100644
index 0000000..e41c3c3
--- /dev/null
+++ b/src/site/asciidoc/users/opm.adoc
@@ -0,0 +1,73 @@
+//
+//  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.
+//
+
+== Object PLC Mapping
+
+
+=== What is Object PLC Mapping
+
+Object PLC Mapping (OPM) is heavily inspired by the Java Persistence API (JPA) [1].
+One of the main goal of the PLC4X Project is to make it easy to communicate with PLC devices to enable the development
+of applications that interact with PLCs.
+As many (or even most) of the application programmers are no experts in PLC Communication and protocols it should be as
+easy as possible to interact with PLCs without too much domain knowledge.
+This is exactly the reason why JPA was initialized many years ago to allow the interaction with a Database by simply
+calling methods on POJOs (Plain old Java Object).
+This is exactly what the OPM Module is for, to enable PLC communication by simply interacting with a POJO.
+
+=== Simple Example
+
+The following short code snippet shows how to read one value from a PLC via OPM.
+First, a _PlcEntityManager_ is instantiated, then a *connected* entity is fetched for a given PLC connection address.
+Connected means that all method calls of the entity are intersected and replaced by PLC calls.
+This is then used to print one value to the console.
+In the second snippet one can see how the Entity class looks. The address where to read the variable _pressure_ from is given
+in the _@PlcField_ annotation.
+[source,java]
+----
+public static void main(String[] args) {
+    PlcEntityManager em = new PlcEntityManager();
+    MyEntity entity = em.connect(MyEntity.class, "s7://...");
+    System.out.println(entity.getPressure());
+}
+----
+The class _MyEntity_ is given by
+[source,java]
+----
+@PlcEntity
+public class MyEntity {
+
+    @PlcField("DB01:DW01:LONG")
+    private double pressure;
+
+    public void MyEntity() {
+        // For OPM
+    }
+
+    public double getPressure() {
+        return pressure;
+    }
+}
+----
+
+=== Annotations
+
+=== More details
+
+=== References
+
+[1] https://www.oracle.com/technetwork/java/javaee/tech/persistence-jsp-140049.html
\ No newline at end of file
diff --git a/src/site/site.xml b/src/site/site.xml
index 557d00f..f30c01e 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -102,6 +102,7 @@
     <menu name="Users">
       <item name="Download" href="users/download.html"/>
       <item name="Getting Started" href="users/gettingstarted.html"/>
+      <item name="Object PLC Mapping (OPM)" href="users/opm.html"/>
       <item name="Industry 4.0 with Apache" href="users/industry40.html"/>
       <item name="Security" href="users/security.html"/>
     </menu>


[incubator-plc4x] 05/11: [plc4j-opm] deactivate caching by default and fixed test

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 73f9b085a6e19e893a3a03e7f3b0766913e0e50c
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Sat Nov 24 13:42:49 2018 +0100

    [plc4j-opm] deactivate caching by default and fixed test
---
 .../main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java | 3 +++
 .../opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java     | 2 +-
 .../test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java  | 8 ++++----
 3 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
index 346e07e..5d0573b 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
@@ -325,6 +325,9 @@ public class PlcEntityInterceptor {
     private static boolean needsToBeSynced(Map<String, Instant> lastSynced, Field field) {
         Validate.notNull(field);
         long cacheDurationMillis = field.getAnnotation(PlcField.class).cacheDurationMillis();
+        if (cacheDurationMillis < 0) {
+            return true;
+        }
         String fqn = getFqn(field);
         if (lastSynced.containsKey(fqn)) {
             Instant last = lastSynced.get(fqn);
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
index e6f16a4..5afbee7 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
@@ -31,5 +31,5 @@ import java.lang.annotation.Target;
 @Target({ElementType.FIELD})
 public @interface PlcField {
     String value();
-    long cacheDurationMillis() default 1000;
+    long cacheDurationMillis() default -1;
 }
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
index bf54364..cec9809 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
@@ -91,16 +91,16 @@ public class ConnectedEntityTest {
         // Trigger a fetch
         CachingEntity entity = entityManager.connect(CachingEntity.class, "mock:cached");
         // Trigger Many Fetches via getter
-        IntStream.range(1, 100).forEach(i -> entity.getField());
-        IntStream.range(1, 100).forEach(i -> entity.dummyMethod());
+        IntStream.range(1, 10).forEach(i -> entity.getField());
+        IntStream.range(1, 10).forEach(i -> entity.dummyMethod());
 
-        verify(mock, timeout(1_000).times(2)).read(any());
+        verify(mock, timeout(1_000).times(1)).read(any());
     }
 
     @PlcEntity
     public static class CachingEntity {
 
-        @PlcField(value = "address", cacheDurationMillis = 100)
+        @PlcField(value = "address", cacheDurationMillis = 500)
         private String field;
 
         public CachingEntity() {


[incubator-plc4x] 08/11: [plc4j-scraper] Added tests.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit c91a4cd4f4d65a26d9eb193d1bfa4b1b7a89933d
Author: Julian Feinauer <j....@pragmaticminds.de>
AuthorDate: Sat Nov 24 13:14:44 2018 +0100

    [plc4j-scraper] Added tests.
---
 plc4j/utils/scraper/pom.xml                        |  8 +-
 .../org/apache/plc4x/java/scraper/Scraper.java     | 90 ++++++++++++++++++++++
 .../apache/plc4x/java/s7/ManualS7PlcDriverMT.java  | 52 +++++++++++--
 .../org/apache/plc4x/java/scraper/ScraperTest.java | 76 ++++++++++++++++++
 4 files changed, 220 insertions(+), 6 deletions(-)

diff --git a/plc4j/utils/scraper/pom.xml b/plc4j/utils/scraper/pom.xml
index 9b2e372..b88e8a2 100644
--- a/plc4j/utils/scraper/pom.xml
+++ b/plc4j/utils/scraper/pom.xml
@@ -49,7 +49,13 @@
     </dependency>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-connection-pool</artifactId>
+      <artifactId>plc4j-connectionString-pool</artifactId>
+      <version>0.3.0-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-protocol-test</artifactId>
       <version>0.3.0-SNAPSHOT</version>
       <scope>test</scope>
     </dependency>
diff --git a/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/Scraper.java b/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/Scraper.java
new file mode 100644
index 0000000..dfd4801
--- /dev/null
+++ b/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/Scraper.java
@@ -0,0 +1,90 @@
+/*
+ * 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.scraper;
+
+import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
+import org.apache.plc4x.java.api.messages.PlcReadResponse;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * Plc Scraper that scrapes one source.
+ */
+public class Scraper implements Runnable {
+
+    private final PlcDriverManager driverManager;
+    private final String connectionString;
+    private final long requestTimeoutMs;
+    private final ResultHandler handler;
+
+    public Scraper(PlcDriverManager driverManager, String connectionString, long requestTimeoutMs, ResultHandler handler) {
+        this.driverManager = driverManager;
+        this.connectionString = connectionString;
+        this.requestTimeoutMs = requestTimeoutMs;
+        this.handler = handler;
+    }
+
+    @Override
+    public void run() {
+        // Does a single fetch
+        try (PlcConnection connection = driverManager.getConnection(connectionString)) {
+            PlcReadResponse response;
+            try {
+                response = connection.readRequestBuilder()
+                    .addItem("item1", "add1")
+                    .build()
+                    .execute()
+                    .get(requestTimeoutMs, TimeUnit.MILLISECONDS);
+            } catch (ExecutionException e) {
+                // Handle execution exception
+                handler.handleException(e);
+                return;
+            }
+            CompletableFuture.runAsync(() -> handler.handle(transformResponseToMap(response)));
+        } catch (PlcConnectionException e) {
+            throw new PlcRuntimeException("Unable to fetch", e);
+        } catch (Exception e) {
+            throw new PlcRuntimeException("Unexpected exception during fetch", e);
+        }
+    }
+
+    private Map<String, Object> transformResponseToMap(PlcReadResponse response) {
+        return response.getFieldNames().stream()
+            .collect(Collectors.toMap(
+                name -> name,
+                response::getObject
+            ));
+    }
+
+    public interface ResultHandler {
+
+        void handle(Map<String, Object> result);
+
+        void handleException(Exception e);
+
+    }
+}
diff --git a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/s7/ManualS7PlcDriverMT.java b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/s7/ManualS7PlcDriverMT.java
index 073c221..8822545 100644
--- a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/s7/ManualS7PlcDriverMT.java
+++ b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/s7/ManualS7PlcDriverMT.java
@@ -25,7 +25,8 @@ import org.apache.plc4x.java.api.PlcConnection;
 import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
 import org.apache.plc4x.java.api.messages.PlcReadResponse;
 import org.apache.plc4x.java.utils.connectionpool.PooledPlcDriverManager;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
@@ -36,14 +37,18 @@ import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Stream;
 
+/**
+ * Manual Test.
+ */
+@Disabled
 public class ManualS7PlcDriverMT {
 
     public static final String CONN_STRING = "s7://10.10.64.22/0/1";
     public static final String FIELD_STRING = "%DB225:DBW0:INT";
 
-
-//    public static final String CONN_STRING = "s7://10.10.64.20/0/1";
+    //    public static final String CONN_STRING = "s7://10.10.64.20/0/1";
 //    public static final String FIELD_STRING = "%DB3:DBD32:DINT";
+
     @Test
     public void simpleLoop() {
         PlcDriverManager plcDriverManager = new PooledPlcDriverManager();
@@ -79,13 +84,48 @@ public class ManualS7PlcDriverMT {
         executorService.awaitTermination(100, TimeUnit.SECONDS);
     }
 
+    @Test
+    public void parallelScheduledLoop() throws InterruptedException {
+        int period = 5;
+        PlcDriverManager plcDriverManager = new PooledPlcDriverManager();
+        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
+        DescriptiveStatistics statistics1 = new DescriptiveStatistics();
+        DescriptiveStatistics statistics2 = new DescriptiveStatistics();
+
+        int numberOfRuns = 1000;
+        AtomicInteger counter1 = new AtomicInteger(0);
+        AtomicInteger counter2 = new AtomicInteger(0);
+        executorService.scheduleAtFixedRate(() -> {
+            // System.out.println("Run: " + counter.get());
+            double timeNs = runSingleRequest(plcDriverManager);
+            statistics1.addValue(timeNs);
+            if (counter1.getAndIncrement() >= numberOfRuns) {
+                executorService.shutdown();
+            }
+        }, 0, period, TimeUnit.MILLISECONDS);
+        executorService.scheduleAtFixedRate(() -> {
+            // System.out.println("Run: " + counter.get());
+            double timeNs = runSingleRequest(plcDriverManager);
+            statistics2.addValue(timeNs);
+            if (counter2.getAndIncrement() >= numberOfRuns) {
+                executorService.shutdown();
+            }
+        }, 0, period, TimeUnit.MILLISECONDS);
+
+        executorService.awaitTermination(100, TimeUnit.SECONDS);
+        System.out.println("Statistics 1");
+        printStatistics(statistics1);
+        System.out.println("Statistics 2");
+        printStatistics(statistics2);
+    }
+
     private static Stream<Arguments> periodAndRus() {
         return Stream.of(
             Arguments.of(10, 100),
             Arguments.of(10, 1000),
             Arguments.of(100, 100),
             Arguments.of(100, 1000)
-            );
+        );
     }
 
     @ParameterizedTest
@@ -122,7 +162,6 @@ public class ManualS7PlcDriverMT {
                     }
                     if (counter.getAndIncrement() >= numberOfRuns) {
                         executorService.shutdown();
-                        ManualS7PlcDriverMT.this.printStatistics(statistics);
                     }
                 }, period, TimeUnit.MILLISECONDS);
             }
@@ -130,11 +169,14 @@ public class ManualS7PlcDriverMT {
 
         executorService.scheduleAtFixedRate(iteration, 0, period, TimeUnit.MILLISECONDS);
         executorService.awaitTermination(100, TimeUnit.SECONDS);
+        // Print statistics
+        ManualS7PlcDriverMT.this.printStatistics(statistics);
     }
 
     private double runSingleRequest(PlcDriverManager plcDriverManager) {
         long start = System.nanoTime();
         try (PlcConnection connection = plcDriverManager.getConnection(CONN_STRING)) {
+            System.out.println("Connection: " + connection);
             CompletableFuture<? extends PlcReadResponse> future = connection.readRequestBuilder()
                 .addItem("distance", FIELD_STRING)
                 .build()
diff --git a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
new file mode 100644
index 0000000..25b526a
--- /dev/null
+++ b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.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.scraper;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.base.messages.items.DefaultStringFieldItem;
+import org.apache.plc4x.java.mock.MockDevice;
+import org.apache.plc4x.java.mock.PlcMockConnection;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Map;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+public class ScraperTest {
+
+    @Test
+    public void scrape() throws PlcConnectionException {
+        PlcDriverManager driverManager = new PlcDriverManager();
+        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:scraper");
+        MockDevice mockDevice = Mockito.mock(MockDevice.class);
+        connection.setDevice(mockDevice);
+        when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("hallo")));
+
+        Scraper scraper = new Scraper(driverManager, "mock:scraper", 1_000, new Scraper.ResultHandler() {
+            @Override
+            public void handle(Map<String, Object> result) {
+                System.out.println(result);
+            }
+
+            @Override
+            public void handleException(Exception e) {
+                System.err.println(e);
+            }
+        });
+
+        scraper.run();
+    }
+
+    @Test
+    public void scrape_badResponseCode_shouldHandleException() throws PlcConnectionException {
+        PlcDriverManager driverManager = new PlcDriverManager();
+        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:scraper");
+        MockDevice mockDevice = Mockito.mock(MockDevice.class);
+        connection.setDevice(mockDevice);
+        when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.NOT_FOUND, new DefaultStringFieldItem("hallo")));
+
+        Scraper.ResultHandler handler = Mockito.mock(Scraper.ResultHandler.class);
+
+        Scraper scraper = new Scraper(driverManager, "mock:scraper", 1_000, null);
+
+        scraper.run();
+    }
+}
\ No newline at end of file


[incubator-plc4x] 10/11: [plc4j-scraper] Fix for S7 Connection. Further implementation.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 6b8bddff186309a25408523b6cc3683b7f80ef6b
Author: Julian Feinauer <j....@pragmaticminds.de>
AuthorDate: Sat Nov 24 18:45:39 2018 +0100

    [plc4j-scraper] Fix for S7 Connection. Further implementation.
---
 .../java/base/connection/NettyPlcConnection.java   |  2 +-
 .../org/apache/plc4x/java/scraper/ScraperTask.java | 23 ++++++++++++++++++++--
 .../org/apache/plc4x/java/scraper/ScraperTest.java | 18 +++++++++++------
 3 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/plc4j/protocols/driver-bases/base/src/main/java/org/apache/plc4x/java/base/connection/NettyPlcConnection.java b/plc4j/protocols/driver-bases/base/src/main/java/org/apache/plc4x/java/base/connection/NettyPlcConnection.java
index 2f992b1..fd1ac78 100644
--- a/plc4j/protocols/driver-bases/base/src/main/java/org/apache/plc4x/java/base/connection/NettyPlcConnection.java
+++ b/plc4j/protocols/driver-bases/base/src/main/java/org/apache/plc4x/java/base/connection/NettyPlcConnection.java
@@ -97,7 +97,7 @@ public abstract class NettyPlcConnection extends AbstractPlcConnection {
 
     @Override
     public boolean isConnected() {
-        return connected;
+        return connected && channel.isActive();
     }
 
     public Channel getChannel() {
diff --git a/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/ScraperTask.java b/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/ScraperTask.java
index 9bbf5e4..b762c7a 100644
--- a/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/ScraperTask.java
+++ b/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/ScraperTask.java
@@ -24,6 +24,8 @@ import org.apache.commons.lang3.time.StopWatch;
 import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
 import org.apache.plc4x.java.PlcDriverManager;
 import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.api.messages.PlcReadRequest;
 import org.apache.plc4x.java.api.messages.PlcReadResponse;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
@@ -83,7 +85,16 @@ public class ScraperTask implements Runnable {
         requestCounter.incrementAndGet();
         StopWatch stopWatch = new StopWatch();
         stopWatch.start();
-        try (PlcConnection connection = driverManager.getConnection(connectionString)) {
+        PlcConnection connection = null;
+        try {
+            CompletableFuture<PlcConnection> future = CompletableFuture.supplyAsync(() -> {
+                try {
+                    return driverManager.getConnection(connectionString);
+                } catch (PlcConnectionException e) {
+                    throw new PlcRuntimeException(e);
+                }
+            }, handlerService);
+            connection = future.get(10*requestTimeoutMs, TimeUnit.MILLISECONDS);
             LOGGER.trace("Connection to {} established: {}", connectionString, connection);
             PlcReadResponse response;
             try {
@@ -111,9 +122,16 @@ public class ScraperTask implements Runnable {
             // Handle response (Async)
             CompletableFuture.runAsync(() -> handle(transformResponseToMap(response)), handlerService);
         } catch (Exception e) {
-            failedStatistics.addValue(1.0);
             LOGGER.debug("Exception during scrape", e);
             handleException(e);
+        } finally {
+            if (connection != null) {
+                try {
+                    connection.close();
+                } catch (Exception e) {
+                    // intentionally do nothing
+                }
+            }
         }
     }
 
@@ -166,6 +184,7 @@ public class ScraperTask implements Runnable {
     }
 
     public void handleException(Exception e) {
+        LOGGER.debug("Exception: ", e);
         failedStatistics.addValue(1.0);
     }
 
diff --git a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
index 3a25f34..bb33c1a 100644
--- a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
+++ b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
@@ -21,6 +21,8 @@ package org.apache.plc4x.java.scraper;
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.pool2.KeyedObjectPool;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
 import org.apache.plc4x.java.PlcDriverManager;
 import org.apache.plc4x.java.api.PlcConnection;
 import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
@@ -51,16 +53,20 @@ class ScraperTest {
     public static final String CONN_STRING_TIM = "s7://10.10.64.22/0/1";
     public static final String FIELD_STRING_TIM = "%DB225:DBW0:INT";
 
-        public static final String CONN_STRING_CH = "s7://10.10.64.20/0/1";
+    public static final String CONN_STRING_CH = "s7://10.10.64.20/0/1";
     public static final String FIELD_STRING_CH = "%DB3:DBD32:DINT";
 
     @Test
     void real_stuff() throws InterruptedException {
-        PlcDriverManager driverManager = new PooledPlcDriverManager(new PooledPlcDriverManager.PoolCreator() {
-            @Override
-            public KeyedObjectPool<PoolKey, PlcConnection> createPool(PooledPlcConnectionFactory pooledPlcConnectionFactory) {
-                return null;
-            }
+        PlcDriverManager driverManager = new PooledPlcDriverManager(pooledPlcConnectionFactory -> {
+            GenericKeyedObjectPoolConfig<PlcConnection> config = new GenericKeyedObjectPoolConfig<>();
+            config.setMaxWaitMillis(-1);
+            config.setMaxTotal(3);
+            config.setMinIdlePerKey(0);
+            config.setBlockWhenExhausted(true);
+            config.setTestOnBorrow(true);
+            config.setTestOnReturn(true);
+            return new GenericKeyedObjectPool<>(pooledPlcConnectionFactory, config);
         });
 
         Scraper scraper = new Scraper(driverManager, Arrays.asList(


[incubator-plc4x] 07/11: [plc4j-opm] fixed tests

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 7da05212e7141bfa5225631e331f49b979797ebe
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Sat Nov 24 16:17:47 2018 +0100

    [plc4j-opm] fixed tests
---
 .../plc4x/java/opm/PlcEntityInterceptorTest.java   | 23 +++++++++++++++++++
 .../plc4x/java/opm/PlcEntityManagerTest.java       | 26 +++++++++-------------
 2 files changed, 34 insertions(+), 15 deletions(-)

diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
index 7ec939b..fadf6f4 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
@@ -25,12 +25,17 @@ import org.apache.plc4x.java.api.messages.PlcReadRequest;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.base.messages.DefaultPlcReadResponse;
 import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
 import org.mockito.stubbing.Answer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Collections;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -42,6 +47,7 @@ import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+@ExtendWith(MockitoExtension.class)
 public class PlcEntityInterceptorTest implements WithAssertions {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(PlcEntityInterceptorTest.class);
@@ -102,6 +108,23 @@ public class PlcEntityInterceptorTest implements WithAssertions {
             .hasMessage("Unable to identify field with name 'field1' for call to 'getField1'");
     }
 
+    @Nested
+    class Misc {
+
+        @Mock
+        Callable callable;
+
+        @Test
+        void missingCases() throws Exception {
+            when(callable.call()).then(invocation -> {
+                throw new PlcRuntimeException("broken");
+            });
+            assertThatThrownBy(() -> PlcEntityInterceptor.interceptGetter(null, this.getClass().getDeclaredMethod("missingCases"), callable, null, null, null, null, null))
+                .isInstanceOf(OPMException.class)
+                .hasMessage("Exception during forwarding call");
+        }
+    }
+
     private void runGetPlcResponseWIthException(Answer a) throws InterruptedException, ExecutionException, TimeoutException, OPMException {
         PlcReadRequest request = mock(PlcReadRequest.class);
         CompletableFuture future = mock(CompletableFuture.class);
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
index 40ee6ef..95f672f 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
@@ -21,11 +21,8 @@ package org.apache.plc4x.java.opm;
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.plc4x.java.PlcDriverManager;
-import org.apache.plc4x.java.api.PlcConnection;
 import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
 import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
-import org.apache.plc4x.java.api.messages.PlcReadRequest;
-import org.apache.plc4x.java.api.metadata.PlcConnectionMetadata;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.base.messages.items.DefaultStringFieldItem;
 import org.apache.plc4x.java.mock.MockDevice;
@@ -34,6 +31,8 @@ import org.assertj.core.api.WithAssertions;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
 
@@ -48,18 +47,15 @@ public class PlcEntityManagerTest implements WithAssertions {
 
     @Nested
     class Read {
+
+        @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+        PlcDriverManager driverManager;
+
         @Test
         public void throwsInvalidFieldException_rethrows() throws PlcConnectionException {
             // Prepare the Mock
-            PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
-            PlcConnection connection = Mockito.mock(PlcConnection.class);
-            PlcConnectionMetadata metadata = Mockito.mock(PlcConnectionMetadata.class);
-            PlcReadRequest.Builder builder = Mockito.mock(PlcReadRequest.Builder.class);
-            when(metadata.canRead()).thenReturn(true);
-            when(connection.readRequestBuilder()).thenReturn(builder);
-            when(connection.getMetadata()).thenReturn(metadata);
-            when(builder.build()).thenThrow(new PlcInvalidFieldException("field1"));
-            when(driverManager.getConnection(any())).thenReturn(connection);
+            when(driverManager.getConnection(any()).readRequestBuilder().build())
+                .thenThrow(new PlcInvalidFieldException("field1"));
 
             // Create Entity Manager
             PlcEntityManager entityManager = new PlcEntityManager(driverManager);
@@ -73,8 +69,8 @@ public class PlcEntityManagerTest implements WithAssertions {
         @Test
         public void unableToConnect_rethrows() throws PlcConnectionException {
             // Prepare the Mock
-            PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
-            when(driverManager.getConnection(any())).thenThrow(new PlcConnectionException(""));
+            when(driverManager.getConnection(any()))
+                .thenThrow(new PlcConnectionException(""));
 
             // Create Entity Manager
             PlcEntityManager entityManager = new PlcEntityManager(driverManager);
@@ -162,6 +158,7 @@ public class PlcEntityManagerTest implements WithAssertions {
 
     @Nested
     class Write {
+
         @Test
         void simpleWrite() throws Exception {
             SimpleAliasRegistry registry = new SimpleAliasRegistry();
@@ -201,7 +198,6 @@ public class PlcEntityManagerTest implements WithAssertions {
             PlcDriverManager driverManager = new PlcDriverManager();
             PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
             MockDevice mockDevice = Mockito.mock(MockDevice.class);
-            when(mockDevice.write(anyString(), any())).thenReturn(PlcResponseCode.OK);
             when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
             connection.setDevice(mockDevice);
 


[incubator-plc4x] 03/11: [plc4j-pool] Fixed visibility of Interface.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 760c78eac0a60095b44dfe99d973470b87b790cf
Author: Julian Feinauer <j....@pragmaticminds.de>
AuthorDate: Fri Nov 23 18:29:40 2018 +0100

    [plc4j-pool] Fixed visibility of Interface.
---
 .../apache/plc4x/java/utils/connectionpool/PooledPlcDriverManager.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManager.java b/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManager.java
index 9a7e5d7..a88794f 100644
--- a/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManager.java
+++ b/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManager.java
@@ -142,7 +142,7 @@ public class PooledPlcDriverManager extends PlcDriverManager {
     }
 
     @FunctionalInterface
-    interface PoolCreator {
+    public interface PoolCreator {
         KeyedObjectPool<PoolKey, PlcConnection> createPool(PooledPlcConnectionFactory pooledPlcConnectionFactory);
     }
 


[incubator-plc4x] 09/11: [plc4j-scraper] Current state.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 2fcd8bd7fa1148522dd8ebeca1c66b398c6ddd8e
Author: Julian Feinauer <j....@pragmaticminds.de>
AuthorDate: Sat Nov 24 17:40:58 2018 +0100

    [plc4j-scraper] Current state.
---
 .../plc4x/java/s7/connection/S7PlcConnection.java  |   2 +-
 plc4j/utils/scraper/pom.xml                        |  15 +-
 .../org/apache/plc4x/java/scraper/Scraper.java     | 161 +++++++++++++------
 .../org/apache/plc4x/java/scraper/ScraperTask.java | 176 +++++++++++++++++++++
 .../apache/plc4x/java/scraper/ScraperTaskTest.java | 111 +++++++++++++
 .../org/apache/plc4x/java/scraper/ScraperTest.java |  78 +++++----
 .../resources/{logback.xml => logback-test.xml}    |   2 +-
 7 files changed, 466 insertions(+), 79 deletions(-)

diff --git a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/connection/S7PlcConnection.java b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/connection/S7PlcConnection.java
index a56228e..63609d5 100644
--- a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/connection/S7PlcConnection.java
+++ b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/connection/S7PlcConnection.java
@@ -242,7 +242,7 @@ public class S7PlcConnection extends NettyPlcConnection implements PlcReader, Pl
             // If the remote didn't close the connection within the given time-frame, we have to take
             // care of closing the connection.
             catch (TimeoutException e) {
-                logger.info("Remote didn't close connection within the configured timeout of {}ms, shutting down actively.", CLOSE_DEVICE_TIMEOUT_MS, e);
+                logger.debug("Remote didn't close connection within the configured timeout of {} ms, shutting down actively.", CLOSE_DEVICE_TIMEOUT_MS, e);
                 channel.close();
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
diff --git a/plc4j/utils/scraper/pom.xml b/plc4j/utils/scraper/pom.xml
index b88e8a2..9956414 100644
--- a/plc4j/utils/scraper/pom.xml
+++ b/plc4j/utils/scraper/pom.xml
@@ -49,7 +49,7 @@
     </dependency>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-connectionString-pool</artifactId>
+      <artifactId>plc4j-connection-pool</artifactId>
       <version>0.3.0-SNAPSHOT</version>
       <scope>test</scope>
     </dependency>
@@ -59,6 +59,19 @@
       <version>0.3.0-SNAPSHOT</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <!--TODO Remove this-->
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-collections4</artifactId>
+    </dependency>
   </dependencies>
 
 
diff --git a/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/Scraper.java b/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/Scraper.java
index dfd4801..90da56a 100644
--- a/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/Scraper.java
+++ b/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/Scraper.java
@@ -19,72 +19,135 @@
 
 package org.apache.plc4x.java.scraper;
 
+import org.apache.commons.collections4.MultiValuedMap;
+import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math3.stat.descriptive.UnivariateStatistic;
 import org.apache.plc4x.java.PlcDriverManager;
-import org.apache.plc4x.java.api.PlcConnection;
-import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.api.messages.PlcReadResponse;
-
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
- * Plc Scraper that scrapes one source.
+ * Main class that orchestrates scraping.
  */
-public class Scraper implements Runnable {
+public class Scraper {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(Scraper.class);
 
+    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10,
+        new BasicThreadFactory.Builder()
+            .namingPattern("scheduler-thread-%d")
+            .daemon(true)
+            .build()
+    );
+    private final ExecutorService handlerPool = Executors.newFixedThreadPool(4,
+        new BasicThreadFactory.Builder()
+            .namingPattern("handler-thread-%d")
+            .daemon(true)
+            .build()
+    );
+    private final MultiValuedMap<ScrapeJob, ScraperTask> tasks = new ArrayListValuedHashMap<>();
     private final PlcDriverManager driverManager;
-    private final String connectionString;
-    private final long requestTimeoutMs;
-    private final ResultHandler handler;
+    private final List<ScrapeJob> jobs;
 
-    public Scraper(PlcDriverManager driverManager, String connectionString, long requestTimeoutMs, ResultHandler handler) {
+    public Scraper(PlcDriverManager driverManager, List<ScrapeJob> jobs) {
+        Validate.notEmpty(jobs);
         this.driverManager = driverManager;
-        this.connectionString = connectionString;
-        this.requestTimeoutMs = requestTimeoutMs;
-        this.handler = handler;
-    }
+        this.jobs = jobs;
+
+        // Schedule all jobs
+        LOGGER.info("Registering jobs...");
+        jobs.stream()
+            .flatMap(job -> job.connections.entrySet().stream()
+                .map(entry -> Triple.of(job, entry.getKey(), entry.getValue()))
+            )
+            .forEach(
+                tuple -> {
+                    LOGGER.debug("Register task for job {} for conn {} ({}) at rate {} ms",
+                        tuple.getLeft().name, tuple.getMiddle(), tuple.getRight(), tuple.getLeft().scrapeRate);
+                    ScraperTask task = new ScraperTask(driverManager,
+                        tuple.getLeft().name, tuple.getMiddle(), tuple.getRight(),
+                        tuple.getLeft().fields,
+                        1_000,
+                        handlerPool);
+                    // Add task to internal list
+                    tasks.put(tuple.getLeft(), task);
+                    scheduler.scheduleAtFixedRate(task,
+                        0, tuple.getLeft().scrapeRate, TimeUnit.MILLISECONDS);
+                }
+            );
 
-    @Override
-    public void run() {
-        // Does a single fetch
-        try (PlcConnection connection = driverManager.getConnection(connectionString)) {
-            PlcReadResponse response;
-            try {
-                response = connection.readRequestBuilder()
-                    .addItem("item1", "add1")
-                    .build()
-                    .execute()
-                    .get(requestTimeoutMs, TimeUnit.MILLISECONDS);
-            } catch (ExecutionException e) {
-                // Handle execution exception
-                handler.handleException(e);
-                return;
+        // Add statistics tracker
+        scheduler.scheduleAtFixedRate(() -> {
+            for (Map.Entry<ScrapeJob, ScraperTask> entry : tasks.entries()) {
+                DescriptiveStatistics statistics = entry.getValue().getLatencyStatistics();
+                String msg = String.format(Locale.ENGLISH, "Job statistics (%s, %s) number of requests: %d (%d success, %.1f %% failed, %.1f %% too slow), mean latency: %.2f ms, median: %.2f ms",
+                    entry.getValue().getJobName(), entry.getValue().getConnectionAlias(), entry.getValue().getRequestCounter(), entry.getValue().getSuccessfullRequestCounter(), entry.getValue().getPercentageFailed(), statistics.apply(new PercentageAboveThreshold(entry.getKey().scrapeRate*1e6)), statistics.getMean()*1e-6, statistics.getPercentile(50)*1e-6);
+                LOGGER.info(msg);
             }
-            CompletableFuture.runAsync(() -> handler.handle(transformResponseToMap(response)));
-        } catch (PlcConnectionException e) {
-            throw new PlcRuntimeException("Unable to fetch", e);
-        } catch (Exception e) {
-            throw new PlcRuntimeException("Unexpected exception during fetch", e);
-        }
+        }, 1_000, 1_000, TimeUnit.MILLISECONDS);
     }
 
-    private Map<String, Object> transformResponseToMap(PlcReadResponse response) {
-        return response.getFieldNames().stream()
-            .collect(Collectors.toMap(
-                name -> name,
-                response::getObject
-            ));
+    public static class ScrapeJob {
+
+        private final String name;
+        private final long scrapeRate;
+        /**
+         * alias -> connection-string
+         */
+        private final Map<String, String> connections;
+        /**
+         * alias -> field-query
+         */
+        private final Map<String, String> fields;
+
+        public ScrapeJob(String name, long scrapeRate, Map<String, String> connections, Map<String, String> fields) {
+            this.name = name;
+            this.scrapeRate = scrapeRate;
+            this.connections = connections;
+            this.fields = fields;
+        }
     }
 
-    public interface ResultHandler {
+    private static class PercentageAboveThreshold implements UnivariateStatistic {
+
+        private final double threshold;
 
-        void handle(Map<String, Object> result);
+        public PercentageAboveThreshold(double threshold) {
+            this.threshold = threshold;
+        }
 
-        void handleException(Exception e);
+        @Override
+        public double evaluate(double[] values) throws MathIllegalArgumentException {
+            long below = Arrays.stream(values)
+                .filter(val -> val <= threshold)
+                .count();
+            return (double)below/values.length;
+        }
 
+        @Override
+        public double evaluate(double[] values, int begin, int length) throws MathIllegalArgumentException {
+            long below = IntStream.range(begin, length)
+                .mapToDouble(i -> values[i])
+                .filter(val -> val > threshold)
+                .count();
+            return 100.0*below/length;
+        }
+
+        @Override
+        public UnivariateStatistic copy() {
+            return new PercentageAboveThreshold(threshold);
+        }
     }
 }
diff --git a/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/ScraperTask.java b/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/ScraperTask.java
new file mode 100644
index 0000000..9bbf5e4
--- /dev/null
+++ b/plc4j/utils/scraper/src/main/java/org/apache/plc4x/java/scraper/ScraperTask.java
@@ -0,0 +1,176 @@
+/*
+ * 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.scraper;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.time.StopWatch;
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
+import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.messages.PlcReadRequest;
+import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Plc Scraper that scrapes one source.
+ */
+public class ScraperTask implements Runnable {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ScraperTask.class);
+
+    private final PlcDriverManager driverManager;
+    private final String jobName;
+    private final String connectionAlias;
+    private final String connectionString;
+    private final Map<String, String> fields;
+    private final long requestTimeoutMs;
+    private final ExecutorService handlerService;
+
+    private final AtomicLong requestCounter = new AtomicLong(0);
+    private final AtomicLong successCounter = new AtomicLong(0);
+    private final DescriptiveStatistics latencyStatistics = new DescriptiveStatistics(1000);
+    private final DescriptiveStatistics failedStatistics = new DescriptiveStatistics(1000);
+
+    public ScraperTask(PlcDriverManager driverManager, String jobName, String connectionAlias, String connectionString,
+                       Map<String, String> fields, long requestTimeoutMs, ExecutorService handlerService) {
+        Validate.notNull(driverManager);
+        Validate.notBlank(jobName);
+        Validate.notBlank(connectionAlias);
+        Validate.notBlank(connectionString);
+        Validate.notEmpty(fields);
+        Validate.isTrue(requestTimeoutMs > 0);
+        this.driverManager = driverManager;
+        this.jobName = jobName;
+        this.connectionAlias = connectionAlias;
+        this.connectionString = connectionString;
+        this.fields = fields;
+        this.requestTimeoutMs = requestTimeoutMs;
+        this.handlerService = handlerService;
+    }
+
+    @Override
+    public void run() {
+        // Does a single fetch
+        LOGGER.trace("Start new scrape of task of job {} for connection {}", jobName, connectionAlias);
+        requestCounter.incrementAndGet();
+        StopWatch stopWatch = new StopWatch();
+        stopWatch.start();
+        try (PlcConnection connection = driverManager.getConnection(connectionString)) {
+            LOGGER.trace("Connection to {} established: {}", connectionString, connection);
+            PlcReadResponse response;
+            try {
+                PlcReadRequest.Builder builder = connection.readRequestBuilder();
+                fields.forEach((alias,qry) -> {
+                    LOGGER.trace("Requesting: {} -> {}", alias, qry);
+                    builder.addItem(alias,qry);
+                });
+                response = builder
+                    .build()
+                    .execute()
+                    .get(requestTimeoutMs, TimeUnit.MILLISECONDS);
+            } catch (ExecutionException e) {
+                // Handle execution exception
+                handleException(e);
+                return;
+            }
+            // Add statistics
+            stopWatch.stop();
+            latencyStatistics.addValue(stopWatch.getNanoTime());
+            failedStatistics.addValue(0.0);
+            successCounter.incrementAndGet();
+            // Validate response
+            validateResponse(response);
+            // Handle response (Async)
+            CompletableFuture.runAsync(() -> handle(transformResponseToMap(response)), handlerService);
+        } catch (Exception e) {
+            failedStatistics.addValue(1.0);
+            LOGGER.debug("Exception during scrape", e);
+            handleException(e);
+        }
+    }
+
+    private void validateResponse(PlcReadResponse response) {
+        Map<String, PlcResponseCode> failedFields = response.getFieldNames().stream()
+            .filter(name -> !PlcResponseCode.OK.equals(response.getResponseCode(name)))
+            .collect(Collectors.toMap(
+                Function.identity(),
+                response::getResponseCode
+            ));
+        if (failedFields.size() > 0) {
+            handleErrorResponse(failedFields);
+        }
+    }
+
+    private Map<String, Object> transformResponseToMap(PlcReadResponse response) {
+        return response.getFieldNames().stream()
+            .collect(Collectors.toMap(
+                name -> name,
+                response::getObject
+            ));
+    }
+
+    public String getJobName() {
+        return jobName;
+    }
+
+    public String getConnectionAlias() {
+        return connectionAlias;
+    }
+
+    public long getRequestCounter() {
+        return requestCounter.get();
+    }
+
+    public long getSuccessfullRequestCounter() {
+        return successCounter.get();
+    }
+
+    public DescriptiveStatistics getLatencyStatistics() {
+        return latencyStatistics;
+    }
+
+    public double getPercentageFailed() {
+        return 100.0*failedStatistics.getMean();
+    }
+
+    public void handle(Map<String, Object> result) {
+        LOGGER.debug("Handling result on gorgeous pool: {}", result);
+    }
+
+    public void handleException(Exception e) {
+        failedStatistics.addValue(1.0);
+    }
+
+    public void handleErrorResponse(Map<String, PlcResponseCode> failed) {
+        LOGGER.warn("Handling error responses: {}", failed);
+    }
+
+}
diff --git a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTaskTest.java b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTaskTest.java
new file mode 100644
index 0000000..546f3cb
--- /dev/null
+++ b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTaskTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.scraper;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.base.messages.items.DefaultStringFieldItem;
+import org.apache.plc4x.java.mock.MockDevice;
+import org.apache.plc4x.java.mock.PlcMockConnection;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.*;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class ScraperTaskTest implements WithAssertions {
+
+    @Mock
+    MockDevice mockDevice;
+
+    @Test
+    public void scrape() throws PlcConnectionException {
+        PlcDriverManager driverManager = new PlcDriverManager();
+        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:scraper");
+        connection.setDevice(mockDevice);
+        when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("hallo")));
+
+        ScraperTask scraperTask = new ScraperTask(driverManager, "job1", "m1", "mock:scraper", Collections.singletonMap("a", "b"),
+            1_000, ForkJoinPool.commonPool());
+
+        scraperTask.run();
+    }
+
+    @Nested
+    class Exceptions {
+
+        @Test
+        public void badResponseCode_shouldHandleException() throws PlcConnectionException {
+            // Given
+            PlcDriverManager driverManager = new PlcDriverManager();
+            PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:scraper");
+            connection.setDevice(mockDevice);
+            when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.NOT_FOUND, new DefaultStringFieldItem("hallo")));
+
+            ScraperTask scraperTask = new ScraperTask(driverManager, "job1", "m1",
+                "mock:scraper", Collections.singletonMap("a", "b"), 1_000, ForkJoinPool.commonPool());
+
+            // When
+            scraperTask.run();
+        }
+
+        @Mock
+        PlcDriverManager driverManager;
+
+        @Test
+        public void handleConnectionException() throws PlcConnectionException {
+            // Given
+            when(driverManager.getConnection(anyString())).thenThrow(new PlcConnectionException("stfu"));
+
+            ScraperTask scraperTask = new ScraperTask(driverManager, "job1", "m1", "mock:scraper", Collections.singletonMap("a", "b"),
+                1_000, ForkJoinPool.commonPool());
+
+            assertThatThrownBy(scraperTask::run)
+                .isInstanceOf(PlcRuntimeException.class)
+                .hasMessageContaining("Unable to fetch connection");
+        }
+
+        @Test
+        void runByScheduler_handledGracefully() throws PlcConnectionException {
+            when(driverManager.getConnection(anyString())).thenThrow(new PlcConnectionException("stfu"));
+            ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
+            ScraperTask scraperTask = new ScraperTask(driverManager, "job1", "m1", "mock:scraper", Collections.singletonMap("a", "b"),
+                1_000, ForkJoinPool.commonPool());
+
+            Future<?> future = pool.scheduleAtFixedRate(scraperTask, 0, 10, TimeUnit.MILLISECONDS);
+
+            assertThat(future).isNotDone();
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
index 25b526a..3a25f34 100644
--- a/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
+++ b/plc4j/utils/scraper/src/test/java/org/apache/plc4x/java/scraper/ScraperTest.java
@@ -20,57 +20,81 @@
 package org.apache.plc4x.java.scraper;
 
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.pool2.KeyedObjectPool;
 import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.PlcConnection;
 import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
-import org.apache.plc4x.java.base.messages.items.DefaultStringFieldItem;
+import org.apache.plc4x.java.base.messages.items.DefaultIntegerFieldItem;
 import org.apache.plc4x.java.mock.MockDevice;
 import org.apache.plc4x.java.mock.PlcMockConnection;
-import org.junit.Test;
-import org.mockito.Mockito;
+import org.apache.plc4x.java.utils.connectionpool.PoolKey;
+import org.apache.plc4x.java.utils.connectionpool.PooledPlcConnectionFactory;
+import org.apache.plc4x.java.utils.connectionpool.PooledPlcDriverManager;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
 
-import java.util.Map;
+import java.util.Arrays;
+import java.util.Collections;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
-public class ScraperTest {
+@ExtendWith(MockitoExtension.class)
+class ScraperTest {
 
-    @Test
-    public void scrape() throws PlcConnectionException {
-        PlcDriverManager driverManager = new PlcDriverManager();
-        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:scraper");
-        MockDevice mockDevice = Mockito.mock(MockDevice.class);
-        connection.setDevice(mockDevice);
-        when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("hallo")));
+    @Mock
+    MockDevice mockDevice;
 
-        Scraper scraper = new Scraper(driverManager, "mock:scraper", 1_000, new Scraper.ResultHandler() {
-            @Override
-            public void handle(Map<String, Object> result) {
-                System.out.println(result);
-            }
+    public static final String CONN_STRING_TIM = "s7://10.10.64.22/0/1";
+    public static final String FIELD_STRING_TIM = "%DB225:DBW0:INT";
+
+        public static final String CONN_STRING_CH = "s7://10.10.64.20/0/1";
+    public static final String FIELD_STRING_CH = "%DB3:DBD32:DINT";
 
+    @Test
+    void real_stuff() throws InterruptedException {
+        PlcDriverManager driverManager = new PooledPlcDriverManager(new PooledPlcDriverManager.PoolCreator() {
             @Override
-            public void handleException(Exception e) {
-                System.err.println(e);
+            public KeyedObjectPool<PoolKey, PlcConnection> createPool(PooledPlcConnectionFactory pooledPlcConnectionFactory) {
+                return null;
             }
         });
 
-        scraper.run();
+        Scraper scraper = new Scraper(driverManager, Arrays.asList(
+            new Scraper.ScrapeJob("job1",
+                10,
+                Collections.singletonMap("tim", CONN_STRING_TIM),
+                Collections.singletonMap("distance", FIELD_STRING_TIM)
+            ),
+            new Scraper.ScrapeJob("job2",
+                10,
+                Collections.singletonMap("chris", CONN_STRING_CH),
+                Collections.singletonMap("counter", FIELD_STRING_CH)
+            )
+        ));
+
+        Thread.sleep(300_000);
     }
 
     @Test
-    public void scrape_badResponseCode_shouldHandleException() throws PlcConnectionException {
+    void scraper_schedulesJob() throws InterruptedException, PlcConnectionException {
         PlcDriverManager driverManager = new PlcDriverManager();
-        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:scraper");
-        MockDevice mockDevice = Mockito.mock(MockDevice.class);
+        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:m1");
         connection.setDevice(mockDevice);
-        when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.NOT_FOUND, new DefaultStringFieldItem("hallo")));
 
-        Scraper.ResultHandler handler = Mockito.mock(Scraper.ResultHandler.class);
+        when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultIntegerFieldItem(1)));
 
-        Scraper scraper = new Scraper(driverManager, "mock:scraper", 1_000, null);
+        Scraper scraper = new Scraper(driverManager, Collections.singletonList(
+            new Scraper.ScrapeJob("job1",
+                10,
+                Collections.singletonMap("m1", "mock:m1"),
+                Collections.singletonMap("field1", "qry1")
+            )
+        ));
 
-        scraper.run();
+        Thread.sleep(5_000);
     }
 }
\ No newline at end of file
diff --git a/plc4j/utils/scraper/src/test/resources/logback.xml b/plc4j/utils/scraper/src/test/resources/logback-test.xml
similarity index 97%
rename from plc4j/utils/scraper/src/test/resources/logback.xml
rename to plc4j/utils/scraper/src/test/resources/logback-test.xml
index f0f2b2e..c562020 100644
--- a/plc4j/utils/scraper/src/test/resources/logback.xml
+++ b/plc4j/utils/scraper/src/test/resources/logback-test.xml
@@ -29,7 +29,7 @@
     </encoder>
   </appender>
 
-  <root level="ERROR">
+  <root level="INFO">
     <appender-ref ref="STDOUT"/>
   </root>
 


[incubator-plc4x] 06/11: [plc4j-opm] fixed some sonar issues.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit eebf86045cdda0ae77f04ccc0aa4a5c750073816
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Sat Nov 24 15:47:02 2018 +0100

    [plc4j-opm] fixed some sonar issues.
---
 .../plc4x/java/opm/PlcEntityInterceptor.java       |  3 +-
 .../apache/plc4x/java/opm/PlcEntityManager.java    |  5 +-
 .../apache/plc4x/java/opm/ConnectedEntityTest.java | 63 ++++++++++----------
 .../plc4x/java/opm/PlcEntityInterceptorTest.java   | 68 +++++++++-------------
 .../plc4x/java/opm/PlcEntityManagerTest.java       |  3 +
 5 files changed, 68 insertions(+), 74 deletions(-)

diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
index 5d0573b..ea0bb2e 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
@@ -87,7 +87,7 @@ public class PlcEntityInterceptor {
      * @return possible result of the original methods invocation
      * @throws OPMException Problems with plc / proxying
      */
-    @SuppressWarnings("unused")
+    @SuppressWarnings({"unused", "squid:S00107"})
     @RuntimeType
     public static Object interceptGetter(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable,
                                          @FieldValue(PlcEntityManager.PLC_ADDRESS_FIELD_NAME) String address,
@@ -151,6 +151,7 @@ public class PlcEntityInterceptor {
         }
     }
 
+    @SuppressWarnings({"unused", "squid:S00107"})
     @RuntimeType
     public static Object interceptSetter(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable,
                                          @FieldValue(PlcEntityManager.PLC_ADDRESS_FIELD_NAME) String address,
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
index 7188d4e..af0d2c0 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
@@ -24,6 +24,7 @@ import net.bytebuddy.description.modifier.Visibility;
 import net.bytebuddy.implementation.MethodDelegation;
 import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.api.messages.PlcReadRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -186,7 +187,7 @@ public class PlcEntityManager {
         try {
             return field.get(object);
         } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
+            throw new PlcRuntimeException(e);
         }
     }
 
@@ -194,7 +195,7 @@ public class PlcEntityManager {
         try {
             field.set(object, value);
         } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
+            throw new PlcRuntimeException(e);
         }
     }
 
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
index cec9809..b592fbf 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
@@ -21,13 +21,15 @@ package org.apache.plc4x.java.opm;
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.plc4x.java.PlcDriverManager;
-import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.base.messages.items.DefaultStringFieldItem;
 import org.apache.plc4x.java.mock.MockDevice;
 import org.apache.plc4x.java.mock.PlcMockConnection;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
 
 import java.util.stream.IntStream;
 
@@ -38,55 +40,54 @@ import static org.mockito.Mockito.*;
 /**
  * Tests for Connected Entities.
  */
+@ExtendWith(MockitoExtension.class)
 public class ConnectedEntityTest {
 
-    @Test
-    public void useCache() throws PlcConnectionException, OPMException {
-        // Mock
-        PlcDriverManager driverManager = new PlcDriverManager();
-        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:cached");
-        MockDevice mock = Mockito.mock(MockDevice.class);
-        when(mock.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("hallo")));
-        connection.setDevice(mock);
-        PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+    PlcDriverManager driverManager;
+
+    PlcMockConnection connection;
+
+    PlcEntityManager entityManager;
+
+    @Mock
+    MockDevice mockDevice;
 
+    @BeforeEach
+    void setUp() throws Exception {
+        driverManager = new PlcDriverManager();
+        connection = (PlcMockConnection) driverManager.getConnection("mock:cached");
+        when(mockDevice.read(any()))
+            .thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("hallo")));
+        connection.setDevice(mockDevice);
+        entityManager = new PlcEntityManager(driverManager);
+    }
+
+    @Test
+    void useCache() throws OPMException {
         // Trigger a fetch
         CachingEntity entity = entityManager.connect(CachingEntity.class, "mock:cached");
         // Trigger second fetch
         assertEquals("hallo", entity.getField());
 
-        verify(mock, timeout(1_000).times(1)).read(any());
+        verify(mockDevice, timeout(1_000).times(1)).read(any());
     }
 
     @Test
-    public void useCache_timeout_refetches() throws PlcConnectionException, OPMException, InterruptedException {
-        // Mock
-        PlcDriverManager driverManager = new PlcDriverManager();
-        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:cached");
-        MockDevice mock = Mockito.mock(MockDevice.class);
-        when(mock.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("hallo")));
-        connection.setDevice(mock);
-        PlcEntityManager entityManager = new PlcEntityManager(driverManager);
-
+    void useCache_timeout_refetches() throws OPMException, InterruptedException {
         // Trigger a fetch
         CachingEntity entity = entityManager.connect(CachingEntity.class, "mock:cached");
         Thread.sleep(500);
         // Trigger second fetch
         assertEquals("hallo", entity.getField());
 
-        verify(mock, timeout(1_000).times(2)).read(any());
+        verify(mockDevice, timeout(1_000).times(2)).read(any());
     }
 
     @Test
-    public void cache_manyRequests_onlyOneToPlc() throws PlcConnectionException, OPMException {
+    void cache_manyRequests_onlyOneToPlc() throws OPMException {
         // Mock
-        PlcDriverManager driverManager = new PlcDriverManager();
-        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:cached");
-        MockDevice mock = Mockito.mock(MockDevice.class);
-        when(mock.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("hallo")));
-        when(mock.write(any(), any())).thenReturn(PlcResponseCode.OK);
-        connection.setDevice(mock);
-        PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+        when(mockDevice.write(any(), any()))
+            .thenReturn(PlcResponseCode.OK);
 
         // Trigger a fetch
         CachingEntity entity = entityManager.connect(CachingEntity.class, "mock:cached");
@@ -94,7 +95,7 @@ public class ConnectedEntityTest {
         IntStream.range(1, 10).forEach(i -> entity.getField());
         IntStream.range(1, 10).forEach(i -> entity.dummyMethod());
 
-        verify(mock, timeout(1_000).times(1)).read(any());
+        verify(mockDevice, timeout(1_000).times(1)).read(any());
     }
 
     @PlcEntity
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
index d30fc92..7ec939b 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
@@ -26,7 +26,6 @@ import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.base.messages.DefaultPlcReadResponse;
 import org.assertj.core.api.WithAssertions;
 import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
 import org.mockito.stubbing.Answer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,41 +36,30 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class PlcEntityInterceptorTest implements WithAssertions {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(PlcEntityInterceptorTest.class);
 
-    private void runGetPlcResponseWIthException(Answer a) throws InterruptedException, ExecutionException, TimeoutException, OPMException {
-        PlcReadRequest request = Mockito.mock(PlcReadRequest.class);
-        CompletableFuture future = Mockito.mock(CompletableFuture.class);
-        when(future.get(anyLong(), any())).then(a);
-        when(request.execute()).thenReturn(future);
-
-        PlcEntityInterceptor.getPlcReadResponse(request);
-    }
-
     @Test
     public void getPlcReadResponse_catchesInterruptedException_rethrows() throws InterruptedException {
         AtomicBoolean exceptionWasThrown = new AtomicBoolean(false);
         // Run in different Thread
-        Thread thread = new Thread(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    runGetPlcResponseWIthException(invocation -> {
-                        throw new InterruptedException();
-                    });
-                } catch (InterruptedException | ExecutionException | TimeoutException e) {
-                    LOGGER.warn("Fetched exception", e);
-                } catch (OPMException e) {
-                    exceptionWasThrown.set(true);
-                }
+        Thread thread = new Thread(() -> {
+            try {
+                runGetPlcResponseWIthException(invocation -> {
+                    throw new InterruptedException();
+                });
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                LOGGER.warn("Fetched exception", e);
+                Thread.currentThread().interrupt();
+            } catch (OPMException e) {
+                exceptionWasThrown.set(true);
             }
         });
         thread.start();
@@ -89,9 +77,8 @@ public class PlcEntityInterceptorTest implements WithAssertions {
 
     @Test
     public void getPlcReadResponse_timeoutOnGet_rethrows() {
-        PlcReadRequest request = Mockito.mock(PlcReadRequest.class);
-        CompletableFuture future = new CompletableFuture<>();
-        when(request.execute()).thenReturn(future);
+        PlcReadRequest request = mock(PlcReadRequest.class);
+        when(request.execute()).thenReturn(new CompletableFuture<>());
 
         assertThatThrownBy(() -> PlcEntityInterceptor.getPlcReadResponse(request))
             .isInstanceOf(OPMException.class);
@@ -100,13 +87,9 @@ public class PlcEntityInterceptorTest implements WithAssertions {
     @Test
     public void getTyped_notOkResponse_throws() {
         DefaultPlcReadResponse response = new DefaultPlcReadResponse(null, Collections.singletonMap("field", Pair.of(PlcResponseCode.NOT_FOUND, null)));
-        String message = null;
-        try {
-            PlcEntityInterceptor.getTyped(Long.class, response, "field");
-        } catch (PlcRuntimeException e) {
-            message = e.getMessage();
-        }
-        assertEquals("Unable to read specified field 'field', response code was 'NOT_FOUND'", message);
+        assertThatThrownBy(() -> PlcEntityInterceptor.getTyped(Long.class, response, "field"))
+            .isInstanceOf(PlcRuntimeException.class)
+            .hasMessage("Unable to read specified field 'field', response code was 'NOT_FOUND'");
     }
 
     @Test
@@ -114,13 +97,18 @@ public class PlcEntityInterceptorTest implements WithAssertions {
         PlcEntityManager entityManager = new PlcEntityManager();
         BadEntity entity = entityManager.connect(BadEntity.class, "test:test");
 
-        String message = null;
-        try {
-            entity.getField1();
-        } catch (Exception e) {
-            message = e.getMessage();
-        }
-        assertEquals("Unable to identify field with name 'field1' for call to 'getField1'", message);
+        assertThatThrownBy(entity::getField1)
+            .isInstanceOf(OPMException.class)
+            .hasMessage("Unable to identify field with name 'field1' for call to 'getField1'");
+    }
+
+    private void runGetPlcResponseWIthException(Answer a) throws InterruptedException, ExecutionException, TimeoutException, OPMException {
+        PlcReadRequest request = mock(PlcReadRequest.class);
+        CompletableFuture future = mock(CompletableFuture.class);
+        when(future.get(anyLong(), any())).then(a);
+        when(request.execute()).thenReturn(future);
+
+        PlcEntityInterceptor.getPlcReadResponse(request);
     }
 
     @PlcEntity
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
index 940719b..40ee6ef 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
@@ -33,7 +33,9 @@ import org.apache.plc4x.java.mock.PlcMockConnection;
 import org.assertj.core.api.WithAssertions;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -41,6 +43,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
 
+@ExtendWith(MockitoExtension.class)
 public class PlcEntityManagerTest implements WithAssertions {
 
     @Nested


[incubator-plc4x] 04/11: [plc4j-opm] fixed issue with detached entity.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit b16cc9ba356495fd73f3a823d94e4a28e92c3c4b
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Sat Nov 24 13:24:59 2018 +0100

    [plc4j-opm] fixed issue with detached entity.
---
 .../java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java | 12 ++++++++++++
 .../java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java | 11 ++++++++---
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
index 1a8953b..346e07e 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
@@ -158,6 +158,18 @@ public class PlcEntityInterceptor {
                                          @FieldValue(PlcEntityManager.ALIAS_REGISTRY) AliasRegistry registry,
                                          @FieldValue(PlcEntityManager.LAST_FETCHED) Map<String, Instant> lastFetched,
                                          @Argument(0) Object argument) throws OPMException {
+        LOGGER.trace("Invoked method {} on connected PlcEntity {}", method.getName(), method.getDeclaringClass().getName());
+
+        // If "detached" (i.e. _driverManager is null) simply forward the call
+        if (driverManager == null) {
+            LOGGER.trace("Entity not connected, simply fowarding call");
+            try {
+                return callable.call();
+            } catch (Exception e) {
+                throw new OPMException("Exception during forwarding call", e);
+            }
+        }
+
         if (method.getName().startsWith("set")) {
             if (method.getParameterCount() != 1) {
                 throw new OPMException("Only setter with one arguments are supported");
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
index 91a6f9e..940719b 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
@@ -178,13 +178,18 @@ public class PlcEntityManagerTest implements WithAssertions {
             AliasEntity connected = entityManager.write(AliasEntity.class, "mock:test", object);
             connected.setAliasedField("changed2");
             connected.getAliasedField();
+            verify(mockDevice, times(0)).read(eq("real_field"));
+            verify(mockDevice, times(1)).write(eq("real_field"), any());
+            AliasEntity merge = entityManager.merge(AliasEntity.class, "mock:test", connected);
+            merge.setAliasedField("changed2");
+            merge.getAliasedField();
 
             // Assert that "field" was queried
             verify(mockDevice, times(1)).read(eq("real_field"));
-            verify(mockDevice, times(2)).write(eq("real_field"), any());
+            verify(mockDevice, times(3)).write(eq("real_field"), any());
 
-            entityManager.disconnect(connected);
-            assertThat(connected.getAliasedField()).isEqualTo("value");
+            entityManager.disconnect(merge);
+            assertThat(merge.getAliasedField()).isEqualTo("value");
         }
 
         @Test


[incubator-plc4x] 01/11: [plc4j-opm] enable write support (PLC4X-70)

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/plc4j-scraper
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit f6c1572c2b9acaa209df97c50b1d4ac5cf235eb2
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Sat Nov 24 13:04:10 2018 +0100

    [plc4j-opm] enable write support (PLC4X-70)
---
 .../org/apache/plc4x/java/mock/MockDevice.java     |  19 +-
 .../java/org/apache/plc4x/java/mock/MockField.java |  13 +
 .../apache/plc4x/java/mock/MockFieldHandler.java   |  70 +++++
 .../org/apache/plc4x/java/mock/MockFieldItem.java} |  25 +-
 .../apache/plc4x/java/mock/PlcMockConnection.java  |  94 ++++--
 .../plc4x/java/opm/PlcEntityInterceptor.java       | 247 +++++++++++++---
 .../apache/plc4x/java/opm/PlcEntityManager.java    | 114 ++++----
 .../java/org/apache/plc4x/java/opm/PlcField.java   |   2 +-
 .../apache/plc4x/java/opm/ConnectedEntityTest.java |   9 +-
 .../org/apache/plc4x/java/opm/OpmUtilsTest.java    |  10 +-
 .../plc4x/java/opm/PlcEntityInterceptorTest.java   |  22 +-
 .../java/opm/PlcEntityManagerComplexTest.java      |  41 +--
 .../plc4x/java/opm/PlcEntityManagerTest.java       | 316 ++++++++++++---------
 .../plc4x/java/opm/SimpleAliasRegistryTest.java    |  11 +-
 14 files changed, 693 insertions(+), 300 deletions(-)

diff --git a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockDevice.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockDevice.java
index b0e1075..999a4ac 100644
--- a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockDevice.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockDevice.java
@@ -20,10 +20,15 @@
 package org.apache.plc4x.java.mock;
 
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.plc4x.java.api.model.PlcField;
+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.items.BaseDefaultFieldItem;
 
+import java.util.Collection;
+import java.util.function.Consumer;
+
 /**
  * Mock Object to do assertions on.
  */
@@ -31,7 +36,15 @@ public interface MockDevice {
 
     Pair<PlcResponseCode, BaseDefaultFieldItem> read(String fieldQuery);
 
-    // TODO Implement this
-    // void write(String fieldQuery, BaseDefaultFieldItem value);
+    PlcResponseCode write(String fieldQuery, Object value);
+
+    Pair<PlcResponseCode, PlcSubscriptionHandle> subscribe(String fieldQuery);
+
+    void unsubscribe();
+
+    // TODO: this might not be right here as you are not really register at the device, rather on the connection
+    PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer, Collection<PlcSubscriptionHandle> handles);
 
+    // TODO: this might not be right here as you are not really register at the device, rather on the connection
+    void unregister(PlcConsumerRegistration registration);
 }
diff --git a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockField.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockField.java
index e570b2b..b7dccc9 100644
--- a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockField.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockField.java
@@ -21,16 +21,29 @@ package org.apache.plc4x.java.mock;
 
 import org.apache.plc4x.java.api.model.PlcField;
 
+import java.util.List;
+
 public class MockField implements PlcField {
 
     private final String fieldQuery;
 
+    private final List<Object> values;
+
     public MockField(String fieldQuery) {
         this.fieldQuery = fieldQuery;
+        values = null;
+    }
+
+    public MockField(String fieldQuery, List<Object> values) {
+        this.fieldQuery = fieldQuery;
+        this.values = values;
     }
 
     public String getFieldQuery() {
         return fieldQuery;
     }
 
+    public List<Object> getValues() {
+        return values;
+    }
 }
diff --git a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldHandler.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldHandler.java
index a8893e2..055769f 100644
--- a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldHandler.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldHandler.java
@@ -22,6 +22,7 @@ package org.apache.plc4x.java.mock;
 import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
 import org.apache.plc4x.java.api.model.PlcField;
 import org.apache.plc4x.java.base.connection.DefaultPlcFieldHandler;
+import org.apache.plc4x.java.base.messages.items.BaseDefaultFieldItem;
 
 public class MockFieldHandler extends DefaultPlcFieldHandler {
 
@@ -30,4 +31,73 @@ public class MockFieldHandler extends DefaultPlcFieldHandler {
         return new MockField(fieldQuery);
     }
 
+    @Override
+    public BaseDefaultFieldItem encodeBoolean(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeByte(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeShort(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeInteger(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeBigInteger(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeLong(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeFloat(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeBigDecimal(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeDouble(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeString(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeTime(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeDate(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeDateTime(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
+
+    @Override
+    public BaseDefaultFieldItem encodeByteArray(PlcField field, Object[] values) {
+        return new MockFieldItem(values);
+    }
 }
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldItem.java
similarity index 65%
copy from plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
copy to plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldItem.java
index f6b499d..2942aa5 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldItem.java
@@ -17,19 +17,18 @@
  under the License.
  */
 
-package org.apache.plc4x.java.opm;
+package org.apache.plc4x.java.mock;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import org.apache.plc4x.java.base.messages.items.BaseDefaultFieldItem;
+
+public class MockFieldItem extends BaseDefaultFieldItem<Object> {
+
+    public MockFieldItem(Object... values) {
+        super(values);
+    }
+
+    public Object getObject(int index) {
+        return getValue(index);
+    }
 
-/**
- * Field that is mapped
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.FIELD})
-public @interface PlcField {
-    String value();
-    long cacheDurationMillis() default 1000;
 }
diff --git a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/PlcMockConnection.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/PlcMockConnection.java
index 1972172..9dc802f 100644
--- a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/PlcMockConnection.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/PlcMockConnection.java
@@ -22,24 +22,24 @@ import org.apache.commons.lang3.Validate;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.plc4x.java.api.PlcConnection;
 import org.apache.plc4x.java.api.authentication.PlcAuthentication;
-import org.apache.plc4x.java.api.exceptions.PlcUnsupportedOperationException;
 import org.apache.plc4x.java.api.messages.*;
 import org.apache.plc4x.java.api.metadata.PlcConnectionMetadata;
+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.DefaultPlcReadRequest;
-import org.apache.plc4x.java.base.messages.DefaultPlcReadResponse;
-import org.apache.plc4x.java.base.messages.PlcReader;
+import org.apache.plc4x.java.base.messages.*;
 import org.apache.plc4x.java.base.messages.items.BaseDefaultFieldItem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Collection;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
-public class PlcMockConnection implements PlcConnection, PlcReader {
+public class PlcMockConnection implements PlcConnection, PlcReader, PlcWriter, PlcSubscriber {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(PlcMockConnection.class);
 
@@ -85,12 +85,12 @@ public class PlcMockConnection implements PlcConnection, PlcReader {
 
             @Override
             public boolean canWrite() {
-                return false;
+                return true;
             }
 
             @Override
             public boolean canSubscribe() {
-                return false;
+                return true;
             }
         };
     }
@@ -102,36 +102,82 @@ public class PlcMockConnection implements PlcConnection, PlcReader {
 
     @Override
     public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
-        return CompletableFuture.supplyAsync(new Supplier<PlcReadResponse>() {
+        return CompletableFuture.supplyAsync(() -> {
+            Validate.notNull(device, "No device is set in the mock connection!");
+            LOGGER.debug("Sending read request to MockDevice");
+            Map<String, Pair<PlcResponseCode, BaseDefaultFieldItem>> response = readRequest.getFieldNames().stream()
+                .collect(Collectors.toMap(
+                    Function.identity(),
+                    name -> device.read(((MockField) readRequest.getField(name)).getFieldQuery())
+                    )
+                );
+            return new DefaultPlcReadResponse((DefaultPlcReadRequest) readRequest, response);
+        });
+    }
 
-            @Override
-            public PlcReadResponse get() {
-                Validate.notNull(device, "No device is set in the mock connection!");
-                LOGGER.debug("Sending read request to MockDevice");
-                Map<String, Pair<PlcResponseCode, BaseDefaultFieldItem>> response = readRequest.getFieldNames().stream()
-                    .collect(Collectors.toMap(
-                        Function.identity(),
-                        name -> device.read(((MockField) readRequest.getField(name)).getFieldQuery())
-                        )
-                    );
-                return new DefaultPlcReadResponse((DefaultPlcReadRequest)readRequest, response);
-            }
+    @Override
+    public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
+        return CompletableFuture.supplyAsync(() -> {
+            Validate.notNull(device, "No device is set in the mock connection!");
+            LOGGER.debug("Sending write request to MockDevice");
+            Map<String, PlcResponseCode> response = writeRequest.getFieldNames().stream()
+                .collect(Collectors.toMap(
+                    Function.identity(),
+                    name -> device.write(((MockField) writeRequest.getField(name)).getFieldQuery(), ((MockField) writeRequest.getField(name)).getValues())
+                    )
+                );
+            return new DefaultPlcWriteResponse((DefaultPlcWriteRequest) writeRequest, response);
+        });
+    }
+
+    @Override
+    public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest subscriptionRequest) {
+        return CompletableFuture.supplyAsync(() -> {
+            Validate.notNull(device, "No device is set in the mock connection!");
+            LOGGER.debug("Sending subsribe request to MockDevice");
+            Map<String, Pair<PlcResponseCode, PlcSubscriptionHandle>> response = subscriptionRequest.getFieldNames().stream()
+                .collect(Collectors.toMap(
+                    Function.identity(),
+                    name -> device.subscribe(((MockField) subscriptionRequest.getField(name)).getFieldQuery())
+                    )
+                );
+            return new DefaultPlcSubscriptionResponse((DefaultPlcSubscriptionRequest) subscriptionRequest, response);
         });
     }
 
     @Override
+    public CompletableFuture<PlcUnsubscriptionResponse> unsubscribe(PlcUnsubscriptionRequest unsubscriptionRequest) {
+        return CompletableFuture.supplyAsync(() -> {
+            Validate.notNull(device, "No device is set in the mock connection!");
+            LOGGER.debug("Sending subsribe request to MockDevice");
+            device.unsubscribe();
+            return new DefaultPlcUnsubscriptionResponse((DefaultPlcUnsubscriptionRequest) unsubscriptionRequest);
+        });
+    }
+
+    @Override
+    public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer, Collection<PlcSubscriptionHandle> handles) {
+        return device.register(consumer, handles);
+    }
+
+    @Override
+    public void unregister(PlcConsumerRegistration registration) {
+        device.unregister(registration);
+    }
+
+    @Override
     public PlcWriteRequest.Builder writeRequestBuilder() {
-        throw new PlcUnsupportedOperationException("Write not supported by Mock Driver");
+        return new DefaultPlcWriteRequest.Builder(this, new MockFieldHandler());
     }
 
     @Override
     public PlcSubscriptionRequest.Builder subscriptionRequestBuilder() {
-        throw new PlcUnsupportedOperationException("Subscription not supported by Mock Driver");
+        return new DefaultPlcSubscriptionRequest.Builder(this, new MockFieldHandler());
     }
 
     @Override
     public PlcUnsubscriptionRequest.Builder unsubscriptionRequestBuilder() {
-        throw new PlcUnsupportedOperationException("Subscription not supported by Mock Driver");
+        return new DefaultPlcUnsubscriptionRequest.Builder(this);
     }
 
     public PlcAuthentication getAuthentication() {
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
index 7aee487..1a8953b 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
@@ -29,8 +29,7 @@ import org.apache.plc4x.java.PlcDriverManager;
 import org.apache.plc4x.java.api.PlcConnection;
 import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
 import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.api.messages.PlcReadRequest;
-import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.messages.*;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -53,9 +52,9 @@ import java.util.concurrent.TimeoutException;
 
 /**
  * Interceptor for dynamic functionality of @{@link PlcEntity}.
- * Basically, its {@link #intercept(Object, Method, Callable, String, PlcDriverManager, AliasRegistry, Map)} method is called for each
+ * Basically, its {@link #interceptGetter(Object, Method, Callable, String, PlcDriverManager, AliasRegistry, Map, Map)} method is called for each
  * invocation of a method on a connected @{@link PlcEntity} and does then the dynamic part.
- *
+ * <p>
  * For those not too familiar with the JVM's dispatch on can roughly imagine the intercept method being a "regular"
  * method on the "proxied" entity and all parameters of the intercept method could then be access to local fields.
  *
@@ -75,26 +74,27 @@ public class PlcEntityInterceptor {
     /**
      * Basic Intersector for all methods on the proxy object.
      * It checks if the invoked method is a getter and if so, only retrieves the requested field, forwarding to
-     * the {@link #fetchValueForGetter(Object, Method, PlcDriverManager, String, AliasRegistry, Map)} method.
+     * the {@link #fetchAndSetValueForGetter(Object, Method, PlcDriverManager, String, AliasRegistry, Map)} method.
      * <p>
      * If the field is no getter, then all fields are refreshed by calling {@link #refetchAllFields(Object, PlcDriverManager, String, AliasRegistry, Map)}
      * and then, the method is invoked.
      *
-     * @param proxy    Object to intercept
-     * @param method   Method that was intercepted
-     * @param callable Callable to call the method after fetching the values
-     * @param address  Address of the plc (injected from private field)
+     * @param proxy         Object to intercept
+     * @param method        Method that was intercepted
+     * @param callable      Callable to call the method after fetching the values
+     * @param address       Address of the plc (injected from private field)
      * @param driverManager DriverManager instance to use (injected from private field)
      * @return possible result of the original methods invocation
      * @throws OPMException Problems with plc / proxying
      */
     @SuppressWarnings("unused")
     @RuntimeType
-    public static Object intercept(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable,
-                                   @FieldValue(PlcEntityManager.PLC_ADDRESS_FIELD_NAME) String address,
-                                   @FieldValue(PlcEntityManager.DRIVER_MANAGER_FIELD_NAME) PlcDriverManager driverManager,
-                                   @FieldValue(PlcEntityManager.ALIAS_REGISTRY) AliasRegistry registry,
-                                   @FieldValue(PlcEntityManager.LAST_FETCHED) Map<String, Instant> lastFetched) throws OPMException {
+    public static Object interceptGetter(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable,
+                                         @FieldValue(PlcEntityManager.PLC_ADDRESS_FIELD_NAME) String address,
+                                         @FieldValue(PlcEntityManager.DRIVER_MANAGER_FIELD_NAME) PlcDriverManager driverManager,
+                                         @FieldValue(PlcEntityManager.ALIAS_REGISTRY) AliasRegistry registry,
+                                         @FieldValue(PlcEntityManager.LAST_FETCHED) Map<String, Instant> lastFetched,
+                                         @FieldValue(PlcEntityManager.LAST_WRITTEN) Map<String, Instant> lastWritten) throws OPMException {
         LOGGER.trace("Invoked method {} on connected PlcEntity {}", method.getName(), method.getDeclaringClass().getName());
 
         // If "detached" (i.e. _driverManager is null) simply forward the call
@@ -115,7 +115,12 @@ public class PlcEntityInterceptor {
             LOGGER.trace("Invoked method {} is getter, trying to find annotated field and return requested value",
                 method.getName());
 
-            return fetchValueForGetter(proxy, method, driverManager, address, registry, lastFetched);
+            fetchAndSetValueForGetter(proxy, method, driverManager, address, registry, lastFetched);
+            try {
+                return callable.call();
+            } catch (Exception e) {
+                throw new OPMException("Unable to forward invocation " + method.getName() + " on connected PlcEntity", e);
+            }
         }
 
         if (method.getName().startsWith("is") && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class)) {
@@ -125,7 +130,43 @@ public class PlcEntityInterceptor {
             // Fetch single value
             LOGGER.trace("Invoked method {} is boolean flag method, trying to find annotated field and return requested value",
                 method.getName());
-            return fetchValueForIsGetter(proxy, method, driverManager, address, registry, lastFetched);
+            fetchAndSetValueForIsGetter(proxy, method, driverManager, address, registry, lastFetched);
+            try {
+                return callable.call();
+            } catch (Exception e) {
+                throw new OPMException("Unable to forward invocation " + method.getName() + " on connected PlcEntity", e);
+            }
+        }
+
+        // Fetch all values, than invoke method
+        try {
+            LOGGER.trace("Invoked method is no getter, refetch all fields and invoke method {} then", method.getName());
+            refetchAllFields(proxy, driverManager, address, registry, lastFetched);
+            Object call = callable.call();
+            // We write back
+            writeAllFields(proxy, driverManager, address, registry, lastWritten);
+            return call;
+        } catch (Exception e) {
+            throw new OPMException("Unable to forward invocation " + method.getName() + " on connected PlcEntity", e);
+        }
+    }
+
+    @RuntimeType
+    public static Object interceptSetter(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable,
+                                         @FieldValue(PlcEntityManager.PLC_ADDRESS_FIELD_NAME) String address,
+                                         @FieldValue(PlcEntityManager.DRIVER_MANAGER_FIELD_NAME) PlcDriverManager driverManager,
+                                         @FieldValue(PlcEntityManager.ALIAS_REGISTRY) AliasRegistry registry,
+                                         @FieldValue(PlcEntityManager.LAST_FETCHED) Map<String, Instant> lastFetched,
+                                         @Argument(0) Object argument) throws OPMException {
+        if (method.getName().startsWith("set")) {
+            if (method.getParameterCount() != 1) {
+                throw new OPMException("Only setter with one arguments are supported");
+            }
+            // Set single value
+            LOGGER.trace("Invoked method {} is setter, trying to find annotated field and return requested value",
+                method.getName());
+
+            return setValueForSetter(proxy, method, callable, driverManager, address, registry, lastFetched, argument);
         }
 
         // Fetch all values, than invoke method
@@ -141,9 +182,9 @@ public class PlcEntityInterceptor {
     /**
      * Renews all values of all Fields that are annotated with {@link PlcEntity}.
      *
-     * @param proxy Object to refresh the fields on.
+     * @param proxy         Object to refresh the fields on.
      * @param driverManager Driver Manager to use
-     * @param registry AliasRegistry to use
+     * @param registry      AliasRegistry to use
      * @param lastFetched
      * @throws OPMException on various errors.
      */
@@ -170,7 +211,7 @@ public class PlcEntityInterceptor {
 
             Arrays.stream(entityClass.getDeclaredFields())
                 .filter(field -> field.isAnnotationPresent(PlcField.class))
-                .filter(field -> needsToBeFetched(lastFetched, field))
+                .filter(field -> needsToBeSynced(lastFetched, field))
                 .forEach(field ->
                     requestBuilder.addItem(
                         getFqn(field),
@@ -204,34 +245,92 @@ public class PlcEntityInterceptor {
         }
     }
 
+    static void writeAllFields(Object proxy, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastWritten) throws OPMException {
+        // Don't log o here as this would cause a second request against a plc so don't touch it, or if you log be aware of that
+        Class<?> entityClass = proxy.getClass().getSuperclass();
+        LOGGER.trace("Writing all fields on proxy object of class {}", entityClass);
+        PlcEntity plcEntity = entityClass.getAnnotation(PlcEntity.class);
+        if (plcEntity == null) {
+            throw new OPMException("Non PlcEntity supplied");
+        }
+
+        // Check if all fields are valid
+        for (Field field : entityClass.getDeclaredFields()) {
+            if (field.isAnnotationPresent(PlcField.class)) {
+                OpmUtils.getOrResolveAddress(registry, field.getAnnotation(PlcField.class).value());
+            }
+        }
+        try (PlcConnection connection = driverManager.getConnection(address)) {
+            // Catch the exception, if no reader present (see below)
+            // Build the query
+            PlcWriteRequest.Builder requestBuilder = connection.writeRequestBuilder();
+
+            Arrays.stream(entityClass.getDeclaredFields())
+                .filter(field -> field.isAnnotationPresent(PlcField.class))
+                .filter(field -> needsToBeSynced(lastWritten, field))
+                .forEach(field ->
+                    requestBuilder.addItem(
+                        getFqn(field),
+                        OpmUtils.getOrResolveAddress(registry, field.getAnnotation(PlcField.class).value()),
+                        getFromField(field, proxy)
+                    )
+                );
+
+            PlcWriteRequest request = requestBuilder.build();
+
+            LOGGER.trace("Request for write of {} was build and is {}", entityClass, request);
+
+            PlcWriteResponse response = getPlcWriteResponse(request);
+
+            // Fill all requested fields
+            for (String fieldName : response.getFieldNames()) {
+                // Fill into Cache
+                lastWritten.put(fieldName, Instant.now());
+            }
+        } catch (PlcConnectionException e) {
+            throw new OPMException("Problem during processing", e);
+        } catch (Exception e) {
+            throw new OPMException("Unexpected error during processing", e);
+        }
+    }
+
+    private static Object getFromField(Field field, Object object) {
+        try {
+            field.setAccessible(true);
+            return field.get(object);
+        } catch (IllegalAccessException e) {
+            throw new PlcRuntimeException(e);
+        }
+    }
+
     private static String getFqn(Field field) {
         return field.getDeclaringClass().getName() + "." + field.getName();
     }
 
     /**
-     * Checks if a field needs to be refetched, i.e., the cached values are too old.
+     * Checks if a field needs to be refetched/rewritten, i.e., the cached values are too old.
      */
-    private static boolean needsToBeFetched(Map<String, Instant> lastFetched, Field field) {
+    private static boolean needsToBeSynced(Map<String, Instant> lastSynced, Field field) {
         Validate.notNull(field);
         long cacheDurationMillis = field.getAnnotation(PlcField.class).cacheDurationMillis();
         String fqn = getFqn(field);
-        if (lastFetched.containsKey(fqn)) {
-            Instant last = lastFetched.get(fqn);
+        if (lastSynced.containsKey(fqn)) {
+            Instant last = lastSynced.get(fqn);
             return Instant.now().minus(cacheDurationMillis, ChronoUnit.MILLIS).isAfter(last);
         }
         return true;
     }
 
-    private static Object fetchValueForIsGetter(Object proxy, Method m, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
-        return fetchValueForGetter(proxy, m, 2, driverManager, address, registry, lastFetched);
+    private static void fetchAndSetValueForIsGetter(Object proxy, Method m, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
+        fetchAndSetValueForGetter(proxy, m, 2, driverManager, address, registry, lastFetched);
     }
 
-    private static Object fetchValueForGetter(Object proxy, Method m, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
-        return fetchValueForGetter(proxy, m, 3, driverManager, address, registry, lastFetched);
+    private static void fetchAndSetValueForGetter(Object proxy, Method m, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
+        fetchAndSetValueForGetter(proxy, m, 3, driverManager, address, registry, lastFetched);
     }
 
-    private static Object fetchValueForGetter(Object proxy, Method m, int prefixLength, PlcDriverManager driverManager,
-                                              String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
+    private static void fetchAndSetValueForGetter(Object proxy, Method m, int prefixLength, PlcDriverManager driverManager,
+                                                  String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
         String s = m.getName().substring(prefixLength);
         // First char to lower
         String variable = s.substring(0, 1).toLowerCase().concat(s.substring(1));
@@ -249,14 +348,8 @@ public class PlcEntityInterceptor {
         String fqn = getFqn(field);
 
         // Check if cache is still active
-        if (!needsToBeFetched(lastFetched, field)) {
-            // Return the current value
-            try {
-                field.setAccessible(true);
-                return field.get(proxy);
-            } catch (IllegalAccessException e) {
-                throw new OPMException("Unable to restore cached (previous) value for field '" + field.getName() + "'", e);
-            }
+        if (!needsToBeSynced(lastFetched, field)) {
+            return;
         }
         try (PlcConnection connection = driverManager.getConnection(address)) {
             // Catch the exception, if no reader present (see below)
@@ -270,7 +363,61 @@ public class PlcEntityInterceptor {
             // Fill into Cache
             lastFetched.put(field.getName(), Instant.now());
 
-            return getTyped(m.getReturnType(), response, fqn);
+            Object value = getTyped(m.getReturnType(), response, fqn);
+            setForField(field, proxy, value);
+        } catch (ClassCastException e) {
+            throw new OPMException("Unable to return response as suitable type", e);
+        } catch (Exception e) {
+            throw new OPMException("Problem during processing", e);
+        }
+    }
+
+    private static void setForField(Field field, Object proxy, Object value) {
+        try {
+            field.setAccessible(true);
+            field.set(proxy, value);
+        } catch (IllegalAccessException e) {
+            throw new PlcRuntimeException(e);
+        }
+    }
+
+    private static Object setValueForSetter(Object proxy, Method m, Callable<?> callable, PlcDriverManager driverManager,
+                                            String address, AliasRegistry registry, Map<String, Instant> lastFetched, Object object) throws OPMException {
+        String s = m.getName().substring(3);
+        // First char to lower
+        String variable = s.substring(0, 1).toLowerCase().concat(s.substring(1));
+        LOGGER.trace("Looking for field with name {} after invokation of getter {}", variable, m.getName());
+        PlcField annotation;
+        Field field;
+        try {
+            field = m.getDeclaringClass().getDeclaredField(variable);
+            annotation = field.getDeclaredAnnotation(PlcField.class);
+        } catch (NoSuchFieldException e) {
+            throw new OPMException("Unable to identify field with name '" + variable + "' for call to '" + m.getName() + "'", e);
+        }
+
+        // Use Fully qualified Name as field index
+        String fqn = getFqn(field);
+
+        try (PlcConnection connection = driverManager.getConnection(address)) {
+            // Catch the exception, if no reader present (see below)
+
+            PlcWriteRequest request = connection.writeRequestBuilder()
+                .addItem(fqn, OpmUtils.getOrResolveAddress(registry, annotation.value()), object)
+                .build();
+
+            PlcWriteResponse response = getPlcWriteResponse(request);
+
+            // Fill into Cache
+            lastFetched.put(field.getName(), Instant.now());
+
+            LOGGER.debug("getTyped clazz: {}, response: {}, fieldName: {}", m.getParameters()[0].getType(), response, fqn);
+            if (response.getResponseCode(fqn) != PlcResponseCode.OK) {
+                throw new PlcRuntimeException(String.format("Unable to read specified field '%s', response code was '%s'",
+                    fqn, response.getResponseCode(fqn)));
+            }
+            callable.call();
+            return null;
         } catch (ClassCastException e) {
             throw new OPMException("Unable to return response as suitable type", e);
         } catch (Exception e) {
@@ -278,6 +425,7 @@ public class PlcEntityInterceptor {
         }
     }
 
+
     /**
      * Tries to set a response Item to a field in the given object.
      * This is one by looking for a field in the class and a response item
@@ -287,7 +435,7 @@ public class PlcEntityInterceptor {
      * @param response        Response to fetch the response from
      * @param targetFieldName Name of the field in the object
      * @param sourceFieldName Name of the field in the response
-     * @throws NoSuchFieldException If a field is not present in entity
+     * @throws NoSuchFieldException   If a field is not present in entity
      * @throws IllegalAccessException If a field in the entity cannot be accessed
      */
     static void setField(Class<?> clazz, Object o, PlcReadResponse response, String targetFieldName, String sourceFieldName) throws NoSuchFieldException, IllegalAccessException {
@@ -302,7 +450,8 @@ public class PlcEntityInterceptor {
         }
     }
 
-    @SuppressWarnings({"squid:S3776", "squid:MethodCyclomaticComplexity"}) // Cognitive Complexity not too high, as highly structured
+    @SuppressWarnings({"squid:S3776", "squid:MethodCyclomaticComplexity"})
+    // Cognitive Complexity not too high, as highly structured
     static Object getTyped(Class<?> clazz, PlcReadResponse response, String sourceFieldName) {
         LOGGER.debug("getTyped clazz: {}, response: {}, fieldName: {}", clazz, response, sourceFieldName);
         if (response.getResponseCode(sourceFieldName) != PlcResponseCode.OK) {
@@ -373,8 +522,24 @@ public class PlcEntityInterceptor {
      * @throws OPMException on {@link InterruptedException} or {@link ExecutionException} or {@link TimeoutException}
      */
     static PlcReadResponse getPlcReadResponse(PlcReadRequest request) throws OPMException {
+        return getFromFuture(request);
+    }
+
+    /**
+     * Fetch the request and do appropriate error handling
+     *
+     * @param request the request to get the exception from
+     * @return the response from the exception.
+     * @throws OPMException on {@link InterruptedException} or {@link ExecutionException} or {@link TimeoutException}
+     */
+    public static PlcWriteResponse getPlcWriteResponse(PlcWriteRequest request) throws OPMException {
+        return getFromFuture(request);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <REQ extends PlcRequest, RES extends PlcResponse> RES getFromFuture(REQ request) throws OPMException {
         try {
-            return request.execute().get(READ_TIMEOUT, TimeUnit.MILLISECONDS);
+            return (RES) request.execute().get(READ_TIMEOUT, TimeUnit.MILLISECONDS);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new OPMException("Exception during execution", e);
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
index 81ebbea..7188d4e 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
@@ -22,21 +22,16 @@ package org.apache.plc4x.java.opm;
 import net.bytebuddy.ByteBuddy;
 import net.bytebuddy.description.modifier.Visibility;
 import net.bytebuddy.implementation.MethodDelegation;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.plc4x.java.PlcDriverManager;
-import org.apache.plc4x.java.api.PlcConnection;
-import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
-import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
 import org.apache.plc4x.java.api.messages.PlcReadRequest;
-import org.apache.plc4x.java.api.messages.PlcReadResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.time.Instant;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -56,17 +51,17 @@ import static net.bytebuddy.matcher.ElementMatchers.not;
  * <li>Needs to be annotated with {@link PlcEntity} and has a valid value which is the connection string</li>
  * </ul>
  * <p>
- * Basically, the {@link PlcEntityManager} has to operation "modes" represented by the methods {@link #read(Class,String)} and
- * {@link #connect(Class,String)}.
+ * Basically, the {@link PlcEntityManager} has to operation "modes" represented by the methods {@link #read(Class, String)} and
+ * {@link #connect(Class, String)}.
  * <p>
  * For a field to get Values from the Plc Injected it needs to be annotated with the {@link PlcField} annotation.
  * The value has to be the plc fields string (which is inserted in the {@link PlcReadRequest}).
  * The connection string is taken from the value of the {@link PlcEntity} annotation on the class.
  * <p>
- * The {@link #read(Class,String)} method has no direkt equivalent in JPA (as far as I know) as it only returns a "detached"
+ * The {@link #read(Class, String)} method has no direkt equivalent in JPA (as far as I know) as it only returns a "detached"
  * entity. This means it fetches all values from the plc that are annotated wiht the {@link PlcField} annotations.
  * <p>
- * The {@link #connect(Class,String)} method is more JPA-like as it returns a "connected" entity. This means, that each
+ * The {@link #connect(Class, String)} method is more JPA-like as it returns a "connected" entity. This means, that each
  * time one of the getters on the returned entity is called a call is made to the plc (and the field value is changed
  * for this specific field).
  * Furthermore, if a method which is no getter is called, then all {@link PlcField}s are refreshed before doing the call.
@@ -77,7 +72,7 @@ import static net.bytebuddy.matcher.ElementMatchers.not;
  * regular Pojo it was before.
  * <p>
  * All invocations on the getters are forwarded to the
- * {@link PlcEntityInterceptor#intercept(Object, Method, Callable, String, PlcDriverManager, AliasRegistry, Map)}
+ * {@link PlcEntityInterceptor#interceptGetter(Object, Method, Callable, String, PlcDriverManager, AliasRegistry, Map, Map)}
  * method.
  */
 public class PlcEntityManager {
@@ -88,6 +83,7 @@ public class PlcEntityManager {
     static final String DRIVER_MANAGER_FIELD_NAME = "_driverManager";
     static final String ALIAS_REGISTRY = "_aliasRegistry";
     public static final String LAST_FETCHED = "_lastFetched";
+    public static final String LAST_WRITTEN = "_lastWritten";
 
     private final PlcDriverManager driverManager;
     private final SimpleAliasRegistry registry;
@@ -106,48 +102,15 @@ public class PlcEntityManager {
     }
 
     public <T> T read(Class<T> clazz, String address) throws OPMException {
-        PlcEntity annotation = OpmUtils.getPlcEntityAndCheckPreconditions(clazz);
-
-        try (PlcConnection connection = driverManager.getConnection(address)) {
-            if (!connection.getMetadata().canRead()) {
-                throw new OPMException("Unable to get Reader for connection with url '" + address + "'");
-            }
-
-            PlcReadRequest.Builder requestBuilder = connection.readRequestBuilder();
-
-            Arrays.stream(clazz.getDeclaredFields())
-                .filter(field -> field.isAnnotationPresent(PlcField.class))
-                .forEach(field ->
-                    requestBuilder.addItem(
-                        field.getDeclaringClass().getName() + "." + field.getName(),
-                        OpmUtils.getOrResolveAddress(registry, field.getAnnotation(PlcField.class).value())
-                    )
-                );
-
-            // Build the request
-            PlcReadRequest request = requestBuilder.build();
-
-            // Perform the request
-            PlcReadResponse response = PlcEntityInterceptor.getPlcReadResponse(request);
-
-            // Construct the Object
-            T instance = clazz.getConstructor().newInstance();
+        T connect = connect(clazz, address);
+        disconnect(connect);
+        return connect;
+    }
 
-            // Fill all requested fields
-            for (String fieldName : response.getFieldNames()) {
-                String targetFieldName = StringUtils.substringAfterLast(fieldName, ".");
-                PlcEntityInterceptor.setField(clazz, instance, response, targetFieldName, fieldName);
-            }
-            return instance;
-        } catch (PlcInvalidFieldException e) {
-            throw new OPMException("Unable to parse field '" + e.getFieldToBeParsed() + "'", e);
-        } catch (PlcConnectionException e) {
-            throw new OPMException("Unable to get connection with url '" + address + "'", e);
-        } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | NoSuchFieldException | IllegalAccessException e) {
-            throw new OPMException("Unable to fetch PlcEntity " + clazz.getName(), e);
-        } catch (Exception e) {
-            throw new OPMException("Unexpected Exception: " + e.getMessage(), e);
-        }
+    public <T> T write(Class<T> clazz, String address, T object) throws OPMException {
+        T merge = merge(clazz, address, object);
+        disconnect(merge);
+        return merge;
     }
 
     /**
@@ -159,6 +122,23 @@ public class PlcEntityManager {
      * @throws OPMException when proxy can't be build.
      */
     public <T> T connect(Class<T> clazz, String address) throws OPMException {
+        return connect(clazz, address, null);
+    }
+
+
+    /**
+     * Returns a connected proxy.
+     *
+     * @param clazz clazz to be connected.
+     * @param <T>   type of param {@code clazz}.
+     * @return a connected entity.
+     * @throws OPMException when proxy can't be build.
+     */
+    public <T> T merge(Class<T> clazz, String address, T instance) throws OPMException {
+        return connect(clazz, address, instance);
+    }
+
+    private <T> T connect(Class<T> clazz, String address, T existingInstance) throws OPMException {
         OpmUtils.getPlcEntityAndCheckPreconditions(clazz);
         try {
             // Use Byte Buddy to generate a subclassed proxy that delegates all PlcField Methods
@@ -169,6 +149,7 @@ public class PlcEntityManager {
                 .defineField(DRIVER_MANAGER_FIELD_NAME, PlcDriverManager.class, Visibility.PRIVATE)
                 .defineField(ALIAS_REGISTRY, AliasRegistry.class, Visibility.PRIVATE)
                 .defineField(LAST_FETCHED, Map.class, Visibility.PRIVATE)
+                .defineField(LAST_WRITTEN, Map.class, Visibility.PRIVATE)
                 .method(not(isDeclaredBy(Object.class))).intercept(MethodDelegation.to(PlcEntityInterceptor.class))
                 .make()
                 .load(Thread.currentThread().getContextClassLoader())
@@ -181,9 +162,19 @@ public class PlcEntityManager {
             FieldUtils.writeDeclaredField(instance, ALIAS_REGISTRY, registry, true);
             Map<String, Instant> lastFetched = new HashMap<>();
             FieldUtils.writeDeclaredField(instance, LAST_FETCHED, lastFetched, true);
+            Map<String, Instant> lastWritten = new HashMap<>();
+            FieldUtils.writeDeclaredField(instance, LAST_WRITTEN, lastWritten, true);
 
             // Initially fetch all values
-            PlcEntityInterceptor.refetchAllFields(instance, driverManager, address, registry, lastFetched);
+            if (existingInstance == null) {
+                PlcEntityInterceptor.refetchAllFields(instance, driverManager, address, registry, lastFetched);
+            } else {
+                FieldUtils.getAllFieldsList(clazz).stream()
+                    .peek(field -> field.setAccessible(true))
+                    .forEach(field -> setValueToField(field, instance, getValueFromField(field, existingInstance)));
+
+                PlcEntityInterceptor.writeAllFields(instance, driverManager, address, registry, lastWritten);
+            }
 
             return instance;
         } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException | IllegalAccessError e) {
@@ -191,8 +182,25 @@ public class PlcEntityManager {
         }
     }
 
+    private Object getValueFromField(Field field, Object object) {
+        try {
+            return field.get(object);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void setValueToField(Field field, Object object, Object value) {
+        try {
+            field.set(object, value);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     /**
      * Disconnects the given instance.
+     *
      * @param entity Instance of a PlcEntity.
      * @throws OPMException Is thrown when the plc is already disconnected or no entity.
      */
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
index f6b499d..e6f16a4 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
@@ -28,7 +28,7 @@ import java.lang.annotation.Target;
  * Field that is mapped
  */
 @Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.FIELD})
+@Target({ElementType.FIELD})
 public @interface PlcField {
     String value();
     long cacheDurationMillis() default 1000;
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
index 0be09df..bf54364 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
@@ -26,7 +26,7 @@ import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.base.messages.items.DefaultStringFieldItem;
 import org.apache.plc4x.java.mock.MockDevice;
 import org.apache.plc4x.java.mock.PlcMockConnection;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
 import java.util.stream.IntStream;
@@ -84,16 +84,17 @@ public class ConnectedEntityTest {
         PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:cached");
         MockDevice mock = Mockito.mock(MockDevice.class);
         when(mock.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("hallo")));
+        when(mock.write(any(), any())).thenReturn(PlcResponseCode.OK);
         connection.setDevice(mock);
         PlcEntityManager entityManager = new PlcEntityManager(driverManager);
 
         // Trigger a fetch
         CachingEntity entity = entityManager.connect(CachingEntity.class, "mock:cached");
         // Trigger Many Fetches via getter
-        IntStream.range(1,100).forEach(i -> entity.getField());
-        IntStream.range(1,100).forEach(i -> entity.dummyMethod());
+        IntStream.range(1, 100).forEach(i -> entity.getField());
+        IntStream.range(1, 100).forEach(i -> entity.dummyMethod());
 
-        verify(mock, timeout(1_000).times(1)).read(any());
+        verify(mock, timeout(1_000).times(2)).read(any());
     }
 
     @PlcEntity
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/OpmUtilsTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/OpmUtilsTest.java
index 27694d4..1753f0b 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/OpmUtilsTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/OpmUtilsTest.java
@@ -19,11 +19,12 @@
 
 package org.apache.plc4x.java.opm;
 
-import org.junit.Test;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Test;
 
 import static org.junit.Assert.*;
 
-public class OpmUtilsTest {
+public class OpmUtilsTest implements WithAssertions {
 
     @Test
     public void expression_matches() {
@@ -57,8 +58,9 @@ public class OpmUtilsTest {
         assertFalse(OpmUtils.isValidExpression("${hallo"));
     }
 
-    @Test(expected = IllegalArgumentException.class)
+    @Test
     public void getAlias_illegalString_throws() {
-        OpmUtils.getAlias("hallo");
+        assertThatThrownBy(() -> OpmUtils.getAlias("hallo"))
+            .isInstanceOf(IllegalArgumentException.class);
     }
 }
\ No newline at end of file
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
index 301272b..d30fc92 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
@@ -24,7 +24,8 @@ import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.api.messages.PlcReadRequest;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.base.messages.DefaultPlcReadResponse;
-import org.junit.Test;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 import org.mockito.stubbing.Answer;
 import org.slf4j.Logger;
@@ -42,7 +43,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.when;
 
-public class PlcEntityInterceptorTest {
+public class PlcEntityInterceptorTest implements WithAssertions {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(PlcEntityInterceptorTest.class);
 
@@ -78,20 +79,22 @@ public class PlcEntityInterceptorTest {
         assertTrue(exceptionWasThrown.get());
     }
 
-    @Test(expected = OPMException.class)
-    public void getPlcReadResponse_catchesExecutionException_rethrows() throws OPMException, InterruptedException, ExecutionException, TimeoutException {
-        runGetPlcResponseWIthException(invocation -> {
+    @Test
+    public void getPlcReadResponse_catchesExecutionException_rethrows() {
+        assertThatThrownBy(() -> runGetPlcResponseWIthException(invocation -> {
             throw new ExecutionException(new Exception());
-        });
+        }))
+            .isInstanceOf(OPMException.class);
     }
 
-    @Test(expected = OPMException.class)
-    public void getPlcReadResponse_timeoutOnGet_rethrows() throws OPMException {
+    @Test
+    public void getPlcReadResponse_timeoutOnGet_rethrows() {
         PlcReadRequest request = Mockito.mock(PlcReadRequest.class);
         CompletableFuture future = new CompletableFuture<>();
         when(request.execute()).thenReturn(future);
 
-        PlcEntityInterceptor.getPlcReadResponse(request);
+        assertThatThrownBy(() -> PlcEntityInterceptor.getPlcReadResponse(request))
+            .isInstanceOf(OPMException.class);
     }
 
     @Test
@@ -128,7 +131,6 @@ public class PlcEntityInterceptorTest {
         }
 
         // Getter with no field
-        @PlcField("field1")
         public String getField1() {
             return "";
         }
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerComplexTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerComplexTest.java
index 4805516..1a310e1 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerComplexTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerComplexTest.java
@@ -25,17 +25,14 @@ import org.apache.plc4x.java.PlcDriverManager;
 import org.apache.plc4x.java.api.PlcConnection;
 import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
 import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
-import org.apache.plc4x.java.api.messages.PlcReadRequest;
 import org.apache.plc4x.java.api.metadata.PlcConnectionMetadata;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.base.connection.PlcFieldHandler;
-import org.apache.plc4x.java.base.messages.DefaultPlcReadRequest;
-import org.apache.plc4x.java.base.messages.DefaultPlcReadResponse;
-import org.apache.plc4x.java.base.messages.InternalPlcReadRequest;
-import org.apache.plc4x.java.base.messages.PlcReader;
+import org.apache.plc4x.java.base.messages.*;
 import org.apache.plc4x.java.base.messages.items.*;
+import org.assertj.core.api.WithAssertions;
 import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mockito;
 
@@ -56,22 +53,24 @@ import static org.junit.Assert.*;
 import static org.mockito.Mockito.when;
 
 
-public class PlcEntityManagerComplexTest {
+public class PlcEntityManagerComplexTest implements WithAssertions {
 
     private PlcDriverManager driverManager;
 
-    @Test(expected = IllegalArgumentException.class)
-    public void noEntity_throws() throws OPMException {
+    @Test
+    public void noEntity_throws() {
         PlcEntityManager manager = new PlcEntityManager();
 
-        manager.read(NoEntity.class, "s7://localhost:5555/0/0");
+        assertThatThrownBy(() -> manager.read(NoEntity.class, "s7://localhost:5555/0/0"))
+            .isInstanceOf(IllegalArgumentException.class);
     }
 
-    @Test(expected = IllegalArgumentException.class)
-    public void noValidConstructor_throws() throws OPMException {
+    @Test
+    public void noValidConstructor_throws() {
         PlcEntityManager manager = new PlcEntityManager();
 
-        manager.read(EntityWithBadConstructor.class, "s7://localhost:5555/0/0");
+        assertThatThrownBy(() -> manager.read(EntityWithBadConstructor.class, "s7://localhost:5555/0/0"))
+            .isInstanceOf(IllegalArgumentException.class);
     }
 
     @Test
@@ -174,14 +173,15 @@ public class PlcEntityManagerComplexTest {
         assertNotNull(connected.getByteVar());
     }
 
-    @Test(expected = OPMException.class)
+    @Test
     public void disconnectTwice_throwsException() throws PlcConnectionException, OPMException {
-        PlcEntityManager manager = getPlcEntityManager(new HashMap<>());
+        PlcEntityManager manager = getInitializedEntityManager();
 
         ConnectedEntity connected = manager.connect(ConnectedEntity.class, "s7://localhost:5555/0/0");
 
         manager.disconnect(connected);
-        manager.disconnect(connected);
+        assertThatThrownBy(() -> manager.disconnect(connected))
+            .isInstanceOf(OPMException.class);
     }
 
     private PlcEntityManager getPlcEntityManager(final Map<String, BaseDefaultFieldItem> responses) throws PlcConnectionException {
@@ -216,6 +216,15 @@ public class PlcEntityManagerComplexTest {
             return CompletableFuture.completedFuture(new DefaultPlcReadResponse((InternalPlcReadRequest) readRequest, map));
         };
         when(connection.readRequestBuilder()).then(invocation -> new DefaultPlcReadRequest.Builder(reader, getFieldHandler()));
+        PlcWriter writer = writeRequest -> {
+            Map<String, PlcResponseCode> map = writeRequest.getFieldNames().stream()
+                .collect(Collectors.toMap(
+                    Function.identity(),
+                    s -> PlcResponseCode.OK
+                ));
+            return CompletableFuture.completedFuture(new DefaultPlcWriteResponse((InternalPlcWriteRequest) writeRequest, map));
+        };
+        when(connection.writeRequestBuilder()).then(invocation -> new DefaultPlcWriteRequest.Builder(writer, getFieldHandler()));
 
         return new PlcEntityManager(mock);
     }
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
index e97bf6e..91a6f9e 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
@@ -30,169 +30,212 @@ import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.base.messages.items.DefaultStringFieldItem;
 import org.apache.plc4x.java.mock.MockDevice;
 import org.apache.plc4x.java.mock.PlcMockConnection;
-import org.junit.Test;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
 
-public class PlcEntityManagerTest {
-
-    @Test
-    public void read_throwsInvalidFieldException_rethrows() throws PlcConnectionException {
-        // Prepare the Mock
-        PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
-        PlcConnection connection = Mockito.mock(PlcConnection.class);
-        PlcConnectionMetadata metadata = Mockito.mock(PlcConnectionMetadata.class);
-        PlcReadRequest.Builder builder = Mockito.mock(PlcReadRequest.Builder.class);
-        when(metadata.canRead()).thenReturn(true);
-        when(connection.readRequestBuilder()).thenReturn(builder);
-        when(connection.getMetadata()).thenReturn(metadata);
-        when(builder.build()).thenThrow(new PlcInvalidFieldException("field1"));
-        when(driverManager.getConnection(any())).thenReturn(connection);
-
-        // Create Entity Manager
-        PlcEntityManager entityManager = new PlcEntityManager(driverManager);
-
-        // Issue Call to trigger interception
-        String message = null;
-        try {
-            BadEntity entity = entityManager.read(BadEntity.class, "mock:test");
-        } catch (Exception e) {
-            message = e.getMessage();
-        }
-
-        assertEquals("Unable to parse field 'field1'", message);
-    }
+public class PlcEntityManagerTest implements WithAssertions {
+
+    @Nested
+    class Read {
+        @Test
+        public void throwsInvalidFieldException_rethrows() throws PlcConnectionException {
+            // Prepare the Mock
+            PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
+            PlcConnection connection = Mockito.mock(PlcConnection.class);
+            PlcConnectionMetadata metadata = Mockito.mock(PlcConnectionMetadata.class);
+            PlcReadRequest.Builder builder = Mockito.mock(PlcReadRequest.Builder.class);
+            when(metadata.canRead()).thenReturn(true);
+            when(connection.readRequestBuilder()).thenReturn(builder);
+            when(connection.getMetadata()).thenReturn(metadata);
+            when(builder.build()).thenThrow(new PlcInvalidFieldException("field1"));
+            when(driverManager.getConnection(any())).thenReturn(connection);
+
+            // Create Entity Manager
+            PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+
+            // Issue Call to trigger interception
+            assertThatThrownBy(() -> entityManager.read(BadEntity.class, "mock:test"))
+                .hasCauseInstanceOf(PlcInvalidFieldException.class)
+                .hasStackTraceContaining("field1 invalid");
+        }
 
-    @Test
-    public void read_unableToConnect_rethrows() throws PlcConnectionException {
-        // Prepare the Mock
-        PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
-        when(driverManager.getConnection(any())).thenThrow(new PlcConnectionException(""));
+        @Test
+        public void unableToConnect_rethrows() throws PlcConnectionException {
+            // Prepare the Mock
+            PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
+            when(driverManager.getConnection(any())).thenThrow(new PlcConnectionException(""));
 
-        // Create Entity Manager
-        PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+            // Create Entity Manager
+            PlcEntityManager entityManager = new PlcEntityManager(driverManager);
 
-        // Issue Call to trigger interception
-        String message = null;
-        try {
-            BadEntity entity = entityManager.read(BadEntity.class, "mock:test");
-        } catch (Exception e) {
-            message = e.getMessage();
+            // Issue Call to trigger interception
+            assertThatThrownBy(() -> entityManager.read(BadEntity.class, "mock:test"))
+                .hasCauseInstanceOf(PlcConnectionException.class)
+                .hasStackTraceContaining("Problem during processing");
         }
 
-        assertEquals("Unable to get connection with url 'mock:test'", message);
-    }
-
-    @Test(expected = OPMException.class)
-    public void read_timeoutOnGet_throwsException() throws PlcConnectionException, OPMException {
-        // Prepare the Mock
-        MockDevice mockDevice = Mockito.mock(MockDevice.class);
-        PlcDriverManager driverManager = new PlcDriverManager();
-        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
-        when(mockDevice.read(any())).thenAnswer(new Answer<Object>() {
-            @Override
-            public Object answer(InvocationOnMock invocation) throws Throwable {
+        @Test
+        public void timeoutOnGet_throwsException() throws PlcConnectionException {
+            // Prepare the Mock
+            MockDevice mockDevice = Mockito.mock(MockDevice.class);
+            PlcDriverManager driverManager = new PlcDriverManager();
+            PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+            when(mockDevice.read(any())).thenAnswer(invocation -> {
                 // Sleep for 3s
                 Thread.sleep(3_000);
                 return Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("Hallo"));
-            }
-        });
-        connection.setDevice(mockDevice);
-
-        // Create Entity Manager
-        PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+            });
+            connection.setDevice(mockDevice);
 
-        // Issue Call which SHOULD timeout
-        BadEntity entity = entityManager.read(BadEntity.class, "mock:test");
-    }
+            // Create Entity Manager
+            PlcEntityManager entityManager = new PlcEntityManager(driverManager);
 
-    @Test(expected = OPMException.class)
-    public void read_uninstantiableEntity_throws() throws OPMException {
-        PlcEntityManager entityManager = new PlcEntityManager();
+            // Issue Call which SHOULD timeout
+            assertThatThrownBy(() -> entityManager.read(BadEntity.class, "mock:test"))
+                .isInstanceOf(OPMException.class);
+        }
 
-        UninstantiableEntity entity = entityManager.read(UninstantiableEntity.class, "mock:test");
-    }
+        @Test
+        public void uninstantiableEntity_throws() {
+            PlcEntityManager entityManager = new PlcEntityManager();
 
-    /**
-     * Class is private, so EntityManager has no access to it
-     * @throws OPMException
-     */
-    @Test(expected = OPMException.class)
-    public void connect_uninstantiableEntity_throws() throws OPMException {
-        PlcEntityManager entityManager = new PlcEntityManager();
+            assertThatThrownBy(() -> entityManager.read(UninstantiableEntity.class, "mock:test"))
+                .isInstanceOf(OPMException.class);
+        }
 
-        UninstantiableEntity entity = entityManager.connect(UninstantiableEntity.class, "mock:test");
-    }
+        @Test
+        public void resolveAlias_works() throws OPMException, PlcConnectionException {
+            SimpleAliasRegistry registry = new SimpleAliasRegistry();
+            registry.register("alias", "real_field");
 
-    @Test
-    public void read_resolveAlias_works() throws OPMException, PlcConnectionException {
-        SimpleAliasRegistry registry = new SimpleAliasRegistry();
-        registry.register("alias", "real_field");
+            // Mock
+            PlcDriverManager driverManager = new PlcDriverManager();
+            PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+            MockDevice mockDevice = Mockito.mock(MockDevice.class);
+            when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
+            connection.setDevice(mockDevice);
 
-        // Mock
-        PlcDriverManager driverManager = new PlcDriverManager();
-        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
-        MockDevice mockDevice = Mockito.mock(MockDevice.class);
-        when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
-        connection.setDevice(mockDevice);
+            PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
+            entityManager.read(AliasEntity.class, "mock:test");
 
-        PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
-        entityManager.read(AliasEntity.class, "mock:test");
+            // Assert that "field" was queried
+            verify(mockDevice).read(eq("real_field"));
+        }
 
-        // Assert that "field" was queried
-        verify(mockDevice).read(eq("real_field"));
-    }
 
-    @Test
-    public void connect_resolveAlias_works() throws PlcConnectionException, OPMException {
-        SimpleAliasRegistry registry = new SimpleAliasRegistry();
-        registry.register("alias", "real_field");
+        @Test
+        public void unknownAlias_throws() {
+            PlcEntityManager entityManager = new PlcEntityManager();
 
-        // Mock
-        PlcDriverManager driverManager = new PlcDriverManager();
-        PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
-        MockDevice mockDevice = Mockito.mock(MockDevice.class);
-        when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
-        connection.setDevice(mockDevice);
+            assertThatThrownBy(() -> entityManager.read(AliasEntity.class, "mock:test"))
+                .isInstanceOf(IllegalArgumentException.class);
+        }
 
-        PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
-        entityManager.connect(AliasEntity.class, "mock:test");
+        @Test
+        public void badAlias_throws() {
+            PlcEntityManager entityManager = new PlcEntityManager();
+
+            String message = null;
+            try {
+                entityManager.read(BadAliasEntity.class, "mock:test");
+            } catch (IllegalArgumentException e) {
+                message = e.getMessage();
+            } catch (OPMException e) {
+                fail("Unexpected Exception" + e);
+            }
 
-        // Assert that "field" was queried
-        verify(mockDevice, times(1)).read(eq("real_field"));
+            assertNotNull(message);
+            assertTrue(message.contains("Invalid Syntax, either use field address (no starting $) or an alias with Syntax ${xxx}. But given was"));
+        }
     }
 
-    @Test(expected = OPMException.class)
-    public void read_unknownAlias_throws() throws OPMException {
-        PlcEntityManager entityManager = new PlcEntityManager();
+    @Nested
+    class Write {
+        @Test
+        void simpleWrite() throws Exception {
+            SimpleAliasRegistry registry = new SimpleAliasRegistry();
+            registry.register("alias", "real_field");
+
+            // Mock
+            PlcDriverManager driverManager = new PlcDriverManager();
+            PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+            MockDevice mockDevice = Mockito.mock(MockDevice.class);
+            when(mockDevice.write(anyString(), any())).thenReturn(PlcResponseCode.OK);
+            when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
+            connection.setDevice(mockDevice);
+
+            PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
+            AliasEntity object = new AliasEntity();
+            object.setAliasedField("changed");
+            AliasEntity connected = entityManager.write(AliasEntity.class, "mock:test", object);
+            connected.setAliasedField("changed2");
+            connected.getAliasedField();
+
+            // Assert that "field" was queried
+            verify(mockDevice, times(1)).read(eq("real_field"));
+            verify(mockDevice, times(2)).write(eq("real_field"), any());
+
+            entityManager.disconnect(connected);
+            assertThat(connected.getAliasedField()).isEqualTo("value");
+        }
 
-        entityManager.read(AliasEntity.class, "mock:test");
+        @Test
+        void simpleWrite_uses_getter() throws Exception {
+            // Mock
+            PlcDriverManager driverManager = new PlcDriverManager();
+            PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+            MockDevice mockDevice = Mockito.mock(MockDevice.class);
+            when(mockDevice.write(anyString(), any())).thenReturn(PlcResponseCode.OK);
+            when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
+            connection.setDevice(mockDevice);
+
+            PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+            CustomGetterEntity connect = entityManager.connect(CustomGetterEntity.class, "mock:test");
+            assertThat(connect.getAsd()).isEqualTo("value!");
+        }
     }
 
-    @Test
-    public void read_badAlias_throws() {
-        PlcEntityManager entityManager = new PlcEntityManager();
-
-        String message = null;
-        try {
-            entityManager.read(BadAliasEntity.class, "mock:test");
-        } catch (OPMException e) {
-            message = e.getMessage();
+    @Nested
+    class Lifecycle {
+        /**
+         * Class is private, so EntityManager has no access to it
+         *
+         * @throws OPMException
+         */
+        @Test
+        public void connect_uninstantiableEntity_throws() {
+            PlcEntityManager entityManager = new PlcEntityManager();
+
+            assertThatThrownBy(() -> entityManager.connect(UninstantiableEntity.class, "mock:test"))
+                .isInstanceOf(OPMException.class);
         }
 
-        assertNotNull(message);
-        assertTrue(message.contains("Invalid Syntax, either use field address (no starting $) or an alias with Syntax ${xxx}. But given was"));
+        @Test
+        public void connect_resolveAlias_works() throws PlcConnectionException, OPMException {
+            SimpleAliasRegistry registry = new SimpleAliasRegistry();
+            registry.register("alias", "real_field");
+
+            // Mock
+            PlcDriverManager driverManager = new PlcDriverManager();
+            PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+            MockDevice mockDevice = Mockito.mock(MockDevice.class);
+            when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
+            connection.setDevice(mockDevice);
+
+            PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
+            entityManager.connect(AliasEntity.class, "mock:test");
+
+            // Assert that "field" was queried
+            verify(mockDevice, times(1)).read(eq("real_field"));
+        }
     }
 
     @PlcEntity
@@ -232,6 +275,10 @@ public class PlcEntityManagerTest {
         public String getAliasedField() {
             return aliasedField;
         }
+
+        public void setAliasedField(String aliasedField) {
+            this.aliasedField = aliasedField;
+        }
     }
 
     @PlcEntity
@@ -249,4 +296,19 @@ public class PlcEntityManagerTest {
         }
     }
 
+    @PlcEntity
+    public static class CustomGetterEntity {
+
+        @PlcField("asd")
+        private String asd;
+
+        public CustomGetterEntity() {
+            // for OPM
+        }
+
+        public String getAsd() {
+            return asd + "!";
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/SimpleAliasRegistryTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/SimpleAliasRegistryTest.java
index bc6d8ad..5e64d58 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/SimpleAliasRegistryTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/SimpleAliasRegistryTest.java
@@ -19,14 +19,15 @@
 
 package org.apache.plc4x.java.opm;
 
-import org.junit.Test;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Test;
 
 import java.util.HashMap;
 import java.util.NoSuchElementException;
 
 import static org.junit.Assert.*;
 
-public class SimpleAliasRegistryTest {
+public class SimpleAliasRegistryTest implements WithAssertions {
 
     public static final String ADDRESS = "DB2:1234";
     public static final String ALIAS = "some_field";
@@ -57,11 +58,13 @@ public class SimpleAliasRegistryTest {
         assertFalse(registry.canResolve(ALIAS));
     }
 
-    @Test(expected = NoSuchElementException.class)
+    @Test
     public void resolve_unknownAlias_throws() {
         SimpleAliasRegistry registry = new SimpleAliasRegistry();
 
-        registry.resolve(ALIAS);
+        assertThatThrownBy(() -> registry.resolve(ALIAS))
+            .isInstanceOf(NoSuchElementException.class);
+
     }
 
     private void checkMethods(SimpleAliasRegistry registry) {