From dcf973b084edb82deded1e8c86a858abc54dcdf5 Mon Sep 17 00:00:00 2001 From: jyin Date: Tue, 7 Sep 2021 15:45:01 +0200 Subject: [PATCH 1/3] add get_time and set_time in util --- snap7/util.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++- test/test_util.py | 23 +++++++++++-- 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/snap7/util.py b/snap7/util.py index b104bafb..15f2c63a 100644 --- a/snap7/util.py +++ b/snap7/util.py @@ -586,6 +586,84 @@ def get_dt(bytearray_: bytearray, byte_index: int) -> str: return date_and_time +def get_time(bytearray_: bytearray, byte_index: int) -> str: + """Get time value from bytearray. + + Notes: + Datatype `time` consists in 4 bytes in the PLC. + Maximum possible value is T#24D_20H_31M_23S_647MS(2147483647). + Lower posible value is T#-24D_20H_31M_23S_648MS(-2147483648). + + Args: + bytearray_: buffer to read. + byte_index: byte index from where to start reading. + + Returns: + Value read. + + Examples: + >>> import struct + >>> data = bytearray(4) + >>> data[:] = struct.pack(">i", 2147483647) + >>> snap7.util.get_time(data, 0) + '24:20:31:23:647' + """ + data_bytearray = bytearray_[byte_index:byte_index + 4] + bits = 32 + sign = 1 + byte_str = data_bytearray.hex() + val = int(byte_str, 16) + if (val & (1 << (bits - 1))) != 0: + sign = -1 # if sign bit is set e.g., 8bit: 128-255 + val = val - (1 << bits) # compute negative value + val = val * sign + + milli_seconds = val % 1000 + seconds = val // 1000 + minutes = seconds // 60 + hours = minutes // 60 + days = hours // 24 + time = str(days * sign) + ":" + str(hours % 24) + ":" + str(minutes % 60) + \ + ":" + str(seconds % 60) + "." + str(milli_seconds) + return time + + +def set_time(bytearray_: bytearray, byte_index: int, time_string: str) -> bytearray: + """Set value in bytearray to time + + Notes: + Datatype `time` consists in 4 bytes in the PLC. + Maximum possible value is T#24D_20H_31M_23S_647MS(2147483647). + Lower posible value is T#-24D_20H_31M_23S_648MS(-2147483648). + + Args: + bytearray_: buffer to write. + byte_index: byte index from where to start writing. + time_string: time value in string + + Examples: + >>> data = bytearray(4) + >>> snap7.util.set_dint(data, 0, '-22:3:57:28.192') + >>> data + bytearray(b'\x8d\xda\xaf\x00') + """ + import re + sign = 1 + bits = 32 + data_list = re.split('[: .]', time_string) + days, hours, minutes, seconds, milli_seconds = [int(x) for x in data_list] + if days < 0: + sign = -1 + time_int = ((days * sign * 3600 * 24 + (hours % 24) * 3600 + (minutes % 60) * 60 + seconds % 60) * 1000 + + milli_seconds) * sign + if sign < 0: + time_int = (1 << bits) + time_int + formatstring = '{:0%ib}' % bits + byte_hex = hex(int(formatstring.format(time_int), 2)).split('x')[1] + bytes_array = bytes.fromhex(byte_hex) + bytearray_[byte_index:byte_index + 4] = bytes_array + return bytearray_ + def set_usint(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray: """set unsigned small int @@ -1012,7 +1090,7 @@ def get_value(self, byte_index: Union[str, int], type_: str) -> Union[ValueError # add these three not implemented data typ to avoid # 'Unable to get repr for class' error elif type_ == 'TIME': - return 'read TIME not implemented' + return get_time(bytearray_, byte_index) elif type_ == 'DATE': return 'read DATE not implemented' @@ -1081,6 +1159,9 @@ def set_value(self, byte_index: Union[str, int], type: str, value: Union[bool, s if type == 'SINT' and isinstance(value, int): return set_sint(bytearray_, byte_index, value) + if type == 'TIME' and isinstance(value, str): + return set_time(bytearray_, byte_index, value) + raise ValueError def write(self, client: Client) -> None: diff --git a/test/test_util.py b/test/test_util.py index 7b27fcb0..6498a2b4 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -22,10 +22,11 @@ 21 testint2 INT 23 testDint DINT 27 testWord WORD -29 tests5time S5TIME +29 testS5time S5TIME 31 testdateandtime DATE_AND_TIME 43 testusint0 USINT 44 testsint0 SINT +46 testTime TIME """ test_spec_indented = """ @@ -70,11 +71,13 @@ # https://support.industry.siemens.com/cs/document/36479/date_and_time-format-bei-s7-?dti=0&lc=de-DE 254, 254, 254, 254, 254, 127, # test small int 128, # test set byte + 143, 255, 255, 255 # test time ]) _new_bytearray = bytearray(100) _new_bytearray[41:41 + 1] = struct.pack("B", 128) # byte_index=41, value=128, bytes=1 _new_bytearray[42:42 + 1] = struct.pack("B", 255) # byte_index=41, value=255, bytes=1 +_new_bytearray[43:43 + 4] = struct.pack("I", 286331153) # byte_index=43, value=286331153(T#3D_7H_32M_11S_153MS), bytes=4 class TestS7util(unittest.TestCase): @@ -100,7 +103,7 @@ def test_get_s5time(self): row = util.DB_Row(test_array, test_spec, layout_offset=4) - self.assertEqual(row['tests5time'], '0:00:00.100000') + self.assertEqual(row['testS5time'], '0:00:00.100000') def test_get_dt(self): """ @@ -112,6 +115,22 @@ def test_get_dt(self): self.assertEqual(row['testdateandtime'], '2020-07-12T17:32:02.854000') + def test_get_time(self): + """ + TIME extraction from bytearray + """ + test_array = bytearray(_bytearray) + + row = util.DB_Row(test_array, test_spec, layout_offset=4) + + self.assertEqual(row['testTime'], '-21:17:57:28.193') + + def test_set_time(self): + test_array = bytearray(_new_bytearray) + util.set_time(test_array, 43, '3:7:32:11.153') + byte_ = util.get_time(test_array, 43) + self.assertEqual(byte_, '3:7:32:11.153') + def test_get_string(self): """ Text extraction from string from bytearray From 71fc175f83dbf8e340f987ea6ea55d3221601fb6 Mon Sep 17 00:00:00 2001 From: jyin Date: Wed, 8 Sep 2021 09:44:03 +0200 Subject: [PATCH 2/3] modified code style --- snap7/util.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/snap7/util.py b/snap7/util.py index 15f2c63a..4666498a 100644 --- a/snap7/util.py +++ b/snap7/util.py @@ -614,7 +614,7 @@ def get_time(bytearray_: bytearray, byte_index: int) -> str: byte_str = data_bytearray.hex() val = int(byte_str, 16) if (val & (1 << (bits - 1))) != 0: - sign = -1 # if sign bit is set e.g., 8bit: 128-255 + sign = -1 # if sign bit is set e.g., 8bit: 128-255 val = val - (1 << bits) # compute negative value val = val * sign @@ -623,9 +623,8 @@ def get_time(bytearray_: bytearray, byte_index: int) -> str: minutes = seconds // 60 hours = minutes // 60 days = hours // 24 - time = str(days * sign) + ":" + str(hours % 24) + ":" + str(minutes % 60) + \ - ":" + str(seconds % 60) + "." + str(milli_seconds) - return time + time_str = str(days * sign) + ":" + str(hours % 24) + ":" + str(minutes % 60) + ":" + str(seconds % 60) + "." + str(milli_seconds) + return time_str def set_time(bytearray_: bytearray, byte_index: int, time_string: str) -> bytearray: @@ -654,8 +653,7 @@ def set_time(bytearray_: bytearray, byte_index: int, time_string: str) -> bytear days, hours, minutes, seconds, milli_seconds = [int(x) for x in data_list] if days < 0: sign = -1 - time_int = ((days * sign * 3600 * 24 + (hours % 24) * 3600 + (minutes % 60) * 60 + seconds % 60) * 1000 + - milli_seconds) * sign + time_int = ((days * sign * 3600 * 24 + (hours % 24) * 3600 + (minutes % 60) * 60 + seconds % 60) * 1000 + milli_seconds) * sign if sign < 0: time_int = (1 << bits) + time_int formatstring = '{:0%ib}' % bits @@ -664,8 +662,9 @@ def set_time(bytearray_: bytearray, byte_index: int, time_string: str) -> bytear bytearray_[byte_index:byte_index + 4] = bytes_array return bytearray_ + def set_usint(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray: - """set unsigned small int + """Set unsigned small int Notes: Datatype `usint` (Unsigned small int) consists on 1 byte in the PLC. @@ -827,7 +826,8 @@ class DB: def __init__(self, db_number: int, bytearray_: bytearray, specification: str, row_size: int, size: int, id_field: Optional[str] = None, - db_offset: Optional[int] = 0, layout_offset: Optional[int] = 0, row_offset: Optional[int] = 0, area: Optional[Areas] = Areas.DB): + db_offset: Optional[int] = 0, layout_offset: Optional[int] = 0, row_offset: Optional[int] = 0, + area: Optional[Areas] = Areas.DB): """ Creates a new instance of the `Row` class. Args: @@ -922,14 +922,14 @@ class DB_Row: _specification: OrderedDict = OrderedDict() # row specification def __init__( - self, - bytearray_: bytearray, - _specification: str, - row_size: Optional[int] = 0, - db_offset: int = 0, - layout_offset: int = 0, - row_offset: Optional[int] = 0, - area: Optional[Areas] = Areas.DB + self, + bytearray_: bytearray, + _specification: str, + row_size: Optional[int] = 0, + db_offset: int = 0, + layout_offset: int = 0, + row_offset: Optional[int] = 0, + area: Optional[Areas] = Areas.DB ): """Creates a new instance of the `DB_Row` class. @@ -1100,7 +1100,8 @@ def get_value(self, byte_index: Union[str, int], type_: str) -> Union[ValueError raise ValueError - def set_value(self, byte_index: Union[str, int], type: str, value: Union[bool, str, int, float]) -> Union[bytearray, None]: + def set_value(self, byte_index: Union[str, int], type: str, value: Union[bool, str, int, float]) -> Union[ + bytearray, None]: """Sets the value for a specific type in the specified byte index. Args: From 8928a918008bb059c1fd763cc8a4ca7cd0ffbace Mon Sep 17 00:00:00 2001 From: jyin Date: Wed, 8 Sep 2021 09:47:15 +0200 Subject: [PATCH 3/3] modified code style --- snap7/util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/snap7/util.py b/snap7/util.py index 4666498a..ad99067f 100644 --- a/snap7/util.py +++ b/snap7/util.py @@ -1100,8 +1100,7 @@ def get_value(self, byte_index: Union[str, int], type_: str) -> Union[ValueError raise ValueError - def set_value(self, byte_index: Union[str, int], type: str, value: Union[bool, str, int, float]) -> Union[ - bytearray, None]: + def set_value(self, byte_index: Union[str, int], type: str, value: Union[bool, str, int, float]) -> Union[bytearray, None]: """Sets the value for a specific type in the specified byte index. Args: