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
examples/__init__.py
ADDED
|
File without changes
|
examples/minimal+pid.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
__copyright__ = (
|
|
2
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
3
|
+
)
|
|
4
|
+
__license__ = "MIT"
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
|
|
8
|
+
from mtppy.part4.automation_services.service import Service
|
|
9
|
+
from mtppy.pea.user_logic.user_logic_registry import register
|
|
10
|
+
from mtppy.utils.logging import logger
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
Prompt für Service SERV, Prozedur PROC und Zustand execute.do:
|
|
14
|
+
Lese obere und untere Grenze aus den Parametern lower_bound
|
|
15
|
+
und upper_bound aus, erzeuge eine Integerzufallszahl in diesen
|
|
16
|
+
Grenzen und schreibe das Ergebnis in die Reportvariable
|
|
17
|
+
generated_value
|
|
18
|
+
"""
|
|
19
|
+
PROC = "cont"
|
|
20
|
+
SERV = "rand_num_gen"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@register(f"{SERV}.{PROC}.execute.do")
|
|
24
|
+
def random_number(self: Service) -> bool:
|
|
25
|
+
lb = self.get_param(PROC, "lower_bound")
|
|
26
|
+
ub = self.get_param(PROC, "upper_bound")
|
|
27
|
+
gn = self.get_param(PROC, "generated_value")
|
|
28
|
+
|
|
29
|
+
gn.V = random.randint(lb.VOut, ub.VOut)
|
|
30
|
+
|
|
31
|
+
logger.app(f"--- generated: {gn.V}")
|
|
32
|
+
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
Prompt für Service "control", Prozedur "ctrl" und Zustand execute.entry:
|
|
38
|
+
Es soll der Prozedurparameter "target_value" ausgelesen werden
|
|
39
|
+
und an den Setpoint des DataAssembly "pid_ctrl" geschickt werden
|
|
40
|
+
"""
|
|
41
|
+
CTRL_PROC = "ctrl"
|
|
42
|
+
CTRL_SERV = "control"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@register(f"{CTRL_SERV}.{CTRL_PROC}.execute.entry")
|
|
46
|
+
def set_target_value(self: Service) -> bool:
|
|
47
|
+
# Read target_value parameter
|
|
48
|
+
target_value = self.get_param(CTRL_PROC, "target_value")
|
|
49
|
+
pid_ctrl = self.ressources["pid_ctrl"]
|
|
50
|
+
|
|
51
|
+
pid_ctrl.SetSpMan = target_value.VOut
|
|
52
|
+
|
|
53
|
+
logger.app(f"--- Target value set to {pid_ctrl.SetSpMan} in PID controller")
|
|
54
|
+
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
Prompt für Service "control", Prozedur "ctrl" und Zustand execute.run:
|
|
60
|
+
Es soll ein Schritt des Regelalgorithmus ausgeführt werden
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@register(f"{CTRL_SERV}.{CTRL_PROC}.execute.run")
|
|
65
|
+
def run_regulation_algorithm(self: Service) -> bool:
|
|
66
|
+
pid_ctrl = self.ressources["pid_ctrl"]
|
|
67
|
+
current_value = self.get_param(CTRL_PROC, "current_value")
|
|
68
|
+
mv_report = self.get_param(CTRL_PROC, "manipulated_value")
|
|
69
|
+
|
|
70
|
+
# Set process value in PID controller
|
|
71
|
+
pid_ctrl.PV = current_value.VOut
|
|
72
|
+
|
|
73
|
+
# Get the calculated manipulated value
|
|
74
|
+
mv = pid_ctrl.MV
|
|
75
|
+
|
|
76
|
+
# In a real system, this would send the MV to an actuator
|
|
77
|
+
logger.app(f"--- PID calculation: PV={current_value.VOut}, MV={mv}")
|
|
78
|
+
|
|
79
|
+
# Update the report value with the manipulated value
|
|
80
|
+
mv_report.V = mv
|
|
81
|
+
|
|
82
|
+
return True
|
examples/minimal.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
__copyright__ = (
|
|
2
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
3
|
+
)
|
|
4
|
+
__license__ = "MIT"
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
|
|
8
|
+
from mtppy.utils.logging import logger
|
|
9
|
+
from mtppy.part4.automation_services.service import Service
|
|
10
|
+
from mtppy.pea.user_logic.user_logic_registry import register
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
Prompt für rand_num_gen.cont.execute.do:
|
|
14
|
+
Lese obere und untere Grenze aus den Parametern lower_bound
|
|
15
|
+
und upper_bound aus, erzeuge eine Integerzufallszahl in diesen
|
|
16
|
+
Grenzen und schreibe das Ergebnis in die Reportvariable
|
|
17
|
+
generated_value.
|
|
18
|
+
Lade erst alle parameter-objekte und rechne anschließend.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@register(f"rand_num_gen.cont.execute.do")
|
|
23
|
+
def random_number(context: Service) -> bool:
|
|
24
|
+
lb = context.get_param("lower_bound")
|
|
25
|
+
ub = context.get_param("upper_bound")
|
|
26
|
+
gn = context.get_param("generated_value")
|
|
27
|
+
|
|
28
|
+
gn.V = random.randint(lb.VOut, ub.VOut)
|
|
29
|
+
|
|
30
|
+
logger.app(f"--- generated: {gn.V} in [{lb.VOut},{ub.VOut}]")
|
|
31
|
+
|
|
32
|
+
return True
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""POL recipe for the minimal random-number-generator example."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from opcua import Client # pyright: ignore
|
|
8
|
+
from mtppy.part4.definitions.command_codes import CommandCodes as CC
|
|
9
|
+
from mtppy.utils.logging import CHOICES, logger, setLogLevel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run(
|
|
13
|
+
client: Any,
|
|
14
|
+
namespace_index: str,
|
|
15
|
+
duration: float = 10.0,
|
|
16
|
+
lower_bound_vop: int = 40,
|
|
17
|
+
upper_bound_vop: int = 60,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Run the recipe against an already connected OPC UA client."""
|
|
20
|
+
|
|
21
|
+
def write(command: str, value: object) -> None:
|
|
22
|
+
node_id = f"ns={namespace_index};s={command}"
|
|
23
|
+
client.get_node(node_id).set_value(value)
|
|
24
|
+
|
|
25
|
+
logger.pol("--- Set procedure parameters to Operator mode ---")
|
|
26
|
+
write("lower_bound.StateOpOp", True)
|
|
27
|
+
write("upper_bound.StateOpOp", True)
|
|
28
|
+
time.sleep(1)
|
|
29
|
+
|
|
30
|
+
logger.pol("--- Set procedure parameter values ---")
|
|
31
|
+
write("lower_bound.VOp", lower_bound_vop)
|
|
32
|
+
write("upper_bound.VOp", upper_bound_vop)
|
|
33
|
+
time.sleep(1)
|
|
34
|
+
|
|
35
|
+
logger.pol("--- Set service to Operator mode ---")
|
|
36
|
+
write("rand_num_gen.StateOpOp", True)
|
|
37
|
+
time.sleep(1)
|
|
38
|
+
|
|
39
|
+
logger.pol("--- Start service ---")
|
|
40
|
+
write("rand_num_gen.ProcedureOp", 1)
|
|
41
|
+
write("rand_num_gen.CommandOp", CC.START.code)
|
|
42
|
+
time.sleep(duration)
|
|
43
|
+
|
|
44
|
+
logger.pol("--- Complete service ---")
|
|
45
|
+
write("rand_num_gen.CommandOp", CC.COMPLETE.code)
|
|
46
|
+
time.sleep(1)
|
|
47
|
+
|
|
48
|
+
logger.pol("--- Reset service ---")
|
|
49
|
+
write("rand_num_gen.CommandOp", CC.RESET.code)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def parse_args(args: list[str] | None = None) -> argparse.Namespace:
|
|
53
|
+
"""Parse command-line options for the minimal OPC UA recipe."""
|
|
54
|
+
parser = argparse.ArgumentParser("minimal MTP OPC UA recipe")
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"-e",
|
|
57
|
+
"--endpoint",
|
|
58
|
+
required=True,
|
|
59
|
+
help="OPC UA endpoint of the running PEA",
|
|
60
|
+
)
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
"-n",
|
|
63
|
+
"--namespace-index",
|
|
64
|
+
required=True,
|
|
65
|
+
help="OPC UA namespace index used by the PEA nodes",
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"-d",
|
|
69
|
+
"--duration",
|
|
70
|
+
type=float,
|
|
71
|
+
default=10.0,
|
|
72
|
+
help="seconds to leave the service running",
|
|
73
|
+
)
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"-l",
|
|
76
|
+
"--log-level",
|
|
77
|
+
default="MTP",
|
|
78
|
+
choices=["DEBUG", "INFO", *CHOICES, "WARNING", "ERROR", "CRITICAL"],
|
|
79
|
+
help="logging level (default: MTP)",
|
|
80
|
+
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"--lower-bound",
|
|
83
|
+
type=int,
|
|
84
|
+
default=40,
|
|
85
|
+
help="value written to lower_bound.VOp (default: 40)",
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"--upper-bound",
|
|
89
|
+
type=int,
|
|
90
|
+
default=60,
|
|
91
|
+
help="value written to upper_bound.VOp (default: 60)",
|
|
92
|
+
)
|
|
93
|
+
return parser.parse_args(args)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main(args: list[str] | None = None) -> None:
|
|
97
|
+
"""Connect to the PEA and execute the minimal OPC UA recipe."""
|
|
98
|
+
parsed_args = parse_args(args)
|
|
99
|
+
setLogLevel(parsed_args.log_level)
|
|
100
|
+
client = Client(parsed_args.endpoint)
|
|
101
|
+
logger.app(f"--- Connect recipe client to {parsed_args.endpoint} ---")
|
|
102
|
+
client.connect()
|
|
103
|
+
try:
|
|
104
|
+
run(
|
|
105
|
+
client,
|
|
106
|
+
parsed_args.namespace_index,
|
|
107
|
+
parsed_args.duration,
|
|
108
|
+
parsed_args.lower_bound,
|
|
109
|
+
parsed_args.upper_bound,
|
|
110
|
+
)
|
|
111
|
+
finally:
|
|
112
|
+
logger.app("--- Disconnect recipe client ---")
|
|
113
|
+
client.disconnect()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
main()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Build a minimal PEA containing one static AlarmSet.Base alarm."""
|
|
2
|
+
|
|
3
|
+
# ruff: noqa: INP001
|
|
4
|
+
|
|
5
|
+
__copyright__ = (
|
|
6
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
7
|
+
)
|
|
8
|
+
__license__ = "MIT"
|
|
9
|
+
|
|
10
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.bin_view import BinView
|
|
11
|
+
from mtppy.part6.alarm_set.alarm import Alarm
|
|
12
|
+
from mtppy.pea.pea import PEA
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_pea() -> PEA:
|
|
16
|
+
"""Build a PEA with a binary level indicator and one static alarm."""
|
|
17
|
+
pea = PEA()
|
|
18
|
+
level_high = BinView(
|
|
19
|
+
"LevelHigh",
|
|
20
|
+
tag_description="High-level indication",
|
|
21
|
+
v_state_0="Level is normal",
|
|
22
|
+
v_state_1="Level is too high",
|
|
23
|
+
)
|
|
24
|
+
pea.add_data_assembly(level_high)
|
|
25
|
+
pea.add_alarm(
|
|
26
|
+
Alarm(
|
|
27
|
+
tag_name="LevelHigh.Alarm",
|
|
28
|
+
data_assembly=level_high,
|
|
29
|
+
trigger=level_high.get_item(BinView.V_),
|
|
30
|
+
trigger_value=True,
|
|
31
|
+
message="Level is too high.",
|
|
32
|
+
ack_required=True,
|
|
33
|
+
severity=500,
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
return pea
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main() -> None:
|
|
40
|
+
"""Build the example and report the registered alarm."""
|
|
41
|
+
pea = build_pea()
|
|
42
|
+
alarm = pea.alarms["LevelHigh.Alarm"]
|
|
43
|
+
print(f"Registered AlarmSet.Base alarm: {alarm.tag_name}") # noqa: T201
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
main()
|
examples/pea_minimal.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""**pea_minimal**, a minimal example of a self-contained PEA application."""
|
|
2
|
+
|
|
3
|
+
__copyright__ = (
|
|
4
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
5
|
+
)
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
import random
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from mtppy.utils.logging import logger, CHOICES, setLogLevel
|
|
14
|
+
from mtppy.pea.pea import PEA
|
|
15
|
+
from mtppy.part3.data_assembly_set.active_elements.pid.pid_ctrl import PIDCtrl
|
|
16
|
+
from mtppy.part3.data_assembly_set.indicator_elements.views.ana_view import AnaView
|
|
17
|
+
from mtppy.part4.automation_services.service import Service
|
|
18
|
+
from mtppy.part4.automation_services.procedure import Procedure
|
|
19
|
+
from mtppy.part4.automation_services.service_elements.parameter_elements.dint_serv_param import (
|
|
20
|
+
DIntServParam,
|
|
21
|
+
)
|
|
22
|
+
from utils.resolve_unit.unit_lib import Units
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RandomNumberGenerator(Service):
|
|
26
|
+
def __init__(self, tag_name: str, tag_description: str):
|
|
27
|
+
super().__init__(tag_name, tag_description)
|
|
28
|
+
|
|
29
|
+
# Define a single procedure with two parameters and one reporting value and add it to the service
|
|
30
|
+
proc_1 = Procedure(1, "cont", is_self_completing=False)
|
|
31
|
+
proc_1.add_procedure_parameter(
|
|
32
|
+
DIntServParam(
|
|
33
|
+
"lower_bound",
|
|
34
|
+
v_op=10,
|
|
35
|
+
v_min=0,
|
|
36
|
+
v_max=100,
|
|
37
|
+
v_scl_min=0,
|
|
38
|
+
v_scl_max=100,
|
|
39
|
+
v_unit=Units.UNSPECIFIED.code,
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
proc_1.add_procedure_parameter(
|
|
43
|
+
DIntServParam(
|
|
44
|
+
"upper_bound",
|
|
45
|
+
v_op=90,
|
|
46
|
+
v_min=0,
|
|
47
|
+
v_max=100,
|
|
48
|
+
v_scl_min=0,
|
|
49
|
+
v_scl_max=100,
|
|
50
|
+
v_unit=Units.UNSPECIFIED.code,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
proc_1.add_procedure_parameter(
|
|
54
|
+
DIntServParam(
|
|
55
|
+
"cycle_time",
|
|
56
|
+
v_op=500,
|
|
57
|
+
v_min=0,
|
|
58
|
+
v_max=1000,
|
|
59
|
+
v_scl_min=0,
|
|
60
|
+
v_scl_max=1000,
|
|
61
|
+
v_unit=Units.UNSPECIFIED.code,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
proc_1.add_report_value(
|
|
65
|
+
AnaView(
|
|
66
|
+
"generated_value",
|
|
67
|
+
v_scl_min=0,
|
|
68
|
+
v_scl_max=100,
|
|
69
|
+
v_unit=Units.UNSPECIFIED.code,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
self.add_procedure(proc_1)
|
|
73
|
+
|
|
74
|
+
# Define the user logic
|
|
75
|
+
def idle(self):
|
|
76
|
+
logger.app("- Idle -")
|
|
77
|
+
cycle = 0
|
|
78
|
+
while self.is_state("idle"):
|
|
79
|
+
logger.app(
|
|
80
|
+
f" Idle cycle={cycle}, sm.act_state={self.state_machine.act_state}"
|
|
81
|
+
)
|
|
82
|
+
cycle += 1
|
|
83
|
+
time.sleep(0.2)
|
|
84
|
+
|
|
85
|
+
def starting(self):
|
|
86
|
+
logger.app("- Starting -")
|
|
87
|
+
logger.app(" Applying procedure parameters...")
|
|
88
|
+
self.state_change()
|
|
89
|
+
|
|
90
|
+
def execute(self):
|
|
91
|
+
logger.app("- Execute -")
|
|
92
|
+
cycle = 0
|
|
93
|
+
while self.is_state("execute"):
|
|
94
|
+
logger.debug(f" About to execute cycle {cycle}")
|
|
95
|
+
# Read procedure parameters
|
|
96
|
+
lower_bound = (
|
|
97
|
+
self._get_procedures()[1]
|
|
98
|
+
.procedure_parameters["lower_bound"]
|
|
99
|
+
.get_v_out()
|
|
100
|
+
)
|
|
101
|
+
upper_bound = (
|
|
102
|
+
self._get_procedures()[1]
|
|
103
|
+
.procedure_parameters["upper_bound"]
|
|
104
|
+
.get_v_out()
|
|
105
|
+
)
|
|
106
|
+
cycle_time = (
|
|
107
|
+
self._get_procedures()[1].procedure_parameters["cycle_time"].get_v_out()
|
|
108
|
+
) / 1000.0
|
|
109
|
+
|
|
110
|
+
# Create and report random number
|
|
111
|
+
generated_number = random.randint(lower_bound, upper_bound)
|
|
112
|
+
self._get_procedures()[1].report_values["generated_value"].set_v(
|
|
113
|
+
generated_number
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
logger.app(
|
|
117
|
+
f" {cycle}, generated_number={generated_number}, bounds={[lower_bound, upper_bound]}, cycle_time={cycle_time}s"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
cycle += 1
|
|
121
|
+
time.sleep(max(0.2, cycle_time))
|
|
122
|
+
|
|
123
|
+
def completing(self):
|
|
124
|
+
self.state_change()
|
|
125
|
+
|
|
126
|
+
def completed(self):
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
def pausing(self):
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
def paused(self):
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
def resuming(self):
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
def holding(self):
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
def held(self):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
def unholding(self):
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
def stopping(self):
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
def stopped(self):
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
def aborting(self):
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
def aborted(self):
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
def resetting(self):
|
|
160
|
+
logger.app("- Resetting -")
|
|
161
|
+
self.state_change()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def parse_args():
|
|
165
|
+
p = argparse.ArgumentParser("minimal PEA example")
|
|
166
|
+
p.add_argument(
|
|
167
|
+
"-l",
|
|
168
|
+
"--log-level",
|
|
169
|
+
dest="log_level",
|
|
170
|
+
default="APP",
|
|
171
|
+
type=str,
|
|
172
|
+
choices=["DEBUG", "INFO"] + CHOICES + ["WARNING", "ERROR", "CRITICAL"],
|
|
173
|
+
help="Set the logging level (default: APP)",
|
|
174
|
+
)
|
|
175
|
+
p.add_argument(
|
|
176
|
+
"--duration",
|
|
177
|
+
type=float,
|
|
178
|
+
default=10.0,
|
|
179
|
+
help="Set the duration of the execute phase",
|
|
180
|
+
)
|
|
181
|
+
p.add_argument(
|
|
182
|
+
"-e",
|
|
183
|
+
"--endpoint",
|
|
184
|
+
default="opc.tcp://localhost:4840",
|
|
185
|
+
help="Set the OPCUA endpoint (default opc.tcp://localhost:4840).",
|
|
186
|
+
)
|
|
187
|
+
p.add_argument(
|
|
188
|
+
"--daemon",
|
|
189
|
+
action="store_true",
|
|
190
|
+
default=False,
|
|
191
|
+
help="don't stop, continue to run (default: exit ).",
|
|
192
|
+
)
|
|
193
|
+
return p.parse_args()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
args = parse_args()
|
|
198
|
+
setLogLevel(args.log_level)
|
|
199
|
+
|
|
200
|
+
# -------------------------- PEA setup --------------------------
|
|
201
|
+
# create PEA instance with specified endpoint
|
|
202
|
+
pea = PEA(endpoint=args.endpoint)
|
|
203
|
+
|
|
204
|
+
# define service and add it to the PEA
|
|
205
|
+
service_1 = RandomNumberGenerator(
|
|
206
|
+
"rand_num_gen", "This service generates random numbers"
|
|
207
|
+
)
|
|
208
|
+
pea.add_service(service_1)
|
|
209
|
+
|
|
210
|
+
# define an active element and add it to the PEA
|
|
211
|
+
pid_ctrl = PIDCtrl("pid_ctrl")
|
|
212
|
+
pea.add_data_assembly(pid_ctrl)
|
|
213
|
+
|
|
214
|
+
# Setup done, now start the PEA
|
|
215
|
+
logger.app("--- Start PEA ---")
|
|
216
|
+
pea.start()
|
|
217
|
+
time.sleep(1)
|
|
218
|
+
|
|
219
|
+
# -------------------------- POL test script --------------------------
|
|
220
|
+
from mtppy.part4.definitions.command_codes import CommandCodes as CC
|
|
221
|
+
|
|
222
|
+
opcua_server = pea.pea_opcua_server.opcua_server
|
|
223
|
+
ns_index = pea.pea_opcua_server._get_opcua_ns_index()
|
|
224
|
+
nodeid = lambda cmd: f"ns={ns_index};s={cmd}"
|
|
225
|
+
opcua_write = lambda cmd, val: opcua_server.get_node(nodeid(cmd)).set_value(val)
|
|
226
|
+
|
|
227
|
+
logger.pol("--- Set procedure parameters to Operator mode ---")
|
|
228
|
+
opcua_write("lower_bound.StateOpOp", True)
|
|
229
|
+
opcua_write("upper_bound.StateOpOp", True)
|
|
230
|
+
time.sleep(1)
|
|
231
|
+
|
|
232
|
+
logger.pol("--- Set procedure parameter values ---")
|
|
233
|
+
opcua_write("lower_bound.VOp", 40)
|
|
234
|
+
opcua_write("upper_bound.VOp", 60)
|
|
235
|
+
opcua_write("cycle_time.VOp", 500)
|
|
236
|
+
time.sleep(1)
|
|
237
|
+
|
|
238
|
+
logger.pol("--- Set service to Operator mode ---")
|
|
239
|
+
opcua_write("rand_num_gen.StateOpOp", True)
|
|
240
|
+
time.sleep(1)
|
|
241
|
+
|
|
242
|
+
logger.pol("--- Start service ---")
|
|
243
|
+
opcua_write("rand_num_gen.ProcedureOp", 1)
|
|
244
|
+
opcua_write("rand_num_gen.CommandOp", CC.START.code)
|
|
245
|
+
time.sleep(args.duration)
|
|
246
|
+
|
|
247
|
+
logger.pol("--- Complete service ---")
|
|
248
|
+
opcua_write("rand_num_gen.CommandOp", CC.COMPLETE.code)
|
|
249
|
+
time.sleep(1)
|
|
250
|
+
|
|
251
|
+
logger.pol("--- Reset service ---")
|
|
252
|
+
opcua_write("rand_num_gen.CommandOp", CC.RESET.code)
|
|
253
|
+
|
|
254
|
+
# -------------------------- PEA shutdown or detach --------------------------
|
|
255
|
+
if not args.daemon:
|
|
256
|
+
logger.app("--- Stop PEA ---")
|
|
257
|
+
pea.stop()
|
|
258
|
+
sys.exit(0)
|
mtppy/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.0.0'
|
mtppy/part1/__init__.py
ADDED
mtppy/part3/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Base Class for Active Elements."""
|
|
2
|
+
|
|
3
|
+
__copyright__ = (
|
|
4
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
5
|
+
)
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
|
|
8
|
+
from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
|
|
9
|
+
from mtppy.part3.data_assembly_set.base_functions.oslevel import OSLevelBaseFunction
|
|
10
|
+
from mtppy.part3.data_assembly_set.base_functions.wqc import WQCBaseFunction
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ActiveElement(DataAssembly):
|
|
14
|
+
os_level: OSLevelBaseFunction
|
|
15
|
+
wqc: WQCBaseFunction
|
|
16
|
+
|
|
17
|
+
def __init__(self, tag_name: str, tag_description: str):
|
|
18
|
+
super().__init__(tag_name, tag_description)
|
|
19
|
+
self.mtpObjectType = "MTPDataObjectSUCLib/DataAssembly/ActiveElement"
|
|
20
|
+
|
|
21
|
+
self.os_level = OSLevelBaseFunction()
|
|
22
|
+
self.wqc = WQCBaseFunction()
|