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)