Skip to content

Commit f637f0e

Browse files
authored
add get_time and set_time in util (#308)
* add get_time and set_time in util * modified code style * modified code style Co-authored-by: jyin <[email protected]>
1 parent 585d05d commit f637f0e

File tree

2 files changed

+113
-13
lines changed

2 files changed

+113
-13
lines changed

snap7/util.py

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,85 @@ def get_dt(bytearray_: bytearray, byte_index: int) -> str:
586586
return date_and_time
587587

588588

589+
def get_time(bytearray_: bytearray, byte_index: int) -> str:
590+
"""Get time value from bytearray.
591+
592+
Notes:
593+
Datatype `time` consists in 4 bytes in the PLC.
594+
Maximum possible value is T#24D_20H_31M_23S_647MS(2147483647).
595+
Lower posible value is T#-24D_20H_31M_23S_648MS(-2147483648).
596+
597+
Args:
598+
bytearray_: buffer to read.
599+
byte_index: byte index from where to start reading.
600+
601+
Returns:
602+
Value read.
603+
604+
Examples:
605+
>>> import struct
606+
>>> data = bytearray(4)
607+
>>> data[:] = struct.pack(">i", 2147483647)
608+
>>> snap7.util.get_time(data, 0)
609+
'24:20:31:23:647'
610+
"""
611+
data_bytearray = bytearray_[byte_index:byte_index + 4]
612+
bits = 32
613+
sign = 1
614+
byte_str = data_bytearray.hex()
615+
val = int(byte_str, 16)
616+
if (val & (1 << (bits - 1))) != 0:
617+
sign = -1 # if sign bit is set e.g., 8bit: 128-255
618+
val = val - (1 << bits) # compute negative value
619+
val = val * sign
620+
621+
milli_seconds = val % 1000
622+
seconds = val // 1000
623+
minutes = seconds // 60
624+
hours = minutes // 60
625+
days = hours // 24
626+
time_str = str(days * sign) + ":" + str(hours % 24) + ":" + str(minutes % 60) + ":" + str(seconds % 60) + "." + str(milli_seconds)
627+
return time_str
628+
629+
630+
def set_time(bytearray_: bytearray, byte_index: int, time_string: str) -> bytearray:
631+
"""Set value in bytearray to time
632+
633+
Notes:
634+
Datatype `time` consists in 4 bytes in the PLC.
635+
Maximum possible value is T#24D_20H_31M_23S_647MS(2147483647).
636+
Lower posible value is T#-24D_20H_31M_23S_648MS(-2147483648).
637+
638+
Args:
639+
bytearray_: buffer to write.
640+
byte_index: byte index from where to start writing.
641+
time_string: time value in string
642+
643+
Examples:
644+
>>> data = bytearray(4)
645+
>>> snap7.util.set_dint(data, 0, '-22:3:57:28.192')
646+
>>> data
647+
bytearray(b'\x8d\xda\xaf\x00')
648+
"""
649+
import re
650+
sign = 1
651+
bits = 32
652+
data_list = re.split('[: .]', time_string)
653+
days, hours, minutes, seconds, milli_seconds = [int(x) for x in data_list]
654+
if days < 0:
655+
sign = -1
656+
time_int = ((days * sign * 3600 * 24 + (hours % 24) * 3600 + (minutes % 60) * 60 + seconds % 60) * 1000 + milli_seconds) * sign
657+
if sign < 0:
658+
time_int = (1 << bits) + time_int
659+
formatstring = '{:0%ib}' % bits
660+
byte_hex = hex(int(formatstring.format(time_int), 2)).split('x')[1]
661+
bytes_array = bytes.fromhex(byte_hex)
662+
bytearray_[byte_index:byte_index + 4] = bytes_array
663+
return bytearray_
664+
665+
589666
def set_usint(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
590-
"""set unsigned small int
667+
"""Set unsigned small int
591668
592669
Notes:
593670
Datatype `usint` (Unsigned small int) consists on 1 byte in the PLC.
@@ -749,7 +826,8 @@ class DB:
749826

750827
def __init__(self, db_number: int, bytearray_: bytearray,
751828
specification: str, row_size: int, size: int, id_field: Optional[str] = None,
752-
db_offset: Optional[int] = 0, layout_offset: Optional[int] = 0, row_offset: Optional[int] = 0, area: Optional[Areas] = Areas.DB):
829+
db_offset: Optional[int] = 0, layout_offset: Optional[int] = 0, row_offset: Optional[int] = 0,
830+
area: Optional[Areas] = Areas.DB):
753831
""" Creates a new instance of the `Row` class.
754832
755833
Args:
@@ -844,14 +922,14 @@ class DB_Row:
844922
_specification: OrderedDict = OrderedDict() # row specification
845923

846924
def __init__(
847-
self,
848-
bytearray_: bytearray,
849-
_specification: str,
850-
row_size: Optional[int] = 0,
851-
db_offset: int = 0,
852-
layout_offset: int = 0,
853-
row_offset: Optional[int] = 0,
854-
area: Optional[Areas] = Areas.DB
925+
self,
926+
bytearray_: bytearray,
927+
_specification: str,
928+
row_size: Optional[int] = 0,
929+
db_offset: int = 0,
930+
layout_offset: int = 0,
931+
row_offset: Optional[int] = 0,
932+
area: Optional[Areas] = Areas.DB
855933
):
856934
"""Creates a new instance of the `DB_Row` class.
857935
@@ -1012,7 +1090,7 @@ def get_value(self, byte_index: Union[str, int], type_: str) -> Union[ValueError
10121090
# add these three not implemented data typ to avoid
10131091
# 'Unable to get repr for class<snap7.util.DB_ROW>' error
10141092
elif type_ == 'TIME':
1015-
return 'read TIME not implemented'
1093+
return get_time(bytearray_, byte_index)
10161094

10171095
elif type_ == 'DATE':
10181096
return 'read DATE not implemented'
@@ -1081,6 +1159,9 @@ def set_value(self, byte_index: Union[str, int], type: str, value: Union[bool, s
10811159
if type == 'SINT' and isinstance(value, int):
10821160
return set_sint(bytearray_, byte_index, value)
10831161

1162+
if type == 'TIME' and isinstance(value, str):
1163+
return set_time(bytearray_, byte_index, value)
1164+
10841165
raise ValueError
10851166

10861167
def write(self, client: Client) -> None:

test/test_util.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
21 testint2 INT
2323
23 testDint DINT
2424
27 testWord WORD
25-
29 tests5time S5TIME
25+
29 testS5time S5TIME
2626
31 testdateandtime DATE_AND_TIME
2727
43 testusint0 USINT
2828
44 testsint0 SINT
29+
46 testTime TIME
2930
"""
3031

3132
test_spec_indented = """
@@ -70,11 +71,13 @@
7071
# https://support.industry.siemens.com/cs/document/36479/date_and_time-format-bei-s7-?dti=0&lc=de-DE
7172
254, 254, 254, 254, 254, 127, # test small int
7273
128, # test set byte
74+
143, 255, 255, 255 # test time
7375
])
7476

7577
_new_bytearray = bytearray(100)
7678
_new_bytearray[41:41 + 1] = struct.pack("B", 128) # byte_index=41, value=128, bytes=1
7779
_new_bytearray[42:42 + 1] = struct.pack("B", 255) # byte_index=41, value=255, bytes=1
80+
_new_bytearray[43:43 + 4] = struct.pack("I", 286331153) # byte_index=43, value=286331153(T#3D_7H_32M_11S_153MS), bytes=4
7881

7982

8083
class TestS7util(unittest.TestCase):
@@ -100,7 +103,7 @@ def test_get_s5time(self):
100103

101104
row = util.DB_Row(test_array, test_spec, layout_offset=4)
102105

103-
self.assertEqual(row['tests5time'], '0:00:00.100000')
106+
self.assertEqual(row['testS5time'], '0:00:00.100000')
104107

105108
def test_get_dt(self):
106109
"""
@@ -112,6 +115,22 @@ def test_get_dt(self):
112115

113116
self.assertEqual(row['testdateandtime'], '2020-07-12T17:32:02.854000')
114117

118+
def test_get_time(self):
119+
"""
120+
TIME extraction from bytearray
121+
"""
122+
test_array = bytearray(_bytearray)
123+
124+
row = util.DB_Row(test_array, test_spec, layout_offset=4)
125+
126+
self.assertEqual(row['testTime'], '-21:17:57:28.193')
127+
128+
def test_set_time(self):
129+
test_array = bytearray(_new_bytearray)
130+
util.set_time(test_array, 43, '3:7:32:11.153')
131+
byte_ = util.get_time(test_array, 43)
132+
self.assertEqual(byte_, '3:7:32:11.153')
133+
115134
def test_get_string(self):
116135
"""
117136
Text extraction from string from bytearray

0 commit comments

Comments
 (0)