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 ce60af02f5 fix(plc4py): added PlcDriver and moved Mock Driver to plugin
ce60af02f5 is described below

commit ce60af02f545254eedd76c4390b5dbc710c56acf
Author: Ben Hutcheson <[email protected]>
AuthorDate: Thu Apr 28 07:04:34 2022 +1000

    fix(plc4py): added PlcDriver and moved Mock Driver to plugin
    
    * Updated the Mock Connection and changed it to a driver module
    
    * Updated PlcValue interface to include stub methods for get_*
    
    * Add some doc strings
    
    * Add test for mock read request
    
    * Fixed the way futures are created
    
    It now uses asyncio.ensure_future and doesn't rely on the explicitly 
setting a result
    
    * Realized I missed the PlcDriver class and went straight to PlcConnection
    
    * Finished including PlcDriver
---
 sandbox/plc4py/plc4py/PlcDriverManager.py          |  11 +-
 sandbox/plc4py/plc4py/api/PlcConnection.py         |  23 ++-
 sandbox/plc4py/plc4py/api/PlcDriver.py             |  70 ++++++++
 .../api/authentication/PlcAuthentication.py}       |   4 +
 .../test => plc4py/api/authentication}/__init__.py |   0
 sandbox/plc4py/plc4py/api/exceptions/exceptions.py |   8 +
 .../api/messages/{PlcField.py => PlcDiscovery.py}  |  10 +-
 sandbox/plc4py/plc4py/api/messages/PlcField.py     |  13 ++
 sandbox/plc4py/plc4py/api/value/PlcValue.py        |   6 +
 .../{PlcConnectionLoader.py => PlcDriverLoader.py} |   6 +-
 .../plc4py/plc4py/drivers/mock/MockConnection.py   | 197 +++++++++++++++++++++
 .../drivers/mock}/MockReadRequestBuilder.py        |   0
 .../api/test => plc4py/drivers/mock}/__init__.py   |   0
 .../plc4py/drivers/modbus/ModbusConnection.py      |  27 ++-
 sandbox/plc4py/plc4py/spi/PlcDriverClassLoader.py  |   4 +-
 .../PlcReader.py}                                  |  28 ++-
 sandbox/plc4py/setup.py                            |   3 +-
 sandbox/plc4py/tests/test_plc4py.py                |  28 +++
 .../tests/unit/plc4py/api/test/MockPlcConection.py |  83 ---------
 .../tests/unit/plc4py/api/test_PlcRequest.py       |  64 ++++++-
 .../tests/unit/plc4py/test_PlcDriverManager.py     |   4 +-
 21 files changed, 462 insertions(+), 127 deletions(-)

diff --git a/sandbox/plc4py/plc4py/PlcDriverManager.py 
b/sandbox/plc4py/plc4py/PlcDriverManager.py
index 8f67acb7df..d5df488252 100644
--- a/sandbox/plc4py/plc4py/PlcDriverManager.py
+++ b/sandbox/plc4py/plc4py/PlcDriverManager.py
@@ -24,6 +24,7 @@ from typing import Generator, Type
 from pluggy import PluginManager  # type: ignore
 
 from plc4py.api.PlcConnection import PlcConnection
+from plc4py.api.PlcDriver import PlcDriver
 from plc4py.spi.PlcDriverClassLoader import PlcDriverClassLoader
 from plc4py.utils.ConnectionStringHandling import get_protocol_code
 
@@ -31,7 +32,7 @@ from plc4py.utils.ConnectionStringHandling import 
get_protocol_code
 @dataclass
 class PlcDriverManager:
     class_loader: PluginManager = field(default_factory=lambda: 
PluginManager("plc4py"))
-    _driverMap: dict[str, Type[PlcConnection]] = field(default_factory=lambda: 
{})
+    _driverMap: dict[str, Type[PlcDriver]] = field(default_factory=lambda: {})
 
     def __post_init__(self):
         logging.info(
@@ -47,7 +48,7 @@ class PlcDriverManager:
         self._driverMap = {
             key: loader
             for key, loader in zip(
-                self.class_loader.hook.key(), 
self.class_loader.hook.get_connection()
+                self.class_loader.hook.key(), 
self.class_loader.hook.get_driver()
             )
         }
         for driver in self._driverMap:
@@ -77,7 +78,7 @@ class PlcDriverManager:
         :return: plc connection
         """
         protocol_code = get_protocol_code(url)
-        return self._driverMap[protocol_code](url)
+        return self._driverMap[protocol_code]().get_connection(url)
 
     def list_drivers(self) -> list[str]:
         """
@@ -86,7 +87,7 @@ class PlcDriverManager:
         """
         return list(self._driverMap.keys())
 
-    def get_driver(self, protocol_code: str) -> Type[PlcConnection]:
+    def get_driver(self, protocol_code: str) -> Type[PlcDriver]:
         """
         Returns suitable driver for protocol or throws an Exception.
         :param protocol_code: protocolCode protocol code identifying the driver
@@ -94,7 +95,7 @@ class PlcDriverManager:
         """
         return self._driverMap[protocol_code]
 
-    def get_driver_for_url(self, url: str) -> Type[PlcConnection]:
+    def get_driver_for_url(self, url: str) -> Type[PlcDriver]:
         """
         Returns the driver class that matches that identified within the 
connection string
         :param url: The plc connection string
diff --git a/sandbox/plc4py/plc4py/api/PlcConnection.py 
b/sandbox/plc4py/plc4py/api/PlcConnection.py
index 52c439ccc0..7f85ea0f88 100644
--- a/sandbox/plc4py/plc4py/api/PlcConnection.py
+++ b/sandbox/plc4py/plc4py/api/PlcConnection.py
@@ -16,12 +16,14 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+import asyncio
 from abc import abstractmethod
 from typing import Awaitable
 
-from plc4py.api.messages.PlcResponse import PlcResponse
-from plc4py.api.messages.PlcRequest import ReadRequestBuilder
+from plc4py.api.messages.PlcResponse import PlcResponse, PlcReadResponse
+from plc4py.api.messages.PlcRequest import ReadRequestBuilder, PlcRequest
 from plc4py.api.exceptions.exceptions import PlcConnectionException
+from plc4py.api.value.PlcValue import PlcResponseCode
 from plc4py.utils.GenericTypes import GenericGenerator
 
 
@@ -60,10 +62,23 @@ class PlcConnection(GenericGenerator):
         pass
 
     @abstractmethod
-    def execute(self, PlcRequest) -> Awaitable[PlcResponse]:
+    def execute(self, request: PlcRequest) -> Awaitable[PlcResponse]:
         """
         Executes a PlcRequest as long as it's already connected
-        :param PlcRequest: Plc Request to execute
+        :param request: Plc Request to execute
         :return: The response from the Plc/Device
         """
         pass
+
+    def _default_failed_request(
+        self, code: PlcResponseCode
+    ) -> Awaitable[PlcReadResponse]:
+        """
+        Returns a default PlcResponse, mainly used in case of a failed request
+        :param code: The response code to return
+        :return: The PlcResponse
+        """
+        loop = asyncio.get_running_loop()
+        future = loop.create_future()
+        future.set_result(PlcResponse(code))
+        return future
diff --git a/sandbox/plc4py/plc4py/api/PlcDriver.py 
b/sandbox/plc4py/plc4py/api/PlcDriver.py
new file mode 100644
index 0000000000..e7ea76bf8b
--- /dev/null
+++ b/sandbox/plc4py/plc4py/api/PlcDriver.py
@@ -0,0 +1,70 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+from abc import abstractmethod
+from dataclasses import dataclass
+
+from plc4py.api.PlcConnection import PlcConnection
+from plc4py.api.authentication.PlcAuthentication import PlcAuthentication
+from plc4py.api.exceptions.exceptions import PlcNotImplementedException
+from plc4py.api.messages.PlcDiscovery import PlcDiscoveryRequestBuilder
+
+
+@dataclass
+class PlcDriverMetaData:
+    """
+    Information about driver capabilities
+    """
+
+    """Indicates that the driver supports discovery"""
+    can_discover: bool = False
+
+
+class PlcDriver:
+    """
+    General interface defining the minimal methods required for adding a new 
type of driver to the PLC4PY system.
+
+    <b>Note that each driver has to add a setuptools entrypoint as 
plc4x.driver in order to be loaded by pluggy</b>
+    """
+
+    def __init__(self):
+        self.protocol_code: str
+        self.protocol_name: str
+
+    @property
+    def metadata(self):
+        return PlcDriverMetaData()
+
+    @abstractmethod
+    def get_connection(
+        self, url: str, authentication: PlcAuthentication = PlcAuthentication()
+    ) -> PlcConnection:
+        """
+        Connects to a PLC using the given plc connection string.
+        :param url: plc connection string
+        :param authentication: authentication credentials.
+        :return PlcConnection: PLC Connection object
+        """
+        pass
+
+    def discovery_request_builder(self) -> PlcDiscoveryRequestBuilder:
+        """
+        Discovery Request Builder aids in generating a discovery request for 
this protocol
+        :return builder: Discovery request builder
+        """
+        raise PlcNotImplementedException(f"Not implemented for 
{self.protocol_name}")
diff --git a/sandbox/plc4py/tests/unit/plc4py/api/test/__init__.py 
b/sandbox/plc4py/plc4py/api/authentication/PlcAuthentication.py
similarity index 95%
copy from sandbox/plc4py/tests/unit/plc4py/api/test/__init__.py
copy to sandbox/plc4py/plc4py/api/authentication/PlcAuthentication.py
index 585be9602f..c2b409f19b 100644
--- a/sandbox/plc4py/tests/unit/plc4py/api/test/__init__.py
+++ b/sandbox/plc4py/plc4py/api/authentication/PlcAuthentication.py
@@ -16,3 +16,7 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+
+
+class PlcAuthentication:
+    pass
diff --git a/sandbox/plc4py/tests/unit/plc4py/api/test/__init__.py 
b/sandbox/plc4py/plc4py/api/authentication/__init__.py
similarity index 100%
copy from sandbox/plc4py/tests/unit/plc4py/api/test/__init__.py
copy to sandbox/plc4py/plc4py/api/authentication/__init__.py
diff --git a/sandbox/plc4py/plc4py/api/exceptions/exceptions.py 
b/sandbox/plc4py/plc4py/api/exceptions/exceptions.py
index 7a7eb4647b..2d41acd8bc 100644
--- a/sandbox/plc4py/plc4py/api/exceptions/exceptions.py
+++ b/sandbox/plc4py/plc4py/api/exceptions/exceptions.py
@@ -25,3 +25,11 @@ class PlcException(Exception):
 
 class PlcConnectionException(Exception):
     logging.error("Unable to establish a connection to the plc")
+
+
+class PlcFieldParseException(Exception):
+    pass
+
+
+class PlcNotImplementedException(Exception):
+    pass
diff --git a/sandbox/plc4py/plc4py/api/messages/PlcField.py 
b/sandbox/plc4py/plc4py/api/messages/PlcDiscovery.py
similarity index 90%
copy from sandbox/plc4py/plc4py/api/messages/PlcField.py
copy to sandbox/plc4py/plc4py/api/messages/PlcDiscovery.py
index fce052b1ab..04049f82b1 100644
--- a/sandbox/plc4py/plc4py/api/messages/PlcField.py
+++ b/sandbox/plc4py/plc4py/api/messages/PlcDiscovery.py
@@ -16,9 +16,11 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-from dataclasses import dataclass
 
 
-@dataclass
-class PlcField:
-    name: str
+class PlcDiscoveryRequest:
+    pass
+
+
+class PlcDiscoveryRequestBuilder:
+    pass
diff --git a/sandbox/plc4py/plc4py/api/messages/PlcField.py 
b/sandbox/plc4py/plc4py/api/messages/PlcField.py
index fce052b1ab..57ed476ddc 100644
--- a/sandbox/plc4py/plc4py/api/messages/PlcField.py
+++ b/sandbox/plc4py/plc4py/api/messages/PlcField.py
@@ -21,4 +21,17 @@ from dataclasses import dataclass
 
 @dataclass
 class PlcField:
+    """
+    Base type for all field types.
+    Typically every driver provides an implementation of this interface in 
order
+    to be able to describe the fields of a resource. As this is completely 
tied to
+    the implemented protocol, this base interface makes absolutely no 
assumption to
+    any information it should provide.
+
+    In order to stay platform and protocol independent every driver connection 
implementation
+    provides a prepareField(String) method that is able to parse a string 
representation of
+    a resource into it's individual field type. Manually constructing PlcField 
objects
+    manually makes the solution less independent from the protocol, but might 
be faster.
+    """
+
     name: str
diff --git a/sandbox/plc4py/plc4py/api/value/PlcValue.py 
b/sandbox/plc4py/plc4py/api/value/PlcValue.py
index 6e2bd59d4b..bded459b4f 100644
--- a/sandbox/plc4py/plc4py/api/value/PlcValue.py
+++ b/sandbox/plc4py/plc4py/api/value/PlcValue.py
@@ -28,6 +28,12 @@ T = TypeVar("T")
 class PlcValue(Generic[T], ABC):
     value: T
 
+    def get_bool(self):
+        return self.value
+
+    def get_int(self):
+        return self.value
+
 
 class PlcResponseCode(Enum):
     OK = auto()
diff --git a/sandbox/plc4py/plc4py/drivers/PlcConnectionLoader.py 
b/sandbox/plc4py/plc4py/drivers/PlcDriverLoader.py
similarity index 91%
rename from sandbox/plc4py/plc4py/drivers/PlcConnectionLoader.py
rename to sandbox/plc4py/plc4py/drivers/PlcDriverLoader.py
index cc8b757ad7..921bcf2b86 100644
--- a/sandbox/plc4py/plc4py/drivers/PlcConnectionLoader.py
+++ b/sandbox/plc4py/plc4py/drivers/PlcDriverLoader.py
@@ -19,10 +19,10 @@
 from abc import abstractmethod
 from typing import Type
 
-from plc4py.api.PlcConnection import PlcConnection
+from plc4py.api.PlcDriver import PlcDriver
 
 
-class PlcConnectionLoader:
+class PlcDriverLoader:
     """
     Abstract class for Plc Driver Loaders.
     Each method should use the @hookimpl decorator to indicate it is a driver 
loader
@@ -30,7 +30,7 @@ class PlcConnectionLoader:
 
     @staticmethod
     @abstractmethod
-    def get_connection() -> Type[PlcConnection]:
+    def get_driver() -> Type[PlcDriver]:
         """
         :return Type[PlcConnection]: Returns the PlcConnection class that is 
used to instantiate the driver
         """
diff --git a/sandbox/plc4py/plc4py/drivers/mock/MockConnection.py 
b/sandbox/plc4py/plc4py/drivers/mock/MockConnection.py
new file mode 100644
index 0000000000..ce3cc98bf8
--- /dev/null
+++ b/sandbox/plc4py/plc4py/drivers/mock/MockConnection.py
@@ -0,0 +1,197 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import asyncio
+import logging
+from dataclasses import dataclass, field
+from typing import Awaitable, Type
+
+import plc4py
+
+from plc4py.api.PlcConnection import PlcConnection
+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 PlcField
+from plc4py.api.messages.PlcRequest import (
+    ReadRequestBuilder,
+    PlcReadRequest,
+    PlcRequest,
+)
+from plc4py.api.messages.PlcResponse import PlcReadResponse, PlcResponse
+from plc4py.api.value.PlcValue import PlcResponseCode, PlcValue
+from plc4py.drivers.PlcDriverLoader import PlcDriverLoader
+from plc4py.spi.messages.PlcReader import PlcReader
+from plc4py.spi.messages.utils.ResponseItem import ResponseItem
+from plc4py.spi.values.PlcBOOL import PlcBOOL
+from plc4py.spi.values.PlcINT import PlcINT
+from plc4py.drivers.mock.MockReadRequestBuilder import MockReadRequestBuilder
+
+
+@dataclass
+class MockPlcField(PlcField):
+    """
+    Mock PLC Field type
+    """
+
+    datatype: str = "INT"
+
+
+class MockPlcFieldHandler:
+    """
+    Helper class to generate MockPlcField based on a fieldquery
+    """
+
+    @staticmethod
+    def of(fieldquery: str) -> MockPlcField:
+        """
+        :param fieldquery: Field identifier string e.g. '1:BOOL'
+        :return: A MockPlcField with the datatype populated
+        """
+        try:
+            datatype = fieldquery.split(":")[1]
+            return MockPlcField(fieldquery, datatype)
+        except IndexError:
+            raise PlcFieldParseException
+
+
+@dataclass
+class MockDevice:
+    fields: dict[str, PlcValue] = field(default_factory=lambda: {})
+
+    def read(self, field: str) -> list[ResponseItem[PlcValue]]:
+        """
+        Reads one field from the Mock Device
+        """
+        logging.debug(f"Reading field {field} from Mock Device")
+        plc_field = MockPlcFieldHandler.of(field)
+        if plc_field.datatype == "BOOL":
+            self.fields[field] = PlcBOOL(False)
+            return [ResponseItem(PlcResponseCode.OK, self.fields[field])]
+        elif plc_field.datatype == "INT":
+            self.fields[field] = PlcINT(0)
+            return [ResponseItem(PlcResponseCode.OK, self.fields[field])]
+        else:
+            raise PlcFieldParseException
+
+
+@dataclass
+class MockConnection(PlcConnection, PlcReader):
+    _is_connected: bool = False
+    device: MockDevice = field(default_factory=lambda: MockDevice())
+
+    def connect(self):
+        """
+        Connect the Mock PLC connection
+        :return:
+        """
+        self._is_connected = True
+        self.device = MockDevice()
+
+    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.
+        """
+        return MockReadRequestBuilder()
+
+    def execute(self, request: PlcRequest) -> Awaitable[PlcResponse]:
+        """
+        Executes a PlcRequest as long as it's already connected
+        :param PlcRequest: Plc Request to execute
+        :return: The response from the Plc/Device
+        """
+        if not self.is_connected():
+            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
+
+        if isinstance(request, PlcReadRequest):
+            return self._read(request)
+
+        return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
+
+    def _read(self, request: PlcReadRequest) -> Awaitable[PlcReadResponse]:
+        """
+        Executes a PlcReadRequest
+        """
+        if self.device is None:
+            logging.error("No device is set in the mock connection!")
+            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
+
+        async def _request(req, device) -> PlcReadResponse:
+            try:
+                response = PlcReadResponse(
+                    PlcResponseCode.OK,
+                    req.fields,
+                    {field: device.read(field) for field in req.field_names},
+                )
+                return response
+            except Exception:
+                # TODO:- This exception is very general and probably should be 
replaced
+                return PlcReadResponse(PlcResponseCode.INTERNAL_ERROR, 
req.fields, {})
+
+        logging.debug("Sending read request to MockDevice")
+        future = asyncio.ensure_future(_request(request, self.device))
+        return future
+
+
+class MockDriver(PlcDriver):
+    def __init__(self):
+        self.protocol_code = "mock"
+        self.protocol_name = "Mock"
+
+    def get_connection(
+        self, url: str, authentication: PlcAuthentication = PlcAuthentication()
+    ) -> PlcConnection:
+        """
+        Connects to a PLC using the given plc connection string.
+        :param url: plc connection string
+        :param authentication: authentication credentials.
+        :return PlcConnection: PLC Connection object
+        """
+        return MockConnection()
+
+
+class MockDriverLoader(PlcDriverLoader):
+    """
+    Mock Connection Loader, after adding this to the setuptools entry point
+    pluggy should be able to find this and import it.
+    """
+
+    @staticmethod
+    @plc4py.hookimpl
+    def get_driver() -> Type[MockDriver]:
+        return MockDriver
+
+    @staticmethod
+    @plc4py.hookimpl
+    def key() -> str:
+        return "mock"
diff --git 
a/sandbox/plc4py/tests/unit/plc4py/api/test/MockReadRequestBuilder.py 
b/sandbox/plc4py/plc4py/drivers/mock/MockReadRequestBuilder.py
similarity index 100%
rename from sandbox/plc4py/tests/unit/plc4py/api/test/MockReadRequestBuilder.py
rename to sandbox/plc4py/plc4py/drivers/mock/MockReadRequestBuilder.py
diff --git a/sandbox/plc4py/tests/unit/plc4py/api/test/__init__.py 
b/sandbox/plc4py/plc4py/drivers/mock/__init__.py
similarity index 100%
rename from sandbox/plc4py/tests/unit/plc4py/api/test/__init__.py
rename to sandbox/plc4py/plc4py/drivers/mock/__init__.py
diff --git a/sandbox/plc4py/plc4py/drivers/modbus/ModbusConnection.py 
b/sandbox/plc4py/plc4py/drivers/modbus/ModbusConnection.py
index c4f14e8b29..7eba4e3ea6 100644
--- a/sandbox/plc4py/plc4py/drivers/modbus/ModbusConnection.py
+++ b/sandbox/plc4py/plc4py/drivers/modbus/ModbusConnection.py
@@ -20,9 +20,11 @@ from typing import Type, Awaitable
 
 import plc4py
 from plc4py.api.PlcConnection import PlcConnection
+from plc4py.api.PlcDriver import PlcDriver
+from plc4py.api.authentication.PlcAuthentication import PlcAuthentication
 from plc4py.api.messages.PlcResponse import PlcResponse
 from plc4py.api.messages.PlcRequest import ReadRequestBuilder
-from plc4py.drivers.PlcConnectionLoader import PlcConnectionLoader
+from plc4py.drivers.PlcDriverLoader import PlcDriverLoader
 
 
 class ModbusConnection(PlcConnection):
@@ -66,11 +68,28 @@ class ModbusConnection(PlcConnection):
         pass
 
 
-class ModbusConnectionLoader(PlcConnectionLoader):
+class ModbusDriver(PlcDriver):
+    def __init__(self):
+        self.protocol_code = "modbus"
+        self.protocol_name = "Modbus"
+
+    def get_connection(
+        self, url: str, authentication: PlcAuthentication = PlcAuthentication()
+    ) -> PlcConnection:
+        """
+        Connects to a PLC using the given plc connection string.
+        :param url: plc connection string
+        :param authentication: authentication credentials.
+        :return PlcConnection: PLC Connection object
+        """
+        return ModbusConnection(url)
+
+
+class ModbusDriverLoader(PlcDriverLoader):
     @staticmethod
     @plc4py.hookimpl
-    def get_connection() -> Type[ModbusConnection]:
-        return ModbusConnection
+    def get_driver() -> Type[ModbusDriver]:
+        return ModbusDriver
 
     @staticmethod
     @plc4py.hookimpl
diff --git a/sandbox/plc4py/plc4py/spi/PlcDriverClassLoader.py 
b/sandbox/plc4py/plc4py/spi/PlcDriverClassLoader.py
index a500aceef0..76901feb8d 100644
--- a/sandbox/plc4py/plc4py/spi/PlcDriverClassLoader.py
+++ b/sandbox/plc4py/plc4py/spi/PlcDriverClassLoader.py
@@ -20,7 +20,7 @@ from typing import Type
 
 import pluggy
 
-from plc4py.api.PlcConnection import PlcConnection
+from plc4py.api.PlcDriver import PlcDriver
 
 
 class PlcDriverClassLoader:
@@ -30,7 +30,7 @@ class PlcDriverClassLoader:
 
     @staticmethod
     @hookspec
-    def get_connection() -> Type[PlcConnection]:
+    def get_driver() -> Type[PlcDriver]:
         """Returns the PlcConnection class that is used to instantiate the 
driver"""
 
     @staticmethod
diff --git a/sandbox/plc4py/plc4py/spi/PlcDriverClassLoader.py 
b/sandbox/plc4py/plc4py/spi/messages/PlcReader.py
similarity index 58%
copy from sandbox/plc4py/plc4py/spi/PlcDriverClassLoader.py
copy to sandbox/plc4py/plc4py/spi/messages/PlcReader.py
index a500aceef0..5f1b993e05 100644
--- a/sandbox/plc4py/plc4py/spi/PlcDriverClassLoader.py
+++ b/sandbox/plc4py/plc4py/spi/messages/PlcReader.py
@@ -16,24 +16,22 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-from typing import Type
 
-import pluggy
+from typing import Awaitable
 
-from plc4py.api.PlcConnection import PlcConnection
+from plc4py.api.messages.PlcRequest import PlcReadRequest
+from plc4py.api.messages.PlcResponse import PlcReadResponse
 
 
-class PlcDriverClassLoader:
-    """Hook spec for PLC4PY Driver Loaders"""
+class PlcReader:
+    """
+    Interface implemented by all PlcConnections that are able to read from 
remote resources.
+    """
 
-    hookspec = pluggy.HookspecMarker("plc4py")
+    def _read(self, request: PlcReadRequest) -> Awaitable[PlcReadResponse]:
+        """
+        Reads a requested value from a PLC
 
-    @staticmethod
-    @hookspec
-    def get_connection() -> Type[PlcConnection]:
-        """Returns the PlcConnection class that is used to instantiate the 
driver"""
-
-    @staticmethod
-    @hookspec
-    def key() -> str:
-        """Unique key to identify the driver"""
+        :param request: object describing the type and location of the value
+        :return: Future, giving async access to the returned value
+        """
diff --git a/sandbox/plc4py/setup.py b/sandbox/plc4py/setup.py
index 9bb84aa512..8d36690742 100644
--- a/sandbox/plc4py/setup.py
+++ b/sandbox/plc4py/setup.py
@@ -51,7 +51,8 @@ setup(
     },
     entry_points={
         "plc4py.drivers": [
-            "modbus = 
plc4py.drivers.modbus.ModbusConnection:ModbusConnectionLoader"
+            "mock = plc4py.drivers.mock.MockConnection:MockDriverLoader",
+            "modbus = 
plc4py.drivers.modbus.ModbusConnection:ModbusDriverLoader",
         ]
     },
 )
diff --git a/sandbox/plc4py/tests/test_plc4py.py 
b/sandbox/plc4py/tests/test_plc4py.py
index 5427c832b5..bc735b61cd 100644
--- a/sandbox/plc4py/tests/test_plc4py.py
+++ b/sandbox/plc4py/tests/test_plc4py.py
@@ -16,10 +16,15 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+from typing import cast
 
 from plc4py import __version__
 from plc4py.PlcDriverManager import PlcDriverManager
 from plc4py.api.PlcConnection import PlcConnection
+from plc4py.api.messages.PlcRequest import PlcFieldRequest
+from plc4py.api.messages.PlcResponse import PlcReadResponse
+from plc4py.api.value.PlcValue import PlcResponseCode
+from plc4py.drivers.mock.MockConnection import MockConnection
 from plc4py.drivers.modbus.ModbusConnection import ModbusConnection
 
 
@@ -37,3 +42,26 @@ def test_plc_driver_manager_init_modbus():
     driver_manager = PlcDriverManager()
     with driver_manager.connection("modbus:tcp://127.0.0.1:502") as connection:
         assert isinstance(connection, ModbusConnection)
+
+
+def test_plc_driver_manager_init_mock():
+    driver_manager = PlcDriverManager()
+    with driver_manager.connection("mock:tcp://127.0.0.1:502") as connection:
+        assert isinstance(connection, MockConnection)
+
+
+async def test_plc_driver_manager_init_mock_read_request():
+    driver_manager = PlcDriverManager()
+    field = "1:BOOL"
+
+    with driver_manager.connection("mock:tcp://127.0.0.1:502") as connection:
+        connection.connect()
+        with connection.read_request_builder() as builder:
+            builder.add_item(field)
+            request: PlcFieldRequest = builder.build()
+            response: PlcReadResponse = cast(
+                PlcReadResponse, await connection.execute(request)
+            )
+
+    # verify that request has one field
+    assert response.code == PlcResponseCode.OK
diff --git a/sandbox/plc4py/tests/unit/plc4py/api/test/MockPlcConection.py 
b/sandbox/plc4py/tests/unit/plc4py/api/test/MockPlcConection.py
deleted file mode 100644
index 0e52c49d2f..0000000000
--- a/sandbox/plc4py/tests/unit/plc4py/api/test/MockPlcConection.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-import asyncio
-from dataclasses import dataclass
-from typing import Awaitable
-
-from plc4py.api.PlcConnection import PlcConnection
-from plc4py.api.messages.PlcRequest import ReadRequestBuilder, PlcReadRequest
-from plc4py.api.messages.PlcResponse import PlcReadResponse, PlcResponse
-from plc4py.api.value.PlcValue import PlcResponseCode
-from tests.unit.plc4py.api.test.MockReadRequestBuilder import 
MockReadRequestBuilder
-
-
-@dataclass
-class MockPlcConnection(PlcConnection):
-    _is_connected: bool = False
-
-    def connect(self):
-        """
-        Connect the Mock PLC connection
-        :return:
-        """
-        self._is_connected = True
-
-    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.
-        """
-        return MockReadRequestBuilder()
-
-    def _default_failed_request(
-        self, code: PlcResponseCode
-    ) -> Awaitable[PlcReadResponse]:
-        """
-        Returns a default PlcResponse, mainly used in case of a failed request
-        :param code: The response code to return
-        :return: The PlcResponse
-        """
-        loop = asyncio.get_running_loop()
-        fut = loop.create_future()
-        fut.set_result(PlcResponse(code))
-        return fut
-
-    def execute(self, request: PlcReadRequest) -> Awaitable[PlcReadResponse]:
-        """
-        Executes a PlcRequest as long as it's already connected
-        :param PlcRequest: Plc Request to execute
-        :return: The response from the Plc/Device
-        """
-        if not self.is_connected():
-            return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
-
-        return self._default_failed_request(PlcResponseCode.NOT_CONNECTED)
diff --git a/sandbox/plc4py/tests/unit/plc4py/api/test_PlcRequest.py 
b/sandbox/plc4py/tests/unit/plc4py/api/test_PlcRequest.py
index 2e15de85c0..027003e8d6 100644
--- a/sandbox/plc4py/tests/unit/plc4py/api/test_PlcRequest.py
+++ b/sandbox/plc4py/tests/unit/plc4py/api/test_PlcRequest.py
@@ -16,6 +16,8 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+from typing import cast
+
 import pytest
 
 from plc4py.api.PlcConnection import PlcConnection
@@ -28,7 +30,7 @@ from plc4py.api.value.PlcValue import PlcResponseCode
 from plc4py.spi.messages.utils.ResponseItem import ResponseItem
 from plc4py.spi.values.PlcBOOL import PlcBOOL
 from plc4py.spi.values.PlcINT import PlcINT
-from tests.unit.plc4py.api.test.MockPlcConection import MockPlcConnection
+from plc4py.drivers.mock.MockConnection import MockConnection
 
 
 def test_read_request_builder_empty_request(mocker) -> None:
@@ -37,7 +39,7 @@ def test_read_request_builder_empty_request(mocker) -> None:
     :param mocker:
     :return:
     """
-    connection: PlcConnection = MockPlcConnection()
+    connection: PlcConnection = MockConnection()
 
     # the connection function is supposed to support context manager
     # so using it in a with statement should result in close being called on 
the connection
@@ -52,7 +54,7 @@ def test_read_request_builder_non_empty_request(mocker) -> 
None:
     :param mocker:
     :return:
     """
-    connection: PlcConnection = MockPlcConnection()
+    connection: PlcConnection = MockConnection()
 
     # the connection function is supposed to support context manager
     # so using it in a with statement should result in close being called on 
the connection
@@ -72,7 +74,7 @@ async def 
test_read_request_builder_non_empty_request_not_connected(mocker) -> N
     :param mocker:
     :return:
     """
-    connection: PlcConnection = MockPlcConnection()
+    connection: PlcConnection = MockConnection()
 
     # the connection function is supposed to support context manager
     # so using it in a with statement should result in close being called on 
the connection
@@ -85,6 +87,60 @@ async def 
test_read_request_builder_non_empty_request_not_connected(mocker) -> N
     assert response.code == PlcResponseCode.NOT_CONNECTED
 
 
[email protected]
+async def test_read_request_builder_non_empty_request_connected_bool(mocker) 
-> None:
+    """
+    Create a request with a field and then confirm an non empty response gets 
returned with a OK code
+    :param mocker:
+    :return:
+    """
+    connection: PlcConnection = MockConnection()
+    connection.connect()
+    field = "1:BOOL"
+
+    # the connection function is supposed to support context manager
+    # so using it in a with statement should result in close being called on 
the connection
+    with connection.read_request_builder() as builder:
+        builder.add_item(field)
+        request: PlcFieldRequest = builder.build()
+        response: PlcReadResponse = cast(
+            PlcReadResponse, await connection.execute(request)
+        )
+
+    # verify that request has one field
+    assert response.code == PlcResponseCode.OK
+
+    value = response.values[field][0].value
+    assert not value.get_bool()
+
+
[email protected]
+async def test_read_request_builder_non_empty_request_connected_int(mocker) -> 
None:
+    """
+    Create a request with a field and then confirm an non empty response gets 
returned with a OK code
+    :param mocker:
+    :return:
+    """
+    connection: PlcConnection = MockConnection()
+    connection.connect()
+    field = "1:INT"
+
+    # the connection function is supposed to support context manager
+    # so using it in a with statement should result in close being called on 
the connection
+    with connection.read_request_builder() as builder:
+        builder.add_item(field)
+        request: PlcFieldRequest = builder.build()
+        response: PlcReadResponse = cast(
+            PlcReadResponse, await connection.execute(request)
+        )
+
+    # verify that request has one field
+    assert response.code == PlcResponseCode.OK
+
+    value = response.values[field][0].value
+    assert value.get_int() == 0
+
+
 def test_read_response_boolean_response(mocker) -> None:
     """
     Create a Plc Response with a boolean field, confirm that a boolean gets 
returned
diff --git a/sandbox/plc4py/tests/unit/plc4py/test_PlcDriverManager.py 
b/sandbox/plc4py/tests/unit/plc4py/test_PlcDriverManager.py
index a18b652f64..cf89c38792 100644
--- a/sandbox/plc4py/tests/unit/plc4py/test_PlcDriverManager.py
+++ b/sandbox/plc4py/tests/unit/plc4py/test_PlcDriverManager.py
@@ -19,7 +19,7 @@
 from unittest.mock import MagicMock
 
 from plc4py.PlcDriverManager import PlcDriverManager
-from tests.unit.plc4py.api.test.MockPlcConection import MockPlcConnection
+from plc4py.drivers.mock.MockConnection import MockConnection
 
 
 def test_connection_context_manager_impl_close_called(mocker) -> None:
@@ -27,7 +27,7 @@ def test_connection_context_manager_impl_close_called(mocker) 
-> None:
 
     # getup a plain return value for get_connection
     connection_mock: MagicMock = mocker.patch.object(manager, "get_connection")
-    connection_mock.return_value = MockPlcConnection()
+    connection_mock.return_value = MockConnection()
 
     # the connection function is supposed to support context manager
     # so using it in a with statement should result in close being called on 
the connection

Reply via email to