DLMS-SPODES 0.87.13__py3-none-any.whl → 0.87.16__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.
Files changed (100) hide show
  1. DLMS_SPODES/Values/EN/__init__.py +1 -1
  2. DLMS_SPODES/Values/EN/actors.py +8 -8
  3. DLMS_SPODES/Values/EN/relation_to_obis_names.py +387 -387
  4. DLMS_SPODES/Values/RU/__init__.py +1 -1
  5. DLMS_SPODES/Values/RU/actors.py +8 -8
  6. DLMS_SPODES/Values/RU/relation_to_obis_names.py +396 -396
  7. DLMS_SPODES/__init__.py +6 -6
  8. DLMS_SPODES/configEN.ini +126 -126
  9. DLMS_SPODES/config_parser.py +53 -53
  10. DLMS_SPODES/cosem_interface_classes/__class_init__.py +3 -3
  11. DLMS_SPODES/cosem_interface_classes/__init__.py +1 -1
  12. DLMS_SPODES/cosem_interface_classes/a_parameter.py +20 -20
  13. DLMS_SPODES/cosem_interface_classes/activity_calendar.py +254 -254
  14. DLMS_SPODES/cosem_interface_classes/arbitrator.py +105 -105
  15. DLMS_SPODES/cosem_interface_classes/association_ln/abstract.py +34 -34
  16. DLMS_SPODES/cosem_interface_classes/association_ln/authentication_mechanism_name.py +25 -25
  17. DLMS_SPODES/cosem_interface_classes/association_ln/mechanism_id.py +25 -25
  18. DLMS_SPODES/cosem_interface_classes/association_ln/method.py +5 -5
  19. DLMS_SPODES/cosem_interface_classes/association_ln/ver0.py +485 -485
  20. DLMS_SPODES/cosem_interface_classes/association_ln/ver1.py +133 -133
  21. DLMS_SPODES/cosem_interface_classes/association_ln/ver2.py +36 -36
  22. DLMS_SPODES/cosem_interface_classes/association_ln/ver3.py +4 -4
  23. DLMS_SPODES/cosem_interface_classes/association_sn/ver0.py +12 -12
  24. DLMS_SPODES/cosem_interface_classes/attr_indexes.py +12 -12
  25. DLMS_SPODES/cosem_interface_classes/clock.py +131 -131
  26. DLMS_SPODES/cosem_interface_classes/collection.py +2122 -2122
  27. DLMS_SPODES/cosem_interface_classes/cosem_interface_class.py +583 -583
  28. DLMS_SPODES/cosem_interface_classes/data.py +21 -21
  29. DLMS_SPODES/cosem_interface_classes/demand_register/ver0.py +59 -59
  30. DLMS_SPODES/cosem_interface_classes/disconnect_control.py +74 -74
  31. DLMS_SPODES/cosem_interface_classes/extended_register.py +27 -27
  32. DLMS_SPODES/cosem_interface_classes/gprs_modem_setup.py +43 -43
  33. DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver0.py +103 -103
  34. DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver1.py +40 -40
  35. DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver2.py +9 -9
  36. DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver0.py +11 -11
  37. DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver1.py +53 -53
  38. DLMS_SPODES/cosem_interface_classes/iec_local_port_setup.py +11 -11
  39. DLMS_SPODES/cosem_interface_classes/image_transfer/image_transfer_status.py +15 -15
  40. DLMS_SPODES/cosem_interface_classes/image_transfer/ver0.py +126 -126
  41. DLMS_SPODES/cosem_interface_classes/implementations/__init__.py +3 -3
  42. DLMS_SPODES/cosem_interface_classes/implementations/arbitrator.py +19 -19
  43. DLMS_SPODES/cosem_interface_classes/implementations/data.py +487 -487
  44. DLMS_SPODES/cosem_interface_classes/implementations/profile_generic.py +83 -83
  45. DLMS_SPODES/cosem_interface_classes/ipv4_setup.py +72 -72
  46. DLMS_SPODES/cosem_interface_classes/limiter.py +111 -111
  47. DLMS_SPODES/cosem_interface_classes/ln_pattern.py +333 -333
  48. DLMS_SPODES/cosem_interface_classes/modem_configuration/ver0.py +65 -65
  49. DLMS_SPODES/cosem_interface_classes/modem_configuration/ver1.py +39 -39
  50. DLMS_SPODES/cosem_interface_classes/ntp_setup/ver0.py +67 -67
  51. DLMS_SPODES/cosem_interface_classes/obis.py +23 -23
  52. DLMS_SPODES/cosem_interface_classes/overview.py +197 -197
  53. DLMS_SPODES/cosem_interface_classes/parameter.py +547 -547
  54. DLMS_SPODES/cosem_interface_classes/parameters.py +172 -172
  55. DLMS_SPODES/cosem_interface_classes/profile_generic/ver0.py +122 -122
  56. DLMS_SPODES/cosem_interface_classes/profile_generic/ver1.py +277 -277
  57. DLMS_SPODES/cosem_interface_classes/push_setup/ver0.py +12 -12
  58. DLMS_SPODES/cosem_interface_classes/push_setup/ver1.py +10 -10
  59. DLMS_SPODES/cosem_interface_classes/push_setup/ver2.py +166 -166
  60. DLMS_SPODES/cosem_interface_classes/register.py +45 -45
  61. DLMS_SPODES/cosem_interface_classes/register_activation/ver0.py +80 -80
  62. DLMS_SPODES/cosem_interface_classes/register_monitor.py +46 -46
  63. DLMS_SPODES/cosem_interface_classes/reports.py +70 -70
  64. DLMS_SPODES/cosem_interface_classes/schedule.py +176 -176
  65. DLMS_SPODES/cosem_interface_classes/script_table.py +87 -87
  66. DLMS_SPODES/cosem_interface_classes/security_setup/ver0.py +68 -68
  67. DLMS_SPODES/cosem_interface_classes/security_setup/ver1.py +158 -158
  68. DLMS_SPODES/cosem_interface_classes/single_action_schedule.py +50 -50
  69. DLMS_SPODES/cosem_interface_classes/special_days_table.py +84 -84
  70. DLMS_SPODES/cosem_interface_classes/tcp_udp_setup.py +42 -42
  71. DLMS_SPODES/cosem_pdu.py +93 -93
  72. DLMS_SPODES/enums.py +625 -625
  73. DLMS_SPODES/exceptions.py +106 -106
  74. DLMS_SPODES/firmwares.py +99 -99
  75. DLMS_SPODES/hdlc/frame.py +875 -875
  76. DLMS_SPODES/hdlc/sub_layer.py +54 -54
  77. DLMS_SPODES/literals.py +17 -17
  78. DLMS_SPODES/obis/__init__.py +1 -1
  79. DLMS_SPODES/obis/media_id.py +931 -931
  80. DLMS_SPODES/pardata.py +22 -22
  81. DLMS_SPODES/pdu_enums.py +98 -98
  82. DLMS_SPODES/relation_to_OBIS.py +465 -463
  83. DLMS_SPODES/settings.py +551 -551
  84. DLMS_SPODES/types/choices.py +142 -142
  85. DLMS_SPODES/types/common_data_types.py +2401 -2401
  86. DLMS_SPODES/types/cosem_service_types.py +109 -109
  87. DLMS_SPODES/types/implementations/arrays.py +25 -25
  88. DLMS_SPODES/types/implementations/bitstrings.py +97 -97
  89. DLMS_SPODES/types/implementations/double_long_usingneds.py +35 -35
  90. DLMS_SPODES/types/implementations/enums.py +57 -57
  91. DLMS_SPODES/types/implementations/integers.py +11 -11
  92. DLMS_SPODES/types/implementations/long_unsigneds.py +127 -127
  93. DLMS_SPODES/types/implementations/octet_string.py +11 -11
  94. DLMS_SPODES/types/implementations/structs.py +64 -64
  95. DLMS_SPODES/types/useful_types.py +677 -677
  96. {dlms_spodes-0.87.13.dist-info → dlms_spodes-0.87.16.dist-info}/METADATA +30 -30
  97. dlms_spodes-0.87.16.dist-info/RECORD +117 -0
  98. {dlms_spodes-0.87.13.dist-info → dlms_spodes-0.87.16.dist-info}/WHEEL +1 -1
  99. dlms_spodes-0.87.13.dist-info/RECORD +0 -117
  100. {dlms_spodes-0.87.13.dist-info → dlms_spodes-0.87.16.dist-info}/top_level.txt +0 -0
@@ -1,2122 +1,2122 @@
1
- """ The OBIS identification system serves as a basis for the COSEM logical names. The system of naming COSEM objects is defined in the basic
2
- principles (see Clause 4 EN 62056-62:2007), the identification of real data items is specified in IEC 62056-61. The following clauses define the
3
- usage of those definitions in the COSEM environment. All codes, which are not explicitly listed, but outside the manufacturer specific range are
4
- reserved for future use."""
5
- from struct import pack
6
- import inspect
7
- from dataclasses import dataclass
8
- from itertools import count, chain
9
- from functools import reduce, cached_property, lru_cache
10
- from typing import TypeAlias, Iterator, Type, Self, Callable, Literal, Iterable, Optional, Hashable, Protocol, cast, Annotated
11
- from semver import Version as SemVer
12
- from StructResult import result
13
- from ..types import common_data_types as cdt, cosem_service_types as cst, useful_types as ut
14
- from ..types.implementations import structs, enums, octet_string
15
- from . import cosem_interface_class as ic
16
- from .ln_pattern import LNPattern, LNPatterns
17
- from .activity_calendar import ActivityCalendar, DayProfileAction
18
- from .arbitrator import Arbitrator
19
- from .association_ln import mechanism_id
20
- from .association_sn.ver0 import AssociationSN as AssociationSNVer0
21
- from .association_ln.ver0 import AssociationLN as AssociationLNVer0, ObjectListElement
22
- from .association_ln.ver1 import AssociationLN as AssociationLNVer1
23
- from .association_ln.ver2 import AssociationLN as AssociationLNVer2
24
- from .push_setup.ver0 import PushSetup as PushSetupVer0
25
- from .push_setup.ver1 import PushSetup as PushSetupVer1
26
- from .push_setup.ver2 import PushSetup as PushSetupVer2
27
- from .clock import Clock
28
- from .data import Data
29
- from .disconnect_control import DisconnectControl
30
- from .gprs_modem_setup import GPRSModemSetup
31
- from .gsm_diagnostic.ver0 import GSMDiagnostic as GSMDiagnosticVer0
32
- from .gsm_diagnostic.ver1 import GSMDiagnostic as GSMDiagnosticVer1
33
- from .gsm_diagnostic.ver2 import GSMDiagnostic as GSMDiagnosticVer2
34
- from .iec_hdlc_setup.ver0 import IECHDLCSetup as IECHDLCSetupVer0
35
- from .iec_hdlc_setup.ver1 import IECHDLCSetup as IECHDLCSetupVer1
36
- from .image_transfer.ver0 import ImageTransfer
37
- from .ipv4_setup import IPv4Setup
38
- from .modem_configuration.ver0 import PSTNModemConfiguration
39
- from .modem_configuration.ver1 import ModemConfigurationVer1
40
- from .limiter import Limiter
41
- from .ntp_setup.ver0 import NTPSetup
42
- from .profile_generic.ver0 import ProfileGeneric as ProfileGenericVer0
43
- from .profile_generic.ver1 import ProfileGeneric as ProfileGenericVer1
44
- from .register import Register
45
- from .extended_register import ExtendedRegister
46
- from .demand_register.ver0 import DemandRegister as DemandRegisterVer0
47
- from .register_activation.ver0 import RegisterActivation
48
- from .register_monitor import RegisterMonitor
49
- from .schedule import Schedule
50
- from .security_setup.ver0 import SecuritySetup as SecuritySetupVer0
51
- from .security_setup.ver1 import SecuritySetup as SecuritySetupVer1
52
- from .script_table import ScriptTable
53
- from .single_action_schedule import SingleActionSchedule
54
- from .special_days_table import SpecialDaysTable
55
- from .tcp_udp_setup import TCPUDPSetup
56
- from .. import exceptions as exc
57
- from ..relation_to_OBIS import get_name
58
- from ..cosem_interface_classes import implementations as impl
59
- from ..cosem_interface_classes.overview import ClassID, CountrySpecificIdentifiers
60
- from . import obis as o, ln_pattern
61
- from .. import pdu_enums as pdu
62
- from ..config_parser import config, get_message
63
- from ..obis import media_id
64
- from .parameter import Parameter
65
- from typing_extensions import deprecated, override
66
- from ..settings import settings
67
-
68
-
69
- class CollectionMapError(exc.DLMSException):
70
- """"""
71
-
72
-
73
- LNContaining: TypeAlias = bytes | str | cst.LogicalName | cdt.Structure | ut.CosemObjectInstanceId | ut.CosemAttributeDescriptor | ut.CosemAttributeDescriptorWithSelection \
74
- | ut.CosemMethodDescriptor
75
-
76
- AssociationSN: TypeAlias = AssociationSNVer0
77
- AssociationLN: TypeAlias = AssociationLNVer0 | AssociationLNVer1 | AssociationLNVer2
78
- ModemConfiguration: TypeAlias = PSTNModemConfiguration | ModemConfigurationVer1
79
- SecuritySetup: TypeAlias = SecuritySetupVer0 | SecuritySetupVer1
80
- PushSetup: TypeAlias = PushSetupVer0 | PushSetupVer1 | PushSetupVer2
81
- ProfileGeneric: TypeAlias = ProfileGenericVer1
82
- DemandRegister: TypeAlias = DemandRegisterVer0
83
- IECHDLCSetup: TypeAlias = IECHDLCSetupVer0 | IECHDLCSetupVer1
84
- GSMDiagnostic: TypeAlias = GSMDiagnosticVer0 | GSMDiagnosticVer1 | GSMDiagnosticVer2
85
- InterfaceClass: TypeAlias = Data | Register | ExtendedRegister | DemandRegister | ProfileGeneric | Clock | ScriptTable | Schedule | SpecialDaysTable | ActivityCalendar | \
86
- SingleActionSchedule | AssociationLN | IECHDLCSetup | DisconnectControl | Limiter | ModemConfiguration | PSTNModemConfiguration | ImageTransfer | \
87
- GPRSModemSetup | GSMDiagnostic | SecuritySetup | TCPUDPSetup | IPv4Setup | Arbitrator | RegisterMonitor | PushSetup | AssociationSN | \
88
- NTPSetup
89
-
90
-
91
- type AttributeIndex = int
92
- UsedAttributes: TypeAlias = dict[cst.LogicalName, set[AttributeIndex]]
93
-
94
-
95
- ObjectTreeMode: TypeAlias = Literal["", "m", "g", "c", "mc", "cm", "gm", "gc", "cg", "gmc"]
96
- SortMode: TypeAlias = Literal["l", "n", "c", "cl", "cn"]
97
-
98
-
99
- # todo: make new class ClassMap(for field Collection.spec_map). fields: name, version, dict(current version ClassMap)
100
- class ClassMap:
101
-
102
- def __init__(self, *values: type[InterfaceClass]):
103
- self._values = values
104
-
105
- def __hash__(self) -> int:
106
- return hash(it.hash_ for it in self._values)
107
-
108
- def get(self, ver: int) -> type[InterfaceClass]:
109
- if (ver := int(ver)) < len(self._values):
110
- return self._values[ver]
111
- raise RuntimeError(f"got {ver=}, expected maximal={len(self._values)}")
112
-
113
- def renew(self, ver: int, cls_: type[InterfaceClass]) -> Self:
114
- """return with one change"""
115
- if ver < (l := len(self._values)):
116
- tmp = list(self._values)
117
- tmp.insert(ver, cls_)
118
- return self.__class__(*tmp)
119
- if ver == l:
120
- return self.__class__(*(self._values + (cls_,)))
121
- raise RuntimeError(f"got {ver=}, expected maximal={len(self._values)}")
122
-
123
- def __str__(self) -> str:
124
- return f"{self._values[0].CLASS_ID}[{len(self._values)}]"
125
-
126
-
127
- DataMap = ClassMap(Data)
128
- DataStaticMap = ClassMap(impl.data.DataStatic)
129
- DataDynamicMap = ClassMap(impl.data.DataDynamic)
130
- RegisterMap = ClassMap(Register)
131
- ExtendedRegisterMap = ClassMap(ExtendedRegister)
132
- DemandRegisterMap = ClassMap(DemandRegisterVer0)
133
- RegisterActivationMap = ClassMap(RegisterActivation)
134
- ProfileGenericMap = ClassMap(ProfileGenericVer0, ProfileGenericVer1)
135
- ClockMap = ClassMap(Clock)
136
- ScriptTableMap = ClassMap(ScriptTable)
137
- ScheduleMap = ClassMap(Schedule)
138
- SpecialDaysTableMap = ClassMap(SpecialDaysTable)
139
- AssociationSNMap = ClassMap(AssociationSNVer0)
140
- AssociationLNMap = ClassMap(AssociationLNVer0, AssociationLNVer1, AssociationLNVer2)
141
- ImageTransferMap = ClassMap(ImageTransfer)
142
- ActivityCalendarMap = ClassMap(ActivityCalendar)
143
- RegisterMonitorMap = ClassMap(RegisterMonitor)
144
- SingleActionScheduleMap = ClassMap(SingleActionSchedule)
145
- IECHDLCSetupMap = ClassMap(IECHDLCSetupVer0, IECHDLCSetupVer1)
146
- ModemConfigurationMap = ClassMap(PSTNModemConfiguration, ModemConfigurationVer1)
147
- TCPUDPSetupMap = ClassMap(TCPUDPSetup)
148
- IPv4SetupMap = ClassMap(IPv4Setup)
149
- GPRSModemSetupMap = ClassMap(GPRSModemSetup)
150
- GSMDiagnosticMap = ClassMap(GSMDiagnosticVer0, GSMDiagnosticVer1, GSMDiagnosticVer2)
151
- PushSetupMap = ClassMap(PushSetupVer0, PushSetupVer1, PushSetupVer2)
152
- SecuritySetupMap = ClassMap(SecuritySetupVer0, SecuritySetupVer1)
153
- ArbitratorMap = ClassMap(Arbitrator)
154
- DisconnectControlMap = ClassMap(DisconnectControl)
155
- LimiterMap = ClassMap(Limiter)
156
- NTPSetupMap = ClassMap(NTPSetup)
157
-
158
- # implementation ClassMap
159
- UnsignedDataMap = ClassMap(impl.data.Unsigned)
160
-
161
- LN_C: TypeAlias = int
162
- LN_D: TypeAlias = int
163
-
164
-
165
- common_interface_class_map: dict[int, ClassMap] = {
166
- 1: DataMap,
167
- 3: RegisterMap,
168
- 4: ExtendedRegisterMap,
169
- 5: DemandRegisterMap,
170
- 6: RegisterActivationMap,
171
- 7: ProfileGenericMap,
172
- 8: ClockMap,
173
- 9: ScriptTableMap,
174
- 10: ScheduleMap,
175
- 11: SpecialDaysTableMap,
176
- 15: AssociationLNMap,
177
- 18: ImageTransferMap,
178
- 20: ActivityCalendarMap,
179
- 21: RegisterMonitorMap,
180
- 22: SingleActionScheduleMap,
181
- 23: IECHDLCSetupMap,
182
- 27: ModemConfigurationMap,
183
- 41: TCPUDPSetupMap,
184
- 42: IPv4SetupMap,
185
- 45: GPRSModemSetupMap,
186
- 47: GSMDiagnosticMap,
187
- 64: SecuritySetupMap,
188
- 68: ArbitratorMap,
189
- 70: DisconnectControlMap,
190
- 71: LimiterMap,
191
- 100: NTPSetupMap,
192
- }
193
-
194
-
195
- def get_interface_class(class_map: dict[int, ClassMap], c_id: ut.CosemClassId, ver: cdt.Unsigned) -> type[InterfaceClass]:
196
- """new version <get_type_from_class>"""
197
- ret = class_map.get(int(c_id), None)
198
- if isinstance(ret, ClassMap):
199
- return ret.get(int(ver))
200
- else:
201
- if int(c_id) not in common_interface_class_map.keys():
202
- raise CollectionMapError(F"unknown {c_id=}")
203
- else:
204
- raise CollectionMapError(F"got {c_id=}, expected {', '.join(map(str, class_map.keys()))}")
205
-
206
-
207
- _CUMULATIVE = (1, 2, 11, 12, 21, 22)
208
- _MAX_MIN_VALUES = (3, 6, 13, 16, 26, 51, 52, 53, 54)
209
- _CURRENT_AND_LAST_AVERAGE_VALUES = (0, 4, 5, 14, 15, 24, 25, 27, 28, 49, 50, 55, 56)
210
- _INSTANTANEOUS_VALUES = (7, 39, 41, 42)
211
- _TIME_INTEGRAL_VALUES = (8, 9, 10, 17, 18, 19, 20, 29, 30, 58)
212
- _OCCURRENCE_COUNTER = 40
213
- _CONTRACTED_VALUES = (46,)
214
- _UNDER_OVER_LIMIT_THRESHOLDS = (31, 35, 43, 44)
215
- _UNDER_OVER_LIMIT_OCCURRENCE_COUNTERS = (32, 36)
216
- _UNDER_OVER_LIMIT_DURATIONS = (33, 37)
217
- _UNDER_OVER_LIMIT_MAGNITUDES = (34, 38)
218
- _NOT_PROCESSING_OF_MEASUREMENT_VALUES = tuple(set(range(256)).difference((0, 93, 94, 96, 97, 98, 99))) # BlueBook DLMS UA 1000-1 Ed.14 7.5.2.1 Table 66
219
- _RU_CHANGE_LIMIT_LEVEL = 134
220
-
221
-
222
- @lru_cache()
223
- def _create_map(maps: ClassMap | tuple[ClassMap]) -> dict[int, ClassMap]:
224
- if isinstance(maps, tuple):
225
- return {int(map_.get(0).CLASS_ID): map_ for map_ in maps}
226
- else:
227
- return {int(maps.get(0).CLASS_ID): maps}
228
-
229
-
230
- A: TypeAlias = int
231
- B: TypeAlias = int
232
- C: TypeAlias = int
233
- D: TypeAlias = int
234
- E: TypeAlias = int
235
-
236
-
237
- class Xgroup(Protocol):
238
- fmt: str
239
-
240
- def get_key(self) -> Iterator[bytes]:
241
- ...
242
-
243
-
244
- class SimpleGroup(Xgroup, Protocol):
245
- def get_key(self) -> Iterator[bytes]:
246
- yield pack(self.fmt, *self) # type: ignore[misc]
247
-
248
-
249
- class ACgroup(SimpleGroup, tuple[A, C]):
250
- """Grouping of OBIS codes by group A (media) and group C (attribute).
251
- Creates a composite key for objects sharing the same media type and attribute.
252
- """
253
- fmt = ">BB"
254
-
255
-
256
- class ACDgroup_(Xgroup, Protocol):
257
- """Grouping of OBIS codes by groups A (media), C (attribute), and D (data) with mask support.
258
- Supports exact values or masks for attribute (C) and data (D) groups.
259
- Allows flexible grouping by media type with variable attribute and data patterns.
260
- """
261
- fmt: str = ">BBB"
262
-
263
-
264
- class ACDgroup(SimpleGroup, ACDgroup_, tuple[A, C, D]):
265
- ...
266
-
267
-
268
- class ACCDgroup(ACDgroup_, tuple[A, tuple[C, ...], D]):
269
- def get_key(self) -> Iterator[bytes]:
270
- a, _, d = self
271
- return (pack(self.fmt, a, c, d) for c in self[1])
272
-
273
-
274
- class ACDDgroup(ACDgroup_, tuple[A, C, tuple[D, ...]]):
275
- def get_key(self) -> Iterator[bytes]:
276
- a, c, _ = self
277
- return (pack(self.fmt, a, c, d) for d in self[2])
278
-
279
-
280
- class ACCDDgroup(ACDgroup_, tuple[A, tuple[C, ...], tuple[D, ...]]):
281
- """Grouping of OBIS codes by groups A (media), C (attribute), and D (data) with mask support.
282
- Supports exact values or masks for attribute (C) and data (D) groups.
283
- Allows flexible grouping by media type with variable attribute and data patterns.
284
- """
285
- def get_key(self) -> Iterator[bytes]:
286
- a = self[0]
287
- return (pack(self.fmt, a, c, d) for c in self[1] for d in self[2])
288
-
289
-
290
- class ACDEgroup_(Xgroup, Protocol):
291
- """Grouping of OBIS codes by groups A (media), C (attribute), D (data), and E (data version) with mask support.
292
- Groups by fixed media and attribute with support for data and data version masks.
293
- Enables precise control over data grouping with optional version filtering.
294
- """
295
- fmt: str = ">BBBB"
296
-
297
-
298
- class ACDEgroup(SimpleGroup, ACDEgroup_, tuple[A, C, D, E]):
299
- ...
300
-
301
-
302
- class ACDDEgroup(ACDEgroup_, tuple[A, C, tuple[D, ...], E]):
303
- def get_key(self) -> Iterator[bytes]:
304
- a, c, _, e = self
305
- return (pack(self.fmt, a, c, d, e) for d in self[2])
306
-
307
-
308
-
309
- class ACDEEgroup(ACDEgroup_, tuple[A, C, D, tuple[E, ...]]):
310
- def get_key(self) -> Iterator[bytes]:
311
- a, c, d, _ = self
312
- return (pack(self.fmt, a, c, d, e) for e in self[3])
313
-
314
-
315
- class ACDDEEgroup(ACDEgroup_, tuple[A, C, tuple[D, ...], tuple[E, ...]]):
316
- def get_key(self) -> Iterator[bytes]:
317
- a, c, _, _ = self
318
- return (pack(self.fmt, a, c, d, e) for d in self[2] for e in self[3])
319
-
320
-
321
- class ABCDEgroup_(Xgroup, Protocol):
322
- """Grouping of OBIS codes by all OBIS groups A-E with data version mask support.
323
-
324
- Comprehensive grouping by:
325
- - A: media
326
- - B: channel/interface
327
- - C: attribute
328
- - D: data
329
- - E: data version (with mask support)
330
-
331
- Provides complete OBIS code grouping with flexible version matching.
332
- """
333
- fmt: str = ">BBBBB"
334
-
335
-
336
- class ABCDEgroup(SimpleGroup, ABCDEgroup_, tuple[A, B, C, D, E]):
337
- ...
338
-
339
-
340
- class ABCCDEgroup(ABCDEgroup_, tuple[A, B, tuple[C, ...], D, E]):
341
- def get_key(self) -> Iterator[bytes]:
342
- a, b, _, d, e = self
343
- return (pack(self.fmt, a, b, c, d, e) for c in self[2])
344
-
345
-
346
- class ABCDEEgroup(ABCDEgroup_, tuple[A, B, C, D, tuple[E, ...]]):
347
- def get_key(self) -> Iterator[bytes]:
348
- a, b, c, d, _ = self
349
- return (pack(self.fmt, a, b, c, d, e) for e in self[4])
350
-
351
-
352
- type PossibleGroup = ACgroup | ACDgroup_ | ACDEgroup | ABCDEgroup
353
- FUNC_MAP: TypeAlias = dict[bytes, dict[int, ClassMap]]
354
- """ln.BCDE | ln.CDE | ln.CD | ln.C: {class_id: {version: CosemInterfaceClass}}"""
355
-
356
-
357
- func_maps: dict[str, FUNC_MAP] = {}
358
- type PossibleClassMap = tuple[ClassMap, ...] | ClassMap
359
-
360
-
361
- def get_func_map(for_create_map: dict[PossibleGroup, PossibleClassMap]) -> FUNC_MAP:
362
- ret: FUNC_MAP = {}
363
- for g in for_create_map:
364
- for k in g.get_key():
365
- ret[k] = _create_map(for_create_map[g])
366
- return ret
367
-
368
-
369
- __func_map_for_create: dict[PossibleGroup, PossibleClassMap] = {
370
- # abstract
371
- ACDgroup((0, 0, 1)): DataMap,
372
- ACDgroup((0, 0, 2)): DataMap,
373
- ACDEgroup((0, 0, 2, 1)): ClassMap(impl.data.ActiveFirmwareId),
374
- ACDgroup((0, 0, 9)): DataMap,
375
- ACDgroup((0, 1, 0)): ClockMap,
376
- ACDgroup((0, 1, 1)): DataMap,
377
- ACDgroup((0, 1, 2)): DataMap,
378
- ACDgroup((0, 1, 3)): DataMap,
379
- ACDgroup((0, 1, 4)): DataMap,
380
- ACDgroup((0, 1, 5)): DataMap,
381
- ACDgroup((0, 1, 6)): DataMap,
382
- ACDEgroup((0, 2, 0, 0)): ModemConfigurationMap,
383
- #
384
- ACDEEgroup((0, 10, 0, (0, 1, 125)+tuple(range(100, 112)))): ScriptTableMap,
385
- ACDgroup((0, 11, 0)): SpecialDaysTableMap,
386
- ACDgroup((0, 12, 0)): ScheduleMap,
387
- ACDgroup((0, 13, 0)): ActivityCalendarMap,
388
- ACDgroup((0, 14, 0)): RegisterActivationMap,
389
- ACDEEgroup((0, 15, 0, tuple(range(0, 8)))): SingleActionScheduleMap,
390
- ACDgroup((0, 16, 0)): RegisterMonitorMap,
391
- ACDEEgroup((0, 16, 1, tuple(range(0, 10)))): RegisterMonitorMap,
392
- #
393
- ACDgroup((0, 17, 0)): LimiterMap,
394
- #
395
- ACDDEEgroup((0, 19, tuple(range(50, 60)), (1, 2))): DataMap,
396
- #
397
- ACDgroup((0, 21, 0)): (DataMap, ProfileGenericMap),
398
- ACDEgroup((0, 22, 0, 0)): IECHDLCSetupMap,
399
- #
400
- ACDEgroup((0, 23, 2, 0)): DataMap,
401
- ACDEEgroup((0, 23, 3, tuple(range(0, 10)))): (DataMap, ProfileGenericMap),
402
- ACDEEgroup((0, 23, 3, tuple(range(10, 256)))): DataMap,
403
- #
404
- ACDgroup((0, 24, 2)): ExtendedRegisterMap,
405
- ACDgroup((0, 24, 3)): ProfileGenericMap,
406
- ACDEgroup((0, 24, 4, 0)): DisconnectControlMap,
407
- ACDEgroup((0, 24, 5, 0)): ProfileGenericMap,
408
- #
409
- ACDEgroup((0, 25, 0, 0)): TCPUDPSetupMap,
410
- ACDEgroup((0, 25, 1, 0)): IPv4SetupMap,
411
- #
412
- ACDEgroup((0, 25, 4, 0)): GPRSModemSetupMap,
413
- #
414
- ACDEgroup((0, 25, 6, 0)): GSMDiagnosticMap,
415
- #
416
- ACDEgroup((0, 25, 9, 0)): PushSetupMap,
417
- ACDEgroup((0, 25, 10, 0)): NTPSetupMap,
418
- #
419
- ABCDEEgroup((0, 0, 40, 0, tuple(range(8)))): (AssociationSNMap, AssociationLNMap), # todo: now limit by 8 association, solve it
420
- #
421
- ABCDEgroup((0, 0, 42, 0, 0)): ClassMap(impl.data.LDN),
422
- ABCDEEgroup((0, 0, 43, 0, tuple(range(256)))): SecuritySetupMap,
423
- ACDgroup((0, 43, 1)): DataMap,
424
- #
425
- ABCDEEgroup((0, 0, 44, 0, tuple(range(256)))): ImageTransferMap,
426
- #
427
- ACDEEgroup((0, 96, 1, tuple(range(0, 11)))): ClassMap(impl.data.DLMSDeviceIDObject),
428
- ACDEgroup((0, 96, 1, 255)): ProfileGenericMap, # todo: add RegisterTable
429
- ACDgroup((0, 96, 2)): DataDynamicMap,
430
- ACDEEgroup((0, 96, 3, tuple(range(0, 4)))): DataMap, # todo: add StatusMapping
431
- ACDEgroup((0, 96, 3, 10)): DisconnectControlMap,
432
- ACDEEgroup((0, 96, 3, tuple(range(20, 29)))): ArbitratorMap,
433
- ACDDEgroup((0, 96, (4, 5), 0)): (DataMap, ProfileGenericMap), # todo: add RegisterTable, StatusMapping
434
- ACDDEEgroup((0, 96, (4, 5), (1, 2, 3, 4))): DataMap, # todo: add StatusMapping
435
- ACDEEgroup((0, 96, 6, tuple(range(0, 7)))): (DataMap, RegisterMap, ExtendedRegisterMap),
436
- ACDEEgroup((0, 96, 7, tuple(range(0, 22)))): (DataMap, RegisterMap, ExtendedRegisterMap),
437
- ACDEEgroup((0, 96, 8, tuple(range(0, 64)))): (DataMap, RegisterMap, ExtendedRegisterMap),
438
- ACDEEgroup((0, 96, 9, (0, 1, 2))): (RegisterMap, ExtendedRegisterMap),
439
- ACDEEgroup((0, 96, 10, tuple(range(1, 10)))): DataMap, # todo: add StatusMapping
440
- ACDEEgroup((0, 96, 11, tuple(range(100)))): (DataDynamicMap, RegisterMap, ExtendedRegisterMap),
441
- ACDEEgroup((0, 96, 12, (0, 1, 2, 3, 5, 6))): (DataMap, RegisterMap, ExtendedRegisterMap),
442
- ACDEgroup((0, 96, 12, 4)): ClassMap(impl.data.CommunicationPortParameter),
443
- ACDEEgroup((0, 96, 13, (0, 1))): (DataMap, RegisterMap, ExtendedRegisterMap),
444
- ACDEEgroup((0, 96, 14, tuple(range(16)))): (DataMap, RegisterMap, ExtendedRegisterMap),
445
- ACDEEgroup((0, 96, 15, tuple(range(100)))): (DataMap, RegisterMap, ExtendedRegisterMap),
446
- ACDEEgroup((0, 96, 16, tuple(range(10)))): (DataMap, RegisterMap, ExtendedRegisterMap),
447
- ACDEEgroup((0, 96, 17, tuple(range(128)))): (DataMap, RegisterMap, ExtendedRegisterMap),
448
- ACDgroup((0, 96, 20)): (DataMap, RegisterMap, ExtendedRegisterMap),
449
- ACDEEgroup((0, 97, 97, tuple(range(10)))): DataMap,
450
- ACDDEgroup((0, 97, (97, 98), 255)): ProfileGenericMap, # todo: add RegisterTable
451
- ACDEEgroup((0, 97, 98, tuple(range(10))+tuple(range(10, 30)))): DataMap,
452
- ACgroup((0, 98)): ProfileGenericMap,
453
- ACDgroup((0, 99, 98)): ProfileGenericMap,
454
- # electricity
455
- ACDEEgroup((1, 0, 0, tuple(range(10)))): DataMap,
456
- ACDEgroup((1, 0, 0, 255)): ProfileGenericMap, # todo: add RegisterTable
457
- ACDgroup((1, 0, 1)): DataMap,
458
- ACDgroup((1, 0, 2)): DataStaticMap,
459
- ACDDgroup((1, 0, (3, 4, 7, 8, 9))): (DataStaticMap, RegisterMap, ExtendedRegisterMap),
460
- ACDDgroup((1, 0, (6, 10))): (RegisterMap, ExtendedRegisterMap),
461
- ACDEEgroup((1, 0, 11, tuple(range(1, 8)))): DataMap,
462
- ACDEEgroup((1, 96, 1, tuple(range(10)))): DataMap,
463
- ACDEgroup((1, 96, 1, 255)): ProfileGenericMap, # todo: add RegisterTable
464
- ACDEEgroup((1, 96, 5, (0, 1, 2, 3, 4, 5))): DataMap, # todo: add StatusMapping
465
- ACDEEgroup((1, 96, 10, (0, 1, 2, 3))): DataMap, # todo: add StatusMapping
466
- ACgroup((1, 98)): ProfileGenericMap,
467
- ACDDgroup((1, 99, (1, 2, 11, 12, 97, 98, 99))): ProfileGenericMap,
468
- ACDDEgroup((1, 99, (3, 13, 14), 0)): ProfileGenericMap,
469
- ACDEEgroup((1, 99, 10, (1, 2, 3))): ProfileGenericMap,
470
- ACCDgroup((1, _CUMULATIVE, _RU_CHANGE_LIMIT_LEVEL)): RegisterMap,
471
- ACCDDgroup((
472
- 1,
473
- _NOT_PROCESSING_OF_MEASUREMENT_VALUES,
474
- tuple(chain(
475
- _CUMULATIVE,
476
- _TIME_INTEGRAL_VALUES,
477
- _CONTRACTED_VALUES,
478
- _UNDER_OVER_LIMIT_THRESHOLDS,
479
- _UNDER_OVER_LIMIT_OCCURRENCE_COUNTERS,
480
- _UNDER_OVER_LIMIT_DURATIONS,
481
- _UNDER_OVER_LIMIT_MAGNITUDES
482
- )))): (RegisterMap, ExtendedRegisterMap),
483
- ACCDDgroup((1, _NOT_PROCESSING_OF_MEASUREMENT_VALUES, _INSTANTANEOUS_VALUES)): RegisterMap,
484
- ACCDDgroup((1, _NOT_PROCESSING_OF_MEASUREMENT_VALUES, _MAX_MIN_VALUES)): (RegisterMap, ExtendedRegisterMap, ProfileGenericMap),
485
- ACCDDgroup((1, _NOT_PROCESSING_OF_MEASUREMENT_VALUES, _CURRENT_AND_LAST_AVERAGE_VALUES)): (RegisterMap, DemandRegisterMap),
486
- ACCDgroup((1, _NOT_PROCESSING_OF_MEASUREMENT_VALUES, 40)): (DataMap, RegisterMap),
487
- }
488
-
489
- func_maps["DLMS_6"] = get_func_map(__func_map_for_create)
490
-
491
-
492
- # SPODES3 Update
493
- __func_map_for_create.update({
494
- ACDgroup((0, 21, 0)): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3DisplayReadout),
495
- ACDEEgroup((0, 96, 1, (0, 2, 4, 5, 8, 9, 10))): ClassMap(impl.data.SPODES3IDNotSpecific),
496
- ACDEgroup((0, 96, 1, 6)): ClassMap(impl.data.SPODES3SPODESVersion),
497
- ACDEEgroup((0, 96, 2, (1, 2, 3, 5, 6, 7, 11, 12))): ClassMap(impl.data.AnyDateTime),
498
- ACDEgroup((0, 96, 3, 20)): ClassMap(impl.arbitrator.SPODES3Arbitrator),
499
- ACDEgroup((0, 96, 4, 3)): ClassMap(impl.data.SPODES3LoadLocker),
500
- ACDEgroup((0, 96, 5, 1)): ClassMap(impl.data.SPODES3PowerQuality2Event),
501
- ACDEgroup((0, 96, 5, 4)): ClassMap(impl.data.SPODES3PowerQuality1Event),
502
- ACDEgroup((0, 96, 5, 132)): ClassMap(impl.data.Unsigned), # TODO: make according with СПОДЭС3 13.9. Контроль чередования фаз
503
- ACDEgroup((0, 96, 11, 0)): ClassMap(impl.data.SPODES3VoltageEvent),
504
- ACDEgroup((0, 96, 11, 1)): ClassMap(impl.data.SPODES3CurrentEvent),
505
- ACDEgroup((0, 96, 11, 2)): ClassMap(impl.data.SPODES3CommutationEvent),
506
- ACDEgroup((0, 96, 11, 3)): ClassMap(impl.data.SPODES3ProgrammingEvent),
507
- ACDEgroup((0, 96, 11, 4)): ClassMap(impl.data.SPODES3ExternalEvent),
508
- ACDEgroup((0, 96, 11, 5)): ClassMap(impl.data.SPODES3CommunicationEvent),
509
- ACDEgroup((0, 96, 11, 6)): ClassMap(impl.data.SPODES3AccessEvent),
510
- ACDEgroup((0, 96, 11, 7)): ClassMap(impl.data.SPODES3SelfDiagnosticEvent),
511
- ACDEgroup((0, 96, 11, 8)): ClassMap(impl.data.SPODES3ReactivePowerEvent),
512
- ABCDEgroup((0, 0, 96, 51, 0)): ClassMap(impl.data.OpeningBody),
513
- ABCDEgroup((0, 0, 96, 51, 1)): ClassMap(impl.data.OpeningCover),
514
- ABCDEgroup((0, 0, 96, 51, 3)): ClassMap(impl.data.ExposureToMagnet),
515
- ABCDEgroup((0, 0, 96, 51, 4)): ClassMap(impl.data.ExposureToHSField),
516
- ABCDEgroup((0, 0, 96, 51, 5)): ClassMap(impl.data.SealStatus),
517
- ABCDEEgroup((0, 0, 96, 51, (6, 7))): UnsignedDataMap,
518
- ABCDEEgroup((0, 0, 96, 51, (8, 9))): ClassMap(impl.data.OctetStringDateTime),
519
- ABCDEEgroup((0, 0, 97, 98, (0, 10, 20))): ClassMap(impl.data.SPODES3Alarm1),
520
- ABCDEEgroup((0, 0, 97, 98, (1, 11))): ClassMap(impl.data.SPODES3ControlAlarm1),
521
- # electricity
522
- ACDEEgroup((1, 0, 8, (4, 5))): ClassMap(impl.data.SPODES3MeasurementPeriod),
523
- ACDgroup((1, 98, 1)): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3MonthProfile),
524
- ACDgroup((1, 98, 2)): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3DailyProfile),
525
- ACDDgroup((1, 99, (1, 2))): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3LoadProfile),
526
- ABCDEgroup((1, 0, 131, 35, 0)): RegisterMap,
527
- ABCDEgroup((1, 0, 133, 35, 0)): RegisterMap,
528
- ABCDEgroup((1, 0, 147, 133, 0)): RegisterMap,
529
- ABCDEgroup((1, 0, 148, 136, 0)): RegisterMap,
530
- ACDEgroup((1, 94, 7, 0)): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3CurrentProfile),
531
- ACDEEgroup((1, 94, 7, (1, 2, 3, 4, 5, 6))): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3ScalesProfile), # Todo: RU. Scaler-profile With 1 entry and more
532
- })
533
-
534
- func_maps["SPODES_3"] = get_func_map(__func_map_for_create)
535
-
536
- # KPZ Update
537
- __func_map_for_create.update({
538
- ACDEgroup((0, 96, 11, 4)): ClassMap(impl.data.KPZSPODES3ExternalEvent),
539
- ABCDEEgroup((0, 0, 97, 98, (0, 10, 20))): ClassMap(impl.data.KPZAlarm1),
540
- ABCDEgroup((0, 128, 25, 6, 0)): ClassMap(impl.data.DataStatic),
541
- ABCDEEgroup((0, 128, 96, 2, (0, 1, 2))): ClassMap(impl.data.KPZAFEOffsets),
542
- ABCDEgroup((0, 128, 96, 13, 1)): ClassMap(impl.data.ITEBitMap),
543
- ABCDEgroup((0, 128, 154, 0, 0)): ClassMap(impl.data.KPZGSMPingIP),
544
- ACDEEgroup((0, 0, 128, (100, 101, 102, 103, 150, 151, 152, 170))): DataMap,
545
- ABCCDEgroup((128, 0, tuple(range(20)), 0, 0)): RegisterMap
546
- })
547
- func_maps["KPZ"] = get_func_map(__func_map_for_create)
548
- # KPZ1 with bag in log event profiles
549
- __func_map_for_create.update({
550
- ACDEgroup((0, 96, 11, 0)): ClassMap(impl.data.KPZ1SPODES3VoltageEvent),
551
- ACDEgroup((0, 96, 11, 1)): ClassMap(impl.data.KPZ1SPODES3CurrentEvent),
552
- ACDEgroup((0, 96, 11, 2)): ClassMap(impl.data.KPZ1SPODES3CommutationEvent),
553
- ACDEgroup((0, 96, 11, 3)): ClassMap(impl.data.KPZ1SPODES3ProgrammingEvent),
554
- ACDEgroup((0, 96, 11, 4)): ClassMap(impl.data.KPZ1SPODES3ExternalEvent),
555
- ACDEgroup((0, 96, 11, 5)): ClassMap(impl.data.KPZ1SPODES3CommunicationEvent),
556
- ACDEgroup((0, 96, 11, 6)): ClassMap(impl.data.KPZ1SPODES3AccessEvent),
557
- ACDEgroup((0, 96, 11, 7)): ClassMap(impl.data.KPZ1SPODES3SelfDiagnosticEvent),
558
- ACDEgroup((0, 96, 11, 8)): ClassMap(impl.data.KPZ1SPODES3ReactivePowerEvent),
559
- })
560
- func_maps["KPZ1"] = get_func_map(__func_map_for_create)
561
-
562
-
563
- def get_type(class_id: ut.CosemClassId,
564
- version: cdt.Unsigned,
565
- ln: cst.LogicalName,
566
- func_map: FUNC_MAP) -> Type[InterfaceClass]:
567
- """use DLMS UA 1000-1 Ed. 14 Table 54"""
568
- c_m: Optional[dict[int, ClassMap]]
569
- if (
570
- (128 <= ln.b <= 199)
571
- or (128 <= ln.c <= 199)
572
- or ln.c == 240
573
- or (128 <= ln.d <= 254)
574
- or (128 <= ln.e <= 254)
575
- or (128 <= ln.f <= 254)
576
- ):
577
- # try search in ABCDE group for manufacture object before in CDE
578
- c_m = func_map.get(ln.contents[:5], common_interface_class_map)
579
- else:
580
- # try search in ABCDE group
581
- c_m = func_map.get(ln.contents[:5], None)
582
- if c_m is None:
583
- # try search in A-CDE group
584
- c_m = func_map.get(ln.contents[:1]+ln.contents[2:5], None)
585
- if c_m is None:
586
- # try search in A-CD group
587
- c_m = func_map.get(ln.contents[:1]+ln.contents[2:4], None)
588
- if c_m is None:
589
- # try search in A-C group
590
- c_m = func_map.get(ln.contents[:1]+ln.contents[3:4], common_interface_class_map)
591
- return get_interface_class(class_map=c_m,
592
- c_id=class_id,
593
- ver=version)
594
-
595
-
596
- @lru_cache(20000)
597
- def get_unit(class_id: ClassID, elements: Iterable[int]) -> int | None:
598
- match class_id, *elements:
599
- case (ClassID.LIMITER, 6 | 7) | (ClassID.LIMITER, 8, 2) | (ClassID.DEMAND_REGISTER, 8) | (ClassID.PROFILE_GENERIC, 4) | (ClassID.PUSH_SETUP, 5) |\
600
- (ClassID.PUSH_SETUP, 7, _) | (ClassID.PUSH_SETUP, 12, 1) | (ClassID.COMMUNICATION_PORT_PROTECTION, 4 | 6) | (ClassID.CHARGE, 8) | (ClassID.IEC_HDLC_SETUP, 8) \
601
- | (ClassID.AUTO_CONNECT, 4):
602
- return 7 # second
603
- case ClassID.CLOCK, 3 | 7:
604
- return 6 # min
605
- case (ClassID.IEC_HDLC_SETUP, 7) | (ClassID.MODEM_CONFIGURATION, 3, 2):
606
- return 7 # millisecond
607
- case ClassID.S_FSK_PHY_MAC_SET_UP, 7, _:
608
- return 44 # HZ
609
- case (ClassID.S_FSK_PHY_MAC_SET_UP, 4 | 5):
610
- return 72 # Db
611
- case ClassID.S_FSK_PHY_MAC_SET_UP, 6:
612
- return 71 # DbmicroV
613
- case _:
614
- return None
615
-
616
-
617
- type ObjFilteredKey = tuple[ClassID | LNPattern | LNPatterns | Channel, ...]
618
-
619
-
620
- def get_filtered(objects: Iterable[InterfaceClass],
621
- keys: ObjFilteredKey) -> list[InterfaceClass]:
622
- c_ids: list[ut.CosemClassId] = list()
623
- patterns: list[LNPattern] = list()
624
- ch: Optional[Channel] = None
625
- for k in keys:
626
- if isinstance(k, ut.CosemClassId):
627
- c_ids.append(k)
628
- elif isinstance(k, LNPattern):
629
- patterns.append(k)
630
- elif isinstance(k, LNPatterns):
631
- patterns.extend(k)
632
- elif isinstance(k, Channel):
633
- ch = k
634
- new_list = list()
635
- for obj in objects:
636
- if obj.CLASS_ID in c_ids:
637
- pass
638
- elif obj.logical_name in patterns:
639
- pass
640
- else:
641
- continue
642
- if ch and not ch.is_approve(obj.logical_name.b):
643
- continue
644
- new_list.append(obj)
645
- return new_list
646
-
647
-
648
- @dataclass(unsafe_hash=True, frozen=True)
649
- class ParameterValue:
650
- par: bytes
651
- value: bytes
652
-
653
- def __str__(self) -> str:
654
- return F"{'.'.join(map(str, self.par[:6]))}:{self.par[6]} - {cdt.get_instance_and_pdu_from_value(self.value)[0].__repr__()}"
655
-
656
- def __bytes__(self) -> bytes:
657
- """par + 0x00 + value""" # todo: 00 in future other parameters
658
- return self.par + b'\x00' + self.value
659
-
660
- @classmethod
661
- def parse(cls, value: bytes) -> Self:
662
- if value[7] != 0:
663
- raise exc.ITEApplication(F"wrong {value!r} for {cls.__name__}")
664
- return cls(
665
- par=value[:7],
666
- value=value[8:]
667
- )
668
-
669
-
670
- @dataclass(frozen=True, unsafe_hash=True)
671
- class ID:
672
- man: bytes
673
- f_id: ParameterValue
674
- f_ver: ParameterValue
675
-
676
-
677
- class Collection:
678
- __id: ID
679
- __dlms_ver: int
680
- __country: Optional[CountrySpecificIdentifiers]
681
- __country_ver: Optional[ParameterValue]
682
- __objs: dict[o.OBIS, InterfaceClass]
683
- __const_objs: int
684
- spec_map: str
685
-
686
- def __init__(self,
687
- id_: ID,
688
- dlms_ver: int = 6,
689
- country: Optional[CountrySpecificIdentifiers] = None,
690
- cntr_ver: Optional[ParameterValue] = None):
691
- self.__id = id_
692
- self.__dlms_ver = dlms_ver
693
- self.__country = country
694
- self.__country_ver = cntr_ver
695
- """country version specification"""
696
- self.spec_map = "DLMS_6"
697
- self.__objs = {}
698
- """ all DLMS objects container with obis key """
699
-
700
- @property
701
- def id(self) -> ID:
702
- return self.__id
703
-
704
- def set_id(self, value: ID) -> None:
705
- if not self.__id:
706
- self.__id = value
707
- else:
708
- if value != self.__id:
709
- raise ValueError(F"got id: {value}, expected {self.__id}")
710
- else:
711
- """success validation"""
712
-
713
- def __eq__(self, other: object) -> bool:
714
- if isinstance(other, Collection):
715
- return hash(self) == hash(other)
716
- raise NotImplementedError
717
-
718
- def __hash__(self) -> int:
719
- return hash(self.id)
720
-
721
- def copy_object_values(self, target: ic.COSEMInterfaceClasses, association_id: int = 3) -> None:
722
- """copy object values according by association. only needed"""
723
- source = self.__objs.get(target.logical_name.contents, None)
724
- if source is None:
725
- raise RuntimeError(f"can't find {target}")
726
- for i, value in source.get_index_with_attributes():
727
- el = target.get_attr_element(i)
728
- if (
729
- i == 1
730
- or value is None
731
- or (
732
- not isinstance(el.DATA_TYPE, ut.CHOICE)
733
- and el.classifier == ic.Classifier.DYNAMIC
734
- )
735
- ):
736
- continue
737
- else:
738
- if (
739
- target.get_attr_element(i).classifier == ic.Classifier.STATIC
740
- and not self.is_writable(
741
- ln=target.logical_name,
742
- index=i,
743
- association_id=association_id)
744
- ):
745
- # todo: may be callbacks inits remove?
746
- if cb_func := target._cbs_attr_before_init.get(i, None):
747
- cb_func(value) # Todo: 'a' as 'new_value' in set_attr are can use?
748
- target._cbs_attr_before_init.pop(i)
749
- target.set_attr(i, value.encoding)
750
- if cb_func := target._cbs_attr_post_init.get(i, None):
751
- cb_func()
752
- target._cbs_attr_post_init.pop(i)
753
- else:
754
- if isinstance(arr := target.get_attr(i), cdt.Array):
755
- arr.set_type(value.TYPE)
756
- try:
757
- target.set_attr(
758
- index=i,
759
- value=value.encoding,
760
- data_type=source.get_attr(i).__class__)
761
- except exc.EmptyObj as e:
762
- print(F"can't copy {target} attr={i}, skipped. {e}")
763
-
764
- def copy(self) -> result.Simple["Collection"]:
765
- """copy collection with value by Association"""
766
- res = result.Simple(Collection(
767
- id_=self.id,
768
- dlms_ver=self.__dlms_ver,
769
- country=self.__country,
770
- cntr_ver=self.__country_ver)
771
- )
772
- res.value.spec_map = self.spec_map
773
- max_ass: AssociationLN | None = None
774
- """more full association""" # todo: move to collection(from_xml)
775
- for obj in self.__objs.values():
776
- new_obj: InterfaceClass = obj.__class__(obj.logical_name)
777
- res.value.__objs[obj.logical_name.contents] = new_obj
778
- new_obj.collection = res.value # todo: remove in future
779
- if obj.CLASS_ID == ClassID.ASSOCIATION_LN:
780
- obj: AssociationLN
781
- if obj.object_list is not None:
782
- if (
783
- max_ass is None
784
- or len(max_ass.object_list) < len(obj.object_list)
785
- ):
786
- max_ass = obj
787
- if max_ass is None:
788
- raise exc.NoObject("collection not has the AssociationLN object")
789
- ln_for_set = max_ass.get_lns()
790
- ass_id: int = max_ass.logical_name.e
791
- while len(ln_for_set) != 0:
792
- ln = ln_for_set.pop(0)
793
- try:
794
- new_obj = res.value.get_object(ln.contents)
795
- self.copy_object_values(
796
- target=new_obj,
797
- association_id=ass_id)
798
- except Exception as e:
799
- res.append_e(e, "copy object value")
800
- return res
801
-
802
- @property
803
- def dlms_ver(self) -> int:
804
- return self.__dlms_ver
805
-
806
- def set_dlms_ver(self, value: int) -> None:
807
- if not self.__dlms_ver:
808
- self.__dlms_ver = value
809
- else:
810
- if value != self.__dlms_ver:
811
- raise ValueError(F"got dlms_version: {value}, expected {self.__dlms_ver}")
812
- else:
813
- """success validation"""
814
-
815
- @property
816
- def country(self) -> Optional[CountrySpecificIdentifiers]:
817
- return self.__country
818
-
819
- def set_country(self, value: CountrySpecificIdentifiers):
820
- if not self.__country:
821
- self.__country = value
822
- else:
823
- if value != self.__country:
824
- raise ValueError(F"got country: {value}, expected {self.__country}")
825
- else:
826
- """success validation"""
827
-
828
- @property
829
- def country_ver(self):
830
- return self.__country_ver
831
-
832
- def set_country_ver(self, value: ParameterValue):
833
- """country version specification"""
834
- if not self.__country_ver:
835
- self.__country_ver = value
836
- else:
837
- if value != self.__country_ver:
838
- raise ValueError(F"got country version: {value}, expected {self.__country_ver}")
839
- else:
840
- """success validation"""
841
-
842
- def __str__(self) -> str:
843
- return F"[{len(self.__objs)}] DLMS version: {self.__dlms_ver}, country: {self.__country}, country specific version: {self.__country_ver}, " \
844
- F"id: {self.id}, uses specification: {self.spec_map}"
845
-
846
- def __iter__(self) -> Iterator[ic.COSEMInterfaceClasses]:
847
- return iter(self.__objs.values())
848
-
849
- def get_spec(self) -> str:
850
- """return functional map to specification by identification fields"""
851
- match self.id.man:
852
- case b"KPZ":
853
- return "KPZ"
854
- case b"101" | b"102" | b"103" | b"104":
855
- return "KPZ1"
856
- case _:
857
- if self.country == CountrySpecificIdentifiers.RUSSIA:
858
- if (
859
- self.country_ver.par == b'\x00\x00`\x01\x06\xff\x02' and
860
- SemVer.parse(bytes(cdt.OctetString(self.country_ver.value)), True) == SemVer(3, 0)
861
- ):
862
- return "SPODES_3"
863
- if self.dlms_ver == 6:
864
- return "DLMS_6"
865
- else:
866
- raise exc.DLMSException("unknown specification")
867
-
868
- def add_if_missing(self, class_id: ut.CosemClassId,
869
- version: cdt.Unsigned | None,
870
- logical_name: cst.LogicalName) -> InterfaceClass:
871
- """ like as add method with check for missing """
872
- if (res := self.__objs.get(logical_name.contents)) is None:
873
- return self.add(
874
- class_id=class_id,
875
- version=version,
876
- logical_name=logical_name)
877
- else:
878
- return res
879
-
880
- def __getitem__(self, item: o.OBIS) -> InterfaceClass:
881
- return self.__objs[item]
882
-
883
- def get(self, obis: o.OBIS) -> InterfaceClass | None:
884
- """ get object, return None if it absence """
885
- return self.__objs.get(obis, None)
886
-
887
- def par2obj(self, par: Parameter) -> result.SimpleOrError[InterfaceClass]:
888
- """return: DLMSObject"""
889
- return self.obis2obj(par.obis)
890
-
891
- def par2data(self, par: Parameter) -> result.Option[cdt.CommonDataType] | result.Error:
892
- """:return CDT by Parameter, return None if data wasn't setting"""
893
- if isinstance((res1 := self.par2obj(par)), result.Error):
894
- return res1
895
- res = result.Option(res1.value.get_attr(par.i))
896
- if res.value is None:
897
- return res
898
- for el in par.elements():
899
- res.value = res.value[el]
900
- return res
901
-
902
- def values(self) -> tuple[InterfaceClass]:
903
- return tuple(self.__objs.values())
904
-
905
- def __len__(self):
906
- return len(self.__objs)
907
-
908
- def add(self, class_id: ut.CosemClassId,
909
- version: cdt.Unsigned | None,
910
- logical_name: cst.LogicalName) -> InterfaceClass:
911
- """ append new DLMS object to collection with return it"""
912
- try:
913
- new_object = get_type(
914
- class_id=class_id,
915
- version=self.find_version(class_id) if version is None else version,
916
- ln=logical_name,
917
- func_map=func_maps[self.spec_map])(logical_name)
918
- new_object.collection = self
919
- self.__objs[o.OBIS(logical_name.contents)] = new_object
920
- print(F'Create {new_object}')
921
- return new_object
922
- except ValueError as e:
923
- raise ValueError(F"error getting DLMS object instance with {class_id=} {version=} {logical_name=}: {e}")
924
- except StopIteration as e:
925
- raise ValueError(F"not find class version for {class_id=} {logical_name=}: {e}")
926
-
927
- def get_class_version(self) -> dict[ut.CosemClassId, cdt.Unsigned]:
928
- """use for check all class version by unique"""
929
- ret: dict[ut.CosemClassId, cdt.Unsigned] = dict()
930
- for obj in self.__objs.values():
931
- if ver := ret.get(obj.CLASS_ID):
932
- if obj.VERSION != ver:
933
- raise ValueError(F"for {obj.CLASS_ID=} exist several versions: {obj.VERSION}, {ver} in one collection")
934
- else:
935
- ret[obj.CLASS_ID] = obj.VERSION
936
- return ret
937
-
938
- def get_n_phases(self) -> int:
939
- """search objects with L2 phase"""
940
- ret: int | None = None
941
- for obj in filter(lambda obj: obj.logical_name.a == 1, self):
942
- if 41 <= obj.logical_name.c <= 60:
943
- return 3
944
- ret = 1
945
- if ret is None:
946
- raise exc.NoObject("no one electricity object was find")
947
- else:
948
- return ret
949
-
950
- def has_sap(self, value: enums.ClientSAP) -> bool:
951
- try:
952
- self.sap2association(value)
953
- return True
954
- except exc.NoObject:
955
- return False
956
-
957
- @lru_cache(maxsize=100) # amount of all ClassID
958
- def find_version(self, class_id: ut.CosemClassId) -> cdt.Unsigned:
959
- """use for add new object from profile_generic if absence in object list"""
960
- return next(filter(lambda obj: obj.CLASS_ID == class_id, self.__objs.values())).VERSION
961
-
962
- def is_in_collection(self, value: LNContaining) -> bool:
963
- obis = lnContents2obis(value)
964
- return False if self.__objs.get(obis) is None else True
965
-
966
- def get_object(self, value: LNContaining) -> InterfaceClass:
967
- """ return object from obis<string> or raise exception if it absence """
968
- return self.obis2obj(lnContents2obis(value)).unwrap()
969
-
970
- @deprecated("use <par2rep>")
971
- def get_report(self,
972
- obj: ic.COSEMInterfaceClasses,
973
- par: bytes,
974
- a_val: cdt.CommonDataType | None
975
- ) -> cdt.Report:
976
- """par: attribute_index, par1, par2, ..."""
977
- rep = cdt.Report(str(a_val))
978
- try:
979
- if a_val is None:
980
- rep.msg = settings.report.empty
981
- rep.log = cdt.EMPTY_VAL
982
- elif isinstance(a_val, cdt.ReportMixin):
983
- rep = a_val.get_report()
984
- elif isinstance(a_val, DayProfileAction):
985
- rep.msg = F"{get_message("$rate$")}-{a_val.script_selector}: {a_val.start_time}"
986
- script_obj = self.get_object(a_val.script_logical_name)
987
- for script in script_obj.scripts:
988
- if script.script_identifier == a_val.script_selector:
989
- break
990
- else:
991
- rep.log = cdt.Log(logging.ERROR, F"absent script with ID: {a_val.script_selector}")
992
- else:
993
- if unit := get_unit(obj.CLASS_ID, par):
994
- rep.unit = cdt.Unit(unit).get_name()
995
- else:
996
- if s_u := self.get_scaler_unit(obj, par):
997
- rep.msg = (settings.report.scaler_format).format(int(a_val) * 10 ** int(s_u.scaler))
998
- rep.unit = s_u.unit.get_name()
999
- else:
1000
- match obj.CLASS_ID, *par:
1001
- case (ClassID.PROFILE_GENERIC, 3, _) | (ClassID.PROFILE_GENERIC, 6):
1002
- a_val: structs.CaptureObjectDefinition
1003
- obj = self.get_object(a_val.logical_name)
1004
- rep.msg = F"{get_name(a_val.logical_name)}.{obj.get_attr_element(int(a_val.attribute_index))}"
1005
- case _:
1006
- pass
1007
- rep.log = cdt.Log(logging.INFO)
1008
- except Exception as e:
1009
- rep.log = cdt.Log(logging.ERROR, e)
1010
- finally:
1011
- return rep
1012
-
1013
- def par2rep(self, par: Parameter, data: Optional[cdt.CommonDataType]) -> cdt.Report:
1014
- rep = cdt.Report(str(data))
1015
- try:
1016
- if data is None:
1017
- rep.msg = settings.report.empty
1018
- rep.log = cdt.EMPTY_VAL
1019
- elif isinstance(data, cdt.ReportMixin):
1020
- rep = data.get_report()
1021
- elif isinstance(data, DayProfileAction):
1022
- rep.msg = F"{get_message("$rate$")}-{data.script_selector}: {data.start_time}"
1023
- script_obj = self.get_object(data.script_logical_name)
1024
- for script in script_obj.scripts:
1025
- if script.script_identifier == data.script_selector:
1026
- break
1027
- else:
1028
- rep.log = cdt.Log(logging.ERROR, F"absent script with ID: {data.script_selector}")
1029
- else:
1030
- obj = self.par2obj(par).unwrap()
1031
- elements = tuple(par.elements())
1032
- if unit := get_unit(obj.CLASS_ID, elements):
1033
- rep.unit = cdt.Unit(unit).get_name()
1034
- else:
1035
- if s_u := self.par2su(par):
1036
- rep.msg = (settings.report.scaler_format).format(int(data) * 10 ** int(s_u.scaler))
1037
- rep.unit = s_u.unit.get_name()
1038
- else:
1039
- match obj.CLASS_ID, *elements:
1040
- case (ClassID.PROFILE_GENERIC, 3, _) | (ClassID.PROFILE_GENERIC, 6):
1041
- data: structs.CaptureObjectDefinition
1042
- obj = self.get_object(data.logical_name)
1043
- rep.msg = F"{get_name(data.logical_name)}.{obj.get_attr_element(int(data.attribute_index))}"
1044
- case _:
1045
- pass
1046
- rep.log = cdt.Log(logging.INFO)
1047
- except Exception as e:
1048
- rep.log = cdt.Log(logging.ERROR, e)
1049
- finally:
1050
- return rep
1051
-
1052
- @deprecated("use par2su")
1053
- def get_scaler_unit(self,
1054
- obj: ic.COSEMInterfaceClasses,
1055
- par: bytes
1056
- ) -> cdt.ScalUnitType | None:
1057
- match obj.CLASS_ID, *par:
1058
- case (ClassID.REGISTER | ClassID.EXT_REGISTER, 2) | (ClassID.DEMAND_REGISTER, 2 | 3):
1059
- obj: Register | DemandRegister
1060
- if (s_u := obj.scaler_unit) is None:
1061
- raise ic.EmptyAttribute(obj.logical_name, 3)
1062
- else:
1063
- if (s := cdt.get_unit_scaler(s_u.unit.contents)) != 0:
1064
- s_u = s_u.copy()
1065
- s_u.scaler.set(int(s_u.scaler)-s)
1066
- return s_u
1067
- case ClassID.LIMITER, 3 | 4 | 5:
1068
- obj: Limiter
1069
- if m_v := obj.monitored_value:
1070
- return self.get_scaler_unit( # recursion 1 level
1071
- obj=self.get_object(m_v.logical_name),
1072
- par=m_v.attribute_index.contents)
1073
- else:
1074
- raise ic.EmptyAttribute(obj.logical_name, 2)
1075
- case ClassID.REGISTER_MONITOR, 2, _:
1076
- obj: RegisterMonitor
1077
- if (m_v := obj.monitored_value) is None:
1078
- raise ic.EmptyAttribute(obj.logical_name, 3)
1079
- else:
1080
- return self.get_scaler_unit( # recursion 1 level
1081
- obj=self.get_object(m_v.logical_name),
1082
- par=m_v.attribute_index.contents
1083
- )
1084
- case _:
1085
- return None
1086
-
1087
- @lru_cache(20000)
1088
- def par2su(self, par: Parameter) -> Optional[cdt.ScalUnitType]:
1089
- """convert Parameter -> Optional[ScalerUnit],
1090
- raise: NoObject, EmptyAttribute"""
1091
- match (obj := self.par2obj(par).unwrap()).CLASS_ID, par.i:
1092
- case (exc.NoObject, _):
1093
- raise obj
1094
- case (ClassID.REGISTER | ClassID.EXT_REGISTER, 2) | (ClassID.DEMAND_REGISTER, 2 | 3):
1095
- obj: Register | DemandRegister
1096
- if (s_u := obj.scaler_unit) is None:
1097
- raise ic.EmptyAttribute(obj.logical_name, 3)
1098
- else:
1099
- if (s := cdt.get_unit_scaler(s_u.unit.contents)) != 0:
1100
- s_u = s_u.copy()
1101
- s_u.scaler.set(int(s_u.scaler)-s)
1102
- return s_u
1103
- case ClassID.LIMITER, 3 | 4 | 5:
1104
- obj: Limiter
1105
- if m_v := obj.monitored_value:
1106
- return self.par2su(Parameter(m_v.logical_name.contents).set_i(int(m_v.attribute_index))) # recursion 1 level
1107
- else:
1108
- raise ic.EmptyAttribute(obj.logical_name, 2)
1109
- case ClassID.REGISTER_MONITOR, 2, _:
1110
- obj: RegisterMonitor
1111
- if (m_v := obj.monitored_value) is None:
1112
- raise ic.EmptyAttribute(obj.logical_name, 3)
1113
- else:
1114
- return self.par2su(Parameter(m_v.logical_name.contents).set_i(int(m_v.attribute_index))) # recursion 1 level
1115
- case _:
1116
- return None
1117
-
1118
- def par2float(self, par: Parameter) -> float:
1119
- """try convert CDT value according with Parameter to build-in float"""
1120
- data = self.par2data(par).unwrap()
1121
- if hasattr(data, "__int__"):
1122
- value = float(int(data))
1123
- elif hasattr(data, "__float__"):
1124
- value = float(data)
1125
- else:
1126
- raise TypeError("can't convert Parameter data to int or float")
1127
- if (su := self.par2su(par)):
1128
- value *= 10 ** int(su.scaler)
1129
- return value
1130
-
1131
-
1132
- def filter_by_ass(self, ass_id: int) -> list[InterfaceClass]:
1133
- """return only association objects"""
1134
- ret = list()
1135
- for olt in self.getASSOCIATION(ass_id).object_list:
1136
- ret.append(self.par2obj(Parameter(olt.logical_name.contents)).unwrap())
1137
- return ret
1138
-
1139
- def sap2objects(self, value: enums.ClientSAP) -> result.List[ic.COSEMInterfaceClasses]:
1140
- res = result.List()
1141
- for par in self.sap2association(value).iter_pars():
1142
- if isinstance(res1 := self.par2obj(par), result.Error):
1143
- res1.append_err(res.err)
1144
- else:
1145
- res.append(res1)
1146
- return res
1147
-
1148
- def iter_classID_objects(self,
1149
- class_id: ut.CosemClassId) -> Iterator[InterfaceClass]:
1150
- return (obj for obj in self.__objs.values() if obj.CLASS_ID == class_id)
1151
-
1152
- def LNPattern2objects(self,
1153
- pat: LNPattern) -> list[InterfaceClass]:
1154
- ret = []
1155
- for obj in self.__objs.values():
1156
- if obj.logical_name in pat:
1157
- ret.append(obj)
1158
- return ret
1159
-
1160
- def get_first(self, values: list[str | bytes | cst.LogicalName]) -> InterfaceClass:
1161
- """ return first object from it exist in collection from value"""
1162
- for val in values:
1163
- if self.is_in_collection(val):
1164
- return self.get_object(val)
1165
- else:
1166
- """search next"""
1167
- else:
1168
- raise exc.NoObject(F"not found at least one DLMS Objects from collection with {values=}")
1169
-
1170
- @deprecated("use <iter_classID_objects>")
1171
- def get_objects_by_class_id(self, value: ut.CosemClassId) -> list[InterfaceClass]:
1172
- return list(filter(lambda obj: obj.CLASS_ID == value, self.__objs.values()))
1173
-
1174
- def get_writable_attr(self) -> UsedAttributes:
1175
- """return all writable {obj.ln: {attribute_index}}"""
1176
- ret: UsedAttributes = dict()
1177
- for ass in self.iter_classID_objects(ClassID.ASSOCIATION_LN):
1178
- if (
1179
- ass.logical_name.e == 0
1180
- or ass.object_list is None
1181
- ):
1182
- continue
1183
- else:
1184
- for list_type in ass.object_list:
1185
- for attr_access in list_type.access_rights.attribute_access:
1186
- if attr_access.access_mode.is_writable():
1187
- if ret.get(list_type.logical_name, None) is None:
1188
- ret[list_type.logical_name] = set()
1189
- ret[list_type.logical_name].add(int(attr_access.attribute_id))
1190
- return ret
1191
-
1192
- def get_profile_s_u(self,
1193
- obj: ProfileGeneric,
1194
- mask: set[int] = None
1195
- ) -> list[cdt.ScalUnitType | None]:
1196
- """return container of scaler_units if possible, mask: position number in capture_objects"""
1197
- res: list[cdt.ScalUnitType | None] = list()
1198
- for i, obj_def in enumerate(obj.capture_objects):
1199
- obj_def: structs.CaptureObjectDefinition
1200
- if mask and i not in mask:
1201
- continue
1202
- s_u = None
1203
- try:
1204
- s_u = self.get_scaler_unit(
1205
- obj=self.get_object(obj_def.logical_name),
1206
- par=bytes([int(obj_def.attribute_index)]))
1207
- except ic.EmptyAttribute as e:
1208
- print(F"Can't fill Scaler and Unit for {get_name(obj_def.logical_name)}: {e}")
1209
- finally:
1210
- res.append(s_u)
1211
- return res
1212
-
1213
- def copy_obj_attr_values_from(self, other: InterfaceClass) -> bool:
1214
- """ copy all attributes value from other and return bool result """
1215
- try:
1216
- obj: InterfaceClass = self.par2obj(Parameter(other.logical_name.contents)).unwrap()
1217
- for i, attr in other.get_index_with_attributes(in_init_order=True):
1218
- if i == 1:
1219
- continue
1220
- else:
1221
- if attr is not None:
1222
- obj.set_attr(i, attr.encoding)
1223
- return True
1224
- except exc.NoObject as e:
1225
- return False
1226
-
1227
- def copy_objects_attr_values_from(self, other: Self) -> bool:
1228
- """ Copy collections values and return True if all was writen """
1229
- if len(other) != 0:
1230
- return bool(reduce(lambda a, b: a or b, map(self.copy_obj_attr_values_from, other.values())))
1231
- else:
1232
- return False
1233
-
1234
- def obis2obj(self, obis: o.OBIS) -> result.SimpleOrError[InterfaceClass]:
1235
- if obj := self.__objs.get(obis):
1236
- return result.Simple(obj)
1237
- return result.Error.from_e(ValueError(str(obis)), "no object")
1238
-
1239
- def logicalName2obj(self, ln: cst.LogicalName) -> result.SimpleOrError[InterfaceClass]:
1240
- return self.obis2obj(o.OBIS(ln.contents))
1241
-
1242
- @cached_property
1243
- def LDN(self) -> impl.data.LDN:
1244
- return self.obis2obj(o.LDN).unwrap()
1245
-
1246
- @cached_property
1247
- def current_association(self) -> AssociationLN:
1248
- return self.obis2obj(o.CURRENT_ASSOCIATION).unwrap()
1249
-
1250
- def getASSOCIATION(self, instance: int) -> AssociationLN:
1251
- return self.obis2obj(o.AssociationLN(instance)).unwrap()
1252
-
1253
- @cached_property
1254
- def PUBLIC_ASSOCIATION(self) -> AssociationLN:
1255
- return self.obis2obj(o.PUBLIC_ASSOCIATION).unwrap()
1256
-
1257
- @property
1258
- def COMMUNICATION_PORT_PARAMETER(self) -> impl.data.CommunicationPortParameter:
1259
- return self.obis2obj(bytes((0, 0, 96, 12, 4, 255))).unwrap()
1260
-
1261
- @property
1262
- def clock(self) -> Clock:
1263
- return self.obis2obj(bytes((0, 0, 1, 0, 0, 255))).unwrap()
1264
-
1265
- @property
1266
- def boot_image_transfer(self) -> ImageTransfer:
1267
- return self.obis2obj(bytes((0, 0, 44, 0, 128, 255))).unwrap()
1268
-
1269
- @property
1270
- def firmware_image_transfer(self) -> ImageTransfer:
1271
- return self.obis2obj(bytes((0, 0, 44, 0, 0, 255))).unwrap()
1272
-
1273
- @property
1274
- def firmwares_description(self) -> Data:
1275
- """ Consist from boot_version, descriptor, ex.: 0005PWRM_M2M_3_F1_5ppm_Spvq. 0.0.128.100.0.255 """
1276
- return self.obis2obj(bytes((0, 0, 128, 100, 0, 255))).unwrap()
1277
-
1278
- @property
1279
- def RU_CLOSE_ELECTRIC_SEAL(self) -> Data:
1280
- """ Russian. СПОДЕС Г.2 """
1281
- return self.obis2obj(bytes((0, 0, 96, 51, 6, 255))).unwrap()
1282
-
1283
- @property
1284
- def RU_ERASE_MAGNETIC_EVENTS(self) -> Data:
1285
- """ Russian. СПОДЕС Г.2 """
1286
- return self.obis2obj(bytes((0, 0, 96, 51, 7, 255))).unwrap()
1287
-
1288
- @property
1289
- def RU_FILTER_ALARM_2(self) -> Data:
1290
- """ Russian. Filter of Alarm register relay"""
1291
- return self.obis2obj(bytes((0, 0, 97, 98, 11, 255))).unwrap()
1292
-
1293
- @property
1294
- def RU_DAILY_PROFILE(self) -> ProfileGeneric:
1295
- """ Russian. Profile of daily values """
1296
- return self.obis2obj(bytes((1, 0, 98, 2, 0, 255))).unwrap()
1297
-
1298
- @property
1299
- def RU_MAXIMUM_CURRENT_EXCESS_LIMIT(self) -> Register:
1300
- """ RU. СТО 34.01-5.1-006-2021 ver3, 11.1. Maximum current excess limit before the subscriber is disconnected, % of IMAX """
1301
- return self.obis2obj(bytes((1, 0, 11, 134, 0, 255))).unwrap()
1302
-
1303
- @property
1304
- def RU_MAXIMUM_VOLTAGE_EXCESS_LIMIT(self) -> Register:
1305
- """ RU. СТО 34.01-5.1-006-2021 ver3, 11.1. Maximum voltage excess limit before the subscriber is disconnected, % of Unominal """
1306
- return self.obis2obj(bytes((1, 0, 12, 134, 0, 255))).unwrap()
1307
-
1308
- def getDISCONNECT_CONTROL(self, ch: int = 0) -> DisconnectControl:
1309
- """DLMS UA 1000-1 Ed 14 6.2.46 Disconnect control objects by channel"""
1310
- return self.obis2obj(bytes((0, ch, 96, 3, 10, 255))).unwrap()
1311
-
1312
- def getARBITRATOR(self, ch: int = 0) -> Arbitrator:
1313
- """DLMS UA 1000-1 Ed 14 6.2.47 Arbitrator objects objects by channel"""
1314
- return self.obis2obj(bytes((0, ch, 96, 3, 20, 255))).unwrap()
1315
-
1316
- @property
1317
- def boot_version(self) -> str:
1318
- try:
1319
- return self.firmwares_description.value.to_str()[:4]
1320
- except Exception as e:
1321
- print(e)
1322
- return 'unknown'
1323
-
1324
- def get_script_names(self, ln: cst.LogicalName, selector: cdt.LongUnsigned) -> str:
1325
- """return name from script by selector"""
1326
- obj = self.par2obj(Parameter(ln.contents)).unwrap()
1327
- if isinstance(obj, ScriptTable):
1328
- for script in obj.scripts:
1329
- script: ScriptTable.scripts
1330
- if script.script_identifier == selector:
1331
- names: list[str] = []
1332
- for action in script.actions:
1333
- action_obj = self.par2obj(Parameter(action.logical_name.contents)).unwrap()
1334
- if int(action_obj.CLASS_ID) != int(action.class_id):
1335
- raise ValueError(F"got {action_obj.CLASS_ID}, expected {action.class_id}")
1336
- match int(action.service_id):
1337
- case 1: # for write
1338
- if isinstance(action.parameter, cdt.NullData):
1339
- names.append(str(action_obj.get_attr_element(int(action.index))))
1340
- else:
1341
- raise TypeError(F"not support by framework") # TODO: make it
1342
- case 2: # for execute
1343
- if isinstance(action.parameter, cdt.NullData):
1344
- names.append(str(action_obj.get_meth_element(int(action.index))))
1345
- else:
1346
- raise TypeError(F"not support by framework") # TODO: make it
1347
- return ", ".join(names)
1348
- else:
1349
- raise ValueError(F"not find {selector} in {obj}")
1350
- else:
1351
- raise ValueError(F"object with {ln} is not {ScriptTable.CLASS_ID}")
1352
-
1353
- @lru_cache(4)
1354
- def get_association_id(self, client_sap: enums.ClientSAP) -> int:
1355
- """return id(association instance) from it client address without current"""
1356
- for ass in get_filtered(iter(self), (ln_pattern.NON_CURRENT_ASSOCIATION,)):
1357
- if ass.associated_partners_id.client_SAP == client_sap:
1358
- return ass.logical_name.e
1359
- else:
1360
- continue
1361
- else:
1362
- raise ValueError(F"absent association with {client_sap}")
1363
-
1364
- def sap2association(self, sap: enums.ClientSAP) -> AssociationLN:
1365
- for ass in self.iter_classID_objects(ClassID.ASSOCIATION_LN):
1366
- if (
1367
- ass.associated_partners_id is not None
1368
- and ass.associated_partners_id.client_SAP == sap
1369
- ):
1370
- return ass
1371
- else:
1372
- continue
1373
- else:
1374
- raise exc.NoObject(F"hasn't association with {sap}")
1375
-
1376
- @lru_cache(maxsize=1000)
1377
- def is_readable(self, ln: cst.LogicalName,
1378
- index: int,
1379
- association_id: int,
1380
- security_policy: pdu.SecurityPolicy = pdu.SecurityPolicyVer0.NOTHING
1381
- ) -> bool:
1382
- return self.getASSOCIATION(association_id).is_readable(
1383
- ln=ln,
1384
- index=index,
1385
- security_policy=security_policy
1386
- )
1387
-
1388
- @lru_cache(maxsize=1000)
1389
- def is_writable(self, ln: cst.LogicalName,
1390
- index: int,
1391
- association_id: int,
1392
- security_policy: pdu.SecurityPolicy = pdu.SecurityPolicyVer0.NOTHING
1393
- ) -> bool:
1394
- return self.getASSOCIATION(association_id).is_writable(
1395
- ln=ln,
1396
- index=index,
1397
- security_policy=security_policy
1398
- )
1399
-
1400
- @lru_cache(maxsize=1000)
1401
- def isnt_mutable(self,
1402
- par: Parameter,
1403
- association_id: int,
1404
- security_policy: pdu.SecurityPolicy = pdu.SecurityPolicyVer0.NOTHING
1405
- ) -> bool:
1406
- """is not writable and STATIC data"""
1407
- if (
1408
- not self.is_writable(par.ln, par.i, association_id, security_policy, security_policy)
1409
- and self.par2obj(par
1410
- ).unwrap().getAElement(par.i
1411
- ).unwrap().classifier == ic.Classifier.STATIC
1412
- ):
1413
- return True
1414
- return False
1415
-
1416
- @lru_cache(maxsize=1000)
1417
- def is_accessible(self, ln: cst.LogicalName,
1418
- index: int,
1419
- association_id: int,
1420
- m_id: mechanism_id.MechanismIdElement = None
1421
- ) -> bool:
1422
- """for ver 0 and 1 only"""
1423
-
1424
- return self.getASSOCIATION(association_id).is_accessible(
1425
- ln=ln,
1426
- index=index,
1427
- m_id=m_id
1428
- )
1429
-
1430
- @lru_cache(maxsize=100)
1431
- def get_name_and_type(self, value: structs.CaptureObjectDefinition) -> tuple[list[str], Type[cdt.CommonDataType]]:
1432
- """ return names and type of element from collection"""
1433
- names: list[str] = list()
1434
- obj = self.par2obj(Parameter(value.logical_name.contents)).unwrap()
1435
- names.append(get_name(obj.logical_name))
1436
- attr_index = int(value.attribute_index)
1437
- data_index = int(value.data_index)
1438
- data_type: Type[cdt.CommonDataType] = obj.get_attr_data_type(attr_index)
1439
- names.append(str(obj.get_attr_element(attr_index)))
1440
- if data_index == 0:
1441
- pass
1442
- elif issubclass(data_type, cdt.Structure):
1443
- if len(data_type.ELEMENTS) < data_index:
1444
- raise ValueError(F"can't create buffer_struct_type for {self}, got {data_index=} in struct {data_type.__name__}, expected 1..{len(data_type.ELEMENTS)}")
1445
- else:
1446
- el: cdt.StructElement = data_type.ELEMENTS[data_index - 1]
1447
- names.append(el.NAME)
1448
- data_type = el.TYPE
1449
- elif isinstance(obj, ProfileGeneric) and attr_index == 2:
1450
- """according to DLMS UA 1000-1 Ed 14. ProfileGeneric.capture_object.data_index annex"""
1451
- return self.get_name_and_type(obj.capture_objects[data_index - 1]) # todo: is recurse need rewrite here
1452
- else:
1453
- pass
1454
- return names, data_type
1455
-
1456
- def get_attr_tree(self,
1457
- ass_id: int,
1458
- obj_mode: ObjectTreeMode = "c",
1459
- obj_filter: ObjFilteredKey = None,
1460
- sort_mode: SortMode = "",
1461
- af_mode: Literal["l", "r", "w", "lr", "lw", "wr", "lrw", "m", "mlrw", "mlr"] = "l",
1462
- oi_filter: tuple[tuple[ClassID, tuple[int, ...]], ...] = None # todo: maybe ai_filter with LNPattern, indexes need?
1463
- ) -> dict[ClassID | media_id.MediaId, dict[ic.COSEMInterfaceClasses, list[int]]] | dict[ic.COSEMInterfaceClasses, list[int]]: # todo: not all ret annotation
1464
- """af_mode(attribute filter mode): l-reduce logical_name, r-show only readable, w-show only writeable,
1465
- oi_filter(object attribute index filter), example: ((ClassID.REGISTER, (2,))) - is restricted for Register only Value attribute without logical_name and scaler_unit
1466
- """
1467
- objects: dict[ic.COSEMInterfaceClasses, list[int]]
1468
- without_ln = True if "l" in af_mode else False
1469
- only_read = True if "r" in af_mode else False
1470
- only_write = True if "w" in af_mode else False
1471
- with_methods = True if "m" in af_mode else False
1472
- oi_f = dict(oi_filter) if oi_filter else dict()
1473
- """objects indexes filter"""
1474
- filtered = self.filter_by_ass(ass_id)
1475
- if obj_filter:
1476
- filtered = get_filtered(filtered, obj_filter)
1477
- ret = get_object_tree(
1478
- objects=get_sorted(
1479
- objects=filtered,
1480
- mode=sort_mode),
1481
- mode=obj_mode)
1482
- stack = [(None, None, ret)]
1483
- while len(stack) != 0:
1484
- d, k, v = stack.pop()
1485
- if isinstance(d, dict):
1486
- for k_, v_ in tuple(d.items()):
1487
- if len(v_) == 0:
1488
- d.pop(k_)
1489
- if isinstance(v, list):
1490
- objects = dict()
1491
- for obj in v:
1492
- obj: ic.COSEMInterfaceClasses
1493
- f_i = oi_f.get(obj.CLASS_ID)
1494
- """filter indexes"""
1495
- indexes = list()
1496
- for i, attr in obj.get_index_with_attributes():
1497
- if without_ln and i == 1:
1498
- continue
1499
- elif only_read and not self.is_readable(obj.logical_name, i, ass_id):
1500
- continue
1501
- elif only_write and not self.is_writable(obj.logical_name, i, ass_id):
1502
- continue
1503
- elif f_i and i not in f_i:
1504
- continue
1505
- else:
1506
- indexes.append(i)
1507
- if with_methods:
1508
- i_meth = count(1)
1509
- for i, m_el in zip(i_meth, obj.M_ELEMENTS):
1510
- try:
1511
- if not self.is_accessible(obj.logical_name, i, ass_id, mechanism_id.LOW):
1512
- continue
1513
- elif f_i and -i not in f_i:
1514
- continue
1515
- indexes.append(-i)
1516
- except exc.ITEApplication as e:
1517
- print(F"skip {i}... methods for {obj}: {e}")
1518
- break
1519
- if len(indexes) != 0:
1520
- objects[obj] = indexes
1521
- if d is None:
1522
- return objects
1523
- if len(objects) != 0:
1524
- d[k] = objects
1525
- else:
1526
- d.pop(k)
1527
- elif isinstance(v, dict):
1528
- for k_, v_ in v.items():
1529
- stack.append((v, k_, v_))
1530
- else:
1531
- raise ValueError('not support')
1532
- return ret
1533
-
1534
-
1535
- if config is not None:
1536
- try:
1537
- __collection_path = settings.collection.path
1538
- except KeyError as e:
1539
- raise exc.TomlKeyError(F"not find {e} in [DLMS.collection]<path>")
1540
-
1541
-
1542
- @deprecated("use lnContents2obis")
1543
- def get_ln_contents(value: LNContaining) -> bytes:
1544
- """return LN as bytes[6] for use in any searching"""
1545
- match value:
1546
- case bytes(): return value
1547
- case cst.LogicalName() | ut.CosemObjectInstanceId(): return value.contents
1548
- case ut.CosemAttributeDescriptor() | ut.CosemMethodDescriptor(): return value.instance_id.contents
1549
- case ut.CosemAttributeDescriptorWithSelection(): return value.cosem_attribute_descriptor.instance_id.contents
1550
- case cdt.Structure(logical_name=value.logical_name): return value.logical_name.contents
1551
- case cdt.Structure() as s:
1552
- s: cdt.Structure
1553
- for it in s:
1554
- if isinstance(it, cst.LogicalName):
1555
- return it.contents
1556
- raise ValueError(F"can't convert {value=} to Logical Name contents. Struct {s} not content the Logical Name")
1557
- case str() if value.find('.') != -1:
1558
- return cst.LogicalName.from_obis(value).contents
1559
- case str(): return cst.LogicalName(value).contents
1560
- case _: raise ValueError(F"can't convert {value=} to Logical Name contents")
1561
-
1562
-
1563
- def lnContents2obis(value: LNContaining) -> o.OBIS:
1564
- """return LN as OBIS for use in any searching"""
1565
- match value:
1566
- case bytes(): return o.OBIS(value)
1567
- case cst.LogicalName() | ut.CosemObjectInstanceId(): return o.OBIS(value.contents)
1568
- case ut.CosemAttributeDescriptor() | ut.CosemMethodDescriptor(): return o.OBIS(value.instance_id.contents)
1569
- case ut.CosemAttributeDescriptorWithSelection(): return o.OBIS(value.cosem_attribute_descriptor.instance_id.contents)
1570
- case cdt.Structure(logical_name=value.logical_name): return o.OBIS(value.logical_name.contents)
1571
- case cdt.Structure() as s:
1572
- s: cdt.Structure
1573
- for it in s:
1574
- if isinstance(it, cst.LogicalName):
1575
- return o.OBIS(it.contents)
1576
- raise ValueError(F"can't convert {value=} to Logical Name contents. Struct {s} not content the Logical Name")
1577
- case str() if value.find('.') != -1:
1578
- return o.OBIS(cst.LogicalName.from_obis(value).contents)
1579
- case str(): return o.OBIS(cst.LogicalName(value).contents)
1580
- case _: raise ValueError(F"can't convert {value=} to Logical Name contents")
1581
-
1582
-
1583
- class AttrDesc:
1584
- """keep constant descriptors # todo: make better"""
1585
- OBJECT_LIST = ut.CosemAttributeDescriptor((ClassID.ASSOCIATION_LN, ut.CosemObjectInstanceId("0.0.40.0.0.255"), ut.CosemObjectAttributeId(2)))
1586
- LDN_VALUE = ut.CosemAttributeDescriptor((ClassID.DATA, ut.CosemObjectInstanceId("0.0.42.0.0.255"), ut.CosemObjectAttributeId(2)))
1587
- SPODES_VERSION = ut.CosemAttributeDescriptor((ClassID.DATA, ut.CosemObjectInstanceId("0.0.96.1.6.255"), ut.CosemObjectAttributeId(2)))
1588
-
1589
-
1590
- __range10_and_255: tuple = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255
1591
- __range63: tuple = tuple(range(0, 64))
1592
- __range120_and_124_127: tuple = tuple(chain(range(0, 121), range(124, 128)))
1593
- __range100_and_255: tuple = tuple(chain(range(0, 100), (255,)))
1594
- __range100_and_101_125_and_255: tuple = tuple(chain(__range100_and_255, range(101, 126)))
1595
- __c1: tuple = tuple(chain(range(1, 10), (13, 14, 33, 34, 53, 54, 73, 74, 82), range(16, 31), range(36, 51), range(56, 70), range(76, 81), range(84, 90)))
1596
- """DLMS UA 1000-1 Ed 14. Table 45 c1"""
1597
- __table44: tuple = (11, 12, 15, 31, 32, 35, 51, 52, 55, 71, 72, 75, 90, 91, 92)
1598
- """DLMS UA 1000-1 Ed 14. Table 44 for active power"""
1599
- __c2: tuple = tuple(chain(__table44, range(100, 108)))
1600
- """DLMS UA 1000-1 Ed 14. Table 45 c2"""
1601
-
1602
-
1603
- def get_media_id(ln: cst.LogicalName) -> media_id.MediaId:
1604
- return media_id.MediaId.from_int(ln.a)
1605
-
1606
-
1607
- def get_class_id(obj: InterfaceClass) -> ClassID:
1608
- return obj.CLASS_ID
1609
-
1610
-
1611
- def get_map_by_obj(objects: list[InterfaceClass] | tuple[InterfaceClass], key: Callable[[InterfaceClass], ...]) -> dict[media_id.MediaId, list[InterfaceClass]]:
1612
- ret = dict()
1613
- for obj in objects:
1614
- if ret.get(new_key := key(obj)):
1615
- ret[new_key].append(obj)
1616
- else:
1617
- ret[new_key] = [obj]
1618
- return ret
1619
-
1620
-
1621
- def get_object_tree(objects: list[InterfaceClass] | tuple[InterfaceClass],
1622
- mode: ObjectTreeMode) -> dict[media_id.MediaId | ClassID, list[InterfaceClass]]:
1623
- """mode: m-media_id, g-relation_group, c-class_id"""
1624
- mode = list(mode)
1625
- ret = objects
1626
- while mode:
1627
- match mode.pop():
1628
- case "m":
1629
- key = lambda obj: get_media_id(obj.logical_name)
1630
- case "g":
1631
- key = lambda obj: get_relation_group(obj.logical_name)
1632
- case "c":
1633
- key = get_class_id
1634
- case _:
1635
- raise KeyError(F"got unknown {mode=} for get_map")
1636
- stack = [(None, None, ret)]
1637
- while len(stack) != 0:
1638
- d, k, v = stack.pop()
1639
- if isinstance(v, list):
1640
- res = get_map_by_obj(v, key)
1641
- if d is None:
1642
- ret = res
1643
- else:
1644
- d[k] = res
1645
- elif isinstance(v, dict):
1646
- stack.extend(((v, k_, v_) for k_, v_ in v.items()))
1647
- else:
1648
- raise ValueError('not support')
1649
- return ret
1650
-
1651
-
1652
- def get_sorted(objects: list[InterfaceClass],
1653
- mode: SortMode) -> list[InterfaceClass]:
1654
- """mode: l-logical_name, n-name, c-class_id
1655
- """
1656
- mode: list[str] = list(mode)
1657
- while mode:
1658
- match mode.pop():
1659
- case "l":
1660
- key = None
1661
- case "n":
1662
- key = lambda obj: get_name(obj.logical_name)
1663
- case "c":
1664
- key = lambda obj: obj.CLASS_ID
1665
- case _:
1666
- raise KeyError(F"got unknown {mode=} for get_map")
1667
- objects = sorted(objects, key=key)
1668
- return objects
1669
-
1670
- @dataclass
1671
- class Channel:
1672
- """for object filter approve"""
1673
- n: int
1674
-
1675
- def __post_init__(self) -> None:
1676
- if not self.is_channel(self.n):
1677
- raise ValueError(F"got value={self.n}, expected (0..64)")
1678
-
1679
- @staticmethod
1680
- def is_channel(b: int) -> bool:
1681
- return True if 0 <= b <= 64 else False
1682
-
1683
- def is_approve(self, b: int) -> bool:
1684
- if self.is_channel(b):
1685
- return True if b == self.n else False
1686
- else:
1687
- return True
1688
-
1689
-
1690
- RelationGroup: TypeAlias = media_id.Abstract | media_id.Electricity | media_id.Hca | media_id.Gas | media_id.Thermal | media_id.Water | media_id.Other
1691
- RelationGroups: tuple[media_id.MediaId, ...] = (media_id.ABSTRACT, media_id.ELECTRICITY, media_id.HCA, media_id.GAS, media_id.THERMAL, media_id.WATER)
1692
-
1693
-
1694
- @lru_cache(maxsize=1000)
1695
- def get_relation_group(ln: cst.LogicalName) -> RelationGroup:
1696
- if ln.a == media_id.ABSTRACT:
1697
- if ln.c == 0:
1698
- if ln.d == 1:
1699
- return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES
1700
- elif ln.d in (2, 9):
1701
- return media_id.OTHER_ABSTRACT_GENERAL_PURPOSE_OBIS_CODES
1702
- elif ln.c == 1:
1703
- if ln.d in (0, 1, 2, 3, 4, 5, 6):
1704
- return media_id.CLOCK_OBJECTS
1705
- elif ln.c == 2:
1706
- if ln.d in (0, 1, 2):
1707
- return media_id.MODEM_CONFIGURATION_AND_RELATED_OBJECTS
1708
- elif ln.c == 10 and ln.d == 0 and ln.e in (0, 1, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 125):
1709
- return media_id.SCRIPT_TABLE_OBJECTS
1710
- elif ln.c == 11 and ln.d == 0:
1711
- return media_id.SPECIAL_DAYS_TABLE_OBJECTS
1712
- elif ln.c == 12 and ln.d == 0:
1713
- return media_id.SCHEDULE_OBJECTS
1714
- elif ln.c == 13 and ln.d == 0:
1715
- return media_id.ACTIVITY_CALENDAR_OBJECTS
1716
- elif ln.c == 14 and ln.d == 0:
1717
- return media_id.REGISTER_ACTIVATION_OBJECTS
1718
- elif ln.c == 15 and ln.d == 0 and ln.e in (0, 1, 2, 3, 4, 5, 6, 7):
1719
- return media_id.SINGLE_ACTION_SCHEDULE_OBJECTS
1720
- elif ln.c == 16:
1721
- if ln.d == 0 or (ln.d == 1 and ln.e in range(0, 10)):
1722
- return media_id.REGISTER_OBJECTS_MONITOR
1723
- elif ln.d == 2:
1724
- return media_id.PARAMETER_MONITOR_OBJECTS
1725
- elif ln.c == 17 and ln.d == 0:
1726
- return media_id.LIMITER_OBJECTS
1727
- elif ln.c == 18 and ln.d == 0:
1728
- return media_id.ARRAY_MANAGER_OBJECT
1729
- elif ln.c == 19:
1730
- if (ln.d in range(0, 10) and ln.e == 0) or ln.d in range(10, 50) or ln.d in (range(50, 60) and ln.e in (1, 2)):
1731
- return media_id.PAYMENT_METERING_RELATED_OBJECTS
1732
- elif ln.c == 20 and ln.d == 0 and ln.e in (0, 1):
1733
- return media_id.IEC_LOCAL_PORT_SETUP_OBJECTS
1734
- elif ln.c == 21 and ln.d == 0:
1735
- return media_id.STANDARD_READOUT_PROFILE_OBJECTS
1736
- elif ln.c == 22 and ln.d == 0 and ln.e == 0:
1737
- return media_id.IEC_HDLC_SETUP_OBJECTS
1738
- elif ln.c == 23:
1739
- if (ln.d in 0, 1, 2 and ln.e == 0) or ln.d == 3:
1740
- return media_id.IEC_TWISTED_PAIR_1_SETUP_OBJECTS
1741
- elif ln.c == 24:
1742
- if (ln.d in (0, 1, 4, 5, 6) and ln.e == 0) or (ln.d in (2, 8, 9)):
1743
- return media_id.OBJECTS_RELATED_TO_DATA_EXCHANGE_OVER_M_BUS
1744
- elif ln.c == 31 and ln.d == 0 and ln.e == 0:
1745
- return media_id.OBJECTS_RELATED_TO_DATA_EXCHANGE_OVER_M_BUS
1746
- elif ln.c == 25:
1747
- if ln.d in (0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15) and ln.e == 0:
1748
- return media_id.OBJECTS_TO_SET_UP_DATA_EXCHANGE_OVER_THE_INTERNET
1749
- elif ln.d == 9 and ln.e == 0:
1750
- return media_id.OBJECTS_TO_SET_UP_PUSH_SETUP
1751
- elif ln.c == 26 and ln.d in (0, 1, 2, 3, 5, 6) and ln.e == 0:
1752
- return media_id.OBJECTS_FOR_SETTING_UP_DATA_EXCHANGE_USING_S_FSK_PLC
1753
- elif ln.c == 27 and ln.d in (0, 1, 2) and ln.e == 0:
1754
- return media_id.OBJECTS_FOR_SETTING_UP_THE_ISO_IEC_8802_2_LLC_LAYER
1755
- elif ln.c == 28 and ln.d in (0, 1, 2, 3, 4, 5, 6, 7) and ln.e == 0:
1756
- return media_id.OBJECTS_FOR_DATA_EXCHANGE_USING_NARROWBAND_OFDM_PLC_FOR_PRIME_NETWORKS
1757
- elif ln.c == 29 and ln.d in (0, 1, 2) and ln.e == 0:
1758
- return media_id.OBJECTS_FOR_DATA_EXCHANGE_USING_NARROW_BAND_OFDM_PLC_FOR_G3_PLC_NETWORKS
1759
- elif ln.c == 30 and ln.d in (0, 1, 2, 3, 4):
1760
- return media_id.ZIGBEE_SETUP_OBJECTS
1761
- elif ln.c == 32 and ln.d in (0, 1, 2, 3) and ln.e == 0:
1762
- return media_id.OBJECTS_FOR_SETTING_UP_AND_MANAGING_DATA_EXCHANGE_USING_ISO_IEC_14908_PLC_NETWORKS
1763
- elif ln.c == 33 and ln.d in (0, 1, 2, 3) and ln.e == 0:
1764
- return media_id.OBJECTS_FOR_DATA_EXCHANGE_USING_HS_PLC_ISO_IEC_12139_1_ISO_EC_12139_1_NETWORKS
1765
- elif ln.c == 34 and ln.d in (0, 1, 2, 3) and ln.e == 0:
1766
- return media_id.OBJECTS_FOR_DATA_EXCHANGE_USING_WI_SUN_NETWORKS
1767
- elif ln.c == 40 and ln.b == 0 and ln.d == 0:
1768
- return media_id.ASSOCIATION_OBJECTS
1769
- elif ln.c == 41 and ln.b == 0 and ln.d == 0 and ln.e == 0:
1770
- return media_id.SAP_ASSIGNMENT_OBJECT
1771
- elif ln.c == 42 and ln.b == 0 and ln.d == 0 and ln.e == 0:
1772
- return media_id.COSEM_LOGICAL_DEVICE_NAME_OBJECT
1773
- elif ln.c == 43:
1774
- if (ln.b == 0 and ln.d == 0) or ln.d in (1, 2):
1775
- return media_id.INFORMATION_SECURITY_RELATED_OBJECTS
1776
- elif ln.c == 44:
1777
- if ln.b == 0:
1778
- if ln.d == 0:
1779
- return media_id.IMAGE_TRANSFER_OBJECTS
1780
- elif ln.d == 1:
1781
- return media_id.FUNCTION_CONTROL_OBJECTS
1782
- elif ln.d == 2:
1783
- return media_id.COMMUNICATION_PORT_PROTECTION_OBJECTS
1784
- elif ln.c == 65 and ln.d in __range63:
1785
- return media_id.UTILITY_TABLE_OBJECTS
1786
- elif ln.c == 66 and ln.d == 0:
1787
- return media_id.COMPACT_DATA_OBJECTS
1788
- elif ln.c == 96:
1789
- if ln.d == 1:
1790
- if ln.e in __range10_and_255:
1791
- return media_id.DEVICE_ID_OBJECTS
1792
- elif ln.e == 10:
1793
- return media_id.METERING_POINT_ID_OBJECTS
1794
- elif ln.d == 2:
1795
- return media_id.PARAMETER_CHANGES_AND_CALIBRATION_OBJECTS
1796
- elif ln.d == 3:
1797
- if ln.e in (0, 1, 2, 3, 4):
1798
- return media_id.I_O_CONTROL_SIGNAL_OBJECTS
1799
- elif ln.e == 10:
1800
- return media_id.DISCONNECT_CONTROL_OBJECTS
1801
- elif ln.e in range(20, 30):
1802
- return media_id.ARBITRATOR_OBJECTS
1803
- elif ln.d == 4 and ln.e in (0, 1, 2, 3, 4):
1804
- return media_id.STATUS_OF_INTERNAL_CONTROL_SIGNALS_OBJECTS
1805
- elif ln.d == 5 and ln.e in (0, 1, 2, 3, 4):
1806
- return media_id.INTERNAL_OPERATING_STATUS_OBJECTS
1807
- elif ln.d == 6 and ln.e in (0, 1, 2, 3, 4, 5, 6):
1808
- return media_id.BATTERY_ENTRIES_OBJECTS
1809
- elif ln.d == 7 and ln.e in range(0, 22):
1810
- return media_id.POWER_FAILURE_MONITORING_OBJECTS
1811
- elif ln.d == 8 and ln.e in __range63:
1812
- return media_id.OPERATING_TIME_OBJECTS
1813
- elif ln.d == 9 and ln.e in (0, 1, 2):
1814
- return media_id.ENVIRONMENT_RELATED_PARAMETERS_OBJECTS
1815
- elif ln.d == 10 and ln.e in range(1, 11):
1816
- return media_id.STATUS_REGISTER_OBJECTS
1817
- elif ln.d == 11 and ln.e in range(0, 100):
1818
- return media_id.EVENT_CODE_OBJECTS
1819
- elif ln.d == 12 and ln.e in range(0, 7):
1820
- return media_id.COMMUNICATION_PORT_LOG_PARAMETER_OBJECTS
1821
- elif ln.d == 13 and ln.e in (0, 1):
1822
- return media_id.CONSUMER_MESSAGE_OBJECTS
1823
- elif ln.d == 14 and ln.e in range(0, 16):
1824
- return media_id.CURRENTLY_ACTIVE_TARIFF_OBJECTS
1825
- elif ln.d == 15 and ln.e in range(0, 100):
1826
- return media_id.EVENT_COUNTER_OBJECTS
1827
- elif ln.d == 16 and ln.e in range(0, 10):
1828
- return media_id.PROFILE_ENTRY_DIGITAL_SIGNATURE_OBJECTS
1829
- elif ln.d == 17 and ln.e in range(0, 128):
1830
- return media_id.PROFILE_ENTRY_COUNTER_OBJECTS
1831
- elif ln.d == 20:
1832
- return media_id.METER_TAMPER_EVENT_RELATED_OBJECTS
1833
- elif ln.d in range(50, 100):
1834
- return media_id.ABSTRACT_MANUFACTURER_SPECIFIC
1835
- elif ln.c == 97:
1836
- if ln.d == 97 and ln.e in __range10_and_255:
1837
- return media_id.ERROR_REGISTER_OBJECTS
1838
- elif ln.d == 98 and (ln.e in chain(range(0, 30), (255,))):
1839
- return media_id.ALARM_REGISTER_FILTER_DESCRIPTOR_OBJECTS
1840
- elif ln.c == 98:
1841
- return media_id.GENERAL_LIST_OBJECTS
1842
- elif ln.c == 99:
1843
- if ln.d in (1, 2, 12, 13, 14, 15, 16, 17, 18) or (ln.d == 3 and ln.e == 0):
1844
- return media_id.ABSTRACT_DATA_PROFILE_OBJECTS
1845
- if ln.d == 98:
1846
- return media_id.EVENT_LOG_OBJECTS
1847
- elif ln.c == 127 and ln.d == 0:
1848
- return media_id.INACTIVE_OBJECTS
1849
- else:
1850
- return media_id.ABSTRACT
1851
- elif ln.a == media_id.ELECTRICITY:
1852
- if ln.c == 0:
1853
- if ln.d == 0 and ln.e in __range10_and_255:
1854
- return media_id.ID_NUMBERS_ELECTRICITY
1855
- elif ln.d == 1:
1856
- return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_EL
1857
- elif ln.d in (2, 3, 4, 6, 7, 8, 9, 10):
1858
- return media_id.OTHER_ELECTRICITY_RELATED_GENERAL_PURPOSE_OBJECTS
1859
- elif ln.d == 11 and ln.e in (1, 2, 3, 4, 5, 6, 7):
1860
- return media_id.MEASUREMENT_ALGORITHM
1861
- elif ln.c in (1, 21, 41, 61):
1862
- return media_id.ACTIVE_POWER_PLUS
1863
- elif ln.c in (2, 22, 42, 62):
1864
- return media_id.ACTIVE_POWER_MINUS
1865
- elif ln.c in (3, 23, 43, 63):
1866
- return media_id.REACTIVE_POWER_PLUS
1867
- elif ln.c in (4, 24, 44, 64):
1868
- return media_id.REACTIVE_POWER_MINUS
1869
- elif ln.c in (5, 25, 45, 65):
1870
- return media_id.REACTIVE_POWER_QI
1871
- elif ln.c in (6, 26, 46, 66):
1872
- return media_id.REACTIVE_POWER_QII
1873
- elif ln.c in (7, 27, 47, 67):
1874
- return media_id.REACTIVE_POWER_QIII
1875
- elif ln.c in (8, 28, 48, 68):
1876
- return media_id.REACTIVE_POWER_QIV
1877
- elif ln.c in (9, 29, 49, 69):
1878
- return media_id.APPARENT_POWER_PLUS
1879
- elif ln.c in (10, 30, 50, 70):
1880
- return media_id.APPARENT_POWER_MINUS
1881
- elif ln.c in (11, 31, 51, 71):
1882
- return media_id.CURRENT
1883
- elif ln.c in (12, 32, 52, 72):
1884
- return media_id.VOLTAGE
1885
- elif ln.c in (13, 33, 53, 73):
1886
- return media_id.POWER_FACTOR
1887
- elif ln.c in (14, 34, 54, 74):
1888
- return media_id.SUPPLY_FREQUENCY
1889
- elif ln.c in (15, 35, 55, 75):
1890
- return media_id.ACTIVE_POWER_SUM
1891
- elif ln.c in (16, 36, 56, 76):
1892
- return media_id.ACTIVE_POWER_DIFF
1893
- elif ln.c in (17, 37, 57, 77):
1894
- return media_id.ACTIVE_POWER_QI
1895
- elif ln.c in (18, 38, 58, 78):
1896
- return media_id.ACTIVE_POWER_QII
1897
- elif ln.c in (19, 39, 59, 79):
1898
- return media_id.ACTIVE_POWER_QIII
1899
- elif ln.c in (20, 40, 60, 80):
1900
- return media_id.ACTIVE_POWER_QIV
1901
- elif ln.c == 96:
1902
- if ln.d == 1 and ln.e in __range10_and_255:
1903
- return media_id.ELECTRICITY_METERING_POINT_ID_OBJECTS
1904
- elif ln.d == 5 and ln.e in (0, 1, 2, 3, 4, 5):
1905
- return media_id.ELECTRICITY_RELATED_STATUS_OBJECTS
1906
- elif ln.d == 10 and ln.e in (0, 1, 2, 3):
1907
- return media_id.ELECTRICITY_RELATED_STATUS_OBJECTS
1908
- elif ln.c == 98:
1909
- return media_id.LIST_OBJECTS_ELECTRICITY
1910
- elif ln.d in range(31, 46) and ln.f in __range100_and_255:
1911
- if ln.c in __c1 and ln.e in __range63:
1912
- return media_id.THRESHOLD_VALUES
1913
- elif ln.c in __table44 and ln.e in __range120_and_124_127:
1914
- return media_id.THRESHOLD_VALUES
1915
- elif ln.f in __range100_and_255:
1916
- if ln.d in (31, 35, 39, 4, 5, 14, 15, 24, 25):
1917
- if ln.c in __c1 and ln.e in __range63:
1918
- return media_id.REGISTER_MONITOR_OBJECTS
1919
- elif ln.c in __c2 and ln.e in __range120_and_124_127:
1920
- return media_id.REGISTER_MONITOR_OBJECTS
1921
- else:
1922
- return media_id.ELECTRICITY
1923
- elif ln.a == media_id.HCA:
1924
- if ln.c == 0:
1925
- if ln.d == 0 and ln.e in __range10_and_255:
1926
- return media_id.ID_NUMBERS_HCA
1927
- elif ln.d == 1 and ln.e in (1, 2, 10, 11):
1928
- return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_HCA
1929
- elif ln.d == 2 and ln.e in (0, 1, 2, 3):
1930
- return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1931
- elif ln.d == 4 and ln.e in (0, 1, 2, 3, 4, 5, 6):
1932
- return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1933
- elif ln.d == 5 and ln.e in (10, 11):
1934
- return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1935
- elif ln.d == 8 and ln.e in (0, 4, 6):
1936
- return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1937
- elif ln.d == 9 and ln.e in (1, 2, 3):
1938
- return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1939
- elif ln.c in (1, 2) and ln.e == 0:
1940
- if ln.d in (0, 6) and ln.f == 255:
1941
- return media_id.MEASURED_VALUES_HCA_CONSUMPTION
1942
- elif ln.d in (1, 2, 3, 4, 5) and ln.f in __range100_and_101_125_and_255:
1943
- return media_id.MEASURED_VALUES_HCA_CONSUMPTION
1944
- elif ln.c in range(3, 8) and ln.d in (0, 4, 5, 6) and ln.e == 255 and ln.f == 255:
1945
- return media_id.MEASURED_VALUES_HCA_TEMPERATURE
1946
- elif ln.c == 97 and ln.d == 97:
1947
- return media_id.ERROR_REGISTER_OBJECTS_HCA
1948
- elif ln.c == 98:
1949
- return media_id.LIST_OBJECTS_HCA
1950
- elif ln.c == 99 and ln.d == 1:
1951
- return media_id.DATA_PROFILE_OBJECTS_HCA
1952
- else:
1953
- return media_id.HCA
1954
- elif ln.a == media_id.THERMAL:
1955
- if ln.c == 0:
1956
- if ln.d == 0 and ln.e in __range10_and_255:
1957
- return media_id.ID_NUMBERS_THERMAL
1958
- elif ln.d == 1 and ln.e in (1, 2, 10, 11):
1959
- return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_THERMAL
1960
- elif ln.d == 2 and ln.e in chain(range(0, 5), range(10, 14)):
1961
- return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1962
- elif ln.d == 4 and ln.e in (1, 2, 3):
1963
- return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1964
- elif ln.d == 5 and ln.e in chain(range(1, 10), range(21, 25)):
1965
- return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1966
- elif ln.d == 8 and ln.e in chain(range(0, 8), range(11, 15), range(21, 26), range(31, 35)):
1967
- return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1968
- elif ln.d == 9 and ln.e in (1, 2, 3):
1969
- return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1970
- elif ln.c in range(1, 8):
1971
- if ln.e in range(10):
1972
- if ln.d in (0, 1, 2, 3, 7) and ln.f == 255:
1973
- return media_id.MEASURED_VALUES_THERMAL_CONSUMPTION
1974
- elif ln.d in (3, 8, 9) and ln.f in __range100_and_101_125_and_255:
1975
- return media_id.MEASURED_VALUES_THERMAL_CONSUMPTION
1976
- elif ln.d in (1, 2, 4, 5, 12, 13, 14, 15) and ln.f in chain(range(100), range(100, 126)):
1977
- return media_id.MEASURED_VALUES_THERMAL_CONSUMPTION
1978
- elif ln.d == 6 and ln.e == 255 and ln.f == 255:
1979
- return media_id.MEASURED_VALUES_THERMAL_CONSUMPTION
1980
- elif ln.e in range(10):
1981
- if ln.f in __range100_and_101_125_and_255:
1982
- if ln.c in range(1, 8) and ln.d in (5, 15):
1983
- return media_id.MEASURED_VALUES_THERMAL_ENERGY
1984
- elif ln.c in (8, 9) and ln.d in (1, 4, 5, 12, 13, 14, 15):
1985
- return media_id.MEASURED_VALUES_THERMAL_ENERGY
1986
- elif ln.c in range(10, 14):
1987
- if ln.d == 0 and ln.f == 255:
1988
- return media_id.MEASURED_VALUES_THERMAL_ENERGY
1989
- elif ln.d in (4, 5, 14, 15) and ln.f in chain(range(100), range(101, 126)):
1990
- return media_id.MEASURED_VALUES_THERMAL_ENERGY
1991
- elif ln.d in (6, 7, 10, 11) and ln.f == 255:
1992
- return media_id.MEASURED_VALUES_THERMAL_ENERGY
1993
- elif ln.c in range(1, 14) and (ln.d in range(20, 26)) and ln.f == 255:
1994
- return media_id.MEASURED_VALUES_THERMAL_ENERGY
1995
- elif ln.c == 97 and ln.d == 97 and ln.e in (0, 1, 2):
1996
- return media_id.ERROR_REGISTER_OBJECTS_THERMAL
1997
- elif ln.c == 98:
1998
- return media_id.LIST_OBJECTS_THERMAL
1999
- elif ln.c == 99 and ln.f == 255:
2000
- if ln.d in (1, 2) and ln.e in (1, 2, 3):
2001
- return media_id.DATA_PROFILE_OBJECTS_THERMAL
2002
- elif ln.d == 3 and ln.e == 1:
2003
- return media_id.DATA_PROFILE_OBJECTS_THERMAL
2004
- elif ln.d == 99:
2005
- return media_id.DATA_PROFILE_OBJECTS_THERMAL
2006
- else:
2007
- return media_id.THERMAL
2008
- elif media_id.GAS == ln.a:
2009
- if ln.c == 0:
2010
- if ln.d == 0 and ln.e in __range10_and_255:
2011
- return media_id.ID_NUMBERS_GAS
2012
- elif ln.d == 1:
2013
- return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_GAS
2014
- elif ln.d in (2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15):
2015
- return media_id.GENERAL_PURPOSE_OBJECTS_GAS
2016
- elif ln.c == 96 and ln.d == 5 and (ln.e in range(10)):
2017
- return media_id.INTERNAL_OPERATING_STATUS_OBJECTS_GAS
2018
- elif ln.c in chain(range(1, 9), range(11, 17), range(21, 27), range(31, 36), range(61, 66)) and ln.e in __range63:
2019
- if ln.d in (24, 25, 26, 42, 43, 44, 63, 64, 65, 81, 82, 83) and ln.f in chain(range(100), range(101, 127)):
2020
- return media_id.MEASURED_VALUES_GAS_INDEXES_AND_INDEX_DIFFERENCES
2021
- elif ln.d in chain(range(6, 24), range(27, 33), range(45, 51), range(66, 72), range(84, 90)) and ln.f == 255:
2022
- return media_id.MEASURED_VALUES_GAS_INDEXES_AND_INDEX_DIFFERENCES
2023
- elif ln.d in chain(range(33, 42), range(52, 63), range(72, 81), range(90, 99)) and ln.f in chain(__range100_and_101_125_and_255, (126,)):
2024
- return media_id.MEASURED_VALUES_GAS_INDEXES_AND_INDEX_DIFFERENCES
2025
- elif ln.c == 42 and ln.e == 0:
2026
- if ln.d in chain(0, 1, 2, 13, range(15, 19), range(19, 31), range(35, 51), range(55, 71)) and ln.f == 255:
2027
- return media_id.MEASURED_VALUES_GAS_FLOW_RATE
2028
- elif ln.d in chain(range(31, 35), range(51, 55)) and ln.f in chain(__range100_and_101_125_and_255, (126,)):
2029
- return media_id.MEASURED_VALUES_GAS_FLOW_RATE
2030
- elif ln.c in chain((41, 42), range(44, 50)) and ln.d in (0, 2, 3, 10, 11, 13, range(15, 92)) and ln.e == 0 and ln.f == 255:
2031
- return media_id.MEASURED_VALUES_GAS_PROCESS_VALUES
2032
- elif ln.c in range(51, 56):
2033
- if ln.d in (0, 2, 3, 10, 11) and ln.e in chain((0, 1), range(11, 29)) and ln.f == 255:
2034
- return media_id.CONVERSION_RELATED_FACTORS_AND_COEFFICIENTS_GAS
2035
- elif ln.d == 12 and ln.e in range(20) and ln.f == 255:
2036
- return media_id.CALCULATION_METHODS_GAS
2037
- elif ln.c == 70 and ln.f == 255:
2038
- if ln.d in (8, 9) and ln.e == 0:
2039
- return media_id.NATURAL_GAS_ANALYSIS
2040
- elif ln.d in chain(range(10, 21), range(60, 85)) and ln.e in chain((0, 1), range(11, 29)):
2041
- return media_id.NATURAL_GAS_ANALYSIS
2042
- elif ln.c == 98:
2043
- return media_id.LIST_OBJECTS_GAS
2044
- else:
2045
- return media_id.GAS
2046
- elif ln.a == media_id.WATER:
2047
- if ln.c == 0:
2048
- if ln.d == 0 and ln.e in __range10_and_255:
2049
- return media_id.ID_NUMBERS_WATER
2050
- elif ln.d == 1 and ln.e in (1, 2, 10, 11, 12):
2051
- return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_WATER
2052
- elif ln.d == 2 and ln.e in (0, 3):
2053
- return media_id.GENERAL_PURPOSE_OBJECTS_WATER
2054
- elif ln.d in (5, 7) and ln.e == 1:
2055
- return media_id.GENERAL_PURPOSE_OBJECTS_WATER
2056
- elif ln.d == 8 and ln.e in (1, 6):
2057
- return media_id.GENERAL_PURPOSE_OBJECTS_WATER
2058
- elif ln.d == 9 and ln.e in (1, 2, 3):
2059
- return media_id.GENERAL_PURPOSE_OBJECTS_WATER
2060
- elif ln.c == 1 and ln.e in range(0, 13):
2061
- if ln.d in (0, 1, 2, 3, 6) and ln.f == 255:
2062
- return media_id.MEASURED_VALUES_WATER_CONSUMPTION
2063
- elif ln.d in range(1, 6) and ln.f in chain(range(100), range(101, 126)):
2064
- return media_id.MEASURED_VALUES_WATER_CONSUMPTION
2065
- elif ln.c in (2, 3) and ln.e in range(0, 13):
2066
- if ln.d in (0, 1, 2, 3, 6) and ln.f == 255:
2067
- return media_id.MEASURED_VALUES_WATER_MONITORING_VALUES
2068
- elif ln.d in range(1, 6) and ln.f in chain(range(100), range(101, 126)):
2069
- return media_id.MEASURED_VALUES_WATER_MONITORING_VALUES
2070
- elif ln.c == 97 and ln.d == 97:
2071
- return media_id.ERROR_REGISTER_OBJECTS_WATER
2072
- elif ln.c == 98:
2073
- return media_id.LIST_OBJECTS_WATER
2074
- elif ln.c == 99 and ln.d == 1:
2075
- return media_id.DATA_PROFILE_OBJECTS_WATER
2076
- else:
2077
- return media_id.WATER
2078
- return media_id.OTHER_MEDIA
2079
-
2080
-
2081
- DLMSObjectContainer: TypeAlias = Collection | list[InterfaceClass] | filter
2082
-
2083
-
2084
- @dataclass
2085
- class Template:
2086
- name: str
2087
- collections: list[Collection]
2088
- used: UsedAttributes
2089
- description: str = ""
2090
- verified: bool = False
2091
-
2092
- def get_not_contains(self, cols: Iterable[Collection]) -> list[Collection]:
2093
- """return of collections not contains in template"""
2094
- ret = list()
2095
- for i, col in enumerate(cols):
2096
- if col not in self.collections:
2097
- ret.append(col)
2098
- return ret
2099
-
2100
- def get_not_valid(self, col: Collection) -> list[Exception]:
2101
- """with update col"""
2102
- attr: cdt.CommonDataType
2103
- ret = list()
2104
- use_col = self.collections[0]
2105
- """temporary used first collection"""
2106
- for ln, indexes in self.used.items():
2107
- try:
2108
- obj = use_col.get_object(ln)
2109
- obj_col = col.get_object(ln)
2110
- except exc.NoObject as e:
2111
- ret.append(e)
2112
- print(F"<add_collection> skip obj{self}: {e}")
2113
- continue
2114
- for i in indexes:
2115
- if (attr := obj.get_attr(i)) is not None:
2116
- try:
2117
- attr.validate()
2118
- except ValueError as e:
2119
- ret.append(ValueError(F"can't decode value {attr} for {ln}:{i}"))
2120
- else:
2121
- ret.append(exc.NoObject(F"has't attribute {i} for {ln}"))
2122
- return ret
1
+ """ The OBIS identification system serves as a basis for the COSEM logical names. The system of naming COSEM objects is defined in the basic
2
+ principles (see Clause 4 EN 62056-62:2007), the identification of real data items is specified in IEC 62056-61. The following clauses define the
3
+ usage of those definitions in the COSEM environment. All codes, which are not explicitly listed, but outside the manufacturer specific range are
4
+ reserved for future use."""
5
+ from struct import pack
6
+ import inspect
7
+ from dataclasses import dataclass
8
+ from itertools import count, chain
9
+ from functools import reduce, cached_property, lru_cache
10
+ from typing import TypeAlias, Iterator, Type, Self, Callable, Literal, Iterable, Optional, Hashable, Protocol, cast, Annotated
11
+ from semver import Version as SemVer
12
+ from StructResult import result
13
+ from ..types import common_data_types as cdt, cosem_service_types as cst, useful_types as ut
14
+ from ..types.implementations import structs, enums, octet_string
15
+ from . import cosem_interface_class as ic
16
+ from .ln_pattern import LNPattern, LNPatterns
17
+ from .activity_calendar import ActivityCalendar, DayProfileAction
18
+ from .arbitrator import Arbitrator
19
+ from .association_ln import mechanism_id
20
+ from .association_sn.ver0 import AssociationSN as AssociationSNVer0
21
+ from .association_ln.ver0 import AssociationLN as AssociationLNVer0, ObjectListElement
22
+ from .association_ln.ver1 import AssociationLN as AssociationLNVer1
23
+ from .association_ln.ver2 import AssociationLN as AssociationLNVer2
24
+ from .push_setup.ver0 import PushSetup as PushSetupVer0
25
+ from .push_setup.ver1 import PushSetup as PushSetupVer1
26
+ from .push_setup.ver2 import PushSetup as PushSetupVer2
27
+ from .clock import Clock
28
+ from .data import Data
29
+ from .disconnect_control import DisconnectControl
30
+ from .gprs_modem_setup import GPRSModemSetup
31
+ from .gsm_diagnostic.ver0 import GSMDiagnostic as GSMDiagnosticVer0
32
+ from .gsm_diagnostic.ver1 import GSMDiagnostic as GSMDiagnosticVer1
33
+ from .gsm_diagnostic.ver2 import GSMDiagnostic as GSMDiagnosticVer2
34
+ from .iec_hdlc_setup.ver0 import IECHDLCSetup as IECHDLCSetupVer0
35
+ from .iec_hdlc_setup.ver1 import IECHDLCSetup as IECHDLCSetupVer1
36
+ from .image_transfer.ver0 import ImageTransfer
37
+ from .ipv4_setup import IPv4Setup
38
+ from .modem_configuration.ver0 import PSTNModemConfiguration
39
+ from .modem_configuration.ver1 import ModemConfigurationVer1
40
+ from .limiter import Limiter
41
+ from .ntp_setup.ver0 import NTPSetup
42
+ from .profile_generic.ver0 import ProfileGeneric as ProfileGenericVer0
43
+ from .profile_generic.ver1 import ProfileGeneric as ProfileGenericVer1
44
+ from .register import Register
45
+ from .extended_register import ExtendedRegister
46
+ from .demand_register.ver0 import DemandRegister as DemandRegisterVer0
47
+ from .register_activation.ver0 import RegisterActivation
48
+ from .register_monitor import RegisterMonitor
49
+ from .schedule import Schedule
50
+ from .security_setup.ver0 import SecuritySetup as SecuritySetupVer0
51
+ from .security_setup.ver1 import SecuritySetup as SecuritySetupVer1
52
+ from .script_table import ScriptTable
53
+ from .single_action_schedule import SingleActionSchedule
54
+ from .special_days_table import SpecialDaysTable
55
+ from .tcp_udp_setup import TCPUDPSetup
56
+ from .. import exceptions as exc
57
+ from ..relation_to_OBIS import get_name
58
+ from ..cosem_interface_classes import implementations as impl
59
+ from ..cosem_interface_classes.overview import ClassID, CountrySpecificIdentifiers
60
+ from . import obis as o, ln_pattern
61
+ from .. import pdu_enums as pdu
62
+ from ..config_parser import config, get_message
63
+ from ..obis import media_id
64
+ from .parameter import Parameter
65
+ from typing_extensions import deprecated, override
66
+ from ..settings import settings
67
+
68
+
69
+ class CollectionMapError(exc.DLMSException):
70
+ """"""
71
+
72
+
73
+ LNContaining: TypeAlias = bytes | str | cst.LogicalName | cdt.Structure | ut.CosemObjectInstanceId | ut.CosemAttributeDescriptor | ut.CosemAttributeDescriptorWithSelection \
74
+ | ut.CosemMethodDescriptor
75
+
76
+ AssociationSN: TypeAlias = AssociationSNVer0
77
+ AssociationLN: TypeAlias = AssociationLNVer0 | AssociationLNVer1 | AssociationLNVer2
78
+ ModemConfiguration: TypeAlias = PSTNModemConfiguration | ModemConfigurationVer1
79
+ SecuritySetup: TypeAlias = SecuritySetupVer0 | SecuritySetupVer1
80
+ PushSetup: TypeAlias = PushSetupVer0 | PushSetupVer1 | PushSetupVer2
81
+ ProfileGeneric: TypeAlias = ProfileGenericVer1
82
+ DemandRegister: TypeAlias = DemandRegisterVer0
83
+ IECHDLCSetup: TypeAlias = IECHDLCSetupVer0 | IECHDLCSetupVer1
84
+ GSMDiagnostic: TypeAlias = GSMDiagnosticVer0 | GSMDiagnosticVer1 | GSMDiagnosticVer2
85
+ InterfaceClass: TypeAlias = Data | Register | ExtendedRegister | DemandRegister | ProfileGeneric | Clock | ScriptTable | Schedule | SpecialDaysTable | ActivityCalendar | \
86
+ SingleActionSchedule | AssociationLN | IECHDLCSetup | DisconnectControl | Limiter | ModemConfiguration | PSTNModemConfiguration | ImageTransfer | \
87
+ GPRSModemSetup | GSMDiagnostic | SecuritySetup | TCPUDPSetup | IPv4Setup | Arbitrator | RegisterMonitor | PushSetup | AssociationSN | \
88
+ NTPSetup
89
+
90
+
91
+ type AttributeIndex = int
92
+ UsedAttributes: TypeAlias = dict[cst.LogicalName, set[AttributeIndex]]
93
+
94
+
95
+ ObjectTreeMode: TypeAlias = Literal["", "m", "g", "c", "mc", "cm", "gm", "gc", "cg", "gmc"]
96
+ SortMode: TypeAlias = Literal["l", "n", "c", "cl", "cn"]
97
+
98
+
99
+ # todo: make new class ClassMap(for field Collection.spec_map). fields: name, version, dict(current version ClassMap)
100
+ class ClassMap:
101
+
102
+ def __init__(self, *values: type[InterfaceClass]):
103
+ self._values = values
104
+
105
+ def __hash__(self) -> int:
106
+ return hash(it.hash_ for it in self._values)
107
+
108
+ def get(self, ver: int) -> type[InterfaceClass]:
109
+ if (ver := int(ver)) < len(self._values):
110
+ return self._values[ver]
111
+ raise RuntimeError(f"got {ver=}, expected maximal={len(self._values)}")
112
+
113
+ def renew(self, ver: int, cls_: type[InterfaceClass]) -> Self:
114
+ """return with one change"""
115
+ if ver < (l := len(self._values)):
116
+ tmp = list(self._values)
117
+ tmp.insert(ver, cls_)
118
+ return self.__class__(*tmp)
119
+ if ver == l:
120
+ return self.__class__(*(self._values + (cls_,)))
121
+ raise RuntimeError(f"got {ver=}, expected maximal={len(self._values)}")
122
+
123
+ def __str__(self) -> str:
124
+ return f"{self._values[0].CLASS_ID}[{len(self._values)}]"
125
+
126
+
127
+ DataMap = ClassMap(Data)
128
+ DataStaticMap = ClassMap(impl.data.DataStatic)
129
+ DataDynamicMap = ClassMap(impl.data.DataDynamic)
130
+ RegisterMap = ClassMap(Register)
131
+ ExtendedRegisterMap = ClassMap(ExtendedRegister)
132
+ DemandRegisterMap = ClassMap(DemandRegisterVer0)
133
+ RegisterActivationMap = ClassMap(RegisterActivation)
134
+ ProfileGenericMap = ClassMap(ProfileGenericVer0, ProfileGenericVer1)
135
+ ClockMap = ClassMap(Clock)
136
+ ScriptTableMap = ClassMap(ScriptTable)
137
+ ScheduleMap = ClassMap(Schedule)
138
+ SpecialDaysTableMap = ClassMap(SpecialDaysTable)
139
+ AssociationSNMap = ClassMap(AssociationSNVer0)
140
+ AssociationLNMap = ClassMap(AssociationLNVer0, AssociationLNVer1, AssociationLNVer2)
141
+ ImageTransferMap = ClassMap(ImageTransfer)
142
+ ActivityCalendarMap = ClassMap(ActivityCalendar)
143
+ RegisterMonitorMap = ClassMap(RegisterMonitor)
144
+ SingleActionScheduleMap = ClassMap(SingleActionSchedule)
145
+ IECHDLCSetupMap = ClassMap(IECHDLCSetupVer0, IECHDLCSetupVer1)
146
+ ModemConfigurationMap = ClassMap(PSTNModemConfiguration, ModemConfigurationVer1)
147
+ TCPUDPSetupMap = ClassMap(TCPUDPSetup)
148
+ IPv4SetupMap = ClassMap(IPv4Setup)
149
+ GPRSModemSetupMap = ClassMap(GPRSModemSetup)
150
+ GSMDiagnosticMap = ClassMap(GSMDiagnosticVer0, GSMDiagnosticVer1, GSMDiagnosticVer2)
151
+ PushSetupMap = ClassMap(PushSetupVer0, PushSetupVer1, PushSetupVer2)
152
+ SecuritySetupMap = ClassMap(SecuritySetupVer0, SecuritySetupVer1)
153
+ ArbitratorMap = ClassMap(Arbitrator)
154
+ DisconnectControlMap = ClassMap(DisconnectControl)
155
+ LimiterMap = ClassMap(Limiter)
156
+ NTPSetupMap = ClassMap(NTPSetup)
157
+
158
+ # implementation ClassMap
159
+ UnsignedDataMap = ClassMap(impl.data.Unsigned)
160
+
161
+ LN_C: TypeAlias = int
162
+ LN_D: TypeAlias = int
163
+
164
+
165
+ common_interface_class_map: dict[int, ClassMap] = {
166
+ 1: DataMap,
167
+ 3: RegisterMap,
168
+ 4: ExtendedRegisterMap,
169
+ 5: DemandRegisterMap,
170
+ 6: RegisterActivationMap,
171
+ 7: ProfileGenericMap,
172
+ 8: ClockMap,
173
+ 9: ScriptTableMap,
174
+ 10: ScheduleMap,
175
+ 11: SpecialDaysTableMap,
176
+ 15: AssociationLNMap,
177
+ 18: ImageTransferMap,
178
+ 20: ActivityCalendarMap,
179
+ 21: RegisterMonitorMap,
180
+ 22: SingleActionScheduleMap,
181
+ 23: IECHDLCSetupMap,
182
+ 27: ModemConfigurationMap,
183
+ 41: TCPUDPSetupMap,
184
+ 42: IPv4SetupMap,
185
+ 45: GPRSModemSetupMap,
186
+ 47: GSMDiagnosticMap,
187
+ 64: SecuritySetupMap,
188
+ 68: ArbitratorMap,
189
+ 70: DisconnectControlMap,
190
+ 71: LimiterMap,
191
+ 100: NTPSetupMap,
192
+ }
193
+
194
+
195
+ def get_interface_class(class_map: dict[int, ClassMap], c_id: ut.CosemClassId, ver: cdt.Unsigned) -> type[InterfaceClass]:
196
+ """new version <get_type_from_class>"""
197
+ ret = class_map.get(int(c_id), None)
198
+ if isinstance(ret, ClassMap):
199
+ return ret.get(int(ver))
200
+ else:
201
+ if int(c_id) not in common_interface_class_map.keys():
202
+ raise CollectionMapError(F"unknown {c_id=}")
203
+ else:
204
+ raise CollectionMapError(F"got {c_id=}, expected {', '.join(map(str, class_map.keys()))}")
205
+
206
+
207
+ _CUMULATIVE = (1, 2, 11, 12, 21, 22)
208
+ _MAX_MIN_VALUES = (3, 6, 13, 16, 26, 51, 52, 53, 54)
209
+ _CURRENT_AND_LAST_AVERAGE_VALUES = (0, 4, 5, 14, 15, 24, 25, 27, 28, 49, 50, 55, 56)
210
+ _INSTANTANEOUS_VALUES = (7, 39, 41, 42)
211
+ _TIME_INTEGRAL_VALUES = (8, 9, 10, 17, 18, 19, 20, 29, 30, 58)
212
+ _OCCURRENCE_COUNTER = 40
213
+ _CONTRACTED_VALUES = (46,)
214
+ _UNDER_OVER_LIMIT_THRESHOLDS = (31, 35, 43, 44)
215
+ _UNDER_OVER_LIMIT_OCCURRENCE_COUNTERS = (32, 36)
216
+ _UNDER_OVER_LIMIT_DURATIONS = (33, 37)
217
+ _UNDER_OVER_LIMIT_MAGNITUDES = (34, 38)
218
+ _NOT_PROCESSING_OF_MEASUREMENT_VALUES = tuple(set(range(256)).difference((0, 93, 94, 96, 97, 98, 99))) # BlueBook DLMS UA 1000-1 Ed.14 7.5.2.1 Table 66
219
+ _RU_CHANGE_LIMIT_LEVEL = 134
220
+
221
+
222
+ @lru_cache()
223
+ def _create_map(maps: ClassMap | tuple[ClassMap]) -> dict[int, ClassMap]:
224
+ if isinstance(maps, tuple):
225
+ return {int(map_.get(0).CLASS_ID): map_ for map_ in maps}
226
+ else:
227
+ return {int(maps.get(0).CLASS_ID): maps}
228
+
229
+
230
+ A: TypeAlias = int
231
+ B: TypeAlias = int
232
+ C: TypeAlias = int
233
+ D: TypeAlias = int
234
+ E: TypeAlias = int
235
+
236
+
237
+ class Xgroup(Protocol):
238
+ fmt: str
239
+
240
+ def get_key(self) -> Iterator[bytes]:
241
+ ...
242
+
243
+
244
+ class SimpleGroup(Xgroup, Protocol):
245
+ def get_key(self) -> Iterator[bytes]:
246
+ yield pack(self.fmt, *self) # type: ignore[misc]
247
+
248
+
249
+ class ACgroup(SimpleGroup, tuple[A, C]):
250
+ """Grouping of OBIS codes by group A (media) and group C (attribute).
251
+ Creates a composite key for objects sharing the same media type and attribute.
252
+ """
253
+ fmt = ">BB"
254
+
255
+
256
+ class ACDgroup_(Xgroup, Protocol):
257
+ """Grouping of OBIS codes by groups A (media), C (attribute), and D (data) with mask support.
258
+ Supports exact values or masks for attribute (C) and data (D) groups.
259
+ Allows flexible grouping by media type with variable attribute and data patterns.
260
+ """
261
+ fmt: str = ">BBB"
262
+
263
+
264
+ class ACDgroup(SimpleGroup, ACDgroup_, tuple[A, C, D]):
265
+ ...
266
+
267
+
268
+ class ACCDgroup(ACDgroup_, tuple[A, tuple[C, ...], D]):
269
+ def get_key(self) -> Iterator[bytes]:
270
+ a, _, d = self
271
+ return (pack(self.fmt, a, c, d) for c in self[1])
272
+
273
+
274
+ class ACDDgroup(ACDgroup_, tuple[A, C, tuple[D, ...]]):
275
+ def get_key(self) -> Iterator[bytes]:
276
+ a, c, _ = self
277
+ return (pack(self.fmt, a, c, d) for d in self[2])
278
+
279
+
280
+ class ACCDDgroup(ACDgroup_, tuple[A, tuple[C, ...], tuple[D, ...]]):
281
+ """Grouping of OBIS codes by groups A (media), C (attribute), and D (data) with mask support.
282
+ Supports exact values or masks for attribute (C) and data (D) groups.
283
+ Allows flexible grouping by media type with variable attribute and data patterns.
284
+ """
285
+ def get_key(self) -> Iterator[bytes]:
286
+ a = self[0]
287
+ return (pack(self.fmt, a, c, d) for c in self[1] for d in self[2])
288
+
289
+
290
+ class ACDEgroup_(Xgroup, Protocol):
291
+ """Grouping of OBIS codes by groups A (media), C (attribute), D (data), and E (data version) with mask support.
292
+ Groups by fixed media and attribute with support for data and data version masks.
293
+ Enables precise control over data grouping with optional version filtering.
294
+ """
295
+ fmt: str = ">BBBB"
296
+
297
+
298
+ class ACDEgroup(SimpleGroup, ACDEgroup_, tuple[A, C, D, E]):
299
+ ...
300
+
301
+
302
+ class ACDDEgroup(ACDEgroup_, tuple[A, C, tuple[D, ...], E]):
303
+ def get_key(self) -> Iterator[bytes]:
304
+ a, c, _, e = self
305
+ return (pack(self.fmt, a, c, d, e) for d in self[2])
306
+
307
+
308
+
309
+ class ACDEEgroup(ACDEgroup_, tuple[A, C, D, tuple[E, ...]]):
310
+ def get_key(self) -> Iterator[bytes]:
311
+ a, c, d, _ = self
312
+ return (pack(self.fmt, a, c, d, e) for e in self[3])
313
+
314
+
315
+ class ACDDEEgroup(ACDEgroup_, tuple[A, C, tuple[D, ...], tuple[E, ...]]):
316
+ def get_key(self) -> Iterator[bytes]:
317
+ a, c, _, _ = self
318
+ return (pack(self.fmt, a, c, d, e) for d in self[2] for e in self[3])
319
+
320
+
321
+ class ABCDEgroup_(Xgroup, Protocol):
322
+ """Grouping of OBIS codes by all OBIS groups A-E with data version mask support.
323
+
324
+ Comprehensive grouping by:
325
+ - A: media
326
+ - B: channel/interface
327
+ - C: attribute
328
+ - D: data
329
+ - E: data version (with mask support)
330
+
331
+ Provides complete OBIS code grouping with flexible version matching.
332
+ """
333
+ fmt: str = ">BBBBB"
334
+
335
+
336
+ class ABCDEgroup(SimpleGroup, ABCDEgroup_, tuple[A, B, C, D, E]):
337
+ ...
338
+
339
+
340
+ class ABCCDEgroup(ABCDEgroup_, tuple[A, B, tuple[C, ...], D, E]):
341
+ def get_key(self) -> Iterator[bytes]:
342
+ a, b, _, d, e = self
343
+ return (pack(self.fmt, a, b, c, d, e) for c in self[2])
344
+
345
+
346
+ class ABCDEEgroup(ABCDEgroup_, tuple[A, B, C, D, tuple[E, ...]]):
347
+ def get_key(self) -> Iterator[bytes]:
348
+ a, b, c, d, _ = self
349
+ return (pack(self.fmt, a, b, c, d, e) for e in self[4])
350
+
351
+
352
+ type PossibleGroup = ACgroup | ACDgroup_ | ACDEgroup | ABCDEgroup
353
+ FUNC_MAP: TypeAlias = dict[bytes, dict[int, ClassMap]]
354
+ """ln.BCDE | ln.CDE | ln.CD | ln.C: {class_id: {version: CosemInterfaceClass}}"""
355
+
356
+
357
+ func_maps: dict[str, FUNC_MAP] = {}
358
+ type PossibleClassMap = tuple[ClassMap, ...] | ClassMap
359
+
360
+
361
+ def get_func_map(for_create_map: dict[PossibleGroup, PossibleClassMap]) -> FUNC_MAP:
362
+ ret: FUNC_MAP = {}
363
+ for g in for_create_map:
364
+ for k in g.get_key():
365
+ ret[k] = _create_map(for_create_map[g])
366
+ return ret
367
+
368
+
369
+ __func_map_for_create: dict[PossibleGroup, PossibleClassMap] = {
370
+ # abstract
371
+ ACDgroup((0, 0, 1)): DataMap,
372
+ ACDgroup((0, 0, 2)): DataMap,
373
+ ACDEgroup((0, 0, 2, 1)): ClassMap(impl.data.ActiveFirmwareId),
374
+ ACDgroup((0, 0, 9)): DataMap,
375
+ ACDgroup((0, 1, 0)): ClockMap,
376
+ ACDgroup((0, 1, 1)): DataMap,
377
+ ACDgroup((0, 1, 2)): DataMap,
378
+ ACDgroup((0, 1, 3)): DataMap,
379
+ ACDgroup((0, 1, 4)): DataMap,
380
+ ACDgroup((0, 1, 5)): DataMap,
381
+ ACDgroup((0, 1, 6)): DataMap,
382
+ ACDEgroup((0, 2, 0, 0)): ModemConfigurationMap,
383
+ #
384
+ ACDEEgroup((0, 10, 0, (0, 1, 125)+tuple(range(100, 112)))): ScriptTableMap,
385
+ ACDgroup((0, 11, 0)): SpecialDaysTableMap,
386
+ ACDgroup((0, 12, 0)): ScheduleMap,
387
+ ACDgroup((0, 13, 0)): ActivityCalendarMap,
388
+ ACDgroup((0, 14, 0)): RegisterActivationMap,
389
+ ACDEEgroup((0, 15, 0, tuple(range(0, 8)))): SingleActionScheduleMap,
390
+ ACDgroup((0, 16, 0)): RegisterMonitorMap,
391
+ ACDEEgroup((0, 16, 1, tuple(range(0, 10)))): RegisterMonitorMap,
392
+ #
393
+ ACDgroup((0, 17, 0)): LimiterMap,
394
+ #
395
+ ACDDEEgroup((0, 19, tuple(range(50, 60)), (1, 2))): DataMap,
396
+ #
397
+ ACDgroup((0, 21, 0)): (DataMap, ProfileGenericMap),
398
+ ACDEgroup((0, 22, 0, 0)): IECHDLCSetupMap,
399
+ #
400
+ ACDEgroup((0, 23, 2, 0)): DataMap,
401
+ ACDEEgroup((0, 23, 3, tuple(range(0, 10)))): (DataMap, ProfileGenericMap),
402
+ ACDEEgroup((0, 23, 3, tuple(range(10, 256)))): DataMap,
403
+ #
404
+ ACDgroup((0, 24, 2)): ExtendedRegisterMap,
405
+ ACDgroup((0, 24, 3)): ProfileGenericMap,
406
+ ACDEgroup((0, 24, 4, 0)): DisconnectControlMap,
407
+ ACDEgroup((0, 24, 5, 0)): ProfileGenericMap,
408
+ #
409
+ ACDEgroup((0, 25, 0, 0)): TCPUDPSetupMap,
410
+ ACDEgroup((0, 25, 1, 0)): IPv4SetupMap,
411
+ #
412
+ ACDEgroup((0, 25, 4, 0)): GPRSModemSetupMap,
413
+ #
414
+ ACDEgroup((0, 25, 6, 0)): GSMDiagnosticMap,
415
+ #
416
+ ACDEgroup((0, 25, 9, 0)): PushSetupMap,
417
+ ACDEgroup((0, 25, 10, 0)): NTPSetupMap,
418
+ #
419
+ ABCDEEgroup((0, 0, 40, 0, tuple(range(8)))): (AssociationSNMap, AssociationLNMap), # todo: now limit by 8 association, solve it
420
+ #
421
+ ABCDEgroup((0, 0, 42, 0, 0)): ClassMap(impl.data.LDN),
422
+ ABCDEEgroup((0, 0, 43, 0, tuple(range(256)))): SecuritySetupMap,
423
+ ACDgroup((0, 43, 1)): DataMap,
424
+ #
425
+ ABCDEEgroup((0, 0, 44, 0, tuple(range(256)))): ImageTransferMap,
426
+ #
427
+ ACDEEgroup((0, 96, 1, tuple(range(0, 11)))): ClassMap(impl.data.DLMSDeviceIDObject),
428
+ ACDEgroup((0, 96, 1, 255)): ProfileGenericMap, # todo: add RegisterTable
429
+ ACDgroup((0, 96, 2)): DataDynamicMap,
430
+ ACDEEgroup((0, 96, 3, tuple(range(0, 4)))): DataMap, # todo: add StatusMapping
431
+ ACDEgroup((0, 96, 3, 10)): DisconnectControlMap,
432
+ ACDEEgroup((0, 96, 3, tuple(range(20, 29)))): ArbitratorMap,
433
+ ACDDEgroup((0, 96, (4, 5), 0)): (DataMap, ProfileGenericMap), # todo: add RegisterTable, StatusMapping
434
+ ACDDEEgroup((0, 96, (4, 5), (1, 2, 3, 4))): DataMap, # todo: add StatusMapping
435
+ ACDEEgroup((0, 96, 6, tuple(range(0, 7)))): (DataMap, RegisterMap, ExtendedRegisterMap),
436
+ ACDEEgroup((0, 96, 7, tuple(range(0, 22)))): (DataMap, RegisterMap, ExtendedRegisterMap),
437
+ ACDEEgroup((0, 96, 8, tuple(range(0, 64)))): (DataMap, RegisterMap, ExtendedRegisterMap),
438
+ ACDEEgroup((0, 96, 9, (0, 1, 2))): (RegisterMap, ExtendedRegisterMap),
439
+ ACDEEgroup((0, 96, 10, tuple(range(1, 10)))): DataMap, # todo: add StatusMapping
440
+ ACDEEgroup((0, 96, 11, tuple(range(100)))): (DataDynamicMap, RegisterMap, ExtendedRegisterMap),
441
+ ACDEEgroup((0, 96, 12, (0, 1, 2, 3, 5, 6))): (DataMap, RegisterMap, ExtendedRegisterMap),
442
+ ACDEgroup((0, 96, 12, 4)): ClassMap(impl.data.CommunicationPortParameter),
443
+ ACDEEgroup((0, 96, 13, (0, 1))): (DataMap, RegisterMap, ExtendedRegisterMap),
444
+ ACDEEgroup((0, 96, 14, tuple(range(16)))): (DataMap, RegisterMap, ExtendedRegisterMap),
445
+ ACDEEgroup((0, 96, 15, tuple(range(100)))): (DataMap, RegisterMap, ExtendedRegisterMap),
446
+ ACDEEgroup((0, 96, 16, tuple(range(10)))): (DataMap, RegisterMap, ExtendedRegisterMap),
447
+ ACDEEgroup((0, 96, 17, tuple(range(128)))): (DataMap, RegisterMap, ExtendedRegisterMap),
448
+ ACDgroup((0, 96, 20)): (DataMap, RegisterMap, ExtendedRegisterMap),
449
+ ACDEEgroup((0, 97, 97, tuple(range(10)))): DataMap,
450
+ ACDDEgroup((0, 97, (97, 98), 255)): ProfileGenericMap, # todo: add RegisterTable
451
+ ACDEEgroup((0, 97, 98, tuple(range(10))+tuple(range(10, 30)))): DataMap,
452
+ ACgroup((0, 98)): ProfileGenericMap,
453
+ ACDgroup((0, 99, 98)): ProfileGenericMap,
454
+ # electricity
455
+ ACDEEgroup((1, 0, 0, tuple(range(10)))): DataMap,
456
+ ACDEgroup((1, 0, 0, 255)): ProfileGenericMap, # todo: add RegisterTable
457
+ ACDgroup((1, 0, 1)): DataMap,
458
+ ACDgroup((1, 0, 2)): DataStaticMap,
459
+ ACDDgroup((1, 0, (3, 4, 7, 8, 9))): (DataStaticMap, RegisterMap, ExtendedRegisterMap),
460
+ ACDDgroup((1, 0, (6, 10))): (RegisterMap, ExtendedRegisterMap),
461
+ ACDEEgroup((1, 0, 11, tuple(range(1, 8)))): DataMap,
462
+ ACDEEgroup((1, 96, 1, tuple(range(10)))): DataMap,
463
+ ACDEgroup((1, 96, 1, 255)): ProfileGenericMap, # todo: add RegisterTable
464
+ ACDEEgroup((1, 96, 5, (0, 1, 2, 3, 4, 5))): DataMap, # todo: add StatusMapping
465
+ ACDEEgroup((1, 96, 10, (0, 1, 2, 3))): DataMap, # todo: add StatusMapping
466
+ ACgroup((1, 98)): ProfileGenericMap,
467
+ ACDDgroup((1, 99, (1, 2, 11, 12, 97, 98, 99))): ProfileGenericMap,
468
+ ACDDEgroup((1, 99, (3, 13, 14), 0)): ProfileGenericMap,
469
+ ACDEEgroup((1, 99, 10, (1, 2, 3))): ProfileGenericMap,
470
+ ACCDgroup((1, _CUMULATIVE, _RU_CHANGE_LIMIT_LEVEL)): RegisterMap,
471
+ ACCDDgroup((
472
+ 1,
473
+ _NOT_PROCESSING_OF_MEASUREMENT_VALUES,
474
+ tuple(chain(
475
+ _CUMULATIVE,
476
+ _TIME_INTEGRAL_VALUES,
477
+ _CONTRACTED_VALUES,
478
+ _UNDER_OVER_LIMIT_THRESHOLDS,
479
+ _UNDER_OVER_LIMIT_OCCURRENCE_COUNTERS,
480
+ _UNDER_OVER_LIMIT_DURATIONS,
481
+ _UNDER_OVER_LIMIT_MAGNITUDES
482
+ )))): (RegisterMap, ExtendedRegisterMap),
483
+ ACCDDgroup((1, _NOT_PROCESSING_OF_MEASUREMENT_VALUES, _INSTANTANEOUS_VALUES)): RegisterMap,
484
+ ACCDDgroup((1, _NOT_PROCESSING_OF_MEASUREMENT_VALUES, _MAX_MIN_VALUES)): (RegisterMap, ExtendedRegisterMap, ProfileGenericMap),
485
+ ACCDDgroup((1, _NOT_PROCESSING_OF_MEASUREMENT_VALUES, _CURRENT_AND_LAST_AVERAGE_VALUES)): (RegisterMap, DemandRegisterMap),
486
+ ACCDgroup((1, _NOT_PROCESSING_OF_MEASUREMENT_VALUES, 40)): (DataMap, RegisterMap),
487
+ }
488
+
489
+ func_maps["DLMS_6"] = get_func_map(__func_map_for_create)
490
+
491
+
492
+ # SPODES3 Update
493
+ __func_map_for_create.update({
494
+ ACDgroup((0, 21, 0)): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3DisplayReadout),
495
+ ACDEEgroup((0, 96, 1, (0, 2, 4, 5, 8, 9, 10))): ClassMap(impl.data.SPODES3IDNotSpecific),
496
+ ACDEgroup((0, 96, 1, 6)): ClassMap(impl.data.SPODES3SPODESVersion),
497
+ ACDEEgroup((0, 96, 2, (1, 2, 3, 5, 6, 7, 11, 12))): ClassMap(impl.data.AnyDateTime),
498
+ ACDEgroup((0, 96, 3, 20)): ClassMap(impl.arbitrator.SPODES3Arbitrator),
499
+ ACDEgroup((0, 96, 4, 3)): ClassMap(impl.data.SPODES3LoadLocker),
500
+ ACDEgroup((0, 96, 5, 1)): ClassMap(impl.data.SPODES3PowerQuality2Event),
501
+ ACDEgroup((0, 96, 5, 4)): ClassMap(impl.data.SPODES3PowerQuality1Event),
502
+ ACDEgroup((0, 96, 5, 132)): ClassMap(impl.data.Unsigned), # TODO: make according with СПОДЭС3 13.9. Контроль чередования фаз
503
+ ACDEgroup((0, 96, 11, 0)): ClassMap(impl.data.SPODES3VoltageEvent),
504
+ ACDEgroup((0, 96, 11, 1)): ClassMap(impl.data.SPODES3CurrentEvent),
505
+ ACDEgroup((0, 96, 11, 2)): ClassMap(impl.data.SPODES3CommutationEvent),
506
+ ACDEgroup((0, 96, 11, 3)): ClassMap(impl.data.SPODES3ProgrammingEvent),
507
+ ACDEgroup((0, 96, 11, 4)): ClassMap(impl.data.SPODES3ExternalEvent),
508
+ ACDEgroup((0, 96, 11, 5)): ClassMap(impl.data.SPODES3CommunicationEvent),
509
+ ACDEgroup((0, 96, 11, 6)): ClassMap(impl.data.SPODES3AccessEvent),
510
+ ACDEgroup((0, 96, 11, 7)): ClassMap(impl.data.SPODES3SelfDiagnosticEvent),
511
+ ACDEgroup((0, 96, 11, 8)): ClassMap(impl.data.SPODES3ReactivePowerEvent),
512
+ ABCDEgroup((0, 0, 96, 51, 0)): ClassMap(impl.data.OpeningBody),
513
+ ABCDEgroup((0, 0, 96, 51, 1)): ClassMap(impl.data.OpeningCover),
514
+ ABCDEgroup((0, 0, 96, 51, 3)): ClassMap(impl.data.ExposureToMagnet),
515
+ ABCDEgroup((0, 0, 96, 51, 4)): ClassMap(impl.data.ExposureToHSField),
516
+ ABCDEgroup((0, 0, 96, 51, 5)): ClassMap(impl.data.SealStatus),
517
+ ABCDEEgroup((0, 0, 96, 51, (6, 7))): UnsignedDataMap,
518
+ ABCDEEgroup((0, 0, 96, 51, (8, 9))): ClassMap(impl.data.OctetStringDateTime),
519
+ ABCDEEgroup((0, 0, 97, 98, (0, 10, 20))): ClassMap(impl.data.SPODES3Alarm1),
520
+ ABCDEEgroup((0, 0, 97, 98, (1, 11))): ClassMap(impl.data.SPODES3ControlAlarm1),
521
+ # electricity
522
+ ACDEEgroup((1, 0, 8, (4, 5))): ClassMap(impl.data.SPODES3MeasurementPeriod),
523
+ ACDgroup((1, 98, 1)): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3MonthProfile),
524
+ ACDgroup((1, 98, 2)): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3DailyProfile),
525
+ ACDDgroup((1, 99, (1, 2))): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3LoadProfile),
526
+ ABCDEgroup((1, 0, 131, 35, 0)): RegisterMap,
527
+ ABCDEgroup((1, 0, 133, 35, 0)): RegisterMap,
528
+ ABCDEgroup((1, 0, 147, 133, 0)): RegisterMap,
529
+ ABCDEgroup((1, 0, 148, 136, 0)): RegisterMap,
530
+ ACDEgroup((1, 94, 7, 0)): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3CurrentProfile),
531
+ ACDEEgroup((1, 94, 7, (1, 2, 3, 4, 5, 6))): ProfileGenericMap.renew(1, impl.profile_generic.SPODES3ScalesProfile), # Todo: RU. Scaler-profile With 1 entry and more
532
+ })
533
+
534
+ func_maps["SPODES_3"] = get_func_map(__func_map_for_create)
535
+
536
+ # KPZ Update
537
+ __func_map_for_create.update({
538
+ ACDEgroup((0, 96, 11, 4)): ClassMap(impl.data.KPZSPODES3ExternalEvent),
539
+ ABCDEEgroup((0, 0, 97, 98, (0, 10, 20))): ClassMap(impl.data.KPZAlarm1),
540
+ ABCDEgroup((0, 128, 25, 6, 0)): ClassMap(impl.data.DataStatic),
541
+ ABCDEEgroup((0, 128, 96, 2, (0, 1, 2))): ClassMap(impl.data.KPZAFEOffsets),
542
+ ABCDEgroup((0, 128, 96, 13, 1)): ClassMap(impl.data.ITEBitMap),
543
+ ABCDEgroup((0, 128, 154, 0, 0)): ClassMap(impl.data.KPZGSMPingIP),
544
+ ACDEEgroup((0, 0, 128, (100, 101, 102, 103, 150, 151, 152, 170))): DataMap,
545
+ ABCCDEgroup((128, 0, tuple(range(20)), 0, 0)): RegisterMap
546
+ })
547
+ func_maps["KPZ"] = get_func_map(__func_map_for_create)
548
+ # KPZ1 with bag in log event profiles
549
+ __func_map_for_create.update({
550
+ ACDEgroup((0, 96, 11, 0)): ClassMap(impl.data.KPZ1SPODES3VoltageEvent),
551
+ ACDEgroup((0, 96, 11, 1)): ClassMap(impl.data.KPZ1SPODES3CurrentEvent),
552
+ ACDEgroup((0, 96, 11, 2)): ClassMap(impl.data.KPZ1SPODES3CommutationEvent),
553
+ ACDEgroup((0, 96, 11, 3)): ClassMap(impl.data.KPZ1SPODES3ProgrammingEvent),
554
+ ACDEgroup((0, 96, 11, 4)): ClassMap(impl.data.KPZ1SPODES3ExternalEvent),
555
+ ACDEgroup((0, 96, 11, 5)): ClassMap(impl.data.KPZ1SPODES3CommunicationEvent),
556
+ ACDEgroup((0, 96, 11, 6)): ClassMap(impl.data.KPZ1SPODES3AccessEvent),
557
+ ACDEgroup((0, 96, 11, 7)): ClassMap(impl.data.KPZ1SPODES3SelfDiagnosticEvent),
558
+ ACDEgroup((0, 96, 11, 8)): ClassMap(impl.data.KPZ1SPODES3ReactivePowerEvent),
559
+ })
560
+ func_maps["KPZ1"] = get_func_map(__func_map_for_create)
561
+
562
+
563
+ def get_type(class_id: ut.CosemClassId,
564
+ version: cdt.Unsigned,
565
+ ln: cst.LogicalName,
566
+ func_map: FUNC_MAP) -> Type[InterfaceClass]:
567
+ """use DLMS UA 1000-1 Ed. 14 Table 54"""
568
+ c_m: Optional[dict[int, ClassMap]]
569
+ if (
570
+ (128 <= ln.b <= 199)
571
+ or (128 <= ln.c <= 199)
572
+ or ln.c == 240
573
+ or (128 <= ln.d <= 254)
574
+ or (128 <= ln.e <= 254)
575
+ or (128 <= ln.f <= 254)
576
+ ):
577
+ # try search in ABCDE group for manufacture object before in CDE
578
+ c_m = func_map.get(ln.contents[:5], common_interface_class_map)
579
+ else:
580
+ # try search in ABCDE group
581
+ c_m = func_map.get(ln.contents[:5], None)
582
+ if c_m is None:
583
+ # try search in A-CDE group
584
+ c_m = func_map.get(ln.contents[:1]+ln.contents[2:5], None)
585
+ if c_m is None:
586
+ # try search in A-CD group
587
+ c_m = func_map.get(ln.contents[:1]+ln.contents[2:4], None)
588
+ if c_m is None:
589
+ # try search in A-C group
590
+ c_m = func_map.get(ln.contents[:1]+ln.contents[3:4], common_interface_class_map)
591
+ return get_interface_class(class_map=c_m,
592
+ c_id=class_id,
593
+ ver=version)
594
+
595
+
596
+ @lru_cache(20000)
597
+ def get_unit(class_id: ClassID, elements: Iterable[int]) -> int | None:
598
+ match class_id, *elements:
599
+ case (ClassID.LIMITER, 6 | 7) | (ClassID.LIMITER, 8, 2) | (ClassID.DEMAND_REGISTER, 8) | (ClassID.PROFILE_GENERIC, 4) | (ClassID.PUSH_SETUP, 5) |\
600
+ (ClassID.PUSH_SETUP, 7, _) | (ClassID.PUSH_SETUP, 12, 1) | (ClassID.COMMUNICATION_PORT_PROTECTION, 4 | 6) | (ClassID.CHARGE, 8) | (ClassID.IEC_HDLC_SETUP, 8) \
601
+ | (ClassID.AUTO_CONNECT, 4):
602
+ return 7 # second
603
+ case ClassID.CLOCK, 3 | 7:
604
+ return 6 # min
605
+ case (ClassID.IEC_HDLC_SETUP, 7) | (ClassID.MODEM_CONFIGURATION, 3, 2):
606
+ return 7 # millisecond
607
+ case ClassID.S_FSK_PHY_MAC_SET_UP, 7, _:
608
+ return 44 # HZ
609
+ case (ClassID.S_FSK_PHY_MAC_SET_UP, 4 | 5):
610
+ return 72 # Db
611
+ case ClassID.S_FSK_PHY_MAC_SET_UP, 6:
612
+ return 71 # DbmicroV
613
+ case _:
614
+ return None
615
+
616
+
617
+ type ObjFilteredKey = tuple[ClassID | LNPattern | LNPatterns | Channel, ...]
618
+
619
+
620
+ def get_filtered(objects: Iterable[InterfaceClass],
621
+ keys: ObjFilteredKey) -> list[InterfaceClass]:
622
+ c_ids: list[ut.CosemClassId] = list()
623
+ patterns: list[LNPattern] = list()
624
+ ch: Optional[Channel] = None
625
+ for k in keys:
626
+ if isinstance(k, ut.CosemClassId):
627
+ c_ids.append(k)
628
+ elif isinstance(k, LNPattern):
629
+ patterns.append(k)
630
+ elif isinstance(k, LNPatterns):
631
+ patterns.extend(k)
632
+ elif isinstance(k, Channel):
633
+ ch = k
634
+ new_list = list()
635
+ for obj in objects:
636
+ if obj.CLASS_ID in c_ids:
637
+ pass
638
+ elif obj.logical_name in patterns:
639
+ pass
640
+ else:
641
+ continue
642
+ if ch and not ch.is_approve(obj.logical_name.b):
643
+ continue
644
+ new_list.append(obj)
645
+ return new_list
646
+
647
+
648
+ @dataclass(unsafe_hash=True, frozen=True)
649
+ class ParameterValue:
650
+ par: bytes
651
+ value: bytes
652
+
653
+ def __str__(self) -> str:
654
+ return F"{'.'.join(map(str, self.par[:6]))}:{self.par[6]} - {cdt.get_instance_and_pdu_from_value(self.value)[0].__repr__()}"
655
+
656
+ def __bytes__(self) -> bytes:
657
+ """par + 0x00 + value""" # todo: 00 in future other parameters
658
+ return self.par + b'\x00' + self.value
659
+
660
+ @classmethod
661
+ def parse(cls, value: bytes) -> Self:
662
+ if value[7] != 0:
663
+ raise exc.ITEApplication(F"wrong {value!r} for {cls.__name__}")
664
+ return cls(
665
+ par=value[:7],
666
+ value=value[8:]
667
+ )
668
+
669
+
670
+ @dataclass(frozen=True, unsafe_hash=True)
671
+ class ID:
672
+ man: bytes
673
+ f_id: ParameterValue
674
+ f_ver: ParameterValue
675
+
676
+
677
+ class Collection:
678
+ __id: ID
679
+ __dlms_ver: int
680
+ __country: Optional[CountrySpecificIdentifiers]
681
+ __country_ver: Optional[ParameterValue]
682
+ __objs: dict[o.OBIS, InterfaceClass]
683
+ __const_objs: int
684
+ spec_map: str
685
+
686
+ def __init__(self,
687
+ id_: ID,
688
+ dlms_ver: int = 6,
689
+ country: Optional[CountrySpecificIdentifiers] = None,
690
+ cntr_ver: Optional[ParameterValue] = None):
691
+ self.__id = id_
692
+ self.__dlms_ver = dlms_ver
693
+ self.__country = country
694
+ self.__country_ver = cntr_ver
695
+ """country version specification"""
696
+ self.spec_map = "DLMS_6"
697
+ self.__objs = {}
698
+ """ all DLMS objects container with obis key """
699
+
700
+ @property
701
+ def id(self) -> ID:
702
+ return self.__id
703
+
704
+ def set_id(self, value: ID) -> None:
705
+ if not self.__id:
706
+ self.__id = value
707
+ else:
708
+ if value != self.__id:
709
+ raise ValueError(F"got id: {value}, expected {self.__id}")
710
+ else:
711
+ """success validation"""
712
+
713
+ def __eq__(self, other: object) -> bool:
714
+ if isinstance(other, Collection):
715
+ return hash(self) == hash(other)
716
+ raise NotImplementedError
717
+
718
+ def __hash__(self) -> int:
719
+ return hash(self.id)
720
+
721
+ def copy_object_values(self, target: ic.COSEMInterfaceClasses, association_id: int = 3) -> None:
722
+ """copy object values according by association. only needed"""
723
+ source = self.__objs.get(target.logical_name.contents, None)
724
+ if source is None:
725
+ raise RuntimeError(f"can't find {target}")
726
+ for i, value in source.get_index_with_attributes():
727
+ el = target.get_attr_element(i)
728
+ if (
729
+ i == 1
730
+ or value is None
731
+ or (
732
+ not isinstance(el.DATA_TYPE, ut.CHOICE)
733
+ and el.classifier == ic.Classifier.DYNAMIC
734
+ )
735
+ ):
736
+ continue
737
+ else:
738
+ if (
739
+ target.get_attr_element(i).classifier == ic.Classifier.STATIC
740
+ and not self.is_writable(
741
+ ln=target.logical_name,
742
+ index=i,
743
+ association_id=association_id)
744
+ ):
745
+ # todo: may be callbacks inits remove?
746
+ if cb_func := target._cbs_attr_before_init.get(i, None):
747
+ cb_func(value) # Todo: 'a' as 'new_value' in set_attr are can use?
748
+ target._cbs_attr_before_init.pop(i)
749
+ target.set_attr(i, value.encoding)
750
+ if cb_func := target._cbs_attr_post_init.get(i, None):
751
+ cb_func()
752
+ target._cbs_attr_post_init.pop(i)
753
+ else:
754
+ if isinstance(arr := target.get_attr(i), cdt.Array):
755
+ arr.set_type(value.TYPE)
756
+ try:
757
+ target.set_attr(
758
+ index=i,
759
+ value=value.encoding,
760
+ data_type=source.get_attr(i).__class__)
761
+ except exc.EmptyObj as e:
762
+ print(F"can't copy {target} attr={i}, skipped. {e}")
763
+
764
+ def copy(self) -> result.Simple["Collection"]:
765
+ """copy collection with value by Association"""
766
+ res = result.Simple(Collection(
767
+ id_=self.id,
768
+ dlms_ver=self.__dlms_ver,
769
+ country=self.__country,
770
+ cntr_ver=self.__country_ver)
771
+ )
772
+ res.value.spec_map = self.spec_map
773
+ max_ass: AssociationLN | None = None
774
+ """more full association""" # todo: move to collection(from_xml)
775
+ for obj in self.__objs.values():
776
+ new_obj: InterfaceClass = obj.__class__(obj.logical_name)
777
+ res.value.__objs[obj.logical_name.contents] = new_obj
778
+ new_obj.collection = res.value # todo: remove in future
779
+ if obj.CLASS_ID == ClassID.ASSOCIATION_LN:
780
+ obj: AssociationLN
781
+ if obj.object_list is not None:
782
+ if (
783
+ max_ass is None
784
+ or len(max_ass.object_list) < len(obj.object_list)
785
+ ):
786
+ max_ass = obj
787
+ if max_ass is None:
788
+ raise exc.NoObject("collection not has the AssociationLN object")
789
+ ln_for_set = max_ass.get_lns()
790
+ ass_id: int = max_ass.logical_name.e
791
+ while len(ln_for_set) != 0:
792
+ ln = ln_for_set.pop(0)
793
+ try:
794
+ new_obj = res.value.get_object(ln.contents)
795
+ self.copy_object_values(
796
+ target=new_obj,
797
+ association_id=ass_id)
798
+ except Exception as e:
799
+ res.append_e(e, "copy object value")
800
+ return res
801
+
802
+ @property
803
+ def dlms_ver(self) -> int:
804
+ return self.__dlms_ver
805
+
806
+ def set_dlms_ver(self, value: int) -> None:
807
+ if not self.__dlms_ver:
808
+ self.__dlms_ver = value
809
+ else:
810
+ if value != self.__dlms_ver:
811
+ raise ValueError(F"got dlms_version: {value}, expected {self.__dlms_ver}")
812
+ else:
813
+ """success validation"""
814
+
815
+ @property
816
+ def country(self) -> Optional[CountrySpecificIdentifiers]:
817
+ return self.__country
818
+
819
+ def set_country(self, value: CountrySpecificIdentifiers):
820
+ if not self.__country:
821
+ self.__country = value
822
+ else:
823
+ if value != self.__country:
824
+ raise ValueError(F"got country: {value}, expected {self.__country}")
825
+ else:
826
+ """success validation"""
827
+
828
+ @property
829
+ def country_ver(self):
830
+ return self.__country_ver
831
+
832
+ def set_country_ver(self, value: ParameterValue):
833
+ """country version specification"""
834
+ if not self.__country_ver:
835
+ self.__country_ver = value
836
+ else:
837
+ if value != self.__country_ver:
838
+ raise ValueError(F"got country version: {value}, expected {self.__country_ver}")
839
+ else:
840
+ """success validation"""
841
+
842
+ def __str__(self) -> str:
843
+ return F"[{len(self.__objs)}] DLMS version: {self.__dlms_ver}, country: {self.__country}, country specific version: {self.__country_ver}, " \
844
+ F"id: {self.id}, uses specification: {self.spec_map}"
845
+
846
+ def __iter__(self) -> Iterator[ic.COSEMInterfaceClasses]:
847
+ return iter(self.__objs.values())
848
+
849
+ def get_spec(self) -> str:
850
+ """return functional map to specification by identification fields"""
851
+ match self.id.man:
852
+ case b"KPZ":
853
+ return "KPZ"
854
+ case b"101" | b"102" | b"103" | b"104":
855
+ return "KPZ1"
856
+ case _:
857
+ if self.country == CountrySpecificIdentifiers.RUSSIA:
858
+ if (
859
+ self.country_ver.par == b'\x00\x00`\x01\x06\xff\x02' and
860
+ SemVer.parse(bytes(cdt.OctetString(self.country_ver.value)), True) == SemVer(3, 0)
861
+ ):
862
+ return "SPODES_3"
863
+ if self.dlms_ver == 6:
864
+ return "DLMS_6"
865
+ else:
866
+ raise exc.DLMSException("unknown specification")
867
+
868
+ def add_if_missing(self, class_id: ut.CosemClassId,
869
+ version: cdt.Unsigned | None,
870
+ logical_name: cst.LogicalName) -> InterfaceClass:
871
+ """ like as add method with check for missing """
872
+ if (res := self.__objs.get(logical_name.contents)) is None:
873
+ return self.add(
874
+ class_id=class_id,
875
+ version=version,
876
+ logical_name=logical_name)
877
+ else:
878
+ return res
879
+
880
+ def __getitem__(self, item: o.OBIS) -> InterfaceClass:
881
+ return self.__objs[item]
882
+
883
+ def get(self, obis: o.OBIS) -> InterfaceClass | None:
884
+ """ get object, return None if it absence """
885
+ return self.__objs.get(obis, None)
886
+
887
+ def par2obj(self, par: Parameter) -> result.SimpleOrError[InterfaceClass]:
888
+ """return: DLMSObject"""
889
+ return self.obis2obj(par.obis)
890
+
891
+ def par2data(self, par: Parameter) -> result.Option[cdt.CommonDataType] | result.Error:
892
+ """:return CDT by Parameter, return None if data wasn't setting"""
893
+ if isinstance((res1 := self.par2obj(par)), result.Error):
894
+ return res1
895
+ res = result.Option(res1.value.get_attr(par.i))
896
+ if res.value is None:
897
+ return res
898
+ for el in par.elements():
899
+ res.value = res.value[el]
900
+ return res
901
+
902
+ def values(self) -> tuple[InterfaceClass]:
903
+ return tuple(self.__objs.values())
904
+
905
+ def __len__(self):
906
+ return len(self.__objs)
907
+
908
+ def add(self, class_id: ut.CosemClassId,
909
+ version: cdt.Unsigned | None,
910
+ logical_name: cst.LogicalName) -> InterfaceClass:
911
+ """ append new DLMS object to collection with return it"""
912
+ try:
913
+ new_object = get_type(
914
+ class_id=class_id,
915
+ version=self.find_version(class_id) if version is None else version,
916
+ ln=logical_name,
917
+ func_map=func_maps[self.spec_map])(logical_name)
918
+ new_object.collection = self
919
+ self.__objs[o.OBIS(logical_name.contents)] = new_object
920
+ print(F'Create {new_object}')
921
+ return new_object
922
+ except ValueError as e:
923
+ raise ValueError(F"error getting DLMS object instance with {class_id=} {version=} {logical_name=}: {e}")
924
+ except StopIteration as e:
925
+ raise ValueError(F"not find class version for {class_id=} {logical_name=}: {e}")
926
+
927
+ def get_class_version(self) -> dict[ut.CosemClassId, cdt.Unsigned]:
928
+ """use for check all class version by unique"""
929
+ ret: dict[ut.CosemClassId, cdt.Unsigned] = dict()
930
+ for obj in self.__objs.values():
931
+ if ver := ret.get(obj.CLASS_ID):
932
+ if obj.VERSION != ver:
933
+ raise ValueError(F"for {obj.CLASS_ID=} exist several versions: {obj.VERSION}, {ver} in one collection")
934
+ else:
935
+ ret[obj.CLASS_ID] = obj.VERSION
936
+ return ret
937
+
938
+ def get_n_phases(self) -> int:
939
+ """search objects with L2 phase"""
940
+ ret: int | None = None
941
+ for obj in filter(lambda obj: obj.logical_name.a == 1, self):
942
+ if 41 <= obj.logical_name.c <= 60:
943
+ return 3
944
+ ret = 1
945
+ if ret is None:
946
+ raise exc.NoObject("no one electricity object was find")
947
+ else:
948
+ return ret
949
+
950
+ def has_sap(self, value: enums.ClientSAP) -> bool:
951
+ try:
952
+ self.sap2association(value)
953
+ return True
954
+ except exc.NoObject:
955
+ return False
956
+
957
+ @lru_cache(maxsize=100) # amount of all ClassID
958
+ def find_version(self, class_id: ut.CosemClassId) -> cdt.Unsigned:
959
+ """use for add new object from profile_generic if absence in object list"""
960
+ return next(filter(lambda obj: obj.CLASS_ID == class_id, self.__objs.values())).VERSION
961
+
962
+ def is_in_collection(self, value: LNContaining) -> bool:
963
+ obis = lnContents2obis(value)
964
+ return False if self.__objs.get(obis) is None else True
965
+
966
+ def get_object(self, value: LNContaining) -> InterfaceClass:
967
+ """ return object from obis<string> or raise exception if it absence """
968
+ return self.obis2obj(lnContents2obis(value)).unwrap()
969
+
970
+ @deprecated("use <par2rep>")
971
+ def get_report(self,
972
+ obj: ic.COSEMInterfaceClasses,
973
+ par: bytes,
974
+ a_val: cdt.CommonDataType | None
975
+ ) -> cdt.Report:
976
+ """par: attribute_index, par1, par2, ..."""
977
+ rep = cdt.Report(str(a_val))
978
+ try:
979
+ if a_val is None:
980
+ rep.msg = settings.report.empty
981
+ rep.log = cdt.EMPTY_VAL
982
+ elif isinstance(a_val, cdt.ReportMixin):
983
+ rep = a_val.get_report()
984
+ elif isinstance(a_val, DayProfileAction):
985
+ rep.msg = F"{get_message("$rate$")}-{a_val.script_selector}: {a_val.start_time}"
986
+ script_obj = self.get_object(a_val.script_logical_name)
987
+ for script in script_obj.scripts:
988
+ if script.script_identifier == a_val.script_selector:
989
+ break
990
+ else:
991
+ rep.log = cdt.Log(logging.ERROR, F"absent script with ID: {a_val.script_selector}")
992
+ else:
993
+ if unit := get_unit(obj.CLASS_ID, par):
994
+ rep.unit = cdt.Unit(unit).get_name()
995
+ else:
996
+ if s_u := self.get_scaler_unit(obj, par):
997
+ rep.msg = (settings.report.scaler_format).format(int(a_val) * 10 ** int(s_u.scaler))
998
+ rep.unit = s_u.unit.get_name()
999
+ else:
1000
+ match obj.CLASS_ID, *par:
1001
+ case (ClassID.PROFILE_GENERIC, 3, _) | (ClassID.PROFILE_GENERIC, 6):
1002
+ a_val: structs.CaptureObjectDefinition
1003
+ obj = self.get_object(a_val.logical_name)
1004
+ rep.msg = F"{get_name(a_val.logical_name, self.spec_map)}.{obj.get_attr_element(int(a_val.attribute_index))}"
1005
+ case _:
1006
+ pass
1007
+ rep.log = cdt.Log(logging.INFO)
1008
+ except Exception as e:
1009
+ rep.log = cdt.Log(logging.ERROR, e)
1010
+ finally:
1011
+ return rep
1012
+
1013
+ def par2rep(self, par: Parameter, data: Optional[cdt.CommonDataType]) -> cdt.Report:
1014
+ rep = cdt.Report(str(data))
1015
+ try:
1016
+ if data is None:
1017
+ rep.msg = settings.report.empty
1018
+ rep.log = cdt.EMPTY_VAL
1019
+ elif isinstance(data, cdt.ReportMixin):
1020
+ rep = data.get_report()
1021
+ elif isinstance(data, DayProfileAction):
1022
+ rep.msg = F"{get_message("$rate$")}-{data.script_selector}: {data.start_time}"
1023
+ script_obj = self.get_object(data.script_logical_name)
1024
+ for script in script_obj.scripts:
1025
+ if script.script_identifier == data.script_selector:
1026
+ break
1027
+ else:
1028
+ rep.log = cdt.Log(logging.ERROR, F"absent script with ID: {data.script_selector}")
1029
+ else:
1030
+ obj = self.par2obj(par).unwrap()
1031
+ elements = tuple(par.elements())
1032
+ if unit := get_unit(obj.CLASS_ID, elements):
1033
+ rep.unit = cdt.Unit(unit).get_name()
1034
+ else:
1035
+ if s_u := self.par2su(par):
1036
+ rep.msg = (settings.report.scaler_format).format(int(data) * 10 ** int(s_u.scaler))
1037
+ rep.unit = s_u.unit.get_name()
1038
+ else:
1039
+ match obj.CLASS_ID, *elements:
1040
+ case (ClassID.PROFILE_GENERIC, 3, _) | (ClassID.PROFILE_GENERIC, 6):
1041
+ data: structs.CaptureObjectDefinition
1042
+ obj = self.get_object(data.logical_name)
1043
+ rep.msg = F"{get_name(data.logical_name, self.spec_map)}.{obj.get_attr_element(int(data.attribute_index))}"
1044
+ case _:
1045
+ pass
1046
+ rep.log = cdt.Log(logging.INFO)
1047
+ except Exception as e:
1048
+ rep.log = cdt.Log(logging.ERROR, e)
1049
+ finally:
1050
+ return rep
1051
+
1052
+ @deprecated("use par2su")
1053
+ def get_scaler_unit(self,
1054
+ obj: ic.COSEMInterfaceClasses,
1055
+ par: bytes
1056
+ ) -> cdt.ScalUnitType | None:
1057
+ match obj.CLASS_ID, *par:
1058
+ case (ClassID.REGISTER | ClassID.EXT_REGISTER, 2) | (ClassID.DEMAND_REGISTER, 2 | 3):
1059
+ obj: Register | DemandRegister
1060
+ if (s_u := obj.scaler_unit) is None:
1061
+ raise ic.EmptyAttribute(obj.logical_name, 3)
1062
+ else:
1063
+ if (s := cdt.get_unit_scaler(s_u.unit.contents)) != 0:
1064
+ s_u = s_u.copy()
1065
+ s_u.scaler.set(int(s_u.scaler)-s)
1066
+ return s_u
1067
+ case ClassID.LIMITER, 3 | 4 | 5:
1068
+ obj: Limiter
1069
+ if m_v := obj.monitored_value:
1070
+ return self.get_scaler_unit( # recursion 1 level
1071
+ obj=self.get_object(m_v.logical_name),
1072
+ par=m_v.attribute_index.contents)
1073
+ else:
1074
+ raise ic.EmptyAttribute(obj.logical_name, 2)
1075
+ case ClassID.REGISTER_MONITOR, 2, _:
1076
+ obj: RegisterMonitor
1077
+ if (m_v := obj.monitored_value) is None:
1078
+ raise ic.EmptyAttribute(obj.logical_name, 3)
1079
+ else:
1080
+ return self.get_scaler_unit( # recursion 1 level
1081
+ obj=self.get_object(m_v.logical_name),
1082
+ par=m_v.attribute_index.contents
1083
+ )
1084
+ case _:
1085
+ return None
1086
+
1087
+ @lru_cache(20000)
1088
+ def par2su(self, par: Parameter) -> Optional[cdt.ScalUnitType]:
1089
+ """convert Parameter -> Optional[ScalerUnit],
1090
+ raise: NoObject, EmptyAttribute"""
1091
+ match (obj := self.par2obj(par).unwrap()).CLASS_ID, par.i:
1092
+ case (exc.NoObject, _):
1093
+ raise obj
1094
+ case (ClassID.REGISTER | ClassID.EXT_REGISTER, 2) | (ClassID.DEMAND_REGISTER, 2 | 3):
1095
+ obj: Register | DemandRegister
1096
+ if (s_u := obj.scaler_unit) is None:
1097
+ raise ic.EmptyAttribute(obj.logical_name, 3)
1098
+ else:
1099
+ if (s := cdt.get_unit_scaler(s_u.unit.contents)) != 0:
1100
+ s_u = s_u.copy()
1101
+ s_u.scaler.set(int(s_u.scaler)-s)
1102
+ return s_u
1103
+ case ClassID.LIMITER, 3 | 4 | 5:
1104
+ obj: Limiter
1105
+ if m_v := obj.monitored_value:
1106
+ return self.par2su(Parameter(m_v.logical_name.contents).set_i(int(m_v.attribute_index))) # recursion 1 level
1107
+ else:
1108
+ raise ic.EmptyAttribute(obj.logical_name, 2)
1109
+ case ClassID.REGISTER_MONITOR, 2, _:
1110
+ obj: RegisterMonitor
1111
+ if (m_v := obj.monitored_value) is None:
1112
+ raise ic.EmptyAttribute(obj.logical_name, 3)
1113
+ else:
1114
+ return self.par2su(Parameter(m_v.logical_name.contents).set_i(int(m_v.attribute_index))) # recursion 1 level
1115
+ case _:
1116
+ return None
1117
+
1118
+ def par2float(self, par: Parameter) -> float:
1119
+ """try convert CDT value according with Parameter to build-in float"""
1120
+ data = self.par2data(par).unwrap()
1121
+ if hasattr(data, "__int__"):
1122
+ value = float(int(data))
1123
+ elif hasattr(data, "__float__"):
1124
+ value = float(data)
1125
+ else:
1126
+ raise TypeError("can't convert Parameter data to int or float")
1127
+ if (su := self.par2su(par)):
1128
+ value *= 10 ** int(su.scaler)
1129
+ return value
1130
+
1131
+
1132
+ def filter_by_ass(self, ass_id: int) -> list[InterfaceClass]:
1133
+ """return only association objects"""
1134
+ ret = list()
1135
+ for olt in self.getASSOCIATION(ass_id).object_list:
1136
+ ret.append(self.par2obj(Parameter(olt.logical_name.contents)).unwrap())
1137
+ return ret
1138
+
1139
+ def sap2objects(self, value: enums.ClientSAP) -> result.List[ic.COSEMInterfaceClasses]:
1140
+ res = result.List()
1141
+ for par in self.sap2association(value).iter_pars():
1142
+ if isinstance(res1 := self.par2obj(par), result.Error):
1143
+ res1.append_err(res.err)
1144
+ else:
1145
+ res.append(res1)
1146
+ return res
1147
+
1148
+ def iter_classID_objects(self,
1149
+ class_id: ut.CosemClassId) -> Iterator[InterfaceClass]:
1150
+ return (obj for obj in self.__objs.values() if obj.CLASS_ID == class_id)
1151
+
1152
+ def LNPattern2objects(self,
1153
+ pat: LNPattern) -> list[InterfaceClass]:
1154
+ ret = []
1155
+ for obj in self.__objs.values():
1156
+ if obj.logical_name in pat:
1157
+ ret.append(obj)
1158
+ return ret
1159
+
1160
+ def get_first(self, values: list[str | bytes | cst.LogicalName]) -> InterfaceClass:
1161
+ """ return first object from it exist in collection from value"""
1162
+ for val in values:
1163
+ if self.is_in_collection(val):
1164
+ return self.get_object(val)
1165
+ else:
1166
+ """search next"""
1167
+ else:
1168
+ raise exc.NoObject(F"not found at least one DLMS Objects from collection with {values=}")
1169
+
1170
+ @deprecated("use <iter_classID_objects>")
1171
+ def get_objects_by_class_id(self, value: ut.CosemClassId) -> list[InterfaceClass]:
1172
+ return list(filter(lambda obj: obj.CLASS_ID == value, self.__objs.values()))
1173
+
1174
+ def get_writable_attr(self) -> UsedAttributes:
1175
+ """return all writable {obj.ln: {attribute_index}}"""
1176
+ ret: UsedAttributes = dict()
1177
+ for ass in self.iter_classID_objects(ClassID.ASSOCIATION_LN):
1178
+ if (
1179
+ ass.logical_name.e == 0
1180
+ or ass.object_list is None
1181
+ ):
1182
+ continue
1183
+ else:
1184
+ for list_type in ass.object_list:
1185
+ for attr_access in list_type.access_rights.attribute_access:
1186
+ if attr_access.access_mode.is_writable():
1187
+ if ret.get(list_type.logical_name, None) is None:
1188
+ ret[list_type.logical_name] = set()
1189
+ ret[list_type.logical_name].add(int(attr_access.attribute_id))
1190
+ return ret
1191
+
1192
+ def get_profile_s_u(self,
1193
+ obj: ProfileGeneric,
1194
+ mask: set[int] = None
1195
+ ) -> list[cdt.ScalUnitType | None]:
1196
+ """return container of scaler_units if possible, mask: position number in capture_objects"""
1197
+ res: list[cdt.ScalUnitType | None] = list()
1198
+ for i, obj_def in enumerate(obj.capture_objects):
1199
+ obj_def: structs.CaptureObjectDefinition
1200
+ if mask and i not in mask:
1201
+ continue
1202
+ s_u = None
1203
+ try:
1204
+ s_u = self.get_scaler_unit(
1205
+ obj=self.get_object(obj_def.logical_name),
1206
+ par=bytes([int(obj_def.attribute_index)]))
1207
+ except ic.EmptyAttribute as e:
1208
+ print(F"Can't fill Scaler and Unit for {get_name(obj_def.logical_name)}: {e}")
1209
+ finally:
1210
+ res.append(s_u)
1211
+ return res
1212
+
1213
+ def copy_obj_attr_values_from(self, other: InterfaceClass) -> bool:
1214
+ """ copy all attributes value from other and return bool result """
1215
+ try:
1216
+ obj: InterfaceClass = self.par2obj(Parameter(other.logical_name.contents)).unwrap()
1217
+ for i, attr in other.get_index_with_attributes(in_init_order=True):
1218
+ if i == 1:
1219
+ continue
1220
+ else:
1221
+ if attr is not None:
1222
+ obj.set_attr(i, attr.encoding)
1223
+ return True
1224
+ except exc.NoObject as e:
1225
+ return False
1226
+
1227
+ def copy_objects_attr_values_from(self, other: Self) -> bool:
1228
+ """ Copy collections values and return True if all was writen """
1229
+ if len(other) != 0:
1230
+ return bool(reduce(lambda a, b: a or b, map(self.copy_obj_attr_values_from, other.values())))
1231
+ else:
1232
+ return False
1233
+
1234
+ def obis2obj(self, obis: o.OBIS) -> result.SimpleOrError[InterfaceClass]:
1235
+ if obj := self.__objs.get(obis):
1236
+ return result.Simple(obj)
1237
+ return result.Error.from_e(ValueError(str(obis)), "no object")
1238
+
1239
+ def logicalName2obj(self, ln: cst.LogicalName) -> result.SimpleOrError[InterfaceClass]:
1240
+ return self.obis2obj(o.OBIS(ln.contents))
1241
+
1242
+ @cached_property
1243
+ def LDN(self) -> impl.data.LDN:
1244
+ return self.obis2obj(o.LDN).unwrap()
1245
+
1246
+ @cached_property
1247
+ def current_association(self) -> AssociationLN:
1248
+ return self.obis2obj(o.CURRENT_ASSOCIATION).unwrap()
1249
+
1250
+ def getASSOCIATION(self, instance: int) -> AssociationLN:
1251
+ return self.obis2obj(o.AssociationLN(instance)).unwrap()
1252
+
1253
+ @cached_property
1254
+ def PUBLIC_ASSOCIATION(self) -> AssociationLN:
1255
+ return self.obis2obj(o.PUBLIC_ASSOCIATION).unwrap()
1256
+
1257
+ @property
1258
+ def COMMUNICATION_PORT_PARAMETER(self) -> impl.data.CommunicationPortParameter:
1259
+ return self.obis2obj(bytes((0, 0, 96, 12, 4, 255))).unwrap()
1260
+
1261
+ @property
1262
+ def clock(self) -> Clock:
1263
+ return self.obis2obj(bytes((0, 0, 1, 0, 0, 255))).unwrap()
1264
+
1265
+ @property
1266
+ def boot_image_transfer(self) -> ImageTransfer:
1267
+ return self.obis2obj(bytes((0, 0, 44, 0, 128, 255))).unwrap()
1268
+
1269
+ @property
1270
+ def firmware_image_transfer(self) -> ImageTransfer:
1271
+ return self.obis2obj(bytes((0, 0, 44, 0, 0, 255))).unwrap()
1272
+
1273
+ @property
1274
+ def firmwares_description(self) -> Data:
1275
+ """ Consist from boot_version, descriptor, ex.: 0005PWRM_M2M_3_F1_5ppm_Spvq. 0.0.128.100.0.255 """
1276
+ return self.obis2obj(bytes((0, 0, 128, 100, 0, 255))).unwrap()
1277
+
1278
+ @property
1279
+ def RU_CLOSE_ELECTRIC_SEAL(self) -> Data:
1280
+ """ Russian. СПОДЕС Г.2 """
1281
+ return self.obis2obj(bytes((0, 0, 96, 51, 6, 255))).unwrap()
1282
+
1283
+ @property
1284
+ def RU_ERASE_MAGNETIC_EVENTS(self) -> Data:
1285
+ """ Russian. СПОДЕС Г.2 """
1286
+ return self.obis2obj(bytes((0, 0, 96, 51, 7, 255))).unwrap()
1287
+
1288
+ @property
1289
+ def RU_FILTER_ALARM_2(self) -> Data:
1290
+ """ Russian. Filter of Alarm register relay"""
1291
+ return self.obis2obj(bytes((0, 0, 97, 98, 11, 255))).unwrap()
1292
+
1293
+ @property
1294
+ def RU_DAILY_PROFILE(self) -> ProfileGeneric:
1295
+ """ Russian. Profile of daily values """
1296
+ return self.obis2obj(bytes((1, 0, 98, 2, 0, 255))).unwrap()
1297
+
1298
+ @property
1299
+ def RU_MAXIMUM_CURRENT_EXCESS_LIMIT(self) -> Register:
1300
+ """ RU. СТО 34.01-5.1-006-2021 ver3, 11.1. Maximum current excess limit before the subscriber is disconnected, % of IMAX """
1301
+ return self.obis2obj(bytes((1, 0, 11, 134, 0, 255))).unwrap()
1302
+
1303
+ @property
1304
+ def RU_MAXIMUM_VOLTAGE_EXCESS_LIMIT(self) -> Register:
1305
+ """ RU. СТО 34.01-5.1-006-2021 ver3, 11.1. Maximum voltage excess limit before the subscriber is disconnected, % of Unominal """
1306
+ return self.obis2obj(bytes((1, 0, 12, 134, 0, 255))).unwrap()
1307
+
1308
+ def getDISCONNECT_CONTROL(self, ch: int = 0) -> DisconnectControl:
1309
+ """DLMS UA 1000-1 Ed 14 6.2.46 Disconnect control objects by channel"""
1310
+ return self.obis2obj(bytes((0, ch, 96, 3, 10, 255))).unwrap()
1311
+
1312
+ def getARBITRATOR(self, ch: int = 0) -> Arbitrator:
1313
+ """DLMS UA 1000-1 Ed 14 6.2.47 Arbitrator objects objects by channel"""
1314
+ return self.obis2obj(bytes((0, ch, 96, 3, 20, 255))).unwrap()
1315
+
1316
+ @property
1317
+ def boot_version(self) -> str:
1318
+ try:
1319
+ return self.firmwares_description.value.to_str()[:4]
1320
+ except Exception as e:
1321
+ print(e)
1322
+ return 'unknown'
1323
+
1324
+ def get_script_names(self, ln: cst.LogicalName, selector: cdt.LongUnsigned) -> str:
1325
+ """return name from script by selector"""
1326
+ obj = self.par2obj(Parameter(ln.contents)).unwrap()
1327
+ if isinstance(obj, ScriptTable):
1328
+ for script in obj.scripts:
1329
+ script: ScriptTable.scripts
1330
+ if script.script_identifier == selector:
1331
+ names: list[str] = []
1332
+ for action in script.actions:
1333
+ action_obj = self.par2obj(Parameter(action.logical_name.contents)).unwrap()
1334
+ if int(action_obj.CLASS_ID) != int(action.class_id):
1335
+ raise ValueError(F"got {action_obj.CLASS_ID}, expected {action.class_id}")
1336
+ match int(action.service_id):
1337
+ case 1: # for write
1338
+ if isinstance(action.parameter, cdt.NullData):
1339
+ names.append(str(action_obj.get_attr_element(int(action.index))))
1340
+ else:
1341
+ raise TypeError(F"not support by framework") # TODO: make it
1342
+ case 2: # for execute
1343
+ if isinstance(action.parameter, cdt.NullData):
1344
+ names.append(str(action_obj.get_meth_element(int(action.index))))
1345
+ else:
1346
+ raise TypeError(F"not support by framework") # TODO: make it
1347
+ return ", ".join(names)
1348
+ else:
1349
+ raise ValueError(F"not find {selector} in {obj}")
1350
+ else:
1351
+ raise ValueError(F"object with {ln} is not {ScriptTable.CLASS_ID}")
1352
+
1353
+ @lru_cache(4)
1354
+ def get_association_id(self, client_sap: enums.ClientSAP) -> int:
1355
+ """return id(association instance) from it client address without current"""
1356
+ for ass in get_filtered(iter(self), (ln_pattern.NON_CURRENT_ASSOCIATION,)):
1357
+ if ass.associated_partners_id.client_SAP == client_sap:
1358
+ return ass.logical_name.e
1359
+ else:
1360
+ continue
1361
+ else:
1362
+ raise ValueError(F"absent association with {client_sap}")
1363
+
1364
+ def sap2association(self, sap: enums.ClientSAP) -> AssociationLN:
1365
+ for ass in self.iter_classID_objects(ClassID.ASSOCIATION_LN):
1366
+ if (
1367
+ ass.associated_partners_id is not None
1368
+ and ass.associated_partners_id.client_SAP == sap
1369
+ ):
1370
+ return ass
1371
+ else:
1372
+ continue
1373
+ else:
1374
+ raise exc.NoObject(F"hasn't association with {sap}")
1375
+
1376
+ @lru_cache(maxsize=1000)
1377
+ def is_readable(self, ln: cst.LogicalName,
1378
+ index: int,
1379
+ association_id: int,
1380
+ security_policy: pdu.SecurityPolicy = pdu.SecurityPolicyVer0.NOTHING
1381
+ ) -> bool:
1382
+ return self.getASSOCIATION(association_id).is_readable(
1383
+ ln=ln,
1384
+ index=index,
1385
+ security_policy=security_policy
1386
+ )
1387
+
1388
+ @lru_cache(maxsize=1000)
1389
+ def is_writable(self, ln: cst.LogicalName,
1390
+ index: int,
1391
+ association_id: int,
1392
+ security_policy: pdu.SecurityPolicy = pdu.SecurityPolicyVer0.NOTHING
1393
+ ) -> bool:
1394
+ return self.getASSOCIATION(association_id).is_writable(
1395
+ ln=ln,
1396
+ index=index,
1397
+ security_policy=security_policy
1398
+ )
1399
+
1400
+ @lru_cache(maxsize=1000)
1401
+ def isnt_mutable(self,
1402
+ par: Parameter,
1403
+ association_id: int,
1404
+ security_policy: pdu.SecurityPolicy = pdu.SecurityPolicyVer0.NOTHING
1405
+ ) -> bool:
1406
+ """is not writable and STATIC data"""
1407
+ if (
1408
+ not self.is_writable(par.ln, par.i, association_id, security_policy, security_policy)
1409
+ and self.par2obj(par
1410
+ ).unwrap().getAElement(par.i
1411
+ ).unwrap().classifier == ic.Classifier.STATIC
1412
+ ):
1413
+ return True
1414
+ return False
1415
+
1416
+ @lru_cache(maxsize=1000)
1417
+ def is_accessible(self, ln: cst.LogicalName,
1418
+ index: int,
1419
+ association_id: int,
1420
+ m_id: mechanism_id.MechanismIdElement = None
1421
+ ) -> bool:
1422
+ """for ver 0 and 1 only"""
1423
+
1424
+ return self.getASSOCIATION(association_id).is_accessible(
1425
+ ln=ln,
1426
+ index=index,
1427
+ m_id=m_id
1428
+ )
1429
+
1430
+ @lru_cache(maxsize=100)
1431
+ def get_name_and_type(self, value: structs.CaptureObjectDefinition) -> tuple[list[str], Type[cdt.CommonDataType]]:
1432
+ """ return names and type of element from collection"""
1433
+ names: list[str] = list()
1434
+ obj = self.par2obj(Parameter(value.logical_name.contents)).unwrap()
1435
+ names.append(get_name(obj.logical_name, self.spec_map))
1436
+ attr_index = int(value.attribute_index)
1437
+ data_index = int(value.data_index)
1438
+ data_type: Type[cdt.CommonDataType] = obj.get_attr_data_type(attr_index)
1439
+ names.append(str(obj.get_attr_element(attr_index)))
1440
+ if data_index == 0:
1441
+ pass
1442
+ elif issubclass(data_type, cdt.Structure):
1443
+ if len(data_type.ELEMENTS) < data_index:
1444
+ raise ValueError(F"can't create buffer_struct_type for {self}, got {data_index=} in struct {data_type.__name__}, expected 1..{len(data_type.ELEMENTS)}")
1445
+ else:
1446
+ el: cdt.StructElement = data_type.ELEMENTS[data_index - 1]
1447
+ names.append(el.NAME)
1448
+ data_type = el.TYPE
1449
+ elif isinstance(obj, ProfileGeneric) and attr_index == 2:
1450
+ """according to DLMS UA 1000-1 Ed 14. ProfileGeneric.capture_object.data_index annex"""
1451
+ return self.get_name_and_type(obj.capture_objects[data_index - 1]) # todo: is recurse need rewrite here
1452
+ else:
1453
+ pass
1454
+ return names, data_type
1455
+
1456
+ def get_attr_tree(self,
1457
+ ass_id: int,
1458
+ obj_mode: ObjectTreeMode = "c",
1459
+ obj_filter: ObjFilteredKey = None,
1460
+ sort_mode: SortMode = "",
1461
+ af_mode: Literal["l", "r", "w", "lr", "lw", "wr", "lrw", "m", "mlrw", "mlr"] = "l",
1462
+ oi_filter: tuple[tuple[ClassID, tuple[int, ...]], ...] = None # todo: maybe ai_filter with LNPattern, indexes need?
1463
+ ) -> dict[ClassID | media_id.MediaId, dict[ic.COSEMInterfaceClasses, list[int]]] | dict[ic.COSEMInterfaceClasses, list[int]]: # todo: not all ret annotation
1464
+ """af_mode(attribute filter mode): l-reduce logical_name, r-show only readable, w-show only writeable,
1465
+ oi_filter(object attribute index filter), example: ((ClassID.REGISTER, (2,))) - is restricted for Register only Value attribute without logical_name and scaler_unit
1466
+ """
1467
+ objects: dict[ic.COSEMInterfaceClasses, list[int]]
1468
+ without_ln = True if "l" in af_mode else False
1469
+ only_read = True if "r" in af_mode else False
1470
+ only_write = True if "w" in af_mode else False
1471
+ with_methods = True if "m" in af_mode else False
1472
+ oi_f = dict(oi_filter) if oi_filter else dict()
1473
+ """objects indexes filter"""
1474
+ filtered = self.filter_by_ass(ass_id)
1475
+ if obj_filter:
1476
+ filtered = get_filtered(filtered, obj_filter)
1477
+ ret = get_object_tree(
1478
+ objects=get_sorted(
1479
+ objects=filtered,
1480
+ mode=sort_mode),
1481
+ mode=obj_mode)
1482
+ stack = [(None, None, ret)]
1483
+ while len(stack) != 0:
1484
+ d, k, v = stack.pop()
1485
+ if isinstance(d, dict):
1486
+ for k_, v_ in tuple(d.items()):
1487
+ if len(v_) == 0:
1488
+ d.pop(k_)
1489
+ if isinstance(v, list):
1490
+ objects = dict()
1491
+ for obj in v:
1492
+ obj: ic.COSEMInterfaceClasses
1493
+ f_i = oi_f.get(obj.CLASS_ID)
1494
+ """filter indexes"""
1495
+ indexes = list()
1496
+ for i, attr in obj.get_index_with_attributes():
1497
+ if without_ln and i == 1:
1498
+ continue
1499
+ elif only_read and not self.is_readable(obj.logical_name, i, ass_id):
1500
+ continue
1501
+ elif only_write and not self.is_writable(obj.logical_name, i, ass_id):
1502
+ continue
1503
+ elif f_i and i not in f_i:
1504
+ continue
1505
+ else:
1506
+ indexes.append(i)
1507
+ if with_methods:
1508
+ i_meth = count(1)
1509
+ for i, m_el in zip(i_meth, obj.M_ELEMENTS):
1510
+ try:
1511
+ if not self.is_accessible(obj.logical_name, i, ass_id, mechanism_id.LOW):
1512
+ continue
1513
+ elif f_i and -i not in f_i:
1514
+ continue
1515
+ indexes.append(-i)
1516
+ except exc.ITEApplication as e:
1517
+ print(F"skip {i}... methods for {obj}: {e}")
1518
+ break
1519
+ if len(indexes) != 0:
1520
+ objects[obj] = indexes
1521
+ if d is None:
1522
+ return objects
1523
+ if len(objects) != 0:
1524
+ d[k] = objects
1525
+ else:
1526
+ d.pop(k)
1527
+ elif isinstance(v, dict):
1528
+ for k_, v_ in v.items():
1529
+ stack.append((v, k_, v_))
1530
+ else:
1531
+ raise ValueError('not support')
1532
+ return ret
1533
+
1534
+
1535
+ if config is not None:
1536
+ try:
1537
+ __collection_path = settings.collection.path
1538
+ except KeyError as e:
1539
+ raise exc.TomlKeyError(F"not find {e} in [DLMS.collection]<path>")
1540
+
1541
+
1542
+ @deprecated("use lnContents2obis")
1543
+ def get_ln_contents(value: LNContaining) -> bytes:
1544
+ """return LN as bytes[6] for use in any searching"""
1545
+ match value:
1546
+ case bytes(): return value
1547
+ case cst.LogicalName() | ut.CosemObjectInstanceId(): return value.contents
1548
+ case ut.CosemAttributeDescriptor() | ut.CosemMethodDescriptor(): return value.instance_id.contents
1549
+ case ut.CosemAttributeDescriptorWithSelection(): return value.cosem_attribute_descriptor.instance_id.contents
1550
+ case cdt.Structure(logical_name=value.logical_name): return value.logical_name.contents
1551
+ case cdt.Structure() as s:
1552
+ s: cdt.Structure
1553
+ for it in s:
1554
+ if isinstance(it, cst.LogicalName):
1555
+ return it.contents
1556
+ raise ValueError(F"can't convert {value=} to Logical Name contents. Struct {s} not content the Logical Name")
1557
+ case str() if value.find('.') != -1:
1558
+ return cst.LogicalName.from_obis(value).contents
1559
+ case str(): return cst.LogicalName(value).contents
1560
+ case _: raise ValueError(F"can't convert {value=} to Logical Name contents")
1561
+
1562
+
1563
+ def lnContents2obis(value: LNContaining) -> o.OBIS:
1564
+ """return LN as OBIS for use in any searching"""
1565
+ match value:
1566
+ case bytes(): return o.OBIS(value)
1567
+ case cst.LogicalName() | ut.CosemObjectInstanceId(): return o.OBIS(value.contents)
1568
+ case ut.CosemAttributeDescriptor() | ut.CosemMethodDescriptor(): return o.OBIS(value.instance_id.contents)
1569
+ case ut.CosemAttributeDescriptorWithSelection(): return o.OBIS(value.cosem_attribute_descriptor.instance_id.contents)
1570
+ case cdt.Structure(logical_name=value.logical_name): return o.OBIS(value.logical_name.contents)
1571
+ case cdt.Structure() as s:
1572
+ s: cdt.Structure
1573
+ for it in s:
1574
+ if isinstance(it, cst.LogicalName):
1575
+ return o.OBIS(it.contents)
1576
+ raise ValueError(F"can't convert {value=} to Logical Name contents. Struct {s} not content the Logical Name")
1577
+ case str() if value.find('.') != -1:
1578
+ return o.OBIS(cst.LogicalName.from_obis(value).contents)
1579
+ case str(): return o.OBIS(cst.LogicalName(value).contents)
1580
+ case _: raise ValueError(F"can't convert {value=} to Logical Name contents")
1581
+
1582
+
1583
+ class AttrDesc:
1584
+ """keep constant descriptors # todo: make better"""
1585
+ OBJECT_LIST = ut.CosemAttributeDescriptor((ClassID.ASSOCIATION_LN, ut.CosemObjectInstanceId("0.0.40.0.0.255"), ut.CosemObjectAttributeId(2)))
1586
+ LDN_VALUE = ut.CosemAttributeDescriptor((ClassID.DATA, ut.CosemObjectInstanceId("0.0.42.0.0.255"), ut.CosemObjectAttributeId(2)))
1587
+ SPODES_VERSION = ut.CosemAttributeDescriptor((ClassID.DATA, ut.CosemObjectInstanceId("0.0.96.1.6.255"), ut.CosemObjectAttributeId(2)))
1588
+
1589
+
1590
+ __range10_and_255: tuple = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255
1591
+ __range63: tuple = tuple(range(0, 64))
1592
+ __range120_and_124_127: tuple = tuple(chain(range(0, 121), range(124, 128)))
1593
+ __range100_and_255: tuple = tuple(chain(range(0, 100), (255,)))
1594
+ __range100_and_101_125_and_255: tuple = tuple(chain(__range100_and_255, range(101, 126)))
1595
+ __c1: tuple = tuple(chain(range(1, 10), (13, 14, 33, 34, 53, 54, 73, 74, 82), range(16, 31), range(36, 51), range(56, 70), range(76, 81), range(84, 90)))
1596
+ """DLMS UA 1000-1 Ed 14. Table 45 c1"""
1597
+ __table44: tuple = (11, 12, 15, 31, 32, 35, 51, 52, 55, 71, 72, 75, 90, 91, 92)
1598
+ """DLMS UA 1000-1 Ed 14. Table 44 for active power"""
1599
+ __c2: tuple = tuple(chain(__table44, range(100, 108)))
1600
+ """DLMS UA 1000-1 Ed 14. Table 45 c2"""
1601
+
1602
+
1603
+ def get_media_id(ln: cst.LogicalName) -> media_id.MediaId:
1604
+ return media_id.MediaId.from_int(ln.a)
1605
+
1606
+
1607
+ def get_class_id(obj: InterfaceClass) -> ClassID:
1608
+ return obj.CLASS_ID
1609
+
1610
+
1611
+ def get_map_by_obj(objects: list[InterfaceClass] | tuple[InterfaceClass], key: Callable[[InterfaceClass], ...]) -> dict[media_id.MediaId, list[InterfaceClass]]:
1612
+ ret = dict()
1613
+ for obj in objects:
1614
+ if ret.get(new_key := key(obj)):
1615
+ ret[new_key].append(obj)
1616
+ else:
1617
+ ret[new_key] = [obj]
1618
+ return ret
1619
+
1620
+
1621
+ def get_object_tree(objects: list[InterfaceClass] | tuple[InterfaceClass],
1622
+ mode: ObjectTreeMode) -> dict[media_id.MediaId | ClassID, list[InterfaceClass]]:
1623
+ """mode: m-media_id, g-relation_group, c-class_id"""
1624
+ mode = list(mode)
1625
+ ret = objects
1626
+ while mode:
1627
+ match mode.pop():
1628
+ case "m":
1629
+ key = lambda obj: get_media_id(obj.logical_name)
1630
+ case "g":
1631
+ key = lambda obj: get_relation_group(obj.logical_name)
1632
+ case "c":
1633
+ key = get_class_id
1634
+ case _:
1635
+ raise KeyError(F"got unknown {mode=} for get_map")
1636
+ stack = [(None, None, ret)]
1637
+ while len(stack) != 0:
1638
+ d, k, v = stack.pop()
1639
+ if isinstance(v, list):
1640
+ res = get_map_by_obj(v, key)
1641
+ if d is None:
1642
+ ret = res
1643
+ else:
1644
+ d[k] = res
1645
+ elif isinstance(v, dict):
1646
+ stack.extend(((v, k_, v_) for k_, v_ in v.items()))
1647
+ else:
1648
+ raise ValueError('not support')
1649
+ return ret
1650
+
1651
+
1652
+ def get_sorted(objects: list[InterfaceClass],
1653
+ mode: SortMode) -> list[InterfaceClass]:
1654
+ """mode: l-logical_name, n-name, c-class_id
1655
+ """
1656
+ mode: list[str] = list(mode)
1657
+ while mode:
1658
+ match mode.pop():
1659
+ case "l":
1660
+ key = None
1661
+ case "n":
1662
+ key = lambda obj: get_name(obj.logical_name)
1663
+ case "c":
1664
+ key = lambda obj: obj.CLASS_ID
1665
+ case _:
1666
+ raise KeyError(F"got unknown {mode=} for get_map")
1667
+ objects = sorted(objects, key=key)
1668
+ return objects
1669
+
1670
+ @dataclass
1671
+ class Channel:
1672
+ """for object filter approve"""
1673
+ n: int
1674
+
1675
+ def __post_init__(self) -> None:
1676
+ if not self.is_channel(self.n):
1677
+ raise ValueError(F"got value={self.n}, expected (0..64)")
1678
+
1679
+ @staticmethod
1680
+ def is_channel(b: int) -> bool:
1681
+ return True if 0 <= b <= 64 else False
1682
+
1683
+ def is_approve(self, b: int) -> bool:
1684
+ if self.is_channel(b):
1685
+ return True if b == self.n else False
1686
+ else:
1687
+ return True
1688
+
1689
+
1690
+ RelationGroup: TypeAlias = media_id.Abstract | media_id.Electricity | media_id.Hca | media_id.Gas | media_id.Thermal | media_id.Water | media_id.Other
1691
+ RelationGroups: tuple[media_id.MediaId, ...] = (media_id.ABSTRACT, media_id.ELECTRICITY, media_id.HCA, media_id.GAS, media_id.THERMAL, media_id.WATER)
1692
+
1693
+
1694
+ @lru_cache(maxsize=1000)
1695
+ def get_relation_group(ln: cst.LogicalName) -> RelationGroup:
1696
+ if ln.a == media_id.ABSTRACT:
1697
+ if ln.c == 0:
1698
+ if ln.d == 1:
1699
+ return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES
1700
+ elif ln.d in (2, 9):
1701
+ return media_id.OTHER_ABSTRACT_GENERAL_PURPOSE_OBIS_CODES
1702
+ elif ln.c == 1:
1703
+ if ln.d in (0, 1, 2, 3, 4, 5, 6):
1704
+ return media_id.CLOCK_OBJECTS
1705
+ elif ln.c == 2:
1706
+ if ln.d in (0, 1, 2):
1707
+ return media_id.MODEM_CONFIGURATION_AND_RELATED_OBJECTS
1708
+ elif ln.c == 10 and ln.d == 0 and ln.e in (0, 1, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 125):
1709
+ return media_id.SCRIPT_TABLE_OBJECTS
1710
+ elif ln.c == 11 and ln.d == 0:
1711
+ return media_id.SPECIAL_DAYS_TABLE_OBJECTS
1712
+ elif ln.c == 12 and ln.d == 0:
1713
+ return media_id.SCHEDULE_OBJECTS
1714
+ elif ln.c == 13 and ln.d == 0:
1715
+ return media_id.ACTIVITY_CALENDAR_OBJECTS
1716
+ elif ln.c == 14 and ln.d == 0:
1717
+ return media_id.REGISTER_ACTIVATION_OBJECTS
1718
+ elif ln.c == 15 and ln.d == 0 and ln.e in (0, 1, 2, 3, 4, 5, 6, 7):
1719
+ return media_id.SINGLE_ACTION_SCHEDULE_OBJECTS
1720
+ elif ln.c == 16:
1721
+ if ln.d == 0 or (ln.d == 1 and ln.e in range(0, 10)):
1722
+ return media_id.REGISTER_OBJECTS_MONITOR
1723
+ elif ln.d == 2:
1724
+ return media_id.PARAMETER_MONITOR_OBJECTS
1725
+ elif ln.c == 17 and ln.d == 0:
1726
+ return media_id.LIMITER_OBJECTS
1727
+ elif ln.c == 18 and ln.d == 0:
1728
+ return media_id.ARRAY_MANAGER_OBJECT
1729
+ elif ln.c == 19:
1730
+ if (ln.d in range(0, 10) and ln.e == 0) or ln.d in range(10, 50) or ln.d in (range(50, 60) and ln.e in (1, 2)):
1731
+ return media_id.PAYMENT_METERING_RELATED_OBJECTS
1732
+ elif ln.c == 20 and ln.d == 0 and ln.e in (0, 1):
1733
+ return media_id.IEC_LOCAL_PORT_SETUP_OBJECTS
1734
+ elif ln.c == 21 and ln.d == 0:
1735
+ return media_id.STANDARD_READOUT_PROFILE_OBJECTS
1736
+ elif ln.c == 22 and ln.d == 0 and ln.e == 0:
1737
+ return media_id.IEC_HDLC_SETUP_OBJECTS
1738
+ elif ln.c == 23:
1739
+ if (ln.d in 0, 1, 2 and ln.e == 0) or ln.d == 3:
1740
+ return media_id.IEC_TWISTED_PAIR_1_SETUP_OBJECTS
1741
+ elif ln.c == 24:
1742
+ if (ln.d in (0, 1, 4, 5, 6) and ln.e == 0) or (ln.d in (2, 8, 9)):
1743
+ return media_id.OBJECTS_RELATED_TO_DATA_EXCHANGE_OVER_M_BUS
1744
+ elif ln.c == 31 and ln.d == 0 and ln.e == 0:
1745
+ return media_id.OBJECTS_RELATED_TO_DATA_EXCHANGE_OVER_M_BUS
1746
+ elif ln.c == 25:
1747
+ if ln.d in (0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15) and ln.e == 0:
1748
+ return media_id.OBJECTS_TO_SET_UP_DATA_EXCHANGE_OVER_THE_INTERNET
1749
+ elif ln.d == 9 and ln.e == 0:
1750
+ return media_id.OBJECTS_TO_SET_UP_PUSH_SETUP
1751
+ elif ln.c == 26 and ln.d in (0, 1, 2, 3, 5, 6) and ln.e == 0:
1752
+ return media_id.OBJECTS_FOR_SETTING_UP_DATA_EXCHANGE_USING_S_FSK_PLC
1753
+ elif ln.c == 27 and ln.d in (0, 1, 2) and ln.e == 0:
1754
+ return media_id.OBJECTS_FOR_SETTING_UP_THE_ISO_IEC_8802_2_LLC_LAYER
1755
+ elif ln.c == 28 and ln.d in (0, 1, 2, 3, 4, 5, 6, 7) and ln.e == 0:
1756
+ return media_id.OBJECTS_FOR_DATA_EXCHANGE_USING_NARROWBAND_OFDM_PLC_FOR_PRIME_NETWORKS
1757
+ elif ln.c == 29 and ln.d in (0, 1, 2) and ln.e == 0:
1758
+ return media_id.OBJECTS_FOR_DATA_EXCHANGE_USING_NARROW_BAND_OFDM_PLC_FOR_G3_PLC_NETWORKS
1759
+ elif ln.c == 30 and ln.d in (0, 1, 2, 3, 4):
1760
+ return media_id.ZIGBEE_SETUP_OBJECTS
1761
+ elif ln.c == 32 and ln.d in (0, 1, 2, 3) and ln.e == 0:
1762
+ return media_id.OBJECTS_FOR_SETTING_UP_AND_MANAGING_DATA_EXCHANGE_USING_ISO_IEC_14908_PLC_NETWORKS
1763
+ elif ln.c == 33 and ln.d in (0, 1, 2, 3) and ln.e == 0:
1764
+ return media_id.OBJECTS_FOR_DATA_EXCHANGE_USING_HS_PLC_ISO_IEC_12139_1_ISO_EC_12139_1_NETWORKS
1765
+ elif ln.c == 34 and ln.d in (0, 1, 2, 3) and ln.e == 0:
1766
+ return media_id.OBJECTS_FOR_DATA_EXCHANGE_USING_WI_SUN_NETWORKS
1767
+ elif ln.c == 40 and ln.b == 0 and ln.d == 0:
1768
+ return media_id.ASSOCIATION_OBJECTS
1769
+ elif ln.c == 41 and ln.b == 0 and ln.d == 0 and ln.e == 0:
1770
+ return media_id.SAP_ASSIGNMENT_OBJECT
1771
+ elif ln.c == 42 and ln.b == 0 and ln.d == 0 and ln.e == 0:
1772
+ return media_id.COSEM_LOGICAL_DEVICE_NAME_OBJECT
1773
+ elif ln.c == 43:
1774
+ if (ln.b == 0 and ln.d == 0) or ln.d in (1, 2):
1775
+ return media_id.INFORMATION_SECURITY_RELATED_OBJECTS
1776
+ elif ln.c == 44:
1777
+ if ln.b == 0:
1778
+ if ln.d == 0:
1779
+ return media_id.IMAGE_TRANSFER_OBJECTS
1780
+ elif ln.d == 1:
1781
+ return media_id.FUNCTION_CONTROL_OBJECTS
1782
+ elif ln.d == 2:
1783
+ return media_id.COMMUNICATION_PORT_PROTECTION_OBJECTS
1784
+ elif ln.c == 65 and ln.d in __range63:
1785
+ return media_id.UTILITY_TABLE_OBJECTS
1786
+ elif ln.c == 66 and ln.d == 0:
1787
+ return media_id.COMPACT_DATA_OBJECTS
1788
+ elif ln.c == 96:
1789
+ if ln.d == 1:
1790
+ if ln.e in __range10_and_255:
1791
+ return media_id.DEVICE_ID_OBJECTS
1792
+ elif ln.e == 10:
1793
+ return media_id.METERING_POINT_ID_OBJECTS
1794
+ elif ln.d == 2:
1795
+ return media_id.PARAMETER_CHANGES_AND_CALIBRATION_OBJECTS
1796
+ elif ln.d == 3:
1797
+ if ln.e in (0, 1, 2, 3, 4):
1798
+ return media_id.I_O_CONTROL_SIGNAL_OBJECTS
1799
+ elif ln.e == 10:
1800
+ return media_id.DISCONNECT_CONTROL_OBJECTS
1801
+ elif ln.e in range(20, 30):
1802
+ return media_id.ARBITRATOR_OBJECTS
1803
+ elif ln.d == 4 and ln.e in (0, 1, 2, 3, 4):
1804
+ return media_id.STATUS_OF_INTERNAL_CONTROL_SIGNALS_OBJECTS
1805
+ elif ln.d == 5 and ln.e in (0, 1, 2, 3, 4):
1806
+ return media_id.INTERNAL_OPERATING_STATUS_OBJECTS
1807
+ elif ln.d == 6 and ln.e in (0, 1, 2, 3, 4, 5, 6):
1808
+ return media_id.BATTERY_ENTRIES_OBJECTS
1809
+ elif ln.d == 7 and ln.e in range(0, 22):
1810
+ return media_id.POWER_FAILURE_MONITORING_OBJECTS
1811
+ elif ln.d == 8 and ln.e in __range63:
1812
+ return media_id.OPERATING_TIME_OBJECTS
1813
+ elif ln.d == 9 and ln.e in (0, 1, 2):
1814
+ return media_id.ENVIRONMENT_RELATED_PARAMETERS_OBJECTS
1815
+ elif ln.d == 10 and ln.e in range(1, 11):
1816
+ return media_id.STATUS_REGISTER_OBJECTS
1817
+ elif ln.d == 11 and ln.e in range(0, 100):
1818
+ return media_id.EVENT_CODE_OBJECTS
1819
+ elif ln.d == 12 and ln.e in range(0, 7):
1820
+ return media_id.COMMUNICATION_PORT_LOG_PARAMETER_OBJECTS
1821
+ elif ln.d == 13 and ln.e in (0, 1):
1822
+ return media_id.CONSUMER_MESSAGE_OBJECTS
1823
+ elif ln.d == 14 and ln.e in range(0, 16):
1824
+ return media_id.CURRENTLY_ACTIVE_TARIFF_OBJECTS
1825
+ elif ln.d == 15 and ln.e in range(0, 100):
1826
+ return media_id.EVENT_COUNTER_OBJECTS
1827
+ elif ln.d == 16 and ln.e in range(0, 10):
1828
+ return media_id.PROFILE_ENTRY_DIGITAL_SIGNATURE_OBJECTS
1829
+ elif ln.d == 17 and ln.e in range(0, 128):
1830
+ return media_id.PROFILE_ENTRY_COUNTER_OBJECTS
1831
+ elif ln.d == 20:
1832
+ return media_id.METER_TAMPER_EVENT_RELATED_OBJECTS
1833
+ elif ln.d in range(50, 100):
1834
+ return media_id.ABSTRACT_MANUFACTURER_SPECIFIC
1835
+ elif ln.c == 97:
1836
+ if ln.d == 97 and ln.e in __range10_and_255:
1837
+ return media_id.ERROR_REGISTER_OBJECTS
1838
+ elif ln.d == 98 and (ln.e in chain(range(0, 30), (255,))):
1839
+ return media_id.ALARM_REGISTER_FILTER_DESCRIPTOR_OBJECTS
1840
+ elif ln.c == 98:
1841
+ return media_id.GENERAL_LIST_OBJECTS
1842
+ elif ln.c == 99:
1843
+ if ln.d in (1, 2, 12, 13, 14, 15, 16, 17, 18) or (ln.d == 3 and ln.e == 0):
1844
+ return media_id.ABSTRACT_DATA_PROFILE_OBJECTS
1845
+ if ln.d == 98:
1846
+ return media_id.EVENT_LOG_OBJECTS
1847
+ elif ln.c == 127 and ln.d == 0:
1848
+ return media_id.INACTIVE_OBJECTS
1849
+ else:
1850
+ return media_id.ABSTRACT
1851
+ elif ln.a == media_id.ELECTRICITY:
1852
+ if ln.c == 0:
1853
+ if ln.d == 0 and ln.e in __range10_and_255:
1854
+ return media_id.ID_NUMBERS_ELECTRICITY
1855
+ elif ln.d == 1:
1856
+ return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_EL
1857
+ elif ln.d in (2, 3, 4, 6, 7, 8, 9, 10):
1858
+ return media_id.OTHER_ELECTRICITY_RELATED_GENERAL_PURPOSE_OBJECTS
1859
+ elif ln.d == 11 and ln.e in (1, 2, 3, 4, 5, 6, 7):
1860
+ return media_id.MEASUREMENT_ALGORITHM
1861
+ elif ln.c in (1, 21, 41, 61):
1862
+ return media_id.ACTIVE_POWER_PLUS
1863
+ elif ln.c in (2, 22, 42, 62):
1864
+ return media_id.ACTIVE_POWER_MINUS
1865
+ elif ln.c in (3, 23, 43, 63):
1866
+ return media_id.REACTIVE_POWER_PLUS
1867
+ elif ln.c in (4, 24, 44, 64):
1868
+ return media_id.REACTIVE_POWER_MINUS
1869
+ elif ln.c in (5, 25, 45, 65):
1870
+ return media_id.REACTIVE_POWER_QI
1871
+ elif ln.c in (6, 26, 46, 66):
1872
+ return media_id.REACTIVE_POWER_QII
1873
+ elif ln.c in (7, 27, 47, 67):
1874
+ return media_id.REACTIVE_POWER_QIII
1875
+ elif ln.c in (8, 28, 48, 68):
1876
+ return media_id.REACTIVE_POWER_QIV
1877
+ elif ln.c in (9, 29, 49, 69):
1878
+ return media_id.APPARENT_POWER_PLUS
1879
+ elif ln.c in (10, 30, 50, 70):
1880
+ return media_id.APPARENT_POWER_MINUS
1881
+ elif ln.c in (11, 31, 51, 71):
1882
+ return media_id.CURRENT
1883
+ elif ln.c in (12, 32, 52, 72):
1884
+ return media_id.VOLTAGE
1885
+ elif ln.c in (13, 33, 53, 73):
1886
+ return media_id.POWER_FACTOR
1887
+ elif ln.c in (14, 34, 54, 74):
1888
+ return media_id.SUPPLY_FREQUENCY
1889
+ elif ln.c in (15, 35, 55, 75):
1890
+ return media_id.ACTIVE_POWER_SUM
1891
+ elif ln.c in (16, 36, 56, 76):
1892
+ return media_id.ACTIVE_POWER_DIFF
1893
+ elif ln.c in (17, 37, 57, 77):
1894
+ return media_id.ACTIVE_POWER_QI
1895
+ elif ln.c in (18, 38, 58, 78):
1896
+ return media_id.ACTIVE_POWER_QII
1897
+ elif ln.c in (19, 39, 59, 79):
1898
+ return media_id.ACTIVE_POWER_QIII
1899
+ elif ln.c in (20, 40, 60, 80):
1900
+ return media_id.ACTIVE_POWER_QIV
1901
+ elif ln.c == 96:
1902
+ if ln.d == 1 and ln.e in __range10_and_255:
1903
+ return media_id.ELECTRICITY_METERING_POINT_ID_OBJECTS
1904
+ elif ln.d == 5 and ln.e in (0, 1, 2, 3, 4, 5):
1905
+ return media_id.ELECTRICITY_RELATED_STATUS_OBJECTS
1906
+ elif ln.d == 10 and ln.e in (0, 1, 2, 3):
1907
+ return media_id.ELECTRICITY_RELATED_STATUS_OBJECTS
1908
+ elif ln.c == 98:
1909
+ return media_id.LIST_OBJECTS_ELECTRICITY
1910
+ elif ln.d in range(31, 46) and ln.f in __range100_and_255:
1911
+ if ln.c in __c1 and ln.e in __range63:
1912
+ return media_id.THRESHOLD_VALUES
1913
+ elif ln.c in __table44 and ln.e in __range120_and_124_127:
1914
+ return media_id.THRESHOLD_VALUES
1915
+ elif ln.f in __range100_and_255:
1916
+ if ln.d in (31, 35, 39, 4, 5, 14, 15, 24, 25):
1917
+ if ln.c in __c1 and ln.e in __range63:
1918
+ return media_id.REGISTER_MONITOR_OBJECTS
1919
+ elif ln.c in __c2 and ln.e in __range120_and_124_127:
1920
+ return media_id.REGISTER_MONITOR_OBJECTS
1921
+ else:
1922
+ return media_id.ELECTRICITY
1923
+ elif ln.a == media_id.HCA:
1924
+ if ln.c == 0:
1925
+ if ln.d == 0 and ln.e in __range10_and_255:
1926
+ return media_id.ID_NUMBERS_HCA
1927
+ elif ln.d == 1 and ln.e in (1, 2, 10, 11):
1928
+ return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_HCA
1929
+ elif ln.d == 2 and ln.e in (0, 1, 2, 3):
1930
+ return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1931
+ elif ln.d == 4 and ln.e in (0, 1, 2, 3, 4, 5, 6):
1932
+ return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1933
+ elif ln.d == 5 and ln.e in (10, 11):
1934
+ return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1935
+ elif ln.d == 8 and ln.e in (0, 4, 6):
1936
+ return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1937
+ elif ln.d == 9 and ln.e in (1, 2, 3):
1938
+ return media_id.GENERAL_PURPOSE_OBJECTS_HCA
1939
+ elif ln.c in (1, 2) and ln.e == 0:
1940
+ if ln.d in (0, 6) and ln.f == 255:
1941
+ return media_id.MEASURED_VALUES_HCA_CONSUMPTION
1942
+ elif ln.d in (1, 2, 3, 4, 5) and ln.f in __range100_and_101_125_and_255:
1943
+ return media_id.MEASURED_VALUES_HCA_CONSUMPTION
1944
+ elif ln.c in range(3, 8) and ln.d in (0, 4, 5, 6) and ln.e == 255 and ln.f == 255:
1945
+ return media_id.MEASURED_VALUES_HCA_TEMPERATURE
1946
+ elif ln.c == 97 and ln.d == 97:
1947
+ return media_id.ERROR_REGISTER_OBJECTS_HCA
1948
+ elif ln.c == 98:
1949
+ return media_id.LIST_OBJECTS_HCA
1950
+ elif ln.c == 99 and ln.d == 1:
1951
+ return media_id.DATA_PROFILE_OBJECTS_HCA
1952
+ else:
1953
+ return media_id.HCA
1954
+ elif ln.a == media_id.THERMAL:
1955
+ if ln.c == 0:
1956
+ if ln.d == 0 and ln.e in __range10_and_255:
1957
+ return media_id.ID_NUMBERS_THERMAL
1958
+ elif ln.d == 1 and ln.e in (1, 2, 10, 11):
1959
+ return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_THERMAL
1960
+ elif ln.d == 2 and ln.e in chain(range(0, 5), range(10, 14)):
1961
+ return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1962
+ elif ln.d == 4 and ln.e in (1, 2, 3):
1963
+ return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1964
+ elif ln.d == 5 and ln.e in chain(range(1, 10), range(21, 25)):
1965
+ return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1966
+ elif ln.d == 8 and ln.e in chain(range(0, 8), range(11, 15), range(21, 26), range(31, 35)):
1967
+ return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1968
+ elif ln.d == 9 and ln.e in (1, 2, 3):
1969
+ return media_id.GENERAL_PURPOSE_OBJECTS_THERMAL
1970
+ elif ln.c in range(1, 8):
1971
+ if ln.e in range(10):
1972
+ if ln.d in (0, 1, 2, 3, 7) and ln.f == 255:
1973
+ return media_id.MEASURED_VALUES_THERMAL_CONSUMPTION
1974
+ elif ln.d in (3, 8, 9) and ln.f in __range100_and_101_125_and_255:
1975
+ return media_id.MEASURED_VALUES_THERMAL_CONSUMPTION
1976
+ elif ln.d in (1, 2, 4, 5, 12, 13, 14, 15) and ln.f in chain(range(100), range(100, 126)):
1977
+ return media_id.MEASURED_VALUES_THERMAL_CONSUMPTION
1978
+ elif ln.d == 6 and ln.e == 255 and ln.f == 255:
1979
+ return media_id.MEASURED_VALUES_THERMAL_CONSUMPTION
1980
+ elif ln.e in range(10):
1981
+ if ln.f in __range100_and_101_125_and_255:
1982
+ if ln.c in range(1, 8) and ln.d in (5, 15):
1983
+ return media_id.MEASURED_VALUES_THERMAL_ENERGY
1984
+ elif ln.c in (8, 9) and ln.d in (1, 4, 5, 12, 13, 14, 15):
1985
+ return media_id.MEASURED_VALUES_THERMAL_ENERGY
1986
+ elif ln.c in range(10, 14):
1987
+ if ln.d == 0 and ln.f == 255:
1988
+ return media_id.MEASURED_VALUES_THERMAL_ENERGY
1989
+ elif ln.d in (4, 5, 14, 15) and ln.f in chain(range(100), range(101, 126)):
1990
+ return media_id.MEASURED_VALUES_THERMAL_ENERGY
1991
+ elif ln.d in (6, 7, 10, 11) and ln.f == 255:
1992
+ return media_id.MEASURED_VALUES_THERMAL_ENERGY
1993
+ elif ln.c in range(1, 14) and (ln.d in range(20, 26)) and ln.f == 255:
1994
+ return media_id.MEASURED_VALUES_THERMAL_ENERGY
1995
+ elif ln.c == 97 and ln.d == 97 and ln.e in (0, 1, 2):
1996
+ return media_id.ERROR_REGISTER_OBJECTS_THERMAL
1997
+ elif ln.c == 98:
1998
+ return media_id.LIST_OBJECTS_THERMAL
1999
+ elif ln.c == 99 and ln.f == 255:
2000
+ if ln.d in (1, 2) and ln.e in (1, 2, 3):
2001
+ return media_id.DATA_PROFILE_OBJECTS_THERMAL
2002
+ elif ln.d == 3 and ln.e == 1:
2003
+ return media_id.DATA_PROFILE_OBJECTS_THERMAL
2004
+ elif ln.d == 99:
2005
+ return media_id.DATA_PROFILE_OBJECTS_THERMAL
2006
+ else:
2007
+ return media_id.THERMAL
2008
+ elif media_id.GAS == ln.a:
2009
+ if ln.c == 0:
2010
+ if ln.d == 0 and ln.e in __range10_and_255:
2011
+ return media_id.ID_NUMBERS_GAS
2012
+ elif ln.d == 1:
2013
+ return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_GAS
2014
+ elif ln.d in (2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15):
2015
+ return media_id.GENERAL_PURPOSE_OBJECTS_GAS
2016
+ elif ln.c == 96 and ln.d == 5 and (ln.e in range(10)):
2017
+ return media_id.INTERNAL_OPERATING_STATUS_OBJECTS_GAS
2018
+ elif ln.c in chain(range(1, 9), range(11, 17), range(21, 27), range(31, 36), range(61, 66)) and ln.e in __range63:
2019
+ if ln.d in (24, 25, 26, 42, 43, 44, 63, 64, 65, 81, 82, 83) and ln.f in chain(range(100), range(101, 127)):
2020
+ return media_id.MEASURED_VALUES_GAS_INDEXES_AND_INDEX_DIFFERENCES
2021
+ elif ln.d in chain(range(6, 24), range(27, 33), range(45, 51), range(66, 72), range(84, 90)) and ln.f == 255:
2022
+ return media_id.MEASURED_VALUES_GAS_INDEXES_AND_INDEX_DIFFERENCES
2023
+ elif ln.d in chain(range(33, 42), range(52, 63), range(72, 81), range(90, 99)) and ln.f in chain(__range100_and_101_125_and_255, (126,)):
2024
+ return media_id.MEASURED_VALUES_GAS_INDEXES_AND_INDEX_DIFFERENCES
2025
+ elif ln.c == 42 and ln.e == 0:
2026
+ if ln.d in chain(0, 1, 2, 13, range(15, 19), range(19, 31), range(35, 51), range(55, 71)) and ln.f == 255:
2027
+ return media_id.MEASURED_VALUES_GAS_FLOW_RATE
2028
+ elif ln.d in chain(range(31, 35), range(51, 55)) and ln.f in chain(__range100_and_101_125_and_255, (126,)):
2029
+ return media_id.MEASURED_VALUES_GAS_FLOW_RATE
2030
+ elif ln.c in chain((41, 42), range(44, 50)) and ln.d in (0, 2, 3, 10, 11, 13, range(15, 92)) and ln.e == 0 and ln.f == 255:
2031
+ return media_id.MEASURED_VALUES_GAS_PROCESS_VALUES
2032
+ elif ln.c in range(51, 56):
2033
+ if ln.d in (0, 2, 3, 10, 11) and ln.e in chain((0, 1), range(11, 29)) and ln.f == 255:
2034
+ return media_id.CONVERSION_RELATED_FACTORS_AND_COEFFICIENTS_GAS
2035
+ elif ln.d == 12 and ln.e in range(20) and ln.f == 255:
2036
+ return media_id.CALCULATION_METHODS_GAS
2037
+ elif ln.c == 70 and ln.f == 255:
2038
+ if ln.d in (8, 9) and ln.e == 0:
2039
+ return media_id.NATURAL_GAS_ANALYSIS
2040
+ elif ln.d in chain(range(10, 21), range(60, 85)) and ln.e in chain((0, 1), range(11, 29)):
2041
+ return media_id.NATURAL_GAS_ANALYSIS
2042
+ elif ln.c == 98:
2043
+ return media_id.LIST_OBJECTS_GAS
2044
+ else:
2045
+ return media_id.GAS
2046
+ elif ln.a == media_id.WATER:
2047
+ if ln.c == 0:
2048
+ if ln.d == 0 and ln.e in __range10_and_255:
2049
+ return media_id.ID_NUMBERS_WATER
2050
+ elif ln.d == 1 and ln.e in (1, 2, 10, 11, 12):
2051
+ return media_id.BILLING_PERIOD_VALUES_RESET_COUNTER_ENTRIES_WATER
2052
+ elif ln.d == 2 and ln.e in (0, 3):
2053
+ return media_id.GENERAL_PURPOSE_OBJECTS_WATER
2054
+ elif ln.d in (5, 7) and ln.e == 1:
2055
+ return media_id.GENERAL_PURPOSE_OBJECTS_WATER
2056
+ elif ln.d == 8 and ln.e in (1, 6):
2057
+ return media_id.GENERAL_PURPOSE_OBJECTS_WATER
2058
+ elif ln.d == 9 and ln.e in (1, 2, 3):
2059
+ return media_id.GENERAL_PURPOSE_OBJECTS_WATER
2060
+ elif ln.c == 1 and ln.e in range(0, 13):
2061
+ if ln.d in (0, 1, 2, 3, 6) and ln.f == 255:
2062
+ return media_id.MEASURED_VALUES_WATER_CONSUMPTION
2063
+ elif ln.d in range(1, 6) and ln.f in chain(range(100), range(101, 126)):
2064
+ return media_id.MEASURED_VALUES_WATER_CONSUMPTION
2065
+ elif ln.c in (2, 3) and ln.e in range(0, 13):
2066
+ if ln.d in (0, 1, 2, 3, 6) and ln.f == 255:
2067
+ return media_id.MEASURED_VALUES_WATER_MONITORING_VALUES
2068
+ elif ln.d in range(1, 6) and ln.f in chain(range(100), range(101, 126)):
2069
+ return media_id.MEASURED_VALUES_WATER_MONITORING_VALUES
2070
+ elif ln.c == 97 and ln.d == 97:
2071
+ return media_id.ERROR_REGISTER_OBJECTS_WATER
2072
+ elif ln.c == 98:
2073
+ return media_id.LIST_OBJECTS_WATER
2074
+ elif ln.c == 99 and ln.d == 1:
2075
+ return media_id.DATA_PROFILE_OBJECTS_WATER
2076
+ else:
2077
+ return media_id.WATER
2078
+ return media_id.OTHER_MEDIA
2079
+
2080
+
2081
+ DLMSObjectContainer: TypeAlias = Collection | list[InterfaceClass] | filter
2082
+
2083
+
2084
+ @dataclass
2085
+ class Template:
2086
+ name: str
2087
+ collections: list[Collection]
2088
+ used: UsedAttributes
2089
+ description: str = ""
2090
+ verified: bool = False
2091
+
2092
+ def get_not_contains(self, cols: Iterable[Collection]) -> list[Collection]:
2093
+ """return of collections not contains in template"""
2094
+ ret = list()
2095
+ for i, col in enumerate(cols):
2096
+ if col not in self.collections:
2097
+ ret.append(col)
2098
+ return ret
2099
+
2100
+ def get_not_valid(self, col: Collection) -> list[Exception]:
2101
+ """with update col"""
2102
+ attr: cdt.CommonDataType
2103
+ ret = list()
2104
+ use_col = self.collections[0]
2105
+ """temporary used first collection"""
2106
+ for ln, indexes in self.used.items():
2107
+ try:
2108
+ obj = use_col.get_object(ln)
2109
+ obj_col = col.get_object(ln)
2110
+ except exc.NoObject as e:
2111
+ ret.append(e)
2112
+ print(F"<add_collection> skip obj{self}: {e}")
2113
+ continue
2114
+ for i in indexes:
2115
+ if (attr := obj.get_attr(i)) is not None:
2116
+ try:
2117
+ attr.validate()
2118
+ except ValueError as e:
2119
+ ret.append(ValueError(F"can't decode value {attr} for {ln}:{i}"))
2120
+ else:
2121
+ ret.append(exc.NoObject(F"has't attribute {i} for {ln}"))
2122
+ return ret