Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plc4py): Code Gen Update #1199

Merged
merged 14 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@ ${import}
</#macro>
<#macro emitImport import>${helper.emitRequiredImport(import)}</#macro>
<@importSectionWithContentBelow>
<@emitImport import="from enum import IntEnum" />
<@emitImport import="from aenum import AutoNumberEnum" />

class ${type.name}(IntEnum):
class ${type.name}(AutoNumberEnum):
<#if type.constantNames?has_content>_init_ = "value, <#list type.constantNames as constantName>${helper.camelCaseToSnakeCase(constantName)}<#sep>, </#sep></#list>"</#if>
<#list type.enumValues as enumValue>
${enumValue.name}: <@compress single_line=true>
${enumValue.name}<#if !type.constantNames?has_content>:</#if> <@compress single_line=true>
<#if type.type.isPresent()>
${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)}
<#if !type.constantNames?has_content>${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)}</#if>
<#if type.type.orElseThrow().isNonSimpleTypeReference()>
<#if type.type.orElseThrow().isEnumTypeReference()>
= <#if type.constantNames?has_content>(</#if>${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)}.${enumValue.value}
Expand Down Expand Up @@ -92,20 +93,6 @@ class ${type.name}(IntEnum):

</#list>

<#if type.constantNames?has_content>
def __new__(cls, value, <@compress single_line=true>
<#list type.constantNames as constantName>
${helper.camelCaseToSnakeCase(constantName)}
<#sep>, </#sep>
</#list>):
</@compress>

obj = object.__new__(cls)
obj._value_ = value
<#list type.constantNames as constantName>obj.${constantName} = ${constantName}</#list>
return obj
</#if>

</@importSectionWithContentBelow>

</#outputformat>
4 changes: 4 additions & 0 deletions sandbox/plc4py/plc4py/api/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ class PlcNotImplementedException(Exception):

class SerializationException(Exception):
pass


class ParseException(Exception):
pass
6 changes: 3 additions & 3 deletions sandbox/plc4py/plc4py/api/messages/PlcField.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


@dataclass
class PlcField:
class PlcTag:
"""
Base type for all field types.
Typically every driver provides an implementation of this interface in order
Expand All @@ -31,7 +31,7 @@ class PlcField:
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.
manually makes the solution less independent of the protocol, but might be faster.
"""

name: str
address: str
16 changes: 8 additions & 8 deletions sandbox/plc4py/plc4py/api/messages/PlcRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
#
from abc import abstractmethod
from dataclasses import dataclass, field
from typing import Union, List
from typing import Union, List, Dict

from plc4py.api.messages.PlcField import PlcField
from plc4py.api.messages.PlcField import PlcTag
from plc4py.api.messages.PlcMessage import PlcMessage
from plc4py.utils.GenericTypes import GenericGenerator

Expand All @@ -32,16 +32,16 @@ class PlcRequest(PlcMessage):


@dataclass
class PlcFieldRequest(PlcRequest):
fields: List[PlcField] = field(default_factory=lambda: [])
class PlcTagRequest(PlcRequest):
tags: Dict[str, PlcTag] = field(default_factory=lambda: {})

@property
def field_names(self):
return [field.name for field in self.fields]
def tag_names(self):
return [tag_name for tag_name in self.tags.keys()]


@dataclass
class PlcReadRequest(PlcFieldRequest):
class PlcReadRequest(PlcTagRequest):
"""
Base type for all messages sent from the plc4x system to a connected plc.
"""
Expand All @@ -53,5 +53,5 @@ def build(self) -> PlcReadRequest:
pass

@abstractmethod
def add_item(self, field_query: Union[str, PlcField]) -> None:
def add_item(self, tag_name: str, address_string: str) -> None:
pass
14 changes: 6 additions & 8 deletions sandbox/plc4py/plc4py/api/messages/PlcResponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from dataclasses import dataclass
from typing import cast, List, Dict

from plc4py.api.messages.PlcField import PlcField
from plc4py.api.messages.PlcField import PlcTag
from plc4py.api.messages.PlcMessage import PlcMessage
from plc4py.api.value.PlcValue import PlcValue, PlcResponseCode
from plc4py.spi.messages.utils.ResponseItem import ResponseItem
Expand All @@ -36,25 +36,23 @@ class PlcResponse(PlcMessage):


@dataclass
class PlcFieldResponse(PlcResponse):
fields: List[PlcField]
class PlcTagResponse(PlcResponse):
values: Dict[str, List[ResponseItem[PlcValue]]]

@property
def field_names(self):
return [fld.name for fld in self.fields]
def tag_names(self):
return [tag_name for tag_name in self.values.keys()]

def response_code(self, name: str) -> PlcResponseCode:
pass


@dataclass
class PlcReadResponse(PlcFieldResponse):
class PlcReadResponse(PlcTagResponse):
"""
Response to a {@link PlcReadRequest}.
"""

values: Dict[str, List[ResponseItem[PlcValue]]]

def get_plc_value(self, name: str, index: int = 0) -> PlcValue:
return self.values[name][index].value

Expand Down
18 changes: 15 additions & 3 deletions sandbox/plc4py/plc4py/api/value/PlcValue.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from abc import ABC
from dataclasses import dataclass
from enum import auto, Enum
from typing import TypeVar, Generic
from typing import TypeVar, Generic, List

T = TypeVar("T")

Expand All @@ -28,10 +28,22 @@
class PlcValue(Generic[T], ABC):
value: T

def get_bool(self):
def get_bool(self) -> bool:
return bool(self.value)

def get_float(self) -> float:
return float(self.value)

def get_str(self) -> str:
return str(self.value)

def get_int(self) -> int:
return int(self.value)

def get_list(self) -> List["PlcValue"]:
return self.value

def get_int(self):
def get_raw(self):
return self.value


Expand Down
65 changes: 18 additions & 47 deletions sandbox/plc4py/plc4py/drivers/mock/MockConnection.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@
from dataclasses import dataclass, field
from typing import Awaitable, Type, List, Dict

from plc4py.drivers.mock import MockTag
from plc4py.drivers.mock.MockTag import MockTagBuilder
from plc4py.spi.messages.PlcRequest import DefaultReadRequestBuilder

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.PlcField import PlcTag
from plc4py.api.messages.PlcRequest import (
ReadRequestBuilder,
PlcReadRequest,
Expand All @@ -39,54 +43,22 @@
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
from plc4py.spi.values.PlcValues import PlcBOOL
from plc4py.spi.values.PlcValues import PlcINT


@dataclass
class MockDevice:
fields: Dict[str, PlcValue] = field(default_factory=lambda: {})

def read(self, field: str) -> List[ResponseItem[PlcValue]]:
def read(self, tag: MockTag) -> 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])]
logging.debug(f"Reading field {str(tag)} from Mock Device")

if tag.data_type == "BOOL":
return [ResponseItem(PlcResponseCode.OK, PlcBOOL(False))]
elif tag.data_type == "INT":
return [ResponseItem(PlcResponseCode.OK, PlcINT(0))]
else:
raise PlcFieldParseException

Expand Down Expand Up @@ -128,7 +100,7 @@ def read_request_builder(self) -> ReadRequestBuilder:
"""
:return: read request builder.
"""
return MockReadRequestBuilder()
return DefaultReadRequestBuilder(MockTagBuilder)

def execute(self, request: PlcRequest) -> Awaitable[PlcResponse]:
"""
Expand Down Expand Up @@ -156,13 +128,12 @@ async def _request(req, device) -> PlcReadResponse:
try:
response = PlcReadResponse(
PlcResponseCode.OK,
req.fields,
{field: device.read(field) for field in req.field_names},
{tag_name: device.read(tag) for tag_name, tag in req.tags.items()},
)
return response
except Exception:
except Exception as e:
# TODO:- This exception is very general and probably should be replaced
return PlcReadResponse(PlcResponseCode.INTERNAL_ERROR, req.fields, {})
return PlcReadResponse(PlcResponseCode.INTERNAL_ERROR, req.tags, {})

logging.debug("Sending read request to MockDevice")
future = asyncio.ensure_future(_request(request, self.device))
Expand Down
23 changes: 2 additions & 21 deletions sandbox/plc4py/plc4py/drivers/mock/MockReadRequestBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
#

from dataclasses import dataclass, field
from typing import Union, List
from typing import Union, List, Dict

from plc4py.api.messages.PlcMessage import PlcMessage
from plc4py.api.messages.PlcRequest import (
ReadRequestBuilder,
PlcField,
PlcTag,
PlcReadRequest,
)
from plc4py.api.messages.PlcResponse import PlcReadResponse
Expand All @@ -32,22 +32,3 @@
class MockPlcReadResponse(PlcReadResponse):
def get_request(self) -> PlcMessage:
return PlcMessage()


class MockPlcReadRequest(PlcReadRequest):
def __init__(self, fields: List[PlcField] = []):
super().__init__(fields)


@dataclass
class MockReadRequestBuilder(ReadRequestBuilder):
items: List[PlcField] = field(default_factory=lambda: [])

def build(self) -> PlcReadRequest:
return MockPlcReadRequest(self.items)

def add_item(self, field_query: Union[str, PlcField]) -> None:
field_temp: PlcField = (
PlcField(field_query) if isinstance(field_query, str) else field_query
)
self.items.append(field_temp)
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,18 @@
#
from dataclasses import dataclass

from plc4py.api.value.PlcValue import PlcValue
from plc4py.api.messages.PlcField import PlcTag
from plc4py.spi.messages.PlcRequest import TagBuilder


@dataclass
class PlcBOOL(PlcValue[bool]):
def get_bool(self):
return self.value
class MockTag(PlcTag):
address: str
data_type: str


class MockTagBuilder(TagBuilder):
@staticmethod
def create(address_string: str) -> MockTag:
address, data_type = address_string.split(":")
return MockTag(address, data_type)
3 changes: 3 additions & 0 deletions sandbox/plc4py/plc4py/drivers/modbus/ModbusConfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ def __init__(self, url):

if self.port is None:
self.port = 502

if "unit_identifier" not in self.parameters:
self.unit_identifier = 1
Loading
Loading