MTPPy2.0 0.1.5__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.
- examples/__init__.py +0 -0
- examples/minimal+pid.py +82 -0
- examples/minimal.py +32 -0
- examples/minimal_recipe.py +117 -0
- examples/pea_alarm_base.py +47 -0
- examples/pea_minimal.py +258 -0
- mtppy/__init__.py +1 -0
- mtppy/part1/__init__.py +4 -0
- mtppy/part1/empty_folder_test.py +7 -0
- mtppy/part3/__init__.py +4 -0
- mtppy/part3/data_assembly_set/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/active_element.py +22 -0
- mtppy/part3/data_assembly_set/active_elements/drives/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/drives/ana_drv.py +446 -0
- mtppy/part3/data_assembly_set/active_elements/drives/ana_drv_test.py +567 -0
- mtppy/part3/data_assembly_set/active_elements/drives/bin_drv.py +365 -0
- mtppy/part3/data_assembly_set/active_elements/drives/bin_drv_test.py +503 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv.py +469 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv_test.py +418 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv.py +295 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv_test.py +265 -0
- mtppy/part3/data_assembly_set/active_elements/pid/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl.py +333 -0
- mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl_test.py +156 -0
- mtppy/part3/data_assembly_set/active_elements/valves/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv.py +402 -0
- mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv_test.py +732 -0
- mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv.py +279 -0
- mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv_test.py +585 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv.py +402 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv_test.py +270 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv.py +286 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv_test.py +224 -0
- mtppy/part3/data_assembly_set/base_functions/__init__.py +4 -0
- mtppy/part3/data_assembly_set/base_functions/operation_mode.py +252 -0
- mtppy/part3/data_assembly_set/base_functions/operation_mode_test.py +260 -0
- mtppy/part3/data_assembly_set/base_functions/oslevel.py +35 -0
- mtppy/part3/data_assembly_set/base_functions/source_mode.py +189 -0
- mtppy/part3/data_assembly_set/base_functions/source_mode_test.py +112 -0
- mtppy/part3/data_assembly_set/base_functions/wqc.py +75 -0
- mtppy/part3/data_assembly_set/base_functions/wqc_test.py +86 -0
- mtppy/part3/data_assembly_set/data_assembly.py +65 -0
- mtppy/part3/data_assembly_set/diagnostic_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/diagnostic_elements/diagnostic_element.py +19 -0
- mtppy/part3/data_assembly_set/indicator_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/indicator_elements/indicator_element.py +22 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/__init__.py +4 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/ana_view.py +68 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/bin_view.py +73 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/dint_view.py +76 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/string_view.py +55 -0
- mtppy/part3/data_assembly_set/operation_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/operation_elements/operation_element.py +19 -0
- mtppy/part3/data_assembly_set/pea_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/pea_elements/pea_element.py +28 -0
- mtppy/part3/data_assembly_set/pea_elements/pea_information_label/__init__.py +4 -0
- mtppy/part3/data_assembly_set/pea_elements/pea_information_label/pea_information_label.py +231 -0
- mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/__init__.py +4 -0
- mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/web_server_url_info.py +42 -0
- mtppy/part3/definitions/__init__.py +4 -0
- mtppy/part3/definitions/mtp_access.py +15 -0
- mtppy/part4/__init__.py +4 -0
- mtppy/part4/automation_services/__init__.py +4 -0
- mtppy/part4/automation_services/base_functions/__init__.py +4 -0
- mtppy/part4/automation_services/base_functions/service_operation_mode.py +368 -0
- mtppy/part4/automation_services/base_functions/service_operation_mode_test.py +242 -0
- mtppy/part4/automation_services/base_functions/service_operator_interaction.py +62 -0
- mtppy/part4/automation_services/base_functions/service_source_mode.py +138 -0
- mtppy/part4/automation_services/base_functions/service_source_mode_test.py +179 -0
- mtppy/part4/automation_services/helpers/__init__.py +4 -0
- mtppy/part4/automation_services/helpers/command_en_control.py +330 -0
- mtppy/part4/automation_services/helpers/command_en_control_test.py +302 -0
- mtppy/part4/automation_services/helpers/state_machine.py +167 -0
- mtppy/part4/automation_services/helpers/state_machine_test.py +294 -0
- mtppy/part4/automation_services/helpers/thread_control.py +39 -0
- mtppy/part4/automation_services/helpers/thread_control_test.py +80 -0
- mtppy/part4/automation_services/procedure.py +111 -0
- mtppy/part4/automation_services/procedure_test.py +185 -0
- mtppy/part4/automation_services/service.py +463 -0
- mtppy/part4/automation_services/service_elements/__init__.py +4 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/__init__.py +4 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param.py +108 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param_test.py +59 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param.py +70 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param_test.py +39 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/common_serv_param.py +111 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param.py +77 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param_test.py +58 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element.py +175 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element_test.py +246 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param.py +48 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param_test.py +37 -0
- mtppy/part4/automation_services/service_elements/procedure_health_view.py +36 -0
- mtppy/part4/automation_services/service_elements/service_control.py +473 -0
- mtppy/part4/automation_services/service_elements/service_control_test.py +150 -0
- mtppy/part4/automation_services/service_elements/service_element.py +20 -0
- mtppy/part4/automation_services/service_test.py +407 -0
- mtppy/part4/definitions/__init__.py +4 -0
- mtppy/part4/definitions/command_codes.py +58 -0
- mtppy/part4/definitions/command_codes_test.py +44 -0
- mtppy/part4/definitions/state_codes.py +65 -0
- mtppy/part4/definitions/state_codes_test.py +44 -0
- mtppy/part4/process_values/__init__.py +4 -0
- mtppy/part4/process_values/base_functions/__init__.py +4 -0
- mtppy/part4/process_values/base_functions/vqc.py +48 -0
- mtppy/part4/process_values/input_elements/__init__.py +4 -0
- mtppy/part4/process_values/input_elements/ana_process_value_in.py +160 -0
- mtppy/part4/process_values/input_elements/bin_process_value_in.py +83 -0
- mtppy/part4/process_values/input_elements/dint_process_value_in.py +159 -0
- mtppy/part4/process_values/input_elements/input_element.py +40 -0
- mtppy/part4/process_values/input_elements/string_process_value_in.py +45 -0
- mtppy/part5/__init__.py +4 -0
- mtppy/part5/communication_object.py +99 -0
- mtppy/part5/communication_object_test.py +59 -0
- mtppy/part5/definitions/__init__.py +4 -0
- mtppy/part5/definitions/iec61131_data_types.py +31 -0
- mtppy/part5/definitions/iec61131_data_types_test.py +82 -0
- mtppy/part5/definitions/opcua_data_types.py +36 -0
- mtppy/part5/definitions/opcua_data_types_test.py +90 -0
- mtppy/part5/definitions/w3c_xml_data_types.py +177 -0
- mtppy/part5/definitions/w3c_xml_data_types_test.py +143 -0
- mtppy/part5/server_assembly_set/__init__.py +5 -0
- mtppy/part6/__init__.py +4 -0
- mtppy/part6/alarm_set/__init__.py +4 -0
- mtppy/part6/alarm_set/alarm.py +132 -0
- mtppy/part6/alarm_set/alarm_test.py +265 -0
- mtppy/pea/__init__.py +19 -0
- mtppy/pea/data_assembly_factory.py +124 -0
- mtppy/pea/data_assembly_factory_test.py +229 -0
- mtppy/pea/mtp_generator/__init__.py +4 -0
- mtppy/pea/mtp_generator/mtp_generator.py +1183 -0
- mtppy/pea/mtp_generator/mtp_generator_test.py +518 -0
- mtppy/pea/pea.py +139 -0
- mtppy/pea/pea_opcua_server/__init__.py +4 -0
- mtppy/pea/pea_opcua_server/marshalling.py +60 -0
- mtppy/pea/pea_opcua_server/opcua_options_provider/__init__.py +4 -0
- mtppy/pea/pea_opcua_server/opcua_options_provider/instances/basic_opcua_options_provider.py +50 -0
- mtppy/pea/pea_opcua_server/opcua_options_provider/opcua_options_provider.py +103 -0
- mtppy/pea/pea_opcua_server/pea_opcua_server.py +492 -0
- mtppy/pea/pea_opcua_server/subscription_list.py +55 -0
- mtppy/pea/pea_test.py +59 -0
- mtppy/pea/rmtp_pea.py +155 -0
- mtppy/pea/rmtp_pea_test.py +27 -0
- mtppy/pea/service_factory.py +178 -0
- mtppy/pea/service_factory_test.py +156 -0
- mtppy/pea/user_logic/__init__.py +4 -0
- mtppy/pea/user_logic/user_logic_pea.py +72 -0
- mtppy/pea/user_logic/user_logic_registry.py +106 -0
- mtppy/pea/user_logic/user_logic_registry_test.py +167 -0
- mtppy/pea/user_logic/user_logic_service.py +228 -0
- mtppy/pea/user_logic/user_logic_service_test.py +289 -0
- mtppy/utils/__init__.py +4 -0
- mtppy/utils/data_assembly_dict.py +61 -0
- mtppy/utils/data_assembly_dict_test.py +107 -0
- mtppy/utils/data_item.py +446 -0
- mtppy/utils/data_item_com_test.py +82 -0
- mtppy/utils/data_item_test.py +325 -0
- mtppy/utils/logging.py +23 -0
- mtppy/utils/logging_test.py +26 -0
- mtppy/utils/mtp_file_handling/__init__.py +4 -0
- mtppy/utils/mtp_file_handling/extract_manifest_from_mtp_file.py +22 -0
- mtppy/utils/mtp_file_handling/extract_mtp_file.py +22 -0
- mtppy/utils/mtp_file_handling/is_valid_mtp.py +22 -0
- mtppy/utils/mtp_file_handling/is_valid_path_for_manifest.py +15 -0
- mtppy/utils/mtp_file_handling/is_valid_path_to_manifest.py +17 -0
- mtppy/utils/mtp_file_handling/is_valid_path_to_mtp.py +17 -0
- mtppy/utils/mtp_file_handling/load_manifest_file.py +24 -0
- mtppy/utils/mtp_file_handling/mtp_reader/__init__.py +4 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader.py +103 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader_test.py +96 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml.py +226 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml_test.py +108 -0
- mtppy/utils/mtp_file_handling/mtp_writer/__init__.py +4 -0
- mtppy/utils/mtp_file_handling/mtp_writer/mtp_writer.py +77 -0
- mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_content_types.py +10 -0
- mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_rels.py +11 -0
- mtppy/utils/mtp_file_handling/mtp_writer/test_mtp_writer.py +25 -0
- mtppy/utils/mtp_file_handling/write_manifest_file.py +29 -0
- mtppy/utils/mtp_file_handling/write_manifest_file_test.py +22 -0
- mtppy/utils/mtp_file_handling/write_mtp_file.py +43 -0
- mtppy/utils/mtp_file_handling/write_mtp_file_test.py +22 -0
- mtppy/utils/testing.py +31 -0
- mtppy2_0-0.1.5.dist-info/METADATA +75 -0
- mtppy2_0-0.1.5.dist-info/RECORD +214 -0
- mtppy2_0-0.1.5.dist-info/WHEEL +5 -0
- mtppy2_0-0.1.5.dist-info/entry_points.txt +3 -0
- mtppy2_0-0.1.5.dist-info/licenses/LICENSE +21 -0
- mtppy2_0-0.1.5.dist-info/top_level.txt +4 -0
- tools/__init__.py +0 -0
- tools/create_bugreport.py +150 -0
- tools/inspect_mtp.py +110 -0
- utils/__init__.py +1 -0
- utils/file_handling/__init__.py +4 -0
- utils/file_handling/cleanup_folder.py +9 -0
- utils/file_handling/get_utc_foldername_with_ms.py +15 -0
- utils/file_handling/get_work_dir.py +9 -0
- utils/file_handling/is_valid_path_for_json.py +11 -0
- utils/file_handling/is_valid_path_to_json.py +17 -0
- utils/file_handling/load_json_file.py +18 -0
- utils/file_handling/read_file_from_file_archive.py +43 -0
- utils/file_handling/read_file_string.py +10 -0
- utils/file_handling/validate_json_data.py +28 -0
- utils/file_handling/write_json_file.py +23 -0
- utils/get_uuid.py +18 -0
- utils/get_uuid_test.py +17 -0
- utils/logging.py +100 -0
- utils/logging_test.py +67 -0
- utils/opcua/check_node.py +79 -0
- utils/resolve_unit/resolve_unit.py +11 -0
- utils/resolve_unit/unit_lib.py +2101 -0
- utils/resolve_unit/unit_lib_test.py +36 -0
- utils/type_conversion.py +236 -0
- utils/type_conversion_test.py +114 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Tests for static AlarmSet.Base alarm descriptions."""
|
|
2
|
+
|
|
3
|
+
# ruff: noqa: S101
|
|
4
|
+
|
|
5
|
+
__copyright__ = (
|
|
6
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
7
|
+
)
|
|
8
|
+
__license__ = "MIT"
|
|
9
|
+
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
from mtppy.utils.data_item import DataItem
|
|
14
|
+
from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
|
|
15
|
+
from mtppy.part3.definitions.mtp_access import MTPAccess
|
|
16
|
+
from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
|
|
17
|
+
from mtppy.part6.alarm_set.alarm import Alarm
|
|
18
|
+
|
|
19
|
+
DEFAULT_SEVERITY = 500
|
|
20
|
+
INTEGER_RANGES = [
|
|
21
|
+
(W3cXmlDataTypes.BYTE, -(2**7), (2**7) - 1),
|
|
22
|
+
(W3cXmlDataTypes.UNSIGNED_BYTE, 0, (2**8) - 1),
|
|
23
|
+
(W3cXmlDataTypes.SHORT, -(2**15), (2**15) - 1),
|
|
24
|
+
(W3cXmlDataTypes.UNSIGNED_SHORT, 0, (2**16) - 1),
|
|
25
|
+
(W3cXmlDataTypes.INT, -(2**31), (2**31) - 1),
|
|
26
|
+
(W3cXmlDataTypes.UNSIGNED_INT, 0, (2**32) - 1),
|
|
27
|
+
(W3cXmlDataTypes.LONG, -(2**63), (2**63) - 1),
|
|
28
|
+
(W3cXmlDataTypes.UNSIGNED_LONG, 0, (2**64) - 1),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TriggerAssembly(DataAssembly):
|
|
33
|
+
"""DataAssembly exposing all trigger types relevant to the tests."""
|
|
34
|
+
|
|
35
|
+
BOOL_TRIGGER = "BoolTrigger"
|
|
36
|
+
INT_TRIGGER = "IntTrigger"
|
|
37
|
+
TEXT_TRIGGER = "TextTrigger"
|
|
38
|
+
FLOAT_TRIGGER = "FloatTrigger"
|
|
39
|
+
|
|
40
|
+
CLASS_DATA_ITEMS: ClassVar = {
|
|
41
|
+
BOOL_TRIGGER: DataItem(
|
|
42
|
+
BOOL_TRIGGER,
|
|
43
|
+
W3cXmlDataTypes.BOOLEAN,
|
|
44
|
+
MTPAccess.ReadAccess,
|
|
45
|
+
init_value=False,
|
|
46
|
+
),
|
|
47
|
+
INT_TRIGGER: DataItem(
|
|
48
|
+
INT_TRIGGER,
|
|
49
|
+
W3cXmlDataTypes.INT,
|
|
50
|
+
MTPAccess.ReadAccess,
|
|
51
|
+
0,
|
|
52
|
+
),
|
|
53
|
+
TEXT_TRIGGER: DataItem(
|
|
54
|
+
TEXT_TRIGGER,
|
|
55
|
+
W3cXmlDataTypes.STRING,
|
|
56
|
+
MTPAccess.ReadAccess,
|
|
57
|
+
"",
|
|
58
|
+
),
|
|
59
|
+
FLOAT_TRIGGER: DataItem(
|
|
60
|
+
FLOAT_TRIGGER,
|
|
61
|
+
W3cXmlDataTypes.FLOAT,
|
|
62
|
+
MTPAccess.ReadAccess,
|
|
63
|
+
0.0,
|
|
64
|
+
),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def build_alarm(
|
|
69
|
+
*,
|
|
70
|
+
assembly: TriggerAssembly | None = None,
|
|
71
|
+
trigger_name: str = TriggerAssembly.BOOL_TRIGGER,
|
|
72
|
+
trigger_value: object = True,
|
|
73
|
+
severity: int = DEFAULT_SEVERITY,
|
|
74
|
+
ack_required: bool = True,
|
|
75
|
+
tag_name: str = "HighLevel.Alarm",
|
|
76
|
+
message: str = "Level is too high.",
|
|
77
|
+
) -> Alarm:
|
|
78
|
+
"""Build a valid alarm while allowing one property to be varied."""
|
|
79
|
+
target = assembly or TriggerAssembly("Triggers")
|
|
80
|
+
return Alarm(
|
|
81
|
+
tag_name=tag_name,
|
|
82
|
+
data_assembly=target,
|
|
83
|
+
trigger=target.get_item(trigger_name),
|
|
84
|
+
trigger_value=trigger_value,
|
|
85
|
+
severity=severity,
|
|
86
|
+
ack_required=ack_required,
|
|
87
|
+
message=message,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def build_integer_alarm(
|
|
92
|
+
data_type: W3cXmlDataTypes,
|
|
93
|
+
trigger_value: int,
|
|
94
|
+
) -> Alarm:
|
|
95
|
+
"""Build an alarm with one concrete W3C integer trigger type."""
|
|
96
|
+
|
|
97
|
+
class IntegerTriggerAssembly(DataAssembly):
|
|
98
|
+
"""DataAssembly containing the parameterized integer trigger."""
|
|
99
|
+
|
|
100
|
+
TRIGGER = "Trigger"
|
|
101
|
+
CLASS_DATA_ITEMS: ClassVar = {
|
|
102
|
+
TRIGGER: DataItem(
|
|
103
|
+
TRIGGER,
|
|
104
|
+
data_type,
|
|
105
|
+
MTPAccess.ReadAccess,
|
|
106
|
+
init_value=0,
|
|
107
|
+
),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
assembly = IntegerTriggerAssembly("IntegerTrigger")
|
|
111
|
+
return Alarm(
|
|
112
|
+
tag_name="Integer.Alarm",
|
|
113
|
+
data_assembly=assembly,
|
|
114
|
+
trigger=assembly.get_item(IntegerTriggerAssembly.TRIGGER),
|
|
115
|
+
trigger_value=trigger_value,
|
|
116
|
+
severity=DEFAULT_SEVERITY,
|
|
117
|
+
ack_required=False,
|
|
118
|
+
message="Integer alarm.",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_alarm_preserves_static_base_metadata() -> None:
|
|
123
|
+
"""A valid AlarmSet.Base description retains its public metadata."""
|
|
124
|
+
assembly = TriggerAssembly("Triggers")
|
|
125
|
+
trigger = assembly.get_item(TriggerAssembly.BOOL_TRIGGER)
|
|
126
|
+
|
|
127
|
+
alarm = build_alarm(assembly=assembly)
|
|
128
|
+
|
|
129
|
+
assert alarm.tag_name == "HighLevel.Alarm"
|
|
130
|
+
assert alarm.data_assembly is assembly
|
|
131
|
+
assert alarm.trigger is trigger
|
|
132
|
+
assert alarm.trigger_value is True
|
|
133
|
+
assert alarm.severity == DEFAULT_SEVERITY
|
|
134
|
+
assert alarm.ack_required is True
|
|
135
|
+
assert alarm.message == "Level is too high."
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.parametrize("severity", [1, 500, 1000])
|
|
139
|
+
def test_alarm_accepts_valid_severity_boundaries(severity: int) -> None:
|
|
140
|
+
"""Severity accepts the complete specified inclusive range."""
|
|
141
|
+
assert build_alarm(severity=severity).severity == severity
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@pytest.mark.parametrize("severity", [0, 1001, -1, True, 1.0, "500", None])
|
|
145
|
+
def test_alarm_rejects_invalid_severity(severity: object) -> None:
|
|
146
|
+
"""Severity rejects out-of-range values and non-integer types."""
|
|
147
|
+
with pytest.raises((TypeError, ValueError)):
|
|
148
|
+
build_alarm(severity=severity) # type: ignore[arg-type]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@pytest.mark.parametrize("ack_required", [0, 1, "true", None])
|
|
152
|
+
def test_alarm_rejects_non_boolean_ack_required(ack_required: object) -> None:
|
|
153
|
+
"""AckRequired must be an actual bool rather than a truthy value."""
|
|
154
|
+
with pytest.raises(TypeError):
|
|
155
|
+
build_alarm(ack_required=ack_required) # type: ignore[arg-type]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@pytest.mark.parametrize(
|
|
159
|
+
("tag_name", "message"),
|
|
160
|
+
[("", "message"), (" ", "message"), ("alarm", ""), ("alarm", " ")],
|
|
161
|
+
)
|
|
162
|
+
def test_alarm_rejects_empty_names_and_messages(tag_name: str, message: str) -> None:
|
|
163
|
+
"""Alarm identity and operator message must contain visible text."""
|
|
164
|
+
with pytest.raises(ValueError, match="must not be empty"):
|
|
165
|
+
build_alarm(tag_name=tag_name, message=message)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.mark.parametrize(
|
|
169
|
+
("trigger_name", "trigger_value"),
|
|
170
|
+
[
|
|
171
|
+
(TriggerAssembly.BOOL_TRIGGER, True),
|
|
172
|
+
(TriggerAssembly.INT_TRIGGER, 12),
|
|
173
|
+
(TriggerAssembly.TEXT_TRIGGER, "ALARM"),
|
|
174
|
+
],
|
|
175
|
+
)
|
|
176
|
+
def test_alarm_accepts_supported_trigger_types(
|
|
177
|
+
trigger_name: str,
|
|
178
|
+
trigger_value: object,
|
|
179
|
+
) -> None:
|
|
180
|
+
"""Boolean, integer and string equality triggers are supported."""
|
|
181
|
+
alarm = build_alarm(trigger_name=trigger_name, trigger_value=trigger_value)
|
|
182
|
+
|
|
183
|
+
assert alarm.trigger_value == trigger_value
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@pytest.mark.parametrize(("data_type", "lower", "upper"), INTEGER_RANGES)
|
|
187
|
+
def test_alarm_accepts_integer_type_boundaries(
|
|
188
|
+
data_type: W3cXmlDataTypes,
|
|
189
|
+
lower: int,
|
|
190
|
+
upper: int,
|
|
191
|
+
) -> None:
|
|
192
|
+
"""Every supported W3C integer type accepts both inclusive boundaries."""
|
|
193
|
+
assert build_integer_alarm(data_type, lower).trigger_value == lower
|
|
194
|
+
assert build_integer_alarm(data_type, upper).trigger_value == upper
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@pytest.mark.parametrize(("data_type", "lower", "upper"), INTEGER_RANGES)
|
|
198
|
+
def test_alarm_rejects_values_outside_integer_type_range(
|
|
199
|
+
data_type: W3cXmlDataTypes,
|
|
200
|
+
lower: int,
|
|
201
|
+
upper: int,
|
|
202
|
+
) -> None:
|
|
203
|
+
"""W3C integer triggers reject values that cannot be represented."""
|
|
204
|
+
for invalid_value in (lower - 1, upper + 1):
|
|
205
|
+
with pytest.raises(ValueError, match="range"):
|
|
206
|
+
build_integer_alarm(data_type, invalid_value)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@pytest.mark.parametrize(
|
|
210
|
+
("trigger_name", "trigger_value"),
|
|
211
|
+
[
|
|
212
|
+
(TriggerAssembly.BOOL_TRIGGER, 1),
|
|
213
|
+
(TriggerAssembly.INT_TRIGGER, True),
|
|
214
|
+
(TriggerAssembly.INT_TRIGGER, 1.0),
|
|
215
|
+
(TriggerAssembly.TEXT_TRIGGER, b"ALARM"),
|
|
216
|
+
],
|
|
217
|
+
)
|
|
218
|
+
def test_alarm_requires_exact_trigger_value_type(
|
|
219
|
+
trigger_name: str,
|
|
220
|
+
trigger_value: object,
|
|
221
|
+
) -> None:
|
|
222
|
+
"""TriggerValue must use the exact Python type declared by the DataItem."""
|
|
223
|
+
with pytest.raises(TypeError, match="TriggerValue"):
|
|
224
|
+
build_alarm(trigger_name=trigger_name, trigger_value=trigger_value)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_alarm_rejects_float_trigger() -> None:
|
|
228
|
+
"""AlarmSet.Base does not support floating-point equality triggers."""
|
|
229
|
+
with pytest.raises(TypeError, match="trigger type"):
|
|
230
|
+
build_alarm(
|
|
231
|
+
trigger_name=TriggerAssembly.FLOAT_TRIGGER,
|
|
232
|
+
trigger_value=1.5,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_alarm_trigger_must_belong_to_data_assembly() -> None:
|
|
237
|
+
"""A trigger cannot be linked through an unrelated DataAssembly."""
|
|
238
|
+
assembly = TriggerAssembly("Triggers")
|
|
239
|
+
foreign = TriggerAssembly("Foreign")
|
|
240
|
+
|
|
241
|
+
with pytest.raises(ValueError, match="DataAssembly"):
|
|
242
|
+
Alarm(
|
|
243
|
+
tag_name="Foreign.Alarm",
|
|
244
|
+
data_assembly=assembly,
|
|
245
|
+
trigger=foreign.get_item(TriggerAssembly.BOOL_TRIGGER),
|
|
246
|
+
trigger_value=True,
|
|
247
|
+
severity=500,
|
|
248
|
+
ack_required=True,
|
|
249
|
+
message="Foreign trigger.",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def test_base_alarm_does_not_install_runtime_callbacks() -> None:
|
|
254
|
+
"""Describing a base alarm must not alter DataItem runtime behavior."""
|
|
255
|
+
assembly = TriggerAssembly("Triggers")
|
|
256
|
+
trigger = assembly.get_item(TriggerAssembly.BOOL_TRIGGER)
|
|
257
|
+
internal_callback = trigger.int_wrt_cb
|
|
258
|
+
external_callback = trigger.ext_wrt_cb
|
|
259
|
+
|
|
260
|
+
alarm = build_alarm(assembly=assembly)
|
|
261
|
+
|
|
262
|
+
assert trigger.int_wrt_cb is internal_callback
|
|
263
|
+
assert trigger.ext_wrt_cb is external_callback
|
|
264
|
+
assert not hasattr(alarm, "acknowledge")
|
|
265
|
+
assert not hasattr(alarm, "shelve")
|
mtppy/pea/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
__copyright__ = (
|
|
2
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
3
|
+
)
|
|
4
|
+
__license__ = "MIT"
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from mtppy.pea.rmtp_pea import RMTPPea
|
|
10
|
+
|
|
11
|
+
__all__ = ["RMTPPea"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __getattr__(name: str):
|
|
15
|
+
if name == "RMTPPea":
|
|
16
|
+
from mtppy.pea.rmtp_pea import RMTPPea
|
|
17
|
+
|
|
18
|
+
return RMTPPea
|
|
19
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Factory for creating data assemblies from a DataAssemblyConfig dictionary.
|
|
2
|
+
|
|
3
|
+
This module provides a factory class for creating DataAssembly instances
|
|
4
|
+
based on a configuration dictionaries of type DataAssemblyConfig.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__copyright__ = (
|
|
9
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
10
|
+
)
|
|
11
|
+
__license__ = "MIT"
|
|
12
|
+
|
|
13
|
+
from typing import ClassVar
|
|
14
|
+
|
|
15
|
+
from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
|
|
16
|
+
from mtppy.part3.data_assembly_set.pea_elements.pea_information_label.pea_information_label import (
|
|
17
|
+
PeaInformationLabel,
|
|
18
|
+
)
|
|
19
|
+
from mtppy.part3.data_assembly_set.active_elements.pid.pid_ctrl import PIDCtrl
|
|
20
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.ana_view import AnaView
|
|
21
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.bin_view import BinView
|
|
22
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.dint_view import DIntView
|
|
23
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.string_view import (
|
|
24
|
+
StringView,
|
|
25
|
+
)
|
|
26
|
+
from mtppy.part4.automation_services.service_elements.parameter_elements.ana_serv_param import (
|
|
27
|
+
AnaServParam,
|
|
28
|
+
)
|
|
29
|
+
from mtppy.part4.automation_services.service_elements.parameter_elements.bin_serv_param import (
|
|
30
|
+
BinServParam,
|
|
31
|
+
)
|
|
32
|
+
from mtppy.part4.automation_services.service_elements.parameter_elements.dint_serv_param import (
|
|
33
|
+
DIntServParam,
|
|
34
|
+
)
|
|
35
|
+
from mtppy.part4.automation_services.service_elements.parameter_elements.string_serv_param import (
|
|
36
|
+
StringServParam,
|
|
37
|
+
)
|
|
38
|
+
from mtppy.part4.automation_services.service_elements.procedure_health_view import (
|
|
39
|
+
ProcedureHealthView,
|
|
40
|
+
)
|
|
41
|
+
from mtppy.part4.automation_services.service_elements.service_control import (
|
|
42
|
+
ServiceControl,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
DataAssemblyConfig = dict[str, str | dict[str, str | None]]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DataAssemblyFactory:
|
|
49
|
+
"""Factory class for creating DataAssembly instances."""
|
|
50
|
+
|
|
51
|
+
_ROOT: str = "MTPDataAssemblySUCLib/DataAssembly"
|
|
52
|
+
|
|
53
|
+
_FACTORY: ClassVar[dict[str, type[DataAssembly]]] = {
|
|
54
|
+
f"{_ROOT}/ActiveElement/PIDCtrl": PIDCtrl,
|
|
55
|
+
f"{_ROOT}/IndicatorElement/AnaView": AnaView,
|
|
56
|
+
f"{_ROOT}/IndicatorElement/BinView": BinView,
|
|
57
|
+
f"{_ROOT}/IndicatorElement/DIntView": DIntView,
|
|
58
|
+
f"{_ROOT}/IndicatorElement/StringView": StringView,
|
|
59
|
+
f"{_ROOT}/ServiceElement/ParameterElement/DIntServParam": DIntServParam,
|
|
60
|
+
f"{_ROOT}/ServiceElement/ParameterElement/AnaServParam": AnaServParam,
|
|
61
|
+
f"{_ROOT}/ServiceElement/ParameterElement/BinServParam": BinServParam,
|
|
62
|
+
f"{_ROOT}/ServiceElement/ParameterElement/StringServParam": StringServParam,
|
|
63
|
+
f"{_ROOT}/PeaElement/PeaInformationLabel": PeaInformationLabel,
|
|
64
|
+
f"{_ROOT}/ServiceElement/ProcedureHealthView": ProcedureHealthView,
|
|
65
|
+
# TODO: reintroduce after fixing namespace handling
|
|
66
|
+
# f"{_ROOT}/ServiceElement/ServiceControl": ServiceControl,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def create_data_assemblies(
|
|
71
|
+
da_dict: dict[str, DataAssemblyConfig],
|
|
72
|
+
) -> dict[str, DataAssembly]:
|
|
73
|
+
"""Create multiple DataAssembly instances from a dictionary of configurations.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
da_dict: Dictionary mapping names to DataAssemblyConfig dictionaries.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Dictionary mapping names to created DataAssembly instances.
|
|
80
|
+
"""
|
|
81
|
+
data_assemblies: dict[str, DataAssembly] = {}
|
|
82
|
+
for key, value in da_dict.items():
|
|
83
|
+
data_assembly = DataAssemblyFactory.create_data_assembly(value)
|
|
84
|
+
if data_assembly is not None:
|
|
85
|
+
data_assemblies[key] = data_assembly
|
|
86
|
+
return data_assemblies
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def create_data_assembly(config: DataAssemblyConfig) -> DataAssembly | None:
|
|
90
|
+
"""Create a single DataAssembly instance from a configuration entry.
|
|
91
|
+
If a default value is given in the config this value is set as init_value of
|
|
92
|
+
the underlying DataItem and the actual value of the DataAssembly is
|
|
93
|
+
initialized accordingly.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
config: Configuration dictionary for the data assembly.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Created DataAssembly instance.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
da_type: str = config["RefBaseSystemUnitPath"]
|
|
103
|
+
tag_name: str = config["TagName"]["value"]
|
|
104
|
+
tag_description: str = (
|
|
105
|
+
config["TagDescription"]["value"] if "TagDescription" in config else ""
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if da_type in DataAssemblyFactory._FACTORY:
|
|
109
|
+
instance = DataAssemblyFactory._FACTORY[da_type](
|
|
110
|
+
tag_name=tag_name,
|
|
111
|
+
tag_description=tag_description,
|
|
112
|
+
)
|
|
113
|
+
for alias, v in config.items():
|
|
114
|
+
if (
|
|
115
|
+
"default" in v
|
|
116
|
+
and v["default"] is not None
|
|
117
|
+
and len(v["default"]) > 0
|
|
118
|
+
):
|
|
119
|
+
if alias in instance.keys():
|
|
120
|
+
da = instance.get_item(alias)
|
|
121
|
+
da.init_value = da.w3c_xml_data_type.converse(v["default"])
|
|
122
|
+
instance[alias] = da.w3c_xml_data_type.converse(v["default"])
|
|
123
|
+
return instance
|
|
124
|
+
return None
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the DataAssemblyFactory class.
|
|
3
|
+
|
|
4
|
+
This module contains tests for the DataAssemblyFactory which creates
|
|
5
|
+
DataAssembly instances from configuration dictionaries.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from mtppy.pea.data_assembly_factory import DataAssemblyFactory, DataAssemblyConfig
|
|
11
|
+
from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
|
|
12
|
+
from mtppy.part3.data_assembly_set.active_elements.pid.pid_ctrl import PIDCtrl
|
|
13
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.ana_view import AnaView
|
|
14
|
+
from mtppy.part3.data_assembly_set.pea_elements.pea_information_label.pea_information_label import (
|
|
15
|
+
PeaInformationLabel,
|
|
16
|
+
)
|
|
17
|
+
from mtppy.part4.automation_services.service_elements.procedure_health_view import (
|
|
18
|
+
ProcedureHealthView,
|
|
19
|
+
)
|
|
20
|
+
from mtppy.part4.automation_services.service_elements.service_control import (
|
|
21
|
+
ServiceControl,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def basic_config() -> DataAssemblyConfig:
|
|
27
|
+
"""Create a basic configuration for testing."""
|
|
28
|
+
return {
|
|
29
|
+
"RefBaseSystemUnitPath": "MTPDataAssemblySUCLib/DataAssembly/IndicatorElement/AnaView",
|
|
30
|
+
"TagName": {"value": "TestAnaView"},
|
|
31
|
+
"TagDescription": {"value": "Test Description"},
|
|
32
|
+
"VSclMin": {"default": "10"},
|
|
33
|
+
"VSclMax": {"default": "100"},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def pid_config() -> DataAssemblyConfig:
|
|
39
|
+
"""Create a PID controller configuration for testing."""
|
|
40
|
+
return {
|
|
41
|
+
"RefBaseSystemUnitPath": "MTPDataAssemblySUCLib/DataAssembly/ActiveElement/PIDCtrl",
|
|
42
|
+
"TagName": {"value": "TestPID"},
|
|
43
|
+
"TagDescription": {"value": "Test PID Controller"},
|
|
44
|
+
"PVSclMin": {"default": "-100.0"},
|
|
45
|
+
"PVSclMax": {"default": "100.0"},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.fixture
|
|
50
|
+
def ana_serv_config() -> DataAssemblyConfig:
|
|
51
|
+
"""Create an ana serv parameter configuration for testing."""
|
|
52
|
+
return {
|
|
53
|
+
"RefBaseSystemUnitPath": "MTPDataAssemblySUCLib/DataAssembly/ServiceElement/ParameterElement/AnaServParam",
|
|
54
|
+
"TagName": {"value": "Input Setpoint"},
|
|
55
|
+
"VMin": {"default": "10.0"},
|
|
56
|
+
"VMax": {"default": "90.0"},
|
|
57
|
+
"VSclMin": {"default": "0.0"},
|
|
58
|
+
"VSclMax": {"default": "100.0"},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_create_data_assembly_success(basic_config: DataAssemblyConfig):
|
|
63
|
+
"""Test successful creation of a DataAssembly instance."""
|
|
64
|
+
# Test with basic configuration
|
|
65
|
+
data_assembly = DataAssemblyFactory.create_data_assembly(basic_config)
|
|
66
|
+
|
|
67
|
+
assert data_assembly is not None
|
|
68
|
+
assert isinstance(data_assembly, AnaView)
|
|
69
|
+
assert hasattr(data_assembly, "tag_name")
|
|
70
|
+
assert hasattr(data_assembly, "tag_description")
|
|
71
|
+
assert data_assembly.tag_name == "TestAnaView"
|
|
72
|
+
assert data_assembly.tag_description == "Test Description"
|
|
73
|
+
assert data_assembly.VSclMin == 10.0
|
|
74
|
+
assert data_assembly.VSclMax == 100.0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_create_data_assembly_with_default_value(basic_config: DataAssemblyConfig):
|
|
78
|
+
"""Test DataAssembly creation with default value."""
|
|
79
|
+
# Modify config to include another default value
|
|
80
|
+
basic_config["VSclMin"] = {"default": "20.0"}
|
|
81
|
+
|
|
82
|
+
data_assembly = DataAssemblyFactory.create_data_assembly(basic_config)
|
|
83
|
+
|
|
84
|
+
# Check that default value was set correctly
|
|
85
|
+
|
|
86
|
+
assert data_assembly is not None
|
|
87
|
+
assert data_assembly["VSclMin"] == 20.0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_created_data_assembly_inner_logic(ana_serv_config: DataAssemblyConfig):
|
|
91
|
+
data_assembly = DataAssemblyFactory.create_data_assembly(ana_serv_config)
|
|
92
|
+
|
|
93
|
+
# setup channels for internal access
|
|
94
|
+
assert data_assembly is not None
|
|
95
|
+
data_assembly.service_op_mode.StateChannel = True
|
|
96
|
+
data_assembly.service_src_mode.SrcChannel = True
|
|
97
|
+
# put in automatic state (from off most obviously)
|
|
98
|
+
data_assembly.service_op_mode.StateAutAut = True
|
|
99
|
+
assert data_assembly.service_op_mode.StateAutAct == True
|
|
100
|
+
# put in internal state
|
|
101
|
+
data_assembly.service_src_mode.SrcIntAut = True
|
|
102
|
+
assert data_assembly.service_src_mode.SrcIntAct == True
|
|
103
|
+
|
|
104
|
+
# set a value within the bounds
|
|
105
|
+
data_assembly.VInt = 30
|
|
106
|
+
data_assembly.ApplyInt = True
|
|
107
|
+
assert data_assembly.VOut == 30
|
|
108
|
+
|
|
109
|
+
# set a value outside the bounds
|
|
110
|
+
data_assembly.VInt = -10
|
|
111
|
+
data_assembly.ApplyInt = True
|
|
112
|
+
assert data_assembly.VOut == 30
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_create_data_assembly_pid_controller(pid_config: DataAssemblyConfig):
|
|
116
|
+
"""Test creation of PID controller DataAssembly."""
|
|
117
|
+
data_assembly = DataAssemblyFactory.create_data_assembly(pid_config)
|
|
118
|
+
|
|
119
|
+
assert data_assembly is not None
|
|
120
|
+
assert isinstance(data_assembly, PIDCtrl)
|
|
121
|
+
assert data_assembly.tag_name == "TestPID"
|
|
122
|
+
assert data_assembly.tag_description == "Test PID Controller"
|
|
123
|
+
assert data_assembly.PVSclMin == -100.0
|
|
124
|
+
assert data_assembly.PVSclMax == 100.0
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.mark.parametrize(
|
|
128
|
+
("assembly_type", "path"),
|
|
129
|
+
[
|
|
130
|
+
(
|
|
131
|
+
PeaInformationLabel,
|
|
132
|
+
"MTPDataAssemblySUCLib/DataAssembly/PeaElement/PeaInformationLabel",
|
|
133
|
+
),
|
|
134
|
+
(
|
|
135
|
+
ProcedureHealthView,
|
|
136
|
+
"MTPDataAssemblySUCLib/DataAssembly/ServiceElement/ProcedureHealthView",
|
|
137
|
+
),
|
|
138
|
+
# TODO: reintroduce after fixing namespace handling
|
|
139
|
+
# (
|
|
140
|
+
# ServiceControl,
|
|
141
|
+
# "MTPDataAssemblySUCLib/DataAssembly/ServiceElement/ServiceControl",
|
|
142
|
+
# ),
|
|
143
|
+
],
|
|
144
|
+
)
|
|
145
|
+
def test_create_newly_supported_data_assemblies(
|
|
146
|
+
assembly_type: type[DataAssembly], path: str
|
|
147
|
+
):
|
|
148
|
+
"""Create each data assembly type registered by the factory."""
|
|
149
|
+
config: DataAssemblyConfig = {
|
|
150
|
+
"RefBaseSystemUnitPath": path,
|
|
151
|
+
"TagName": {"value": "TestAssembly"},
|
|
152
|
+
"TagDescription": {"value": "Test description"},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
data_assembly = DataAssemblyFactory.create_data_assembly(config)
|
|
156
|
+
|
|
157
|
+
assert data_assembly is not None
|
|
158
|
+
assert isinstance(data_assembly, assembly_type)
|
|
159
|
+
assert data_assembly.tag_name == "TestAssembly"
|
|
160
|
+
assert data_assembly.tag_description == "Test description"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_create_data_assembly_returns_none_for_unknown_type():
|
|
164
|
+
"""Unsupported AML paths are ignored by the factory."""
|
|
165
|
+
config: DataAssemblyConfig = {
|
|
166
|
+
"RefBaseSystemUnitPath": "Unknown/DataAssembly",
|
|
167
|
+
"TagName": {"value": "Unsupported"},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
assert DataAssemblyFactory.create_data_assembly(config) is None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_create_data_assembly_ignores_empty_default(basic_config: DataAssemblyConfig):
|
|
174
|
+
"""Empty defaults do not overwrite a class default value."""
|
|
175
|
+
basic_config["VSclMin"] = {"default": ""}
|
|
176
|
+
|
|
177
|
+
data_assembly = DataAssemblyFactory.create_data_assembly(basic_config)
|
|
178
|
+
|
|
179
|
+
assert data_assembly is not None
|
|
180
|
+
assert data_assembly.VSclMin == 0.0
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_create_data_assemblies_multiple():
|
|
184
|
+
"""Test creating multiple DataAssembly instances."""
|
|
185
|
+
config_dict: dict[str, DataAssemblyConfig] = {
|
|
186
|
+
"assembly1": {
|
|
187
|
+
"RefBaseSystemUnitPath": "MTPDataAssemblySUCLib/DataAssembly/IndicatorElement/AnaView",
|
|
188
|
+
"TagName": {"value": "TestAnaView1"},
|
|
189
|
+
"TagDescription": {"value": "Test Description 1"},
|
|
190
|
+
},
|
|
191
|
+
"assembly2": {
|
|
192
|
+
"RefBaseSystemUnitPath": "MTPDataAssemblySUCLib/DataAssembly/IndicatorElement/BinView",
|
|
193
|
+
"TagName": {"value": "TestBinView"},
|
|
194
|
+
"TagDescription": {"value": "Test Description 2"},
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
assemblies = DataAssemblyFactory.create_data_assemblies(config_dict)
|
|
199
|
+
|
|
200
|
+
assert len(assemblies) == 2
|
|
201
|
+
assert "assembly1" in assemblies
|
|
202
|
+
assert "assembly2" in assemblies
|
|
203
|
+
assert assemblies["assembly1"].tag_name == "TestAnaView1"
|
|
204
|
+
assert assemblies["assembly2"].tag_name == "TestBinView"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_factory_contains_expected_types():
|
|
208
|
+
"""Test that the factory contains expected types."""
|
|
209
|
+
expected_types = [
|
|
210
|
+
"MTPDataAssemblySUCLib/DataAssembly/ActiveElement/PIDCtrl",
|
|
211
|
+
"MTPDataAssemblySUCLib/DataAssembly/IndicatorElement/AnaView",
|
|
212
|
+
"MTPDataAssemblySUCLib/DataAssembly/IndicatorElement/BinView",
|
|
213
|
+
"MTPDataAssemblySUCLib/DataAssembly/IndicatorElement/DIntView",
|
|
214
|
+
"MTPDataAssemblySUCLib/DataAssembly/ServiceElement/ParameterElement/DIntServParam",
|
|
215
|
+
"MTPDataAssemblySUCLib/DataAssembly/ServiceElement/ParameterElement/AnaServParam",
|
|
216
|
+
"MTPDataAssemblySUCLib/DataAssembly/ServiceElement/ParameterElement/BinServParam",
|
|
217
|
+
"MTPDataAssemblySUCLib/DataAssembly/ServiceElement/ParameterElement/StringServParam",
|
|
218
|
+
"MTPDataAssemblySUCLib/DataAssembly/PeaElement/PeaInformationLabel",
|
|
219
|
+
"MTPDataAssemblySUCLib/DataAssembly/ServiceElement/ProcedureHealthView",
|
|
220
|
+
# TODO: reintroduce after fixing namespace handling
|
|
221
|
+
# "MTPDataAssemblySUCLib/DataAssembly/ServiceElement/ServiceControl",
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
for expected_type in expected_types:
|
|
225
|
+
assert expected_type in DataAssemblyFactory._FACTORY
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
if __name__ == "__main__":
|
|
229
|
+
pytest.main([__file__])
|