From d9fcb7f2c544861bb0cdda71fc75bde1ba367bc1 Mon Sep 17 00:00:00 2001 From: drunsinn Date: Mon, 24 Jun 2024 19:36:06 +0200 Subject: [PATCH] Nativ plc addressing (#68) * add first implementation for nativ addressing * move decoder and add test * add new data types for input and output double words * use better exception type and message * add test for new memory access function --- pyLSV2/client.py | 20 ++++++++++++++++- pyLSV2/misc.py | 50 +++++++++++++++++++++++++++++++++++++++++- tests/test_misc.py | 28 +++++++++++++++++++++++ tests/test_plc_read.py | 29 ++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) diff --git a/pyLSV2/client.py b/pyLSV2/client.py index d807725..15fb0bf 100644 --- a/pyLSV2/client.py +++ b/pyLSV2/client.py @@ -1377,6 +1377,24 @@ def read_plc_memory( ) return plc_values + 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 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 + """ + + m_type, m_num = lm.decode_plc_memory_address(address) + + if m_type is None or m_num is None: + raise LSV2InputException("could not translate address %s to valid memory location" % address) + + return self.read_plc_memory(m_num, m_type, 1)[0] + def set_keyboard_access(self, unlocked: bool) -> bool: """ Enable or disable the keyboard on the control. @@ -1629,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: diff --git a/pyLSV2/misc.py b/pyLSV2/misc.py index 21c8eaa..27487c6 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,50 @@ 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 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 + + 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) 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()