DLMS-SPODES 0.87.16__py3-none-any.whl → 0.88.1__py3-none-any.whl

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