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,185 @@
1
+ """Test Suite for Procedure."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from mtppy.part3.data_assembly_set.indicator_elements.views.ana_view import AnaView
9
+ from mtppy.part3.data_assembly_set.indicator_elements.views.bin_view import BinView
10
+ from mtppy.part3.data_assembly_set.indicator_elements.views.dint_view import DIntView
11
+ from mtppy.part3.data_assembly_set.indicator_elements.views.string_view import StringView
12
+ from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
13
+ from mtppy.part4.process_values.input_elements.bin_process_value_in import BinProcessValueIn
14
+ from mtppy.part4.process_values.input_elements.string_process_value_in import StringProcessValueIn
15
+ from mtppy.part4.automation_services.procedure import Procedure
16
+ from mtppy.part4.automation_services.service_elements.parameter_elements.ana_serv_param import AnaServParam
17
+ from mtppy.part4.automation_services.service_elements.parameter_elements.bin_serv_param import BinServParam
18
+ from mtppy.part4.automation_services.service_elements.parameter_elements.dint_serv_param import DIntServParam
19
+ from mtppy.part4.automation_services.service_elements.parameter_elements.string_serv_param import StringServParam
20
+
21
+
22
+ operation_elements = [
23
+ AnaServParam(
24
+ "serv_param_ana", v_min=0, v_max=50, v_scl_min=0, v_scl_max=10, v_unit=23
25
+ ),
26
+ DIntServParam(
27
+ "serv_param_dint", v_min=-10, v_max=10, v_scl_min=0, v_scl_max=-10, v_unit=23
28
+ ),
29
+ BinServParam("serv_param_bin", v_state_0="state_0", v_state_1="state_1"),
30
+ StringServParam("serv_param_str"),
31
+ ]
32
+
33
+ indicator_elements = [
34
+ AnaView("proc_rv_ana", v_scl_min=0, v_scl_max=10, v_unit=23),
35
+ DIntView("proc_rv_dint", v_scl_min=0, v_scl_max=-10, v_unit=23),
36
+ BinView("proc_rv_bin", v_state_0="state_0", v_state_1="state_1"),
37
+ StringView("proc_rv_str"),
38
+ ]
39
+
40
+
41
+ def create_dummy_procedure():
42
+ return Procedure(1, "DummyProcedure", "procedure for testing", False)
43
+
44
+
45
+ def test_procedure_initialization():
46
+ procedure = Procedure(7, "TestProcedure", "Test description", True)
47
+
48
+ assert procedure.procedure_id == 7
49
+ assert procedure.name == "TestProcedure"
50
+ assert procedure.is_self_completing is True
51
+ assert procedure.procedure_parameters == {}
52
+ assert procedure.process_value_ins == {}
53
+ assert procedure.process_value_outs == {}
54
+ assert procedure.report_values == {}
55
+ assert procedure.required_equipment == {}
56
+
57
+
58
+ def test_add_procedure_parameter_list_and_dict():
59
+ procedure = create_dummy_procedure()
60
+ parameters = [
61
+ AnaServParam("Parameter1", v_min=0, v_max=10, v_scl_min=0, v_scl_max=10, v_unit=23),
62
+ DIntServParam("Parameter2", v_min=-10, v_max=10, v_scl_min=-10, v_scl_max=10, v_unit=23),
63
+ ]
64
+
65
+ procedure.add_procedure_parameter(parameters)
66
+ procedure.add_procedure_parameter({"parameter": parameters[0]})
67
+
68
+ assert procedure.procedure_parameters == {
69
+ parameter.tag_name: parameter for parameter in parameters
70
+ }
71
+
72
+
73
+ def test_add_procedure_parameter():
74
+ valid_parameters = operation_elements
75
+ invalid_parameters = indicator_elements
76
+
77
+ procedure = create_dummy_procedure()
78
+ for valid_parameter in valid_parameters:
79
+ procedure.add_procedure_parameter(valid_parameter)
80
+ assert (
81
+ procedure.procedure_parameters[valid_parameter.tag_name] == valid_parameter
82
+ )
83
+
84
+ procedure = create_dummy_procedure()
85
+ for invalid_parameter in invalid_parameters:
86
+ try:
87
+ procedure.add_procedure_parameter(invalid_parameter)
88
+ assert False
89
+ except TypeError:
90
+ assert True
91
+
92
+
93
+ def test_add_report_values():
94
+ valid_parameters = indicator_elements
95
+ invalid_parameters = operation_elements
96
+
97
+ procedure = create_dummy_procedure()
98
+ for valid_parameter in valid_parameters:
99
+ procedure.add_report_value(valid_parameter)
100
+ assert procedure.report_values[valid_parameter.tag_name] == valid_parameter
101
+
102
+ procedure = create_dummy_procedure()
103
+ for invalid_parameter in invalid_parameters:
104
+ try:
105
+ procedure.add_report_value(invalid_parameter)
106
+ assert False
107
+ except TypeError:
108
+ assert True
109
+
110
+
111
+ def test_add_value_outs():
112
+ valid_parameters = indicator_elements
113
+ invalid_parameters = operation_elements
114
+
115
+ procedure = create_dummy_procedure()
116
+ for valid_parameter in valid_parameters:
117
+ procedure.add_process_value_out(valid_parameter)
118
+ assert procedure.process_value_outs[valid_parameter.tag_name] == valid_parameter
119
+
120
+ procedure = create_dummy_procedure()
121
+ for invalid_parameter in invalid_parameters:
122
+ try:
123
+ procedure.add_process_value_out(invalid_parameter)
124
+ assert False
125
+ except TypeError:
126
+ assert True
127
+
128
+
129
+ def test_add_process_value_ins_single_and_list():
130
+ procedure = create_dummy_procedure()
131
+ process_value_in = BinProcessValueIn("Input1", "Binary input")
132
+ process_value_ins = [
133
+ StringProcessValueIn("Input2", "String input"),
134
+ StringProcessValueIn("Input3", "Another string input"),
135
+ ]
136
+
137
+ procedure.add_process_value_in(process_value_in)
138
+ procedure.add_process_value_in(process_value_ins)
139
+
140
+ assert procedure.process_value_ins == {
141
+ "Input1": process_value_in,
142
+ "Input2": process_value_ins[0],
143
+ "Input3": process_value_ins[1],
144
+ }
145
+
146
+
147
+ def test_add_process_value_in_rejects_wrong_type():
148
+ procedure = create_dummy_procedure()
149
+
150
+ try:
151
+ procedure.add_process_value_in(indicator_elements[0])
152
+ assert False
153
+ except TypeError:
154
+ assert True
155
+
156
+
157
+ def test_add_required_equipment_single_list_dict_and_none():
158
+ equipment = [DataAssembly("Equipment1"), DataAssembly("Equipment2")]
159
+ procedure = Procedure(
160
+ 1,
161
+ "ProcedureWithEquipment",
162
+ required_equipment={"first": equipment[0], "second": equipment[1]},
163
+ )
164
+
165
+ procedure.add_required_equipment(equipment)
166
+ procedure.add_required_equipment(None)
167
+
168
+ assert procedure.required_equipment == {
169
+ "Equipment1": equipment[0],
170
+ "Equipment2": equipment[1],
171
+ }
172
+
173
+
174
+ def test_apply_procedure_parameters(monkeypatch):
175
+ procedure = create_dummy_procedure()
176
+ parameter = AnaServParam(
177
+ "Parameter1", v_min=0, v_max=10, v_scl_min=0, v_scl_max=10, v_unit=23
178
+ )
179
+ update_calls = []
180
+ monkeypatch.setattr(parameter, "update_v_out", lambda: update_calls.append(parameter))
181
+ procedure.add_procedure_parameter(parameter)
182
+
183
+ procedure.apply_procedure_parameters()
184
+
185
+ assert update_calls == [parameter]
@@ -0,0 +1,463 @@
1
+ """Service is an abstract class implementing the concepts specified in
2
+ PNO MTP 2.0 Part 4, Sections 8.2.
3
+ """
4
+
5
+ __copyright__ = (
6
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
7
+ )
8
+ __license__ = "MIT"
9
+
10
+ from abc import ABC, abstractmethod
11
+
12
+ from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
13
+ from mtppy.part4.automation_services.base_functions.service_operation_mode import (
14
+ ServiceOperationMode,
15
+ )
16
+ from mtppy.part4.automation_services.helpers.state_machine import StateMachine
17
+ from mtppy.part4.automation_services.helpers.thread_control import ThreadControl
18
+ from mtppy.part4.automation_services.procedure import Procedure
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,
24
+ )
25
+ from mtppy.utils.logging import logger
26
+
27
+
28
+ def _has_zero_id(
29
+ procedure: Procedure | list[Procedure | None] | dict[str, Procedure | None] | None,
30
+ ) -> bool:
31
+ """Check whether a procedure or procedure collection contains ID zero.
32
+
33
+ Args:
34
+ procedure: A procedure, a list of procedures, a mapping of procedures,
35
+ or ``None``.
36
+
37
+ Returns:
38
+ ``True`` if any supplied procedure has an ID of zero; otherwise,
39
+ ``False``.
40
+ """
41
+ logger.info(f"procedure={procedure}")
42
+ if procedure is None: # No procedure does no harm
43
+ return False
44
+ if isinstance(procedure, dict):
45
+ # Check if any value in the dict has procedure_id == 0, ignoring None values
46
+ return any(p is not None and p.procedure_id == 0 for p in procedure.values())
47
+ if isinstance(procedure, list):
48
+ # Check if any element in the list has procedure_id == 0, ignoring None values
49
+ if all(p is None for p in procedure):
50
+ return False
51
+ return any(p is not None and p.procedure_id == 0 for p in procedure)
52
+ # For a single Procedure object
53
+ return procedure.procedure_id == 0
54
+
55
+
56
+ class Service(ABC):
57
+ """Abstract Base class implementing the Service specification.
58
+
59
+ This abstract base class provides the foundational structure for
60
+ implementing services according to PNO MTP 2.0 Part 4 specifications.
61
+ It handles state management, procedure execution, and configuration
62
+ parameter handling through a state machine and thread control system.
63
+
64
+ It provides functions for all the defined states, such as
65
+ `idle`, `starting`, ..., `resetting`, a.s.o, that shall be implemented
66
+ by instantiable derivatives.
67
+
68
+
69
+ Attributes:
70
+ thread_ctrl: Thread control manager for service operations.
71
+ configuration_parameters: Dictionary of configuration parameters.
72
+ procedures: Dictionary mapping procedure IDs to Procedure objects.
73
+ service_control: Service control instance managing operation modes.
74
+ state_machine: State machine for service state transitions.
75
+ """
76
+
77
+ thread_ctrl: ThreadControl
78
+ configuration_parameters: dict[str, ParameterElement]
79
+ procedures: dict[int, Procedure]
80
+ service_control: ServiceControl
81
+ state_machine: StateMachine
82
+ tag_name: str
83
+
84
+ def __init__(self, tag_name: str, tag_description: str = "") -> None:
85
+ """Initialize a service instance.
86
+
87
+ Args:
88
+ tag_name: Tag name of the service.
89
+ tag_description: Optional description of the service.
90
+ """
91
+ super().__init__()
92
+ self.tag_name = tag_name
93
+ self.tag_description = tag_description
94
+ self.thread_ctrl = ThreadControl()
95
+
96
+ self.configuration_parameters = {}
97
+ self.procedures = {}
98
+
99
+ self.service_control = ServiceControl(
100
+ tag_name,
101
+ tag_description,
102
+ execution_procedure=self.command_execution,
103
+ )
104
+ self.state_machine = StateMachine(
105
+ service_control=self.service_control,
106
+ execution_routine=self._state_change_callback,
107
+ )
108
+
109
+ self.service_control.service_op_mode.add_enter_offline_callback(
110
+ self.state_machine.command_en_ctrl.disable_all,
111
+ )
112
+ self.service_control.service_op_mode.add_exit_offline_callback(
113
+ self.state_machine.command_en_ctrl.set_default,
114
+ )
115
+ self.service_control.service_op_mode.add_exit_offline_callback(
116
+ self.apply_configuration_parameters,
117
+ )
118
+ self.service_control.service_op_mode.add_exit_offline_callback(
119
+ self._init_idle_state,
120
+ )
121
+
122
+ self.state_machine.activate()
123
+
124
+ # Backwards compatibility
125
+ @property
126
+ def name(self) -> str:
127
+ """Return the service tag name.
128
+
129
+ Returns:
130
+ The current service tag name.
131
+ """
132
+ return self.tag_name
133
+
134
+ @name.setter
135
+ def name(self, value: str) -> None:
136
+ """Set the service tag name.
137
+
138
+ Args:
139
+ value: New service tag name.
140
+ """
141
+ self.tag_name = value
142
+
143
+ def command_execution(self, value: int) -> None:
144
+ """Forward a command to the service state machine.
145
+
146
+ Args:
147
+ value: Command value defined by the service control interface.
148
+ """
149
+ self.state_machine.command_execution(value)
150
+
151
+ def _init_idle_state(self) -> None:
152
+ """Initialize the service's idle state."""
153
+ logger.debug(f"Initialize service {self.tag_name}'s idle_state")
154
+ self._state_change_callback()
155
+
156
+ def _state_change_callback(self) -> None:
157
+ """Handle a service state change requested by the state machine."""
158
+ msg = f"{self.tag_name},som={self.service_control.service_op_mode.items()}"
159
+ logger.info(msg)
160
+
161
+ if self.service_control.service_op_mode[ServiceOperationMode.STATE_OFF_ACT]:
162
+ logger.info(
163
+ f"service {self.tag_name} not initializable, StateOffAct = True",
164
+ )
165
+ return
166
+
167
+ state_str = self.state_machine.get_current_state_str()
168
+ logger.info(f"{self.tag_name} in state={state_str}")
169
+ if state_str != "undefined":
170
+ if state_str == "starting": # PNO MTP 2.0 Part 4, Section 8.2.4.4
171
+ # we assume that service_control did set Procedure_Cur
172
+ self.apply_procedure_parameters()
173
+
174
+ function_to_execute = getattr(self, state_str)
175
+ self.thread_ctrl.request_state(state_str, function_to_execute)
176
+ self.thread_ctrl.reallocate_running_thread()
177
+
178
+ if state_str == "idle":
179
+ self.service_control.service_op_mode.allow_switch_to_offline_mode(
180
+ allow_flag=True,
181
+ )
182
+ else:
183
+ self.service_control.service_op_mode.allow_switch_to_offline_mode(
184
+ allow_flag=False,
185
+ )
186
+
187
+ def is_state(self, state_str: str) -> bool:
188
+ """Check whether the service is in a specified state.
189
+
190
+ If the current state differs, the running thread is reallocated.
191
+
192
+ Args:
193
+ state_str: State name to compare with the current state.
194
+
195
+ Returns:
196
+ ``True`` if the service is in ``state_str``; otherwise, ``False``.
197
+ """
198
+ if state_str == self.state_machine.get_current_state_str():
199
+ return True
200
+ self.thread_ctrl.reallocate_running_thread()
201
+ return False
202
+
203
+ def state_change(self) -> None:
204
+ """Request a state change from a cyclic state to the next one."""
205
+ self.state_machine.state_change()
206
+
207
+ def get_param(self, param_name: str) -> DataAssembly | None:
208
+ """Return a named item from the current procedure.
209
+
210
+ Args:
211
+ param_name: Name of the procedure parameter or value to retrieve.
212
+
213
+ Returns:
214
+ The matching data assembly, or ``None`` if no item has that name.
215
+
216
+ Raises:
217
+ RuntimeError: If the service has no current procedure.
218
+ """
219
+ proc: Procedure = self.get_current_procedure()
220
+ if param_name in proc.procedure_parameters:
221
+ return proc.procedure_parameters[param_name]
222
+ if param_name in proc.process_value_ins:
223
+ return proc.process_value_ins[param_name]
224
+ if param_name in proc.process_value_outs:
225
+ return proc.process_value_outs[param_name]
226
+ if param_name in proc.report_values:
227
+ return proc.report_values[param_name]
228
+ if param_name in proc.required_equipment:
229
+ return proc.required_equipment[param_name]
230
+ return None
231
+
232
+ def add_configuration_parameter(
233
+ self,
234
+ configuration_parameter: ParameterElement,
235
+ ) -> None:
236
+ """Add a configuration parameter to the service.
237
+
238
+ Args:
239
+ configuration_parameter: Parameter to add, keyed by its tag name.
240
+ """
241
+ self.configuration_parameters[configuration_parameter.tag_name] = (
242
+ configuration_parameter
243
+ )
244
+
245
+ def apply_configuration_parameters(self) -> None:
246
+ """Apply all configuration parameters to the service.
247
+
248
+ Each parameter is updated through its ``update_v_out`` method.
249
+ """
250
+ logger.debug(f"values={self.configuration_parameters.values()}")
251
+ for configuration_parameter in self.configuration_parameters.values():
252
+ configuration_parameter: ParameterElement
253
+ configuration_parameter.update_v_out()
254
+
255
+ def add_procedure(
256
+ self,
257
+ procedure: (
258
+ Procedure | list[Procedure | None] | dict[str, Procedure | None] | None
259
+ ),
260
+ ) -> None:
261
+ """Add one or more procedures to the service.
262
+
263
+ Args:
264
+ procedure: A procedure, a list of procedures, a mapping of names to
265
+ procedures, or ``None``.
266
+
267
+ Raises:
268
+ ValueError: If any procedure has an ID of zero.
269
+ """
270
+ if procedure is None:
271
+ return
272
+ if _has_zero_id(procedure):
273
+ msg = "Bad ProcedureID, shall not be 0"
274
+ raise ValueError(msg)
275
+ if isinstance(procedure, dict):
276
+ for proc in procedure.values():
277
+ if proc is not None:
278
+ self.procedures[proc.procedure_id] = proc
279
+ self.service_control.add_procedure_key(proc.procedure_id)
280
+ elif isinstance(procedure, list):
281
+ for proc in procedure:
282
+ if proc is not None:
283
+ self.procedures[proc.procedure_id] = proc
284
+ self.service_control.add_procedure_key(proc.procedure_id)
285
+ else:
286
+ self.procedures[procedure.procedure_id] = procedure
287
+ self.service_control.add_procedure_key(procedure.procedure_id)
288
+
289
+ def _lookup_procedure_cur_name(self) -> str:
290
+ """Return the name of the currently selected procedure.
291
+
292
+ Returns:
293
+ The current procedure name, or ``"undefined"`` when no procedure
294
+ is selected.
295
+ """
296
+ procedure_cur: int = self.service_control.get_procedure_cur()
297
+ if procedure_cur == 0:
298
+ return "undefined"
299
+ procedure_name = self.procedures[procedure_cur].name
300
+ logger.debug(
301
+ f"procedureCur={procedure_cur}, procedure_name={procedure_name}",
302
+ )
303
+ return procedure_name
304
+
305
+ def get_current_procedure(self) -> Procedure:
306
+ """Return the currently selected procedure.
307
+
308
+ Returns:
309
+ The procedure selected by ``PROCEDURE_CUR``.
310
+
311
+ Raises:
312
+ RuntimeError: If no current procedure is selected.
313
+ """
314
+ procedure_cur: int = self.service_control.get_procedure_cur()
315
+ if procedure_cur == 0:
316
+ msg = "No current procedure selected"
317
+ raise RuntimeError(msg)
318
+ return self.procedures[procedure_cur]
319
+
320
+ def _get_procedures(self) -> dict[int, Procedure]:
321
+ """Return the service's procedure mapping.
322
+
323
+ Returns:
324
+ A mapping from procedure IDs to procedure objects.
325
+ """
326
+ return self.procedures
327
+
328
+ def apply_procedure_parameters(self) -> None:
329
+ """Apply parameters for the currently selected procedure."""
330
+ msg = f"Applying procedure parameters for service={self.name}"
331
+ logger.debug(msg)
332
+
333
+ current_procedure_id: int = self.service_control.get_procedure_cur()
334
+ if current_procedure_id > 0:
335
+ self.procedures[current_procedure_id].apply_procedure_parameters()
336
+
337
+ @abstractmethod
338
+ def idle(self) -> bool:
339
+ """Execute idle state logic.
340
+
341
+ Returns:
342
+ ``True`` if the state transition is successful; otherwise, ``False``.
343
+ """
344
+
345
+ @abstractmethod
346
+ def starting(self) -> bool:
347
+ """Execute starting state logic.
348
+
349
+ Returns:
350
+ ``True`` if the state transition is successful; otherwise, ``False``.
351
+ """
352
+
353
+ @abstractmethod
354
+ def execute(self) -> bool:
355
+ """Execute execution state logic.
356
+
357
+ Returns:
358
+ ``True`` if the state transition is successful; otherwise, ``False``.
359
+ """
360
+
361
+ @abstractmethod
362
+ def completing(self) -> bool:
363
+ """Execute completing state logic.
364
+
365
+ Returns:
366
+ ``True`` if the state transition is successful; otherwise, ``False``.
367
+ """
368
+
369
+ @abstractmethod
370
+ def completed(self) -> bool:
371
+ """Execute completed state logic.
372
+
373
+ Returns:
374
+ ``True`` if the state transition is successful; otherwise, ``False``.
375
+ """
376
+
377
+ @abstractmethod
378
+ def pausing(self) -> bool:
379
+ """Execute pausing state logic.
380
+
381
+ Returns:
382
+ ``True`` if the state transition is successful; otherwise, ``False``.
383
+ """
384
+
385
+ @abstractmethod
386
+ def paused(self) -> bool:
387
+ """Execute paused state logic.
388
+
389
+ Returns:
390
+ ``True`` if the state transition is successful; otherwise, ``False``.
391
+ """
392
+
393
+ @abstractmethod
394
+ def resuming(self) -> bool:
395
+ """Execute resuming state logic.
396
+
397
+ Returns:
398
+ ``True`` if the state transition is successful; otherwise, ``False``.
399
+ """
400
+
401
+ @abstractmethod
402
+ def holding(self) -> bool:
403
+ """Execute holding state logic.
404
+
405
+ Returns:
406
+ ``True`` if the state transition is successful; otherwise, ``False``.
407
+ """
408
+
409
+ @abstractmethod
410
+ def held(self) -> bool:
411
+ """Execute held state logic.
412
+
413
+ Returns:
414
+ ``True`` if the state transition is successful; otherwise, ``False``.
415
+ """
416
+
417
+ @abstractmethod
418
+ def unholding(self) -> bool:
419
+ """Execute unholding state logic.
420
+
421
+ Returns:
422
+ ``True`` if the state transition is successful; otherwise, ``False``.
423
+ """
424
+
425
+ @abstractmethod
426
+ def stopping(self) -> bool:
427
+ """Execute stopping state logic.
428
+
429
+ Returns:
430
+ ``True`` if the state transition is successful; otherwise, ``False``.
431
+ """
432
+
433
+ @abstractmethod
434
+ def stopped(self) -> bool:
435
+ """Execute stopped state logic.
436
+
437
+ Returns:
438
+ ``True`` if the state transition is successful; otherwise, ``False``.
439
+ """
440
+
441
+ @abstractmethod
442
+ def aborting(self) -> bool:
443
+ """Execute aborting state logic.
444
+
445
+ Returns:
446
+ ``True`` if the state transition is successful; otherwise, ``False``.
447
+ """
448
+
449
+ @abstractmethod
450
+ def aborted(self) -> bool:
451
+ """Execute aborted state logic.
452
+
453
+ Returns:
454
+ ``True`` if the state transition is successful; otherwise, ``False``.
455
+ """
456
+
457
+ @abstractmethod
458
+ def resetting(self) -> bool:
459
+ """Execute resetting state logic.
460
+
461
+ Returns:
462
+ ``True`` if the state transition is successful; otherwise, ``False``.
463
+ """
@@ -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"