From 02c42980cf52f32b7cdc091baf8ae851d00254fe Mon Sep 17 00:00:00 2001 From: hutcheb Date: Tue, 22 Oct 2024 07:38:15 +0800 Subject: [PATCH 1/8] feat(plc4py/umas): Add Write Support --- plc4py/plc4py/drivers/umas/UmasConnection.py | 17 +- plc4py/plc4py/drivers/umas/UmasDevice.py | 107 ++++++++- plc4py/plc4py/drivers/umas/UmasVariables.py | 112 +++++++++- .../protocols/umas/readwrite/DataItem.py | 47 ++++ .../UmasPDUReadUnlocatedVariableNames.py | 153 ------------- .../UmasUnlocatedVariableReference.py | 57 +---- .../umas/readwrite/UmasVariableBlock.py | 149 ------------- .../readwrite/VariableRequestReference.py | 185 ---------------- .../VariableWriteRequestReference.py | 19 +- plc4py/plc4py/spi/generation/WriteBuffer.py | 8 +- plc4py/plc4py/spi/values/PlcValues.py | 2 +- plc4py/setup.py | 1 + .../drivers/umas/test_umas_connection.py | 32 ++- .../main/resources/protocols/umas/umas.mspec | 20 +- src/site/asciidoc/users/protocols/umas.adoc | 209 ++++++++++++++++++ 15 files changed, 544 insertions(+), 574 deletions(-) delete mode 100644 plc4py/plc4py/protocols/umas/readwrite/UmasPDUReadUnlocatedVariableNames.py delete mode 100644 plc4py/plc4py/protocols/umas/readwrite/UmasVariableBlock.py delete mode 100644 plc4py/plc4py/protocols/umas/readwrite/VariableRequestReference.py create mode 100644 src/site/asciidoc/users/protocols/umas.adoc diff --git a/plc4py/plc4py/drivers/umas/UmasConnection.py b/plc4py/plc4py/drivers/umas/UmasConnection.py index 4257b3c3d24..ca71a6faf90 100644 --- a/plc4py/plc4py/drivers/umas/UmasConnection.py +++ b/plc4py/plc4py/drivers/umas/UmasConnection.py @@ -29,6 +29,8 @@ PlcReadRequest, PlcRequest, ReadRequestBuilder, + WriteRequestBuilder, + PlcWriteRequest, ) from plc4py.api.messages.PlcResponse import ( PlcResponse, @@ -46,12 +48,16 @@ from plc4py.spi.messages.PlcRequest import ( DefaultBrowseRequestBuilder, DefaultReadRequestBuilder, + DefaultWriteRequestBuilder, ) from plc4py.spi.transport.Plc4xBaseTransport import Plc4xBaseTransport from plc4py.spi.transport.TCPTransport import TCPTransport +from plc4py.spi.messages.PlcWriter import DefaultPlcWriter -class UmasConnection(PlcConnection, DefaultPlcReader, DefaultPlcBrowser): +class UmasConnection( + PlcConnection, DefaultPlcReader, DefaultPlcWriter, DefaultPlcBrowser +): """ Umas TCP PLC connection implementation """ @@ -115,6 +121,12 @@ def read_request_builder(self) -> ReadRequestBuilder: """ return DefaultReadRequestBuilder(UmasTagBuilder) + def write_request_builder(self) -> WriteRequestBuilder: + """ + :return: write request builder. + """ + return DefaultWriteRequestBuilder(UmasTagBuilder) + def browse_request_builder(self) -> BrowseRequestBuilder: """ :return: browse request builder. @@ -133,6 +145,9 @@ async def execute(self, request: PlcRequest) -> PlcResponse: if isinstance(request, PlcReadRequest): return await self._read(request) + if isinstance(request, PlcWriteRequest): + return await self._write(request) + if isinstance(request, PlcBrowseRequest): return await self._browse(request) diff --git a/plc4py/plc4py/drivers/umas/UmasDevice.py b/plc4py/plc4py/drivers/umas/UmasDevice.py index 5eb104143e5..e5a135886ae 100644 --- a/plc4py/plc4py/drivers/umas/UmasDevice.py +++ b/plc4py/plc4py/drivers/umas/UmasDevice.py @@ -100,6 +100,16 @@ from plc4py.spi.generation.ReadBuffer import ReadBufferByteBased from plc4py.spi.messages.utils.ResponseItem import ResponseItem from plc4py.utils.GenericTypes import ByteOrder +from plc4py.protocols.umas.readwrite.UmasPDUWriteVariableRequest import ( + UmasPDUWriteVariableRequestBuilder, +) +from plc4py.protocols.umas.readwrite.UmasPDUWriteVariableResponse import ( + UmasPDUWriteVariableResponse, +) +from plc4py.protocols.umas.readwrite.VariableWriteRequestReference import ( + VariableWriteRequestReference, +) +from plc4py.spi.values.PlcValues import PlcNull @dataclass @@ -171,7 +181,7 @@ def _generate_variable_tree( return_dict = {} for kea, tag in tags.items(): temp_variable = UmasVariableBuilder( - kea, tag, data_types, data_type_children, base_offset=tag.base_offset + kea, tag, data_types, data_type_children, base_offset=0 ).build() if temp_variable is not None: return_dict[kea] = temp_variable @@ -413,6 +423,52 @@ async def _send_read_variable_request( response = PlcReadResponse(PlcResponseCode.OK, values) return response + async def _send_write_variable_request( + self, + transport: Transport, + loop: AbstractEventLoop, + request, + sorted_tags, + ): + message_future = loop.create_future() + + sorted_variable_list: List[VariableWriteRequestReference] = [ + variable_reference[1] for variable_reference in sorted_tags + ] + request_pdu = UmasPDUWriteVariableRequestBuilder( + crc=self.project_crc, + variable_count=len(sorted_variable_list), + variables=sorted_variable_list, + ).build(0, 0) + + protocol = transport.protocol + protocol.write_wait_for_response( + request_pdu, + transport, + message_future, + ) + + await message_future + variable_name_response: UmasPDUWriteVariableResponse = message_future.result() + + values: Dict[str, ResponseItem[PlcValue]] = {} + for key, tag in sorted_tags: + if variable_name_response.umas_request_function_key != 254: + response_item = ResponseItem( + PlcResponseCode.OK, + PlcNull(None), + ) + else: + response_item = ResponseItem( + PlcResponseCode.REMOTE_ERROR, + PlcNull(None), + ) + + values[key] = response_item + + response = PlcReadResponse(PlcResponseCode.OK, values) + return response + def _sort_tags_based_on_memory_address(self, request): tag_list: List[List[Tuple[str, VariableReadRequestReference]]] = [[]] current_list_index = 0 @@ -432,7 +488,7 @@ def _sort_tags_based_on_memory_address(self, request): current_list.append( ( kea, - variable.get_variable_reference(umas_tag.tag_name), + variable.get_read_variable_reference(umas_tag.tag_name), ) ) @@ -446,6 +502,41 @@ def _sort_tags_based_on_memory_address(self, request): return sorted_tag_lists + def _sort_write_tags_based_on_memory_address(self, request): + tag_list: List[List[Tuple[str, VariableWriteRequestReference]]] = [[]] + current_list_index = 0 + current_list = tag_list[current_list_index] + byte_count: int = 0 + for kea, tag in request.tags.items(): + umas_tag = cast(UmasTag, tag) + base_tag_name = umas_tag.tag_name.split(".")[0] + variable = self.variables[base_tag_name] + + if byte_count + variable.get_byte_length() > self.max_frame_size: + current_list_index += 1 + tag_list.append([]) + current_list = tag_list[current_list_index] + byte_count = 0 + byte_count += variable.get_byte_length() + current_list.append( + ( + kea, + variable.get_write_variable_reference( + umas_tag.tag_name, request.values[kea] + ), + ) + ) + + sorted_tag_lists: List[List[Tuple[str, VariableWriteRequestReference]]] = [] + for request in tag_list: + sorted_tags = sorted( + request, + key=lambda x: (x[1].block * 100000) + x[1].base_offset + x[1].offset, + ) + sorted_tag_lists.append(sorted_tags) + + return sorted_tag_lists + async def read( self, request: PlcReadRequest, transport: Transport ) -> PlcReadResponse: @@ -470,7 +561,17 @@ async def write( """ Writes one field from the UMAS Device """ - pass + loop = asyncio.get_running_loop() + await self._update_plc_project_info(transport, loop) + sorted_tag_list = self._sort_write_tags_based_on_memory_address(request) + response = PlcWriteResponse(PlcResponseCode.OK, {}) + for sorted_tags in sorted_tag_list: + response_chunk = await self._send_write_variable_request( + transport, loop, request, sorted_tags + ) + response.code = response_chunk.response_code + response.tags = {**response.tags, **response_chunk.tags} + return response async def browse( self, request: PlcBrowseRequest, transport: Transport diff --git a/plc4py/plc4py/drivers/umas/UmasVariables.py b/plc4py/plc4py/drivers/umas/UmasVariables.py index 421173bee33..e730cbff16f 100644 --- a/plc4py/plc4py/drivers/umas/UmasVariables.py +++ b/plc4py/plc4py/drivers/umas/UmasVariables.py @@ -20,6 +20,7 @@ from dataclasses import dataclass from typing import AnyStr, Dict, List, Pattern, Union +from plc4py.api.value.PlcValue import PlcValue from plc4py.api.exceptions.exceptions import PlcDataTypeNotFoundException from plc4py.protocols.umas.readwrite.UmasDataType import UmasDataType from plc4py.protocols.umas.readwrite.UmasDatatypeReference import ( @@ -32,6 +33,12 @@ from plc4py.protocols.umas.readwrite.VariableReadRequestReference import ( VariableReadRequestReference, ) +from plc4py.protocols.umas.readwrite.VariableWriteRequestReference import ( + VariableWriteRequestReference, +) +from plc4py.protocols.umas.readwrite.DataItem import DataItem +from plc4py.spi.generation.WriteBuffer import WriteBufferByteBased +from plc4py.utils.GenericTypes import ByteOrder @dataclass @@ -42,7 +49,14 @@ class UmasVariable: base_offset: int offset: int - def get_variable_reference(self, address: str) -> VariableReadRequestReference: + def get_read_variable_reference(self, address: str) -> VariableReadRequestReference: + raise NotImplementedError( + f"UmasVariable subclass not implemented for variable {self.variable_name}" + ) + + def get_write_variable_reference( + self, address: str, value: PlcValue + ) -> VariableWriteRequestReference: raise NotImplementedError( f"UmasVariable subclass not implemented for variable {self.variable_name}" ) @@ -55,14 +69,16 @@ def get_byte_length(self) -> int: @dataclass class UmasElementryVariable(UmasVariable): - def get_variable_reference(self, address: str) -> VariableReadRequestReference: + def get_read_variable_reference(self, address: str) -> VariableReadRequestReference: + base_offset = self.base_offset + (self.offset // 0x100) + offset = self.offset % 0x100 if self.data_type == UmasDataType.STRING.value: return VariableReadRequestReference( is_array=1, data_size_index=UmasDataType(self.data_type).request_size, block=self.block_no, - base_offset=self.base_offset, - offset=self.offset, + base_offset=base_offset, + offset=offset, array_length=16, ) else: @@ -70,9 +86,44 @@ def get_variable_reference(self, address: str) -> VariableReadRequestReference: is_array=0, data_size_index=UmasDataType(self.data_type).request_size, block=self.block_no, - base_offset=self.base_offset, - offset=self.offset, + base_offset=base_offset, + offset=offset, + array_length=None, + ) + + def get_write_variable_reference( + self, address: str, value: PlcValue + ) -> VariableWriteRequestReference: + ss = UmasDataType(self.data_type).data_type_size + write_buffer = WriteBufferByteBased( + UmasDataType(self.data_type).data_type_size, ByteOrder.LITTLE_ENDIAN + ) + DataItem.static_serialize( + write_buffer, + value, + UmasDataType(self.data_type), + 1, + ByteOrder.LITTLE_ENDIAN, + ) + if self.data_type == UmasDataType.STRING.value: + return VariableWriteRequestReference( + is_array=1, + data_size_index=UmasDataType(self.data_type).request_size, + block=self.block_no, + base_offset=self.offset, + offset=self.base_offset, + array_length=16, + record_data=bytearray(write_buffer.bb), + ) + else: + return VariableWriteRequestReference( + is_array=0, + data_size_index=UmasDataType(self.data_type).request_size, + block=self.block_no, + base_offset=self.offset, + offset=self.base_offset, array_length=None, + record_data=bytearray(write_buffer.bb), ) def get_byte_length(self) -> int: @@ -83,17 +134,30 @@ def get_byte_length(self) -> int: class UmasCustomVariable(UmasVariable): children: Dict[str, UmasVariable] - def get_variable_reference(self, address: str) -> VariableReadRequestReference: + def get_read_variable_reference(self, address: str) -> VariableReadRequestReference: split_tag_address: List[str] = address.split(".") child_index = None if len(split_tag_address) > 1: child_index = split_tag_address[1] - return self.children[child_index].get_variable_reference( + return self.children[child_index].get_read_variable_reference( ".".join(split_tag_address[1:]) ) else: raise NotImplementedError("Unable to read structures of UDT's") + def get_write_variable_reference( + self, address: str, value: PlcValue + ) -> VariableWriteRequestReference: + split_tag_address: List[str] = address.split(".") + child_index = None + if len(split_tag_address) > 1: + child_index = split_tag_address[1] + return self.children[child_index].get_write_variable_reference( + ".".join(split_tag_address[1:]) + ) + else: + raise NotImplementedError("Unable to write structures of UDT's") + def get_byte_length(self) -> int: byte_count = 0 for key, child in self.children.items(): @@ -106,7 +170,7 @@ class UmasArrayVariable(UmasVariable): start_index: int end_index: int - def get_variable_reference(self, address: str) -> VariableReadRequestReference: + def get_read_variable_reference(self, address: str) -> VariableReadRequestReference: split_tag_address: List[str] = address.split(".") address_index = None if len(split_tag_address) > 1: @@ -132,6 +196,36 @@ def get_variable_reference(self, address: str) -> VariableReadRequestReference: array_length=self.end_index - self.start_index + 1, ) + def get_write_variable_reference( + self, address: str, value: PlcValue + ) -> VariableWriteRequestReference: + split_tag_address: List[str] = address.split(".") + address_index = None + if len(split_tag_address) > 1: + address_index = int(split_tag_address[1]) + data_type_enum = UmasDataType(self.data_type) + if address_index: + return VariableWriteRequestReference( + is_array=0, + data_size_index=data_type_enum.request_size, + block=self.block_no, + base_offset=self.base_offset, + offset=self.offset + + (address_index - self.start_index) * data_type_enum.data_type_size, + array_length=None, + record_data=None, + ) + else: + return VariableWriteRequestReference( + is_array=1, + data_size_index=data_type_enum.request_size, + block=self.block_no, + base_offset=self.base_offset, + offset=self.offset, + array_length=self.end_index - self.start_index + 1, + record_data=None, + ) + def get_byte_length(self) -> int: return 9 diff --git a/plc4py/plc4py/protocols/umas/readwrite/DataItem.py b/plc4py/plc4py/protocols/umas/readwrite/DataItem.py index 800ec4d68db..83b54d806d4 100644 --- a/plc4py/plc4py/protocols/umas/readwrite/DataItem.py +++ b/plc4py/plc4py/protocols/umas/readwrite/DataItem.py @@ -71,6 +71,32 @@ def static_parse( for _ in range(item_count): value.append(PlcBOOL(bool(read_buffer.read_bit("")))) + return PlcList(value) + if data_type == UmasDataType.EBOOL and number_of_values == int(1): # BOOL + + # Reserved Field (Compartmentalized so the "reserved" variable can't leak) + reserved: int = read_buffer.read_unsigned_short(7, logical_name="") + if reserved != int(0x0000): + logging.warning( + "Expected constant value " + + str(0x0000) + + " but got " + + str(reserved) + + " for reserved field." + ) + + # Simple Field (value) + value: bool = read_buffer.read_bit("") + + return PlcBOOL(value) + if data_type == UmasDataType.EBOOL: # List + # Array field (value) + # Count array + item_count: int = int(number_of_values) + value: List[PlcValue] = [] + for _ in range(item_count): + value.append(PlcBOOL(bool(read_buffer.read_bit("")))) + return PlcList(value) if data_type == UmasDataType.BYTE and number_of_values == int(1): # BYTE @@ -223,6 +249,19 @@ def static_serialize( value: bool = val.get_bool() write_buffer.write_bit((value), "value") + elif data_type == UmasDataType.EBOOL and number_of_values == int(1): # BOOL + # Reserved Field + write_buffer.write_byte(int(0x0000), 7, "int0x0000") + # Simple Field (value) + value: bool = _value.get_bool() + write_buffer.write_bit((value), "value") + + elif data_type == UmasDataType.EBOOL: # List + values: PlcList = cast(PlcList, _value) + for val in values.get_list(): + value: bool = val.get_bool() + write_buffer.write_bit((value), "value") + elif data_type == UmasDataType.BYTE and number_of_values == int(1): # BYTE # Simple Field (value) value: int = _value.get_int() @@ -332,6 +371,14 @@ def get_length_in_bits( elif data_type == UmasDataType.BOOL: # List values: PlcList = cast(PlcList, _value) size_in_bits += len(values.get_list()) * 1 + elif data_type == UmasDataType.EBOOL and number_of_values == int(1): # BOOL + # Reserved Field + size_in_bits += 7 + # Simple Field (value) + size_in_bits += 1 + elif data_type == UmasDataType.EBOOL: # List + values: PlcList = cast(PlcList, _value) + size_in_bits += len(values.get_list()) * 1 elif data_type == UmasDataType.BYTE and number_of_values == int(1): # BYTE # Simple Field (value) size_in_bits += 8 diff --git a/plc4py/plc4py/protocols/umas/readwrite/UmasPDUReadUnlocatedVariableNames.py b/plc4py/plc4py/protocols/umas/readwrite/UmasPDUReadUnlocatedVariableNames.py deleted file mode 100644 index 3ab7e8882f9..00000000000 --- a/plc4py/plc4py/protocols/umas/readwrite/UmasPDUReadUnlocatedVariableNames.py +++ /dev/null @@ -1,153 +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 -# -# 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 math -from dataclasses import dataclass -from typing import Any, ClassVar, List - -from plc4py.api.exceptions.exceptions import ( - PlcRuntimeException, - SerializationException, -) -from plc4py.api.messages.PlcMessage import PlcMessage -from plc4py.protocols.umas.readwrite.UmasPDUItem import UmasPDUItem -from plc4py.protocols.umas.readwrite.UmasUnlocatedVariableReference import ( - UmasUnlocatedVariableReference, -) -from plc4py.spi.generation.ReadBuffer import ReadBuffer -from plc4py.spi.generation.WriteBuffer import WriteBuffer -from plc4py.utils.GenericTypes import ByteOrder - - -@dataclass -class UmasPDUReadUnlocatedVariableNames(UmasPDUItem): - range: int - no_of_records: int - records: List[UmasUnlocatedVariableReference] - # Accessors for discriminator values. - umas_function_key: ClassVar[int] = 0xFE - umas_request_function_key: ClassVar[int] = 0x26 - - def serialize_umas_pdu_item_child(self, write_buffer: WriteBuffer): - write_buffer.push_context("UmasPDUReadUnlocatedVariableNames") - - # Simple Field (range) - write_buffer.write_unsigned_int(self.range, logical_name="range") - - # Simple Field (noOfRecords) - write_buffer.write_unsigned_int(self.no_of_records, logical_name="noOfRecords") - - # Array Field (records) - write_buffer.write_complex_array(self.records, logical_name="records") - - write_buffer.pop_context("UmasPDUReadUnlocatedVariableNames") - - def length_in_bytes(self) -> int: - return int(math.ceil(float(self.length_in_bits() / 8.0))) - - def length_in_bits(self) -> int: - length_in_bits: int = super().length_in_bits() - _value: UmasPDUReadUnlocatedVariableNames = self - - # Simple field (range) - length_in_bits += 32 - - # Simple field (noOfRecords) - length_in_bits += 32 - - # Array field - if self.records is not None: - for element in self.records: - length_in_bits += element.length_in_bits() - - return length_in_bits - - @staticmethod - def static_parse_builder(read_buffer: ReadBuffer, umas_request_function_key: int): - read_buffer.push_context("UmasPDUReadUnlocatedVariableNames") - - range: int = read_buffer.read_unsigned_int( - logical_name="range", - bit_length=32, - byte_order=ByteOrder.LITTLE_ENDIAN, - umas_request_function_key=umas_request_function_key, - ) - - no_of_records: int = read_buffer.read_unsigned_int( - logical_name="noOfRecords", - bit_length=32, - byte_order=ByteOrder.LITTLE_ENDIAN, - umas_request_function_key=umas_request_function_key, - ) - - records: List[Any] = read_buffer.read_array_field( - logical_name="records", - read_function=UmasUnlocatedVariableReference.static_parse, - count=no_of_records, - byte_order=ByteOrder.LITTLE_ENDIAN, - umas_request_function_key=umas_request_function_key, - ) - - read_buffer.pop_context("UmasPDUReadUnlocatedVariableNames") - # Create the instance - return UmasPDUReadUnlocatedVariableNamesBuilder(range, no_of_records, records) - - def equals(self, o: object) -> bool: - if self == o: - return True - - if not isinstance(o, UmasPDUReadUnlocatedVariableNames): - return False - - that: UmasPDUReadUnlocatedVariableNames = UmasPDUReadUnlocatedVariableNames(o) - return ( - (self.range == that.range) - and (self.no_of_records == that.no_of_records) - and (self.records == that.records) - and super().equals(that) - and True - ) - - def hash_code(self) -> int: - return hash(self) - - def __str__(self) -> str: - pass - # write_buffer_box_based: WriteBufferBoxBased = WriteBufferBoxBased(True, True) - # try: - # write_buffer_box_based.writeSerializable(self) - # except SerializationException as e: - # raise PlcRuntimeException(e) - - # return "\n" + str(write_buffer_box_based.get_box()) + "\n" - - -@dataclass -class UmasPDUReadUnlocatedVariableNamesBuilder: - range: int - no_of_records: int - records: List[UmasUnlocatedVariableReference] - - def build(self, pairing_key) -> UmasPDUReadUnlocatedVariableNames: - umas_pdu_read_unlocated_variable_names: UmasPDUReadUnlocatedVariableNames = ( - UmasPDUReadUnlocatedVariableNames( - pairing_key, self.range, self.no_of_records, self.records - ) - ) - return umas_pdu_read_unlocated_variable_names diff --git a/plc4py/plc4py/protocols/umas/readwrite/UmasUnlocatedVariableReference.py b/plc4py/plc4py/protocols/umas/readwrite/UmasUnlocatedVariableReference.py index 9a7874957f2..00f1f9bdbad 100644 --- a/plc4py/plc4py/protocols/umas/readwrite/UmasUnlocatedVariableReference.py +++ b/plc4py/plc4py/protocols/umas/readwrite/UmasUnlocatedVariableReference.py @@ -31,10 +31,8 @@ @dataclass class UmasUnlocatedVariableReference: data_type: int - unknown1: int block: int offset: int - base_offset: int unknown4: int string_length: int value: str @@ -43,13 +41,8 @@ def serialize(self, write_buffer: WriteBuffer): write_buffer.push_context("UmasUnlocatedVariableReference") # Simple Field (dataType) - write_buffer.write_unsigned_byte( - self.data_type, bit_length=8, logical_name="dataType" - ) - - # Simple Field (unknown1) - write_buffer.write_unsigned_byte( - self.unknown1, bit_length=8, logical_name="unknown1" + write_buffer.write_unsigned_short( + self.data_type, bit_length=16, logical_name="dataType" ) # Simple Field (block) @@ -58,13 +51,8 @@ def serialize(self, write_buffer: WriteBuffer): ) # Simple Field (offset) - write_buffer.write_unsigned_byte( - self.offset, bit_length=8, logical_name="offset" - ) - - # Simple Field (baseOffset) - write_buffer.write_unsigned_byte( - self.base_offset, bit_length=8, logical_name="baseOffset" + write_buffer.write_unsigned_short( + self.offset, bit_length=16, logical_name="offset" ) # Simple Field (unknown4) @@ -94,19 +82,13 @@ def length_in_bits(self) -> int: _value: UmasUnlocatedVariableReference = self # Simple field (dataType) - length_in_bits += 8 - - # Simple field (unknown1) - length_in_bits += 8 + length_in_bits += 16 # Simple field (block) length_in_bits += 16 # Simple field (offset) - length_in_bits += 8 - - # Simple field (baseOffset) - length_in_bits += 8 + length_in_bits += 16 # Simple field (unknown4) length_in_bits += 16 @@ -127,24 +109,16 @@ def static_parse(read_buffer: ReadBuffer, **kwargs): def static_parse_context(read_buffer: ReadBuffer): read_buffer.push_context("UmasUnlocatedVariableReference") - data_type: int = read_buffer.read_unsigned_byte( - logical_name="data_type", bit_length=8 - ) - - unknown1: int = read_buffer.read_unsigned_byte( - logical_name="unknown1", bit_length=8 + data_type: int = read_buffer.read_unsigned_short( + logical_name="data_type", bit_length=16 ) block: int = read_buffer.read_unsigned_short( logical_name="block", bit_length=16 ) - offset: int = read_buffer.read_unsigned_byte( - logical_name="offset", bit_length=8 - ) - - base_offset: int = read_buffer.read_unsigned_byte( - logical_name="base_offset", bit_length=8 + offset: int = read_buffer.read_unsigned_short( + logical_name="offset", bit_length=16 ) unknown4: int = read_buffer.read_unsigned_short( @@ -166,14 +140,7 @@ def static_parse_context(read_buffer: ReadBuffer): # Create the instance _umas_unlocated_variable_reference: UmasUnlocatedVariableReference = ( UmasUnlocatedVariableReference( - data_type, - unknown1, - block, - offset, - base_offset, - unknown4, - string_length, - value, + data_type, block, offset, unknown4, string_length, value ) ) return _umas_unlocated_variable_reference @@ -188,10 +155,8 @@ def equals(self, o: object) -> bool: that: UmasUnlocatedVariableReference = UmasUnlocatedVariableReference(o) return ( (self.data_type == that.data_type) - and (self.unknown1 == that.unknown1) and (self.block == that.block) and (self.offset == that.offset) - and (self.base_offset == that.base_offset) and (self.unknown4 == that.unknown4) and (self.string_length == that.string_length) and (self.value == that.value) diff --git a/plc4py/plc4py/protocols/umas/readwrite/UmasVariableBlock.py b/plc4py/plc4py/protocols/umas/readwrite/UmasVariableBlock.py deleted file mode 100644 index 5a314a823d9..00000000000 --- a/plc4py/plc4py/protocols/umas/readwrite/UmasVariableBlock.py +++ /dev/null @@ -1,149 +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 -# -# 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 math -from abc import ABC, abstractmethod -from dataclasses import dataclass - -from plc4py.api.exceptions.exceptions import ( - ParseException, - PlcRuntimeException, - SerializationException, -) -from plc4py.api.messages.PlcMessage import PlcMessage -from plc4py.spi.generation.ReadBuffer import ReadBuffer -from plc4py.spi.generation.WriteBuffer import WriteBuffer - - -@dataclass -class UmasVariableBlock(ABC, PlcMessage): - # Abstract accessors for discriminator values. - @property - def record_format(self) -> int: - pass - - @abstractmethod - def serialize_umas_variable_block_child(self, write_buffer: WriteBuffer) -> None: - pass - - def serialize(self, write_buffer: WriteBuffer): - write_buffer.push_context("UmasVariableBlock") - - # Switch field (Serialize the sub-type) - self.serialize_umas_variable_block_child(write_buffer) - - write_buffer.pop_context("UmasVariableBlock") - - def length_in_bytes(self) -> int: - return int(math.ceil(float(self.length_in_bits() / 8.0))) - - def length_in_bits(self) -> int: - length_in_bits: int = 0 - _value: UmasVariableBlock = self - - # Length of subtype elements will be added by sub-type... - - return length_in_bits - - @staticmethod - def static_parse(read_buffer: ReadBuffer, **kwargs): - if kwargs is None: - raise PlcRuntimeException( - "Wrong number of arguments, expected 1, but got None" - ) - - record_format: int = 0 - if isinstance(kwargs.get("record_format"), int): - record_format = int(kwargs.get("record_format")) - elif isinstance(kwargs.get("record_format"), str): - record_format = int(str(kwargs.get("record_format"))) - else: - raise PlcRuntimeException( - "Argument 0 expected to be of type int or a string which is parseable but was " - + kwargs.get("record_format").getClass().getName() - ) - - return UmasVariableBlock.static_parse_context(read_buffer, record_format) - - @staticmethod - def static_parse_context(read_buffer: ReadBuffer, record_format: int): - read_buffer.push_context("UmasVariableBlock") - - # Switch Field (Depending on the discriminator values, passes the instantiation to a sub-type) - builder: UmasVariableBlockBuilder = None - from plc4py.protocols.umas.readwrite.UmasPDUReadUnlocatedVariableNamesResponse import ( - UmasPDUReadUnlocatedVariableNamesResponse, - ) - - if record_format == int(0xDD02): - builder = UmasPDUReadUnlocatedVariableNamesResponse.static_parse_builder( - read_buffer, record_format - ) - from plc4py.protocols.umas.readwrite.UmasPDUReadDatatypeNamesResponse import ( - UmasPDUReadDatatypeNamesResponse, - ) - - if record_format == int(0xDD03): - builder = UmasPDUReadDatatypeNamesResponse.static_parse_builder( - read_buffer, record_format - ) - if builder is None: - raise ParseException( - "Unsupported case for discriminated type" - + " parameters [" - + "recordFormat=" - + str(record_format) - + "]" - ) - - read_buffer.pop_context("UmasVariableBlock") - # Create the instance - _umas_variable_block: UmasVariableBlock = builder.build() - return _umas_variable_block - - def equals(self, o: object) -> bool: - if self == o: - return True - - if not isinstance(o, UmasVariableBlock): - return False - - that: UmasVariableBlock = UmasVariableBlock(o) - return True - - def hash_code(self) -> int: - return hash(self) - - def __str__(self) -> str: - pass - # write_buffer_box_based: WriteBufferBoxBased = WriteBufferBoxBased(True, True) - # try: - # write_buffer_box_based.writeSerializable(self) - # except SerializationException as e: - # raise PlcRuntimeException(e) - - # return "\n" + str(write_buffer_box_based.get_box()) + "\n" - - -@dataclass -class UmasVariableBlockBuilder: - def build( - self, - ) -> UmasVariableBlock: - pass diff --git a/plc4py/plc4py/protocols/umas/readwrite/VariableRequestReference.py b/plc4py/plc4py/protocols/umas/readwrite/VariableRequestReference.py deleted file mode 100644 index 7c43a744b87..00000000000 --- a/plc4py/plc4py/protocols/umas/readwrite/VariableRequestReference.py +++ /dev/null @@ -1,185 +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 -# -# 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 math -from dataclasses import dataclass - -from plc4py.api.exceptions.exceptions import ( - PlcRuntimeException, - SerializationException, -) -from plc4py.api.messages.PlcMessage import PlcMessage -from plc4py.spi.generation.ReadBuffer import ReadBuffer -from plc4py.spi.generation.WriteBuffer import WriteBuffer - - -@dataclass -class VariableRequestReference: - is_array: int - data_size_index: int - block: int - base_offset: int - offset: int - array_length: int - UNKNOWN1: int = 0x01 - - def serialize(self, write_buffer: WriteBuffer): - write_buffer.push_context("VariableRequestReference") - - # Simple Field (isArray) - write_buffer.write_unsigned_byte( - self.is_array, bit_length=4, logical_name="isArray" - ) - - # Simple Field (dataSizeIndex) - write_buffer.write_unsigned_byte( - self.data_size_index, bit_length=4, logical_name="dataSizeIndex" - ) - - # Simple Field (block) - write_buffer.write_unsigned_short( - self.block, bit_length=16, logical_name="block" - ) - - # Const Field (unknown1) - write_buffer.write_unsigned_byte(self.UNKNOWN1, logical_name="unknown1") - - # Simple Field (baseOffset) - write_buffer.write_unsigned_short( - self.base_offset, bit_length=16, logical_name="baseOffset" - ) - - # Simple Field (offset) - write_buffer.write_unsigned_byte( - self.offset, bit_length=8, logical_name="offset" - ) - - # Optional Field (arrayLength) (Can be skipped, if the value is null) - write_buffer.write_unsigned_short(self.array_length, logical_name="arrayLength") - - write_buffer.pop_context("VariableRequestReference") - - def length_in_bytes(self) -> int: - return int(math.ceil(float(self.length_in_bits() / 8.0))) - - def length_in_bits(self) -> int: - length_in_bits: int = 0 - _value: VariableRequestReference = self - - # Simple field (isArray) - length_in_bits += 4 - - # Simple field (dataSizeIndex) - length_in_bits += 4 - - # Simple field (block) - length_in_bits += 16 - - # Const Field (unknown1) - length_in_bits += 8 - - # Simple field (baseOffset) - length_in_bits += 16 - - # Simple field (offset) - length_in_bits += 8 - - # Optional Field (arrayLength) - length_in_bits += 16 - - return length_in_bits - - @staticmethod - def static_parse(read_buffer: ReadBuffer, **kwargs): - return VariableRequestReference.static_parse_context(read_buffer) - - @staticmethod - def static_parse_context(read_buffer: ReadBuffer): - read_buffer.push_context("VariableRequestReference") - - is_array: int = read_buffer.read_unsigned_byte( - logical_name="isArray", bit_length=4 - ) - - data_size_index: int = read_buffer.read_unsigned_byte( - logical_name="dataSizeIndex", bit_length=4 - ) - - block: int = read_buffer.read_unsigned_short( - logical_name="block", bit_length=16 - ) - - UNKNOWN1: int = read_buffer.read_unsigned_byte(logical_name="unknown1") - - base_offset: int = read_buffer.read_unsigned_short( - logical_name="baseOffset", bit_length=16 - ) - - offset: int = read_buffer.read_unsigned_byte( - logical_name="offset", bit_length=8 - ) - - array_length: int = None - if self.is_array: - array_length = read_buffer.read_unsigned_short(logical_name="arrayLength") - - read_buffer.pop_context("VariableRequestReference") - # Create the instance - _variable_request_reference: VariableRequestReference = ( - VariableRequestReference( - is_array, - data_size_index, - block, - base_offset, - offset, - array_length, - ) - ) - return _variable_request_reference - - def equals(self, o: object) -> bool: - if self == o: - return True - - if not isinstance(o, VariableRequestReference): - return False - - that: VariableRequestReference = VariableRequestReference(o) - return ( - (self.is_array == that.is_array) - and (self.data_size_index == that.data_size_index) - and (self.block == that.block) - and (self.base_offset == that.base_offset) - and (self.offset == that.offset) - and (self.array_length == that.array_length) - and True - ) - - def hash_code(self) -> int: - return hash(self) - - def __str__(self) -> str: - pass - # write_buffer_box_based: WriteBufferBoxBased = WriteBufferBoxBased(True, True) - # try: - # write_buffer_box_based.writeSerializable(self) - # except SerializationException as e: - # raise PlcRuntimeException(e) - - # return "\n" + str(write_buffer_box_based.get_box()) + "\n" diff --git a/plc4py/plc4py/protocols/umas/readwrite/VariableWriteRequestReference.py b/plc4py/plc4py/protocols/umas/readwrite/VariableWriteRequestReference.py index 055270c687b..30e34892d8d 100644 --- a/plc4py/plc4py/protocols/umas/readwrite/VariableWriteRequestReference.py +++ b/plc4py/plc4py/protocols/umas/readwrite/VariableWriteRequestReference.py @@ -38,7 +38,6 @@ class VariableWriteRequestReference: offset: int array_length: int record_data: List[int] - UNKNOWN1: int = 0x01 def serialize(self, write_buffer: WriteBuffer): write_buffer.push_context("VariableWriteRequestReference") @@ -58,17 +57,14 @@ def serialize(self, write_buffer: WriteBuffer): self.block, bit_length=16, logical_name="block" ) - # Const Field (unknown1) - write_buffer.write_unsigned_byte(self.UNKNOWN1, logical_name="unknown1") - # Simple Field (baseOffset) write_buffer.write_unsigned_short( self.base_offset, bit_length=16, logical_name="baseOffset" ) # Simple Field (offset) - write_buffer.write_unsigned_byte( - self.offset, bit_length=8, logical_name="offset" + write_buffer.write_unsigned_short( + self.offset, bit_length=16, logical_name="offset" ) # Optional Field (arrayLength) (Can be skipped, if the value is null) @@ -98,14 +94,11 @@ def length_in_bits(self) -> int: # Simple field (block) length_in_bits += 16 - # Const Field (unknown1) - length_in_bits += 8 - # Simple field (baseOffset) length_in_bits += 16 # Simple field (offset) - length_in_bits += 8 + length_in_bits += 16 # Optional Field (arrayLength) if self.is_array: @@ -137,14 +130,12 @@ def static_parse_context(read_buffer: ReadBuffer): logical_name="block", bit_length=16 ) - UNKNOWN1: int = read_buffer.read_unsigned_byte(logical_name="unknown1") - base_offset: int = read_buffer.read_unsigned_short( logical_name="base_offset", bit_length=16 ) - offset: int = read_buffer.read_unsigned_byte( - logical_name="offset", bit_length=8 + offset: int = read_buffer.read_unsigned_short( + logical_name="offset", bit_length=16 ) array_length: int = None diff --git a/plc4py/plc4py/spi/generation/WriteBuffer.py b/plc4py/plc4py/spi/generation/WriteBuffer.py index b6519f5ee40..2517f8e79c6 100644 --- a/plc4py/plc4py/spi/generation/WriteBuffer.py +++ b/plc4py/plc4py/spi/generation/WriteBuffer.py @@ -195,8 +195,10 @@ def write_bit(self, value: bool, logical_name: str = "", **kwargs) -> None: self.bb[self.position] = value self.position += 1 - def write_byte(self, value: int, logical_name: str = "", **kwargs) -> None: - self.write_unsigned_byte(value, 8, logical_name, **kwargs) + def write_byte( + self, value: int, bit_length: int = 8, logical_name: str = "", **kwargs + ) -> None: + self.write_unsigned_byte(value, bit_length, logical_name, **kwargs) def write_byte_array( self, value: List[int], logical_name: str = "", **kwargs @@ -376,10 +378,12 @@ def _handle_numeric_encoding(self, value, bit_length: int, **kwargs): endianness = "<" if not isinstance(value, int): pass + result: bytes = struct.pack( endianness + numeric_format, value, ) + src.frombytes(result) if ( byte_order == ByteOrder.BIG_ENDIAN_BYTE_SWAP diff --git a/plc4py/plc4py/spi/values/PlcValues.py b/plc4py/plc4py/spi/values/PlcValues.py index 437c6a795f2..3c2e5ca8848 100644 --- a/plc4py/plc4py/spi/values/PlcValues.py +++ b/plc4py/plc4py/spi/values/PlcValues.py @@ -19,7 +19,7 @@ from dataclasses import dataclass from typing import Any, Dict, List -from ...api.value.PlcValue import PlcValue +from plc4py.api.value.PlcValue import PlcValue class PlcINT(PlcValue[int]): diff --git a/plc4py/setup.py b/plc4py/setup.py index 9505736f533..15faec5ac6a 100644 --- a/plc4py/setup.py +++ b/plc4py/setup.py @@ -47,6 +47,7 @@ "bitarray", "typing_extensions", "pluggy", + "xsdata", ], extras_require={ "dev": [ diff --git a/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py b/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py index 82a18f1f1fe..932456db1a6 100644 --- a/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py +++ b/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py @@ -22,7 +22,9 @@ import pytest +from plc4py.api.value.PlcValue import PlcResponseCode from plc4py.PlcDriverManager import PlcDriverManager +from plc4py.spi.values.PlcValues import PlcBOOL, PlcINT, PlcREAL @pytest.mark.asyncio @@ -40,9 +42,11 @@ async def test_plc_driver_umas_read(): log = logging.getLogger(__name__) driver_manager = PlcDriverManager() - async with driver_manager.connection("umas://192.168.190.174:502") as connection: + async with driver_manager.connection("umas://192.168.190.152:502") as connection: with connection.read_request_builder() as builder: - builder.add_item(f"Random Tag {1}", "blurbe:REAL") + #builder.add_item(f"Random Tag {1}", "TESTING_10:BOOL") + builder.add_item(f"Random Tag {2}", "TESTING_REAL:REAL") + request = builder.build() future = connection.execute(request) @@ -51,6 +55,26 @@ async def test_plc_driver_umas_read(): assert value == 0.0 +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_write(): + log = logging.getLogger(__name__) + + driver_manager = PlcDriverManager() + async with driver_manager.connection("umas://192.168.190.152:502") as connection: + with connection.write_request_builder() as builder: + # builder.add_item(f"Random Tag {1}", "TESTING_10:BOOL", PlcBOOL(True)) + # builder.add_item(f"Random Tag {2}", "TESTING_INT:INT", PlcINT(10)) + # builder.add_item(f"Random Tag {3}", "TESTING_EBOOL:BOOL", PlcBOOL(True)) + builder.add_item(f"Random Tag {4}", "TESTING_REAL:REAL", PlcREAL(3.18)) + request = builder.build() + + future = connection.execute(request) + response = await future + value = response.tags["Random Tag 1"].response_code + assert value == PlcResponseCode.OK + + @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_browse(): @@ -61,6 +85,6 @@ async def test_plc_driver_umas_browse(): request = builder.build() future = connection.execute(request) - await future - response = future.result() + response = await future + pass diff --git a/protocols/umas/src/main/resources/protocols/umas/umas.mspec b/protocols/umas/src/main/resources/protocols/umas/umas.mspec index 1b5a32b0c12..878f64117c7 100644 --- a/protocols/umas/src/main/resources/protocols/umas/umas.mspec +++ b/protocols/umas/src/main/resources/protocols/umas/umas.mspec @@ -196,19 +196,16 @@ [simple uint 4 isArray] [simple uint 4 dataSizeIndex] [simple uint 16 block] - [const uint 8 unknown1 0x01] [simple uint 16 baseOffset] - [simple uint 8 offset] + [simple uint 16 offset] [optional uint 16 arrayLength 'isArray'] - [array byte recordData length 'isArray == 1 ? dataSizeIndex * arrayLength : dataSizeIndex'] + [array byte recordData length 'isArray == 1 ? dataSizeIndex * arrayLength : dataSizeIndex'] ] [type UmasUnlocatedVariableReference - [simple uint 8 dataType] - [simple uint 8 unknown1] + [simple uint 16 dataType] [simple uint 16 block] - [simple uint 8 offset] - [simple uint 8 baseOffset] + [simple uint 16 offset] [simple uint 16 unknown4] [simple uint 16 stringLength] [manual vstring value 'STATIC_CALL("parseTerminatedString", readBuffer, stringLength)' 'STATIC_CALL("serializeTerminatedString", writeBuffer, value, stringLength)' '(stringLength * 8)'] @@ -250,6 +247,15 @@ // TODO: Handle adding some reserved bits at the end to fill up the last word. [array bit value count 'numberOfValues' ] ] + ['EBOOL','1' BOOL + // TODO: Possibly change the order of the bit and the reserved part. + [reserved uint 7 '0x0000' ] + [simple bit value ] + ] + ['EBOOL' List + // TODO: Handle adding some reserved bits at the end to fill up the last word. + [array bit value count 'numberOfValues' ] + ] ['BYTE','1' BYTE [simple uint 8 value] ] diff --git a/src/site/asciidoc/users/protocols/umas.adoc b/src/site/asciidoc/users/protocols/umas.adoc new file mode 100644 index 00000000000..18c2d000984 --- /dev/null +++ b/src/site/asciidoc/users/protocols/umas.adoc @@ -0,0 +1,209 @@ +// +// 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. +// +:imagesdir: ../../images/users/protocols +:icons: font + +== UMAS (Schneider Electric PLCs) + +=== Connection String Options + +==== Modbus TCP + +include::../../../plc4j/drivers/all/src/site/generated/modbus-tcp.adoc[] + +==== Modbus RTU + +include::../../../plc4j/drivers/all/src/site/generated/modbus-rtu.adoc[] + +==== Modbus ASCII + +include::../../../plc4j/drivers/all/src/site/generated/modbus-ascii.adoc[] + +=== Supported Operations + +[cols="2,2a,5a"] +|=== +|Name |Value |Description + +3+|Supported Operations + +| +2+| `read` + +| +2+| `write` +|=== + +=== Individual Resource Address Format + +==== Connection String + +Modbus has the following connection string format:- +---- +modbus-tcp:{transport}://{ip-address}:{port}?{options} +---- +An example connection string would look like:- +---- +modbus-tcp:tcp://127.0.0.1:502 +---- +Note the transport, port and option fields are optional. + + +==== General Format + +In general all Modbus addresses have this format: + +---- +{memory-Area}{start-address}:{data-type}[{array-size}]:{name-value-tag-options} +---- + +If the array-size part is omitted, the size-default of `1` is assumed. +If the data-type part is omitted, it defaults to BOOL for Coils and Discrete Inputs and INT for input, holding and extended registers. +If the name-value-tag-options part is omitted, simply no configuration fine-tuning is applied. + +Additionally address can contain tag configuration: +---- +{unit-id: 123} +---- +Specifying this value overrides value of `default-unit-id` parameter specified at the connection string. + +---- +{byte-order: 'LITTLE_ENDIAN'} +---- +With this, can the default byte-order be overridden on a per-tag basis. If not provided the default-byte-order from the connection string is used, or BIG_ENDIAN, if this is also not provided. + +==== Memory Areas + +There are a number of memory areas defined in the Modbus specification. + +- Discrete Input Area +- Coil Area +- Input Register Area +- Holding Register +- Extended Register Area + +[cols="2,2a,5,1,2,1"] +|=== +|Name |Memory Area Aliases |Description |Bit-Size | Permissions | Starting Address + +|Discrete Input |`discrete-input:` or `1` or `1x` |Boolean input value, usually representing a binary input to the PLC |1 |Read Only|1 +|Coil |`coil:` or `0` or `0x` |Boolean value, usually representing a binary output from the PLC |1 |Read/Write|1 +|Input Register |`input-register:` or `3` or `3x` |Short input value, usually representing an analog input to the PLC |16 |Read Only|1 +|Holding Register |`holding-register:` or `4` or `4x` |Short value, usually representing an analog output from the PLC |16 |Read/Write|1 +|Extended Register |`extended-register:` or `6` or `6x` |Short value, |16 |Read/Write|0 + +|=== + +Initially the Modbus format allowed up to 10000 address to be specified or the discrete inputs, coils, input registers and holding registers. +Later on, this was expanded to allow up 65536 address within each memory area (except the extended register area). +When using the long address format i.e. input-registers:1 the addresses between 1 and 65535 are able to be specified. +When using the shorter versions there are two formats available i.e. 30001 and 300001. +With the shorter format 3XXXX being limited to between 30001 and 39999, while the longer format 3XXXXX being limited to between 300001 and 365535. +These memory areas all start at address 1. + +For the extended register area the addresses 0-99999 are able to be specified. These registers are mapped to file records with a length of 10000. Address 600000 corresponds to the first address in file record 0. +Address 610000 is then the first address in the second file record and so on. It is noted that there is generally only 10 file records (600000 thru to 699999) however the spec allows for 65536 file records. +Using the extended-register: format you are able to reference all of these, if the shorter format is used then it is limited to 699999. +This memory area starts at address 0. + +==== Data Types + +The following data types are supported + +- BOOL (boolean) +- SINT (int 8) +- USINT (uint 8) +- BYTE (uint 8) +- INT (int 16) +- UINT (uint 16) +- WORD (uint 16) +- DINT (int 32) +- UDINT (uint 32) +- DWORD (uint 32) +- LINT (int 64) +- ULINT (uint 64) +- LWORD (uint 64) +- REAL (float) +- LREAL (double) +- CHAR (char) +- WCHAR (2 byte char) + +==== Some useful tips + +Most memory areas start at address 1, except for the extended register area which starts at 0. These are both mapped to 0x0000 when it is sent in the Modbus protocol. + +The input, holding and extended registers consist of 16-bit registers while the discrete input and coil areas consist of bits. + +The following Modbus function codes are supported:- + +- 0x01 (Read Coils) +- 0x02 (Read Discrete Inputs) +- 0x03 (Read Holding Registers) +- 0x04 (Read Input Registers) +- 0x05 (Write Single Coil) +- 0x06 (Write Single Register) +- 0x0F (Write Multiple Coils) +- 0x10 (Write Multiple Registers) +- 0x14 (Read File Record)(Extended Register Read) +- 0x15 (Write File Record)(Extended Register Write) + +==== Examples + +To read 10 holding registers starting at address 20 and parse as Unsigned Integers the following examples are all valid. + +- holding-register:20:UINT[10] +- 400020:UINT[10] +- 4x00020:UINT[10] +- 40020:UINT[10] +- 4x0020:UINT[10] + +To read 1 holding register at address 5678 the following examples are valid. + +- holding-register:5678 +- 405678 +- 4x05678 +- 45678 +- 4x5678 + +To read 1 holding register of unit 10 at address 5678 the following examples are valid. + +- holding-register:5678{unit-id: 10} +- 405678{unit-id: 10} +- 4x05678{unit-id: 10} +- 45678{unit-id: 10} +- 4x5678{unit-id: 10} + +To read 10 extended registers starting at address 50 the following examples are valid. + +- extended-register:50[10] +- 600050[10] +- 6x00050[10] +- 60050[10] +- 6x0050[10] + +This corresponds to addresses 50-59 in file record 1. + +To read 10 extended registers starting at address 9995 the following examples are valid. + +- extended-register:9995[10] +- 609995[10] +- 6x09995[10] +- 69995[10] +- 6x9995[10] + +This corresponds to addresses 9995-9999 in file record 1 and addresses 0-5 in file record 2. +Note that this request is split into 2 sub requests in the Modbus protocol. From 6f959f4a733998ddc9af3692611450e4a43e6db2 Mon Sep 17 00:00:00 2001 From: hutcheb Date: Thu, 24 Oct 2024 22:42:36 +0800 Subject: [PATCH 2/8] fix(plc4py/umas): Start testing all the data types. --- .../python/PythonLanguageTemplateHelper.java | 14 +- .../python/data-io-template.python.ftlh | 32 +- plc4py/plc4py/drivers/umas/UmasDevice.py | 56 ++- plc4py/plc4py/drivers/umas/UmasTag.py | 2 +- plc4py/plc4py/drivers/umas/UmasVariables.py | 2 +- .../protocols/modbus/readwrite/DataItem.py | 14 +- .../protocols/simulated/readwrite/DataItem.py | 18 +- .../protocols/umas/readwrite/DataItem.py | 216 ++++++++++- .../protocols/umas/readwrite/UmasDataType.py | 2 +- plc4py/plc4py/spi/generation/ReadBuffer.py | 107 ++++-- plc4py/plc4py/spi/values/PlcValues.py | 5 +- .../drivers/modbus/test_modbus_connection.py | 2 +- .../drivers/umas/test_umas_connection.py | 335 +++++++++++++++--- .../main/resources/protocols/umas/umas.mspec | 33 +- src/site/asciidoc/users/protocols/index.adoc | 18 + src/site/asciidoc/users/protocols/umas.adoc | 52 ++- src/site/site.xml | 1 + 17 files changed, 764 insertions(+), 145 deletions(-) diff --git a/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java index bd2370721bd..c4054c61cec 100644 --- a/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java +++ b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java @@ -579,7 +579,7 @@ public String getPlcValueTypeForTypeReference(TypeReference typeReference) { case BYTE: // Byte values are represented as signed integers in PLC4Py emitRequiredImport("from plc4py.spi.values.PlcValues import PlcSINT"); - return "PlcSINT"; + return "PlcBYTE"; case UINT: IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference; if (unsignedIntegerTypeReference.getSizeInBits() <= 4) { @@ -807,10 +807,10 @@ public String getReadBufferReadMethodCall(String logicalName, SimpleTypeReferenc switch (simpleTypeReference.getBaseType()) { case BIT: String bitType = "bit"; - return "read_buffer.read_" + bitType + "(\"" + logicalName + "\")"; + return "read_buffer.read_" + bitType + "(\"" + logicalName + "\""; case BYTE: String byteType = "byte"; - return "read_buffer.read_" + byteType + "(\"" + logicalName + "\")"; + return "read_buffer.read_" + byteType + "(\"" + logicalName + "\""; case UINT: String unsignedIntegerType; IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference; @@ -825,7 +825,7 @@ public String getReadBufferReadMethodCall(String logicalName, SimpleTypeReferenc } else { unsignedIntegerType = "unsigned_long"; } - return "read_buffer.read_" + unsignedIntegerType + "(" + simpleTypeReference.getSizeInBits() + ", logical_name=\"" + logicalName + "\")"; + return "read_buffer.read_" + unsignedIntegerType + "(" + simpleTypeReference.getSizeInBits() + ", logical_name=\"" + logicalName + "\""; case INT: String integerType; if (simpleTypeReference.getSizeInBits() <= 8) { @@ -839,10 +839,10 @@ public String getReadBufferReadMethodCall(String logicalName, SimpleTypeReferenc } else { integerType = "long"; } - return "read_buffer.read_" + integerType + "(" + simpleTypeReference.getSizeInBits() + ", logical_name=\"" + logicalName + "\")"; + return "read_buffer.read_" + integerType + "(" + simpleTypeReference.getSizeInBits() + ", logical_name=\"" + logicalName + "\""; case FLOAT: String floatType = (simpleTypeReference.getSizeInBits() <= 32) ? "float" : "double"; - return "read_buffer.read_" + floatType + "(" + simpleTypeReference.getSizeInBits() + ", logical_name=\"" + logicalName + "\")"; + return "read_buffer.read_" + floatType + "(" + simpleTypeReference.getSizeInBits() + ", logical_name=\"" + logicalName + "\""; case STRING: case VSTRING: String stringType = "str"; @@ -856,7 +856,7 @@ public String getReadBufferReadMethodCall(String logicalName, SimpleTypeReferenc VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference; length = toParseExpression(field, INT_TYPE_REFERENCE, vstringTypeReference.getLengthExpression(), null); } - return "read_buffer.read_" + stringType + "(" + simpleTypeReference.getSizeInBits() + ", logical_name=\"" + logicalName + "\", encoding=" + "\"\")"; + return "read_buffer.read_" + stringType + "(" + simpleTypeReference.getSizeInBits() + ", logical_name=\"" + logicalName + "\""; default: return ""; diff --git a/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh b/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh index 029c4854085..19ad41c98ad 100644 --- a/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh +++ b/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh @@ -89,6 +89,7 @@ class ${type.name}: <#switch field.typeName> <#case "array"> <#assign arrayField = field.asArrayField().orElseThrow()> + <#assign typedField = field.asTypedField().orElseThrow()> <#assign elementTypeReference=arrayField.type.elementTypeReference> # Array field (${helper.camelCaseToSnakeCase(arrayField.name)}) <#-- Only update curPos if the length expression uses it --> @@ -103,7 +104,7 @@ class ${type.name}: <@emitImport import="from plc4py.api.value.PlcValue import PlcValue" /> ${helper.camelCaseToSnakeCase(arrayField.name)}: List[PlcValue] = [] 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>, ))) + ${helper.camelCaseToSnakeCase(arrayField.name)}.append(${helper.getPlcValueTypeForTypeReference(elementTypeReference)}(${helper.getLanguageTypeNameForTypeReference(elementTypeReference, false)}(<#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)}${helper.getFieldOptions(typedField, parserArguments)}))<#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>, ))) <#-- In all other cases do we have to work with a list, that is later converted to an array --> <#else> @@ -118,7 +119,7 @@ class ${type.name}: while read_buffer.get_pos() < ${helper.camelCaseToSnakeCase(arrayField.name)}_end_pos): value.append(<@compress single_line=true> <#if elementTypeReference.isSimpleTypeReference()> - ${helper.getPlcValueTypeForTypeReference(elementTypeReference)}(${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)}) + ${helper.getPlcValueTypeForTypeReference(elementTypeReference)}(${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)}${helper.getFieldOptions(typedField, parserArguments)})) <#else>${elementTypeReference.asNonSimpleTypeReference().orElseThrow().name}IO.static_parse(readBuffer <#if elementTypeReference.params.isPresent()>, <#list elementTypeReference.params.orElseThrow() as parserArgument> @@ -136,7 +137,7 @@ class ${type.name}: # Terminated array ${arrayField.name}: ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} = new LinkedList<>() while not bool(${helper.camelCaseToSnakeCase(helper.toParseExpression(arrayField, helper.boolTypeReference, arrayField.loopExpression,parserArguments))})): - ${helper.camelCaseToSnakeCase(arrayField.name)}.append(<#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)}<#else>${elementTypeReference.asComplexTypeReference().orElseThrow().name}IO.static_parse(readBuffer<#if arrayField.params.isPresent()>, <#list arrayField.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index), true)}) (${helper.toParseExpression(arrayField, parserArgument,parserArguments)})<#sep>, )) + ${helper.camelCaseToSnakeCase(arrayField.name)}.append(<#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)}${helper.getFieldOptions(typedField, parserArguments)})<#else>${elementTypeReference.asComplexTypeReference().orElseThrow().name}IO.static_parse(readBuffer<#if arrayField.params.isPresent()>, <#list arrayField.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index), true)}) (${helper.toParseExpression(arrayField, parserArgument,parserArguments)})<#sep>, )) <#-- After parsing, update the current position, but only if it's needed --> <#if arrayField.loopExpression.contains("curPos")> @@ -151,9 +152,10 @@ class ${type.name}: <#break> <#case "const"> <#assign constField=field.asConstField().orElseThrow()> + <#assign typedField = field.asTypedField().orElseThrow()> # Const Field (${helper.camelCaseToSnakeCase(constField.name)}) - ${helper.camelCaseToSnakeCase(constField.name)}: ${helper.getNonPrimitiveLanguageTypeNameForField(constField)} = ${helper.getReadBufferReadMethodCall(constField.type.asSimpleTypeReference().orElseThrow(), "", constField)} + ${helper.camelCaseToSnakeCase(constField.name)}: ${helper.getNonPrimitiveLanguageTypeNameForField(constField)} = ${helper.getReadBufferReadMethodCall(constField.type.asSimpleTypeReference().orElseThrow(), "", constField)}${helper.getFieldOptions(typedField, parserArguments)}) if ${helper.camelCaseToSnakeCase(constField.name)} != ${dataIoTypeDefinition.name}.${constField.name?upper_case}): <@emitImport import="from plc4py.api.exceptions.exceptions import ParseException" /> raise ParseException("Expected constant value " + ${dataIoTypeDefinition.name}.${constField.name?upper_case} + " but got " + ${helper.camelCaseToSnakeCase(constField.name)}) @@ -164,9 +166,10 @@ class ${type.name}: <#break> <#case "enum"> <#assign enumField=field.asEnumField().orElseThrow()> + <#assign typedField = field.asTypedField().orElseThrow()> # Enum field (${helper.camelCaseToSnakeCase(enumField.name)}) - ${helper.camelCaseToSnakeCase(enumField.name)}: ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)} = ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)}.enum_for_value(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(enumField.type.asSimpleTypeReference().orElseThrow()), "", enumField)}) + ${helper.camelCaseToSnakeCase(enumField.name)}: ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)} = ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)}.enum_for_value(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(enumField.type.asSimpleTypeReference().orElseThrow()), "", enumField)}${helper.getFieldOptions(typedField, parserArguments)})) <#if enumField.name == "value"> <#assign valueDefined=true> @@ -182,22 +185,24 @@ class ${type.name}: <#break> <#case "reserved"> <#assign reservedField=field.asReservedField().orElseThrow()> + <#assign typedField = field.asTypedField().orElseThrow()> # Reserved Field (Compartmentalized so the "reserved" variable can't leak) - reserved: ${helper.getLanguageTypeNameForField(field)} = ${helper.getReadBufferReadMethodCall(reservedField.type.asSimpleTypeReference().orElseThrow(), "", reservedField)} + reserved: ${helper.getLanguageTypeNameForField(field)} = ${helper.getReadBufferReadMethodCall(reservedField.type.asSimpleTypeReference().orElseThrow(), "", reservedField)}${helper.getFieldOptions(typedField, parserArguments)}) if reserved != ${helper.getReservedValue(reservedField)}: <@emitImport import="import logging" /> logging.warning("Expected constant value " + str(${reservedField.referenceValue}) + " but got " + str(reserved) + " for reserved field.") <#break> <#case "simple"> <#assign simpleField=field.asSimpleField().orElseThrow()> + <#assign typedField = field.asTypedField().orElseThrow()> <#if helper.isEnumField(simpleField)> # Enum field (${simpleField.name}) - ${helper.camelCaseToSnakeCase(simpleField.name)}: ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} = ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)}(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(simpleField.type), "", simpleField)}) + ${helper.camelCaseToSnakeCase(simpleField.name)}: ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} = ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)}(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(simpleField.type), "", simpleField)}${helper.getFieldOptions(typedField, parserArguments)})) <#else> # Simple Field (${simpleField.name}) - ${helper.camelCaseToSnakeCase(simpleField.name)}: ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} = <#if simpleField.type.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(simpleField.type.asSimpleTypeReference().orElseThrow(), "", simpleField)}<#else>${simpleField.type.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if simpleField.params.isPresent()>, <#list field.params.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(simpleField.type, parserArgument?index), true)}(${helper.toParseExpression(simpleField, simpleField.type, parserArgument,parserArguments)})<#sep>, ) + ${helper.camelCaseToSnakeCase(simpleField.name)}: ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} = <#if simpleField.type.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(simpleField.type.asSimpleTypeReference().orElseThrow(), "", simpleField)}${helper.getFieldOptions(typedField, parserArguments)})<#else>${simpleField.type.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if simpleField.params.isPresent()>, <#list field.params.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(simpleField.type, parserArgument?index), true)}(${helper.toParseExpression(simpleField, simpleField.type, parserArgument,parserArguments)})<#sep>, ${helper.getFieldOptions(typedField, parserArguments)}) <#if case.name == "Struct" || ((case.name == "DATE_AND_TIME") && ((simpleField.name == "year") || (simpleField.name == "month") || (simpleField.name == "day") || (simpleField.name == "hour") || (simpleField.name == "minutes") || (simpleField.name == "seconds"))) || @@ -289,7 +294,8 @@ class ${type.name}: <#break> <#case "DATE"> <#if helper.hasFieldsWithNames(case.fields, "year", "month", "day")> - value: LocalDate = LocalDate(int(year), (month == 0) ? 1 : int(month), (day == 0) ? 1 : int(day)) + <@emitImport import="import datetime" /> + value: datetime = datetime.datetime(int(year), int(month), int(day)) <@emitImport import="from plc4py.spi.values.PlcValues import PlcDATE" /> return PlcDATE(value) @@ -304,12 +310,10 @@ class ${type.name}: return PlcTIME_OF_DAY(value) <#break> <#case "DATE_AND_TIME"> - <#if helper.hasFieldsWithNames(case.fields, "year", "month", "day", "hour", "minutes", "seconds", "nanos")> - value: LocalDateTime = LocalDateTime(int(year), (month == 0) ? 1 : int(month), (day == 0) ? 1 : int(day), int(hour), int(minutes), int(seconds), int(nanos)) + <#if helper.hasFieldsWithNames(case.fields, "year", "month", "day", "hour", "minutes", "seconds", "microseconds")> + value: datetime = datetime.datetime(int(year), int(month), int(day), int(hour), int(minutes), int(seconds), int(microseconds)) <#elseif helper.hasFieldsWithNames(case.fields, "year", "month", "day", "hour", "minutes", "seconds")> - value: LocalDateTime = LocalDateTime(int(year), (month == 0) ? 1 : int(month), (day == 0) ? 1 : int(day), int(hour), int(minutes), int(seconds)) - <#elseif helper.hasFieldsWithNames(case.fields, "secondsSinceEpoch")> - value: LocalDateTime = LocalDateTime.ofEpochSecond(secondsSinceEpoch, 0, ZoneOffset.UTC) + value: datetime = datetime.datetime(int(year), int(month), int(day), int(hour), int(minutes), int(seconds)) <@emitImport import="from plc4py.spi.values.PlcValues import PlcDATE_AND_TIME" /> return PlcDATE_AND_TIME(value) diff --git a/plc4py/plc4py/drivers/umas/UmasDevice.py b/plc4py/plc4py/drivers/umas/UmasDevice.py index e5a135886ae..593a4e8cd76 100644 --- a/plc4py/plc4py/drivers/umas/UmasDevice.py +++ b/plc4py/plc4py/drivers/umas/UmasDevice.py @@ -21,6 +21,7 @@ from dataclasses import dataclass, field from typing import Dict, List, Tuple, cast +from plc4py.api.exceptions.exceptions import PlcFieldParseException from plc4py.api.messages.PlcRequest import ( PlcBrowseRequest, PlcReadRequest, @@ -34,7 +35,11 @@ from plc4py.api.value.PlcValue import PlcResponseCode, PlcValue from plc4py.drivers.umas.UmasConfiguration import UmasConfiguration from plc4py.drivers.umas.UmasTag import UmasTag -from plc4py.drivers.umas.UmasVariables import UmasVariable, UmasVariableBuilder +from plc4py.drivers.umas.UmasVariables import ( + UmasVariable, + UmasVariableBuilder, + UmasElementryVariable, +) from plc4py.protocols.umas.readwrite import ( UmasPDUReadUnlocatedVariableResponse, ) @@ -110,6 +115,7 @@ VariableWriteRequestReference, ) from plc4py.spi.values.PlcValues import PlcNull +from plc4py.protocols.umas.readwrite.UmasDataType import UmasDataType @dataclass @@ -413,9 +419,17 @@ async def _send_read_variable_request( else: quantity = 1 + if request_tag.data_type == None: + request_tag.data_type = UmasDataType( + self.variables[request_tag.tag_name].data_type + ) + + value = DataItem.static_parse( + read_buffer, request_tag.data_type, request_tag.quantity + ) response_item = ResponseItem( PlcResponseCode.OK, - DataItem.static_parse(read_buffer, request_tag.data_type, quantity), + value, ) values[key] = response_item @@ -469,6 +483,32 @@ async def _send_write_variable_request( response = PlcReadResponse(PlcResponseCode.OK, values) return response + def _get_internal_words(self, tag) -> UmasElementryVariable: + system_word_block = 0x002B + area = tag[1:3].upper() + if area == "SW": + area_offset = 80 + word_number = (int(tag[3:]) * 2) + area_offset + data_type = UmasDataType.INT.value + base_offset = word_number // 0x100 + offset = word_number % 0x100 + return UmasElementryVariable( + tag, data_type, system_word_block, base_offset, offset + ) + + area = tag[1:2].upper() + if area == "S": + area_offset = 50 + word_number = (int(tag[3:]) * 2) + area_offset + data_type = UmasDataType.BOOL.value + base_offset = word_number // 0x100 + offset = word_number % 0x100 + return UmasElementryVariable( + tag, data_type, system_word_block, base_offset, offset + ) + else: + raise PlcFieldParseException("Unable to read non system addresses") + def _sort_tags_based_on_memory_address(self, request): tag_list: List[List[Tuple[str, VariableReadRequestReference]]] = [[]] current_list_index = 0 @@ -477,7 +517,10 @@ def _sort_tags_based_on_memory_address(self, request): for kea, tag in request.tags.items(): umas_tag = cast(UmasTag, tag) base_tag_name = umas_tag.tag_name.split(".")[0] - variable = self.variables[base_tag_name] + if base_tag_name[:1] != "%": + variable = self.variables[base_tag_name] + else: + variable = self._get_internal_words(base_tag_name) if byte_count + variable.get_byte_length() > self.max_frame_size: current_list_index += 1 @@ -510,14 +553,17 @@ def _sort_write_tags_based_on_memory_address(self, request): for kea, tag in request.tags.items(): umas_tag = cast(UmasTag, tag) base_tag_name = umas_tag.tag_name.split(".")[0] - variable = self.variables[base_tag_name] + if base_tag_name[:1] != "%": + variable = self.variables[base_tag_name] + else: + variable = self._get_internal_words(base_tag_name) if byte_count + variable.get_byte_length() > self.max_frame_size: current_list_index += 1 tag_list.append([]) current_list = tag_list[current_list_index] byte_count = 0 - byte_count += variable.get_byte_length() + byte_count += variable.get_byte_length() + variable.data_type current_list.append( ( kea, diff --git a/plc4py/plc4py/drivers/umas/UmasTag.py b/plc4py/plc4py/drivers/umas/UmasTag.py index 480a41dc85a..c6f48fdf92d 100644 --- a/plc4py/plc4py/drivers/umas/UmasTag.py +++ b/plc4py/plc4py/drivers/umas/UmasTag.py @@ -64,7 +64,7 @@ def create(cls, address_string): if "dataType" in matcher.groupdict() and matcher.group("dataType") is not None and len(matcher.group("dataType")) is not 0 - else cls._DEFAULT_DATA_TYPE + else None ) return cls(tag_name, quantity, data_type) diff --git a/plc4py/plc4py/drivers/umas/UmasVariables.py b/plc4py/plc4py/drivers/umas/UmasVariables.py index e730cbff16f..92eb364b028 100644 --- a/plc4py/plc4py/drivers/umas/UmasVariables.py +++ b/plc4py/plc4py/drivers/umas/UmasVariables.py @@ -153,7 +153,7 @@ def get_write_variable_reference( if len(split_tag_address) > 1: child_index = split_tag_address[1] return self.children[child_index].get_write_variable_reference( - ".".join(split_tag_address[1:]) + ".".join(split_tag_address[1:]), value ) else: raise NotImplementedError("Unable to write structures of UDT's") diff --git a/plc4py/plc4py/protocols/modbus/readwrite/DataItem.py b/plc4py/plc4py/protocols/modbus/readwrite/DataItem.py index 2eec7384365..9d76786554f 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/DataItem.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/DataItem.py @@ -425,7 +425,7 @@ def static_parse( if data_type == ModbusDataType.CHAR and number_of_values == int(1): # CHAR # Simple Field (value) - value: str = read_buffer.read_str(8, logical_name="", encoding="") + value: str = read_buffer.read_str(8, logical_name="", encoding='"UTF-8"') return PlcCHAR(value) if data_type == ModbusDataType.CHAR: # List @@ -436,7 +436,9 @@ def static_parse( for _ in range(item_count): value.append( PlcSTRING( - str(read_buffer.read_str(8, logical_name="", encoding="")) + str( + read_buffer.read_str(8, logical_name="", encoding='"UTF-8"') + ) ) ) @@ -444,7 +446,7 @@ def static_parse( if data_type == ModbusDataType.WCHAR and number_of_values == int(1): # WCHAR # Simple Field (value) - value: str = read_buffer.read_str(16, logical_name="", encoding="") + value: str = read_buffer.read_str(16, logical_name="", encoding='"UTF-16"') return PlcWCHAR(value) if data_type == ModbusDataType.WCHAR: # List @@ -455,7 +457,11 @@ def static_parse( for _ in range(item_count): value.append( PlcSTRING( - str(read_buffer.read_str(16, logical_name="", encoding="")) + str( + read_buffer.read_str( + 16, logical_name="", encoding='"UTF-16"' + ) + ) ) ) diff --git a/plc4py/plc4py/protocols/simulated/readwrite/DataItem.py b/plc4py/plc4py/protocols/simulated/readwrite/DataItem.py index f5a2dc2bbf1..02af3dea890 100644 --- a/plc4py/plc4py/protocols/simulated/readwrite/DataItem.py +++ b/plc4py/plc4py/protocols/simulated/readwrite/DataItem.py @@ -298,7 +298,7 @@ def static_parse(read_buffer: ReadBuffer, data_type: str, number_of_values: int) if data_type == "CHAR" and number_of_values == int(1): # CHAR # Simple Field (value) - value: str = read_buffer.read_str(8, logical_name="", encoding="") + value: str = read_buffer.read_str(8, logical_name="", encoding='"UTF-8"') return PlcCHAR(value) if data_type == "CHAR": # List @@ -309,7 +309,9 @@ def static_parse(read_buffer: ReadBuffer, data_type: str, number_of_values: int) for _ in range(item_count): value.append( PlcSTRING( - str(read_buffer.read_str(8, logical_name="", encoding="")) + str( + read_buffer.read_str(8, logical_name="", encoding='"UTF-8"') + ) ) ) @@ -317,7 +319,7 @@ def static_parse(read_buffer: ReadBuffer, data_type: str, number_of_values: int) if data_type == "WCHAR" and number_of_values == int(1): # WCHAR # Simple Field (value) - value: str = read_buffer.read_str(16, logical_name="", encoding="") + value: str = read_buffer.read_str(16, logical_name="", encoding='"UTF-16"') return PlcWCHAR(value) if data_type == "WCHAR": # List @@ -328,7 +330,11 @@ def static_parse(read_buffer: ReadBuffer, data_type: str, number_of_values: int) for _ in range(item_count): value.append( PlcSTRING( - str(read_buffer.read_str(16, logical_name="", encoding="")) + str( + read_buffer.read_str( + 16, logical_name="", encoding='"UTF-16"' + ) + ) ) ) @@ -336,13 +342,13 @@ def static_parse(read_buffer: ReadBuffer, data_type: str, number_of_values: int) if data_type == "STRING": # STRING # Simple Field (value) - value: str = read_buffer.read_str(255, logical_name="", encoding="") + value: str = read_buffer.read_str(255, logical_name="", encoding='"UTF-8"') return PlcSTRING(value) if data_type == "WSTRING": # STRING # Simple Field (value) - value: str = read_buffer.read_str(255, logical_name="", encoding="") + value: str = read_buffer.read_str(255, logical_name="", encoding='"UTF-16"') return PlcSTRING(value) return None diff --git a/plc4py/plc4py/protocols/umas/readwrite/DataItem.py b/plc4py/plc4py/protocols/umas/readwrite/DataItem.py index 83b54d806d4..8e75e9bf42e 100644 --- a/plc4py/plc4py/protocols/umas/readwrite/DataItem.py +++ b/plc4py/plc4py/protocols/umas/readwrite/DataItem.py @@ -24,12 +24,17 @@ from plc4py.spi.generation.WriteBuffer import WriteBuffer from plc4py.spi.values.PlcValues import PlcBOOL from plc4py.spi.values.PlcValues import PlcBYTE +from plc4py.spi.values.PlcValues import PlcDATE +from plc4py.spi.values.PlcValues import PlcDATE_AND_TIME from plc4py.spi.values.PlcValues import PlcDINT from plc4py.spi.values.PlcValues import PlcDWORD from plc4py.spi.values.PlcValues import PlcINT from plc4py.spi.values.PlcValues import PlcList from plc4py.spi.values.PlcValues import PlcREAL +from plc4py.spi.values.PlcValues import PlcSINT from plc4py.spi.values.PlcValues import PlcSTRING +from plc4py.spi.values.PlcValues import PlcTIME +from plc4py.spi.values.PlcValues import PlcTIME_OF_DAY from plc4py.spi.values.PlcValues import PlcUDINT from plc4py.spi.values.PlcValues import PlcUINT from plc4py.spi.values.PlcValues import PlcULINT @@ -37,6 +42,7 @@ from plc4py.utils.GenericTypes import ByteOrder from typing import List from typing import cast +import datetime import logging import math @@ -101,7 +107,7 @@ def static_parse( if data_type == UmasDataType.BYTE and number_of_values == int(1): # BYTE # Simple Field (value) - value: int = read_buffer.read_unsigned_short(8, logical_name="") + value: int = read_buffer.read_byte("") return PlcBYTE(value) if data_type == UmasDataType.BYTE: # List @@ -110,7 +116,7 @@ def static_parse( item_count: int = int(number_of_values * int(8)) value: List[PlcValue] = [] for _ in range(item_count): - value.append(PlcBOOL(bool(read_buffer.read_bit("")))) + value.append(PlcBYTE(int(read_buffer.read_byte("")))) return PlcList(value) if data_type == UmasDataType.WORD: # WORD @@ -226,6 +232,98 @@ def static_parse( ) return PlcList(value) + if data_type == UmasDataType.TIME and number_of_values == int(1): # TIME + + # Simple Field (value) + value: int = read_buffer.read_unsigned_long(32, logical_name="") + + return PlcTIME(value) + if data_type == UmasDataType.TIME: # List + # Array field (value) + # Count array + item_count: int = int(number_of_values) + value: List[PlcValue] = [] + for _ in range(item_count): + value.append( + PlcULINT(int(read_buffer.read_unsigned_long(32, logical_name=""))) + ) + + return PlcList(value) + if data_type == UmasDataType.DATE and number_of_values == int(1): # DATE + + # Simple Field (day) + day: int = read_buffer.read_unsigned_short( + 8, logical_name="", encoding="BCD" + ) + + # Simple Field (month) + month: int = read_buffer.read_unsigned_short( + 8, logical_name="", encoding="BCD" + ) + + # Simple Field (year) + year: int = read_buffer.read_unsigned_int( + 16, logical_name="", encoding="BCD" + ) + + value: datetime = datetime.datetime(int(year), int(month), int(day)) + return PlcDATE(value) + if data_type == UmasDataType.TOD and number_of_values == int(1): # TIME_OF_DAY + + # Simple Field (value) + value: int = read_buffer.read_unsigned_long(32, logical_name="") + + return PlcTIME_OF_DAY(value) + if data_type == UmasDataType.TOD: # List + # Array field (value) + # Count array + item_count: int = int(number_of_values) + value: List[PlcValue] = [] + for _ in range(item_count): + value.append( + PlcULINT(int(read_buffer.read_unsigned_long(32, logical_name=""))) + ) + + return PlcList(value) + if data_type == UmasDataType.DT and number_of_values == int(1): # DATE_AND_TIME + + # Simple Field (unused) + unused: int = read_buffer.read_unsigned_short(8, logical_name="") + + # Simple Field (seconds) + seconds: int = read_buffer.read_unsigned_short( + 8, logical_name="", encoding="BCD" + ) + + # Simple Field (minutes) + minutes: int = read_buffer.read_unsigned_short( + 8, logical_name="", encoding="BCD" + ) + + # Simple Field (hour) + hour: int = read_buffer.read_unsigned_short( + 8, logical_name="", encoding="BCD" + ) + + # Simple Field (day) + day: int = read_buffer.read_unsigned_short( + 8, logical_name="", encoding="BCD" + ) + + # Simple Field (month) + month: int = read_buffer.read_unsigned_short( + 8, logical_name="", encoding="BCD" + ) + + # Simple Field (year) + year: int = read_buffer.read_unsigned_int( + 16, logical_name="", encoding="BCD" + ) + + value: datetime = datetime.datetime( + int(year), int(month), int(day), int(hour), int(minutes), int(seconds) + ) + return PlcDATE_AND_TIME(value) return None @staticmethod @@ -265,13 +363,13 @@ def static_serialize( elif data_type == UmasDataType.BYTE and number_of_values == int(1): # BYTE # Simple Field (value) value: int = _value.get_int() - write_buffer.write_byte((value), 8, "value") + write_buffer.write_byte((value), "value") elif data_type == UmasDataType.BYTE: # List values: PlcList = cast(PlcList, _value) for val in values.get_list(): - value: bool = val.get_bool() - write_buffer.write_bit((value), "value") + value: List[int] = val.get_raw() + write_buffer.write_byte_array("", value) elif data_type == UmasDataType.WORD: # WORD # Simple Field (value) @@ -347,6 +445,74 @@ def static_serialize( value: float = val.get_float() write_buffer.write_float((value), 32, "value") + elif data_type == UmasDataType.TIME and number_of_values == int(1): # TIME + # Simple Field (value) + value: int = _value.get_int() + write_buffer.write_unsigned_int((value), 32, "value") + + elif data_type == UmasDataType.TIME: # List + values: PlcList = cast(PlcList, _value) + for val in values.get_list(): + value: int = val.get_int() + write_buffer.write_unsigned_int((value), 32, "value") + + elif data_type == UmasDataType.DATE and number_of_values == int(1): # DATE + # Simple Field (day) + day: int = 0 + write_buffer.write_byte((day), 8, "day") + + # Simple Field (month) + month: int = 0 + write_buffer.write_byte((month), 8, "month") + + # Simple Field (year) + year: int = 0 + write_buffer.write_unsigned_short((year), 16, "year") + + elif data_type == UmasDataType.TOD and number_of_values == int( + 1 + ): # TIME_OF_DAY + # Simple Field (value) + value: int = _value.get_int() + write_buffer.write_unsigned_int((value), 32, "value") + + elif data_type == UmasDataType.TOD: # List + values: PlcList = cast(PlcList, _value) + for val in values.get_list(): + value: int = val.get_int() + write_buffer.write_unsigned_int((value), 32, "value") + + elif data_type == UmasDataType.DT and number_of_values == int( + 1 + ): # DATE_AND_TIME + # Simple Field (unused) + unused: int = 0 + write_buffer.write_byte((unused), 8, "unused") + + # Simple Field (seconds) + seconds: int = 0 + write_buffer.write_byte((seconds), 8, "seconds") + + # Simple Field (minutes) + minutes: int = 0 + write_buffer.write_byte((minutes), 8, "minutes") + + # Simple Field (hour) + hour: int = 0 + write_buffer.write_byte((hour), 8, "hour") + + # Simple Field (day) + day: int = 0 + write_buffer.write_byte((day), 8, "day") + + # Simple Field (month) + month: int = 0 + write_buffer.write_byte((month), 8, "month") + + # Simple Field (year) + year: int = 0 + write_buffer.write_unsigned_short((year), 16, "year") + @staticmethod def get_length_in_bytes( _value: PlcValue, data_type: UmasDataType, number_of_values: int @@ -384,7 +550,7 @@ def get_length_in_bits( size_in_bits += 8 elif data_type == UmasDataType.BYTE: # List values: PlcList = cast(PlcList, _value) - size_in_bits += len(values.get_list()) * 1 + size_in_bits += len(values.get_list()) * 8 elif data_type == UmasDataType.WORD: # WORD # Simple Field (value) size_in_bits += 16 @@ -427,5 +593,43 @@ def get_length_in_bits( elif data_type == UmasDataType.STRING: # List values: PlcList = cast(PlcList, _value) size_in_bits += len(values.get_list()) * 32 + elif data_type == UmasDataType.TIME and number_of_values == int(1): # TIME + # Simple Field (value) + size_in_bits += 32 + elif data_type == UmasDataType.TIME: # List + values: PlcList = cast(PlcList, _value) + size_in_bits += len(values.get_list()) * 32 + elif data_type == UmasDataType.DATE and number_of_values == int(1): # DATE + # Simple Field (day) + size_in_bits += 8 + # Simple Field (month) + size_in_bits += 8 + # Simple Field (year) + size_in_bits += 16 + elif data_type == UmasDataType.TOD and number_of_values == int( + 1 + ): # TIME_OF_DAY + # Simple Field (value) + size_in_bits += 32 + elif data_type == UmasDataType.TOD: # List + values: PlcList = cast(PlcList, _value) + size_in_bits += len(values.get_list()) * 32 + elif data_type == UmasDataType.DT and number_of_values == int( + 1 + ): # DATE_AND_TIME + # Simple Field (unused) + size_in_bits += 8 + # Simple Field (seconds) + size_in_bits += 8 + # Simple Field (minutes) + size_in_bits += 8 + # Simple Field (hour) + size_in_bits += 8 + # Simple Field (day) + size_in_bits += 8 + # Simple Field (month) + size_in_bits += 8 + # Simple Field (year) + size_in_bits += 16 return size_in_bits diff --git a/plc4py/plc4py/protocols/umas/readwrite/UmasDataType.py b/plc4py/plc4py/protocols/umas/readwrite/UmasDataType.py index 69fcc90f31c..061afb668b8 100644 --- a/plc4py/plc4py/protocols/umas/readwrite/UmasDataType.py +++ b/plc4py/plc4py/protocols/umas/readwrite/UmasDataType.py @@ -38,7 +38,7 @@ class UmasDataType(AutoNumberEnum): UNKNOWN13 = (13, int(1), int(1)) DATE = (14, int(3), int(4)) TOD = (15, int(3), int(4)) - DT = (16, int(3), int(4)) + DT = (16, int(4), int(8)) UNKNOWN17 = (17, int(1), int(1)) UNKNOWN18 = (18, int(1), int(1)) UNKNOWN19 = (19, int(1), int(1)) diff --git a/plc4py/plc4py/spi/generation/ReadBuffer.py b/plc4py/plc4py/spi/generation/ReadBuffer.py index f250582b979..2a985cd7bad 100644 --- a/plc4py/plc4py/spi/generation/ReadBuffer.py +++ b/plc4py/plc4py/spi/generation/ReadBuffer.py @@ -24,7 +24,7 @@ from bitarray import bitarray from bitarray.util import ba2base, ba2int, zeros -from plc4py.api.exceptions.exceptions import SerializationException +from plc4py.api.exceptions.exceptions import SerializationException, ParseException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.utils.GenericTypes import ByteOrder, ByteOrderAware @@ -220,6 +220,7 @@ def read_unsigned_short( self, bit_length: int = 16, logical_name: str = "", **kwargs ) -> int: byte_order = kwargs.get("byte_order", self.byte_order) + encoding = kwargs.get("encoding", "") if bit_length <= 0: raise SerializationException("unsigned short must contain at least 1 bit") elif bit_length > 16: @@ -227,19 +228,41 @@ def read_unsigned_short( else: if byte_order == ByteOrder.LITTLE_ENDIAN: endian_string = "<" + padded = bitarray( + self.bb[self.position : self.position + bit_length] + ) + (16 - bit_length) * bitarray("0") else: endian_string = ">" - padded = (16 - bit_length) * bitarray("0") + bitarray( - self.bb[self.position : self.position + bit_length] - ) - result: int = struct.unpack(endian_string + "H", padded)[0] - self.position += bit_length - return result + padded = (16 - bit_length) * bitarray("0") + bitarray( + self.bb[self.position : self.position + bit_length] + ) + + if encoding == "BCD": + if bit_length % 4 != 0: + raise ParseException( + "'BCD' encoded fields must have a length that is a multiple of 4 bits long" + ) + result: int = 0 + for i in range(0, bit_length, 4): + digit: int = ba2int(padded[i : i + 4]) + if digit > 9: + raise ParseException( + "'BCD' encoded value is not a correctly encoded BCD value" + ) + multiplier = 10 ** ((int(bit_length - i) / 4) - 1) + result += multiplier * digit + self.position += bit_length + return result + else: + result: int = struct.unpack(endian_string + "H", padded)[0] + self.position += bit_length + return result def read_unsigned_int( self, bit_length: int = 32, logical_name: str = "", **kwargs ) -> int: byte_order = kwargs.get("byte_order", self.byte_order) + encoding = kwargs.get("encoding", "") if bit_length <= 0: raise SerializationException("unsigned int must contain at least 1 bit") elif bit_length > 32: @@ -247,14 +270,36 @@ def read_unsigned_int( else: if byte_order == ByteOrder.LITTLE_ENDIAN: endian_string = "<" + padded = bitarray( + self.bb[self.position : self.position + bit_length] + ) + (32 - bit_length) * bitarray("0") else: endian_string = ">" - padded = (32 - bit_length) * bitarray("0") + bitarray( - self.bb[self.position : self.position + bit_length] - ) - result: int = struct.unpack(endian_string + "I", padded)[0] - self.position += bit_length - return result + padded = (32 - bit_length) * bitarray("0") + bitarray( + self.bb[self.position : self.position + bit_length] + ) + if encoding == "BCD": + if bit_length % 4 != 0: + raise ParseException( + "'BCD' encoded fields must have a length that is a multiple of 4 bits long" + ) + if byte_order == ByteOrder.LITTLE_ENDIAN: + padded = padded[8:16] + padded[:8] + padded[24:32] + padded[16:24] + result: int = 0 + for i in range(0, bit_length, 4): + digit: int = ba2int(padded[i : i + 4]) + if digit > 9: + raise ParseException( + "'BCD' encoded value is not a correctly encoded BCD value" + ) + multiplier = 10 ** ((int(bit_length - i) / 4) - 1) + result += multiplier * digit + self.position += bit_length + return result + else: + result: int = struct.unpack(endian_string + "I", padded)[0] + self.position += bit_length + return result def read_unsigned_long( self, bit_length: int = 64, logical_name: str = "", **kwargs @@ -267,11 +312,14 @@ def read_unsigned_long( else: if byte_order == ByteOrder.LITTLE_ENDIAN: endian_string = "<" + padded = bitarray( + self.bb[self.position : self.position + bit_length] + ) + (64 - bit_length) * bitarray("0") else: endian_string = ">" - padded = (64 - bit_length) * bitarray("0") + bitarray( - self.bb[self.position : self.position + bit_length] - ) + padded = (64 - bit_length) * bitarray("0") + bitarray( + self.bb[self.position : self.position + bit_length] + ) result: int = struct.unpack(endian_string + "Q", padded)[0] self.position += bit_length return result @@ -301,11 +349,14 @@ def read_short(self, bit_length: int = 16, logical_name: str = "", **kwargs) -> else: if byte_order == ByteOrder.LITTLE_ENDIAN: endian_string = "<" + padded = bitarray( + self.bb[self.position : self.position + bit_length] + ) + (16 - bit_length) * bitarray("0") else: endian_string = ">" - padded = (16 - bit_length) * bitarray("0") + bitarray( - self.bb[self.position : self.position + bit_length] - ) + padded = (16 - bit_length) * bitarray("0") + bitarray( + self.bb[self.position : self.position + bit_length] + ) result: int = struct.unpack(endian_string + "h", padded)[0] self.position += bit_length return result @@ -319,11 +370,14 @@ def read_int(self, bit_length: int = 32, logical_name: str = "", **kwargs) -> in else: if byte_order == ByteOrder.LITTLE_ENDIAN: endian_string = "<" + padded = bitarray( + self.bb[self.position : self.position + bit_length] + ) + (32 - bit_length) * bitarray("0") else: endian_string = ">" - padded = (32 - bit_length) * bitarray("0") + bitarray( - self.bb[self.position : self.position + bit_length] - ) + padded = (32 - bit_length) * bitarray("0") + bitarray( + self.bb[self.position : self.position + bit_length] + ) if ( byte_order == ByteOrder.BIG_ENDIAN_BYTE_SWAP or byte_order == ByteOrder.LITTLE_ENDIAN_BYTE_SWAP @@ -342,11 +396,14 @@ def read_long(self, bit_length: int = 64, logical_name: str = "", **kwargs) -> i else: if byte_order == ByteOrder.LITTLE_ENDIAN: endian_string = "<" + padded = bitarray( + self.bb[self.position : self.position + bit_length] + ) + (64 - bit_length) * bitarray("0") else: endian_string = ">" - padded = (64 - bit_length) * bitarray("0") + bitarray( - self.bb[self.position : self.position + bit_length] - ) + padded = (64 - bit_length) * bitarray("0") + bitarray( + self.bb[self.position : self.position + bit_length] + ) if ( byte_order == ByteOrder.BIG_ENDIAN_BYTE_SWAP or byte_order == ByteOrder.LITTLE_ENDIAN_BYTE_SWAP diff --git a/plc4py/plc4py/spi/values/PlcValues.py b/plc4py/plc4py/spi/values/PlcValues.py index 3c2e5ca8848..ed207cfb74d 100644 --- a/plc4py/plc4py/spi/values/PlcValues.py +++ b/plc4py/plc4py/spi/values/PlcValues.py @@ -17,6 +17,7 @@ # under the License. # from dataclasses import dataclass +from datetime import datetime from typing import Any, Dict, List from plc4py.api.value.PlcValue import PlcValue @@ -34,11 +35,11 @@ class PlcCHAR(PlcValue[str]): pass -class PlcDATE(PlcValue[int]): +class PlcDATE(PlcValue[datetime]): pass -class PlcDATE_AND_TIME(PlcValue[int]): +class PlcDATE_AND_TIME(PlcValue[datetime]): pass diff --git a/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py b/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py index f3d2ea13ad8..edf295d8e98 100644 --- a/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py +++ b/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py @@ -28,7 +28,7 @@ from plc4py.spi.values.PlcValues import PlcINT, PlcREAL, PlcList logger = logging.getLogger("testing") -TEST_SERVER_IP = "192.168.190.174" +TEST_SERVER_IP = "192.168.190.152" @pytest.mark.asyncio diff --git a/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py b/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py index 932456db1a6..a7a37fb4d03 100644 --- a/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py +++ b/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py @@ -17,74 +17,329 @@ # under the License. # import asyncio +import datetime import logging import time +from typing import AsyncGenerator import pytest +import pytest_asyncio +from plc4py.api.PlcConnection import PlcConnection from plc4py.api.value.PlcValue import PlcResponseCode from plc4py.PlcDriverManager import PlcDriverManager from plc4py.spi.values.PlcValues import PlcBOOL, PlcINT, PlcREAL +@pytest_asyncio.fixture +async def connection() -> AsyncGenerator[PlcConnection, None]: + driver_manager = PlcDriverManager() + async with driver_manager.connection("umas://192.168.190.152:502") as connection: + yield connection + + @pytest.mark.asyncio @pytest.mark.xfail -async def manual_test_plc_driver_umas_connect(): - driver_manager = PlcDriverManager() - async with driver_manager.connection("umas://127.0.0.1:5555") as connection: - assert connection.is_connected() - assert not connection.is_connected() +async def test_plc_driver_umas_connect(connection): + assert connection.is_connected @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_umas_read(): - log = logging.getLogger(__name__) +async def test_plc_driver_umas_read_boolean(connection): + tag_alias = "Random Tag" + tag_name = "TESTING" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == True + assert response_code == PlcResponseCode.OK - driver_manager = PlcDriverManager() - async with driver_manager.connection("umas://192.168.190.152:502") as connection: - with connection.read_request_builder() as builder: - #builder.add_item(f"Random Tag {1}", "TESTING_10:BOOL") - builder.add_item(f"Random Tag {2}", "TESTING_REAL:REAL") - request = builder.build() +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_boolean_with_data_type(connection): + tag_alias = "Random Tag" + tag_name = "TESTING:BOOL" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == True + assert response_code == PlcResponseCode.OK + - future = connection.execute(request) - response = await future - value = response.tags["Random Tag 1"].value - assert value == 0.0 +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_int(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_INT" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == 99 + assert response_code == PlcResponseCode.OK @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_umas_write(): - log = logging.getLogger(__name__) +async def test_plc_driver_umas_read_int_with_data_type(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_INT:INT" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == 99 + assert response_code == PlcResponseCode.OK - driver_manager = PlcDriverManager() - async with driver_manager.connection("umas://192.168.190.152:502") as connection: - with connection.write_request_builder() as builder: - # builder.add_item(f"Random Tag {1}", "TESTING_10:BOOL", PlcBOOL(True)) - # builder.add_item(f"Random Tag {2}", "TESTING_INT:INT", PlcINT(10)) - # builder.add_item(f"Random Tag {3}", "TESTING_EBOOL:BOOL", PlcBOOL(True)) - builder.add_item(f"Random Tag {4}", "TESTING_REAL:REAL", PlcREAL(3.18)) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags["Random Tag 1"].response_code - assert value == PlcResponseCode.OK +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_dint(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_DINT" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == 763539 + assert response_code == PlcResponseCode.OK @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_umas_browse(): - driver_manager = PlcDriverManager() - async with driver_manager.connection("umas://192.168.190.174:502") as connection: - with connection.browse_request_builder() as builder: - builder.add_query("All Tags", "*") - request = builder.build() +async def test_plc_driver_umas_read_dint_with_data_type(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_DINT:DINT" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == 763539 + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_ebool(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_EBOOL" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == True + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_ebool_with_data_type(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_EBOOL:BOOL" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == True + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_string(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_STRING" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == "Hello World" + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_string_with_data_type(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_STRING:STRING" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == "Hello World" + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_time(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_TIME" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == 200000 + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_time_with_data_type(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_TIME:TIME" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == 200000 + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_byte(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_BYTE" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == 253 + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_byte_with_data_type(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_BYTE:BYTE" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == 253 + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_date(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_DATE" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == datetime.datetime(2024, 10, 25) + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_time_with_data_type(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_DATE:DATE" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == datetime.datetime(2024, 10, 25) + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_dt(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_DT" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == datetime.datetime(2000, 1, 10, 0, 40) + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_dt_with_data_type(connection): + tag_alias = "Random Tag" + tag_name = "TESTING_DT:DATE_AND_TIME" + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + request = builder.build() + future = connection.execute(request) + response = await future + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == datetime.datetime(2000, 1, 10, 0, 40) + assert response_code == PlcResponseCode.OK + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_browse(connection): + with connection.browse_request_builder() as builder: + builder.add_query("All Tags", "*") + request = builder.build() - future = connection.execute(request) - response = await future + future = connection.execute(request) + response = await future - pass + pass diff --git a/protocols/umas/src/main/resources/protocols/umas/umas.mspec b/protocols/umas/src/main/resources/protocols/umas/umas.mspec index 878f64117c7..de9079130aa 100644 --- a/protocols/umas/src/main/resources/protocols/umas/umas.mspec +++ b/protocols/umas/src/main/resources/protocols/umas/umas.mspec @@ -257,11 +257,10 @@ [array bit value count 'numberOfValues' ] ] ['BYTE','1' BYTE - [simple uint 8 value] + [simple byte value] ] ['BYTE' List - // TODO: If the number of values is odd, add a reserved byte - [array bit value count 'numberOfValues * 8' ] + [array byte value count 'numberOfValues * 8' ] ] ['WORD' WORD [simple uint 16 value] @@ -305,6 +304,32 @@ ['STRING' List [array float 32 value count 'numberOfValues'] ] + ['TIME','1' TIME + [simple uint 32 value] + ] + ['TIME' List + [array uint 32 value count 'numberOfValues'] + ] + ['DATE','1' DATE + [simple uint 8 day encoding='BCD'] + [simple uint 8 month encoding='BCD'] + [simple uint 16 year encoding='BCD'] + ] + ['TOD','1' TIME_OF_DAY + [simple uint 32 value] + ] + ['TOD' List + [array uint 32 value count 'numberOfValues'] + ] + ['DT','1' DATE_AND_TIME + [simple uint 8 unused] + [simple uint 8 seconds encoding='BCD'] + [simple uint 8 minutes encoding='BCD'] + [simple uint 8 hour encoding='BCD'] + [simple uint 8 day encoding='BCD'] + [simple uint 8 month encoding='BCD'] + [simple uint 16 year encoding='BCD'] + ] ] ] @@ -324,7 +349,7 @@ ['13' UNKNOWN13 ['1','1']] ['14' DATE ['4','3']] ['15' TOD ['4','3']] - ['16' DT ['4','3']] + ['16' DT ['8','4']] ['17' UNKNOWN17 ['1','1']] ['18' UNKNOWN18 ['1','1']] ['19' UNKNOWN19 ['1','1']] diff --git a/src/site/asciidoc/users/protocols/index.adoc b/src/site/asciidoc/users/protocols/index.adoc index 5ad3536941c..4bb5e16e541 100644 --- a/src/site/asciidoc/users/protocols/index.adoc +++ b/src/site/asciidoc/users/protocols/index.adoc @@ -153,6 +153,13 @@ |icon:check[role="green"] |icon:times[role="red"] +|UMAS +|icon:times[role="red"] +|icon:times[role="red"] +|icon:times[role="red"] +|icon:times[role="red"] +|icon:check[role="green"] + |=== Legend: @@ -379,6 +386,17 @@ The following table contains a list of operations and the protocols that support |icon:question[role="red"] |icon:question[role="red"] +|UMAS +|icon:question[role="red"] +|icon:question[role="red"] +|icon:check[role="green"] +|icon:check[role="green"] +|icon:check[role="green"] +|icon:question[role="red"] +|icon:question[role="red"] +|icon:question[role="red"] +|icon:question[role="red"] + |=== Legend: diff --git a/src/site/asciidoc/users/protocols/umas.adoc b/src/site/asciidoc/users/protocols/umas.adoc index 18c2d000984..534ba8f08e1 100644 --- a/src/site/asciidoc/users/protocols/umas.adoc +++ b/src/site/asciidoc/users/protocols/umas.adoc @@ -21,18 +21,20 @@ === Connection String Options -==== Modbus TCP - -include::../../../plc4j/drivers/all/src/site/generated/modbus-tcp.adoc[] - -==== Modbus RTU - -include::../../../plc4j/drivers/all/src/site/generated/modbus-rtu.adoc[] - -==== Modbus ASCII - -include::../../../plc4j/drivers/all/src/site/generated/modbus-ascii.adoc[] +[cols="2,2a,2a,2a,4a"] +|=== +|Name |Type |Default Value |Required |Description +|Name 4+|UMAS +|Code 4+|`umas` +|Default Transport 4+|`tcp` +|Supported Transports 4+| + - `tcp` +5+|Config options: +|`request-timeout` |INT |5000| |Default timeout for all types of requests. +|`default-unit-identifier` |INT |1| |Unit-identifier or slave-id that identifies the target PLC (On RS485 multiple Modbus Devices can be listening). Defaults to 1. ++++ +|=== === Supported Operations [cols="2,2a,5a"] @@ -46,19 +48,22 @@ include::../../../plc4j/drivers/all/src/site/generated/modbus-ascii.adoc[] | 2+| `write` + +| +2+| `browse` |=== === Individual Resource Address Format ==== Connection String -Modbus has the following connection string format:- +UMAS has the following connection string format:- ---- -modbus-tcp:{transport}://{ip-address}:{port}?{options} +umas:{transport}://{ip-address}:{port}?{options} ---- An example connection string would look like:- ---- -modbus-tcp:tcp://127.0.0.1:502 +umas:tcp://127.0.0.1:502 ---- Note the transport, port and option fields are optional. @@ -68,23 +73,14 @@ Note the transport, port and option fields are optional. In general all Modbus addresses have this format: ---- -{memory-Area}{start-address}:{data-type}[{array-size}]:{name-value-tag-options} +{tag-name}.{child-name}.{child-name}:{data-type}[{array-size}] ---- +Depending on the type of tag the child-name parameters are optional. +e.g. A tag with a BOOL data type could be 'TESTING_BOOL_1' whereas +if it is a UDT the tag name is followed by the child 'TESTING_UDT_1.START' which in itself could be a BOOL. If the array-size part is omitted, the size-default of `1` is assumed. -If the data-type part is omitted, it defaults to BOOL for Coils and Discrete Inputs and INT for input, holding and extended registers. -If the name-value-tag-options part is omitted, simply no configuration fine-tuning is applied. - -Additionally address can contain tag configuration: ----- -{unit-id: 123} ----- -Specifying this value overrides value of `default-unit-id` parameter specified at the connection string. - ----- -{byte-order: 'LITTLE_ENDIAN'} ----- -With this, can the default byte-order be overridden on a per-tag basis. If not provided the default-byte-order from the connection string is used, or BIG_ENDIAN, if this is also not provided. +If the data-type part is omitted, it defaults to the data type of the tag read from the PLC. ==== Memory Areas diff --git a/src/site/site.xml b/src/site/site.xml index 0e4d6cd09af..8f37821fd30 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -87,6 +87,7 @@ + From 8d514900f4bf667f071993050b1e5d30338eb9d1 Mon Sep 17 00:00:00 2001 From: hutcheb Date: Tue, 29 Oct 2024 17:38:32 +0800 Subject: [PATCH 3/8] feat(plc4py/umas): Update documentation, test and look at random datatypes --- .../python/PythonLanguageTemplateHelper.java | 9 +- .../python/complex-type-template.python.ftlh | 2 +- .../python/data-io-template.python.ftlh | 8 +- plc4py/plc4py/api/messages/PlcResponse.py | 7 +- .../plc4py/drivers/modbus/ModbusConnection.py | 2 +- plc4py/plc4py/drivers/modbus/ModbusDevice.py | 1 + plc4py/plc4py/drivers/umas/UmasDevice.py | 90 +++- plc4py/plc4py/drivers/umas/UmasTag.py | 6 +- plc4py/plc4py/drivers/umas/UmasVariables.py | 35 +- .../protocols/modbus/readwrite/DataItem.py | 8 +- .../protocols/modbus/readwrite/ModbusADU.py | 2 +- .../modbus/readwrite/ModbusAsciiADU.py | 2 +- .../protocols/modbus/readwrite/ModbusPDU.py | 2 +- .../readwrite/ModbusPDUDiagnosticRequest.py | 2 +- .../readwrite/ModbusPDUDiagnosticResponse.py | 2 +- .../modbus/readwrite/ModbusPDUError.py | 2 +- .../ModbusPDUGetComEventCounterRequest.py | 2 +- .../ModbusPDUGetComEventCounterResponse.py | 2 +- .../ModbusPDUGetComEventLogRequest.py | 2 +- .../ModbusPDUGetComEventLogResponse.py | 2 +- ...odbusPDUMaskWriteHoldingRegisterRequest.py | 2 +- ...dbusPDUMaskWriteHoldingRegisterResponse.py | 2 +- .../readwrite/ModbusPDUReadCoilsRequest.py | 2 +- .../readwrite/ModbusPDUReadCoilsResponse.py | 2 +- ...odbusPDUReadDeviceIdentificationRequest.py | 2 +- ...dbusPDUReadDeviceIdentificationResponse.py | 2 +- .../ModbusPDUReadDiscreteInputsRequest.py | 2 +- .../ModbusPDUReadDiscreteInputsResponse.py | 2 +- .../ModbusPDUReadExceptionStatusRequest.py | 2 +- .../ModbusPDUReadExceptionStatusResponse.py | 2 +- .../ModbusPDUReadFifoQueueRequest.py | 2 +- .../ModbusPDUReadFifoQueueResponse.py | 2 +- .../ModbusPDUReadFileRecordRequest.py | 2 +- .../ModbusPDUReadFileRecordResponse.py | 2 +- .../ModbusPDUReadHoldingRegistersRequest.py | 2 +- .../ModbusPDUReadHoldingRegistersResponse.py | 2 +- .../ModbusPDUReadInputRegistersRequest.py | 2 +- .../ModbusPDUReadInputRegistersResponse.py | 2 +- ...eadWriteMultipleHoldingRegistersRequest.py | 2 +- ...adWriteMultipleHoldingRegistersResponse.py | 2 +- .../ModbusPDUReportServerIdRequest.py | 2 +- .../ModbusPDUReportServerIdResponse.py | 2 +- .../ModbusPDUWriteFileRecordRequest.py | 2 +- .../ModbusPDUWriteFileRecordResponse.py | 2 +- .../ModbusPDUWriteMultipleCoilsRequest.py | 2 +- .../ModbusPDUWriteMultipleCoilsResponse.py | 2 +- ...PDUWriteMultipleHoldingRegistersRequest.py | 2 +- ...DUWriteMultipleHoldingRegistersResponse.py | 2 +- .../ModbusPDUWriteSingleCoilRequest.py | 2 +- .../ModbusPDUWriteSingleCoilResponse.py | 2 +- .../ModbusPDUWriteSingleRegisterRequest.py | 2 +- .../ModbusPDUWriteSingleRegisterResponse.py | 2 +- .../modbus/readwrite/ModbusRtuADU.py | 2 +- .../modbus/readwrite/ModbusTcpADU.py | 2 +- .../protocols/simulated/readwrite/DataItem.py | 12 +- plc4py/plc4py/protocols/umas/StaticHelper.py | 7 +- .../protocols/umas/readwrite/DataItem.py | 57 +-- .../protocols/umas/readwrite/UmasDataType.py | 52 +-- plc4py/plc4py/spi/generation/WriteBuffer.py | 14 + plc4py/plc4py/spi/messages/PlcWriter.py | 2 +- .../plc4py/spi/messages/utils/ResponseItem.py | 30 +- .../plc4py/utils/ConnectionStringHandling.py | 8 + plc4py/pom.xml | 18 + plc4py/pyproject.toml | 81 ++++ plc4py/requirements.txt | 54 --- plc4py/{setup.py => setup_old.py} | 1 + .../drivers/modbus/test_modbus_connection.py | 398 +++++------------- .../drivers/umas/test_umas_connection.py | 256 ++++------- .../main/resources/protocols/umas/umas.mspec | 70 ++- src/site/asciidoc/users/protocols/umas.adoc | 119 +----- 70 files changed, 594 insertions(+), 841 deletions(-) create mode 100644 plc4py/pyproject.toml delete mode 100644 plc4py/requirements.txt rename plc4py/{setup.py => setup_old.py} (99%) diff --git a/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java index c4054c61cec..c842e5ebe95 100644 --- a/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java +++ b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java @@ -895,7 +895,8 @@ public String getWriteBufferWriteMethodCall(String logicalName, SimpleTypeRefere case BIT: return "write_buffer.write_bit(" + fieldName + ", \"" + logicalName + "\"" + writerArgsString + ")"; case BYTE: - return "write_buffer.write_byte(" + fieldName + ", \"" + logicalName + "\"" + writerArgsString + ")"; + ByteTypeReference byteTypeReference = (ByteTypeReference) simpleTypeReference; + return "write_buffer.write_byte(" + fieldName + ", " + byteTypeReference.getSizeInBits() + ", \"" + logicalName + "\"" + writerArgsString + ")"; case UINT: IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference; if (unsignedIntegerTypeReference.getSizeInBits() <= 8) { @@ -945,7 +946,7 @@ public String getWriteBufferWriteMethodCall(String logicalName, SimpleTypeRefere .orElseThrow(() -> new FreemarkerException("Encoding must be a quoted string value")).getValue(); String length = Integer.toString(simpleTypeReference.getSizeInBits()); return "write_buffer.write_str(" + fieldName + ", " + length + ", \"" + - encoding + "\", \"" + logicalName + "\"" + writerArgsString + ")"; + logicalName + "\", \"" + encoding + "\"" + writerArgsString + ")"; } case VSTRING: { VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference; @@ -957,7 +958,7 @@ public String getWriteBufferWriteMethodCall(String logicalName, SimpleTypeRefere String lengthExpression = toExpression(field, null, vstringTypeReference.getLengthExpression(), null, Collections.singletonList(new DefaultArgument("stringLength", new DefaultIntegerTypeReference(SimpleTypeReference.SimpleBaseType.INT, 32))), true, false); String length = Integer.toString(simpleTypeReference.getSizeInBits()); return "write_buffer.write_str(" + fieldName + ", " + lengthExpression + ", \"" + - encoding + "\", \"" + logicalName + "\"" + writerArgsString + ")"; + logicalName + "\", \"" + encoding + "\"" + writerArgsString + ")"; } case DATE: case TIME: @@ -1511,7 +1512,7 @@ else if ((variableLiteral.getChild().isPresent()) && ((ComplexTypeDefinition) th } else if ((serializerArguments != null) && serializerArguments.stream() .anyMatch(argument -> argument.getName().equals(variableLiteralName))) { tracer = tracer.dive("serialization argument"); - return tracer + "self." + camelCaseToSnakeCase(variableLiteralName) + + return tracer + camelCaseToSnakeCase(variableLiteralName) + variableLiteral.getChild() .map(child -> "." + camelCaseToSnakeCase(toVariableExpression(field, typeReference, child, parserArguments, serializerArguments, serialize, suppressPointerAccess, true))) .orElse(""); diff --git a/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh b/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh index 626d950a536..10a98c6a29c 100644 --- a/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh +++ b/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh @@ -565,7 +565,7 @@ class ${type.name}<#if type.isDiscriminatedParentTypeDefinition()>(<#if ty <#if parserArgument.type.isEnumTypeReference()> ${helper.camelCaseToSnakeCase(parserArgument.name)} = ${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)}[${helper.camelCaseToSnakeCase(parserArgument.name)}] <#elseif helper.getLanguageTypeNameForTypeReference(parserArgument.type, false) = "bool"> - <@emitImport import="from distutils.util import strtobool" /> + <@emitImport import="from plc4py.utils.ConnectionStringHandling import strtobool" /> ${helper.camelCaseToSnakeCase(parserArgument.name)} = bool(strtobool(${helper.camelCaseToSnakeCase(parserArgument.name)})) <#else> ${helper.camelCaseToSnakeCase(parserArgument.name)} = ${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)}(${helper.camelCaseToSnakeCase(parserArgument.name)}) diff --git a/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh b/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh index 19ad41c98ad..1daeb76bcc6 100644 --- a/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh +++ b/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh @@ -372,7 +372,7 @@ class ${type.name}: for val in values.getStruct().get("${arrayField.name}").get_list(): <#if elementTypeReference.isByteBased()> value: ${helper.getLanguageTypeNameForField(arrayField)} = val.get_raw() - write_buffer.write_byte_array("", value) + write_buffer.write_byte(value, 8, "") <#else> value: ${helper.getLanguageTypeNameForField(arrayField)} = val.get_${helper.camelCaseToSnakeCase(helper.getLanguageTypeNameForField(arrayField)?cap_first)}() ${helper.getWriteBufferWriteMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "value", arrayField)} @@ -382,7 +382,7 @@ class ${type.name}: <#if elementTypeReference.isByteBased()> <@emitImport import="from typing import List" /> value: ${helper.getLanguageTypeNameForField(arrayField)} = val.get_raw() - write_buffer.write_byte_array("", value) + write_buffer.write_byte(value, 8, "") <#else> value: ${helper.getLanguageTypeNameForTypeReference(elementTypeReference)} = val.get_${helper.camelCaseToSnakeCase(helper.getLanguageTypeNameForTypeReference(elementTypeReference)?cap_first)}() ${helper.getWriteBufferWriteMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "(" + arrayField.name + ")", arrayField)} @@ -410,7 +410,9 @@ class ${type.name}: <#case "manual"> <#assign manualField=field.asManualField().orElseThrow()> # Manual Field (${manualField.name}) - ${helper.toSerializationExpression(manualField, manualField.type, manualField.serializeExpression, type.parserArguments.orElse(null))} + <@emitImport import="from plc4py.protocols.${protocolName} import StaticHelper" /> + value: PlcValue = _value + StaticHelper.${helper.toSerializationExpression(manualField, manualField.type, manualField.serializeExpression, type.parserArguments.orElse(null))} <#break> <#case "reserved"> diff --git a/plc4py/plc4py/api/messages/PlcResponse.py b/plc4py/plc4py/api/messages/PlcResponse.py index 807dba5d43d..f61457bd965 100644 --- a/plc4py/plc4py/api/messages/PlcResponse.py +++ b/plc4py/plc4py/api/messages/PlcResponse.py @@ -22,7 +22,7 @@ from plc4py.api.messages.PlcField import PlcTag from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.api.value.PlcValue import PlcResponseCode, PlcValue -from plc4py.spi.messages.utils.ResponseItem import ResponseItem +from plc4py.spi.messages.utils.ResponseItem import ResponseItem, PlcBrowseItem @dataclass @@ -75,7 +75,7 @@ class PlcWriteResponse(PlcTagResponse): @dataclass class PlcQueryResponse(PlcResponse): - tags: Dict[str, List[ResponseItem[PlcTag]]] + tags: Dict[str, List[PlcBrowseItem[PlcTag]]] @property def tag_names(self): @@ -84,5 +84,6 @@ def tag_names(self): @dataclass class PlcBrowseResponse(PlcQueryResponse): - def get_tags(self) -> Dict[str, List[ResponseItem[PlcTag]]]: + + def get_tags(self) -> Dict[str, List[PlcBrowseItem[PlcTag]]]: return self.tags diff --git a/plc4py/plc4py/drivers/modbus/ModbusConnection.py b/plc4py/plc4py/drivers/modbus/ModbusConnection.py index d12b437f6a3..dc915464010 100644 --- a/plc4py/plc4py/drivers/modbus/ModbusConnection.py +++ b/plc4py/plc4py/drivers/modbus/ModbusConnection.py @@ -104,7 +104,7 @@ async def create(url: str) -> "ModbusConnection": host=config.host, port=config.port, ), - 10, + 15, ) except asyncio.TimeoutError: # If the creation times out, cancel the Future diff --git a/plc4py/plc4py/drivers/modbus/ModbusDevice.py b/plc4py/plc4py/drivers/modbus/ModbusDevice.py index b52993027ad..056d3798bd2 100644 --- a/plc4py/plc4py/drivers/modbus/ModbusDevice.py +++ b/plc4py/plc4py/drivers/modbus/ModbusDevice.py @@ -209,6 +209,7 @@ async def write( message_future = loop.create_future() values = request.values[request.tag_names[0]] if isinstance(tag, ModbusTagCoil): + values = self._serialize_data_items(tag, values) pdu = ModbusPDUWriteMultipleCoilsRequest(tag.address, tag.quantity, values) elif isinstance(tag, ModbusTagDiscreteInput): raise PlcRuntimeException( diff --git a/plc4py/plc4py/drivers/umas/UmasDevice.py b/plc4py/plc4py/drivers/umas/UmasDevice.py index 593a4e8cd76..cb3ad770759 100644 --- a/plc4py/plc4py/drivers/umas/UmasDevice.py +++ b/plc4py/plc4py/drivers/umas/UmasDevice.py @@ -19,9 +19,11 @@ import asyncio from asyncio import AbstractEventLoop, Transport from dataclasses import dataclass, field +from logging import raiseExceptions from typing import Dict, List, Tuple, cast -from plc4py.api.exceptions.exceptions import PlcFieldParseException +from plc4py.drivers.umas.UmasVariables import UmasCustomVariable, UmasArrayVariable +from plc4py.api.exceptions.exceptions import PlcFieldParseException, PlcRuntimeException from plc4py.api.messages.PlcRequest import ( PlcBrowseRequest, PlcReadRequest, @@ -34,7 +36,7 @@ ) from plc4py.api.value.PlcValue import PlcResponseCode, PlcValue from plc4py.drivers.umas.UmasConfiguration import UmasConfiguration -from plc4py.drivers.umas.UmasTag import UmasTag +from plc4py.drivers.umas.UmasTag import UmasTag, UmasTagBuilder from plc4py.drivers.umas.UmasVariables import ( UmasVariable, UmasVariableBuilder, @@ -103,7 +105,11 @@ VariableReadRequestReference, ) from plc4py.spi.generation.ReadBuffer import ReadBufferByteBased -from plc4py.spi.messages.utils.ResponseItem import ResponseItem +from plc4py.spi.messages.utils.ResponseItem import ( + ResponseItem, + PlcBrowseItem, + ArrayInfo, +) from plc4py.utils.GenericTypes import ByteOrder from plc4py.protocols.umas.readwrite.UmasPDUWriteVariableRequest import ( UmasPDUWriteVariableRequestBuilder, @@ -420,13 +426,11 @@ async def _send_read_variable_request( quantity = 1 if request_tag.data_type == None: - request_tag.data_type = UmasDataType( - self.variables[request_tag.tag_name].data_type - ) + data_type = UmasDataType(self.variables[request_tag.tag_name].data_type) + else: + data_type = UmasDataType[request_tag.data_type] - value = DataItem.static_parse( - read_buffer, request_tag.data_type, request_tag.quantity - ) + value = DataItem.static_parse(read_buffer, data_type, request_tag.quantity) response_item = ResponseItem( PlcResponseCode.OK, value, @@ -619,6 +623,66 @@ async def write( response.tags = {**response.tags, **response_chunk.tags} return response + def generate_browse_tree(self, tag) -> PlcBrowseItem[UmasTag]: + builder = UmasTagBuilder() + if isinstance(tag, UmasElementryVariable): + plc_tag = builder.create( + tag.variable_name + + ":" + + str(UmasDataType(tag.data_type).data_type_conversion) + ) + + return PlcBrowseItem[UmasTag]( + plc_tag, tag.variable_name, True, True, False, False, False + ) + + elif isinstance(tag, UmasCustomVariable): + plc_tag = builder.create(tag.variable_name) + children: Dict[str, "PlcBrowseItem"] = {} + for tag_name, child in tag.children.items(): + children[tag_name] = self.generate_browse_tree(child) + return PlcBrowseItem[UmasTag]( + plc_tag, + tag.variable_name, + True, + True, + False, + False, + False, + [], + children, + ) + elif isinstance(tag, UmasArrayVariable): + plc_tag = builder.create( + tag.variable_name + + ":" + + str(UmasDataType(tag.data_type).data_type_conversion) + + "[" + + str(tag.end_index) + + "]" + ) + + return PlcBrowseItem[UmasTag]( + plc_tag, + tag.variable_name, + True, + True, + False, + False, + True, + [ + ArrayInfo( + tag.end_index - tag.start_index + 1, + tag.start_index, + tag.end_index, + ) + ], + ) + else: + raise PlcRuntimeException( + f"Variable of type {type(tag).__name__} is not supported" + ) + async def browse( self, request: PlcBrowseRequest, transport: Transport ) -> PlcBrowseResponse: @@ -628,9 +692,9 @@ async def browse( loop = asyncio.get_running_loop() await self._update_plc_project_info(transport, loop) response_items = {} + for key, query in request.queries.items(): - response_items[key] = [ - ResponseItem[UmasTag](PlcResponseCode.OK, tag) - for tag in self.variables.values() - ] + response_items[key] = [] + for tag in self.variables.values(): + response_items[key].append(self.generate_browse_tree(tag)) return PlcBrowseResponse(PlcResponseCode.OK, response_items) diff --git a/plc4py/plc4py/drivers/umas/UmasTag.py b/plc4py/plc4py/drivers/umas/UmasTag.py index c6f48fdf92d..69ab09202b0 100644 --- a/plc4py/plc4py/drivers/umas/UmasTag.py +++ b/plc4py/plc4py/drivers/umas/UmasTag.py @@ -27,7 +27,7 @@ class UmasTag(PlcTag): _ADDRESS_PATTERN: str = ( - "^(?P[%a-zA-Z_.0-9]+\\[?[0-9]*]?):?(?P[A-Z]*):?(?P[0-9]*)" + "^(?P[%a-zA-Z_.0-9]+):?(?P[A-Z_]*)(\[(?P[0-9]*)\])?" ) _ADDRESS_COMPILED: Pattern[AnyStr] = re.compile(_ADDRESS_PATTERN) @@ -36,7 +36,7 @@ class UmasTag(PlcTag): def __init__(self, tag_name: str, quantity: int, data_type: UmasDataType): self.tag_name: str = tag_name self.quantity: int = quantity - self.data_type: UmasDataType = data_type + self.data_type: str = data_type @classmethod def matches(cls, address_string: str): @@ -60,7 +60,7 @@ def create(cls, address_string): else 1 ) data_type = ( - UmasDataType[matcher.group("dataType")] + matcher.group("dataType") if "dataType" in matcher.groupdict() and matcher.group("dataType") is not None and len(matcher.group("dataType")) is not 0 diff --git a/plc4py/plc4py/drivers/umas/UmasVariables.py b/plc4py/plc4py/drivers/umas/UmasVariables.py index 92eb364b028..8a22194eaf7 100644 --- a/plc4py/plc4py/drivers/umas/UmasVariables.py +++ b/plc4py/plc4py/drivers/umas/UmasVariables.py @@ -94,7 +94,6 @@ def get_read_variable_reference(self, address: str) -> VariableReadRequestRefere def get_write_variable_reference( self, address: str, value: PlcValue ) -> VariableWriteRequestReference: - ss = UmasDataType(self.data_type).data_type_size write_buffer = WriteBufferByteBased( UmasDataType(self.data_type).data_type_size, ByteOrder.LITTLE_ENDIAN ) @@ -108,11 +107,11 @@ def get_write_variable_reference( if self.data_type == UmasDataType.STRING.value: return VariableWriteRequestReference( is_array=1, - data_size_index=UmasDataType(self.data_type).request_size, + data_size_index=1, block=self.block_no, base_offset=self.offset, offset=self.base_offset, - array_length=16, + array_length=len(value.value), record_data=bytearray(write_buffer.bb), ) else: @@ -204,7 +203,19 @@ def get_write_variable_reference( if len(split_tag_address) > 1: address_index = int(split_tag_address[1]) data_type_enum = UmasDataType(self.data_type) + if address_index: + write_buffer = WriteBufferByteBased( + UmasDataType(self.data_type).data_type_size * address_index, + ByteOrder.LITTLE_ENDIAN, + ) + DataItem.static_serialize( + write_buffer, + value, + UmasDataType(self.data_type), + address_index, + ByteOrder.LITTLE_ENDIAN, + ) return VariableWriteRequestReference( is_array=0, data_size_index=data_type_enum.request_size, @@ -212,10 +223,22 @@ def get_write_variable_reference( base_offset=self.base_offset, offset=self.offset + (address_index - self.start_index) * data_type_enum.data_type_size, - array_length=None, - record_data=None, + array_length=address_index, + record_data=bytearray(write_buffer.bb), ) else: + write_buffer = WriteBufferByteBased( + UmasDataType(self.data_type).data_type_size + * (self.end_index - self.start_index + 1), + ByteOrder.LITTLE_ENDIAN, + ) + DataItem.static_serialize( + write_buffer, + value, + UmasDataType(self.data_type), + self.end_index - self.start_index + 1, + ByteOrder.LITTLE_ENDIAN, + ) return VariableWriteRequestReference( is_array=1, data_size_index=data_type_enum.request_size, @@ -223,7 +246,7 @@ def get_write_variable_reference( base_offset=self.base_offset, offset=self.offset, array_length=self.end_index - self.start_index + 1, - record_data=None, + record_data=bytearray(write_buffer.bb), ) def get_byte_length(self) -> int: diff --git a/plc4py/plc4py/protocols/modbus/readwrite/DataItem.py b/plc4py/plc4py/protocols/modbus/readwrite/DataItem.py index 9d76786554f..50fbafeefa1 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/DataItem.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/DataItem.py @@ -697,24 +697,24 @@ def static_serialize( elif data_type == ModbusDataType.CHAR and number_of_values == int(1): # CHAR # Simple Field (value) value: str = _value.get_str() - write_buffer.write_str((value), 8, "UTF-8", "value") + write_buffer.write_str((value), 8, "value", "UTF-8") elif data_type == ModbusDataType.CHAR: # List values: PlcList = cast(PlcList, _value) for val in values.get_list(): value: str = val.get_str() - write_buffer.write_str((value), 8, "UTF-8", "value") + write_buffer.write_str((value), 8, "value", "UTF-8") elif data_type == ModbusDataType.WCHAR and number_of_values == int(1): # WCHAR # Simple Field (value) value: str = _value.get_str() - write_buffer.write_str((value), 16, "UTF-16", "value") + write_buffer.write_str((value), 16, "value", "UTF-16") elif data_type == ModbusDataType.WCHAR: # List values: PlcList = cast(PlcList, _value) for val in values.get_list(): value: str = val.get_str() - write_buffer.write_str((value), 16, "UTF-16", "value") + write_buffer.write_str((value), 16, "value", "UTF-16") @staticmethod def get_length_in_bytes( diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusADU.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusADU.py index 7cac135246f..ec7864cbc79 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusADU.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusADU.py @@ -21,7 +21,6 @@ from abc import ABC from abc import abstractmethod -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import ParseException from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException @@ -29,6 +28,7 @@ from plc4py.protocols.modbus.readwrite.DriverType import DriverType from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusAsciiADU.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusAsciiADU.py index e18f5e9facd..8f78079ba2b 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusAsciiADU.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusAsciiADU.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -29,6 +28,7 @@ from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from plc4py.utils.GenericTypes import ByteOrder from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDU.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDU.py index d2fbad670b4..e03ec11f67e 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDU.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDU.py @@ -21,13 +21,13 @@ from abc import ABC from abc import abstractmethod -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import ParseException from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUDiagnosticRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUDiagnosticRequest.py index 9d8dc6ecbd6..92bab7e1f9e 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUDiagnosticRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUDiagnosticRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUDiagnosticResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUDiagnosticResponse.py index 5644e5cbd7e..dc618192201 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUDiagnosticResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUDiagnosticResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUError.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUError.py index 0d0625be68b..f7a542b19b1 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUError.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUError.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -27,6 +26,7 @@ from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventCounterRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventCounterRequest.py index eed5c85f3ac..1a8967bbba2 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventCounterRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventCounterRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventCounterResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventCounterResponse.py index ddb93a5e45d..0a857bb2478 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventCounterResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventCounterResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventLogRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventLogRequest.py index 61327aae3c1..68ca6e23df8 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventLogRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventLogRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventLogResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventLogResponse.py index 2717794df7d..f0fbcceb40e 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventLogResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUGetComEventLogResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUMaskWriteHoldingRegisterRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUMaskWriteHoldingRegisterRequest.py index 7f594f9608f..bf9ec7bfba2 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUMaskWriteHoldingRegisterRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUMaskWriteHoldingRegisterRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUMaskWriteHoldingRegisterResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUMaskWriteHoldingRegisterResponse.py index 8bddc99dcab..9b14ca9e609 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUMaskWriteHoldingRegisterResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUMaskWriteHoldingRegisterResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadCoilsRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadCoilsRequest.py index 9f95e4d164b..72b5180d94c 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadCoilsRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadCoilsRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadCoilsResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadCoilsResponse.py index c3ddcba5a1a..2c0133cc06b 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadCoilsResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadCoilsResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDeviceIdentificationRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDeviceIdentificationRequest.py index 21418393f26..00a81f26414 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDeviceIdentificationRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDeviceIdentificationRequest.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -29,6 +28,7 @@ from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDeviceIdentificationResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDeviceIdentificationResponse.py index 77fa52e63f3..558ffd12f06 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDeviceIdentificationResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDeviceIdentificationResponse.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -38,6 +37,7 @@ from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDiscreteInputsRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDiscreteInputsRequest.py index a93d3ca31af..11f544328af 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDiscreteInputsRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDiscreteInputsRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDiscreteInputsResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDiscreteInputsResponse.py index 8f86ea5ad47..54c20fd0ed1 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDiscreteInputsResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadDiscreteInputsResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadExceptionStatusRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadExceptionStatusRequest.py index 38b316eb947..da9b7c0736c 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadExceptionStatusRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadExceptionStatusRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadExceptionStatusResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadExceptionStatusResponse.py index b5641feeab6..6c845db1828 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadExceptionStatusResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadExceptionStatusResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFifoQueueRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFifoQueueRequest.py index 41a8615efa2..62617f9f60f 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFifoQueueRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFifoQueueRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFifoQueueResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFifoQueueResponse.py index 085c7744616..2b74fd2d174 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFifoQueueResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFifoQueueResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFileRecordRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFileRecordRequest.py index a0b948f08f1..04b340d5e7d 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFileRecordRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFileRecordRequest.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -30,6 +29,7 @@ from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer from plc4py.spi.values.Common import get_size_of_array +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFileRecordResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFileRecordResponse.py index 90173fa6bed..89e58c226e6 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFileRecordResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadFileRecordResponse.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -30,6 +29,7 @@ from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer from plc4py.spi.values.Common import get_size_of_array +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadHoldingRegistersRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadHoldingRegistersRequest.py index fae809b11f6..6a784d06514 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadHoldingRegistersRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadHoldingRegistersRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadHoldingRegistersResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadHoldingRegistersResponse.py index 2bc94d6ea89..35b9dc7760a 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadHoldingRegistersResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadHoldingRegistersResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadInputRegistersRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadInputRegistersRequest.py index e37e5aef01a..14f8d978c76 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadInputRegistersRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadInputRegistersRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadInputRegistersResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadInputRegistersResponse.py index 68782da420b..1fbefc6fd36 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadInputRegistersResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadInputRegistersResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadWriteMultipleHoldingRegistersRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadWriteMultipleHoldingRegistersRequest.py index c7889578520..8a669dbb3bf 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadWriteMultipleHoldingRegistersRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadWriteMultipleHoldingRegistersRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadWriteMultipleHoldingRegistersResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadWriteMultipleHoldingRegistersResponse.py index 7c493df65b9..a23dcdafec5 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadWriteMultipleHoldingRegistersResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReadWriteMultipleHoldingRegistersResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReportServerIdRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReportServerIdRequest.py index eec8b492611..213f265abd0 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReportServerIdRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReportServerIdRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReportServerIdResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReportServerIdResponse.py index 60a26aa2ecf..43725092e7f 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReportServerIdResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUReportServerIdResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteFileRecordRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteFileRecordRequest.py index ee142fb92b9..53e8fa097c7 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteFileRecordRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteFileRecordRequest.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -30,6 +29,7 @@ from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer from plc4py.spi.values.Common import get_size_of_array +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteFileRecordResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteFileRecordResponse.py index f1905c6395f..7a1cc065651 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteFileRecordResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteFileRecordResponse.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -30,6 +29,7 @@ from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer from plc4py.spi.values.Common import get_size_of_array +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleCoilsRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleCoilsRequest.py index bd95b6f942f..3f82c855f32 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleCoilsRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleCoilsRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleCoilsResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleCoilsResponse.py index d8b8b42150a..fb4ec735d01 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleCoilsResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleCoilsResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleHoldingRegistersRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleHoldingRegistersRequest.py index fdaec56e0dd..380debfe54d 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleHoldingRegistersRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleHoldingRegistersRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import Any from typing import ClassVar from typing import List diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleHoldingRegistersResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleHoldingRegistersResponse.py index 4c014d685aa..6898b495f6d 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleHoldingRegistersResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteMultipleHoldingRegistersResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleCoilRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleCoilRequest.py index 044baa61dfc..ddcba2e4346 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleCoilRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleCoilRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleCoilResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleCoilResponse.py index 670e698d030..2f1d45a1a99 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleCoilResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleCoilResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleRegisterRequest.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleRegisterRequest.py index 1303b8c5574..99482c8896d 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleRegisterRequest.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleRegisterRequest.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleRegisterResponse.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleRegisterResponse.py index f363b238b45..9e43b12b809 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleRegisterResponse.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusPDUWriteSingleRegisterResponse.py @@ -19,13 +19,13 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusRtuADU.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusRtuADU.py index 30cd9f37fc8..48b881c666b 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusRtuADU.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusRtuADU.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -29,6 +28,7 @@ from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from plc4py.utils.GenericTypes import ByteOrder from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/modbus/readwrite/ModbusTcpADU.py b/plc4py/plc4py/protocols/modbus/readwrite/ModbusTcpADU.py index b0dac480c58..e89c73d4878 100644 --- a/plc4py/plc4py/protocols/modbus/readwrite/ModbusTcpADU.py +++ b/plc4py/plc4py/protocols/modbus/readwrite/ModbusTcpADU.py @@ -19,7 +19,6 @@ from dataclasses import dataclass -from distutils.util import strtobool from plc4py.api.exceptions.exceptions import PlcRuntimeException from plc4py.api.exceptions.exceptions import SerializationException from plc4py.api.messages.PlcMessage import PlcMessage @@ -28,6 +27,7 @@ from plc4py.protocols.modbus.readwrite.ModbusPDU import ModbusPDU from plc4py.spi.generation.ReadBuffer import ReadBuffer from plc4py.spi.generation.WriteBuffer import WriteBuffer +from plc4py.utils.ConnectionStringHandling import strtobool from plc4py.utils.GenericTypes import ByteOrder from typing import ClassVar import math diff --git a/plc4py/plc4py/protocols/simulated/readwrite/DataItem.py b/plc4py/plc4py/protocols/simulated/readwrite/DataItem.py index 02af3dea890..7ff9685f549 100644 --- a/plc4py/plc4py/protocols/simulated/readwrite/DataItem.py +++ b/plc4py/plc4py/protocols/simulated/readwrite/DataItem.py @@ -529,34 +529,34 @@ def static_serialize( elif data_type == "CHAR" and number_of_values == int(1): # CHAR # Simple Field (value) value: str = _value.get_str() - write_buffer.write_str((value), 8, "UTF-8", "value") + write_buffer.write_str((value), 8, "value", "UTF-8") elif data_type == "CHAR": # List values: PlcList = cast(PlcList, _value) for val in values.get_list(): value: str = val.get_str() - write_buffer.write_str((value), 8, "UTF-8", "value") + write_buffer.write_str((value), 8, "value", "UTF-8") elif data_type == "WCHAR" and number_of_values == int(1): # WCHAR # Simple Field (value) value: str = _value.get_str() - write_buffer.write_str((value), 16, "UTF-16", "value") + write_buffer.write_str((value), 16, "value", "UTF-16") elif data_type == "WCHAR": # List values: PlcList = cast(PlcList, _value) for val in values.get_list(): value: str = val.get_str() - write_buffer.write_str((value), 16, "UTF-16", "value") + write_buffer.write_str((value), 16, "value", "UTF-16") elif data_type == "STRING": # STRING # Simple Field (value) value: str = _value.get_str() - write_buffer.write_str((value), 255, "UTF-8", "value") + write_buffer.write_str((value), 255, "value", "UTF-8") elif data_type == "WSTRING": # STRING # Simple Field (value) value: str = _value.get_str() - write_buffer.write_str((value), 255, "UTF-16", "value") + write_buffer.write_str((value), 255, "value", "UTF-16") @staticmethod def get_length_in_bytes( diff --git a/plc4py/plc4py/protocols/umas/StaticHelper.py b/plc4py/plc4py/protocols/umas/StaticHelper.py index b6f7e153929..686acf1adf3 100644 --- a/plc4py/plc4py/protocols/umas/StaticHelper.py +++ b/plc4py/plc4py/protocols/umas/StaticHelper.py @@ -137,4 +137,9 @@ def parse_terminated_string_bytes(read_buffer: ReadBuffer, string_length) -> str def serialize_terminated_string(write_buffer, value, string_length): - pass + terminated_char_position = len(value.value) + for i in range(len(value.value)): + if value.value[i] == "": + terminated_char_position = i + break + write_buffer.write_str(value.value, (terminated_char_position + 1) * 8) diff --git a/plc4py/plc4py/protocols/umas/readwrite/DataItem.py b/plc4py/plc4py/protocols/umas/readwrite/DataItem.py index 8e75e9bf42e..0fa4e4827be 100644 --- a/plc4py/plc4py/protocols/umas/readwrite/DataItem.py +++ b/plc4py/plc4py/protocols/umas/readwrite/DataItem.py @@ -69,15 +69,6 @@ def static_parse( value: bool = read_buffer.read_bit("") return PlcBOOL(value) - if data_type == UmasDataType.BOOL: # List - # Array field (value) - # Count array - item_count: int = int(number_of_values) - value: List[PlcValue] = [] - for _ in range(item_count): - value.append(PlcBOOL(bool(read_buffer.read_bit("")))) - - return PlcList(value) if data_type == UmasDataType.EBOOL and number_of_values == int(1): # BOOL # Reserved Field (Compartmentalized so the "reserved" variable can't leak) @@ -95,15 +86,6 @@ def static_parse( value: bool = read_buffer.read_bit("") return PlcBOOL(value) - if data_type == UmasDataType.EBOOL: # List - # Array field (value) - # Count array - item_count: int = int(number_of_values) - value: List[PlcValue] = [] - for _ in range(item_count): - value.append(PlcBOOL(bool(read_buffer.read_bit("")))) - - return PlcList(value) if data_type == UmasDataType.BYTE and number_of_values == int(1): # BYTE # Simple Field (value) @@ -113,7 +95,7 @@ def static_parse( if data_type == UmasDataType.BYTE: # List # Array field (value) # Count array - item_count: int = int(number_of_values * int(8)) + item_count: int = int(number_of_values) value: List[PlcValue] = [] for _ in range(item_count): value.append(PlcBYTE(int(read_buffer.read_byte("")))) @@ -285,7 +267,9 @@ def static_parse( ) return PlcList(value) - if data_type == UmasDataType.DT and number_of_values == int(1): # DATE_AND_TIME + if data_type == UmasDataType.DATE_AND_TIME and number_of_values == int( + 1 + ): # DATE_AND_TIME # Simple Field (unused) unused: int = read_buffer.read_unsigned_short(8, logical_name="") @@ -341,12 +325,6 @@ def static_serialize( value: bool = _value.get_bool() write_buffer.write_bit((value), "value") - elif data_type == UmasDataType.BOOL: # List - values: PlcList = cast(PlcList, _value) - for val in values.get_list(): - value: bool = val.get_bool() - write_buffer.write_bit((value), "value") - elif data_type == UmasDataType.EBOOL and number_of_values == int(1): # BOOL # Reserved Field write_buffer.write_byte(int(0x0000), 7, "int0x0000") @@ -354,22 +332,16 @@ def static_serialize( value: bool = _value.get_bool() write_buffer.write_bit((value), "value") - elif data_type == UmasDataType.EBOOL: # List - values: PlcList = cast(PlcList, _value) - for val in values.get_list(): - value: bool = val.get_bool() - write_buffer.write_bit((value), "value") - elif data_type == UmasDataType.BYTE and number_of_values == int(1): # BYTE # Simple Field (value) value: int = _value.get_int() - write_buffer.write_byte((value), "value") + write_buffer.write_byte((value), 8, "value") elif data_type == UmasDataType.BYTE: # List values: PlcList = cast(PlcList, _value) for val in values.get_list(): value: List[int] = val.get_raw() - write_buffer.write_byte_array("", value) + write_buffer.write_byte(value, 8, "") elif data_type == UmasDataType.WORD: # WORD # Simple Field (value) @@ -438,7 +410,10 @@ def static_serialize( elif data_type == UmasDataType.STRING and number_of_values == int(1): # STRING # Manual Field (value) - serialize_terminated_string(write_buffer, self.value, self.number_of_values) + value: PlcValue = _value + StaticHelper.serialize_terminated_string( + write_buffer, value, number_of_values + ) elif data_type == UmasDataType.STRING: # List values: PlcList = cast(PlcList, _value) for val in values.get_list(): @@ -482,7 +457,7 @@ def static_serialize( value: int = val.get_int() write_buffer.write_unsigned_int((value), 32, "value") - elif data_type == UmasDataType.DT and number_of_values == int( + elif data_type == UmasDataType.DATE_AND_TIME and number_of_values == int( 1 ): # DATE_AND_TIME # Simple Field (unused) @@ -534,17 +509,11 @@ def get_length_in_bits( size_in_bits += 7 # Simple Field (value) size_in_bits += 1 - elif data_type == UmasDataType.BOOL: # List - values: PlcList = cast(PlcList, _value) - size_in_bits += len(values.get_list()) * 1 elif data_type == UmasDataType.EBOOL and number_of_values == int(1): # BOOL # Reserved Field size_in_bits += 7 # Simple Field (value) size_in_bits += 1 - elif data_type == UmasDataType.EBOOL: # List - values: PlcList = cast(PlcList, _value) - size_in_bits += len(values.get_list()) * 1 elif data_type == UmasDataType.BYTE and number_of_values == int(1): # BYTE # Simple Field (value) size_in_bits += 8 @@ -589,7 +558,7 @@ def get_length_in_bits( size_in_bits += len(values.get_list()) * 32 elif data_type == UmasDataType.STRING and number_of_values == int(1): # STRING # Manual Field (value) - size_in_bits += self.number_of_values * int(8) + size_in_bits += number_of_values * int(8) elif data_type == UmasDataType.STRING: # List values: PlcList = cast(PlcList, _value) size_in_bits += len(values.get_list()) * 32 @@ -614,7 +583,7 @@ def get_length_in_bits( elif data_type == UmasDataType.TOD: # List values: PlcList = cast(PlcList, _value) size_in_bits += len(values.get_list()) * 32 - elif data_type == UmasDataType.DT and number_of_values == int( + elif data_type == UmasDataType.DATE_AND_TIME and number_of_values == int( 1 ): # DATE_AND_TIME # Simple Field (unused) diff --git a/plc4py/plc4py/protocols/umas/readwrite/UmasDataType.py b/plc4py/plc4py/protocols/umas/readwrite/UmasDataType.py index 061afb668b8..81366a22521 100644 --- a/plc4py/plc4py/protocols/umas/readwrite/UmasDataType.py +++ b/plc4py/plc4py/protocols/umas/readwrite/UmasDataType.py @@ -22,29 +22,29 @@ class UmasDataType(AutoNumberEnum): - _init_ = "value, request_size, data_type_size" - BOOL = (1, int(1), int(1)) - UNKNOWN2 = (2, int(1), int(1)) - UNKNOWN3 = (3, int(1), int(1)) - INT = (4, int(2), int(2)) - UINT = (5, int(2), int(2)) - DINT = (6, int(3), int(4)) - UDINT = (7, int(3), int(4)) - REAL = (8, int(3), int(4)) - STRING = (9, int(17), int(1)) - TIME = (10, int(3), int(4)) - UNKNOWN11 = (11, int(1), int(1)) - UNKNOWN12 = (12, int(1), int(1)) - UNKNOWN13 = (13, int(1), int(1)) - DATE = (14, int(3), int(4)) - TOD = (15, int(3), int(4)) - DT = (16, int(4), int(8)) - UNKNOWN17 = (17, int(1), int(1)) - UNKNOWN18 = (18, int(1), int(1)) - UNKNOWN19 = (19, int(1), int(1)) - UNKNOWN20 = (20, int(1), int(1)) - BYTE = (21, int(1), int(1)) - WORD = (22, int(2), int(2)) - DWORD = (23, int(3), int(4)) - UNKNOWN24 = (24, int(1), int(1)) - EBOOL = (25, int(1), int(1)) + _init_ = "value, data_type_conversion, request_size, data_type_size" + BOOL = (1, str("BOOL"), int(1), int(1)) + UNKNOWN2 = (2, str("BOOL"), int(1), int(1)) + UNKNOWN3 = (3, str("BOOL"), int(1), int(1)) + INT = (4, str("INT"), int(2), int(2)) + UINT = (5, str("UINT"), int(2), int(2)) + DINT = (6, str("DINT"), int(3), int(4)) + UDINT = (7, str("UDINT"), int(3), int(4)) + REAL = (8, str("REAL"), int(3), int(4)) + STRING = (9, str("STRING"), int(17), int(1)) + TIME = (10, str("TIME"), int(3), int(4)) + UNKNOWN11 = (11, str("BYTE"), int(1), int(1)) + UNKNOWN12 = (12, str("BYTE"), int(1), int(1)) + UNKNOWN13 = (13, str("BYTE"), int(1), int(1)) + DATE = (14, str("DATE"), int(3), int(4)) + TOD = (15, str("TIME_OF_DAY"), int(3), int(4)) + DATE_AND_TIME = (16, str("DATE_AND_TIME"), int(4), int(8)) + UNKNOWN17 = (17, str("BYTE"), int(1), int(1)) + UNKNOWN18 = (18, str("BYTE"), int(1), int(1)) + UNKNOWN19 = (19, str("BYTE"), int(1), int(1)) + UNKNOWN20 = (20, str("BYTE"), int(1), int(1)) + BYTE = (21, str("BYTE"), int(1), int(1)) + WORD = (22, str("WORD"), int(2), int(2)) + DWORD = (23, str("DWORD"), int(3), int(4)) + UNKNOWN24 = (24, str("BYTE"), int(1), int(1)) + EBOOL = (25, str("BOOL"), int(1), int(1)) diff --git a/plc4py/plc4py/spi/generation/WriteBuffer.py b/plc4py/plc4py/spi/generation/WriteBuffer.py index 2517f8e79c6..2a5e0ec145b 100644 --- a/plc4py/plc4py/spi/generation/WriteBuffer.py +++ b/plc4py/plc4py/spi/generation/WriteBuffer.py @@ -348,6 +348,20 @@ def write_double( raise SerializationException("Double can only contain max 64 bits") self._handle_numeric_encoding(value, bit_length, numeric_format="d", **kwargs) + def write_str( + self, + value: str, + bit_length: int = -1, + logical_name: str = "", + encoding: str = "UTF-8", + **kwargs, + ) -> None: + bit_order = kwargs.get("bit_order", ByteOrder.BIG_ENDIAN) + src = bitarray(endian=ByteOrder.get_short_name(bit_order)) + src.frombytes(value.encode(encoding)) + self.bb[self.position : self.position + bit_length] = src[:bit_length] + self.position += bit_length + def write_complex_array( self, value: List[PlcMessage], logical_name: str = "", **kwargs ) -> None: diff --git a/plc4py/plc4py/spi/messages/PlcWriter.py b/plc4py/plc4py/spi/messages/PlcWriter.py index f3d943c5fd7..fc9c36b56db 100644 --- a/plc4py/plc4py/spi/messages/PlcWriter.py +++ b/plc4py/plc4py/spi/messages/PlcWriter.py @@ -86,7 +86,7 @@ async def _write(self, request: PlcWriteRequest) -> PlcWriteResponse: # 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 + self._device.write(request, self._transport), 15 ) # Return the response return response diff --git a/plc4py/plc4py/spi/messages/utils/ResponseItem.py b/plc4py/plc4py/spi/messages/utils/ResponseItem.py index f62912773cf..ddf225d206e 100644 --- a/plc4py/plc4py/spi/messages/utils/ResponseItem.py +++ b/plc4py/plc4py/spi/messages/utils/ResponseItem.py @@ -17,9 +17,11 @@ # under the License. # from abc import ABC -from dataclasses import dataclass -from typing import Generic, TypeVar, Union +from dataclasses import dataclass, field +from typing import Generic, TypeVar, Union, Dict, List + +from plc4py.api.messages.PlcField import PlcTag from plc4py.api.messages.PlcResponse import PlcResponseCode from plc4py.api.value.PlcValue import PlcValue @@ -30,3 +32,27 @@ class ResponseItem(Generic[T], ABC): response_code: PlcResponseCode value: T + + +@dataclass +class ArrayInfo: + size: int + lower_bound: int + upper_bound: int + + +R = TypeVar("R", bound=Union[PlcTag, None]) + + +@dataclass +class PlcBrowseItem(Generic[R], ABC): + tag: R + name: str + readable: bool + writeable: bool + subscribable: bool + publishable: bool + array: bool + array_info: List[ArrayInfo] = field(default_factory=lambda: []) + children: Dict[str, "PlcBrowseItem"] = field(default_factory=lambda: {}) + options: Dict[str, PlcValue] = field(default_factory=lambda: {}) diff --git a/plc4py/plc4py/utils/ConnectionStringHandling.py b/plc4py/plc4py/utils/ConnectionStringHandling.py index c4954de32db..4968c5b7eaf 100644 --- a/plc4py/plc4py/utils/ConnectionStringHandling.py +++ b/plc4py/plc4py/utils/ConnectionStringHandling.py @@ -30,3 +30,11 @@ def get_protocol_code(url: str) -> str: """ parsed = urlparse(url) return parsed.scheme + + +def strtobool(value: str) -> bool: + """Credit goes to https://danielms.site/zet/2023/pythons-distutil-strtobool-replacement/""" + value = value.lower() + if value in ("y", "yes", "on", "1", "true", "t"): + return True + return False diff --git a/plc4py/pom.xml b/plc4py/pom.xml index a2133680572..eefa47fa70e 100644 --- a/plc4py/pom.xml +++ b/plc4py/pom.xml @@ -247,6 +247,24 @@ + + python-dependency-check + test-compile + + exec + + + ${python.venv.bin}${python.exe.bin} + + -m + deptry + -ddg + dev + . + + + + python-test test diff --git a/plc4py/pyproject.toml b/plc4py/pyproject.toml new file mode 100644 index 00000000000..811b21ef18d --- /dev/null +++ b/plc4py/pyproject.toml @@ -0,0 +1,81 @@ +# +# 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. +# +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["."] # list of folders that contain the packages (["."] by default) +include = ["plc4py*"] # package names should match these glob patterns (["*"] by default) +#exclude = ["my_package.tests*"] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) + +[project] +name = "plc4py" +description = "Plc4Py The Python Industrial IOT Adapter" +version = "0.13" +readme = "README.md" +dependencies = [ + "setuptools", + "aenum", + "bitarray", + "typing_extensions", + "pluggy", + "xsdata", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3.8", + "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator", +] +keywords=["modbus", "plc4x"] +authors=[ + {name='"Apache PLC4X <>"', email="dev@plc4x.apache.org"} +] + +[project.urls] +HomePage="https://plc4x.apache.org" +Repository="https://github.com/apache/plc4x" + +[project.optional-dependencies] +dev = [ + "pytest-asyncio>=0.18.3", + "pip-tools", + "black", + "pip", + "deptry", + "requires", + "pre-commit>=2.6.0", + "pytest-mock>=3.8.1", + "mock>=4.0.2", + "mypy>=0.942", + "flake8>=4.0.1", + "pytest-asyncio", + "xsdata", +] + +[project.entry-points."plc4py.drivers"] +mock = "plc4py.drivers.mock.MockConnection:MockDriverLoader" +modbus = "plc4py.drivers.modbus.ModbusConnection:ModbusDriverLoader" +umas = "plc4py.drivers.umas.UmasConnection:UmasDriverLoader" + +[project.entry-points."plc4py.transports"] +tcp = "plc4py.spi.transport.TCPTransport:TCPTransportLoader" +mock = "plc4py.spi.transport.MockTransport:MockTransportLoader" diff --git a/plc4py/requirements.txt b/plc4py/requirements.txt deleted file mode 100644 index f35cd238a22..00000000000 --- a/plc4py/requirements.txt +++ /dev/null @@ -1,54 +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 -# -# 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. -# -aenum==3.1.15 -annotated-types==0.7.0 -bitarray==3.0.0 -black==24.10.0 -build==1.2.2.post1 -cfgv==3.4.0 -click==8.1.7 -coverage==7.6.3 -distlib==0.3.9 -filelock==3.16.1 -flake8==7.1.1 -funkify==0.4.5 -identify==2.6.1 -iniconfig==2.0.0 -mccabe==0.7.0 -mock==5.1.0 -mypy==1.12.0 -mypy-extensions==1.0.0 -nodeenv==1.9.1 -packaging==24.1 -pathspec==0.12.1 -pip-tools==7.4.1 -platformdirs==4.3.6 -pluggy==1.5.0 -pre-commit==4.0.1 -pycodestyle==2.12.1 -pyflakes==3.2.0 -pyproject_hooks==1.2.0 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-mock==3.14.0 -PyYAML==6.0.2 -requires==0.10.5 -typing_extensions==4.12.2 -virtualenv==20.26.6 -xtyping==0.8.2 diff --git a/plc4py/setup.py b/plc4py/setup_old.py similarity index 99% rename from plc4py/setup.py rename to plc4py/setup_old.py index 15faec5ac6a..39f2facb613 100644 --- a/plc4py/setup.py +++ b/plc4py/setup_old.py @@ -39,6 +39,7 @@ "wheel", ], install_requires=[ + "setuptools", "pytest-asyncio>=0.18.3", "pip-tools", "black", diff --git a/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py b/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py index edf295d8e98..7b385ca2914 100644 --- a/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py +++ b/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py @@ -17,382 +17,172 @@ # under the License. # import time +from typing import AsyncGenerator from unittest import TestCase import pytest +import pytest_asyncio +from plc4py.api.PlcConnection import PlcConnection from plc4py.PlcDriverManager import PlcDriverManager from plc4py.api.value.PlcValue import PlcResponseCode import logging -from plc4py.spi.values.PlcValues import PlcINT, PlcREAL, PlcList +from plc4py.spi.values.PlcValues import PlcINT, PlcREAL, PlcList, PlcBOOL, PlcCHAR logger = logging.getLogger("testing") TEST_SERVER_IP = "192.168.190.152" -@pytest.mark.asyncio -@pytest.mark.xfail -async def manual_test_plc_driver_modbus_connect(): - """ - Test the connection to a Modbus PLC using PlcDriverManager. - """ - # Initialize the PlcDriverManager +@pytest_asyncio.fixture +async def connection() -> AsyncGenerator[PlcConnection, None]: driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection(f"modbus://{TEST_SERVER_IP}") as connection: - # Check if the connection is successful - assert connection.is_connected() - - # Ensure the connection is closed after exiting the context manager - assert not connection.is_connected() + async with driver_manager.connection("modbus://192.168.190.152:502") as connection: + yield connection @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_coil(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() +async def test_plc_driver_modbus_connect(connection): + assert connection.is_connected - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "0x00001") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == True +async def write_read(connection, tag_name, tag_value): + tag_alias = "Some Random Alias" + with connection.write_request_builder() as builder: + builder.add_item(tag_alias, tag_name, tag_value) + write_request = builder.build() + with connection.read_request_builder() as builder: + builder.add_item(tag_alias, tag_name) + read_request = builder.build() + future = connection.execute(write_request) + response = await future + assert response.response_code == PlcResponseCode.OK -@pytest.mark.asyncio -@pytest.mark.xfail -async def test_plc_driver_modbus_read_coil_non_bool(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() + future = connection.execute(read_request) + response = await future + assert response.response_code == PlcResponseCode.OK - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "0x00001:REAL") - request = builder.build() - TestCase.assertRaises( - await connection.execute(request), NotImplementedError - ) + value = response.tags[tag_alias].value + response_code = response.tags[tag_alias].response_code + assert value == tag_value + assert response_code == PlcResponseCode.OK @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_coil_array(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "0x00001[2]") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == [True, False] +async def test_plc_driver_modbus_read_boolean(connection): + tag_name = "0x00001" + await write_read(connection, tag_name, PlcBOOL(True)) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_contacts(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "1x00001") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == True +async def test_plc_driver_modbus_read_int(connection): + tag_name = "4x00001" + await write_read(connection, tag_name, PlcINT(83)) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_contact_array(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "1x00001[2]") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == [True, False] +async def test_plc_driver_modbus_read_real(connection): + tag_name = "4x00001" + await write_read(connection, tag_name, PlcINT(83)) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_input_register(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "3x00001") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == 333 +async def test_plc_driver_modbus_read_bool_array(connection): + tag_name = "0x00001[2]" + await write_read(connection, tag_name, PlcList([PlcBOOL(True), PlcBOOL(False)])) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_input_register_array(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "3x00001[2]") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == [333, 0] +async def test_plc_driver_modbus_read_contacts(connection): + tag_name = "1x00001" + await write_read(connection, tag_name, PlcBOOL(True)) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_holding(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "4x00001") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == 874 +async def test_plc_driver_modbus_read_bool_array_discrete_input(connection): + tag_name = "1x00001[2]" + await write_read(connection, tag_name, PlcList([PlcBOOL(True), PlcBOOL(False)])) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_holding(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "4x00001[2]") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == [874, 0] +async def test_plc_driver_modbus_read_input_register(connection): + tag_name = "3x00001" + await write_read(connection, tag_name, PlcINT(333)) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_holding_real(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502?byte_order=BIG_ENDIAN_BYTE_SWAP" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "4x00011:REAL[2]") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == [PlcREAL(value=874), PlcREAL(value=0.0)] +async def test_plc_driver_modbus_read_input_register_array(connection): + tag_name = "3x00001[2]" + await write_read(connection, tag_name, PlcList([PlcINT(333), PlcINT(0)])) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_holding_string_even(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "4x00041:CHAR[6]") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == [b"F", b"A", b"F", b"B", b"C", b"B"] +async def test_plc_driver_modbus_read_holding_register(connection): + tag_name = "4x00001" + await write_read(connection, tag_name, PlcINT(334)) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_read_holding_string_odd(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.read_request_builder() as builder: - builder.add_item("Random Tag", "4x00041:CHAR[5]") - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"].value - assert value == [b"F", b"A", b"F", b"B", b"C"] +async def test_plc_driver_modbus_read_holdiong_register_array(connection): + tag_name = "4x00001[2]" + await write_read(connection, tag_name, PlcList([PlcINT(334), PlcINT(0)])) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_write_holding_int(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.write_request_builder() as builder: - builder.add_item("Random Tag", "4x00001", PlcINT(874)) - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"] - assert value.response_code == PlcResponseCode.OK +async def test_plc_driver_modbus_read_holding_register_real(connection): + tag_name = "4x00011:REAL[2]" + await write_read( + connection, tag_name, PlcList([PlcREAL(value=874), PlcREAL(value=0.0)]) + ) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_write_holding_int_array(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() - - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502" - ) as connection: - with connection.write_request_builder() as builder: - builder.add_item( - "Random Tag", - "4x00001[5]", - PlcList([PlcINT(874), PlcINT(0), PlcINT(3), PlcINT(4), PlcINT(5)]), - ) - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"] - assert value.response_code == PlcResponseCode.OK +async def test_plc_driver_modbus_read_holding_string_even(connection): + tag_name = "4x00041:CHAR[6]" + await write_read( + connection, + tag_name, + PlcList( + [ + PlcCHAR(value=b"F"), + PlcCHAR(value=b"A"), + PlcCHAR(value=b"F"), + PlcCHAR(value=b"B"), + PlcCHAR(value=b"C"), + PlcCHAR(value=b"B"), + ] + ), + ) @pytest.mark.asyncio @pytest.mark.xfail -async def test_plc_driver_modbus_write_holding_real(): - """ - Test reading data from a Modbus PLC. - """ - log = logging.getLogger(__name__) - - # Initialize the PlcDriverManager - driver_manager = PlcDriverManager() +async def test_plc_driver_modbus_read_holding_string_odd(connection): + tag_name = "4x00041:CHAR[5]" + await write_read( + connection, + tag_name, + PlcList( + [ + PlcCHAR(value=b"F"), + PlcCHAR(value=b"A"), + PlcCHAR(value=b"F"), + PlcCHAR(value=b"B"), + PlcCHAR(value=b"C"), + ] + ), + ) - # Establish a connection to the Modbus PLC - async with driver_manager.connection( - f"modbus://{TEST_SERVER_IP}:502?byte_order=BIG_ENDIAN_BYTE_SWAP" - ) as connection: - with connection.write_request_builder() as builder: - builder.add_item("Random Tag", "4x00011:REAL", PlcREAL(874)) - request = builder.build() - response = await connection.execute(request) - value = response.tags["Random Tag"] - assert value.response_code == PlcResponseCode.OK diff --git a/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py b/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py index a7a37fb4d03..251bd8eb87e 100644 --- a/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py +++ b/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py @@ -28,7 +28,18 @@ from plc4py.api.value.PlcValue import PlcResponseCode from plc4py.PlcDriverManager import PlcDriverManager -from plc4py.spi.values.PlcValues import PlcBOOL, PlcINT, PlcREAL +from plc4py.spi.values.PlcValues import ( + PlcBOOL, + PlcINT, + PlcREAL, + PlcDINT, + PlcSTRING, + PlcTIME, + PlcBYTE, + PlcDATE, + PlcDATE_AND_TIME, + PlcList, +) @pytest_asyncio.fixture @@ -44,276 +55,147 @@ async def test_plc_driver_umas_connect(connection): assert connection.is_connected -@pytest.mark.asyncio -@pytest.mark.xfail -async def test_plc_driver_umas_read_boolean(connection): - tag_alias = "Random Tag" - tag_name = "TESTING" +async def write_read(connection, tag_name, tag_value): + tag_alias = "Some Random Alias" + with connection.write_request_builder() as builder: + builder.add_item(tag_alias, tag_name, tag_value) + write_request = builder.build() with connection.read_request_builder() as builder: builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) + read_request = builder.build() + future = connection.execute(write_request) + response = await future + assert response.response_code == PlcResponseCode.OK + + future = connection.execute(read_request) response = await future + assert response.response_code == PlcResponseCode.OK + value = response.tags[tag_alias].value response_code = response.tags[tag_alias].response_code - assert value == True + assert value == tag_value assert response_code == PlcResponseCode.OK +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_boolean(connection): + tag_name = "TESTING" + await write_read(connection, tag_name, PlcBOOL(True)) + + @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_boolean_with_data_type(connection): - tag_alias = "Random Tag" tag_name = "TESTING:BOOL" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == True - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcBOOL(True)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_int(connection): - tag_alias = "Random Tag" tag_name = "TESTING_INT" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == 99 - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcINT(99)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_int_with_data_type(connection): - tag_alias = "Random Tag" tag_name = "TESTING_INT:INT" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == 99 - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcINT(99)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_dint(connection): - tag_alias = "Random Tag" tag_name = "TESTING_DINT" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == 763539 - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcDINT(763539)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_dint_with_data_type(connection): - tag_alias = "Random Tag" tag_name = "TESTING_DINT:DINT" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == 763539 - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcDINT(763539)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_ebool(connection): - tag_alias = "Random Tag" tag_name = "TESTING_EBOOL" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == True - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcBOOL(True)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_ebool_with_data_type(connection): - tag_alias = "Random Tag" tag_name = "TESTING_EBOOL:BOOL" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == True - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcBOOL(True)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_string(connection): - tag_alias = "Random Tag" tag_name = "TESTING_STRING" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == "Hello World" - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcSTRING("Hello pyToddy!")) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_string_with_data_type(connection): - tag_alias = "Random Tag" tag_name = "TESTING_STRING:STRING" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == "Hello World" - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcSTRING("Hello pyToddy!")) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_time(connection): - tag_alias = "Random Tag" tag_name = "TESTING_TIME" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == 200000 - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcTIME(200000)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_time_with_data_type(connection): - tag_alias = "Random Tag" tag_name = "TESTING_TIME:TIME" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == 200000 - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcTIME(200000)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_byte(connection): - tag_alias = "Random Tag" tag_name = "TESTING_BYTE" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == 253 - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcBYTE(253)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_byte_with_data_type(connection): - tag_alias = "Random Tag" tag_name = "TESTING_BYTE:BYTE" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == 253 - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcBYTE(253)) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_date(connection): - tag_alias = "Random Tag" tag_name = "TESTING_DATE" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == datetime.datetime(2024, 10, 25) - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcDATE(datetime.datetime(2024, 10, 25))) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_time_with_data_type(connection): - tag_alias = "Random Tag" tag_name = "TESTING_DATE:DATE" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == datetime.datetime(2024, 10, 25) - assert response_code == PlcResponseCode.OK + await write_read(connection, tag_name, PlcDATE(datetime.datetime(2025, 11, 22))) @pytest.mark.asyncio @pytest.mark.xfail async def test_plc_driver_umas_read_dt(connection): - tag_alias = "Random Tag" tag_name = "TESTING_DT" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == datetime.datetime(2000, 1, 10, 0, 40) - assert response_code == PlcResponseCode.OK + await write_read( + connection, tag_name, PlcDATE_AND_TIME(datetime.datetime(2000, 1, 10, 0, 40)) + ) @pytest.mark.asyncio @@ -321,15 +203,33 @@ async def test_plc_driver_umas_read_dt(connection): async def test_plc_driver_umas_read_dt_with_data_type(connection): tag_alias = "Random Tag" tag_name = "TESTING_DT:DATE_AND_TIME" - with connection.read_request_builder() as builder: - builder.add_item(tag_alias, tag_name) - request = builder.build() - future = connection.execute(request) - response = await future - value = response.tags[tag_alias].value - response_code = response.tags[tag_alias].response_code - assert value == datetime.datetime(2000, 1, 10, 0, 40) - assert response_code == PlcResponseCode.OK + await write_read( + connection, tag_name, PlcDATE_AND_TIME(datetime.datetime(2002, 10, 31, 3, 38)) + ) + + +@pytest.mark.asyncio +@pytest.mark.xfail +async def test_plc_driver_umas_read_array(connection): + tag_name = "TESTING_BYTE_ARRAY:BYTE[10]" + await write_read( + connection, + tag_name, + PlcList( + [ + PlcBYTE(1), + PlcBYTE(2), + PlcBYTE(3), + PlcBYTE(4), + PlcBYTE(5), + PlcBYTE(6), + PlcBYTE(7), + PlcBYTE(8), + PlcBYTE(9), + PlcBYTE(10), + ] + ), + ) @pytest.mark.asyncio diff --git a/protocols/umas/src/main/resources/protocols/umas/umas.mspec b/protocols/umas/src/main/resources/protocols/umas/umas.mspec index de9079130aa..cf65be4c060 100644 --- a/protocols/umas/src/main/resources/protocols/umas/umas.mspec +++ b/protocols/umas/src/main/resources/protocols/umas/umas.mspec @@ -235,32 +235,22 @@ [simple uint 32 memoryLength] ] - [dataIo DataItem(UmasDataType dataType, uint 16 numberOfValues) [typeSwitch dataType,numberOfValues ['BOOL','1' BOOL - // TODO: Possibly change the order of the bit and the reserved part. [reserved uint 7 '0x0000' ] - [simple bit value ] - ] - ['BOOL' List - // TODO: Handle adding some reserved bits at the end to fill up the last word. - [array bit value count 'numberOfValues' ] + [simple bit value ] ] ['EBOOL','1' BOOL // TODO: Possibly change the order of the bit and the reserved part. [reserved uint 7 '0x0000' ] [simple bit value ] ] - ['EBOOL' List - // TODO: Handle adding some reserved bits at the end to fill up the last word. - [array bit value count 'numberOfValues' ] - ] ['BYTE','1' BYTE [simple byte value] ] ['BYTE' List - [array byte value count 'numberOfValues * 8' ] + [array byte value count 'numberOfValues' ] ] ['WORD' WORD [simple uint 16 value] @@ -299,7 +289,7 @@ [array float 32 value count 'numberOfValues'] ] ['STRING','1' STRING - [manual vstring value 'STATIC_CALL("parseTerminatedStringBytes", readBuffer, numberOfValues)' 'STATIC_CALL("serializeTerminatedString", writeBuffer, value, numberOfValues)' '(numberOfValues * 8)'] + [manual vstring value 'STATIC_CALL("parseTerminatedStringBytes", readBuffer, numberOfValues)' 'STATIC_CALL("serializeTerminatedString", writeBuffer, _value, numberOfValues)' '(numberOfValues * 8)'] ] ['STRING' List [array float 32 value count 'numberOfValues'] @@ -321,7 +311,7 @@ ['TOD' List [array uint 32 value count 'numberOfValues'] ] - ['DT','1' DATE_AND_TIME + ['DATE_AND_TIME','1' DATE_AND_TIME [simple uint 8 unused] [simple uint 8 seconds encoding='BCD'] [simple uint 8 minutes encoding='BCD'] @@ -333,32 +323,32 @@ ] ] -[enum uint 8 UmasDataType(uint 8 dataTypeSize, uint 8 requestSize) - ['1' BOOL ['1','1']] - ['2' UNKNOWN2 ['1','1']] - ['3' UNKNOWN3 ['1','1']] - ['4' INT ['2', '2']] - ['5' UINT ['2','2']] - ['6' DINT ['4','3']] - ['7' UDINT ['4','3']] - ['8' REAL ['4','3']] - ['9' STRING ['1','17']] - ['10' TIME ['4','3']] - ['11' UNKNOWN11 ['1','1']] - ['12' UNKNOWN12 ['1','1']] - ['13' UNKNOWN13 ['1','1']] - ['14' DATE ['4','3']] - ['15' TOD ['4','3']] - ['16' DT ['8','4']] - ['17' UNKNOWN17 ['1','1']] - ['18' UNKNOWN18 ['1','1']] - ['19' UNKNOWN19 ['1','1']] - ['20' UNKNOWN20 ['1','1']] - ['21' BYTE ['1','1']] - ['22' WORD ['2','2']] - ['23' DWORD ['4','3']] - ['24' UNKNOWN24 ['1','1']] - ['25' EBOOL ['1','1']] +[enum uint 8 UmasDataType(uint 8 dataTypeSize, uint 8 requestSize, vstring data_type_conversion ) + ['1' BOOL ['1','1','"BOOL"']] + ['2' UNKNOWN2 ['1','1','"BOOL"']] + ['3' UNKNOWN3 ['1','1','"BOOL"']] + ['4' INT ['2', '2','"INT"']] + ['5' UINT ['2','2','"UINT"']] + ['6' DINT ['4','3','"DINT"']] + ['7' UDINT ['4','3','"UDINT"']] + ['8' REAL ['4','3','"REAL"']] + ['9' STRING ['1','17','"STRING"']] + ['10' TIME ['4','3','"TIME"']] + ['11' UNKNOWN11 ['1','1','"BYTE"']] + ['12' UNKNOWN12 ['1','1','"BYTE"']] + ['13' UNKNOWN13 ['1','1','"BYTE"']] + ['14' DATE ['4','3','"DATE"']] + ['15' TOD ['4','3','"TIME_OF_DAY"']] + ['16' DATE_AND_TIME ['8','4','"DATE_AND_TIME"']] + ['17' UNKNOWN17 ['1','1','"BYTE"']] + ['18' UNKNOWN18 ['1','1','"BYTE"']] + ['19' UNKNOWN19 ['1','1','"BYTE"']] + ['20' UNKNOWN20 ['1','1','"BYTE"']] + ['21' BYTE ['1','1','"BYTE"']] + ['22' WORD ['2','2','"WORD"']] + ['23' DWORD ['4','3','"DWORD"']] + ['24' UNKNOWN24 ['1','1','"BYTE"']] + ['25' EBOOL ['1','1','"BOOL"']] ] [enum uint 8 ModbusErrorCode diff --git a/src/site/asciidoc/users/protocols/umas.adoc b/src/site/asciidoc/users/protocols/umas.adoc index 534ba8f08e1..3b188c97ee2 100644 --- a/src/site/asciidoc/users/protocols/umas.adoc +++ b/src/site/asciidoc/users/protocols/umas.adoc @@ -19,6 +19,8 @@ == UMAS (Schneider Electric PLCs) +(Supported by Plc4Py Only) + === Connection String Options [cols="2,2a,2a,2a,4a"] @@ -31,7 +33,7 @@ - `tcp` 5+|Config options: |`request-timeout` |INT |5000| |Default timeout for all types of requests. -|`default-unit-identifier` |INT |1| |Unit-identifier or slave-id that identifies the target PLC (On RS485 multiple Modbus Devices can be listening). Defaults to 1. +|`unit-identifier` |INT |1| |Unit-identifier or slave-id that identifies the target PLC. Defaults to 1. +++ |=== @@ -70,7 +72,7 @@ Note the transport, port and option fields are optional. ==== General Format -In general all Modbus addresses have this format: +In general all UMAS addresses have this format: ---- {tag-name}.{child-name}.{child-name}:{data-type}[{array-size}] @@ -79,42 +81,20 @@ In general all Modbus addresses have this format: Depending on the type of tag the child-name parameters are optional. e.g. A tag with a BOOL data type could be 'TESTING_BOOL_1' whereas if it is a UDT the tag name is followed by the child 'TESTING_UDT_1.START' which in itself could be a BOOL. + If the array-size part is omitted, the size-default of `1` is assumed. + If the data-type part is omitted, it defaults to the data type of the tag read from the PLC. ==== Memory Areas -There are a number of memory areas defined in the Modbus specification. - -- Discrete Input Area -- Coil Area -- Input Register Area -- Holding Register -- Extended Register Area - -[cols="2,2a,5,1,2,1"] -|=== -|Name |Memory Area Aliases |Description |Bit-Size | Permissions | Starting Address - -|Discrete Input |`discrete-input:` or `1` or `1x` |Boolean input value, usually representing a binary input to the PLC |1 |Read Only|1 -|Coil |`coil:` or `0` or `0x` |Boolean value, usually representing a binary output from the PLC |1 |Read/Write|1 -|Input Register |`input-register:` or `3` or `3x` |Short input value, usually representing an analog input to the PLC |16 |Read Only|1 -|Holding Register |`holding-register:` or `4` or `4x` |Short value, usually representing an analog output from the PLC |16 |Read/Write|1 -|Extended Register |`extended-register:` or `6` or `6x` |Short value, |16 |Read/Write|0 +Apart from tags defined in the PLC the driver is also able to access the %S and %SW +system memory areas. -|=== - -Initially the Modbus format allowed up to 10000 address to be specified or the discrete inputs, coils, input registers and holding registers. -Later on, this was expanded to allow up 65536 address within each memory area (except the extended register area). -When using the long address format i.e. input-registers:1 the addresses between 1 and 65535 are able to be specified. -When using the shorter versions there are two formats available i.e. 30001 and 300001. -With the shorter format 3XXXX being limited to between 30001 and 39999, while the longer format 3XXXXX being limited to between 300001 and 365535. -These memory areas all start at address 1. +The specific address details of the data in these areas are outlined in the devices +manual. -For the extended register area the addresses 0-99999 are able to be specified. These registers are mapped to file records with a length of 10000. Address 600000 corresponds to the first address in file record 0. -Address 610000 is then the first address in the second file record and so on. It is noted that there is generally only 10 file records (600000 thru to 699999) however the spec allows for 65536 file records. -Using the extended-register: format you are able to reference all of these, if the shorter format is used then it is limited to 699999. -This memory area starts at address 0. +An example of the address format of these areas is %SW1 or %S20. ==== Data Types @@ -130,76 +110,9 @@ The following data types are supported - DINT (int 32) - UDINT (uint 32) - DWORD (uint 32) -- LINT (int 64) -- ULINT (uint 64) -- LWORD (uint 64) - REAL (float) -- LREAL (double) -- CHAR (char) -- WCHAR (2 byte char) - -==== Some useful tips - -Most memory areas start at address 1, except for the extended register area which starts at 0. These are both mapped to 0x0000 when it is sent in the Modbus protocol. - -The input, holding and extended registers consist of 16-bit registers while the discrete input and coil areas consist of bits. - -The following Modbus function codes are supported:- - -- 0x01 (Read Coils) -- 0x02 (Read Discrete Inputs) -- 0x03 (Read Holding Registers) -- 0x04 (Read Input Registers) -- 0x05 (Write Single Coil) -- 0x06 (Write Single Register) -- 0x0F (Write Multiple Coils) -- 0x10 (Write Multiple Registers) -- 0x14 (Read File Record)(Extended Register Read) -- 0x15 (Write File Record)(Extended Register Write) - -==== Examples - -To read 10 holding registers starting at address 20 and parse as Unsigned Integers the following examples are all valid. - -- holding-register:20:UINT[10] -- 400020:UINT[10] -- 4x00020:UINT[10] -- 40020:UINT[10] -- 4x0020:UINT[10] - -To read 1 holding register at address 5678 the following examples are valid. - -- holding-register:5678 -- 405678 -- 4x05678 -- 45678 -- 4x5678 - -To read 1 holding register of unit 10 at address 5678 the following examples are valid. - -- holding-register:5678{unit-id: 10} -- 405678{unit-id: 10} -- 4x05678{unit-id: 10} -- 45678{unit-id: 10} -- 4x5678{unit-id: 10} - -To read 10 extended registers starting at address 50 the following examples are valid. - -- extended-register:50[10] -- 600050[10] -- 6x00050[10] -- 60050[10] -- 6x0050[10] - -This corresponds to addresses 50-59 in file record 1. - -To read 10 extended registers starting at address 9995 the following examples are valid. - -- extended-register:9995[10] -- 609995[10] -- 6x09995[10] -- 69995[10] -- 6x9995[10] - -This corresponds to addresses 9995-9999 in file record 1 and addresses 0-5 in file record 2. -Note that this request is split into 2 sub requests in the Modbus protocol. +- STRING (char) +- TIME +- DATE +- TOD (Time of Day) +- DATE_AND_TIME From 422055d1e7a3c3e9cf5f5fe3faeb82d741a2b164 Mon Sep 17 00:00:00 2001 From: hutcheb Date: Tue, 29 Oct 2024 17:45:26 +0800 Subject: [PATCH 4/8] fix(plc4py/modbus): Update trailing white space in modebus tests --- .../tests/unit/plc4py/drivers/modbus/test_modbus_connection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py b/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py index 7b385ca2914..201358816ad 100644 --- a/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py +++ b/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py @@ -185,4 +185,3 @@ async def test_plc_driver_modbus_read_holding_string_odd(connection): ] ), ) - From d6edb3c3a05cedb1b71d0ba62cf3fa717e4f7b5c Mon Sep 17 00:00:00 2001 From: hutcheb Date: Tue, 29 Oct 2024 17:53:58 +0800 Subject: [PATCH 5/8] fix(plc4py): Switch integration tests to include pyproject.toml --- .../language-python/src/test/resources/plc4py/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code-generation/language-python/src/test/resources/plc4py/pom.xml b/code-generation/language-python/src/test/resources/plc4py/pom.xml index 059fea2df21..972a06a0aa0 100644 --- a/code-generation/language-python/src/test/resources/plc4py/pom.xml +++ b/code-generation/language-python/src/test/resources/plc4py/pom.xml @@ -53,7 +53,8 @@ . ${project.basedir}/../../../../../plc4py/ - setup.* + setup.cfg + pyproject.toml true From 3eef6d75a3ac56f6ed58fd03c6ef89a57c013906 Mon Sep 17 00:00:00 2001 From: hutcheb Date: Tue, 29 Oct 2024 17:57:23 +0800 Subject: [PATCH 6/8] fix(plc4py): Switch integration tests to include pyproject.toml --- .../language-python/src/test/resources/plc4py/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/code-generation/language-python/src/test/resources/plc4py/pom.xml b/code-generation/language-python/src/test/resources/plc4py/pom.xml index 972a06a0aa0..9b7b08237a0 100644 --- a/code-generation/language-python/src/test/resources/plc4py/pom.xml +++ b/code-generation/language-python/src/test/resources/plc4py/pom.xml @@ -53,7 +53,6 @@ . ${project.basedir}/../../../../../plc4py/ - setup.cfg pyproject.toml true From a7439311f69f7b78c5f03a0b435a9595da81febb Mon Sep 17 00:00:00 2001 From: hutcheb Date: Tue, 29 Oct 2024 18:10:05 +0800 Subject: [PATCH 7/8] fix(plc4py): Switch integration tests to include pyproject.toml --- .../src/test/resources/plc4py/pom.xml | 21 +++++++++--------- plc4py/pom.xml | 22 +++++++++---------- .../drivers/modbus/test_modbus_connection.py | 3 +-- .../drivers/umas/test_umas_connection.py | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/code-generation/language-python/src/test/resources/plc4py/pom.xml b/code-generation/language-python/src/test/resources/plc4py/pom.xml index 9b7b08237a0..f9b131f540b 100644 --- a/code-generation/language-python/src/test/resources/plc4py/pom.xml +++ b/code-generation/language-python/src/test/resources/plc4py/pom.xml @@ -219,35 +219,36 @@ - python-black + python-test-compile process-sources exec - ${python.venv.bin}${python.exe.bin} + ${python.venv.bin}pip3 - -m - black - . + install + .[dev] - python-test-compile - test-compile + python-black + process-sources exec - ${python.venv.bin}pip3 + ${python.venv.bin}${python.exe.bin} - install - .[dev] + -m + black + . + diff --git a/plc4py/pom.xml b/plc4py/pom.xml index eefa47fa70e..4952326df9e 100644 --- a/plc4py/pom.xml +++ b/plc4py/pom.xml @@ -216,33 +216,33 @@ - python-black + python-test-compile process-sources exec - ${python.venv.bin}${python.exe.bin} + ${skipTests} + ${python.venv.bin}pip3 - -m - black - . + install + .[dev] - python-test-compile - test-compile + python-black + process-sources exec - ${skipTests} - ${python.venv.bin}pip3 + ${python.venv.bin}${python.exe.bin} - install - .[dev] + -m + black + . diff --git a/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py b/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py index 201358816ad..b957876e7f0 100644 --- a/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py +++ b/plc4py/tests/unit/plc4py/drivers/modbus/test_modbus_connection.py @@ -31,13 +31,12 @@ from plc4py.spi.values.PlcValues import PlcINT, PlcREAL, PlcList, PlcBOOL, PlcCHAR logger = logging.getLogger("testing") -TEST_SERVER_IP = "192.168.190.152" @pytest_asyncio.fixture async def connection() -> AsyncGenerator[PlcConnection, None]: driver_manager = PlcDriverManager() - async with driver_manager.connection("modbus://192.168.190.152:502") as connection: + async with driver_manager.connection("modbus://127.0.0.1:502") as connection: yield connection diff --git a/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py b/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py index 251bd8eb87e..e03d4cdb9f6 100644 --- a/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py +++ b/plc4py/tests/unit/plc4py/drivers/umas/test_umas_connection.py @@ -45,7 +45,7 @@ @pytest_asyncio.fixture async def connection() -> AsyncGenerator[PlcConnection, None]: driver_manager = PlcDriverManager() - async with driver_manager.connection("umas://192.168.190.152:502") as connection: + async with driver_manager.connection("umas://127.0.0.1:502") as connection: yield connection From dd9eac9cbcc62d30c29a82e3b98d7feca19f5d1c Mon Sep 17 00:00:00 2001 From: hutcheb Date: Tue, 29 Oct 2024 18:50:16 +0800 Subject: [PATCH 8/8] fix(plc4py): Clean up config files --- plc4py/pyproject.toml | 10 +++++- plc4py/pytest.ini | 21 ------------ plc4py/setup.cfg | 22 ------------- plc4py/setup_old.py | 76 ------------------------------------------- 4 files changed, 9 insertions(+), 120 deletions(-) delete mode 100644 plc4py/pytest.ini delete mode 100644 plc4py/setup.cfg delete mode 100644 plc4py/setup_old.py diff --git a/plc4py/pyproject.toml b/plc4py/pyproject.toml index 811b21ef18d..b58db1f1a81 100644 --- a/plc4py/pyproject.toml +++ b/plc4py/pyproject.toml @@ -32,7 +32,6 @@ description = "Plc4Py The Python Industrial IOT Adapter" version = "0.13" readme = "README.md" dependencies = [ - "setuptools", "aenum", "bitarray", "typing_extensions", @@ -79,3 +78,12 @@ umas = "plc4py.drivers.umas.UmasConnection:UmasDriverLoader" [project.entry-points."plc4py.transports"] tcp = "plc4py.spi.transport.TCPTransport:TCPTransportLoader" mock = "plc4py.spi.transport.MockTransport:MockTransportLoader" + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-ra -q" +testpaths = [ + "tests", +] +asyncio_mode = "auto" +log_cli = "true" diff --git a/plc4py/pytest.ini b/plc4py/pytest.ini deleted file mode 100644 index 537563cdb10..00000000000 --- a/plc4py/pytest.ini +++ /dev/null @@ -1,21 +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 -# -# 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. -# - -[pytest] -log_cli = true \ No newline at end of file diff --git a/plc4py/setup.cfg b/plc4py/setup.cfg deleted file mode 100644 index 812fb33cb26..00000000000 --- a/plc4py/setup.cfg +++ /dev/null @@ -1,22 +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 -# -# 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. -# - -[tool:pytest] -testpaths = tests -asyncio_mode=auto \ No newline at end of file diff --git a/plc4py/setup_old.py b/plc4py/setup_old.py deleted file mode 100644 index 39f2facb613..00000000000 --- a/plc4py/setup_old.py +++ /dev/null @@ -1,76 +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 -# -# 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. -# - -from setuptools import setup, find_packages - -setup( - name="plc4py", - version="0.13", - description="Plc4py The Python Industrial IOT Adapter", - classifiers=[ - "Development Status :: 3 - Alpha", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.8", - "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator", - ], - keywords="modbus plc4x", - url="https://plc4x.apache.org", - author='"Apache PLC4X <>"', - author_email="dev@plc4x.apache.org", - license="Apache 2.0", - packages=find_packages(include=["plc4py", "plc4py.*"]), - setup_requires=[ - "wheel", - ], - install_requires=[ - "setuptools", - "pytest-asyncio>=0.18.3", - "pip-tools", - "black", - "pip", - "aenum", - "bitarray", - "typing_extensions", - "pluggy", - "xsdata", - ], - extras_require={ - "dev": [ - "requires", - "pre-commit>=2.6.0", - "pytest-mock>=3.8.1", - "mock>=4.0.2", - "mypy>=0.942", - "flake8>=4.0.1", - "pytest-asyncio", - "xsdata", - ] - }, - entry_points={ - "plc4py.drivers": [ - "mock = plc4py.drivers.mock.MockConnection:MockDriverLoader", - "modbus = plc4py.drivers.modbus.ModbusConnection:ModbusDriverLoader", - "umas = plc4py.drivers.umas.UmasConnection:UmasDriverLoader", - ], - "plc4py.transports": [ - "tcp = plc4py.spi.transport.TCPTransport:TCPTransportLoader", - "mock = plc4py.spi.transport.MockTransport:MockTransportLoader", - ], - }, -)