DLMS-SPODES 0.87.16__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 -122
- 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.16.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.16.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.16.dist-info/RECORD +0 -117
- {dlms_spodes-0.87.16.dist-info → dlms_spodes-0.88.1.dist-info}/top_level.txt +0 -0
|
@@ -1,548 +1,549 @@
|
|
|
1
|
-
from typing_extensions import deprecated
|
|
2
|
-
from dataclasses import dataclass, field
|
|
3
|
-
import numpy as np
|
|
4
|
-
from struct import Struct, pack, unpack_from
|
|
5
|
-
from typing import Optional, cast, Iterator, Self
|
|
6
|
-
import re
|
|
7
|
-
from functools import cached_property
|
|
8
|
-
from .. import exceptions as exc
|
|
9
|
-
from .
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
tmp_
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
return
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
- "a.0.1.0.0.f
|
|
310
|
-
- "a.0.1.0.0.f:
|
|
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
|
-
|
|
336
|
-
current_pos
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
current_pos
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if part.startswith('!(')
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
current_pos
|
|
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
|
-
and
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
param.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
ParPattern.parse("a.0.1.0.0.f:
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1
|
+
from typing_extensions import deprecated
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
import numpy as np
|
|
4
|
+
from struct import Struct, pack, unpack_from
|
|
5
|
+
from typing import Optional, cast, Iterator, Self
|
|
6
|
+
import re
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
from .. import exceptions as exc
|
|
9
|
+
from ..types.type_alias import Obis
|
|
10
|
+
from .obis import OBIS
|
|
11
|
+
|
|
12
|
+
_pattern = re.compile("((?:\d{1,3}\.){5}\d{1,3})(?::(m?\d{1,3}))?")
|
|
13
|
+
Index = Struct("?B")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class Parameter:
|
|
18
|
+
"""
|
|
19
|
+
Parameter ::= SEQUENCE
|
|
20
|
+
{
|
|
21
|
+
ln Cosem-Object-Instance-Id
|
|
22
|
+
descriptor OPTIONAL
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
index Unsigned8
|
|
26
|
+
nest_index Unsigned16
|
|
27
|
+
piece Unsigned8
|
|
28
|
+
|
|
29
|
+
descriptor :: = CHOICE
|
|
30
|
+
{
|
|
31
|
+
attribute [0] IMPLICIT Desc
|
|
32
|
+
method [1] IMPLICIT Desc
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Desc ::= SEQUENCE
|
|
36
|
+
{
|
|
37
|
+
index
|
|
38
|
+
SEQUENCE (SIZE(255)) OF nest_index OPTIONAL
|
|
39
|
+
piece OPTIONAL
|
|
40
|
+
}
|
|
41
|
+
"""
|
|
42
|
+
_value: bytes
|
|
43
|
+
|
|
44
|
+
def __bytes__(self) -> bytes:
|
|
45
|
+
return self._value
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def parse(cls, value: str) -> Self:
|
|
49
|
+
"""create from string. Only LN, attr/meth type ddd.ddd.ddd.ddd.ddd.ddd:aaa, ex.: 0.0.1.0.0.255 """
|
|
50
|
+
if (res := _pattern.fullmatch(value)) is None:
|
|
51
|
+
raise ValueError(F"in {cls.__name__}.parse got wrong :{value:}")
|
|
52
|
+
else:
|
|
53
|
+
groups = iter(res.groups())
|
|
54
|
+
ret = bytes(map(int, next(groups).split(".")))
|
|
55
|
+
if (a := next(groups)) is not None:
|
|
56
|
+
if a.startswith('m'):
|
|
57
|
+
a = a[1:]
|
|
58
|
+
g1 = 256
|
|
59
|
+
else:
|
|
60
|
+
g1 = 0
|
|
61
|
+
ret += (g1 + int(a)).to_bytes(2)
|
|
62
|
+
return cls(ret)
|
|
63
|
+
|
|
64
|
+
@cached_property
|
|
65
|
+
def logical_name(self) -> "Parameter":
|
|
66
|
+
return self.get_attr(1)
|
|
67
|
+
|
|
68
|
+
def __eq__(self, other: object) -> bool:
|
|
69
|
+
if isinstance(other, Parameter):
|
|
70
|
+
return cast("bool", self._value == other._value)
|
|
71
|
+
return NotImplemented
|
|
72
|
+
|
|
73
|
+
def __lt__(self, other: "Parameter") -> bool:
|
|
74
|
+
"""comparing for sort method"""
|
|
75
|
+
if len(self._value) > len(other._value):
|
|
76
|
+
return True
|
|
77
|
+
else:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
def __str__(self) -> str:
|
|
81
|
+
if (l := len(self._value)) < 6:
|
|
82
|
+
return "No valid"
|
|
83
|
+
elif l == 7:
|
|
84
|
+
return "No valid Index"
|
|
85
|
+
else:
|
|
86
|
+
res = F"{".".join(map(str, self._value[:6]))}"
|
|
87
|
+
if l > 6:
|
|
88
|
+
res += F":{"m" if self.is_method() else ""}{self.i}"
|
|
89
|
+
if l > 8:
|
|
90
|
+
res += F" {"/".join(map(str, self.elements()))}"
|
|
91
|
+
if self.has_piece():
|
|
92
|
+
res += F"p{self.piece}"
|
|
93
|
+
return res
|
|
94
|
+
|
|
95
|
+
def validate(self) -> None:
|
|
96
|
+
if (length := len(self._value)) < 6:
|
|
97
|
+
raise exc.DLMSException(F"Parameter got {length=}, expected at least 6")
|
|
98
|
+
if length == 7:
|
|
99
|
+
raise exc.DLMSException(F"Parameter got wrong index")
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def has_index(self) -> bool:
|
|
103
|
+
return len(self._value) > 6
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
@deprecated("use obis")
|
|
107
|
+
def ln(self) -> bytes:
|
|
108
|
+
"""Logical Name"""
|
|
109
|
+
return self._value[:6]
|
|
110
|
+
|
|
111
|
+
def is_method(self) -> bool:
|
|
112
|
+
return self._value[6] == 1
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def i(self) -> int:
|
|
116
|
+
"""attribute or method index"""
|
|
117
|
+
return self._value[7]
|
|
118
|
+
|
|
119
|
+
def get_attr(self, i: int) -> Self:
|
|
120
|
+
"""get attribute"""
|
|
121
|
+
val = Index.pack(0, i)
|
|
122
|
+
return self.__class__(self._value[:6] + val)
|
|
123
|
+
|
|
124
|
+
def get_meth(self, i: int) -> Self:
|
|
125
|
+
"""get method"""
|
|
126
|
+
val = Index.pack(1, i)
|
|
127
|
+
return self.__class__(self._value[:6] + val)
|
|
128
|
+
|
|
129
|
+
def set_i(self, index: int, is_method: bool = False) -> "Parameter":
|
|
130
|
+
val = Index.pack(is_method, index)
|
|
131
|
+
if len(self._value) == 6:
|
|
132
|
+
tmp = self._value + val
|
|
133
|
+
else:
|
|
134
|
+
tmp_ = bytearray(self._value)
|
|
135
|
+
tmp_[6:8] = val
|
|
136
|
+
tmp = bytes(tmp_)
|
|
137
|
+
return self.__class__(tmp)
|
|
138
|
+
|
|
139
|
+
def append_validate(self) -> None:
|
|
140
|
+
if (l := len(self._value)) < 7:
|
|
141
|
+
raise exc.DLMSException(F"Parameter must has index before")
|
|
142
|
+
elif l % 2 != 0:
|
|
143
|
+
raise exc.DLMSException(F"Can't append to Parameter with piece")
|
|
144
|
+
|
|
145
|
+
def append(self, index: int) -> "Parameter":
|
|
146
|
+
"""add new sequence(array or struct) index element"""
|
|
147
|
+
self.append_validate()
|
|
148
|
+
return self.__class__(self._value + pack(">H", index))
|
|
149
|
+
|
|
150
|
+
def extend(self, *indexes: int) -> "Parameter":
|
|
151
|
+
self.append_validate()
|
|
152
|
+
return self.__class__(self._value + pack(F">{len(indexes)}H", *indexes))
|
|
153
|
+
|
|
154
|
+
def pop(self) -> tuple[Optional[int], int, "Parameter"]:
|
|
155
|
+
"""
|
|
156
|
+
:return piece, last index and parent Parameter
|
|
157
|
+
ex.: Parameter("0.0.0.0.0.0:2 1/1/1 p3") => (1, Parameter("0.0.0.0.0.0:2 1/1"))
|
|
158
|
+
"""
|
|
159
|
+
if self.has_piece():
|
|
160
|
+
return self._value[-1], int.from_bytes(self._value[-3:-1]), self.__class__(self._value[:-3])
|
|
161
|
+
else:
|
|
162
|
+
return None, int.from_bytes(self._value[-2:]), self.__class__(self._value[:-2])
|
|
163
|
+
|
|
164
|
+
def set_piece(self, index: int) -> "Parameter":
|
|
165
|
+
"""add new sequence(array or struct) index element"""
|
|
166
|
+
if len(self._value) >= 7:
|
|
167
|
+
return self.__class__(self._value + pack("B", index))
|
|
168
|
+
else:
|
|
169
|
+
raise exc.DLMSException(F"Parameter must has index before")
|
|
170
|
+
|
|
171
|
+
def has_piece(self) -> bool:
|
|
172
|
+
if (
|
|
173
|
+
(l := len(self._value)) >= 9
|
|
174
|
+
and l % 2 != 0
|
|
175
|
+
):
|
|
176
|
+
return True
|
|
177
|
+
else:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def piece(self) -> Optional[int]:
|
|
182
|
+
if self.has_piece():
|
|
183
|
+
return self._value[-1]
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
def clear_piece(self) -> "Parameter":
|
|
187
|
+
if self.has_piece():
|
|
188
|
+
return self.__class__(self._value[:-1])
|
|
189
|
+
return self
|
|
190
|
+
|
|
191
|
+
def elements(self, start: int = 0) -> Iterator[int]:
|
|
192
|
+
"""return: index elements nested in attribute, started with"""
|
|
193
|
+
for i in range(8 + start, 8 + 2 * self.n_elements, 2):
|
|
194
|
+
res = int.from_bytes(self._value[i:i + 2], "big")
|
|
195
|
+
yield res
|
|
196
|
+
|
|
197
|
+
def __iter__(self) -> Iterator[int]:
|
|
198
|
+
for it in self._value[:6]:
|
|
199
|
+
yield it
|
|
200
|
+
if self._value[6] == 1:
|
|
201
|
+
yield -self.i
|
|
202
|
+
else:
|
|
203
|
+
yield self.i
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def last_element(self) -> int:
|
|
207
|
+
""":return last element index"""
|
|
208
|
+
if self.n_elements == 0:
|
|
209
|
+
raise ValueError("Parameter hasn't elements")
|
|
210
|
+
if self.has_piece():
|
|
211
|
+
val = self._value[-3: -1]
|
|
212
|
+
else:
|
|
213
|
+
val = self._value[-2:]
|
|
214
|
+
return int.from_bytes(val, "big")
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def n_elements(self) -> int:
|
|
218
|
+
"""return: amount of elements nested in attribute"""
|
|
219
|
+
return max(0, (len(self._value) - 8) // 2)
|
|
220
|
+
|
|
221
|
+
def set(self,
|
|
222
|
+
a: Optional[int] = None,
|
|
223
|
+
b: Optional[int] = None,
|
|
224
|
+
c: Optional[int] = None,
|
|
225
|
+
d: Optional[int] = None,
|
|
226
|
+
e: Optional[int] = None,
|
|
227
|
+
f: Optional[int] = None
|
|
228
|
+
) -> "Parameter":
|
|
229
|
+
val = bytearray(self._value)
|
|
230
|
+
if a is not None:
|
|
231
|
+
val[0] = a
|
|
232
|
+
if b is not None:
|
|
233
|
+
val[1] = b
|
|
234
|
+
if c is not None:
|
|
235
|
+
val[2] = c
|
|
236
|
+
if d is not None:
|
|
237
|
+
val[3] = d
|
|
238
|
+
if e is not None:
|
|
239
|
+
val[4] = e
|
|
240
|
+
if f is not None:
|
|
241
|
+
val[5] = f
|
|
242
|
+
return self.__class__(bytes(val))
|
|
243
|
+
|
|
244
|
+
def __contains__(self, item: "Parameter") -> bool:
|
|
245
|
+
return item._value in self._value
|
|
246
|
+
|
|
247
|
+
def __getitem__(self, item: int) -> Optional[int]:
|
|
248
|
+
if self.n_elements > 0:
|
|
249
|
+
return cast(int, unpack_from(">H", self._value, item * 2 + 8)[0])
|
|
250
|
+
else:
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def a(self) -> int:
|
|
255
|
+
return self._value[0]
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def b(self) -> int:
|
|
259
|
+
return self._value[1]
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def c(self) -> int:
|
|
263
|
+
return self._value[2]
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def d(self) -> int:
|
|
267
|
+
return self._value[3]
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def e(self) -> int:
|
|
271
|
+
return self._value[4]
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def f(self) -> int:
|
|
275
|
+
return self._value[5]
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def attr(self) -> "Parameter":
|
|
279
|
+
if self.has_index:
|
|
280
|
+
return Parameter(self._value[:8])
|
|
281
|
+
else:
|
|
282
|
+
raise exc.DLMSException(F"Parameter must has index before")
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def obj(self) -> "Parameter":
|
|
286
|
+
return Parameter(self._value[:6])
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def obis(self) -> Obis:
|
|
290
|
+
return self._value[:6]
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
RANGE64 = bytes(range(65)) # Предвычисленный диапазон 0-64
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@dataclass(eq=False, frozen=True)
|
|
297
|
+
class ParPattern:
|
|
298
|
+
value: bytes
|
|
299
|
+
positions: tuple[int, ...]
|
|
300
|
+
"7 elements , -1 is SKIP, last element is attribute index if positive and method index else negative"
|
|
301
|
+
|
|
302
|
+
def __post_init__(self) -> None:
|
|
303
|
+
if 6 > len(self.positions) > 7:
|
|
304
|
+
raise ValueError(f"positions must have exactly 6 elements, got {len(self.positions)}")
|
|
305
|
+
|
|
306
|
+
@classmethod
|
|
307
|
+
def parse(cls, pattern: str) -> "ParPattern":
|
|
308
|
+
"""Парсинг строк вида:
|
|
309
|
+
- "a.0.(1,2,3).(0-64).0.f" (6 элементов)
|
|
310
|
+
- "a.0.1.0.0.f:2" (6 элементов + индекс через :)
|
|
311
|
+
- "a.0.1.0.0.f:m2" (6 элементов + метод через :)
|
|
312
|
+
"""
|
|
313
|
+
# Разделяем основную часть и индекс
|
|
314
|
+
if ':' in pattern:
|
|
315
|
+
main_part, index_part = pattern.rsplit(':', 1)
|
|
316
|
+
is_method = index_part.startswith('m')
|
|
317
|
+
if is_method:
|
|
318
|
+
index_part = index_part[1:]
|
|
319
|
+
else:
|
|
320
|
+
main_part = pattern
|
|
321
|
+
index_part = None
|
|
322
|
+
|
|
323
|
+
# Парсим основную часть (6 элементов)
|
|
324
|
+
parts = main_part.split('.', maxsplit=5)
|
|
325
|
+
if len(parts) < 6:
|
|
326
|
+
parts.extend(['f'] * (6 - len(parts))) # Дополняем до 6 элементов
|
|
327
|
+
|
|
328
|
+
value_parts = []
|
|
329
|
+
positions = []
|
|
330
|
+
current_pos = 0
|
|
331
|
+
|
|
332
|
+
for i, part in enumerate(parts[:6]): # Ровно 6 элементов
|
|
333
|
+
if len(part)==1 and ord(part)==97 + i: # a-f
|
|
334
|
+
if part=='b':
|
|
335
|
+
value_parts.append(RANGE64)
|
|
336
|
+
positions.append(current_pos)
|
|
337
|
+
current_pos += len(RANGE64)
|
|
338
|
+
else:
|
|
339
|
+
positions.append(-1) # SKIP
|
|
340
|
+
continue
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
if part.isdigit(): # Простое число
|
|
344
|
+
num = int(part)
|
|
345
|
+
if not 0 <= num <= 255:
|
|
346
|
+
raise ValueError
|
|
347
|
+
value_parts.append(bytes([num]))
|
|
348
|
+
positions.append(current_pos)
|
|
349
|
+
current_pos += 1
|
|
350
|
+
elif part.startswith(('(', '!(')) and part.endswith(')'):
|
|
351
|
+
elements = cls._parse_group(part[2 if part.startswith('!(') else 1:-1])
|
|
352
|
+
if part.startswith('!('):
|
|
353
|
+
elements = set(range(256)) - elements
|
|
354
|
+
value_parts.append(bytes(elements))
|
|
355
|
+
positions.append(current_pos)
|
|
356
|
+
current_pos += len(value_parts[-1])
|
|
357
|
+
else:
|
|
358
|
+
raise ValueError
|
|
359
|
+
except ValueError:
|
|
360
|
+
raise ValueError(f"Invalid pattern part: {part} at position {i}")
|
|
361
|
+
|
|
362
|
+
# Добавляем индекс (7-й элемент)
|
|
363
|
+
if index_part is not None:
|
|
364
|
+
try:
|
|
365
|
+
index_val = int(index_part)
|
|
366
|
+
if not 0 <= index_val <= 255:
|
|
367
|
+
raise ValueError
|
|
368
|
+
# Упаковываем: старший бит = is_method, остальные 7 бит = значение
|
|
369
|
+
packed = bytes([(0x80 if is_method else 0) | (index_val & 0x7F)])
|
|
370
|
+
value_parts.append(packed)
|
|
371
|
+
positions.append(current_pos)
|
|
372
|
+
except ValueError:
|
|
373
|
+
raise ValueError(f"Invalid index value: {index_part}")
|
|
374
|
+
else:
|
|
375
|
+
positions.append(-1) # SKIP для 7-го элемента
|
|
376
|
+
|
|
377
|
+
return cls(
|
|
378
|
+
value=b''.join(value_parts),
|
|
379
|
+
positions=tuple(positions)
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
@staticmethod
|
|
383
|
+
def _parse_group(group_str: str) -> set[int]:
|
|
384
|
+
"""Парсинг групп вида '1,2,3' или '10-20'"""
|
|
385
|
+
elements: set[int] = set()
|
|
386
|
+
for item in group_str.split(','):
|
|
387
|
+
item = item.strip()
|
|
388
|
+
if '-' in item:
|
|
389
|
+
start, end = map(int, item.split('-'))
|
|
390
|
+
elements.update(range(start, end + 1))
|
|
391
|
+
else:
|
|
392
|
+
elements.add(int(item))
|
|
393
|
+
return elements
|
|
394
|
+
|
|
395
|
+
# @functools.cache
|
|
396
|
+
def __hash__(self) -> int:
|
|
397
|
+
print("hash")
|
|
398
|
+
hash_parts: list[Optional[bytes]] = []
|
|
399
|
+
for pos in self.positions:
|
|
400
|
+
if pos == -1:
|
|
401
|
+
hash_parts.append(None)
|
|
402
|
+
else:
|
|
403
|
+
hash_parts.append(self.value[pos:pos + 1])
|
|
404
|
+
return hash(tuple(hash_parts))
|
|
405
|
+
|
|
406
|
+
def __eq__(self, other: object) -> bool:
|
|
407
|
+
print("eq")
|
|
408
|
+
if not isinstance(other, ParPattern):
|
|
409
|
+
return False
|
|
410
|
+
|
|
411
|
+
for i in range(6):
|
|
412
|
+
pos_self = self.positions[i]
|
|
413
|
+
pos_other = other.positions[i]
|
|
414
|
+
|
|
415
|
+
# Проверка соответствия схемы позиций
|
|
416
|
+
if (pos_self == -1) != (pos_other == -1):
|
|
417
|
+
return False
|
|
418
|
+
|
|
419
|
+
if pos_self != -1:
|
|
420
|
+
# Сравнение значений
|
|
421
|
+
val_self = self.value[pos_self]
|
|
422
|
+
val_other = other.value[pos_other]
|
|
423
|
+
if val_self != val_other:
|
|
424
|
+
return False
|
|
425
|
+
return True
|
|
426
|
+
|
|
427
|
+
def __str__(self) -> str:
|
|
428
|
+
parts = []
|
|
429
|
+
group_chars = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f'}
|
|
430
|
+
for i in range(6):
|
|
431
|
+
pos = self.positions[i]
|
|
432
|
+
if pos == -1:
|
|
433
|
+
parts.append(group_chars[i])
|
|
434
|
+
continue
|
|
435
|
+
if i < 5:
|
|
436
|
+
next_pos = self.positions[i + 1] if self.positions[i + 1] != -1 else len(self.value)
|
|
437
|
+
else:
|
|
438
|
+
next_pos = len(self.value)
|
|
439
|
+
length = next_pos - pos
|
|
440
|
+
val = self.value[pos]
|
|
441
|
+
if (
|
|
442
|
+
i == 1
|
|
443
|
+
and length == 65
|
|
444
|
+
and self.value[pos:pos + 65] == RANGE64
|
|
445
|
+
):
|
|
446
|
+
parts.append('(0-64)')
|
|
447
|
+
continue
|
|
448
|
+
if length > 1:
|
|
449
|
+
values = list(self.value[pos:next_pos])
|
|
450
|
+
ranges = []
|
|
451
|
+
start = values[0]
|
|
452
|
+
prev = start
|
|
453
|
+
for v in values[1:]:
|
|
454
|
+
if v != prev + 1:
|
|
455
|
+
if start == prev:
|
|
456
|
+
ranges.append(str(start))
|
|
457
|
+
else:
|
|
458
|
+
ranges.append(f"{start}-{prev}")
|
|
459
|
+
start = v
|
|
460
|
+
prev = v
|
|
461
|
+
if start == prev:
|
|
462
|
+
ranges.append(str(start))
|
|
463
|
+
else:
|
|
464
|
+
ranges.append(f"{start}-{prev}")
|
|
465
|
+
if (
|
|
466
|
+
len(ranges) == 1
|
|
467
|
+
and '-' in ranges[0]
|
|
468
|
+
):
|
|
469
|
+
parts.append(f"({ranges[0]})")
|
|
470
|
+
else:
|
|
471
|
+
parts.append(f"({','.join(ranges)})")
|
|
472
|
+
else:
|
|
473
|
+
parts.append(str(val))
|
|
474
|
+
return '.'.join(parts)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@dataclass(frozen=True)
|
|
478
|
+
class PatternMatcher:
|
|
479
|
+
"""Класс для быстрого сопоставления Parameter с ParPattern"""
|
|
480
|
+
patterns: dict['ParPattern', str]
|
|
481
|
+
_masks: list[tuple[tuple[np.ndarray, ...], str]] = field(init=False)
|
|
482
|
+
|
|
483
|
+
def __post_init__(self) -> None:
|
|
484
|
+
# Предварительная обработка шаблонов
|
|
485
|
+
masks = []
|
|
486
|
+
for pattern, value in self.patterns.items():
|
|
487
|
+
pattern_masks = []
|
|
488
|
+
for i in range(7): # Для всех 7 элементов
|
|
489
|
+
if pattern.positions[i]==-1: # SKIP
|
|
490
|
+
mask = np.ones(256, dtype=bool) # Все значения подходят
|
|
491
|
+
else:
|
|
492
|
+
start = pattern.positions[i]
|
|
493
|
+
if i < 6: # Первые 6 элементов (a-f)
|
|
494
|
+
end = start + 1
|
|
495
|
+
values = pattern.value[start:end]
|
|
496
|
+
else: # 7-й элемент (индекс)
|
|
497
|
+
end = start + 1
|
|
498
|
+
values = pattern.value[start:end]
|
|
499
|
+
# Для методов учитываем старший бит
|
|
500
|
+
if values[0] & 0x80:
|
|
501
|
+
values = bytes([values[0] & 0x7F])
|
|
502
|
+
|
|
503
|
+
mask = np.zeros(256, dtype=bool)
|
|
504
|
+
for v in values:
|
|
505
|
+
mask[v] = True
|
|
506
|
+
pattern_masks.append(mask)
|
|
507
|
+
masks.append((tuple(pattern_masks), value))
|
|
508
|
+
object.__setattr__(self, '_masks', masks)
|
|
509
|
+
|
|
510
|
+
def find_match(self, param: 'Parameter') -> str:
|
|
511
|
+
"""Находит первое совпадение параметра с шаблоном"""
|
|
512
|
+
param_values = (
|
|
513
|
+
param.a, param.b, param.c, param.d, param.e, param.f,
|
|
514
|
+
param.i if param.has_index else -1
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
for masks, value in self._masks:
|
|
518
|
+
match = True
|
|
519
|
+
for i in range(7):
|
|
520
|
+
val = param_values[i]
|
|
521
|
+
if val==-1: # Нет значения (для 7-го элемента)
|
|
522
|
+
if masks[i].any(): # Если в шаблоне требуется конкретное значение
|
|
523
|
+
match = False
|
|
524
|
+
break
|
|
525
|
+
elif not masks[i][val]:
|
|
526
|
+
match = False
|
|
527
|
+
break
|
|
528
|
+
if match:
|
|
529
|
+
return value
|
|
530
|
+
return None
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
if __name__ == "__main__":
|
|
534
|
+
# Создаем тестовые шаблоны
|
|
535
|
+
patterns = {
|
|
536
|
+
ParPattern.parse("a.0.(1,2,3).(0-64).0.f:2"): "Pattern1",
|
|
537
|
+
ParPattern.parse("a.0.1.0.0.f:m2"): "Pattern2",
|
|
538
|
+
# ... другие шаблоны
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
# Создаем матчер
|
|
542
|
+
matcher = PatternMatcher(patterns)
|
|
543
|
+
|
|
544
|
+
# Тестовый параметр
|
|
545
|
+
param = Parameter.parse("0.0.1.0.0.255:2")
|
|
546
|
+
|
|
547
|
+
# Поиск совпадения
|
|
548
|
+
result = matcher.find_match(param)
|
|
548
549
|
print(f"Found: {result}") # Выведет "Pattern1"
|