STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
gui.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3Simple ADC Plotter GUI
4- Reads 1000-byte packets from serial (83 samples × 3 channels)
5- Plots voltage values for connectors 1, 2, 3
6- Requirements: pip install pyqt6 pyqtgraph pyserial numpy
7"""
8
9import sys
10import struct
11import time
12import csv
13from collections import deque
14from datetime import datetime
15
16from PyQt6 import QtCore, QtGui, QtWidgets
17import pyqtgraph as pg
18import numpy as np
19import serial
20import serial.tools.list_ports
21
22pg.setConfigOptions(antialias=False)
23
24# ---------------------- Protocol constants ----------------------
25MAGIC = b"AD26"
26BUFFER_SIZE = (
27 1996 # 4 magic + 83 samples × 3 channels × (4 bytes timestamp + 4 bytes reading)
28)
29MAGIC_SIZE = 4
30SAMPLES_PER_BUFFER = 83
31NUM_CHANNELS = 3
32DATA_SIZE = (
33 SAMPLES_PER_BUFFER * NUM_CHANNELS * 8
34) # 1992 bytes (timestamp + reading per channel)
35
36# ADC conversion constants
37V_REF = 2.5
38ADC_SCALE = 2147483648.0 # 2^31
39
40BAUD = 115200
41DEFAULT_WINDOW_SECONDS = 10.0
42MAX_POINTS = 10000
43
44# Colors for the 3 channels
45CHANNEL_COLORS = [
46 (255, 80, 80), # Red - Connector 1
47 (80, 255, 80), # Green - Connector 2
48 (80, 150, 255), # Blue - Connector 3
49]
50
51CHANNEL_NAMES = ["Conn 1", "Conn 2", "Conn 3"]
52
53
55 return [p.device for p in serial.tools.list_ports.comports()]
56
57
58# ---------------------- Serial reader thread ----------------------
59class Reader(QtCore.QThread):
60 samples_ready = QtCore.pyqtSignal(
61 list
62 ) # Emits list of (ch, timestamp_us, volts) tuples for a packet
63 status = QtCore.pyqtSignal(str)
64 raw_bytes = QtCore.pyqtSignal(bytes)
65
66 def __init__(self, port: str, baud: int):
67 super().__init__()
68 self.port = port
69 self.baud = baud
70 self._stop = False
71 self.buf = bytearray()
72 self.ser = None
75
76 def stop(self):
77 self._stop = True
78 try:
79 if self.ser and self.ser.is_open:
80 self.ser.close()
81 except Exception:
82 pass
83
85 """Find magic bytes and parse complete packets"""
86 parsed_samples = []
87
88 while True:
89 # Find magic header
90 idx = self.buf.find(MAGIC)
91 if idx == -1:
92 # Keep last 3 bytes in case magic is split across reads
93 if len(self.buf) > 3:
94 del self.buf[:-3]
95 break
96
97 # Discard bytes before magic
98 if idx > 0:
99 del self.buf[:idx]
100
101 # Check if we have a complete packet
102 if len(self.buf) < BUFFER_SIZE:
103 break
104
105 # Extract packet data (skip magic)
106 data = bytes(self.buf[MAGIC_SIZE:BUFFER_SIZE])
107
108 # Parse all samples from this packet
109 # Each sample has 3 channels, each with: 4 bytes timestamp + 4 bytes reading
110 for sample_idx in range(SAMPLES_PER_BUFFER):
111 base = (
112 sample_idx * NUM_CHANNELS * 8
113 ) # 8 bytes per channel (timestamp + reading)
114 for ch in range(NUM_CHANNELS):
115 offset = base + ch * 8
116 # Extract timestamp (uint32_t, microseconds)
117 timestamp_us = struct.unpack_from("<I", data, offset)[0]
118 # Extract reading (int32_t)
119 raw = struct.unpack_from("<i", data, offset + 4)[0]
120 volts = raw * V_REF / ADC_SCALE
121 parsed_samples.append((ch, timestamp_us, volts))
122
123 self.packets_received += 1
124 self.total_samples += len(parsed_samples)
125
126 # Remove processed packet
127 del self.buf[:BUFFER_SIZE]
128
129 return parsed_samples
130
131 def run(self):
132 try:
133 self.ser = serial.Serial(self.port, self.baud, timeout=0.05)
134 self.status.emit(f"Connected {self.port} @ {self.baud}")
135 except Exception as e:
136 self.status.emit(f"Open failed: {e}")
137 return
138
139 last_status_time = time.monotonic()
140
141 while not self._stop:
142 try:
143 data = self.ser.read(self.ser.in_waiting or 1)
144 if data:
145 self.raw_bytes.emit(data)
146 self.buf.extend(data)
147
148 parsed = self._find_and_parse_packets()
149 if parsed:
150 self.samples_ready.emit(parsed)
151
152 # Periodic status update
153 now = time.monotonic()
154 if now - last_status_time >= 0.5:
155 self.status.emit(
156 f"Reader: {self.packets_received} pkts, {self.total_samples} samples, buf={len(self.buf)}B"
157 )
158 last_status_time = now
159
160 except serial.SerialException as e:
161 self.status.emit(f"Serial error: {e}")
162 break
163 except Exception as e:
164 self.status.emit(f"Unexpected: {e}")
165 break
166
167 try:
168 if self.ser and self.ser.is_open:
169 self.ser.close()
170 except Exception:
171 pass
172 self.status.emit("Disconnected")
173
174
175# ---------------------- Settings window ----------------------
176class SettingsWindow(QtWidgets.QDialog):
177 def __init__(self, parent):
178 super().__init__(parent)
179 self.parent_app = parent
180 self.setWindowTitle("Settings")
181 self.resize(360, 200)
182
183 layout = QtWidgets.QVBoxLayout(self)
184
185 # Window seconds
186 layout.addWidget(QtWidgets.QLabel("Viewing window (seconds)"))
187 row = QtWidgets.QHBoxLayout()
188 self.slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
189 self.slider.setMinimum(1)
190 self.slider.setMaximum(60)
191 self.slider.setValue(int(self.parent_app.window_seconds))
192 self.slider.valueChanged.connect(self._on_change)
193 self.lbl = QtWidgets.QLabel(f"{self.parent_app.window_seconds:.1f}s")
194 row.addWidget(self.slider, 1)
195 row.addWidget(self.lbl)
196 layout.addLayout(row)
197
198 # Max voltage
199 layout.addWidget(QtWidgets.QLabel("Max voltage (V)"))
200 row2 = QtWidgets.QHBoxLayout()
201 self.slider_maxv = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
202 self.slider_maxv.setMinimum(5)
203 self.slider_maxv.setMaximum(100)
204 self.slider_maxv.setValue(int(self.parent_app.max_v * 10))
205 self.slider_maxv.valueChanged.connect(self._on_change_maxv)
206 self.lbl_maxv = QtWidgets.QLabel(f"{self.parent_app.max_v:.1f} V")
207 row2.addWidget(self.slider_maxv, 1)
208 row2.addWidget(self.lbl_maxv)
209 layout.addLayout(row2)
210
211 btn = QtWidgets.QPushButton("Close")
212 btn.clicked.connect(self.accept)
213 layout.addWidget(btn)
214
215 def _on_change(self, val):
216 self.parent_app.window_seconds = float(val)
217 self.lbl.setText(f"{float(val):.1f}s")
218
219 def _on_change_maxv(self, val):
220 self.parent_app.max_v = float(val) / 10.0
221 self.lbl_maxv.setText(f"{self.parent_app.max_v:.1f} V")
222
223
224# ---------------------- Raw console window ----------------------
225class ConsoleWindow(QtWidgets.QDialog):
226 def __init__(self, parent):
227 super().__init__(parent)
228 self.setWindowTitle("Raw Serial Console")
229 self.resize(800, 400)
230
231 top = QtWidgets.QHBoxLayout()
232 self.chk_pause = QtWidgets.QCheckBox("Pause")
233 self.chk_hex = QtWidgets.QCheckBox("Hex View")
234 btn_clear = QtWidgets.QPushButton("Clear")
235 btn_clear.clicked.connect(self._clear)
236 top.addWidget(self.chk_pause)
237 top.addWidget(self.chk_hex)
238 top.addWidget(btn_clear)
239 top.addStretch(1)
240
241 self.text = QtWidgets.QPlainTextEdit()
242 self.text.setReadOnly(True)
243 font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.SystemFont.FixedFont)
244 font.setPointSize(10)
245 self.text.setFont(font)
246
247 layout = QtWidgets.QVBoxLayout(self)
248 layout.addLayout(top)
249 layout.addWidget(self.text)
250
251 def _clear(self):
252 self.text.clear()
253
254 @QtCore.pyqtSlot(bytes)
255 def on_bytes(self, data: bytes):
256 if self.chk_pause.isChecked():
257 return
258 try:
259 if self.chk_hex.isChecked():
260 s = data.hex(" ")
261 else:
262 s = data.decode("utf-8", errors="replace")
263 self.text.appendPlainText(s)
264 except Exception:
265 pass
266
267
268# ---------------------- Main application window ----------------------
269class App(QtWidgets.QMainWindow):
270 def __init__(self):
271 super().__init__()
272 self.setWindowTitle("ADC Plotter - Simple Test")
273 self.resize(1280, 740)
274
275 self.window_seconds = DEFAULT_WINDOW_SECONDS
276 self.console_win = None
277 self.autoscale = True
278 self.max_v = 2.5
279
280 central = QtWidgets.QWidget()
281 self.setCentralWidget(central)
282 root = QtWidgets.QVBoxLayout(central)
283
284 # Top bar
285 top = QtWidgets.QHBoxLayout()
286 top.addWidget(QtWidgets.QLabel("Port"))
287 self.cmb_port = QtWidgets.QComboBox()
288 self._refresh_port_list()
289 top.addWidget(self.cmb_port)
290
291 top.addWidget(QtWidgets.QLabel("Baud"))
292 self.cmb_baud = QtWidgets.QComboBox()
293 self.cmb_baud.addItems(
294 [
295 str(b)
296 for b in [9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600]
297 ]
298 )
299 self.cmb_baud.setCurrentText(str(BAUD))
300 top.addWidget(self.cmb_baud)
301
302 btn_refresh = QtWidgets.QPushButton("Refresh")
303 btn_refresh.clicked.connect(self._refresh_port_list)
304 top.addWidget(btn_refresh)
305
306 self.btn_connect = QtWidgets.QPushButton("Connect")
307 self.btn_connect.clicked.connect(self._toggle_connect)
308 top.addWidget(self.btn_connect)
309
310 btn_settings = QtWidgets.QPushButton("Settings")
311 btn_settings.clicked.connect(self._open_settings)
312 top.addWidget(btn_settings)
313
314 btn_console = QtWidgets.QPushButton("Console")
315 btn_console.clicked.connect(self._open_console)
316 top.addWidget(btn_console)
317
318 btn_export = QtWidgets.QPushButton("Download as CSV")
319 btn_export.clicked.connect(self._export_csv)
320 top.addWidget(btn_export)
321
322 self.chk_autoscale = QtWidgets.QCheckBox("Autoscale Y")
323 self.chk_autoscale.setChecked(True)
324 self.chk_autoscale.stateChanged.connect(self._on_autoscale)
325 top.addWidget(self.chk_autoscale)
326
327 top.addStretch(1)
328 root.addLayout(top)
329
330 # Main split: plot on left, controls on right
331 main = QtWidgets.QHBoxLayout()
332 root.addLayout(main, 1)
333
334 # Plot
335 self.plot = pg.PlotWidget()
336 self.plot.setLabel("bottom", "Time", units="s")
337 self.plot.setLabel("left", "Voltage", units="V")
338 self.plot.showGrid(x=True, y=True, alpha=0.3)
339 self.plot.setTitle("ADC Readings - Connectors 1, 2, 3")
340 self.plot.setClipToView(True)
341 self.plot.setDownsampling(mode="peak")
342 self.plot.setMouseEnabled(x=True, y=True)
343 self.legend = self.plot.addLegend(labelTextSize="10pt")
344 main.addWidget(self.plot, 1)
345
346 # Right panel
347 right = QtWidgets.QVBoxLayout()
348 main.addLayout(right)
349
350 # Channel toggles
351 right.addWidget(QtWidgets.QLabel("Show channels:"))
352 self.chk = {}
353 self.curves = {}
354 for ch in range(NUM_CHANNELS):
355 cb = QtWidgets.QCheckBox(CHANNEL_NAMES[ch])
356 cb.setChecked(True)
357 cb.stateChanged.connect(self._on_toggle)
358 right.addWidget(cb)
359 self.chk[ch] = cb
360 color = CHANNEL_COLORS[ch]
361 pen = pg.mkPen(color=color, width=2)
362 self.curves[ch] = self.plot.plot([], [], name=CHANNEL_NAMES[ch], pen=pen)
363
364 # Statistics box
365 box = QtWidgets.QGroupBox("Statistics")
366 form = QtWidgets.QVBoxLayout(box)
367
368 self.lbl_sps = QtWidgets.QLabel("SPS: n/a")
369 form.addWidget(self.lbl_sps)
370
371 form.addWidget(self._hline())
372 form.addWidget(QtWidgets.QLabel("Mean voltage (last 100 ms):"))
373
374 self.per_ch = {}
375 for ch in range(NUM_CHANNELS):
376 lbl = QtWidgets.QLabel(f"{CHANNEL_NAMES[ch]}: n/a")
377 form.addWidget(lbl)
378 self.per_ch[ch] = lbl
379 right.addWidget(box)
380
381 # Status box
382 status_box = QtWidgets.QGroupBox("Status")
383 status_layout = QtWidgets.QVBoxLayout(status_box)
384 self.lbl_status = QtWidgets.QLabel("Idle")
385 self.lbl_status.setWordWrap(True)
386 status_layout.addWidget(self.lbl_status)
387 right.addWidget(status_box)
388 right.addStretch(1)
389
390 # Data storage
391 self.t0_us = None # First timestamp in microseconds (from Arduino)
392 self.test_start_time = None # When the test/connection started (for filename)
395 self.t = {ch: deque(maxlen=MAX_POINTS) for ch in range(NUM_CHANNELS)}
396 self.v = {ch: deque(maxlen=MAX_POINTS) for ch in range(NUM_CHANNELS)}
397
398 # For SPS calculation
399 self.last_sps_time = None
401
402 # Timer for plot updates - only update when needed
403 self.timer = QtCore.QTimer(self)
404 self.timer.timeout.connect(self._update_plot)
405 self.timer.start(100) # Reduced frequency from 50ms to 100ms
406 self._plot_needs_update = False # Flag to skip updates when no data
407
408 # Serial reader holder
409 self.reader = None
410
411 def _hline(self):
412 line = QtWidgets.QFrame()
413 line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
414 line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
415 return line
416
418 ports = list_ports()
419 self.cmb_port.clear()
420 self.cmb_port.addItems(ports)
421 if ports:
422 self.cmb_port.setCurrentIndex(0)
423
424 def set_status(self, text):
425 self.lbl_status.setText(text)
426
428 if self.reader is None:
429 port = self.cmb_port.currentText().strip()
430 try:
431 baud = int(self.cmb_baud.currentText())
432 except Exception:
433 self.set_status("Bad baud")
434 return
435 self._connect(port, baud)
436 self.btn_connect.setText("Disconnect")
437 else:
438 self.reader.stop()
439 self.reader = None
440 self.set_status("Disconnected")
441 self.btn_connect.setText("Connect")
442
443 def _connect(self, port, baud):
444 self.t0_us = None
445 self.test_start_time = datetime.now() # Record when test started
446 self.sample_count = 0
447 self.packets_received = 0
448 self.last_sps_time = None
449 self.last_sps_count = 0
450 for ch in range(NUM_CHANNELS):
451 self.t[ch].clear()
452 self.v[ch].clear()
453
454 self.reader = Reader(port, baud)
455 self.reader.samples_ready.connect(
456 self._on_samples, QtCore.Qt.ConnectionType.QueuedConnection
457 )
458 self.reader.status.connect(
459 self.set_status, QtCore.Qt.ConnectionType.QueuedConnection
460 )
461 self.reader.raw_bytes.connect(
462 self._on_raw_bytes, QtCore.Qt.ConnectionType.QueuedConnection
463 )
464 self.reader.start()
465
466 @QtCore.pyqtSlot(list)
467 def _on_samples(self, samples):
468 """Handle batch of samples from a packet"""
469 if not samples:
470 return
471
472 self.packets_received += 1
473
474 # Initialize timing on first sample
475 if self.t0_us is None and samples:
476 # Use the first timestamp as reference
477 _, first_timestamp_us, _ = samples[0]
478 self.t0_us = first_timestamp_us
479 self.last_sps_time = time.monotonic()
480 self.last_sps_count = 0
481
482 # Process each sample with its actual timestamp
483 for ch, timestamp_us, volts in samples:
484 # Convert microseconds to seconds relative to first timestamp
485 t_rel = (timestamp_us - self.t0_us) / 1_000_000.0
486
487 self.t[ch].append(t_rel)
488 self.v[ch].append(volts)
489 self.sample_count += 1
490
491 # Mark that plot needs update
492 self._plot_needs_update = True
493
494 @QtCore.pyqtSlot(bytes)
495 def _on_raw_bytes(self, data: bytes):
496 if self.console_win is not None:
497 self.console_win.on_bytes(data)
498
499 def _on_toggle(self):
500 # Trigger plot update when channel visibility changes
501 self._plot_needs_update = True
502
503 def _update_plot(self):
504 # Early exit if no data and no update needed
505 has_data = any(len(self.t.get(ch, [])) > 0 for ch in range(NUM_CHANNELS))
506 if not has_data and not self._plot_needs_update:
507 return
508
509 self._plot_needs_update = False
510
511 # Update curves - only convert to list if we have data
512 for ch, cb in self.chk.items():
513 if cb.isChecked():
514 t_data = self.t.get(ch, [])
515 v_data = self.v.get(ch, [])
516 if t_data and v_data:
517 # Only convert to list if we have data
518 self.curves[ch].setData(list(t_data), list(v_data))
519 else:
520 self.curves[ch].setData([], [])
521 else:
522 self.curves[ch].setData([], [])
523
524 # Calculate latest time across all channels
525 latest = 0.0
526 for ch in range(NUM_CHANNELS):
527 if self.chk[ch].isChecked() and self.t.get(ch):
528 latest = max(latest, self.t[ch][-1])
529
530 if has_data:
531 xmin = max(0.0, latest - self.window_seconds)
532 self.plot.setXRange(xmin, max(xmin + 1e-3, latest), padding=0)
533
534 # Y limits - only calculate if we have data
535 if self.autoscale:
536 values = []
537 for ch in range(NUM_CHANNELS):
538 if self.chk[ch].isChecked():
539 v_data = self.v.get(ch, [])
540 if v_data:
541 values.extend(v_data)
542 if values:
543 vmin, vmax = min(values), max(values)
544 if vmax == vmin:
545 pad = 0.1 if vmax == 0 else abs(vmax) * 0.1
546 self.plot.setYRange(vmin - pad, vmax + pad, padding=0)
547 else:
548 rng = vmax - vmin
549 self.plot.setYRange(
550 vmin - 0.1 * rng, vmax + 0.1 * rng, padding=0
551 )
552 else:
553 self.plot.setYRange(0.0, float(self.max_v) + 0.1, padding=0)
554
555 # SPS calculation
556 now = time.monotonic()
557 if self.last_sps_time is not None:
558 elapsed = now - self.last_sps_time
559 if elapsed >= 1.0:
560 samples_delta = self.sample_count - self.last_sps_count
561 sps = samples_delta / elapsed
562 # Show more info including data points per channel
563 pts = [len(self.t[ch]) for ch in range(NUM_CHANNELS)]
564 self.lbl_sps.setText(
565 f"SPS: {sps:.1f} | Pkts: {self.packets_received} | Pts: {pts}"
566 )
567 self.last_sps_time = now
568 self.last_sps_count = self.sample_count
569
570 # Per-channel mean voltage (last 100ms) - only if we have data
571 if has_data:
572 for ch in range(NUM_CHANNELS):
573 if not self.chk[ch].isChecked():
574 self.per_ch[ch].setText(f"{CHANNEL_NAMES[ch]}: n/a")
575 continue
576
577 ts = self.t.get(ch, [])
578 vs = self.v.get(ch, [])
579 if ts and vs:
580 cutoff = latest - 0.1 # Last 100ms
581 # More efficient: iterate backwards since we want recent data
582 recent = []
583 for i in range(len(ts) - 1, -1, -1):
584 if ts[i] >= cutoff:
585 recent.append(vs[i])
586 else:
587 break
588 if recent:
589 mean_v = sum(recent) / len(recent)
590 self.per_ch[ch].setText(f"{CHANNEL_NAMES[ch]}: {mean_v:.4f} V")
591 else:
592 self.per_ch[ch].setText(f"{CHANNEL_NAMES[ch]}: n/a")
593 else:
594 self.per_ch[ch].setText(f"{CHANNEL_NAMES[ch]}: n/a")
595 else:
596 # No data - just set n/a for all
597 for ch in range(NUM_CHANNELS):
598 self.per_ch[ch].setText(f"{CHANNEL_NAMES[ch]}: n/a")
599
600 def _open_settings(self):
601 dlg = SettingsWindow(self)
602 dlg.exec()
603
604 def _open_console(self):
605 if self.console_win is None or not self.console_win.isVisible():
606 self.console_win = ConsoleWindow(self)
607 self.console_win.show()
608 self.console_win.raise_()
609 self.console_win.activateWindow()
610
611 def _on_autoscale(self):
612 self.autoscale = self.chk_autoscale.isChecked()
613 self._plot_needs_update = True
614 self._update_plot()
615
616 def _export_csv(self):
617 """Export current data to CSV file"""
618 # Check if we have any data
619 total_points = sum(len(self.t[ch]) for ch in range(NUM_CHANNELS))
620 if total_points == 0:
621 QtWidgets.QMessageBox.warning(
622 self, "No Data", "No data available to export."
623 )
624 return
625
626 # Generate default filename with test start time, or current time if test hasn't started
627 if self.test_start_time:
628 timestamp = self.test_start_time.strftime("%Y%m%d_%H%M%S")
629 else:
630 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
631 default_filename = f"adc_data_{timestamp}.csv"
632
633 # Open file dialog
634 filename, _ = QtWidgets.QFileDialog.getSaveFileName(
635 self, "Save CSV File", default_filename, "CSV Files (*.csv);;All Files (*)"
636 )
637
638 if not filename:
639 return # User cancelled
640
641 try:
642 with open(filename, "w", newline="", encoding="utf-8") as csvfile:
643 writer = csv.writer(csvfile)
644
645 # Write header with single time column and all channels
646 header = ["Time (s)"]
647 for ch in range(NUM_CHANNELS):
648 header.append(f"{CHANNEL_NAMES[ch]} (V)")
649 writer.writerow(header)
650
651 # Use first channel's timestamps as the reference time
652 # All channels should have the same number of samples since they're sampled together
653 time_ref = list(self.t[0]) if len(self.t[0]) > 0 else []
654 max_points = max(len(self.t[ch]) for ch in range(NUM_CHANNELS))
655
656 # Write data row by row with single time column
657 for i in range(max_points):
658 row = []
659
660 # Use first channel's time, or empty if not available
661 if i < len(time_ref):
662 row.append(f"{time_ref[i]:.6f}")
663 else:
664 row.append("")
665
666 # Add voltage for each channel
667 for ch in range(NUM_CHANNELS):
668 if i < len(self.v[ch]):
669 row.append(f"{self.v[ch][i]:.6f}")
670 else:
671 row.append("")
672
673 writer.writerow(row)
674
675 # Count actual data points
676 points_per_channel = [len(self.t[ch]) for ch in range(NUM_CHANNELS)]
677 QtWidgets.QMessageBox.information(
678 self,
679 "Export Successful",
680 f"Data exported successfully to:\n{filename}\n\n"
681 f"Points per channel: {points_per_channel}",
682 )
683 except Exception as e:
684 QtWidgets.QMessageBox.critical(
685 self, "Export Failed", f"Failed to export data:\n{str(e)}"
686 )
687
688 def closeEvent(self, event: QtGui.QCloseEvent):
689 try:
690 if self.reader is not None:
691 self.reader.stop()
692 self.reader.wait(500)
693 except Exception:
694 pass
695 super().closeEvent(event)
696
697
698# ---------------------- Entry point ----------------------
699def main():
700 app = QtWidgets.QApplication(sys.argv)
701 pg.setConfigOptions(antialias=False)
702 w = App()
703 w.show()
704 try:
705 sys.exit(app.exec())
706 except KeyboardInterrupt:
707 sys.exit(0)
708
709
710if __name__ == "__main__":
711 main()
#define min(a, b)
Definition Arduino.h:42
#define max(a, b)
Definition Arduino.h:43
static route_t * find(uint16_t pin)
Definition board_exti.c:20
closeEvent(self, QtGui.QCloseEvent event)
Definition gui.py:688
window_seconds
Definition gui.py:275
_toggle_connect
Definition gui.py:307
_open_console
Definition gui.py:315
dict chk
Definition gui.py:352
chk_autoscale
Definition gui.py:322
dict per_ch
Definition gui.py:374
cmb_port
Definition gui.py:287
_on_toggle
Definition gui.py:357
_update_plot
Definition gui.py:404
legend
Definition gui.py:343
_refresh_port_list
Definition gui.py:303
float max_v
Definition gui.py:278
_on_autoscale
Definition gui.py:324
dict curves
Definition gui.py:353
console_win
Definition gui.py:276
bool autoscale
Definition gui.py:277
_export_csv
Definition gui.py:319
test_start_time
Definition gui.py:392
__init__(self)
Definition gui.py:270
t0_us
Definition gui.py:391
set_status
Definition gui.py:459
dict v
Definition gui.py:396
lbl_status
Definition gui.py:384
btn_connect
Definition gui.py:306
int sample_count
Definition gui.py:393
reader
Definition gui.py:409
_hline(self)
Definition gui.py:411
_on_samples
Definition gui.py:456
dict t
Definition gui.py:395
int packets_received
Definition gui.py:394
last_sps_time
Definition gui.py:399
_open_settings
Definition gui.py:311
bool _plot_needs_update
Definition gui.py:406
cmb_baud
Definition gui.py:292
_connect(self, port, baud)
Definition gui.py:443
plot
Definition gui.py:335
lbl_sps
Definition gui.py:368
int last_sps_count
Definition gui.py:400
timer
Definition gui.py:403
_on_raw_bytes
Definition gui.py:462
on_bytes(self, bytes data)
Definition gui.py:255
__init__(self, parent)
Definition gui.py:226
samples_ready
Definition gui.py:60
__init__(self, str port, int baud)
Definition gui.py:66
int total_samples
Definition gui.py:74
bool _stop
Definition gui.py:70
stop(self)
Definition gui.py:76
_find_and_parse_packets(self)
Definition gui.py:84
int packets_received
Definition gui.py:73
raw_bytes
Definition gui.py:64
__init__(self, parent)
Definition gui.py:177
static void stop(emmc_log_t *log, emmc_disk_state_t why)
Definition emmc_log.c:262
static void start(gnss_frame_parser_t *p, uint8_t byte)
Definition gnss_frame.c:45
list_ports()
Definition gui.py:54
Definition run.py:1
static void emit(sensors_core_t *c, uint8_t type, const void *data, uint8_t len)
int main()
static double read(int32_t code)