diff options
| author | Christian Kolset <christian.kolset@gmail.com> | 2026-04-22 11:06:45 -0600 |
|---|---|---|
| committer | Christian Kolset <christian.kolset@gmail.com> | 2026-04-22 11:06:45 -0600 |
| commit | 38ba9706e43c24a9d882b9d0d627812e01a30744 (patch) | |
| tree | fdf6720447a6cc6e4285fa6c4b73889c829c302c | |
| parent | a00ed2a932fdb0f3103777ac3eac106701484ca2 (diff) | |
Added port scanning for new devices and only shows related backend config lines
54 files changed, 208 insertions, 76 deletions
@@ -39,7 +39,7 @@ Control widgets (left panel) go other direction: UI → `ControlWidget._write()` ### Key design patterns -**Shared serial port** — `api_layers/port_registry.py` holds module-level `port_registry` singleton. When multiple devices share one Arduino (e.g. `AnalogInputDevice` + `DigitalIODevice` on same port), both call `port_registry.get_layer(port, baud)`, get same `ArduinoLayer` instance. Never construct `ArduinoLayer` directly in device code. +**Shared serial port** — `api_layers/port_registry.py` holds module-level `port_registry` singleton. Multiple devices sharing one Arduino (e.g. `AnalogInputDevice` + `DigitalIODevice` on same port) both call `port_registry.get_layer(port, baud)`, get same `ArduinoLayer` instance. Never construct `ArduinoLayer` directly in device code. **Device auto-discovery** — `DeviceRegistry._discover()` scans `devices/` with `pkgutil`, imports every module, registers any class subclassing `BaseDevice`. New device type = drop file in `devices/`, no registration needed. diff --git a/CLAUDE.original.md b/CLAUDE.original.md index 05c7092..5347d9d 100644 --- a/CLAUDE.original.md +++ b/CLAUDE.original.md @@ -1,6 +1,6 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Guidance for Claude Code (claude.ai/code) working in this repo. ## Running the app @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co python main.py ``` -No build step. Runs in simulation mode by default (no hardware needed). +No build step. Simulation mode default (no hardware needed). ## Dependencies @@ -35,17 +35,17 @@ Hardware / Simulation ui/strip_chart.py StripChartWidget — consumes processed_data, renders via pyqtgraph ``` -Control widgets (left panel) go the other direction: UI → `ControlWidget._write()` → `DeviceRegistry.get_instance()` → `device.write_channel()` → api_layer. +Control widgets (left panel) go other direction: UI → `ControlWidget._write()` → `DeviceRegistry.get_instance()` → `device.write_channel()` → api_layer. ### Key design patterns -**Shared serial port** — `api_layers/port_registry.py` holds a module-level `port_registry` singleton. When multiple devices share one Arduino (e.g. `AnalogInputDevice` + `DigitalIODevice` on the same port), both call `port_registry.get_layer(port, baud)` and get the same `ArduinoLayer` instance. Never construct `ArduinoLayer` directly in device code. +**Shared serial port** — `api_layers/port_registry.py` holds module-level `port_registry` singleton. When multiple devices share one Arduino (e.g. `AnalogInputDevice` + `DigitalIODevice` on same port), both call `port_registry.get_layer(port, baud)`, get same `ArduinoLayer` instance. Never construct `ArduinoLayer` directly in device code. -**Device auto-discovery** — `DeviceRegistry._discover()` scans `devices/` with `pkgutil`, imports every module, and registers any class that subclasses `BaseDevice`. Adding a new device type = drop a file in `devices/`, no registration needed. +**Device auto-discovery** — `DeviceRegistry._discover()` scans `devices/` with `pkgutil`, imports every module, registers any class subclassing `BaseDevice`. New device type = drop file in `devices/`, no registration needed. -**Backend switching at runtime** — `AnalogInputDevice` and `DigitalIODevice` both have `switch_backend(backend, simulate, ...)` that disconnects, reconfigures, and reconnects without restarting. Called from "Apply & Reconnect" in the config dialog. +**Backend switching at runtime** — `AnalogInputDevice` and `DigitalIODevice` both have `switch_backend(backend, simulate, ...)` — disconnects, reconfigures, reconnects without restart. Called from "Apply & Reconnect" in config dialog. -**Profile persistence** — `core/profile.py` serialises the full operator state (controls, plot layout, signal pipelines, derived channels) to `.labdaq` JSON files. `ProfileManager` handles save/load. +**Profile persistence** — `core/profile.py` serialises full operator state (controls, plot layout, signal pipelines, derived channels) to `.labdaq` JSON files. `ProfileManager` handles save/load. ### Directory map @@ -69,13 +69,13 @@ Control widgets (left panel) go the other direction: UI → `ControlWidget._writ ### Arduino firmware -The firmware is embedded as `ARDUINO_FIRMWARE` in `api_layers/arduino_layer.py`. When editing it, set `N_DIG_OUT` to match the number of digital output pins in use (default was 0 — leaving pins in INPUT mode causes inverted write behaviour). Upload via Arduino IDE. +Firmware embedded as `ARDUINO_FIRMWARE` in `api_layers/arduino_layer.py`. When editing: set `N_DIG_OUT` to match digital output pins in use (default 0 — leaves pins in INPUT mode, causes inverted write behaviour). Upload via Arduino IDE. Serial protocol: `A0:3.14,D2:1\n` stream from Arduino; `W:D7:1\n` / `P:D9:128\n` commands from PC. ### Adding a new device type -1. Subclass `BaseDevice` in a new file under `devices/` +1. Subclass `BaseDevice` in new file under `devices/` 2. Implement: `connect()`, `disconnect()`, `read_channels() → Dict[str, float]`, `write_channel(channel_id, value) → bool`, `get_config_widget() → QWidget` -3. Include `switch_backend()` if the device supports runtime reconfiguration -4. `DeviceRegistry` discovers it automatically on next run +3. Include `switch_backend()` if device supports runtime reconfiguration +4. `DeviceRegistry` discovers it automatically on next run
\ No newline at end of file diff --git a/api_layers/__pycache__/__init__.cpython-312.pyc b/api_layers/__pycache__/__init__.cpython-312.pyc Binary files differindex 3c56ecd..a9a035b 100644 --- a/api_layers/__pycache__/__init__.cpython-312.pyc +++ b/api_layers/__pycache__/__init__.cpython-312.pyc diff --git a/api_layers/__pycache__/__init__.cpython-314.pyc b/api_layers/__pycache__/__init__.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..01ab34b --- /dev/null +++ b/api_layers/__pycache__/__init__.cpython-314.pyc diff --git a/api_layers/__pycache__/arduino_layer.cpython-312.pyc b/api_layers/__pycache__/arduino_layer.cpython-312.pyc Binary files differindex a0191ad..1222766 100644 --- a/api_layers/__pycache__/arduino_layer.cpython-312.pyc +++ b/api_layers/__pycache__/arduino_layer.cpython-312.pyc diff --git a/api_layers/__pycache__/arduino_layer.cpython-314.pyc b/api_layers/__pycache__/arduino_layer.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..d40bc88 --- /dev/null +++ b/api_layers/__pycache__/arduino_layer.cpython-314.pyc diff --git a/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc b/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc Binary files differindex 93a5ef2..8dda62e 100644 --- a/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc +++ b/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc diff --git a/api_layers/__pycache__/nidaqmx_layer.cpython-314.pyc b/api_layers/__pycache__/nidaqmx_layer.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..c11967d --- /dev/null +++ b/api_layers/__pycache__/nidaqmx_layer.cpython-314.pyc diff --git a/api_layers/__pycache__/port_registry.cpython-312.pyc b/api_layers/__pycache__/port_registry.cpython-312.pyc Binary files differindex 44a52e8..088d6d3 100644 --- a/api_layers/__pycache__/port_registry.cpython-312.pyc +++ b/api_layers/__pycache__/port_registry.cpython-312.pyc diff --git a/core/__pycache__/__init__.cpython-312.pyc b/core/__pycache__/__init__.cpython-312.pyc Binary files differindex d1caea1..91f5de3 100644 --- a/core/__pycache__/__init__.cpython-312.pyc +++ b/core/__pycache__/__init__.cpython-312.pyc diff --git a/core/__pycache__/__init__.cpython-314.pyc b/core/__pycache__/__init__.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..fc70c21 --- /dev/null +++ b/core/__pycache__/__init__.cpython-314.pyc diff --git a/core/__pycache__/acquisition.cpython-312.pyc b/core/__pycache__/acquisition.cpython-312.pyc Binary files differindex a929bdf..999e174 100644 --- a/core/__pycache__/acquisition.cpython-312.pyc +++ b/core/__pycache__/acquisition.cpython-312.pyc diff --git a/core/__pycache__/acquisition.cpython-314.pyc b/core/__pycache__/acquisition.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..8a48b96 --- /dev/null +++ b/core/__pycache__/acquisition.cpython-314.pyc diff --git a/core/__pycache__/profile.cpython-312.pyc b/core/__pycache__/profile.cpython-312.pyc Binary files differindex 1c5afbf..c910e62 100644 --- a/core/__pycache__/profile.cpython-312.pyc +++ b/core/__pycache__/profile.cpython-312.pyc diff --git a/core/__pycache__/profile.cpython-314.pyc b/core/__pycache__/profile.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..f009c3f --- /dev/null +++ b/core/__pycache__/profile.cpython-314.pyc diff --git a/core/__pycache__/signal_processor.cpython-312.pyc b/core/__pycache__/signal_processor.cpython-312.pyc Binary files differindex fc31c25..adcf10d 100644 --- a/core/__pycache__/signal_processor.cpython-312.pyc +++ b/core/__pycache__/signal_processor.cpython-312.pyc diff --git a/core/__pycache__/signal_processor.cpython-314.pyc b/core/__pycache__/signal_processor.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..4a945a6 --- /dev/null +++ b/core/__pycache__/signal_processor.cpython-314.pyc diff --git a/devices/__pycache__/__init__.cpython-312.pyc b/devices/__pycache__/__init__.cpython-312.pyc Binary files differindex 7c1bbbd..0e0929f 100644 --- a/devices/__pycache__/__init__.cpython-312.pyc +++ b/devices/__pycache__/__init__.cpython-312.pyc diff --git a/devices/__pycache__/__init__.cpython-314.pyc b/devices/__pycache__/__init__.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..f1080d7 --- /dev/null +++ b/devices/__pycache__/__init__.cpython-314.pyc diff --git a/devices/__pycache__/analog_input.cpython-312.pyc b/devices/__pycache__/analog_input.cpython-312.pyc Binary files differindex fefede6..d285167 100644 --- a/devices/__pycache__/analog_input.cpython-312.pyc +++ b/devices/__pycache__/analog_input.cpython-312.pyc diff --git a/devices/__pycache__/analog_input.cpython-314.pyc b/devices/__pycache__/analog_input.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..1df8300 --- /dev/null +++ b/devices/__pycache__/analog_input.cpython-314.pyc diff --git a/devices/__pycache__/base_device.cpython-312.pyc b/devices/__pycache__/base_device.cpython-312.pyc Binary files differindex bfab8b5..087e500 100644 --- a/devices/__pycache__/base_device.cpython-312.pyc +++ b/devices/__pycache__/base_device.cpython-312.pyc diff --git a/devices/__pycache__/base_device.cpython-314.pyc b/devices/__pycache__/base_device.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..b8f94f4 --- /dev/null +++ b/devices/__pycache__/base_device.cpython-314.pyc diff --git a/devices/__pycache__/device_registry.cpython-312.pyc b/devices/__pycache__/device_registry.cpython-312.pyc Binary files differindex d8b72e0..3495e35 100644 --- a/devices/__pycache__/device_registry.cpython-312.pyc +++ b/devices/__pycache__/device_registry.cpython-312.pyc diff --git a/devices/__pycache__/device_registry.cpython-314.pyc b/devices/__pycache__/device_registry.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..f3f7d3e --- /dev/null +++ b/devices/__pycache__/device_registry.cpython-314.pyc diff --git a/devices/__pycache__/digital_io.cpython-312.pyc b/devices/__pycache__/digital_io.cpython-312.pyc Binary files differindex 7fa10a5..5cc1faf 100644 --- a/devices/__pycache__/digital_io.cpython-312.pyc +++ b/devices/__pycache__/digital_io.cpython-312.pyc diff --git a/devices/__pycache__/digital_io.cpython-314.pyc b/devices/__pycache__/digital_io.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..1b438fc --- /dev/null +++ b/devices/__pycache__/digital_io.cpython-314.pyc diff --git a/devices/__pycache__/serial_device.cpython-312.pyc b/devices/__pycache__/serial_device.cpython-312.pyc Binary files differindex 5cacb6b..03a29f7 100644 --- a/devices/__pycache__/serial_device.cpython-312.pyc +++ b/devices/__pycache__/serial_device.cpython-312.pyc diff --git a/devices/__pycache__/serial_device.cpython-314.pyc b/devices/__pycache__/serial_device.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..0bbe0bc --- /dev/null +++ b/devices/__pycache__/serial_device.cpython-314.pyc diff --git a/ui/__pycache__/__init__.cpython-312.pyc b/ui/__pycache__/__init__.cpython-312.pyc Binary files differindex fb20d59..8dc1c2f 100644 --- a/ui/__pycache__/__init__.cpython-312.pyc +++ b/ui/__pycache__/__init__.cpython-312.pyc diff --git a/ui/__pycache__/__init__.cpython-314.pyc b/ui/__pycache__/__init__.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..15d71d0 --- /dev/null +++ b/ui/__pycache__/__init__.cpython-314.pyc diff --git a/ui/__pycache__/add_device_dialog.cpython-312.pyc b/ui/__pycache__/add_device_dialog.cpython-312.pyc Binary files differindex 8af1f9c..e9aab22 100644 --- a/ui/__pycache__/add_device_dialog.cpython-312.pyc +++ b/ui/__pycache__/add_device_dialog.cpython-312.pyc diff --git a/ui/__pycache__/config_dialog.cpython-312.pyc b/ui/__pycache__/config_dialog.cpython-312.pyc Binary files differindex 5a051dc..cbc9cf0 100644 --- a/ui/__pycache__/config_dialog.cpython-312.pyc +++ b/ui/__pycache__/config_dialog.cpython-312.pyc diff --git a/ui/__pycache__/control_editor.cpython-312.pyc b/ui/__pycache__/control_editor.cpython-312.pyc Binary files differindex d12d50f..42855d5 100644 --- a/ui/__pycache__/control_editor.cpython-312.pyc +++ b/ui/__pycache__/control_editor.cpython-312.pyc diff --git a/ui/__pycache__/control_editor.cpython-314.pyc b/ui/__pycache__/control_editor.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..aaea0ee --- /dev/null +++ b/ui/__pycache__/control_editor.cpython-314.pyc diff --git a/ui/__pycache__/control_panel.cpython-312.pyc b/ui/__pycache__/control_panel.cpython-312.pyc Binary files differindex 6d020f5..8761466 100644 --- a/ui/__pycache__/control_panel.cpython-312.pyc +++ b/ui/__pycache__/control_panel.cpython-312.pyc diff --git a/ui/__pycache__/control_panel.cpython-314.pyc b/ui/__pycache__/control_panel.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..79f9084 --- /dev/null +++ b/ui/__pycache__/control_panel.cpython-314.pyc diff --git a/ui/__pycache__/main_window.cpython-312.pyc b/ui/__pycache__/main_window.cpython-312.pyc Binary files differindex b528ac2..a290571 100644 --- a/ui/__pycache__/main_window.cpython-312.pyc +++ b/ui/__pycache__/main_window.cpython-312.pyc diff --git a/ui/__pycache__/main_window.cpython-314.pyc b/ui/__pycache__/main_window.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..acef085 --- /dev/null +++ b/ui/__pycache__/main_window.cpython-314.pyc diff --git a/ui/__pycache__/profile_manager_ui.cpython-312.pyc b/ui/__pycache__/profile_manager_ui.cpython-312.pyc Binary files differindex c9fbb4a..97cbe3a 100644 --- a/ui/__pycache__/profile_manager_ui.cpython-312.pyc +++ b/ui/__pycache__/profile_manager_ui.cpython-312.pyc diff --git a/ui/__pycache__/profile_manager_ui.cpython-314.pyc b/ui/__pycache__/profile_manager_ui.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..ea12b21 --- /dev/null +++ b/ui/__pycache__/profile_manager_ui.cpython-314.pyc diff --git a/ui/__pycache__/strip_chart.cpython-312.pyc b/ui/__pycache__/strip_chart.cpython-312.pyc Binary files differindex 47dbea1..ff41c1a 100644 --- a/ui/__pycache__/strip_chart.cpython-312.pyc +++ b/ui/__pycache__/strip_chart.cpython-312.pyc diff --git a/ui/__pycache__/strip_chart.cpython-314.pyc b/ui/__pycache__/strip_chart.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..e56015f --- /dev/null +++ b/ui/__pycache__/strip_chart.cpython-314.pyc 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( diff --git a/ui/windows/__pycache__/__init__.cpython-312.pyc b/ui/windows/__pycache__/__init__.cpython-312.pyc Binary files differindex 37c4a45..66495c5 100644 --- a/ui/windows/__pycache__/__init__.cpython-312.pyc +++ b/ui/windows/__pycache__/__init__.cpython-312.pyc diff --git a/ui/windows/__pycache__/__init__.cpython-314.pyc b/ui/windows/__pycache__/__init__.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..873ff5b --- /dev/null +++ b/ui/windows/__pycache__/__init__.cpython-314.pyc diff --git a/ui/windows/__pycache__/devices_window.cpython-312.pyc b/ui/windows/__pycache__/devices_window.cpython-312.pyc Binary files differindex ca01f31..601221d 100644 --- a/ui/windows/__pycache__/devices_window.cpython-312.pyc +++ b/ui/windows/__pycache__/devices_window.cpython-312.pyc diff --git a/ui/windows/__pycache__/devices_window.cpython-314.pyc b/ui/windows/__pycache__/devices_window.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..e1a4868 --- /dev/null +++ b/ui/windows/__pycache__/devices_window.cpython-314.pyc diff --git a/ui/windows/__pycache__/plot_window.cpython-312.pyc b/ui/windows/__pycache__/plot_window.cpython-312.pyc Binary files differindex c2e3745..288e026 100644 --- a/ui/windows/__pycache__/plot_window.cpython-312.pyc +++ b/ui/windows/__pycache__/plot_window.cpython-312.pyc diff --git a/ui/windows/__pycache__/plot_window.cpython-314.pyc b/ui/windows/__pycache__/plot_window.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..30b0a23 --- /dev/null +++ b/ui/windows/__pycache__/plot_window.cpython-314.pyc diff --git a/ui/windows/__pycache__/settings_window.cpython-312.pyc b/ui/windows/__pycache__/settings_window.cpython-312.pyc Binary files differindex 72f96e1..d596487 100644 --- a/ui/windows/__pycache__/settings_window.cpython-312.pyc +++ b/ui/windows/__pycache__/settings_window.cpython-312.pyc diff --git a/ui/windows/__pycache__/settings_window.cpython-314.pyc b/ui/windows/__pycache__/settings_window.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..cc9691b --- /dev/null +++ b/ui/windows/__pycache__/settings_window.cpython-314.pyc diff --git a/ui/windows/__pycache__/signals_window.cpython-312.pyc b/ui/windows/__pycache__/signals_window.cpython-312.pyc Binary files differindex 0526a85..1f6a3db 100644 --- a/ui/windows/__pycache__/signals_window.cpython-312.pyc +++ b/ui/windows/__pycache__/signals_window.cpython-312.pyc diff --git a/ui/windows/__pycache__/signals_window.cpython-314.pyc b/ui/windows/__pycache__/signals_window.cpython-314.pyc Binary files differnew file mode 100644 index 0000000..224e31e --- /dev/null +++ b/ui/windows/__pycache__/signals_window.cpython-314.pyc |
