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.
- 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 -2401
- DLMS_SPODES/types/cosem_service_types.py +109 -109
- DLMS_SPODES/types/implementations/arrays.py +25 -25
- DLMS_SPODES/types/implementations/bitstrings.py +97 -97
- DLMS_SPODES/types/implementations/double_long_usingneds.py +35 -35
- DLMS_SPODES/types/implementations/enums.py +57 -57
- DLMS_SPODES/types/implementations/integers.py +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.13.dist-info → dlms_spodes-0.87.16.dist-info}/METADATA +30 -30
- dlms_spodes-0.87.16.dist-info/RECORD +117 -0
- {dlms_spodes-0.87.13.dist-info → dlms_spodes-0.87.16.dist-info}/WHEEL +1 -1
- dlms_spodes-0.87.13.dist-info/RECORD +0 -117
- {dlms_spodes-0.87.13.dist-info → dlms_spodes-0.87.16.dist-info}/top_level.txt +0 -0
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
from .__class_init__ import *
|
|
2
|
-
from ..types import choices
|
|
3
|
-
from ..types.implementations import structs
|
|
4
|
-
from .overview import VERSION_0
|
|
5
|
-
threshold_scaler_unit = cdt.ScalUnitType(b'\x02\x02\x0f\x00\x16\x07')
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Thresholds(cdt.Array):
|
|
9
|
-
TYPE = choices.simple_dt
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ActionSet(cdt.Structure):
|
|
13
|
-
"""TODO:"""
|
|
14
|
-
action_up: structs.ActionItem
|
|
15
|
-
action_down: structs.ActionItem
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Actions(cdt.Array):
|
|
19
|
-
"""Defines the scripts to be executed when the monitored attribute of the referenced object crosses the corresponding threshold. The attribute actions has exactly
|
|
20
|
-
the same number of elements as the attribute thresholds. The ordering of the action_items corresponds to the ordering of the thresholds (see above)."""
|
|
21
|
-
TYPE = ActionSet
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class RegisterMonitor(ic.COSEMInterfaceClasses):
|
|
25
|
-
""" DLMS UA 1000-1 Ed.14. 4.5.6. This IC allows modelling the function of monitoring of values modelled by “Data”, “Register”, “Extended register” or “Demand register” objects.
|
|
26
|
-
It allows specifying thresholds, the value monitored, and a set of scripts (see 4.5.2) that are executed when the value monitored crosses a threshold """
|
|
27
|
-
CLASS_ID = ClassID.REGISTER_MONITOR
|
|
28
|
-
VERSION = VERSION_0
|
|
29
|
-
A_ELEMENTS = (ic.ICAElement("thresholds", Thresholds),
|
|
30
|
-
ic.ICAElement("monitored_value", structs.ValueDefinition),
|
|
31
|
-
ic.ICAElement("actions", Actions))
|
|
32
|
-
|
|
33
|
-
def characteristics_init(self):
|
|
34
|
-
...
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def thresholds(self) -> Thresholds:
|
|
38
|
-
return self.get_attr(2)
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def monitored_value(self) -> structs.ValueDefinition:
|
|
42
|
-
return self.get_attr(3)
|
|
43
|
-
|
|
44
|
-
@property
|
|
45
|
-
def threshold_normal(self) -> Actions:
|
|
46
|
-
return self.get_attr(4)
|
|
1
|
+
from .__class_init__ import *
|
|
2
|
+
from ..types import choices
|
|
3
|
+
from ..types.implementations import structs
|
|
4
|
+
from .overview import VERSION_0
|
|
5
|
+
threshold_scaler_unit = cdt.ScalUnitType(b'\x02\x02\x0f\x00\x16\x07')
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Thresholds(cdt.Array):
|
|
9
|
+
TYPE = choices.simple_dt
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ActionSet(cdt.Structure):
|
|
13
|
+
"""TODO:"""
|
|
14
|
+
action_up: structs.ActionItem
|
|
15
|
+
action_down: structs.ActionItem
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Actions(cdt.Array):
|
|
19
|
+
"""Defines the scripts to be executed when the monitored attribute of the referenced object crosses the corresponding threshold. The attribute actions has exactly
|
|
20
|
+
the same number of elements as the attribute thresholds. The ordering of the action_items corresponds to the ordering of the thresholds (see above)."""
|
|
21
|
+
TYPE = ActionSet
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RegisterMonitor(ic.COSEMInterfaceClasses):
|
|
25
|
+
""" DLMS UA 1000-1 Ed.14. 4.5.6. This IC allows modelling the function of monitoring of values modelled by “Data”, “Register”, “Extended register” or “Demand register” objects.
|
|
26
|
+
It allows specifying thresholds, the value monitored, and a set of scripts (see 4.5.2) that are executed when the value monitored crosses a threshold """
|
|
27
|
+
CLASS_ID = ClassID.REGISTER_MONITOR
|
|
28
|
+
VERSION = VERSION_0
|
|
29
|
+
A_ELEMENTS = (ic.ICAElement("thresholds", Thresholds),
|
|
30
|
+
ic.ICAElement("monitored_value", structs.ValueDefinition),
|
|
31
|
+
ic.ICAElement("actions", Actions))
|
|
32
|
+
|
|
33
|
+
def characteristics_init(self):
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def thresholds(self) -> Thresholds:
|
|
38
|
+
return self.get_attr(2)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def monitored_value(self) -> structs.ValueDefinition:
|
|
42
|
+
return self.get_attr(3)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def threshold_normal(self) -> Actions:
|
|
46
|
+
return self.get_attr(4)
|
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
from typing import Any, Iterable
|
|
2
|
-
from StructResult import result
|
|
3
|
-
from . import cosem_interface_class as ic
|
|
4
|
-
from ..types import cdt
|
|
5
|
-
from .parameter import Parameter
|
|
6
|
-
from ..cosem_interface_classes import collection
|
|
7
|
-
from ..config_parser import get_values
|
|
8
|
-
from ..settings import settings
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def from_obj(
|
|
12
|
-
col: collection.Collection,
|
|
13
|
-
obj: ic.COSEMInterfaceClasses,
|
|
14
|
-
attr_index_par: tuple[int, ...]
|
|
15
|
-
) -> result.SimpleOrError[str]:
|
|
16
|
-
if not hasattr(from_obj, "struct_pattern"):
|
|
17
|
-
from_obj.struct_pattern = dict(settings.report.struct)
|
|
18
|
-
ret: str = F"[{collection.get_name(obj.logical_name)}]\n"
|
|
19
|
-
for i in attr_index_par:
|
|
20
|
-
par = Parameter(obj.logical_name.contents).set_i(i)
|
|
21
|
-
if isinstance(res_data := col.par2data(par), result.Error):
|
|
22
|
-
return res_data
|
|
23
|
-
a_data = res_data.value
|
|
24
|
-
if isinstance(a_data, cdt.SimpleDataType):
|
|
25
|
-
rep = col.par2rep(par, a_data)
|
|
26
|
-
ret += F" {obj.get_attr_element(i)}: {rep.msg}{f" {rep.unit}" if rep.unit else ""}\n"
|
|
27
|
-
elif isinstance(a_data, cdt.ComplexDataType):
|
|
28
|
-
ret += F" [{obj.get_attr_element(i)}]\n"
|
|
29
|
-
stack: list[tuple[Any, Any]] = [("", iter(a_data))]
|
|
30
|
-
while stack:
|
|
31
|
-
name, value_it = stack[-1]
|
|
32
|
-
indent = F"{' ' * (len(stack) + 1)}"
|
|
33
|
-
data = next(value_it, None)
|
|
34
|
-
if data:
|
|
35
|
-
if not isinstance(name, str):
|
|
36
|
-
name = str(next(name))
|
|
37
|
-
if isinstance(data, cdt.Array):
|
|
38
|
-
ret += F"{indent}[{name}]\n"
|
|
39
|
-
stack.append(("*", iter(data)))
|
|
40
|
-
elif isinstance(data, cdt.Structure):
|
|
41
|
-
if (pattern := from_obj.struct_pattern.get(data.__class__.__name__)):
|
|
42
|
-
val = list(pattern)
|
|
43
|
-
val.reverse()
|
|
44
|
-
result_ = str()
|
|
45
|
-
while val:
|
|
46
|
-
match val.pop():
|
|
47
|
-
case "%":
|
|
48
|
-
par_ = val.pop()
|
|
49
|
-
index = int(val.pop() + val.pop())
|
|
50
|
-
match par_:
|
|
51
|
-
case "n":
|
|
52
|
-
result_ += str(data.ELEMENTS[index])
|
|
53
|
-
case "v":
|
|
54
|
-
result_ += str(data[index])
|
|
55
|
-
case err:
|
|
56
|
-
raise ValueError(F"unknown macros &{err}{index}")
|
|
57
|
-
case symbol:
|
|
58
|
-
result_ += symbol
|
|
59
|
-
ret += F"{indent}{result_}\n"
|
|
60
|
-
else:
|
|
61
|
-
if name=="":
|
|
62
|
-
ret += "\n"
|
|
63
|
-
else:
|
|
64
|
-
ret += F"{indent}[{name}]\n"
|
|
65
|
-
stack.append((iter(data.ELEMENTS), iter(data)))
|
|
66
|
-
else:
|
|
67
|
-
ret += F"{indent}{name}: {data}\n"
|
|
68
|
-
else:
|
|
69
|
-
stack.pop()
|
|
70
|
-
return result.Simple(ret)
|
|
1
|
+
from typing import Any, Iterable
|
|
2
|
+
from StructResult import result
|
|
3
|
+
from . import cosem_interface_class as ic
|
|
4
|
+
from ..types import cdt
|
|
5
|
+
from .parameter import Parameter
|
|
6
|
+
from ..cosem_interface_classes import collection
|
|
7
|
+
from ..config_parser import get_values
|
|
8
|
+
from ..settings import settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def from_obj(
|
|
12
|
+
col: collection.Collection,
|
|
13
|
+
obj: ic.COSEMInterfaceClasses,
|
|
14
|
+
attr_index_par: tuple[int, ...]
|
|
15
|
+
) -> result.SimpleOrError[str]:
|
|
16
|
+
if not hasattr(from_obj, "struct_pattern"):
|
|
17
|
+
from_obj.struct_pattern = dict(settings.report.struct)
|
|
18
|
+
ret: str = F"[{collection.get_name(obj.logical_name)}]\n"
|
|
19
|
+
for i in attr_index_par:
|
|
20
|
+
par = Parameter(obj.logical_name.contents).set_i(i)
|
|
21
|
+
if isinstance(res_data := col.par2data(par), result.Error):
|
|
22
|
+
return res_data
|
|
23
|
+
a_data = res_data.value
|
|
24
|
+
if isinstance(a_data, cdt.SimpleDataType):
|
|
25
|
+
rep = col.par2rep(par, a_data)
|
|
26
|
+
ret += F" {obj.get_attr_element(i)}: {rep.msg}{f" {rep.unit}" if rep.unit else ""}\n"
|
|
27
|
+
elif isinstance(a_data, cdt.ComplexDataType):
|
|
28
|
+
ret += F" [{obj.get_attr_element(i)}]\n"
|
|
29
|
+
stack: list[tuple[Any, Any]] = [("", iter(a_data))]
|
|
30
|
+
while stack:
|
|
31
|
+
name, value_it = stack[-1]
|
|
32
|
+
indent = F"{' ' * (len(stack) + 1)}"
|
|
33
|
+
data = next(value_it, None)
|
|
34
|
+
if data:
|
|
35
|
+
if not isinstance(name, str):
|
|
36
|
+
name = str(next(name))
|
|
37
|
+
if isinstance(data, cdt.Array):
|
|
38
|
+
ret += F"{indent}[{name}]\n"
|
|
39
|
+
stack.append(("*", iter(data)))
|
|
40
|
+
elif isinstance(data, cdt.Structure):
|
|
41
|
+
if (pattern := from_obj.struct_pattern.get(data.__class__.__name__)):
|
|
42
|
+
val = list(pattern)
|
|
43
|
+
val.reverse()
|
|
44
|
+
result_ = str()
|
|
45
|
+
while val:
|
|
46
|
+
match val.pop():
|
|
47
|
+
case "%":
|
|
48
|
+
par_ = val.pop()
|
|
49
|
+
index = int(val.pop() + val.pop())
|
|
50
|
+
match par_:
|
|
51
|
+
case "n":
|
|
52
|
+
result_ += str(data.ELEMENTS[index])
|
|
53
|
+
case "v":
|
|
54
|
+
result_ += str(data[index])
|
|
55
|
+
case err:
|
|
56
|
+
raise ValueError(F"unknown macros &{err}{index}")
|
|
57
|
+
case symbol:
|
|
58
|
+
result_ += symbol
|
|
59
|
+
ret += F"{indent}{result_}\n"
|
|
60
|
+
else:
|
|
61
|
+
if name=="":
|
|
62
|
+
ret += "\n"
|
|
63
|
+
else:
|
|
64
|
+
ret += F"{indent}[{name}]\n"
|
|
65
|
+
stack.append((iter(data.ELEMENTS), iter(data)))
|
|
66
|
+
else:
|
|
67
|
+
ret += F"{indent}{name}: {data}\n"
|
|
68
|
+
else:
|
|
69
|
+
stack.pop()
|
|
70
|
+
return result.Simple(ret)
|
|
@@ -1,176 +1,176 @@
|
|
|
1
|
-
from typing import Callable, Self
|
|
2
|
-
from itertools import count
|
|
3
|
-
from .__class_init__ import *
|
|
4
|
-
from .overview import VERSION_0
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Index(cdt.LongUnsigned, min=1, max=9999):
|
|
8
|
-
""" LongUnsigned type with validation """
|
|
9
|
-
__cb_get_indexes: Callable
|
|
10
|
-
DEFAULT = 1
|
|
11
|
-
|
|
12
|
-
def set_callback(self, cb: Callable):
|
|
13
|
-
self.__cb_get_indexes = cb
|
|
14
|
-
|
|
15
|
-
def get_indexes(self) -> list[int]:
|
|
16
|
-
""" return indexes container """
|
|
17
|
-
return self.__cb_get_indexes()
|
|
18
|
-
|
|
19
|
-
def check(self, string: str):
|
|
20
|
-
""" raise ValueError with message if string not is valid """
|
|
21
|
-
instance = type(self)(value=string)
|
|
22
|
-
if int(instance) in self.__cb_get_indexes():
|
|
23
|
-
raise ValueError('New index not unique')
|
|
24
|
-
|
|
25
|
-
@classmethod
|
|
26
|
-
def with_cb(cls, value, cb: Callable) -> Self:
|
|
27
|
-
""" get instance with callback """
|
|
28
|
-
ret = cls(value)
|
|
29
|
-
ret.set_callback(cb)
|
|
30
|
-
return ret
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class ScheduleTableEntry(cdt.Structure):
|
|
34
|
-
""" Specifies the scripts to be executed at given times. There is only one script that can be executed per entry. """
|
|
35
|
-
index: Index
|
|
36
|
-
enable: cdt.Boolean
|
|
37
|
-
script_logical_name: cst.LogicalName
|
|
38
|
-
script_selector: cdt.LongUnsigned
|
|
39
|
-
switch_time: cst.OctetStringTime
|
|
40
|
-
validity_window: cdt.LongUnsigned
|
|
41
|
-
exec_weekdays: cdt.BitString
|
|
42
|
-
exec_specdays: cdt.BitString
|
|
43
|
-
begin_date: cst.OctetStringDate
|
|
44
|
-
end_date: cst.OctetStringDate
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# TODO: rewrite to new API
|
|
48
|
-
class Entries(cdt.Array):
|
|
49
|
-
""" Specifies the list of schedule_table_entry. Todo: validate by unique index"""
|
|
50
|
-
TYPE = ScheduleTableEntry
|
|
51
|
-
unique = True
|
|
52
|
-
|
|
53
|
-
def __init__(self, value: bytes = None):
|
|
54
|
-
super(Entries, self).__init__(value)
|
|
55
|
-
# setting callback for validate schedule_table_entry index
|
|
56
|
-
for schedule_table_entry in self:
|
|
57
|
-
schedule_table_entry: ScheduleTableEntry
|
|
58
|
-
schedule_table_entry.index.set_callback(self.get_indexes)
|
|
59
|
-
|
|
60
|
-
def get_indexes(self) -> list[int]:
|
|
61
|
-
""" getter for callback Index """
|
|
62
|
-
return [entries_element.index.decode() for entries_element in self]
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class DataED(cdt.Structure):
|
|
66
|
-
""" Sets the disabled bit of range A entries to true and then enables the entries of range B.
|
|
67
|
-
* firstIndexA/B < lastIndexA/B: all entries of the range A/B are disabled/enabled
|
|
68
|
-
* firstIndexA/B == lastIndexA/B: one entry is disabled/enabled,
|
|
69
|
-
* firstIndexA/B > lastIndexA/B: nothing disabled/enabled,
|
|
70
|
-
* firstIndexA/B and lastIndexA/B > 9999: no entry is disabled/enabled """
|
|
71
|
-
firstIndexA: Index
|
|
72
|
-
lastIndexA: Index
|
|
73
|
-
firstIndexB: Index
|
|
74
|
-
lastIndexB: Index
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class DataDelete(cdt.Structure):
|
|
78
|
-
""" Deletes a range of entries in the table.
|
|
79
|
-
* firstIndex < lastIndex: all entries of the range A/B are deleted,
|
|
80
|
-
* firstIndex ::= lastIndex: one entry is deleted,
|
|
81
|
-
* firstIndex > lastIndex: nothing deleted """
|
|
82
|
-
firstIndex: Index
|
|
83
|
-
lastIndex: Index
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class Schedule(ic.COSEMInterfaceClasses):
|
|
87
|
-
""" The IC “Schedule” together with an object of the IC “Special days” table handles time and date driven activities within a device.
|
|
88
|
-
The following picture gives an overview and shows the interactions between them:
|
|
89
|
-
Schedule:
|
|
90
|
-
|
|
91
|
-
Index | enable | action | Switch_time | validity_window | exec_weekdays | exec_specdays | date range
|
|
92
|
-
| |(script)| | | Mo Tu We Th Fr Sa Su | S1 S2 ... S8 S9 | begin_date | end_date
|
|
93
|
-
120 Yes xxxx:yy 06:00 0xFFFF x x x x x x xx-04-01 xx-09-30
|
|
94
|
-
121 Yes xxxx:yy 22:00 15 x x x x x xx-04-01 xx-09-30
|
|
95
|
-
122 Yes xxxx:yy 12:00 0 x xx-04-01 xx-09-30
|
|
96
|
-
200 No xxxx:yy 06:30 x x x x x x xx-04-01 xx-09-30
|
|
97
|
-
201 No xxxx:yy 21:30 x x x x x xx-04-01 xx-09-30
|
|
98
|
-
202 No xxxx:yy 11:00 x xx-04-01 xx-09-30
|
|
99
|
-
|
|
100
|
-
Special days table:
|
|
101
|
-
|
|
102
|
-
Index | special_day_date | day_id
|
|
103
|
-
12 xx-12-24 S1
|
|
104
|
-
33 xx-12-25 S3
|
|
105
|
-
77 97-03-31 S3
|
|
106
|
-
|
|
107
|
-
Recovery after power failure
|
|
108
|
-
After a power failure, the whole schedule is processed to execute all the necessary scripts that would get lost during a power failure. For this,
|
|
109
|
-
the entries that were not executed during the power failure must be detected. Depending on the validity window attribute they are executed in
|
|
110
|
-
the correct order (as they would have been executed in normal operation).
|
|
111
|
-
|
|
112
|
-
Handling of time changes
|
|
113
|
-
There are four different "actions" of time changes:
|
|
114
|
-
a) time setting forward; b) time setting backwards; c) time synchronization; d) daylight saving action.
|
|
115
|
-
All these four actions need a different handling executed by the schedule in interaction with the time setting activity.
|
|
116
|
-
|
|
117
|
-
Time setting forward*
|
|
118
|
-
This is handled the same way as a power failure. All entries missed are executed depending on the validity window attribute.
|
|
119
|
-
A (manufacturer specific defined) short time setting can be handled like time synchronization.
|
|
120
|
-
* Writing to the attribute “time” of the “Clock” object.
|
|
121
|
-
|
|
122
|
-
Time setting backward*
|
|
123
|
-
This results in a repetition of those entries that are activated during the repeated time. A (manufacturer specific defined) short time setting
|
|
124
|
-
can be handled like time synchronization.
|
|
125
|
-
* Writing to the attribute “time” of the “Clock” object.
|
|
126
|
-
|
|
127
|
-
Time synchronization*
|
|
128
|
-
Time synchronization is used to correct small deviations between a master clock and the local clock. The algorithm is manufacturer specific.
|
|
129
|
-
It shall guarantee that no entry of the schedule gets lost, or is executed twice. The validity window attribute has no effect, because all
|
|
130
|
-
entries must be executed in normal operation.
|
|
131
|
-
* Using the method “adjust_to_quarter” of the “Clock” object.
|
|
132
|
-
|
|
133
|
-
Daylight saving
|
|
134
|
-
If the clock is put forward, then all scripts, which fall into the forwarding interval (and would therefore get lost) are executed.
|
|
135
|
-
If the clock is put back, re-execution of the scripts, which fall into the backwarding interval is suppressed. """
|
|
136
|
-
CLASS_ID = ClassID.SCHEDULE
|
|
137
|
-
VERSION = VERSION_0
|
|
138
|
-
A_ELEMENTS = ic.ICAElement("entries", Entries),
|
|
139
|
-
M_ELEMENTS = (ic.ICMElement("enable_disable", DataED),
|
|
140
|
-
ic.ICMElement("insert", ScheduleTableEntry),
|
|
141
|
-
ic.ICMElement("delete", DataDelete))
|
|
142
|
-
|
|
143
|
-
def characteristics_init(self):
|
|
144
|
-
self.set_attr(2, None)
|
|
145
|
-
self._cbs_attr_post_init.update({2: self.__set_index_cbs})
|
|
146
|
-
|
|
147
|
-
@property
|
|
148
|
-
def entries(self) -> Entries:
|
|
149
|
-
return self.get_attr(2)
|
|
150
|
-
|
|
151
|
-
@property
|
|
152
|
-
def enable_disable(self) -> DataED:
|
|
153
|
-
return self.get_meth(1)
|
|
154
|
-
|
|
155
|
-
@property
|
|
156
|
-
def insert(self) -> ScheduleTableEntry:
|
|
157
|
-
return self.get_meth(2)
|
|
158
|
-
|
|
159
|
-
@property
|
|
160
|
-
def delete(self) -> DataDelete:
|
|
161
|
-
return self.get_meth(3)
|
|
162
|
-
|
|
163
|
-
def __set_index_cbs(self):
|
|
164
|
-
""" set callbacks to methods """
|
|
165
|
-
try:
|
|
166
|
-
indexes: Callable = self.entries.get_indexes
|
|
167
|
-
self.enable_disable.firstIndexA.set_callback(indexes)
|
|
168
|
-
self.enable_disable.firstIndexB.set_callback(indexes)
|
|
169
|
-
self.enable_disable.lastIndexA.set_callback(indexes)
|
|
170
|
-
self.enable_disable.lastIndexB.set_callback(indexes)
|
|
171
|
-
self.insert.index.set_callback(indexes)
|
|
172
|
-
self.delete.firstIndex.set_callback(indexes)
|
|
173
|
-
self.delete.lastIndex.set_callback(indexes)
|
|
174
|
-
# print('set delete')
|
|
175
|
-
except KeyError: # At init time
|
|
176
|
-
print('set delete NO:')
|
|
1
|
+
from typing import Callable, Self
|
|
2
|
+
from itertools import count
|
|
3
|
+
from .__class_init__ import *
|
|
4
|
+
from .overview import VERSION_0
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Index(cdt.LongUnsigned, min=1, max=9999):
|
|
8
|
+
""" LongUnsigned type with validation """
|
|
9
|
+
__cb_get_indexes: Callable
|
|
10
|
+
DEFAULT = 1
|
|
11
|
+
|
|
12
|
+
def set_callback(self, cb: Callable):
|
|
13
|
+
self.__cb_get_indexes = cb
|
|
14
|
+
|
|
15
|
+
def get_indexes(self) -> list[int]:
|
|
16
|
+
""" return indexes container """
|
|
17
|
+
return self.__cb_get_indexes()
|
|
18
|
+
|
|
19
|
+
def check(self, string: str):
|
|
20
|
+
""" raise ValueError with message if string not is valid """
|
|
21
|
+
instance = type(self)(value=string)
|
|
22
|
+
if int(instance) in self.__cb_get_indexes():
|
|
23
|
+
raise ValueError('New index not unique')
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def with_cb(cls, value, cb: Callable) -> Self:
|
|
27
|
+
""" get instance with callback """
|
|
28
|
+
ret = cls(value)
|
|
29
|
+
ret.set_callback(cb)
|
|
30
|
+
return ret
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ScheduleTableEntry(cdt.Structure):
|
|
34
|
+
""" Specifies the scripts to be executed at given times. There is only one script that can be executed per entry. """
|
|
35
|
+
index: Index
|
|
36
|
+
enable: cdt.Boolean
|
|
37
|
+
script_logical_name: cst.LogicalName
|
|
38
|
+
script_selector: cdt.LongUnsigned
|
|
39
|
+
switch_time: cst.OctetStringTime
|
|
40
|
+
validity_window: cdt.LongUnsigned
|
|
41
|
+
exec_weekdays: cdt.BitString
|
|
42
|
+
exec_specdays: cdt.BitString
|
|
43
|
+
begin_date: cst.OctetStringDate
|
|
44
|
+
end_date: cst.OctetStringDate
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# TODO: rewrite to new API
|
|
48
|
+
class Entries(cdt.Array):
|
|
49
|
+
""" Specifies the list of schedule_table_entry. Todo: validate by unique index"""
|
|
50
|
+
TYPE = ScheduleTableEntry
|
|
51
|
+
unique = True
|
|
52
|
+
|
|
53
|
+
def __init__(self, value: bytes = None):
|
|
54
|
+
super(Entries, self).__init__(value)
|
|
55
|
+
# setting callback for validate schedule_table_entry index
|
|
56
|
+
for schedule_table_entry in self:
|
|
57
|
+
schedule_table_entry: ScheduleTableEntry
|
|
58
|
+
schedule_table_entry.index.set_callback(self.get_indexes)
|
|
59
|
+
|
|
60
|
+
def get_indexes(self) -> list[int]:
|
|
61
|
+
""" getter for callback Index """
|
|
62
|
+
return [entries_element.index.decode() for entries_element in self]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DataED(cdt.Structure):
|
|
66
|
+
""" Sets the disabled bit of range A entries to true and then enables the entries of range B.
|
|
67
|
+
* firstIndexA/B < lastIndexA/B: all entries of the range A/B are disabled/enabled
|
|
68
|
+
* firstIndexA/B == lastIndexA/B: one entry is disabled/enabled,
|
|
69
|
+
* firstIndexA/B > lastIndexA/B: nothing disabled/enabled,
|
|
70
|
+
* firstIndexA/B and lastIndexA/B > 9999: no entry is disabled/enabled """
|
|
71
|
+
firstIndexA: Index
|
|
72
|
+
lastIndexA: Index
|
|
73
|
+
firstIndexB: Index
|
|
74
|
+
lastIndexB: Index
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class DataDelete(cdt.Structure):
|
|
78
|
+
""" Deletes a range of entries in the table.
|
|
79
|
+
* firstIndex < lastIndex: all entries of the range A/B are deleted,
|
|
80
|
+
* firstIndex ::= lastIndex: one entry is deleted,
|
|
81
|
+
* firstIndex > lastIndex: nothing deleted """
|
|
82
|
+
firstIndex: Index
|
|
83
|
+
lastIndex: Index
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Schedule(ic.COSEMInterfaceClasses):
|
|
87
|
+
""" The IC “Schedule” together with an object of the IC “Special days” table handles time and date driven activities within a device.
|
|
88
|
+
The following picture gives an overview and shows the interactions between them:
|
|
89
|
+
Schedule:
|
|
90
|
+
|
|
91
|
+
Index | enable | action | Switch_time | validity_window | exec_weekdays | exec_specdays | date range
|
|
92
|
+
| |(script)| | | Mo Tu We Th Fr Sa Su | S1 S2 ... S8 S9 | begin_date | end_date
|
|
93
|
+
120 Yes xxxx:yy 06:00 0xFFFF x x x x x x xx-04-01 xx-09-30
|
|
94
|
+
121 Yes xxxx:yy 22:00 15 x x x x x xx-04-01 xx-09-30
|
|
95
|
+
122 Yes xxxx:yy 12:00 0 x xx-04-01 xx-09-30
|
|
96
|
+
200 No xxxx:yy 06:30 x x x x x x xx-04-01 xx-09-30
|
|
97
|
+
201 No xxxx:yy 21:30 x x x x x xx-04-01 xx-09-30
|
|
98
|
+
202 No xxxx:yy 11:00 x xx-04-01 xx-09-30
|
|
99
|
+
|
|
100
|
+
Special days table:
|
|
101
|
+
|
|
102
|
+
Index | special_day_date | day_id
|
|
103
|
+
12 xx-12-24 S1
|
|
104
|
+
33 xx-12-25 S3
|
|
105
|
+
77 97-03-31 S3
|
|
106
|
+
|
|
107
|
+
Recovery after power failure
|
|
108
|
+
After a power failure, the whole schedule is processed to execute all the necessary scripts that would get lost during a power failure. For this,
|
|
109
|
+
the entries that were not executed during the power failure must be detected. Depending on the validity window attribute they are executed in
|
|
110
|
+
the correct order (as they would have been executed in normal operation).
|
|
111
|
+
|
|
112
|
+
Handling of time changes
|
|
113
|
+
There are four different "actions" of time changes:
|
|
114
|
+
a) time setting forward; b) time setting backwards; c) time synchronization; d) daylight saving action.
|
|
115
|
+
All these four actions need a different handling executed by the schedule in interaction with the time setting activity.
|
|
116
|
+
|
|
117
|
+
Time setting forward*
|
|
118
|
+
This is handled the same way as a power failure. All entries missed are executed depending on the validity window attribute.
|
|
119
|
+
A (manufacturer specific defined) short time setting can be handled like time synchronization.
|
|
120
|
+
* Writing to the attribute “time” of the “Clock” object.
|
|
121
|
+
|
|
122
|
+
Time setting backward*
|
|
123
|
+
This results in a repetition of those entries that are activated during the repeated time. A (manufacturer specific defined) short time setting
|
|
124
|
+
can be handled like time synchronization.
|
|
125
|
+
* Writing to the attribute “time” of the “Clock” object.
|
|
126
|
+
|
|
127
|
+
Time synchronization*
|
|
128
|
+
Time synchronization is used to correct small deviations between a master clock and the local clock. The algorithm is manufacturer specific.
|
|
129
|
+
It shall guarantee that no entry of the schedule gets lost, or is executed twice. The validity window attribute has no effect, because all
|
|
130
|
+
entries must be executed in normal operation.
|
|
131
|
+
* Using the method “adjust_to_quarter” of the “Clock” object.
|
|
132
|
+
|
|
133
|
+
Daylight saving
|
|
134
|
+
If the clock is put forward, then all scripts, which fall into the forwarding interval (and would therefore get lost) are executed.
|
|
135
|
+
If the clock is put back, re-execution of the scripts, which fall into the backwarding interval is suppressed. """
|
|
136
|
+
CLASS_ID = ClassID.SCHEDULE
|
|
137
|
+
VERSION = VERSION_0
|
|
138
|
+
A_ELEMENTS = ic.ICAElement("entries", Entries),
|
|
139
|
+
M_ELEMENTS = (ic.ICMElement("enable_disable", DataED),
|
|
140
|
+
ic.ICMElement("insert", ScheduleTableEntry),
|
|
141
|
+
ic.ICMElement("delete", DataDelete))
|
|
142
|
+
|
|
143
|
+
def characteristics_init(self):
|
|
144
|
+
self.set_attr(2, None)
|
|
145
|
+
self._cbs_attr_post_init.update({2: self.__set_index_cbs})
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def entries(self) -> Entries:
|
|
149
|
+
return self.get_attr(2)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def enable_disable(self) -> DataED:
|
|
153
|
+
return self.get_meth(1)
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def insert(self) -> ScheduleTableEntry:
|
|
157
|
+
return self.get_meth(2)
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def delete(self) -> DataDelete:
|
|
161
|
+
return self.get_meth(3)
|
|
162
|
+
|
|
163
|
+
def __set_index_cbs(self):
|
|
164
|
+
""" set callbacks to methods """
|
|
165
|
+
try:
|
|
166
|
+
indexes: Callable = self.entries.get_indexes
|
|
167
|
+
self.enable_disable.firstIndexA.set_callback(indexes)
|
|
168
|
+
self.enable_disable.firstIndexB.set_callback(indexes)
|
|
169
|
+
self.enable_disable.lastIndexA.set_callback(indexes)
|
|
170
|
+
self.enable_disable.lastIndexB.set_callback(indexes)
|
|
171
|
+
self.insert.index.set_callback(indexes)
|
|
172
|
+
self.delete.firstIndex.set_callback(indexes)
|
|
173
|
+
self.delete.lastIndex.set_callback(indexes)
|
|
174
|
+
# print('set delete')
|
|
175
|
+
except KeyError: # At init time
|
|
176
|
+
print('set delete NO:')
|