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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. DLMS_SPODES/Values/EN/__init__.py +1 -1
  2. DLMS_SPODES/Values/EN/actors.py +8 -8
  3. DLMS_SPODES/Values/EN/relation_to_obis_names.py +387 -387
  4. DLMS_SPODES/Values/RU/__init__.py +1 -1
  5. DLMS_SPODES/Values/RU/actors.py +8 -8
  6. DLMS_SPODES/Values/RU/relation_to_obis_names.py +396 -396
  7. DLMS_SPODES/__init__.py +6 -6
  8. DLMS_SPODES/configEN.ini +126 -126
  9. DLMS_SPODES/config_parser.py +53 -53
  10. DLMS_SPODES/cosem_interface_classes/__class_init__.py +3 -3
  11. DLMS_SPODES/cosem_interface_classes/__init__.py +1 -1
  12. DLMS_SPODES/cosem_interface_classes/a_parameter.py +20 -20
  13. DLMS_SPODES/cosem_interface_classes/activity_calendar.py +254 -254
  14. DLMS_SPODES/cosem_interface_classes/arbitrator.py +105 -105
  15. DLMS_SPODES/cosem_interface_classes/association_ln/abstract.py +34 -34
  16. DLMS_SPODES/cosem_interface_classes/association_ln/authentication_mechanism_name.py +25 -25
  17. DLMS_SPODES/cosem_interface_classes/association_ln/mechanism_id.py +25 -25
  18. DLMS_SPODES/cosem_interface_classes/association_ln/method.py +5 -5
  19. DLMS_SPODES/cosem_interface_classes/association_ln/ver0.py +485 -485
  20. DLMS_SPODES/cosem_interface_classes/association_ln/ver1.py +133 -133
  21. DLMS_SPODES/cosem_interface_classes/association_ln/ver2.py +36 -36
  22. DLMS_SPODES/cosem_interface_classes/association_ln/ver3.py +4 -4
  23. DLMS_SPODES/cosem_interface_classes/association_sn/ver0.py +12 -12
  24. DLMS_SPODES/cosem_interface_classes/attr_indexes.py +12 -12
  25. DLMS_SPODES/cosem_interface_classes/clock.py +131 -131
  26. DLMS_SPODES/cosem_interface_classes/collection.py +2122 -2122
  27. DLMS_SPODES/cosem_interface_classes/cosem_interface_class.py +583 -583
  28. DLMS_SPODES/cosem_interface_classes/data.py +21 -21
  29. DLMS_SPODES/cosem_interface_classes/demand_register/ver0.py +59 -59
  30. DLMS_SPODES/cosem_interface_classes/disconnect_control.py +74 -74
  31. DLMS_SPODES/cosem_interface_classes/extended_register.py +27 -27
  32. DLMS_SPODES/cosem_interface_classes/gprs_modem_setup.py +43 -43
  33. DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver0.py +103 -103
  34. DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver1.py +40 -40
  35. DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver2.py +9 -9
  36. DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver0.py +11 -11
  37. DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver1.py +53 -53
  38. DLMS_SPODES/cosem_interface_classes/iec_local_port_setup.py +11 -11
  39. DLMS_SPODES/cosem_interface_classes/image_transfer/image_transfer_status.py +15 -15
  40. DLMS_SPODES/cosem_interface_classes/image_transfer/ver0.py +126 -126
  41. DLMS_SPODES/cosem_interface_classes/implementations/__init__.py +3 -3
  42. DLMS_SPODES/cosem_interface_classes/implementations/arbitrator.py +19 -19
  43. DLMS_SPODES/cosem_interface_classes/implementations/data.py +487 -487
  44. DLMS_SPODES/cosem_interface_classes/implementations/profile_generic.py +83 -83
  45. DLMS_SPODES/cosem_interface_classes/ipv4_setup.py +72 -72
  46. DLMS_SPODES/cosem_interface_classes/limiter.py +111 -111
  47. DLMS_SPODES/cosem_interface_classes/ln_pattern.py +333 -333
  48. DLMS_SPODES/cosem_interface_classes/modem_configuration/ver0.py +65 -65
  49. DLMS_SPODES/cosem_interface_classes/modem_configuration/ver1.py +39 -39
  50. DLMS_SPODES/cosem_interface_classes/ntp_setup/ver0.py +67 -67
  51. DLMS_SPODES/cosem_interface_classes/obis.py +23 -23
  52. DLMS_SPODES/cosem_interface_classes/overview.py +197 -197
  53. DLMS_SPODES/cosem_interface_classes/parameter.py +547 -547
  54. DLMS_SPODES/cosem_interface_classes/parameters.py +172 -172
  55. DLMS_SPODES/cosem_interface_classes/profile_generic/ver0.py +122 -122
  56. DLMS_SPODES/cosem_interface_classes/profile_generic/ver1.py +277 -277
  57. DLMS_SPODES/cosem_interface_classes/push_setup/ver0.py +12 -12
  58. DLMS_SPODES/cosem_interface_classes/push_setup/ver1.py +10 -10
  59. DLMS_SPODES/cosem_interface_classes/push_setup/ver2.py +166 -166
  60. DLMS_SPODES/cosem_interface_classes/register.py +45 -45
  61. DLMS_SPODES/cosem_interface_classes/register_activation/ver0.py +80 -80
  62. DLMS_SPODES/cosem_interface_classes/register_monitor.py +46 -46
  63. DLMS_SPODES/cosem_interface_classes/reports.py +70 -70
  64. DLMS_SPODES/cosem_interface_classes/schedule.py +176 -176
  65. DLMS_SPODES/cosem_interface_classes/script_table.py +87 -87
  66. DLMS_SPODES/cosem_interface_classes/security_setup/ver0.py +68 -68
  67. DLMS_SPODES/cosem_interface_classes/security_setup/ver1.py +158 -158
  68. DLMS_SPODES/cosem_interface_classes/single_action_schedule.py +50 -50
  69. DLMS_SPODES/cosem_interface_classes/special_days_table.py +84 -84
  70. DLMS_SPODES/cosem_interface_classes/tcp_udp_setup.py +42 -42
  71. DLMS_SPODES/cosem_pdu.py +93 -93
  72. DLMS_SPODES/enums.py +625 -625
  73. DLMS_SPODES/exceptions.py +106 -106
  74. DLMS_SPODES/firmwares.py +99 -99
  75. DLMS_SPODES/hdlc/frame.py +875 -875
  76. DLMS_SPODES/hdlc/sub_layer.py +54 -54
  77. DLMS_SPODES/literals.py +17 -17
  78. DLMS_SPODES/obis/__init__.py +1 -1
  79. DLMS_SPODES/obis/media_id.py +931 -931
  80. DLMS_SPODES/pardata.py +22 -22
  81. DLMS_SPODES/pdu_enums.py +98 -98
  82. DLMS_SPODES/relation_to_OBIS.py +465 -463
  83. DLMS_SPODES/settings.py +551 -551
  84. DLMS_SPODES/types/choices.py +142 -142
  85. DLMS_SPODES/types/common_data_types.py +2401 -2401
  86. DLMS_SPODES/types/cosem_service_types.py +109 -109
  87. DLMS_SPODES/types/implementations/arrays.py +25 -25
  88. DLMS_SPODES/types/implementations/bitstrings.py +97 -97
  89. DLMS_SPODES/types/implementations/double_long_usingneds.py +35 -35
  90. DLMS_SPODES/types/implementations/enums.py +57 -57
  91. DLMS_SPODES/types/implementations/integers.py +11 -11
  92. DLMS_SPODES/types/implementations/long_unsigneds.py +127 -127
  93. DLMS_SPODES/types/implementations/octet_string.py +11 -11
  94. DLMS_SPODES/types/implementations/structs.py +64 -64
  95. DLMS_SPODES/types/useful_types.py +677 -677
  96. {dlms_spodes-0.87.13.dist-info → dlms_spodes-0.87.16.dist-info}/METADATA +30 -30
  97. dlms_spodes-0.87.16.dist-info/RECORD +117 -0
  98. {dlms_spodes-0.87.13.dist-info → dlms_spodes-0.87.16.dist-info}/WHEEL +1 -1
  99. dlms_spodes-0.87.13.dist-info/RECORD +0 -117
  100. {dlms_spodes-0.87.13.dist-info → dlms_spodes-0.87.16.dist-info}/top_level.txt +0 -0
@@ -1,254 +1,254 @@
1
- from typing_extensions import deprecated
2
- from typing import Self
3
- from .overview import VERSION_0
4
- from .__class_init__ import *
5
- from ..types.implementations import integers, octet_string
6
-
7
-
8
- class Season(cdt.Structure):
9
- """ Defined by their starting date and a specific week_profile to be executed """
10
- season_profile_name: cdt.OctetString
11
- season_start: cst.OctetStringDateTime
12
- week_name: cdt.OctetString
13
-
14
-
15
- class SeasonProfile(cdt.Array):
16
- """ Contains a list of seasons defined by their starting date and a specific week_profile to be executed. The list is sorted according to season_start. """
17
- TYPE = Season
18
- values: list[Season]
19
-
20
- def new_element(self) -> Season:
21
- names: list[bytes] = [bytes(el.season_profile_name) for el in self.values]
22
- for new_name in (i.to_bytes(1, 'big') for i in range(256)):
23
- if new_name not in names:
24
- return Season((bytearray(new_name), None, bytearray(b'week_name?')))
25
- raise ValueError(F'in {self} all season names is busy')
26
-
27
- def sort(self, date_time: cdt.DateTime) -> Self:
28
- """sort by date-time
29
- :return now Season + next Seasons"""
30
- s: Season
31
- d_t = date_time.to_datetime()
32
- l = list()
33
- """left datetime"""
34
- r = list()
35
- """right datetime"""
36
- for i, s in enumerate(self):
37
- if (el := s.season_start.get_left_nearest_datetime(d_t)) is not None:
38
- l.append((i, el))
39
- if (el := s.season_start.get_right_nearest_datetime(d_t)) is not None:
40
- r.append((i, el))
41
- l.sort(key=lambda it: it[1])
42
- r.sort(key=lambda it: it[1])
43
- now = l[-1][0]
44
- indexes: list[int] = [now]
45
- for el in r:
46
- if el[0] == now:
47
- continue
48
- else:
49
- indexes.append(el[0])
50
- sorted_data = self.__class__()
51
- for i in indexes:
52
- sorted_data.append(self[i])
53
- return sorted_data
54
-
55
-
56
- class WeekProfile(cdt.Structure):
57
- """ For each week_profile, the day_profile for every day of a week is identified. """
58
- week_profile_name: cdt.OctetString
59
- monday: cdt.Unsigned
60
- tuesday: cdt.Unsigned
61
- wednesday: cdt.Unsigned
62
- thursday: cdt.Unsigned
63
- friday: cdt.Unsigned
64
- saturday: cdt.Unsigned
65
- sunday: cdt.Unsigned
66
-
67
-
68
- class WeekProfileTable(cdt.Array):
69
- """ Contains an array of week_profiles to be used in the different seasons. For each week_profile, the day_profile for every day of a week is
70
- identified. """
71
- TYPE = WeekProfile
72
- values: list[WeekProfile]
73
-
74
- def new_element(self) -> WeekProfile:
75
- """return default WeekProfile with vacant week_profile_name, existed day ID and insert callback for validate change DayID"""
76
- names: list[bytes] = [bytes(el.week_profile_name) for el in self.values]
77
- for new_name in (i.to_bytes(1, 'big') for i in range(256)):
78
- if new_name not in names:
79
- return WeekProfile((bytearray(new_name), *[0]*7))
80
- raise ValueError(F'in {self} all week names is busy')
81
-
82
- def get_week_profile_names(self) -> tuple[cdt.OctetString, ...]:
83
- return tuple((el.week_profile_name for el in self.values))
84
-
85
-
86
- class DayProfileAction(cdt.Structure):
87
- """ Scheduled action is defined by a script to be executed and the corresponding activation time (start_time). """
88
- start_time: cst.OctetStringTime
89
- script_logical_name: cst.LogicalName
90
- script_selector: cdt.LongUnsigned
91
-
92
- def __lt__(self, other: Self):
93
- return self.start_time.to_time() < other.start_time.to_time()
94
-
95
-
96
- # TODO: make unique by start_time
97
- class DaySchedule(cdt.Array):
98
- """ Contains an array of day_profile_action. """
99
- TYPE = DayProfileAction
100
- values: list[DayProfileAction]
101
-
102
-
103
- class DayProfile(cdt.Structure):
104
- """ list of Scheduled actions is defined by a script to be executed and the corresponding activation time (start_time) with day ID. """
105
- day_id: cdt.Unsigned
106
- day_schedule: DaySchedule
107
-
108
-
109
- class DayProfileTable(cdt.Array):
110
- """ Contains an array of day_profiles, identified by their day_id. For each day_profile, a list of scheduled actions is defined by a script to be
111
- executed and the corresponding activation time (start_time). The list is sorted according to start_time. """
112
- TYPE = DayProfile
113
- values: list[DayProfile]
114
-
115
- def new_element(self) -> DayProfile:
116
- """return default DayProfile with vacant Day ID"""
117
- day_ids: list[int] = [int(el.day_id) for el in self.values]
118
- for i in range(0xff):
119
- if i not in day_ids:
120
- return DayProfile((i, None))
121
- raise ValueError(F'in {self} all days ID is busy')
122
-
123
- def get_day_ids(self) -> tuple[cdt.Unsigned, ...]:
124
- return tuple((day_profile.day_id for day_profile in self.values))
125
-
126
- def normalize(self) -> bool:
127
- d_p: DayProfile
128
- res = False
129
- for d_p in self:
130
- if (new := DaySchedule(sorted(d_p.day_schedule))) != d_p.day_schedule:
131
- res = True
132
- d_p.day_schedule.__dict__["values"] = new.values
133
- return res
134
-
135
-
136
- class ActivityCalendar(ic.COSEMInterfaceClasses):
137
- """DLMS UA 1000-1 Ed. 14 4.5.5 Activity calendar"""
138
- CLASS_ID = ClassID.ACTIVITY_CALENDAR
139
- VERSION = VERSION_0
140
- A_ELEMENTS = (ic.ICAElement("calendar_name_active", octet_string.ID),
141
- ic.ICAElement("season_profile_active", SeasonProfile),
142
- ic.ICAElement("week_profile_table_active", WeekProfileTable),
143
- ic.ICAElement("day_profile_table_active", DayProfileTable),
144
- ic.ICAElement("calendar_name_passive", octet_string.ID),
145
- ic.ICAElement("season_profile_passive", SeasonProfile),
146
- ic.ICAElement("week_profile_table_passive", WeekProfileTable),
147
- ic.ICAElement("day_profile_table_passive", DayProfileTable),
148
- ic.ICAElement("activate_passive_calendar_time", cst.OctetStringDateTime))
149
- M_ELEMENTS = ic.ICMElement(
150
- NAME="activate_passive_calendar",
151
- DATA_TYPE=integers.Only0),
152
-
153
- def characteristics_init(self):
154
- # Attributes called …_active are currently active, attributes called …_passive will be activated by the specific
155
- # method activate_passive_calendar.
156
- self.set_attr(ai.DAY_PROFILE_TABLE_ACTIVE, None)
157
- self.set_attr(ai.WEEK_PROFILE_TABLE_ACTIVE, None)
158
- self.set_attr(ai.SEASON_PROFILE_ACTIVE, None)
159
- self.set_attr(ai.DAY_PROFILE_TABLE_PASSIVE, None)
160
- self.set_attr(ai.WEEK_PROFILE_TABLE_PASSIVE, None)
161
- self.set_attr(ai.SEASON_PROFILE_PASSIVE, None)
162
-
163
- @property
164
- def calendar_name_active(self) -> octet_string.ID:
165
- return self.get_attr(2)
166
-
167
- @property
168
- def season_profile_active(self) -> SeasonProfile:
169
- return self.get_attr(3)
170
-
171
- @property
172
- def week_profile_table_active(self) -> WeekProfileTable:
173
- return self.get_attr(4)
174
-
175
- @property
176
- def day_profile_table_active(self) -> DayProfileTable:
177
- return self.get_attr(5)
178
-
179
- @property
180
- def calendar_name_passive(self) -> octet_string.ID:
181
- return self.get_attr(6)
182
-
183
- @property
184
- def season_profile_passive(self) -> SeasonProfile:
185
- return self.get_attr(7)
186
-
187
- @property
188
- def week_profile_table_passive(self) -> WeekProfileTable:
189
- return self.get_attr(8)
190
-
191
- @property
192
- def day_profile_table_passive(self) -> DayProfileTable:
193
- return self.get_attr(9)
194
-
195
- @property
196
- def activate_passive_calendar_time(self) -> cst.OctetStringDateTime:
197
- return self.get_attr(10)
198
-
199
- @property
200
- @deprecated("use ActivatePassiveCalendar")
201
- def activate_passive_calendar(self) -> integers.Only0:
202
- return self.get_meth(1)
203
-
204
- @classmethod
205
- def ActivatePassiveCalendar(cls, value=None) -> integers.Only0:
206
- return cls.M_ELEMENTS[0].DATA_TYPE
207
-
208
- def validate(self):
209
- def validate_seasons(index: int):
210
- def handle_duplicates(name: str):
211
- nonlocal index, duplicates
212
- if len(duplicates) != 0:
213
- raise ic.ObjectValidationError(
214
- ln=self.logical_name,
215
- i=index,
216
- message=F"find duplicate {name}: {', '.join(map(str, duplicates))} in {self.get_attr_element(index)}")
217
- index -= 1
218
-
219
- days: list[DayProfile.day_id] = list()
220
- duplicates: set[DayProfile.day_id] | set[WeekProfile.week_profile_name] | set[Season.season_profile_name] = set()
221
- for it in self.get_attr(index):
222
- if it.day_id not in days:
223
- days.append(it.day_id)
224
- else:
225
- duplicates.add(it.day_id)
226
- handle_duplicates("day_id")
227
- weeks: list[WeekProfile.week_profile_name] = list()
228
- for week_profile in self.get_attr(index):
229
- if week_profile.week_profile_name not in weeks:
230
- weeks.append(week_profile.week_profile_name)
231
- else:
232
- duplicates.add(week_profile.week_profile_name)
233
- for i in range(1, 7):
234
- if week_profile[i] not in days:
235
- raise ic.ObjectValidationError(
236
- ln=self.logical_name,
237
- i=index,
238
- message=F"in {self.get_attr_element(index)} got {week_profile} with day_id: {week_profile[i]}; expected: {', '.join(map(str, days))}")
239
- handle_duplicates("week_profile_name")
240
- seasons: list[Season.season_profile_name] = list()
241
- for season in self.get_attr(index):
242
- if season.season_profile_name not in seasons:
243
- seasons.append(season.season_profile_name)
244
- else:
245
- duplicates.add(season.season_profile_name)
246
- if season.week_name not in weeks:
247
- raise ic.ObjectValidationError(
248
- ln=self.logical_name,
249
- i=index,
250
- message=F"in {self.get_attr_element(index)} got {season} with: {season.week_name}, expected: {', '.join(map(str, weeks))}")
251
- handle_duplicates("season_profile_name")
252
-
253
- validate_seasons(5)
254
- validate_seasons(9)
1
+ from typing_extensions import deprecated
2
+ from typing import Self
3
+ from .overview import VERSION_0
4
+ from .__class_init__ import *
5
+ from ..types.implementations import integers, octet_string
6
+
7
+
8
+ class Season(cdt.Structure):
9
+ """ Defined by their starting date and a specific week_profile to be executed """
10
+ season_profile_name: cdt.OctetString
11
+ season_start: cst.OctetStringDateTime
12
+ week_name: cdt.OctetString
13
+
14
+
15
+ class SeasonProfile(cdt.Array):
16
+ """ Contains a list of seasons defined by their starting date and a specific week_profile to be executed. The list is sorted according to season_start. """
17
+ TYPE = Season
18
+ values: list[Season]
19
+
20
+ def new_element(self) -> Season:
21
+ names: list[bytes] = [bytes(el.season_profile_name) for el in self.values]
22
+ for new_name in (i.to_bytes(1, 'big') for i in range(256)):
23
+ if new_name not in names:
24
+ return Season((bytearray(new_name), None, bytearray(b'week_name?')))
25
+ raise ValueError(F'in {self} all season names is busy')
26
+
27
+ def sort(self, date_time: cdt.DateTime) -> Self:
28
+ """sort by date-time
29
+ :return now Season + next Seasons"""
30
+ s: Season
31
+ d_t = date_time.to_datetime()
32
+ l = list()
33
+ """left datetime"""
34
+ r = list()
35
+ """right datetime"""
36
+ for i, s in enumerate(self):
37
+ if (el := s.season_start.get_left_nearest_datetime(d_t)) is not None:
38
+ l.append((i, el))
39
+ if (el := s.season_start.get_right_nearest_datetime(d_t)) is not None:
40
+ r.append((i, el))
41
+ l.sort(key=lambda it: it[1])
42
+ r.sort(key=lambda it: it[1])
43
+ now = l[-1][0]
44
+ indexes: list[int] = [now]
45
+ for el in r:
46
+ if el[0] == now:
47
+ continue
48
+ else:
49
+ indexes.append(el[0])
50
+ sorted_data = self.__class__()
51
+ for i in indexes:
52
+ sorted_data.append(self[i])
53
+ return sorted_data
54
+
55
+
56
+ class WeekProfile(cdt.Structure):
57
+ """ For each week_profile, the day_profile for every day of a week is identified. """
58
+ week_profile_name: cdt.OctetString
59
+ monday: cdt.Unsigned
60
+ tuesday: cdt.Unsigned
61
+ wednesday: cdt.Unsigned
62
+ thursday: cdt.Unsigned
63
+ friday: cdt.Unsigned
64
+ saturday: cdt.Unsigned
65
+ sunday: cdt.Unsigned
66
+
67
+
68
+ class WeekProfileTable(cdt.Array):
69
+ """ Contains an array of week_profiles to be used in the different seasons. For each week_profile, the day_profile for every day of a week is
70
+ identified. """
71
+ TYPE = WeekProfile
72
+ values: list[WeekProfile]
73
+
74
+ def new_element(self) -> WeekProfile:
75
+ """return default WeekProfile with vacant week_profile_name, existed day ID and insert callback for validate change DayID"""
76
+ names: list[bytes] = [bytes(el.week_profile_name) for el in self.values]
77
+ for new_name in (i.to_bytes(1, 'big') for i in range(256)):
78
+ if new_name not in names:
79
+ return WeekProfile((bytearray(new_name), *[0]*7))
80
+ raise ValueError(F'in {self} all week names is busy')
81
+
82
+ def get_week_profile_names(self) -> tuple[cdt.OctetString, ...]:
83
+ return tuple((el.week_profile_name for el in self.values))
84
+
85
+
86
+ class DayProfileAction(cdt.Structure):
87
+ """ Scheduled action is defined by a script to be executed and the corresponding activation time (start_time). """
88
+ start_time: cst.OctetStringTime
89
+ script_logical_name: cst.LogicalName
90
+ script_selector: cdt.LongUnsigned
91
+
92
+ def __lt__(self, other: Self):
93
+ return self.start_time.to_time() < other.start_time.to_time()
94
+
95
+
96
+ # TODO: make unique by start_time
97
+ class DaySchedule(cdt.Array):
98
+ """ Contains an array of day_profile_action. """
99
+ TYPE = DayProfileAction
100
+ values: list[DayProfileAction]
101
+
102
+
103
+ class DayProfile(cdt.Structure):
104
+ """ list of Scheduled actions is defined by a script to be executed and the corresponding activation time (start_time) with day ID. """
105
+ day_id: cdt.Unsigned
106
+ day_schedule: DaySchedule
107
+
108
+
109
+ class DayProfileTable(cdt.Array):
110
+ """ Contains an array of day_profiles, identified by their day_id. For each day_profile, a list of scheduled actions is defined by a script to be
111
+ executed and the corresponding activation time (start_time). The list is sorted according to start_time. """
112
+ TYPE = DayProfile
113
+ values: list[DayProfile]
114
+
115
+ def new_element(self) -> DayProfile:
116
+ """return default DayProfile with vacant Day ID"""
117
+ day_ids: list[int] = [int(el.day_id) for el in self.values]
118
+ for i in range(0xff):
119
+ if i not in day_ids:
120
+ return DayProfile((i, None))
121
+ raise ValueError(F'in {self} all days ID is busy')
122
+
123
+ def get_day_ids(self) -> tuple[cdt.Unsigned, ...]:
124
+ return tuple((day_profile.day_id for day_profile in self.values))
125
+
126
+ def normalize(self) -> bool:
127
+ d_p: DayProfile
128
+ res = False
129
+ for d_p in self:
130
+ if (new := DaySchedule(sorted(d_p.day_schedule))) != d_p.day_schedule:
131
+ res = True
132
+ d_p.day_schedule.__dict__["values"] = new.values
133
+ return res
134
+
135
+
136
+ class ActivityCalendar(ic.COSEMInterfaceClasses):
137
+ """DLMS UA 1000-1 Ed. 14 4.5.5 Activity calendar"""
138
+ CLASS_ID = ClassID.ACTIVITY_CALENDAR
139
+ VERSION = VERSION_0
140
+ A_ELEMENTS = (ic.ICAElement("calendar_name_active", octet_string.ID),
141
+ ic.ICAElement("season_profile_active", SeasonProfile),
142
+ ic.ICAElement("week_profile_table_active", WeekProfileTable),
143
+ ic.ICAElement("day_profile_table_active", DayProfileTable),
144
+ ic.ICAElement("calendar_name_passive", octet_string.ID),
145
+ ic.ICAElement("season_profile_passive", SeasonProfile),
146
+ ic.ICAElement("week_profile_table_passive", WeekProfileTable),
147
+ ic.ICAElement("day_profile_table_passive", DayProfileTable),
148
+ ic.ICAElement("activate_passive_calendar_time", cst.OctetStringDateTime))
149
+ M_ELEMENTS = ic.ICMElement(
150
+ NAME="activate_passive_calendar",
151
+ DATA_TYPE=integers.Only0),
152
+
153
+ def characteristics_init(self):
154
+ # Attributes called …_active are currently active, attributes called …_passive will be activated by the specific
155
+ # method activate_passive_calendar.
156
+ self.set_attr(ai.DAY_PROFILE_TABLE_ACTIVE, None)
157
+ self.set_attr(ai.WEEK_PROFILE_TABLE_ACTIVE, None)
158
+ self.set_attr(ai.SEASON_PROFILE_ACTIVE, None)
159
+ self.set_attr(ai.DAY_PROFILE_TABLE_PASSIVE, None)
160
+ self.set_attr(ai.WEEK_PROFILE_TABLE_PASSIVE, None)
161
+ self.set_attr(ai.SEASON_PROFILE_PASSIVE, None)
162
+
163
+ @property
164
+ def calendar_name_active(self) -> octet_string.ID:
165
+ return self.get_attr(2)
166
+
167
+ @property
168
+ def season_profile_active(self) -> SeasonProfile:
169
+ return self.get_attr(3)
170
+
171
+ @property
172
+ def week_profile_table_active(self) -> WeekProfileTable:
173
+ return self.get_attr(4)
174
+
175
+ @property
176
+ def day_profile_table_active(self) -> DayProfileTable:
177
+ return self.get_attr(5)
178
+
179
+ @property
180
+ def calendar_name_passive(self) -> octet_string.ID:
181
+ return self.get_attr(6)
182
+
183
+ @property
184
+ def season_profile_passive(self) -> SeasonProfile:
185
+ return self.get_attr(7)
186
+
187
+ @property
188
+ def week_profile_table_passive(self) -> WeekProfileTable:
189
+ return self.get_attr(8)
190
+
191
+ @property
192
+ def day_profile_table_passive(self) -> DayProfileTable:
193
+ return self.get_attr(9)
194
+
195
+ @property
196
+ def activate_passive_calendar_time(self) -> cst.OctetStringDateTime:
197
+ return self.get_attr(10)
198
+
199
+ @property
200
+ @deprecated("use ActivatePassiveCalendar")
201
+ def activate_passive_calendar(self) -> integers.Only0:
202
+ return self.get_meth(1)
203
+
204
+ @classmethod
205
+ def ActivatePassiveCalendar(cls, value=None) -> integers.Only0:
206
+ return cls.M_ELEMENTS[0].DATA_TYPE
207
+
208
+ def validate(self):
209
+ def validate_seasons(index: int):
210
+ def handle_duplicates(name: str):
211
+ nonlocal index, duplicates
212
+ if len(duplicates) != 0:
213
+ raise ic.ObjectValidationError(
214
+ ln=self.logical_name,
215
+ i=index,
216
+ message=F"find duplicate {name}: {', '.join(map(str, duplicates))} in {self.get_attr_element(index)}")
217
+ index -= 1
218
+
219
+ days: list[DayProfile.day_id] = list()
220
+ duplicates: set[DayProfile.day_id] | set[WeekProfile.week_profile_name] | set[Season.season_profile_name] = set()
221
+ for it in self.get_attr(index):
222
+ if it.day_id not in days:
223
+ days.append(it.day_id)
224
+ else:
225
+ duplicates.add(it.day_id)
226
+ handle_duplicates("day_id")
227
+ weeks: list[WeekProfile.week_profile_name] = list()
228
+ for week_profile in self.get_attr(index):
229
+ if week_profile.week_profile_name not in weeks:
230
+ weeks.append(week_profile.week_profile_name)
231
+ else:
232
+ duplicates.add(week_profile.week_profile_name)
233
+ for i in range(1, 7):
234
+ if week_profile[i] not in days:
235
+ raise ic.ObjectValidationError(
236
+ ln=self.logical_name,
237
+ i=index,
238
+ message=F"in {self.get_attr_element(index)} got {week_profile} with day_id: {week_profile[i]}; expected: {', '.join(map(str, days))}")
239
+ handle_duplicates("week_profile_name")
240
+ seasons: list[Season.season_profile_name] = list()
241
+ for season in self.get_attr(index):
242
+ if season.season_profile_name not in seasons:
243
+ seasons.append(season.season_profile_name)
244
+ else:
245
+ duplicates.add(season.season_profile_name)
246
+ if season.week_name not in weeks:
247
+ raise ic.ObjectValidationError(
248
+ ln=self.logical_name,
249
+ i=index,
250
+ message=F"in {self.get_attr_element(index)} got {season} with: {season.week_name}, expected: {', '.join(map(str, weeks))}")
251
+ handle_duplicates("season_profile_name")
252
+
253
+ validate_seasons(5)
254
+ validate_seasons(9)