DLMS-SPODES 0.87.12__py3-none-any.whl → 0.87.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- DLMS_SPODES/Values/EN/__init__.py +1 -1
- DLMS_SPODES/Values/EN/actors.py +8 -8
- DLMS_SPODES/Values/EN/relation_to_obis_names.py +387 -387
- DLMS_SPODES/Values/RU/__init__.py +1 -1
- DLMS_SPODES/Values/RU/actors.py +8 -8
- DLMS_SPODES/Values/RU/relation_to_obis_names.py +396 -396
- DLMS_SPODES/__init__.py +6 -6
- DLMS_SPODES/configEN.ini +126 -126
- DLMS_SPODES/config_parser.py +53 -53
- DLMS_SPODES/cosem_interface_classes/__class_init__.py +3 -3
- DLMS_SPODES/cosem_interface_classes/__init__.py +1 -1
- DLMS_SPODES/cosem_interface_classes/a_parameter.py +20 -20
- DLMS_SPODES/cosem_interface_classes/activity_calendar.py +254 -254
- DLMS_SPODES/cosem_interface_classes/arbitrator.py +105 -105
- DLMS_SPODES/cosem_interface_classes/association_ln/abstract.py +34 -34
- DLMS_SPODES/cosem_interface_classes/association_ln/authentication_mechanism_name.py +25 -25
- DLMS_SPODES/cosem_interface_classes/association_ln/mechanism_id.py +25 -25
- DLMS_SPODES/cosem_interface_classes/association_ln/method.py +5 -5
- DLMS_SPODES/cosem_interface_classes/association_ln/ver0.py +485 -485
- DLMS_SPODES/cosem_interface_classes/association_ln/ver1.py +133 -133
- DLMS_SPODES/cosem_interface_classes/association_ln/ver2.py +36 -36
- DLMS_SPODES/cosem_interface_classes/association_ln/ver3.py +4 -4
- DLMS_SPODES/cosem_interface_classes/association_sn/ver0.py +12 -12
- DLMS_SPODES/cosem_interface_classes/attr_indexes.py +12 -12
- DLMS_SPODES/cosem_interface_classes/clock.py +131 -131
- DLMS_SPODES/cosem_interface_classes/collection.py +2122 -2122
- DLMS_SPODES/cosem_interface_classes/cosem_interface_class.py +583 -583
- DLMS_SPODES/cosem_interface_classes/data.py +21 -21
- DLMS_SPODES/cosem_interface_classes/demand_register/ver0.py +59 -59
- DLMS_SPODES/cosem_interface_classes/disconnect_control.py +74 -74
- DLMS_SPODES/cosem_interface_classes/extended_register.py +27 -27
- DLMS_SPODES/cosem_interface_classes/gprs_modem_setup.py +43 -43
- DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver0.py +103 -103
- DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver1.py +40 -40
- DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver2.py +9 -9
- DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver0.py +11 -11
- DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver1.py +53 -53
- DLMS_SPODES/cosem_interface_classes/iec_local_port_setup.py +11 -11
- DLMS_SPODES/cosem_interface_classes/image_transfer/image_transfer_status.py +15 -15
- DLMS_SPODES/cosem_interface_classes/image_transfer/ver0.py +126 -126
- DLMS_SPODES/cosem_interface_classes/implementations/__init__.py +3 -3
- DLMS_SPODES/cosem_interface_classes/implementations/arbitrator.py +19 -19
- DLMS_SPODES/cosem_interface_classes/implementations/data.py +487 -487
- DLMS_SPODES/cosem_interface_classes/implementations/profile_generic.py +83 -83
- DLMS_SPODES/cosem_interface_classes/ipv4_setup.py +72 -72
- DLMS_SPODES/cosem_interface_classes/limiter.py +111 -111
- DLMS_SPODES/cosem_interface_classes/ln_pattern.py +333 -333
- DLMS_SPODES/cosem_interface_classes/modem_configuration/ver0.py +65 -65
- DLMS_SPODES/cosem_interface_classes/modem_configuration/ver1.py +39 -39
- DLMS_SPODES/cosem_interface_classes/ntp_setup/ver0.py +67 -67
- DLMS_SPODES/cosem_interface_classes/obis.py +23 -23
- DLMS_SPODES/cosem_interface_classes/overview.py +197 -197
- DLMS_SPODES/cosem_interface_classes/parameter.py +547 -547
- DLMS_SPODES/cosem_interface_classes/parameters.py +172 -172
- DLMS_SPODES/cosem_interface_classes/profile_generic/ver0.py +122 -122
- DLMS_SPODES/cosem_interface_classes/profile_generic/ver1.py +277 -277
- DLMS_SPODES/cosem_interface_classes/push_setup/ver0.py +12 -12
- DLMS_SPODES/cosem_interface_classes/push_setup/ver1.py +10 -10
- DLMS_SPODES/cosem_interface_classes/push_setup/ver2.py +166 -166
- DLMS_SPODES/cosem_interface_classes/register.py +45 -45
- DLMS_SPODES/cosem_interface_classes/register_activation/ver0.py +80 -80
- DLMS_SPODES/cosem_interface_classes/register_monitor.py +46 -46
- DLMS_SPODES/cosem_interface_classes/reports.py +70 -70
- DLMS_SPODES/cosem_interface_classes/schedule.py +176 -176
- DLMS_SPODES/cosem_interface_classes/script_table.py +87 -87
- DLMS_SPODES/cosem_interface_classes/security_setup/ver0.py +68 -68
- DLMS_SPODES/cosem_interface_classes/security_setup/ver1.py +158 -158
- DLMS_SPODES/cosem_interface_classes/single_action_schedule.py +50 -50
- DLMS_SPODES/cosem_interface_classes/special_days_table.py +84 -84
- DLMS_SPODES/cosem_interface_classes/tcp_udp_setup.py +42 -42
- DLMS_SPODES/cosem_pdu.py +93 -93
- DLMS_SPODES/enums.py +625 -625
- DLMS_SPODES/exceptions.py +106 -106
- DLMS_SPODES/firmwares.py +99 -99
- DLMS_SPODES/hdlc/frame.py +875 -875
- DLMS_SPODES/hdlc/sub_layer.py +54 -54
- DLMS_SPODES/literals.py +17 -17
- DLMS_SPODES/obis/__init__.py +1 -1
- DLMS_SPODES/obis/media_id.py +931 -931
- DLMS_SPODES/pardata.py +22 -22
- DLMS_SPODES/pdu_enums.py +98 -98
- DLMS_SPODES/relation_to_OBIS.py +465 -463
- DLMS_SPODES/settings.py +551 -551
- DLMS_SPODES/types/choices.py +142 -142
- DLMS_SPODES/types/common_data_types.py +2401 -2399
- DLMS_SPODES/types/cosem_service_types.py +109 -109
- DLMS_SPODES/types/implementations/arrays.py +25 -25
- DLMS_SPODES/types/implementations/bitstrings.py +97 -97
- DLMS_SPODES/types/implementations/double_long_usingneds.py +35 -35
- DLMS_SPODES/types/implementations/enums.py +57 -57
- DLMS_SPODES/types/implementations/integers.py +11 -11
- DLMS_SPODES/types/implementations/long_unsigneds.py +127 -127
- DLMS_SPODES/types/implementations/octet_string.py +11 -11
- DLMS_SPODES/types/implementations/structs.py +64 -64
- DLMS_SPODES/types/useful_types.py +677 -677
- {dlms_spodes-0.87.12.dist-info → dlms_spodes-0.87.15.dist-info}/METADATA +30 -30
- dlms_spodes-0.87.15.dist-info/RECORD +117 -0
- {dlms_spodes-0.87.12.dist-info → dlms_spodes-0.87.15.dist-info}/WHEEL +1 -1
- dlms_spodes-0.87.12.dist-info/RECORD +0 -117
- {dlms_spodes-0.87.12.dist-info → dlms_spodes-0.87.15.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)
|