enode-host 0.1.0__py3-none-any.whl → 0.1.2__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.
- enode_host/async_socket.py +42 -20
- enode_host/cli.py +132 -0
- enode_host/constants.py +1 -0
- enode_host/framed_mesh.py +9 -2
- enode_host/gui_framed.py +11 -2
- enode_host/model.py +161 -9
- enode_host/protocol.py +11 -1
- enode_host/view.py +471 -99
- {enode_host-0.1.0.dist-info → enode_host-0.1.2.dist-info}/METADATA +7 -1
- {enode_host-0.1.0.dist-info → enode_host-0.1.2.dist-info}/RECORD +13 -13
- {enode_host-0.1.0.dist-info → enode_host-0.1.2.dist-info}/WHEEL +0 -0
- {enode_host-0.1.0.dist-info → enode_host-0.1.2.dist-info}/entry_points.txt +0 -0
- {enode_host-0.1.0.dist-info → enode_host-0.1.2.dist-info}/top_level.txt +0 -0
enode_host/view.py
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
## PLEASE DO *NOT* EDIT THIS FILE!
|
|
14
14
|
###########################################################################
|
|
15
15
|
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
16
18
|
import wx
|
|
17
19
|
import wx.xrc
|
|
18
20
|
import wx.grid
|
|
@@ -28,9 +30,23 @@ from matplotlib.backends.backend_wxagg import \
|
|
|
28
30
|
NavigationToolbar2WxAgg as NavigationToolbar
|
|
29
31
|
from matplotlib.figure import Figure
|
|
30
32
|
import matplotlib.dates as mdates
|
|
33
|
+
import numpy as np
|
|
31
34
|
import wx.grid as gridlib
|
|
35
|
+
def _env_truthy(name: str) -> bool:
|
|
36
|
+
value = os.environ.get(name)
|
|
37
|
+
if not value:
|
|
38
|
+
return False
|
|
39
|
+
return value.strip().lower() in {"1", "true", "yes", "y", "on"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if sys.platform.startswith("linux"):
|
|
43
|
+
os.environ.setdefault("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
|
|
44
|
+
|
|
32
45
|
try:
|
|
33
|
-
|
|
46
|
+
if _env_truthy("ENODE_DISABLE_WEBVIEW"):
|
|
47
|
+
webview = None
|
|
48
|
+
else:
|
|
49
|
+
import wx.html2 as webview
|
|
34
50
|
except ImportError:
|
|
35
51
|
webview = None
|
|
36
52
|
try:
|
|
@@ -52,6 +68,7 @@ try:
|
|
|
52
68
|
PPS_AGE_LIMIT_SEC,
|
|
53
69
|
PPS_AGE_UPDATE_MS,
|
|
54
70
|
PPS_COL,
|
|
71
|
+
BAT_COL,
|
|
55
72
|
)
|
|
56
73
|
except ImportError:
|
|
57
74
|
from constants import (
|
|
@@ -62,6 +79,7 @@ except ImportError:
|
|
|
62
79
|
PPS_AGE_LIMIT_SEC,
|
|
63
80
|
PPS_AGE_UPDATE_MS,
|
|
64
81
|
PPS_COL,
|
|
82
|
+
BAT_COL,
|
|
65
83
|
)
|
|
66
84
|
import pandas as pd
|
|
67
85
|
|
|
@@ -147,6 +165,8 @@ class TableUpdater:
|
|
|
147
165
|
self.grid.SetColSize(self.col_index[RSSI_COL], 40)
|
|
148
166
|
self.grid.SetColSize(self.col_index["Children"], 70)
|
|
149
167
|
self.grid.SetColSize(self.col_index[PPS_COL], 40)
|
|
168
|
+
if BAT_COL in self.col_index:
|
|
169
|
+
self.grid.SetColSize(self.col_index[BAT_COL], 50)
|
|
150
170
|
self.grid.SetColSize(self.col_index["CMD"], 200)
|
|
151
171
|
for hidden in [
|
|
152
172
|
"PPS-time",
|
|
@@ -226,6 +246,7 @@ class PlotUpdater:
|
|
|
226
246
|
self.timehistory_lines = {}
|
|
227
247
|
self.psd_lines = {}
|
|
228
248
|
self._psd_legend_labels = {}
|
|
249
|
+
self.psd_auto_x = True
|
|
229
250
|
|
|
230
251
|
def init_plot(self):
|
|
231
252
|
self.timehistory_lines = {}
|
|
@@ -247,7 +268,8 @@ class PlotUpdater:
|
|
|
247
268
|
self.axes1.set_xlabel('Time')
|
|
248
269
|
self.axes1.set_ylabel('Acceleration (g)')
|
|
249
270
|
self.axes1.grid(True)
|
|
250
|
-
self.
|
|
271
|
+
if not self.view.time_y_auto:
|
|
272
|
+
self.axes1.set_ylim(*self.view.time_y_limits)
|
|
251
273
|
self.axes1.tick_params(axis='x', labelbottom=True, pad=2)
|
|
252
274
|
self.axes1.legend()
|
|
253
275
|
self.view.m_fig.figure.autofmt_xdate(rotation=0, ha='right')
|
|
@@ -265,11 +287,16 @@ class PlotUpdater:
|
|
|
265
287
|
self.axes2.set_xlabel('Frequency (Hz)')
|
|
266
288
|
self.axes2.set_ylabel('ASD (g/sq(Hz))')
|
|
267
289
|
self.axes2.grid(True)
|
|
268
|
-
self.
|
|
290
|
+
if not self.view.psd_y_auto:
|
|
291
|
+
self.axes2.set_ylim(*self.view.psd_y_limits)
|
|
269
292
|
self.axes2.set_xlim(0, 25)
|
|
293
|
+
self.psd_auto_x = True
|
|
294
|
+
self.axes2.callbacks.connect('xlim_changed', self._on_psd_xlim_changed)
|
|
270
295
|
self.axes2.legend()
|
|
271
296
|
|
|
272
297
|
def figure_update(self):
|
|
298
|
+
min_time_y = None
|
|
299
|
+
max_time_y = None
|
|
273
300
|
for _, row in self.model.mesh_status_data.iterrows():
|
|
274
301
|
node_id = row['nodeID']
|
|
275
302
|
for idx, ch in enumerate(['X', 'Y', 'Z']):
|
|
@@ -278,30 +305,74 @@ class PlotUpdater:
|
|
|
278
305
|
with self.model.plot_mutex[node_id]:
|
|
279
306
|
if len(self.model.timehistory_xdata[node_id]) > 1:
|
|
280
307
|
self.timehistory_lines[node_id_ch].set_xdata(self.model.timehistory_xdata[node_id])
|
|
281
|
-
self.
|
|
308
|
+
series = self.model.timehistory_ydata[node_id][:, idx]
|
|
309
|
+
self.timehistory_lines[node_id_ch].set_ydata(series)
|
|
310
|
+
if self.view.time_y_auto and series is not None and len(series) > 0:
|
|
311
|
+
finite = series[np.isfinite(series)]
|
|
312
|
+
if finite.size:
|
|
313
|
+
smin = float(finite.min())
|
|
314
|
+
smax = float(finite.max())
|
|
315
|
+
min_time_y = smin if min_time_y is None else min(min_time_y, smin)
|
|
316
|
+
max_time_y = smax if max_time_y is None else max(max_time_y, smax)
|
|
282
317
|
if self.model.timehistory_xlim:
|
|
283
318
|
self.axes1.set_xlim(self.model.timehistory_xlim)
|
|
319
|
+
if self.view.time_y_auto and min_time_y is not None and max_time_y is not None:
|
|
320
|
+
if min_time_y == max_time_y:
|
|
321
|
+
pad = 1.0 if min_time_y == 0 else abs(min_time_y) * 0.1
|
|
322
|
+
else:
|
|
323
|
+
pad = (max_time_y - min_time_y) * 0.05
|
|
324
|
+
self.axes1.set_ylim(min_time_y - pad, max_time_y + pad)
|
|
284
325
|
try:
|
|
285
326
|
self.view.m_fig.figure.canvas.draw()
|
|
286
327
|
except Exception:
|
|
287
328
|
raise
|
|
288
329
|
|
|
330
|
+
max_f = None
|
|
331
|
+
min_psd_y = None
|
|
332
|
+
max_psd_y = None
|
|
289
333
|
for _, row in self.model.mesh_status_data.iterrows():
|
|
290
334
|
node_id = row['nodeID']
|
|
335
|
+
f = self.model.psd_xdata.get(node_id)
|
|
291
336
|
for idx, ch in enumerate(['X', 'Y', 'Z']):
|
|
292
337
|
node_id_ch = row['Node ID'] + ch
|
|
293
338
|
if node_id_ch in self.psd_lines and node_id in self.model.psd_ydata:
|
|
294
339
|
with self.model.plot_mutex[node_id]:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
340
|
+
if f is not None and len(f) > 0:
|
|
341
|
+
self.psd_lines[node_id_ch].set_xdata(f)
|
|
342
|
+
last_f = f[-1]
|
|
343
|
+
if max_f is None or last_f > max_f:
|
|
344
|
+
max_f = last_f
|
|
345
|
+
series = self.model.psd_ydata[node_id][:, idx]
|
|
346
|
+
self.psd_lines[node_id_ch].set_ydata(series)
|
|
347
|
+
if self.view.psd_y_auto and series is not None and len(series) > 0:
|
|
348
|
+
finite = series[np.isfinite(series)]
|
|
349
|
+
finite = finite[finite > 0]
|
|
350
|
+
if finite.size:
|
|
351
|
+
smin = float(finite.min())
|
|
352
|
+
smax = float(finite.max())
|
|
353
|
+
min_psd_y = smin if min_psd_y is None else min(min_psd_y, smin)
|
|
354
|
+
max_psd_y = smax if max_psd_y is None else max(max_psd_y, smax)
|
|
355
|
+
|
|
356
|
+
if self.psd_auto_x and max_f is not None:
|
|
357
|
+
self.axes2.set_xlim(0, round(max_f))
|
|
358
|
+
if self.view.psd_y_auto and min_psd_y is not None and max_psd_y is not None:
|
|
359
|
+
low = min_psd_y * 0.8
|
|
360
|
+
high = max_psd_y * 1.2
|
|
361
|
+
if low <= 0:
|
|
362
|
+
low = min_psd_y * 0.5
|
|
363
|
+
if low > 0 and high > low:
|
|
364
|
+
self.axes2.set_ylim(low, high)
|
|
300
365
|
try:
|
|
301
366
|
self.view.m_fig.figure.canvas.draw()
|
|
302
367
|
except Exception:
|
|
303
368
|
raise
|
|
304
369
|
|
|
370
|
+
def _on_psd_xlim_changed(self, _axes):
|
|
371
|
+
# Disable auto-scaling after user zoom/pan.
|
|
372
|
+
if not self.psd_auto_x:
|
|
373
|
+
return
|
|
374
|
+
self.psd_auto_x = False
|
|
375
|
+
|
|
305
376
|
def update_psd_legend(self):
|
|
306
377
|
if not self.psd_lines:
|
|
307
378
|
return
|
|
@@ -348,6 +419,7 @@ class MergedPlotUpdater:
|
|
|
348
419
|
self.axes2 = view.axes2_merged
|
|
349
420
|
self.timehistory_lines = {}
|
|
350
421
|
self.psd_lines = {}
|
|
422
|
+
self.psd_auto_x = True
|
|
351
423
|
|
|
352
424
|
def _node_label(self, node_id):
|
|
353
425
|
return str(node_id).upper()
|
|
@@ -370,7 +442,8 @@ class MergedPlotUpdater:
|
|
|
370
442
|
self.axes1.set_xlabel('Time')
|
|
371
443
|
self.axes1.set_ylabel('Acceleration (g)')
|
|
372
444
|
self.axes1.grid(True)
|
|
373
|
-
self.
|
|
445
|
+
if not self.view.time_y_auto:
|
|
446
|
+
self.axes1.set_ylim(*self.view.time_y_limits)
|
|
374
447
|
self.axes1.tick_params(axis='x', labelbottom=True, pad=2)
|
|
375
448
|
self.axes1.legend()
|
|
376
449
|
self.view.m_fig_merged.figure.autofmt_xdate(rotation=0, ha='right')
|
|
@@ -387,11 +460,16 @@ class MergedPlotUpdater:
|
|
|
387
460
|
self.axes2.set_xlabel('Frequency (Hz)')
|
|
388
461
|
self.axes2.set_ylabel('ASD (g/sq(Hz))')
|
|
389
462
|
self.axes2.grid(True)
|
|
390
|
-
self.
|
|
463
|
+
if not self.view.psd_y_auto:
|
|
464
|
+
self.axes2.set_ylim(*self.view.psd_y_limits)
|
|
391
465
|
self.axes2.set_xlim(0, 25)
|
|
466
|
+
self.psd_auto_x = True
|
|
467
|
+
self.axes2.callbacks.connect('xlim_changed', self._on_psd_xlim_changed)
|
|
392
468
|
self.axes2.legend()
|
|
393
469
|
|
|
394
470
|
def figure_update(self):
|
|
471
|
+
min_time_y = None
|
|
472
|
+
max_time_y = None
|
|
395
473
|
for node_id in self.model.merged_node_ids:
|
|
396
474
|
node_label = self._node_label(node_id)
|
|
397
475
|
xdata = self.model.merged_timehistory_xdata.get(node_id)
|
|
@@ -403,14 +481,30 @@ class MergedPlotUpdater:
|
|
|
403
481
|
line = self.timehistory_lines.get(label)
|
|
404
482
|
if line is not None:
|
|
405
483
|
line.set_xdata(xdata)
|
|
406
|
-
|
|
484
|
+
series = ydata[:, idx]
|
|
485
|
+
line.set_ydata(series)
|
|
486
|
+
if self.view.time_y_auto and series is not None and len(series) > 0:
|
|
487
|
+
finite = series[np.isfinite(series)]
|
|
488
|
+
if finite.size:
|
|
489
|
+
smin = float(finite.min())
|
|
490
|
+
smax = float(finite.max())
|
|
491
|
+
min_time_y = smin if min_time_y is None else min(min_time_y, smin)
|
|
492
|
+
max_time_y = smax if max_time_y is None else max(max_time_y, smax)
|
|
407
493
|
if self.model.merged_timehistory_xlim:
|
|
408
494
|
self.axes1.set_xlim(self.model.merged_timehistory_xlim)
|
|
495
|
+
if self.view.time_y_auto and min_time_y is not None and max_time_y is not None:
|
|
496
|
+
if min_time_y == max_time_y:
|
|
497
|
+
pad = 1.0 if min_time_y == 0 else abs(min_time_y) * 0.1
|
|
498
|
+
else:
|
|
499
|
+
pad = (max_time_y - min_time_y) * 0.05
|
|
500
|
+
self.axes1.set_ylim(min_time_y - pad, max_time_y + pad)
|
|
409
501
|
try:
|
|
410
502
|
self.view.m_fig_merged.figure.canvas.draw()
|
|
411
503
|
except Exception:
|
|
412
504
|
raise
|
|
413
505
|
|
|
506
|
+
min_psd_y = None
|
|
507
|
+
max_psd_y = None
|
|
414
508
|
f = self.model.merged_psd_xdata
|
|
415
509
|
if f is None:
|
|
416
510
|
f = []
|
|
@@ -424,15 +518,37 @@ class MergedPlotUpdater:
|
|
|
424
518
|
line = self.psd_lines.get(label)
|
|
425
519
|
if line is not None:
|
|
426
520
|
line.set_xdata(f)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
521
|
+
series = ydata[:, idx]
|
|
522
|
+
line.set_ydata(series)
|
|
523
|
+
if self.view.psd_y_auto and series is not None and len(series) > 0:
|
|
524
|
+
finite = series[np.isfinite(series)]
|
|
525
|
+
finite = finite[finite > 0]
|
|
526
|
+
if finite.size:
|
|
527
|
+
smin = float(finite.min())
|
|
528
|
+
smax = float(finite.max())
|
|
529
|
+
min_psd_y = smin if min_psd_y is None else min(min_psd_y, smin)
|
|
530
|
+
max_psd_y = smax if max_psd_y is None else max(max_psd_y, smax)
|
|
531
|
+
|
|
532
|
+
if self.psd_auto_x and len(f) > 0:
|
|
430
533
|
self.axes2.set_xlim(0, round(f[-1]))
|
|
534
|
+
if self.view.psd_y_auto and min_psd_y is not None and max_psd_y is not None:
|
|
535
|
+
low = min_psd_y * 0.8
|
|
536
|
+
high = max_psd_y * 1.2
|
|
537
|
+
if low <= 0:
|
|
538
|
+
low = min_psd_y * 0.5
|
|
539
|
+
if low > 0 and high > low:
|
|
540
|
+
self.axes2.set_ylim(low, high)
|
|
431
541
|
try:
|
|
432
542
|
self.view.m_fig_merged.figure.canvas.draw()
|
|
433
543
|
except Exception:
|
|
434
544
|
raise
|
|
435
545
|
|
|
546
|
+
def _on_psd_xlim_changed(self, _axes):
|
|
547
|
+
# Disable auto-scaling after user zoom/pan.
|
|
548
|
+
if not self.psd_auto_x:
|
|
549
|
+
return
|
|
550
|
+
self.psd_auto_x = False
|
|
551
|
+
|
|
436
552
|
###########################################################################
|
|
437
553
|
## Class View
|
|
438
554
|
###########################################################################
|
|
@@ -447,12 +563,19 @@ class View(wx.Frame):
|
|
|
447
563
|
self.model = self.controller.model
|
|
448
564
|
if self.model is not None:
|
|
449
565
|
self.timespan_length = self.model.timespan_length
|
|
566
|
+
else:
|
|
567
|
+
self.timespan_length = 30
|
|
450
568
|
self.m_table = None
|
|
451
569
|
self.table_updater = None
|
|
452
570
|
self.plot_updater = None
|
|
453
571
|
self.merged_plot_updater = None
|
|
454
572
|
self.PPS_outdate_check = True
|
|
455
573
|
self.channel_selection = [False, True, False]
|
|
574
|
+
self.time_y_auto = False
|
|
575
|
+
self.time_y_limits = (-2.0, 2.0)
|
|
576
|
+
self.psd_y_auto = False
|
|
577
|
+
self.psd_y_limits = (1e-6, 1e1)
|
|
578
|
+
self._load_plot_options()
|
|
456
579
|
|
|
457
580
|
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
|
|
458
581
|
|
|
@@ -479,17 +602,14 @@ class View(wx.Frame):
|
|
|
479
602
|
self.m_panel_status = wx.Panel(self.m_left_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
480
603
|
self.m_panel_map = wx.Panel(self.m_left_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
481
604
|
self.m_panel_plots = wx.Panel(self.m_left_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
482
|
-
self.m_panel_merged_plots = wx.Panel(self.m_left_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
483
605
|
self.m_left_notebook.AddPage(self.m_panel_nodes, _(u"Nodes"), False)
|
|
484
606
|
self.m_left_notebook.AddPage(self.m_panel_status, _(u"Mesh"), True)
|
|
485
607
|
self.m_left_notebook.AddPage(self.m_panel_map, _(u"Map"), False)
|
|
486
|
-
self.m_left_notebook.AddPage(self.m_panel_plots, _(u"
|
|
487
|
-
self.m_left_notebook.AddPage(self.m_panel_merged_plots, _(u"Merged Plot"), False)
|
|
608
|
+
self.m_left_notebook.AddPage(self.m_panel_plots, _(u"Plots"), False)
|
|
488
609
|
self.tab_index_nodes = 0
|
|
489
610
|
self.tab_index_status = 1
|
|
490
611
|
self.tab_index_map = 2
|
|
491
612
|
self.tab_index_rt_plots = 3
|
|
492
|
-
self.tab_index_merged_plots = 4
|
|
493
613
|
|
|
494
614
|
# Sensor Nodes definition tab
|
|
495
615
|
nodes_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
@@ -505,7 +625,7 @@ class View(wx.Frame):
|
|
|
505
625
|
self.controller.model.options.get('acc_nums_txt', '[1:3]'),
|
|
506
626
|
wx.DefaultPosition,
|
|
507
627
|
wx.DefaultSize,
|
|
508
|
-
|
|
628
|
+
wx.TE_PROCESS_ENTER,
|
|
509
629
|
)
|
|
510
630
|
nodes_grid.Add(self.m_textCtrl_acc, 1, wx.EXPAND)
|
|
511
631
|
|
|
@@ -517,7 +637,7 @@ class View(wx.Frame):
|
|
|
517
637
|
self.controller.model.options.get('tmp_nums_txt', '[]'),
|
|
518
638
|
wx.DefaultPosition,
|
|
519
639
|
wx.DefaultSize,
|
|
520
|
-
|
|
640
|
+
wx.TE_PROCESS_ENTER,
|
|
521
641
|
)
|
|
522
642
|
nodes_grid.Add(self.m_textCtrl_tmp, 1, wx.EXPAND)
|
|
523
643
|
|
|
@@ -529,7 +649,7 @@ class View(wx.Frame):
|
|
|
529
649
|
self.controller.model.options.get('str_nums_txt', '[]'),
|
|
530
650
|
wx.DefaultPosition,
|
|
531
651
|
wx.DefaultSize,
|
|
532
|
-
|
|
652
|
+
wx.TE_PROCESS_ENTER,
|
|
533
653
|
)
|
|
534
654
|
nodes_grid.Add(self.m_textCtrl_str, 1, wx.EXPAND)
|
|
535
655
|
|
|
@@ -541,7 +661,7 @@ class View(wx.Frame):
|
|
|
541
661
|
self.controller.model.options.get('veh_nums_txt', '[]'),
|
|
542
662
|
wx.DefaultPosition,
|
|
543
663
|
wx.DefaultSize,
|
|
544
|
-
|
|
664
|
+
wx.TE_PROCESS_ENTER,
|
|
545
665
|
)
|
|
546
666
|
nodes_grid.Add(self.m_textCtrl_veh, 1, wx.EXPAND)
|
|
547
667
|
|
|
@@ -573,6 +693,7 @@ class View(wx.Frame):
|
|
|
573
693
|
self.m_map = webview.WebView.New(self.m_panel_map)
|
|
574
694
|
map_sizer.Add(self.m_map, 1, wx.ALL | wx.EXPAND, 5)
|
|
575
695
|
self.m_map_loaded = False
|
|
696
|
+
self._map_nodes = set()
|
|
576
697
|
self.m_map.Bind(webview.EVT_WEBVIEW_LOADED, self.on_map_loaded)
|
|
577
698
|
self._load_map()
|
|
578
699
|
else:
|
|
@@ -586,14 +707,88 @@ class View(wx.Frame):
|
|
|
586
707
|
self.m_panel_map.SetSizer(map_sizer)
|
|
587
708
|
|
|
588
709
|
plots_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
710
|
+
|
|
711
|
+
plot_controls_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
712
|
+
|
|
713
|
+
self.m_button_clf = wx.Button(self.m_panel_plots, wx.ID_ANY, _(u"Clear Figure"), wx.DefaultPosition, wx.DefaultSize, 0)
|
|
714
|
+
plot_controls_sizer.Add(self.m_button_clf, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
715
|
+
|
|
716
|
+
plot_controls_sizer.Add(wx.StaticText(self.m_panel_plots, wx.ID_ANY, _(u"Channels:")), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
717
|
+
self.m_check_chx = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"X"))
|
|
718
|
+
self.m_check_chy = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"Y"))
|
|
719
|
+
self.m_check_chz = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"Z"))
|
|
720
|
+
self.m_check_chx.SetValue(self.channel_selection[0])
|
|
721
|
+
self.m_check_chy.SetValue(self.channel_selection[1])
|
|
722
|
+
self.m_check_chz.SetValue(self.channel_selection[2])
|
|
723
|
+
plot_controls_sizer.Add(self.m_check_chx, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
724
|
+
plot_controls_sizer.Add(self.m_check_chy, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
725
|
+
plot_controls_sizer.Add(self.m_check_chz, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
726
|
+
|
|
727
|
+
plot_controls_sizer.Add(wx.StaticText(self.m_panel_plots, wx.ID_ANY, _(u"Timespan:")), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
728
|
+
self.timespan_choices = [30, 20, 10, 5, 2]
|
|
729
|
+
self.m_choice_timespan = wx.Choice(
|
|
730
|
+
self.m_panel_plots,
|
|
731
|
+
choices=[f"{v} sec" for v in self.timespan_choices],
|
|
732
|
+
)
|
|
733
|
+
if self.timespan_length in self.timespan_choices:
|
|
734
|
+
self.m_choice_timespan.SetSelection(self.timespan_choices.index(self.timespan_length))
|
|
735
|
+
else:
|
|
736
|
+
self.m_choice_timespan.SetSelection(0)
|
|
737
|
+
plot_controls_sizer.Add(self.m_choice_timespan, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
738
|
+
|
|
739
|
+
plots_sizer.Add(plot_controls_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5)
|
|
740
|
+
|
|
741
|
+
ylim_sizer = wx.FlexGridSizer(1, 6, 2, 6)
|
|
742
|
+
ylim_sizer.AddGrowableCol(2, 1)
|
|
743
|
+
ylim_sizer.AddGrowableCol(5, 1)
|
|
744
|
+
|
|
745
|
+
self.m_check_time_y_auto = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"Time Y auto"))
|
|
746
|
+
self.m_check_time_y_auto.SetValue(self.time_y_auto)
|
|
747
|
+
ylim_sizer.Add(self.m_check_time_y_auto, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
748
|
+
ylim_sizer.Add(wx.StaticText(self.m_panel_plots, wx.ID_ANY, _(u"ylim:")), 0, wx.ALIGN_CENTER_VERTICAL)
|
|
749
|
+
self.m_text_time_ylim = wx.TextCtrl(
|
|
750
|
+
self.m_panel_plots,
|
|
751
|
+
wx.ID_ANY,
|
|
752
|
+
f"[{self.time_y_limits[0]}, {self.time_y_limits[1]}]",
|
|
753
|
+
wx.DefaultPosition,
|
|
754
|
+
wx.Size(120, -1),
|
|
755
|
+
wx.TE_PROCESS_ENTER,
|
|
756
|
+
)
|
|
757
|
+
self.m_text_time_ylim.Enable(not self.time_y_auto)
|
|
758
|
+
ylim_sizer.Add(self.m_text_time_ylim, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
759
|
+
|
|
760
|
+
self.m_check_psd_y_auto = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"ASD Y auto"))
|
|
761
|
+
self.m_check_psd_y_auto.SetValue(self.psd_y_auto)
|
|
762
|
+
ylim_sizer.Add(self.m_check_psd_y_auto, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
763
|
+
ylim_sizer.Add(wx.StaticText(self.m_panel_plots, wx.ID_ANY, _(u"ylim:")), 0, wx.ALIGN_CENTER_VERTICAL)
|
|
764
|
+
self.m_text_psd_ylim = wx.TextCtrl(
|
|
765
|
+
self.m_panel_plots,
|
|
766
|
+
wx.ID_ANY,
|
|
767
|
+
f"[{self.psd_y_limits[0]}, {self.psd_y_limits[1]}]",
|
|
768
|
+
wx.DefaultPosition,
|
|
769
|
+
wx.Size(120, -1),
|
|
770
|
+
wx.TE_PROCESS_ENTER,
|
|
771
|
+
)
|
|
772
|
+
self.m_text_psd_ylim.Enable(not self.psd_y_auto)
|
|
773
|
+
ylim_sizer.Add(self.m_text_psd_ylim, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
774
|
+
|
|
775
|
+
ylim_row = wx.BoxSizer(wx.HORIZONTAL)
|
|
776
|
+
indent = self.m_button_clf.GetBestSize().width + 8
|
|
777
|
+
ylim_row.AddSpacer(indent)
|
|
778
|
+
ylim_row.Add(ylim_sizer, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
779
|
+
plots_sizer.Add(ylim_row, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 5)
|
|
589
780
|
self.m_plot_panel = wx.Panel(self.m_panel_plots, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
590
781
|
plots_sizer.Add(self.m_plot_panel, 1, wx.ALL | wx.EXPAND, 5)
|
|
591
782
|
self.m_panel_plots.SetSizer(plots_sizer)
|
|
592
783
|
|
|
593
|
-
|
|
594
|
-
self.
|
|
595
|
-
|
|
596
|
-
self.
|
|
784
|
+
self.m_check_chx.Bind(wx.EVT_CHECKBOX, self.onChannelToggled)
|
|
785
|
+
self.m_check_chy.Bind(wx.EVT_CHECKBOX, self.onChannelToggled)
|
|
786
|
+
self.m_check_chz.Bind(wx.EVT_CHECKBOX, self.onChannelToggled)
|
|
787
|
+
self.m_choice_timespan.Bind(wx.EVT_CHOICE, self.onTimeSpanChange)
|
|
788
|
+
self.m_check_time_y_auto.Bind(wx.EVT_CHECKBOX, self.on_time_y_auto_toggle)
|
|
789
|
+
self.m_text_time_ylim.Bind(wx.EVT_TEXT_ENTER, self.on_time_ylim_enter)
|
|
790
|
+
self.m_check_psd_y_auto.Bind(wx.EVT_CHECKBOX, self.on_psd_y_auto_toggle)
|
|
791
|
+
self.m_text_psd_ylim.Bind(wx.EVT_TEXT_ENTER, self.on_psd_ylim_enter)
|
|
597
792
|
|
|
598
793
|
bSizer3.Add(self.m_left_notebook, 1, wx.ALL | wx.EXPAND, 5)
|
|
599
794
|
bSizer1.Add( bSizer3, 1, wx.EXPAND, 5 )
|
|
@@ -613,6 +808,7 @@ class View(wx.Frame):
|
|
|
613
808
|
"SD Stream Start",
|
|
614
809
|
"SD Stream Stop",
|
|
615
810
|
"SD Clear All",
|
|
811
|
+
"Shutdown",
|
|
616
812
|
],
|
|
617
813
|
)
|
|
618
814
|
cmd_choice_sizer.Add(self.m_choice_OperationMode, 0, wx.ALL | wx.CENTER, 5)
|
|
@@ -681,46 +877,6 @@ class View(wx.Frame):
|
|
|
681
877
|
self.m_menu_data.AppendSubMenu(self.m_menu_data_clear, _(u"Clear"))
|
|
682
878
|
self.m_menubar.Append(self.m_menu_data, _(u"&Data"))
|
|
683
879
|
|
|
684
|
-
self.m_menu_cmd = wx.Menu()
|
|
685
|
-
self.m_menu_cmd_sdclear = wx.MenuItem(self.m_menu_cmd, wx.ID_ANY, _(u"SD &Clear"), wx.EmptyString, wx.ITEM_NORMAL)
|
|
686
|
-
self.m_menu_cmd.Append(self.m_menu_cmd_sdclear)
|
|
687
|
-
self.m_menu_cmd_shutdown = wx.MenuItem(self.m_menu_cmd, wx.ID_ANY, _(u"&Shutdown"), wx.EmptyString, wx.ITEM_NORMAL)
|
|
688
|
-
self.m_menu_cmd.Append(self.m_menu_cmd_shutdown)
|
|
689
|
-
self.m_menubar.Append(self.m_menu_cmd, _(u"&CMD"))
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
self.m_menu_view = wx.Menu()
|
|
693
|
-
self.m_menu_view_clf = wx.MenuItem(self.m_menu_view, wx.ID_ANY, _(u"&Clear Figure"), wx.EmptyString, wx.ITEM_NORMAL)
|
|
694
|
-
self.m_menu_view.Append(self.m_menu_view_clf)
|
|
695
|
-
|
|
696
|
-
self.m_menu_view_chsel = wx.Menu()
|
|
697
|
-
self.m_menu_view_chsel_x = self.m_menu_view_chsel.AppendCheckItem(wx.ID_ANY, "X")
|
|
698
|
-
self.m_menu_view_chsel_y = self.m_menu_view_chsel.AppendCheckItem(wx.ID_ANY, "Y")
|
|
699
|
-
self.m_menu_view_chsel_z = self.m_menu_view_chsel.AppendCheckItem(wx.ID_ANY, "Z")
|
|
700
|
-
self.m_menu_view_chsel_y.Check(True)
|
|
701
|
-
self.m_menu_view.AppendSubMenu(self.m_menu_view_chsel, "Channel Selection")
|
|
702
|
-
self.Bind(wx.EVT_MENU, self.onChannelToggled, self.m_menu_view_chsel_x)
|
|
703
|
-
self.Bind(wx.EVT_MENU, self.onChannelToggled, self.m_menu_view_chsel_y)
|
|
704
|
-
self.Bind(wx.EVT_MENU, self.onChannelToggled, self.m_menu_view_chsel_z)
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
self.m_menu_view_timespan = wx.Menu()
|
|
708
|
-
self.m_menu_view_timespan_radio_30s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "30 sec")
|
|
709
|
-
self.m_menu_view_timespan_radio_20s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "20 sec")
|
|
710
|
-
self.m_menu_view_timespan_radio_10s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "10 sec")
|
|
711
|
-
self.m_menu_view_timespan_radio_05s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "5 sec")
|
|
712
|
-
self.m_menu_view_timespan_radio_02s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "2 sec")
|
|
713
|
-
self.m_menu_view_timespan_radio_30s.Check(True)
|
|
714
|
-
self.m_menu_view.AppendSubMenu(self.m_menu_view_timespan, "Timespan Select")
|
|
715
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_30s)
|
|
716
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_20s)
|
|
717
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_10s)
|
|
718
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_05s)
|
|
719
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_02s)
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
self.m_menubar.Append(self.m_menu_view, _(u"&View"))
|
|
723
|
-
|
|
724
880
|
self.m_menu_help = wx.Menu()
|
|
725
881
|
self.m_menu_help_about = wx.MenuItem(self.m_menu_help, wx.ID_ANY, _(u"&Help"), wx.EmptyString, wx.ITEM_NORMAL)
|
|
726
882
|
self.m_menu_help.Append(self.m_menu_help_about)
|
|
@@ -739,23 +895,25 @@ class View(wx.Frame):
|
|
|
739
895
|
self.plot_updater = PlotUpdater(self)
|
|
740
896
|
self.plot_updater.init_plot()
|
|
741
897
|
self.m_plot_canvas = self.m_fig.canvas
|
|
742
|
-
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
743
|
-
sizer.Add(self.m_fig, 1, wx.EXPAND)
|
|
744
|
-
self.m_plot_panel.SetSizer(sizer)
|
|
745
898
|
self.m_plot_canvas.Bind(wx.EVT_LEFT_DOWN, self._focus_plot_canvas)
|
|
746
899
|
|
|
747
|
-
self.m_fig_merged = Plot(self.
|
|
900
|
+
self.m_fig_merged = Plot(self.m_plot_panel)
|
|
748
901
|
self.axes1_merged = self.m_fig_merged.figure.add_subplot(2, 1, 1)
|
|
749
902
|
self.axes2_merged = self.m_fig_merged.figure.add_subplot(2, 1, 2)
|
|
750
903
|
self.m_fig_merged.figure.subplots_adjust(hspace=0.28, bottom=0.08, top=0.95)
|
|
751
904
|
self.merged_plot_updater = MergedPlotUpdater(self)
|
|
752
905
|
self.merged_plot_updater.init_plot()
|
|
753
906
|
self.m_plot_canvas_merged = self.m_fig_merged.canvas
|
|
754
|
-
merged_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
755
|
-
merged_sizer.Add(self.m_fig_merged, 1, wx.EXPAND)
|
|
756
|
-
self.m_plot_panel_merged.SetSizer(merged_sizer)
|
|
757
907
|
self.m_plot_canvas_merged.Bind(wx.EVT_LEFT_DOWN, self._focus_plot_canvas)
|
|
758
908
|
|
|
909
|
+
plot_stack = wx.BoxSizer(wx.VERTICAL)
|
|
910
|
+
plot_stack.Add(self.m_fig, 1, wx.EXPAND)
|
|
911
|
+
plot_stack.Add(self.m_fig_merged, 1, wx.EXPAND)
|
|
912
|
+
self.m_fig_merged.Hide()
|
|
913
|
+
self.m_plot_panel.SetSizer(plot_stack)
|
|
914
|
+
self._plot_stack = plot_stack
|
|
915
|
+
self._active_plot = "rt"
|
|
916
|
+
|
|
759
917
|
# UPDATE TIMERS
|
|
760
918
|
|
|
761
919
|
self._last_plot_version = 0
|
|
@@ -796,14 +954,6 @@ class View(wx.Frame):
|
|
|
796
954
|
self.m_left_notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_tab_changed)
|
|
797
955
|
|
|
798
956
|
|
|
799
|
-
self.timespan_map = {
|
|
800
|
-
self.m_menu_view_timespan_radio_30s.GetId(): 30,
|
|
801
|
-
self.m_menu_view_timespan_radio_20s.GetId(): 20,
|
|
802
|
-
self.m_menu_view_timespan_radio_10s.GetId(): 10,
|
|
803
|
-
self.m_menu_view_timespan_radio_05s.GetId(): 5,
|
|
804
|
-
self.m_menu_view_timespan_radio_02s.GetId(): 2,
|
|
805
|
-
}
|
|
806
|
-
|
|
807
957
|
def mesh_status_data_view(self):
|
|
808
958
|
if self.table_updater is None:
|
|
809
959
|
self.table_updater = TableUpdater(self)
|
|
@@ -821,6 +971,15 @@ class View(wx.Frame):
|
|
|
821
971
|
self.m_textCtrl_tmp,
|
|
822
972
|
self.m_textCtrl_str,
|
|
823
973
|
self.m_textCtrl_veh,
|
|
974
|
+
getattr(self, "m_button_clf", None),
|
|
975
|
+
getattr(self, "m_check_chx", None),
|
|
976
|
+
getattr(self, "m_check_chy", None),
|
|
977
|
+
getattr(self, "m_check_chz", None),
|
|
978
|
+
getattr(self, "m_choice_timespan", None),
|
|
979
|
+
getattr(self, "m_check_time_y_auto", None),
|
|
980
|
+
getattr(self, "m_text_time_ylim", None),
|
|
981
|
+
getattr(self, "m_check_psd_y_auto", None),
|
|
982
|
+
getattr(self, "m_text_psd_ylim", None),
|
|
824
983
|
getattr(self, "m_plot_canvas", None),
|
|
825
984
|
getattr(self, "m_plot_canvas_merged", None),
|
|
826
985
|
):
|
|
@@ -900,9 +1059,32 @@ class View(wx.Frame):
|
|
|
900
1059
|
return
|
|
901
1060
|
self.merged_plot_updater.figure_update()
|
|
902
1061
|
|
|
1062
|
+
def _set_active_plot(self, mode: str):
|
|
1063
|
+
if mode not in ("rt", "merged"):
|
|
1064
|
+
return
|
|
1065
|
+
if getattr(self, "_active_plot", None) == mode:
|
|
1066
|
+
return
|
|
1067
|
+
self._active_plot = mode
|
|
1068
|
+
if mode == "merged":
|
|
1069
|
+
self.m_fig.Hide()
|
|
1070
|
+
self.m_fig_merged.Show()
|
|
1071
|
+
else:
|
|
1072
|
+
self.m_fig_merged.Hide()
|
|
1073
|
+
self.m_fig.Show()
|
|
1074
|
+
try:
|
|
1075
|
+
self.m_plot_panel.Layout()
|
|
1076
|
+
except Exception:
|
|
1077
|
+
pass
|
|
1078
|
+
|
|
1079
|
+
def show_rt_plots(self):
|
|
1080
|
+
self._set_active_plot("rt")
|
|
1081
|
+
if hasattr(self, "tab_index_rt_plots"):
|
|
1082
|
+
self.m_left_notebook.SetSelection(self.tab_index_rt_plots)
|
|
1083
|
+
|
|
903
1084
|
def show_merged_plots(self):
|
|
904
|
-
|
|
905
|
-
|
|
1085
|
+
self._set_active_plot("merged")
|
|
1086
|
+
if hasattr(self, "tab_index_rt_plots"):
|
|
1087
|
+
self.m_left_notebook.SetSelection(self.tab_index_rt_plots)
|
|
906
1088
|
|
|
907
1089
|
def _focus_plot_canvas(self, event):
|
|
908
1090
|
try:
|
|
@@ -986,14 +1168,12 @@ class View(wx.Frame):
|
|
|
986
1168
|
|
|
987
1169
|
def on_tab_changed(self, event):
|
|
988
1170
|
idx = event.GetSelection()
|
|
989
|
-
if idx == self.tab_index_rt_plots
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
pass
|
|
994
|
-
if idx == self.tab_index_merged_plots and hasattr(self, "m_plot_canvas_merged"):
|
|
1171
|
+
if idx == self.tab_index_rt_plots:
|
|
1172
|
+
canvas = self.m_plot_canvas
|
|
1173
|
+
if getattr(self, "_active_plot", "rt") == "merged":
|
|
1174
|
+
canvas = self.m_plot_canvas_merged
|
|
995
1175
|
try:
|
|
996
|
-
|
|
1176
|
+
canvas.SetFocus()
|
|
997
1177
|
except Exception:
|
|
998
1178
|
pass
|
|
999
1179
|
event.Skip()
|
|
@@ -1004,10 +1184,24 @@ class View(wx.Frame):
|
|
|
1004
1184
|
def map_update(self, event):
|
|
1005
1185
|
if self.m_map is None or not self.m_map_loaded:
|
|
1006
1186
|
return
|
|
1187
|
+
connected = set()
|
|
1188
|
+
if self.model is not None:
|
|
1189
|
+
for _, row in self.model.mesh_status_data.iterrows():
|
|
1190
|
+
if row.get("Connection") == "connected":
|
|
1191
|
+
connected.add(row.get("nodeID"))
|
|
1192
|
+
|
|
1193
|
+
for node_id in list(self._map_nodes):
|
|
1194
|
+
if node_id not in connected:
|
|
1195
|
+
safe_id = str(node_id).replace("'", "\\'")
|
|
1196
|
+
self.m_map.RunScript(f"removeMarker('{safe_id}');")
|
|
1197
|
+
self._map_nodes.discard(node_id)
|
|
1198
|
+
|
|
1007
1199
|
positions = getattr(self.model, "gnss_positions", {})
|
|
1008
1200
|
if not positions:
|
|
1009
1201
|
return
|
|
1010
1202
|
for node_id, pos in positions.items():
|
|
1203
|
+
if node_id not in connected:
|
|
1204
|
+
continue
|
|
1011
1205
|
if not pos.get("valid"):
|
|
1012
1206
|
continue
|
|
1013
1207
|
lat = pos.get("lat")
|
|
@@ -1016,6 +1210,7 @@ class View(wx.Frame):
|
|
|
1016
1210
|
continue
|
|
1017
1211
|
safe_id = str(node_id).replace("'", "\\'")
|
|
1018
1212
|
self.m_map.RunScript(f"updateMarker('{safe_id}', {lat}, {lon});")
|
|
1213
|
+
self._map_nodes.add(node_id)
|
|
1019
1214
|
|
|
1020
1215
|
def _load_map(self):
|
|
1021
1216
|
if self.m_map is None:
|
|
@@ -1043,6 +1238,26 @@ class View(wx.Frame):
|
|
|
1043
1238
|
attribution: '© OpenStreetMap contributors'
|
|
1044
1239
|
}).addTo(map);
|
|
1045
1240
|
const markers = {};
|
|
1241
|
+
let bounds = L.latLngBounds();
|
|
1242
|
+
let autoFit = true;
|
|
1243
|
+
|
|
1244
|
+
function rebuildBounds() {
|
|
1245
|
+
bounds = L.latLngBounds();
|
|
1246
|
+
let hasMarker = false;
|
|
1247
|
+
for (const key in markers) {
|
|
1248
|
+
if (!Object.prototype.hasOwnProperty.call(markers, key)) continue;
|
|
1249
|
+
const latlng = markers[key].getLatLng();
|
|
1250
|
+
bounds.extend(latlng);
|
|
1251
|
+
hasMarker = true;
|
|
1252
|
+
}
|
|
1253
|
+
if (autoFit && hasMarker && bounds.isValid()) {
|
|
1254
|
+
map.fitBounds(bounds.pad(0.15), {animate: false});
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
map.on('dragstart zoomstart', () => {
|
|
1259
|
+
autoFit = false;
|
|
1260
|
+
});
|
|
1046
1261
|
window.updateMarker = function(nodeId, lat, lon) {
|
|
1047
1262
|
let m = markers[nodeId];
|
|
1048
1263
|
if (!m) {
|
|
@@ -1051,6 +1266,17 @@ class View(wx.Frame):
|
|
|
1051
1266
|
markers[nodeId] = m;
|
|
1052
1267
|
}
|
|
1053
1268
|
m.setLatLng([lat, lon]);
|
|
1269
|
+
bounds.extend([lat, lon]);
|
|
1270
|
+
if (autoFit && bounds.isValid()) {
|
|
1271
|
+
map.fitBounds(bounds.pad(0.15), {animate: false});
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
window.removeMarker = function(nodeId) {
|
|
1275
|
+
const m = markers[nodeId];
|
|
1276
|
+
if (!m) return;
|
|
1277
|
+
map.removeLayer(m);
|
|
1278
|
+
delete markers[nodeId];
|
|
1279
|
+
rebuildBounds();
|
|
1054
1280
|
};
|
|
1055
1281
|
</script>
|
|
1056
1282
|
</body>
|
|
@@ -1136,14 +1362,156 @@ class View(wx.Frame):
|
|
|
1136
1362
|
return
|
|
1137
1363
|
self.plot_updater.update_psd_legend()
|
|
1138
1364
|
|
|
1365
|
+
def _parse_ylim_text(self, text: str):
|
|
1366
|
+
if text is None:
|
|
1367
|
+
return None
|
|
1368
|
+
raw = text.strip()
|
|
1369
|
+
if not raw:
|
|
1370
|
+
return None
|
|
1371
|
+
if raw.startswith("[") and raw.endswith("]"):
|
|
1372
|
+
raw = raw[1:-1].strip()
|
|
1373
|
+
parts = [p.strip() for p in raw.split(",") if p.strip()]
|
|
1374
|
+
if len(parts) != 2:
|
|
1375
|
+
parts = [p.strip() for p in raw.split() if p.strip()]
|
|
1376
|
+
if len(parts) != 2:
|
|
1377
|
+
return None
|
|
1378
|
+
try:
|
|
1379
|
+
lo = float(parts[0])
|
|
1380
|
+
hi = float(parts[1])
|
|
1381
|
+
except (TypeError, ValueError):
|
|
1382
|
+
return None
|
|
1383
|
+
if lo == hi:
|
|
1384
|
+
return None
|
|
1385
|
+
if lo > hi:
|
|
1386
|
+
lo, hi = hi, lo
|
|
1387
|
+
return lo, hi
|
|
1388
|
+
|
|
1389
|
+
def _parse_bool_option(self, value, default: bool) -> bool:
|
|
1390
|
+
if value is None:
|
|
1391
|
+
return default
|
|
1392
|
+
if isinstance(value, bool):
|
|
1393
|
+
return value
|
|
1394
|
+
text = str(value).strip().lower()
|
|
1395
|
+
if text in {"1", "true", "yes", "y", "on"}:
|
|
1396
|
+
return True
|
|
1397
|
+
if text in {"0", "false", "no", "n", "off"}:
|
|
1398
|
+
return False
|
|
1399
|
+
return default
|
|
1400
|
+
|
|
1401
|
+
def _parse_int_option(self, value, default: int) -> int:
|
|
1402
|
+
if value is None:
|
|
1403
|
+
return default
|
|
1404
|
+
try:
|
|
1405
|
+
return int(str(value).strip())
|
|
1406
|
+
except (TypeError, ValueError):
|
|
1407
|
+
return default
|
|
1139
1408
|
|
|
1140
|
-
def
|
|
1141
|
-
|
|
1409
|
+
def _load_plot_options(self) -> None:
|
|
1410
|
+
if self.model is None:
|
|
1411
|
+
return
|
|
1412
|
+
options = getattr(self.model, "options", {}) or {}
|
|
1142
1413
|
self.channel_selection = [
|
|
1143
|
-
self.
|
|
1144
|
-
self.
|
|
1145
|
-
self.
|
|
1414
|
+
self._parse_bool_option(options.get("plot_channel_x"), self.channel_selection[0]),
|
|
1415
|
+
self._parse_bool_option(options.get("plot_channel_y"), self.channel_selection[1]),
|
|
1416
|
+
self._parse_bool_option(options.get("plot_channel_z"), self.channel_selection[2]),
|
|
1146
1417
|
]
|
|
1418
|
+
self.timespan_length = self._parse_int_option(
|
|
1419
|
+
options.get("plot_timespan"), self.timespan_length
|
|
1420
|
+
)
|
|
1421
|
+
self.model.timespan_length = self.timespan_length
|
|
1422
|
+
self.time_y_auto = self._parse_bool_option(
|
|
1423
|
+
options.get("plot_time_y_auto"), self.time_y_auto
|
|
1424
|
+
)
|
|
1425
|
+
time_ylim = self._parse_ylim_text(options.get("plot_time_ylim", ""))
|
|
1426
|
+
if time_ylim is not None:
|
|
1427
|
+
self.time_y_limits = time_ylim
|
|
1428
|
+
self.psd_y_auto = self._parse_bool_option(
|
|
1429
|
+
options.get("plot_psd_y_auto"), self.psd_y_auto
|
|
1430
|
+
)
|
|
1431
|
+
psd_ylim = self._parse_ylim_text(options.get("plot_psd_ylim", ""))
|
|
1432
|
+
if psd_ylim is not None:
|
|
1433
|
+
self.psd_y_limits = psd_ylim
|
|
1434
|
+
|
|
1435
|
+
def _persist_plot_options(self) -> None:
|
|
1436
|
+
if self.model is None:
|
|
1437
|
+
return
|
|
1438
|
+
options = getattr(self.model, "options", None)
|
|
1439
|
+
if options is None:
|
|
1440
|
+
return
|
|
1441
|
+
options["plot_channel_x"] = "true" if self.channel_selection[0] else "false"
|
|
1442
|
+
options["plot_channel_y"] = "true" if self.channel_selection[1] else "false"
|
|
1443
|
+
options["plot_channel_z"] = "true" if self.channel_selection[2] else "false"
|
|
1444
|
+
options["plot_timespan"] = str(self.timespan_length)
|
|
1445
|
+
options["plot_time_y_auto"] = "true" if self.time_y_auto else "false"
|
|
1446
|
+
options["plot_time_ylim"] = f"[{self.time_y_limits[0]}, {self.time_y_limits[1]}]"
|
|
1447
|
+
options["plot_psd_y_auto"] = "true" if self.psd_y_auto else "false"
|
|
1448
|
+
options["plot_psd_ylim"] = f"[{self.psd_y_limits[0]}, {self.psd_y_limits[1]}]"
|
|
1449
|
+
self.model.save_config()
|
|
1450
|
+
|
|
1451
|
+
def on_time_y_auto_toggle(self, event):
|
|
1452
|
+
self.time_y_auto = self.m_check_time_y_auto.GetValue()
|
|
1453
|
+
self.m_text_time_ylim.Enable(not self.time_y_auto)
|
|
1454
|
+
self._persist_plot_options()
|
|
1455
|
+
self.init_plot()
|
|
1456
|
+
self.figure_update()
|
|
1457
|
+
self.init_merged_plot()
|
|
1458
|
+
self.figure_update_merged()
|
|
1459
|
+
|
|
1460
|
+
def on_psd_y_auto_toggle(self, event):
|
|
1461
|
+
self.psd_y_auto = self.m_check_psd_y_auto.GetValue()
|
|
1462
|
+
self.m_text_psd_ylim.Enable(not self.psd_y_auto)
|
|
1463
|
+
self._persist_plot_options()
|
|
1464
|
+
self.init_plot()
|
|
1465
|
+
self.figure_update()
|
|
1466
|
+
self.init_merged_plot()
|
|
1467
|
+
self.figure_update_merged()
|
|
1468
|
+
|
|
1469
|
+
def on_time_ylim_enter(self, event):
|
|
1470
|
+
lims = self._parse_ylim_text(self.m_text_time_ylim.GetValue())
|
|
1471
|
+
if lims is None:
|
|
1472
|
+
self.append_status_message("[plot] invalid time ylim (use [low, high])")
|
|
1473
|
+
return
|
|
1474
|
+
self.time_y_limits = lims
|
|
1475
|
+
self.m_text_time_ylim.SetValue(f"[{lims[0]}, {lims[1]}]")
|
|
1476
|
+
self._persist_plot_options()
|
|
1477
|
+
if not self.time_y_auto:
|
|
1478
|
+
self.init_plot()
|
|
1479
|
+
self.figure_update()
|
|
1480
|
+
self.init_merged_plot()
|
|
1481
|
+
self.figure_update_merged()
|
|
1482
|
+
|
|
1483
|
+
def on_psd_ylim_enter(self, event):
|
|
1484
|
+
lims = self._parse_ylim_text(self.m_text_psd_ylim.GetValue())
|
|
1485
|
+
if lims is None:
|
|
1486
|
+
self.append_status_message("[plot] invalid ASD ylim (use [low, high])")
|
|
1487
|
+
return
|
|
1488
|
+
self.psd_y_limits = lims
|
|
1489
|
+
self.m_text_psd_ylim.SetValue(f"[{lims[0]}, {lims[1]}]")
|
|
1490
|
+
self._persist_plot_options()
|
|
1491
|
+
if not self.psd_y_auto:
|
|
1492
|
+
self.init_plot()
|
|
1493
|
+
self.figure_update()
|
|
1494
|
+
self.init_merged_plot()
|
|
1495
|
+
self.figure_update_merged()
|
|
1496
|
+
|
|
1497
|
+
def _read_channel_selection(self):
|
|
1498
|
+
if hasattr(self, "m_check_chx"):
|
|
1499
|
+
return [
|
|
1500
|
+
self.m_check_chx.GetValue(),
|
|
1501
|
+
self.m_check_chy.GetValue(),
|
|
1502
|
+
self.m_check_chz.GetValue(),
|
|
1503
|
+
]
|
|
1504
|
+
if hasattr(self, "m_menu_view_chsel_x"):
|
|
1505
|
+
return [
|
|
1506
|
+
self.m_menu_view_chsel_x.IsChecked(),
|
|
1507
|
+
self.m_menu_view_chsel_y.IsChecked(),
|
|
1508
|
+
self.m_menu_view_chsel_z.IsChecked(),
|
|
1509
|
+
]
|
|
1510
|
+
return list(self.channel_selection)
|
|
1511
|
+
|
|
1512
|
+
def onChannelToggled(self, event):
|
|
1513
|
+
self.channel_selection = self._read_channel_selection()
|
|
1514
|
+
self._persist_plot_options()
|
|
1147
1515
|
|
|
1148
1516
|
self.init_plot()
|
|
1149
1517
|
self.figure_update()
|
|
@@ -1210,13 +1578,17 @@ class View(wx.Frame):
|
|
|
1210
1578
|
event.Skip()
|
|
1211
1579
|
|
|
1212
1580
|
def onTimeSpanChange(self, event):
|
|
1213
|
-
|
|
1214
|
-
|
|
1581
|
+
ts = None
|
|
1582
|
+
if hasattr(self, "m_choice_timespan"):
|
|
1583
|
+
idx = self.m_choice_timespan.GetSelection()
|
|
1584
|
+
if idx != wx.NOT_FOUND and hasattr(self, "timespan_choices"):
|
|
1585
|
+
ts = self.timespan_choices[idx]
|
|
1215
1586
|
if ts:
|
|
1216
1587
|
logger.info(f"Timespan selected: {ts} sec")
|
|
1217
1588
|
self.timespan_length = ts
|
|
1218
1589
|
if self.model is not None:
|
|
1219
1590
|
self.model.timespan_length = ts
|
|
1591
|
+
self._persist_plot_options()
|
|
1220
1592
|
self.init_plot()
|
|
1221
1593
|
self.figure_update()
|
|
1222
1594
|
self.init_merged_plot()
|
|
@@ -1228,6 +1600,6 @@ class View(wx.Frame):
|
|
|
1228
1600
|
def _append_status_message(self, msg: str) -> None:
|
|
1229
1601
|
if hasattr(self, "m_statusBar1") and self.m_statusBar1 is not None:
|
|
1230
1602
|
self.m_statusBar1.SetStatusText(msg)
|
|
1231
|
-
|
|
1603
|
+
logger.info("%s", msg)
|
|
1232
1604
|
|
|
1233
1605
|
|