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/model.py
CHANGED
|
@@ -69,6 +69,7 @@ try:
|
|
|
69
69
|
SPEED_COL,
|
|
70
70
|
RSSI_COL,
|
|
71
71
|
PPS_COL,
|
|
72
|
+
BAT_COL,
|
|
72
73
|
LEVEL_COL,
|
|
73
74
|
RT_STREAM_HD5_DIR,
|
|
74
75
|
RT_STREAM_MERGED_DIR,
|
|
@@ -82,6 +83,7 @@ except ImportError:
|
|
|
82
83
|
SPEED_COL,
|
|
83
84
|
RSSI_COL,
|
|
84
85
|
PPS_COL,
|
|
86
|
+
BAT_COL,
|
|
85
87
|
LEVEL_COL,
|
|
86
88
|
RT_STREAM_HD5_DIR,
|
|
87
89
|
RT_STREAM_MERGED_DIR,
|
|
@@ -163,6 +165,7 @@ class Model():
|
|
|
163
165
|
RSSI_COL,
|
|
164
166
|
'Children',
|
|
165
167
|
PPS_COL,
|
|
168
|
+
BAT_COL,
|
|
166
169
|
'CMD',
|
|
167
170
|
#'Children_nodeIDs', # invisable in table
|
|
168
171
|
'PPS-time', # invisable in table
|
|
@@ -184,7 +187,7 @@ class Model():
|
|
|
184
187
|
self.full_range_xlim = []
|
|
185
188
|
self.merge_full_range = False
|
|
186
189
|
self.psder = {} # {1:psd_recursive.PSD_Recursive(1024, 62.5), ...}
|
|
187
|
-
self.psd_xdata =
|
|
190
|
+
self.psd_xdata = {} # per-node frequency arrays {node_id: 1d ndarray}
|
|
188
191
|
self.psd_ydata = {} # psd amplitude {1:ndarray}
|
|
189
192
|
self.merged_timehistory_xdata = {}
|
|
190
193
|
self.merged_timehistory_ydata = {}
|
|
@@ -200,6 +203,11 @@ class Model():
|
|
|
200
203
|
self.data_queue = pyqueue.Queue(maxsize=5000)
|
|
201
204
|
self.speed_bytes = {}
|
|
202
205
|
self.node_fs = {}
|
|
206
|
+
self.node_fs_known = {}
|
|
207
|
+
self.psd_base_nfft = 1024
|
|
208
|
+
self.psd_min_nfft = 256
|
|
209
|
+
self.psd_max_nfft = 16384
|
|
210
|
+
self.psd_target_df = None
|
|
203
211
|
self.speed_last_calc = datetime.datetime.now(datetime.timezone.utc)
|
|
204
212
|
self.timespan_length = 30
|
|
205
213
|
self._pps_count = 0
|
|
@@ -233,6 +241,7 @@ class Model():
|
|
|
233
241
|
self.parse_node_nums_txt()
|
|
234
242
|
self.init_mesh_status_data()
|
|
235
243
|
self.init_other_data()
|
|
244
|
+
self._apply_sampling_rate_from_status()
|
|
236
245
|
|
|
237
246
|
# Thread for framed connection reports
|
|
238
247
|
self.conn_report_thread = threading.Thread(target=self._conn_report_worker, daemon=True)
|
|
@@ -341,10 +350,74 @@ class Model():
|
|
|
341
350
|
if prev is not None and abs(prev - fs) < 1e-6:
|
|
342
351
|
return
|
|
343
352
|
self.node_fs[node_id] = fs
|
|
353
|
+
self.node_fs_known[node_id] = True
|
|
344
354
|
if node_id in self.resampler:
|
|
345
355
|
self.resampler[node_id].set_fs(fs)
|
|
346
356
|
if node_id in self.psder:
|
|
347
357
|
self.psder[node_id].set_fs(fs)
|
|
358
|
+
self._recompute_psd_nfft()
|
|
359
|
+
|
|
360
|
+
def _compute_psd_target_df(self):
|
|
361
|
+
fs_values = [fs for node_id, fs in self.node_fs.items() if self.node_fs_known.get(node_id)]
|
|
362
|
+
if not fs_values:
|
|
363
|
+
return None
|
|
364
|
+
min_fs = min(fs_values)
|
|
365
|
+
if min_fs <= 0:
|
|
366
|
+
return None
|
|
367
|
+
return min_fs / float(self.psd_base_nfft)
|
|
368
|
+
|
|
369
|
+
def _choose_nfft(self, fs, target_df):
|
|
370
|
+
if target_df is None or target_df <= 0:
|
|
371
|
+
return self.psd_base_nfft
|
|
372
|
+
nfft = int(round(fs / target_df))
|
|
373
|
+
if nfft < self.psd_min_nfft:
|
|
374
|
+
nfft = self.psd_min_nfft
|
|
375
|
+
elif nfft > self.psd_max_nfft:
|
|
376
|
+
nfft = self.psd_max_nfft
|
|
377
|
+
return nfft
|
|
378
|
+
|
|
379
|
+
def _recompute_psd_nfft(self):
|
|
380
|
+
target_df = self._compute_psd_target_df()
|
|
381
|
+
if target_df is None:
|
|
382
|
+
return
|
|
383
|
+
if self.psd_target_df is None or abs(self.psd_target_df - target_df) > 1e-9:
|
|
384
|
+
self.psd_target_df = target_df
|
|
385
|
+
self._ui_log(
|
|
386
|
+
f"[psd] target df={target_df:.6f} Hz (base_nfft={self.psd_base_nfft})"
|
|
387
|
+
)
|
|
388
|
+
for node_id, fs in self.node_fs.items():
|
|
389
|
+
if not self.node_fs_known.get(node_id):
|
|
390
|
+
continue
|
|
391
|
+
nfft = self._choose_nfft(fs, target_df)
|
|
392
|
+
if self.psd_nfft.get(node_id) == nfft:
|
|
393
|
+
continue
|
|
394
|
+
self.psd_nfft[node_id] = nfft
|
|
395
|
+
self.psder[node_id] = psd_recursive.PSD_Recursive(nfft, fs)
|
|
396
|
+
self.psd_xdata.pop(node_id, None)
|
|
397
|
+
self.psd_ydata.pop(node_id, None)
|
|
398
|
+
df = fs / float(nfft)
|
|
399
|
+
self._ui_log(f"[psd] node={node_id} fs={fs:.3f} nfft={nfft} df={df:.6f} Hz")
|
|
400
|
+
|
|
401
|
+
def _update_observed_fs(self, node_id, ts_list):
|
|
402
|
+
if not ts_list or len(ts_list) < 2:
|
|
403
|
+
return
|
|
404
|
+
duration = (ts_list[-1] - ts_list[0]).total_seconds()
|
|
405
|
+
if duration <= 0:
|
|
406
|
+
return
|
|
407
|
+
fs_est = (len(ts_list) - 1) / duration
|
|
408
|
+
if fs_est <= 0:
|
|
409
|
+
return
|
|
410
|
+
fs_nom = self.node_fs.get(node_id)
|
|
411
|
+
if fs_nom:
|
|
412
|
+
ratio = fs_est / fs_nom
|
|
413
|
+
if ratio < 0.5 or ratio > 1.5:
|
|
414
|
+
return
|
|
415
|
+
prev = self.node_fs_obs.get(node_id)
|
|
416
|
+
if prev is None:
|
|
417
|
+
self.node_fs_obs[node_id] = fs_est
|
|
418
|
+
else:
|
|
419
|
+
alpha = 0.1
|
|
420
|
+
self.node_fs_obs[node_id] = prev * (1 - alpha) + fs_est * alpha
|
|
348
421
|
|
|
349
422
|
def load_config(self):
|
|
350
423
|
config = configparser.ConfigParser()
|
|
@@ -356,7 +429,19 @@ class Model():
|
|
|
356
429
|
'tmp_nums_txt': '[]',
|
|
357
430
|
'str_nums_txt': '[]',
|
|
358
431
|
'veh_nums_txt': '[]',
|
|
359
|
-
'option2': 'default_value2'
|
|
432
|
+
'option2': 'default_value2',
|
|
433
|
+
'log_file_pps': 'true',
|
|
434
|
+
'log_file_conn_rpt': 'true',
|
|
435
|
+
'log_terminal_pps': 'true',
|
|
436
|
+
'log_terminal_conn_rpt': 'false',
|
|
437
|
+
'plot_channel_x': 'false',
|
|
438
|
+
'plot_channel_y': 'true',
|
|
439
|
+
'plot_channel_z': 'false',
|
|
440
|
+
'plot_timespan': '30',
|
|
441
|
+
'plot_time_y_auto': 'false',
|
|
442
|
+
'plot_time_ylim': '[-2.0, 2.0]',
|
|
443
|
+
'plot_psd_y_auto': 'false',
|
|
444
|
+
'plot_psd_ylim': '[1e-06, 10.0]',
|
|
360
445
|
}
|
|
361
446
|
return
|
|
362
447
|
|
|
@@ -367,7 +452,19 @@ class Model():
|
|
|
367
452
|
'tmp_nums_txt': config.get('Settings', 'tmp_nums_txt', fallback='[]'),
|
|
368
453
|
'str_nums_txt': config.get('Settings', 'str_nums_txt', fallback='[]'),
|
|
369
454
|
'veh_nums_txt': config.get('Settings', 'veh_nums_txt', fallback='[]'),
|
|
370
|
-
'option2': config.get('Settings', 'option2', fallback='default_value2')
|
|
455
|
+
'option2': config.get('Settings', 'option2', fallback='default_value2'),
|
|
456
|
+
'log_file_pps': config.get('Logging', 'file_pps', fallback='true'),
|
|
457
|
+
'log_file_conn_rpt': config.get('Logging', 'file_conn_rpt', fallback='true'),
|
|
458
|
+
'log_terminal_pps': config.get('Logging', 'terminal_pps', fallback='true'),
|
|
459
|
+
'log_terminal_conn_rpt': config.get('Logging', 'terminal_conn_rpt', fallback='false'),
|
|
460
|
+
'plot_channel_x': config.get('Plot', 'channel_x', fallback='false'),
|
|
461
|
+
'plot_channel_y': config.get('Plot', 'channel_y', fallback='true'),
|
|
462
|
+
'plot_channel_z': config.get('Plot', 'channel_z', fallback='false'),
|
|
463
|
+
'plot_timespan': config.get('Plot', 'timespan', fallback='30'),
|
|
464
|
+
'plot_time_y_auto': config.get('Plot', 'time_y_auto', fallback='false'),
|
|
465
|
+
'plot_time_ylim': config.get('Plot', 'time_ylim', fallback='[-2.0, 2.0]'),
|
|
466
|
+
'plot_psd_y_auto': config.get('Plot', 'psd_y_auto', fallback='false'),
|
|
467
|
+
'plot_psd_ylim': config.get('Plot', 'psd_ylim', fallback='[1e-06, 10.0]'),
|
|
371
468
|
}
|
|
372
469
|
|
|
373
470
|
def save_config(self):
|
|
@@ -381,6 +478,22 @@ class Model():
|
|
|
381
478
|
'veh_nums_txt': self.options['veh_nums_txt'],
|
|
382
479
|
'option2': self.options['option2']
|
|
383
480
|
}
|
|
481
|
+
config['Logging'] = {
|
|
482
|
+
'file_pps': self.options.get('log_file_pps', 'true'),
|
|
483
|
+
'file_conn_rpt': self.options.get('log_file_conn_rpt', 'true'),
|
|
484
|
+
'terminal_pps': self.options.get('log_terminal_pps', 'true'),
|
|
485
|
+
'terminal_conn_rpt': self.options.get('log_terminal_conn_rpt', 'false'),
|
|
486
|
+
}
|
|
487
|
+
config['Plot'] = {
|
|
488
|
+
'channel_x': self.options.get('plot_channel_x', 'false'),
|
|
489
|
+
'channel_y': self.options.get('plot_channel_y', 'true'),
|
|
490
|
+
'channel_z': self.options.get('plot_channel_z', 'false'),
|
|
491
|
+
'timespan': self.options.get('plot_timespan', '30'),
|
|
492
|
+
'time_y_auto': self.options.get('plot_time_y_auto', 'false'),
|
|
493
|
+
'time_ylim': self.options.get('plot_time_ylim', '[-2.0, 2.0]'),
|
|
494
|
+
'psd_y_auto': self.options.get('plot_psd_y_auto', 'false'),
|
|
495
|
+
'psd_ylim': self.options.get('plot_psd_ylim', '[1e-06, 10.0]'),
|
|
496
|
+
}
|
|
384
497
|
|
|
385
498
|
with open(CONFIG_FILE, 'w') as configfile:
|
|
386
499
|
config.write(configfile)
|
|
@@ -451,6 +564,7 @@ class Model():
|
|
|
451
564
|
RSSI_COL:[''],
|
|
452
565
|
'Children':[''],
|
|
453
566
|
PPS_COL:[''],
|
|
567
|
+
BAT_COL:[''],
|
|
454
568
|
'CMD':[''],
|
|
455
569
|
#'Children_nodeIDs': [''],
|
|
456
570
|
'PPS-time':[''],
|
|
@@ -478,7 +592,7 @@ class Model():
|
|
|
478
592
|
self.full_range_xlim = []
|
|
479
593
|
self.merge_full_range = False
|
|
480
594
|
self.psder.clear()
|
|
481
|
-
self.psd_xdata =
|
|
595
|
+
self.psd_xdata = {}
|
|
482
596
|
self.psd_ydata.clear()
|
|
483
597
|
self.speed_bytes = {}
|
|
484
598
|
self.speed_last_value = {}
|
|
@@ -486,6 +600,9 @@ class Model():
|
|
|
486
600
|
self.speed_hist = {}
|
|
487
601
|
self.speed_window_sec = 5.0
|
|
488
602
|
self.speed_last_calc = datetime.datetime.now(datetime.timezone.utc)
|
|
603
|
+
self.node_fs_obs = {}
|
|
604
|
+
self.psd_nfft = {}
|
|
605
|
+
self.node_fs_known = {}
|
|
489
606
|
|
|
490
607
|
for index, row in self.mesh_status_data.iterrows():
|
|
491
608
|
nodeID = row['nodeID']
|
|
@@ -496,14 +613,26 @@ class Model():
|
|
|
496
613
|
self.resampler[nodeID] = resampling.Resampling(50)
|
|
497
614
|
self.psder[nodeID] = psd_recursive.PSD_Recursive(1024, 50)
|
|
498
615
|
self.node_fs[nodeID] = 50
|
|
616
|
+
self.node_fs_obs[nodeID] = None
|
|
617
|
+
self.psd_nfft[nodeID] = 1024
|
|
618
|
+
self.node_fs_known[nodeID] = False
|
|
499
619
|
self.speed_bytes[nodeID] = 0
|
|
500
620
|
self.speed_last_value[nodeID] = None
|
|
501
621
|
self.speed_last_rx[nodeID] = None
|
|
502
622
|
self.speed_hist[nodeID] = deque()
|
|
503
623
|
self.pps_flash_until[nodeID] = None
|
|
624
|
+
|
|
625
|
+
def _apply_sampling_rate_from_status(self):
|
|
626
|
+
for _, row in self.mesh_status_data.iterrows():
|
|
627
|
+
node_id = row.get("nodeID") or row.get("Node ID")
|
|
628
|
+
if not node_id:
|
|
629
|
+
continue
|
|
630
|
+
fs = self._extract_fs_from_daq_label(row.get("DAQ Mode", ""))
|
|
631
|
+
if fs:
|
|
632
|
+
self._update_sampling_rate(node_id, fs)
|
|
504
633
|
|
|
505
634
|
|
|
506
|
-
def enqueue_conn_report(self, nodeID, level, parent_mac, self_mac, rssi, acc_model: int = 0, daq_mode: int = 0, daq_on: int = 0, stream_status: int = 0, notify: bool = False):
|
|
635
|
+
def enqueue_conn_report(self, nodeID, level, parent_mac, self_mac, rssi, acc_model: int = 0, daq_mode: int = 0, daq_on: int = 0, stream_status: int = 0, bat_vol: float | None = None, notify: bool = False):
|
|
507
636
|
try:
|
|
508
637
|
self.conn_report_queue.put_nowait(
|
|
509
638
|
{
|
|
@@ -516,6 +645,7 @@ class Model():
|
|
|
516
645
|
"daq_mode": daq_mode,
|
|
517
646
|
"daq_on": daq_on,
|
|
518
647
|
"stream_status": stream_status,
|
|
648
|
+
"bat_vol": bat_vol,
|
|
519
649
|
"notify": notify,
|
|
520
650
|
}
|
|
521
651
|
)
|
|
@@ -535,6 +665,7 @@ class Model():
|
|
|
535
665
|
daq_mode=item.get("daq_mode", 0),
|
|
536
666
|
daq_on=item.get("daq_on", 0),
|
|
537
667
|
stream_status=item.get("stream_status", 0),
|
|
668
|
+
bat_vol=item.get("bat_vol"),
|
|
538
669
|
notify=item["notify"],
|
|
539
670
|
)
|
|
540
671
|
|
|
@@ -643,6 +774,7 @@ class Model():
|
|
|
643
774
|
ts_, ys = self.timestamper[nodeID].push_cc_pps(cc, epoch_time)
|
|
644
775
|
ts = self._time_delay_correction(ts_, node_number)
|
|
645
776
|
if len(ts) > 0:
|
|
777
|
+
self._update_observed_fs(nodeID, ts)
|
|
646
778
|
self._ts_count += len(ts)
|
|
647
779
|
self.plot_dirty = True
|
|
648
780
|
self.plot_dirty_version += 1
|
|
@@ -669,7 +801,11 @@ class Model():
|
|
|
669
801
|
|
|
670
802
|
if self.psder[nodeID].isUpdated:
|
|
671
803
|
f, asd = self.psder[nodeID].get_asd()
|
|
672
|
-
|
|
804
|
+
fs_nom = self.node_fs.get(nodeID)
|
|
805
|
+
fs_obs = self.node_fs_obs.get(nodeID)
|
|
806
|
+
if fs_nom and fs_obs:
|
|
807
|
+
f = f * (fs_obs / fs_nom)
|
|
808
|
+
self.psd_xdata[nodeID] = f
|
|
673
809
|
self.psd_ydata[nodeID] = asd
|
|
674
810
|
except (OSError, OverflowError, ValueError):
|
|
675
811
|
pass
|
|
@@ -699,6 +835,7 @@ class Model():
|
|
|
699
835
|
RSSI_COL: [''],
|
|
700
836
|
'Children': [''],
|
|
701
837
|
PPS_COL: [''],
|
|
838
|
+
BAT_COL: [''],
|
|
702
839
|
'CMD': [''],
|
|
703
840
|
'PPS-time': [''],
|
|
704
841
|
'PPS-flash-time': [''],
|
|
@@ -774,6 +911,7 @@ class Model():
|
|
|
774
911
|
ts_list, ys = self.timestamper[node_id].push_cc_pps(cc, epoch)
|
|
775
912
|
ts_list = self._time_delay_correction(ts_list, node_number)
|
|
776
913
|
if ts_list:
|
|
914
|
+
self._update_observed_fs(node_id, ts_list)
|
|
777
915
|
with self.plot_mutex[node_id]:
|
|
778
916
|
self.timehistory_xdata[node_id] += ts_list
|
|
779
917
|
self.timehistory_ydata[node_id] = append(
|
|
@@ -791,7 +929,11 @@ class Model():
|
|
|
791
929
|
self.psder[node_id].push(array(yrs), array(trs))
|
|
792
930
|
if self.psder[node_id].isUpdated:
|
|
793
931
|
f, asd = self.psder[node_id].get_asd()
|
|
794
|
-
|
|
932
|
+
fs_nom = self.node_fs.get(node_id)
|
|
933
|
+
fs_obs = self.node_fs_obs.get(node_id)
|
|
934
|
+
if fs_nom and fs_obs:
|
|
935
|
+
f = f * (fs_obs / fs_nom)
|
|
936
|
+
self.psd_xdata[node_id] = f
|
|
795
937
|
self.psd_ydata[node_id] = asd
|
|
796
938
|
if min_ts is None or ts_list[0] < min_ts:
|
|
797
939
|
min_ts = ts_list[0]
|
|
@@ -992,7 +1134,7 @@ class Model():
|
|
|
992
1134
|
self_mac[5] += 1
|
|
993
1135
|
return self_mac == parent_mac
|
|
994
1136
|
|
|
995
|
-
def handle_conn_report(self, nodeID, level, parent_mac, self_mac, rssi, acc_model: int = 0, daq_mode: int = 0, daq_on: int = 0, stream_status: int = 0, notify: bool = True):
|
|
1137
|
+
def handle_conn_report(self, nodeID, level, parent_mac, self_mac, rssi, acc_model: int = 0, daq_mode: int = 0, daq_on: int = 0, stream_status: int = 0, bat_vol: float | None = None, notify: bool = True):
|
|
996
1138
|
logger.info('Conn Rpt: L={}, par_MAC={}, self_MAC={}, RSSI={}'.format(level, parent_mac, self_mac, rssi))
|
|
997
1139
|
|
|
998
1140
|
condition = self._node_condition(nodeID)
|
|
@@ -1020,6 +1162,11 @@ class Model():
|
|
|
1020
1162
|
"DAQ": daq_status,
|
|
1021
1163
|
"Stream": stream_label,
|
|
1022
1164
|
}
|
|
1165
|
+
if bat_vol is not None:
|
|
1166
|
+
try:
|
|
1167
|
+
fields[BAT_COL] = f"{float(bat_vol):3.1f}"
|
|
1168
|
+
except (TypeError, ValueError):
|
|
1169
|
+
pass
|
|
1023
1170
|
if initial_connect:
|
|
1024
1171
|
fields[SPEED_COL] = "0.0"
|
|
1025
1172
|
self._set_node_fields(
|
|
@@ -1088,6 +1235,7 @@ class Model():
|
|
|
1088
1235
|
"Parent",
|
|
1089
1236
|
RSSI_COL,
|
|
1090
1237
|
"Children",
|
|
1238
|
+
BAT_COL,
|
|
1091
1239
|
"CMD",
|
|
1092
1240
|
]:
|
|
1093
1241
|
if col in self.mesh_status_data.columns:
|
|
@@ -1358,7 +1506,11 @@ class Model():
|
|
|
1358
1506
|
def _set_node_fields(self, nodeID, mark_dirty: bool = True, **fields):
|
|
1359
1507
|
condition = self._node_condition(nodeID)
|
|
1360
1508
|
if condition is None:
|
|
1361
|
-
|
|
1509
|
+
# Ensure late-arriving nodes (e.g., PPS before status) get a row.
|
|
1510
|
+
self._ensure_node_row(nodeID)
|
|
1511
|
+
condition = self._node_condition(nodeID)
|
|
1512
|
+
if condition is None:
|
|
1513
|
+
return False
|
|
1362
1514
|
for column, value in fields.items():
|
|
1363
1515
|
self.mesh_status_data.loc[condition, column] = value
|
|
1364
1516
|
status = self.node_status.get(nodeID)
|
enode_host/protocol.py
CHANGED
|
@@ -27,6 +27,7 @@ class CommandId(enum.IntEnum):
|
|
|
27
27
|
STOP_SD_STREAM = 0x09
|
|
28
28
|
SD_CLEAR = 0x0A
|
|
29
29
|
SET_DAQ_MODE = 0x0B
|
|
30
|
+
SHUTDOWN = 0x0C
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
class Mode(enum.IntEnum):
|
|
@@ -51,6 +52,7 @@ class StatusReport:
|
|
|
51
52
|
daq_mode: int = 0
|
|
52
53
|
daq_on: int = 0
|
|
53
54
|
stream_status: int = 0
|
|
55
|
+
bat_vol: Optional[float] = None
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
@dataclass
|
|
@@ -154,7 +156,7 @@ def build_set_daq_mode(mode: int) -> bytes:
|
|
|
154
156
|
|
|
155
157
|
|
|
156
158
|
def parse_status(payload: bytes) -> StatusReport:
|
|
157
|
-
if len(payload) not in (16, 18, 19, 20):
|
|
159
|
+
if len(payload) not in (16, 18, 19, 20, 24):
|
|
158
160
|
raise ValueError(f"invalid status payload length {len(payload)}")
|
|
159
161
|
node_type = payload[0]
|
|
160
162
|
node_number = payload[1]
|
|
@@ -166,6 +168,9 @@ def parse_status(payload: bytes) -> StatusReport:
|
|
|
166
168
|
daq_mode = payload[17] if len(payload) >= 18 else 0
|
|
167
169
|
daq_on = payload[18] if len(payload) >= 19 else 0
|
|
168
170
|
stream_status = payload[19] if len(payload) >= 20 else 0
|
|
171
|
+
bat_vol = None
|
|
172
|
+
if len(payload) >= 24:
|
|
173
|
+
bat_vol = struct.unpack(">f", payload[20:24])[0]
|
|
169
174
|
return StatusReport(
|
|
170
175
|
node_type=node_type,
|
|
171
176
|
node_number=node_number,
|
|
@@ -177,6 +182,7 @@ def parse_status(payload: bytes) -> StatusReport:
|
|
|
177
182
|
daq_mode=daq_mode,
|
|
178
183
|
daq_on=daq_on,
|
|
179
184
|
stream_status=stream_status,
|
|
185
|
+
bat_vol=bat_vol,
|
|
180
186
|
)
|
|
181
187
|
|
|
182
188
|
|
|
@@ -295,6 +301,10 @@ def build_stop_daq() -> bytes:
|
|
|
295
301
|
return build_command(CommandId.STOP_DAQ)
|
|
296
302
|
|
|
297
303
|
|
|
304
|
+
def build_shutdown() -> bytes:
|
|
305
|
+
return build_command(CommandId.SHUTDOWN)
|
|
306
|
+
|
|
307
|
+
|
|
298
308
|
def build_realtime_stream(toggle: Toggle) -> bytes:
|
|
299
309
|
command_id = (
|
|
300
310
|
CommandId.START_REALTIME_STREAM if toggle == Toggle.START else CommandId.STOP_REALTIME_STREAM
|