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,252 @@
1
+ """Implementation of the Operation Mode 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, Callable
9
+
10
+ from mtppy.utils.logging import logger
11
+ from mtppy.utils.data_item import DataItem, DataItemCollection
12
+ from mtppy.part3.definitions.mtp_access import MTPAccess
13
+ from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
14
+
15
+
16
+ class OperationMode(DataItemCollection):
17
+ # DataItem constant definitions
18
+ STATE_CHANNEL = "StateChannel"
19
+ STATE_OFF_AUT = "StateOffAut"
20
+ STATE_OP_AUT = "StateOpAut"
21
+ STATE_AUT_AUT = "StateAutAut"
22
+ STATE_OFF_OP = "StateOffOp"
23
+ STATE_OP_OP = "StateOpOp"
24
+ STATE_AUT_OP = "StateAutOp"
25
+ STATE_OP_ACT = "StateOpAct"
26
+ STATE_AUT_ACT = "StateAutAct"
27
+ STATE_OFF_ACT = "StateOffAct"
28
+
29
+ # Class-level data items dictionary
30
+ CLASS_DATA_ITEMS: ClassVar = {
31
+ STATE_CHANNEL: DataItem(
32
+ STATE_CHANNEL, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, False
33
+ ),
34
+ STATE_OFF_AUT: DataItem(
35
+ STATE_OFF_AUT, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, False
36
+ ),
37
+ STATE_OP_AUT: DataItem(
38
+ STATE_OP_AUT, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, False
39
+ ),
40
+ STATE_AUT_AUT: DataItem(
41
+ STATE_AUT_AUT, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, False
42
+ ),
43
+ STATE_OFF_OP: DataItem(
44
+ STATE_OFF_OP, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadWriteAccess, False
45
+ ),
46
+ STATE_OP_OP: DataItem(
47
+ STATE_OP_OP, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadWriteAccess, False
48
+ ),
49
+ STATE_AUT_OP: DataItem(
50
+ STATE_AUT_OP, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadWriteAccess, False
51
+ ),
52
+ STATE_OP_ACT: DataItem(
53
+ STATE_OP_ACT, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, False
54
+ ),
55
+ STATE_AUT_ACT: DataItem(
56
+ STATE_AUT_ACT, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, False
57
+ ),
58
+ STATE_OFF_ACT: DataItem(
59
+ STATE_OFF_ACT, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, True
60
+ ),
61
+ }
62
+
63
+ switch_to_offline_mode_allowed: bool
64
+
65
+ enter_offline_callbacks: list[Callable[[], None]]
66
+ exit_offline_callbacks: list[Callable[[], None]]
67
+ enter_operator_callbacks: list[Callable[[], None]]
68
+ exit_operator_callbacks: list[Callable[[], None]]
69
+ enter_automatic_callbacks: list[Callable[[], None]]
70
+ exit_automatic_callbacks: list[Callable[[], None]]
71
+
72
+ def __init__(self) -> None:
73
+ """
74
+ Represents the operation mode BaseFunction.
75
+ """
76
+ super().__init__()
77
+ # Initialize data items with dynamic values (using class-defined data items)
78
+ self.get_item(OperationMode.STATE_CHANNEL).init_value = False
79
+ self.get_item(OperationMode.STATE_OFF_AUT).init_value = False
80
+ self.get_item(OperationMode.STATE_OP_AUT).init_value = False
81
+ self.get_item(OperationMode.STATE_AUT_AUT).init_value = False
82
+ self.get_item(OperationMode.STATE_OFF_OP).init_value = False
83
+ self.get_item(OperationMode.STATE_OP_OP).init_value = False
84
+ self.get_item(OperationMode.STATE_AUT_OP).init_value = False
85
+ self.get_item(OperationMode.STATE_OP_ACT).init_value = False
86
+ self.get_item(OperationMode.STATE_AUT_ACT).init_value = False
87
+ self.get_item(OperationMode.STATE_OFF_ACT).init_value = True
88
+
89
+ self.switch_to_offline_mode_allowed = True
90
+
91
+ self.enter_offline_callbacks = []
92
+ self.exit_offline_callbacks = []
93
+
94
+ self.enter_operator_callbacks = []
95
+ self.exit_operator_callbacks = []
96
+
97
+ self.enter_automatic_callbacks = []
98
+ self.exit_automatic_callbacks = []
99
+
100
+ # Set callbacks for writable items (using the correct pattern for callbacks)
101
+ self.get_item(OperationMode.STATE_OFF_OP).ext_wrt_cb = self.set_state_off_op
102
+ self.get_item(OperationMode.STATE_OP_OP).ext_wrt_cb = self.set_state_op_op
103
+ self.get_item(OperationMode.STATE_AUT_OP).ext_wrt_cb = self.set_state_aut_op
104
+
105
+ self.get_item(OperationMode.STATE_OP_AUT).int_wrt_cb = self.set_state_op_aut
106
+ self.get_item(OperationMode.STATE_OFF_AUT).int_wrt_cb = self.set_state_off_aut
107
+ self.get_item(OperationMode.STATE_AUT_AUT).int_wrt_cb = self.set_state_aut_aut
108
+
109
+ def allow_switch_to_offline_mode(self, allow_flag: bool) -> None:
110
+ self.switch_to_offline_mode_allowed = allow_flag
111
+
112
+ def add_enter_offline_callback(self, callback: Callable[[], None]) -> None:
113
+ self.enter_offline_callbacks.append(callback)
114
+
115
+ def add_exit_offline_callback(self, callback: Callable[[], None]) -> None:
116
+ self.exit_offline_callbacks.append(callback)
117
+
118
+ def add_enter_operator_callback(self, callback: Callable[[], None]) -> None:
119
+ self.enter_operator_callbacks.append(callback)
120
+
121
+ def add_exit_operator_callback(self, callback: Callable[[], None]) -> None:
122
+ self.exit_operator_callbacks.append(callback)
123
+
124
+ def add_enter_automatic_callback(self, callback: Callable[[], None]) -> None:
125
+ self.enter_automatic_callbacks.append(callback)
126
+
127
+ def add_exit_automatic_callback(self, callback: Callable[[], None]) -> None:
128
+ self.exit_automatic_callbacks.append(callback)
129
+
130
+ def _enter_off(self) -> None:
131
+ if len(self.enter_offline_callbacks):
132
+ logger.debug("Applying enter offline mode callbacks")
133
+ [cb() for cb in self.enter_offline_callbacks]
134
+
135
+ def _exit_off(self) -> None:
136
+ if len(self.exit_offline_callbacks):
137
+ logger.debug("Applying exit offline mode callbacks")
138
+ [cb() for cb in self.exit_offline_callbacks]
139
+
140
+ def _enter_op(self) -> None:
141
+ if len(self.enter_operator_callbacks):
142
+ logger.debug("Applying enter operator mode callbacks")
143
+ [cb() for cb in self.enter_operator_callbacks]
144
+
145
+ def _exit_op(self) -> None:
146
+ if len(self.exit_operator_callbacks):
147
+ logger.debug("Applying exit operator mode callbacks")
148
+ [cb() for cb in self.exit_operator_callbacks]
149
+
150
+ def _enter_aut(self) -> None:
151
+ if len(self.enter_automatic_callbacks):
152
+ logger.debug("Applying enter automatic mode callbacks")
153
+ [cb() for cb in self.enter_automatic_callbacks]
154
+
155
+ def _exit_aut(self) -> None:
156
+ if len(self.exit_automatic_callbacks):
157
+ logger.debug("Applying exit automatic mode callbacks")
158
+ [cb() for cb in self.exit_automatic_callbacks]
159
+
160
+ def _opmode_to_off(self) -> None:
161
+ current_mode_is_op = self[OperationMode.STATE_OP_ACT]
162
+
163
+ # Mode change callbacks
164
+ if current_mode_is_op:
165
+ self[OperationMode.STATE_OFF_ACT] = True
166
+ self[OperationMode.STATE_OP_ACT] = False
167
+ self[OperationMode.STATE_AUT_ACT] = False
168
+ self._exit_op()
169
+ self._enter_off()
170
+ logger.debug("Operation mode is now off")
171
+
172
+ def _opmode_to_aut(self) -> None:
173
+ current_mode_is_op = self[OperationMode.STATE_OP_ACT]
174
+
175
+ # Mode change callbacks
176
+ if current_mode_is_op:
177
+ self[OperationMode.STATE_OFF_ACT] = False
178
+ self[OperationMode.STATE_OP_ACT] = False
179
+ self[OperationMode.STATE_AUT_ACT] = True
180
+ self._exit_op()
181
+ self._enter_aut()
182
+ logger.debug("Operation mode is now aut")
183
+
184
+ def _opmode_to_op(self) -> None:
185
+ current_mode_is_off = self[OperationMode.STATE_OFF_ACT]
186
+ current_mode_is_aut = self[OperationMode.STATE_AUT_ACT]
187
+
188
+ # Mode change callbacks
189
+ if current_mode_is_off or current_mode_is_aut:
190
+ if current_mode_is_off:
191
+ self._exit_off()
192
+ elif current_mode_is_aut:
193
+ self._exit_aut()
194
+ self[OperationMode.STATE_OFF_ACT] = False
195
+ self[OperationMode.STATE_OP_ACT] = True
196
+ self[OperationMode.STATE_AUT_ACT] = False
197
+ self._enter_op()
198
+ logger.debug("Operation mode is now op")
199
+
200
+ def set_state_channel(self, value: bool) -> None:
201
+ logger.debug(f"OperationMode StateChannel is now {value}")
202
+ self[OperationMode.STATE_CHANNEL] = value
203
+
204
+ def set_state_aut_aut(self, value: bool) -> None:
205
+ logger.debug(f"StateAutAut set to {value}")
206
+ if self[OperationMode.STATE_CHANNEL] and value:
207
+ if self[OperationMode.STATE_OP_ACT]:
208
+ self._opmode_to_aut()
209
+
210
+ def set_state_aut_op(self, value: bool) -> None:
211
+ logger.debug(f"StateAutOp set to {value}")
212
+ if not self[OperationMode.STATE_CHANNEL] and value:
213
+ if self[OperationMode.STATE_OP_ACT]:
214
+ self._opmode_to_aut()
215
+ # handshake
216
+ self[OperationMode.STATE_AUT_OP] = False
217
+
218
+ def set_state_off_aut(self, value: bool) -> None:
219
+ logger.debug(f"StateOffAut set to {value}")
220
+ if (
221
+ self[OperationMode.STATE_CHANNEL]
222
+ and value
223
+ and self.switch_to_offline_mode_allowed
224
+ ):
225
+ if self[OperationMode.STATE_OP_ACT]:
226
+ self._opmode_to_off()
227
+
228
+ def set_state_off_op(self, value: bool) -> None:
229
+ logger.debug(f"StateOffOp set to {value}")
230
+ if (
231
+ not self[OperationMode.STATE_CHANNEL]
232
+ and value
233
+ and self.switch_to_offline_mode_allowed
234
+ ):
235
+ if self[OperationMode.STATE_OP_ACT]:
236
+ self._opmode_to_off()
237
+ # handshake
238
+ self[OperationMode.STATE_OFF_OP] = False
239
+
240
+ def set_state_op_aut(self, value: bool) -> None:
241
+ logger.debug(f"StateOpAut set to {value}")
242
+ if self[OperationMode.STATE_CHANNEL] and value:
243
+ if self[OperationMode.STATE_OFF_ACT] or self[OperationMode.STATE_AUT_ACT]:
244
+ self._opmode_to_op()
245
+
246
+ def set_state_op_op(self, value: bool) -> None:
247
+ logger.debug(f"StateOpOp set to {value}")
248
+ if not self[OperationMode.STATE_CHANNEL] and value:
249
+ if self[OperationMode.STATE_OFF_ACT] or self[OperationMode.STATE_AUT_ACT]:
250
+ self._opmode_to_op()
251
+ # handshake
252
+ self[OperationMode.STATE_OP_OP] = False
@@ -0,0 +1,260 @@
1
+ """Test Suite for OperationMode."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ import pytest
9
+
10
+ from mtppy.part3.data_assembly_set.base_functions.operation_mode import OperationMode
11
+
12
+
13
+ def init_op_mode(
14
+ init_mode: str = "off",
15
+ state_channel: bool = False,
16
+ allow_switch_to_offline: bool = True,
17
+ ):
18
+
19
+ op_mode = OperationMode()
20
+ op_mode.allow_switch_to_offline_mode(allow_switch_to_offline)
21
+ op_mode.set_state_channel(state_channel)
22
+
23
+ if init_mode == "off":
24
+ op_mode[OperationMode.STATE_AUT_ACT] = False
25
+ op_mode[OperationMode.STATE_OFF_ACT] = True
26
+ op_mode[OperationMode.STATE_OP_ACT] = False
27
+ elif init_mode == "op":
28
+ op_mode[OperationMode.STATE_AUT_ACT] = False
29
+ op_mode[OperationMode.STATE_OFF_ACT] = False
30
+ op_mode[OperationMode.STATE_OP_ACT] = True
31
+ elif init_mode == "aut":
32
+ op_mode[OperationMode.STATE_AUT_ACT] = True
33
+ op_mode[OperationMode.STATE_OFF_ACT] = False
34
+ op_mode[OperationMode.STATE_OP_ACT] = False
35
+ else:
36
+ raise ValueError(f"Operation mode {init_mode} is unknown")
37
+
38
+ return op_mode
39
+
40
+
41
+ def operation_mode_is_in_specific_mode(
42
+ op_mode: OperationMode, state_channel_assertion: bool, mode_assertion: str
43
+ ):
44
+ return all(
45
+ [
46
+ op_mode[OperationMode.STATE_CHANNEL] == state_channel_assertion,
47
+ op_mode[OperationMode.STATE_OFF_AUT] == False,
48
+ op_mode[OperationMode.STATE_OP_AUT] == False,
49
+ op_mode[OperationMode.STATE_AUT_AUT] == False,
50
+ op_mode[OperationMode.STATE_OFF_OP] == False,
51
+ op_mode[OperationMode.STATE_OP_OP] == False,
52
+ op_mode[OperationMode.STATE_AUT_OP] == False,
53
+ op_mode[OperationMode.STATE_OP_ACT] == (mode_assertion == "op"),
54
+ op_mode[OperationMode.STATE_AUT_ACT] == (mode_assertion == "aut"),
55
+ op_mode[OperationMode.STATE_OFF_ACT] == (mode_assertion == "off"),
56
+ ]
57
+ )
58
+
59
+
60
+ def test_initial_state():
61
+ op_mode = init_op_mode(init_mode="off", state_channel=False)
62
+ assert operation_mode_is_in_specific_mode(op_mode, False, "off") == True
63
+
64
+
65
+ def test_state_channel_change():
66
+ op_mode = init_op_mode()
67
+ op_mode.StateChannel = True
68
+ assert op_mode["StateChannel"] == True
69
+ op_mode.StateChannel = False
70
+ assert op_mode["StateChannel"] == False
71
+ assert operation_mode_is_in_specific_mode(op_mode, False, "off") == True
72
+
73
+
74
+ def test_off_to_op_state_channel_false():
75
+ # Initial mode
76
+ op_mode = init_op_mode(init_mode="off", state_channel=False)
77
+ assert operation_mode_is_in_specific_mode(op_mode, False, "off") == True
78
+ # Emulate mode transition by external writer
79
+ op_mode.get_item(OperationMode.STATE_OP_OP).ext_wrt_cb(True)
80
+ assert operation_mode_is_in_specific_mode(op_mode, False, "op") == True
81
+
82
+
83
+ def test_off_to_op_state_channel_true():
84
+ # Initial mode
85
+ op_mode = init_op_mode(init_mode="off", state_channel=True)
86
+ assert operation_mode_is_in_specific_mode(op_mode, True, "off") == True
87
+ # Trigger mode transition
88
+ op_mode.StateOpAut = True
89
+ assert operation_mode_is_in_specific_mode(op_mode, True, "op") == True
90
+
91
+
92
+ def test_op_to_off_state_channel_false():
93
+ # Initial mode
94
+ op_mode = init_op_mode(init_mode="op", state_channel=False)
95
+ assert operation_mode_is_in_specific_mode(op_mode, False, "op") == True
96
+ # Trigger mode transition
97
+ op_mode.get_item(OperationMode.STATE_OFF_OP).ext_wrt_cb(True)
98
+ assert operation_mode_is_in_specific_mode(op_mode, False, "off") == True
99
+
100
+
101
+ def test_op_to_off_state_channel_true():
102
+ # Initial mode
103
+ op_mode = init_op_mode(init_mode="op", state_channel=True)
104
+ assert operation_mode_is_in_specific_mode(op_mode, True, "op") == True
105
+ # Trigger mode transition
106
+ op_mode.StateOffAut = True
107
+ assert operation_mode_is_in_specific_mode(op_mode, True, "off") == True
108
+
109
+
110
+ def test_off_to_aut_state_channel_false():
111
+ # Initial mode
112
+ op_mode = init_op_mode(init_mode="off", state_channel=False)
113
+ assert operation_mode_is_in_specific_mode(op_mode, False, "off") == True
114
+ # Trigger invalid mode transition --> no change expected
115
+ op_mode.get_item(OperationMode.STATE_AUT_OP).ext_wrt_cb(True)
116
+ assert operation_mode_is_in_specific_mode(op_mode, False, "off") == True
117
+
118
+
119
+ def test_off_to_aut_state_channel_true():
120
+ # Initial mode
121
+ op_mode = init_op_mode(init_mode="off", state_channel=True)
122
+ assert operation_mode_is_in_specific_mode(op_mode, True, "off") == True
123
+ # Trigger invalid mode transition --> no change expected
124
+ op_mode.StateAutAut = True
125
+ assert operation_mode_is_in_specific_mode(op_mode, True, "off") == True
126
+
127
+
128
+ def test_aut_to_off_state_channel_false():
129
+ # Initial mode
130
+ op_mode = init_op_mode(init_mode="aut", state_channel=False)
131
+ assert operation_mode_is_in_specific_mode(op_mode, False, "aut") == True
132
+ # Trigger invalid mode transition --> no change expected
133
+ op_mode.get_item(OperationMode.STATE_OFF_OP).ext_wrt_cb(True)
134
+ assert operation_mode_is_in_specific_mode(op_mode, False, "aut") == True
135
+
136
+
137
+ def test_aut_to_off_state_channel_true():
138
+ # Initial mode
139
+ op_mode = init_op_mode(init_mode="aut", state_channel=True)
140
+ assert operation_mode_is_in_specific_mode(op_mode, True, "aut") == True
141
+ # Trigger invalid mode transition --> no change expected
142
+ op_mode.StateOffAut = True
143
+ assert operation_mode_is_in_specific_mode(op_mode, True, "aut") == True
144
+
145
+
146
+ def test_aut_to_op_state_channel_false():
147
+ # Initial mode
148
+ op_mode = init_op_mode(init_mode="aut", state_channel=False)
149
+ assert operation_mode_is_in_specific_mode(op_mode, False, "aut") == True
150
+ # Trigger mode transition
151
+ op_mode.get_item(OperationMode.STATE_OP_OP).ext_wrt_cb(True)
152
+ assert operation_mode_is_in_specific_mode(op_mode, False, "op") == True
153
+
154
+
155
+ def test_aut_to_op_state_channel_true():
156
+ # Initial mode
157
+ op_mode = init_op_mode(init_mode="aut", state_channel=True)
158
+ assert operation_mode_is_in_specific_mode(op_mode, True, "aut") == True
159
+ # Trigger mode transition
160
+ op_mode.StateOpAut = True
161
+ assert operation_mode_is_in_specific_mode(op_mode, True, "op") == True
162
+
163
+
164
+ def test_op_to_aut_state_channel_false():
165
+ # Initial mode
166
+ op_mode = init_op_mode(init_mode="op", state_channel=False)
167
+ assert operation_mode_is_in_specific_mode(op_mode, False, "op") == True
168
+ # Trigger mode transition
169
+ op_mode.get_item(OperationMode.STATE_AUT_OP).ext_wrt_cb(True)
170
+ assert operation_mode_is_in_specific_mode(op_mode, False, "aut") == True
171
+
172
+
173
+ def test_op_to_aut_state_channel_true():
174
+ # Initial mode
175
+ op_mode = init_op_mode(init_mode="op", state_channel=True)
176
+ assert operation_mode_is_in_specific_mode(op_mode, True, "op") == True
177
+ # Trigger mode transition
178
+ op_mode.StateAutAut = True
179
+ assert operation_mode_is_in_specific_mode(op_mode, True, "aut") == True
180
+
181
+
182
+ def test_allowance_change_op_to_off_state_channel_false():
183
+ # Initial mode
184
+ op_mode = init_op_mode(
185
+ init_mode="op", state_channel=False, allow_switch_to_offline=False
186
+ )
187
+ assert operation_mode_is_in_specific_mode(op_mode, False, "op") == True
188
+ # Trigger mode transition
189
+ op_mode.get_item(OperationMode.STATE_OFF_OP).ext_wrt_cb(True)
190
+ assert operation_mode_is_in_specific_mode(op_mode, False, "op") == True
191
+
192
+
193
+ def test_blocked_change_op_to_off_state_channel_true():
194
+ # Initial mode with offline switch blocked
195
+ op_mode = init_op_mode(
196
+ init_mode="op", state_channel=True, allow_switch_to_offline=False
197
+ )
198
+ assert operation_mode_is_in_specific_mode(op_mode, True, "op") == True
199
+ # Trigger valid mode transition
200
+ op_mode.get_item(OperationMode.STATE_OFF_AUT).int_wrt_cb(True)
201
+ assert operation_mode_is_in_specific_mode(op_mode, True, "op") == True
202
+
203
+
204
+ def test_forbidden_change_aut_to_off_state_channel_true():
205
+ # Initial mode
206
+ op_mode = init_op_mode(
207
+ init_mode="aut", state_channel=True, allow_switch_to_offline=True
208
+ )
209
+ assert operation_mode_is_in_specific_mode(op_mode, True, "aut") == True
210
+ # Trigger invalid mode transition
211
+ op_mode.get_item(OperationMode.STATE_OFF_AUT).int_wrt_cb(True)
212
+ assert operation_mode_is_in_specific_mode(op_mode, True, "aut") == True
213
+
214
+
215
+ def test_exit_offline_callback_off_to_op():
216
+ test_value = {"value": 0}
217
+
218
+ def callback_function():
219
+ test_value["value"] = 10
220
+
221
+ # Initial mode
222
+ op_mode = init_op_mode()
223
+ assert operation_mode_is_in_specific_mode(op_mode, False, "off") == True
224
+
225
+ # Simulate external write on StateOpOp
226
+ op_mode.get_item(OperationMode.STATE_OP_OP).ext_wrt_cb(True)
227
+ assert operation_mode_is_in_specific_mode(op_mode, False, "op") == True
228
+ assert test_value["value"] == 0
229
+
230
+ # Initial mode
231
+ op_mode = init_op_mode()
232
+ op_mode.add_exit_offline_callback(callback_function)
233
+ assert operation_mode_is_in_specific_mode(op_mode, False, "off") == True
234
+ # Trigger mode transition with registered callback
235
+ op_mode.get_item(OperationMode.STATE_OP_OP).ext_wrt_cb(True)
236
+ assert operation_mode_is_in_specific_mode(op_mode, False, "op") == True
237
+ assert test_value["value"] == 10
238
+
239
+
240
+ @pytest.mark.parametrize(
241
+ "state_request",
242
+ [
243
+ OperationMode.STATE_OFF_OP,
244
+ OperationMode.STATE_OP_OP,
245
+ OperationMode.STATE_AUT_OP,
246
+ ],
247
+ )
248
+ def test_external_state_request_handshake(state_request: str) -> None:
249
+ op_mode = init_op_mode()
250
+
251
+ callback = op_mode.get_item(state_request).ext_wrt_cb
252
+ assert callback is not None
253
+ callback(True)
254
+
255
+ assert op_mode[state_request] is False
256
+
257
+
258
+ if __name__ == "__main__":
259
+ # This allows running the tests directly if needed
260
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,35 @@
1
+ """Implementation of the OS Level 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
+ from mtppy.utils.data_item import DataItem, DataItemCollection
11
+ from mtppy.part3.definitions.mtp_access import MTPAccess
12
+ from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
13
+
14
+
15
+ class OSLevelBaseFunction(DataItemCollection):
16
+ OS_LEVEL = "OSLevel"
17
+
18
+ CLASS_DATA_ITEMS: ClassVar = {
19
+ OS_LEVEL: DataItem(
20
+ OS_LEVEL,
21
+ W3cXmlDataTypes.UNSIGNED_SHORT,
22
+ MTPAccess.ReadWriteAccess,
23
+ init_value=0,
24
+ ),
25
+ }
26
+
27
+ def __init__(self) -> None:
28
+ """
29
+ Initialize the OSLevel BaseFunction.
30
+ """
31
+ super().__init__()
32
+ self.get_item(self.OS_LEVEL).ext_wrt_cb = self.set_os_level
33
+
34
+ def set_os_level(self, value) -> None:
35
+ self[OSLevelBaseFunction.OS_LEVEL] = value