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,259 @@
|
|
|
1
|
+
import pytz
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from math import ceil
|
|
4
|
+
from ..singleton import Singleton
|
|
5
|
+
from ..tags.cvt import CVTEngine
|
|
6
|
+
from ..tags import Tag
|
|
7
|
+
from ..buffer import Buffer
|
|
8
|
+
from ..models import StringType
|
|
9
|
+
from ..logger.datalogger import DataLoggerEngine
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SubHandler(Singleton):
|
|
13
|
+
r"""
|
|
14
|
+
Subscription Handler. To receive events from server for a subscription
|
|
15
|
+
data_change and event methods are called directly from receiving thread.
|
|
16
|
+
Do not do expensive, slow or network operation there. Create another
|
|
17
|
+
thread if you need to do such a thing
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
|
|
22
|
+
self.monitored_items = dict()
|
|
23
|
+
|
|
24
|
+
def subscribe(self, subscription, client_name, node_id):
|
|
25
|
+
r"""
|
|
26
|
+
Documentation here
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
if client_name not in self.monitored_items:
|
|
30
|
+
|
|
31
|
+
monitored_item = subscription.subscribe_data_change(
|
|
32
|
+
node_id
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
self.monitored_items[client_name] = {
|
|
36
|
+
node_id: {
|
|
37
|
+
"subscription": subscription,
|
|
38
|
+
"monitored_item": monitored_item,
|
|
39
|
+
"server": client_name
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
else:
|
|
44
|
+
|
|
45
|
+
if node_id not in self.monitored_items[client_name]:
|
|
46
|
+
|
|
47
|
+
monitored_item = subscription.subscribe_data_change(
|
|
48
|
+
node_id
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self.monitored_items[client_name].update({
|
|
52
|
+
node_id: {
|
|
53
|
+
"subscription": subscription,
|
|
54
|
+
"monitored_item": monitored_item,
|
|
55
|
+
"server": client_name
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
def unsubscribe_all(self):
|
|
60
|
+
r"""
|
|
61
|
+
Documentation here
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
for _, monitored_items in self.monitored_items.items():
|
|
65
|
+
|
|
66
|
+
for _, monitored_item in monitored_items.items():
|
|
67
|
+
|
|
68
|
+
item = monitored_item["monitored_item"]
|
|
69
|
+
subscription = monitored_item["subscription"]
|
|
70
|
+
subscription.unsubscribe(item)
|
|
71
|
+
|
|
72
|
+
self.monitored_items = dict()
|
|
73
|
+
|
|
74
|
+
def resubscribe_all(self, client):
|
|
75
|
+
for client_name, monitored_items in self.monitored_items.items():
|
|
76
|
+
for node_id, monitored_item in monitored_items.items():
|
|
77
|
+
subscription = monitored_item["subscription"]
|
|
78
|
+
monitored_item["monitored_item"] = subscription.subscribe_data_change(client.get_node(node_id))
|
|
79
|
+
|
|
80
|
+
def datachange_notification(self, node, val, data):
|
|
81
|
+
r"""
|
|
82
|
+
Documentation here
|
|
83
|
+
"""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
class SubHandlerServer(Singleton):
|
|
87
|
+
|
|
88
|
+
def __init__(self):
|
|
89
|
+
from ..core import PyAutomation
|
|
90
|
+
self.app = PyAutomation()
|
|
91
|
+
self.subscriptions = dict()
|
|
92
|
+
|
|
93
|
+
def is_property(self, node):
|
|
94
|
+
from ..state_machine import ua
|
|
95
|
+
# A property is a variable that is part of another node (often an Object or another Variable)
|
|
96
|
+
parent = node.get_parent()
|
|
97
|
+
references = node.get_references(refs=ua.ObjectIds.HasProperty)
|
|
98
|
+
return bool(references)
|
|
99
|
+
|
|
100
|
+
def is_variable(self, node):
|
|
101
|
+
from ..state_machine import ua
|
|
102
|
+
return node.get_node_class() == ua.NodeClass.Variable
|
|
103
|
+
|
|
104
|
+
def datachange_notification(self, node, val, data):
|
|
105
|
+
from .. import SEGMENT, MANUFACTURER, PyAutomation
|
|
106
|
+
|
|
107
|
+
app = PyAutomation()
|
|
108
|
+
|
|
109
|
+
timestamp = data.monitored_item.Value.SourceTimestamp
|
|
110
|
+
if not timestamp:
|
|
111
|
+
timestamp = datetime.now(pytz.utc)
|
|
112
|
+
timestamp = timestamp.replace(tzinfo=pytz.UTC)
|
|
113
|
+
tag_name = node.get_display_name().Text
|
|
114
|
+
|
|
115
|
+
tag = self.app.get_tag_by_name(name=tag_name)
|
|
116
|
+
if tag:
|
|
117
|
+
if tag.get_value()!=val:
|
|
118
|
+
|
|
119
|
+
val = tag.value.convert_value(value=val, from_unit=tag.get_unit(), to_unit=tag.get_display_unit())
|
|
120
|
+
if tag.manufacturer==MANUFACTURER and tag.segment==SEGMENT:
|
|
121
|
+
self.app.cvt.set_value(id=tag.id, value=val, timestamp=timestamp)
|
|
122
|
+
elif not MANUFACTURER and not SEGMENT:
|
|
123
|
+
self.app.cvt.set_value(id=tag.id, value=val, timestamp=timestamp)
|
|
124
|
+
else:
|
|
125
|
+
|
|
126
|
+
parent = node.get_parent()
|
|
127
|
+
if parent:
|
|
128
|
+
machine_name = parent.get_display_name().Text
|
|
129
|
+
machine = app.get_machine(name=StringType(machine_name))
|
|
130
|
+
attr = getattr(machine, tag_name)
|
|
131
|
+
attr.value = val
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class DAS(Singleton):
|
|
135
|
+
r"""
|
|
136
|
+
Subscription Handler. To receive events from server for a subscription
|
|
137
|
+
data_change and event methods are called directly from receiving thread.
|
|
138
|
+
Do not do expensive, slow or network operation there. Create another
|
|
139
|
+
thread if you need to do such a thing
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(self):
|
|
143
|
+
|
|
144
|
+
self.monitored_items = dict()
|
|
145
|
+
self.cvt = CVTEngine()
|
|
146
|
+
self.logger = DataLoggerEngine()
|
|
147
|
+
self.buffer = dict()
|
|
148
|
+
|
|
149
|
+
def restart_buffer(self, tag:Tag):
|
|
150
|
+
r"""
|
|
151
|
+
Documentation here
|
|
152
|
+
"""
|
|
153
|
+
scan_time = tag.get_scan_time()
|
|
154
|
+
if scan_time:
|
|
155
|
+
|
|
156
|
+
self.buffer[tag.get_name()].update({
|
|
157
|
+
"timestamp": Buffer(size=ceil(600/ ceil(scan_time / 1000))),
|
|
158
|
+
"values": Buffer(size=ceil(600 / ceil(scan_time / 1000)))
|
|
159
|
+
})
|
|
160
|
+
else:
|
|
161
|
+
self.buffer[tag.get_name()].update({
|
|
162
|
+
"timestamp": Buffer(size=600),
|
|
163
|
+
"values": Buffer(size=600)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
def subscribe(self, subscription, client_name, node_id):
|
|
167
|
+
r"""
|
|
168
|
+
Documentation here
|
|
169
|
+
"""
|
|
170
|
+
if client_name not in self.monitored_items:
|
|
171
|
+
|
|
172
|
+
monitored_item = subscription.subscribe_data_change(
|
|
173
|
+
node_id
|
|
174
|
+
)
|
|
175
|
+
self.monitored_items[client_name] = {
|
|
176
|
+
node_id.get_display_name().Text: {
|
|
177
|
+
"subscription": subscription,
|
|
178
|
+
"monitored_item": monitored_item,
|
|
179
|
+
"server": client_name,
|
|
180
|
+
"namespace": node_id.nodeid.to_string()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
else:
|
|
185
|
+
|
|
186
|
+
if node_id not in self.monitored_items[client_name]:
|
|
187
|
+
|
|
188
|
+
monitored_item = subscription.subscribe_data_change(
|
|
189
|
+
node_id
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
self.monitored_items[client_name].update({
|
|
193
|
+
node_id.get_display_name().Text: {
|
|
194
|
+
"subscription": subscription,
|
|
195
|
+
"monitored_item": monitored_item,
|
|
196
|
+
"server": client_name,
|
|
197
|
+
"namespace": node_id.nodeid.to_string()
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
## Trying to get the value of the tag into OPCUA Client
|
|
202
|
+
try:
|
|
203
|
+
val = node_id.get_value()
|
|
204
|
+
self.update_tag_value(node=node_id, val=val)
|
|
205
|
+
except Exception:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
def unsubscribe(self, client_name:str, node_id):
|
|
209
|
+
r"""
|
|
210
|
+
Documentation here
|
|
211
|
+
"""
|
|
212
|
+
if client_name in self.monitored_items:
|
|
213
|
+
|
|
214
|
+
if node_id.get_display_name().Text in self.monitored_items[client_name]:
|
|
215
|
+
|
|
216
|
+
node = self.monitored_items[client_name].pop(node_id.get_display_name().Text)
|
|
217
|
+
item = node["monitored_item"]
|
|
218
|
+
subscription = node["subscription"]
|
|
219
|
+
subscription.unsubscribe(item)
|
|
220
|
+
|
|
221
|
+
def resubscribe_all(self, client):
|
|
222
|
+
for client_name, monitored_items in self.monitored_items.items():
|
|
223
|
+
for node_id, monitored_item in monitored_items.items():
|
|
224
|
+
subscription = monitored_item["subscription"]
|
|
225
|
+
monitored_item["monitored_item"] = subscription.subscribe_data_change(client.get_node(node_id))
|
|
226
|
+
|
|
227
|
+
def update_tag_value(self, node, val, timestamp=None):
|
|
228
|
+
r"""
|
|
229
|
+
Update tag value in CVT and buffer
|
|
230
|
+
"""
|
|
231
|
+
from .. import SEGMENT, MANUFACTURER, TIMEZONE
|
|
232
|
+
|
|
233
|
+
if not timestamp:
|
|
234
|
+
timestamp = datetime.now(pytz.utc)
|
|
235
|
+
timestamp = timestamp.replace(tzinfo=pytz.UTC)
|
|
236
|
+
|
|
237
|
+
namespace = node.nodeid.to_string()
|
|
238
|
+
tag = self.cvt.get_tag_by_node_namespace(node_namespace=namespace)
|
|
239
|
+
|
|
240
|
+
if tag:
|
|
241
|
+
tag_name = tag.get_name()
|
|
242
|
+
val = tag.value.convert_value(value=val, from_unit=tag.get_unit(), to_unit=tag.get_display_unit())
|
|
243
|
+
if tag.manufacturer==MANUFACTURER and tag.segment==SEGMENT:
|
|
244
|
+
val = self.cvt.set_value(id=tag.id, value=val, timestamp=timestamp)
|
|
245
|
+
elif not MANUFACTURER and not SEGMENT:
|
|
246
|
+
val = self.cvt.set_value(id=tag.id, value=val, timestamp=timestamp)
|
|
247
|
+
timestamp = timestamp.astimezone(TIMEZONE)
|
|
248
|
+
if tag_name in self.buffer:
|
|
249
|
+
self.buffer[tag_name]["timestamp"](timestamp)
|
|
250
|
+
self.buffer[tag_name]["values"](val)
|
|
251
|
+
|
|
252
|
+
def datachange_notification(self, node, val, data):
|
|
253
|
+
r"""
|
|
254
|
+
Documentation here
|
|
255
|
+
"""
|
|
256
|
+
timestamp = data.monitored_item.Value.SourceTimestamp
|
|
257
|
+
self.update_tag_value(node, val, timestamp)
|
|
258
|
+
|
|
259
|
+
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import dash
|
|
2
|
+
import dash_bootstrap_components as dbc
|
|
3
|
+
from automation.pages.components import Components
|
|
4
|
+
from automation.pages.components.alarms import AlarmsComponents
|
|
5
|
+
|
|
6
|
+
dash.register_page(__name__)
|
|
7
|
+
|
|
8
|
+
layout = dbc.Container(
|
|
9
|
+
[
|
|
10
|
+
dbc.Breadcrumb(
|
|
11
|
+
items=[
|
|
12
|
+
{"label": "Home", "href": "/"},
|
|
13
|
+
{"label": "Alarms", "active": True},
|
|
14
|
+
],
|
|
15
|
+
),
|
|
16
|
+
Components.modal_error(
|
|
17
|
+
title="Error",
|
|
18
|
+
modal_id="modal-alarm-create",
|
|
19
|
+
button_close_id="close-model-alarm-create",
|
|
20
|
+
body_id="modal-body-alarm-create"
|
|
21
|
+
),
|
|
22
|
+
Components.modal_confirm(
|
|
23
|
+
title="Confirmation",
|
|
24
|
+
modal_id="modal-update-delete-alarm",
|
|
25
|
+
body_id="modal-update-delete-alarm-body",
|
|
26
|
+
yes_button_id="update-delete-alarm-yes",
|
|
27
|
+
no_button_id="update-delete-alarm-no"
|
|
28
|
+
),
|
|
29
|
+
AlarmsComponents.create_alarm_form(),
|
|
30
|
+
AlarmsComponents.alarms_table()
|
|
31
|
+
],
|
|
32
|
+
fluid=False,
|
|
33
|
+
className="my-3"
|
|
34
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import dash
|
|
2
|
+
import dash_bootstrap_components as dbc
|
|
3
|
+
from automation.pages.components.alarms_summary import AlarmSummaryComponents
|
|
4
|
+
|
|
5
|
+
dash.register_page(__name__)
|
|
6
|
+
|
|
7
|
+
layout = dbc.Container(
|
|
8
|
+
[
|
|
9
|
+
dbc.Breadcrumb(
|
|
10
|
+
items=[
|
|
11
|
+
{"label": "Home", "href": "/"}, # Primer nivel
|
|
12
|
+
{"label": "Alarms", "href": "/alarms"}, # Segundo nivel
|
|
13
|
+
{"label": "Alarms History", "active": True}, # Página actual (sin enlace)
|
|
14
|
+
],
|
|
15
|
+
),
|
|
16
|
+
dash.dcc.Location(id='alarms_history_page', refresh=False),
|
|
17
|
+
AlarmSummaryComponents.alarm_summary_table()
|
|
18
|
+
],
|
|
19
|
+
fluid=False,
|
|
20
|
+
className="my-3",
|
|
21
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import dash
|
|
2
|
+
from .tags import init_callback as init_callback_tags
|
|
3
|
+
from .opcua import init_callback as init_callback_opcua
|
|
4
|
+
from .alarms import init_callback as init_callback_alarms
|
|
5
|
+
from .alarms_summary import init_callback as init_callback_alarms_summary
|
|
6
|
+
from .trends import init_callback as init_callback_trends
|
|
7
|
+
from .db import init_callback as init_callback_db
|
|
8
|
+
from .machines import init_callback as init_callback_machines
|
|
9
|
+
from .machines_detailed import init_callback as init_callback_machines_detailed
|
|
10
|
+
from .filter import init_callback as init_callback_filter
|
|
11
|
+
from .opcua_server import init_callback as init_callback_opcua_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def init_callbacks(app:dash.Dash):
|
|
15
|
+
r"""
|
|
16
|
+
Documentation here
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
init_callback_tags(app=app)
|
|
20
|
+
init_callback_opcua(app=app)
|
|
21
|
+
init_callback_alarms(app=app)
|
|
22
|
+
init_callback_alarms_summary(app=app)
|
|
23
|
+
init_callback_trends(app=app)
|
|
24
|
+
init_callback_db(app=app)
|
|
25
|
+
init_callback_machines(app=app)
|
|
26
|
+
init_callback_machines_detailed(app=app)
|
|
27
|
+
init_callback_filter(app=app)
|
|
28
|
+
init_callback_opcua_server(app=app)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import dash
|
|
2
|
+
from ...tags.cvt import CVTEngine
|
|
3
|
+
from ...utils import find_differences_between_lists, generate_dropdown_conditional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
tag_engine = CVTEngine()
|
|
7
|
+
|
|
8
|
+
def init_callback(app:dash.Dash):
|
|
9
|
+
|
|
10
|
+
@app.callback(
|
|
11
|
+
dash.Input("tag_alarm_input", "value"),
|
|
12
|
+
dash.Input("alarm_name_input", "value"),
|
|
13
|
+
dash.Input("alarm_type_input", "value"),
|
|
14
|
+
dash.Input("alarm_trigger_value_input", "value")
|
|
15
|
+
)
|
|
16
|
+
def create_tag(
|
|
17
|
+
tag:str,
|
|
18
|
+
name:str,
|
|
19
|
+
type:str,
|
|
20
|
+
trigger_value:int|float
|
|
21
|
+
)->str:
|
|
22
|
+
r"""
|
|
23
|
+
Documentation here
|
|
24
|
+
"""
|
|
25
|
+
if tag:
|
|
26
|
+
_tag = tag_engine.get_tag_by_name(name=tag)
|
|
27
|
+
dash.set_props("alarm_trigger_unit", {'children': _tag.get_display_unit()})
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if name and tag and type and trigger_value:
|
|
31
|
+
|
|
32
|
+
dash.set_props("create_alarm_button", {'disabled': False})
|
|
33
|
+
|
|
34
|
+
else:
|
|
35
|
+
|
|
36
|
+
dash.set_props("create_alarm_button", {'disabled': True})
|
|
37
|
+
|
|
38
|
+
@app.callback(
|
|
39
|
+
dash.Output("modal-alarm-create", "is_open"),
|
|
40
|
+
dash.Input("close-model-alarm-create", "n_clicks"),
|
|
41
|
+
[dash.State("modal-alarm-create", "is_open")],
|
|
42
|
+
)
|
|
43
|
+
def toggle_modal(n, is_open):
|
|
44
|
+
r"""
|
|
45
|
+
Documentation here
|
|
46
|
+
"""
|
|
47
|
+
if n:
|
|
48
|
+
|
|
49
|
+
return not is_open
|
|
50
|
+
|
|
51
|
+
return is_open
|
|
52
|
+
|
|
53
|
+
@app.callback(
|
|
54
|
+
dash.Output("alarm_description_input", "value"),
|
|
55
|
+
dash.Input("alarm_description_radio_button", "value")
|
|
56
|
+
)
|
|
57
|
+
def enable_description(enable:bool):
|
|
58
|
+
r"""
|
|
59
|
+
Documentation here
|
|
60
|
+
"""
|
|
61
|
+
dash.set_props("alarm_description_input", {'disabled': not enable})
|
|
62
|
+
return ""
|
|
63
|
+
|
|
64
|
+
@app.callback(
|
|
65
|
+
dash.Output('alarms_datatable', 'data', allow_duplicate=True),
|
|
66
|
+
dash.Output('tag_alarm_input', 'options'),
|
|
67
|
+
dash.Output('alarms_datatable', 'dropdown'),
|
|
68
|
+
dash.Output('alarm_type_input', 'options'),
|
|
69
|
+
dash.Output('alarms_datatable', 'dropdown_conditional'),
|
|
70
|
+
dash.Input('alarms_page', 'pathname'),
|
|
71
|
+
prevent_initial_call=True
|
|
72
|
+
)
|
|
73
|
+
def display_page(pathname):
|
|
74
|
+
r"""
|
|
75
|
+
Documentation here
|
|
76
|
+
"""
|
|
77
|
+
if pathname=="/alarms":
|
|
78
|
+
|
|
79
|
+
data = app.alarms_table_data()
|
|
80
|
+
|
|
81
|
+
dropdown_options_type = [
|
|
82
|
+
{'label': 'HIGH-HIGH', 'value': 'HIGH-HIGH'},
|
|
83
|
+
{'label': 'HIGH', 'value': 'HIGH'},
|
|
84
|
+
{'label': 'LOW', 'value': 'LOW'},
|
|
85
|
+
{'label': 'LOW-LOW', 'value': 'LOW-LOW'},
|
|
86
|
+
{'label': 'BOOL', 'value': 'BOOL'}
|
|
87
|
+
]
|
|
88
|
+
dropdown_options_tag = [{"label": tag["name"], "value": tag["name"]} for tag in app.automation.cvt.get_tags()]
|
|
89
|
+
dropdown = {
|
|
90
|
+
"alarm_type": {
|
|
91
|
+
"options": dropdown_options_type,
|
|
92
|
+
"clearable": False
|
|
93
|
+
},
|
|
94
|
+
"tag": {
|
|
95
|
+
"options": dropdown_options_tag,
|
|
96
|
+
"clearable": False
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return data, dropdown_options_tag, dropdown, dropdown_options_type, generate_dropdown_conditional()
|
|
101
|
+
|
|
102
|
+
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
|
|
103
|
+
|
|
104
|
+
@app.callback(
|
|
105
|
+
dash.Output('alarms_datatable', 'data', allow_duplicate=True),
|
|
106
|
+
dash.Input('create_alarm_button', 'n_clicks'),
|
|
107
|
+
dash.State("tag_alarm_input", "value"),
|
|
108
|
+
dash.State("alarm_name_input", "value"),
|
|
109
|
+
dash.State("alarm_description_input", "value"),
|
|
110
|
+
dash.State("alarm_type_input", "value"),
|
|
111
|
+
dash.State("alarm_trigger_value_input", "value"),
|
|
112
|
+
prevent_initial_call=True
|
|
113
|
+
)
|
|
114
|
+
def CreateAlarmButton(
|
|
115
|
+
btn1,
|
|
116
|
+
tag_name,
|
|
117
|
+
alarm_name,
|
|
118
|
+
alarm_description,
|
|
119
|
+
alarm_type,
|
|
120
|
+
trigger_value
|
|
121
|
+
):
|
|
122
|
+
r"""
|
|
123
|
+
Documentation here
|
|
124
|
+
"""
|
|
125
|
+
if "create_alarm_button" == dash.ctx.triggered_id:
|
|
126
|
+
|
|
127
|
+
alarm, message = app.automation.create_alarm(
|
|
128
|
+
name=alarm_name,
|
|
129
|
+
tag=tag_name,
|
|
130
|
+
alarm_type=alarm_type,
|
|
131
|
+
trigger_value=float(trigger_value),
|
|
132
|
+
description=alarm_description
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if not alarm:
|
|
136
|
+
|
|
137
|
+
dash.set_props("modal-body-alarm-create", {"children": message})
|
|
138
|
+
dash.set_props("modal-alarm-create", {'is_open': True})
|
|
139
|
+
|
|
140
|
+
return app.alarms_table_data()
|
|
141
|
+
|
|
142
|
+
@app.callback(
|
|
143
|
+
dash.Input('alarms_datatable', 'data_timestamp'),
|
|
144
|
+
dash.State('alarms_datatable', 'data_previous'),
|
|
145
|
+
dash.State('alarms_datatable', 'data'),
|
|
146
|
+
)
|
|
147
|
+
def delete_update_alarms(timestamp, previous, current):
|
|
148
|
+
|
|
149
|
+
if timestamp:
|
|
150
|
+
|
|
151
|
+
if previous and current: # UPDATE TAG DEFINITION
|
|
152
|
+
|
|
153
|
+
to_updates = find_differences_between_lists(previous, current)
|
|
154
|
+
alarm_to_update = to_updates[0]
|
|
155
|
+
alarm_id = alarm_to_update.pop("id")
|
|
156
|
+
message = f"Do you want to update alarm {alarm_id} To {alarm_to_update}?"
|
|
157
|
+
|
|
158
|
+
# OPEN MODAL TO CONFIRM CHANGES
|
|
159
|
+
dash.set_props("modal-update-delete-alarm-body", {"children": message})
|
|
160
|
+
dash.set_props("modal-update-delete-alarm", {'is_open': True})
|
|
161
|
+
|
|
162
|
+
@app.callback(
|
|
163
|
+
[
|
|
164
|
+
dash.Output("modal-update-delete-alarm", "is_open"),
|
|
165
|
+
dash.Output('alarms_datatable', 'data'),
|
|
166
|
+
dash.Output('alarms_datatable', 'data_timestamp'),
|
|
167
|
+
dash.Output("update-delete-alarm-yes", "n_clicks"),
|
|
168
|
+
dash.Output("update-delete-alarm-no", "n_clicks")
|
|
169
|
+
],
|
|
170
|
+
[dash.Input("update-delete-alarm-yes", "n_clicks"), dash.Input("update-delete-alarm-no", "n_clicks")],
|
|
171
|
+
[
|
|
172
|
+
dash.State('alarms_datatable', 'data_timestamp'),
|
|
173
|
+
dash.State("modal-update-delete-alarm", "is_open"),
|
|
174
|
+
dash.State('alarms_datatable', 'data_previous'),
|
|
175
|
+
dash.State('alarms_datatable', 'data')
|
|
176
|
+
]
|
|
177
|
+
)
|
|
178
|
+
def toggle_modal_update_delete_alarm(yes_n, no_n, timestamp, is_open, previous, current):
|
|
179
|
+
r"""
|
|
180
|
+
Documentation here
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
if yes_n:
|
|
184
|
+
|
|
185
|
+
if timestamp:
|
|
186
|
+
|
|
187
|
+
if len(previous) > len(current): # DELETE ALARM
|
|
188
|
+
|
|
189
|
+
removed_rows = [row for row in previous if row not in current]
|
|
190
|
+
|
|
191
|
+
for row in removed_rows:
|
|
192
|
+
_id = row['id']
|
|
193
|
+
message = app.automation.delete_alarm(id=_id)
|
|
194
|
+
|
|
195
|
+
if message:
|
|
196
|
+
dash.set_props("modal-body-alarm-create", {"children": message})
|
|
197
|
+
dash.set_props("modal-alarm-create", {'is_open': True})
|
|
198
|
+
|
|
199
|
+
elif previous and current: # UPDATE TAG DEFINITION
|
|
200
|
+
to_updates = find_differences_between_lists(previous, current)
|
|
201
|
+
alarm_to_update = to_updates[0]
|
|
202
|
+
alarm_id = alarm_to_update.pop("id")
|
|
203
|
+
message = app.automation.update_alarm(id=alarm_id, **alarm_to_update)
|
|
204
|
+
|
|
205
|
+
if message:
|
|
206
|
+
dash.set_props("modal-body-alarm-create", {"children": message})
|
|
207
|
+
dash.set_props("modal-alarm-create", {'is_open': True})
|
|
208
|
+
|
|
209
|
+
return not is_open, app.alarms_table_data(), None, 0, 0
|
|
210
|
+
|
|
211
|
+
elif no_n:
|
|
212
|
+
|
|
213
|
+
return not is_open, app.alarms_table_data(), None, 0, 0
|
|
214
|
+
|
|
215
|
+
else:
|
|
216
|
+
|
|
217
|
+
return is_open, app.alarms_table_data(), None, 0, 0
|
|
218
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import dash
|
|
2
|
+
|
|
3
|
+
def init_callback(app:dash.Dash):
|
|
4
|
+
|
|
5
|
+
@app.callback(
|
|
6
|
+
dash.Output('alarms_summary_datatable', 'data'),
|
|
7
|
+
dash.Input('alarms_history_page', 'pathname'),
|
|
8
|
+
prevent_initial_call=True
|
|
9
|
+
)
|
|
10
|
+
def display_page(pathname):
|
|
11
|
+
r"""
|
|
12
|
+
Documentation here
|
|
13
|
+
"""
|
|
14
|
+
if pathname=="/alarms-history":
|
|
15
|
+
|
|
16
|
+
data = app.automation.get_lasts_alarms()
|
|
17
|
+
|
|
18
|
+
return data
|
|
19
|
+
|
|
20
|
+
return dash.no_update
|