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,266 @@
|
|
|
1
|
+
import dash, csv, io
|
|
2
|
+
from ...utils import get_nodes_info, get_data_to_update_into_opcua_table
|
|
3
|
+
from ...pages.components.opcua import OPCUAComponents
|
|
4
|
+
from ...opcua.subscription import SubHandler
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
subscription_handler = SubHandler()
|
|
8
|
+
opcua_components = OPCUAComponents()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def init_callback(app:dash.Dash):
|
|
12
|
+
|
|
13
|
+
@app.callback(
|
|
14
|
+
dash.Output("data_access_view_table", "children", allow_duplicate=True),
|
|
15
|
+
dash.Input('timestamp-interval', 'n_intervals'),
|
|
16
|
+
dash.State({'type': 'file-checklist', 'index': dash.dependencies.ALL}, 'value'),
|
|
17
|
+
prevent_initial_call=False
|
|
18
|
+
)
|
|
19
|
+
def update_data_access_table( n_intervals, selected_files):
|
|
20
|
+
|
|
21
|
+
to_get_node_values = get_nodes_info(selected_files=selected_files)
|
|
22
|
+
data = get_data_to_update_into_opcua_table(app=app, to_get_node_values=to_get_node_values)
|
|
23
|
+
|
|
24
|
+
return opcua_components.data_access_view_table(data=data)
|
|
25
|
+
|
|
26
|
+
@app.callback(
|
|
27
|
+
dash.Output("data_access_view_table", "children"),
|
|
28
|
+
dash.Input({'type': 'file-checklist', 'index': dash.dependencies.ALL}, 'value')
|
|
29
|
+
)
|
|
30
|
+
def display_selected_file(selected_files):
|
|
31
|
+
|
|
32
|
+
subscription_handler.unsubscribe_all()
|
|
33
|
+
to_get_node_values = get_nodes_info(selected_files=selected_files)
|
|
34
|
+
|
|
35
|
+
data = list()
|
|
36
|
+
subscriptions = dict()
|
|
37
|
+
for client_name, namespaces in to_get_node_values.items():
|
|
38
|
+
|
|
39
|
+
client = app.automation.get_opcua_client(client_name=client_name)
|
|
40
|
+
if client:
|
|
41
|
+
subscriptions[client_name] = client.create_subscription(1000, subscription_handler)
|
|
42
|
+
infos = app.automation.get_node_attributes(client_name=client_name, namespaces=namespaces)
|
|
43
|
+
if infos:
|
|
44
|
+
for info in infos:
|
|
45
|
+
_info = info[0]
|
|
46
|
+
namespace = _info["Namespace"]
|
|
47
|
+
data.append(
|
|
48
|
+
{
|
|
49
|
+
"server": client_name,
|
|
50
|
+
"namespace": namespace,
|
|
51
|
+
"data_type": _info["DataType"],
|
|
52
|
+
"display_name": _info["DisplayName"],
|
|
53
|
+
"value": _info["Value"],
|
|
54
|
+
"source_timestamp": _info["DataValue"].SourceTimestamp,
|
|
55
|
+
"status_code": _info["DataValue"].StatusCode.name
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
node_id = client.get_node_id_by_namespace(namespace)
|
|
60
|
+
subscription = subscriptions[client_name]
|
|
61
|
+
subscription_handler.subscribe(subscription=subscription, client_name=client_name, node_id=node_id)
|
|
62
|
+
|
|
63
|
+
return opcua_components.data_access_view_table(data=data)
|
|
64
|
+
|
|
65
|
+
@app.callback(
|
|
66
|
+
dash.Output("add_server_modal", "is_open"),
|
|
67
|
+
dash.Input("add_server_button", "n_clicks"),
|
|
68
|
+
[dash.State("add_server_modal", "is_open")],
|
|
69
|
+
)
|
|
70
|
+
def add_server_button(n, is_open):
|
|
71
|
+
r"""
|
|
72
|
+
Documentation here
|
|
73
|
+
"""
|
|
74
|
+
if n:
|
|
75
|
+
|
|
76
|
+
return not is_open
|
|
77
|
+
|
|
78
|
+
return is_open
|
|
79
|
+
|
|
80
|
+
@app.callback(
|
|
81
|
+
dash.Output("remove_server_modal", "is_open"),
|
|
82
|
+
dash.Input("remove_server_button", "n_clicks"),
|
|
83
|
+
[dash.State("remove_server_modal", "is_open")],
|
|
84
|
+
)
|
|
85
|
+
def remove_server_button(n, is_open):
|
|
86
|
+
r"""
|
|
87
|
+
Documentation here
|
|
88
|
+
"""
|
|
89
|
+
if n:
|
|
90
|
+
clients = list()
|
|
91
|
+
for client_name in app.automation.get_opcua_clients().keys():
|
|
92
|
+
|
|
93
|
+
clients.append(
|
|
94
|
+
{"label": client_name, "value": client_name}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
dash.set_props("opcua_client_names_options", {"options": clients})
|
|
98
|
+
return not is_open
|
|
99
|
+
|
|
100
|
+
return is_open
|
|
101
|
+
|
|
102
|
+
@app.callback(
|
|
103
|
+
dash.Output("add_server_modal", "is_open", allow_duplicate=True),
|
|
104
|
+
dash.Output("server_tree", "children"),
|
|
105
|
+
dash.Input("add_server_ok_button_modal", "n_clicks"),
|
|
106
|
+
dash.State("opcua_client_name_input", "value"),
|
|
107
|
+
dash.State("opcua_client_host_input", "value"),
|
|
108
|
+
dash.State("opcua_client_port_input", "value")
|
|
109
|
+
)
|
|
110
|
+
def ok_add_server_button(n, client_name:str, host:str="127.0.0.1", port:int=4840):
|
|
111
|
+
r"""
|
|
112
|
+
Documentation here
|
|
113
|
+
"""
|
|
114
|
+
resp, _ = app.automation.add_opcua_client(client_name=client_name, host=host, port=port)
|
|
115
|
+
if resp:
|
|
116
|
+
|
|
117
|
+
data = OPCUAComponents.get_opcua_tree(app)
|
|
118
|
+
subscription_handler.unsubscribe_all()
|
|
119
|
+
|
|
120
|
+
return False, data
|
|
121
|
+
|
|
122
|
+
message = f"Connection refused on opc.tcp://{host}:{port}"
|
|
123
|
+
dash.set_props("modal-error-opcua-connection-body", {"children": message})
|
|
124
|
+
dash.set_props("modal-error-opcua-connection", {'is_open': True})
|
|
125
|
+
|
|
126
|
+
return False, {}
|
|
127
|
+
|
|
128
|
+
@app.callback(
|
|
129
|
+
dash.Output("add_server_modal", "is_open", allow_duplicate=True),
|
|
130
|
+
dash.Input("add_server_cancel_button_modal", "n_clicks"),
|
|
131
|
+
)
|
|
132
|
+
def cancel_add_server_button(n):
|
|
133
|
+
r"""
|
|
134
|
+
Documentation here
|
|
135
|
+
"""
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
@app.callback(
|
|
139
|
+
dash.Output("remove_server_modal", "is_open", allow_duplicate=True),
|
|
140
|
+
dash.Input("remove_server_ok_button_modal", "n_clicks"),
|
|
141
|
+
dash.State("opcua_client_names_options", "value")
|
|
142
|
+
)
|
|
143
|
+
def ok_remove_server_button(n, client_name:str):
|
|
144
|
+
r"""
|
|
145
|
+
Documentation here
|
|
146
|
+
"""
|
|
147
|
+
if client_name:
|
|
148
|
+
|
|
149
|
+
app.automation.remove_opcua_client(client_name=client_name)
|
|
150
|
+
|
|
151
|
+
data = OPCUAComponents.get_opcua_tree(app)
|
|
152
|
+
dash.set_props("server_tree", {"children": data})
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
@app.callback(
|
|
156
|
+
dash.Output("remove_server_modal", "is_open", allow_duplicate=True),
|
|
157
|
+
dash.Input("remove_server_cancel_button_modal", "n_clicks"),
|
|
158
|
+
)
|
|
159
|
+
def cancel_remove_server_button(n):
|
|
160
|
+
r"""
|
|
161
|
+
Documentation here
|
|
162
|
+
"""
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
@app.callback(
|
|
166
|
+
dash.Output("server_tree", "children", allow_duplicate=True),
|
|
167
|
+
dash.Input('communications_page', 'pathname'),
|
|
168
|
+
prevent_initial_call=True
|
|
169
|
+
)
|
|
170
|
+
def display_page(pathname):
|
|
171
|
+
r"""
|
|
172
|
+
Documentation here
|
|
173
|
+
"""
|
|
174
|
+
if pathname=="/":
|
|
175
|
+
|
|
176
|
+
data = OPCUAComponents.get_opcua_tree(app)
|
|
177
|
+
subscription_handler.unsubscribe_all()
|
|
178
|
+
|
|
179
|
+
return data
|
|
180
|
+
|
|
181
|
+
@app.callback(
|
|
182
|
+
dash.Output("modal-success-opcua-connection", "is_open"),
|
|
183
|
+
dash.Input("close-success-opcua-connection", "n_clicks"),
|
|
184
|
+
[dash.State("modal-success-opcua-connection", "is_open")],
|
|
185
|
+
)
|
|
186
|
+
def close_success_button(n, is_open):
|
|
187
|
+
r"""
|
|
188
|
+
Documentation here
|
|
189
|
+
"""
|
|
190
|
+
if n:
|
|
191
|
+
|
|
192
|
+
return not is_open
|
|
193
|
+
|
|
194
|
+
return is_open
|
|
195
|
+
|
|
196
|
+
@app.callback(
|
|
197
|
+
dash.Output("modal-error-opcua-connection", "is_open"),
|
|
198
|
+
dash.Input("close-error-opcua-connection", "n_clicks"),
|
|
199
|
+
[dash.State("modal-error-opcua-connection", "is_open")],
|
|
200
|
+
)
|
|
201
|
+
def close_error_opcua_connection_button(n, is_open):
|
|
202
|
+
r"""
|
|
203
|
+
Documentation here
|
|
204
|
+
"""
|
|
205
|
+
if n:
|
|
206
|
+
|
|
207
|
+
return not is_open
|
|
208
|
+
|
|
209
|
+
return is_open
|
|
210
|
+
|
|
211
|
+
@app.callback(
|
|
212
|
+
dash.Output("download_node_info_modal", "is_open"),
|
|
213
|
+
dash.Input("download_node_info_button", "n_clicks"),
|
|
214
|
+
[dash.State("download_node_info_modal", "is_open")],
|
|
215
|
+
)
|
|
216
|
+
def download_node_info_button(n, is_open):
|
|
217
|
+
r"""
|
|
218
|
+
Documentation here
|
|
219
|
+
"""
|
|
220
|
+
if n:
|
|
221
|
+
clients = list()
|
|
222
|
+
for client_name in app.automation.get_opcua_clients().keys():
|
|
223
|
+
|
|
224
|
+
clients.append(
|
|
225
|
+
{"label": client_name, "value": client_name}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
dash.set_props("download_opcua_client_names_options", {"options": clients})
|
|
229
|
+
return not is_open
|
|
230
|
+
|
|
231
|
+
return is_open
|
|
232
|
+
|
|
233
|
+
@app.callback(
|
|
234
|
+
dash.Output("download_node_info_modal", "is_open", allow_duplicate=True),
|
|
235
|
+
dash.Output("download", "data"),
|
|
236
|
+
dash.Input("download_node_info_ok_button_modal", "n_clicks"),
|
|
237
|
+
dash.State("download_opcua_client_names_options", "value")
|
|
238
|
+
)
|
|
239
|
+
def ok_download_node_info_button(n, client_name:str):
|
|
240
|
+
r"""
|
|
241
|
+
Documentation here
|
|
242
|
+
"""
|
|
243
|
+
if client_name:
|
|
244
|
+
|
|
245
|
+
opcua_tree = app.automation.get_opcua_tree(client_name=client_name)
|
|
246
|
+
opcua_tree = opcua_tree[0]['Objects']
|
|
247
|
+
flat = list()
|
|
248
|
+
for tree in opcua_tree:
|
|
249
|
+
|
|
250
|
+
flat.extend(OPCUAComponents.flatten_dict(tree))
|
|
251
|
+
output = io.StringIO()
|
|
252
|
+
writer = csv.DictWriter(output, fieldnames=["name", "namespace", "NodeClass"])
|
|
253
|
+
writer.writeheader()
|
|
254
|
+
writer.writerows(flat)
|
|
255
|
+
|
|
256
|
+
return False, dict(content=output.getvalue(), filename=f"opcua_server_node_{client_name}.csv")
|
|
257
|
+
|
|
258
|
+
@app.callback(
|
|
259
|
+
dash.Output("download_node_info_modal", "is_open", allow_duplicate=True),
|
|
260
|
+
dash.Input("download_node_info_cancel_button_modal", "n_clicks"),
|
|
261
|
+
)
|
|
262
|
+
def cancel_remove_server_button(n):
|
|
263
|
+
r"""
|
|
264
|
+
Documentation here
|
|
265
|
+
"""
|
|
266
|
+
return False
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import dash
|
|
2
|
+
from ...pages.components.opcua import OPCUAComponents
|
|
3
|
+
from ...opcua.subscription import SubHandler
|
|
4
|
+
from ...models import StringType
|
|
5
|
+
from ...state_machine import Node, ua
|
|
6
|
+
from ...utils import find_differences_between_lists_opcua_server
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
subscription_handler = SubHandler()
|
|
10
|
+
opcua_components = OPCUAComponents()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def init_callback(app:dash.Dash):
|
|
14
|
+
|
|
15
|
+
def create_opcua_server_table(opcua_server_machine):
|
|
16
|
+
|
|
17
|
+
attrs = list()
|
|
18
|
+
for attr in dir(opcua_server_machine):
|
|
19
|
+
if hasattr(opcua_server_machine, attr):
|
|
20
|
+
node = getattr(opcua_server_machine, attr)
|
|
21
|
+
if isinstance(node, Node):
|
|
22
|
+
|
|
23
|
+
node_class = node.get_node_class()
|
|
24
|
+
if node_class == ua.NodeClass.Variable:
|
|
25
|
+
|
|
26
|
+
display_name = node.get_attribute(ua.AttributeIds.DisplayName).Value.Value.Text
|
|
27
|
+
# Obtén el nodo padre
|
|
28
|
+
parent_node = node.get_parent()
|
|
29
|
+
|
|
30
|
+
# Obtén el nombre de la carpeta padre (nodo padre)
|
|
31
|
+
parent_name = parent_node.get_browse_name().Name
|
|
32
|
+
access_level = node.get_access_level()
|
|
33
|
+
# Verificar los niveles de acceso
|
|
34
|
+
write_only = ua.AccessLevel.CurrentWrite in access_level and ua.AccessLevel.CurrentRead not in access_level
|
|
35
|
+
read_write = ua.AccessLevel.CurrentRead in access_level and ua.AccessLevel.CurrentWrite in access_level
|
|
36
|
+
access_type = "Read"
|
|
37
|
+
if write_only:
|
|
38
|
+
|
|
39
|
+
access_type = "Write"
|
|
40
|
+
|
|
41
|
+
elif read_write:
|
|
42
|
+
|
|
43
|
+
access_type = "ReadWrite"
|
|
44
|
+
|
|
45
|
+
attrs.append({
|
|
46
|
+
"name": f"{parent_name}.{display_name}",
|
|
47
|
+
"namespace": node.nodeid.to_string(),
|
|
48
|
+
"access_type": access_type
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
properties = node.get_properties()
|
|
52
|
+
for prop in properties:
|
|
53
|
+
prop_name = prop.get_display_name().Text
|
|
54
|
+
# prop_value = prop.get_value()
|
|
55
|
+
|
|
56
|
+
access_level = prop.get_access_level()
|
|
57
|
+
# Verificar los niveles de acceso
|
|
58
|
+
write_only = ua.AccessLevel.CurrentWrite in access_level and ua.AccessLevel.CurrentRead not in access_level
|
|
59
|
+
read_write = ua.AccessLevel.CurrentRead in access_level and ua.AccessLevel.CurrentWrite in access_level
|
|
60
|
+
access_type = "Read"
|
|
61
|
+
if write_only:
|
|
62
|
+
|
|
63
|
+
access_type = "Write"
|
|
64
|
+
|
|
65
|
+
elif read_write:
|
|
66
|
+
|
|
67
|
+
access_type = "ReadWrite"
|
|
68
|
+
|
|
69
|
+
attrs.append({
|
|
70
|
+
"name": f"{parent_name}.{display_name}.{prop_name}",
|
|
71
|
+
"namespace": prop.nodeid.to_string(),
|
|
72
|
+
"access_type": access_type
|
|
73
|
+
})
|
|
74
|
+
return attrs
|
|
75
|
+
|
|
76
|
+
@app.callback(
|
|
77
|
+
dash.Output("opcua_server_datatable", "data", allow_duplicate=True),
|
|
78
|
+
dash.Input('opcua_server', 'pathname'),
|
|
79
|
+
prevent_initial_call=True
|
|
80
|
+
)
|
|
81
|
+
def display_page(pathname):
|
|
82
|
+
r"""
|
|
83
|
+
Documentation here
|
|
84
|
+
"""
|
|
85
|
+
attrs = list()
|
|
86
|
+
|
|
87
|
+
if pathname=="/opcua-server":
|
|
88
|
+
opcua_server_machine = app.automation.get_machine(name=StringType("OPCUAServer"))
|
|
89
|
+
attrs = create_opcua_server_table(opcua_server_machine=opcua_server_machine)
|
|
90
|
+
|
|
91
|
+
return attrs
|
|
92
|
+
|
|
93
|
+
@app.callback(
|
|
94
|
+
dash.Input('opcua_server_datatable', 'data_timestamp'),
|
|
95
|
+
dash.State('opcua_server_datatable', 'data_previous'),
|
|
96
|
+
dash.State('opcua_server_datatable', 'data'),
|
|
97
|
+
)
|
|
98
|
+
def update_read_only(timestamp, previous, current):
|
|
99
|
+
message = None
|
|
100
|
+
attr_not_clearable = ("name", "namespace")
|
|
101
|
+
if timestamp:
|
|
102
|
+
|
|
103
|
+
if previous and current: # UPDATE TAG DEFINITION
|
|
104
|
+
|
|
105
|
+
to_updates = find_differences_between_lists_opcua_server(previous, current)
|
|
106
|
+
node_to_update = to_updates[0]
|
|
107
|
+
node_name = node_to_update.pop("name")
|
|
108
|
+
node_to_update.pop("namespace")
|
|
109
|
+
for attr in attr_not_clearable:
|
|
110
|
+
if attr in node_to_update:
|
|
111
|
+
if not node_to_update[attr]:
|
|
112
|
+
message = f"You can not empty {attr} attribute"
|
|
113
|
+
|
|
114
|
+
if message:
|
|
115
|
+
dash.set_props("modal-update-opcua-server-body", {"children": message})
|
|
116
|
+
dash.set_props("modal-update-opcua-server-centered", {'is_open': True})
|
|
117
|
+
return
|
|
118
|
+
message = f"Do you want to update node {node_name} Access Type to {node_to_update['access_type']}?"
|
|
119
|
+
# OPEN MODAL TO CONFIRM CHANGES
|
|
120
|
+
dash.set_props("modal-update-opcua-server-body", {"children": message})
|
|
121
|
+
dash.set_props("modal-update-opcua-server-centered", {'is_open': True})
|
|
122
|
+
|
|
123
|
+
@app.callback(
|
|
124
|
+
[
|
|
125
|
+
dash.Output("modal-update-opcua-server-centered", "is_open"),
|
|
126
|
+
dash.Output('opcua_server_datatable', 'data'),
|
|
127
|
+
dash.Output('opcua_server_datatable', 'data_timestamp'),
|
|
128
|
+
dash.Output("update-opcua-server-yes", "n_clicks"),
|
|
129
|
+
dash.Output("update-opcua-server-no", "n_clicks")
|
|
130
|
+
],
|
|
131
|
+
[dash.Input("update-opcua-server-yes", "n_clicks"), dash.Input("update-opcua-server-no", "n_clicks")],
|
|
132
|
+
[
|
|
133
|
+
dash.State('opcua_server_datatable', 'data_timestamp'),
|
|
134
|
+
dash.State("modal-update-opcua-server-centered", "is_open"),
|
|
135
|
+
dash.State('opcua_server_datatable', 'data_previous'),
|
|
136
|
+
dash.State('opcua_server_datatable', 'data')
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
def toggle_modal_update_read_only(yes_n, no_n, timestamp, is_open, previous, current):
|
|
140
|
+
r"""
|
|
141
|
+
Documentation here
|
|
142
|
+
"""
|
|
143
|
+
from ...opcua.subscription import SubHandlerServer
|
|
144
|
+
|
|
145
|
+
handler = SubHandlerServer()
|
|
146
|
+
opcua_server_machine = app.automation.get_machine(name=StringType("OPCUAServer"))
|
|
147
|
+
attrs = create_opcua_server_table(opcua_server_machine=opcua_server_machine)
|
|
148
|
+
|
|
149
|
+
if yes_n:
|
|
150
|
+
|
|
151
|
+
if timestamp:
|
|
152
|
+
|
|
153
|
+
if previous and current: # UPDATE TAG DEFINITION
|
|
154
|
+
to_updates = find_differences_between_lists_opcua_server(previous, current)
|
|
155
|
+
node_to_update = to_updates[0]
|
|
156
|
+
namespace = node_to_update.pop("namespace")
|
|
157
|
+
access_type = node_to_update.pop("access_type")
|
|
158
|
+
# Code for update read_only attribute
|
|
159
|
+
opcua_server_machine = app.automation.get_machine(name=StringType("OPCUAServer"))
|
|
160
|
+
opcua_server_attrs = dir(opcua_server_machine)
|
|
161
|
+
node = False
|
|
162
|
+
|
|
163
|
+
for i, item in enumerate(opcua_server_attrs):
|
|
164
|
+
|
|
165
|
+
if hasattr(opcua_server_machine, item):
|
|
166
|
+
node = getattr(opcua_server_machine, item)
|
|
167
|
+
if isinstance(node, Node):
|
|
168
|
+
|
|
169
|
+
node_class = node.get_node_class()
|
|
170
|
+
|
|
171
|
+
if node_class == ua.NodeClass.Variable:
|
|
172
|
+
|
|
173
|
+
if node.nodeid.to_string()==namespace:
|
|
174
|
+
|
|
175
|
+
break
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
props = node.get_properties()
|
|
179
|
+
flag = False
|
|
180
|
+
for node in props:
|
|
181
|
+
|
|
182
|
+
if node.nodeid.to_string()==namespace:
|
|
183
|
+
|
|
184
|
+
flag = True
|
|
185
|
+
break
|
|
186
|
+
|
|
187
|
+
if flag:
|
|
188
|
+
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
if node:
|
|
192
|
+
|
|
193
|
+
opcua_server_obj = app.automation.get_opcua_server_record_by_namespace(namespace=namespace)
|
|
194
|
+
if opcua_server_obj:
|
|
195
|
+
app.automation.update_opcua_server_access_type(namespace=namespace, access_type=access_type)
|
|
196
|
+
else:
|
|
197
|
+
app.automation.create_opcua_server_record(name=node_to_update["name"], namespace=namespace, access_type=access_type)
|
|
198
|
+
access_type = access_type.lower()
|
|
199
|
+
# Limpiar todos los bits de acceso primero
|
|
200
|
+
node.unset_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentRead)
|
|
201
|
+
node.unset_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentWrite)
|
|
202
|
+
node.unset_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentRead)
|
|
203
|
+
node.unset_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentWrite)
|
|
204
|
+
|
|
205
|
+
subscriptions = handler.subscriptions
|
|
206
|
+
# Unsubscribe
|
|
207
|
+
|
|
208
|
+
if namespace in subscriptions:
|
|
209
|
+
|
|
210
|
+
_sub = subscriptions.pop(namespace)
|
|
211
|
+
_sub.delete()
|
|
212
|
+
|
|
213
|
+
if access_type == "write":
|
|
214
|
+
# Solo escritura: deshabilitamos la lectura y habilitamos la escritura
|
|
215
|
+
node.set_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentWrite)
|
|
216
|
+
node.set_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentWrite)
|
|
217
|
+
sub = opcua_server_machine.server.create_subscription(100, handler)
|
|
218
|
+
sub.subscribe_data_change(node)
|
|
219
|
+
handler.subscriptions[namespace] = sub
|
|
220
|
+
elif access_type == "read":
|
|
221
|
+
# Solo lectura: habilitamos la lectura y deshabilitamos la escritura
|
|
222
|
+
node.set_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentRead)
|
|
223
|
+
node.set_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentRead)
|
|
224
|
+
elif access_type == "readwrite":
|
|
225
|
+
# Lectura y escritura: habilitamos ambos
|
|
226
|
+
node.set_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentRead)
|
|
227
|
+
node.set_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentWrite)
|
|
228
|
+
node.set_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentRead)
|
|
229
|
+
node.set_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentWrite)
|
|
230
|
+
sub = opcua_server_machine.server.create_subscription(100, handler)
|
|
231
|
+
sub.subscribe_data_change(node)
|
|
232
|
+
handler.subscriptions[namespace] = sub
|
|
233
|
+
|
|
234
|
+
attrs = create_opcua_server_table(opcua_server_machine=opcua_server_machine)
|
|
235
|
+
|
|
236
|
+
return not is_open, attrs, None, 0, 0
|
|
237
|
+
|
|
238
|
+
elif no_n:
|
|
239
|
+
|
|
240
|
+
return not is_open, attrs, None, 0, 0
|
|
241
|
+
|
|
242
|
+
else:
|
|
243
|
+
|
|
244
|
+
return is_open, attrs, None, 0, 0
|