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.
Files changed (214) hide show
  1. examples/__init__.py +0 -0
  2. examples/minimal+pid.py +82 -0
  3. examples/minimal.py +32 -0
  4. examples/minimal_recipe.py +117 -0
  5. examples/pea_alarm_base.py +47 -0
  6. examples/pea_minimal.py +258 -0
  7. mtppy/__init__.py +1 -0
  8. mtppy/part1/__init__.py +4 -0
  9. mtppy/part1/empty_folder_test.py +7 -0
  10. mtppy/part3/__init__.py +4 -0
  11. mtppy/part3/data_assembly_set/__init__.py +4 -0
  12. mtppy/part3/data_assembly_set/active_elements/__init__.py +4 -0
  13. mtppy/part3/data_assembly_set/active_elements/active_element.py +22 -0
  14. mtppy/part3/data_assembly_set/active_elements/drives/__init__.py +4 -0
  15. mtppy/part3/data_assembly_set/active_elements/drives/ana_drv.py +446 -0
  16. mtppy/part3/data_assembly_set/active_elements/drives/ana_drv_test.py +567 -0
  17. mtppy/part3/data_assembly_set/active_elements/drives/bin_drv.py +365 -0
  18. mtppy/part3/data_assembly_set/active_elements/drives/bin_drv_test.py +503 -0
  19. mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv.py +469 -0
  20. mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv_test.py +418 -0
  21. mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv.py +295 -0
  22. mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv_test.py +265 -0
  23. mtppy/part3/data_assembly_set/active_elements/pid/__init__.py +4 -0
  24. mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl.py +333 -0
  25. mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl_test.py +156 -0
  26. mtppy/part3/data_assembly_set/active_elements/valves/__init__.py +4 -0
  27. mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv.py +402 -0
  28. mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv_test.py +732 -0
  29. mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv.py +279 -0
  30. mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv_test.py +585 -0
  31. mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv.py +402 -0
  32. mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv_test.py +270 -0
  33. mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv.py +286 -0
  34. mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv_test.py +224 -0
  35. mtppy/part3/data_assembly_set/base_functions/__init__.py +4 -0
  36. mtppy/part3/data_assembly_set/base_functions/operation_mode.py +252 -0
  37. mtppy/part3/data_assembly_set/base_functions/operation_mode_test.py +260 -0
  38. mtppy/part3/data_assembly_set/base_functions/oslevel.py +35 -0
  39. mtppy/part3/data_assembly_set/base_functions/source_mode.py +189 -0
  40. mtppy/part3/data_assembly_set/base_functions/source_mode_test.py +112 -0
  41. mtppy/part3/data_assembly_set/base_functions/wqc.py +75 -0
  42. mtppy/part3/data_assembly_set/base_functions/wqc_test.py +86 -0
  43. mtppy/part3/data_assembly_set/data_assembly.py +65 -0
  44. mtppy/part3/data_assembly_set/diagnostic_elements/__init__.py +4 -0
  45. mtppy/part3/data_assembly_set/diagnostic_elements/diagnostic_element.py +19 -0
  46. mtppy/part3/data_assembly_set/indicator_elements/__init__.py +4 -0
  47. mtppy/part3/data_assembly_set/indicator_elements/indicator_element.py +22 -0
  48. mtppy/part3/data_assembly_set/indicator_elements/views/__init__.py +4 -0
  49. mtppy/part3/data_assembly_set/indicator_elements/views/ana_view.py +68 -0
  50. mtppy/part3/data_assembly_set/indicator_elements/views/bin_view.py +73 -0
  51. mtppy/part3/data_assembly_set/indicator_elements/views/dint_view.py +76 -0
  52. mtppy/part3/data_assembly_set/indicator_elements/views/string_view.py +55 -0
  53. mtppy/part3/data_assembly_set/operation_elements/__init__.py +4 -0
  54. mtppy/part3/data_assembly_set/operation_elements/operation_element.py +19 -0
  55. mtppy/part3/data_assembly_set/pea_elements/__init__.py +4 -0
  56. mtppy/part3/data_assembly_set/pea_elements/pea_element.py +28 -0
  57. mtppy/part3/data_assembly_set/pea_elements/pea_information_label/__init__.py +4 -0
  58. mtppy/part3/data_assembly_set/pea_elements/pea_information_label/pea_information_label.py +231 -0
  59. mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/__init__.py +4 -0
  60. mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/web_server_url_info.py +42 -0
  61. mtppy/part3/definitions/__init__.py +4 -0
  62. mtppy/part3/definitions/mtp_access.py +15 -0
  63. mtppy/part4/__init__.py +4 -0
  64. mtppy/part4/automation_services/__init__.py +4 -0
  65. mtppy/part4/automation_services/base_functions/__init__.py +4 -0
  66. mtppy/part4/automation_services/base_functions/service_operation_mode.py +368 -0
  67. mtppy/part4/automation_services/base_functions/service_operation_mode_test.py +242 -0
  68. mtppy/part4/automation_services/base_functions/service_operator_interaction.py +62 -0
  69. mtppy/part4/automation_services/base_functions/service_source_mode.py +138 -0
  70. mtppy/part4/automation_services/base_functions/service_source_mode_test.py +179 -0
  71. mtppy/part4/automation_services/helpers/__init__.py +4 -0
  72. mtppy/part4/automation_services/helpers/command_en_control.py +330 -0
  73. mtppy/part4/automation_services/helpers/command_en_control_test.py +302 -0
  74. mtppy/part4/automation_services/helpers/state_machine.py +167 -0
  75. mtppy/part4/automation_services/helpers/state_machine_test.py +294 -0
  76. mtppy/part4/automation_services/helpers/thread_control.py +39 -0
  77. mtppy/part4/automation_services/helpers/thread_control_test.py +80 -0
  78. mtppy/part4/automation_services/procedure.py +111 -0
  79. mtppy/part4/automation_services/procedure_test.py +185 -0
  80. mtppy/part4/automation_services/service.py +463 -0
  81. mtppy/part4/automation_services/service_elements/__init__.py +4 -0
  82. mtppy/part4/automation_services/service_elements/parameter_elements/__init__.py +4 -0
  83. mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param.py +108 -0
  84. mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param_test.py +59 -0
  85. mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param.py +70 -0
  86. mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param_test.py +39 -0
  87. mtppy/part4/automation_services/service_elements/parameter_elements/common_serv_param.py +111 -0
  88. mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param.py +77 -0
  89. mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param_test.py +58 -0
  90. mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element.py +175 -0
  91. mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element_test.py +246 -0
  92. mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param.py +48 -0
  93. mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param_test.py +37 -0
  94. mtppy/part4/automation_services/service_elements/procedure_health_view.py +36 -0
  95. mtppy/part4/automation_services/service_elements/service_control.py +473 -0
  96. mtppy/part4/automation_services/service_elements/service_control_test.py +150 -0
  97. mtppy/part4/automation_services/service_elements/service_element.py +20 -0
  98. mtppy/part4/automation_services/service_test.py +407 -0
  99. mtppy/part4/definitions/__init__.py +4 -0
  100. mtppy/part4/definitions/command_codes.py +58 -0
  101. mtppy/part4/definitions/command_codes_test.py +44 -0
  102. mtppy/part4/definitions/state_codes.py +65 -0
  103. mtppy/part4/definitions/state_codes_test.py +44 -0
  104. mtppy/part4/process_values/__init__.py +4 -0
  105. mtppy/part4/process_values/base_functions/__init__.py +4 -0
  106. mtppy/part4/process_values/base_functions/vqc.py +48 -0
  107. mtppy/part4/process_values/input_elements/__init__.py +4 -0
  108. mtppy/part4/process_values/input_elements/ana_process_value_in.py +160 -0
  109. mtppy/part4/process_values/input_elements/bin_process_value_in.py +83 -0
  110. mtppy/part4/process_values/input_elements/dint_process_value_in.py +159 -0
  111. mtppy/part4/process_values/input_elements/input_element.py +40 -0
  112. mtppy/part4/process_values/input_elements/string_process_value_in.py +45 -0
  113. mtppy/part5/__init__.py +4 -0
  114. mtppy/part5/communication_object.py +99 -0
  115. mtppy/part5/communication_object_test.py +59 -0
  116. mtppy/part5/definitions/__init__.py +4 -0
  117. mtppy/part5/definitions/iec61131_data_types.py +31 -0
  118. mtppy/part5/definitions/iec61131_data_types_test.py +82 -0
  119. mtppy/part5/definitions/opcua_data_types.py +36 -0
  120. mtppy/part5/definitions/opcua_data_types_test.py +90 -0
  121. mtppy/part5/definitions/w3c_xml_data_types.py +177 -0
  122. mtppy/part5/definitions/w3c_xml_data_types_test.py +143 -0
  123. mtppy/part5/server_assembly_set/__init__.py +5 -0
  124. mtppy/part6/__init__.py +4 -0
  125. mtppy/part6/alarm_set/__init__.py +4 -0
  126. mtppy/part6/alarm_set/alarm.py +132 -0
  127. mtppy/part6/alarm_set/alarm_test.py +265 -0
  128. mtppy/pea/__init__.py +19 -0
  129. mtppy/pea/data_assembly_factory.py +124 -0
  130. mtppy/pea/data_assembly_factory_test.py +229 -0
  131. mtppy/pea/mtp_generator/__init__.py +4 -0
  132. mtppy/pea/mtp_generator/mtp_generator.py +1183 -0
  133. mtppy/pea/mtp_generator/mtp_generator_test.py +518 -0
  134. mtppy/pea/pea.py +139 -0
  135. mtppy/pea/pea_opcua_server/__init__.py +4 -0
  136. mtppy/pea/pea_opcua_server/marshalling.py +60 -0
  137. mtppy/pea/pea_opcua_server/opcua_options_provider/__init__.py +4 -0
  138. mtppy/pea/pea_opcua_server/opcua_options_provider/instances/basic_opcua_options_provider.py +50 -0
  139. mtppy/pea/pea_opcua_server/opcua_options_provider/opcua_options_provider.py +103 -0
  140. mtppy/pea/pea_opcua_server/pea_opcua_server.py +492 -0
  141. mtppy/pea/pea_opcua_server/subscription_list.py +55 -0
  142. mtppy/pea/pea_test.py +59 -0
  143. mtppy/pea/rmtp_pea.py +155 -0
  144. mtppy/pea/rmtp_pea_test.py +27 -0
  145. mtppy/pea/service_factory.py +178 -0
  146. mtppy/pea/service_factory_test.py +156 -0
  147. mtppy/pea/user_logic/__init__.py +4 -0
  148. mtppy/pea/user_logic/user_logic_pea.py +72 -0
  149. mtppy/pea/user_logic/user_logic_registry.py +106 -0
  150. mtppy/pea/user_logic/user_logic_registry_test.py +167 -0
  151. mtppy/pea/user_logic/user_logic_service.py +228 -0
  152. mtppy/pea/user_logic/user_logic_service_test.py +289 -0
  153. mtppy/utils/__init__.py +4 -0
  154. mtppy/utils/data_assembly_dict.py +61 -0
  155. mtppy/utils/data_assembly_dict_test.py +107 -0
  156. mtppy/utils/data_item.py +446 -0
  157. mtppy/utils/data_item_com_test.py +82 -0
  158. mtppy/utils/data_item_test.py +325 -0
  159. mtppy/utils/logging.py +23 -0
  160. mtppy/utils/logging_test.py +26 -0
  161. mtppy/utils/mtp_file_handling/__init__.py +4 -0
  162. mtppy/utils/mtp_file_handling/extract_manifest_from_mtp_file.py +22 -0
  163. mtppy/utils/mtp_file_handling/extract_mtp_file.py +22 -0
  164. mtppy/utils/mtp_file_handling/is_valid_mtp.py +22 -0
  165. mtppy/utils/mtp_file_handling/is_valid_path_for_manifest.py +15 -0
  166. mtppy/utils/mtp_file_handling/is_valid_path_to_manifest.py +17 -0
  167. mtppy/utils/mtp_file_handling/is_valid_path_to_mtp.py +17 -0
  168. mtppy/utils/mtp_file_handling/load_manifest_file.py +24 -0
  169. mtppy/utils/mtp_file_handling/mtp_reader/__init__.py +4 -0
  170. mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader.py +103 -0
  171. mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader_test.py +96 -0
  172. mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml.py +226 -0
  173. mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml_test.py +108 -0
  174. mtppy/utils/mtp_file_handling/mtp_writer/__init__.py +4 -0
  175. mtppy/utils/mtp_file_handling/mtp_writer/mtp_writer.py +77 -0
  176. mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_content_types.py +10 -0
  177. mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_rels.py +11 -0
  178. mtppy/utils/mtp_file_handling/mtp_writer/test_mtp_writer.py +25 -0
  179. mtppy/utils/mtp_file_handling/write_manifest_file.py +29 -0
  180. mtppy/utils/mtp_file_handling/write_manifest_file_test.py +22 -0
  181. mtppy/utils/mtp_file_handling/write_mtp_file.py +43 -0
  182. mtppy/utils/mtp_file_handling/write_mtp_file_test.py +22 -0
  183. mtppy/utils/testing.py +31 -0
  184. mtppy2_0-0.1.5.dist-info/METADATA +75 -0
  185. mtppy2_0-0.1.5.dist-info/RECORD +214 -0
  186. mtppy2_0-0.1.5.dist-info/WHEEL +5 -0
  187. mtppy2_0-0.1.5.dist-info/entry_points.txt +3 -0
  188. mtppy2_0-0.1.5.dist-info/licenses/LICENSE +21 -0
  189. mtppy2_0-0.1.5.dist-info/top_level.txt +4 -0
  190. tools/__init__.py +0 -0
  191. tools/create_bugreport.py +150 -0
  192. tools/inspect_mtp.py +110 -0
  193. utils/__init__.py +1 -0
  194. utils/file_handling/__init__.py +4 -0
  195. utils/file_handling/cleanup_folder.py +9 -0
  196. utils/file_handling/get_utc_foldername_with_ms.py +15 -0
  197. utils/file_handling/get_work_dir.py +9 -0
  198. utils/file_handling/is_valid_path_for_json.py +11 -0
  199. utils/file_handling/is_valid_path_to_json.py +17 -0
  200. utils/file_handling/load_json_file.py +18 -0
  201. utils/file_handling/read_file_from_file_archive.py +43 -0
  202. utils/file_handling/read_file_string.py +10 -0
  203. utils/file_handling/validate_json_data.py +28 -0
  204. utils/file_handling/write_json_file.py +23 -0
  205. utils/get_uuid.py +18 -0
  206. utils/get_uuid_test.py +17 -0
  207. utils/logging.py +100 -0
  208. utils/logging_test.py +67 -0
  209. utils/opcua/check_node.py +79 -0
  210. utils/resolve_unit/resolve_unit.py +11 -0
  211. utils/resolve_unit/unit_lib.py +2101 -0
  212. utils/resolve_unit/unit_lib_test.py +36 -0
  213. utils/type_conversion.py +236 -0
  214. utils/type_conversion_test.py +114 -0
examples/__init__.py ADDED
File without changes
@@ -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()
@@ -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'
@@ -0,0 +1,4 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"
@@ -0,0 +1,7 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"
5
+
6
+ def test_true():
7
+ assert True == True
@@ -0,0 +1,4 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"
@@ -0,0 +1,4 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"
@@ -0,0 +1,4 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"
@@ -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()
@@ -0,0 +1,4 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"