Skip to content

Commit

Permalink
feat(plc4py): Code Gen Update (#1199) - Incremental update to python
Browse files Browse the repository at this point in the history
* feat(plc4y): Still getting the static parse code working

* feat(plc4y): Start looking at testing generated code

* feat(plc4y): Sorting out code gen issues

* chore(plc4py): Update the generated code

* chore(plc4py): Start writing modbus logic :)

* chore(plc4py): Start writing modbus logic :)

* chore(plc4py): Renamed Field -> Tag, fixed up tag handlers

* chore(plc4py): Fixed build wrong import was used re.Pattern instead of typing.Pattern

* chore(plc4py): Fixed Enums and working on DataIO template

* chore(plc4py): Update the PlcValues and Data IO template

* chore(plc4j): Accidentally pushed a change to the Java driver

* fix(plc4py): Fix parsing of Enums

* fix(plc4py): Fix license classifier to match a pypi classifier
  • Loading branch information
hutcheb committed Jan 6, 2024
1 parent 05a3263 commit 9b7bf47
Show file tree
Hide file tree
Showing 89 changed files with 4,818 additions and 3,481 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

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
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
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
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
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
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
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
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)
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
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

0 comments on commit 9b7bf47

Please sign in to comment.