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,518 @@
|
|
|
1
|
+
"""test suite for the mtp_generator."""
|
|
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
|
+
import time
|
|
11
|
+
import random
|
|
12
|
+
import xml.etree.ElementTree as ET
|
|
13
|
+
from types import SimpleNamespace
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from mtppy.pea.pea import PEA
|
|
18
|
+
from mtppy.pea.mtp_generator.mtp_generator import MTPGenerator
|
|
19
|
+
from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
|
|
20
|
+
from mtppy.part3.data_assembly_set.active_elements.pid.pid_ctrl import PIDCtrl
|
|
21
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.ana_view import AnaView
|
|
22
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.bin_view import BinView
|
|
23
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.dint_view import DIntView
|
|
24
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.string_view import (
|
|
25
|
+
StringView,
|
|
26
|
+
)
|
|
27
|
+
from mtppy.part4.automation_services.service import Service
|
|
28
|
+
from mtppy.part4.automation_services.procedure import Procedure
|
|
29
|
+
from mtppy.part4.automation_services.service_elements.service_control import (
|
|
30
|
+
ServiceControl,
|
|
31
|
+
)
|
|
32
|
+
from mtppy.part4.automation_services.service_elements.parameter_elements.dint_serv_param import (
|
|
33
|
+
DIntServParam,
|
|
34
|
+
)
|
|
35
|
+
from mtppy.part6.alarm_set.alarm import Alarm
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RandomNumberGenerator(Service):
|
|
39
|
+
def __init__(self, tag_name, tag_description):
|
|
40
|
+
super().__init__(tag_name, tag_description)
|
|
41
|
+
|
|
42
|
+
# Procedure definition
|
|
43
|
+
proc_0 = Procedure(1, "cont", is_self_completing=False)
|
|
44
|
+
|
|
45
|
+
# Adding two procedure parameters
|
|
46
|
+
proc_0.add_procedure_parameter(
|
|
47
|
+
DIntServParam(
|
|
48
|
+
"lower_bound", v_min=0, v_max=100, v_scl_min=0, v_scl_max=100, v_unit=23
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
proc_0.add_procedure_parameter(
|
|
52
|
+
DIntServParam(
|
|
53
|
+
"upper_bound", v_min=0, v_max=100, v_scl_min=0, v_scl_max=100, v_unit=23
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Adding procedure report value
|
|
58
|
+
proc_0.add_report_value(
|
|
59
|
+
AnaView("generated_value", v_scl_min=0, v_scl_max=100, v_unit=23),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Allocating procedure to the service
|
|
63
|
+
self.add_procedure(proc_0)
|
|
64
|
+
|
|
65
|
+
def idle(self):
|
|
66
|
+
print("- Idle -")
|
|
67
|
+
cycle = 0
|
|
68
|
+
while self.is_state("idle"):
|
|
69
|
+
print(f"Idle cycle {cycle}")
|
|
70
|
+
print("Doing nothing...")
|
|
71
|
+
cycle += 1
|
|
72
|
+
time.sleep(1)
|
|
73
|
+
|
|
74
|
+
def starting(self):
|
|
75
|
+
print("- Starting -")
|
|
76
|
+
print("Applying procedure parameters...")
|
|
77
|
+
self.state_change()
|
|
78
|
+
|
|
79
|
+
def execute(self):
|
|
80
|
+
print("- Execute -")
|
|
81
|
+
cycle = 0
|
|
82
|
+
while self.is_state("execute"):
|
|
83
|
+
print(f"Execute cycle {cycle}")
|
|
84
|
+
|
|
85
|
+
# Read procedure parameters
|
|
86
|
+
lower_bound = (
|
|
87
|
+
self.service_control.procedures[0]
|
|
88
|
+
.procedure_parameters["lower_bound"]
|
|
89
|
+
.get_v_out()
|
|
90
|
+
)
|
|
91
|
+
upper_bound = (
|
|
92
|
+
self.service_control.procedures[0]
|
|
93
|
+
.procedure_parameters["upper_bound"]
|
|
94
|
+
.get_v_out()
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Execute random number generation
|
|
98
|
+
generated_number = random.randint(lower_bound, upper_bound)
|
|
99
|
+
|
|
100
|
+
# Return report value
|
|
101
|
+
self.service_control.procedures[0].report_values["generated_value"].set_v(
|
|
102
|
+
generated_number
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
cycle += 1
|
|
106
|
+
time.sleep(0.1)
|
|
107
|
+
|
|
108
|
+
def completing(self):
|
|
109
|
+
self.state_change()
|
|
110
|
+
|
|
111
|
+
def completed(self):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
def pausing(self):
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
def paused(self):
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
def resuming(self):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
def holding(self):
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
def held(self):
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
def unholding(self):
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
def stopping(self):
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
def stopped(self):
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
def aborting(self):
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
def aborted(self):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
def resetting(self):
|
|
145
|
+
print("- Resetting -")
|
|
146
|
+
self.state_change()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def init_empty_pea():
|
|
150
|
+
pea = PEA(endpoint="opc.tcp://localhost:4060/")
|
|
151
|
+
|
|
152
|
+
# Service definition
|
|
153
|
+
service_1 = RandomNumberGenerator(
|
|
154
|
+
"rand_num_gen", "This services generates random number"
|
|
155
|
+
)
|
|
156
|
+
pea.add_service(service_1)
|
|
157
|
+
|
|
158
|
+
# Active element
|
|
159
|
+
pid_ctrl = PIDCtrl("pid_ctrl")
|
|
160
|
+
pea.add_data_assembly(pid_ctrl)
|
|
161
|
+
|
|
162
|
+
return pea
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_mtp_generator():
|
|
166
|
+
pea = init_empty_pea()
|
|
167
|
+
try:
|
|
168
|
+
pea.start()
|
|
169
|
+
mtp_generator = MTPGenerator()
|
|
170
|
+
mtp_generator.generate(pea)
|
|
171
|
+
finally:
|
|
172
|
+
pea.pea_opcua_server.opcua_server.stop()
|
|
173
|
+
time.sleep(1)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _capture_manifest(pea: PEA) -> ET.Element:
|
|
177
|
+
"""Generate and parse the manifest without writing an MTP archive."""
|
|
178
|
+
mtp_generator = MTPGenerator()
|
|
179
|
+
captured: list[bytes] = []
|
|
180
|
+
|
|
181
|
+
def capture(content: bytes, **_kwargs: object) -> None:
|
|
182
|
+
captured.append(content)
|
|
183
|
+
|
|
184
|
+
mtp_generator._mtp_writer.write_mtp = capture # noqa: SLF001
|
|
185
|
+
mtp_generator.generate(pea)
|
|
186
|
+
|
|
187
|
+
assert len(captured) == 1
|
|
188
|
+
return ET.fromstring(captured[0]) # noqa: S314
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _required_element(parent: ET.Element, path: str) -> ET.Element:
|
|
192
|
+
"""Return a required XML element with a useful assertion failure."""
|
|
193
|
+
element = parent.find(path)
|
|
194
|
+
assert element is not None, f"Missing generated element: {path}"
|
|
195
|
+
return element
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_generate_serializes_alarm_set_base_with_resolved_links() -> None:
|
|
199
|
+
"""Generated Base alarms link their group, trigger and message targets."""
|
|
200
|
+
pea = PEA()
|
|
201
|
+
trigger_assembly = BinView("LevelHigh")
|
|
202
|
+
trigger_item = trigger_assembly.get_item(BinView.V_)
|
|
203
|
+
# Model the existing node hosted by the PEA OPC UA server. This is not a
|
|
204
|
+
# connection to an external OPC UA device.
|
|
205
|
+
trigger_item.comm_obj = SimpleNamespace(
|
|
206
|
+
_node_id=SimpleNamespace(
|
|
207
|
+
nodeid=SimpleNamespace(Identifier="LevelHigh.V"),
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
pea.add_data_assembly(trigger_assembly)
|
|
211
|
+
pea.add_alarm(
|
|
212
|
+
Alarm(
|
|
213
|
+
tag_name="LevelHigh.Alarm",
|
|
214
|
+
data_assembly=trigger_assembly,
|
|
215
|
+
trigger=trigger_item,
|
|
216
|
+
trigger_value=True,
|
|
217
|
+
message="Level is too high.",
|
|
218
|
+
ack_required=True,
|
|
219
|
+
severity=500,
|
|
220
|
+
),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
root = _capture_manifest(pea)
|
|
224
|
+
data_assembly = _required_element(
|
|
225
|
+
root,
|
|
226
|
+
".//{*}InstanceHierarchy[@Name='DataAssemblies']/"
|
|
227
|
+
"{*}InternalElement[@Name='LevelHigh']",
|
|
228
|
+
)
|
|
229
|
+
trigger = _required_element(
|
|
230
|
+
data_assembly,
|
|
231
|
+
"{*}Attribute[@Name='V']",
|
|
232
|
+
)
|
|
233
|
+
alarm_group = _required_element(
|
|
234
|
+
root,
|
|
235
|
+
".//{*}InstanceHierarchy[@Name='Alarms']/"
|
|
236
|
+
"{*}InternalElement[@RefBaseSystemUnitPath='MTPAlarmSUCLib/AlarmGroup']",
|
|
237
|
+
)
|
|
238
|
+
alarm = _required_element(
|
|
239
|
+
alarm_group,
|
|
240
|
+
"{*}InternalElement[@RefBaseSystemUnitPath='MTPAlarmSUCLib/Alarm']",
|
|
241
|
+
)
|
|
242
|
+
message = _required_element(
|
|
243
|
+
root,
|
|
244
|
+
".//{*}InstanceHierarchy[@Name='Texts']/"
|
|
245
|
+
"{*}InternalElement["
|
|
246
|
+
"@RefBaseSystemUnitPath='MTPTextSUCLib/TextDefinition/AlarmMessage']",
|
|
247
|
+
)
|
|
248
|
+
alarm_text = _required_element(
|
|
249
|
+
message,
|
|
250
|
+
"{*}InternalElement["
|
|
251
|
+
"@RefBaseSystemUnitPath="
|
|
252
|
+
"'MTPTextSUCLib/TextDefinition/AlarmMessage/AlarmText']",
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
assert alarm_group.get("Name") == "LevelHigh_AlarmGroup"
|
|
256
|
+
assert alarm_group.findtext("{*}Attribute[@Name='RefID']/{*}Value") == (
|
|
257
|
+
data_assembly.findtext("{*}Attribute[@Name='RefID']/{*}Value")
|
|
258
|
+
)
|
|
259
|
+
assert alarm.get("Name") == "LevelHigh.Alarm"
|
|
260
|
+
assert alarm.findtext("{*}Attribute[@Name='AckRequired']/{*}Value") == "true"
|
|
261
|
+
assert alarm.findtext("{*}Attribute[@Name='Severity']/{*}Value") == "500"
|
|
262
|
+
assert alarm.findtext("{*}Attribute[@Name='Trigger']/{*}Value") == trigger.get(
|
|
263
|
+
"ID",
|
|
264
|
+
)
|
|
265
|
+
assert trigger.get("AttributeDataType") == "xs:string"
|
|
266
|
+
assert alarm.findtext("{*}Attribute[@Name='TriggerValue']/{*}Value") == ("true")
|
|
267
|
+
assert alarm.findtext("{*}Attribute[@Name='MessageRef']/{*}Value") == (
|
|
268
|
+
message.get("ID")
|
|
269
|
+
)
|
|
270
|
+
assert alarm_text.findtext("{*}Attribute[@Name='Text']/{*}Value") == (
|
|
271
|
+
"Level is too high."
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
trigger_reference = _required_element(
|
|
275
|
+
alarm,
|
|
276
|
+
"{*}Attribute[@Name='Trigger']",
|
|
277
|
+
)
|
|
278
|
+
message_reference = _required_element(
|
|
279
|
+
alarm,
|
|
280
|
+
"{*}Attribute[@Name='MessageRef']",
|
|
281
|
+
)
|
|
282
|
+
trigger_value = _required_element(
|
|
283
|
+
alarm,
|
|
284
|
+
"{*}Attribute[@Name='TriggerValue']",
|
|
285
|
+
)
|
|
286
|
+
classification = _required_element(
|
|
287
|
+
alarm,
|
|
288
|
+
"{*}Attribute[@Name='Classification']",
|
|
289
|
+
)
|
|
290
|
+
assert trigger_reference.get("RefAttributeType") == (
|
|
291
|
+
"MTPATLib/IDReferenceType/IDLinkAttributeType"
|
|
292
|
+
)
|
|
293
|
+
assert message_reference.get("RefAttributeType") == (
|
|
294
|
+
"MTPTextATLib/TextRefAttributeType"
|
|
295
|
+
)
|
|
296
|
+
assert trigger_value.get("AttributeDataType") == "xs:boolean"
|
|
297
|
+
assert classification.get("RefAttributeType") == (
|
|
298
|
+
"AutomationMLBaseAttributeTypeLib/OrderedListType"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
object_ids = {element.get("ID") for element in root.iter() if element.get("ID")}
|
|
302
|
+
assert trigger_reference.findtext("{*}Value") in object_ids
|
|
303
|
+
assert message_reference.findtext("{*}Value") in object_ids
|
|
304
|
+
pea_server_reference = trigger.findtext("{*}Value")
|
|
305
|
+
pea_server_item = _required_element(
|
|
306
|
+
root,
|
|
307
|
+
".//{*}InstanceHierarchy[@Name='ServerAssemblies']/"
|
|
308
|
+
"{*}InternalElement/"
|
|
309
|
+
f"{{*}}ExternalInterface[@ID='{pea_server_reference}']",
|
|
310
|
+
)
|
|
311
|
+
assert pea_server_item.findtext(
|
|
312
|
+
"{*}Attribute[@Name='Identifier']/{*}Value",
|
|
313
|
+
) == ("LevelHigh.V")
|
|
314
|
+
assert alarm.find("{*}Attribute[@Name='AlarmMgmtRef']") is None
|
|
315
|
+
assert (
|
|
316
|
+
alarm.find(
|
|
317
|
+
"{*}SupportedRoleClass[@RefRoleClassPath='MTPAlarmRCLib/IsManaged']",
|
|
318
|
+
)
|
|
319
|
+
is None
|
|
320
|
+
)
|
|
321
|
+
assert (
|
|
322
|
+
root.find(
|
|
323
|
+
".//{*}InternalElement["
|
|
324
|
+
"@RefBaseSystemUnitPath="
|
|
325
|
+
"'MTPDataAssemblySUCLib/DataAssembly/AlarmElement/AlarmMgmt']",
|
|
326
|
+
)
|
|
327
|
+
is None
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def test_generate_rejects_alarm_with_unregistered_data_assembly() -> None:
|
|
332
|
+
"""Generator fails instead of emitting a dangling AlarmGroup RefID."""
|
|
333
|
+
pea = PEA()
|
|
334
|
+
trigger_assembly = BinView("NotRegistered")
|
|
335
|
+
pea.add_alarm(
|
|
336
|
+
Alarm(
|
|
337
|
+
tag_name="NotRegistered.Alarm",
|
|
338
|
+
data_assembly=trigger_assembly,
|
|
339
|
+
trigger=trigger_assembly.get_item(BinView.V_),
|
|
340
|
+
trigger_value=True,
|
|
341
|
+
message="Unregistered trigger.",
|
|
342
|
+
ack_required=False,
|
|
343
|
+
severity=100,
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
mtp_generator = MTPGenerator()
|
|
347
|
+
mtp_generator._mtp_writer.write_mtp = lambda *_args, **_kwargs: None # noqa: SLF001
|
|
348
|
+
|
|
349
|
+
with pytest.raises(ValueError, match="registered DataAssembly"):
|
|
350
|
+
mtp_generator.generate(pea)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def test_generate_revalidates_mutated_alarm_trigger_type() -> None:
|
|
354
|
+
"""Generator rejects an Alarm whose mutable DataItem changed after creation."""
|
|
355
|
+
pea = PEA()
|
|
356
|
+
trigger_assembly = BinView("Mutated")
|
|
357
|
+
alarm = Alarm(
|
|
358
|
+
tag_name="Mutated.Alarm",
|
|
359
|
+
data_assembly=trigger_assembly,
|
|
360
|
+
trigger=trigger_assembly.get_item(BinView.V_),
|
|
361
|
+
trigger_value=True,
|
|
362
|
+
message="Mutated trigger.",
|
|
363
|
+
ack_required=False,
|
|
364
|
+
severity=100,
|
|
365
|
+
)
|
|
366
|
+
pea.add_data_assembly(trigger_assembly)
|
|
367
|
+
pea.add_alarm(alarm)
|
|
368
|
+
alarm.trigger.w3c_xml_data_type = W3cXmlDataTypes.FLOAT
|
|
369
|
+
mtp_generator = MTPGenerator()
|
|
370
|
+
mtp_generator._mtp_writer.write_mtp = lambda *_args, **_kwargs: None # noqa: SLF001
|
|
371
|
+
|
|
372
|
+
with pytest.raises(TypeError, match="trigger type"):
|
|
373
|
+
mtp_generator.generate(pea)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def test_alarm_links_stay_with_multiply_registered_data_assembly() -> None:
|
|
377
|
+
"""Alarm group and trigger links resolve within the same DataAssembly."""
|
|
378
|
+
pea = PEA()
|
|
379
|
+
service = RandomNumberGenerator(
|
|
380
|
+
"DuplicatedService",
|
|
381
|
+
"Service control registered through two public paths",
|
|
382
|
+
)
|
|
383
|
+
target = service.service_control
|
|
384
|
+
trigger = target.get_item(ServiceControl.PROC_PARAM_APPLY_EN)
|
|
385
|
+
pea.add_data_assembly(target)
|
|
386
|
+
pea.add_service(service)
|
|
387
|
+
pea.add_alarm(
|
|
388
|
+
Alarm(
|
|
389
|
+
tag_name="DuplicatedService.Alarm",
|
|
390
|
+
data_assembly=target,
|
|
391
|
+
trigger=trigger,
|
|
392
|
+
trigger_value=False,
|
|
393
|
+
message="Duplicate target.",
|
|
394
|
+
ack_required=False,
|
|
395
|
+
severity=100,
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
root = _capture_manifest(pea)
|
|
400
|
+
data_assemblies = root.findall(
|
|
401
|
+
".//{*}InstanceHierarchy[@Name='DataAssemblies']/"
|
|
402
|
+
"{*}InternalElement[@Name='DuplicatedService']",
|
|
403
|
+
)
|
|
404
|
+
alarm_group = _required_element(
|
|
405
|
+
root,
|
|
406
|
+
".//{*}InstanceHierarchy[@Name='Alarms']/"
|
|
407
|
+
"{*}InternalElement[@RefBaseSystemUnitPath='MTPAlarmSUCLib/AlarmGroup']",
|
|
408
|
+
)
|
|
409
|
+
alarm = _required_element(
|
|
410
|
+
alarm_group,
|
|
411
|
+
"{*}InternalElement[@RefBaseSystemUnitPath='MTPAlarmSUCLib/Alarm']",
|
|
412
|
+
)
|
|
413
|
+
group_ref = alarm_group.findtext("{*}Attribute[@Name='RefID']/{*}Value")
|
|
414
|
+
trigger_id = alarm.findtext("{*}Attribute[@Name='Trigger']/{*}Value")
|
|
415
|
+
referenced_assemblies = [
|
|
416
|
+
assembly
|
|
417
|
+
for assembly in data_assemblies
|
|
418
|
+
if assembly.findtext("{*}Attribute[@Name='RefID']/{*}Value") == group_ref
|
|
419
|
+
]
|
|
420
|
+
|
|
421
|
+
assert len(referenced_assemblies) == 1
|
|
422
|
+
assert (
|
|
423
|
+
referenced_assemblies[0].find(
|
|
424
|
+
f"{{*}}Attribute[@ID='{trigger_id}']",
|
|
425
|
+
)
|
|
426
|
+
is not None
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def test_generate_groups_alarms_by_referenced_data_assembly() -> None:
|
|
431
|
+
"""Alarms sharing a DataAssembly share one generated AlarmGroup."""
|
|
432
|
+
pea = PEA()
|
|
433
|
+
first_assembly = BinView("First")
|
|
434
|
+
second_assembly = BinView("Second")
|
|
435
|
+
pea.add_data_assembly(first_assembly)
|
|
436
|
+
pea.add_data_assembly(second_assembly)
|
|
437
|
+
for tag_name, assembly, trigger_value in (
|
|
438
|
+
("First.High", first_assembly, True),
|
|
439
|
+
("First.Normal", first_assembly, False),
|
|
440
|
+
("Second.High", second_assembly, True),
|
|
441
|
+
):
|
|
442
|
+
pea.add_alarm(
|
|
443
|
+
Alarm(
|
|
444
|
+
tag_name=tag_name,
|
|
445
|
+
data_assembly=assembly,
|
|
446
|
+
trigger=assembly.get_item(BinView.V_),
|
|
447
|
+
trigger_value=trigger_value,
|
|
448
|
+
message=f"{tag_name} message.",
|
|
449
|
+
ack_required=False,
|
|
450
|
+
severity=100,
|
|
451
|
+
),
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
root = _capture_manifest(pea)
|
|
455
|
+
alarm_hierarchy = _required_element(
|
|
456
|
+
root,
|
|
457
|
+
".//{*}InstanceHierarchy[@Name='Alarms']",
|
|
458
|
+
)
|
|
459
|
+
groups = alarm_hierarchy.findall(
|
|
460
|
+
"{*}InternalElement[@RefBaseSystemUnitPath='MTPAlarmSUCLib/AlarmGroup']",
|
|
461
|
+
)
|
|
462
|
+
assert {group.get("Name") for group in groups} == {
|
|
463
|
+
"First_AlarmGroup",
|
|
464
|
+
"Second_AlarmGroup",
|
|
465
|
+
}
|
|
466
|
+
alarm_counts = {
|
|
467
|
+
group.get("Name"): len(
|
|
468
|
+
group.findall(
|
|
469
|
+
"{*}InternalElement[" "@RefBaseSystemUnitPath='MTPAlarmSUCLib/Alarm']",
|
|
470
|
+
),
|
|
471
|
+
)
|
|
472
|
+
for group in groups
|
|
473
|
+
}
|
|
474
|
+
assert alarm_counts == {"First_AlarmGroup": 2, "Second_AlarmGroup": 1}
|
|
475
|
+
|
|
476
|
+
first_data_assembly = _required_element(
|
|
477
|
+
root,
|
|
478
|
+
".//{*}InstanceHierarchy[@Name='DataAssemblies']/"
|
|
479
|
+
"{*}InternalElement[@Name='First']",
|
|
480
|
+
)
|
|
481
|
+
assert len(first_data_assembly.findall("{*}Attribute[@Name='V']")) == 1
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@pytest.mark.parametrize(
|
|
485
|
+
("assembly_type", "trigger_value", "xml_type"),
|
|
486
|
+
[(DIntView, 12, "xs:short"), (StringView, "ALARM", "xs:string")],
|
|
487
|
+
)
|
|
488
|
+
def test_generate_preserves_trigger_value_xml_type(
|
|
489
|
+
assembly_type: type[DIntView] | type[StringView],
|
|
490
|
+
trigger_value: int | str,
|
|
491
|
+
xml_type: str,
|
|
492
|
+
) -> None:
|
|
493
|
+
"""TriggerValue carries the concrete W3C type of integer and string data."""
|
|
494
|
+
pea = PEA()
|
|
495
|
+
trigger_assembly = assembly_type("TypedTrigger")
|
|
496
|
+
pea.add_data_assembly(trigger_assembly)
|
|
497
|
+
pea.add_alarm(
|
|
498
|
+
Alarm(
|
|
499
|
+
tag_name="TypedTrigger.Alarm",
|
|
500
|
+
data_assembly=trigger_assembly,
|
|
501
|
+
trigger=trigger_assembly.get_item(trigger_assembly.V_),
|
|
502
|
+
trigger_value=trigger_value,
|
|
503
|
+
message="Typed trigger.",
|
|
504
|
+
ack_required=False,
|
|
505
|
+
severity=100,
|
|
506
|
+
),
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
root = _capture_manifest(pea)
|
|
510
|
+
trigger_value_attribute = _required_element(
|
|
511
|
+
root,
|
|
512
|
+
".//{*}InstanceHierarchy[@Name='Alarms']/"
|
|
513
|
+
"{*}InternalElement[@RefBaseSystemUnitPath='MTPAlarmSUCLib/AlarmGroup']/"
|
|
514
|
+
"{*}InternalElement[@RefBaseSystemUnitPath='MTPAlarmSUCLib/Alarm']/"
|
|
515
|
+
"{*}Attribute[@Name='TriggerValue']",
|
|
516
|
+
)
|
|
517
|
+
assert trigger_value_attribute.get("AttributeDataType") == xml_type
|
|
518
|
+
assert trigger_value_attribute.findtext("{*}Value") == str(trigger_value)
|
mtppy/pea/pea.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""The PEA (Process Equipment Assembly)."""
|
|
2
|
+
|
|
3
|
+
__copyright__ = (
|
|
4
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
5
|
+
)
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
|
|
8
|
+
from mtppy.pea.pea_opcua_server.opcua_options_provider.instances.basic_opcua_options_provider import (
|
|
9
|
+
BasicOPCUAOptionsProvider,
|
|
10
|
+
)
|
|
11
|
+
from mtppy.pea.pea_opcua_server.pea_opcua_server import PeaOPCUAServer
|
|
12
|
+
from mtppy.pea.pea_opcua_server.subscription_list import SubscriptionList
|
|
13
|
+
|
|
14
|
+
from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
|
|
15
|
+
from mtppy.part3.data_assembly_set.indicator_elements.indicator_element import (
|
|
16
|
+
IndicatorElement,
|
|
17
|
+
)
|
|
18
|
+
from mtppy.part3.data_assembly_set.pea_elements.pea_information_label.pea_information_label import (
|
|
19
|
+
PeaInformationLabel,
|
|
20
|
+
)
|
|
21
|
+
from mtppy.part4.process_values.input_elements.input_element import InputElement
|
|
22
|
+
from mtppy.part4.automation_services.service import Service
|
|
23
|
+
from mtppy.part6.alarm_set.alarm import Alarm
|
|
24
|
+
|
|
25
|
+
from mtppy.utils.data_assembly_dict import DataAssemblyDict
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PEA:
|
|
29
|
+
"""Main Process Equipment Assembly (PEA) class.
|
|
30
|
+
|
|
31
|
+
The PEA class serves as the central coordinator for managing all aspects of a
|
|
32
|
+
Process Element Assembly, including data assemblies, process values, services,
|
|
33
|
+
and OPC UA server functionality. It provides methods to add components and control
|
|
34
|
+
the overall operation of the PEA system.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
service_set: Dictionary of service instances managed by this PEA
|
|
38
|
+
data_assemblies: Dictionary of data assemblies managed by this PEA
|
|
39
|
+
process_value_ins: Dictionary of input process values managed by this PEA
|
|
40
|
+
process_value_outs: Dictionary of output process values managed by this PEA
|
|
41
|
+
alarms: Dictionary of alarm instances managed by this PEA
|
|
42
|
+
endpoint: OPC UA server endpoint address
|
|
43
|
+
opcua_options_provider: Provider for OPC UA server options
|
|
44
|
+
pea_opcua_server: OPC UA server instance for this PEA
|
|
45
|
+
subscription_list: List of active subscriptions
|
|
46
|
+
web_server_url: URL for web server access
|
|
47
|
+
pea_information_label: Information label containing PEA metadata
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, endpoint: str = "opc.tcp://localhost:4840/") -> None:
|
|
51
|
+
"""Initializes the PEA with default or specified OPC UA endpoint.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
endpoint: Endpoint of the OPC UA server. If not specified, opc.tcp://localhost:4840/ is used.
|
|
55
|
+
"""
|
|
56
|
+
self.service_set = DataAssemblyDict(Service)
|
|
57
|
+
self.data_assemblies = DataAssemblyDict(DataAssembly)
|
|
58
|
+
self.process_value_ins = DataAssemblyDict(InputElement)
|
|
59
|
+
self.process_value_outs = DataAssemblyDict(IndicatorElement)
|
|
60
|
+
self.alarms = DataAssemblyDict(Alarm)
|
|
61
|
+
self.endpoint = endpoint
|
|
62
|
+
self.opcua_options_provider = BasicOPCUAOptionsProvider()
|
|
63
|
+
self.pea_opcua_server = PeaOPCUAServer(
|
|
64
|
+
self.opcua_options_provider, self.endpoint
|
|
65
|
+
)
|
|
66
|
+
self.subscription_list = SubscriptionList()
|
|
67
|
+
self.web_server_url = None
|
|
68
|
+
|
|
69
|
+
self.pea_information_label = PeaInformationLabel("PeaInformationLabel", "")
|
|
70
|
+
|
|
71
|
+
def add_data_assembly(
|
|
72
|
+
self,
|
|
73
|
+
data_assembly: DataAssembly | list[DataAssembly] | dict[str, DataAssembly],
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Add a DataAssembly to the PEA.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
data_assembly: The DataAssembly instance to add
|
|
79
|
+
"""
|
|
80
|
+
self.data_assemblies.add(data_assembly)
|
|
81
|
+
|
|
82
|
+
def add_alarm(self, alarm: Alarm) -> None:
|
|
83
|
+
"""Add a static AlarmSet.Base alarm to the PEA.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
alarm: Alarm description to register.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
TypeError: If alarm is not an Alarm instance.
|
|
90
|
+
ValueError: If an alarm with the same tag name is already registered.
|
|
91
|
+
"""
|
|
92
|
+
if not isinstance(alarm, Alarm):
|
|
93
|
+
msg = f"{getattr(alarm, 'tag_name', repr(alarm))} is not an Alarm"
|
|
94
|
+
raise TypeError(msg)
|
|
95
|
+
if alarm.tag_name in self.alarms:
|
|
96
|
+
msg = f"Alarm {alarm.tag_name!r} is already registered"
|
|
97
|
+
raise ValueError(msg)
|
|
98
|
+
self.alarms.add(alarm)
|
|
99
|
+
|
|
100
|
+
def add_process_value_in(self, process_value_in: InputElement):
|
|
101
|
+
"""Add an input ProcessValue to the PEA.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
process_value_in: The InputElement instance to add as input process value
|
|
105
|
+
"""
|
|
106
|
+
self.process_value_ins.add(process_value_in)
|
|
107
|
+
|
|
108
|
+
def add_process_value_out(self, process_value_out: IndicatorElement):
|
|
109
|
+
"""Add an output ProcessValue to the PEA.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
process_value_out: The output process value to add.
|
|
113
|
+
"""
|
|
114
|
+
self.process_value_outs.add(process_value_out)
|
|
115
|
+
|
|
116
|
+
def add_service(self, service: Service):
|
|
117
|
+
"""Add a Service to the PEA.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
service: The Service instance to add
|
|
121
|
+
"""
|
|
122
|
+
self.service_set.add(service)
|
|
123
|
+
|
|
124
|
+
def start(self):
|
|
125
|
+
"""Start the OPC UA server instance and begin PEA operation.
|
|
126
|
+
|
|
127
|
+
Initializes and starts the OPC UA server with the configured data assemblies
|
|
128
|
+
and services, making the PEA available for communication and operation.
|
|
129
|
+
"""
|
|
130
|
+
self.pea_opcua_server.init_opcua_server()
|
|
131
|
+
self.pea_opcua_server.run_opcua_server(self.data_assemblies, self.service_set)
|
|
132
|
+
|
|
133
|
+
def stop(self):
|
|
134
|
+
"""Stop the OPC UA server instance and shut down PEA operation.
|
|
135
|
+
|
|
136
|
+
Stops the OPC UA server and performs cleanup operations to properly
|
|
137
|
+
terminate the PEA's operation.
|
|
138
|
+
"""
|
|
139
|
+
self.pea_opcua_server.opcua_server.stop()
|