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,495 @@
1
+ import dash
2
+ from ...utils import find_differences_between_lists, generate_dropdown_conditional
3
+ from ...variables import VARIABLES
4
+
5
+ def init_callback(app:dash.Dash):
6
+
7
+ @app.callback(
8
+ dash.Input("tag_name_input", "value"),
9
+ dash.Input("variable_input", "value"),
10
+ dash.Input("datatype_input", "value"),
11
+ dash.Input("unit_input", "value"),
12
+ dash.Input("display_unit_input", "value"),
13
+ dash.Input("manufacturer_input", "value"),
14
+ dash.Input("segment_input", "value"),
15
+ dash.Input("segment_radio_button", "value")
16
+ )
17
+ def create_tag(
18
+ name:str,
19
+ variable:str,
20
+ datatype:str,
21
+ unit:str,
22
+ display_unit:str,
23
+ manufacturer:str,
24
+ segment:str,
25
+ segment_bool:bool
26
+ )->str:
27
+ r"""
28
+ Documentation here
29
+ """
30
+ if dash.ctx.triggered_id.lower()=="variable_input":
31
+
32
+ options = [{"label": value, "value": value} for _, value in VARIABLES[variable].items()]
33
+ unit = list(VARIABLES[variable].values())[0]
34
+
35
+ dash.set_props("unit_input", {'options': options})
36
+ dash.set_props("display_unit_input", {'options': options})
37
+ dash.set_props("unit_input", {'value': unit})
38
+ dash.set_props("unit_input", {'disabled': False})
39
+ dash.set_props("display_unit_input", {'value': unit})
40
+ dash.set_props("display_unit_input", {'disabled': False})
41
+
42
+ if not segment_bool:
43
+
44
+ if name and datatype and unit and variable and display_unit:
45
+
46
+ dash.set_props("create_tag_button", {'disabled': False})
47
+
48
+ else:
49
+
50
+ dash.set_props("create_tag_button", {'disabled': True})
51
+
52
+ else:
53
+
54
+ if name and datatype and unit and variable and display_unit and manufacturer and segment:
55
+
56
+ dash.set_props("create_tag_button", {'disabled': False})
57
+
58
+ else:
59
+
60
+ dash.set_props("create_tag_button", {'disabled': True})
61
+
62
+ @app.callback(
63
+ dash.Output("description_input", "value"),
64
+ dash.Input("description_radio_button", "value")
65
+ )
66
+ def enable_description(enable:bool):
67
+ r"""
68
+ Documentation here
69
+ """
70
+ dash.set_props("description_input", {'disabled': not enable})
71
+ return ""
72
+
73
+ @app.callback(
74
+ dash.Output("segment_input", "value"),
75
+ dash.Output("manufacturer_input", "value", allow_duplicate=True),
76
+ dash.Input("segment_radio_button", "value")
77
+ )
78
+ def enable_segment(enable:bool):
79
+ r"""
80
+ Documentation here
81
+ """
82
+ dash.set_props("segment_input", {'disabled': not enable})
83
+ dash.set_props("manufacturer_input", {'disabled': not enable})
84
+ return "", ""
85
+
86
+ @app.callback(
87
+ dash.Output("node_namespace_input", "value"),
88
+ dash.Input("opcua_address_input", "value")
89
+ )
90
+ def enable_node_namespace(opcua_server:str):
91
+ r"""
92
+ Documentation here
93
+ """
94
+ if opcua_server:
95
+
96
+ dash.set_props("node_namespace_input", {'disabled': False})
97
+
98
+ else:
99
+
100
+ dash.set_props("node_namespace_input", {'disabled': True})
101
+
102
+ return ""
103
+
104
+ @app.callback(
105
+ dash.Output('scan_time_input', 'value'),
106
+ dash.Output('dead_band_input', 'value'),
107
+ dash.Input("node_namespace_input", "value")
108
+ )
109
+ def enable_scan_time_and_dead_band(node_namespace:str):
110
+ r"""
111
+ Documentation here
112
+ """
113
+ if node_namespace:
114
+
115
+ dash.set_props("scan_time_input", {'disabled': False})
116
+ dash.set_props("dead_band_input", {'disabled': False})
117
+
118
+ else:
119
+
120
+ dash.set_props("scan_time_input", {'disabled': True})
121
+ dash.set_props("dead_band_input", {'disabled': True})
122
+
123
+ return "", ""
124
+
125
+ @app.callback(
126
+ dash.Output("display_name_input", "value"),
127
+ dash.Input("display_name_radio_button", "value")
128
+ )
129
+ def enable_display(enable:bool):
130
+ r"""
131
+ Documentation here
132
+ """
133
+ dash.set_props("display_name_input", {'disabled': not enable})
134
+ return ""
135
+
136
+ @app.callback(
137
+ dash.Output("dead_band_unit", "children"),
138
+ dash.Input("unit_input", "value")
139
+ )
140
+ def update_unit(unit:str):
141
+ r"""
142
+ Documentation here
143
+ """
144
+ return unit
145
+
146
+ @app.callback(
147
+ dash.Output('tags_datatable', 'data', allow_duplicate=True),
148
+ dash.Output('opcua_address_input', 'options'),
149
+ dash.Output('tags_datatable', 'dropdown', allow_duplicate=True),
150
+ dash.Output('tags_datatable', 'dropdown_conditional', allow_duplicate=True),
151
+ dash.Input('tags_page', 'pathname'),
152
+ prevent_initial_call=True
153
+ )
154
+ def display_page(pathname):
155
+ r"""
156
+ Documentation here
157
+ """
158
+
159
+ opcua_client_options = [{"label": "", "value": ""}]
160
+
161
+ for opcua_client, info in app.automation.get_opcua_clients().items():
162
+
163
+ opcua_client_options.append({
164
+ "label": opcua_client, "value": info['server_url']
165
+ })
166
+
167
+ dropdown = {
168
+ 'data_type': {
169
+ 'options': [
170
+ {'label': 'Float', 'value': 'float'},
171
+ {'label': 'Integer', 'value': 'integer'},
172
+ {'label': 'Boolean', 'value': 'boolean'},
173
+ {'label': 'String', 'value': 'string'}
174
+ ],
175
+ 'clearable': False
176
+ },
177
+ 'opcua_address': {
178
+ 'options': opcua_client_options
179
+ },
180
+ 'segment': {
181
+ 'options': [{'label': f"{segment['manufacturer']['name']}->{segment['name']}", 'value': f"{segment['manufacturer']['name']}->{segment['name']}"} for segment in app.automation.get_segments() if app.automation.get_segments()]
182
+ },
183
+ 'variable': {
184
+ 'options': [{"label": key, "value": key} for key, _ in VARIABLES.items()]
185
+ }
186
+ }
187
+ dropdown_conditional = generate_dropdown_conditional()
188
+ if pathname=="/tags":
189
+ return app.tags_table_data(), opcua_client_options, dropdown, dropdown_conditional
190
+
191
+ return dash.no_update, opcua_client_options, dropdown, dropdown_conditional
192
+
193
+ @app.callback(
194
+ dash.Output('node_namespace_input', 'options'),
195
+ dash.Input('opcua_address_input', 'value'),
196
+ prevent_initial_call=True
197
+ )
198
+ def select_opcua_server(server_url):
199
+ r"""
200
+ Documentation here
201
+ """
202
+ opcua_clients = app.automation.get_opcua_clients()
203
+ for client_name, info in opcua_clients.items():
204
+
205
+ if server_url==info["server_url"]:
206
+
207
+ break
208
+
209
+ nodes = [{"label": "None", "value": "None"}]
210
+ tree = app.automation.get_opcua_tree(client_name)
211
+ for node in tree[0]["Objects"][0]["children"]:
212
+
213
+ nodes.append(
214
+ {
215
+ "label": node["title"],
216
+ "value": node["key"]
217
+ }
218
+ )
219
+ return nodes
220
+
221
+ @app.callback(
222
+ dash.Output('tags_datatable', 'data', allow_duplicate=True),
223
+ dash.Output('tags_datatable', 'dropdown_conditional', allow_duplicate=True),
224
+ dash.Output('tags_datatable', 'dropdown', allow_duplicate=True),
225
+ dash.Input('create_tag_button', 'n_clicks'),
226
+ dash.State("tag_name_input", "value"),
227
+ dash.State("datatype_input", "value"),
228
+ dash.State("unit_input", "value"),
229
+ dash.State("display_unit_input", "value"),
230
+ dash.State("variable_input", "value"),
231
+ dash.State("display_name_input", "value"),
232
+ dash.State("description_input", "value"),
233
+ dash.State("manufacturer_input", "value"),
234
+ dash.State("segment_input", "value"),
235
+ dash.State("opcua_address_input", "value"),
236
+ dash.State("node_namespace_input", "value"),
237
+ dash.State("scan_time_input", "value"),
238
+ dash.State("dead_band_input", "value"),
239
+ prevent_initial_call=True
240
+ )
241
+ def displayClick(
242
+ btn1,
243
+ tag_name,
244
+ datatype,
245
+ unit,
246
+ display_unit,
247
+ variable,
248
+ display_name,
249
+ description,
250
+ manufacturer,
251
+ segment,
252
+ opcua_address,
253
+ node_namespace,
254
+ scan_time:int|None=None,
255
+ dead_band:float|None=None
256
+ ):
257
+ r"""
258
+ Documentation here
259
+ """
260
+ if "create_tag_button" == dash.ctx.triggered_id:
261
+
262
+ if not scan_time:
263
+
264
+ scan_time = None
265
+
266
+ if not dead_band:
267
+
268
+ dead_band = None
269
+
270
+ tag, message = app.automation.create_tag(
271
+ name=tag_name,
272
+ unit=unit,
273
+ display_unit=display_unit,
274
+ data_type=datatype,
275
+ variable=variable,
276
+ description=description,
277
+ display_name=display_name,
278
+ opcua_address=opcua_address,
279
+ node_namespace=node_namespace,
280
+ scan_time=scan_time,
281
+ dead_band=dead_band,
282
+ manufacturer=manufacturer,
283
+ segment=segment
284
+ )
285
+ if not tag:
286
+ segment = {
287
+ 'options': []
288
+ }
289
+ dash.set_props("modal-body", {"children": message})
290
+ dash.set_props("modal-centered", {'is_open': True})
291
+
292
+ else:
293
+ segment = {
294
+ 'options': [{'label': f"{segment['manufacturer']['name']}->{segment['name']}", 'value': f"{segment['manufacturer']['name']}->{segment['name']}"} for segment in app.automation.get_segments() if app.automation.get_segments()]
295
+ }
296
+ dash.set_props("modal-success-body", {"children": message})
297
+ dash.set_props("modal-success", {'is_open': True})
298
+
299
+ opcua_client_options = [{"label": "", "value": ""}]
300
+
301
+ for opcua_client, info in app.automation.get_opcua_clients().items():
302
+
303
+ opcua_client_options.append({
304
+ "label": opcua_client, "value": info['server_url']
305
+ })
306
+ dropdown = {
307
+ 'data_type': {
308
+ 'options': [
309
+ {'label': 'Float', 'value': 'float'},
310
+ {'label': 'Integer', 'value': 'integer'},
311
+ {'label': 'Boolean', 'value': 'boolean'},
312
+ {'label': 'String', 'value': 'string'}
313
+ ],
314
+ 'clearable': False
315
+ },
316
+ 'opcua_address': {
317
+ 'options': opcua_client_options
318
+ },
319
+ 'segment': segment,
320
+ 'variable': {
321
+ 'options': [{"label": key, "value": key} for key, _ in VARIABLES.items()]
322
+ }
323
+ }
324
+
325
+ return app.tags_table_data(), generate_dropdown_conditional(), dropdown
326
+
327
+ @app.callback(
328
+ dash.Input('tags_datatable', 'data_timestamp'),
329
+ dash.State('tags_datatable', 'data_previous'),
330
+ dash.State('tags_datatable', 'data'),
331
+ )
332
+ def delete_update_tags(timestamp, previous, current):
333
+ message = None
334
+ attr_not_clearable = ("name", "unit", "display_name", "display_unit", "data_type", "variable")
335
+ if timestamp:
336
+
337
+ if len(previous) > len(current): # DELETE TAG
338
+
339
+ removed_rows = [row for row in previous if row not in current]
340
+
341
+ for row in removed_rows:
342
+
343
+ _id = row['id']
344
+ message = f"Do you want to delete Tag ID: {_id}?"
345
+ # OPEN MODAL TO CONFIRM CHANGES
346
+ dash.set_props("modal-update-delete-tag-body", {"children": message})
347
+ dash.set_props("modal-update_delete-centered", {'is_open': True})
348
+
349
+ elif previous and current: # UPDATE TAG DEFINITION
350
+
351
+ to_updates = find_differences_between_lists(previous, current)
352
+ tag_to_update = to_updates[0]
353
+ tag_id = tag_to_update.pop("id")
354
+ for attr in attr_not_clearable:
355
+ if attr in tag_to_update:
356
+ if not tag_to_update[attr]:
357
+ message = f"You can not empty {attr} attribute"
358
+
359
+ if message:
360
+ dash.set_props("modal-body", {"children": message})
361
+ dash.set_props("modal-centered", {'is_open': True})
362
+ return
363
+ message = f"Do you want to update tag {tag_id} To {tag_to_update}?"
364
+ # OPEN MODAL TO CONFIRM CHANGES
365
+ dash.set_props("modal-update-delete-tag-body", {"children": message})
366
+ dash.set_props("modal-update_delete-centered", {'is_open': True})
367
+
368
+ @app.callback(
369
+ dash.Output("modal-centered", "is_open"),
370
+ dash.Input("close-centered", "n_clicks"),
371
+ [dash.State("modal-centered", "is_open")],
372
+ )
373
+ def close_error_button(n, is_open):
374
+ r"""
375
+ Documentation here
376
+ """
377
+ if n:
378
+
379
+ return not is_open
380
+
381
+ return is_open
382
+
383
+ @app.callback(
384
+ dash.Output("modal-success", "is_open"),
385
+ dash.Input("close-success", "n_clicks"),
386
+ [dash.State("modal-success", "is_open")],
387
+ )
388
+ def close_success_button(n, is_open):
389
+ r"""
390
+ Documentation here
391
+ """
392
+ if n:
393
+
394
+ return not is_open
395
+
396
+ return is_open
397
+
398
+ @app.callback(
399
+ [
400
+ dash.Output("modal-update_delete-centered", "is_open"),
401
+ dash.Output('tags_datatable', 'data'),
402
+ dash.Output('tags_datatable', 'data_timestamp'),
403
+ dash.Output("update-delete-tag-yes", "n_clicks"),
404
+ dash.Output("update-delete-tag-no", "n_clicks")
405
+ ],
406
+ [dash.Input("update-delete-tag-yes", "n_clicks"), dash.Input("update-delete-tag-no", "n_clicks")],
407
+ [
408
+ dash.State('tags_datatable', 'data_timestamp'),
409
+ dash.State("modal-update_delete-centered", "is_open"),
410
+ dash.State('tags_datatable', 'data_previous'),
411
+ dash.State('tags_datatable', 'data')
412
+ ]
413
+ )
414
+ def toggle_modal_update_delete_tag(yes_n, no_n, timestamp, is_open, previous, current):
415
+ r"""
416
+ Documentation here
417
+ """
418
+
419
+ if yes_n:
420
+
421
+ if timestamp:
422
+
423
+ if len(previous) > len(current): # DELETE TAG
424
+
425
+ removed_rows = [row for row in previous if row not in current]
426
+
427
+ for row in removed_rows:
428
+ _id = row['id']
429
+ message = app.automation.delete_tag(id=_id)
430
+
431
+ if message:
432
+ dash.set_props("modal-body", {"children": message})
433
+ dash.set_props("modal-centered", {'is_open': True})
434
+
435
+ elif previous and current: # UPDATE TAG DEFINITION
436
+ to_updates = find_differences_between_lists(previous, current)
437
+ tag_to_update = to_updates[0]
438
+ tag_id = tag_to_update.pop("id")
439
+ if "segment" in tag_to_update:
440
+ manufacturer_segment = tag_to_update['segment'].split("->")
441
+ manufacturer = manufacturer_segment[0]
442
+ segment = manufacturer_segment[1]
443
+ tag_to_update.update({
444
+ "segment": segment,
445
+ "manufacturer": manufacturer
446
+ })
447
+
448
+ tag, message = app.automation.update_tag(id=tag_id, **tag_to_update)
449
+
450
+ if not tag:
451
+ dash.set_props("modal-body", {"children": message})
452
+ dash.set_props("modal-centered", {'is_open': True})
453
+
454
+ return not is_open, app.tags_table_data(), None, 0, 0
455
+
456
+ elif no_n:
457
+
458
+ return not is_open, app.tags_table_data(), None, 0, 0
459
+
460
+ else:
461
+
462
+ return is_open, app.tags_table_data(), None, 0, 0
463
+
464
+ @app.callback(
465
+ [
466
+ dash.Output('alert', 'is_open'),
467
+ dash.Output('alert', 'children'),
468
+ dash.Output('output', 'children')
469
+ ],
470
+ [
471
+ dash.Input('scan_time_input', 'value')
472
+ ],
473
+ [
474
+ dash.State('scan_time_input', 'min'),
475
+ dash.State('scan_time_input', 'max')
476
+ ]
477
+ )
478
+ def update_scan_time(value, min_value, max_value):
479
+ r"""
480
+ Documentation here
481
+ """
482
+ if value is None:
483
+
484
+ return False, '', min_value
485
+
486
+ if value < min_value:
487
+
488
+ return True, f'Value {value} is out of range ({min_value}-{max_value})', min_value
489
+
490
+ if value > max_value:
491
+
492
+ return True, f'Value {value} is out of range ({min_value}-{max_value})', max_value
493
+
494
+ return False, '', f'Current value: {value} ms'
495
+
@@ -0,0 +1,119 @@
1
+ from math import ceil
2
+ import dash
3
+ import plotly.graph_objects as go
4
+ from ...tags import CVTEngine
5
+
6
+ cvt = CVTEngine()
7
+
8
+
9
+ def init_callback(app:dash.Dash):
10
+
11
+ @app.callback(
12
+ dash.Output('trends_tags_dropdown', 'options'),
13
+ dash.Input('trends_page', 'pathname'),
14
+ prevent_initial_call=True
15
+ )
16
+ def display_page(pathname):
17
+ r"""
18
+ Documentation here
19
+ """
20
+
21
+ if pathname=="/trends":
22
+
23
+ tags_options = [tag["name"] for tag in app.automation.cvt.get_tags()]
24
+
25
+ return tags_options
26
+
27
+ return dash.no_update
28
+
29
+ @app.callback(
30
+ dash.Output('trends_cvt_datatable', 'data'),
31
+ dash.Output('trends_figure', 'figure'),
32
+ dash.Input('timestamp-interval', 'n_intervals'),
33
+ dash.State('trends_tags_dropdown', 'value'),
34
+ prevent_initial_call=True
35
+ )
36
+ def tags(n_intervals, values):
37
+ r"""
38
+ Documentation here
39
+ """
40
+ if values:
41
+
42
+ fig = go.Figure()
43
+ counter_axis = 0
44
+ labels = dict()
45
+ data = list()
46
+ units = list()
47
+
48
+ for tag_name in values:
49
+
50
+ timestamp = app.automation.das.buffer[tag_name]["timestamp"]
51
+ values = app.automation.das.buffer[tag_name]["values"]
52
+ unit = app.automation.das.buffer[tag_name]["unit"]
53
+
54
+ current_value = values.current()
55
+ if current_value:
56
+ data.append({
57
+ "tag": tag_name, "value": f"{current_value} {unit}"
58
+ })
59
+
60
+ if unit not in units:
61
+ counter_axis += 1
62
+ units.append(unit)
63
+
64
+ if counter_axis==1:
65
+
66
+ fig.add_trace(go.Scatter(x=timestamp, y=values, name=tag_name))
67
+ labels["yaxis"] = {
68
+ "title": unit
69
+ }
70
+ else:
71
+
72
+ fig.add_trace(go.Scatter(x=timestamp, y=values, name=tag_name, yaxis=f"y{counter_axis}"))
73
+ labels[f"yaxis{counter_axis}"] = {
74
+ "title": unit,
75
+ "anchor": "free",
76
+ "overlaying": "y",
77
+ "autoshift": True
78
+ }
79
+
80
+ fig.update_layout(**labels)
81
+
82
+ return data, fig
83
+
84
+ return dash.no_update, dash.no_update
85
+
86
+
87
+ @app.callback(
88
+ dash.Input('trends_last_values_dropdown', 'value'),
89
+ dash.State('trends_tags_dropdown', 'value'),
90
+ prevent_initial_call=True
91
+ )
92
+ def last_values(last_values, tags):
93
+ r"""
94
+ Documentation here
95
+ """
96
+ for tag_name in tags:
97
+
98
+ # COMPUTATION OF MAX LENGTH OF THE BUFFER
99
+ buffer_size = get_buffer_size(tag_name=tag_name, last_values=last_values)
100
+ app.automation.das.buffer[tag_name]["timestamp"].size = buffer_size
101
+ app.automation.das.buffer[tag_name]["values"].size = buffer_size
102
+
103
+ def get_buffer_size(tag_name:str, last_values:int):
104
+ r"""
105
+ Documentation here
106
+ """
107
+ tag = cvt.get_tag_by_name(name=tag_name)
108
+ scan_time = tag.get_scan_time() # Milliseconds
109
+
110
+ if not scan_time:
111
+
112
+ current_timestamp = app.automation.das.buffer[tag_name]["timestamp"].current()
113
+ previous_last = app.automation.das.buffer[tag_name]["timestamp"].previous_current()
114
+ dt = current_timestamp - previous_last
115
+ return ceil(last_values / dt.total_seconds())
116
+
117
+ scan_time = scan_time / 1000
118
+ return ceil(last_values / scan_time)
119
+