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.
Files changed (138) hide show
  1. automation/__init__.py +46 -0
  2. automation/alarms/__init__.py +563 -0
  3. automation/alarms/states.py +192 -0
  4. automation/alarms/trigger.py +64 -0
  5. automation/buffer.py +132 -0
  6. automation/core.py +1792 -0
  7. automation/dbmodels/__init__.py +23 -0
  8. automation/dbmodels/alarms.py +549 -0
  9. automation/dbmodels/core.py +86 -0
  10. automation/dbmodels/events.py +178 -0
  11. automation/dbmodels/logs.py +155 -0
  12. automation/dbmodels/machines.py +181 -0
  13. automation/dbmodels/opcua.py +81 -0
  14. automation/dbmodels/opcua_server.py +174 -0
  15. automation/dbmodels/tags.py +921 -0
  16. automation/dbmodels/users.py +259 -0
  17. automation/extensions/__init__.py +15 -0
  18. automation/extensions/api.py +149 -0
  19. automation/extensions/cors.py +18 -0
  20. automation/filter/__init__.py +19 -0
  21. automation/iad/__init__.py +3 -0
  22. automation/iad/frozen_data.py +54 -0
  23. automation/iad/out_of_range.py +51 -0
  24. automation/iad/outliers.py +51 -0
  25. automation/logger/__init__.py +0 -0
  26. automation/logger/alarms.py +434 -0
  27. automation/logger/core.py +265 -0
  28. automation/logger/datalogger.py +877 -0
  29. automation/logger/events.py +202 -0
  30. automation/logger/logdict.py +53 -0
  31. automation/logger/logs.py +203 -0
  32. automation/logger/machines.py +248 -0
  33. automation/logger/opcua_server.py +130 -0
  34. automation/logger/users.py +96 -0
  35. automation/managers/__init__.py +4 -0
  36. automation/managers/alarms.py +455 -0
  37. automation/managers/db.py +328 -0
  38. automation/managers/opcua_client.py +186 -0
  39. automation/managers/state_machine.py +183 -0
  40. automation/models.py +174 -0
  41. automation/modules/__init__.py +14 -0
  42. automation/modules/alarms/__init__.py +0 -0
  43. automation/modules/alarms/resources/__init__.py +10 -0
  44. automation/modules/alarms/resources/alarms.py +280 -0
  45. automation/modules/alarms/resources/summary.py +81 -0
  46. automation/modules/events/__init__.py +0 -0
  47. automation/modules/events/resources/__init__.py +10 -0
  48. automation/modules/events/resources/events.py +85 -0
  49. automation/modules/events/resources/logs.py +109 -0
  50. automation/modules/tags/__init__.py +0 -0
  51. automation/modules/tags/resources/__init__.py +8 -0
  52. automation/modules/tags/resources/tags.py +254 -0
  53. automation/modules/users/__init__.py +2 -0
  54. automation/modules/users/resources/__init__.py +10 -0
  55. automation/modules/users/resources/models/__init__.py +2 -0
  56. automation/modules/users/resources/models/roles.py +5 -0
  57. automation/modules/users/resources/models/users.py +14 -0
  58. automation/modules/users/resources/roles.py +38 -0
  59. automation/modules/users/resources/users.py +113 -0
  60. automation/modules/users/roles.py +121 -0
  61. automation/modules/users/users.py +335 -0
  62. automation/opcua/__init__.py +1 -0
  63. automation/opcua/models.py +541 -0
  64. automation/opcua/subscription.py +259 -0
  65. automation/pages/__init__.py +0 -0
  66. automation/pages/alarms.py +34 -0
  67. automation/pages/alarms_history.py +21 -0
  68. automation/pages/assets/styles.css +7 -0
  69. automation/pages/callbacks/__init__.py +28 -0
  70. automation/pages/callbacks/alarms.py +218 -0
  71. automation/pages/callbacks/alarms_summary.py +20 -0
  72. automation/pages/callbacks/db.py +222 -0
  73. automation/pages/callbacks/filter.py +238 -0
  74. automation/pages/callbacks/machines.py +29 -0
  75. automation/pages/callbacks/machines_detailed.py +581 -0
  76. automation/pages/callbacks/opcua.py +266 -0
  77. automation/pages/callbacks/opcua_server.py +244 -0
  78. automation/pages/callbacks/tags.py +495 -0
  79. automation/pages/callbacks/trends.py +119 -0
  80. automation/pages/communications.py +129 -0
  81. automation/pages/components/__init__.py +123 -0
  82. automation/pages/components/alarms.py +151 -0
  83. automation/pages/components/alarms_summary.py +45 -0
  84. automation/pages/components/database.py +128 -0
  85. automation/pages/components/gaussian_filter.py +69 -0
  86. automation/pages/components/machines.py +396 -0
  87. automation/pages/components/opcua.py +384 -0
  88. automation/pages/components/opcua_server.py +53 -0
  89. automation/pages/components/tags.py +253 -0
  90. automation/pages/components/trends.py +66 -0
  91. automation/pages/database.py +26 -0
  92. automation/pages/filter.py +55 -0
  93. automation/pages/machines.py +20 -0
  94. automation/pages/machines_detailed.py +41 -0
  95. automation/pages/main.py +63 -0
  96. automation/pages/opcua_server.py +28 -0
  97. automation/pages/tags.py +40 -0
  98. automation/pages/trends.py +35 -0
  99. automation/singleton.py +30 -0
  100. automation/state_machine.py +1674 -0
  101. automation/tags/__init__.py +2 -0
  102. automation/tags/cvt.py +1198 -0
  103. automation/tags/filter.py +55 -0
  104. automation/tags/tag.py +418 -0
  105. automation/tests/__init__.py +10 -0
  106. automation/tests/test_alarms.py +110 -0
  107. automation/tests/test_core.py +257 -0
  108. automation/tests/test_unit.py +21 -0
  109. automation/tests/test_user.py +155 -0
  110. automation/utils/__init__.py +164 -0
  111. automation/utils/decorators.py +222 -0
  112. automation/utils/npw.py +294 -0
  113. automation/utils/observer.py +21 -0
  114. automation/utils/units.py +118 -0
  115. automation/variables/__init__.py +55 -0
  116. automation/variables/adimentional.py +30 -0
  117. automation/variables/current.py +71 -0
  118. automation/variables/density.py +115 -0
  119. automation/variables/eng_time.py +68 -0
  120. automation/variables/force.py +90 -0
  121. automation/variables/length.py +104 -0
  122. automation/variables/mass.py +80 -0
  123. automation/variables/mass_flow.py +101 -0
  124. automation/variables/percentage.py +30 -0
  125. automation/variables/power.py +113 -0
  126. automation/variables/pressure.py +93 -0
  127. automation/variables/temperature.py +168 -0
  128. automation/variables/volume.py +70 -0
  129. automation/variables/volumetric_flow.py +100 -0
  130. automation/workers/__init__.py +2 -0
  131. automation/workers/logger.py +164 -0
  132. automation/workers/state_machine.py +207 -0
  133. automation/workers/worker.py +36 -0
  134. pyautomationio-1.1.1.dist-info/METADATA +199 -0
  135. pyautomationio-1.1.1.dist-info/RECORD +138 -0
  136. pyautomationio-1.1.1.dist-info/WHEEL +5 -0
  137. pyautomationio-1.1.1.dist-info/licenses/LICENSE +21 -0
  138. 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,7 @@
1
+ .radiobutton-box {
2
+ height: 38px; /* Adjust the scale as needed */
3
+ }
4
+
5
+ .Select-menu-outer {
6
+ display: block !important;
7
+ }
@@ -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