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.
- automation/__init__.py +46 -0
- automation/alarms/__init__.py +563 -0
- automation/alarms/states.py +192 -0
- automation/alarms/trigger.py +64 -0
- automation/buffer.py +132 -0
- automation/core.py +1775 -0
- automation/dbmodels/__init__.py +23 -0
- automation/dbmodels/alarms.py +524 -0
- automation/dbmodels/core.py +86 -0
- automation/dbmodels/events.py +153 -0
- automation/dbmodels/logs.py +155 -0
- automation/dbmodels/machines.py +181 -0
- automation/dbmodels/opcua.py +81 -0
- automation/dbmodels/opcua_server.py +174 -0
- automation/dbmodels/tags.py +921 -0
- automation/dbmodels/users.py +259 -0
- automation/extensions/__init__.py +15 -0
- automation/extensions/api.py +149 -0
- automation/extensions/cors.py +18 -0
- automation/filter/__init__.py +19 -0
- automation/iad/__init__.py +3 -0
- automation/iad/frozen_data.py +54 -0
- automation/iad/out_of_range.py +51 -0
- automation/iad/outliers.py +51 -0
- automation/logger/__init__.py +0 -0
- automation/logger/alarms.py +426 -0
- automation/logger/core.py +265 -0
- automation/logger/datalogger.py +646 -0
- automation/logger/events.py +194 -0
- automation/logger/logdict.py +53 -0
- automation/logger/logs.py +203 -0
- automation/logger/machines.py +248 -0
- automation/logger/opcua_server.py +130 -0
- automation/logger/users.py +96 -0
- automation/managers/__init__.py +4 -0
- automation/managers/alarms.py +455 -0
- automation/managers/db.py +328 -0
- automation/managers/opcua_client.py +186 -0
- automation/managers/state_machine.py +183 -0
- automation/models.py +174 -0
- automation/modules/__init__.py +14 -0
- automation/modules/alarms/__init__.py +0 -0
- automation/modules/alarms/resources/__init__.py +10 -0
- automation/modules/alarms/resources/alarms.py +280 -0
- automation/modules/alarms/resources/summary.py +79 -0
- automation/modules/events/__init__.py +0 -0
- automation/modules/events/resources/__init__.py +10 -0
- automation/modules/events/resources/events.py +83 -0
- automation/modules/events/resources/logs.py +109 -0
- automation/modules/tags/__init__.py +0 -0
- automation/modules/tags/resources/__init__.py +8 -0
- automation/modules/tags/resources/tags.py +201 -0
- automation/modules/users/__init__.py +2 -0
- automation/modules/users/resources/__init__.py +10 -0
- automation/modules/users/resources/models/__init__.py +2 -0
- automation/modules/users/resources/models/roles.py +5 -0
- automation/modules/users/resources/models/users.py +14 -0
- automation/modules/users/resources/roles.py +38 -0
- automation/modules/users/resources/users.py +113 -0
- automation/modules/users/roles.py +121 -0
- automation/modules/users/users.py +335 -0
- automation/opcua/__init__.py +1 -0
- automation/opcua/models.py +541 -0
- automation/opcua/subscription.py +259 -0
- automation/pages/__init__.py +0 -0
- automation/pages/alarms.py +34 -0
- automation/pages/alarms_history.py +21 -0
- automation/pages/assets/styles.css +7 -0
- automation/pages/callbacks/__init__.py +28 -0
- automation/pages/callbacks/alarms.py +218 -0
- automation/pages/callbacks/alarms_summary.py +20 -0
- automation/pages/callbacks/db.py +222 -0
- automation/pages/callbacks/filter.py +238 -0
- automation/pages/callbacks/machines.py +29 -0
- automation/pages/callbacks/machines_detailed.py +581 -0
- automation/pages/callbacks/opcua.py +266 -0
- automation/pages/callbacks/opcua_server.py +244 -0
- automation/pages/callbacks/tags.py +495 -0
- automation/pages/callbacks/trends.py +119 -0
- automation/pages/communications.py +129 -0
- automation/pages/components/__init__.py +123 -0
- automation/pages/components/alarms.py +151 -0
- automation/pages/components/alarms_summary.py +45 -0
- automation/pages/components/database.py +128 -0
- automation/pages/components/gaussian_filter.py +69 -0
- automation/pages/components/machines.py +396 -0
- automation/pages/components/opcua.py +384 -0
- automation/pages/components/opcua_server.py +53 -0
- automation/pages/components/tags.py +253 -0
- automation/pages/components/trends.py +66 -0
- automation/pages/database.py +26 -0
- automation/pages/filter.py +55 -0
- automation/pages/machines.py +20 -0
- automation/pages/machines_detailed.py +41 -0
- automation/pages/main.py +63 -0
- automation/pages/opcua_server.py +28 -0
- automation/pages/tags.py +40 -0
- automation/pages/trends.py +35 -0
- automation/singleton.py +30 -0
- automation/state_machine.py +1672 -0
- automation/tags/__init__.py +2 -0
- automation/tags/cvt.py +1198 -0
- automation/tags/filter.py +55 -0
- automation/tags/tag.py +418 -0
- automation/tests/__init__.py +10 -0
- automation/tests/test_alarms.py +110 -0
- automation/tests/test_core.py +257 -0
- automation/tests/test_unit.py +21 -0
- automation/tests/test_user.py +155 -0
- automation/utils/__init__.py +164 -0
- automation/utils/decorators.py +222 -0
- automation/utils/npw.py +294 -0
- automation/utils/observer.py +21 -0
- automation/utils/units.py +118 -0
- automation/variables/__init__.py +55 -0
- automation/variables/adimentional.py +30 -0
- automation/variables/current.py +71 -0
- automation/variables/density.py +115 -0
- automation/variables/eng_time.py +68 -0
- automation/variables/force.py +90 -0
- automation/variables/length.py +104 -0
- automation/variables/mass.py +80 -0
- automation/variables/mass_flow.py +101 -0
- automation/variables/percentage.py +30 -0
- automation/variables/power.py +113 -0
- automation/variables/pressure.py +93 -0
- automation/variables/temperature.py +168 -0
- automation/variables/volume.py +70 -0
- automation/variables/volumetric_flow.py +100 -0
- automation/workers/__init__.py +2 -0
- automation/workers/logger.py +164 -0
- automation/workers/state_machine.py +207 -0
- automation/workers/worker.py +36 -0
- pyautomationio-0.0.0.dist-info/METADATA +198 -0
- pyautomationio-0.0.0.dist-info/RECORD +138 -0
- pyautomationio-0.0.0.dist-info/WHEEL +5 -0
- pyautomationio-0.0.0.dist-info/licenses/LICENSE +21 -0
- pyautomationio-0.0.0.dist-info/top_level.txt +1 -0
automation/tags/cvt.py
ADDED
|
@@ -0,0 +1,1198 @@
|
|
|
1
|
+
import threading, copy, logging
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from ..singleton import Singleton
|
|
4
|
+
from ..models import FloatType, StringType, IntegerType, BooleanType
|
|
5
|
+
from ..modules.users.users import User
|
|
6
|
+
from ..modules.users.users import User
|
|
7
|
+
from ..utils.decorators import set_event, logging_error_handler
|
|
8
|
+
from ..filter import filter
|
|
9
|
+
# from ..iad import iad_outlier, iad_frozen_data, iad_out_of_range
|
|
10
|
+
from .tag import Tag
|
|
11
|
+
from flask_socketio import SocketIO
|
|
12
|
+
|
|
13
|
+
class CVT:
|
|
14
|
+
"""
|
|
15
|
+
Current Value Table (CVT) class for a tag-based repository.
|
|
16
|
+
|
|
17
|
+
This class is designed to hold in-memory tag-based values and manage observers for the required tags. It is intended to be used exclusively by PyAutomation and should not be used for other purposes.
|
|
18
|
+
|
|
19
|
+
**Usage Example**:
|
|
20
|
+
|
|
21
|
+
.. code-block:: python
|
|
22
|
+
|
|
23
|
+
>>> from automation.tags import CVT
|
|
24
|
+
>>> _cvt = CVT()
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
|
|
30
|
+
self._tags = dict()
|
|
31
|
+
self.data_types = ["float", "int", "bool", "str"]
|
|
32
|
+
self.sio:SocketIO|None = None
|
|
33
|
+
|
|
34
|
+
def set_socketio(self, sio:SocketIO):
|
|
35
|
+
r"""
|
|
36
|
+
Documentation here
|
|
37
|
+
"""
|
|
38
|
+
self.sio:SocketIO = sio
|
|
39
|
+
|
|
40
|
+
@set_event(message=f"Created", classification="Tag", priority=1, criticity=1)
|
|
41
|
+
def set_tag(
|
|
42
|
+
self,
|
|
43
|
+
name:str,
|
|
44
|
+
unit:str,
|
|
45
|
+
data_type:str,
|
|
46
|
+
description:str,
|
|
47
|
+
variable:str,
|
|
48
|
+
display_name:str="",
|
|
49
|
+
display_unit:str="",
|
|
50
|
+
opcua_address:str="",
|
|
51
|
+
node_namespace:str="",
|
|
52
|
+
scan_time:int=None,
|
|
53
|
+
dead_band:float=None,
|
|
54
|
+
process_filter:bool=False,
|
|
55
|
+
gaussian_filter:bool=False,
|
|
56
|
+
gaussian_filter_threshold:float=1.0,
|
|
57
|
+
gaussian_filter_r_value:float=0.0,
|
|
58
|
+
outlier_detection:bool=False,
|
|
59
|
+
out_of_range_detection:bool=False,
|
|
60
|
+
frozen_data_detection:bool=False,
|
|
61
|
+
manufacturer:str="",
|
|
62
|
+
segment:str="",
|
|
63
|
+
id:str=None,
|
|
64
|
+
user:User=None
|
|
65
|
+
)->tuple[Tag, str]:
|
|
66
|
+
"""Initialize a new Tag object in the _tags dictionary.
|
|
67
|
+
|
|
68
|
+
# Parameters
|
|
69
|
+
name (str):
|
|
70
|
+
Tag name.
|
|
71
|
+
data_type (str):
|
|
72
|
+
Tag value type ("int", "float", "bool", "str")
|
|
73
|
+
"""
|
|
74
|
+
if isinstance(data_type, str):
|
|
75
|
+
|
|
76
|
+
if data_type in self.data_types:
|
|
77
|
+
if data_type == "float":
|
|
78
|
+
value = 0.0
|
|
79
|
+
elif data_type == "int":
|
|
80
|
+
value = 0
|
|
81
|
+
elif data_type == "str":
|
|
82
|
+
value = ""
|
|
83
|
+
else:
|
|
84
|
+
value = False
|
|
85
|
+
|
|
86
|
+
elif isinstance(data_type, (FloatType, IntegerType, StringType, BooleanType)):
|
|
87
|
+
|
|
88
|
+
value = data_type()
|
|
89
|
+
data_type.set(name, value)
|
|
90
|
+
data_type = data_type.__name__
|
|
91
|
+
self.set_data_type(data_type)
|
|
92
|
+
|
|
93
|
+
has_duplicates, message = self.has_duplicates(name=name, display_name=display_name, opcua_address=opcua_address, node_namespace=node_namespace)
|
|
94
|
+
if has_duplicates:
|
|
95
|
+
|
|
96
|
+
return None, message
|
|
97
|
+
|
|
98
|
+
if not display_unit:
|
|
99
|
+
|
|
100
|
+
display_unit = unit
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
tag = Tag(
|
|
104
|
+
name=name,
|
|
105
|
+
unit=unit,
|
|
106
|
+
data_type=data_type,
|
|
107
|
+
description=description,
|
|
108
|
+
variable=variable,
|
|
109
|
+
display_name=display_name,
|
|
110
|
+
display_unit=display_unit,
|
|
111
|
+
opcua_address=opcua_address,
|
|
112
|
+
node_namespace=node_namespace,
|
|
113
|
+
scan_time=scan_time,
|
|
114
|
+
dead_band=dead_band,
|
|
115
|
+
process_filter=process_filter,
|
|
116
|
+
gaussian_filter=gaussian_filter,
|
|
117
|
+
gaussian_filter_threshold=gaussian_filter_threshold,
|
|
118
|
+
gaussian_filter_r_value=gaussian_filter_r_value,
|
|
119
|
+
outlier_detection=outlier_detection,
|
|
120
|
+
out_of_range_detection=out_of_range_detection,
|
|
121
|
+
frozen_data_detection=frozen_data_detection,
|
|
122
|
+
manufacturer=manufacturer,
|
|
123
|
+
segment=segment,
|
|
124
|
+
id=id
|
|
125
|
+
)
|
|
126
|
+
self._tags[tag.id] = tag
|
|
127
|
+
|
|
128
|
+
return tag, message
|
|
129
|
+
|
|
130
|
+
@set_event(message=f"Updated", classification="Tag", priority=1, criticity=3)
|
|
131
|
+
def update_tag(
|
|
132
|
+
self,
|
|
133
|
+
id:str,
|
|
134
|
+
user:User=None,
|
|
135
|
+
**kwargs
|
|
136
|
+
)->tuple[Tag|None, str]:
|
|
137
|
+
r"""Documentation here
|
|
138
|
+
|
|
139
|
+
# Parameters
|
|
140
|
+
|
|
141
|
+
-
|
|
142
|
+
|
|
143
|
+
# Returns
|
|
144
|
+
|
|
145
|
+
-
|
|
146
|
+
"""
|
|
147
|
+
check = dict()
|
|
148
|
+
if "name" in kwargs:
|
|
149
|
+
check["name"] = kwargs["name"]
|
|
150
|
+
if "display_name" in kwargs:
|
|
151
|
+
check["display_name"] = kwargs["display_name"]
|
|
152
|
+
if "node_namespace" in kwargs:
|
|
153
|
+
check["node_namespace"] = kwargs["node_namespace"]
|
|
154
|
+
if "opcua_address" in kwargs:
|
|
155
|
+
check["opcua_address"] = kwargs["opcua_address"]
|
|
156
|
+
has_duplicates, message = self.has_duplicates(**check)
|
|
157
|
+
if has_duplicates:
|
|
158
|
+
|
|
159
|
+
return None, message
|
|
160
|
+
|
|
161
|
+
tag = self._tags[id]
|
|
162
|
+
if "name" in kwargs:
|
|
163
|
+
tag.set_name(name=kwargs["name"])
|
|
164
|
+
if "unit" in kwargs:
|
|
165
|
+
tag.set_unit(unit=kwargs["unit"])
|
|
166
|
+
if "data_type" in kwargs:
|
|
167
|
+
tag.set_data_type(data_type=kwargs["data_type"])
|
|
168
|
+
if "description" in kwargs:
|
|
169
|
+
tag.set_description(description=kwargs["description"])
|
|
170
|
+
if "variable" in kwargs:
|
|
171
|
+
tag.set_variable(variable=kwargs["variable"])
|
|
172
|
+
if "display_name" in kwargs:
|
|
173
|
+
tag.set_display_name(name=kwargs["display_name"])
|
|
174
|
+
if "display_unit" in kwargs:
|
|
175
|
+
tag.set_display_unit(unit=kwargs["display_unit"])
|
|
176
|
+
if "opcua_address" in kwargs:
|
|
177
|
+
tag.set_opcua_address(opcua_address=kwargs["opcua_address"])
|
|
178
|
+
if "node_namespace" in kwargs:
|
|
179
|
+
tag.set_node_namespace(node_namespace=kwargs["node_namespace"])
|
|
180
|
+
if "scan_time" in kwargs:
|
|
181
|
+
if isinstance(kwargs["scan_time"], int):
|
|
182
|
+
tag.set_scan_time(scan_time=kwargs["scan_time"])
|
|
183
|
+
if "dead_band" in kwargs:
|
|
184
|
+
tag.set_dead_band(dead_band=kwargs["dead_band"])
|
|
185
|
+
if "segment" in kwargs:
|
|
186
|
+
tag.segment = kwargs["segment"]
|
|
187
|
+
if "manufacturer" in kwargs:
|
|
188
|
+
tag.manufacturer = kwargs["manufacturer"]
|
|
189
|
+
if "gaussian_filter" in kwargs:
|
|
190
|
+
|
|
191
|
+
if kwargs['gaussian_filter'].lower() in ('1', 'true'):
|
|
192
|
+
|
|
193
|
+
tag.gaussian_filter = True
|
|
194
|
+
|
|
195
|
+
else:
|
|
196
|
+
|
|
197
|
+
tag.gaussian_filter = False
|
|
198
|
+
|
|
199
|
+
if "gaussian_filter_r_value" in kwargs:
|
|
200
|
+
|
|
201
|
+
tag.gaussian_filter_r_value = kwargs['gaussian_filter_r_value']
|
|
202
|
+
|
|
203
|
+
if "gaussian_filter_threshold" in kwargs:
|
|
204
|
+
|
|
205
|
+
tag.gaussian_filter_threshold = kwargs['gaussian_filter_threshold']
|
|
206
|
+
|
|
207
|
+
self._tags[id] = tag
|
|
208
|
+
|
|
209
|
+
return tag, f"Tag: {tag.name}"
|
|
210
|
+
|
|
211
|
+
@set_event(message=f"Updated", classification="Tag", priority=1, criticity=5)
|
|
212
|
+
def delete_tag(self, id:str, user:User):
|
|
213
|
+
r"""Documentation here
|
|
214
|
+
|
|
215
|
+
# Parameters
|
|
216
|
+
|
|
217
|
+
-
|
|
218
|
+
|
|
219
|
+
# Returns
|
|
220
|
+
|
|
221
|
+
-
|
|
222
|
+
"""
|
|
223
|
+
tag = self._tags.pop(id)
|
|
224
|
+
return tag, f"Tag: {tag.name}"
|
|
225
|
+
|
|
226
|
+
def get_tag(self, id:str)->Tag|None:
|
|
227
|
+
r"""Documentation here
|
|
228
|
+
|
|
229
|
+
# Parameters
|
|
230
|
+
|
|
231
|
+
-
|
|
232
|
+
|
|
233
|
+
# Returns
|
|
234
|
+
|
|
235
|
+
-
|
|
236
|
+
"""
|
|
237
|
+
for _id, tag in self._tags.items():
|
|
238
|
+
|
|
239
|
+
if _id==id:
|
|
240
|
+
|
|
241
|
+
return tag
|
|
242
|
+
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
def get_unit_by_tag(self, tag:str)->Tag|None:
|
|
246
|
+
r"""Documentation here
|
|
247
|
+
|
|
248
|
+
# Parameters
|
|
249
|
+
|
|
250
|
+
-
|
|
251
|
+
|
|
252
|
+
# Returns
|
|
253
|
+
|
|
254
|
+
-
|
|
255
|
+
"""
|
|
256
|
+
for _id, _tag in self._tags.items():
|
|
257
|
+
|
|
258
|
+
if _tag.name==tag:
|
|
259
|
+
|
|
260
|
+
return _tag.unit
|
|
261
|
+
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
def get_display_unit_by_tag(self, tag:str)->Tag|None:
|
|
265
|
+
r"""Documentation here
|
|
266
|
+
|
|
267
|
+
# Parameters
|
|
268
|
+
|
|
269
|
+
-
|
|
270
|
+
|
|
271
|
+
# Returns
|
|
272
|
+
|
|
273
|
+
-
|
|
274
|
+
"""
|
|
275
|
+
for _id, _tag in self._tags.items():
|
|
276
|
+
|
|
277
|
+
if _tag.name==tag:
|
|
278
|
+
|
|
279
|
+
return _tag.display_unit
|
|
280
|
+
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
def get_tags(self)->list:
|
|
284
|
+
r"""
|
|
285
|
+
Returns a list of the defined tags names.
|
|
286
|
+
"""
|
|
287
|
+
if self._tags:
|
|
288
|
+
|
|
289
|
+
return [tag.serialize() for _, tag in self._tags.items()]
|
|
290
|
+
|
|
291
|
+
return list()
|
|
292
|
+
|
|
293
|
+
def get_tags_by_names(self, names:list)->list:
|
|
294
|
+
r"""
|
|
295
|
+
Returns a list of the defined tags names.
|
|
296
|
+
"""
|
|
297
|
+
if self._tags:
|
|
298
|
+
|
|
299
|
+
return [tag.serialize() for _, tag in self._tags.items() if tag.name in names]
|
|
300
|
+
|
|
301
|
+
return list()
|
|
302
|
+
|
|
303
|
+
def get_field_tags_names(self)->list:
|
|
304
|
+
r"""
|
|
305
|
+
Returns a list of the defined tags names.
|
|
306
|
+
"""
|
|
307
|
+
if self._tags:
|
|
308
|
+
|
|
309
|
+
return [tag.name for _, tag in self._tags.items() if tag.opcua_address and tag.node_namespace]
|
|
310
|
+
|
|
311
|
+
return list()
|
|
312
|
+
|
|
313
|
+
def get_cuasi_field_tags_names(self)->list:
|
|
314
|
+
r"""
|
|
315
|
+
Returns a list of the defined tags names.
|
|
316
|
+
"""
|
|
317
|
+
if self._tags:
|
|
318
|
+
|
|
319
|
+
return [tag.name for _, tag in self._tags.items() if tag.opcua_address]
|
|
320
|
+
|
|
321
|
+
return list()
|
|
322
|
+
|
|
323
|
+
def get_tag_by_name(self, name:str)->Tag|None:
|
|
324
|
+
r"""Documentation here
|
|
325
|
+
|
|
326
|
+
# Parameters
|
|
327
|
+
|
|
328
|
+
-
|
|
329
|
+
|
|
330
|
+
# Returns
|
|
331
|
+
|
|
332
|
+
-
|
|
333
|
+
"""
|
|
334
|
+
for _, tag in self._tags.items():
|
|
335
|
+
|
|
336
|
+
if tag.get_name()==name:
|
|
337
|
+
|
|
338
|
+
return tag
|
|
339
|
+
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
def get_tag_by_display_name(self, display_name:str)->Tag|None:
|
|
343
|
+
r"""Documentation here
|
|
344
|
+
|
|
345
|
+
# Parameters
|
|
346
|
+
|
|
347
|
+
-
|
|
348
|
+
|
|
349
|
+
# Returns
|
|
350
|
+
|
|
351
|
+
-
|
|
352
|
+
"""
|
|
353
|
+
for _, tag in self._tags.items():
|
|
354
|
+
|
|
355
|
+
if tag.get_display_name()==display_name:
|
|
356
|
+
|
|
357
|
+
return tag
|
|
358
|
+
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
def get_tag_by_node_namespace(self, node_namespace:str)->Tag|None:
|
|
362
|
+
r"""Documentation here
|
|
363
|
+
|
|
364
|
+
# Parameters
|
|
365
|
+
|
|
366
|
+
-
|
|
367
|
+
|
|
368
|
+
# Returns
|
|
369
|
+
|
|
370
|
+
-
|
|
371
|
+
"""
|
|
372
|
+
for _, tag in self._tags.items():
|
|
373
|
+
|
|
374
|
+
if tag.get_node_namespace()==node_namespace:
|
|
375
|
+
|
|
376
|
+
return tag
|
|
377
|
+
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
def get_value(self, id:str)->str|float|int|bool:
|
|
381
|
+
r"""Documentation here
|
|
382
|
+
|
|
383
|
+
# Parameters
|
|
384
|
+
|
|
385
|
+
-
|
|
386
|
+
|
|
387
|
+
# Returns
|
|
388
|
+
|
|
389
|
+
-
|
|
390
|
+
"""
|
|
391
|
+
tag = self._tags[id]
|
|
392
|
+
_new_object = copy.copy(tag.get_value())
|
|
393
|
+
return _new_object
|
|
394
|
+
|
|
395
|
+
def get_timestamp(self, id:str)->datetime:
|
|
396
|
+
r"""
|
|
397
|
+
Documentation here
|
|
398
|
+
"""
|
|
399
|
+
tag = self._tags[id]
|
|
400
|
+
|
|
401
|
+
return tag.get_timestamp()
|
|
402
|
+
|
|
403
|
+
def get_value_by_name(self, name:str)->str|float|int|bool:
|
|
404
|
+
r"""Documentation here
|
|
405
|
+
|
|
406
|
+
# Parameters
|
|
407
|
+
|
|
408
|
+
-
|
|
409
|
+
|
|
410
|
+
# Returns
|
|
411
|
+
|
|
412
|
+
-
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
tag = self.get_tag_by_name(name=name)
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
"value": tag.get_value(),
|
|
419
|
+
"unit": tag.get_unit(),
|
|
420
|
+
"timestamp": tag.get_timestamp()
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
def get_values_by_name(self, names:list[str])->str|float|int|bool:
|
|
424
|
+
r"""Documentation here
|
|
425
|
+
|
|
426
|
+
# Parameters
|
|
427
|
+
|
|
428
|
+
-
|
|
429
|
+
|
|
430
|
+
# Returns
|
|
431
|
+
|
|
432
|
+
-
|
|
433
|
+
"""
|
|
434
|
+
data = dict()
|
|
435
|
+
|
|
436
|
+
for name in names:
|
|
437
|
+
|
|
438
|
+
tag = self.get_tag_by_name(name=name)
|
|
439
|
+
data[name] = {
|
|
440
|
+
"value": tag.get_value(),
|
|
441
|
+
"unit": tag.get_unit(),
|
|
442
|
+
"timestamp": tag.get_timestamp()
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return data
|
|
446
|
+
|
|
447
|
+
@logging_error_handler
|
|
448
|
+
@filter
|
|
449
|
+
# @iad_frozen_data
|
|
450
|
+
# @iad_out_of_range
|
|
451
|
+
# @iad_outlier
|
|
452
|
+
def set_value(self, id:str, value, timestamp:datetime):
|
|
453
|
+
"""Sets a new value for a defined tag.
|
|
454
|
+
|
|
455
|
+
# Parameters
|
|
456
|
+
name (str):
|
|
457
|
+
Tag name.
|
|
458
|
+
value (float, int, bool):
|
|
459
|
+
Tag value ("int", "float", "bool")
|
|
460
|
+
"""
|
|
461
|
+
from .. import TIMEZONE
|
|
462
|
+
tag = self._tags[id]
|
|
463
|
+
|
|
464
|
+
# Deadband Logic Wrapper for CVT
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
if tag.dead_band and isinstance(value, (int, float)):
|
|
468
|
+
try:
|
|
469
|
+
current_value = tag.value.value
|
|
470
|
+
if abs(value - current_value) < tag.dead_band:
|
|
471
|
+
return value
|
|
472
|
+
except Exception as e:
|
|
473
|
+
logging.error(f"Error in deadband logic: {e}")
|
|
474
|
+
|
|
475
|
+
tag.set_value(value=value, timestamp=timestamp)
|
|
476
|
+
if self.sio:
|
|
477
|
+
timestamp = timestamp.astimezone(TIMEZONE)
|
|
478
|
+
self._tags[id].timestamp = timestamp
|
|
479
|
+
self.sio.emit("on.tag", data=self._tags[id].serialize())
|
|
480
|
+
|
|
481
|
+
return value
|
|
482
|
+
|
|
483
|
+
def set_data_type(self, data_type):
|
|
484
|
+
r"""Documentation here
|
|
485
|
+
|
|
486
|
+
# Parameters
|
|
487
|
+
|
|
488
|
+
-
|
|
489
|
+
|
|
490
|
+
# Returns
|
|
491
|
+
|
|
492
|
+
-
|
|
493
|
+
"""
|
|
494
|
+
self.data_types.append(data_type)
|
|
495
|
+
self.data_types = list(set(self.data_types))
|
|
496
|
+
|
|
497
|
+
def is_tag_defined(self, name:str)->bool:
|
|
498
|
+
r"""Documentation here
|
|
499
|
+
|
|
500
|
+
# Parameters
|
|
501
|
+
|
|
502
|
+
-
|
|
503
|
+
|
|
504
|
+
# Returns
|
|
505
|
+
|
|
506
|
+
-
|
|
507
|
+
"""
|
|
508
|
+
|
|
509
|
+
return name in self._tags
|
|
510
|
+
|
|
511
|
+
def attach_observer(self, name, observer):
|
|
512
|
+
r"""Attaches a new observer to a tag object defined by name.
|
|
513
|
+
|
|
514
|
+
# Parameters
|
|
515
|
+
name (str):
|
|
516
|
+
Tag name.
|
|
517
|
+
observer (TagObserver):
|
|
518
|
+
Tag observer object, will update once a tag object is changed.
|
|
519
|
+
"""
|
|
520
|
+
tag = self.get_tag_by_name(name)
|
|
521
|
+
if tag:
|
|
522
|
+
|
|
523
|
+
self._tags[tag.id].attach(observer)
|
|
524
|
+
|
|
525
|
+
else:
|
|
526
|
+
logger = logging.getLogger("pyautomation")
|
|
527
|
+
logger.warning(f"{name} tag Not exists in CVT.attach_observer method")
|
|
528
|
+
|
|
529
|
+
def detach_observer(self, name, observer):
|
|
530
|
+
r"""
|
|
531
|
+
Detaches an observer from a tag object defined by name.
|
|
532
|
+
|
|
533
|
+
# Parameters
|
|
534
|
+
name (str):
|
|
535
|
+
Tag name.
|
|
536
|
+
observer (TagObserver):
|
|
537
|
+
Tag observer object.
|
|
538
|
+
"""
|
|
539
|
+
tag = self.get_tag_by_name(name)
|
|
540
|
+
self._tags[tag.id].detach(observer)
|
|
541
|
+
|
|
542
|
+
def has_duplicates(self, tag:Tag=None, name:str=None, display_name:str=None, node_namespace:str=None, opcua_address:str=None):
|
|
543
|
+
r"""Documentation here
|
|
544
|
+
|
|
545
|
+
# Parameters
|
|
546
|
+
|
|
547
|
+
-
|
|
548
|
+
|
|
549
|
+
# Returns
|
|
550
|
+
|
|
551
|
+
-
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
for _, _tag in self._tags.items():
|
|
555
|
+
|
|
556
|
+
if name:
|
|
557
|
+
|
|
558
|
+
if _tag.get_name()==name:
|
|
559
|
+
|
|
560
|
+
return True, f"Duplicated Tag Name: {name}"
|
|
561
|
+
|
|
562
|
+
if display_name:
|
|
563
|
+
|
|
564
|
+
if _tag.get_display_name()==display_name:
|
|
565
|
+
|
|
566
|
+
return True, f"Duplicated Display Name: {display_name}"
|
|
567
|
+
|
|
568
|
+
if node_namespace:
|
|
569
|
+
|
|
570
|
+
if _tag.get_node_namespace()==node_namespace:
|
|
571
|
+
|
|
572
|
+
if tag:
|
|
573
|
+
|
|
574
|
+
if _tag.get_opcua_address()==tag.get_opcua_address():
|
|
575
|
+
|
|
576
|
+
return True, f"Duplicated Node Namespace: {node_namespace}"
|
|
577
|
+
|
|
578
|
+
return True, f"Duplicated Node Namespace: {node_namespace}"
|
|
579
|
+
|
|
580
|
+
return False, f"Valid Tag Name: {name} - Display Name: {display_name}"
|
|
581
|
+
|
|
582
|
+
def serialize(self, id:str)->dict:
|
|
583
|
+
r"""Returns a tag type defined by name.
|
|
584
|
+
|
|
585
|
+
# Parameters
|
|
586
|
+
name (str):
|
|
587
|
+
Tag name.
|
|
588
|
+
"""
|
|
589
|
+
return self._tags[id].serialize()
|
|
590
|
+
|
|
591
|
+
def serialize_by_tag_name(self, name:str)->dict|None:
|
|
592
|
+
r"""Documentation here
|
|
593
|
+
|
|
594
|
+
# Parameters
|
|
595
|
+
|
|
596
|
+
-
|
|
597
|
+
|
|
598
|
+
# Returns
|
|
599
|
+
|
|
600
|
+
-
|
|
601
|
+
"""
|
|
602
|
+
tag = self.get_tag_by_name(name)
|
|
603
|
+
|
|
604
|
+
if tag:
|
|
605
|
+
|
|
606
|
+
return tag.serialize()
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
class CVTEngine(Singleton):
|
|
610
|
+
"""
|
|
611
|
+
Current Value Table (CVT) Engine class for a tag-based, thread-safe repository.
|
|
612
|
+
|
|
613
|
+
This class is designed to hold in-memory tag-based values and manage observers for the required tags. It is implemented as a singleton, ensuring that each sub-thread within the PyAutomation application can access and modify tags in a thread-safe manner.
|
|
614
|
+
|
|
615
|
+
**Usage Example**:
|
|
616
|
+
|
|
617
|
+
.. code-block:: python
|
|
618
|
+
|
|
619
|
+
>>> from automation.tags import CVTEngine
|
|
620
|
+
>>> tag_engine = CVTEngine()
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def __init__(self):
|
|
625
|
+
|
|
626
|
+
super(CVTEngine, self).__init__()
|
|
627
|
+
self._cvt = CVT()
|
|
628
|
+
self._request_lock = threading.Lock()
|
|
629
|
+
self._response_lock = threading.Lock()
|
|
630
|
+
self._config = None
|
|
631
|
+
self._response = None
|
|
632
|
+
self._response_lock.acquire()
|
|
633
|
+
self.DATETIME_FORMAT = "%m/%d/%Y, %H:%M:%S.%f"
|
|
634
|
+
|
|
635
|
+
def set_tag(
|
|
636
|
+
self,
|
|
637
|
+
name:str,
|
|
638
|
+
unit:str,
|
|
639
|
+
data_type:str,
|
|
640
|
+
variable:str,
|
|
641
|
+
description:str,
|
|
642
|
+
display_unit:str="",
|
|
643
|
+
display_name:str="",
|
|
644
|
+
opcua_address:str="",
|
|
645
|
+
node_namespace:str="",
|
|
646
|
+
scan_time:int=0,
|
|
647
|
+
dead_band:float=0.0,
|
|
648
|
+
process_filter:bool=False,
|
|
649
|
+
gaussian_filter:bool=False,
|
|
650
|
+
gaussian_filter_threshold:float=1.0,
|
|
651
|
+
gaussian_filter_r_value:float=0.0,
|
|
652
|
+
outlier_detection:bool=False,
|
|
653
|
+
out_of_range_detection:bool=False,
|
|
654
|
+
frozen_data_detection:bool=False,
|
|
655
|
+
manufacturer:str="",
|
|
656
|
+
segment:str="",
|
|
657
|
+
id:str="",
|
|
658
|
+
user:User|None=None
|
|
659
|
+
)->tuple[Tag, str]:
|
|
660
|
+
r"""Documentation here
|
|
661
|
+
|
|
662
|
+
# Parameters
|
|
663
|
+
|
|
664
|
+
-
|
|
665
|
+
|
|
666
|
+
# Returns
|
|
667
|
+
|
|
668
|
+
-
|
|
669
|
+
"""
|
|
670
|
+
_query = dict()
|
|
671
|
+
_query["action"] = "set_tag"
|
|
672
|
+
_query["parameters"] = dict()
|
|
673
|
+
_query["parameters"]["name"] = name
|
|
674
|
+
_query["parameters"]["unit"] = unit
|
|
675
|
+
_query["parameters"]["data_type"] = data_type
|
|
676
|
+
_query["parameters"]["variable"] = variable
|
|
677
|
+
_query["parameters"]["description"] = description
|
|
678
|
+
_query["parameters"]["display_unit"] = display_unit
|
|
679
|
+
_query["parameters"]["display_name"] = display_name
|
|
680
|
+
_query["parameters"]["opcua_address"] = opcua_address
|
|
681
|
+
_query["parameters"]["node_namespace"] = node_namespace
|
|
682
|
+
_query["parameters"]["scan_time"] = scan_time
|
|
683
|
+
_query["parameters"]["dead_band"] = dead_band
|
|
684
|
+
_query["parameters"]["process_filter"] = process_filter
|
|
685
|
+
_query["parameters"]["gaussian_filter"] = gaussian_filter
|
|
686
|
+
_query["parameters"]["gaussian_filter_threshold"] = gaussian_filter_threshold
|
|
687
|
+
_query["parameters"]["gaussian_filter_r_value"] = gaussian_filter_r_value
|
|
688
|
+
_query["parameters"]["outlier_detection"] = outlier_detection
|
|
689
|
+
_query["parameters"]["out_of_range_detection"] = out_of_range_detection
|
|
690
|
+
_query["parameters"]["frozen_data_detection"] = frozen_data_detection
|
|
691
|
+
_query["parameters"]["manufacturer"] = manufacturer
|
|
692
|
+
_query["parameters"]["segment"] = segment
|
|
693
|
+
_query["parameters"]["id"] = id
|
|
694
|
+
_query["parameters"]["user"] = user
|
|
695
|
+
return self.__query(_query)
|
|
696
|
+
|
|
697
|
+
def update_tag(
|
|
698
|
+
self,
|
|
699
|
+
id:str,
|
|
700
|
+
user:User=None,
|
|
701
|
+
**kwargs
|
|
702
|
+
):
|
|
703
|
+
r"""Documentation here
|
|
704
|
+
|
|
705
|
+
# Parameters
|
|
706
|
+
|
|
707
|
+
-
|
|
708
|
+
|
|
709
|
+
# Returns
|
|
710
|
+
|
|
711
|
+
-
|
|
712
|
+
"""
|
|
713
|
+
_query = dict()
|
|
714
|
+
_query["action"] = "update_tag"
|
|
715
|
+
_query["parameters"] = dict()
|
|
716
|
+
_query["parameters"]["id"] = id
|
|
717
|
+
_query["parameters"]["user"] = user
|
|
718
|
+
for key, value in kwargs.items():
|
|
719
|
+
|
|
720
|
+
_query["parameters"][key] = value
|
|
721
|
+
return self.__query(_query)
|
|
722
|
+
|
|
723
|
+
def delete_tag(self, id:str, user:User|None=None):
|
|
724
|
+
r"""Documentation here
|
|
725
|
+
|
|
726
|
+
# Parameters
|
|
727
|
+
|
|
728
|
+
-
|
|
729
|
+
|
|
730
|
+
# Returns
|
|
731
|
+
|
|
732
|
+
-
|
|
733
|
+
"""
|
|
734
|
+
_query = dict()
|
|
735
|
+
_query["action"] = "delete_tag"
|
|
736
|
+
_query["parameters"] = dict()
|
|
737
|
+
_query["parameters"]["id"] = id
|
|
738
|
+
_query["parameters"]["user"] = user
|
|
739
|
+
return self.__query(_query)
|
|
740
|
+
|
|
741
|
+
def get_tag(
|
|
742
|
+
self,
|
|
743
|
+
id:str=None
|
|
744
|
+
)->Tag:
|
|
745
|
+
r"""Documentation here
|
|
746
|
+
|
|
747
|
+
# Parameters
|
|
748
|
+
|
|
749
|
+
-
|
|
750
|
+
|
|
751
|
+
# Returns
|
|
752
|
+
|
|
753
|
+
-
|
|
754
|
+
"""
|
|
755
|
+
_query = dict()
|
|
756
|
+
_query["action"] = "get_tag"
|
|
757
|
+
_query["parameters"] = dict()
|
|
758
|
+
_query["parameters"]["id"] = id
|
|
759
|
+
return self.__query(_query)
|
|
760
|
+
|
|
761
|
+
def get_tags(self):
|
|
762
|
+
r"""Documentation here
|
|
763
|
+
|
|
764
|
+
# Parameters
|
|
765
|
+
|
|
766
|
+
-
|
|
767
|
+
|
|
768
|
+
# Returns
|
|
769
|
+
|
|
770
|
+
-
|
|
771
|
+
"""
|
|
772
|
+
_query = dict()
|
|
773
|
+
_query["action"] = "get_tags"
|
|
774
|
+
return self.__query(_query)
|
|
775
|
+
|
|
776
|
+
def get_tags_by_names(self, names:list[str]):
|
|
777
|
+
r"""Documentation here
|
|
778
|
+
|
|
779
|
+
# Parameters
|
|
780
|
+
|
|
781
|
+
-
|
|
782
|
+
|
|
783
|
+
# Returns
|
|
784
|
+
|
|
785
|
+
-
|
|
786
|
+
"""
|
|
787
|
+
_query = dict()
|
|
788
|
+
_query["action"] = "get_tags_by_names"
|
|
789
|
+
_query["parameters"] = dict()
|
|
790
|
+
_query["parameters"]["names"] = names
|
|
791
|
+
return self.__query(_query)
|
|
792
|
+
|
|
793
|
+
def get_tag_by_name(self, name:str)->Tag|None:
|
|
794
|
+
r"""Documentation here
|
|
795
|
+
|
|
796
|
+
# Parameters
|
|
797
|
+
|
|
798
|
+
-
|
|
799
|
+
|
|
800
|
+
# Returns
|
|
801
|
+
|
|
802
|
+
-
|
|
803
|
+
"""
|
|
804
|
+
_query = dict()
|
|
805
|
+
_query["action"] = "get_tag_by_name"
|
|
806
|
+
_query["parameters"] = dict()
|
|
807
|
+
_query["parameters"]["name"] = name
|
|
808
|
+
return self.__query(_query)
|
|
809
|
+
|
|
810
|
+
def get_tag_by_display_name(self, display_name:str)->Tag|None:
|
|
811
|
+
r"""Documentation here
|
|
812
|
+
|
|
813
|
+
# Parameters
|
|
814
|
+
|
|
815
|
+
-
|
|
816
|
+
|
|
817
|
+
# Returns
|
|
818
|
+
|
|
819
|
+
-
|
|
820
|
+
"""
|
|
821
|
+
_query = dict()
|
|
822
|
+
_query["action"] = "get_tag_by_display_name"
|
|
823
|
+
_query["parameters"] = dict()
|
|
824
|
+
_query["parameters"]["display_name"] = display_name
|
|
825
|
+
return self.__query(_query)
|
|
826
|
+
|
|
827
|
+
def get_tag_by_node_namespace(self, node_namespace:str)->Tag|None:
|
|
828
|
+
r"""Documentation here
|
|
829
|
+
|
|
830
|
+
# Parameters
|
|
831
|
+
|
|
832
|
+
-
|
|
833
|
+
|
|
834
|
+
# Returns
|
|
835
|
+
|
|
836
|
+
-
|
|
837
|
+
"""
|
|
838
|
+
_query = dict()
|
|
839
|
+
_query["action"] = "get_tag_by_node_namespace"
|
|
840
|
+
_query["parameters"] = dict()
|
|
841
|
+
_query["parameters"]["node_namespace"] = node_namespace
|
|
842
|
+
return self.__query(_query)
|
|
843
|
+
|
|
844
|
+
def get_value(self, id:str)->str|float|int|bool:
|
|
845
|
+
r"""Documentation here
|
|
846
|
+
|
|
847
|
+
# Parameters
|
|
848
|
+
|
|
849
|
+
-
|
|
850
|
+
|
|
851
|
+
# Returns
|
|
852
|
+
|
|
853
|
+
-
|
|
854
|
+
"""
|
|
855
|
+
_query = dict()
|
|
856
|
+
_query["action"] = "get_value"
|
|
857
|
+
_query["parameters"] = dict()
|
|
858
|
+
_query["parameters"]["id"] = id
|
|
859
|
+
return self.__query(_query)
|
|
860
|
+
|
|
861
|
+
def get_value_by_name(self, tag_name:str)->dict:
|
|
862
|
+
r"""Documentation here
|
|
863
|
+
|
|
864
|
+
# Parameters
|
|
865
|
+
|
|
866
|
+
-
|
|
867
|
+
|
|
868
|
+
# Returns
|
|
869
|
+
|
|
870
|
+
-
|
|
871
|
+
"""
|
|
872
|
+
_query = dict()
|
|
873
|
+
_query["action"] = "get_value_by_name"
|
|
874
|
+
_query["parameters"] = dict()
|
|
875
|
+
_query["parameters"]["name"] = tag_name
|
|
876
|
+
return self.__query(_query)
|
|
877
|
+
|
|
878
|
+
def get_values_by_name(self, tag_names:list[str])->str|float|int|bool:
|
|
879
|
+
r"""Documentation here
|
|
880
|
+
|
|
881
|
+
# Parameters
|
|
882
|
+
|
|
883
|
+
-
|
|
884
|
+
|
|
885
|
+
# Returns
|
|
886
|
+
|
|
887
|
+
-
|
|
888
|
+
"""
|
|
889
|
+
_query = dict()
|
|
890
|
+
_query["action"] = "get_values_by_name"
|
|
891
|
+
_query["parameters"] = dict()
|
|
892
|
+
_query["parameters"]["names"] = tag_names
|
|
893
|
+
return self.__query(_query)
|
|
894
|
+
|
|
895
|
+
def get_scan_time(self, id:str)->str|float|int|bool:
|
|
896
|
+
r"""Documentation here
|
|
897
|
+
|
|
898
|
+
# Parameters
|
|
899
|
+
|
|
900
|
+
-
|
|
901
|
+
|
|
902
|
+
# Returns
|
|
903
|
+
|
|
904
|
+
-
|
|
905
|
+
"""
|
|
906
|
+
_query = dict()
|
|
907
|
+
_query["action"] = "get_scan_time"
|
|
908
|
+
_query["parameters"] = dict()
|
|
909
|
+
_query["parameters"]["id"] = id
|
|
910
|
+
return self.__query(_query)
|
|
911
|
+
|
|
912
|
+
def get_dead_band(self, id:str)->str|float|int|bool:
|
|
913
|
+
r"""Documentation here
|
|
914
|
+
|
|
915
|
+
# Parameters
|
|
916
|
+
|
|
917
|
+
-
|
|
918
|
+
|
|
919
|
+
# Returns
|
|
920
|
+
|
|
921
|
+
-
|
|
922
|
+
"""
|
|
923
|
+
_query = dict()
|
|
924
|
+
_query["action"] = "get_dead_band"
|
|
925
|
+
_query["parameters"] = dict()
|
|
926
|
+
_query["parameters"]["id"] = id
|
|
927
|
+
return self.__query(_query)
|
|
928
|
+
|
|
929
|
+
def get_display_unit_by_tag(self, tag:str)->str:
|
|
930
|
+
r"""Documentation here
|
|
931
|
+
|
|
932
|
+
# Parameters
|
|
933
|
+
|
|
934
|
+
-
|
|
935
|
+
|
|
936
|
+
# Returns
|
|
937
|
+
|
|
938
|
+
-
|
|
939
|
+
"""
|
|
940
|
+
_query = dict()
|
|
941
|
+
_query["action"] = "get_display_unit_by_tag"
|
|
942
|
+
_query["parameters"] = dict()
|
|
943
|
+
_query["parameters"]["tag"] = tag
|
|
944
|
+
return self.__query(_query)
|
|
945
|
+
|
|
946
|
+
@logging_error_handler
|
|
947
|
+
def set_value(self, id:str, value, timestamp:datetime):
|
|
948
|
+
r"""Documentation here
|
|
949
|
+
|
|
950
|
+
# Parameters
|
|
951
|
+
|
|
952
|
+
-
|
|
953
|
+
|
|
954
|
+
# Returns
|
|
955
|
+
|
|
956
|
+
-
|
|
957
|
+
"""
|
|
958
|
+
if not timestamp:
|
|
959
|
+
timestamp = datetime.now()
|
|
960
|
+
_query = dict()
|
|
961
|
+
_query["action"] = "set_value"
|
|
962
|
+
_query["parameters"] = dict()
|
|
963
|
+
_query["parameters"]["id"] = id
|
|
964
|
+
_query["parameters"]["value"] = value
|
|
965
|
+
_query["parameters"]["timestamp"] = timestamp
|
|
966
|
+
return self.__query(_query)
|
|
967
|
+
|
|
968
|
+
def set_data_type(self, data_type):
|
|
969
|
+
r"""Documentation here
|
|
970
|
+
|
|
971
|
+
# Parameters
|
|
972
|
+
|
|
973
|
+
-
|
|
974
|
+
|
|
975
|
+
# Returns
|
|
976
|
+
|
|
977
|
+
-
|
|
978
|
+
"""
|
|
979
|
+
_query = dict()
|
|
980
|
+
_query["action"] = "set_data_type"
|
|
981
|
+
_query["parameters"] = dict()
|
|
982
|
+
_query["parameters"]["data_type"] = data_type
|
|
983
|
+
return self.__query(_query)
|
|
984
|
+
|
|
985
|
+
def is_tag_defined(self, name:str)->bool:
|
|
986
|
+
r"""Documentation here
|
|
987
|
+
|
|
988
|
+
# Parameters
|
|
989
|
+
|
|
990
|
+
-
|
|
991
|
+
|
|
992
|
+
# Returns
|
|
993
|
+
|
|
994
|
+
-
|
|
995
|
+
"""
|
|
996
|
+
_query = dict()
|
|
997
|
+
_query["action"] = "is_tag_defined"
|
|
998
|
+
_query["parameters"] = dict()
|
|
999
|
+
_query["parameters"]["name"] = name
|
|
1000
|
+
return self.__query(_query)
|
|
1001
|
+
|
|
1002
|
+
def attach(self, name:str, observer):
|
|
1003
|
+
"""
|
|
1004
|
+
Attaches an observer object to a Tag, observer gets notified when the Tag value changes.
|
|
1005
|
+
|
|
1006
|
+
**Parameters:**
|
|
1007
|
+
|
|
1008
|
+
* **name** (str): Tag name.
|
|
1009
|
+
* **observer** (str): TagObserver instance.
|
|
1010
|
+
"""
|
|
1011
|
+
_query = dict()
|
|
1012
|
+
_query["action"] = "attach_observer"
|
|
1013
|
+
_query["parameters"] = dict()
|
|
1014
|
+
_query["parameters"]["name"] = name
|
|
1015
|
+
_query["parameters"]["observer"] = observer
|
|
1016
|
+
return self.__query(_query)
|
|
1017
|
+
|
|
1018
|
+
def detach(self, name:str, observer):
|
|
1019
|
+
"""
|
|
1020
|
+
Detaches an observer object from a Tag, observer no longer gets notified when the Tag value changes.
|
|
1021
|
+
|
|
1022
|
+
**Parameters:**
|
|
1023
|
+
|
|
1024
|
+
* **name** (str): Tag name.
|
|
1025
|
+
* **observer** (str): TagObserver instance.
|
|
1026
|
+
"""
|
|
1027
|
+
|
|
1028
|
+
_query = dict()
|
|
1029
|
+
_query["action"] = "detach_observer"
|
|
1030
|
+
|
|
1031
|
+
_query["parameters"] = dict()
|
|
1032
|
+
_query["parameters"]["name"] = name
|
|
1033
|
+
_query["parameters"]["observer"] = observer
|
|
1034
|
+
|
|
1035
|
+
self.request(_query)
|
|
1036
|
+
result = self.response()
|
|
1037
|
+
|
|
1038
|
+
if result["result"]:
|
|
1039
|
+
return result["response"]
|
|
1040
|
+
|
|
1041
|
+
def serialize(self, id:str)->dict:
|
|
1042
|
+
r"""Documentation here
|
|
1043
|
+
|
|
1044
|
+
# Parameters
|
|
1045
|
+
|
|
1046
|
+
-
|
|
1047
|
+
|
|
1048
|
+
# Returns
|
|
1049
|
+
|
|
1050
|
+
-
|
|
1051
|
+
"""
|
|
1052
|
+
_query = dict()
|
|
1053
|
+
_query["action"] = "serialize"
|
|
1054
|
+
_query["parameters"] = dict()
|
|
1055
|
+
_query["parameters"]["id"] = id
|
|
1056
|
+
return self.__query(_query)
|
|
1057
|
+
|
|
1058
|
+
def serialize_by_tag_name(self, name:str)->dict|None:
|
|
1059
|
+
r"""Documentation here
|
|
1060
|
+
|
|
1061
|
+
# Parameters
|
|
1062
|
+
|
|
1063
|
+
-
|
|
1064
|
+
|
|
1065
|
+
# Returns
|
|
1066
|
+
|
|
1067
|
+
-
|
|
1068
|
+
"""
|
|
1069
|
+
_query = dict()
|
|
1070
|
+
_query["action"] = "serialize_by_tag_name"
|
|
1071
|
+
_query["parameters"] = dict()
|
|
1072
|
+
_query["parameters"]["name"] = name
|
|
1073
|
+
return self.__query(_query)
|
|
1074
|
+
|
|
1075
|
+
def __query(self, query:dict)->dict:
|
|
1076
|
+
|
|
1077
|
+
self.request(query)
|
|
1078
|
+
result = self.response()
|
|
1079
|
+
if result["result"]:
|
|
1080
|
+
return result["response"]
|
|
1081
|
+
|
|
1082
|
+
def request(self, query:dict):
|
|
1083
|
+
r"""
|
|
1084
|
+
It does the request to the tags repository according query's structure, in a thread-safe mechanism
|
|
1085
|
+
|
|
1086
|
+
**Parameters**
|
|
1087
|
+
|
|
1088
|
+
* **query** (dict): Query to tags repository
|
|
1089
|
+
|
|
1090
|
+
## Query Structure
|
|
1091
|
+
|
|
1092
|
+
```python
|
|
1093
|
+
query = {
|
|
1094
|
+
"action": (str)
|
|
1095
|
+
"parameters": (dict)
|
|
1096
|
+
}
|
|
1097
|
+
```
|
|
1098
|
+
## Valid actions in query
|
|
1099
|
+
|
|
1100
|
+
* set_tag
|
|
1101
|
+
* get_tags
|
|
1102
|
+
* get_value
|
|
1103
|
+
* get_data_type
|
|
1104
|
+
* get_unit
|
|
1105
|
+
* get_description
|
|
1106
|
+
* get_display_name
|
|
1107
|
+
* get_min_value
|
|
1108
|
+
* get_max_value
|
|
1109
|
+
* get_attributes
|
|
1110
|
+
* set_value
|
|
1111
|
+
* attach
|
|
1112
|
+
* detach
|
|
1113
|
+
|
|
1114
|
+
## Parameters strcuture in query
|
|
1115
|
+
|
|
1116
|
+
```python
|
|
1117
|
+
parameters = {
|
|
1118
|
+
"name": (str) tag name to do request
|
|
1119
|
+
"unit": (str)[Optional] Unit to get value
|
|
1120
|
+
"value": (float)[Optional] If you use *set_value* function, you must pass this parameter
|
|
1121
|
+
"observer": (TagObserver)[Optional] If you use *attach* and *detach* function, you must pass this parameter
|
|
1122
|
+
}
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
"""
|
|
1126
|
+
self._request_lock.acquire()
|
|
1127
|
+
action = query["action"]
|
|
1128
|
+
error_msg = f"Error in CVTEngine with action: {action}"
|
|
1129
|
+
|
|
1130
|
+
try:
|
|
1131
|
+
|
|
1132
|
+
if hasattr(self._cvt, action):
|
|
1133
|
+
|
|
1134
|
+
method = getattr(self._cvt, action)
|
|
1135
|
+
|
|
1136
|
+
if 'parameters' in query:
|
|
1137
|
+
|
|
1138
|
+
resp = method(**query["parameters"])
|
|
1139
|
+
|
|
1140
|
+
else:
|
|
1141
|
+
|
|
1142
|
+
resp = method()
|
|
1143
|
+
|
|
1144
|
+
self.__true_response(resp)
|
|
1145
|
+
|
|
1146
|
+
except Exception as e:
|
|
1147
|
+
|
|
1148
|
+
self.__log_error(e, error_msg)
|
|
1149
|
+
|
|
1150
|
+
self._response_lock.release()
|
|
1151
|
+
|
|
1152
|
+
def __log_error(self, e:Exception, msg:str):
|
|
1153
|
+
r"""
|
|
1154
|
+
Documentation here
|
|
1155
|
+
"""
|
|
1156
|
+
logging.error(f"{e} Message: {msg}")
|
|
1157
|
+
self._response = {
|
|
1158
|
+
"result": False,
|
|
1159
|
+
"response": None
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
def __true_response(self, resp):
|
|
1163
|
+
r"""
|
|
1164
|
+
Documentation here
|
|
1165
|
+
"""
|
|
1166
|
+
self._response = {
|
|
1167
|
+
"result": True,
|
|
1168
|
+
"response": resp
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
def response(self)->dict:
|
|
1172
|
+
r"""
|
|
1173
|
+
Handles the python GIL to emit the request's response in a thread-safe mechanism.
|
|
1174
|
+
"""
|
|
1175
|
+
self._response_lock.acquire()
|
|
1176
|
+
|
|
1177
|
+
result = self._response
|
|
1178
|
+
|
|
1179
|
+
self._request_lock.release()
|
|
1180
|
+
|
|
1181
|
+
return result
|
|
1182
|
+
|
|
1183
|
+
def __getstate__(self):
|
|
1184
|
+
|
|
1185
|
+
self._response_lock.release()
|
|
1186
|
+
state = self.__dict__.copy()
|
|
1187
|
+
del state['_request_lock']
|
|
1188
|
+
del state['_response_lock']
|
|
1189
|
+
return state
|
|
1190
|
+
|
|
1191
|
+
def __setstate__(self, state):
|
|
1192
|
+
|
|
1193
|
+
self.__dict__.update(state)
|
|
1194
|
+
self._request_lock = threading.Lock()
|
|
1195
|
+
self._response_lock = threading.Lock()
|
|
1196
|
+
self._response_lock.acquire()
|
|
1197
|
+
|
|
1198
|
+
|