Skip to content

Commit

Permalink
fix(plc4py): Documentation and Write Requests (#1437)
Browse files Browse the repository at this point in the history
* Get started on PLC4PY documentation

* fix(plc4py): Continue working through help docs. Implemented metadata

* fix(plc4py/umas): Add support for metadata.

* fix(plc4py): Add support for adding PlcTags instead of address string in read request builder.

* fix(plc4py): Continue working through help docs. Cleaned up the response

* fix(plc4py): Add write support to the api

* fix(plc4py): Add write support to the api

* fix(plc4py): pytest-asyncio not installed and markers not added to tests

* fix(plc4py): Started to clean up sonarlint issues

* fix(plc4py): Continue working through help docs.
  • Loading branch information
hutcheb authored Mar 4, 2024
1 parent 1204c92 commit bbd7f1e
Show file tree
Hide file tree
Showing 31 changed files with 1,245 additions and 499 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class ${type.name}:
<#else>
${helper.camelCaseToSnakeCase(helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments))}
</#if>
<#sep> and </#sep>
<#sep> & </#sep>
</#list>
:</@compress> # ${case.name}
<#else>
Expand All @@ -102,7 +102,7 @@ class ${type.name}:
<@emitImport import="from typing import List" />
<@emitImport import="from plc4py.api.value.PlcValue import PlcValue" />
${helper.camelCaseToSnakeCase(arrayField.name)}: List[PlcValue] = []
for cur_item in range(item_count):
for _ in range(item_count):
${helper.camelCaseToSnakeCase(arrayField.name)}.append(${helper.getPlcValueTypeForTypeReference(elementTypeReference)}(${helper.getLanguageTypeNameForTypeReference(elementTypeReference, false)}(<#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)})<#else>${elementTypeReference.asComplexTypeReference().orElseThrow().name}IO.static_parse(read_buffer<#if elementTypeReference.params.isPresent()>, <#list elementTypeReference.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index), true)}) (${helper.toParseExpression(arrayField, elementTypeReference, parserArgument,parserArguments)})<#sep>, </#sep></#list></#if>)</#if>))

<#-- In all other cases do we have to work with a list, that is later converted to an array -->
Expand Down Expand Up @@ -340,9 +340,8 @@ class ${type.name}:
def static_serialize(write_buffer: WriteBuffer, _value: PlcValue<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.camelCaseToSnakeCase(parserArgument.name)}: ${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)}<#sep>, </#sep></#list></#if>, byte_order: ByteOrder) -> None:
<#assign defaultCaseOutput=false>
<#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
<#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
<#if case.discriminatorValueTerms?has_content>
if <@compress single_line=true>
if <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case><#if case.discriminatorValueTerms?has_content> <@compress single_line=true>

<#list case.discriminatorValueTerms as discriminatorValueTerm>
<#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
<#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
Expand All @@ -358,14 +357,13 @@ class ${type.name}:
<#else>
<#assign defaultCaseOutput=true>
</#if>

<#list case.fields as field>
<#switch field.typeName>
<#case "array">
<#assign arrayField=field.asArrayField().orElseThrow()>
<#assign elementTypeReference=arrayField.type.elementTypeReference>
values: PlcList = _value

<@emitImport import="from typing import cast" />
values: PlcList = cast(PlcList, _value)
<#if case.name == "Struct">
for val in values.getStruct().get("${arrayField.name}").get_list():
<#if elementTypeReference.isByteBased()>
Expand Down Expand Up @@ -397,22 +395,26 @@ class ${type.name}:
# Const Field (${constField.name})
${helper.getWriteBufferWriteMethodCall(constField.type.asSimpleTypeReference().orElseThrow(), constField.referenceValue, constField)}
<#break>

<#case "enum">
<#assign enumField=field.asEnumField().orElseThrow()>
# Enum field (${enumField.name})
${enumField.name}: ${helper.getLanguageTypeNameForField(field)} = _value.get_${helper.camelCaseToSnakeCase(enumField.name?cap_first)}()
${helper.getWriteBufferWriteMethodCall(helper.getEnumBaseTypeReference(field.asTypedField().orElseThrow().type), "(" + enumField.name + ".value)", enumField)}
<#break>

<#case "manual">
<#assign manualField=field.asManualField().orElseThrow()>
# Manual Field (${manualField.name})
${helper.toSerializationExpression(manualField, manualField.type, manualField.serializeExpression, type.parserArguments.orElse(null))}
<#break>

<#case "reserved">
<#assign reservedField=field.asReservedField().orElseThrow()>
# Reserved Field
${helper.getWriteBufferWriteMethodCall(reservedField.type.asSimpleTypeReference().orElseThrow(), helper.getReservedValue(reservedField), reservedField)}
<#break>

<#case "simple">
<#assign simpleField=field.asSimpleField().orElseThrow()>
# Simple Field (${simpleField.name})
Expand All @@ -430,13 +432,16 @@ class ${type.name}:
</#if>
<#if simpleField.type.isSimpleTypeReference()>
${helper.getWriteBufferWriteMethodCall(simpleField.type.asSimpleTypeReference().orElseThrow(), "(" + simpleField.name + ")", simpleField)}

<#else>
${simpleField.type.asComplexTypeReference().orElseThrow().name}IO.static_serialize(write_buffer, ${helper.camelCaseToSnakeCase(simpleField.name)})

</#if>
<#break>

</#switch>
</#list>
<#sep></#sep></#list>
<#sep><@compress single_line=true>elif </@compress></#sep></#list>
</#if>

<@emitImport import="import math" />
Expand All @@ -452,9 +457,7 @@ class ${type.name}:
size_in_bits: int = 0
<#assign defaultCaseOutput=false>
<#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
<#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
<#if case.discriminatorValueTerms?has_content>
if <@compress single_line=true>
if <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case><#if case.discriminatorValueTerms?has_content> <@compress single_line=true>
<#list case.discriminatorValueTerms as discriminatorValueTerm>
<#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
<#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
Expand All @@ -464,7 +467,7 @@ class ${type.name}:
<#else>
${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
</#if>
<#sep> and </#sep>
<#sep> & </#sep>
</#list>
</@compress>: # ${case.name}
<#else>
Expand All @@ -475,7 +478,8 @@ class ${type.name}:
<#case "array">
<#assign arrayField=field.asArrayField().orElseThrow()>
<#assign elementTypeReference=arrayField.type.elementTypeReference>
values: PlcList = _value
<@emitImport import="from typing import cast" />
values: PlcList = cast(PlcList, _value)
<#if case.name == "Struct">
# TODO: Finish this!
<#elseif elementTypeReference.isComplexTypeReference()>
Expand Down Expand Up @@ -512,7 +516,7 @@ class ${type.name}:
<#break>
</#switch>
</#list>
<#sep></#sep></#list>
<#sep><@compress single_line=true>elif </@compress></#sep></#list>
return size_in_bits


Expand Down
50 changes: 46 additions & 4 deletions sandbox/plc4py/plc4py/api/PlcConnection.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@
#
import asyncio
from abc import abstractmethod
from typing import Awaitable
from typing import Awaitable, Dict, Union

from plc4py.api.messages.PlcResponse import PlcResponse, PlcReadResponse
from plc4py.api.messages.PlcResponse import (
PlcResponse,
PlcTagResponse,
PlcReadResponse,
PlcWriteResponse,
PlcBrowseResponse,
)
from plc4py.api.messages.PlcRequest import ReadRequestBuilder, PlcRequest
from plc4py.api.value.PlcValue import PlcResponseCode
from plc4py.api.value.PlcValue import PlcResponseCode, PlcValue
from plc4py.spi.configuration.PlcConfiguration import PlcConfiguration
from plc4py.spi.messages.utils.ResponseItem import ResponseItem
from plc4py.utils.GenericTypes import GenericGenerator


Expand Down Expand Up @@ -65,7 +72,7 @@ def execute(self, request: PlcRequest) -> Awaitable[PlcResponse]:

def _default_failed_request(
self, code: PlcResponseCode
) -> Awaitable[PlcReadResponse]:
) -> Awaitable[Union[PlcReadResponse, PlcWriteResponse, PlcBrowseResponse]]:
"""
Returns a default PlcResponse, mainly used in case of a failed request
:param code: The response code to return
Expand All @@ -75,3 +82,38 @@ def _default_failed_request(
future = loop.create_future()
future.set_result(PlcResponse(code))
return future


class PlcConnectionMetaData:

@abstractmethod
def is_read_supported(self) -> bool:
"""
Indicates if the connection supports read requests.
:return: True if connection supports reading, False otherwise
"""
pass

@abstractmethod
def is_write_supported(self) -> bool:
"""
Indicates if the connection supports write requests.
:return: True if connection supports writing, False otherwise
"""
pass

@abstractmethod
def is_subscribe_supported(self) -> bool:
"""
Indicates if the connection supports subscription requests.
:return: True if connection supports subscriptions, False otherwise
"""
pass

@abstractmethod
def is_browse_supported(self) -> bool:
"""
Indicates if the connection supports browsing requests.
:return: True if connection supports browsing, False otherwise
"""
pass
3 changes: 2 additions & 1 deletion sandbox/plc4py/plc4py/api/messages/PlcMessage.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
# specific language governing permissions and limitations
# under the License.
#
from plc4py.spi.values.Common import Serializable


class PlcMessage:
class PlcMessage(Serializable):
pass
22 changes: 21 additions & 1 deletion sandbox/plc4py/plc4py/api/messages/PlcRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

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


Expand Down Expand Up @@ -53,10 +54,19 @@ def query_names(self):
@dataclass
class PlcReadRequest(PlcTagRequest):
"""
Base type for all messages sent from the plc4x system to a connected plc.
Base type for all read messages sent from the plc4x system to a connected plc.
"""


@dataclass
class PlcWriteRequest(PlcTagRequest):
"""
Base type for all write messages sent from the plc4x system to a connected plc.
"""

values: Dict[str, PlcValue] = field(default_factory=lambda: OrderedDict())


@dataclass
class PlcBrowseRequest(PlcQueryRequest):
"""
Expand All @@ -74,6 +84,16 @@ def add_item(self, tag_name: str, address_string: str) -> None:
pass


class WriteRequestBuilder(GenericGenerator):
@abstractmethod
def build(self) -> PlcWriteRequest:
pass

@abstractmethod
def add_item(self, tag_name: str, address_string: str, value: PlcValue) -> None:
pass


class BrowseRequestBuilder(GenericGenerator):
@abstractmethod
def build(self) -> PlcBrowseRequest:
Expand Down
40 changes: 19 additions & 21 deletions sandbox/plc4py/plc4py/api/messages/PlcResponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,16 @@ class PlcResponse(PlcMessage):
from a plc to the plc4x system.
"""

code: PlcResponseCode
response_code: PlcResponseCode


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

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

def response_code(self, name: str) -> PlcResponseCode:
pass
return [tag_name for tag_name in self.tags.keys()]


@dataclass
Expand All @@ -53,23 +50,27 @@ class PlcReadResponse(PlcTagResponse):
Response to a {@link PlcReadRequest}.
"""

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

def number_of_values(self, name: str) -> int:
return len(self.values[name])
def is_boolean(self, name: str):
return isinstance(self.tags[name].value.value, bool)

def is_boolean(self, name: str, index: int = 0):
return isinstance(self.values[name][index].value.value, bool)
def get_boolean(self, name: str) -> bool:
return cast(bool, self.tags[name].value.value)

def get_boolean(self, name: str, index: int = 0) -> bool:
return cast(bool, self.values[name][index].value.value)
def is_int(self, name: str):
return isinstance(self.tags[name].value.value, int)

def is_int(self, name: str, index: int = 0):
return isinstance(self.values[name][index].value.value, int)
def get_int(self, name: str) -> int:
return cast(int, self.tags[name].value.value)

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

@dataclass
class PlcWriteResponse(PlcTagResponse):
"""
Response to a {@link PlcWriteRequest}.
"""


@dataclass
Expand All @@ -80,9 +81,6 @@ class PlcQueryResponse(PlcResponse):
def tag_names(self):
return [tag_name for tag_name in self.tags.keys()]

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


@dataclass
class PlcBrowseResponse(PlcQueryResponse):
Expand Down
5 changes: 5 additions & 0 deletions sandbox/plc4py/plc4py/api/value/PlcValue.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ def get_list(self) -> List["PlcValue"]:
def get_raw(self):
return self.value

def __len__(self):
if isinstance(self.value, list):
return len(self.value)
return 1


class PlcResponseCode(Enum):
OK = auto()
Expand Down
Loading

0 comments on commit bbd7f1e

Please sign in to comment.