You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by hu...@apache.org on 2024/05/26 05:25:15 UTC

(plc4x) branch develop updated: fix(plc4py): Move a lot of functions to interfaces

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

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


The following commit(s) were added to refs/heads/develop by this push:
     new 0977349bba fix(plc4py): Move a lot of functions to interfaces
0977349bba is described below

commit 0977349bba7adfaa2bf55369ee1b6aaf457f4fa4
Author: Ben Hutcheson <be...@gmail.com>
AuthorDate: Sun May 26 07:25:05 2024 +0200

    fix(plc4py): Move a lot of functions to interfaces
---
 plc4py/plc4py/api/PlcConnection.py                 |  21 ++-
 plc4py/plc4py/drivers/mock/MockConnection.py       | 134 ++-----------------
 plc4py/plc4py/drivers/modbus/ModbusConnection.py   | 126 +-----------------
 plc4py/plc4py/drivers/umas/UmasConnection.py       | 142 +--------------------
 plc4py/plc4py/spi/messages/PlcBrowser.py           |  60 +++++++++
 plc4py/plc4py/spi/messages/PlcReader.py            |  42 +++++-
 plc4py/plc4py/spi/messages/PlcWriter.py            |  46 ++++++-
 .../PlcReader.py => transport/MockTransport.py}    |  34 +++--
 8 files changed, 194 insertions(+), 411 deletions(-)

diff --git a/plc4py/plc4py/api/PlcConnection.py b/plc4py/plc4py/api/PlcConnection.py
index c096d6cad9..db87c4a74d 100644
--- a/plc4py/plc4py/api/PlcConnection.py
+++ b/plc4py/plc4py/api/PlcConnection.py
@@ -37,22 +37,35 @@ from plc4py.utils.GenericTypes import GenericGenerator
 class PlcConnection(GenericGenerator):
     def __init__(self, config: PlcConfiguration):
         self._configuration = config
+        self._transport = None
 
-    @abstractmethod
     def is_connected(self) -> bool:
         """
         Indicates if the connection is established to a remote PLC.
+
         :return: True if connection, False otherwise
         """
-        pass
+        """
+        The function checks if the connection to the remote PLC is established.
+        The connection is considered established if the transport object is not None
+        and the transport is not closing.
+
+        :return bool: True if connection, False otherwise
+        """
+        if self._transport is not None:
+            # The transport is not None if a connection is established
+            return not self._transport.is_closing()
+        else:
+            # The transport is None if no connection is established
+            return False
 
-    @abstractmethod
     def close(self) -> None:
         """
         Closes the connection to the remote PLC.
         :return:
         """
-        pass
+        if self._transport is not None:
+            self._transport.close()
 
     @abstractmethod
     def read_request_builder(self) -> ReadRequestBuilder:
diff --git a/plc4py/plc4py/drivers/mock/MockConnection.py b/plc4py/plc4py/drivers/mock/MockConnection.py
index e153b31f79..8e93053cf6 100644
--- a/plc4py/plc4py/drivers/mock/MockConnection.py
+++ b/plc4py/plc4py/drivers/mock/MockConnection.py
@@ -17,45 +17,37 @@
 # under the License.
 #
 
-import asyncio
 import logging
 from asyncio import Transport
 from dataclasses import dataclass, field
-from typing import Awaitable, Type, List, Dict, Union
-
-from plc4py.spi.messages.PlcWriter import PlcWriter
-
-from plc4py.drivers.mock import MockTag
-from plc4py.drivers.mock.MockTag import MockTagBuilder
-from plc4py.spi.messages.PlcRequest import DefaultReadRequestBuilder
+from typing import Type, Union
 
 import plc4py
-
 from plc4py.api.PlcConnection import PlcConnection, PlcConnectionMetaData
 from plc4py.api.PlcDriver import PlcDriver
 from plc4py.api.authentication.PlcAuthentication import PlcAuthentication
 from plc4py.api.exceptions.exceptions import PlcFieldParseException
-from plc4py.api.messages.PlcField import PlcTag
 from plc4py.api.messages.PlcRequest import (
     ReadRequestBuilder,
     PlcReadRequest,
     PlcRequest,
-    PlcWriteRequest,
 )
 from plc4py.api.messages.PlcResponse import (
     PlcReadResponse,
     PlcResponse,
-    PlcWriteResponse,
 )
-from plc4py.api.value.PlcValue import PlcResponseCode, PlcValue
+from plc4py.api.value.PlcValue import PlcResponseCode
 from plc4py.drivers.PlcDriverLoader import PlcDriverLoader
+from plc4py.drivers.mock.MockTag import MockTagBuilder
 from plc4py.spi.messages.PlcReader import PlcReader
+from plc4py.spi.messages.PlcRequest import DefaultReadRequestBuilder
+from plc4py.spi.messages.PlcWriter import PlcWriter
 from plc4py.spi.messages.utils.ResponseItem import ResponseItem
+from plc4py.spi.transport.MockTransport import MockTransport
+from plc4py.spi.transport.Plc4xBaseTransport import Plc4xBaseTransport
 from plc4py.spi.values.PlcValues import PlcBOOL
 from plc4py.spi.values.PlcValues import PlcINT
 
-from plc4py.spi.transport.Plc4xBaseTransport import Plc4xBaseTransport
-
 
 @dataclass
 class MockDevice:
@@ -70,7 +62,9 @@ class MockDevice:
             logging.debug(f"Reading field {str(tag)} from Mock Device")
 
             if tag.data_type == "BOOL":
-                response_items[tag_name] = ResponseItem(PlcResponseCode.OK, PlcBOOL(False))
+                response_items[tag_name] = ResponseItem(
+                    PlcResponseCode.OK, PlcBOOL(False)
+                )
             elif tag.data_type == "INT":
                 response_items[tag_name] = ResponseItem(PlcResponseCode.OK, PlcINT(0))
             else:
@@ -80,7 +74,6 @@ class MockDevice:
 
 @dataclass
 class MockConnection(PlcConnection, PlcReader, PlcWriter, PlcConnectionMetaData):
-    _is_connected: bool = False
     _device: MockDevice = field(default_factory=lambda: MockDevice())
     _transport: Union[Plc4xBaseTransport, None] = None
 
@@ -95,23 +88,10 @@ class MockConnection(PlcConnection, PlcReader, PlcWriter, PlcConnectionMetaData)
     async def create(url):
         # config = PlcConfiguration(url)
         connection = MockConnection()
+        connection._transport = await MockTransport.create(None, None, None)
         connection._connect()
         return connection
 
-    def is_connected(self) -> bool:
-        """
-        Return the current status of the Mock PLC Connection
-        :return bool: True is connected
-        """
-        return self._is_connected
-
-    def close(self) -> None:
-        """
-        Closes the connection to the remote PLC.
-        :return:
-        """
-        self._is_connected = False
-
     def read_request_builder(self) -> ReadRequestBuilder:
         """
         :return: read request builder.
@@ -132,98 +112,6 @@ class MockConnection(PlcConnection, PlcReader, PlcWriter, PlcConnectionMetaData)
 
         return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
 
-    def _check_connection(self) -> bool:
-        """
-        Checks if a ModbusDevice is set.
-
-        :return: True if no device is set, False otherwise.
-        """
-        """
-        A ModbusDevice is only set if the device was successfully connected during the constructor.
-        If no device is set, it's not possible to execute any read or write requests.
-
-        This method is used to prevent calling methods on the ModbusConnection which are not possible
-        if no device is set.
-        """
-        return self._device is None
-
-    async def _read(self, request: PlcReadRequest) -> PlcReadResponse:
-        """
-        Executes a PlcReadRequest
-
-        This method sends a read request to the connected modbus device and waits for a response.
-        The response is then returned as a PlcReadResponse.
-
-        If no device is set, an error is logged and a PlcResponseCode.NOT_CONNECTED is returned.
-        If an error occurs during the execution of the read request, a PlcResponseCode.INTERNAL_ERROR is
-        returned.
-
-        :param request: PlcReadRequest to execute
-        :return: PlcReadResponse
-        """
-        if self._check_connection():
-            logging.error("No device is set in the Mock connection!")
-            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
-
-        # TODO: Insert Optimizer base on data from a browse request
-        try:
-            logging.debug("Sending read request to Mock Device")
-            response = await asyncio.wait_for(
-                self._device.read(request, self._transport), 10
-            )
-            return response
-        except Exception:
-            # TODO:- This exception is very general and probably should be replaced
-            return PlcReadResponse(PlcResponseCode.INTERNAL_ERROR, {})
-
-    async def _write(self, request: PlcWriteRequest) -> PlcWriteResponse:
-        """
-        Executes a PlcWriteRequest
-
-        This method sends a write request to the connected Modbus device and waits for a response.
-        The response is then returned as a PlcWriteResponse.
-
-        If no device is set, an error is logged and a PlcWriteResponse with the
-        PlcResponseCode.NOT_CONNECTED code is returned.
-        If an error occurs during the execution of the write request, a
-        PlcWriteResponse with the PlcResponseCode.INTERNAL_ERROR code is returned.
-
-        :param request: PlcWriteRequest to execute
-        :return: PlcWriteResponse
-        """
-        if self._check_connection():
-            # If no device is set, log an error and return a response with the NOT_CONNECTED code
-            logging.error("No device is set in the Mock connection!")
-            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
-
-        try:
-            # Send the write request to the device and wait for a response
-            logging.debug("Sending write request to Mock Device")
-            response = await asyncio.wait_for(
-                self._device.write(request, self._transport), 5
-            )
-            # Return the response
-            return response
-        except Exception:
-            # If an error occurs during the execution of the write request, return a response with
-            # the INTERNAL_ERROR code. This exception is very general and probably should be replaced.
-            # TODO:- This exception is very general and probably should be replaced
-            return PlcWriteResponse(PlcResponseCode.INTERNAL_ERROR, {})
-
-    def is_read_supported(self) -> bool:
-        """
-        Indicates if the connection supports read requests.
-        :return: True if connection supports reading, False otherwise
-        """
-        return True
-
-    def is_write_supported(self) -> bool:
-        """
-        Indicates if the connection supports write requests.
-        :return: True if connection supports writing, False otherwise
-        """
-        return False
-
     def is_subscribe_supported(self) -> bool:
         """
         Indicates if the connection supports subscription requests.
diff --git a/plc4py/plc4py/drivers/modbus/ModbusConnection.py b/plc4py/plc4py/drivers/modbus/ModbusConnection.py
index f1c7233f65..fa79e82884 100644
--- a/plc4py/plc4py/drivers/modbus/ModbusConnection.py
+++ b/plc4py/plc4py/drivers/modbus/ModbusConnection.py
@@ -117,40 +117,6 @@ class ModbusConnection(PlcConnection, PlcReader, PlcWriter, PlcConnectionMetaDat
         # using the configuration from the url provided and the transport
         return ModbusConnection(config, transport)
 
-    def is_connected(self) -> bool:
-        """
-        Indicates if the connection is established to a remote PLC.
-
-        :return: True if connection, False otherwise
-        """
-        """
-        The function checks if the connection to the remote PLC is established.
-        The connection is considered established if the transport object is not None
-        and the transport is not closing.
-
-        :return bool: True if connection, False otherwise
-        """
-        if self._transport is not None:
-            # The transport is not None if a connection is established
-            return not self._transport.is_closing()
-        else:
-            # The transport is None if no connection is established
-            return False
-
-    def close(self) -> None:
-        """
-        Closes the connection to the remote PLC.
-        :return:
-        """
-        if self._transport is not None:
-            self._transport.close()
-
-    def read_request_builder(self) -> ReadRequestBuilder:
-        """
-        :return: read request builder.
-        """
-        return DefaultReadRequestBuilder(ModbusTagBuilder)
-
     async def execute(self, request: PlcRequest) -> PlcResponse:
         """
         Executes a PlcRequest as long as it's already connected
@@ -168,97 +134,11 @@ class ModbusConnection(PlcConnection, PlcReader, PlcWriter, PlcConnectionMetaDat
 
         return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
 
-    def _check_connection(self) -> bool:
-        """
-        Checks if a ModbusDevice is set.
-
-        :return: True if no device is set, False otherwise.
-        """
-        """
-        A ModbusDevice is only set if the device was successfully connected during the constructor.
-        If no device is set, it's not possible to execute any read or write requests.
-
-        This method is used to prevent calling methods on the ModbusConnection which are not possible
-        if no device is set.
-        """
-        return self._device is None
-
-    async def _read(self, request: PlcReadRequest) -> PlcReadResponse:
-        """
-        Executes a PlcReadRequest
-
-        This method sends a read request to the connected modbus device and waits for a response.
-        The response is then returned as a PlcReadResponse.
-
-        If no device is set, an error is logged and a PlcResponseCode.NOT_CONNECTED is returned.
-        If an error occurs during the execution of the read request, a PlcResponseCode.INTERNAL_ERROR is
-        returned.
-
-        :param request: PlcReadRequest to execute
-        :return: PlcReadResponse
-        """
-        if self._check_connection():
-            logging.error("No device is set in the Modbus connection!")
-            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
-
-        # TODO: Insert Optimizer base on data from a browse request
-        try:
-            logging.debug("Sending read request to Modbus Device")
-            response = await asyncio.wait_for(
-                self._device.read(request, self._transport), 10
-            )
-            return response
-        except Exception:
-            # TODO:- This exception is very general and probably should be replaced
-            return PlcReadResponse(PlcResponseCode.INTERNAL_ERROR, {})
-
-    async def _write(self, request: PlcWriteRequest) -> PlcWriteResponse:
-        """
-        Executes a PlcWriteRequest
-
-        This method sends a write request to the connected Modbus device and waits for a response.
-        The response is then returned as a PlcWriteResponse.
-
-        If no device is set, an error is logged and a PlcWriteResponse with the
-        PlcResponseCode.NOT_CONNECTED code is returned.
-        If an error occurs during the execution of the write request, a
-        PlcWriteResponse with the PlcResponseCode.INTERNAL_ERROR code is returned.
-
-        :param request: PlcWriteRequest to execute
-        :return: PlcWriteResponse
-        """
-        if self._check_connection():
-            # If no device is set, log an error and return a response with the NOT_CONNECTED code
-            logging.error("No device is set in the modbus connection!")
-            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
-
-        try:
-            # Send the write request to the device and wait for a response
-            logging.debug("Sending write request to Modbus Device")
-            response = await asyncio.wait_for(
-                self._device.write(request, self._transport), 5
-            )
-            # Return the response
-            return response
-        except Exception:
-            # If an error occurs during the execution of the write request, return a response with
-            # the INTERNAL_ERROR code. This exception is very general and probably should be replaced.
-            # TODO:- This exception is very general and probably should be replaced
-            return PlcWriteResponse(PlcResponseCode.INTERNAL_ERROR, {})
-
-    def is_read_supported(self) -> bool:
-        """
-        Indicates if the connection supports read requests.
-        :return: True if connection supports reading, False otherwise
-        """
-        return True
-
-    def is_write_supported(self) -> bool:
+    def read_request_builder(self) -> ReadRequestBuilder:
         """
-        Indicates if the connection supports write requests.
-        :return: True if connection supports writing, False otherwise
+        :return: read request builder.
         """
-        return False
+        return DefaultReadRequestBuilder(ModbusTagBuilder)
 
     def is_subscribe_supported(self) -> bool:
         """
diff --git a/plc4py/plc4py/drivers/umas/UmasConnection.py b/plc4py/plc4py/drivers/umas/UmasConnection.py
index b60fae6e4b..e90a305736 100644
--- a/plc4py/plc4py/drivers/umas/UmasConnection.py
+++ b/plc4py/plc4py/drivers/umas/UmasConnection.py
@@ -25,20 +25,15 @@ from plc4py.api.PlcConnection import PlcConnection, PlcConnectionMetaData
 from plc4py.api.PlcDriver import PlcDriver
 from plc4py.api.authentication.PlcAuthentication import PlcAuthentication
 from plc4py.api.exceptions.exceptions import PlcConnectionException
-from plc4py.api.messages.PlcResponse import (
-    PlcResponse,
-    PlcReadResponse,
-    PlcBrowseResponse,
-    PlcTagResponse,
-    PlcWriteResponse,
-)
 from plc4py.api.messages.PlcRequest import (
     ReadRequestBuilder,
     PlcRequest,
     PlcReadRequest,
     BrowseRequestBuilder,
     PlcBrowseRequest,
-    PlcWriteRequest,
+)
+from plc4py.api.messages.PlcResponse import (
+    PlcResponse,
 )
 from plc4py.api.value.PlcValue import PlcResponseCode
 from plc4py.drivers.PlcDriverLoader import PlcDriverLoader
@@ -46,17 +41,17 @@ from plc4py.drivers.umas.UmasConfiguration import UmasConfiguration
 from plc4py.drivers.umas.UmasDevice import UmasDevice
 from plc4py.drivers.umas.UmasProtocol import UmasProtocol
 from plc4py.drivers.umas.UmasTag import UmasTagBuilder
+from plc4py.spi.messages.PlcBrowser import PlcBrowser
 from plc4py.spi.messages.PlcReader import PlcReader
 from plc4py.spi.messages.PlcRequest import (
     DefaultReadRequestBuilder,
     DefaultBrowseRequestBuilder,
 )
-from plc4py.spi.messages.PlcWriter import PlcWriter
 from plc4py.spi.transport.Plc4xBaseTransport import Plc4xBaseTransport
 from plc4py.spi.transport.TCPTransport import TCPTransport
 
 
-class UmasConnection(PlcConnection, PlcReader, PlcWriter, PlcConnectionMetaData):
+class UmasConnection(PlcConnection, PlcReader, PlcBrowser, PlcConnectionMetaData):
     """
     Umas TCP PLC connection implementation
     """
@@ -113,24 +108,6 @@ class UmasConnection(PlcConnection, PlcReader, PlcWriter, PlcConnectionMetaData)
             )
         return connection
 
-    def is_connected(self) -> bool:
-        """
-        Indicates if the connection is established to a remote PLC.
-        :return: True if connection, False otherwise
-        """
-        if self._transport is not None:
-            return not self._transport.is_closing()
-        else:
-            return False
-
-    def close(self) -> None:
-        """
-        Closes the connection to the remote PLC.
-        :return:
-        """
-        if self._transport is not None:
-            self._transport.close()
-
     def read_request_builder(self) -> ReadRequestBuilder:
         """
         :return: read request builder.
@@ -160,108 +137,6 @@ class UmasConnection(PlcConnection, PlcReader, PlcWriter, PlcConnectionMetaData)
 
         return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
 
-    async def _read(self, request: PlcReadRequest) -> PlcReadResponse:
-        """
-        Executes a PlcReadRequest
-
-        This method sends a read request to the connected modbus device and waits for a response.
-        The response is then returned as a PlcReadResponse.
-
-        If no device is set, an error is logged and a PlcResponseCode.NOT_CONNECTED is returned.
-        If an error occurs during the execution of the read request, a PlcResponseCode.INTERNAL_ERROR is
-        returned.
-
-        :param request: PlcReadRequest to execute
-        :return: PlcReadResponse
-        """
-        if self._check_connection():
-            logging.error("No device is set in the Umas connection!")
-            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
-
-        # TODO: Insert Optimizer base on data from a browse request
-        try:
-            logging.debug("Sending read request to Umas Device")
-            response = await asyncio.wait_for(
-                self._device.read(request, self._transport), 10
-            )
-            return response
-        except Exception:
-            # TODO:- This exception is very general and probably should be replaced
-            return PlcReadResponse(PlcResponseCode.INTERNAL_ERROR, {})
-
-    async def _write(self, request: PlcWriteRequest) -> PlcWriteResponse:
-        """
-        Executes a PlcWriteRequest
-
-        This method sends a write request to the connected Modbus device and waits for a response.
-        The response is then returned as a PlcWriteResponse.
-
-        If no device is set, an error is logged and a PlcWriteResponse with the
-        PlcResponseCode.NOT_CONNECTED code is returned.
-        If an error occurs during the execution of the write request, a
-        PlcWriteResponse with the PlcResponseCode.INTERNAL_ERROR code is returned.
-
-        :param request: PlcWriteRequest to execute
-        :return: PlcWriteResponse
-        """
-        if self._check_connection():
-            # If no device is set, log an error and return a response with the NOT_CONNECTED code
-            logging.error("No device is set in the Umas connection!")
-            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
-
-        try:
-            # Send the write request to the device and wait for a response
-            logging.debug("Sending write request to Umas Device")
-            response = await asyncio.wait_for(
-                self._device.write(request, self._transport), 5
-            )
-            # Return the response
-            return response
-        except Exception:
-            # If an error occurs during the execution of the write request, return a response with
-            # the INTERNAL_ERROR code. This exception is very general and probably should be replaced.
-            # TODO:- This exception is very general and probably should be replaced
-            return PlcWriteResponse(PlcResponseCode.INTERNAL_ERROR, {})
-
-    def _browse(self, request: PlcBrowseRequest) -> Awaitable[PlcBrowseResponse]:
-        """
-        Executes a PlcBrowseRequest
-        """
-        if self._device is None:
-            logging.error("No device is set in the Umas connection!")
-            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
-
-        async def _request(req, device) -> PlcBrowseResponse:
-            try:
-                response = await asyncio.wait_for(
-                    device.browse(req, self._transport), 10
-                )
-                return response
-            except Exception:
-                # TODO:- This exception is very general and probably should be replaced
-                self.log.exception(
-                    "Caught an exception while executing a browse request"
-                )
-                return PlcBrowseResponse(PlcResponseCode.INTERNAL_ERROR, {})
-
-        logging.debug("Sending browse request to UmasDevice")
-        future = asyncio.ensure_future(_request(request, self._device))
-        return future
-
-    def is_read_supported(self) -> bool:
-        """
-        Indicates if the connection supports read requests.
-        :return: True if connection supports reading, False otherwise
-        """
-        return True
-
-    def is_write_supported(self) -> bool:
-        """
-        Indicates if the connection supports write requests.
-        :return: True if connection supports writing, False otherwise
-        """
-        return False
-
     def is_subscribe_supported(self) -> bool:
         """
         Indicates if the connection supports subscription requests.
@@ -269,13 +144,6 @@ class UmasConnection(PlcConnection, PlcReader, PlcWriter, PlcConnectionMetaData)
         """
         return False
 
-    def is_browse_supported(self) -> bool:
-        """
-        Indicates if the connection supports browsing requests.
-        :return: True if connection supports browsing, False otherwise
-        """
-        return True
-
 
 class UmasDriver(PlcDriver):
     def __init__(self):
diff --git a/plc4py/plc4py/spi/messages/PlcBrowser.py b/plc4py/plc4py/spi/messages/PlcBrowser.py
new file mode 100644
index 0000000000..768ddfcf1c
--- /dev/null
+++ b/plc4py/plc4py/spi/messages/PlcBrowser.py
@@ -0,0 +1,60 @@
+#
+# 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
+#
+#     https://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.
+#
+import asyncio
+import logging
+
+from plc4py.api.messages.PlcRequest import PlcBrowseRequest
+from plc4py.api.messages.PlcResponse import PlcBrowseResponse
+from plc4py.api.value.PlcValue import PlcResponseCode
+
+
+class PlcBrowser:
+    """
+    Interface implemented by all PlcConnections that are able to read from remote resources.
+    """
+
+    def __init__(self):
+        self._transport = None
+        self._device = None
+
+    async def _browse(self, request: PlcBrowseRequest) -> PlcBrowseResponse:
+        """
+        Executes a PlcBrowseRequest
+        """
+
+        try:
+            # Send the browse request to the device and wait for a response
+            logging.debug("Sending browse request to Device")
+            response = await asyncio.wait_for(
+                self._device.browse(request, self._transport), 5
+            )
+            # Return the response
+            return response
+        except Exception:
+            # If an error occurs during the execution of the browse request, return a response with
+            # the INTERNAL_ERROR code. This exception is very general and probably should be replaced.
+            # TODO:- This exception is very general and probably should be replaced
+            return PlcBrowseResponse(PlcResponseCode.INTERNAL_ERROR, {})
+
+    def is_browse_supported(self) -> bool:
+        """
+        Indicates if the connection supports browsing requests.
+        :return: True if connection supports browsing, False otherwise
+        """
+        return True
diff --git a/plc4py/plc4py/spi/messages/PlcReader.py b/plc4py/plc4py/spi/messages/PlcReader.py
index 40cb17fb7e..44b3aabc3a 100644
--- a/plc4py/plc4py/spi/messages/PlcReader.py
+++ b/plc4py/plc4py/spi/messages/PlcReader.py
@@ -16,11 +16,12 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-from typing import Awaitable
+import asyncio
+import logging
 
 from plc4py.api.messages.PlcRequest import PlcReadRequest
 from plc4py.api.messages.PlcResponse import PlcReadResponse
+from plc4py.api.value.PlcValue import PlcResponseCode
 
 
 class PlcReader:
@@ -28,10 +29,39 @@ class PlcReader:
     Interface implemented by all PlcConnections that are able to read from remote resources.
     """
 
-    def _read(self, request: PlcReadRequest) -> Awaitable[PlcReadResponse]:
+    def __init__(self):
+        self._transport = None
+        self._device = None
+
+    async def _read(self, request: PlcReadRequest) -> PlcReadResponse:
+        """
+        Executes a PlcReadRequest
+
+        This method sends a read request to the connected device and waits for a response.
+        The response is then returned as a PlcReadResponse.
+
+        If no device is set, an error is logged and a PlcResponseCode.NOT_CONNECTED is returned.
+        If an error occurs during the execution of the read request, a PlcResponseCode.INTERNAL_ERROR is
+        returned.
+
+        :param request: PlcReadRequest to execute
+        :return: PlcReadResponse
         """
-        Reads a requested value from a PLC
 
-        :param request: object describing the type and location of the value
-        :return: Future, giving async access to the returned value
+        # TODO: Insert Optimizer base on data from a browse request
+        try:
+            logging.debug("Sending read request to Device")
+            response = await asyncio.wait_for(
+                self._device.read(request, self._transport), 10
+            )
+            return response
+        except Exception:
+            # TODO:- This exception is very general and probably should be replaced
+            return PlcReadResponse(PlcResponseCode.INTERNAL_ERROR, {})
+
+    def is_read_supported(self) -> bool:
+        """
+        Indicates if the connection supports read requests.
+        :return: True if connection supports reading, False otherwise
         """
+        return True
diff --git a/plc4py/plc4py/spi/messages/PlcWriter.py b/plc4py/plc4py/spi/messages/PlcWriter.py
index a4186beb6f..fd7f4a8f59 100644
--- a/plc4py/plc4py/spi/messages/PlcWriter.py
+++ b/plc4py/plc4py/spi/messages/PlcWriter.py
@@ -16,11 +16,12 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-from typing import Awaitable
+import asyncio
+import logging
 
 from plc4py.api.messages.PlcRequest import PlcWriteRequest
 from plc4py.api.messages.PlcResponse import PlcWriteResponse
+from plc4py.api.value.PlcValue import PlcResponseCode
 
 
 class PlcWriter:
@@ -28,10 +29,43 @@ class PlcWriter:
     Interface implemented by all PlcConnections that are able to write from remote resources.
     """
 
-    def _write(self, request: PlcWriteRequest) -> Awaitable[PlcWriteResponse]:
+    def __init__(self):
+        self._transport = None
+        self._device = None
+
+    async def _write(self, request: PlcWriteRequest) -> PlcWriteResponse:
+        """
+        Executes a PlcWriteRequest
+
+        This method sends a write request to the connected Modbus device and waits for a response.
+        The response is then returned as a PlcWriteResponse.
+
+        If no device is set, an error is logged and a PlcWriteResponse with the
+        PlcResponseCode.NOT_CONNECTED code is returned.
+        If an error occurs during the execution of the write request, a
+        PlcWriteResponse with the PlcResponseCode.INTERNAL_ERROR code is returned.
+
+        :param request: PlcWriteRequest to execute
+        :return: PlcWriteResponse
         """
-        Writes a requested value to a PLC
 
-        :param request: object describing the type and location of the value
-        :return: Future, giving async access to the returned value
+        try:
+            # Send the write request to the device and wait for a response
+            logging.debug("Sending write request to Device")
+            response = await asyncio.wait_for(
+                self._device.write(request, self._transport), 5
+            )
+            # Return the response
+            return response
+        except Exception:
+            # If an error occurs during the execution of the write request, return a response with
+            # the INTERNAL_ERROR code. This exception is very general and probably should be replaced.
+            # TODO:- This exception is very general and probably should be replaced
+            return PlcWriteResponse(PlcResponseCode.INTERNAL_ERROR, {})
+
+    def is_write_supported(self) -> bool:
+        """
+        Indicates if the connection supports write requests.
+        :return: True if connection supports writing, False otherwise
         """
+        return True
diff --git a/plc4py/plc4py/spi/messages/PlcReader.py b/plc4py/plc4py/spi/transport/MockTransport.py
similarity index 56%
copy from plc4py/plc4py/spi/messages/PlcReader.py
copy to plc4py/plc4py/spi/transport/MockTransport.py
index 40cb17fb7e..e0a740e866 100644
--- a/plc4py/plc4py/spi/messages/PlcReader.py
+++ b/plc4py/plc4py/spi/transport/MockTransport.py
@@ -16,22 +16,32 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+import asyncio
+import logging
+from asyncio import Protocol
+from dataclasses import dataclass
+from typing import Callable
 
-from typing import Awaitable
+from plc4py.spi.transport.Plc4xBaseTransport import Plc4xBaseTransport
 
-from plc4py.api.messages.PlcRequest import PlcReadRequest
-from plc4py.api.messages.PlcResponse import PlcReadResponse
 
-
-class PlcReader:
+@dataclass
+class MockTransport(Plc4xBaseTransport):
     """
-    Interface implemented by all PlcConnections that are able to read from remote resources.
+    Wrapper for the Mock Transport
     """
 
-    def _read(self, request: PlcReadRequest) -> Awaitable[PlcReadResponse]:
-        """
-        Reads a requested value from a PLC
+    host: str
+    port: int
+
+    def write(self, data):
+        logging.debug("Writing data to Mock Transport")
+
+    def is_closing(self):
+        return False
 
-        :param request: object describing the type and location of the value
-        :return: Future, giving async access to the returned value
-        """
+    @staticmethod
+    async def create(
+        protocol_factory: Callable[[], Protocol], host: str, port: int
+    ) -> Plc4xBaseTransport:
+        return MockTransport(None, None, host, port)