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,402 @@
1
+ """Implementation of MonAnaVlv."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+ from typing import ClassVar
8
+
9
+ from mtppy.utils.logging import logger
10
+ import threading
11
+ from time import sleep
12
+
13
+ from mtppy.utils.data_item import DataItem
14
+ from mtppy.part3.definitions.mtp_access import MTPAccess
15
+ from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
16
+ from mtppy.part3.data_assembly_set.active_elements.valves.ana_vlv import AnaVlv
17
+ from mtppy.part3.data_assembly_set.base_functions.operation_mode import (
18
+ OperationMode as OM,
19
+ )
20
+
21
+
22
+ class MonAnaVlvValues:
23
+ def __init__(
24
+ self, open_aut, open_op, close_aut, close_op, reset_aut, reset_op, pos
25
+ ):
26
+ self.open_aut = open_aut
27
+ self.open_op = open_op
28
+ self.close_aut = close_aut
29
+ self.close_op = close_op
30
+ self.reset_aut = reset_aut
31
+ self.reset_op = reset_op
32
+ self.pos = pos
33
+ self.lock = threading.Lock()
34
+ self.stop_event_lock = threading.Event()
35
+
36
+
37
+ class MonAnaVlv(AnaVlv):
38
+ # DataItem constant definitions
39
+ MON_EN = "MonEn"
40
+ MON_SAFE_POS = "MonSafePos"
41
+ MON_STAT_ERR = "MonStatErr"
42
+ MON_DYN_ERR = "MonDynErr"
43
+ MON_STAT_TI = "MonStatTi"
44
+ MON_DYN_TI = "MonDynTi"
45
+ POS_REACHED_FBK = "PosReachedFbk"
46
+ POS_TOLERANCE = "PosTolerance"
47
+ MON_POS_TI = "MonPosTi"
48
+ MON_POS_ERR = "MonPosErr"
49
+
50
+ # Class-level data items dictionary
51
+ CLASS_DATA_ITEMS: ClassVar = {
52
+ MON_EN: DataItem(MON_EN, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, None),
53
+ MON_SAFE_POS: DataItem(
54
+ MON_SAFE_POS, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, None
55
+ ),
56
+ MON_STAT_ERR: DataItem(
57
+ MON_STAT_ERR, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, None
58
+ ),
59
+ MON_DYN_ERR: DataItem(
60
+ MON_DYN_ERR, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, None
61
+ ),
62
+ MON_STAT_TI: DataItem(
63
+ MON_STAT_TI, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, None
64
+ ),
65
+ MON_DYN_TI: DataItem(
66
+ MON_DYN_TI, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, None
67
+ ),
68
+ POS_REACHED_FBK: DataItem(
69
+ POS_REACHED_FBK, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, None
70
+ ),
71
+ POS_TOLERANCE: DataItem(
72
+ POS_TOLERANCE, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, None
73
+ ),
74
+ MON_POS_TI: DataItem(
75
+ MON_POS_TI, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, None
76
+ ),
77
+ MON_POS_ERR: DataItem(
78
+ MON_POS_ERR, W3cXmlDataTypes.BOOLEAN, MTPAccess.ReadAccess, None
79
+ ),
80
+ }
81
+
82
+ monitored_values: MonAnaVlvValues
83
+ monitor_static_thread: threading.Thread
84
+ monitor_dynamic_thread: threading.Thread
85
+ monitor_pos_thread: threading.Thread
86
+
87
+ def __init__(
88
+ self,
89
+ tag_name,
90
+ tag_description="",
91
+ pos_min=0,
92
+ pos_max=100,
93
+ pos_scl_min=0,
94
+ pos_scl_max=100,
95
+ pos_unit=1342,
96
+ open_fbk_calc=True,
97
+ close_fbk_calc=True,
98
+ pos_fbk_calc=True,
99
+ safe_pos=0,
100
+ safe_pos_en=False,
101
+ perm_en=False,
102
+ intl_en=False,
103
+ prot_en=False,
104
+ mon_en=True,
105
+ mon_safe_pos=True,
106
+ mon_stat_ti=1,
107
+ mon_dyn_ti=1,
108
+ pos_tolerance=1,
109
+ mon_pos_ti=1,
110
+ ) -> None:
111
+ super().__init__(
112
+ tag_name,
113
+ tag_description,
114
+ pos_min,
115
+ pos_max,
116
+ pos_scl_min,
117
+ pos_scl_max,
118
+ pos_unit,
119
+ open_fbk_calc,
120
+ close_fbk_calc,
121
+ pos_fbk_calc,
122
+ safe_pos,
123
+ safe_pos_en,
124
+ perm_en,
125
+ intl_en,
126
+ prot_en,
127
+ )
128
+ self.mtpObjectType = (
129
+ "MTPDataObjectSUCLib/DataAssembly/ActiveElement/AnaVlv/MonAnaVlv"
130
+ )
131
+
132
+ # Initialize data items with dynamic values (using class-defined data items)
133
+ self[MonAnaVlv.MON_EN] = mon_en
134
+ self[MonAnaVlv.MON_SAFE_POS] = mon_safe_pos
135
+ self[MonAnaVlv.MON_STAT_ERR] = False
136
+ self[MonAnaVlv.MON_DYN_ERR] = False
137
+ self[MonAnaVlv.MON_STAT_TI] = mon_stat_ti
138
+ self[MonAnaVlv.MON_DYN_TI] = mon_dyn_ti
139
+ self[MonAnaVlv.POS_REACHED_FBK] = False
140
+ self[MonAnaVlv.POS_TOLERANCE] = pos_tolerance
141
+ self[MonAnaVlv.MON_POS_TI] = mon_pos_ti
142
+ self[MonAnaVlv.MON_POS_ERR] = False
143
+
144
+ self.monitored_values = MonAnaVlvValues(
145
+ self[MonAnaVlv.OPEN_AUT],
146
+ self[MonAnaVlv.OPEN_OP],
147
+ self[MonAnaVlv.CLOSE_AUT],
148
+ self[MonAnaVlv.CLOSE_OP],
149
+ self[MonAnaVlv.RESET_AUT],
150
+ self[MonAnaVlv.RESET_OP],
151
+ self[MonAnaVlv.POS_],
152
+ )
153
+ self.monitor_static_thread = None
154
+ self.monitor_dynamic_thread = None
155
+ self.monitor_pos_thread = None
156
+
157
+ def _go_save_pos(self):
158
+ if self[AnaVlv.SAFE_POS] == 1:
159
+ self._run_open_vlv()
160
+ safe_pos = self[AnaVlv.POS_MAX]
161
+ else:
162
+ self._run_close_vlv()
163
+ safe_pos = self[AnaVlv.POS_MIN]
164
+
165
+ self[AnaVlv.POS_] = safe_pos
166
+ logger.debug(f"Pos set to safe position {safe_pos}")
167
+ if self[MonAnaVlv.MON_EN]:
168
+ self.monitored_values.pos = safe_pos
169
+ self.monitor_pos_thread = threading.Thread(
170
+ target=self.monitor_position_reached, daemon=True
171
+ )
172
+ self.monitor_pos_thread.start()
173
+
174
+ if self[AnaVlv.POS_FBK_CALC]:
175
+ if self[MonAnaVlv.MON_EN]:
176
+ self.monitored_values.lock.acquire()
177
+ self[AnaVlv.POS_FBK] = safe_pos
178
+ self.monitored_values.lock.release()
179
+ else:
180
+ self[AnaVlv.POS_FBK] = safe_pos
181
+
182
+ def compare_states_control_signals(
183
+ self, monitor_time
184
+ ) -> tuple[list[bool], list[bool]]:
185
+ """
186
+ compare states of valve and control signals
187
+
188
+ Argument:
189
+ monitor_time: MonStatTi or MonDynTi times
190
+
191
+ Returns:
192
+ results of states and control signals comparisons (a list including True or False)
193
+ """
194
+ self.monitored_values.lock.acquire()
195
+ open_fbk1 = self[AnaVlv.OPEN_FBK]
196
+ close_fbk1 = self[AnaVlv.CLOSE_FBK]
197
+ open_op1 = self.monitored_values.open_op
198
+ open_aut1 = self.monitored_values.open_aut
199
+ close_op1 = self.monitored_values.close_op
200
+ close_aut1 = self.monitored_values.close_aut
201
+ reset_op1 = self.monitored_values.reset_op
202
+ reset_aut1 = self.monitored_values.reset_aut
203
+ self.monitored_values.lock.release()
204
+
205
+ sleep(monitor_time)
206
+
207
+ self.monitored_values.lock.acquire()
208
+ open_fbk2 = self[AnaVlv.OPEN_FBK]
209
+ close_fbk2 = self[AnaVlv.CLOSE_FBK]
210
+ open_op2 = self.monitored_values.open_op
211
+ open_aut2 = self.monitored_values.open_aut
212
+ close_op2 = self.monitored_values.close_op
213
+ close_aut2 = self.monitored_values.close_aut
214
+ reset_op2 = self.monitored_values.reset_op
215
+ reset_aut2 = self.monitored_values.reset_aut
216
+ self.monitored_values.lock.release()
217
+
218
+ control_signals_comparison = [
219
+ open_op1 == open_op2,
220
+ open_aut1 == open_aut2,
221
+ close_op1 == close_op2,
222
+ close_aut1 == close_aut2,
223
+ reset_op1 == reset_op2,
224
+ reset_aut1 == reset_aut2,
225
+ ]
226
+ state_comparison = [open_fbk1 == open_fbk2, close_fbk1 == close_fbk2]
227
+
228
+ return state_comparison, control_signals_comparison
229
+
230
+ def monitor_static_error(self):
231
+ """
232
+ monitor static error
233
+ """
234
+ while True:
235
+ if self.monitored_values.stop_event_lock.is_set():
236
+ logger.debug("static monitoring stopped")
237
+ break
238
+ states, control_signals = self.compare_states_control_signals(
239
+ self[MonAnaVlv.MON_STAT_TI]
240
+ )
241
+
242
+ if not all(states): # if the states of valve changed
243
+ if all(control_signals): # but the control signals did not change
244
+ self[MonAnaVlv.MON_STAT_ERR] = True
245
+ logger.debug("Static error set to True")
246
+ self._handle_monitored_error()
247
+
248
+ def monitor_dynamic_error(self):
249
+ """
250
+ monitor dynamic error
251
+ """
252
+ while True:
253
+ if self.monitored_values.stop_event_lock.is_set():
254
+ logger.debug("dynamic monitoring stopped")
255
+ break
256
+ states, control_signals = self.compare_states_control_signals(
257
+ self[MonAnaVlv.MON_DYN_TI]
258
+ )
259
+
260
+ if all(states): # if the states of valve did not change
261
+ if not all(control_signals): # but a control command is executed
262
+ self[MonAnaVlv.MON_DYN_ERR] = True
263
+ logger.debug("Dynamic error set to True")
264
+ self._handle_monitored_error()
265
+
266
+ def monitor_position_reached(self):
267
+ """
268
+ monitor position error
269
+ """
270
+ pos = self.monitored_values.pos
271
+ sleep(self[MonAnaVlv.MON_POS_TI])
272
+ pos_reached_fbk = (
273
+ abs(self[AnaVlv.POS_FBK] - pos) <= self[MonAnaVlv.POS_TOLERANCE]
274
+ )
275
+ self[MonAnaVlv.POS_REACHED_FBK] = pos_reached_fbk
276
+ if not pos_reached_fbk:
277
+ self[MonAnaVlv.MON_POS_ERR] = True
278
+ logger.debug("position error set to True")
279
+ self._handle_monitored_error()
280
+
281
+ def _handle_monitored_error(self):
282
+ if self[MonAnaVlv.MON_SAFE_POS]:
283
+ logger.debug("set valve to safety position")
284
+ if self[AnaVlv.SAFE_POS_EN]:
285
+ self._go_save_pos()
286
+ else:
287
+ logger.debug("Device has no safe position")
288
+ self[AnaVlv.SAFE_POS_ACT] = True
289
+
290
+ def start_monitor(self):
291
+ if self[MonAnaVlv.MON_EN]:
292
+ self.monitor_static_thread = threading.Thread(
293
+ target=self.monitor_static_error, daemon=True
294
+ )
295
+ self.monitor_static_thread.start()
296
+ logger.debug("static monitoring start")
297
+ self.monitor_dynamic_thread = threading.Thread(
298
+ target=self.monitor_dynamic_error, daemon=True
299
+ )
300
+ self.monitor_dynamic_thread.start()
301
+ logger.debug("dynamic monitoring start")
302
+
303
+ def set_open_aut(self, value: bool):
304
+ logger.debug(f"OpenAut set to {value}")
305
+ if self.op_mode[OM.STATE_AUT_ACT]:
306
+ if value and self._run_allowed():
307
+ if self[MonAnaVlv.MON_EN]:
308
+ self.monitored_values.lock.acquire()
309
+ self.monitored_values.open_aut = value
310
+ self.monitored_values.lock.release()
311
+ self._run_open_vlv()
312
+
313
+ def set_close_aut(self, value: bool):
314
+ if self.op_mode[OM.STATE_AUT_ACT]:
315
+ if value and self._run_allowed():
316
+ if self[MonAnaVlv.MON_EN]:
317
+ self.monitored_values.lock.acquire()
318
+ self.monitored_values.close_aut = value
319
+ self.monitored_values.lock.release()
320
+ logger.debug(f"CloseAut set to {value}")
321
+ self._run_close_vlv()
322
+
323
+ def set_reset_aut(self, value: bool):
324
+ logger.debug(f"ResetAut set to {value}")
325
+ if self.op_mode[OM.STATE_AUT_ACT] and value:
326
+ if self[MonAnaVlv.MON_EN]:
327
+ self.monitored_values.lock.acquire()
328
+ self.monitored_values.reset_aut = value
329
+ self.monitored_values.lock.release()
330
+ self._reset_vlv()
331
+
332
+ def set_open_op(self, value: bool):
333
+ logger.debug(f"OpenOp set to {value}")
334
+ if self.op_mode[OM.STATE_OP_ACT]:
335
+ if value and self._run_allowed():
336
+ if self[MonAnaVlv.MON_EN]:
337
+ self.monitored_values.lock.acquire()
338
+ self.monitored_values.open_op = value
339
+ self.monitored_values.lock.release()
340
+ self._run_open_vlv()
341
+ self[AnaVlv.OPEN_OP] = False
342
+
343
+ def set_close_op(self, value: bool):
344
+ logger.debug(f"CloseOp set to {value}")
345
+ if self.op_mode[OM.STATE_OP_ACT]:
346
+ if value and self._run_allowed():
347
+ if self[MonAnaVlv.MON_EN]:
348
+ self.monitored_values.lock.acquire()
349
+ self.monitored_values.close_op = value
350
+ self.monitored_values.lock.release()
351
+ self._run_close_vlv()
352
+ self[AnaVlv.CLOSE_OP] = False
353
+
354
+ def set_reset_op(self, value: bool):
355
+ logger.debug(f"ResetOp set to {value}")
356
+ if self.op_mode[OM.STATE_OP_ACT] and value:
357
+ if self[MonAnaVlv.MON_EN]:
358
+ self.monitored_values.lock.acquire()
359
+ self.monitored_values.reset_op = value
360
+ self.monitored_values.lock.release()
361
+ self._reset_vlv()
362
+ self[AnaVlv.RESET_OP] = False
363
+
364
+ def _set_pos(self, value: float):
365
+ if self.valid_value(value):
366
+ # if SafePosAct is inactive -> manual or internal position specification
367
+ if self[AnaVlv.OPEN_ACT] and not self[AnaVlv.SAFE_POS_ACT]:
368
+ value = value
369
+ elif self[AnaVlv.CLOSE_ACT] and not self[AnaVlv.SAFE_POS_ACT]:
370
+ value = self[AnaVlv.POS_MIN]
371
+
372
+ # if SafePosAct is active, safety setting for the position is accepted
373
+ elif self[AnaVlv.SAFE_POS_ACT] or (
374
+ self[AnaVlv.PERM_EN] and self[AnaVlv.PERMIT_] == 0
375
+ ):
376
+ logger.debug("manual or internal position specification inactive")
377
+ return
378
+
379
+ self[AnaVlv.POS_] = value
380
+ logger.debug(f"Pos set to {value}")
381
+
382
+ # start monitor_pos_thread to after pos has been changed
383
+ # if the position does not change, there is no need to check the desired position
384
+ if self[MonAnaVlv.MON_EN]:
385
+ self.monitored_values.pos = value
386
+ self.monitor_pos_thread = threading.Thread(
387
+ target=self.monitor_position_reached
388
+ )
389
+ self.monitor_pos_thread.start()
390
+
391
+ if self[AnaVlv.POS_FBK_CALC]:
392
+ if self[MonAnaVlv.MON_EN]:
393
+ self.monitored_values.lock.acquire()
394
+ self[AnaVlv.POS_FBK] = value
395
+ self.monitored_values.lock.release()
396
+ else:
397
+ self[AnaVlv.POS_FBK] = value
398
+ else:
399
+ logger.debug(f"Pos cannot be set to {value} (out of range)")
400
+
401
+ def set_stop_monitor(self):
402
+ self.monitored_values.stop_event_lock.set()
@@ -0,0 +1,270 @@
1
+ """Test Suite for MonAnaVlv."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+ import pytest
8
+ import time
9
+
10
+ from mtppy.utils.testing import data_object_set_modes
11
+ from mtppy.part3.data_assembly_set.active_elements.valves.mon_ana_vlv import MonAnaVlv
12
+
13
+
14
+ def init_mon_ana_vlv(
15
+ init_op_mode="off",
16
+ init_src_mode="int",
17
+ open_fbk_calc=True,
18
+ close_fbk_calc=True,
19
+ pos_fbk_calc=True,
20
+ safe_pos=1,
21
+ safe_pos_en=True,
22
+ perm_en=False,
23
+ intl_en=False,
24
+ prot_en=False,
25
+ mon_en=True,
26
+ mon_safe_pos=True,
27
+ mon_stat_ti=1,
28
+ mon_dyn_ti=1,
29
+ pos_tolerance=1,
30
+ mon_pos_ti=1,
31
+ ):
32
+ mon_ana_vlv = MonAnaVlv(
33
+ "tag",
34
+ tag_description="",
35
+ pos_min=2,
36
+ pos_max=10,
37
+ pos_scl_min=0,
38
+ pos_scl_max=10,
39
+ pos_unit=0,
40
+ open_fbk_calc=open_fbk_calc,
41
+ close_fbk_calc=close_fbk_calc,
42
+ pos_fbk_calc=pos_fbk_calc,
43
+ safe_pos=safe_pos,
44
+ safe_pos_en=safe_pos_en,
45
+ perm_en=perm_en,
46
+ intl_en=intl_en,
47
+ prot_en=prot_en,
48
+ mon_en=mon_en,
49
+ mon_safe_pos=mon_safe_pos,
50
+ mon_stat_ti=mon_stat_ti,
51
+ mon_dyn_ti=mon_dyn_ti,
52
+ pos_tolerance=pos_tolerance,
53
+ mon_pos_ti=mon_pos_ti,
54
+ )
55
+ data_object_set_modes(mon_ana_vlv, init_op_mode, init_src_mode)
56
+ return mon_ana_vlv
57
+
58
+
59
+ test_scenario_no_control_signals = [
60
+ ("off", "int", "set_open_op"),
61
+ ("off", "man", "set_open_op"),
62
+ ("aut", "int", "set_open_op"),
63
+ ("aut", "man", "set_open_op"),
64
+ ("off", "int", "set_open_aut"),
65
+ ("off", "man", "set_open_aut"),
66
+ ("op", "int", "set_open_aut"),
67
+ ("op", "man", "set_open_aut"),
68
+ ("off", "int", "set_close_op"),
69
+ ("off", "man", "set_close_op"),
70
+ ("aut", "int", "set_close_op"),
71
+ ("aut", "man", "set_close_op"),
72
+ ("off", "int", "set_close_aut"),
73
+ ("off", "man", "set_close_aut"),
74
+ ("op", "int", "set_close_aut"),
75
+ ("op", "man", "set_close_aut"),
76
+ ]
77
+
78
+
79
+ @pytest.mark.parametrize(
80
+ "op_mode, src_mode, set_command", test_scenario_no_control_signals
81
+ )
82
+ def test_static_error(op_mode, src_mode, set_command):
83
+ for command in [True, False]:
84
+ mon_ana_vlv = init_mon_ana_vlv(init_op_mode=op_mode, init_src_mode=src_mode)
85
+ try:
86
+ mon_ana_vlv.start_monitor()
87
+ eval(f"mon_ana_vlv.{set_command}({command})")
88
+ print(f"Scenario: mode {op_mode} {src_mode}, {set_command}")
89
+
90
+ # mimic an error situation: OpenFbk changes to True without any change in control signal
91
+ mon_ana_vlv["OpenFbk"] = True
92
+ time.sleep(1.1) # monitor time has expired (> 1s)
93
+
94
+ assert mon_ana_vlv["MonStatErr"] == True
95
+ assert mon_ana_vlv["MonDynErr"] == False
96
+ assert mon_ana_vlv["SafePosAct"] == True
97
+ assert mon_ana_vlv["OpenFbk"] == True # safe position of valve is open
98
+
99
+ time.sleep(0.5)
100
+
101
+ # after valve is set to safe position (open), the OpenFbk becomes to False --> another static error
102
+ mon_ana_vlv["OpenFbk"] = False
103
+ time.sleep(1.1) # monitor time has expired (> 1s)
104
+
105
+ assert mon_ana_vlv["MonStatErr"] == True
106
+ assert mon_ana_vlv["MonDynErr"] == False
107
+ assert mon_ana_vlv["SafePosAct"] == True
108
+ assert mon_ana_vlv["OpenFbk"] == True # safe position of valve is open
109
+ finally:
110
+ mon_ana_vlv.set_stop_monitor()
111
+ mon_ana_vlv.monitor_static_thread.join()
112
+ mon_ana_vlv.monitor_dynamic_thread.join()
113
+
114
+
115
+ @pytest.mark.parametrize(
116
+ "op_mode, src_mode, set_command", test_scenario_no_control_signals
117
+ )
118
+ def test_static_error_within_monitor_time(op_mode, src_mode, set_command):
119
+ """If the required state is set on the interface before the time has expired (wait tme < 1s in this test),
120
+ there is no error"""
121
+
122
+ for command in [True, False]:
123
+ mon_ana_vlv = init_mon_ana_vlv(init_op_mode=op_mode, init_src_mode=src_mode)
124
+ try:
125
+ mon_ana_vlv.start_monitor()
126
+ eval(f"mon_ana_vlv.{set_command}({command})")
127
+ print(f"Scenario: mode {op_mode} {src_mode}, {set_command}")
128
+
129
+ # mimic an error situation: OpenFbk changes to True without any change in control signal
130
+ mon_ana_vlv["OpenFbk"] = True
131
+ time.sleep(0.6) # monitor time has not expired (1s)
132
+
133
+ assert mon_ana_vlv["MonStatErr"] == False
134
+ assert mon_ana_vlv["MonDynErr"] == False
135
+ assert mon_ana_vlv["SafePosAct"] == False
136
+ finally:
137
+ mon_ana_vlv.set_stop_monitor()
138
+ mon_ana_vlv.monitor_static_thread.join()
139
+ mon_ana_vlv.monitor_dynamic_thread.join()
140
+
141
+
142
+ test_scenario_control_signals = [
143
+ ("op", "int", "set_open_op", "set_close_op"),
144
+ ("op", "man", "set_open_op", "set_close_op"),
145
+ ("aut", "int", "set_open_aut", "set_close_aut"),
146
+ ("aut", "man", "set_open_aut", "set_close_aut"),
147
+ ]
148
+
149
+
150
+ @pytest.mark.parametrize(
151
+ "op_mode, src_mode, open_command, close_command", test_scenario_control_signals
152
+ )
153
+ def test_dynamic_error_open_close(op_mode, src_mode, open_command, close_command):
154
+ mon_ana_vlv = init_mon_ana_vlv(init_op_mode=op_mode, init_src_mode=src_mode)
155
+ try:
156
+ mon_ana_vlv.start_monitor()
157
+ eval(f"mon_ana_vlv.{open_command}(True)")
158
+ print(f"Scenario: mode {op_mode} {src_mode}, {open_command}")
159
+
160
+ # mimic an error situation: OpenFbk does not change to True after control signal has changed to open
161
+ mon_ana_vlv["OpenFbk"] = False
162
+ time.sleep(1.1) # monitor time has expired (> 1s)
163
+
164
+ assert mon_ana_vlv["MonDynErr"] == True
165
+ assert mon_ana_vlv["MonStatErr"] == False
166
+ assert mon_ana_vlv["SafePosAct"] == True
167
+ assert mon_ana_vlv["OpenFbk"] == True # safe position of valve is open
168
+
169
+ eval(f"mon_ana_vlv.{close_command}(True)")
170
+ print(f"Scenario: mode {op_mode} {src_mode}, {close_command}")
171
+
172
+ # mimic an error situation: OpenFbk does not change to False after control signal has changed to close
173
+ mon_ana_vlv["OpenFbk"] = True
174
+ time.sleep(1.1) # monitor time has expired (> 1s)
175
+
176
+ assert mon_ana_vlv["MonDynErr"] == True
177
+ assert mon_ana_vlv["MonStatErr"] == False
178
+ assert mon_ana_vlv["SafePosAct"] == True
179
+ assert mon_ana_vlv["OpenFbk"] == True # safe position of valve is open
180
+ finally:
181
+ mon_ana_vlv.set_stop_monitor()
182
+ mon_ana_vlv.monitor_static_thread.join()
183
+ mon_ana_vlv.monitor_dynamic_thread.join()
184
+
185
+
186
+ test_scenario_pos = [
187
+ ("op", "int", "set_pos_int"),
188
+ ("aut", "int", "set_pos_int"),
189
+ ("op", "man", "set_pos_man"),
190
+ ("aut", "man", "set_pos_man"),
191
+ ]
192
+
193
+
194
+ @pytest.mark.parametrize("op_mode, src_mode, set_command", test_scenario_pos)
195
+ def test_pos_open(op_mode, src_mode, set_command):
196
+ for pos_value in [-2, 4, 7, 10, 100]: # pos min is 2, pos max is 10
197
+ mon_ana_vlv = init_mon_ana_vlv(
198
+ init_op_mode=op_mode, init_src_mode=src_mode, pos_fbk_calc=True
199
+ )
200
+ if op_mode == "op":
201
+ mon_ana_vlv.set_open_op(True)
202
+ elif op_mode == "aut":
203
+ mon_ana_vlv.set_open_aut(True)
204
+ eval(f"mon_ana_vlv.{set_command}({pos_value})")
205
+ print(
206
+ f"Scenario: modes=({op_mode},{src_mode}), cmd={set_command}, pos={pos_value}"
207
+ )
208
+ # position tolerance is 1, pos_fbk is 2.5 --> pos error
209
+ mon_ana_vlv["PosFbk"] = 7.5
210
+ time.sleep(1.5) # time for monitoring pos error is 1s
211
+ if 2 <= pos_value < 6 or 8 < pos_value <= 10: # out of the pos tolerance range
212
+ assert mon_ana_vlv["PosReachedFbk"] == False
213
+ assert mon_ana_vlv["MonPosErr"] == True
214
+ assert mon_ana_vlv["SafePosAct"] == True
215
+ assert mon_ana_vlv["OpenAct"] == True
216
+
217
+ elif 6 <= pos_value <= 8: # within the pos tolerance range
218
+ assert mon_ana_vlv["PosReachedFbk"] == True
219
+ assert mon_ana_vlv["MonPosErr"] == False
220
+ assert mon_ana_vlv["SafePosAct"] == False
221
+
222
+ else:
223
+ assert mon_ana_vlv["PosReachedFbk"] == False
224
+ assert mon_ana_vlv["MonPosErr"] == False
225
+ assert mon_ana_vlv["SafePosAct"] == False
226
+
227
+
228
+ test_scenario_control_signals_reset = [
229
+ ("op", "int", "set_reset_op"),
230
+ ("op", "man", "set_reset_op"),
231
+ ("aut", "int", "set_reset_aut"),
232
+ ("aut", "man", "set_reset_aut"),
233
+ ]
234
+
235
+
236
+ @pytest.mark.parametrize(
237
+ "op_mode, src_mode, set_command", test_scenario_control_signals_reset
238
+ )
239
+ def test_dynamic_error_reset(op_mode, src_mode, set_command):
240
+ mon_ana_vlv = init_mon_ana_vlv(init_op_mode=op_mode, init_src_mode=src_mode)
241
+ try:
242
+ mon_ana_vlv.start_monitor()
243
+ # set valve to open
244
+ if op_mode == "op":
245
+ mon_ana_vlv.set_open_op(True)
246
+ elif op_mode == "aut":
247
+ mon_ana_vlv.set_open_aut(True)
248
+ time.sleep(1.1)
249
+
250
+ # reset valve
251
+ eval(f"mon_ana_vlv.{set_command}(True)")
252
+ print(f"Scenario: mode {op_mode} {src_mode}, {set_command}")
253
+
254
+ # mimic an error situation: OpenFbk does not change to False after reset changes to True
255
+ mon_ana_vlv["OpenFbk"] = True
256
+ time.sleep(1.1)
257
+
258
+ assert mon_ana_vlv["MonDynErr"] == True
259
+ assert mon_ana_vlv["MonStatErr"] == False
260
+ assert mon_ana_vlv["SafePosAct"] == True
261
+ assert mon_ana_vlv["OpenFbk"] == True # safe position of valve is open
262
+ finally:
263
+ mon_ana_vlv.set_stop_monitor()
264
+ mon_ana_vlv.monitor_static_thread.join()
265
+ mon_ana_vlv.monitor_dynamic_thread.join()
266
+
267
+
268
+ if __name__ == "__main__":
269
+ # This allows running the tests directly if needed
270
+ pytest.main([__file__, "-v"])