PyAutomationIO 1.1.1__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 +1792 -0
- automation/dbmodels/__init__.py +23 -0
- automation/dbmodels/alarms.py +549 -0
- automation/dbmodels/core.py +86 -0
- automation/dbmodels/events.py +178 -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 +434 -0
- automation/logger/core.py +265 -0
- automation/logger/datalogger.py +877 -0
- automation/logger/events.py +202 -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 +81 -0
- automation/modules/events/__init__.py +0 -0
- automation/modules/events/resources/__init__.py +10 -0
- automation/modules/events/resources/events.py +85 -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 +254 -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 +1674 -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-1.1.1.dist-info/METADATA +199 -0
- pyautomationio-1.1.1.dist-info/RECORD +138 -0
- pyautomationio-1.1.1.dist-info/WHEEL +5 -0
- pyautomationio-1.1.1.dist-info/licenses/LICENSE +21 -0
- pyautomationio-1.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
from opcua import Client as OPCClient
|
|
2
|
+
from opcua import ua
|
|
3
|
+
# import sched
|
|
4
|
+
from opcua.ua.uatypes import NodeId, datatype_to_varianttype
|
|
5
|
+
import re, uuid, logging, time
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Client(OPCClient):
|
|
9
|
+
r"""
|
|
10
|
+
Documentation here
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, url, client_name:str, timeout=60):
|
|
13
|
+
r"""
|
|
14
|
+
Documentation here
|
|
15
|
+
"""
|
|
16
|
+
self._id = None
|
|
17
|
+
self._server_url = url
|
|
18
|
+
self._timeout = timeout
|
|
19
|
+
self.name = client_name
|
|
20
|
+
self._client = None
|
|
21
|
+
self._is_open = False
|
|
22
|
+
self._opc_ua_tree = dict()
|
|
23
|
+
# self.scheduler = sched.scheduler(time.time, time.sleep)
|
|
24
|
+
# self.token_renewal_interval = 30 # Cada 10 minutos
|
|
25
|
+
super(Client, self).__init__(url, timeout)
|
|
26
|
+
|
|
27
|
+
def get_id(self):
|
|
28
|
+
r"""
|
|
29
|
+
Documentation here
|
|
30
|
+
"""
|
|
31
|
+
return self._id
|
|
32
|
+
|
|
33
|
+
def is_token_valid(self):
|
|
34
|
+
try:
|
|
35
|
+
secure_channel = self.uaclient._uasocket._connection
|
|
36
|
+
token_id = secure_channel.security_token.TokenId
|
|
37
|
+
if token_id == secure_channel.next_security_token.TokenId or token_id == secure_channel.prev_security_token.TokenId:
|
|
38
|
+
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
else:
|
|
42
|
+
logging.error("Security token is not valid.")
|
|
43
|
+
return False
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logging.error(f"Failed to check security token: {e}")
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
def connect(self):
|
|
49
|
+
r"""
|
|
50
|
+
Documentation here
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
# Connect to the server
|
|
54
|
+
super(Client, self).connect()
|
|
55
|
+
|
|
56
|
+
# Now you're connected again!
|
|
57
|
+
self._is_open = True
|
|
58
|
+
self._id = str(uuid.uuid4())
|
|
59
|
+
result = {
|
|
60
|
+
'message': 'Successful connection',
|
|
61
|
+
'url': self._server_url,
|
|
62
|
+
'is_connected': self._is_open,
|
|
63
|
+
'id': self.get_id()
|
|
64
|
+
}
|
|
65
|
+
return result, 200
|
|
66
|
+
|
|
67
|
+
except Exception as _err:
|
|
68
|
+
logger = logging.getLogger("pyautomation")
|
|
69
|
+
logger.error(f"Error during connection: {_err}")
|
|
70
|
+
self._is_open = False
|
|
71
|
+
result = {
|
|
72
|
+
'message': 'Connection could not be established',
|
|
73
|
+
'url': self._server_url,
|
|
74
|
+
'is_connected': self._is_open,
|
|
75
|
+
'id': self.get_id()
|
|
76
|
+
}
|
|
77
|
+
return result, 404
|
|
78
|
+
|
|
79
|
+
def revolve_security_tokens(self):
|
|
80
|
+
logging.critical("Trying revolving security token")
|
|
81
|
+
try:
|
|
82
|
+
self.uaclient._uasocket._connection.revolve_tokens()
|
|
83
|
+
logging.critical("Security tokens revolved successfully")
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logging.error(f"Failed to revolve security tokens: {e}")
|
|
86
|
+
|
|
87
|
+
def reconnect(self):
|
|
88
|
+
|
|
89
|
+
# if not self.is_connected() or not self.is_token_valid():
|
|
90
|
+
if not self.is_connected():
|
|
91
|
+
|
|
92
|
+
from automation import PyAutomation
|
|
93
|
+
app = PyAutomation()
|
|
94
|
+
app.sio.emit("on.opcua.disconnected", data={"message": f"Disconneted from {self._server_url}"})
|
|
95
|
+
logging.critical(f"Attempting to reconnect to {self._server_url}")
|
|
96
|
+
try:
|
|
97
|
+
|
|
98
|
+
result, status = self.connect()
|
|
99
|
+
|
|
100
|
+
if status == 200:
|
|
101
|
+
# Revolver tokens de seguridad para asegurar la validez
|
|
102
|
+
# self.revolve_security_tokens()
|
|
103
|
+
app.sio.emit("on.opcua.connected", data={"message": f"Conneted to {self._server_url}"})
|
|
104
|
+
tags = app.get_tags()
|
|
105
|
+
for tag in tags:
|
|
106
|
+
_tag = app.cvt.get_tag(id=tag["id"])
|
|
107
|
+
app.subscribe_opcua(tag=_tag, opcua_address=tag['opcua_address'], node_namespace=tag['node_namespace'], scan_time=tag['scan_time'], reload=True)
|
|
108
|
+
|
|
109
|
+
logging.critical(f"Reconnected to {self._server_url}")
|
|
110
|
+
except:
|
|
111
|
+
logging.critical(f"Reconnection failed...")
|
|
112
|
+
|
|
113
|
+
def __reset_object_attributes(self):
|
|
114
|
+
r"""
|
|
115
|
+
Documentation here
|
|
116
|
+
"""
|
|
117
|
+
self._server_url = None
|
|
118
|
+
self._client = None
|
|
119
|
+
self._opc_ua_tree = dict()
|
|
120
|
+
|
|
121
|
+
def disconnect(self):
|
|
122
|
+
r"""
|
|
123
|
+
Documentation here
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
super(Client, self).disconnect()
|
|
127
|
+
self.__reset_object_attributes()
|
|
128
|
+
result = {
|
|
129
|
+
'message': 'Successful disconnection',
|
|
130
|
+
'is_connected': False
|
|
131
|
+
}
|
|
132
|
+
return result, 200
|
|
133
|
+
|
|
134
|
+
except Exception as _err:
|
|
135
|
+
result = {'message': 'Disconnect could not be performed'}
|
|
136
|
+
return result, 404
|
|
137
|
+
|
|
138
|
+
def get_opc_ua_tree(self):
|
|
139
|
+
r"""
|
|
140
|
+
Documentation here
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
if self.is_connected():
|
|
144
|
+
root = self.get_objects_node()
|
|
145
|
+
node = self.get_node(root)
|
|
146
|
+
tree = self.__walk_into_nodes(node)
|
|
147
|
+
return tree, 200
|
|
148
|
+
|
|
149
|
+
except Exception as _err:
|
|
150
|
+
self.disconnect()
|
|
151
|
+
result = { 'message': str(_err)}
|
|
152
|
+
return result, 500
|
|
153
|
+
|
|
154
|
+
def __walk_into_nodes(self, node, tree=None):
|
|
155
|
+
r"""
|
|
156
|
+
Documentation here
|
|
157
|
+
"""
|
|
158
|
+
if tree is None:
|
|
159
|
+
|
|
160
|
+
tree = dict()
|
|
161
|
+
|
|
162
|
+
_object = list()
|
|
163
|
+
|
|
164
|
+
if self.is_connected():
|
|
165
|
+
|
|
166
|
+
for ref in node.get_children_descriptions():
|
|
167
|
+
|
|
168
|
+
_node = self.get_node(ref.NodeId)
|
|
169
|
+
# ('Aliases', 'MyObjects', 'Server', 'StaticData')
|
|
170
|
+
|
|
171
|
+
if _node.get_browse_name().Name not in ('Aliases', 'MyObjects', 'Server', 'StaticData'):
|
|
172
|
+
|
|
173
|
+
result = self.__opc_ua_tree(ref.NodeId)
|
|
174
|
+
|
|
175
|
+
if _node.get_children():
|
|
176
|
+
|
|
177
|
+
_children = self.__get_children_node_recursively(_node)
|
|
178
|
+
|
|
179
|
+
result['children'] = _children
|
|
180
|
+
|
|
181
|
+
_object.append(result)
|
|
182
|
+
|
|
183
|
+
tree[f"{node.get_browse_name().Name}"] = _object
|
|
184
|
+
|
|
185
|
+
return tree
|
|
186
|
+
|
|
187
|
+
def __get_children_node_recursively(self, node, children=None):
|
|
188
|
+
r"""
|
|
189
|
+
Documentation here
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
if children is None:
|
|
193
|
+
|
|
194
|
+
children = list()
|
|
195
|
+
if self.is_connected():
|
|
196
|
+
for child in node.get_children():
|
|
197
|
+
|
|
198
|
+
result = self.__opc_ua_tree(child.nodeid)
|
|
199
|
+
|
|
200
|
+
if child.get_children():
|
|
201
|
+
|
|
202
|
+
_children = self.__get_children_node_recursively(child)
|
|
203
|
+
|
|
204
|
+
result['children'] = _children
|
|
205
|
+
|
|
206
|
+
children.append(result)
|
|
207
|
+
|
|
208
|
+
return children
|
|
209
|
+
|
|
210
|
+
def __opc_ua_tree(self, namespace_node):
|
|
211
|
+
r"""
|
|
212
|
+
Documentation here
|
|
213
|
+
"""
|
|
214
|
+
if self.is_connected():
|
|
215
|
+
_node = self.get_node(namespace_node)
|
|
216
|
+
|
|
217
|
+
result = {
|
|
218
|
+
"title": _node.get_browse_name().Name,
|
|
219
|
+
"key": _node.nodeid.to_string(),
|
|
220
|
+
"children": [],
|
|
221
|
+
"NodeClass": _node.get_node_class().name,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return result
|
|
225
|
+
|
|
226
|
+
def get_values(self, nodes:list):
|
|
227
|
+
r"""
|
|
228
|
+
Documentation here
|
|
229
|
+
"""
|
|
230
|
+
if self.is_connected():
|
|
231
|
+
results = self.uaclient.get_attributes(nodes, ua.AttributeIds.Value)
|
|
232
|
+
result = [{"Namespace": nodes[id].to_string(), "Value": result.Value.Value, "Timestamp": result.SourceTimestamp} for id, result in enumerate(results)]
|
|
233
|
+
|
|
234
|
+
return result, 200
|
|
235
|
+
|
|
236
|
+
def get_nodes_id_by_namespaces(self, namespaces:list):
|
|
237
|
+
r"""
|
|
238
|
+
Documentar here
|
|
239
|
+
"""
|
|
240
|
+
nodes = list()
|
|
241
|
+
|
|
242
|
+
for namespace in namespaces:
|
|
243
|
+
if self.is_connected():
|
|
244
|
+
_node = self.get_node(NodeId.from_string(namespace))
|
|
245
|
+
nodes.append(_node)
|
|
246
|
+
|
|
247
|
+
return nodes
|
|
248
|
+
|
|
249
|
+
def get_node_id_by_namespace(self, namespace:str):
|
|
250
|
+
r"""
|
|
251
|
+
Documentar here
|
|
252
|
+
"""
|
|
253
|
+
if self.is_connected():
|
|
254
|
+
return self.get_node(NodeId.from_string(namespace))
|
|
255
|
+
|
|
256
|
+
def get_nodes_values(self, namespaces:list)->list:
|
|
257
|
+
r"""
|
|
258
|
+
Documentation here
|
|
259
|
+
"""
|
|
260
|
+
result = list()
|
|
261
|
+
nodes = list()
|
|
262
|
+
|
|
263
|
+
for namespace in namespaces:
|
|
264
|
+
if self.is_connected():
|
|
265
|
+
_node = self.get_node(NodeId.from_string(namespace))
|
|
266
|
+
nodes.append(_node)
|
|
267
|
+
|
|
268
|
+
if _node.get_node_class().name.lower()=='variable':
|
|
269
|
+
node = {
|
|
270
|
+
"Namespace": namespace,
|
|
271
|
+
"Value": _node.get_value()
|
|
272
|
+
}
|
|
273
|
+
result.append(node)
|
|
274
|
+
|
|
275
|
+
return result, 200
|
|
276
|
+
|
|
277
|
+
def write_value(self, node_namespace: str, value):
|
|
278
|
+
r"""
|
|
279
|
+
Escribe un valor en un nodo variable del servidor OPC UA
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
node_namespace: Namespace del nodo en formato string (ej: "ns=2;i=1234")
|
|
283
|
+
value: Valor a escribir (el tipo debe ser compatible con el nodo)
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
tuple: (dict con resultado, status_code)
|
|
287
|
+
"""
|
|
288
|
+
try:
|
|
289
|
+
if not self.is_connected():
|
|
290
|
+
return {
|
|
291
|
+
'message': 'Cliente no conectado al servidor',
|
|
292
|
+
'namespace': node_namespace,
|
|
293
|
+
'success': False
|
|
294
|
+
}, 400
|
|
295
|
+
|
|
296
|
+
_node = self.get_node(NodeId.from_string(node_namespace))
|
|
297
|
+
|
|
298
|
+
# Verificar que es un nodo variable
|
|
299
|
+
if _node.get_node_class().name.lower() != 'variable':
|
|
300
|
+
return {
|
|
301
|
+
'message': f'El nodo no es de tipo Variable, es {_node.get_node_class().name}',
|
|
302
|
+
'namespace': node_namespace,
|
|
303
|
+
'success': False
|
|
304
|
+
}, 400
|
|
305
|
+
|
|
306
|
+
# Verificar permisos de escritura
|
|
307
|
+
access_level = _node.get_access_level()
|
|
308
|
+
user_access_level = _node.get_user_access_level()
|
|
309
|
+
|
|
310
|
+
# Escribir el valor
|
|
311
|
+
_node.set_value(value)
|
|
312
|
+
|
|
313
|
+
result = {
|
|
314
|
+
'message': 'Valor escrito exitosamente',
|
|
315
|
+
'namespace': node_namespace,
|
|
316
|
+
'value': value,
|
|
317
|
+
'success': True
|
|
318
|
+
}
|
|
319
|
+
return result, 200
|
|
320
|
+
|
|
321
|
+
except Exception as err:
|
|
322
|
+
logger = logging.getLogger("pyautomation")
|
|
323
|
+
logger.error(f"Error escribiendo valor en {node_namespace}: {err}")
|
|
324
|
+
result = {
|
|
325
|
+
'message': f'Error al escribir valor: {str(err)}',
|
|
326
|
+
'namespace': node_namespace,
|
|
327
|
+
'success': False
|
|
328
|
+
}
|
|
329
|
+
return result, 500
|
|
330
|
+
|
|
331
|
+
@staticmethod
|
|
332
|
+
def find_servers(hostname, port):
|
|
333
|
+
r"""
|
|
334
|
+
Documentation here
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
_client = OPCClient(f'opc.tcp://{hostname}:{port}')
|
|
338
|
+
servers = _client.connect_and_find_servers()
|
|
339
|
+
_servers = list()
|
|
340
|
+
for server in servers:
|
|
341
|
+
_server = dict()
|
|
342
|
+
_server['ApplicationUri'] = server.ApplicationUri
|
|
343
|
+
_server['ProductUri'] = server.ProductUri
|
|
344
|
+
_server['ApplicationName'] = server.ApplicationName.Text
|
|
345
|
+
_server['ApplicationType'] = server.ApplicationType.Server
|
|
346
|
+
_server['GatewayServerUri'] = server.GatewayServerUri
|
|
347
|
+
_server['DiscoveryProfileUri'] = server.DiscoveryProfileUri
|
|
348
|
+
_server['DiscoveryUrls'] = server.DiscoveryUrls
|
|
349
|
+
_servers.append(_server)
|
|
350
|
+
|
|
351
|
+
return _servers
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
def get_endpoints(hostname, port):
|
|
355
|
+
r"""
|
|
356
|
+
Documentation here
|
|
357
|
+
"""
|
|
358
|
+
try:
|
|
359
|
+
_client = OPCClient(f'opc.tcp://{hostname}:{port}')
|
|
360
|
+
endpoints = _client.connect_and_get_server_endpoints()
|
|
361
|
+
_endpoints = list()
|
|
362
|
+
for ep in endpoints:
|
|
363
|
+
|
|
364
|
+
if isinstance(ep.Server.DiscoveryUrls, list):
|
|
365
|
+
|
|
366
|
+
_endpoints.extend(ep.Server.DiscoveryUrls)
|
|
367
|
+
|
|
368
|
+
else:
|
|
369
|
+
|
|
370
|
+
_endpoints.append(ep.Server.DiscoveryUrls)
|
|
371
|
+
|
|
372
|
+
_endpoints = list(set(_endpoints))
|
|
373
|
+
|
|
374
|
+
for ep in _endpoints:
|
|
375
|
+
if not ep.startswith('opc.tcp'):
|
|
376
|
+
_endpoints.remove(ep)
|
|
377
|
+
|
|
378
|
+
result = [re.sub('//.*?/',f'//{hostname}:{port}/', __ep) for __ep in _endpoints]
|
|
379
|
+
result = {
|
|
380
|
+
'message': 'Successful search',
|
|
381
|
+
'endpoints': result
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return result, 200
|
|
385
|
+
|
|
386
|
+
except Exception as err:
|
|
387
|
+
|
|
388
|
+
result = {
|
|
389
|
+
'message': 'Unsuccessful search',
|
|
390
|
+
'endpoints': []
|
|
391
|
+
}
|
|
392
|
+
return result, 400
|
|
393
|
+
|
|
394
|
+
def is_connected(self):
|
|
395
|
+
r"""
|
|
396
|
+
Documentation here
|
|
397
|
+
"""
|
|
398
|
+
try:
|
|
399
|
+
return self.uaclient._uasocket._connection.is_open()
|
|
400
|
+
|
|
401
|
+
except Exception as _err:
|
|
402
|
+
|
|
403
|
+
return False
|
|
404
|
+
|
|
405
|
+
def get_node_attributes(self, node_namespace)->dict:
|
|
406
|
+
r"""
|
|
407
|
+
Documentation here
|
|
408
|
+
"""
|
|
409
|
+
if self.is_connected():
|
|
410
|
+
_node = self.get_node(NodeId.from_string(node_namespace))
|
|
411
|
+
|
|
412
|
+
node_class = _node.get_node_class().name.lower()
|
|
413
|
+
|
|
414
|
+
if node_class=='variable':
|
|
415
|
+
|
|
416
|
+
result = {
|
|
417
|
+
"NamespaceIndex": _node.nodeid.NamespaceIndex,
|
|
418
|
+
"NamespaceUri": _node.nodeid.NamespaceUri,
|
|
419
|
+
"Identifier": _node.nodeid.Identifier,
|
|
420
|
+
"Namespace": _node.nodeid.to_string(),
|
|
421
|
+
"NodeClass": _node.get_node_class().name,
|
|
422
|
+
"BrowseName": _node.get_browse_name().Name,
|
|
423
|
+
"DataValue": _node.get_data_value(),
|
|
424
|
+
"DisplayName": _node.get_display_name().Text,
|
|
425
|
+
"DataType": datatype_to_varianttype(_node.get_data_type()).name,
|
|
426
|
+
"AccesLevel": [access_lvl.name for access_lvl in _node.get_access_level()],
|
|
427
|
+
"UserAccessLevel": [user_access_lvl.name for user_access_lvl in _node.get_user_access_level()],
|
|
428
|
+
"Description": _node.get_description().Text if _node.get_description() else None,
|
|
429
|
+
"Value": _node.get_value(),
|
|
430
|
+
"ArrayDimensions": _node.get_array_dimensions(),
|
|
431
|
+
"ValueRank": _node.get_value_rank().name
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
else:
|
|
435
|
+
|
|
436
|
+
result = {
|
|
437
|
+
"NamespaceIndex": _node.nodeid.NamespaceIndex,
|
|
438
|
+
"NamespaceUri": _node.nodeid.NamespaceUri,
|
|
439
|
+
"Identifier": _node.nodeid.Identifier,
|
|
440
|
+
"Namespace": _node.nodeid.to_string(),
|
|
441
|
+
"NodeClass": _node.get_node_class().name,
|
|
442
|
+
"BrowseName": _node.get_browse_name().Name,
|
|
443
|
+
"DisplayName": _node.get_display_name().Text,
|
|
444
|
+
"Description": _node.get_description().Text if _node.get_description() else ''
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return result, 200
|
|
448
|
+
|
|
449
|
+
return {}, 400
|
|
450
|
+
|
|
451
|
+
def get_nodes_attributes(self, namespaces:list)->list:
|
|
452
|
+
r"""
|
|
453
|
+
Documentation here
|
|
454
|
+
"""
|
|
455
|
+
nodes = list()
|
|
456
|
+
for namespace in namespaces:
|
|
457
|
+
if self.is_connected():
|
|
458
|
+
node = self.get_node_attributes(node_namespace=namespace)
|
|
459
|
+
nodes.append(node)
|
|
460
|
+
|
|
461
|
+
return nodes
|
|
462
|
+
|
|
463
|
+
def get_referenced_nodes(self, node_id):
|
|
464
|
+
r"""
|
|
465
|
+
Documentation here
|
|
466
|
+
"""
|
|
467
|
+
result = list()
|
|
468
|
+
if self.is_connected():
|
|
469
|
+
_node = self.get_node(NodeId.from_string(node_id))
|
|
470
|
+
referenced_nodes = _node.get_referenced_nodes()
|
|
471
|
+
|
|
472
|
+
for count, node in enumerate(referenced_nodes):
|
|
473
|
+
|
|
474
|
+
node_name = node.get_browse_name().Name
|
|
475
|
+
if count==0:
|
|
476
|
+
result.append(('OrganizedBy', node_name))
|
|
477
|
+
elif count==1:
|
|
478
|
+
result.append(('HasTypeDefinition', node_name))
|
|
479
|
+
else:
|
|
480
|
+
result.append(('Organizes', node_name))
|
|
481
|
+
|
|
482
|
+
return result, 200
|
|
483
|
+
|
|
484
|
+
return result, 400
|
|
485
|
+
|
|
486
|
+
def browse_tree(self, node):
|
|
487
|
+
children_list = []
|
|
488
|
+
if self.is_connected():
|
|
489
|
+
if node.get_node_class() == ua.NodeClass.Object:
|
|
490
|
+
children = node.get_children()
|
|
491
|
+
for child_id in children:
|
|
492
|
+
child_node = self.get_node(child_id)
|
|
493
|
+
display_name = child_node.get_display_name().Text or "Unnamed Node"
|
|
494
|
+
if display_name not in ('Aliases', 'MyObjects', 'Server', 'StaticData', 'Types', 'ReferenceTypes', 'EventTypes', 'InterfaceTypes', 'Views'):
|
|
495
|
+
child_dict = {
|
|
496
|
+
"title": display_name,
|
|
497
|
+
"key": child_node.nodeid.to_string(),
|
|
498
|
+
"NodeClass": child_node.get_node_class().name,
|
|
499
|
+
"children": self.browse_tree(child_node) if child_node.get_node_class() == ua.NodeClass.Object else []
|
|
500
|
+
}
|
|
501
|
+
if child_node.get_node_class() == ua.NodeClass.Variable:
|
|
502
|
+
variable_info = {
|
|
503
|
+
"title": display_name,
|
|
504
|
+
"key": child_node.nodeid.to_string(),
|
|
505
|
+
"NodeClass": child_node.get_node_class().name,
|
|
506
|
+
"children": []
|
|
507
|
+
}
|
|
508
|
+
for prop_id in child_node.get_properties():
|
|
509
|
+
prop_node = self.get_node(prop_id)
|
|
510
|
+
prop_display_name = prop_node.get_display_name().Text or "Unnamed Property"
|
|
511
|
+
try:
|
|
512
|
+
prop_dict = {
|
|
513
|
+
"title": prop_display_name,
|
|
514
|
+
"key": prop_node.nodeid.to_string(),
|
|
515
|
+
"NodeClass": prop_node.get_node_class().name,
|
|
516
|
+
"value": prop_node.get_value(),
|
|
517
|
+
"children": []
|
|
518
|
+
}
|
|
519
|
+
variable_info["children"].append(prop_dict)
|
|
520
|
+
except ua.uaerrors.BadWaitingForInitialData:
|
|
521
|
+
variable_info["children"].append({
|
|
522
|
+
"title": prop_display_name,
|
|
523
|
+
"key": prop_node.nodeid.to_string(),
|
|
524
|
+
"NodeClass": prop_node.get_node_class().name,
|
|
525
|
+
"value": None,
|
|
526
|
+
"children": []
|
|
527
|
+
})
|
|
528
|
+
child_dict = variable_info
|
|
529
|
+
children_list.append(child_dict)
|
|
530
|
+
return children_list
|
|
531
|
+
|
|
532
|
+
def serialize(self):
|
|
533
|
+
r"""
|
|
534
|
+
Documentation here
|
|
535
|
+
"""
|
|
536
|
+
return {
|
|
537
|
+
'client_id': self.get_id(),
|
|
538
|
+
'server_url': self._server_url,
|
|
539
|
+
'timeout': self._timeout,
|
|
540
|
+
'is_opened': self.is_connected()
|
|
541
|
+
}
|