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
@@ -0,0 +1,294 @@
1
+ """Test Suite for StateMachine."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ import pytest
9
+
10
+ from mtppy.part4.automation_services.base_functions.service_operation_mode_test import (
11
+ service_operation_mode_is_in_specific_mode,
12
+ )
13
+ from mtppy.part4.automation_services.base_functions.service_source_mode_test import (
14
+ service_source_mode_is_in_specific_mode,
15
+ )
16
+ from mtppy.part4.automation_services.helpers.state_machine import StateMachine
17
+ from mtppy.part4.definitions.state_codes import StateCodes
18
+ from mtppy.part4.definitions.command_codes import CommandCodes
19
+ from mtppy.part4.automation_services.service_elements.service_control import (
20
+ ServiceControl,
21
+ )
22
+
23
+ valid_commands = {
24
+ "stopped": {"reset": "resetting", "abort": "aborting"},
25
+ "starting": {
26
+ "complete": "completing",
27
+ "hold": "holding",
28
+ "stop": "stopping",
29
+ "abort": "aborting",
30
+ },
31
+ "idle": {"start": "starting", "stop": "stopping", "abort": "aborting"},
32
+ "paused": {
33
+ "resume": "resuming",
34
+ "complete": "completing",
35
+ "hold": "holding",
36
+ "stop": "stopping",
37
+ "abort": "aborting",
38
+ },
39
+ "execute": {
40
+ "complete": "completing",
41
+ "restart": "starting",
42
+ "pause": "pausing",
43
+ "hold": "holding",
44
+ "stop": "stopping",
45
+ "abort": "aborting",
46
+ },
47
+ "stopping": {"abort": "aborting"},
48
+ "aborting": {},
49
+ "aborted": {"reset": "resetting"},
50
+ "holding": {"stop": "stopping", "abort": "aborting"},
51
+ "held": {"unhold": "unholding", "stop": "stopping", "abort": "aborting"},
52
+ "unholding": {
53
+ "complete": "completing",
54
+ "hold": "holding",
55
+ "stop": "stopping",
56
+ "abort": "aborting",
57
+ },
58
+ "pausing": {
59
+ "complete": "completing",
60
+ "hold": "holding",
61
+ "stop": "stopping",
62
+ "abort": "aborting",
63
+ },
64
+ "resuming": {
65
+ "complete": "completing",
66
+ "hold": "holding",
67
+ "stop": "stopping",
68
+ "abort": "aborting",
69
+ },
70
+ "resetting": {"stop": "stopping", "abort": "aborting"},
71
+ "completing": {"hold": "holding", "stop": "stopping", "abort": "aborting"},
72
+ "completed": {"reset": "resetting", "stop": "stopping", "abort": "aborting"},
73
+ }
74
+
75
+
76
+ def init_service_control(
77
+ service_op_mode="off", service_src_mode="int"
78
+ ) -> ServiceControl:
79
+ service_control = ServiceControl("Test")
80
+
81
+ if service_op_mode == "off":
82
+ service_control.service_op_mode["StateOffAct"] = True
83
+ elif service_op_mode == "op":
84
+ service_control.service_op_mode["StateOffAct"] = False
85
+ service_control.service_op_mode["StateOpAct"] = True
86
+ elif service_op_mode == "aut":
87
+ service_control.service_op_mode["StateOffAct"] = False
88
+ service_control.service_op_mode["StateAutAct"] = True
89
+ else:
90
+ raise ValueError(f"ServiceOperationMode {service_op_mode} is unknown")
91
+
92
+ if service_src_mode == "int":
93
+ service_control.service_src_mode["SrcIntAct"] = True
94
+ elif service_src_mode == "ext":
95
+ service_control.service_src_mode["SrcExtAct"] = True
96
+ else:
97
+ raise ValueError(f"ServiceSourceMode {service_src_mode} is unknown")
98
+
99
+ return service_control
100
+
101
+
102
+ class CallbackObject:
103
+ def __init__(self):
104
+ self.value = 0
105
+
106
+ def value_callback(self):
107
+ self.value = 10
108
+
109
+
110
+ def init_state_machine(
111
+ state: str = "idle", service_op_mode: str = "off", service_src_mode: str = "int"
112
+ ):
113
+ callback_object = CallbackObject()
114
+ service_control = init_service_control(
115
+ service_op_mode=service_op_mode, service_src_mode=service_src_mode
116
+ )
117
+ state_machine = StateMachine(
118
+ service_control=service_control,
119
+ execution_routine=callback_object.value_callback,
120
+ )
121
+ if service_op_mode != "off":
122
+ state_machine.activate()
123
+ state_machine.act_state = StateCodes.from_string(state)
124
+ state_machine.command_en_ctrl.execute(state)
125
+
126
+ if not (
127
+ state_machine.get_current_state_str() == state
128
+ and service_operation_mode_is_in_specific_mode(
129
+ service_control.service_op_mode, False, service_op_mode
130
+ )
131
+ and service_source_mode_is_in_specific_mode(
132
+ service_control.service_src_mode, False, service_src_mode
133
+ )
134
+ ):
135
+ print(f"Initialization failed")
136
+ return state_machine, callback_object
137
+
138
+
139
+ test_scenario_inactive = [
140
+ ("off", "int", "set_command_op", False),
141
+ ("off", "ext", "set_command_op", False),
142
+ ("off", "int", "set_command_int", False),
143
+ ("off", "ext", "set_command_int", False),
144
+ ("off", "int", "set_command_ext", False),
145
+ ("off", "ext", "set_command_ext", False),
146
+ ]
147
+
148
+ test_scenario = [
149
+ ("op", "int", "set_command_op", True),
150
+ ("op", "ext", "set_command_op", True),
151
+ ("aut", "int", "set_command_op", False),
152
+ ("aut", "ext", "set_command_op", False),
153
+ ("op", "int", "set_command_int", False),
154
+ ("op", "ext", "set_command_int", False),
155
+ ("aut", "int", "set_command_int", True),
156
+ ("aut", "ext", "set_command_int", False),
157
+ ("op", "int", "set_command_ext", False),
158
+ ("op", "ext", "set_command_ext", False),
159
+ ("aut", "int", "set_command_ext", False),
160
+ ("aut", "ext", "set_command_ext", True),
161
+ ]
162
+
163
+
164
+ @pytest.mark.parametrize(
165
+ "op_mode, src_mode, set_command, change_allowed", test_scenario
166
+ )
167
+ def test_set_command(op_mode, src_mode, set_command, change_allowed):
168
+ for state in StateCodes.list_of_strings():
169
+ if state == "undefined":
170
+ continue
171
+ for command in CommandCodes.list_of_codes():
172
+ state_machine, callback_object = init_state_machine(
173
+ state=state, service_op_mode=op_mode, service_src_mode=src_mode
174
+ )
175
+ # print(f'Initial State: {state_machine.get_current_state_str()} now running: state_machine.{set_command}({command})')
176
+ eval(f"state_machine.{set_command}({command})")
177
+ command_str = CommandCodes.from_code(command)
178
+ # eval(f'state_machine.{command_str}()')
179
+ if command_str in valid_commands[state].keys() and change_allowed:
180
+ expected_state = valid_commands[state][command_str]
181
+ else:
182
+ expected_state = state
183
+ final_state = state_machine.get_current_state_str()
184
+ print(
185
+ f"Scenario: op_mode {op_mode} src_mode {src_mode}, {set_command}, valid change: {change_allowed},",
186
+ end=" ",
187
+ )
188
+ print(
189
+ f"expect transition {state}->command({command_str})->{expected_state} (actual: {final_state})"
190
+ )
191
+ assert final_state == expected_state
192
+
193
+
194
+ def test_activate_and_deactivate():
195
+ state_machine, callback_object = init_state_machine(service_op_mode="off")
196
+
197
+ assert state_machine.is_active is False
198
+ state_machine.activate()
199
+
200
+ assert state_machine.is_active is True
201
+ assert state_machine.get_current_state_str() == "idle"
202
+ assert callback_object.value == 10
203
+
204
+ state_machine.deactivate()
205
+
206
+ assert state_machine.is_active is False
207
+ assert state_machine.get_current_state_str() == "undefined"
208
+
209
+
210
+ def test_activate_does_not_reinitialize_active_machine():
211
+ state_machine, callback_object = init_state_machine(service_op_mode="off")
212
+ state_machine.activate()
213
+ callback_object.value = 0
214
+
215
+ state_machine.activate()
216
+
217
+ assert state_machine.get_current_state_str() == "idle"
218
+ assert callback_object.value == 0
219
+
220
+
221
+ def test_deactivate_only_works_from_idle():
222
+ state_machine, _ = init_state_machine(state="execute", service_op_mode="op")
223
+
224
+ state_machine.deactivate()
225
+
226
+ assert state_machine.is_active is True
227
+ assert state_machine.get_current_state_str() == "execute"
228
+
229
+
230
+ def test_command_execution_ignores_inactive_and_unknown_commands():
231
+ state_machine, _ = init_state_machine(service_op_mode="off")
232
+
233
+ state_machine.command_execution(CommandCodes.START.code)
234
+ state_machine.command_execution(-1)
235
+
236
+ assert state_machine.is_active is False
237
+ assert state_machine.get_current_state_str() == "undefined"
238
+
239
+
240
+ def test_active_command_execution_ignores_unknown_command():
241
+ state_machine, _ = init_state_machine(state="idle", service_op_mode="op")
242
+
243
+ state_machine.command_execution(-1)
244
+
245
+ assert state_machine.get_current_state_str() == "idle"
246
+
247
+
248
+ def test_command_execution_ignores_disabled_command():
249
+ state_machine, _ = init_state_machine(state="idle", service_op_mode="op")
250
+ state_machine.command_en_ctrl.disable_all()
251
+
252
+ state_machine.command_execution(CommandCodes.START.code)
253
+
254
+ assert state_machine.get_current_state_str() == "idle"
255
+
256
+
257
+ @pytest.mark.parametrize(
258
+ ("state", "expected_state"),
259
+ [
260
+ ("starting", "execute"),
261
+ ("completing", "completed"),
262
+ ("pausing", "paused"),
263
+ ("resuming", "execute"),
264
+ ("resetting", "idle"),
265
+ ("holding", "held"),
266
+ ("unholding", "execute"),
267
+ ("stopping", "stopped"),
268
+ ],
269
+ )
270
+ def test_state_change_transitions(state, expected_state):
271
+ state_machine, _ = init_state_machine(state=state, service_op_mode="op")
272
+
273
+ state_machine.state_change()
274
+
275
+ assert state_machine.get_current_state_str() == expected_state
276
+
277
+
278
+ def test_state_change_does_nothing_without_transition():
279
+ state_machine, _ = init_state_machine(state="idle", service_op_mode="op")
280
+
281
+ state_machine.state_change()
282
+
283
+ assert state_machine.get_current_state_str() == "idle"
284
+
285
+
286
+ def test_set_command_requires_matching_operation_and_source_modes():
287
+ state_machine, _ = init_state_machine(
288
+ state="idle", service_op_mode="aut", service_src_mode="int"
289
+ )
290
+
291
+ state_machine.set_command_op(CommandCodes.START.code)
292
+ state_machine.set_command_ext(CommandCodes.START.code)
293
+
294
+ assert state_machine.get_current_state_str() == "idle"
@@ -0,0 +1,39 @@
1
+ """Class for Thread Control."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ import logging
9
+ from threading import Thread, Event
10
+
11
+
12
+ class ThreadControl:
13
+ def __init__(self):
14
+ """
15
+ Represents a thread control to be able to run with multithreading.
16
+ """
17
+ self.thread = None
18
+ self.running_state = ""
19
+ self.requested_state = ""
20
+ self.callback_function = None
21
+ self._stop_event = Event()
22
+
23
+ def request_state(self, state: str, cb_function: callable):
24
+ logging.debug(f"State {state} requested")
25
+ self.requested_state = state
26
+ self.callback_function = cb_function
27
+
28
+ def reallocate_running_thread(self):
29
+ logging.debug(f"Reallocate thread to state {self.requested_state}")
30
+ if self.requested_state is not self.running_state:
31
+ self.thread = Thread(target=self.callback_function, daemon=True)
32
+ self.thread.start()
33
+ self.running_state = self.requested_state
34
+
35
+ def stop(self):
36
+ self._stop_event.set()
37
+
38
+ def is_stopped(self):
39
+ return self._stop_event.is_set()
@@ -0,0 +1,80 @@
1
+ """Test suite for ThreadControl."""
2
+
3
+ from threading import Event
4
+
5
+ from mtppy.part4.automation_services.helpers.thread_control import ThreadControl
6
+
7
+
8
+ def test_initial_state():
9
+ thread_control = ThreadControl()
10
+
11
+ assert thread_control.thread is None
12
+ assert thread_control.running_state == ""
13
+ assert thread_control.requested_state == ""
14
+ assert thread_control.callback_function is None
15
+ assert thread_control.is_stopped() is False
16
+
17
+
18
+ def test_request_state_stores_state_and_callback():
19
+ thread_control = ThreadControl()
20
+ callback = lambda: None
21
+
22
+ thread_control.request_state("idle", callback)
23
+
24
+ assert thread_control.requested_state == "idle"
25
+ assert thread_control.callback_function is callback
26
+
27
+
28
+ def test_reallocate_running_thread_starts_requested_callback():
29
+ thread_control = ThreadControl()
30
+ callback_finished = Event()
31
+
32
+ thread_control.request_state("idle", callback_finished.set)
33
+ thread_control.reallocate_running_thread()
34
+
35
+ thread_control.thread.join(timeout=1)
36
+ assert callback_finished.is_set() is True
37
+ assert thread_control.running_state == "idle"
38
+ assert thread_control.thread.is_alive() is False
39
+
40
+
41
+ def test_reallocate_running_thread_switches_to_new_state():
42
+ thread_control = ThreadControl()
43
+ first_callback_finished = Event()
44
+ second_callback_finished = Event()
45
+
46
+ thread_control.request_state("starting", first_callback_finished.set)
47
+ thread_control.reallocate_running_thread()
48
+ thread_control.thread.join(timeout=1)
49
+
50
+ thread_control.request_state("execute", second_callback_finished.set)
51
+ thread_control.reallocate_running_thread()
52
+ thread_control.thread.join(timeout=1)
53
+
54
+ assert first_callback_finished.is_set() is True
55
+ assert second_callback_finished.is_set() is True
56
+ assert thread_control.running_state == "execute"
57
+
58
+
59
+ def test_reallocate_running_thread_does_not_start_when_state_is_unchanged():
60
+ thread_control = ThreadControl()
61
+ callback_finished = Event()
62
+
63
+ thread_control.request_state("idle", callback_finished.set)
64
+ thread_control.reallocate_running_thread()
65
+ first_thread = thread_control.thread
66
+ first_thread.join(timeout=1)
67
+ callback_finished.clear()
68
+
69
+ thread_control.reallocate_running_thread()
70
+
71
+ assert thread_control.thread is first_thread
72
+ assert callback_finished.is_set() is False
73
+
74
+
75
+ def test_stop_sets_stop_event():
76
+ thread_control = ThreadControl()
77
+
78
+ thread_control.stop()
79
+
80
+ assert thread_control.is_stopped() is True
@@ -0,0 +1,111 @@
1
+ """Implementation of the Procedure Object (PNO MTP 2.0 Part 4, Section 8.2.4)."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from mtppy.utils.logging import logger
9
+ from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
10
+ from mtppy.part3.data_assembly_set.indicator_elements.indicator_element import (
11
+ IndicatorElement,
12
+ )
13
+ from mtppy.part4.process_values.input_elements.input_element import InputElement
14
+ from mtppy.part4.automation_services.service_elements.procedure_health_view import (
15
+ ProcedureHealthView,
16
+ )
17
+ from mtppy.part4.automation_services.service_elements.parameter_elements.parameter_element import (
18
+ ParameterElement,
19
+ )
20
+ from mtppy.utils.data_assembly_dict import DataAssemblyDict
21
+
22
+
23
+ class Procedure:
24
+ def __init__(
25
+ self,
26
+ procedure_id: int,
27
+ tag_name: str,
28
+ tag_description: str = "",
29
+ is_self_completing=False,
30
+ required_equipment: DataAssembly | list[DataAssembly] | None = None,
31
+ ) -> None:
32
+
33
+ self.name = tag_name
34
+ if procedure_id == 0:
35
+ raise ValueError("ProcedureId must be bigger than 0")
36
+ self.procedure_id = procedure_id
37
+ self.is_self_completing = is_self_completing
38
+
39
+ self.procedure_health_view = ProcedureHealthView(tag_name, tag_description)
40
+
41
+ self.procedure_parameters = DataAssemblyDict(ParameterElement)
42
+ self.process_value_ins = DataAssemblyDict(InputElement)
43
+ self.process_value_outs = DataAssemblyDict(IndicatorElement)
44
+ self.report_values = DataAssemblyDict(IndicatorElement)
45
+
46
+ self.required_equipment = DataAssemblyDict(DataAssembly)
47
+ self.add_required_equipment(required_equipment)
48
+
49
+ def add_procedure_parameter(
50
+ self, procedure_parameter: ParameterElement | list[ParameterElement]
51
+ ) -> None:
52
+ """Add one or more procedure parameter to the procedure.
53
+
54
+ Arguments:
55
+ procedure_parameter: ParemeterElement or list of type ParameterElement.
56
+ """
57
+
58
+ self.procedure_parameters.add(procedure_parameter)
59
+
60
+ def add_process_value_in(
61
+ self, process_value_in: InputElement | list[InputElement]
62
+ ) -> None:
63
+ """Add one or more InputElements to the procedure.
64
+
65
+ Arguments:
66
+ process_value_in: InputElement or list of InputElements.
67
+ """
68
+ self.process_value_ins.add(process_value_in)
69
+
70
+ def add_process_value_out(
71
+ self, process_value_out: IndicatorElement | list[IndicatorElement]
72
+ ) -> None:
73
+ """Add one or more IndicatorElements to the procedure.
74
+
75
+ Arguments:
76
+ process_value_out: IndicatorElement or list of IndicatorElement.
77
+ """
78
+ self.process_value_outs.add(process_value_out)
79
+
80
+ def add_report_value(
81
+ self,
82
+ report_value: IndicatorElement | list[IndicatorElement],
83
+ ) -> None:
84
+ """Add one or more IndicatorElements to the procedure as ReportValue.
85
+
86
+ Arguments:
87
+ report_value: IndicatorElement or list of IndicatorElement.
88
+ """
89
+ self.report_values.add(report_value)
90
+
91
+ def apply_procedure_parameters(self) -> None:
92
+ """Apply ProcedureParameters."""
93
+ for procedure_parameter in self.procedure_parameters.values():
94
+ logger.mtp(f"self={self.name},VReq={procedure_parameter["VReq"]}")
95
+ procedure_parameter.update_v_out()
96
+
97
+ def add_required_equipment(
98
+ self,
99
+ equipment: (
100
+ DataAssembly
101
+ | list[DataAssembly | None]
102
+ | dict[str, DataAssembly | None]
103
+ | None
104
+ ),
105
+ ) -> None:
106
+ """Add one or more DataAssemblies to the procedure as Required Equipment.
107
+
108
+ Arguments:
109
+ equipment: DataAssembly or list of DataAssemblies.
110
+ """
111
+ self.required_equipment.add(equipment)