You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2018/11/24 12:12:36 UTC

[incubator-plc4x] branch develop updated: [plc4j-opm] enable write support (PLC4X-70)

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

sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git


The following commit(s) were added to refs/heads/develop by this push:
     new 6fa99c2  [plc4j-opm] enable write support (PLC4X-70)
6fa99c2 is described below

commit 6fa99c232b7c810d8e009eb951387fc025de636a
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) {