From d0a624dd3e08620d95bc502ed77957a9c8d1add8 Mon Sep 17 00:00:00 2001 From: rimbertm Date: Wed, 13 Oct 2021 11:24:39 +0200 Subject: [PATCH 1/6] logix_driver.py Method _read_build_multi_requests Correct empty multi_requests for ReadFragmentedRequests --- pycomm3/logix_driver.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pycomm3/logix_driver.py b/pycomm3/logix_driver.py index 6cd3608..04ee860 100644 --- a/pycomm3/logix_driver.py +++ b/pycomm3/logix_driver.py @@ -993,6 +993,7 @@ def _read_build_multi_requests(self, parsed_tags): """ creates a list of multi-request packets """ + multi_requests = [] fragmented_requests = [] read_requests = [] # [ (request, response_size), ...] for request_id, tag_data in parsed_tags.items(): @@ -1035,9 +1036,11 @@ def _read_build_multi_requests(self, parsed_tags): current_group.append(req) current_response_size += resp_size - multi_requests = [ - MultiServiceRequestPacket(self._sequence, group) for group in grouped_requests - ] + #test if the first list is empty + if grouped_requests[0]: + multi_requests = [ + MultiServiceRequestPacket(self._sequence, group) for group in grouped_requests + ] return multi_requests + fragmented_requests From ce0f78e6a82e82921ee371042878d009f43dde74 Mon Sep 17 00:00:00 2001 From: ottowayi Date: Fri, 12 Nov 2021 10:21:19 -0600 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=90=9B=20fixed=20bool=20member=20to?= =?UTF-8?q?=20host=20mapping=20for=20predefined=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pycomm3/logix_driver.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pycomm3/logix_driver.py b/pycomm3/logix_driver.py index 6cd3608..e47a5ab 100644 --- a/pycomm3/logix_driver.py +++ b/pycomm3/logix_driver.py @@ -817,9 +817,17 @@ def _parse_template_data(self, data, template): if info["data_type_name"] == "BOOL": if predefine: if 0 in _host_members and _host_members[0][0] == "CTL": - # for most predefined types, we assume all 'offsets' refer to the - # bit number of the hidden CTL attribute - _bit_members[member] = (_host_members[0][0], info["bit"]) + # for most predefined types, we assume all 'offsets' refer to the hidden CTL attribute + if info["offset"] in _multibyte_hosts: + _, host_offset, packet_offset = _multibyte_hosts[info["offset"]] + # adjust the bit number by the byte offset within the host member + # but use the packet offset aka host member's offset to map the bit member to the host + _bit_members[member] = ( + _host_members[packet_offset][0], + info["bit"] + 8 * host_offset, + ) + else: + _bit_members[member] = (_host_members[0][0], info["bit"]) else: # some predefined types don't list their host members, at least ANALOG_ALARM doesn't # but if trying to access the whole struct for ANALOG_ALARM leads to a 'permission denied' From 9117566fca92d1eb0daf4c87a5d0b7af88782849 Mon Sep 17 00:00:00 2001 From: ottowayi Date: Fri, 12 Nov 2021 10:28:20 -0600 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=9A=A7=20new=20attempt=20at=20detecti?= =?UTF-8?q?ng=20predefined=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pycomm3/logix_driver.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pycomm3/logix_driver.py b/pycomm3/logix_driver.py index e47a5ab..875a411 100644 --- a/pycomm3/logix_driver.py +++ b/pycomm3/logix_driver.py @@ -656,7 +656,7 @@ def _create_tag(self, name, raw_tag): template_instance_id = raw_tag["symbol_type"] & 0b_0000_1111_1111_1111 tag_type = "struct" new_tag["template_instance_id"] = template_instance_id - new_tag["data_type"] = self._get_data_type(template_instance_id) + new_tag["data_type"] = self._get_data_type(template_instance_id, raw_tag["symbol_type"]) new_tag["data_type_name"] = new_tag["data_type"]["name"] else: tag_type = "atomic" @@ -750,7 +750,7 @@ def _read_template(self, instance_id, object_definition_size): else: return template_raw - def _parse_template_data(self, data, template): + def _parse_template_data(self, data, template, symbol_type): info_len = template["member_count"] * TEMPLATE_MEMBER_INFO_LEN info_data = data[:info_len] self.__log.debug(f"Parsing template {template!r} from {data!r}") @@ -775,7 +775,8 @@ def _parse_template_data(self, data, template): except ValueError as err: raise ResponseError("Unable to decode template or member names") from err - predefine = template_name is None + # predefine = template_name is None + predefine = symbol_type & (1 << 8) if predefine: # predefined types put name as first member (DWORD) template_name = member_names.pop(0) @@ -795,7 +796,7 @@ def _parse_template_data(self, data, template): _multibyte_hosts = {} # hosts for bits that are larger than sints for member, info in zip(member_names, member_data): if (member.startswith("ZZZZZZZZZZ") or member.startswith("__")) or ( - predefine and member == "CTL" + predefine and member in {"CTL", "Control"} ): _host_members[info["offset"]] = (member, info["type_class"]) if info["type_class"].size > USINT.size: @@ -816,7 +817,7 @@ def _parse_template_data(self, data, template): if info["data_type_name"] == "BOOL": if predefine: - if 0 in _host_members and _host_members[0][0] == "CTL": + if 0 in _host_members and _host_members[0][0] in {"CTL", "Control"}: # for most predefined types, we assume all 'offsets' refer to the hidden CTL attribute if info["offset"] in _multibyte_hosts: _, host_offset, packet_offset = _multibyte_hosts[info["offset"]] @@ -836,10 +837,7 @@ def _parse_template_data(self, data, template): _bit_members[member] = (info["offset"], info["bit"]) elif info["offset"] in _host_members: - _bit_members[member] = ( - _host_members[info["offset"]][0], - info["bit"] - ) + _bit_members[member] = (_host_members[info["offset"]][0], info["bit"]) elif info["offset"] in _multibyte_hosts: _, host_offset, packet_offset = _multibyte_hosts[info["offset"]] # adjust the bit number by the byte offset within the host member @@ -891,7 +889,7 @@ def _parse_template_data_member_info(self, info): data_type = str(type_class) if data_type is None: tag_type = "struct" - data_type = self._get_data_type(instance_id) + data_type = self._get_data_type(instance_id, typ) type_class = data_type["type_class"] member["tag_type"] = tag_type @@ -909,14 +907,14 @@ def _parse_template_data_member_info(self, info): return member - def _get_data_type(self, instance_id): + def _get_data_type(self, instance_id, symbol_type): if instance_id not in self._cache["id:udt"]: try: self.__log.debug(f"Getting data type for id {instance_id}") template = self._get_structure_makeup(instance_id) # instance id from type if not template.get("error"): _data = self._read_template(instance_id, template["object_definition_size"]) - data_type = self._parse_template_data(_data, template) + data_type = self._parse_template_data(_data, template, symbol_type) self._cache["id:udt"][instance_id] = data_type self._data_types[data_type["name"]] = data_type self.__log.debug(f'Got data type {data_type["name"]} for id {instance_id}') From ec096fbbed517c71fc2a08945457ce1506fd13fc Mon Sep 17 00:00:00 2001 From: ottowayi Date: Sat, 13 Nov 2021 09:27:06 -0600 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=90=9B=20better=20predefined=20struct?= =?UTF-8?q?=20identification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pycomm3/logix_driver.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pycomm3/logix_driver.py b/pycomm3/logix_driver.py index 064e20f..5644fea 100644 --- a/pycomm3/logix_driver.py +++ b/pycomm3/logix_driver.py @@ -412,7 +412,7 @@ def get_tag_list(self, program: str = None, cache: bool = True) -> List[dict]: "id:udt": {}, } - if program in ("*", None): + if program in {"*", None}: self._info["programs"] = {} self._info["tasks"] = {} self._info["modules"] = {} @@ -707,7 +707,7 @@ def _get_structure_makeup(self, instance_id): name=f"_get_structure_makeup(instance_id={instance_id!r})", ) if not response: - raise ResponseError(f"send_unit_data returned not valid data", response.error) + raise ResponseError("send_unit_data returned not valid data", response.error) _struct = _parse_structure_makeup_attributes(response) self._cache["id:struct"][instance_id] = _struct self._cache["handle:id"][_struct["structure_handle"]] = instance_id @@ -775,9 +775,13 @@ def _parse_template_data(self, data, template, symbol_type): except ValueError as err: raise ResponseError("Unable to decode template or member names") from err - # predefine = template_name is None - predefine = symbol_type & (1 << 8) + _type = symbol_type & 0b_0000_1111_1111_1111 + + # range of non-predefined structs is 0x100 - 0xEFF according to spec + # so if outside that range assume it is a predefined type + predefine = _type < 0x100 or _type > 0xEFF if predefine: # predefined types put name as first member (DWORD) + template_name = member_names.pop(0) if template_name == "ASCIISTRING82": # internal name for STRING builtin type @@ -1042,7 +1046,7 @@ def _read_build_multi_requests(self, parsed_tags): current_group.append(req) current_response_size += resp_size - #test if the first list is empty + # test if the first list is empty if grouped_requests[0]: multi_requests = [ MultiServiceRequestPacket(self._sequence, group) for group in grouped_requests From 37a07ded68babbac85842c11867767670bdf20b5 Mon Sep 17 00:00:00 2001 From: ottowayi Date: Tue, 16 Nov 2021 10:43:09 -0600 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=90=9B=20fixed=20parsing=20of=20struc?= =?UTF-8?q?ts=20for=20predefined=20types=20on=20Logix=20v32+?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pycomm3/logix_driver.py | 3 +- setup.py | 3 + tests/online/__init__.py | 22 +++ tests/pycomm3.L5X | 351 +++++++++++++++++++++++++++++++++++---- 4 files changed, 348 insertions(+), 31 deletions(-) diff --git a/pycomm3/logix_driver.py b/pycomm3/logix_driver.py index 5644fea..7829ed0 100644 --- a/pycomm3/logix_driver.py +++ b/pycomm3/logix_driver.py @@ -780,8 +780,7 @@ def _parse_template_data(self, data, template, symbol_type): # range of non-predefined structs is 0x100 - 0xEFF according to spec # so if outside that range assume it is a predefined type predefine = _type < 0x100 or _type > 0xEFF - if predefine: # predefined types put name as first member (DWORD) - + if predefine and template_name is None: # predefined types put name as first member (DWORD) template_name = member_names.pop(0) if template_name == "ASCIISTRING82": # internal name for STRING builtin type diff --git a/setup.py b/setup.py index a470507..f04c076 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,9 @@ def read(file_name): package_data={"pycomm3": ["py.typed"]}, python_requires=">=3.6.1", include_package_data=True, + extras_require={ + 'tests': ['pytest'] + }, classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", diff --git a/tests/online/__init__.py b/tests/online/__init__.py index d2cbc2e..2c51945 100644 --- a/tests/online/__init__.py +++ b/tests/online/__init__.py @@ -238,6 +238,12 @@ }, } +_timer1_values = {"PRE": 1, "ACC": 0, "EN": False, "TT": False, "DN": True} +_timer2_values = {"PRE": 30000, "ACC": 1234, "EN": True, "TT": True, "DN": False} +_timer_empty = {"PRE": 0, "ACC": 0, "EN": False, "TT": False, "DN": False} +_timer_ary1 = [_timer_empty, _timer1_values, _timer_empty, _timer_empty, _timer2_values] + + BASE_STRUCT_TESTS = [ # struct of just atomic values ("_udt1", "pycomm3_AtomicUDT", _udt1_values), @@ -318,4 +324,20 @@ ("_aoi1.param_dint1.31", "BOOL", False), ("_aoi1.local_dint1", "DINT", _aoi1_values["local_dint1"]), ("_aoi1.local_udt1", "pycomm3_NestedUDT", _aoi1_values["local_udt1"]), + # predefined types + ("_timer1", "TIMER", _timer1_values), + ("_timer1.PRE", "DINT", _timer1_values["PRE"]), + ("_timer1.ACC", "DINT", _timer1_values["ACC"]), + ("_timer1.EN", "BOOL", _timer1_values["EN"]), + ("_timer1.TT", "BOOL", _timer1_values["TT"]), + ("_timer1.DN", "BOOL", _timer1_values["DN"]), + ("_timer2", "TIMER", _timer2_values), + ("_timer2.PRE", "DINT", _timer2_values["PRE"]), + ("_timer2.ACC", "DINT", _timer2_values["ACC"]), + ("_timer2.EN", "BOOL", _timer2_values["EN"]), + ("_timer2.TT", "BOOL", _timer2_values["TT"]), + ("_timer2.DN", "BOOL", _timer2_values["DN"]), + ('_timer_ary1{5}', 'TIMER[5]', _timer_ary1), + ('_timer_ary1[0]', 'TIMER', _timer_ary1[0]), + ('_timer_ary1[3]{2}', 'TIMER[2]', _timer_ary1[3:]), ] diff --git a/tests/pycomm3.L5X b/tests/pycomm3.L5X index 4716d59..eb42045 100644 --- a/tests/pycomm3.L5X +++ b/tests/pycomm3.L5X @@ -1,5 +1,5 @@  - + @@ -1618,6 +1618,61 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 + +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 00 00 C8 42 @@ -1757,9 +1812,9 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 FF FF FF FF -00 00 00 00 FF FF FF FF 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 +00 00 00 00 FF FF FF FF 08 00 00 00 00 00 08 00 +08 00 00 00 00 00 08 00 00 00 00 00 00 00 00 00 +20 00 00 00 00 00 01 00 @@ -2606,7 +2661,7 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 - + @@ -2639,7 +2694,7 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 - + @@ -2647,7 +2702,7 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 - + @@ -2680,7 +2735,7 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 - + @@ -2731,7 +2786,7 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 - + @@ -2759,7 +2814,7 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 - + @@ -2770,11 +2825,11 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 -C2 E0 11 C0 30 75 00 00 A0 0F 00 00 +D5 60 3C C0 30 75 00 00 60 54 00 00 - + @@ -4003,6 +4058,61 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 + +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 00 00 00 00 30 75 00 00 D8 59 00 00 @@ -4079,13 +4189,13 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 -00 00 00 00 00 00 00 00 +08 00 00 00 00 00 08 00 - + @@ -4118,7 +4228,7 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 - + @@ -4224,9 +4334,9 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 00 00 00 00 FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 FF FF FF FF -00 00 00 00 FF FF FF FF 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -00 00 00 00 00 00 00 00 +00 00 00 00 FF FF FF FF 08 00 00 00 00 00 08 00 +08 00 00 00 00 00 08 00 00 00 00 00 00 00 00 00 +20 00 00 00 00 00 01 00 @@ -5073,7 +5183,7 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 - + @@ -5106,7 +5216,7 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 - + @@ -5114,7 +5224,7 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 - + @@ -5147,7 +5257,7 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 - + @@ -5198,7 +5308,7 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 - + @@ -5226,7 +5336,7 @@ D0 07 00 00 34 08 00 00 98 08 00 00 FC 08 00 00 - + @@ -5346,13 +5456,13 @@ F0 0A 00 00 54 0B 00 00 -00 00 00 00 00 00 00 00 +08 00 00 00 00 00 08 00 - + @@ -5385,7 +5495,7 @@ F0 0A 00 00 54 0B 00 00 - + @@ -8087,6 +8197,85 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 + +00 00 00 00 01 00 00 00 00 00 00 00 + + + + + + + + + + + +00 00 00 00 30 75 00 00 78 50 00 00 + + + + + + + + + + + +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 01 FF 01 00 0A 00 00 00 64 00 00 00 00 00 00 00 93 00 7A 44 @@ -10363,6 +10552,85 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 + +00 00 00 00 00 00 00 00 00 00 00 00 + + + + + + + + + + + +00 00 00 00 00 00 00 00 00 00 00 00 + + + + + + + + + + + +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 01 FF 01 00 0A 00 00 00 64 00 00 00 00 00 00 00 93 00 7A 44 @@ -10796,11 +11064,16 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 - + + + + + + @@ -10835,7 +11108,7 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 - + @@ -10953,6 +11226,21 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 + + + + + + + + + + + + + + + @@ -10992,6 +11280,11 @@ BD BE BF 00 00 00 00 00 00 00 00 00 00 00 00 00 + + + + + From 25bb8a697037dc14b7dad5b64576fa5081f73640 Mon Sep 17 00:00:00 2001 From: ottowayi Date: Tue, 16 Nov 2021 10:50:10 -0600 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=94=96=20v1.2.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/releases.rst | 8 ++++++++ pycomm3/_version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/releases.rst b/docs/releases.rst index 9c2c01a..983eb0f 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -2,6 +2,14 @@ Release History =============== +1.2.5 +===== + +LogixDriver +----------- + +- |:bug:| fixed issue parsing struct definitions for predefined types for v32+ #186 + 1.2.4 ===== diff --git a/pycomm3/_version.py b/pycomm3/_version.py index 93db12a..ba6891d 100644 --- a/pycomm3/_version.py +++ b/pycomm3/_version.py @@ -22,5 +22,5 @@ # SOFTWARE. # -__version_info__ = (1, 2, 4) +__version_info__ = (1, 2, 5) __version__ = ".".join(f"{x}" for x in __version_info__)