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,333 @@
1
+ """Implementation of PidCtrl and PID Algorithm."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from mtppy.utils.logging import logger
9
+ import threading
10
+ from typing import ClassVar
11
+
12
+ from simple_pid import PID
13
+
14
+ from mtppy.utils.data_item import DataItem
15
+ from mtppy.part3.definitions.mtp_access import MTPAccess
16
+ from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
17
+ from mtppy.part3.data_assembly_set.active_elements.active_element import ActiveElement
18
+ from mtppy.part3.data_assembly_set.base_functions.operation_mode import OperationMode
19
+ from mtppy.part3.data_assembly_set.base_functions.source_mode import SourceMode
20
+
21
+
22
+ class PIDController:
23
+ def __init__(
24
+ self,
25
+ mv_init_value: float = 0,
26
+ kp: float = 100,
27
+ ki: float = 10,
28
+ kd: float = 1,
29
+ mv_min: float = 0,
30
+ mv_max: float = 100,
31
+ sample_time: float = 0.1,
32
+ ) -> None:
33
+ self.kp = kp
34
+ self.ki = ki
35
+ self.kd = kd
36
+ self.mv_min = mv_min
37
+ self.mv_max = mv_max
38
+ self.sp = 0
39
+ self.pv = 0
40
+ self.mv = mv_init_value
41
+ self.sample_time = sample_time
42
+
43
+ self.ctrl = PID(
44
+ Kp=self.kp,
45
+ Ki=self.ki,
46
+ Kd=self.kd,
47
+ output_limits=(mv_min, mv_max),
48
+ sample_time=sample_time,
49
+ )
50
+
51
+ self._stop_event = threading.Event()
52
+ self._thread = None
53
+
54
+ def set_limits(self, mv_min, mv_max):
55
+ self.ctrl.output_limits = (mv_min, mv_max)
56
+
57
+ def start(self):
58
+ logger.debug("Starting PID controller...")
59
+ self._stop_event.clear()
60
+ self._thread = threading.Thread(target=self.loop, daemon=True)
61
+ self.ctrl.setpoint = self.sp
62
+ self._thread.start()
63
+ logger.debug("Started")
64
+
65
+ def stop(self):
66
+ logger.debug("Stopping PID controller...")
67
+ self._stop_event.set()
68
+ if self._thread is not None:
69
+ self._thread.join(3.0)
70
+ if self._thread.is_alive():
71
+ logger.error("PID loop failed to stop within 3 secs")
72
+ self.ctrl.reset()
73
+ logger.debug("Stopped")
74
+
75
+ def set_sp(self, sp):
76
+ self.sp = sp
77
+ self.ctrl.setpoint = sp
78
+
79
+ def set_pv(self, pv):
80
+ self.pv = pv
81
+
82
+ def loop(self):
83
+ while not self._stop_event.wait(self.sample_time):
84
+ self.mv = self.ctrl(self.pv)
85
+
86
+ def get_mv(self):
87
+ return self.mv
88
+
89
+ def set_kp(self, kp):
90
+ self.ctrl.Kp = kp
91
+
92
+ def set_ki(self, ki):
93
+ self.ctrl.Ki = ki
94
+
95
+ def set_kd(self, kd):
96
+ self.ctrl.Kd = kd
97
+
98
+
99
+ class PIDCtrl(ActiveElement):
100
+ # DataItem constant definitions
101
+ PV_: str = "PV"
102
+ PV_SCL_MIN: str = "PVSclMin"
103
+ PV_SCL_MAX: str = "PVSclMax"
104
+ PV_UNIT: str = "PVUnit"
105
+ SP_MAN = "SPMan"
106
+ SP_INT = "SPInt"
107
+ SP_SCL_MIN = "SPSclMin"
108
+ SP_SCL_MAX = "SPSclMax"
109
+ SP_UNIT = "SPUnit"
110
+ SP_INT_MIN = "SPIntMin"
111
+ SP_INT_MAX = "SPIntMax"
112
+ SP_MAN_MIN = "SPManMin"
113
+ SP_MAN_MAX = "SPManMax"
114
+ SP_ = "SP"
115
+ MV_MAN = "MVMan"
116
+ MV_ = "MV"
117
+ MV_MIN = "MVMin"
118
+ MV_MAX = "MVMax"
119
+ MV_UNIT = "MVUnit"
120
+ MV_SCL_MIN = "MVSclMin"
121
+ MV_SCL_MAX = "MVSclMax"
122
+ P_: str = "P"
123
+ TI_: str = "Ti"
124
+ TD_: str = "Td"
125
+
126
+ # Class-level data items dictionary
127
+ CLASS_DATA_ITEMS: ClassVar = {
128
+ PV_: DataItem(PV_, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0),
129
+ PV_SCL_MIN: DataItem(
130
+ PV_SCL_MIN, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
131
+ ),
132
+ PV_SCL_MAX: DataItem(
133
+ PV_SCL_MAX, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
134
+ ),
135
+ PV_UNIT: DataItem(PV_UNIT, W3cXmlDataTypes.SHORT, MTPAccess.ReadAccess, 0),
136
+ SP_MAN: DataItem(SP_MAN, W3cXmlDataTypes.FLOAT, MTPAccess.ReadWriteAccess, 0),
137
+ SP_INT: DataItem(SP_INT, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0),
138
+ SP_SCL_MIN: DataItem(
139
+ SP_SCL_MIN, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
140
+ ),
141
+ SP_SCL_MAX: DataItem(
142
+ SP_SCL_MAX, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
143
+ ),
144
+ SP_UNIT: DataItem(SP_UNIT, W3cXmlDataTypes.SHORT, MTPAccess.ReadAccess, 0),
145
+ SP_INT_MIN: DataItem(
146
+ SP_INT_MIN, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
147
+ ),
148
+ SP_INT_MAX: DataItem(
149
+ SP_INT_MAX, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
150
+ ),
151
+ SP_MAN_MIN: DataItem(
152
+ SP_MAN_MIN, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
153
+ ),
154
+ SP_MAN_MAX: DataItem(
155
+ SP_MAN_MAX, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
156
+ ),
157
+ SP_: DataItem(SP_, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0),
158
+ MV_MAN: DataItem(MV_MAN, W3cXmlDataTypes.FLOAT, MTPAccess.ReadWriteAccess, 0),
159
+ MV_: DataItem(MV_, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0),
160
+ MV_MIN: DataItem(MV_MIN, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0),
161
+ MV_MAX: DataItem(MV_MAX, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0),
162
+ MV_UNIT: DataItem(MV_UNIT, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0),
163
+ MV_SCL_MIN: DataItem(
164
+ MV_SCL_MIN, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
165
+ ),
166
+ MV_SCL_MAX: DataItem(
167
+ MV_SCL_MAX, W3cXmlDataTypes.FLOAT, MTPAccess.ReadAccess, 0
168
+ ),
169
+ P_: DataItem(P_, W3cXmlDataTypes.FLOAT, MTPAccess.ReadWriteAccess, 0),
170
+ TI_: DataItem(TI_, W3cXmlDataTypes.FLOAT, MTPAccess.ReadWriteAccess, 0),
171
+ TD_: DataItem(TD_, W3cXmlDataTypes.FLOAT, MTPAccess.ReadWriteAccess, 0),
172
+ }
173
+
174
+ op_mode: OperationMode
175
+ src_mode: SourceMode
176
+ ctrl: PIDController
177
+ _mv_update_thread: threading.Thread
178
+ _stop_event: threading.Event
179
+
180
+ def __init__(
181
+ self,
182
+ tag_name: str,
183
+ tag_description: str = "",
184
+ pv_scl_min: float = 0,
185
+ pv_scl_max: float = 100,
186
+ pv_unit: float = 0,
187
+ sp_scl_min: float = 0,
188
+ sp_scl_max: float = 100,
189
+ sp_unit: float = 0,
190
+ sp_int_min: float = 0,
191
+ sp_int_max: float = 100,
192
+ sp_man_min: float = 0,
193
+ sp_man_max: float = 100,
194
+ mv_min: float = 0,
195
+ mv_max: float = 1000,
196
+ mv_unit: float = 0,
197
+ mv_scl_min: float = 0,
198
+ mv_scl_max: float = 100,
199
+ prop: float = 100,
200
+ ti: float = 10,
201
+ td: float = 1,
202
+ ):
203
+ super().__init__(tag_name, tag_description)
204
+ self.mtpObjectType = "MTPDataObjectSUCLib/DataAssembly/ActiveElement/PIDCtrl"
205
+
206
+ self.op_mode = OperationMode()
207
+ self.src_mode = SourceMode()
208
+
209
+ self.ctrl = PIDController(
210
+ mv_init_value=mv_min,
211
+ mv_min=mv_min,
212
+ mv_max=mv_max,
213
+ kp=prop,
214
+ ki=ti,
215
+ kd=td,
216
+ sample_time=0.1,
217
+ )
218
+
219
+ # Initialize data items with dynamic values (using class-defined data items)
220
+ self[PIDCtrl.PV_] = 0
221
+ self[PIDCtrl.PV_SCL_MIN] = pv_scl_min
222
+ self[PIDCtrl.PV_SCL_MAX] = pv_scl_max
223
+ self[PIDCtrl.PV_UNIT] = pv_unit
224
+ self[PIDCtrl.SP_MAN] = sp_man_min
225
+ self[PIDCtrl.SP_INT] = sp_int_min
226
+ self[PIDCtrl.SP_SCL_MIN] = sp_scl_min
227
+ self[PIDCtrl.SP_SCL_MAX] = sp_scl_max
228
+ self[PIDCtrl.SP_UNIT] = sp_unit
229
+ self[PIDCtrl.SP_INT_MIN] = sp_int_min
230
+ self[PIDCtrl.SP_INT_MAX] = sp_int_max
231
+ self[PIDCtrl.SP_MAN_MIN] = sp_man_min
232
+ self[PIDCtrl.SP_MAN_MAX] = sp_man_max
233
+ self[PIDCtrl.SP_] = sp_int_min
234
+ self[PIDCtrl.MV_MAN] = mv_min
235
+ self[PIDCtrl.MV_] = mv_min
236
+ self[PIDCtrl.MV_MIN] = mv_min
237
+ self[PIDCtrl.MV_MAX] = mv_max
238
+ self[PIDCtrl.MV_UNIT] = mv_unit
239
+ self[PIDCtrl.MV_SCL_MIN] = mv_scl_min
240
+ self[PIDCtrl.MV_SCL_MAX] = mv_scl_max
241
+ self[PIDCtrl.P_] = prop
242
+ self[PIDCtrl.TI_] = ti
243
+ self[PIDCtrl.TD_] = td
244
+
245
+ # Set callbacks for writable items
246
+ self.get_item(PIDCtrl.SP_MAN).ext_wrt_cb = self.set_sp_man
247
+ self.get_item(PIDCtrl.MV_MAN).ext_wrt_cb = self.set_mv_man
248
+ self.get_item(PIDCtrl.P_).ext_wrt_cb = self.set_p
249
+ self.get_item(PIDCtrl.TI_).ext_wrt_cb = self.set_ti
250
+ self.get_item(PIDCtrl.TD_).ext_wrt_cb = self.set_td
251
+
252
+ self.op_mode.add_enter_automatic_callback(self._start_aut_mv_update_loop)
253
+ self.op_mode.add_exit_automatic_callback(self._stop_aut_mv_update_loop)
254
+
255
+ self._mv_update_thread = None
256
+ self._stop_event = threading.Event()
257
+
258
+ def set_p(self, value):
259
+ self[PIDCtrl.P_] = value
260
+ self.ctrl.set_kp(value)
261
+
262
+ def set_ti(self, value):
263
+ self[PIDCtrl.TI_] = value
264
+ self.ctrl.set_ki(value)
265
+
266
+ def set_td(self, value):
267
+ self[PIDCtrl.TD_] = value
268
+ self.ctrl.set_kd(value)
269
+
270
+ @staticmethod
271
+ def _valid_value(value, v_min, v_max):
272
+ return v_min <= value <= v_max
273
+
274
+ def _set_sp(self, value, v_min, v_max):
275
+ if self._valid_value(value, v_min, v_max):
276
+ self[PIDCtrl.SP_] = value
277
+ self.ctrl.set_sp(value)
278
+ logger.debug(f"SP set to {value}")
279
+ else:
280
+ logger.debug(f"SP cannot be set to {value} (out of range)")
281
+
282
+ def set_sp_man(self, value):
283
+ logger.debug(f"SPMan set to {value}")
284
+ if self.src_mode["SrcManAct"] and self.op_mode["StateAutAct"]:
285
+ self._set_sp(value, self[PIDCtrl.SP_MAN_MIN], self[PIDCtrl.SP_MAN_MAX])
286
+
287
+ def set_sp_int(self, value):
288
+ logger.debug(f"SPInt set to {value}")
289
+ if self.src_mode["SrcIntAct"] and self.op_mode["StateAutAct"]:
290
+ self._set_sp(value, self[PIDCtrl.SP_INT_MIN], self[PIDCtrl.SP_INT_MAX])
291
+
292
+ def set_mv_man(self, value):
293
+ logger.debug(f"MVMan set to {value}")
294
+ if self.op_mode["StateOpAct"]:
295
+ self._set_mv(value, self[PIDCtrl.MV_MIN], self[PIDCtrl.MV_MAX])
296
+
297
+ def _set_mv(self, value, v_min, v_max):
298
+ if self._valid_value(value, v_min, v_max):
299
+ self[PIDCtrl.MV_] = value
300
+ logger.debug(f"MV set to {value}")
301
+ else:
302
+ logger.debug(f"MV cannot be set to {value} (out of range)")
303
+
304
+ def _update_mv_int_loop(self):
305
+ while not self._stop_event.wait(0.1):
306
+ if self.op_mode["StateAutAct"]:
307
+ value = self.ctrl.get_mv()
308
+ self._set_mv(value, self[PIDCtrl.MV_MIN], self[PIDCtrl.MV_MAX])
309
+
310
+ def _start_aut_mv_update_loop(self):
311
+ self.ctrl.start()
312
+ self._mv_update_thread = threading.Thread(
313
+ target=self._update_mv_int_loop, daemon=True
314
+ )
315
+ self._mv_update_thread.start()
316
+
317
+ def _stop_aut_mv_update_loop(self):
318
+ self.ctrl.stop()
319
+ self._stop_event.set()
320
+ if self._mv_update_thread is not None:
321
+ self._mv_update_thread.join(timeout=3.0)
322
+ if self._mv_update_thread.is_alive():
323
+ logger.error("PID update_mv loop failed to stop within 3 sec")
324
+
325
+ def set_pv(self, value):
326
+ self[PIDCtrl.PV_] = value
327
+ self.ctrl.set_pv(value)
328
+
329
+ def get_mv(self):
330
+ return self[PIDCtrl.MV_]
331
+
332
+ def get_sp(self):
333
+ return self[PIDCtrl.SP_]
@@ -0,0 +1,156 @@
1
+ """Test Suite for PIDCtrl."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+ from time import sleep
8
+
9
+ import pytest
10
+ from mtppy.utils.testing import data_object_set_modes
11
+ from mtppy.part3.data_assembly_set.active_elements.pid.pid_ctrl import PIDCtrl
12
+
13
+
14
+ def init_pid_ctrl(init_op_mode: str = "off", init_src_mode: str = "int") -> PIDCtrl:
15
+ instance = PIDCtrl(
16
+ "tag",
17
+ tag_description="",
18
+ pv_scl_min=0,
19
+ pv_scl_max=100,
20
+ pv_unit=0,
21
+ sp_scl_min=0,
22
+ sp_scl_max=100,
23
+ sp_unit=0,
24
+ sp_int_min=-10,
25
+ sp_int_max=1000,
26
+ sp_man_min=-10,
27
+ sp_man_max=1000,
28
+ mv_min=-10,
29
+ mv_max=1000,
30
+ mv_unit=0,
31
+ mv_scl_min=0,
32
+ mv_scl_max=100,
33
+ # p=1,
34
+ ti=10,
35
+ td=1,
36
+ )
37
+ data_object_set_modes(instance, init_op_mode, init_src_mode)
38
+ return instance
39
+
40
+
41
+ test_scenario_sp = [
42
+ ("off", "int", "set_sp_man", False),
43
+ ("off", "man", "set_sp_man", False),
44
+ ("op", "int", "set_sp_man", False),
45
+ ("op", "man", "set_sp_man", False),
46
+ ("aut", "int", "set_sp_man", False),
47
+ ("aut", "man", "set_sp_man", True),
48
+ ("off", "int", "set_sp_int", False),
49
+ ("off", "man", "set_sp_int", False),
50
+ ("op", "int", "set_sp_int", False),
51
+ ("op", "man", "set_sp_int", False),
52
+ ("aut", "int", "set_sp_int", True),
53
+ ("aut", "man", "set_sp_int", False),
54
+ ]
55
+
56
+
57
+ @pytest.mark.parametrize(
58
+ "op_mode, src_mode, set_command, changes_expected", test_scenario_sp
59
+ )
60
+ def test_set_sp(op_mode, src_mode, set_command, changes_expected):
61
+ for command in [-500, -10, 500, 1000, 10000]:
62
+ try:
63
+ pid_ctrl = init_pid_ctrl(init_op_mode=op_mode, init_src_mode=src_mode)
64
+ eval(f"pid_ctrl.{set_command}({command})")
65
+ print(
66
+ f"\nScenario: mode {op_mode} {src_mode}, {set_command}, changes expected: {changes_expected}"
67
+ )
68
+ if changes_expected:
69
+ if -10 <= command <= 1000:
70
+ assert pid_ctrl.get_sp() == command
71
+ else:
72
+ assert pid_ctrl.get_sp() == -10
73
+ else:
74
+ assert pid_ctrl.get_sp() == -10
75
+ finally:
76
+ pid_ctrl.op_mode._opmode_to_off()
77
+
78
+
79
+ test_scenario_mv = [
80
+ ("off", "int", "set_mv_man", False),
81
+ ("off", "man", "set_mv_man", False),
82
+ ("op", "int", "set_mv_man", True),
83
+ ("op", "man", "set_mv_man", True),
84
+ ]
85
+
86
+
87
+ @pytest.mark.parametrize(
88
+ "op_mode, src_mode, set_command, changes_expected", test_scenario_mv
89
+ )
90
+ def test_set_mv_man(op_mode, src_mode, set_command, changes_expected):
91
+ for command in [-500, -10, 500, 1000, 10000]:
92
+ try:
93
+ pid_ctrl = init_pid_ctrl(init_op_mode=op_mode, init_src_mode=src_mode)
94
+ eval(f"pid_ctrl.{set_command}({command})")
95
+ print(
96
+ f"Scenario: mode {op_mode} {src_mode}, {set_command}, changes expected: {changes_expected}"
97
+ )
98
+ if changes_expected:
99
+ if -10 <= command <= 1000:
100
+ assert pid_ctrl.get_mv() == command
101
+ else:
102
+ assert pid_ctrl.get_mv() == -10
103
+ else:
104
+ assert pid_ctrl.get_mv() == -10
105
+ finally:
106
+ pid_ctrl.op_mode._opmode_to_off()
107
+
108
+
109
+ # Assumption - after initialization sp_int = sp_int_min = -10
110
+ scenario_pid_cmds = [
111
+ # block 1: internal setpoint - ignored when outside bounds
112
+ ("aut", "int", "set_sp_int", -100, 0, "get_sp", -10),
113
+ ("aut", "int", "set_sp_int", -10, 0, "get_sp", -10),
114
+ ("aut", "int", "set_sp_int", 100, 0, "get_sp", 100),
115
+ ("aut", "int", "set_sp_int", 1000, 0, "get_sp", 1000),
116
+ ("aut", "int", "set_sp_int", 2000, 0, "get_sp", -10),
117
+ # block 2: internal setpoint - push to limits when inside bounds
118
+ ("aut", "int", "set_sp_int", -100, 1, "get_mv", 0),
119
+ ("aut", "int", "set_sp_int", -10, 1, "get_mv", -10),
120
+ ("aut", "int", "set_sp_int", 100, 1, "get_mv", 1000),
121
+ ("aut", "int", "set_sp_int", 1000, 1, "get_mv", 1000),
122
+ ("aut", "int", "set_sp_int", 2000, 1, "get_mv", 0),
123
+ # block 3: manual setpoint - set validation
124
+ ("aut", "man", "set_sp_man", -100, 0, "get_sp", -10),
125
+ ("aut", "man", "set_sp_man", -10, 0, "get_sp", -10),
126
+ ("aut", "man", "set_sp_man", 100, 0, "get_sp", 100),
127
+ ("aut", "man", "set_sp_man", 1000, 0, "get_sp", 1000),
128
+ ("aut", "man", "set_sp_man", 2000, 0, "get_sp", -10),
129
+ # block 4: manual setpoint - push to limits when inside bounds
130
+ ("aut", "man", "set_sp_man", -100, 1, "get_mv", 0),
131
+ ("aut", "man", "set_sp_man", -10, 1, "get_mv", -10),
132
+ ("aut", "man", "set_sp_man", 100, 1, "get_mv", 1000),
133
+ ("aut", "man", "set_sp_man", 1000, 1, "get_mv", 1000),
134
+ ("aut", "man", "set_sp_man", 2000, 1, "get_mv", 0),
135
+ ]
136
+
137
+
138
+ @pytest.mark.parametrize(
139
+ "op_mode, src_mode, set_cmd, value, delay, get_cmd, expected", scenario_pid_cmds
140
+ )
141
+ def test_pid_cmds(op_mode, src_mode, set_cmd, value, delay, get_cmd, expected):
142
+ try:
143
+ pid_ctrl = init_pid_ctrl(init_op_mode=op_mode, init_src_mode=src_mode)
144
+ eval(f"pid_ctrl.{set_cmd}({value})")
145
+ sleep(delay)
146
+ assert pid_ctrl[PIDCtrl.SP_INT_MIN] == -10
147
+ assert pid_ctrl[PIDCtrl.SP_INT_MAX] == 1000
148
+ retval = eval(f"pid_ctrl.{get_cmd}()")
149
+ assert retval == expected
150
+ finally:
151
+ pid_ctrl.op_mode._opmode_to_off()
152
+
153
+
154
+ if __name__ == "__main__":
155
+ # This allows running the tests directly if needed
156
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,4 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"