python-snap7 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_snap7-3.0.0.dist-info/METADATA +94 -0
- python_snap7-3.0.0.dist-info/RECORD +22 -0
- python_snap7-3.0.0.dist-info/WHEEL +5 -0
- python_snap7-3.0.0.dist-info/entry_points.txt +2 -0
- python_snap7-3.0.0.dist-info/licenses/LICENSE +20 -0
- python_snap7-3.0.0.dist-info/top_level.txt +1 -0
- snap7/__init__.py +34 -0
- snap7/client.py +1923 -0
- snap7/connection.py +456 -0
- snap7/datatypes.py +312 -0
- snap7/error.py +444 -0
- snap7/logo.py +222 -0
- snap7/partner.py +680 -0
- snap7/py.typed +0 -0
- snap7/s7protocol.py +1509 -0
- snap7/server/__init__.py +2681 -0
- snap7/server/__main__.py +41 -0
- snap7/type.py +391 -0
- snap7/util/__init__.py +108 -0
- snap7/util/db.py +789 -0
- snap7/util/getters.py +766 -0
- snap7/util/setters.py +733 -0
snap7/util/setters.py
ADDED
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import struct
|
|
3
|
+
from datetime import date, datetime, timedelta
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from .getters import get_bool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def set_bool(bytearray_: bytearray, byte_index: int, bool_index: int, value: bool) -> bytearray:
|
|
10
|
+
"""Set boolean value on location in bytearray.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
bytearray_: buffer to write to.
|
|
14
|
+
byte_index: byte index to write to.
|
|
15
|
+
bool_index: bit index to write to.
|
|
16
|
+
value: value to write.
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
>>> buffer = bytearray([0b00000000])
|
|
20
|
+
>>> set_bool(buffer, 0, 0, True)
|
|
21
|
+
>>> buffer
|
|
22
|
+
bytearray(b"\\x01")
|
|
23
|
+
"""
|
|
24
|
+
if value not in {0, 1, True, False}:
|
|
25
|
+
raise TypeError(f"Value value:{value} is not a boolean expression.")
|
|
26
|
+
|
|
27
|
+
current_value = get_bool(bytearray_, byte_index, bool_index)
|
|
28
|
+
index_value = 1 << bool_index
|
|
29
|
+
|
|
30
|
+
# check if bool already has correct value
|
|
31
|
+
if current_value == value:
|
|
32
|
+
return bytearray_
|
|
33
|
+
|
|
34
|
+
if value:
|
|
35
|
+
# make sure index_v is IN current byte
|
|
36
|
+
bytearray_[byte_index] += index_value
|
|
37
|
+
else:
|
|
38
|
+
# make sure index_v is NOT in current byte
|
|
39
|
+
bytearray_[byte_index] -= index_value
|
|
40
|
+
return bytearray_
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def set_byte(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
|
|
44
|
+
"""Set value in bytearray to byte
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
bytearray_: buffer to write to.
|
|
48
|
+
byte_index: byte index to write.
|
|
49
|
+
_int: value to write.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
buffer with the written value.
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
>>> buffer = bytearray([0b00000000])
|
|
56
|
+
>>> set_byte(buffer, 0, 255)
|
|
57
|
+
bytearray(b"\\xFF")
|
|
58
|
+
"""
|
|
59
|
+
_int = int(_int)
|
|
60
|
+
bytearray_[byte_index : byte_index + 1] = struct.pack("B", _int)
|
|
61
|
+
return bytearray_
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def set_word(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
|
|
65
|
+
"""Set value in bytearray to word
|
|
66
|
+
|
|
67
|
+
Notes:
|
|
68
|
+
Word datatype is 2 bytes long.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
bytearray_: buffer to be written.
|
|
72
|
+
byte_index: byte index to start write from.
|
|
73
|
+
_int: value to write.
|
|
74
|
+
|
|
75
|
+
Return:
|
|
76
|
+
buffer with the written value
|
|
77
|
+
"""
|
|
78
|
+
_int = int(_int)
|
|
79
|
+
bytearray_[byte_index : byte_index + 2] = struct.pack(">H", _int)
|
|
80
|
+
return bytearray_
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def set_int(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
|
|
84
|
+
"""Set value in bytearray to int
|
|
85
|
+
|
|
86
|
+
Notes:
|
|
87
|
+
An datatype `int` in the PLC consists of two `bytes`.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
bytearray_: buffer to write on.
|
|
91
|
+
byte_index: byte index to start writing from.
|
|
92
|
+
_int: int value to write.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Buffer with the written value.
|
|
96
|
+
|
|
97
|
+
Examples:
|
|
98
|
+
>>> data = bytearray(2)
|
|
99
|
+
>>> set_int(data, 0, 255)
|
|
100
|
+
bytearray(b'\\x00\\xff')
|
|
101
|
+
"""
|
|
102
|
+
# make sure were dealing with an int
|
|
103
|
+
_int = int(_int)
|
|
104
|
+
bytearray_[byte_index : byte_index + 2] = struct.pack(">h", _int)
|
|
105
|
+
return bytearray_
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def set_uint(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
|
|
109
|
+
"""Set value in bytearray to unsigned int
|
|
110
|
+
|
|
111
|
+
Notes:
|
|
112
|
+
An datatype `uint` in the PLC consists of two `bytes`.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
bytearray_: buffer to write on.
|
|
116
|
+
byte_index: byte index to start writing from.
|
|
117
|
+
_int: int value to write.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Buffer with the written value.
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
>>> from snap7.util import set_uint
|
|
124
|
+
>>> data = bytearray(2)
|
|
125
|
+
>>> set_uint(data, 0, 65535)
|
|
126
|
+
bytearray(b'\\xff\\xff')
|
|
127
|
+
"""
|
|
128
|
+
# make sure were dealing with an int
|
|
129
|
+
_int = int(_int)
|
|
130
|
+
bytearray_[byte_index : byte_index + 2] = struct.pack(">H", _int)
|
|
131
|
+
return bytearray_
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def set_real(bytearray_: bytearray, byte_index: int, real: Union[bool, str, float, int]) -> bytearray:
|
|
135
|
+
"""Set Real value
|
|
136
|
+
|
|
137
|
+
Notes:
|
|
138
|
+
Datatype `real` is represented in 4 bytes in the PLC.
|
|
139
|
+
The packed representation uses the `IEEE 754 binary32`.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
bytearray_: buffer to write to.
|
|
143
|
+
byte_index: byte index to start writing from.
|
|
144
|
+
real: value to be written.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Buffer with the value written.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
>>> data = bytearray(4)
|
|
151
|
+
>>> set_real(data, 0, 123.321)
|
|
152
|
+
bytearray(b'B\\xf6\\xa4Z')
|
|
153
|
+
"""
|
|
154
|
+
bytearray_[byte_index : byte_index + 4] = struct.pack(">f", float(real))
|
|
155
|
+
return bytearray_
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def set_fstring(bytearray_: bytearray, byte_index: int, value: str, max_length: int) -> bytearray:
|
|
159
|
+
"""Set space-padded fixed-length string value
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
bytearray_: buffer to write to.
|
|
163
|
+
byte_index: byte index to start writing from.
|
|
164
|
+
value: string to write.
|
|
165
|
+
max_length: maximum string length, i.e. the fixed size of the string.
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
:obj:`TypeError`: if the `value` is not a :obj:`str`.
|
|
169
|
+
:obj:`ValueError`: if the length of the `value` is larger than the `max_size`
|
|
170
|
+
or 'value' contains non-ascii characters.
|
|
171
|
+
|
|
172
|
+
Examples:
|
|
173
|
+
>>> data = bytearray(20)
|
|
174
|
+
>>> set_fstring(data, 0, "hello world", 15)
|
|
175
|
+
>>> data
|
|
176
|
+
bytearray(b'hello world \x00\x00\x00\x00\x00')
|
|
177
|
+
"""
|
|
178
|
+
if not value.isascii():
|
|
179
|
+
raise ValueError("Value contains non-ascii values.")
|
|
180
|
+
# FAIL HARD WHEN trying to write too much data into PLC
|
|
181
|
+
size = len(value)
|
|
182
|
+
if size > max_length:
|
|
183
|
+
raise ValueError(f"size {size} > max_length {max_length} {value}")
|
|
184
|
+
|
|
185
|
+
# fill array with chr integers
|
|
186
|
+
for i, c in enumerate(value):
|
|
187
|
+
bytearray_[byte_index + i] = ord(c)
|
|
188
|
+
|
|
189
|
+
# fill the rest with empty space
|
|
190
|
+
for r in range(len(value), max_length):
|
|
191
|
+
bytearray_[byte_index + r] = ord(" ")
|
|
192
|
+
|
|
193
|
+
return bytearray_
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def set_string(bytearray_: bytearray, byte_index: int, value: str, max_size: int = 254) -> bytearray:
|
|
197
|
+
"""Set string value
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
bytearray_: buffer to write to.
|
|
201
|
+
byte_index: byte index to start writing from.
|
|
202
|
+
value: string to write.
|
|
203
|
+
max_size: maximum possible string size, max. 254 as default.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
:obj:`TypeError`: if the `value` is not a :obj:`str`.
|
|
207
|
+
:obj:`ValueError`: if the length of the `value` is larger than the `max_size`
|
|
208
|
+
or 'max_size' is greater than 254 or 'value' contains ascii characters > 255.
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
>>> from snap7.util import set_string
|
|
212
|
+
>>> data = bytearray(20)
|
|
213
|
+
>>> set_string(data, 0, "hello world", 254)
|
|
214
|
+
>>> data
|
|
215
|
+
bytearray(b'\\xff\\x0bhello world\\x00\\x00\\x00\\x00\\x00\\x00\\x00')
|
|
216
|
+
"""
|
|
217
|
+
if not isinstance(value, str):
|
|
218
|
+
raise TypeError(f"Value value:{value} is not from Type string")
|
|
219
|
+
|
|
220
|
+
if max_size > 254:
|
|
221
|
+
raise ValueError(f"max_size: {max_size} > max. allowed 254 chars")
|
|
222
|
+
|
|
223
|
+
if any(ord(char) < 0 or ord(char) > 255 for char in value):
|
|
224
|
+
raise ValueError(
|
|
225
|
+
"Value contains ascii values > 255, which is not compatible with PLC Type STRING. "
|
|
226
|
+
"Check encoding of value or try set_wstring()."
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
size = len(value)
|
|
230
|
+
# FAIL HARD WHEN trying to write too much data into PLC
|
|
231
|
+
if size > max_size:
|
|
232
|
+
raise ValueError(f"size {size} > max_size {max_size} {value}")
|
|
233
|
+
|
|
234
|
+
# set max string size
|
|
235
|
+
bytearray_[byte_index] = max_size
|
|
236
|
+
|
|
237
|
+
# set len count on first position
|
|
238
|
+
bytearray_[byte_index + 1] = len(value)
|
|
239
|
+
|
|
240
|
+
# fill array with chr integers
|
|
241
|
+
for i, c in enumerate(value):
|
|
242
|
+
bytearray_[byte_index + 2 + i] = ord(c)
|
|
243
|
+
|
|
244
|
+
# fill the rest with empty space
|
|
245
|
+
for r in range(len(value), bytearray_[byte_index]):
|
|
246
|
+
bytearray_[byte_index + 2 + r] = ord(" ")
|
|
247
|
+
|
|
248
|
+
return bytearray_
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def set_dword(bytearray_: bytearray, byte_index: int, dword: int) -> bytearray:
|
|
252
|
+
"""Set a DWORD to the buffer.
|
|
253
|
+
|
|
254
|
+
Notes:
|
|
255
|
+
Datatype `dword` consists in 8 bytes in the PLC.
|
|
256
|
+
The maximum value posible is `4294967295`
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
bytearray_: buffer to write to.
|
|
260
|
+
byte_index: byte index from where to write.
|
|
261
|
+
dword: value to write.
|
|
262
|
+
|
|
263
|
+
Examples:
|
|
264
|
+
>>> data = bytearray(4)
|
|
265
|
+
>>> set_dword(data,0, 4294967295)
|
|
266
|
+
>>> data
|
|
267
|
+
bytearray(b'\\xff\\xff\\xff\\xff')
|
|
268
|
+
"""
|
|
269
|
+
dword = int(dword)
|
|
270
|
+
bytearray_[byte_index : byte_index + 4] = struct.pack(">I", dword)
|
|
271
|
+
return bytearray_
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def set_dint(bytearray_: bytearray, byte_index: int, dint: int) -> bytearray:
|
|
275
|
+
"""Set value in bytearray to dint
|
|
276
|
+
|
|
277
|
+
Notes:
|
|
278
|
+
Datatype `dint` consists in 4 bytes in the PLC.
|
|
279
|
+
Maximum possible value is 2147483647.
|
|
280
|
+
Lower posible value is -2147483648.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
bytearray_: buffer to write.
|
|
284
|
+
byte_index: byte index from where to start writing.
|
|
285
|
+
dint: double integer value
|
|
286
|
+
|
|
287
|
+
Examples:
|
|
288
|
+
>>> data = bytearray(4)
|
|
289
|
+
>>> set_dint(data, 0, 2147483647)
|
|
290
|
+
>>> data
|
|
291
|
+
bytearray(b'\\x7f\\xff\\xff\\xff')
|
|
292
|
+
"""
|
|
293
|
+
dint = int(dint)
|
|
294
|
+
bytearray_[byte_index : byte_index + 4] = struct.pack(">i", dint)
|
|
295
|
+
return bytearray_
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def set_udint(bytearray_: bytearray, byte_index: int, udint: int) -> bytearray:
|
|
299
|
+
"""Set value in bytearray to unsigned dint
|
|
300
|
+
|
|
301
|
+
Notes:
|
|
302
|
+
Datatype `dint` consists in 4 bytes in the PLC.
|
|
303
|
+
Maximum possible value is 4294967295.
|
|
304
|
+
Minimum posible value is 0.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
bytearray_: buffer to write.
|
|
308
|
+
byte_index: byte index from where to start writing.
|
|
309
|
+
udint: unsigned double integer value
|
|
310
|
+
|
|
311
|
+
Examples:
|
|
312
|
+
>>> data = bytearray(4)
|
|
313
|
+
>>> set_udint(data, 0, 4294967295)
|
|
314
|
+
>>> data
|
|
315
|
+
bytearray(b'\\xff\\xff\\xff\\xff')
|
|
316
|
+
"""
|
|
317
|
+
udint = int(udint)
|
|
318
|
+
bytearray_[byte_index : byte_index + 4] = struct.pack(">I", udint)
|
|
319
|
+
return bytearray_
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def set_time(bytearray_: bytearray, byte_index: int, time_string: str) -> bytearray:
|
|
323
|
+
"""Set value in bytearray to time
|
|
324
|
+
|
|
325
|
+
Notes:
|
|
326
|
+
Datatype `time` consists in 4 bytes in the PLC.
|
|
327
|
+
Maximum possible value is T#24D_20H_31M_23S_647MS(2147483647).
|
|
328
|
+
Lower posible value is T#-24D_20H_31M_23S_648MS(-2147483648).
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
bytearray_: buffer to write.
|
|
332
|
+
byte_index: byte index from where to start writing.
|
|
333
|
+
time_string: time value in string
|
|
334
|
+
|
|
335
|
+
Examples:
|
|
336
|
+
>>> data = bytearray(4)
|
|
337
|
+
|
|
338
|
+
>>> set_time(data, 0, '-22:3:57:28.192')
|
|
339
|
+
|
|
340
|
+
>>> data
|
|
341
|
+
bytearray(b'\x8d\xda\xaf\x00')
|
|
342
|
+
"""
|
|
343
|
+
sign = 1
|
|
344
|
+
if re.fullmatch(
|
|
345
|
+
r"(-?(2[0-3]|1?\d):(2[0-3]|1?\d|\d):([1-5]?\d):[1-5]?\d.\d{1,3})|"
|
|
346
|
+
r"(-24:(20|1?\d):(3[0-1]|[0-2]?\d):(2[0-3]|1?\d).(64[0-8]|6[0-3]\d|[0-5]\d{1,2}))|"
|
|
347
|
+
r"(24:(20|1?\d):(3[0-1]|[0-2]?\d):(2[0-3]|1?\d).(64[0-7]|6[0-3]\d|[0-5]\d{1,2}))",
|
|
348
|
+
time_string,
|
|
349
|
+
):
|
|
350
|
+
data_list = re.split("[: .]", time_string)
|
|
351
|
+
days: str = data_list[0]
|
|
352
|
+
hours: int = int(data_list[1])
|
|
353
|
+
minutes: int = int(data_list[2])
|
|
354
|
+
seconds: int = int(data_list[3])
|
|
355
|
+
milli_seconds: int = int(data_list[4].ljust(3, "0"))
|
|
356
|
+
if re.match(r"^-\d{1,2}$", days):
|
|
357
|
+
sign = -1
|
|
358
|
+
|
|
359
|
+
time_int = (
|
|
360
|
+
(int(days) * sign * 3600 * 24 + (hours % 24) * 3600 + (minutes % 60) * 60 + seconds % 60) * 1000 + milli_seconds
|
|
361
|
+
) * sign
|
|
362
|
+
bytes_array = time_int.to_bytes(4, byteorder="big", signed=True)
|
|
363
|
+
bytearray_[byte_index : byte_index + 4] = bytes_array
|
|
364
|
+
return bytearray_
|
|
365
|
+
else:
|
|
366
|
+
raise ValueError("time value out of range, please check the value interval")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def set_usint(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
|
|
370
|
+
"""Set unsigned small int
|
|
371
|
+
|
|
372
|
+
Notes:
|
|
373
|
+
Datatype `usint` (Unsigned small int) consists on 1 byte in the PLC.
|
|
374
|
+
Maximum posible value is 255.
|
|
375
|
+
Lower posible value is 0.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
bytearray_: buffer to write.
|
|
379
|
+
byte_index: byte index from where to start writing.
|
|
380
|
+
_int: value to write.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
Buffer with the written value.
|
|
384
|
+
|
|
385
|
+
Examples:
|
|
386
|
+
>>> data = bytearray(1)
|
|
387
|
+
>>> set_usint(data, 0, 255)
|
|
388
|
+
bytearray(b'\\xff')
|
|
389
|
+
"""
|
|
390
|
+
_int = int(_int)
|
|
391
|
+
bytearray_[byte_index] = struct.pack(">B", _int)[0]
|
|
392
|
+
return bytearray_
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def set_sint(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
|
|
396
|
+
"""Set small int to the buffer.
|
|
397
|
+
|
|
398
|
+
Notes:
|
|
399
|
+
Datatype `sint` (Small int) consists in 1 byte in the PLC.
|
|
400
|
+
Maximum value posible is 127.
|
|
401
|
+
Lowest value posible is -128.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
bytearray_: buffer to write to.
|
|
405
|
+
byte_index: byte index from where to start writing.
|
|
406
|
+
_int: value to write.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Buffer with the written value.
|
|
410
|
+
|
|
411
|
+
Examples:
|
|
412
|
+
>>> data = bytearray(1)
|
|
413
|
+
>>> set_sint(data, 0, 127)
|
|
414
|
+
bytearray(b'\\x7f')
|
|
415
|
+
"""
|
|
416
|
+
_int = int(_int)
|
|
417
|
+
bytearray_[byte_index] = struct.pack(">b", _int)[0]
|
|
418
|
+
return bytearray_
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def set_lreal(bytearray_: bytearray, byte_index: int, lreal: float) -> bytearray:
|
|
422
|
+
"""Set the long real
|
|
423
|
+
|
|
424
|
+
Notes:
|
|
425
|
+
Datatype `lreal` (long real) consists in 8 bytes in the PLC.
|
|
426
|
+
Negative Range: -1.7976931348623158e+308 to -2.2250738585072014e-308
|
|
427
|
+
Positive Range: +2.2250738585072014e-308 to +1.7976931348623158e+308
|
|
428
|
+
Zero: ±0
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
bytearray_: buffer to read from.
|
|
432
|
+
byte_index: byte index from where to start reading.
|
|
433
|
+
lreal: float value to set
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
Value to write.
|
|
437
|
+
|
|
438
|
+
Examples:
|
|
439
|
+
write lreal value (here as example 12345.12345) to DB1.10 of a PLC
|
|
440
|
+
>>> data = set_lreal(data, 12345.12345)
|
|
441
|
+
>>> from snap7 import Client
|
|
442
|
+
>>> Client().db_write(db_number=1, start=10, data=data)
|
|
443
|
+
|
|
444
|
+
"""
|
|
445
|
+
lreal = float(lreal)
|
|
446
|
+
struct.pack_into(">d", bytearray_, byte_index, lreal)
|
|
447
|
+
return bytearray_
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def set_lword(bytearray_: bytearray, byte_index: int, lword: int) -> bytearray:
|
|
451
|
+
"""Set the long word
|
|
452
|
+
|
|
453
|
+
Notes:
|
|
454
|
+
Datatype `lword` (long word) consists in 8 bytes in the PLC.
|
|
455
|
+
Maximum value is 18446744073709551615 (0xFFFFFFFFFFFFFFFF).
|
|
456
|
+
Minimum value is 0.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
bytearray_: buffer to write to.
|
|
460
|
+
byte_index: byte index from where to start writing.
|
|
461
|
+
lword: unsigned 64-bit value to write.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
Buffer with the written value.
|
|
465
|
+
|
|
466
|
+
Examples:
|
|
467
|
+
>>> data = bytearray(8)
|
|
468
|
+
>>> set_lword(data, 0, 0xABCD)
|
|
469
|
+
>>> data
|
|
470
|
+
bytearray(b'\\x00\\x00\\x00\\x00\\x00\\x00\\xab\\xcd')
|
|
471
|
+
"""
|
|
472
|
+
lword = int(lword)
|
|
473
|
+
bytearray_[byte_index : byte_index + 8] = struct.pack(">Q", lword)
|
|
474
|
+
return bytearray_
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def set_char(bytearray_: bytearray, byte_index: int, chr_: str) -> bytearray:
|
|
478
|
+
"""Set char value in a bytearray.
|
|
479
|
+
|
|
480
|
+
Notes:
|
|
481
|
+
Datatype `char` in the PLC is represented in 1 byte. It has to be in ASCII-format
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
bytearray_: buffer to write to.
|
|
485
|
+
byte_index: byte index from where to start writing.
|
|
486
|
+
chr_: `char` to write.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
Buffer with the written value.
|
|
490
|
+
|
|
491
|
+
Examples:
|
|
492
|
+
write `char` (here as example 'C') to DB1.10 of a PLC
|
|
493
|
+
>>> data = bytearray(1)
|
|
494
|
+
>>> set_char(data, 0, 'C')
|
|
495
|
+
>>> data
|
|
496
|
+
bytearray('0x43')
|
|
497
|
+
"""
|
|
498
|
+
if not isinstance(chr_, str):
|
|
499
|
+
raise TypeError(f"Value value:{chr_} is not from Type string")
|
|
500
|
+
|
|
501
|
+
if len(chr_) > 1:
|
|
502
|
+
raise ValueError(f"size chr_ : {chr_} > 1")
|
|
503
|
+
elif len(chr_) < 1:
|
|
504
|
+
raise ValueError(f"size chr_ : {chr_} < 1")
|
|
505
|
+
|
|
506
|
+
if 0 <= ord(chr_) <= 255:
|
|
507
|
+
bytearray_[byte_index] = ord(chr_)
|
|
508
|
+
return bytearray_
|
|
509
|
+
else:
|
|
510
|
+
raise ValueError(f"chr_ : {chr_} contains ascii value > 255, which is not compatible with PLC Type CHAR.")
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def set_date(bytearray_: bytearray, byte_index: int, date_: date) -> bytearray:
|
|
514
|
+
"""Set value in bytearray to date
|
|
515
|
+
Notes:
|
|
516
|
+
Datatype `date` consists in the number of days elapsed from 1990-01-01.
|
|
517
|
+
It is stored as an int (2 bytes) in the PLC.
|
|
518
|
+
Args:
|
|
519
|
+
bytearray_: buffer to write.
|
|
520
|
+
byte_index: byte index from where to start writing.
|
|
521
|
+
date_: date object
|
|
522
|
+
Examples:
|
|
523
|
+
>>> data = bytearray(2)
|
|
524
|
+
>>> set_date(data, 0, date(2024, 3, 27))
|
|
525
|
+
>>> data
|
|
526
|
+
bytearray(b'\x30\xd8')
|
|
527
|
+
"""
|
|
528
|
+
if date_ < date(1990, 1, 1):
|
|
529
|
+
raise ValueError("date is lower than specification allows.")
|
|
530
|
+
elif date_ > date(2168, 12, 31):
|
|
531
|
+
raise ValueError("date is higher than specification allows.")
|
|
532
|
+
_days = (date_ - date(1990, 1, 1)).days
|
|
533
|
+
bytearray_[byte_index : byte_index + 2] = struct.pack(">h", _days)
|
|
534
|
+
return bytearray_
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def set_wchar(bytearray_: bytearray, byte_index: int, chr_: str) -> bytearray:
|
|
538
|
+
"""Set wchar value in a bytearray.
|
|
539
|
+
|
|
540
|
+
Notes:
|
|
541
|
+
Datatype `wchar` in the PLC is represented in 2 bytes as UTF-16-BE.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
bytearray_: buffer to write to.
|
|
545
|
+
byte_index: byte index from where to start writing.
|
|
546
|
+
chr_: single character to write.
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
Buffer with the written value.
|
|
550
|
+
|
|
551
|
+
Examples:
|
|
552
|
+
>>> data = bytearray(2)
|
|
553
|
+
>>> set_wchar(data, 0, 'C')
|
|
554
|
+
>>> data
|
|
555
|
+
bytearray(b'\\x00C')
|
|
556
|
+
"""
|
|
557
|
+
if not isinstance(chr_, str):
|
|
558
|
+
raise TypeError(f"Value value:{chr_} is not from Type string")
|
|
559
|
+
if len(chr_) != 1:
|
|
560
|
+
raise ValueError(f"Expected single character, got length {len(chr_)}")
|
|
561
|
+
encoded = chr_.encode("utf-16-be")
|
|
562
|
+
bytearray_[byte_index : byte_index + 2] = encoded
|
|
563
|
+
return bytearray_
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def set_wstring(bytearray_: bytearray, byte_index: int, value: str, max_size: int = 16382) -> None:
|
|
567
|
+
"""Set wstring value
|
|
568
|
+
|
|
569
|
+
Notes:
|
|
570
|
+
Byte 0-1: max size (number of characters, 2-byte big-endian).
|
|
571
|
+
Byte 2-3: current length (number of characters, 2-byte big-endian).
|
|
572
|
+
Byte 4+: UTF-16-BE encoded characters (2 bytes each).
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
bytearray_: buffer to write to.
|
|
576
|
+
byte_index: byte index to start writing from.
|
|
577
|
+
value: string to write.
|
|
578
|
+
max_size: maximum number of characters allowed (default 16382).
|
|
579
|
+
|
|
580
|
+
Raises:
|
|
581
|
+
TypeError: if the value is not a string.
|
|
582
|
+
ValueError: if the string is too long or max_size exceeds 16382.
|
|
583
|
+
|
|
584
|
+
Examples:
|
|
585
|
+
>>> data = bytearray(26)
|
|
586
|
+
>>> set_wstring(data, 0, "hello", 10)
|
|
587
|
+
"""
|
|
588
|
+
if not isinstance(value, str):
|
|
589
|
+
raise TypeError(f"Value value:{value} is not from Type string")
|
|
590
|
+
|
|
591
|
+
if max_size > 16382:
|
|
592
|
+
raise ValueError(f"max_size: {max_size} > max. allowed 16382 chars")
|
|
593
|
+
|
|
594
|
+
size = len(value)
|
|
595
|
+
if size > max_size:
|
|
596
|
+
raise ValueError(f"size {size} > max_size {max_size}")
|
|
597
|
+
|
|
598
|
+
# set max string size (2 bytes, big-endian)
|
|
599
|
+
bytearray_[byte_index : byte_index + 2] = struct.pack(">H", max_size)
|
|
600
|
+
|
|
601
|
+
# set current length (2 bytes, big-endian)
|
|
602
|
+
bytearray_[byte_index + 2 : byte_index + 4] = struct.pack(">H", size)
|
|
603
|
+
|
|
604
|
+
# encode and write UTF-16-BE characters
|
|
605
|
+
encoded = value.encode("utf-16-be")
|
|
606
|
+
bytearray_[byte_index + 4 : byte_index + 4 + len(encoded)] = encoded
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def set_tod(bytearray_: bytearray, byte_index: int, tod: timedelta) -> bytearray:
|
|
610
|
+
"""Set TIME_OF_DAY value in bytearray.
|
|
611
|
+
|
|
612
|
+
Notes:
|
|
613
|
+
Datatype `TIME_OF_DAY` is stored as milliseconds since midnight in 4 bytes.
|
|
614
|
+
Range: 0 to 86399999 ms (00:00:00.000 to 23:59:59.999).
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
bytearray_: buffer to write to.
|
|
618
|
+
byte_index: byte index from where to start writing.
|
|
619
|
+
tod: timedelta representing the time of day.
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
Buffer with the written value.
|
|
623
|
+
|
|
624
|
+
Examples:
|
|
625
|
+
>>> from datetime import timedelta
|
|
626
|
+
>>> data = bytearray(4)
|
|
627
|
+
>>> set_tod(data, 0, timedelta(hours=12, minutes=30, seconds=15, milliseconds=500))
|
|
628
|
+
"""
|
|
629
|
+
if tod.days >= 1 or tod < timedelta(0):
|
|
630
|
+
raise ValueError("TIME_OF_DAY must be between 00:00:00.000 and 23:59:59.999")
|
|
631
|
+
ms = int(tod.total_seconds() * 1000)
|
|
632
|
+
bytearray_[byte_index : byte_index + 4] = ms.to_bytes(4, byteorder="big")
|
|
633
|
+
return bytearray_
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def set_dtl(bytearray_: bytearray, byte_index: int, dt_: datetime) -> bytearray:
|
|
637
|
+
"""Set DTL (Date and Time Long) value in bytearray.
|
|
638
|
+
|
|
639
|
+
Notes:
|
|
640
|
+
Datatype `DTL` consists of 12 bytes in the PLC:
|
|
641
|
+
- Bytes 0-1: Year (uint16, big-endian)
|
|
642
|
+
- Byte 2: Month (1-12)
|
|
643
|
+
- Byte 3: Day (1-31)
|
|
644
|
+
- Byte 4: Weekday (1=Sunday, 7=Saturday)
|
|
645
|
+
- Byte 5: Hour (0-23)
|
|
646
|
+
- Byte 6: Minute (0-59)
|
|
647
|
+
- Byte 7: Second (0-59)
|
|
648
|
+
- Bytes 8-11: Nanoseconds (uint32, big-endian)
|
|
649
|
+
|
|
650
|
+
Args:
|
|
651
|
+
bytearray_: buffer to write to.
|
|
652
|
+
byte_index: byte index from where to start writing.
|
|
653
|
+
dt_: datetime object to write.
|
|
654
|
+
|
|
655
|
+
Returns:
|
|
656
|
+
Buffer with the written value.
|
|
657
|
+
|
|
658
|
+
Examples:
|
|
659
|
+
>>> from datetime import datetime
|
|
660
|
+
>>> data = bytearray(12)
|
|
661
|
+
>>> set_dtl(data, 0, datetime(2024, 3, 27, 14, 30, 0))
|
|
662
|
+
"""
|
|
663
|
+
if dt_ > datetime(2554, 12, 31, 23, 59, 59):
|
|
664
|
+
raise ValueError("date_val is higher than specification allows.")
|
|
665
|
+
|
|
666
|
+
# Year as 2-byte big-endian
|
|
667
|
+
bytearray_[byte_index : byte_index + 2] = struct.pack(">H", dt_.year)
|
|
668
|
+
bytearray_[byte_index + 2] = dt_.month
|
|
669
|
+
bytearray_[byte_index + 3] = dt_.day
|
|
670
|
+
# Weekday: isoweekday() returns 1=Monday..7=Sunday, S7 uses 1=Sunday..7=Saturday
|
|
671
|
+
bytearray_[byte_index + 4] = (dt_.isoweekday() % 7) + 1
|
|
672
|
+
bytearray_[byte_index + 5] = dt_.hour
|
|
673
|
+
bytearray_[byte_index + 6] = dt_.minute
|
|
674
|
+
bytearray_[byte_index + 7] = dt_.second
|
|
675
|
+
# Nanoseconds from microseconds
|
|
676
|
+
nanoseconds = dt_.microsecond * 1000
|
|
677
|
+
bytearray_[byte_index + 8 : byte_index + 12] = struct.pack(">I", nanoseconds)
|
|
678
|
+
return bytearray_
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def set_dt(bytearray_: bytearray, byte_index: int, dt_: datetime) -> bytearray:
|
|
682
|
+
"""Set DATE_AND_TIME value in bytearray.
|
|
683
|
+
|
|
684
|
+
Notes:
|
|
685
|
+
Datatype `DATE_AND_TIME` consists of 8 bytes in BCD encoding:
|
|
686
|
+
- Byte 0: Year (BCD, 0-99, 90-99 = 1990-1999, 0-89 = 2000-2089)
|
|
687
|
+
- Byte 1: Month (BCD)
|
|
688
|
+
- Byte 2: Day (BCD)
|
|
689
|
+
- Byte 3: Hour (BCD)
|
|
690
|
+
- Byte 4: Minute (BCD)
|
|
691
|
+
- Byte 5: Second (BCD)
|
|
692
|
+
- Byte 6-7: Milliseconds (BCD) + weekday
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
bytearray_: buffer to write to.
|
|
696
|
+
byte_index: byte index from where to start writing.
|
|
697
|
+
dt_: datetime object to write.
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
Buffer with the written value.
|
|
701
|
+
|
|
702
|
+
Examples:
|
|
703
|
+
>>> from datetime import datetime
|
|
704
|
+
>>> data = bytearray(8)
|
|
705
|
+
>>> set_dt(data, 0, datetime(2020, 7, 12, 17, 32, 2, 854000))
|
|
706
|
+
"""
|
|
707
|
+
|
|
708
|
+
def byte_to_bcd(val: int) -> int:
|
|
709
|
+
return ((val // 10) << 4) | (val % 10)
|
|
710
|
+
|
|
711
|
+
year = dt_.year
|
|
712
|
+
if year < 1990 or year > 2089:
|
|
713
|
+
raise ValueError("DATE_AND_TIME year must be between 1990 and 2089")
|
|
714
|
+
|
|
715
|
+
year_bcd = year - 2000 if year >= 2000 else year - 1900
|
|
716
|
+
bytearray_[byte_index] = byte_to_bcd(year_bcd)
|
|
717
|
+
bytearray_[byte_index + 1] = byte_to_bcd(dt_.month)
|
|
718
|
+
bytearray_[byte_index + 2] = byte_to_bcd(dt_.day)
|
|
719
|
+
bytearray_[byte_index + 3] = byte_to_bcd(dt_.hour)
|
|
720
|
+
bytearray_[byte_index + 4] = byte_to_bcd(dt_.minute)
|
|
721
|
+
bytearray_[byte_index + 5] = byte_to_bcd(dt_.second)
|
|
722
|
+
|
|
723
|
+
# Milliseconds: 3 BCD digits in byte 6 and upper nibble of byte 7
|
|
724
|
+
ms = dt_.microsecond // 1000
|
|
725
|
+
ms_hundreds = ms // 100
|
|
726
|
+
ms_tens = (ms % 100) // 10
|
|
727
|
+
ms_ones = ms % 10
|
|
728
|
+
bytearray_[byte_index + 6] = byte_to_bcd(ms_hundreds * 10 + ms_tens)
|
|
729
|
+
# Lower nibble of byte 7: weekday (1=Sunday..7=Saturday)
|
|
730
|
+
weekday = (dt_.isoweekday() % 7) + 1
|
|
731
|
+
bytearray_[byte_index + 7] = (ms_ones << 4) | weekday
|
|
732
|
+
|
|
733
|
+
return bytearray_
|