From 7a9fb1a599e3a7abb8b5582b26288a0ddf498899 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 21 Apr 2024 14:05:25 +0200 Subject: [PATCH 1/5] add first implementation for nativ addressing --- pyLSV2/client.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pyLSV2/client.py b/pyLSV2/client.py index 65fc7b8..315f083 100644 --- a/pyLSV2/client.py +++ b/pyLSV2/client.py @@ -1366,6 +1366,36 @@ def read_plc_memory( ) return plc_values + def read_plc_address(self, value_address: str) -> Union[None, int, float, str]: + """ + read from plc memory using the nativ addressing scheme of the control + Requires access level ``PLCDEBUG`` to work. + + :param value_address: address of the plc memory location in the format used by the nc like W1090, M0 or S20 + + :raises LSV2InputException: if unknowns memory type is requested or if the to many elements are requested + :raises LSV2DataException: if number of received values does not match the number of expected + """ + if result := re.fullmatch(r"(?P[MBWDS])(?P\d+)", value_address): + if result.group("type") == "M": + val_num = int(result.group("num")) + val_type = lc.MemoryType.MARKER + elif result.group("type") == "B": + val_num = int(result.group("num")) + val_type = lc.MemoryType.BYTE + elif result.group("type") == "W": + val_num = int(result.group("num")) / 2 + val_type = lc.MemoryType.WORD + elif result.group("type") == "D": + val_num = int(result.group("num")) / 4 + val_type = lc.MemoryType.DWORD + else: # "S" + val_num = int(result.group("num")) + val_type = lc.MemoryType.STRING + else: + return None + return self.read_plc_memory(int(val_num), val_type, 1)[0] + def set_keyboard_access(self, unlocked: bool) -> bool: """ Enable or disable the keyboard on the control. From 0f78d28ed5db461160bd93b47eca92e1034b665c Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 30 May 2024 12:27:33 +0200 Subject: [PATCH 2/5] move decoder and add test --- pyLSV2/client.py | 30 +++++++++--------------------- pyLSV2/misc.py | 44 +++++++++++++++++++++++++++++++++++++++++++- tests/test_misc.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 22 deletions(-) diff --git a/pyLSV2/client.py b/pyLSV2/client.py index 315f083..ee64bf2 100644 --- a/pyLSV2/client.py +++ b/pyLSV2/client.py @@ -1366,35 +1366,23 @@ def read_plc_memory( ) return plc_values - def read_plc_address(self, value_address: str) -> Union[None, int, float, str]: + def read_plc_address(self, address: str) -> Union[None, int, float, str]: """ read from plc memory using the nativ addressing scheme of the control Requires access level ``PLCDEBUG`` to work. - :param value_address: address of the plc memory location in the format used by the nc like W1090, M0 or S20 + :param address: address of the plc memory location in the format used by the nc like W1090, M0 or S20 :raises LSV2InputException: if unknowns memory type is requested or if the to many elements are requested :raises LSV2DataException: if number of received values does not match the number of expected """ - if result := re.fullmatch(r"(?P[MBWDS])(?P\d+)", value_address): - if result.group("type") == "M": - val_num = int(result.group("num")) - val_type = lc.MemoryType.MARKER - elif result.group("type") == "B": - val_num = int(result.group("num")) - val_type = lc.MemoryType.BYTE - elif result.group("type") == "W": - val_num = int(result.group("num")) / 2 - val_type = lc.MemoryType.WORD - elif result.group("type") == "D": - val_num = int(result.group("num")) / 4 - val_type = lc.MemoryType.DWORD - else: # "S" - val_num = int(result.group("num")) - val_type = lc.MemoryType.STRING - else: - return None - return self.read_plc_memory(int(val_num), val_type, 1)[0] + + m_type, m_num = lm.decode_plc_memory_address(address) + + if m_type is None or m_num is None: + raise ValueError() + + return self.read_plc_memory(m_num, m_type, 1)[0] def set_keyboard_access(self, unlocked: bool) -> bool: """ diff --git a/pyLSV2/misc.py b/pyLSV2/misc.py index 21c8eaa..bb2f283 100644 --- a/pyLSV2/misc.py +++ b/pyLSV2/misc.py @@ -2,12 +2,13 @@ # -*- coding: utf-8 -*- """misc helper functions for pyLSV2""" import struct +import re from datetime import datetime from pathlib import Path from typing import Union, List, Dict from . import dat_cls as ld -from .const import BIN_FILES, PATH_SEP, ControlType +from .const import BIN_FILES, PATH_SEP, ControlType, MemoryType from .err import LSV2DataException @@ -328,3 +329,44 @@ def decode_timestamp(data_set: bytearray) -> datetime: """ timestamp = struct.unpack("!L", data_set[0:4])[0] return datetime.fromtimestamp(timestamp) + + +def decode_plc_memory_address(address: str): + """ + Decode memory address location from the format used by the plc program to + sequential representation. + """ + val_num = None + val_type = None + if result := re.fullmatch(r"(?P[MBWDSIO])(?P[WD])?(?P\d+)", address): + m_type = result.group("m_type") + s_type = result.group("s_type") + num = int(result.group("num")) + val_num = num + + if m_type == "M" and s_type is None: + val_type = MemoryType.MARKER + elif m_type == "B" and s_type is None: + val_type = MemoryType.BYTE + elif m_type == "W" and s_type is None: + val_num = int(val_num / 2) + val_type = MemoryType.WORD + elif m_type == "D" and s_type is None: + val_num = int(val_num / 4) + val_type = MemoryType.DWORD + elif m_type == "I": + if s_type is None: + val_type = MemoryType.INPUT + elif s_type == "W": + val_num = int(val_num / 2) + val_type = MemoryType.INPUT_WORD + elif m_type == "O": + if s_type is None: + val_type = MemoryType.OUTPUT + elif s_type == "W": + val_num = int(val_num / 2) + val_type = MemoryType.OUTPUT_WORD + elif m_type == "S" and s_type is None: + val_type = MemoryType.STRING + + return val_type, val_num diff --git a/tests/test_misc.py b/tests/test_misc.py index d812fcc..a2d1f6e 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -5,6 +5,7 @@ import tempfile from pathlib import Path import pyLSV2 +import pyLSV2.misc def test_grab_screen_dump(address: str, timeout: float): @@ -18,3 +19,30 @@ def test_grab_screen_dump(address: str, timeout: float): assert local_bmp_path.stat().st_size > 1 lsv2.disconnect() + + +def test_plc_address_decode(): + """test the decode function for plc memory addresses""" + + assert pyLSV2.misc.decode_plc_memory_address("M0001") == (pyLSV2.const.MemoryType.MARKER, 1) + assert pyLSV2.misc.decode_plc_memory_address("M0") == (pyLSV2.const.MemoryType.MARKER, 0) + assert pyLSV2.misc.decode_plc_memory_address("M1") == (pyLSV2.const.MemoryType.MARKER, 1) + assert pyLSV2.misc.decode_plc_memory_address("M1234") == (pyLSV2.const.MemoryType.MARKER, 1234) + + assert pyLSV2.misc.decode_plc_memory_address("B0") == (pyLSV2.const.MemoryType.BYTE, 0) + assert pyLSV2.misc.decode_plc_memory_address("W0") == (pyLSV2.const.MemoryType.WORD, 0) + assert pyLSV2.misc.decode_plc_memory_address("D0") == (pyLSV2.const.MemoryType.DWORD, 0) + + assert pyLSV2.misc.decode_plc_memory_address("B4") == (pyLSV2.const.MemoryType.BYTE, 4) + assert pyLSV2.misc.decode_plc_memory_address("W4") == (pyLSV2.const.MemoryType.WORD, 2) + assert pyLSV2.misc.decode_plc_memory_address("D4") == (pyLSV2.const.MemoryType.DWORD, 1) + + assert pyLSV2.misc.decode_plc_memory_address("B16") == (pyLSV2.const.MemoryType.BYTE, 16) + assert pyLSV2.misc.decode_plc_memory_address("W16") == (pyLSV2.const.MemoryType.WORD, 8) + assert pyLSV2.misc.decode_plc_memory_address("D16") == (pyLSV2.const.MemoryType.DWORD, 4) + + assert pyLSV2.misc.decode_plc_memory_address("O8") == (pyLSV2.const.MemoryType.OUTPUT, 8) + assert pyLSV2.misc.decode_plc_memory_address("OW8") == (pyLSV2.const.MemoryType.OUTPUT_WORD, 4) + + assert pyLSV2.misc.decode_plc_memory_address("I8") == (pyLSV2.const.MemoryType.INPUT, 8) + assert pyLSV2.misc.decode_plc_memory_address("IW8") == (pyLSV2.const.MemoryType.INPUT_WORD, 4) From 32e5e78daa1f72085b76177065419688f839fac5 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 22 Jun 2024 15:57:19 +0200 Subject: [PATCH 3/5] add new data types for input and output double words --- pyLSV2/misc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyLSV2/misc.py b/pyLSV2/misc.py index bb2f283..27487c6 100644 --- a/pyLSV2/misc.py +++ b/pyLSV2/misc.py @@ -360,12 +360,18 @@ def decode_plc_memory_address(address: str): elif s_type == "W": val_num = int(val_num / 2) val_type = MemoryType.INPUT_WORD + elif s_type == "D": + val_num = int(val_num / 4) + val_type = MemoryType.INPUT_DWORD elif m_type == "O": if s_type is None: val_type = MemoryType.OUTPUT elif s_type == "W": val_num = int(val_num / 2) val_type = MemoryType.OUTPUT_WORD + elif s_type == "D": + val_num = int(val_num / 4) + val_type = MemoryType.OUTPUT_DWORD elif m_type == "S" and s_type is None: val_type = MemoryType.STRING From 97b095aea60526e75b2019a5a69bb84b353062fa Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 22 Jun 2024 15:57:47 +0200 Subject: [PATCH 4/5] use better exception type and message --- pyLSV2/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyLSV2/client.py b/pyLSV2/client.py index cce2b1a..15fb0bf 100644 --- a/pyLSV2/client.py +++ b/pyLSV2/client.py @@ -1391,7 +1391,7 @@ def read_plc_address(self, address: str) -> Union[None, int, float, str]: m_type, m_num = lm.decode_plc_memory_address(address) if m_type is None or m_num is None: - raise ValueError() + raise LSV2InputException("could not translate address %s to valid memory location" % address) return self.read_plc_memory(m_num, m_type, 1)[0] @@ -1647,7 +1647,7 @@ def get_file_list(self, path: str = "", descend: bool = True, pattern: str = "") return [] if self.change_directory(path) is False: - self._logger.warning("could not change to directory %s" % path) + self._logger.warning("could not change to directory %s", path) return [] if len(pattern) == 0: From 5eea418a0d679f1fbf23cb07ec615fec532d55a3 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 22 Jun 2024 16:06:17 +0200 Subject: [PATCH 5/5] add test for new memory access function --- tests/test_plc_read.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_plc_read.py b/tests/test_plc_read.py index 5eaa241..af48c90 100644 --- a/tests/test_plc_read.py +++ b/tests/test_plc_read.py @@ -134,3 +134,32 @@ def test_comapare_values(address: str, timeout: float): assert v1 == v2 lsv2.disconnect() + + +def test_plc_mem_access(address: str, timeout: float): + """test to see if reading via plc address and plc memory returns the same value""" + + lsv2 = pyLSV2.LSV2(address, port=19000, timeout=timeout, safe_mode=False) + lsv2.connect() + + for mem_address in [0, 1, 2, 4, 8, 12, 68, 69, 151, 300, 420]: + v1 = lsv2.read_plc_memory(mem_address, pyLSV2.MemoryType.DWORD, 1)[0] + v2 = lsv2.read_plc_address("D%d" % (mem_address * 4)) + assert v1 == v2 + + for mem_address in [0, 1, 2, 4, 8, 12, 68, 69, 151, 300, 420]: + v1 = lsv2.read_plc_memory(mem_address, pyLSV2.MemoryType.WORD, 1)[0] + v2 = lsv2.read_plc_address("W%d" % (mem_address * 2)) + assert v1 == v2 + + for mem_address in [0, 1, 2, 4, 8, 12, 68, 69, 151, 300, 420]: + v1 = lsv2.read_plc_memory(mem_address, pyLSV2.MemoryType.BYTE, 1)[0] + v2 = lsv2.read_plc_address("B%d" % (mem_address * 1)) + assert v1 == v2 + + for mem_address in [0, 1, 2, 4, 8, 12, 68, 69, 151, 300, 420]: + v1 = lsv2.read_plc_memory(mem_address, pyLSV2.MemoryType.MARKER, 1)[0] + v2 = lsv2.read_plc_address("M%d" % (mem_address * 1)) + assert v1 == v2 + + lsv2.disconnect()