summaryrefslogtreecommitdiff
path: root/ui/add_device_dialog.py
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-04-22 11:06:45 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-04-22 11:06:45 -0600
commit38ba9706e43c24a9d882b9d0d627812e01a30744 (patch)
treefdf6720447a6cc6e4285fa6c4b73889c829c302c /ui/add_device_dialog.py
parenta00ed2a932fdb0f3103777ac3eac106701484ca2 (diff)
Added port scanning for new devices and only shows related backend config lines
Diffstat (limited to 'ui/add_device_dialog.py')
-rw-r--r--ui/add_device_dialog.py260
1 files changed, 196 insertions, 64 deletions
diff --git a/ui/add_device_dialog.py b/ui/add_device_dialog.py
index 03ece63..50f234d 100644
--- a/ui/add_device_dialog.py
+++ b/ui/add_device_dialog.py
@@ -36,6 +36,150 @@ class PortScanThread(QThread):
self.ports_found.emit(ArduinoLayer.list_ports())
+# ── Background scan threads ───────────────────────────────────────────────────
+
+class NIScanThread(QThread):
+ devices_found = pyqtSignal(list)
+
+ def run(self):
+ try:
+ import nidaqmx # type: ignore
+ devs = [(d.name, d.product_type) for d in nidaqmx.system.System().devices]
+ except Exception:
+ devs = []
+ self.devices_found.emit(devs)
+
+
+# ── Reusable port scanner widget ─────────────────────────────────────────────
+
+class PortScanGroup(QGroupBox):
+ """Scan-and-click widget that fills a target QLineEdit with the chosen port."""
+
+ def __init__(self, target_edit: QLineEdit):
+ super().__init__("Available Serial Ports")
+ self._target = target_edit
+ self._scanner = None
+
+ lay = QVBoxLayout(self)
+ lay.setSpacing(4)
+
+ top = QHBoxLayout()
+ self._scan_btn = QPushButton("🔍 Scan Ports")
+ self._scan_btn.setObjectName("addTraceBtn")
+ self._scan_btn.clicked.connect(self._scan)
+ self._status = QLabel("Click Scan to detect ports")
+ self._status.setObjectName("traceSource")
+ top.addWidget(self._scan_btn)
+ top.addWidget(self._status, 1)
+ lay.addLayout(top)
+
+ self._list = QListWidget()
+ self._list.setMaximumHeight(100)
+ self._list.setObjectName("portList")
+ self._list.itemClicked.connect(self._on_select)
+ self._list.setToolTip("Click a port to select it")
+ lay.addWidget(self._list)
+
+ hint = QLabel("↑ Click a port above to fill the Port field")
+ hint.setObjectName("traceSource")
+ lay.addWidget(hint)
+
+ def _scan(self):
+ self._scan_btn.setEnabled(False)
+ self._status.setText("Scanning…")
+ self._list.clear()
+ self._scanner = PortScanThread()
+ self._scanner.ports_found.connect(self._on_found)
+ self._scanner.start()
+
+ def _on_found(self, ports):
+ self._scan_btn.setEnabled(True)
+ self._list.clear()
+ if not ports:
+ self._status.setText("No ports found")
+ item = QListWidgetItem(" No serial ports detected")
+ item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable)
+ self._list.addItem(item)
+ else:
+ self._status.setText(f"{len(ports)} port(s) found")
+ for device, desc in ports:
+ label = f" {device}"
+ if desc and desc != device:
+ label += f" — {desc}"
+ item = QListWidgetItem(label)
+ item.setData(Qt.ItemDataRole.UserRole, device)
+ self._list.addItem(item)
+
+ def _on_select(self, item: QListWidgetItem):
+ port = item.data(Qt.ItemDataRole.UserRole)
+ if port:
+ self._target.setText(port)
+
+
+class NIScanGroup(QGroupBox):
+ """Scan-and-click widget that fills a target QLineEdit with the chosen NI device."""
+
+ def __init__(self, target_edit: QLineEdit):
+ super().__init__("Available NI Devices")
+ self._target = target_edit
+ self._scanner = None
+
+ lay = QVBoxLayout(self)
+ lay.setSpacing(4)
+
+ top = QHBoxLayout()
+ self._scan_btn = QPushButton("🔍 Scan NI Devices")
+ self._scan_btn.setObjectName("addTraceBtn")
+ self._scan_btn.clicked.connect(self._scan)
+ self._status = QLabel("Click Scan to detect NI devices")
+ self._status.setObjectName("traceSource")
+ top.addWidget(self._scan_btn)
+ top.addWidget(self._status, 1)
+ lay.addLayout(top)
+
+ self._list = QListWidget()
+ self._list.setMaximumHeight(100)
+ self._list.setObjectName("portList")
+ self._list.itemClicked.connect(self._on_select)
+ self._list.setToolTip("Click a device to select it")
+ lay.addWidget(self._list)
+
+ hint = QLabel("↑ Click a device above to fill the NI Device field")
+ hint.setObjectName("traceSource")
+ lay.addWidget(hint)
+
+ def _scan(self):
+ self._scan_btn.setEnabled(False)
+ self._status.setText("Scanning…")
+ self._list.clear()
+ self._scanner = NIScanThread()
+ self._scanner.devices_found.connect(self._on_found)
+ self._scanner.start()
+
+ def _on_found(self, devices):
+ self._scan_btn.setEnabled(True)
+ self._list.clear()
+ if not devices:
+ self._status.setText("No NI devices found")
+ item = QListWidgetItem(" No NI devices detected")
+ item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable)
+ self._list.addItem(item)
+ else:
+ self._status.setText(f"{len(devices)} device(s) found")
+ for name, product in devices:
+ label = f" {name}"
+ if product:
+ label += f" — {product}"
+ item = QListWidgetItem(label)
+ item.setData(Qt.ItemDataRole.UserRole, name)
+ self._list.addItem(item)
+
+ def _on_select(self, item: QListWidgetItem):
+ name = item.data(Qt.ItemDataRole.UserRole)
+ if name:
+ self._target.setText(name)
+
+
# ── Per-type config panels ────────────────────────────────────────────────────
class AnalogInputPanel(QWidget):
@@ -73,7 +217,6 @@ class AnalogInputPanel(QWidget):
self.max_v_spin.setSuffix(" V")
lay.addRow("Max Voltage:", self.max_v_spin)
- # Arduino port (shown always, relevant for arduino backend)
self.ard_port_edit = QLineEdit("")
self.ard_port_edit.setPlaceholderText("e.g. COM3 or /dev/ttyUSB0")
lay.addRow("Arduino Port:", self.ard_port_edit)
@@ -85,9 +228,34 @@ class AnalogInputPanel(QWidget):
# Simulate
self.sim_chk = QCheckBox("Simulation mode")
- self.sim_chk.setChecked(True)
+ self.sim_chk.setChecked(False)
lay.addRow(self.sim_chk)
+ self._ni_scan_grp = NIScanGroup(self.ni_device_edit)
+ lay.addRow(self._ni_scan_grp)
+
+ self._ard_scan_grp = PortScanGroup(self.ard_port_edit)
+ lay.addRow(self._ard_scan_grp)
+
+ self._ni_widgets = [self.ni_device_edit, self.min_v_spin, self.max_v_spin, self._ni_scan_grp]
+ self._ard_widgets = [self.ard_port_edit, self.ard_baud_cb, self._ard_scan_grp]
+ self.backend_cb.currentTextChanged.connect(self._on_backend_changed)
+ self._on_backend_changed(self.backend_cb.currentText())
+
+ def _on_backend_changed(self, backend: str):
+ ni = backend == "nidaqmx"
+ lay = self.layout()
+ for w in self._ni_widgets:
+ w.setVisible(ni)
+ lbl = lay.labelForField(w)
+ if lbl:
+ lbl.setVisible(ni)
+ for w in self._ard_widgets:
+ w.setVisible(not ni)
+ lbl = lay.labelForField(w)
+ if lbl:
+ lbl.setVisible(not ni)
+
def build_device(self, device_id: str) -> AnalogInputDevice:
return AnalogInputDevice(
device_id=device_id,
@@ -132,9 +300,32 @@ class DigitalIOPanel(QWidget):
lay.addRow("Arduino Port:", self.ard_port_edit)
self.sim_chk = QCheckBox("Simulation mode")
- self.sim_chk.setChecked(True)
+ self.sim_chk.setChecked(False)
lay.addRow(self.sim_chk)
+ self._ni_scan_grp = NIScanGroup(self.ni_device_edit)
+ lay.addRow(self._ni_scan_grp)
+
+ self._ard_scan_grp = PortScanGroup(self.ard_port_edit)
+ lay.addRow(self._ard_scan_grp)
+
+ self.backend_cb.currentTextChanged.connect(self._on_backend_changed)
+ self._on_backend_changed(self.backend_cb.currentText())
+
+ def _on_backend_changed(self, backend: str):
+ ni = backend == "nidaqmx"
+ lay = self.layout()
+ for w, show in [
+ (self.ni_device_edit, ni),
+ (self._ni_scan_grp, ni),
+ (self.ard_port_edit, not ni),
+ (self._ard_scan_grp, not ni),
+ ]:
+ w.setVisible(show)
+ lbl = lay.labelForField(w)
+ if lbl:
+ lbl.setVisible(show)
+
def build_device(self, device_id: str) -> DigitalIODevice:
return DigitalIODevice(
device_id=device_id,
@@ -177,70 +368,11 @@ class SerialPanel(QWidget):
form.addRow("Parse Format:", self.fmt_cb)
self.sim_chk = QCheckBox("Simulation mode")
- self.sim_chk.setChecked(True)
+ self.sim_chk.setChecked(False)
form.addRow(self.sim_chk)
lay.addLayout(form)
-
- # Port scanner
- scan_grp = QGroupBox("Available Serial Ports")
- scan_lay = QVBoxLayout(scan_grp)
- scan_lay.setSpacing(4)
-
- scan_top = QHBoxLayout()
- self._scan_btn = QPushButton("🔍 Scan Ports")
- self._scan_btn.setObjectName("addTraceBtn")
- self._scan_btn.clicked.connect(self._scan_ports)
- self._scan_status = QLabel("Click Scan to detect ports")
- self._scan_status.setObjectName("traceSource")
- scan_top.addWidget(self._scan_btn)
- scan_top.addWidget(self._scan_status, 1)
- scan_lay.addLayout(scan_top)
-
- self._port_list = QListWidget()
- self._port_list.setMaximumHeight(100)
- self._port_list.setObjectName("portList")
- self._port_list.itemClicked.connect(self._on_port_selected)
- self._port_list.setToolTip("Click a port to select it")
- scan_lay.addWidget(self._port_list)
-
- hint = QLabel("↑ Click a port above to fill the Port field")
- hint.setObjectName("traceSource")
- scan_lay.addWidget(hint)
- lay.addWidget(scan_grp)
-
- self._scanner = None
-
- def _scan_ports(self):
- self._scan_btn.setEnabled(False)
- self._scan_status.setText("Scanning…")
- self._port_list.clear()
- self._scanner = PortScanThread()
- self._scanner.ports_found.connect(self._on_ports_found)
- self._scanner.start()
-
- def _on_ports_found(self, ports):
- self._scan_btn.setEnabled(True)
- self._port_list.clear()
- if not ports:
- self._scan_status.setText("No ports found")
- item = QListWidgetItem(" No serial ports detected")
- item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable)
- self._port_list.addItem(item)
- else:
- self._scan_status.setText(f"{len(ports)} port(s) found")
- for device, desc in ports:
- label = f" {device}"
- if desc and desc != device:
- label += f" — {desc}"
- item = QListWidgetItem(label)
- item.setData(Qt.ItemDataRole.UserRole, device)
- self._port_list.addItem(item)
-
- def _on_port_selected(self, item: QListWidgetItem):
- port = item.data(Qt.ItemDataRole.UserRole)
- if port:
- self.port_edit.setText(port)
+ lay.addWidget(PortScanGroup(self.port_edit))
def build_device(self, device_id: str) -> SerialDevice:
return SerialDevice(