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.
snap7/util/getters.py ADDED
@@ -0,0 +1,766 @@
1
+ import struct
2
+ from datetime import timedelta, datetime, date
3
+ from typing import NoReturn
4
+ from logging import getLogger
5
+
6
+ logger = getLogger(__name__)
7
+
8
+
9
+ def get_bool(bytearray_: bytearray, byte_index: int, bool_index: int) -> bool:
10
+ """Get the boolean value from location in bytearray
11
+
12
+ Args:
13
+ bytearray_: buffer data.
14
+ byte_index: byte index to read from.
15
+ bool_index: bit index to read from.
16
+
17
+ Returns:
18
+ True if the bit is 1, else 0.
19
+
20
+ Examples:
21
+ >>> buffer = bytearray([0b00000001]) # Only one byte length
22
+ >>> get_bool(buffer, 0, 0) # The bit 0 starts at the right.
23
+ True
24
+ """
25
+ index_value = 1 << bool_index
26
+ byte_value = bytearray_[byte_index]
27
+ current_value = byte_value & index_value
28
+ return current_value == index_value
29
+
30
+
31
+ def get_byte(bytearray_: bytearray, byte_index: int) -> bytes:
32
+ """Get byte value from bytearray.
33
+
34
+ Notes:
35
+ WORD 8bit 1bytes Decimal number unsigned B#(0) to B#(255) => 0 to 255
36
+
37
+ Args:
38
+ bytearray_: buffer to be read from.
39
+ byte_index: byte index to be read.
40
+
41
+ Returns:
42
+ value get from the byte index.
43
+ """
44
+ data = bytearray_[byte_index : byte_index + 1]
45
+ data[0] = data[0] & 0xFF
46
+ packed = struct.pack("B", *data)
47
+ value: bytes = struct.unpack("B", packed)[0]
48
+ return value
49
+
50
+
51
+ def get_word(bytearray_: bytearray, byte_index: int) -> bytearray:
52
+ """Get word value from bytearray.
53
+
54
+ Notes:
55
+ WORD 16bit 2bytes Decimal number unsigned B#(0,0) to B#(255,255) => 0 to 65535
56
+
57
+ Args:
58
+ bytearray_: buffer to get the word from.
59
+ byte_index: byte index from where start reading from.
60
+
61
+ Returns:
62
+ Word value.
63
+
64
+ Examples:
65
+ >>> get_word(bytearray([0, 100]), 0)
66
+ 100
67
+ """
68
+ data = bytearray_[byte_index : byte_index + 2]
69
+ data[1] = data[1] & 0xFF
70
+ data[0] = data[0] & 0xFF
71
+ packed = struct.pack("2B", *data)
72
+ value: bytearray = struct.unpack(">H", packed)[0]
73
+ return value
74
+
75
+
76
+ def get_int(bytearray_: bytearray, byte_index: int) -> int:
77
+ """Get int value from bytearray.
78
+
79
+ Notes:
80
+ Datatype `int` in the PLC is represented in two bytes
81
+
82
+ Args:
83
+ bytearray_: buffer to read from.
84
+ byte_index: byte index to start reading from.
85
+
86
+ Returns:
87
+ Value read.
88
+
89
+ Examples:
90
+ >>> get_int(bytearray([0, 255]), 0)
91
+ 255
92
+ """
93
+ data = bytearray_[byte_index : byte_index + 2]
94
+ data[1] = data[1] & 0xFF
95
+ data[0] = data[0] & 0xFF
96
+ packed = struct.pack("2B", *data)
97
+ value: int = struct.unpack(">h", packed)[0]
98
+ return value
99
+
100
+
101
+ def get_uint(bytearray_: bytearray, byte_index: int) -> int:
102
+ """Get unsigned int value from bytearray.
103
+
104
+ Notes:
105
+ Datatype `uint` in the PLC is represented in two bytes
106
+ Maximum posible value is 65535.
107
+ Lower posible value is 0.
108
+
109
+ Args:
110
+ bytearray_: buffer to read from.
111
+ byte_index: byte index to start reading from.
112
+
113
+ Returns:
114
+ Value read.
115
+
116
+ Examples:
117
+ >>> data = bytearray([255, 255])
118
+ >>> get_uint(data, 0)
119
+ 65535
120
+ """
121
+ return int(get_word(bytearray_, byte_index))
122
+
123
+
124
+ def get_real(bytearray_: bytearray, byte_index: int) -> float:
125
+ """Get real value.
126
+
127
+ Notes:
128
+ Datatype `real` is represented in 4 bytes in the PLC.
129
+ The packed representation uses the `IEEE 754 binary32`.
130
+
131
+ Args:
132
+ bytearray_: buffer to read from.
133
+ byte_index: byte index to reading from.
134
+
135
+ Returns:
136
+ Real value.
137
+
138
+ Examples:
139
+ >>> data = bytearray(b'B\\xf6\\xa4Z')
140
+ >>> get_real(data, 0)
141
+ 123.32099914550781
142
+ """
143
+ x = bytearray_[byte_index : byte_index + 4]
144
+ real: float = struct.unpack(">f", struct.pack("4B", *x))[0]
145
+ return real
146
+
147
+
148
+ def get_fstring(bytearray_: bytearray, byte_index: int, max_length: int, remove_padding: bool = True) -> str:
149
+ """Parse space-padded fixed-length string from bytearray
150
+
151
+ Notes:
152
+ This function supports fixed-length ASCII strings, right-padded with spaces.
153
+
154
+ Args:
155
+ bytearray_: buffer from where to get the string.
156
+ byte_index: byte index from where to start reading.
157
+ max_length: the maximum length of the string.
158
+ remove_padding: whether to remove the right-padding.
159
+
160
+ Returns:
161
+ String value.
162
+
163
+ Examples:
164
+ >>> data = [ord(letter) for letter in "hello world "]
165
+ >>> get_fstring(data, 0, 15)
166
+ 'hello world'
167
+ >>> get_fstring(data, 0, 15, remove_padding=False)
168
+ 'hello world '
169
+ """
170
+ data = map(chr, bytearray_[byte_index : byte_index + max_length])
171
+ string = "".join(data)
172
+
173
+ if remove_padding:
174
+ return string.rstrip(" ")
175
+ else:
176
+ return string
177
+
178
+
179
+ def get_string(bytearray_: bytearray, byte_index: int) -> str:
180
+ """Parse string from bytearray
181
+
182
+ Notes:
183
+ The first byte of the buffer will contain the max size posible for a string.
184
+ The second byte contains the length of the string that contains.
185
+
186
+ Args:
187
+ bytearray_: buffer from where to get the string.
188
+ byte_index: byte index from where to start reading.
189
+
190
+ Returns:
191
+ String value.
192
+
193
+ Examples:
194
+ >>> data = bytearray([254, len("hello world")] + [ord(l) for letter in "hello world"])
195
+ >>> get_string(data, 0)
196
+ 'hello world'
197
+ """
198
+
199
+ str_length = int(bytearray_[byte_index + 1])
200
+ max_string_size = int(bytearray_[byte_index])
201
+
202
+ if str_length > max_string_size or max_string_size > 254:
203
+ logger.error("The string is too big for the size encountered in specification")
204
+ logger.error("WRONG SIZED STRING ENCOUNTERED")
205
+ raise TypeError(
206
+ "String contains {str_length} chars, but max. {max_string_size} chars are expected or is "
207
+ "larger than 254. Bytearray doesn't seem to be a valid string."
208
+ )
209
+ data = map(chr, bytearray_[byte_index + 2 : byte_index + 2 + str_length])
210
+ return "".join(data)
211
+
212
+
213
+ def get_dword(bytearray_: bytearray, byte_index: int) -> int:
214
+ """Gets the dword from the buffer.
215
+
216
+ Notes:
217
+ Datatype `dword` consists in 8 bytes in the PLC.
218
+ The maximum value posible is `4294967295`
219
+
220
+ Args:
221
+ bytearray_: buffer to read.
222
+ byte_index: byte index from where to start reading.
223
+
224
+ Returns:
225
+ Value read.
226
+
227
+ Examples:
228
+ >>> data = bytearray(8)
229
+ >>> data[:] = b"\\x12\\x34\\xAB\\xCD"
230
+ >>> get_dword(data, 0)
231
+ 4294967295
232
+ """
233
+ data = bytearray_[byte_index : byte_index + 4]
234
+ dword: int = struct.unpack(">I", struct.pack("4B", *data))[0]
235
+ return dword
236
+
237
+
238
+ def get_dint(bytearray_: bytearray, byte_index: int) -> int:
239
+ """Get dint value from bytearray.
240
+
241
+ Notes:
242
+ Datatype `dint` consists in 4 bytes in the PLC.
243
+ Maximum possible value is 2147483647.
244
+ Lower posible value is -2147483648.
245
+
246
+ Args:
247
+ bytearray_: buffer to read.
248
+ byte_index: byte index from where to start reading.
249
+
250
+ Returns:
251
+ Value read.
252
+
253
+ Examples:
254
+ >>> import struct
255
+ >>> data = bytearray(4)
256
+ >>> data[:] = struct.pack(">i", 2147483647)
257
+ >>> get_dint(data, 0)
258
+ 2147483647
259
+ """
260
+ data = bytearray_[byte_index : byte_index + 4]
261
+ dint: int = struct.unpack(">i", struct.pack("4B", *data))[0]
262
+ return dint
263
+
264
+
265
+ def get_udint(bytearray_: bytearray, byte_index: int) -> int:
266
+ """Get unsigned dint value from bytearray.
267
+
268
+ Notes:
269
+ Datatype `udint` consists in 4 bytes in the PLC.
270
+ Maximum possible value is 4294967295.
271
+ Minimum posible value is 0.
272
+
273
+ Args:
274
+ bytearray_: buffer to read.
275
+ byte_index: byte index from where to start reading.
276
+
277
+ Returns:
278
+ Value read.
279
+
280
+ Examples:
281
+ >>> import struct
282
+ >>> data = bytearray(4)
283
+ >>> data[:] = struct.pack(">I", 4294967295)
284
+ >>> get_udint(data, 0)
285
+ 4294967295
286
+ """
287
+ data = bytearray_[byte_index : byte_index + 4]
288
+ dint: int = struct.unpack(">I", struct.pack("4B", *data))[0]
289
+ return dint
290
+
291
+
292
+ def get_s5time(bytearray_: bytearray, byte_index: int) -> str:
293
+ micro_to_milli = 1000
294
+ data_bytearray = bytearray_[byte_index : byte_index + 2]
295
+ s5time_data_int_like = list(data_bytearray.hex())
296
+ if s5time_data_int_like[0] == "0":
297
+ # 10ms
298
+ time_base = 10
299
+ elif s5time_data_int_like[0] == "1":
300
+ # 100ms
301
+ time_base = 100
302
+ elif s5time_data_int_like[0] == "2":
303
+ # 1s
304
+ time_base = 1000
305
+ elif s5time_data_int_like[0] == "3":
306
+ # 10s
307
+ time_base = 10000
308
+ else:
309
+ raise ValueError("This value should not be greater than 3")
310
+
311
+ s5time_bcd = int(s5time_data_int_like[1]) * 100 + int(s5time_data_int_like[2]) * 10 + int(s5time_data_int_like[3])
312
+ s5time_microseconds = time_base * s5time_bcd
313
+ s5time = timedelta(microseconds=s5time_microseconds * micro_to_milli)
314
+ # here we must return a string like variable, otherwise nothing will return
315
+ return "".join(str(s5time))
316
+
317
+
318
+ def get_dt(bytearray_: bytearray, byte_index: int) -> str:
319
+ """Get DATE_AND_TIME Value from bytearray as ISO 8601 formatted Date String
320
+ Notes:
321
+ Datatype `DATE_AND_TIME` consists in 8 bytes in the PLC.
322
+ Args:
323
+ bytearray_: buffer to read.
324
+ byte_index: byte index from where to start writing.
325
+ Examples:
326
+ >>> data = bytearray(8)
327
+ >>> data[:] = [32, 7, 18, 23, 50, 2, 133, 65] #'2020-07-12T17:32:02.854000'
328
+ >>> get_dt(data,0)
329
+ '2020-07-12T17:32:02.854000'
330
+ """
331
+ return get_date_time_object(bytearray_, byte_index).isoformat(timespec="microseconds")
332
+
333
+
334
+ def get_date_time_object(bytearray_: bytearray, byte_index: int) -> datetime:
335
+ """Get DATE_AND_TIME Value from bytearray as python datetime object
336
+ Notes:
337
+ Datatype `DATE_AND_TIME` consists in 8 bytes in the PLC.
338
+ Args:
339
+ bytearray_: buffer to read.
340
+ byte_index: byte index from where to start writing.
341
+ Examples:
342
+ >>> data = bytearray(8)
343
+ >>> data[:] = [32, 7, 18, 23, 50, 2, 133, 65] #date '2020-07-12 17:32:02.854'
344
+ >>> get_date_time_object(data,0)
345
+ datetime.datetime(2020, 7, 12, 17, 32, 2, 854000)
346
+ """
347
+
348
+ def bcd_to_byte(byte: int) -> int:
349
+ return (byte >> 4) * 10 + (byte & 0xF)
350
+
351
+ year = bcd_to_byte(bytearray_[byte_index])
352
+ # between 1990 and 2089, only last two digits are saved in DB 90 - 89
353
+ year = 2000 + year if year < 90 else 1900 + year
354
+ month = bcd_to_byte(bytearray_[byte_index + 1])
355
+ day = bcd_to_byte(bytearray_[byte_index + 2])
356
+ hour = bcd_to_byte(bytearray_[byte_index + 3])
357
+ min_ = bcd_to_byte(bytearray_[byte_index + 4])
358
+ sec = bcd_to_byte(bytearray_[byte_index + 5])
359
+ # plc save miliseconds in two bytes with the most signifanct byte used only
360
+ # in the last byte for microseconds the other for weekday
361
+ # * 1000 because pythoin datetime needs microseconds not milli
362
+ microsec = (bcd_to_byte(bytearray_[byte_index + 6]) * 10 + bcd_to_byte(bytearray_[byte_index + 7] >> 4)) * 1000
363
+
364
+ return datetime(year, month, day, hour, min_, sec, microsec)
365
+
366
+
367
+ def get_time(bytearray_: bytearray, byte_index: int) -> str:
368
+ """Get time value from bytearray.
369
+
370
+ Notes:
371
+ Datatype `time` consists in 4 bytes in the PLC.
372
+ Maximum possible value is T#24D_20H_31M_23S_647MS(2147483647).
373
+ Lower posible value is T#-24D_20H_31M_23S_648MS(-2147483648).
374
+
375
+ Args:
376
+ bytearray_: buffer to read.
377
+ byte_index: byte index from where to start reading.
378
+
379
+ Returns:
380
+ Value read.
381
+
382
+ Examples:
383
+ >>> import struct
384
+ >>> data = bytearray(4)
385
+ >>> data[:] = struct.pack(">i", 2147483647)
386
+ >>> get_time(data, 0)
387
+ '24:20:31:23:647'
388
+ """
389
+ data_bytearray = bytearray_[byte_index : byte_index + 4]
390
+ bits = 32
391
+ sign = 1
392
+ byte_str = data_bytearray.hex()
393
+ val = int(byte_str, 16)
394
+ if (val & (1 << (bits - 1))) != 0:
395
+ sign = -1 # if sign bit is set e.g., 8bit: 128-255
396
+ val -= 1 << bits # compute negative value
397
+ val *= sign
398
+
399
+ milli_seconds = val % 1000
400
+ seconds = val // 1000
401
+ minutes = seconds // 60
402
+ hours = minutes // 60
403
+ days = hours // 24
404
+
405
+ sign_str = "" if sign >= 0 else "-"
406
+ time_str = f"{sign_str}{days!s}:{hours % 24!s}:{minutes % 60!s}:{seconds % 60!s}.{milli_seconds!s}"
407
+
408
+ return time_str
409
+
410
+
411
+ def get_usint(bytearray_: bytearray, byte_index: int) -> int:
412
+ """Get the unsigned small int from the bytearray
413
+
414
+ Notes:
415
+ Datatype `usint` (Unsigned small int) consists on 1 byte in the PLC.
416
+ Maximum posible value is 255.
417
+ Lower posible value is 0.
418
+
419
+ Args:
420
+ bytearray_: buffer to read from.
421
+ byte_index: byte index from where to start reading.
422
+
423
+ Returns:
424
+ Value read.
425
+
426
+ Examples:
427
+ >>> data = bytearray([255])
428
+ >>> get_usint(data, 0)
429
+ 255
430
+ """
431
+ data = bytearray_[byte_index] & 0xFF
432
+ packed = struct.pack("B", data)
433
+ value: int = struct.unpack(">B", packed)[0]
434
+ return value
435
+
436
+
437
+ def get_sint(bytearray_: bytearray, byte_index: int) -> int:
438
+ """Get the small int
439
+
440
+ Notes:
441
+ Datatype `sint` (Small int) consists in 1 byte in the PLC.
442
+ Maximum value posible is 127.
443
+ Lowest value posible is -128.
444
+
445
+ Args:
446
+ bytearray_: buffer to read from.
447
+ byte_index: byte index from where to start reading.
448
+
449
+ Returns:
450
+ Value read.
451
+
452
+ Examples:
453
+ >>> data = bytearray([127])
454
+ >>> get_sint(data, 0)
455
+ 127
456
+ """
457
+ data = bytearray_[byte_index]
458
+ packed = struct.pack("B", data)
459
+ value: int = struct.unpack(">b", packed)[0]
460
+ return value
461
+
462
+
463
+ def get_lint(bytearray_: bytearray, byte_index: int) -> int:
464
+ """Get the long int
465
+
466
+ THIS VALUE IS NEITHER TESTED NOR VERIFIED BY A REAL PLC AT THE MOMENT
467
+
468
+ Notes:
469
+ Datatype `lint` (long int) consists in 8 bytes in the PLC.
470
+ Maximum value posible is +9223372036854775807
471
+ Lowest value posible is -9223372036854775808
472
+
473
+ Args:
474
+ bytearray_: buffer to read from.
475
+ byte_index: byte index from where to start reading.
476
+
477
+ Returns:
478
+ Value read.
479
+
480
+ Examples:
481
+ read lint value (here as example 12345) from DB1.10 of a PLC
482
+ >>> from snap7 import Client
483
+ >>> data = Client().db_read(db_number=1, start=10, size=8)
484
+ >>> get_lint(data, 0)
485
+ 12345
486
+ """
487
+
488
+ raw_lint = bytearray_[byte_index : byte_index + 8]
489
+ lint = struct.unpack(">q", struct.pack("8B", *raw_lint))[0]
490
+ return int(lint)
491
+
492
+
493
+ def get_lreal(bytearray_: bytearray, byte_index: int) -> float:
494
+ """Get the long real
495
+
496
+ Datatype `lreal` (long real) consists in 8 bytes in the PLC.
497
+ Negative Range: -1.7976931348623158e+308 to -2.2250738585072014e-308
498
+ Positive Range: +2.2250738585072014e-308 to +1.7976931348623158e+308
499
+ Zero: ±0
500
+
501
+ Args:
502
+ bytearray_: buffer to read from.
503
+ byte_index: byte index from where to start reading.
504
+
505
+ Returns:
506
+ The real value.
507
+
508
+ Examples:
509
+ read lreal value (here as example 12345.12345) from DB1.10 of a PLC
510
+ >>> from snap7 import Client
511
+ >>> data = Client().db_read(db_number=1, start=10, size=8)
512
+ >>> get_lreal(data, 0)
513
+ 12345.12345
514
+ """
515
+ return float(struct.unpack_from(">d", bytearray_, offset=byte_index)[0])
516
+
517
+
518
+ def get_lword(bytearray_: bytearray, byte_index: int) -> int:
519
+ """Get the long word
520
+
521
+ Notes:
522
+ Datatype `lword` (long word) consists in 8 bytes in the PLC.
523
+ Maximum value is 18446744073709551615 (0xFFFFFFFFFFFFFFFF).
524
+ Minimum value is 0.
525
+
526
+ Args:
527
+ bytearray_: buffer to read from.
528
+ byte_index: byte index from where to start reading.
529
+
530
+ Returns:
531
+ Value read.
532
+
533
+ Examples:
534
+ >>> data = bytearray(b"\\x00\\x00\\x00\\x00\\x00\\x00\\xAB\\xCD")
535
+ >>> get_lword(data, 0)
536
+ 43981
537
+ """
538
+ data = bytearray_[byte_index : byte_index + 8]
539
+ lword: int = struct.unpack(">Q", struct.pack("8B", *data))[0]
540
+ return lword
541
+
542
+
543
+ def get_ulint(bytearray_: bytearray, byte_index: int) -> int:
544
+ """Get ulint value from bytearray.
545
+
546
+ Notes:
547
+ Datatype `int` in the PLC is represented in 8 bytes
548
+
549
+ Args:
550
+ bytearray_: buffer to read from.
551
+ byte_index: byte index to start reading from.
552
+
553
+ Returns:
554
+ Value read.
555
+
556
+ Examples:
557
+ Read 8 Bytes raw from DB1.10, where an ulint value is stored. Return Python compatible value.
558
+ >>> from snap7 import Client
559
+ >>> data = Client().db_read(db_number=1, start=10, size=8)
560
+ >>> get_ulint(data, 0)
561
+ 12345
562
+ """
563
+ raw_ulint = bytearray_[byte_index : byte_index + 8]
564
+ lint: int = struct.unpack(">Q", struct.pack("8B", *raw_ulint))[0]
565
+ return lint
566
+
567
+
568
+ def get_tod(bytearray_: bytearray, byte_index: int) -> timedelta:
569
+ len_bytearray_ = len(bytearray_)
570
+ byte_range = byte_index + 4
571
+ if len_bytearray_ < byte_range:
572
+ raise ValueError("Date can't be extracted from bytearray. bytearray_[Index:Index+16] would cause overflow.")
573
+ time_val = timedelta(milliseconds=int.from_bytes(bytearray_[byte_index:byte_range], byteorder="big"))
574
+ if time_val.days >= 1:
575
+ raise ValueError("Time_Of_Date can't be extracted from bytearray. Bytearray contains unexpected values.")
576
+ return time_val
577
+
578
+
579
+ def get_date(bytearray_: bytearray, byte_index: int = 0) -> date:
580
+ len_bytearray_ = len(bytearray_)
581
+ byte_range = byte_index + 2
582
+ if len_bytearray_ < byte_range:
583
+ raise ValueError("Date can't be extracted from bytearray. bytearray_[Index:Index+16] would cause overflow.")
584
+ date_val = date(1990, 1, 1) + timedelta(days=int.from_bytes(bytearray_[byte_index:byte_range], byteorder="big"))
585
+ if date_val > date(2168, 12, 31):
586
+ raise ValueError("date_val is higher than specification allows.")
587
+ return date_val
588
+
589
+
590
+ def get_ltime(bytearray_: bytearray, byte_index: int) -> timedelta:
591
+ """Get LTIME value from bytearray.
592
+
593
+ Notes:
594
+ Datatype `LTIME` consists of 8 bytes (64-bit signed integer) representing
595
+ nanoseconds. Used in S7-1500 PLCs.
596
+
597
+ Args:
598
+ bytearray_: buffer to read from.
599
+ byte_index: byte index from where to start reading.
600
+
601
+ Returns:
602
+ timedelta value.
603
+
604
+ Examples:
605
+ >>> data = bytearray(8)
606
+ >>> data[:] = b'\\x00\\x00\\x00\\x00\\x3b\\x9a\\xca\\x00' # 1 second in nanoseconds
607
+ >>> get_ltime(data, 0)
608
+ datetime.timedelta(seconds=1)
609
+ """
610
+ raw = bytearray_[byte_index : byte_index + 8]
611
+ nanoseconds: int = struct.unpack(">q", struct.pack("8B", *raw))[0]
612
+ return timedelta(microseconds=nanoseconds // 1000)
613
+
614
+
615
+ def get_ltod(bytearray_: bytearray, byte_index: int) -> timedelta:
616
+ """Get LTOD (Long Time of Day) value from bytearray.
617
+
618
+ Notes:
619
+ Datatype `LTOD` consists of 8 bytes (64-bit unsigned integer) representing
620
+ nanoseconds since midnight. Used in S7-1500 PLCs.
621
+ Range: 0 to 86399999999999 ns.
622
+
623
+ Args:
624
+ bytearray_: buffer to read from.
625
+ byte_index: byte index from where to start reading.
626
+
627
+ Returns:
628
+ timedelta value representing time of day.
629
+ """
630
+ raw = bytearray_[byte_index : byte_index + 8]
631
+ nanoseconds: int = struct.unpack(">Q", struct.pack("8B", *raw))[0]
632
+ result = timedelta(microseconds=nanoseconds // 1000)
633
+ if result.days >= 1:
634
+ raise ValueError("LTOD value exceeds 24 hours")
635
+ return result
636
+
637
+
638
+ def get_ldt(bytearray_: bytearray, byte_index: int) -> datetime:
639
+ """Get LDT (Long Date and Time) value from bytearray.
640
+
641
+ Notes:
642
+ Datatype `LDT` consists of 8 bytes (64-bit unsigned integer) representing
643
+ nanoseconds since 1970-01-01 00:00:00 UTC. Used in S7-1500 PLCs.
644
+
645
+ Args:
646
+ bytearray_: buffer to read from.
647
+ byte_index: byte index from where to start reading.
648
+
649
+ Returns:
650
+ datetime value.
651
+ """
652
+ raw = bytearray_[byte_index : byte_index + 8]
653
+ nanoseconds: int = struct.unpack(">Q", struct.pack("8B", *raw))[0]
654
+ epoch = datetime(1970, 1, 1)
655
+ return epoch + timedelta(microseconds=nanoseconds // 1000)
656
+
657
+
658
+ def get_dtl(bytearray_: bytearray, byte_index: int) -> datetime:
659
+ time_to_datetime = datetime(
660
+ year=int.from_bytes(bytearray_[byte_index : byte_index + 2], byteorder="big"),
661
+ month=int(bytearray_[byte_index + 2]),
662
+ day=int(bytearray_[byte_index + 3]),
663
+ hour=int(bytearray_[byte_index + 5]),
664
+ minute=int(bytearray_[byte_index + 6]),
665
+ second=int(bytearray_[byte_index + 7]),
666
+ microsecond=int(bytearray_[byte_index + 8]),
667
+ ) # --- ? noch nicht genau genug
668
+ if time_to_datetime > datetime(2554, 12, 31, 23, 59, 59):
669
+ raise ValueError("date_val is higher than specification allows.")
670
+ return time_to_datetime
671
+
672
+
673
+ def get_char(bytearray_: bytearray, byte_index: int) -> str:
674
+ """Get char value from bytearray.
675
+
676
+ Notes:
677
+ Datatype `char` in the PLC is represented in 1 byte. It has to be in ASCII-format.
678
+
679
+ Args:
680
+ bytearray_: buffer to read from.
681
+ byte_index: byte index to start reading from.
682
+
683
+ Returns:
684
+ Value read.
685
+
686
+ Examples:
687
+ Read 1 Byte raw from DB1.10, where a char value is stored. Return Python compatible value.
688
+ >>> from snap7 import Client
689
+ >>> data = Client().db_read(db_number=1, start=10, size=1)
690
+ >>> get_char(data, 0)
691
+ 'C'
692
+ """
693
+ char = chr(bytearray_[byte_index])
694
+ return char
695
+
696
+
697
+ def get_wchar(bytearray_: bytearray, byte_index: int) -> str:
698
+ """Get wchar value from bytearray.
699
+
700
+ Datatype `wchar` in the PLC is represented in 2 bytes. It has to be in utf-16-be format.
701
+
702
+ Args:
703
+ bytearray_: buffer to read from.
704
+ byte_index: byte index to start reading from.
705
+
706
+ Returns:
707
+ Value read.
708
+
709
+ Examples:
710
+ Read 2 Bytes raw from DB1.10, where a wchar value is stored. Return Python compatible value.
711
+ >>> from snap7 import Client
712
+ >>> data = Client().db_read(db_number=1, start=10, size=2)
713
+ >>> get_wchar(data, 0)
714
+ 'C'
715
+ """
716
+ if bytearray_[byte_index] == 0:
717
+ return chr(bytearray_[byte_index + 1])
718
+ return bytearray_[byte_index : byte_index + 2].decode("utf-16-be")
719
+
720
+
721
+ def get_wstring(bytearray_: bytearray, byte_index: int) -> str:
722
+ """Parse wstring from bytearray
723
+
724
+ Notes:
725
+ Byte 0 and 1 contains the max size posible for a string (2 Byte value).
726
+ byte 2 and 3 contains the length of the string that contains (2 Byte value).
727
+ The other bytes contain WCHARs (2Byte) in utf-16-be style.
728
+
729
+ Args:
730
+ bytearray_: buffer from where to get the string.
731
+ byte_index: byte index from where to start reading.
732
+
733
+ Returns:
734
+ String value.
735
+
736
+ Examples:
737
+ Read from DB1.10 22, where the WSTRING is stored, the raw 22 Bytes and convert them to a python string
738
+ >>> from snap7 import Client
739
+ >>> data = Client().db_read(db_number=1, start=10, size=22)
740
+ >>> get_wstring(data, 0)
741
+ 'hello world'
742
+ """
743
+ # Byte 0 + 1 --> total length of wstring, should be bytearray_ - 4
744
+ # Byte 2, 3 --> used length of wstring
745
+ wstring_start = byte_index + 4
746
+
747
+ max_wstring_size = bytearray_[byte_index : byte_index + 2]
748
+ packed = struct.pack("2B", *max_wstring_size)
749
+ max_wstring_symbols = struct.unpack(">H", packed)[0] * 2
750
+
751
+ wstr_length_raw = bytearray_[byte_index + 2 : byte_index + 4]
752
+ wstr_symbols_amount = struct.unpack(">H", struct.pack("2B", *wstr_length_raw))[0] * 2
753
+
754
+ if wstr_symbols_amount > max_wstring_symbols or max_wstring_symbols > 16382:
755
+ logger.error("The wstring is too big for the size encountered in specification")
756
+ logger.error("WRONG SIZED STRING ENCOUNTERED")
757
+ raise TypeError(
758
+ f"WString contains {wstr_symbols_amount} chars, but max {max_wstring_symbols} chars are "
759
+ f"expected or is larger than 16382. Bytearray doesn't seem to be a valid string."
760
+ )
761
+
762
+ return bytearray_[wstring_start : wstring_start + wstr_symbols_amount].decode("utf-16-be")
763
+
764
+
765
+ def get_array(bytearray_: bytearray, byte_index: int) -> NoReturn:
766
+ raise NotImplementedError