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,407 @@
1
+ """test suite for service."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+ from unittest.mock import Mock
8
+
9
+ import pytest
10
+
11
+ from mtppy.part4.automation_services.base_functions.service_operation_mode import (
12
+ ServiceOperationMode as SOM,
13
+ )
14
+ from mtppy.part4.automation_services.base_functions.service_source_mode import (
15
+ ServiceSourceMode as SSM,
16
+ )
17
+ from mtppy.part4.automation_services.procedure import Procedure
18
+ from mtppy.part4.automation_services.service import Service, _has_zero_id
19
+ from mtppy.part4.automation_services.service_elements.parameter_elements.parameter_element import (
20
+ ParameterElement,
21
+ )
22
+ from mtppy.part4.automation_services.service_elements.service_control import (
23
+ ServiceControl as SC,
24
+ )
25
+
26
+
27
+ class MockService(Service):
28
+ def idle(self):
29
+ pass
30
+
31
+ def starting(self):
32
+ pass
33
+
34
+ def execute(self):
35
+ pass
36
+
37
+ def completing(self):
38
+ pass
39
+
40
+ def completed(self):
41
+ pass
42
+
43
+ def pausing(self):
44
+ pass
45
+
46
+ def paused(self):
47
+ pass
48
+
49
+ def resuming(self):
50
+ pass
51
+
52
+ def holding(self):
53
+ pass
54
+
55
+ def held(self):
56
+ pass
57
+
58
+ def unholding(self):
59
+ pass
60
+
61
+ def stopping(self):
62
+ pass
63
+
64
+ def stopped(self):
65
+ pass
66
+
67
+ def aborting(self):
68
+ pass
69
+
70
+ def aborted(self):
71
+ pass
72
+
73
+ def resetting(self):
74
+ pass
75
+
76
+
77
+ def init_service_with_procedures(service_op_mode="off", service_src_mode="int"):
78
+ service = MockService("mock", "my fancy mock service")
79
+
80
+ dummy_procedures = [
81
+ Procedure(1, "first"),
82
+ Procedure(2, "second"),
83
+ Procedure(3, "third"),
84
+ Procedure(4, "forth"),
85
+ ]
86
+
87
+ service.add_procedure(dummy_procedures)
88
+
89
+ if service_op_mode == "off":
90
+ service.service_control.service_op_mode[SOM.STATE_OFF_ACT] = True
91
+ elif service_op_mode == "op":
92
+ service.service_control.service_op_mode[SOM.STATE_OFF_ACT] = False
93
+ service.service_control.service_op_mode[SOM.STATE_OP_ACT] = True
94
+ elif service_op_mode == "aut":
95
+ service.service_control.service_op_mode[SOM.STATE_OFF_ACT] = False
96
+ service.service_control.service_op_mode[SOM.STATE_AUT_ACT] = True
97
+ else:
98
+ raise ValueError(f"ServiceOperationMode {service_op_mode} is unknown")
99
+
100
+ if service_src_mode == "int":
101
+ service.service_control.service_src_mode[SSM.SRC_INT_ACT] = True
102
+ elif service_src_mode == "ext":
103
+ service.service_control.service_src_mode[SSM.SRC_EXT_ACT] = True
104
+ else:
105
+ raise ValueError(f"ServiceSourceMode {service_src_mode} is unknown")
106
+
107
+ return service
108
+
109
+
110
+ def test_service_initialization():
111
+ service = MockService("TestService", "Test Description")
112
+ assert service.name == "TestService"
113
+ assert service.service_control is not None
114
+ assert service.state_machine is not None
115
+
116
+
117
+ def test_service_name_can_be_updated():
118
+ service = MockService("TestService")
119
+
120
+ service.name = "RenamedService"
121
+
122
+ assert service.name == "RenamedService"
123
+ assert service.tag_name == "RenamedService"
124
+
125
+
126
+ def test_add_procedure_single():
127
+ service = MockService("TestService")
128
+ proc = Procedure(1, "Proc1")
129
+ service.add_procedure(proc)
130
+ assert 1 in service.procedures
131
+ assert service.procedures[1].name == "Proc1"
132
+
133
+
134
+ def test_add_procedure_list():
135
+ service = MockService("TestService")
136
+ procs = [Procedure(1, "Proc1"), Procedure(2, "Proc2")]
137
+ service.add_procedure(procs)
138
+ assert 1 in service.procedures
139
+ assert 2 in service.procedures
140
+ assert len(service.procedures) == 2
141
+
142
+
143
+ def test_add_procedure_dict_and_none_values():
144
+ service = MockService("TestService")
145
+ first = Procedure(1, "Proc1")
146
+ second = Procedure(2, "Proc2")
147
+
148
+ service.add_procedure({"first": first, "missing": None, "second": second})
149
+ service.add_procedure(None)
150
+
151
+ assert service.procedures == {1: first, 2: second}
152
+ assert service.service_control.procedure_keys == [1, 2]
153
+
154
+
155
+ def test_create_procedure_invalid_id():
156
+ with pytest.raises(ValueError, match="ProcedureId must be bigger than 0"):
157
+ Procedure(0, "InvalidProc")
158
+
159
+
160
+ def test_add_configuration_parameter():
161
+ service = MockService("TestService")
162
+ param = ParameterElement("Param1", "Desc1")
163
+ service.add_configuration_parameter(param)
164
+ assert "Param1" in service.configuration_parameters
165
+ assert service.configuration_parameters["Param1"] == param
166
+
167
+
168
+ def test_apply_configuration_parameters(monkeypatch):
169
+ service = MockService("TestService")
170
+ param = ParameterElement("Param1", "Desc1")
171
+ update_calls = []
172
+ monkeypatch.setattr(param, "update_v_out", lambda: update_calls.append(param))
173
+ service.add_configuration_parameter(param)
174
+
175
+ service.apply_configuration_parameters()
176
+
177
+ assert update_calls == [param]
178
+
179
+
180
+ def test_apply_procedure_parameters_for_current_procedure(monkeypatch):
181
+ service = MockService("TestService")
182
+ procedure = Procedure(1, "Proc1")
183
+ apply_calls = []
184
+ monkeypatch.setattr(
185
+ procedure,
186
+ "apply_procedure_parameters",
187
+ lambda: apply_calls.append(procedure),
188
+ )
189
+ service.add_procedure(procedure)
190
+ service.service_control[SC.PROCEDURE_CUR] = 1
191
+
192
+ service.apply_procedure_parameters()
193
+
194
+ assert apply_calls == [procedure]
195
+
196
+
197
+ def test_current_procedure_lookup():
198
+ service = MockService("TestService")
199
+ procedure = Procedure(1, "Proc1")
200
+ service.add_procedure(procedure)
201
+ service.service_control[SC.PROCEDURE_CUR] = 1
202
+
203
+ assert service.get_current_procedure() is procedure
204
+ assert service._lookup_procedure_cur_name() == "Proc1"
205
+
206
+
207
+ def test_lookup_procedure_name_without_current_procedure():
208
+ service = MockService("TestService")
209
+ service.service_control[SC.PROCEDURE_CUR] = 0
210
+
211
+ assert service._lookup_procedure_cur_name() == "undefined"
212
+
213
+
214
+ def test_get_procedures_returns_service_procedures():
215
+ service = MockService("TestService")
216
+
217
+ assert service._get_procedures() is service.procedures
218
+
219
+
220
+ def test_get_param_returns_current_procedure_parameter(monkeypatch):
221
+ service = MockService("TestService")
222
+ procedure = Mock()
223
+ parameter = Mock()
224
+ procedure.procedure_parameters = {"param1": parameter}
225
+ procedure.process_value_ins = {}
226
+ procedure.process_value_outs = {}
227
+ procedure.report_values = {}
228
+ procedure.required_equipment = {}
229
+ monkeypatch.setattr(service, "get_current_procedure", lambda: procedure)
230
+
231
+ assert service.get_param("param1") is parameter
232
+
233
+
234
+ def test_get_param_returns_none_when_parameter_is_not_found(monkeypatch):
235
+ service = MockService("TestService")
236
+ procedure = Mock()
237
+ procedure.procedure_parameters = {}
238
+ procedure.process_value_ins = {}
239
+ procedure.process_value_outs = {}
240
+ procedure.report_values = {}
241
+ procedure.required_equipment = {}
242
+ monkeypatch.setattr(service, "get_current_procedure", lambda: procedure)
243
+
244
+ assert service.get_param("missing") is None
245
+
246
+
247
+ def test_is_state_reallocates_when_state_does_not_match(monkeypatch):
248
+ service = MockService("TestService")
249
+ reallocate_calls = []
250
+ monkeypatch.setattr(
251
+ service.thread_ctrl,
252
+ "reallocate_running_thread",
253
+ lambda: reallocate_calls.append(True),
254
+ )
255
+
256
+ assert service.is_state("idle") is True
257
+ assert service.is_state("execute") is False
258
+ assert reallocate_calls == [True]
259
+
260
+
261
+ def test_state_change_delegates_to_state_machine(monkeypatch):
262
+ service = MockService("TestService")
263
+ state_change_calls = []
264
+ monkeypatch.setattr(
265
+ service.state_machine,
266
+ "state_change",
267
+ lambda: state_change_calls.append(True),
268
+ )
269
+
270
+ service.state_change()
271
+
272
+ assert state_change_calls == [True]
273
+
274
+
275
+ def test_get_current_procedure_none():
276
+ service = MockService("TestService")
277
+ service.service_control[SC.PROCEDURE_REQ] = 0
278
+ with pytest.raises(RuntimeError, match="No current procedure"):
279
+ service.get_current_procedure()
280
+
281
+
282
+ def test_service_state_callback_returns_while_offline(monkeypatch):
283
+ service = MockService("TestService")
284
+ state_calls = []
285
+ monkeypatch.setattr(
286
+ service.state_machine,
287
+ "get_current_state_str",
288
+ lambda: state_calls.append(True) or "idle",
289
+ )
290
+ service.service_control.service_op_mode[SOM.STATE_OFF_ACT] = True
291
+
292
+ service._state_change_callback()
293
+
294
+ assert state_calls == []
295
+
296
+
297
+ def test_service_state_callback_handles_undefined_state(monkeypatch):
298
+ service = MockService("TestService")
299
+ requested_states = []
300
+ monkeypatch.setattr(
301
+ service.state_machine,
302
+ "get_current_state_str",
303
+ lambda: "undefined",
304
+ )
305
+ monkeypatch.setattr(
306
+ service.service_control.service_op_mode,
307
+ "allow_switch_to_offline_mode",
308
+ lambda allow_flag: requested_states.append(allow_flag),
309
+ )
310
+ service.service_control.service_op_mode[SOM.STATE_OFF_ACT] = False
311
+
312
+ service._state_change_callback()
313
+
314
+ assert requested_states == [False]
315
+
316
+
317
+ def test_service_state_callback_applies_starting_procedure_parameters(monkeypatch):
318
+ service = MockService("TestService")
319
+ procedure = Procedure(1, "Proc1")
320
+ service.add_procedure(procedure)
321
+ service.service_control[SC.PROCEDURE_CUR] = 1
322
+ apply_calls = []
323
+ thread_calls = []
324
+ monkeypatch.setattr(
325
+ service,
326
+ "apply_procedure_parameters",
327
+ lambda: apply_calls.append(True),
328
+ )
329
+ monkeypatch.setattr(
330
+ service.state_machine,
331
+ "get_current_state_str",
332
+ lambda: "starting",
333
+ )
334
+ monkeypatch.setattr(
335
+ service.thread_ctrl,
336
+ "request_state",
337
+ lambda state, callback: thread_calls.append((state, callback)),
338
+ )
339
+ monkeypatch.setattr(
340
+ service.thread_ctrl,
341
+ "reallocate_running_thread",
342
+ lambda: thread_calls.append("reallocate"),
343
+ )
344
+ monkeypatch.setattr(
345
+ service.service_control.service_op_mode,
346
+ "allow_switch_to_offline_mode",
347
+ lambda allow_flag: thread_calls.append(("offline", allow_flag)),
348
+ )
349
+ service.service_control.service_op_mode[SOM.STATE_OFF_ACT] = False
350
+
351
+ service._state_change_callback()
352
+
353
+ assert apply_calls == [True]
354
+ assert thread_calls[0][0] == "starting"
355
+ assert thread_calls[1] == "reallocate"
356
+ assert thread_calls[2] == ("offline", False)
357
+
358
+
359
+ def test_service_state_callback_allows_offline_only_in_idle(monkeypatch):
360
+ service = MockService("TestService")
361
+ offline_requests = []
362
+ monkeypatch.setattr(
363
+ service.state_machine,
364
+ "get_current_state_str",
365
+ lambda: "idle",
366
+ )
367
+ monkeypatch.setattr(
368
+ service.thread_ctrl,
369
+ "request_state",
370
+ lambda state, callback: None,
371
+ )
372
+ monkeypatch.setattr(
373
+ service.thread_ctrl,
374
+ "reallocate_running_thread",
375
+ lambda: None,
376
+ )
377
+ monkeypatch.setattr(
378
+ service.service_control.service_op_mode,
379
+ "allow_switch_to_offline_mode",
380
+ lambda allow_flag: offline_requests.append(allow_flag),
381
+ )
382
+ service.service_control.service_op_mode[SOM.STATE_OFF_ACT] = False
383
+
384
+ service._state_change_callback()
385
+
386
+ assert offline_requests == [True]
387
+
388
+
389
+ class ProcedureIdDouble:
390
+ def __init__(self, procedure_id):
391
+ self.procedure_id = procedure_id
392
+
393
+
394
+ @pytest.mark.parametrize(
395
+ ("procedure", "expected"),
396
+ [
397
+ (None, False),
398
+ (ProcedureIdDouble(1), False),
399
+ (ProcedureIdDouble(0), True),
400
+ ([None, ProcedureIdDouble(1)], False),
401
+ ([None, ProcedureIdDouble(0)], True),
402
+ ({"missing": None, "valid": ProcedureIdDouble(1)}, False),
403
+ ({"missing": None, "zero": ProcedureIdDouble(0)}, True),
404
+ ],
405
+ )
406
+ def test_has_zero_id_supports_procedure_shapes(procedure, expected):
407
+ assert _has_zero_id(procedure) is expected
@@ -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,58 @@
1
+ """Definition of CommandCodes."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from enum import Enum
9
+
10
+
11
+ class CommandCodes(Enum):
12
+ """
13
+ List of commands defined for MTP services
14
+ """
15
+
16
+ RESET = ("reset", 2)
17
+ START = ("start", 4)
18
+ STOP = ("stop", 8)
19
+ HOLD = ("hold", 16)
20
+ UNHOLD = ("unhold", 32)
21
+ PAUSE = ("pause", 64)
22
+ RESUME = ("resume", 128)
23
+ ABORT = ("abort", 256)
24
+ RESTART = ("restart", 512)
25
+ COMPLETE = ("complete", 1024)
26
+
27
+ def __init__(self, string: str, code: int):
28
+ self.string = string
29
+ self.code = code
30
+
31
+ @classmethod
32
+ def list_of_strings(cls) -> list[str]:
33
+ return [c.string for c in CommandCodes]
34
+
35
+ @classmethod
36
+ def list_of_codes(cls) -> list[int]:
37
+ return [c.code for c in CommandCodes]
38
+
39
+ @classmethod
40
+ def from_code(cls, code: int) -> str:
41
+ for member in cls:
42
+ if member.code == code:
43
+ return member.string
44
+ raise ValueError(f"Unknown command code: {code}")
45
+
46
+ @classmethod
47
+ def from_string(cls, string: str) -> int:
48
+ search_string = string.lower()
49
+ for member in cls:
50
+ if member.string == search_string:
51
+ return member.code
52
+ raise ValueError(f"Unknown command string: {string}")
53
+
54
+ def __str__(self):
55
+ return self.string
56
+
57
+ def __int__(self):
58
+ return self.code
@@ -0,0 +1,44 @@
1
+ """Test suite for CommandCodes."""
2
+
3
+ import pytest
4
+
5
+ from mtppy.part4.definitions.command_codes import CommandCodes
6
+
7
+
8
+ def test_command_metadata_and_conversions():
9
+ assert CommandCodes.START.string == "start"
10
+ assert CommandCodes.START.code == 4
11
+ assert str(CommandCodes.START) == "start"
12
+ assert int(CommandCodes.START) == 4
13
+
14
+
15
+ def test_list_helpers_return_all_commands_in_enum_order():
16
+ assert CommandCodes.list_of_strings() == [member.string for member in CommandCodes]
17
+ assert CommandCodes.list_of_codes() == [member.code for member in CommandCodes]
18
+ assert len(CommandCodes.list_of_codes()) == len(CommandCodes)
19
+
20
+
21
+ @pytest.mark.parametrize("code", [member.code for member in CommandCodes])
22
+ def test_from_code_returns_command_string(code):
23
+ member = next(member for member in CommandCodes if member.code == code)
24
+
25
+ assert CommandCodes.from_code(code) == member.string
26
+
27
+
28
+ @pytest.mark.parametrize("string", ["RESET", "Start", "complete"])
29
+ def test_from_string_is_case_insensitive(string):
30
+ expected = next(
31
+ member.code for member in CommandCodes if member.string == string.lower()
32
+ )
33
+
34
+ assert CommandCodes.from_string(string) == expected
35
+
36
+
37
+ def test_invalid_command_code_is_rejected():
38
+ with pytest.raises(ValueError, match="Unknown command code: 999"):
39
+ CommandCodes.from_code(999)
40
+
41
+
42
+ def test_invalid_command_string_is_rejected():
43
+ with pytest.raises(ValueError, match="Unknown command string: launch"):
44
+ CommandCodes.from_string("launch")
@@ -0,0 +1,65 @@
1
+ """Definition of StateCodes."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from enum import Enum
9
+
10
+
11
+ class StateCodes(Enum):
12
+ """
13
+ List of states defined for MTP services
14
+ """
15
+
16
+ UNDEFINED = ("undefined", 0)
17
+ STOPPED = ("stopped", 4)
18
+ STARTING = ("starting", 8)
19
+ IDLE = ("idle", 16)
20
+ PAUSED = ("paused", 32)
21
+ EXECUTE = ("execute", 64)
22
+ STOPPING = ("stopping", 128)
23
+ ABORTING = ("aborting", 256)
24
+ ABORTED = ("aborted", 512)
25
+ HOLDING = ("holding", 1024)
26
+ HELD = ("held", 2048)
27
+ UNHOLDING = ("unholding", 4096)
28
+ PAUSING = ("pausing", 8192)
29
+ RESUMING = ("resuming", 16384)
30
+ RESETTING = ("resetting", 32678)
31
+ COMPLETING = ("completing", 65536)
32
+ COMPLETED = ("completed", 131072)
33
+
34
+ def __init__(self, string: str, code: int):
35
+ self.string = string
36
+ self.code = code
37
+
38
+ @classmethod
39
+ def list_of_strings(cls) -> list[str]:
40
+ return [c.string for c in StateCodes]
41
+
42
+ @classmethod
43
+ def list_of_codes(cls) -> list[int]:
44
+ return [c.code for c in StateCodes]
45
+
46
+ @classmethod
47
+ def from_code(cls, code: int) -> str:
48
+ for member in cls:
49
+ if member.code == code:
50
+ return member.string
51
+ raise ValueError(f"Unknown state: {code}")
52
+
53
+ @classmethod
54
+ def from_string(cls, string: str) -> int:
55
+ search_string = string.lower()
56
+ for member in cls:
57
+ if member.string == search_string:
58
+ return member.code
59
+ raise ValueError(f"Unknown state string: {string}")
60
+
61
+ def __str__(self):
62
+ return self.string
63
+
64
+ def __int__(self):
65
+ return self.code
@@ -0,0 +1,44 @@
1
+ """Test suite for StateCodes."""
2
+
3
+ import pytest
4
+
5
+ from mtppy.part4.definitions.state_codes import StateCodes
6
+
7
+
8
+ def test_state_metadata_and_conversions():
9
+ assert StateCodes.IDLE.string == "idle"
10
+ assert StateCodes.IDLE.code == 16
11
+ assert str(StateCodes.IDLE) == "idle"
12
+ assert int(StateCodes.IDLE) == 16
13
+
14
+
15
+ def test_list_helpers_return_all_states_in_enum_order():
16
+ assert StateCodes.list_of_strings() == [member.string for member in StateCodes]
17
+ assert StateCodes.list_of_codes() == [member.code for member in StateCodes]
18
+ assert len(StateCodes.list_of_codes()) == len(StateCodes)
19
+
20
+
21
+ @pytest.mark.parametrize("code", [member.code for member in StateCodes])
22
+ def test_from_code_returns_state_string(code):
23
+ member = next(member for member in StateCodes if member.code == code)
24
+
25
+ assert StateCodes.from_code(code) == member.string
26
+
27
+
28
+ @pytest.mark.parametrize("string", ["UNDEFINED", "Idle", "COMPLETED"])
29
+ def test_from_string_is_case_insensitive(string):
30
+ expected = next(
31
+ member.code for member in StateCodes if member.string == string.lower()
32
+ )
33
+
34
+ assert StateCodes.from_string(string) == expected
35
+
36
+
37
+ def test_invalid_state_code_is_rejected():
38
+ with pytest.raises(ValueError, match="Unknown state: 999"):
39
+ StateCodes.from_code(999)
40
+
41
+
42
+ def test_invalid_state_string_is_rejected():
43
+ with pytest.raises(ValueError, match="Unknown state string: launch"):
44
+ StateCodes.from_string("launch")
@@ -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,48 @@
1
+ """Implementation of the VQC Base Function."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from typing import ClassVar
9
+
10
+ import utils.type_conversion as tc
11
+ from mtppy.part3.definitions.mtp_access import MTPAccess
12
+ from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
13
+ from mtppy.utils.data_item import DataItem, DataItemCollection
14
+
15
+
16
+ class VQCBaseFunction(DataItemCollection):
17
+ """Provide the VQC process value base function.
18
+
19
+ This base function stores the VQC value and exposes it through a
20
+ read/write data item.
21
+ """
22
+
23
+ VQC_ = "VQC"
24
+
25
+ CLASS_DATA_ITEMS: ClassVar = {
26
+ VQC_: DataItem(
27
+ VQC_,
28
+ W3cXmlDataTypes.UNSIGNED_BYTE,
29
+ MTPAccess.ReadWriteAccess,
30
+ 255,
31
+ ),
32
+ }
33
+
34
+ def __init__(self) -> None:
35
+ """Initialize the VQC BaseFunction.
36
+
37
+ Sets up the VQC data item with the appropriate write callback.
38
+ """
39
+ super().__init__()
40
+
41
+ self.get_item(VQCBaseFunction.VQC_).ext_wrt_cb = self._set_vqc
42
+
43
+ def _set_vqc(self, value: tc.ANY_TYPE) -> None:
44
+ self[VQCBaseFunction.VQC_] = value
45
+
46
+ def get_vqc(self) -> int:
47
+ """Return the current VQC value."""
48
+ return tc.cast_to_int(self[VQCBaseFunction.VQC_])
@@ -0,0 +1,4 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"