MTPPy2.0 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- examples/__init__.py +0 -0
- examples/minimal+pid.py +82 -0
- examples/minimal.py +32 -0
- examples/minimal_recipe.py +117 -0
- examples/pea_alarm_base.py +47 -0
- examples/pea_minimal.py +258 -0
- mtppy/__init__.py +1 -0
- mtppy/part1/__init__.py +4 -0
- mtppy/part1/empty_folder_test.py +7 -0
- mtppy/part3/__init__.py +4 -0
- mtppy/part3/data_assembly_set/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/active_element.py +22 -0
- mtppy/part3/data_assembly_set/active_elements/drives/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/drives/ana_drv.py +446 -0
- mtppy/part3/data_assembly_set/active_elements/drives/ana_drv_test.py +567 -0
- mtppy/part3/data_assembly_set/active_elements/drives/bin_drv.py +365 -0
- mtppy/part3/data_assembly_set/active_elements/drives/bin_drv_test.py +503 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv.py +469 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv_test.py +418 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv.py +295 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv_test.py +265 -0
- mtppy/part3/data_assembly_set/active_elements/pid/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl.py +333 -0
- mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl_test.py +156 -0
- mtppy/part3/data_assembly_set/active_elements/valves/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv.py +402 -0
- mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv_test.py +732 -0
- mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv.py +279 -0
- mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv_test.py +585 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv.py +402 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv_test.py +270 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv.py +286 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv_test.py +224 -0
- mtppy/part3/data_assembly_set/base_functions/__init__.py +4 -0
- mtppy/part3/data_assembly_set/base_functions/operation_mode.py +252 -0
- mtppy/part3/data_assembly_set/base_functions/operation_mode_test.py +260 -0
- mtppy/part3/data_assembly_set/base_functions/oslevel.py +35 -0
- mtppy/part3/data_assembly_set/base_functions/source_mode.py +189 -0
- mtppy/part3/data_assembly_set/base_functions/source_mode_test.py +112 -0
- mtppy/part3/data_assembly_set/base_functions/wqc.py +75 -0
- mtppy/part3/data_assembly_set/base_functions/wqc_test.py +86 -0
- mtppy/part3/data_assembly_set/data_assembly.py +65 -0
- mtppy/part3/data_assembly_set/diagnostic_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/diagnostic_elements/diagnostic_element.py +19 -0
- mtppy/part3/data_assembly_set/indicator_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/indicator_elements/indicator_element.py +22 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/__init__.py +4 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/ana_view.py +68 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/bin_view.py +73 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/dint_view.py +76 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/string_view.py +55 -0
- mtppy/part3/data_assembly_set/operation_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/operation_elements/operation_element.py +19 -0
- mtppy/part3/data_assembly_set/pea_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/pea_elements/pea_element.py +28 -0
- mtppy/part3/data_assembly_set/pea_elements/pea_information_label/__init__.py +4 -0
- mtppy/part3/data_assembly_set/pea_elements/pea_information_label/pea_information_label.py +231 -0
- mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/__init__.py +4 -0
- mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/web_server_url_info.py +42 -0
- mtppy/part3/definitions/__init__.py +4 -0
- mtppy/part3/definitions/mtp_access.py +15 -0
- mtppy/part4/__init__.py +4 -0
- mtppy/part4/automation_services/__init__.py +4 -0
- mtppy/part4/automation_services/base_functions/__init__.py +4 -0
- mtppy/part4/automation_services/base_functions/service_operation_mode.py +368 -0
- mtppy/part4/automation_services/base_functions/service_operation_mode_test.py +242 -0
- mtppy/part4/automation_services/base_functions/service_operator_interaction.py +62 -0
- mtppy/part4/automation_services/base_functions/service_source_mode.py +138 -0
- mtppy/part4/automation_services/base_functions/service_source_mode_test.py +179 -0
- mtppy/part4/automation_services/helpers/__init__.py +4 -0
- mtppy/part4/automation_services/helpers/command_en_control.py +330 -0
- mtppy/part4/automation_services/helpers/command_en_control_test.py +302 -0
- mtppy/part4/automation_services/helpers/state_machine.py +167 -0
- mtppy/part4/automation_services/helpers/state_machine_test.py +294 -0
- mtppy/part4/automation_services/helpers/thread_control.py +39 -0
- mtppy/part4/automation_services/helpers/thread_control_test.py +80 -0
- mtppy/part4/automation_services/procedure.py +111 -0
- mtppy/part4/automation_services/procedure_test.py +185 -0
- mtppy/part4/automation_services/service.py +463 -0
- mtppy/part4/automation_services/service_elements/__init__.py +4 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/__init__.py +4 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param.py +108 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param_test.py +59 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param.py +70 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param_test.py +39 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/common_serv_param.py +111 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param.py +77 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param_test.py +58 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element.py +175 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element_test.py +246 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param.py +48 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param_test.py +37 -0
- mtppy/part4/automation_services/service_elements/procedure_health_view.py +36 -0
- mtppy/part4/automation_services/service_elements/service_control.py +473 -0
- mtppy/part4/automation_services/service_elements/service_control_test.py +150 -0
- mtppy/part4/automation_services/service_elements/service_element.py +20 -0
- mtppy/part4/automation_services/service_test.py +407 -0
- mtppy/part4/definitions/__init__.py +4 -0
- mtppy/part4/definitions/command_codes.py +58 -0
- mtppy/part4/definitions/command_codes_test.py +44 -0
- mtppy/part4/definitions/state_codes.py +65 -0
- mtppy/part4/definitions/state_codes_test.py +44 -0
- mtppy/part4/process_values/__init__.py +4 -0
- mtppy/part4/process_values/base_functions/__init__.py +4 -0
- mtppy/part4/process_values/base_functions/vqc.py +48 -0
- mtppy/part4/process_values/input_elements/__init__.py +4 -0
- mtppy/part4/process_values/input_elements/ana_process_value_in.py +160 -0
- mtppy/part4/process_values/input_elements/bin_process_value_in.py +83 -0
- mtppy/part4/process_values/input_elements/dint_process_value_in.py +159 -0
- mtppy/part4/process_values/input_elements/input_element.py +40 -0
- mtppy/part4/process_values/input_elements/string_process_value_in.py +45 -0
- mtppy/part5/__init__.py +4 -0
- mtppy/part5/communication_object.py +99 -0
- mtppy/part5/communication_object_test.py +59 -0
- mtppy/part5/definitions/__init__.py +4 -0
- mtppy/part5/definitions/iec61131_data_types.py +31 -0
- mtppy/part5/definitions/iec61131_data_types_test.py +82 -0
- mtppy/part5/definitions/opcua_data_types.py +36 -0
- mtppy/part5/definitions/opcua_data_types_test.py +90 -0
- mtppy/part5/definitions/w3c_xml_data_types.py +177 -0
- mtppy/part5/definitions/w3c_xml_data_types_test.py +143 -0
- mtppy/part5/server_assembly_set/__init__.py +5 -0
- mtppy/part6/__init__.py +4 -0
- mtppy/part6/alarm_set/__init__.py +4 -0
- mtppy/part6/alarm_set/alarm.py +132 -0
- mtppy/part6/alarm_set/alarm_test.py +265 -0
- mtppy/pea/__init__.py +19 -0
- mtppy/pea/data_assembly_factory.py +124 -0
- mtppy/pea/data_assembly_factory_test.py +229 -0
- mtppy/pea/mtp_generator/__init__.py +4 -0
- mtppy/pea/mtp_generator/mtp_generator.py +1183 -0
- mtppy/pea/mtp_generator/mtp_generator_test.py +518 -0
- mtppy/pea/pea.py +139 -0
- mtppy/pea/pea_opcua_server/__init__.py +4 -0
- mtppy/pea/pea_opcua_server/marshalling.py +60 -0
- mtppy/pea/pea_opcua_server/opcua_options_provider/__init__.py +4 -0
- mtppy/pea/pea_opcua_server/opcua_options_provider/instances/basic_opcua_options_provider.py +50 -0
- mtppy/pea/pea_opcua_server/opcua_options_provider/opcua_options_provider.py +103 -0
- mtppy/pea/pea_opcua_server/pea_opcua_server.py +492 -0
- mtppy/pea/pea_opcua_server/subscription_list.py +55 -0
- mtppy/pea/pea_test.py +59 -0
- mtppy/pea/rmtp_pea.py +155 -0
- mtppy/pea/rmtp_pea_test.py +27 -0
- mtppy/pea/service_factory.py +178 -0
- mtppy/pea/service_factory_test.py +156 -0
- mtppy/pea/user_logic/__init__.py +4 -0
- mtppy/pea/user_logic/user_logic_pea.py +72 -0
- mtppy/pea/user_logic/user_logic_registry.py +106 -0
- mtppy/pea/user_logic/user_logic_registry_test.py +167 -0
- mtppy/pea/user_logic/user_logic_service.py +228 -0
- mtppy/pea/user_logic/user_logic_service_test.py +289 -0
- mtppy/utils/__init__.py +4 -0
- mtppy/utils/data_assembly_dict.py +61 -0
- mtppy/utils/data_assembly_dict_test.py +107 -0
- mtppy/utils/data_item.py +446 -0
- mtppy/utils/data_item_com_test.py +82 -0
- mtppy/utils/data_item_test.py +325 -0
- mtppy/utils/logging.py +23 -0
- mtppy/utils/logging_test.py +26 -0
- mtppy/utils/mtp_file_handling/__init__.py +4 -0
- mtppy/utils/mtp_file_handling/extract_manifest_from_mtp_file.py +22 -0
- mtppy/utils/mtp_file_handling/extract_mtp_file.py +22 -0
- mtppy/utils/mtp_file_handling/is_valid_mtp.py +22 -0
- mtppy/utils/mtp_file_handling/is_valid_path_for_manifest.py +15 -0
- mtppy/utils/mtp_file_handling/is_valid_path_to_manifest.py +17 -0
- mtppy/utils/mtp_file_handling/is_valid_path_to_mtp.py +17 -0
- mtppy/utils/mtp_file_handling/load_manifest_file.py +24 -0
- mtppy/utils/mtp_file_handling/mtp_reader/__init__.py +4 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader.py +103 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader_test.py +96 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml.py +226 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml_test.py +108 -0
- mtppy/utils/mtp_file_handling/mtp_writer/__init__.py +4 -0
- mtppy/utils/mtp_file_handling/mtp_writer/mtp_writer.py +77 -0
- mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_content_types.py +10 -0
- mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_rels.py +11 -0
- mtppy/utils/mtp_file_handling/mtp_writer/test_mtp_writer.py +25 -0
- mtppy/utils/mtp_file_handling/write_manifest_file.py +29 -0
- mtppy/utils/mtp_file_handling/write_manifest_file_test.py +22 -0
- mtppy/utils/mtp_file_handling/write_mtp_file.py +43 -0
- mtppy/utils/mtp_file_handling/write_mtp_file_test.py +22 -0
- mtppy/utils/testing.py +31 -0
- mtppy2_0-0.1.5.dist-info/METADATA +75 -0
- mtppy2_0-0.1.5.dist-info/RECORD +214 -0
- mtppy2_0-0.1.5.dist-info/WHEEL +5 -0
- mtppy2_0-0.1.5.dist-info/entry_points.txt +3 -0
- mtppy2_0-0.1.5.dist-info/licenses/LICENSE +21 -0
- mtppy2_0-0.1.5.dist-info/top_level.txt +4 -0
- tools/__init__.py +0 -0
- tools/create_bugreport.py +150 -0
- tools/inspect_mtp.py +110 -0
- utils/__init__.py +1 -0
- utils/file_handling/__init__.py +4 -0
- utils/file_handling/cleanup_folder.py +9 -0
- utils/file_handling/get_utc_foldername_with_ms.py +15 -0
- utils/file_handling/get_work_dir.py +9 -0
- utils/file_handling/is_valid_path_for_json.py +11 -0
- utils/file_handling/is_valid_path_to_json.py +17 -0
- utils/file_handling/load_json_file.py +18 -0
- utils/file_handling/read_file_from_file_archive.py +43 -0
- utils/file_handling/read_file_string.py +10 -0
- utils/file_handling/validate_json_data.py +28 -0
- utils/file_handling/write_json_file.py +23 -0
- utils/get_uuid.py +18 -0
- utils/get_uuid_test.py +17 -0
- utils/logging.py +100 -0
- utils/logging_test.py +67 -0
- utils/opcua/check_node.py +79 -0
- utils/resolve_unit/resolve_unit.py +11 -0
- utils/resolve_unit/unit_lib.py +2101 -0
- utils/resolve_unit/unit_lib_test.py +36 -0
- utils/type_conversion.py +236 -0
- utils/type_conversion_test.py +114 -0
|
@@ -0,0 +1,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)
|