PyAutomationIO 0.0.0__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 (138) hide show
  1. automation/__init__.py +46 -0
  2. automation/alarms/__init__.py +563 -0
  3. automation/alarms/states.py +192 -0
  4. automation/alarms/trigger.py +64 -0
  5. automation/buffer.py +132 -0
  6. automation/core.py +1775 -0
  7. automation/dbmodels/__init__.py +23 -0
  8. automation/dbmodels/alarms.py +524 -0
  9. automation/dbmodels/core.py +86 -0
  10. automation/dbmodels/events.py +153 -0
  11. automation/dbmodels/logs.py +155 -0
  12. automation/dbmodels/machines.py +181 -0
  13. automation/dbmodels/opcua.py +81 -0
  14. automation/dbmodels/opcua_server.py +174 -0
  15. automation/dbmodels/tags.py +921 -0
  16. automation/dbmodels/users.py +259 -0
  17. automation/extensions/__init__.py +15 -0
  18. automation/extensions/api.py +149 -0
  19. automation/extensions/cors.py +18 -0
  20. automation/filter/__init__.py +19 -0
  21. automation/iad/__init__.py +3 -0
  22. automation/iad/frozen_data.py +54 -0
  23. automation/iad/out_of_range.py +51 -0
  24. automation/iad/outliers.py +51 -0
  25. automation/logger/__init__.py +0 -0
  26. automation/logger/alarms.py +426 -0
  27. automation/logger/core.py +265 -0
  28. automation/logger/datalogger.py +646 -0
  29. automation/logger/events.py +194 -0
  30. automation/logger/logdict.py +53 -0
  31. automation/logger/logs.py +203 -0
  32. automation/logger/machines.py +248 -0
  33. automation/logger/opcua_server.py +130 -0
  34. automation/logger/users.py +96 -0
  35. automation/managers/__init__.py +4 -0
  36. automation/managers/alarms.py +455 -0
  37. automation/managers/db.py +328 -0
  38. automation/managers/opcua_client.py +186 -0
  39. automation/managers/state_machine.py +183 -0
  40. automation/models.py +174 -0
  41. automation/modules/__init__.py +14 -0
  42. automation/modules/alarms/__init__.py +0 -0
  43. automation/modules/alarms/resources/__init__.py +10 -0
  44. automation/modules/alarms/resources/alarms.py +280 -0
  45. automation/modules/alarms/resources/summary.py +79 -0
  46. automation/modules/events/__init__.py +0 -0
  47. automation/modules/events/resources/__init__.py +10 -0
  48. automation/modules/events/resources/events.py +83 -0
  49. automation/modules/events/resources/logs.py +109 -0
  50. automation/modules/tags/__init__.py +0 -0
  51. automation/modules/tags/resources/__init__.py +8 -0
  52. automation/modules/tags/resources/tags.py +201 -0
  53. automation/modules/users/__init__.py +2 -0
  54. automation/modules/users/resources/__init__.py +10 -0
  55. automation/modules/users/resources/models/__init__.py +2 -0
  56. automation/modules/users/resources/models/roles.py +5 -0
  57. automation/modules/users/resources/models/users.py +14 -0
  58. automation/modules/users/resources/roles.py +38 -0
  59. automation/modules/users/resources/users.py +113 -0
  60. automation/modules/users/roles.py +121 -0
  61. automation/modules/users/users.py +335 -0
  62. automation/opcua/__init__.py +1 -0
  63. automation/opcua/models.py +541 -0
  64. automation/opcua/subscription.py +259 -0
  65. automation/pages/__init__.py +0 -0
  66. automation/pages/alarms.py +34 -0
  67. automation/pages/alarms_history.py +21 -0
  68. automation/pages/assets/styles.css +7 -0
  69. automation/pages/callbacks/__init__.py +28 -0
  70. automation/pages/callbacks/alarms.py +218 -0
  71. automation/pages/callbacks/alarms_summary.py +20 -0
  72. automation/pages/callbacks/db.py +222 -0
  73. automation/pages/callbacks/filter.py +238 -0
  74. automation/pages/callbacks/machines.py +29 -0
  75. automation/pages/callbacks/machines_detailed.py +581 -0
  76. automation/pages/callbacks/opcua.py +266 -0
  77. automation/pages/callbacks/opcua_server.py +244 -0
  78. automation/pages/callbacks/tags.py +495 -0
  79. automation/pages/callbacks/trends.py +119 -0
  80. automation/pages/communications.py +129 -0
  81. automation/pages/components/__init__.py +123 -0
  82. automation/pages/components/alarms.py +151 -0
  83. automation/pages/components/alarms_summary.py +45 -0
  84. automation/pages/components/database.py +128 -0
  85. automation/pages/components/gaussian_filter.py +69 -0
  86. automation/pages/components/machines.py +396 -0
  87. automation/pages/components/opcua.py +384 -0
  88. automation/pages/components/opcua_server.py +53 -0
  89. automation/pages/components/tags.py +253 -0
  90. automation/pages/components/trends.py +66 -0
  91. automation/pages/database.py +26 -0
  92. automation/pages/filter.py +55 -0
  93. automation/pages/machines.py +20 -0
  94. automation/pages/machines_detailed.py +41 -0
  95. automation/pages/main.py +63 -0
  96. automation/pages/opcua_server.py +28 -0
  97. automation/pages/tags.py +40 -0
  98. automation/pages/trends.py +35 -0
  99. automation/singleton.py +30 -0
  100. automation/state_machine.py +1672 -0
  101. automation/tags/__init__.py +2 -0
  102. automation/tags/cvt.py +1198 -0
  103. automation/tags/filter.py +55 -0
  104. automation/tags/tag.py +418 -0
  105. automation/tests/__init__.py +10 -0
  106. automation/tests/test_alarms.py +110 -0
  107. automation/tests/test_core.py +257 -0
  108. automation/tests/test_unit.py +21 -0
  109. automation/tests/test_user.py +155 -0
  110. automation/utils/__init__.py +164 -0
  111. automation/utils/decorators.py +222 -0
  112. automation/utils/npw.py +294 -0
  113. automation/utils/observer.py +21 -0
  114. automation/utils/units.py +118 -0
  115. automation/variables/__init__.py +55 -0
  116. automation/variables/adimentional.py +30 -0
  117. automation/variables/current.py +71 -0
  118. automation/variables/density.py +115 -0
  119. automation/variables/eng_time.py +68 -0
  120. automation/variables/force.py +90 -0
  121. automation/variables/length.py +104 -0
  122. automation/variables/mass.py +80 -0
  123. automation/variables/mass_flow.py +101 -0
  124. automation/variables/percentage.py +30 -0
  125. automation/variables/power.py +113 -0
  126. automation/variables/pressure.py +93 -0
  127. automation/variables/temperature.py +168 -0
  128. automation/variables/volume.py +70 -0
  129. automation/variables/volumetric_flow.py +100 -0
  130. automation/workers/__init__.py +2 -0
  131. automation/workers/logger.py +164 -0
  132. automation/workers/state_machine.py +207 -0
  133. automation/workers/worker.py +36 -0
  134. pyautomationio-0.0.0.dist-info/METADATA +198 -0
  135. pyautomationio-0.0.0.dist-info/RECORD +138 -0
  136. pyautomationio-0.0.0.dist-info/WHEEL +5 -0
  137. pyautomationio-0.0.0.dist-info/licenses/LICENSE +21 -0
  138. pyautomationio-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,55 @@
1
+ from ..utils.decorators import decorator
2
+ import numpy as np
3
+
4
+ class KalmanFilter:
5
+ def __init__(
6
+ self,
7
+ x,
8
+ A:np.array=np.array([[1]]),
9
+ B:np.array=np.array([[0]]),
10
+ H:np.array=np.array([[1]]),
11
+ P:np.array=np.array([[1]]),
12
+ Q:np.array=np.array([[1e-5]]),
13
+ R:np.array=np.array([[0.5]])
14
+ ):
15
+ self.A = A
16
+ self.B = B
17
+ self.H = H
18
+ self.Q = Q
19
+ self.R = R
20
+ self.P = P
21
+ self.x = x
22
+ self.previous_innov = None # Para guardar la innovación anterior
23
+
24
+ def predict(self, u=0):
25
+ self.x = np.dot(self.A, self.x) + np.dot(self.B, u)
26
+ self.P = np.dot(np.dot(self.A, self.P), self.A.T) + self.Q
27
+
28
+ def update(self, z, threshold:float=100, r_value:float=0.5):
29
+
30
+ innov = z - np.dot(self.H, self.x) # Innovación
31
+ if self.previous_innov is not None:
32
+ innov_var = np.std([self.previous_innov, innov]) # Varianza de las innovaciones
33
+ self.R = 0.0 if innov_var > threshold else r_value # Ajustar R
34
+ K = np.dot(np.dot(self.P, self.H.T), np.linalg.inv(np.dot(np.dot(self.H, self.P), self.H.T) + self.R))
35
+ self.x = self.x + np.dot(K, innov)
36
+ self.P = self.P - np.dot(np.dot(K, self.H), self.P)
37
+ self.previous_innov = innov
38
+
39
+
40
+ class GaussianFilter:
41
+
42
+ def __init__(self):
43
+
44
+ self.kf = None
45
+
46
+ def __call__(self, value:float, threshold:float=100, r_value:float=0.5):
47
+
48
+ if self.kf is None:
49
+
50
+ self.kf = KalmanFilter(value)
51
+
52
+ self.kf.predict()
53
+ self.kf.update(value, threshold=threshold, r_value=r_value)
54
+ filtered_value = self.kf.x[0][0]
55
+ return filtered_value
automation/tags/tag.py ADDED
@@ -0,0 +1,418 @@
1
+ import secrets, logging
2
+ from datetime import datetime
3
+ from ..utils import Observer
4
+ from ..utils.decorators import logging_error_handler
5
+ from ..buffer import Buffer
6
+ from ..variables import (
7
+ Temperature,
8
+ Length,
9
+ Current,
10
+ Time,
11
+ Pressure,
12
+ Mass,
13
+ Force,
14
+ Power,
15
+ VolumetricFlow,
16
+ MassFlow,
17
+ Density,
18
+ Percentage,
19
+ Adimentional,
20
+ Volume
21
+ )
22
+ from .filter import GaussianFilter
23
+
24
+ DATETIME_FORMAT = "%m/%d/%Y, %H:%M:%S.%f"
25
+
26
+ class Tag:
27
+
28
+ def __init__(
29
+ self,
30
+ name:str,
31
+ unit:str,
32
+ variable:str,
33
+ data_type:str,
34
+ display_name:str=None,
35
+ display_unit:str=None,
36
+ description:str="",
37
+ opcua_address:str=None,
38
+ node_namespace:str=None,
39
+ scan_time:int=None,
40
+ dead_band:float=None,
41
+ timestamp:datetime=None,
42
+ process_filter:bool=False,
43
+ gaussian_filter:bool=False,
44
+ gaussian_filter_threshold:float=1.0,
45
+ gaussian_filter_r_value:float=0.0,
46
+ outlier_detection:bool=False,
47
+ out_of_range_detection:bool=False,
48
+ frozen_data_detection:bool=False,
49
+ manufacturer:str="",
50
+ segment:str="",
51
+ id:str=None
52
+ ):
53
+ self.id = secrets.token_hex(4)
54
+ if id:
55
+ self.id = id
56
+ self.name = name
57
+ self.data_type = data_type
58
+ self.description = description
59
+ self.variable = variable
60
+ self.display_name = name
61
+ if display_name:
62
+ self.display_name = display_name
63
+ self.display_unit = unit
64
+ if display_unit:
65
+ self.display_unit = display_unit
66
+ self.unit=unit
67
+ if variable.lower()=="temperature":
68
+ self.value = Temperature(value=0.0, unit=self.unit)
69
+ elif variable.lower()=="length":
70
+ self.value = Length(value=0.0, unit=self.unit)
71
+ elif variable.lower()=="time":
72
+ self.value = Time(value=0.0, unit=self.unit)
73
+ elif variable.lower()=="pressure":
74
+ self.value = Pressure(value=0.0, unit=self.unit)
75
+ elif variable.lower()=="mass":
76
+ self.value = Mass(value=0.0, unit=self.unit)
77
+ elif variable.lower()=="force":
78
+ self.value = Force(value=0.0, unit=self.unit)
79
+ elif variable.lower()=="power":
80
+ self.value = Power(value=0.0, unit=self.unit)
81
+ elif variable.lower()=="current":
82
+ self.value = Current(value=0.0, unit=self.unit)
83
+ elif variable.lower()=="volumetricflow":
84
+ self.value = VolumetricFlow(value=0.0, unit=self.unit)
85
+ elif variable.lower()=="massflow":
86
+ self.value = MassFlow(value=0.0, unit=self.unit)
87
+ elif variable.lower()=="density":
88
+ self.value = Density(value=0.0, unit=self.unit)
89
+ elif variable.lower()=="percentage":
90
+ self.value = Percentage(value=0.0, unit=self.unit)
91
+ elif variable.lower()=="adimentional":
92
+ self.value = Adimentional(value=0.0, unit=self.unit)
93
+ elif variable.lower()=="volume":
94
+ self.value = Volume(value=0.0, unit=self.unit)
95
+
96
+ self.values = Buffer()
97
+ self.timestamps = Buffer()
98
+ self.opcua_address = opcua_address
99
+ self.node_namespace = node_namespace
100
+ self.scan_time = scan_time
101
+ self.dead_band = dead_band
102
+ self.timestamp = timestamp
103
+ self.process_filter = process_filter
104
+ self.gaussian_filter = gaussian_filter
105
+ self.gaussian_filter_threshold = gaussian_filter_threshold
106
+ self.gaussian_filter_r_value = gaussian_filter_r_value
107
+ self.outlier_detection = outlier_detection
108
+ self.out_of_range_detection = out_of_range_detection
109
+ self.frozen_data_detection = frozen_data_detection
110
+ self.manufacturer = manufacturer
111
+ self.segment = segment
112
+ self.filter = GaussianFilter()
113
+ self._observers = set()
114
+
115
+ def set_name(self, name:str):
116
+ r"""
117
+ Documentation here
118
+ """
119
+ self.name = name
120
+
121
+ @logging_error_handler
122
+ def set_value(self, value:float|str|int|bool, timestamp:datetime=None):
123
+ r"""
124
+ Documentation here
125
+ """
126
+ if self.dead_band and isinstance(value, (int, float)):
127
+ try:
128
+ current_value = self.value.value
129
+ if abs(value - current_value) < self.dead_band:
130
+
131
+ return
132
+ except Exception as e:
133
+ logging.error(f"Error in deadband logic: {e}")
134
+
135
+ if not timestamp:
136
+ timestamp = datetime.now()
137
+ self.value.set_value(value=value, unit=self.display_unit)
138
+ self.timestamp = timestamp
139
+ self.values(self.get_value())
140
+ self.timestamps(timestamp.strftime(DATETIME_FORMAT))
141
+ self.notify()
142
+
143
+ def set_display_name(self, name:str):
144
+ r"""
145
+ Documentation here
146
+ """
147
+
148
+ self.display_name = name
149
+
150
+ def set_data_type(self, data_type:str):
151
+ r"""
152
+ Documentation here
153
+ """
154
+ self.data_type = data_type
155
+
156
+ def set_variable(self, variable:str):
157
+ r"""
158
+ Documentation here
159
+ """
160
+
161
+ self.variable = variable
162
+ if variable.lower()=="temperature":
163
+ self.value = Temperature(value=0.0, unit=self.unit)
164
+ elif variable.lower()=="length":
165
+ self.value = Length(value=0.0, unit=self.unit)
166
+ elif variable.lower()=="time":
167
+ self.value = Time(value=0.0, unit=self.unit)
168
+ elif variable.lower()=="pressure":
169
+ self.value = Pressure(value=0.0, unit=self.unit)
170
+ elif variable.lower()=="mass":
171
+ self.value = Mass(value=0.0, unit=self.unit)
172
+ elif variable.lower()=="force":
173
+ self.value = Force(value=0.0, unit=self.unit)
174
+ elif variable.lower()=="power":
175
+ self.value = Power(value=0.0, unit=self.unit)
176
+ elif variable.lower()=="current":
177
+ self.value = Current(value=0.0, unit=self.unit)
178
+ elif variable.lower()=="volumetricflow":
179
+ self.value = VolumetricFlow(value=0.0, unit=self.unit)
180
+ elif variable.lower()=="massflow":
181
+ self.value = MassFlow(value=0.0, unit=self.unit)
182
+ elif variable.lower()=="density":
183
+ self.value = Density(value=0.0, unit=self.unit)
184
+ elif variable.lower()=="percentage":
185
+ self.value = Percentage(value=0.0, unit=self.unit)
186
+ elif variable.lower()=="adimentional":
187
+ self.value = Adimentional(value=0.0, unit=self.unit)
188
+ elif variable.lower()=="volume":
189
+ self.value = Volume(value=0.0, unit=self.unit)
190
+
191
+ def set_opcua_address(self, opcua_address:str):
192
+ r"""
193
+ Documentation here
194
+ """
195
+ self.opcua_address = opcua_address
196
+
197
+ def set_unit(self, unit:str):
198
+ r"""
199
+ Documentation here
200
+ """
201
+ self.unit = unit
202
+
203
+ def set_display_unit(self, unit:str):
204
+ r"""
205
+ Documentation here
206
+ """
207
+ self.display_unit = unit
208
+
209
+ def set_node_namespace(self, node_namespace:str):
210
+ r"""
211
+ Documentation here
212
+ """
213
+ self.node_namespace = node_namespace
214
+
215
+ def get_value(self):
216
+ r"""
217
+ Documentation here
218
+ """
219
+ return round(self.value.convert(to_unit=self.display_unit), 3)
220
+
221
+ def set_description(self, description:str):
222
+ r"""
223
+ Documentation here
224
+ """
225
+ self.description = description
226
+
227
+ def set_scan_time(self, scan_time:int):
228
+ r"""
229
+ Documentation here
230
+ """
231
+ self.scan_time = scan_time
232
+
233
+ def set_dead_band(self, dead_band:float):
234
+ r"""
235
+ Documentation here
236
+ """
237
+ self.dead_band = dead_band
238
+
239
+ def get_timestamp(self):
240
+ r"""
241
+ Documentation here
242
+ """
243
+ return self.timestamp
244
+
245
+ def get_scan_time(self):
246
+ r"""
247
+ Documentation here
248
+ """
249
+ return self.scan_time
250
+
251
+ def get_dead_band(self):
252
+ r"""
253
+ Documentation here
254
+ """
255
+ return self.dead_band
256
+
257
+ def get_data_type(self):
258
+ r"""
259
+ Documentation here
260
+ """
261
+ return self.data_type
262
+
263
+ def get_unit(self):
264
+ r"""
265
+ Documentation here
266
+ """
267
+ return self.unit
268
+
269
+ def get_display_unit(self):
270
+ r"""
271
+ Documentation here
272
+ """
273
+ return self.display_unit
274
+
275
+ def get_description(self):
276
+ r"""
277
+ Documentation here
278
+ """
279
+ return self.description
280
+
281
+ def get_display_name(self)->str:
282
+ r"""
283
+ Documentation here
284
+ """
285
+
286
+ return self.display_name
287
+
288
+ def get_variable(self)->str:
289
+ r"""
290
+ Documentation here
291
+ """
292
+
293
+ return self.variable
294
+
295
+ def get_id(self)->str:
296
+ r"""
297
+ Documentation here
298
+ """
299
+
300
+ return self.id
301
+
302
+ def get_name(self)->str:
303
+ r"""
304
+ Documentation here
305
+ """
306
+
307
+ return self.name
308
+
309
+ def get_opcua_address(self):
310
+ r"""
311
+ Documentation here
312
+ """
313
+ return self.opcua_address
314
+
315
+ def get_node_namespace(self):
316
+ r"""
317
+ Documentation here
318
+ """
319
+ return self.node_namespace
320
+
321
+ def attach(self, observer:Observer):
322
+ r"""
323
+ Documentation here
324
+ """
325
+ observer._subject = self
326
+ self._observers.add(observer)
327
+
328
+ def detach(self, observer:Observer):
329
+ r"""
330
+ Documentation here
331
+ """
332
+ observer._subject = None
333
+ self._observers.discard(observer)
334
+
335
+ def notify(self):
336
+ r"""
337
+ Documentation here
338
+ """
339
+ for observer in self._observers:
340
+
341
+ observer.update()
342
+
343
+ def serialize(self):
344
+
345
+ timestamp = self.get_timestamp()
346
+ if timestamp:
347
+
348
+ timestamp = timestamp.strftime(DATETIME_FORMAT)
349
+
350
+ return {
351
+ "id": self.get_id(),
352
+ "value": self.get_value(),
353
+ "timestamp": timestamp,
354
+ "values": list(self.values),
355
+ "timestamps": list(self.timestamps),
356
+ "name": self.name,
357
+ "unit": self.get_unit(),
358
+ "display_unit": self.get_display_unit(),
359
+ "data_type": self.get_data_type(),
360
+ "variable": self.get_variable(),
361
+ "description": self.get_description(),
362
+ "display_name": self.get_display_name(),
363
+ "opcua_address": self.get_opcua_address(),
364
+ "node_namespace": self.get_node_namespace(),
365
+ "scan_time": self.get_scan_time(),
366
+ "dead_band": self.get_dead_band(),
367
+ "segment": self.segment,
368
+ "manufacturer": self.manufacturer,
369
+ "process_filter": self.process_filter,
370
+ "gaussian_filter": self.gaussian_filter,
371
+ "out_of_range_detection": self.out_of_range_detection,
372
+ "frozen_data_detection": self.frozen_data_detection,
373
+ "outlier_detection": self.outlier_detection
374
+ }
375
+
376
+
377
+ class TagObserver(Observer):
378
+ """
379
+ Implement the Observer updating interface to keep its state
380
+ consistent with the subject's.
381
+ Store state that should stay consistent with the subject's.
382
+ """
383
+ def __init__(self, tag_queue):
384
+
385
+ super(TagObserver, self).__init__()
386
+ self._tag_queue = tag_queue
387
+
388
+ def update(self):
389
+
390
+ """
391
+ This methods inserts the changing Tag into a
392
+ Producer-Consumer Queue Design Pattern
393
+ """
394
+ result = dict()
395
+ result["tag"] = self._subject.name
396
+ result["value"] = self._subject.value.convert(self._subject.get_display_unit())
397
+ result["timestamp"] = self._subject.timestamp
398
+ self._tag_queue.put(result, block=False)
399
+
400
+
401
+ class MachineObserver(Observer):
402
+ """
403
+ Implement the Observer updating interface to keep its state
404
+ consistent with the subject's.
405
+ Store state that should stay consistent with the subject's.
406
+ """
407
+ def __init__(self, machine):
408
+
409
+ super(MachineObserver, self).__init__()
410
+ self.machine = machine
411
+
412
+ def update(self):
413
+
414
+ """
415
+ This methods inserts the changing Tag into a
416
+ Producer-Consumer Queue Design Pattern
417
+ """
418
+ self.machine.notify(tag=self._subject.name, value=self._subject.value, timestamp=self._subject.timestamp)
@@ -0,0 +1,10 @@
1
+
2
+ def assert_dict_contains_subset(subset, superset, msg=None):
3
+ """
4
+ Custom assertion to check if `subset` is a subset of `superset`.
5
+ """
6
+ missing_keys = {key for key in subset if key not in superset}
7
+ assert not missing_keys, f"{msg or 'Dictionary subset check failed'}: Missing keys {missing_keys}"
8
+
9
+ for key, value in subset.items():
10
+ assert superset[key] == value, f"{msg or 'Dictionary subset check failed'}: Value mismatch for key '{key}'"
@@ -0,0 +1,110 @@
1
+ import unittest
2
+ from automation.alarms import Alarm
3
+ from automation.tags.tag import Tag
4
+ from automation.tags.cvt import CVTEngine
5
+ from automation.models import StringType, FloatType
6
+
7
+ cvt = CVTEngine()
8
+
9
+ class TestAlarms(unittest.TestCase):
10
+
11
+ def setUp(self) -> None:
12
+
13
+ return super().setUp()
14
+
15
+ def tearDown(self) -> None:
16
+
17
+ return super().tearDown()
18
+
19
+ def test_create_alarm(self):
20
+ r"""
21
+ Documentation here
22
+ """
23
+ name = "alarm1"
24
+ cvt.set_tag(
25
+ name="tag1",
26
+ variable="Temperature",
27
+ unit="C",
28
+ data_type="FLOAT",
29
+ description="tag1"
30
+ )
31
+ tag = cvt.get_tag_by_name(name="tag1")
32
+ alarm = Alarm(
33
+ name=name,
34
+ tag=tag,
35
+ alarm_type=StringType("HIGH"),
36
+ alarm_setpoint=FloatType(50.0)
37
+ )
38
+
39
+ self.assertEqual(alarm.state.state.lower(), "normal")
40
+
41
+ def test_alarm_state_attribute(self):
42
+ r"""
43
+ Documentation here
44
+ """
45
+ name = "alarm1"
46
+ cvt.set_tag(
47
+ name="tag2",
48
+ variable="Temperature",
49
+ unit="C",
50
+ data_type="FLOAT",
51
+ description="tag2"
52
+ )
53
+ tag = cvt.get_tag_by_name(name="tag2")
54
+ alarm = Alarm(
55
+ name=name,
56
+ tag=tag,
57
+ alarm_type=StringType("HIGH"),
58
+ alarm_setpoint=FloatType(50.0)
59
+ )
60
+
61
+ with self.subTest("Test alarm Unack status"):
62
+ tag.set_value(value=55)
63
+ self.assertEqual(alarm.state.state.lower(), "unacknowledged")
64
+
65
+ with self.subTest("Test alarm Ack status"):
66
+ alarm.acknowledge()
67
+ self.assertEqual(alarm.state.state.lower(), "acknowledged")
68
+
69
+ with self.subTest("Test alarm Normal status"):
70
+ tag.set_value(value=45)
71
+ self.assertEqual(alarm.state.state.lower(), "normal")
72
+
73
+ def test_alarm_state_machine(self):
74
+ r"""
75
+ Documentation here
76
+ """
77
+ name = "alarm1"
78
+ cvt.set_tag(
79
+ name="tag3",
80
+ variable="Temperature",
81
+ unit="C",
82
+ data_type="FLOAT",
83
+ description="tag3"
84
+ )
85
+ tag = cvt.get_tag_by_name(name="tag3")
86
+ alarm = Alarm(
87
+ name=name,
88
+ tag=tag,
89
+ alarm_type=StringType("HIGH"),
90
+ alarm_setpoint=FloatType(50.0)
91
+ )
92
+
93
+ tag.set_value(value=55)
94
+ with self.subTest("Test alarm Unack status"):
95
+ self.assertEqual(alarm.current_state.value.lower(), "unack_alarm")
96
+
97
+ with self.subTest("Test alarm Ack status"):
98
+ alarm.acknowledge()
99
+ self.assertEqual(alarm.current_state.value.lower(), "ack_alarm")
100
+
101
+ with self.subTest("Test alarm Normal status"):
102
+ tag.set_value(value=45)
103
+ self.assertEqual(alarm.current_state.value.lower(), "normal")
104
+
105
+
106
+
107
+
108
+
109
+
110
+