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,222 @@
1
+ import dash
2
+
3
+ def init_callback(app:dash.Dash):
4
+
5
+ @app.callback(
6
+ dash.Input("db_type_input", "value"),
7
+ dash.Input("db_name_input", "value"),
8
+ dash.Input("db_host_input", "value"),
9
+ dash.Input("db_port_input", "value"),
10
+ dash.Input("db_user_input", "value")
11
+ )
12
+ def connection(
13
+ db_type_value:str,
14
+ db_name_value:str,
15
+ db_host_value:str,
16
+ db_port_value:str,
17
+ db_user_value:str
18
+ )->str:
19
+ r"""
20
+ Documentation here
21
+ """
22
+
23
+ if db_type_value:
24
+
25
+ if db_type_value.lower()=="sqlite":
26
+
27
+ dash.set_props("db_host_input", {'disabled': True})
28
+ dash.set_props("db_port_input", {'disabled': True})
29
+ dash.set_props("db_user_input", {'disabled': True})
30
+ dash.set_props("db_password_input", {'disabled': True})
31
+
32
+ dash.set_props("db_host_input", {'value': ""})
33
+ dash.set_props("db_port_input", {'value': ""})
34
+ dash.set_props("db_user_input", {'value': ""})
35
+ dash.set_props("db_password_input", {'value': ""})
36
+
37
+ if db_name_value:
38
+
39
+ dash.set_props("connect_disconnect_db_button", {'disabled': False})
40
+
41
+ else:
42
+
43
+ dash.set_props("connect_disconnect_db_button", {'disabled': True})
44
+
45
+ else:
46
+
47
+ if not app.automation.is_db_connected():
48
+
49
+ dash.set_props("db_host_input", {'disabled': False})
50
+ dash.set_props("db_port_input", {'disabled': False})
51
+ dash.set_props("db_user_input", {'disabled': False})
52
+ dash.set_props("db_password_input", {'disabled': False})
53
+
54
+ if db_name_value and db_host_value and db_port_value and db_user_value:
55
+
56
+ dash.set_props("connect_disconnect_db_button", {'disabled': False})
57
+
58
+ else:
59
+
60
+ dash.set_props("connect_disconnect_db_button", {'disabled': True})
61
+
62
+ else:
63
+
64
+ dash.set_props("db_host_input", {'disabled': False})
65
+ dash.set_props("db_port_input", {'disabled': False})
66
+ dash.set_props("db_user_input", {'disabled': False})
67
+ dash.set_props("db_password_input", {'disabled': False})
68
+
69
+ if db_name_value and db_host_value and db_port_value and db_user_value:
70
+
71
+ dash.set_props("connect_disconnect_db_button", {'disabled': False})
72
+
73
+ else:
74
+
75
+ dash.set_props("connect_disconnect_db_button", {'disabled': True})
76
+
77
+
78
+ @app.callback(
79
+ dash.Input('connect_disconnect_db_button', 'n_clicks'),
80
+ dash.State('connect_disconnect_db_button', 'children'),
81
+ dash.State("db_type_input", "value"),
82
+ dash.State("db_name_input", "value"),
83
+ dash.State("db_host_input", "value"),
84
+ dash.State("db_port_input", "value"),
85
+ dash.State("db_user_input", "value"),
86
+ dash.State("db_password_input", "value")
87
+ )
88
+ def connect_button(
89
+ btn1,
90
+ connect_action:str,
91
+ db_type_value:str,
92
+ db_name_value:str,
93
+ db_host_value:str="127.0.0.1",
94
+ db_port_value:str="5432",
95
+ db_user_value:str="",
96
+ db_password_value:str=""
97
+ )->str:
98
+ r"""
99
+ Documentation here
100
+ """
101
+
102
+ if connect_action.lower()=="connect":
103
+
104
+ app.automation.set_db_config(
105
+ dbtype=db_type_value,
106
+ dbfile=db_name_value,
107
+ user=db_user_value,
108
+ password=db_password_value,
109
+ host=db_host_value,
110
+ port=db_port_value,
111
+ name=db_name_value
112
+ )
113
+ app.automation.connect_to_db(reload=True)
114
+ if app.automation.is_db_connected():
115
+
116
+ message = f"Connection to db {db_name_value} was successfully"
117
+
118
+ # OPEN MODAL TO CONFIRM CHANGES
119
+ dash.set_props("modal-body-db-connection", {"children": message})
120
+ dash.set_props("modal-db-connection", {'is_open': True})
121
+ dash.set_props("connect_disconnect_db_button", {"children": "Disconnect"})
122
+ else:
123
+
124
+ db_config = app.automation.get_db_config()
125
+ app.automation.disconnect_to_db()
126
+ dash.set_props("connect_disconnect_db_button", {"children": "Connect"})
127
+ if db_config['dbtype']=="sqlite":
128
+
129
+ dash.set_props("db_type_input", {'value': ""})
130
+ dash.set_props("db_name_input", {'value': ""})
131
+ dash.set_props("db_type_input", {'disabled': False})
132
+ dash.set_props("db_name_input", {'disabled': False})
133
+
134
+ else:
135
+
136
+ dash.set_props("db_type_input", {'value': ""})
137
+ dash.set_props("db_name_input", {'value': db_config['name']})
138
+ dash.set_props("db_host_input", {'value': db_config['host']})
139
+ dash.set_props("db_port_input", {'value': db_config['port']})
140
+ dash.set_props("db_user_input", {'value': db_config['user']})
141
+ dash.set_props("db_password_input", {'value': db_config['password']})
142
+
143
+
144
+ dash.set_props("db_type_input", {'disabled': False})
145
+ dash.set_props("db_name_input", {'disabled': False})
146
+ dash.set_props("db_host_input", {'disabled': False})
147
+ dash.set_props("db_port_input", {'disabled': False})
148
+ dash.set_props("db_user_input", {'disabled': False})
149
+ dash.set_props("db_password_input", {'disabled': False})
150
+
151
+
152
+ @app.callback(
153
+ dash.Output("modal-db-connection", "is_open"),
154
+ dash.Input("close-model-db-connection", "n_clicks"),
155
+ [dash.State("modal-db-connection", "is_open")],
156
+ )
157
+ def toggle_modal(n, is_open):
158
+ r"""
159
+ Documentation here
160
+ """
161
+ if n:
162
+
163
+ return not is_open
164
+
165
+ return is_open
166
+
167
+ @app.callback(
168
+ dash.State('db_type_input', 'value'),
169
+ dash.Input('database_page', 'pathname')
170
+ )
171
+ def connection_notification(db_type, pathname):
172
+ r"""
173
+ Documentation here
174
+ """
175
+ if pathname=="/database":
176
+
177
+ if app.automation.is_db_connected():
178
+
179
+ dash.set_props("connect_disconnect_db_button", {'children': "Disconnect"})
180
+ db_config = app.automation.get_db_config()
181
+
182
+ if db_config:
183
+
184
+ if db_config['dbtype']=="sqlite":
185
+
186
+ dash.set_props("db_type_input", {'value': "sqlite"})
187
+ dash.set_props("db_name_input", {'value': db_config['dbfile']})
188
+
189
+ else:
190
+
191
+ dash.set_props("db_type_input", {'value': db_config['dbtype']})
192
+ dash.set_props("db_name_input", {'value': db_config['name']})
193
+ dash.set_props("db_host_input", {'value': db_config['host']})
194
+ dash.set_props("db_port_input", {'value': db_config['port']})
195
+ dash.set_props("db_user_input", {'value': db_config['user']})
196
+ dash.set_props("db_password_input", {'value': db_config['password']})
197
+
198
+ dash.set_props("db_type_input", {'disabled': True})
199
+ dash.set_props("db_name_input", {'disabled': True})
200
+ dash.set_props("db_host_input", {'disabled': True})
201
+ dash.set_props("db_port_input", {'disabled': True})
202
+ dash.set_props("db_user_input", {'disabled': True})
203
+ dash.set_props("db_password_input", {'disabled': True})
204
+
205
+ else:
206
+
207
+ dash.set_props("connect_disconnect_db_button", {'children': "Connect"})
208
+ if db_type=="sqlite":
209
+ dash.set_props("db_type_input", {'disabled': False})
210
+ dash.set_props("db_name_input", {'disabled': False})
211
+ dash.set_props("db_host_input", {'disabled': True})
212
+ dash.set_props("db_port_input", {'disabled': True})
213
+ dash.set_props("db_user_input", {'disabled': True})
214
+ dash.set_props("db_password_input", {'disabled': True})
215
+
216
+ else:
217
+ dash.set_props("db_type_input", {'disabled': False})
218
+ dash.set_props("db_name_input", {'disabled': False})
219
+ dash.set_props("db_host_input", {'disabled': False})
220
+ dash.set_props("db_port_input", {'disabled': False})
221
+ dash.set_props("db_user_input", {'disabled': False})
222
+ dash.set_props("db_password_input", {'disabled': False})
@@ -0,0 +1,238 @@
1
+ from math import ceil
2
+ import dash
3
+ from ...utils import find_differences_between_lists
4
+ import plotly.graph_objects as go
5
+ from ...tags import CVTEngine
6
+
7
+ cvt = CVTEngine()
8
+
9
+
10
+ def init_callback(app:dash.Dash):
11
+
12
+ @app.callback(
13
+ dash.Output('filter_tags_dropdown', 'options'),
14
+ dash.Input('filter_page', 'pathname'),
15
+ prevent_initial_call=True
16
+ )
17
+ def display_page(pathname):
18
+ r"""
19
+ Documentation here
20
+ """
21
+ if pathname=="/filter":
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('filter_trends_figure', 'figure'),
31
+ dash.Input('timestamp-interval', 'n_intervals'),
32
+ dash.State('filter_tags_dropdown', 'value'),
33
+ prevent_initial_call=True
34
+ )
35
+ def fig_tags(n_intervals, values):
36
+ r"""
37
+ Documentation here
38
+ """
39
+ if values:
40
+
41
+ fig = go.Figure()
42
+ counter_axis = 0
43
+ labels = dict()
44
+ units = list()
45
+
46
+ for tag_name in values:
47
+
48
+ timestamp = app.automation.das.buffer[tag_name]["timestamp"]
49
+ values = app.automation.das.buffer[tag_name]["values"]
50
+ unit = app.automation.das.buffer[tag_name]["unit"]
51
+
52
+ if unit not in units:
53
+ counter_axis += 1
54
+ units.append(unit)
55
+
56
+ if counter_axis==1:
57
+
58
+ fig.add_trace(go.Scatter(x=timestamp, y=values, name=tag_name))
59
+ labels["yaxis"] = {
60
+ "title": unit
61
+ }
62
+ else:
63
+
64
+ fig.add_trace(go.Scatter(x=timestamp, y=values, name=tag_name, yaxis=f"y{counter_axis}"))
65
+ labels[f"yaxis{counter_axis}"] = {
66
+ "title": unit,
67
+ "anchor": "free",
68
+ "overlaying": "y",
69
+ "autoshift": True
70
+ }
71
+
72
+ fig.update_layout(**labels)
73
+
74
+ return fig
75
+
76
+ return dash.no_update
77
+
78
+
79
+ @app.callback(
80
+ dash.Output('filter_cvt_datatable', 'data', allow_duplicate=True),
81
+ dash.Input('filter_page', 'pathname'),
82
+ dash.Input('filter_tags_dropdown', 'value'),
83
+ prevent_initial_call=True
84
+ )
85
+ def data_tags(pathname, values):
86
+ r"""
87
+ Documentation here
88
+ """
89
+ if values:
90
+
91
+ data = list()
92
+ for tag_name in values:
93
+
94
+ tag = app.automation.cvt.get_tag_by_name(name=tag_name)
95
+
96
+ data.append({
97
+ "id": tag.id,
98
+ "display_name": tag.display_name,
99
+ "gaussian_filter": tag.gaussian_filter,
100
+ "threshold": tag.gaussian_filter_threshold,
101
+ "R-value": tag.gaussian_filter_r_value * 100.0
102
+ })
103
+
104
+ return data
105
+
106
+ return dash.no_update
107
+
108
+
109
+ @app.callback(
110
+ dash.Input('filter_last_values_dropdown', 'value'),
111
+ dash.State('filter_tags_dropdown', 'value'),
112
+ prevent_initial_call=True
113
+ )
114
+ def last_values(last_values, tags):
115
+ r"""
116
+ Documentation here
117
+ """
118
+ for tag_name in tags:
119
+
120
+ # COMPUTATION OF MAX LENGTH OF THE BUFFER
121
+ buffer_size = get_buffer_size(tag_name=tag_name, last_values=last_values)
122
+ app.automation.das.buffer[tag_name]["timestamp"].size = buffer_size
123
+ app.automation.das.buffer[tag_name]["values"].size = buffer_size
124
+
125
+ def get_buffer_size(tag_name:str, last_values:int):
126
+ r"""
127
+ Documentation here
128
+ """
129
+ tag = cvt.get_tag_by_name(name=tag_name)
130
+ scan_time = tag.get_scan_time() # Milliseconds
131
+
132
+ if not scan_time:
133
+
134
+ current_timestamp = app.automation.das.buffer[tag_name]["timestamp"].current()
135
+ previous_last = app.automation.das.buffer[tag_name]["timestamp"].previous_current()
136
+ dt = current_timestamp - previous_last
137
+ return ceil(last_values / dt.total_seconds())
138
+
139
+ scan_time = scan_time / 1000
140
+ return ceil(last_values / scan_time)
141
+
142
+
143
+ @app.callback(
144
+ dash.Output('filter_cvt_datatable', 'data'),
145
+ dash.Input('filter_cvt_datatable', 'data_timestamp'),
146
+ dash.State('filter_cvt_datatable', 'data_previous'),
147
+ dash.State('filter_cvt_datatable', 'data'),
148
+ dash.State('filter_tags_dropdown', 'value'),
149
+ )
150
+ def update_tags(timestamp, previous, current, tags):
151
+
152
+ message = None
153
+
154
+ if timestamp:
155
+
156
+ data = list()
157
+ to_updates = find_differences_between_lists(previous, current)
158
+ tag_to_update = to_updates[0]
159
+ tag_id = tag_to_update.pop("id")
160
+
161
+ if message:
162
+
163
+ dash.set_props("modal-error-filter-tags-body", {"children": message})
164
+ dash.set_props("modal-error-filter-tags", {'is_open': True})
165
+ for tag_name in tags:
166
+
167
+ tag = app.automation.cvt.get_tag_by_name(name=tag_name)
168
+
169
+ data.append({
170
+ "id": tag.id,
171
+ "display_name": tag.display_name,
172
+ "gaussian_filter": tag.gaussian_filter,
173
+ "threshold": tag.gaussian_filter_threshold,
174
+ "R-value": tag.gaussian_filter_r_value * 100.0
175
+ })
176
+
177
+ return data
178
+
179
+ if "segment" in tag_to_update:
180
+
181
+ manufacturer_segment = tag_to_update['segment'].split("->")
182
+ manufacturer = manufacturer_segment[0]
183
+ segment = manufacturer_segment[1]
184
+ tag_to_update.update({
185
+ "segment": segment,
186
+ "manufacturer": manufacturer
187
+ })
188
+
189
+ tag, message = app.automation.update_tag(id=tag_id, **tag_to_update)
190
+
191
+ if not tag:
192
+
193
+ dash.set_props("modal-error-filter-tags-body", {"children": message})
194
+ dash.set_props("modal-error-filter-tags", {'is_open': True})
195
+
196
+ for tag_name in tags:
197
+
198
+ tag = app.automation.cvt.get_tag_by_name(name=tag_name)
199
+
200
+ data.append({
201
+ "id": tag.id,
202
+ "display_name": tag.display_name,
203
+ "gaussian_filter": tag.gaussian_filter,
204
+ "threshold": tag.gaussian_filter_threshold,
205
+ "R-value": tag.gaussian_filter_r_value * 100.0
206
+ })
207
+
208
+ return data
209
+
210
+ @app.callback(
211
+ dash.Output("modal-update-filter-tags", "is_open"),
212
+ dash.Input("close-modal-update-filter-tags", "n_clicks"),
213
+ [dash.State("modal-update-filter-tags", "is_open")],
214
+ )
215
+ def close_error_button(n, is_open):
216
+ r"""
217
+ Documentation here
218
+ """
219
+ if n:
220
+
221
+ return not is_open
222
+
223
+ return is_open
224
+
225
+ @app.callback(
226
+ dash.Output("modal-success-filter-tags", "is_open"),
227
+ dash.Input("close-modal-success-filter-tags", "n_clicks"),
228
+ [dash.State("modal-success-filter-tags", "is_open")],
229
+ )
230
+ def close_success_button(n, is_open):
231
+ r"""
232
+ Documentation here
233
+ """
234
+ if n:
235
+
236
+ return not is_open
237
+
238
+ return is_open
@@ -0,0 +1,29 @@
1
+ import dash
2
+
3
+ def init_callback(app:dash.Dash):
4
+
5
+ @app.callback(
6
+ dash.Output('machines_datatable', 'data'),
7
+ dash.Input('machines_page', 'pathname'),
8
+ prevent_initial_call=True
9
+ )
10
+ def display_page(pathname):
11
+ r"""
12
+ Documentation here
13
+ """
14
+ if pathname=="/machines":
15
+
16
+ data = app.machines_table_data()
17
+ return data
18
+
19
+ return dash.no_update
20
+
21
+ @app.callback(
22
+ dash.Output('machines_datatable', 'data', allow_duplicate=True),
23
+ dash.Input('timestamp-interval', 'n_intervals'),
24
+ prevent_initial_call=False
25
+ )
26
+ def update_table( n_intervals):
27
+
28
+ data = app.machines_table_data()
29
+ return data