DLMS-SPODES 0.87.17__py3-none-any.whl → 0.88.1__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.
- DLMS_SPODES/Values/EN/__init__.py +1 -1
- DLMS_SPODES/Values/EN/actors.py +8 -8
- DLMS_SPODES/Values/EN/relation_to_obis_names.py +387 -387
- DLMS_SPODES/Values/RU/__init__.py +1 -1
- DLMS_SPODES/Values/RU/actors.py +8 -8
- DLMS_SPODES/Values/RU/relation_to_obis_names.py +396 -396
- DLMS_SPODES/__init__.py +6 -6
- DLMS_SPODES/configEN.ini +126 -126
- DLMS_SPODES/config_parser.py +53 -53
- DLMS_SPODES/cosem_interface_classes/Overview/__init__.py +0 -0
- DLMS_SPODES/cosem_interface_classes/Overview/class_id.py +107 -0
- DLMS_SPODES/cosem_interface_classes/__class_init__.py +3 -3
- DLMS_SPODES/cosem_interface_classes/__init__.py +3 -2
- DLMS_SPODES/cosem_interface_classes/activity_calendar.py +210 -254
- DLMS_SPODES/cosem_interface_classes/arbitrator.py +78 -105
- DLMS_SPODES/cosem_interface_classes/association_ln/abstract.py +50 -34
- DLMS_SPODES/cosem_interface_classes/association_ln/authentication_mechanism_name.py +25 -25
- DLMS_SPODES/cosem_interface_classes/association_ln/mechanism_id.py +25 -25
- DLMS_SPODES/cosem_interface_classes/association_ln/method.py +5 -5
- DLMS_SPODES/cosem_interface_classes/association_ln/ver0.py +440 -485
- DLMS_SPODES/cosem_interface_classes/association_ln/ver1.py +126 -133
- DLMS_SPODES/cosem_interface_classes/association_ln/ver2.py +30 -36
- DLMS_SPODES/cosem_interface_classes/association_ln/ver3.py +3 -4
- DLMS_SPODES/cosem_interface_classes/association_sn/ver0.py +14 -12
- DLMS_SPODES/cosem_interface_classes/clock.py +81 -131
- DLMS_SPODES/cosem_interface_classes/collection.py +2106 -2122
- DLMS_SPODES/cosem_interface_classes/cosem_interface_class.py +525 -583
- DLMS_SPODES/cosem_interface_classes/data.py +12 -21
- DLMS_SPODES/cosem_interface_classes/demand_register/ver0.py +32 -59
- DLMS_SPODES/cosem_interface_classes/disconnect_control.py +56 -74
- DLMS_SPODES/cosem_interface_classes/extended_register.py +18 -27
- DLMS_SPODES/cosem_interface_classes/gprs_modem_setup.py +33 -43
- DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver0.py +78 -103
- DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver1.py +42 -40
- DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver2.py +6 -9
- DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver0.py +11 -11
- DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver1.py +27 -53
- DLMS_SPODES/cosem_interface_classes/iec_local_port_setup.py +9 -11
- DLMS_SPODES/cosem_interface_classes/image_transfer/image_transfer_status.py +15 -15
- DLMS_SPODES/cosem_interface_classes/image_transfer/ver0.py +54 -126
- DLMS_SPODES/cosem_interface_classes/implementations/__init__.py +3 -3
- DLMS_SPODES/cosem_interface_classes/implementations/arbitrator.py +19 -19
- DLMS_SPODES/cosem_interface_classes/implementations/data.py +491 -487
- DLMS_SPODES/cosem_interface_classes/implementations/profile_generic.py +85 -83
- DLMS_SPODES/cosem_interface_classes/ipv4_setup.py +42 -72
- DLMS_SPODES/cosem_interface_classes/limiter.py +77 -111
- DLMS_SPODES/cosem_interface_classes/ln_pattern.py +334 -333
- DLMS_SPODES/cosem_interface_classes/modem_configuration/ver0.py +51 -65
- DLMS_SPODES/cosem_interface_classes/modem_configuration/ver1.py +27 -39
- DLMS_SPODES/cosem_interface_classes/ntp_setup/ver0.py +48 -67
- DLMS_SPODES/cosem_interface_classes/obis.py +28 -23
- DLMS_SPODES/cosem_interface_classes/overview.py +198 -197
- DLMS_SPODES/cosem_interface_classes/parameter.py +548 -547
- DLMS_SPODES/cosem_interface_classes/parameters.py +172 -172
- DLMS_SPODES/cosem_interface_classes/profile_generic/ver0.py +90 -133
- DLMS_SPODES/cosem_interface_classes/profile_generic/ver1.py +268 -277
- DLMS_SPODES/cosem_interface_classes/push_setup/ver0.py +13 -12
- DLMS_SPODES/cosem_interface_classes/push_setup/ver1.py +9 -10
- DLMS_SPODES/cosem_interface_classes/push_setup/ver2.py +124 -166
- DLMS_SPODES/cosem_interface_classes/register.py +18 -45
- DLMS_SPODES/cosem_interface_classes/register_activation/ver0.py +45 -80
- DLMS_SPODES/cosem_interface_classes/register_monitor.py +33 -46
- DLMS_SPODES/cosem_interface_classes/reports.py +72 -70
- DLMS_SPODES/cosem_interface_classes/schedule.py +88 -176
- DLMS_SPODES/cosem_interface_classes/script_table.py +54 -87
- DLMS_SPODES/cosem_interface_classes/security_setup/ver0.py +45 -68
- DLMS_SPODES/cosem_interface_classes/security_setup/ver1.py +122 -158
- DLMS_SPODES/cosem_interface_classes/single_action_schedule.py +34 -50
- DLMS_SPODES/cosem_interface_classes/special_days_table.py +54 -84
- DLMS_SPODES/cosem_interface_classes/tcp_udp_setup.py +20 -42
- DLMS_SPODES/cosem_pdu.py +93 -93
- DLMS_SPODES/enums.py +625 -625
- DLMS_SPODES/exceptions.py +106 -106
- DLMS_SPODES/firmwares.py +99 -99
- DLMS_SPODES/hdlc/frame.py +875 -875
- DLMS_SPODES/hdlc/sub_layer.py +54 -54
- DLMS_SPODES/literals.py +17 -17
- DLMS_SPODES/obis/__init__.py +1 -1
- DLMS_SPODES/obis/media_id.py +931 -931
- DLMS_SPODES/pardata.py +22 -22
- DLMS_SPODES/pdu_enums.py +98 -98
- DLMS_SPODES/relation_to_OBIS.py +463 -465
- DLMS_SPODES/settings.py +551 -551
- DLMS_SPODES/types/choices.py +140 -142
- DLMS_SPODES/types/common_data_types.py +2379 -2401
- DLMS_SPODES/types/cosem_service_types.py +109 -109
- DLMS_SPODES/types/implementations/arrays.py +25 -25
- DLMS_SPODES/types/implementations/bitstrings.py +97 -97
- DLMS_SPODES/types/implementations/double_long_usingneds.py +35 -35
- DLMS_SPODES/types/implementations/enums.py +57 -57
- DLMS_SPODES/types/implementations/integers.py +12 -11
- DLMS_SPODES/types/implementations/long_unsigneds.py +127 -127
- DLMS_SPODES/types/implementations/octet_string.py +11 -11
- DLMS_SPODES/types/implementations/structs.py +64 -64
- DLMS_SPODES/types/type_alias.py +74 -0
- DLMS_SPODES/types/useful_types.py +627 -677
- {dlms_spodes-0.87.17.dist-info → dlms_spodes-0.88.1.dist-info}/METADATA +30 -30
- dlms_spodes-0.88.1.dist-info/RECORD +118 -0
- {dlms_spodes-0.87.17.dist-info → dlms_spodes-0.88.1.dist-info}/WHEEL +1 -1
- DLMS_SPODES/cosem_interface_classes/a_parameter.py +0 -20
- DLMS_SPODES/cosem_interface_classes/attr_indexes.py +0 -12
- dlms_spodes-0.87.17.dist-info/RECORD +0 -117
- {dlms_spodes-0.87.17.dist-info → dlms_spodes-0.88.1.dist-info}/top_level.txt +0 -0
|
@@ -1,677 +1,627 @@
|
|
|
1
|
-
from
|
|
2
|
-
from functools import lru_cache
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import Type, Any, Callable
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from
|
|
7
|
-
from ..
|
|
8
|
-
from ..
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
case
|
|
31
|
-
case
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def
|
|
149
|
-
"""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
case
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def
|
|
336
|
-
return self.
|
|
337
|
-
|
|
338
|
-
def
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
class
|
|
463
|
-
""" TODO """
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
self.
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
def
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
self.values[
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
self.values[1] = self.ELEMENTS[1].TYPE.ELEMENTS[int(self.access_selector)].TYPE()
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
def
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
def
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
def
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
@
|
|
608
|
-
def
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
a = CosemClassId(cdt.LongUnsigned(1).contents)
|
|
630
|
-
a = InvokeIdAndPriority()
|
|
631
|
-
c = a.invoke_id
|
|
632
|
-
d = a.service_class
|
|
633
|
-
f = a.priority
|
|
634
|
-
a.service_class = 1
|
|
635
|
-
a.invoke_id = 14
|
|
636
|
-
e = InvokeIdAndPriority.from_parameters(1, 1, 1)
|
|
637
|
-
class AccessSelector(Unsigned8):
|
|
638
|
-
""" Unsigned8 1..4 """
|
|
639
|
-
def __init__(self, value: int | str | Unsigned8 = 1):
|
|
640
|
-
super(AccessSelector, self).__init__(value)
|
|
641
|
-
if int(self) > 4 or int(self) < 1:
|
|
642
|
-
raise ValueError(F'The {self.__class__.__name__} got {self}, expected 1..4')
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
class MyData(Data):
|
|
646
|
-
ELEMENTS = {1: SequenceElement('0 a', cdt.NullData),
|
|
647
|
-
2: SequenceElement('1 d', cdt.Integer),
|
|
648
|
-
3: SequenceElement('second', cdt.ScalUnitType),
|
|
649
|
-
4: SequenceElement('3', cdt.Integer)}
|
|
650
|
-
|
|
651
|
-
class My(SelectiveAccessDescriptor):
|
|
652
|
-
access_selector: AccessSelector
|
|
653
|
-
access_parameters: MyData
|
|
654
|
-
ELEMENTS = (SequenceElement('access_selector', AccessSelector),
|
|
655
|
-
SequenceElement('access_parameters', MyData))
|
|
656
|
-
|
|
657
|
-
ba = My()
|
|
658
|
-
b = My((3, (10, 10)))
|
|
659
|
-
b2 = b.access_selector
|
|
660
|
-
b3 = b.access_parameters
|
|
661
|
-
b4 = b.access_parameters.unit
|
|
662
|
-
b_repr = My(b'\x03\x0f"')
|
|
663
|
-
b.set_selector(3, 34)
|
|
664
|
-
a = CosemAttributeDescriptor((1, '1.1.1.1.1.1', 1))
|
|
665
|
-
a_repr = CosemAttributeDescriptor(b'\x00\x01\x01\x01\x01\x01\x01\x01\x01\x00')
|
|
666
|
-
|
|
667
|
-
class MyWith(CosemAttributeDescriptorWithSelection):
|
|
668
|
-
access_selection: My
|
|
669
|
-
ELEMENTS = (SequenceElement('cosem_attribute_descriptor', CosemAttributeDescriptor),
|
|
670
|
-
SequenceElement('access_selection', My))
|
|
671
|
-
|
|
672
|
-
c = MyWith()
|
|
673
|
-
c_from = MyWith((a, b))
|
|
674
|
-
c_repr = MyWith(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00')
|
|
675
|
-
print(a)
|
|
676
|
-
c = CosemAttributeDescriptor(b'\x00\x01\x01\x01\x01\x01\x01\x01\x01\x00')
|
|
677
|
-
print(c)
|
|
1
|
+
from typing_extensions import deprecated
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Type, Any, Callable, Protocol, runtime_checkable
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from StructResult import result
|
|
7
|
+
from ..types import common_data_types as cdt
|
|
8
|
+
from ..types.type_alias import Encoding
|
|
9
|
+
from ..exceptions import DLMSException
|
|
10
|
+
from ..settings import settings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UserfulTypesException(DLMSException):
|
|
14
|
+
"""override DLMSException"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class UsefulType(Protocol):
|
|
19
|
+
""""""
|
|
20
|
+
contents: bytes
|
|
21
|
+
__match_args__ = ('contents',)
|
|
22
|
+
cb_post_set: Callable
|
|
23
|
+
cb_preset: Callable
|
|
24
|
+
|
|
25
|
+
def __init__(self, value):
|
|
26
|
+
""" constructor """
|
|
27
|
+
|
|
28
|
+
def __eq__(self, other: "UsefulType"):
|
|
29
|
+
match other:
|
|
30
|
+
case self.__class__(self.contents): return True
|
|
31
|
+
case _: return False
|
|
32
|
+
|
|
33
|
+
def set_contents_from(self, value: bytes | bytearray | str | int | bool | None):
|
|
34
|
+
new_value = self.__class__(value)
|
|
35
|
+
if hasattr(self, 'cb_preset'):
|
|
36
|
+
self.cb_preset(new_value)
|
|
37
|
+
self.__dict__['contents'] = new_value.contents
|
|
38
|
+
if hasattr(self, 'cb_post_set'):
|
|
39
|
+
self.cb_post_set()
|
|
40
|
+
|
|
41
|
+
def __setattr__(self, key, value):
|
|
42
|
+
match key:
|
|
43
|
+
case 'contents' as prop if hasattr(self, 'contents'): raise ValueError(F"Don't support set {prop}")
|
|
44
|
+
case _: super().__setattr__(key, value)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _String(Protocol):
|
|
48
|
+
LENGTH: int | None
|
|
49
|
+
|
|
50
|
+
def __init__(self, value: bytes | bytearray | str | int | tuple | UsefulType = None):
|
|
51
|
+
match value:
|
|
52
|
+
case None: self.__dict__["contents"] = bytes(self.LENGTH)
|
|
53
|
+
case bytes() if self.LENGTH is None or self.LENGTH <= len(value): self.__dict__["contents"] = value[:self.LENGTH]
|
|
54
|
+
# case bytes() if self.LENGTH <= len(value): self.contents = value[:self.LENGTH]
|
|
55
|
+
case bytes(): raise ValueError(F'Length of contents for {self.__class__.__name__} must be at least {self.LENGTH}, but got {len(value)}')
|
|
56
|
+
case tuple():
|
|
57
|
+
if len(value) == self.LENGTH:
|
|
58
|
+
self.__dict__["contents"] = bytes(value)
|
|
59
|
+
else:
|
|
60
|
+
raise ValueError(F"in {self.__class__.__name__} with {value=} got length: {len(value)}, expect {self.LENGTH}")
|
|
61
|
+
case bytearray(): self.__dict__["contents"] = bytes(value) # Attention!!! changed method content getting from bytearray
|
|
62
|
+
case str(): self.__dict__["contents"] = self.from_str(value)
|
|
63
|
+
case int(): self.__dict__["contents"] = self.from_int(value)
|
|
64
|
+
case UsefulType(): self.__dict__["contents"] = value.contents # TODO: make right type
|
|
65
|
+
case _: raise ValueError(F'Error create {self.__class__.__name__} with value {value}')
|
|
66
|
+
|
|
67
|
+
def __len__(self):
|
|
68
|
+
""" define in subclasses """
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class OCTET_STRING(_String):
|
|
72
|
+
""" An ordered sequence of octets (8 bit bytes) """
|
|
73
|
+
|
|
74
|
+
def from_str(self, value: str) -> bytes:
|
|
75
|
+
""" input as hex code """
|
|
76
|
+
return bytes.fromhex(value)
|
|
77
|
+
|
|
78
|
+
def from_int(self, value: int) -> bytes:
|
|
79
|
+
""" Convert with recursion. Maximum convert length is 32 """
|
|
80
|
+
def to_bytes_with(length_):
|
|
81
|
+
try:
|
|
82
|
+
return int.to_bytes(value, length_, 'big')
|
|
83
|
+
except OverflowError:
|
|
84
|
+
if length_ > 31:
|
|
85
|
+
raise ValueError(F'Value {value} is big to convert to bytes')
|
|
86
|
+
return to_bytes_with(length_+1)
|
|
87
|
+
length = 1
|
|
88
|
+
return to_bytes_with(length)
|
|
89
|
+
|
|
90
|
+
def __str__(self):
|
|
91
|
+
return self.contents.hex(' ')
|
|
92
|
+
|
|
93
|
+
def __len__(self):
|
|
94
|
+
return len(self.contents)
|
|
95
|
+
|
|
96
|
+
def __getitem__(self, item):
|
|
97
|
+
return self.contents[item]
|
|
98
|
+
|
|
99
|
+
# TODO: maybe remove as redundante?
|
|
100
|
+
def decode(self) -> bytes:
|
|
101
|
+
""" decode to build in bytes type """
|
|
102
|
+
return self.contents
|
|
103
|
+
|
|
104
|
+
# TODO: maybe remove as redundante?
|
|
105
|
+
def to_str(self, encoding: str = 'cp1251') -> str:
|
|
106
|
+
""" decode to cp1251 by default, replace to '?' if unsupported """
|
|
107
|
+
temp = list()
|
|
108
|
+
for i in self.contents:
|
|
109
|
+
temp.append(i if i > 32 else 63)
|
|
110
|
+
return bytes(temp).decode(encoding)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@runtime_checkable
|
|
114
|
+
class CHOICE(Protocol):
|
|
115
|
+
""" TODO: with cdt.CHOICE """
|
|
116
|
+
ELEMENTS: "dict[int, SequenceElement | dict[int, SequenceElement]]"
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def TYPE(self) -> Any:
|
|
120
|
+
""" return valid types """
|
|
121
|
+
|
|
122
|
+
# def __init_subclass__(cls, **kwargs):
|
|
123
|
+
# if hasattr(cls, 'ELEMENTS'):
|
|
124
|
+
# for el in cls.ELEMENTS.values():
|
|
125
|
+
# if isinstance(el, dict):
|
|
126
|
+
# """pass, maybe it is for cst.AnyTime"""
|
|
127
|
+
# elif issubclass(el.TYPE, cls.TYPE):
|
|
128
|
+
# """ type in order """
|
|
129
|
+
# else:
|
|
130
|
+
# raise ValueError(F'For {cls.__name__} got type {el.TYPE.__name__} with {el.NAME=}, expected {cls.TYPE.__name__}')
|
|
131
|
+
# else:
|
|
132
|
+
# """ subclass with type carry initiate """
|
|
133
|
+
|
|
134
|
+
def __getitem__(self, item: int) -> "SequenceElement":
|
|
135
|
+
return self.ELEMENTS[item]
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def NAME(self) -> str:
|
|
139
|
+
return F'CHOICE[{len(self)}]'
|
|
140
|
+
|
|
141
|
+
def __len__(self):
|
|
142
|
+
return len(self.ELEMENTS)
|
|
143
|
+
|
|
144
|
+
def is_key(self, value: int) -> bool:
|
|
145
|
+
return value in self.ELEMENTS.keys()
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def parse(cls, value: cdt.Transcript) -> cdt.CommonDataType:
|
|
149
|
+
"""get instance by pattern: <tag>:<value>"""
|
|
150
|
+
tag, value = value.split(":", maxsplit=1)
|
|
151
|
+
if (d_t := cls.ELEMENTS.get(int(tag))) is None:
|
|
152
|
+
raise UserfulTypesException(F"for {cls.__name__} got {cdt.CommonDataType.__name__}: {cdt.TAG(int(tag).to_bytes(1))}; "
|
|
153
|
+
F"expected: {', '.join(map(lambda el: el.NAME, cls.ELEMENTS.values()))}")
|
|
154
|
+
else:
|
|
155
|
+
return d_t.TYPE.parse(value)
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def from_encoding(cls, encoding: Encoding) -> result.SimpleOrError[cdt.CommonDataType]:
|
|
159
|
+
match cls.ELEMENTS[encoding[0]]:
|
|
160
|
+
case SequenceElement() as el:
|
|
161
|
+
return el.TYPE.from_encoding(encoding)
|
|
162
|
+
case dict() as ch:
|
|
163
|
+
if encoding[1] in ch.keys():
|
|
164
|
+
return ch[encoding[1]].TYPE.from_encoding(encoding) # use for choice cst.Time | DateTime | Date as OctetString
|
|
165
|
+
else:
|
|
166
|
+
return result.Error.from_e(ValueError(F"got type with tag: {encoding[0]} and length: {encoding[1]}, expected length {tuple(ch.keys())}"))
|
|
167
|
+
case err:
|
|
168
|
+
raise RuntimeError(F"got {err.__name__}, expected {SequenceElement.__name__} or {dict.__name__}")
|
|
169
|
+
|
|
170
|
+
@deprecated("use <from_encoding> and more")
|
|
171
|
+
def __call__(self,
|
|
172
|
+
value: bytes | int = None,
|
|
173
|
+
force: bool = False) -> cdt.CommonDataType:
|
|
174
|
+
""" get instance from encoding or tag(with default value). For CommonDataType only """
|
|
175
|
+
try:
|
|
176
|
+
match value:
|
|
177
|
+
case bytes() as encoding: return self.__class__.from_encoding(encoding).unwrap()
|
|
178
|
+
case int() if force: return cdt.get_common_data_type_from(value.to_bytes(1, "big"))()
|
|
179
|
+
case int() as tag: return self.ELEMENTS[tag].TYPE()
|
|
180
|
+
case None: return tuple(self.ELEMENTS.values())[0].TYPE()
|
|
181
|
+
case error: raise ValueError(F'Unknown value type {error}')
|
|
182
|
+
except KeyError as e:
|
|
183
|
+
raise UserfulTypesException(F"for {self.__class__.__name__} got {cdt.CommonDataType.__name__}: {cdt.TAG(e.args[0].to_bytes(1))}; expected: {', '.join(map(lambda el: el.NAME, self.ELEMENTS.values()))}")
|
|
184
|
+
|
|
185
|
+
def __get_elements(self) -> "list[SequenceElement]":
|
|
186
|
+
"""all elements with nested values"""
|
|
187
|
+
elements = []
|
|
188
|
+
for el in self.ELEMENTS.values():
|
|
189
|
+
match el:
|
|
190
|
+
case SequenceElement():
|
|
191
|
+
elements.append(el)
|
|
192
|
+
case dict() as dict_el:
|
|
193
|
+
elements.extend(dict_el.values())
|
|
194
|
+
case err:
|
|
195
|
+
raise ValueError(F"unknown CHOICE element type {err}")
|
|
196
|
+
return elements
|
|
197
|
+
|
|
198
|
+
def get_types(self) -> tuple[Type[cdt.CommonDataType]]:
|
|
199
|
+
""" Use in setter attribute.value for validate """
|
|
200
|
+
return tuple((seq_el.TYPE for seq_el in self.__get_elements()))
|
|
201
|
+
|
|
202
|
+
def __str__(self):
|
|
203
|
+
return F'{CHOICE}: {", ".join((el.NAME for el in self.__get_elements()))}'
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def get_instance_and_context(meta: type[UsefulType], value: bytes) -> tuple[UsefulType, bytes]:
|
|
207
|
+
instance = meta(value)
|
|
208
|
+
return instance, value[len(instance.contents):]
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class SEQUENCE(Protocol):
|
|
212
|
+
""" TODO: """
|
|
213
|
+
ELEMENTS: "tuple[SequenceElement | SEQUENCE, ...]"
|
|
214
|
+
values: list[UsefulType]
|
|
215
|
+
|
|
216
|
+
def __init__(self, value: "bytes | tuple | list | None | SEQUENCE" = None):
|
|
217
|
+
self.__dict__['values'] = [None] * len(self.ELEMENTS)
|
|
218
|
+
match value:
|
|
219
|
+
case tuple() | list():
|
|
220
|
+
if len(value) != len(self):
|
|
221
|
+
raise ValueError(F'Struct {self.__class__.__name__} got length:{len(value)}, expected length:{len(self)}')
|
|
222
|
+
self.from_tuple(value)
|
|
223
|
+
case bytes(): self.from_bytes(value)
|
|
224
|
+
case None: self.from_default()
|
|
225
|
+
case self.__class__(): self.from_bytes(value.contents)
|
|
226
|
+
case _: raise TypeError(F'Value: "{value}" not supported')
|
|
227
|
+
|
|
228
|
+
def from_default(self):
|
|
229
|
+
for i in range(len(self)):
|
|
230
|
+
self.values[i] = self.ELEMENTS[i].TYPE()
|
|
231
|
+
|
|
232
|
+
def from_bytes(self, value: bytes | bytearray):
|
|
233
|
+
for i, element in enumerate(self.ELEMENTS):
|
|
234
|
+
self.values[i], value = get_instance_and_context(element.TYPE, value)
|
|
235
|
+
|
|
236
|
+
def from_tuple(self, value: tuple | list):
|
|
237
|
+
for i, val in enumerate(value):
|
|
238
|
+
self.values[i] = self.ELEMENTS[i].TYPE(val)
|
|
239
|
+
|
|
240
|
+
def __str__(self):
|
|
241
|
+
return F'{{{", ".join(map(lambda val: F"{val[0].NAME}: {val[1]}", zip(self.ELEMENTS, self.values)))}}}'
|
|
242
|
+
|
|
243
|
+
def __repr__(self):
|
|
244
|
+
return F'{self.__class__.__name__}(({(", ".join(map(str, self.values)))}))'
|
|
245
|
+
|
|
246
|
+
def __get_index(self, name: str) -> int | None:
|
|
247
|
+
""" get index by name. Return None if not found """
|
|
248
|
+
for i, element in enumerate(self.ELEMENTS):
|
|
249
|
+
if element.NAME == name:
|
|
250
|
+
return i
|
|
251
|
+
else:
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
def __setattr__(self, key, value: UsefulType):
|
|
255
|
+
match self.__get_index(key):
|
|
256
|
+
case int() as i if isinstance(value, self.ELEMENTS[i]): self.values[i] = value
|
|
257
|
+
case int() as i: raise ValueError(F'Try assign {key} Type got {value.__class__.__name__}, expected {self.ELEMENTS[i].NAME}')
|
|
258
|
+
case _: raise ValueError(F'Unsupported change: {key}')
|
|
259
|
+
|
|
260
|
+
def __getattr__(self, item: str) -> UsefulType:
|
|
261
|
+
match self.__get_index(item):
|
|
262
|
+
case int() as i: return self.values[i]
|
|
263
|
+
case _: return self.__dict__[item]
|
|
264
|
+
|
|
265
|
+
def __getitem__(self, item: str | int) -> UsefulType:
|
|
266
|
+
""" get element by index or name """
|
|
267
|
+
match item:
|
|
268
|
+
case str() as name: return self.values[self.__get_index(name)]
|
|
269
|
+
case int() as index: return self.values[index]
|
|
270
|
+
case _: raise ValueError(F'Unsupported type {item.__class__}')
|
|
271
|
+
|
|
272
|
+
def __setitem__(self, key: int, value: UsefulType):
|
|
273
|
+
""" set data to element by index """
|
|
274
|
+
if isinstance(value, self.ELEMENTS[key].TYPE):
|
|
275
|
+
self.values[key] = value
|
|
276
|
+
else:
|
|
277
|
+
raise ValueError(F'Type got {value.__class__.__name__}, expected {self.ELEMENTS[key].TYPE}')
|
|
278
|
+
|
|
279
|
+
def __len__(self):
|
|
280
|
+
return len(self.ELEMENTS)
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def contents(self) -> bytes:
|
|
284
|
+
return b''.join(el.contents for el in self.values)
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def NAME(self) -> str:
|
|
288
|
+
return F'{self.__class__.__name__}[{len(self)}]'
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@dataclass(frozen=True)
|
|
292
|
+
class SequenceElement:
|
|
293
|
+
NAME: str
|
|
294
|
+
TYPE: Type[UsefulType | SEQUENCE | CHOICE | cdt.CommonDataType]
|
|
295
|
+
|
|
296
|
+
def __str__(self):
|
|
297
|
+
return F'{self.NAME}: {self.TYPE.__name__}'
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class DigitalMixin:
|
|
302
|
+
""" Default value is 0 """
|
|
303
|
+
SIGNED: bool
|
|
304
|
+
LENGTH: int
|
|
305
|
+
contents: bytes
|
|
306
|
+
|
|
307
|
+
def __init__(self, value: "bytes | bytearray | str | int | DigitalMixin" = None):
|
|
308
|
+
match value:
|
|
309
|
+
case bytes() if self.LENGTH <= len(value): self.__dict__["contents"] = value[:self.LENGTH]
|
|
310
|
+
case bytes(): raise ValueError(F'Length of contents for {self.__class__.__name__} must be at least {self.LENGTH}, but got {len(value)}')
|
|
311
|
+
case bytearray(): self.__dict__["contents"] = bytes(value) # Attention!!! changed method content getting from bytearray
|
|
312
|
+
case str('-') if self.SIGNED: self.__dict__["contents"] = bytes(self.LENGTH)
|
|
313
|
+
case int(): self.__dict__["contents"] = self.from_int(value)
|
|
314
|
+
case str(): self.__dict__["contents"] = self.from_str(value)
|
|
315
|
+
case None: self.__dict__["contents"] = bytes(self.LENGTH)
|
|
316
|
+
case self.__class__(): self.__dict__["contents"] = value.contents
|
|
317
|
+
case _: raise ValueError(F'Error create {self.__class__.__name__} with value: {value}')
|
|
318
|
+
|
|
319
|
+
def from_int(self, value: int | float) -> bytes:
|
|
320
|
+
try:
|
|
321
|
+
return int(value).to_bytes(self.LENGTH, 'big', signed=self.SIGNED)
|
|
322
|
+
except OverflowError:
|
|
323
|
+
raise ValueError(F'value {value} out of range')
|
|
324
|
+
|
|
325
|
+
def from_str(self, value: str) -> bytes:
|
|
326
|
+
return self.from_int(float(value))
|
|
327
|
+
|
|
328
|
+
def __int__(self):
|
|
329
|
+
""" return the build in integer type """
|
|
330
|
+
return int.from_bytes(self.contents, 'big', signed=self.SIGNED)
|
|
331
|
+
|
|
332
|
+
def __str__(self):
|
|
333
|
+
return str(int(self))
|
|
334
|
+
|
|
335
|
+
def __repr__(self):
|
|
336
|
+
return F'{self.__class__.__name__}({self})'
|
|
337
|
+
|
|
338
|
+
def __gt__(self, other: "DigitalMixin"):
|
|
339
|
+
match other:
|
|
340
|
+
case DigitalMixin(): return int(self) > int(other)
|
|
341
|
+
case _: raise TypeError(F'Compare type is {other.__class__}, expected Digital')
|
|
342
|
+
|
|
343
|
+
def __len__(self) -> int:
|
|
344
|
+
return self.LENGTH
|
|
345
|
+
|
|
346
|
+
def __hash__(self):
|
|
347
|
+
return int(self)
|
|
348
|
+
|
|
349
|
+
def validate_from(self, value: str, cursor_position: int) -> tuple[str, int]:
|
|
350
|
+
""" return validated value and cursor position. Use in Entry. TODO: remove it, make better """
|
|
351
|
+
type(self)(value=value)
|
|
352
|
+
return value, cursor_position
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class Integer8(DigitalMixin, UsefulType):
|
|
356
|
+
""" INTEGER(-127…128) """
|
|
357
|
+
LENGTH = 1
|
|
358
|
+
SIGNED = True
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class Integer16(DigitalMixin, UsefulType):
|
|
362
|
+
""" INTEGER(-32 768...32 767) """
|
|
363
|
+
LENGTH = 2
|
|
364
|
+
SIGNED = True
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class Integer32(DigitalMixin, UsefulType):
|
|
368
|
+
""" INTEGER(-2 147 483 648...2 147 483 647) """
|
|
369
|
+
LENGTH = 4
|
|
370
|
+
SIGNED = True
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class Integer64(DigitalMixin, UsefulType):
|
|
374
|
+
""" INTEGER(-2^63...2^63-1) """
|
|
375
|
+
LENGTH = 8
|
|
376
|
+
SIGNED = True
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class Unsigned8(DigitalMixin, UsefulType):
|
|
380
|
+
""" INTEGER(0...255) """
|
|
381
|
+
LENGTH = 1
|
|
382
|
+
SIGNED = False
|
|
383
|
+
# __match_args__ = ('contents',)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class Unsigned16(DigitalMixin, UsefulType):
|
|
387
|
+
""" INTEGER(0...65 535) """
|
|
388
|
+
LENGTH = 2
|
|
389
|
+
SIGNED = False
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class Unsigned32(DigitalMixin, UsefulType):
|
|
393
|
+
""" INTEGER(0...4 294 967 295) """
|
|
394
|
+
LENGTH = 4
|
|
395
|
+
SIGNED = False
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class Unsigned64(DigitalMixin, UsefulType):
|
|
399
|
+
""" INTEGER(0...264-1) """
|
|
400
|
+
LENGTH = 8
|
|
401
|
+
SIGNED = False
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class CosemClassId(Unsigned16):
|
|
405
|
+
""" Identification code of the IC (range 0 to 65 535). The class_id of each object is retrieved together with the logical name by reading the object_list attribute of an
|
|
406
|
+
“Association LN” / ”Association SN” object.
|
|
407
|
+
- class_id-s from 0 to 8 191 are reserved to be specified by the DLMS UA.
|
|
408
|
+
- class_id-s from 8 192 to 32 767 are reserved for manufacturer specific ICs.
|
|
409
|
+
- class_id-s from 32 768 to 65 535 are reserved for user group specific ICs.
|
|
410
|
+
The DLMS UA reserves the right to assign ranges to individual manufacturers or user groups. """
|
|
411
|
+
|
|
412
|
+
def __str__(self) -> str:
|
|
413
|
+
if res := settings.class_name.get(int(self)):
|
|
414
|
+
return res
|
|
415
|
+
else:
|
|
416
|
+
return repr(self)
|
|
417
|
+
|
|
418
|
+
def __repr__(self):
|
|
419
|
+
return F"{self.__class__.__name__}({int(self)})"
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class CosemObjectInstanceId(OCTET_STRING, UsefulType):
|
|
423
|
+
LENGTH = 6
|
|
424
|
+
|
|
425
|
+
def __str__(self):
|
|
426
|
+
return F"\"{'.'.join(map(str, self.contents))}\""
|
|
427
|
+
|
|
428
|
+
def from_str(self, value: str) -> bytes:
|
|
429
|
+
""" create logical_name: octet_string from string type ddd.ddd.ddd.ddd.ddd.ddd, ex.: 0.0.1.0.0.255 """
|
|
430
|
+
raw_value = bytes()
|
|
431
|
+
for typecast, separator in zip((self.__from_group_A, )*5+(self.__from_group_F, ), ('.', '.', '.', '.', '.', ' ')):
|
|
432
|
+
try:
|
|
433
|
+
element, value = value.split(separator, 1)
|
|
434
|
+
except ValueError:
|
|
435
|
+
element, value = value, ''
|
|
436
|
+
raw_value += typecast(element)
|
|
437
|
+
return raw_value
|
|
438
|
+
|
|
439
|
+
def __from_group_A(self, value: str) -> bytes:
|
|
440
|
+
if isinstance(value, str):
|
|
441
|
+
if value == '':
|
|
442
|
+
return b'\x00'
|
|
443
|
+
try:
|
|
444
|
+
return int(value).to_bytes(1, 'big')
|
|
445
|
+
except OverflowError:
|
|
446
|
+
raise ValueError(F'Int too big to convert {value}')
|
|
447
|
+
else:
|
|
448
|
+
raise TypeError(F'Unsupported type validation from string, got {value.__class__}')
|
|
449
|
+
|
|
450
|
+
def __from_group_F(self, value: str) -> bytes:
|
|
451
|
+
if isinstance(value, str):
|
|
452
|
+
if value == '':
|
|
453
|
+
return b'\xff'
|
|
454
|
+
try:
|
|
455
|
+
return int(value).to_bytes(1, 'big')
|
|
456
|
+
except OverflowError:
|
|
457
|
+
raise ValueError(F'Int too big to convert {value}')
|
|
458
|
+
else:
|
|
459
|
+
raise TypeError(F'Unsupported type validation from string, got {value.__class__}')
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
class CosemObjectAttributeId(Integer8):
|
|
463
|
+
""" TODO """
|
|
464
|
+
|
|
465
|
+
@lru_cache(14) # for test
|
|
466
|
+
def __new__(cls, *args, **kwargs):
|
|
467
|
+
return super().__new__(cls)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class CosemObjectMethodId(Integer8):
|
|
471
|
+
""" TODO """
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class AccessSelectionParameters(Unsigned8):
|
|
475
|
+
""" Unsigned8(0..1) """
|
|
476
|
+
|
|
477
|
+
def __init__(self, value: int | str | Unsigned8 = 1):
|
|
478
|
+
super(AccessSelectionParameters, self).__init__(value)
|
|
479
|
+
if int(self) > 1 or int(self) < 0:
|
|
480
|
+
raise ValueError(F'The {self.__class__.__name__} got {self}, expected 0..1')
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
class CosemAttributeDescriptor(SEQUENCE):
|
|
484
|
+
class_id: CosemClassId
|
|
485
|
+
instance_id: CosemObjectInstanceId
|
|
486
|
+
attribute_id: CosemObjectAttributeId
|
|
487
|
+
access_selection_parameters: AccessSelectionParameters
|
|
488
|
+
ELEMENTS = (SequenceElement('class_id', CosemClassId),
|
|
489
|
+
SequenceElement('instance_id', CosemObjectInstanceId),
|
|
490
|
+
SequenceElement('attribute_id', CosemObjectAttributeId))
|
|
491
|
+
|
|
492
|
+
def __init__(self, value: bytes | tuple | list = None):
|
|
493
|
+
super(CosemAttributeDescriptor, self).__init__(value)
|
|
494
|
+
self.__dict__['access_selection_parameters'] = AccessSelectionParameters(0)
|
|
495
|
+
|
|
496
|
+
@property
|
|
497
|
+
def contents(self) -> bytes:
|
|
498
|
+
""" Always contain Access_Selection_Parameters. DLMS UA 1000-2 Ed.9 Excerpt 9.3.9.1.3 """
|
|
499
|
+
return super(CosemAttributeDescriptor, self).contents + self.access_selection_parameters.contents
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class CosemMethodDescriptor(SEQUENCE):
|
|
503
|
+
class_id: CosemClassId
|
|
504
|
+
instance_id: CosemObjectInstanceId
|
|
505
|
+
method_id: CosemObjectMethodId
|
|
506
|
+
ELEMENTS = (SequenceElement('class_id', CosemClassId),
|
|
507
|
+
SequenceElement('instance_id', CosemObjectInstanceId),
|
|
508
|
+
SequenceElement('method_id', CosemObjectMethodId))
|
|
509
|
+
|
|
510
|
+
def __init__(self, value: tuple[CosemClassId, CosemObjectInstanceId, CosemObjectMethodId]):
|
|
511
|
+
super(CosemMethodDescriptor, self).__init__(value)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
class Data(CHOICE, Protocol):
|
|
515
|
+
TYPE = cdt.CommonDataType
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
class SelectiveAccessDescriptor(SEQUENCE):
|
|
519
|
+
""" Selective access specification always starts with an access selector, followed by an access-specific access parameter list.
|
|
520
|
+
Specified IS/IEC 62056-53 : 2006, 7.4.1.6 Selective access """
|
|
521
|
+
access_selector: Unsigned8
|
|
522
|
+
access_parameters: cdt.CommonDataType
|
|
523
|
+
ELEMENTS: tuple[SequenceElement, SequenceElement]
|
|
524
|
+
|
|
525
|
+
def __init__(self, value: tuple | bytes | None = None):
|
|
526
|
+
super(SelectiveAccessDescriptor, self).__init__(value)
|
|
527
|
+
self.access_selector.cb_post_set = self.__validate_selector
|
|
528
|
+
|
|
529
|
+
@property
|
|
530
|
+
def ELEMENTS(self) -> tuple[SequenceElement, SequenceElement]:
|
|
531
|
+
""" return elements """
|
|
532
|
+
|
|
533
|
+
def from_default(self):
|
|
534
|
+
self.values[0] = self.ELEMENTS[0].TYPE()
|
|
535
|
+
self.values[1] = self.ELEMENTS[1].TYPE.ELEMENTS[int(self.access_selector)].TYPE()
|
|
536
|
+
|
|
537
|
+
def from_bytes(self, value: bytes | bytearray):
|
|
538
|
+
self.values[0], value = get_instance_and_context(self.ELEMENTS[0].TYPE, value)
|
|
539
|
+
self.values[1] = self.ELEMENTS[1].TYPE.ELEMENTS[int(self.access_selector)].TYPE(value)
|
|
540
|
+
|
|
541
|
+
def from_tuple(self, value: tuple | list):
|
|
542
|
+
self.values[0] = self.ELEMENTS[0].TYPE(value[0])
|
|
543
|
+
self.values[1] = self.ELEMENTS[1].TYPE.ELEMENTS[int(self.access_selector)].TYPE(value[1])
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def contents(self) -> bytes:
|
|
547
|
+
return self.access_selector.contents+self.access_parameters.encoding
|
|
548
|
+
|
|
549
|
+
def __validate_selector(self):
|
|
550
|
+
self.values[1] = self.ELEMENTS[1].TYPE.ELEMENTS[int(self.access_selector)].TYPE()
|
|
551
|
+
print('change sel')
|
|
552
|
+
|
|
553
|
+
def set_selector(self, index: int, value=None):
|
|
554
|
+
""" set selector value from int type """
|
|
555
|
+
self.access_selector.set_contents_from(index)
|
|
556
|
+
if value:
|
|
557
|
+
self.access_parameters.set(value)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
class CosemAttributeDescriptorWithSelection(SEQUENCE):
|
|
561
|
+
cosem_attribute_descriptor: CosemAttributeDescriptor
|
|
562
|
+
access_selection: SelectiveAccessDescriptor
|
|
563
|
+
ELEMENTS: tuple[CosemAttributeDescriptor, SequenceElement]
|
|
564
|
+
|
|
565
|
+
def __init__(self, value: bytes | tuple | list = None):
|
|
566
|
+
super(CosemAttributeDescriptorWithSelection, self).__init__(value)
|
|
567
|
+
self.cosem_attribute_descriptor.access_selection_parameters.set_contents_from(1)
|
|
568
|
+
|
|
569
|
+
@property
|
|
570
|
+
def ELEMENTS(self) -> tuple[SequenceElement, SequenceElement]:
|
|
571
|
+
""" return elements. Need initiate in subclass """
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
class InvokeIdAndPriority(Unsigned8):
|
|
575
|
+
|
|
576
|
+
def __init__(self, value: bytes | bytearray | str | int | DigitalMixin = 0b1100_0000):
|
|
577
|
+
super(InvokeIdAndPriority, self).__init__(value)
|
|
578
|
+
if self.contents[0] & 0b00110000:
|
|
579
|
+
raise ValueError(F'For {self.__class__.__name__} set reserved bits')
|
|
580
|
+
|
|
581
|
+
@classmethod
|
|
582
|
+
def from_parameters(cls, invoke_id: int = 0,
|
|
583
|
+
service_class: int = 0,
|
|
584
|
+
priority: int = 0):
|
|
585
|
+
instance = cls()
|
|
586
|
+
instance.invoke_id = invoke_id
|
|
587
|
+
instance.service_class = service_class
|
|
588
|
+
instance.priority = priority
|
|
589
|
+
return instance
|
|
590
|
+
|
|
591
|
+
@property
|
|
592
|
+
def invoke_id(self) -> int:
|
|
593
|
+
return self.contents[0] & 0b0000_1111
|
|
594
|
+
|
|
595
|
+
@invoke_id.setter
|
|
596
|
+
def invoke_id(self, value: int):
|
|
597
|
+
if value & 0b00001111:
|
|
598
|
+
self.__dict__['contents'] = int((self.contents[0] & 0b1111_0000) | value).to_bytes(1, 'big')
|
|
599
|
+
else:
|
|
600
|
+
raise ValueError(F'Got {value} for invoke-id, expected 0..15')
|
|
601
|
+
|
|
602
|
+
@property
|
|
603
|
+
def service_class(self) -> int:
|
|
604
|
+
""" 0: Unconfirmed or 1: Confirmed bit return """
|
|
605
|
+
return (self.contents[0] >> 6) & 0b1
|
|
606
|
+
|
|
607
|
+
@service_class.setter
|
|
608
|
+
def service_class(self, value: int):
|
|
609
|
+
match value:
|
|
610
|
+
case 0 | 1: self.__dict__['contents'] = int((self.contents[0] & 0b1011_1111) | (value << 6)).to_bytes(1, 'big')
|
|
611
|
+
case _: raise ValueError(F'Got {value} for service_class, expected 0..1')
|
|
612
|
+
|
|
613
|
+
@property
|
|
614
|
+
def priority(self) -> int:
|
|
615
|
+
""" 0: Normal or 1: High bit return """
|
|
616
|
+
return (self.contents[0] >> 7) & 0b1
|
|
617
|
+
|
|
618
|
+
@priority.setter
|
|
619
|
+
def priority(self, value: int):
|
|
620
|
+
match value:
|
|
621
|
+
case 0 | 1: self.__dict__['contents'] = int((self.contents[0] & 0b0111_1111) | (value << 7)).to_bytes(1, 'big')
|
|
622
|
+
case _: raise ValueError(F'Got {value} for service_class, expected 0..1')
|
|
623
|
+
|
|
624
|
+
def __str__(self):
|
|
625
|
+
return F'priority: {"High" if self.contents[0] & 0b1000_0000 else "Normal"}, ' \
|
|
626
|
+
F'service-class: {"Confirmed" if self.contents[0] & 0b0100_0000 else "Unconfirmed"}, ' \
|
|
627
|
+
F'invoke-id: {self.invoke_id},'
|