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,368 @@
1
+ """
2
+ Implementation of ServiceOperationMode BaseFunction as specified in PNO MTP 2.0 Part 4, Sections 8.2.2.3
3
+
4
+ The ServiceOperationMode BaseFunction is a concept that allows the service to select its operation mode
5
+ based on the state of the service and the configuration parameters. It is used to determine whether
6
+ the service should operate in offline, operator, or automatic mode.
7
+
8
+ - Manual Operations: Operator actions are conveyed via the `Op` variables,
9
+ - Automatic Operation: PEA-internal actions made visible via the `Aut` variables
10
+ - Actual Operation: The actual state of the service is reflected in the `Act` variables.
11
+
12
+ """
13
+
14
+ __copyright__ = (
15
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
16
+ )
17
+ __license__ = "MIT"
18
+
19
+ from collections.abc import Callable
20
+ from typing import ClassVar
21
+
22
+ import inspect
23
+
24
+ from mtppy.utils.logging import logger
25
+ from mtppy.utils.data_item import DataItem, DataItemCollection
26
+ from mtppy.part3.definitions.mtp_access import MTPAccess
27
+ from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
28
+
29
+
30
+ class ServiceOperationMode(DataItemCollection):
31
+ STATE_CHANNEL = "StateChannel"
32
+ STATE_OFF_AUT = "StateOffAut"
33
+ STATE_OP_AUT = "StateOpAut"
34
+ STATE_AUT_AUT = "StateAutAut"
35
+ STATE_OFF_OP = "StateOffOp"
36
+ STATE_OP_OP = "StateOpOp"
37
+ STATE_AUT_OP = "StateAutOp"
38
+ STATE_OP_ACT = "StateOpAct"
39
+ STATE_AUT_ACT = "StateAutAct"
40
+ STATE_OFF_ACT = "StateOffAct"
41
+
42
+ CLASS_DATA_ITEMS: ClassVar = {
43
+ STATE_CHANNEL: DataItem(
44
+ STATE_CHANNEL,
45
+ W3cXmlDataTypes.BOOLEAN,
46
+ MTPAccess.ReadAccess,
47
+ init_value=False,
48
+ ),
49
+ STATE_OFF_AUT: DataItem(
50
+ STATE_OFF_AUT,
51
+ W3cXmlDataTypes.BOOLEAN,
52
+ MTPAccess.ReadAccess,
53
+ init_value=False,
54
+ ),
55
+ STATE_OP_AUT: DataItem(
56
+ STATE_OP_AUT,
57
+ W3cXmlDataTypes.BOOLEAN,
58
+ MTPAccess.ReadAccess,
59
+ init_value=False,
60
+ ),
61
+ STATE_AUT_AUT: DataItem(
62
+ STATE_AUT_AUT,
63
+ W3cXmlDataTypes.BOOLEAN,
64
+ MTPAccess.ReadAccess,
65
+ init_value=False,
66
+ ),
67
+ STATE_OFF_OP: DataItem(
68
+ STATE_OFF_OP,
69
+ W3cXmlDataTypes.BOOLEAN,
70
+ MTPAccess.ReadWriteAccess,
71
+ init_value=False,
72
+ # self.set_state_off_op,
73
+ ),
74
+ STATE_OP_OP: DataItem(
75
+ STATE_OP_OP,
76
+ W3cXmlDataTypes.BOOLEAN,
77
+ MTPAccess.ReadWriteAccess,
78
+ init_value=False,
79
+ # self.set_state_op_op,
80
+ ),
81
+ STATE_AUT_OP: DataItem(
82
+ STATE_AUT_OP,
83
+ W3cXmlDataTypes.BOOLEAN,
84
+ MTPAccess.ReadWriteAccess,
85
+ init_value=False,
86
+ # self.set_state_aut_op,
87
+ ),
88
+ STATE_OP_ACT: DataItem(
89
+ STATE_OP_ACT,
90
+ W3cXmlDataTypes.BOOLEAN,
91
+ MTPAccess.ReadAccess,
92
+ init_value=False,
93
+ ),
94
+ STATE_AUT_ACT: DataItem(
95
+ STATE_AUT_ACT,
96
+ W3cXmlDataTypes.BOOLEAN,
97
+ MTPAccess.ReadAccess,
98
+ init_value=False,
99
+ ),
100
+ STATE_OFF_ACT: DataItem(
101
+ STATE_OFF_ACT,
102
+ W3cXmlDataTypes.BOOLEAN,
103
+ MTPAccess.ReadAccess,
104
+ init_value=True,
105
+ ),
106
+ }
107
+
108
+ switch_to_offline_mode_allowed: bool
109
+
110
+ enter_offline_callbacks: list[Callable[[], None]]
111
+ exit_offline_callbacks: list[Callable[[], None]]
112
+ enter_operator_callbacks: list[Callable[[], None]]
113
+ exit_operator_callbacks: list[Callable[[], None]]
114
+ enter_automatic_callbacks: list[Callable[[], None]]
115
+ exit_automatic_callbacks: list[Callable[[], None]]
116
+
117
+ def __init__(self):
118
+ """
119
+ Represents the operation mode BaseFunction for service part.
120
+ """
121
+ super().__init__()
122
+
123
+ self.switch_to_offline_mode_allowed = False
124
+
125
+ self.enter_offline_callbacks = []
126
+ self.exit_offline_callbacks = []
127
+
128
+ self.enter_operator_callbacks = []
129
+ self.exit_operator_callbacks = []
130
+
131
+ self.enter_automatic_callbacks = []
132
+ self.exit_automatic_callbacks = []
133
+
134
+ self.get_item(ServiceOperationMode.STATE_OFF_OP).ext_wrt_cb = (
135
+ self.set_state_off_op
136
+ )
137
+ self.get_item(ServiceOperationMode.STATE_OP_OP).ext_wrt_cb = (
138
+ self.set_state_op_op
139
+ )
140
+ self.get_item(ServiceOperationMode.STATE_AUT_OP).ext_wrt_cb = (
141
+ self.set_state_aut_op
142
+ )
143
+ # check if this brings us back in call back hell!!
144
+ # self.get_item(ServiceOperationMode.STATE_AUT_OP).int_wrt_cb = (
145
+ # self.set_state_aut_op
146
+ # )
147
+
148
+ self.get_item(ServiceOperationMode.STATE_OFF_AUT).int_wrt_cb = (
149
+ self.set_state_off_aut
150
+ )
151
+ self.get_item(ServiceOperationMode.STATE_OP_AUT).int_wrt_cb = (
152
+ self.set_state_op_aut
153
+ )
154
+ self.get_item(ServiceOperationMode.STATE_AUT_AUT).int_wrt_cb = (
155
+ self.set_state_aut_aut
156
+ )
157
+
158
+ def _get_callback_list_str(self, callbacks: list[Callable]) -> str:
159
+ return (
160
+ "["
161
+ + ", ".join(
162
+ f"{inspect.getmodule(cb).__name__}.{cb.__name__}" for cb in callbacks
163
+ )
164
+ + "]"
165
+ )
166
+
167
+ def allow_switch_to_offline_mode(self, allow_flag: bool):
168
+ self.switch_to_offline_mode_allowed = allow_flag
169
+
170
+ def add_enter_offline_callback(self, callback: Callable[[], None]):
171
+ self.enter_offline_callbacks.append(callback)
172
+
173
+ def add_exit_offline_callback(self, callback: Callable[[], None]):
174
+ self.exit_offline_callbacks.append(callback)
175
+
176
+ def add_enter_operator_callback(self, callback: Callable[[], None]):
177
+ self.enter_operator_callbacks.append(callback)
178
+
179
+ def add_exit_operator_callback(self, callback: Callable[[], None]):
180
+ self.exit_operator_callbacks.append(callback)
181
+
182
+ def add_enter_automatic_callback(self, callback: Callable[[], None]):
183
+ self.enter_automatic_callbacks.append(callback)
184
+
185
+ def add_exit_automatic_callback(self, callback: Callable[[], None]):
186
+ self.exit_automatic_callbacks.append(callback)
187
+
188
+ def _enter_off(self):
189
+ if len(self.enter_offline_callbacks):
190
+ logger.mtp("Applying enter offline mode callbacks")
191
+ [cb() for cb in self.enter_offline_callbacks]
192
+
193
+ def _exit_off(self):
194
+ if len(self.exit_offline_callbacks):
195
+ logger.mtp(
196
+ f"Applying exit offline mode callbacks {self._get_callback_list_str(self.exit_offline_callbacks)}"
197
+ )
198
+ [cb() for cb in self.exit_offline_callbacks]
199
+
200
+ def _enter_op(self):
201
+ if len(self.enter_operator_callbacks):
202
+ logger.mtp("Applying enter operator mode callbacks")
203
+ [cb() for cb in self.enter_operator_callbacks]
204
+
205
+ def _exit_op(self):
206
+ if len(self.exit_operator_callbacks):
207
+ logger.mtp("Applying exit operator mode callbacks")
208
+ [cb() for cb in self.exit_operator_callbacks]
209
+
210
+ def _enter_aut(self):
211
+ if len(self.enter_automatic_callbacks):
212
+ logger.mtp("Applying enter automatic mode callbacks")
213
+ [cb() for cb in self.enter_automatic_callbacks]
214
+
215
+ def _exit_aut(self):
216
+ if len(self.exit_automatic_callbacks):
217
+ logger.mtp("Applying exit automatic mode callbacks")
218
+ [cb() for cb in self.exit_automatic_callbacks]
219
+
220
+ def _opmode_to_off(self):
221
+ logger.debug(f"_opmode_to_off: args: -")
222
+
223
+ prev_mode_is_op = self[ServiceOperationMode.STATE_OP_ACT]
224
+ prev_mode_is_aut = self[ServiceOperationMode.STATE_AUT_ACT]
225
+
226
+ self[ServiceOperationMode.STATE_OP_ACT] = False
227
+ self[ServiceOperationMode.STATE_AUT_ACT] = False
228
+ self[ServiceOperationMode.STATE_OFF_ACT] = True
229
+
230
+ # Mode change callbacks
231
+ if prev_mode_is_op:
232
+ self._exit_op()
233
+ elif prev_mode_is_aut:
234
+ self._exit_aut()
235
+ self._enter_off()
236
+
237
+ logger.mtp("ServiceOperationMode is now off")
238
+
239
+ def _opmode_to_aut(self):
240
+ logger.debug(f"_opmode_to_aut: args: -")
241
+
242
+ prev_mode_is_off = self[ServiceOperationMode.STATE_OFF_ACT]
243
+ prev_mode_is_op = self[ServiceOperationMode.STATE_OP_ACT]
244
+
245
+ self[ServiceOperationMode.STATE_OP_ACT] = False
246
+ self[ServiceOperationMode.STATE_AUT_ACT] = True
247
+ self[ServiceOperationMode.STATE_OFF_ACT] = False
248
+
249
+ # Mode change callbacks
250
+ if prev_mode_is_off:
251
+ self._exit_off()
252
+ elif prev_mode_is_op:
253
+ self._exit_op()
254
+ self._enter_aut()
255
+
256
+ logger.mtp("ServiceOperationMode is now aut")
257
+
258
+ def _opmode_to_op(self):
259
+ logger.debug(f"_opmode_to_op: args: -")
260
+
261
+ prev_mode_is_off = self[ServiceOperationMode.STATE_OFF_ACT]
262
+ prev_mode_is_aut = self[ServiceOperationMode.STATE_AUT_ACT]
263
+
264
+ self[ServiceOperationMode.STATE_OP_ACT] = True
265
+ self[ServiceOperationMode.STATE_AUT_ACT] = False
266
+ self[ServiceOperationMode.STATE_OFF_ACT] = False
267
+
268
+ # Mode change callbacks
269
+ if prev_mode_is_off:
270
+ self._exit_off()
271
+ elif prev_mode_is_aut:
272
+ self._exit_aut()
273
+ self._enter_op()
274
+
275
+ logger.mtp(
276
+ f"ServiceOperationMode is now op, StateOpAct={self[ServiceOperationMode.STATE_OP_ACT]}"
277
+ )
278
+
279
+ def set_state_channel(self, value: bool):
280
+ logger.debug(f"set_state_channel: value={value}")
281
+
282
+ self[ServiceOperationMode.STATE_CHANNEL] = value
283
+
284
+ logger.info(
285
+ f"requested {value}, got {self[ServiceOperationMode.STATE_CHANNEL]}"
286
+ )
287
+
288
+ def set_state_aut_aut(self, value: bool):
289
+ logger.debug(f"value={value}")
290
+
291
+ if self[ServiceOperationMode.STATE_CHANNEL] and value:
292
+ if (
293
+ self[ServiceOperationMode.STATE_OFF_ACT]
294
+ or self[ServiceOperationMode.STATE_OP_ACT]
295
+ ):
296
+ self._opmode_to_aut()
297
+
298
+ logger.info(
299
+ f"requested {value}, got {self[ServiceOperationMode.STATE_AUT_AUT]}"
300
+ )
301
+
302
+ def set_state_aut_op(self, value: bool):
303
+ logger.debug(f"set_state_aut_op: value={value}")
304
+
305
+ if not self[ServiceOperationMode.STATE_CHANNEL] and value:
306
+ if (
307
+ self[ServiceOperationMode.STATE_OFF_ACT]
308
+ or self[ServiceOperationMode.STATE_OP_ACT]
309
+ ):
310
+ self._opmode_to_aut()
311
+ # handshake
312
+ self[ServiceOperationMode.STATE_AUT_OP] = False
313
+
314
+ def set_state_off_aut(self, value: bool):
315
+ logger.debug(f"set_state_off_aut: value={value}")
316
+
317
+ if (
318
+ self[ServiceOperationMode.STATE_CHANNEL]
319
+ and value
320
+ and self.switch_to_offline_mode_allowed
321
+ ):
322
+ if (
323
+ self[ServiceOperationMode.STATE_AUT_ACT]
324
+ or self[ServiceOperationMode.STATE_OP_ACT]
325
+ ):
326
+ self._opmode_to_off()
327
+
328
+ logger.info(
329
+ f"requested {value}, got {self[ServiceOperationMode.STATE_OFF_AUT]}"
330
+ )
331
+
332
+ def set_state_off_op(self, value: bool):
333
+ logger.debug(f"value={value}")
334
+
335
+ if (
336
+ value
337
+ and not self[ServiceOperationMode.STATE_CHANNEL]
338
+ and self.switch_to_offline_mode_allowed
339
+ ):
340
+ if (
341
+ self[ServiceOperationMode.STATE_AUT_ACT]
342
+ or self[ServiceOperationMode.STATE_OP_ACT]
343
+ ):
344
+ self._opmode_to_off()
345
+ self[ServiceOperationMode.STATE_OFF_OP] = False
346
+
347
+ def set_state_op_aut(self, value: bool):
348
+ logger.debug(f"set_state_op_aut: value={value}")
349
+
350
+ if self[ServiceOperationMode.STATE_CHANNEL] and value:
351
+ if (
352
+ self[ServiceOperationMode.STATE_OFF_ACT]
353
+ or self[ServiceOperationMode.STATE_AUT_ACT]
354
+ ):
355
+ self._opmode_to_op()
356
+
357
+ logger.info(f"requested {value}, got {self[ServiceOperationMode.STATE_OP_AUT]}")
358
+
359
+ def set_state_op_op(self, value: bool):
360
+ logger.debug(f"set_state_op_op: value={value}")
361
+
362
+ if not self[ServiceOperationMode.STATE_CHANNEL] and value:
363
+ if (
364
+ self[ServiceOperationMode.STATE_OFF_ACT] # offline
365
+ or self[ServiceOperationMode.STATE_AUT_ACT] # automatic
366
+ ):
367
+ self._opmode_to_op()
368
+ self[ServiceOperationMode.STATE_OP_OP] = False
@@ -0,0 +1,242 @@
1
+ """Test Suite for ServiceOperationMode."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ import pytest
9
+ from mtppy.part4.automation_services.base_functions.service_operation_mode import ServiceOperationMode
10
+
11
+
12
+ def init_service_op_mode(
13
+ init_mode="off", state_channel=False, allow_switch_to_offline=True
14
+ ):
15
+ service_op_mode = ServiceOperationMode()
16
+ service_op_mode.allow_switch_to_offline_mode(allow_switch_to_offline)
17
+ service_op_mode.set_state_channel(state_channel)
18
+
19
+ # MTP Part 4 Figure 2
20
+ if init_mode == "off":
21
+ service_op_mode[ServiceOperationMode.STATE_OFF_ACT] = True
22
+ service_op_mode[ServiceOperationMode.STATE_OP_ACT] = False
23
+ service_op_mode[ServiceOperationMode.STATE_AUT_ACT] = False
24
+ elif init_mode == "op":
25
+ service_op_mode[ServiceOperationMode.STATE_OFF_ACT] = False
26
+ service_op_mode[ServiceOperationMode.STATE_OP_ACT] = True
27
+ service_op_mode[ServiceOperationMode.STATE_AUT_ACT] = False
28
+ elif init_mode == "aut":
29
+ service_op_mode[ServiceOperationMode.STATE_OFF_ACT] = False
30
+ service_op_mode[ServiceOperationMode.STATE_OP_ACT] = False
31
+ service_op_mode[ServiceOperationMode.STATE_AUT_ACT] = True
32
+ else:
33
+ raise ValueError(f"ServiceOperationMode {init_mode} is unknown")
34
+
35
+ return service_op_mode
36
+
37
+
38
+ def service_operation_mode_is_in_specific_mode(
39
+ service_op_mode: ServiceOperationMode,
40
+ state_channel_assertion: bool,
41
+ mode_assertion: str,
42
+ ):
43
+ mode = lambda m: (mode_assertion == m)
44
+ return all(
45
+ [
46
+ service_op_mode[ServiceOperationMode.STATE_CHANNEL]
47
+ == state_channel_assertion,
48
+ service_op_mode[ServiceOperationMode.STATE_OFF_AUT] == False,
49
+ service_op_mode[ServiceOperationMode.STATE_OP_AUT] == False,
50
+ service_op_mode[ServiceOperationMode.STATE_AUT_AUT] == False,
51
+ service_op_mode[ServiceOperationMode.STATE_OFF_OP] == False,
52
+ service_op_mode[ServiceOperationMode.STATE_OP_OP] == False,
53
+ service_op_mode[ServiceOperationMode.STATE_AUT_OP] == False,
54
+ service_op_mode[ServiceOperationMode.STATE_OP_ACT] == mode("op"),
55
+ service_op_mode[ServiceOperationMode.STATE_AUT_ACT] == mode("aut"),
56
+ service_op_mode[ServiceOperationMode.STATE_OFF_ACT] == mode("off"),
57
+ ]
58
+ )
59
+
60
+
61
+ def assert_service_operation_mode_is_in_specific_mode(
62
+ service_op_mode: ServiceOperationMode,
63
+ state_channel_assertion: bool,
64
+ mode_assertion: str,
65
+ ) -> None:
66
+ mode = lambda m: (mode_assertion == m)
67
+ assert (
68
+ service_op_mode[ServiceOperationMode.STATE_CHANNEL] == state_channel_assertion
69
+ )
70
+ assert service_op_mode[ServiceOperationMode.STATE_OP_ACT] == mode("op")
71
+ assert service_op_mode[ServiceOperationMode.STATE_AUT_ACT] == mode("aut")
72
+ assert service_op_mode[ServiceOperationMode.STATE_OFF_ACT] == mode("off")
73
+
74
+
75
+ def test_initial_state():
76
+ service_op_mode = init_service_op_mode(init_mode="off", state_channel=False)
77
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "off")
78
+
79
+
80
+ def test_state_channel_change():
81
+ # Initial mode
82
+ service_op_mode = init_service_op_mode()
83
+ # Trigger source channel change
84
+ service_op_mode["StateChannel"] = True
85
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, True, "off")
86
+ # Trigger source channel change back
87
+ service_op_mode["StateChannel"] = False
88
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "off")
89
+
90
+
91
+ @pytest.mark.parametrize(
92
+ "init_mode, target_mode, param_extern, param_intern",
93
+ [
94
+ (
95
+ "off",
96
+ "op",
97
+ ServiceOperationMode.STATE_OP_OP,
98
+ ServiceOperationMode.STATE_OP_AUT,
99
+ ),
100
+ (
101
+ "op",
102
+ "off",
103
+ ServiceOperationMode.STATE_OFF_OP,
104
+ ServiceOperationMode.STATE_OFF_AUT,
105
+ ),
106
+ (
107
+ "off",
108
+ "aut",
109
+ ServiceOperationMode.STATE_AUT_OP,
110
+ ServiceOperationMode.STATE_AUT_AUT,
111
+ ),
112
+ (
113
+ "aut",
114
+ "off",
115
+ ServiceOperationMode.STATE_OFF_OP,
116
+ ServiceOperationMode.STATE_OFF_AUT,
117
+ ),
118
+ (
119
+ "aut",
120
+ "op",
121
+ ServiceOperationMode.STATE_OP_OP,
122
+ ServiceOperationMode.STATE_OP_AUT,
123
+ ),
124
+ (
125
+ "op",
126
+ "aut",
127
+ ServiceOperationMode.STATE_AUT_OP,
128
+ ServiceOperationMode.STATE_AUT_AUT,
129
+ ),
130
+ ],
131
+ )
132
+ def test_switch_state(
133
+ init_mode: str,
134
+ target_mode: str,
135
+ param_extern: str,
136
+ param_intern: str,
137
+ ):
138
+ # Step 1 - request by operator (= external write), state_channel = False
139
+ sc = False
140
+ op_mode = init_service_op_mode(init_mode=init_mode, state_channel=sc)
141
+ assert_service_operation_mode_is_in_specific_mode(op_mode, sc, init_mode)
142
+ op_mode.get_item(param_extern).ext_wrt_cb(True)
143
+ assert_service_operation_mode_is_in_specific_mode(op_mode, sc, target_mode)
144
+
145
+ # Step 2 - request by automation (= internal write), state_channel = True
146
+ sc = True
147
+ op_mode = init_service_op_mode(init_mode=init_mode, state_channel=sc)
148
+ assert_service_operation_mode_is_in_specific_mode(op_mode, sc, init_mode)
149
+ op_mode[param_intern] = True
150
+ assert_service_operation_mode_is_in_specific_mode(op_mode, sc, target_mode)
151
+
152
+
153
+ def test_allowance_change_op_to_off_state_channel_false():
154
+ # Initial mode
155
+ service_op_mode = init_service_op_mode(
156
+ init_mode="op", state_channel=False, allow_switch_to_offline=False
157
+ )
158
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "op")
159
+ # Trigger mode transition
160
+ service_op_mode.get_item(ServiceOperationMode.STATE_OFF_OP).ext_wrt_cb(True)
161
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "op")
162
+
163
+
164
+ def test_allowance_change_op_to_off_state_channel_true():
165
+ # Initial mode
166
+ service_op_mode = init_service_op_mode(
167
+ init_mode="op", state_channel=True, allow_switch_to_offline=False
168
+ )
169
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, True, "op")
170
+ # Trigger mode transition
171
+ service_op_mode[ServiceOperationMode.STATE_OFF_AUT] = True
172
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, True, "op")
173
+
174
+
175
+ def test_allowance_change_aut_to_off_state_channel_false():
176
+ # Initial mode
177
+ service_op_mode = init_service_op_mode(
178
+ init_mode="aut", state_channel=False, allow_switch_to_offline=False
179
+ )
180
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "aut")
181
+ # Trigger mode transition
182
+ service_op_mode.get_item(ServiceOperationMode.STATE_OFF_OP).ext_wrt_cb(True)
183
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "aut")
184
+
185
+
186
+ def test_allowance_change_aut_to_off_state_channel_true():
187
+ # Initial mode
188
+ service_op_mode = init_service_op_mode(
189
+ init_mode="aut", state_channel=True, allow_switch_to_offline=False
190
+ )
191
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, True, "aut")
192
+ # Trigger mode transition
193
+ service_op_mode.set_state_off_aut(True)
194
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, True, "aut")
195
+
196
+
197
+ def test_exit_offline_callback_off_to_op():
198
+ test_value = {"value": 0}
199
+
200
+ def callback_function():
201
+ test_value["value"] = 10
202
+
203
+ # Initial mode
204
+ service_op_mode = init_service_op_mode(init_mode="off")
205
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "off")
206
+ # Emulate external write on StateOpOp w/o exit callback
207
+ service_op_mode.get_item(ServiceOperationMode.STATE_OP_OP).ext_wrt_cb(True)
208
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "op")
209
+ assert test_value["value"] == 0
210
+
211
+ # Initial mode
212
+ service_op_mode = init_service_op_mode(init_mode="off")
213
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "off")
214
+ service_op_mode.add_exit_offline_callback(callback_function)
215
+ # Trigger mode transition with exit callback
216
+ service_op_mode.get_item(ServiceOperationMode.STATE_OP_OP).ext_wrt_cb(True)
217
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "op")
218
+ assert test_value["value"] == 10
219
+
220
+
221
+ def test_exit_offline_callback_off_to_aut():
222
+ test_value = {"value": 0}
223
+
224
+ def callback_function():
225
+ test_value["value"] = 10
226
+
227
+ # Initial mode
228
+ service_op_mode = init_service_op_mode(init_mode="off")
229
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "off")
230
+ # Trigger mode transition without callback
231
+ service_op_mode.get_item(ServiceOperationMode.STATE_AUT_OP).ext_wrt_cb(True)
232
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "aut")
233
+ assert test_value["value"] == 0
234
+
235
+ # Initial mode
236
+ service_op_mode = init_service_op_mode(init_mode="off")
237
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "off")
238
+ service_op_mode.add_exit_offline_callback(callback_function)
239
+ # Trigger mode transition with callback
240
+ service_op_mode.get_item(ServiceOperationMode.STATE_AUT_OP).ext_wrt_cb(True)
241
+ assert service_operation_mode_is_in_specific_mode(service_op_mode, False, "aut")
242
+ assert test_value["value"] == 10
@@ -0,0 +1,62 @@
1
+ """Implementation of ServiceOperatorInteraction."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from mtppy.utils.data_item import DataItem, DataItemCollection
9
+ from mtppy.part3.definitions.mtp_access import MTPAccess
10
+ from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
11
+ from typing import ClassVar
12
+
13
+
14
+ class ServiceOperatorInteraction(DataItemCollection):
15
+ # DataItem constant definitions
16
+ INTERACT_QUESTION_ID = "InteractQuestionID"
17
+ INTERACT_ANSWER_ID = "InteractAnswerID"
18
+ POS_TEXT_ID = "PosTextID"
19
+ INTERACT_ADD_INFO = "InteractAddInfo"
20
+
21
+ # Class-level data items dictionary
22
+ CLASS_DATA_ITEMS: ClassVar = {
23
+ INTERACT_QUESTION_ID: DataItem(
24
+ INTERACT_QUESTION_ID,
25
+ W3cXmlDataTypes.UNSIGNED_SHORT,
26
+ MTPAccess.ReadAccess,
27
+ 0,
28
+ ),
29
+ INTERACT_ANSWER_ID: DataItem(
30
+ INTERACT_ANSWER_ID,
31
+ W3cXmlDataTypes.UNSIGNED_SHORT,
32
+ MTPAccess.ReadWriteAccess,
33
+ 0,
34
+ ),
35
+ POS_TEXT_ID: DataItem(
36
+ POS_TEXT_ID, W3cXmlDataTypes.UNSIGNED_SHORT, MTPAccess.ReadAccess, 0
37
+ ),
38
+ INTERACT_ADD_INFO: DataItem(
39
+ INTERACT_ADD_INFO, W3cXmlDataTypes.STRING, MTPAccess.ReadAccess, ""
40
+ ),
41
+ }
42
+
43
+ def __init__(self):
44
+ """
45
+ Represents the service interactions BaseFunction for service part.
46
+ """
47
+ super().__init__()
48
+ # Initialize data items with dynamic values (using class-defined data items)
49
+ self[ServiceOperatorInteraction.INTERACT_QUESTION_ID] = 0
50
+ self[ServiceOperatorInteraction.INTERACT_ANSWER_ID] = 0
51
+ self[ServiceOperatorInteraction.POS_TEXT_ID] = 0
52
+ self[ServiceOperatorInteraction.INTERACT_ADD_INFO] = ""
53
+
54
+ # Set callbacks for writable items (using the correct pattern for callbacks)
55
+ self.get_item(ServiceOperatorInteraction.INTERACT_ANSWER_ID).ext_wrt_cb = (
56
+ self.set_interact_answer_id
57
+ )
58
+
59
+ def set_interact_answer_id(self, value):
60
+ self[ServiceOperatorInteraction.INTERACT_ANSWER_ID] = value
61
+
62
+ # A list of interactions shall be added and method such as "raise_service_interaction" added