diff options
| author | Christian Kolset <christian.kolset@gmail.com> | 2026-06-09 15:40:35 -0600 |
|---|---|---|
| committer | Christian Kolset <christian.kolset@gmail.com> | 2026-06-09 15:40:35 -0600 |
| commit | a35b9b121218625445bda40729a8222243199c77 (patch) | |
| tree | a429f65f6dbe62c1253c8c31730b8420153678be | |
| parent | 17b2f07545b89487c2d1bb936dc52a612d38d470 (diff) | |
Redesign Add Device dialog with streamlined two-section layout
- Unified device scanner at top (serial ports, NI devices, cameras)
replaces per-panel scan groups; clicking pre-fills port/type
- Port, baud rate, NI Device, and simulation toggle moved into
Available Devices section; shown/hidden by device type
- Device type, ID, and Protocol/Format grouped under Configuration
- Panel order: Serial/UART β Arduino β NI-DAQ (Serial first)
- Add button always enabled; validation on press shows error popup
with focus jump to the offending field
- CameraPanel loses sim_chk (sim state set by dialog via set_simulate)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | plugins/motion_capture/camera_panel.py | 92 | ||||
| -rw-r--r-- | ui/add_device_dialog.py | 485 |
2 files changed, 270 insertions, 307 deletions
diff --git a/plugins/motion_capture/camera_panel.py b/plugins/motion_capture/camera_panel.py index 611c942..3fdd286 100644 --- a/plugins/motion_capture/camera_panel.py +++ b/plugins/motion_capture/camera_panel.py @@ -1,17 +1,15 @@ """ motion_capture/camera_panel.py -CameraPanel for AddDeviceDialog β scans for OpenCV cameras and builds +CameraPanel for AddDeviceDialog β shows selected camera info and builds a CameraDevice when the user clicks "Add Device". +Camera discovery and simulation toggle are handled by AddDeviceDialog. """ from __future__ import annotations -from PyQt6.QtCore import Qt, QThread, pyqtSignal -from PyQt6.QtWidgets import ( - QCheckBox, QGroupBox, QHBoxLayout, QLabel, QListWidget, - QListWidgetItem, QPushButton, QVBoxLayout, QWidget, -) +from PyQt6.QtCore import QThread, pyqtSignal +from PyQt6.QtWidgets import QLabel, QVBoxLayout, QWidget class CameraScanThread(QThread): @@ -40,44 +38,17 @@ class CameraPanel(QWidget): def __init__(self): super().__init__() - self._cameras: list = [] self._selected_index: int = 0 - self._scanner = None + self._simulate: bool = False lay = QVBoxLayout(self) lay.setContentsMargins(0, 4, 0, 4) lay.setSpacing(8) - self.sim_chk = QCheckBox("Simulation mode (Lissajous pattern, no camera needed)") - self.sim_chk.setChecked(False) - lay.addWidget(self.sim_chk) - - scan_grp = QGroupBox("Available Cameras") - sg = QVBoxLayout(scan_grp) - sg.setSpacing(4) - - top = QHBoxLayout() - self._scan_btn = QPushButton("π Scan Cameras") - self._scan_btn.setObjectName("addTraceBtn") - self._scan_btn.clicked.connect(self._scan) - self._status = QLabel("Click Scan to detect cameras") - self._status.setObjectName("traceSource") - top.addWidget(self._scan_btn) - top.addWidget(self._status, 1) - sg.addLayout(top) - - self._list = QListWidget() - self._list.setMaximumHeight(120) - self._list.setObjectName("portList") - self._list.itemClicked.connect(self._on_select) - self._list.setToolTip("Click a camera to select it") - sg.addWidget(self._list) - - hint = QLabel("β Click a camera above to select it") - hint.setObjectName("traceSource") - sg.addWidget(hint) - - lay.addWidget(scan_grp) + self._selected_lbl = QLabel("No camera selected β scan above and click a camera") + self._selected_lbl.setObjectName("traceSource") + self._selected_lbl.setWordWrap(True) + lay.addWidget(self._selected_lbl) note = QLabel( "Requires OpenCV: pip install opencv-python\n" @@ -88,45 +59,20 @@ class CameraPanel(QWidget): lay.addWidget(note) lay.addStretch() - # ββ Scan βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ - - def _scan(self): - self._scan_btn.setEnabled(False) - self._status.setText("Scanningβ¦") - self._list.clear() - self._scanner = CameraScanThread() - self._scanner.cameras_found.connect(self._on_found) - self._scanner.start() - - def _on_found(self, cameras): - self._scan_btn.setEnabled(True) - self._cameras = cameras - self._list.clear() - if not cameras: - self._status.setText("No cameras found") - item = QListWidgetItem(" No cameras detected") - item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable) - self._list.addItem(item) - else: - self._status.setText(f"{len(cameras)} camera(s) found") - for idx, label in cameras: - item = QListWidgetItem(f" {label}") - item.setData(Qt.ItemDataRole.UserRole, idx) - self._list.addItem(item) - self._list.setCurrentRow(0) - self._selected_index = cameras[0][0] - - def _on_select(self, item: QListWidgetItem): - idx = item.data(Qt.ItemDataRole.UserRole) - if idx is not None: - self._selected_index = idx - - # ββ Build βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + def is_valid(self) -> bool: + return True + + def set_simulate(self, simulate: bool): + self._simulate = simulate + + def set_camera_index(self, index: int): + self._selected_index = index + self._selected_lbl.setText(f"Selected: Camera {index}") def build_device(self, device_id: str): from device import CameraDevice return CameraDevice( device_id=device_id, camera_index=self._selected_index, - simulate=self.sim_chk.isChecked(), + simulate=self._simulate, ) diff --git a/ui/add_device_dialog.py b/ui/add_device_dialog.py index 5258995..f0cbde1 100644 --- a/ui/add_device_dialog.py +++ b/ui/add_device_dialog.py @@ -1,12 +1,9 @@ """ ui/add_device_dialog.py -Add Device dialog β user selects physical device type (Arduino / NI-DAQ / Serial). -Backend is determined by device type; Arduino and NI-DAQ combine analog input -and digital I/O into one physical device entry. +Add Device dialog β unified device scanner + type/config form. """ -import os from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QComboBox, QLineEdit, QSpinBox, QDoubleSpinBox, @@ -15,7 +12,6 @@ from PyQt6.QtWidgets import ( QListWidgetItem, QFrame, QSizePolicy, ) from PyQt6.QtCore import Qt, QThread, pyqtSignal -from PyQt6.QtGui import QFont from devices.device_registry import DeviceRegistry from devices.arduino_device import ArduinoDevice @@ -43,154 +39,132 @@ class NIScanThread(QThread): self.devices_found.emit(devs) -# ββ Reusable scanner widgets ββββββββββββββββββββββββββββββββββββββββββββββββββ +# ββ Device list scanner (embedded in Available Devices section) βββββββββββββββ -class PortScanGroup(QGroupBox): - """Scan-and-click widget that fills a target QLineEdit with the chosen port.""" +class _DeviceListScanner(QWidget): + """Scan button + result list. Emits device_selected(kind, value) on click.""" - def __init__(self, target_edit: QLineEdit): - super().__init__("Available Serial Ports") - self._target = target_edit - self._scanner = None + device_selected = pyqtSignal(str, object) # kind = "serial"|"ni"|"camera" + + def __init__(self): + super().__init__() + self._port_scanner = None + self._ni_scanner = None + self._cam_scanner = None + self._pending = 0 lay = QVBoxLayout(self) + lay.setContentsMargins(0, 0, 0, 0) lay.setSpacing(4) top = QHBoxLayout() - self._scan_btn = QPushButton("π Scan Ports") + self._scan_btn = QPushButton("π Scan All") self._scan_btn.setObjectName("addTraceBtn") - self._scan_btn.clicked.connect(self._scan) - self._status = QLabel("Click Scan to detect ports") + self._scan_btn.clicked.connect(self.scan_all) + self._status = QLabel("Click Scan to detect 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.setFixedHeight(120) self._list.setObjectName("portList") self._list.itemClicked.connect(self._on_select) - self._list.setToolTip("Click a port to select it") + self._list.setToolTip("Click a device to pre-fill configuration") lay.addWidget(self._list) - hint = QLabel("β Click a port above to fill the Port field") + hint = QLabel("β Click a device to pre-fill port and configuration") hint.setObjectName("traceSource") lay.addWidget(hint) - def _scan(self): + def scan_all(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) + self._pending = 0 - 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._pending += 1 + self._port_scanner = PortScanThread() + self._port_scanner.ports_found.connect(self._on_ports) + self._port_scanner.start() - 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) + self._pending += 1 + self._ni_scanner = NIScanThread() + self._ni_scanner.devices_found.connect(self._on_ni) + self._ni_scanner.start() - 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() + try: + from plugins.motion_capture.camera_panel import CameraScanThread + self._pending += 1 + self._cam_scanner = CameraScanThread() + self._cam_scanner.cameras_found.connect(self._on_cameras) + self._cam_scanner.start() + except Exception: + pass + + def _check_done(self): + self._pending -= 1 + if self._pending <= 0: + self._scan_btn.setEnabled(True) + n = sum( + 1 for i in range(self._list.count()) + if self._list.item(i).flags() & Qt.ItemFlag.ItemIsSelectable + ) + self._status.setText(f"{n} device(s) found" if n else "No devices found") + + def _on_ports(self, ports): + for device, desc in ports: + label = f"[Serial] {device}" + if desc and desc != device: + label += f" β {desc}" + item = QListWidgetItem(label) + item.setData(Qt.ItemDataRole.UserRole, ("serial", device)) + self._list.addItem(item) + self._check_done() + + def _on_ni(self, devices): + for name, product in devices: + label = f"[NI-DAQ] {name}" + if product: + label += f" β {product}" + item = QListWidgetItem(label) + item.setData(Qt.ItemDataRole.UserRole, ("ni", name)) + self._list.addItem(item) + self._check_done() - 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) + def _on_cameras(self, cameras): + for idx, label_str in cameras: + item = QListWidgetItem(f"[Camera] {label_str}") + item.setData(Qt.ItemDataRole.UserRole, ("camera", idx)) 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) + self._check_done() def _on_select(self, item: QListWidgetItem): - name = item.data(Qt.ItemDataRole.UserRole) - if name: - self._target.setText(name) - + data = item.data(Qt.ItemDataRole.UserRole) + if data: + self.device_selected.emit(data[0], data[1]) -# ββ Per-type config panels ββββββββββββββββββββββββββββββββββββββββββββββββββββ -class ArduinoPanel(QWidget): - """Config fields for ArduinoDevice (analog + digital I/O combined).""" +# ββ Per-type panels (remaining type-specific fields only) βββββββββββββββββββββ +class SerialPanel(QWidget): def __init__(self): super().__init__() - lay = QFormLayout(self) + lay = QVBoxLayout(self) lay.setContentsMargins(0, 4, 0, 4) + note = QLabel("Additional protocol settings available in the device config dialog.") + note.setObjectName("traceSource") + note.setWordWrap(True) + lay.addWidget(note) + lay.addStretch() - self.port_edit = QLineEdit("") - self.port_edit.setPlaceholderText("e.g. COM3 or /dev/ttyUSB0") - lay.addRow("Port:", self.port_edit) - self.baud_cb = QComboBox() - self.baud_cb.addItems(["9600", "57600", "115200", "230400"]) - self.baud_cb.setCurrentText("115200") - lay.addRow("Baud Rate:", self.baud_cb) +class ArduinoPanel(QWidget): + def __init__(self): + super().__init__() + lay = QFormLayout(self) + lay.setContentsMargins(0, 4, 0, 4) self.analog_pins_edit = QLineEdit("0, 1, 2, 3") self.analog_pins_edit.setPlaceholderText("e.g. 0, 1, 2, 3 (indices into A0βA5)") @@ -204,39 +178,13 @@ class ArduinoPanel(QWidget): self.do_pins_edit.setPlaceholderText("e.g. 5, 6, 7, 9") lay.addRow("Digital Output Pins:", self.do_pins_edit) - self.sim_chk = QCheckBox("Simulation mode") - self.sim_chk.setChecked(False) - lay.addRow(self.sim_chk) - - lay.addRow(PortScanGroup(self.port_edit)) - - def build_device(self, device_id: str) -> ArduinoDevice: - from devices.arduino_device import _parse_pin_edit, _parse_analog_pin_edit - analog = _parse_analog_pin_edit(self.analog_pins_edit.text()) or None - di = _parse_pin_edit(self.di_pins_edit.text()) or None - do = _parse_pin_edit(self.do_pins_edit.text()) or None - return ArduinoDevice( - device_id=device_id, - analog_pins=analog, - di_pins=di, - do_pins=do, - simulate=self.sim_chk.isChecked(), - port=self.port_edit.text().strip() or "COM3", - baud=int(self.baud_cb.currentText()), - ) - class NidaqmxPanel(QWidget): - """Config fields for NidaqmxDevice (analog + digital I/O combined).""" - def __init__(self): super().__init__() lay = QFormLayout(self) lay.setContentsMargins(0, 4, 0, 4) - self.ni_device_edit = QLineEdit("Dev1") - lay.addRow("NI Device:", self.ni_device_edit) - self.analog_spin = QSpinBox() self.analog_spin.setRange(0, 16) self.analog_spin.setValue(4) @@ -264,90 +212,29 @@ class NidaqmxPanel(QWidget): self.do_spin.setValue(4) lay.addRow("Digital Outputs:", self.do_spin) - self.sim_chk = QCheckBox("Simulation mode") - self.sim_chk.setChecked(False) - lay.addRow(self.sim_chk) - - lay.addRow(NIScanGroup(self.ni_device_edit)) - - def build_device(self, device_id: str) -> NidaqmxDevice: - return NidaqmxDevice( - device_id=device_id, - num_analog=self.analog_spin.value(), - min_v=self.min_v_spin.value(), - max_v=self.max_v_spin.value(), - num_di=self.di_spin.value(), - num_do=self.do_spin.value(), - simulate=self.sim_chk.isChecked(), - ni_device=self.ni_device_edit.text().strip() or "Dev1", - ) - - -class SerialPanel(QWidget): - """Config fields for SerialDevice β port, baud, protocol/format, simulate.""" - - def __init__(self): - super().__init__() - lay = QVBoxLayout(self) - lay.setContentsMargins(0, 4, 0, 4) - lay.setSpacing(8) - - form = QFormLayout() - self.port_edit = QLineEdit("") - self.port_edit.setPlaceholderText("e.g. COM3 or /dev/ttyUSB0") - form.addRow("Port:", self.port_edit) +# ββ Panel registry (order matters: first = default) βββββββββββββββββββββββββββ - self.baud_cb = QComboBox() - self.baud_cb.addItems(["1200", "2400", "4800", "9600", "19200", "38400", - "57600", "115200", "230400", "460800"]) - self.baud_cb.setCurrentText("115200") - form.addRow("Baud Rate:", self.baud_cb) - - self.fmt_cb = QComboBox() - self.fmt_cb.addItems(list(_FORMAT_LABELS.keys())) - form.addRow("Protocol / Format:", self.fmt_cb) - - self.sim_chk = QCheckBox("Simulation mode") - self.sim_chk.setChecked(False) - form.addRow(self.sim_chk) - - note = QLabel("Protocol-specific settings available in device config dialog.") - note.setObjectName("traceSource") - note.setWordWrap(True) - - lay.addLayout(form) - lay.addWidget(note) - lay.addWidget(PortScanGroup(self.port_edit)) +_PANELS = { + "Serial / UART": (SerialPanel, "ser"), + "Arduino": (ArduinoPanel, "ard"), + "NI-DAQ": (NidaqmxPanel, "ni"), +} - def build_device(self, device_id: str) -> SerialDevice: - fmt = _FORMAT_LABELS.get(self.fmt_cb.currentText(), "key:val") - return SerialDevice( - device_id=device_id, - port=self.port_edit.text().strip() or "COM3", - baud_rate=int(self.baud_cb.currentText()), - parse_format=fmt, - simulate=self.sim_chk.isChecked(), - ) +_SERIAL_TYPES = {"Serial / UART", "Arduino"} +_NI_TYPES = {"NI-DAQ"} # ββ Main dialog βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ -_PANELS = { - "Arduino": (ArduinoPanel, "ard"), - "NI-DAQ": (NidaqmxPanel, "ni"), - "Serial / UART":(SerialPanel, "ser"), -} - - class AddDeviceDialog(QDialog): def __init__(self, registry: DeviceRegistry, parent=None): super().__init__(parent) self.registry = registry self.created_device = None self.setWindowTitle("Add Device") - self.setMinimumSize(480, 480) - self.resize(500, 620) + self.setMinimumSize(480, 580) + self.resize(500, 700) self._build() def _build(self): @@ -358,26 +245,67 @@ class AddDeviceDialog(QDialog): hdr.setObjectName("devWindowTitle") root.addWidget(hdr) - div = QFrame() - div.setFrameShape(QFrame.Shape.HLine) - div.setObjectName("devWindowDivider") - root.addWidget(div) + root.addWidget(_divider()) + + # ββ Available Devices βββββββββββββββββββββββββββββββββββββββββββββ + avail_grp = QGroupBox("Available Devices") + avail_lay = QVBoxLayout(avail_grp) + avail_lay.setSpacing(6) + + self._scanner = _DeviceListScanner() + self._scanner.device_selected.connect(self._on_device_selected) + avail_lay.addWidget(self._scanner) + + conn_form = QFormLayout() + conn_form.setSpacing(6) + + self._port_edit = QLineEdit() + self._port_edit.setPlaceholderText("e.g. COM3 or /dev/ttyUSB0") + self._port_lbl = QLabel("Port:") + conn_form.addRow(self._port_lbl, self._port_edit) + + self._baud_cb = QComboBox() + self._baud_cb.addItems(["1200", "2400", "4800", "9600", "19200", "38400", + "57600", "115200", "230400", "460800"]) + self._baud_cb.setCurrentText("115200") + self._baud_lbl = QLabel("Baud Rate:") + conn_form.addRow(self._baud_lbl, self._baud_cb) + + self._ni_edit = QLineEdit("Dev1") + self._ni_lbl = QLabel("NI Device:") + conn_form.addRow(self._ni_lbl, self._ni_edit) + + self._sim_chk = QCheckBox("Simulation mode") + conn_form.addRow(self._sim_chk) + + avail_lay.addLayout(conn_form) + root.addWidget(avail_grp) + + root.addWidget(_divider()) + + # ββ Configuration βββββββββββββββββββββββββββββββββββββββββββββββββ + cfg_hdr = QLabel("Configuration") + cfg_hdr.setObjectName("devWindowTitle") + root.addWidget(cfg_hdr) + + cfg_form = QFormLayout() + cfg_form.setSpacing(6) - type_row = QFormLayout() self._type_cb = QComboBox() self._type_cb.addItems(list(_PANELS.keys())) self._type_cb.currentIndexChanged.connect(self._on_type_changed) - type_row.addRow("Device Type:", self._type_cb) + cfg_form.addRow("Device Type:", self._type_cb) self._id_edit = QLineEdit() self._id_edit.setPlaceholderText("Leave blank for auto") - type_row.addRow("Device ID:", self._id_edit) - root.addLayout(type_row) + cfg_form.addRow("Device ID:", self._id_edit) + + self._fmt_cb = QComboBox() + self._fmt_cb.addItems(list(_FORMAT_LABELS.keys())) + self._fmt_lbl = QLabel("Protocol / Format:") + cfg_form.addRow(self._fmt_lbl, self._fmt_cb) - div2 = QFrame() - div2.setFrameShape(QFrame.Shape.HLine) - div2.setObjectName("devWindowDivider") - root.addWidget(div2) + root.addLayout(cfg_form) self._stack = QStackedWidget() self._panels = {} @@ -387,28 +315,59 @@ class AddDeviceDialog(QDialog): self._stack.addWidget(panel) root.addWidget(self._stack, 1) - div3 = QFrame() - div3.setFrameShape(QFrame.Shape.HLine) - div3.setObjectName("devWindowDivider") - root.addWidget(div3) + root.addWidget(_divider()) btn_row = QHBoxLayout() btn_row.addStretch() cancel = QPushButton("Cancel") cancel.clicked.connect(self.reject) - add = QPushButton("Add Device") - add.setObjectName("applyButton") - add.setDefault(True) - add.clicked.connect(self._on_add) + self._add_btn = QPushButton("Add Device") + self._add_btn.setObjectName("applyButton") + self._add_btn.setDefault(True) + self._add_btn.clicked.connect(self._on_add) btn_row.addWidget(cancel) - btn_row.addWidget(add) + btn_row.addWidget(self._add_btn) root.addLayout(btn_row) self._on_type_changed(0) + # ββ Handlers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + + def _on_device_selected(self, kind: str, value): + if kind == "serial": + self._port_edit.setText(value) + self._set_type("Serial / UART") + elif kind == "ni": + self._ni_edit.setText(value) + self._set_type("NI-DAQ") + elif kind == "camera": + self._set_type("Camera") + panel = self._panels.get("Camera") + if panel and hasattr(panel, "set_camera_index"): + panel.set_camera_index(value) + + def _set_type(self, name: str): + idx = self._type_cb.findText(name) + if idx >= 0: + self._type_cb.setCurrentIndex(idx) + def _on_type_changed(self, idx: int): self._stack.setCurrentIndex(idx) type_name = self._type_cb.currentText() + + is_serial = type_name in _SERIAL_TYPES + is_ni = type_name in _NI_TYPES + is_serial_uart = type_name == "Serial / UART" + + self._port_lbl.setVisible(is_serial) + self._port_edit.setVisible(is_serial) + self._baud_lbl.setVisible(is_serial) + self._baud_cb.setVisible(is_serial) + self._ni_lbl.setVisible(is_ni) + self._ni_edit.setVisible(is_ni) + self._fmt_lbl.setVisible(is_serial_uart) + self._fmt_cb.setVisible(is_serial_uart) + _, prefix = _PANELS.get(type_name, (None, "dev")) existing = {d.info.device_id for d in self.registry.all_instances()} for i in range(100): @@ -420,7 +379,22 @@ class AddDeviceDialog(QDialog): def _on_add(self): type_name = self._type_cb.currentText() panel = self._panels[type_name] - dev_id = self._id_edit.text().strip() + sim = self._sim_chk.isChecked() + + if type_name in _SERIAL_TYPES and not sim and not self._port_edit.text().strip(): + QMessageBox.warning(self, "Port Required", + "Enter a port (e.g. COM3 or /dev/ttyUSB0)\n" + "or enable Simulation mode.") + self._port_edit.setFocus() + return + if type_name in _NI_TYPES and not sim and not self._ni_edit.text().strip(): + QMessageBox.warning(self, "NI Device Required", + "Enter an NI device name (e.g. Dev1)\n" + "or enable Simulation mode.") + self._ni_edit.setFocus() + return + + dev_id = self._id_edit.text().strip() if not dev_id: _, prefix = _PANELS.get(type_name, (None, "dev")) @@ -437,9 +411,52 @@ class AddDeviceDialog(QDialog): f"Choose a different ID.") return + port = self._port_edit.text().strip() or "COM3" + baud = int(self._baud_cb.currentText()) + ni = self._ni_edit.text().strip() or "Dev1" + fmt = _FORMAT_LABELS.get(self._fmt_cb.currentText(), "key:val") + try: - dev = panel.build_device(dev_id) + if type_name == "Serial / UART": + dev = SerialDevice( + device_id=dev_id, port=port, baud_rate=baud, + parse_format=fmt, simulate=sim, + ) + elif type_name == "Arduino": + from devices.arduino_device import _parse_pin_edit, _parse_analog_pin_edit + analog = _parse_analog_pin_edit(panel.analog_pins_edit.text()) or None + di = _parse_pin_edit(panel.di_pins_edit.text()) or None + do = _parse_pin_edit(panel.do_pins_edit.text()) or None + dev = ArduinoDevice( + device_id=dev_id, analog_pins=analog, di_pins=di, do_pins=do, + simulate=sim, port=port, baud=baud, + ) + elif type_name == "NI-DAQ": + dev = NidaqmxDevice( + device_id=dev_id, + num_analog=panel.analog_spin.value(), + min_v=panel.min_v_spin.value(), + max_v=panel.max_v_spin.value(), + num_di=panel.di_spin.value(), + num_do=panel.do_spin.value(), + simulate=sim, + ni_device=ni, + ) + else: + if hasattr(panel, "set_simulate"): + panel.set_simulate(sim) + dev = panel.build_device(dev_id) + self.created_device = dev self.accept() except Exception as e: QMessageBox.critical(self, "Error creating device", str(e)) + + +# ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + +def _divider() -> QFrame: + div = QFrame() + div.setFrameShape(QFrame.Shape.HLine) + div.setObjectName("devWindowDivider") + return div |
