summaryrefslogtreecommitdiff
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
parenta00ed2a932fdb0f3103777ac3eac106701484ca2 (diff)
Added port scanning for new devices and only shows related backend config lines
-rw-r--r--CLAUDE.md2
-rw-r--r--CLAUDE.original.md22
-rw-r--r--api_layers/__pycache__/__init__.cpython-312.pycbin309 -> 311 bytes
-rw-r--r--api_layers/__pycache__/__init__.cpython-314.pycbin0 -> 313 bytes
-rw-r--r--api_layers/__pycache__/arduino_layer.cpython-312.pycbin31077 -> 30983 bytes
-rw-r--r--api_layers/__pycache__/arduino_layer.cpython-314.pycbin0 -> 35535 bytes
-rw-r--r--api_layers/__pycache__/nidaqmx_layer.cpython-312.pycbin8492 -> 8501 bytes
-rw-r--r--api_layers/__pycache__/nidaqmx_layer.cpython-314.pycbin0 -> 10240 bytes
-rw-r--r--api_layers/__pycache__/port_registry.cpython-312.pycbin5972 -> 5907 bytes
-rw-r--r--core/__pycache__/__init__.cpython-312.pycbin262 -> 264 bytes
-rw-r--r--core/__pycache__/__init__.cpython-314.pycbin0 -> 266 bytes
-rw-r--r--core/__pycache__/acquisition.cpython-312.pycbin11080 -> 11068 bytes
-rw-r--r--core/__pycache__/acquisition.cpython-314.pycbin0 -> 13170 bytes
-rw-r--r--core/__pycache__/profile.cpython-312.pycbin11805 -> 11779 bytes
-rw-r--r--core/__pycache__/profile.cpython-314.pycbin0 -> 13214 bytes
-rw-r--r--core/__pycache__/signal_processor.cpython-312.pycbin23755 -> 23674 bytes
-rw-r--r--core/__pycache__/signal_processor.cpython-314.pycbin0 -> 29226 bytes
-rw-r--r--devices/__pycache__/__init__.cpython-312.pycbin290 -> 292 bytes
-rw-r--r--devices/__pycache__/__init__.cpython-314.pycbin0 -> 387 bytes
-rw-r--r--devices/__pycache__/analog_input.cpython-312.pycbin24161 -> 24176 bytes
-rw-r--r--devices/__pycache__/analog_input.cpython-314.pycbin0 -> 25792 bytes
-rw-r--r--devices/__pycache__/base_device.cpython-312.pycbin5604 -> 5604 bytes
-rw-r--r--devices/__pycache__/base_device.cpython-314.pycbin0 -> 7574 bytes
-rw-r--r--devices/__pycache__/device_registry.cpython-312.pycbin4579 -> 4571 bytes
-rw-r--r--devices/__pycache__/device_registry.cpython-314.pycbin0 -> 5916 bytes
-rw-r--r--devices/__pycache__/digital_io.cpython-312.pycbin21995 -> 21962 bytes
-rw-r--r--devices/__pycache__/digital_io.cpython-314.pycbin0 -> 21130 bytes
-rw-r--r--devices/__pycache__/serial_device.cpython-312.pycbin14645 -> 14669 bytes
-rw-r--r--devices/__pycache__/serial_device.cpython-314.pycbin0 -> 16417 bytes
-rw-r--r--ui/__pycache__/__init__.cpython-312.pycbin138 -> 140 bytes
-rw-r--r--ui/__pycache__/__init__.cpython-314.pycbin0 -> 142 bytes
-rw-r--r--ui/__pycache__/add_device_dialog.cpython-312.pycbin21811 -> 29737 bytes
-rw-r--r--ui/__pycache__/config_dialog.cpython-312.pycbin6931 -> 6917 bytes
-rw-r--r--ui/__pycache__/control_editor.cpython-312.pycbin20127 -> 20099 bytes
-rw-r--r--ui/__pycache__/control_editor.cpython-314.pycbin0 -> 22435 bytes
-rw-r--r--ui/__pycache__/control_panel.cpython-312.pycbin42194 -> 42162 bytes
-rw-r--r--ui/__pycache__/control_panel.cpython-314.pycbin0 -> 44201 bytes
-rw-r--r--ui/__pycache__/main_window.cpython-312.pycbin25379 -> 25329 bytes
-rw-r--r--ui/__pycache__/main_window.cpython-314.pycbin0 -> 27190 bytes
-rw-r--r--ui/__pycache__/profile_manager_ui.cpython-312.pycbin8360 -> 8357 bytes
-rw-r--r--ui/__pycache__/profile_manager_ui.cpython-314.pycbin0 -> 9209 bytes
-rw-r--r--ui/__pycache__/strip_chart.cpython-312.pycbin14890 -> 14879 bytes
-rw-r--r--ui/__pycache__/strip_chart.cpython-314.pycbin0 -> 16625 bytes
-rw-r--r--ui/add_device_dialog.py260
-rw-r--r--ui/windows/__pycache__/__init__.cpython-312.pycbin146 -> 148 bytes
-rw-r--r--ui/windows/__pycache__/__init__.cpython-314.pycbin0 -> 150 bytes
-rw-r--r--ui/windows/__pycache__/devices_window.cpython-312.pycbin22030 -> 22008 bytes
-rw-r--r--ui/windows/__pycache__/devices_window.cpython-314.pycbin0 -> 23231 bytes
-rw-r--r--ui/windows/__pycache__/plot_window.cpython-312.pycbin44675 -> 44551 bytes
-rw-r--r--ui/windows/__pycache__/plot_window.cpython-314.pycbin0 -> 46111 bytes
-rw-r--r--ui/windows/__pycache__/settings_window.cpython-312.pycbin15924 -> 15906 bytes
-rw-r--r--ui/windows/__pycache__/settings_window.cpython-314.pycbin0 -> 16392 bytes
-rw-r--r--ui/windows/__pycache__/signals_window.cpython-312.pycbin51458 -> 51384 bytes
-rw-r--r--ui/windows/__pycache__/signals_window.cpython-314.pycbin0 -> 53420 bytes
54 files changed, 208 insertions, 76 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 5347d9d..0a10c22 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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
index 3c56ecd..a9a035b 100644
--- a/api_layers/__pycache__/__init__.cpython-312.pyc
+++ b/api_layers/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/api_layers/__pycache__/__init__.cpython-314.pyc b/api_layers/__pycache__/__init__.cpython-314.pyc
new file mode 100644
index 0000000..01ab34b
--- /dev/null
+++ b/api_layers/__pycache__/__init__.cpython-314.pyc
Binary files differ
diff --git a/api_layers/__pycache__/arduino_layer.cpython-312.pyc b/api_layers/__pycache__/arduino_layer.cpython-312.pyc
index a0191ad..1222766 100644
--- a/api_layers/__pycache__/arduino_layer.cpython-312.pyc
+++ b/api_layers/__pycache__/arduino_layer.cpython-312.pyc
Binary files differ
diff --git a/api_layers/__pycache__/arduino_layer.cpython-314.pyc b/api_layers/__pycache__/arduino_layer.cpython-314.pyc
new file mode 100644
index 0000000..d40bc88
--- /dev/null
+++ b/api_layers/__pycache__/arduino_layer.cpython-314.pyc
Binary files differ
diff --git a/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc b/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc
index 93a5ef2..8dda62e 100644
--- a/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc
+++ b/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc
Binary files differ
diff --git a/api_layers/__pycache__/nidaqmx_layer.cpython-314.pyc b/api_layers/__pycache__/nidaqmx_layer.cpython-314.pyc
new file mode 100644
index 0000000..c11967d
--- /dev/null
+++ b/api_layers/__pycache__/nidaqmx_layer.cpython-314.pyc
Binary files differ
diff --git a/api_layers/__pycache__/port_registry.cpython-312.pyc b/api_layers/__pycache__/port_registry.cpython-312.pyc
index 44a52e8..088d6d3 100644
--- a/api_layers/__pycache__/port_registry.cpython-312.pyc
+++ b/api_layers/__pycache__/port_registry.cpython-312.pyc
Binary files differ
diff --git a/core/__pycache__/__init__.cpython-312.pyc b/core/__pycache__/__init__.cpython-312.pyc
index d1caea1..91f5de3 100644
--- a/core/__pycache__/__init__.cpython-312.pyc
+++ b/core/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/core/__pycache__/__init__.cpython-314.pyc b/core/__pycache__/__init__.cpython-314.pyc
new file mode 100644
index 0000000..fc70c21
--- /dev/null
+++ b/core/__pycache__/__init__.cpython-314.pyc
Binary files differ
diff --git a/core/__pycache__/acquisition.cpython-312.pyc b/core/__pycache__/acquisition.cpython-312.pyc
index a929bdf..999e174 100644
--- a/core/__pycache__/acquisition.cpython-312.pyc
+++ b/core/__pycache__/acquisition.cpython-312.pyc
Binary files differ
diff --git a/core/__pycache__/acquisition.cpython-314.pyc b/core/__pycache__/acquisition.cpython-314.pyc
new file mode 100644
index 0000000..8a48b96
--- /dev/null
+++ b/core/__pycache__/acquisition.cpython-314.pyc
Binary files differ
diff --git a/core/__pycache__/profile.cpython-312.pyc b/core/__pycache__/profile.cpython-312.pyc
index 1c5afbf..c910e62 100644
--- a/core/__pycache__/profile.cpython-312.pyc
+++ b/core/__pycache__/profile.cpython-312.pyc
Binary files differ
diff --git a/core/__pycache__/profile.cpython-314.pyc b/core/__pycache__/profile.cpython-314.pyc
new file mode 100644
index 0000000..f009c3f
--- /dev/null
+++ b/core/__pycache__/profile.cpython-314.pyc
Binary files differ
diff --git a/core/__pycache__/signal_processor.cpython-312.pyc b/core/__pycache__/signal_processor.cpython-312.pyc
index fc31c25..adcf10d 100644
--- a/core/__pycache__/signal_processor.cpython-312.pyc
+++ b/core/__pycache__/signal_processor.cpython-312.pyc
Binary files differ
diff --git a/core/__pycache__/signal_processor.cpython-314.pyc b/core/__pycache__/signal_processor.cpython-314.pyc
new file mode 100644
index 0000000..4a945a6
--- /dev/null
+++ b/core/__pycache__/signal_processor.cpython-314.pyc
Binary files differ
diff --git a/devices/__pycache__/__init__.cpython-312.pyc b/devices/__pycache__/__init__.cpython-312.pyc
index 7c1bbbd..0e0929f 100644
--- a/devices/__pycache__/__init__.cpython-312.pyc
+++ b/devices/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/devices/__pycache__/__init__.cpython-314.pyc b/devices/__pycache__/__init__.cpython-314.pyc
new file mode 100644
index 0000000..f1080d7
--- /dev/null
+++ b/devices/__pycache__/__init__.cpython-314.pyc
Binary files differ
diff --git a/devices/__pycache__/analog_input.cpython-312.pyc b/devices/__pycache__/analog_input.cpython-312.pyc
index fefede6..d285167 100644
--- a/devices/__pycache__/analog_input.cpython-312.pyc
+++ b/devices/__pycache__/analog_input.cpython-312.pyc
Binary files differ
diff --git a/devices/__pycache__/analog_input.cpython-314.pyc b/devices/__pycache__/analog_input.cpython-314.pyc
new file mode 100644
index 0000000..1df8300
--- /dev/null
+++ b/devices/__pycache__/analog_input.cpython-314.pyc
Binary files differ
diff --git a/devices/__pycache__/base_device.cpython-312.pyc b/devices/__pycache__/base_device.cpython-312.pyc
index bfab8b5..087e500 100644
--- a/devices/__pycache__/base_device.cpython-312.pyc
+++ b/devices/__pycache__/base_device.cpython-312.pyc
Binary files differ
diff --git a/devices/__pycache__/base_device.cpython-314.pyc b/devices/__pycache__/base_device.cpython-314.pyc
new file mode 100644
index 0000000..b8f94f4
--- /dev/null
+++ b/devices/__pycache__/base_device.cpython-314.pyc
Binary files differ
diff --git a/devices/__pycache__/device_registry.cpython-312.pyc b/devices/__pycache__/device_registry.cpython-312.pyc
index d8b72e0..3495e35 100644
--- a/devices/__pycache__/device_registry.cpython-312.pyc
+++ b/devices/__pycache__/device_registry.cpython-312.pyc
Binary files differ
diff --git a/devices/__pycache__/device_registry.cpython-314.pyc b/devices/__pycache__/device_registry.cpython-314.pyc
new file mode 100644
index 0000000..f3f7d3e
--- /dev/null
+++ b/devices/__pycache__/device_registry.cpython-314.pyc
Binary files differ
diff --git a/devices/__pycache__/digital_io.cpython-312.pyc b/devices/__pycache__/digital_io.cpython-312.pyc
index 7fa10a5..5cc1faf 100644
--- a/devices/__pycache__/digital_io.cpython-312.pyc
+++ b/devices/__pycache__/digital_io.cpython-312.pyc
Binary files differ
diff --git a/devices/__pycache__/digital_io.cpython-314.pyc b/devices/__pycache__/digital_io.cpython-314.pyc
new file mode 100644
index 0000000..1b438fc
--- /dev/null
+++ b/devices/__pycache__/digital_io.cpython-314.pyc
Binary files differ
diff --git a/devices/__pycache__/serial_device.cpython-312.pyc b/devices/__pycache__/serial_device.cpython-312.pyc
index 5cacb6b..03a29f7 100644
--- a/devices/__pycache__/serial_device.cpython-312.pyc
+++ b/devices/__pycache__/serial_device.cpython-312.pyc
Binary files differ
diff --git a/devices/__pycache__/serial_device.cpython-314.pyc b/devices/__pycache__/serial_device.cpython-314.pyc
new file mode 100644
index 0000000..0bbe0bc
--- /dev/null
+++ b/devices/__pycache__/serial_device.cpython-314.pyc
Binary files differ
diff --git a/ui/__pycache__/__init__.cpython-312.pyc b/ui/__pycache__/__init__.cpython-312.pyc
index fb20d59..8dc1c2f 100644
--- a/ui/__pycache__/__init__.cpython-312.pyc
+++ b/ui/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/ui/__pycache__/__init__.cpython-314.pyc b/ui/__pycache__/__init__.cpython-314.pyc
new file mode 100644
index 0000000..15d71d0
--- /dev/null
+++ b/ui/__pycache__/__init__.cpython-314.pyc
Binary files differ
diff --git a/ui/__pycache__/add_device_dialog.cpython-312.pyc b/ui/__pycache__/add_device_dialog.cpython-312.pyc
index 8af1f9c..e9aab22 100644
--- a/ui/__pycache__/add_device_dialog.cpython-312.pyc
+++ b/ui/__pycache__/add_device_dialog.cpython-312.pyc
Binary files differ
diff --git a/ui/__pycache__/config_dialog.cpython-312.pyc b/ui/__pycache__/config_dialog.cpython-312.pyc
index 5a051dc..cbc9cf0 100644
--- a/ui/__pycache__/config_dialog.cpython-312.pyc
+++ b/ui/__pycache__/config_dialog.cpython-312.pyc
Binary files differ
diff --git a/ui/__pycache__/control_editor.cpython-312.pyc b/ui/__pycache__/control_editor.cpython-312.pyc
index d12d50f..42855d5 100644
--- a/ui/__pycache__/control_editor.cpython-312.pyc
+++ b/ui/__pycache__/control_editor.cpython-312.pyc
Binary files differ
diff --git a/ui/__pycache__/control_editor.cpython-314.pyc b/ui/__pycache__/control_editor.cpython-314.pyc
new file mode 100644
index 0000000..aaea0ee
--- /dev/null
+++ b/ui/__pycache__/control_editor.cpython-314.pyc
Binary files differ
diff --git a/ui/__pycache__/control_panel.cpython-312.pyc b/ui/__pycache__/control_panel.cpython-312.pyc
index 6d020f5..8761466 100644
--- a/ui/__pycache__/control_panel.cpython-312.pyc
+++ b/ui/__pycache__/control_panel.cpython-312.pyc
Binary files differ
diff --git a/ui/__pycache__/control_panel.cpython-314.pyc b/ui/__pycache__/control_panel.cpython-314.pyc
new file mode 100644
index 0000000..79f9084
--- /dev/null
+++ b/ui/__pycache__/control_panel.cpython-314.pyc
Binary files differ
diff --git a/ui/__pycache__/main_window.cpython-312.pyc b/ui/__pycache__/main_window.cpython-312.pyc
index b528ac2..a290571 100644
--- a/ui/__pycache__/main_window.cpython-312.pyc
+++ b/ui/__pycache__/main_window.cpython-312.pyc
Binary files differ
diff --git a/ui/__pycache__/main_window.cpython-314.pyc b/ui/__pycache__/main_window.cpython-314.pyc
new file mode 100644
index 0000000..acef085
--- /dev/null
+++ b/ui/__pycache__/main_window.cpython-314.pyc
Binary files differ
diff --git a/ui/__pycache__/profile_manager_ui.cpython-312.pyc b/ui/__pycache__/profile_manager_ui.cpython-312.pyc
index c9fbb4a..97cbe3a 100644
--- a/ui/__pycache__/profile_manager_ui.cpython-312.pyc
+++ b/ui/__pycache__/profile_manager_ui.cpython-312.pyc
Binary files differ
diff --git a/ui/__pycache__/profile_manager_ui.cpython-314.pyc b/ui/__pycache__/profile_manager_ui.cpython-314.pyc
new file mode 100644
index 0000000..ea12b21
--- /dev/null
+++ b/ui/__pycache__/profile_manager_ui.cpython-314.pyc
Binary files differ
diff --git a/ui/__pycache__/strip_chart.cpython-312.pyc b/ui/__pycache__/strip_chart.cpython-312.pyc
index 47dbea1..ff41c1a 100644
--- a/ui/__pycache__/strip_chart.cpython-312.pyc
+++ b/ui/__pycache__/strip_chart.cpython-312.pyc
Binary files differ
diff --git a/ui/__pycache__/strip_chart.cpython-314.pyc b/ui/__pycache__/strip_chart.cpython-314.pyc
new file mode 100644
index 0000000..e56015f
--- /dev/null
+++ b/ui/__pycache__/strip_chart.cpython-314.pyc
Binary files differ
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
index 37c4a45..66495c5 100644
--- a/ui/windows/__pycache__/__init__.cpython-312.pyc
+++ b/ui/windows/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/ui/windows/__pycache__/__init__.cpython-314.pyc b/ui/windows/__pycache__/__init__.cpython-314.pyc
new file mode 100644
index 0000000..873ff5b
--- /dev/null
+++ b/ui/windows/__pycache__/__init__.cpython-314.pyc
Binary files differ
diff --git a/ui/windows/__pycache__/devices_window.cpython-312.pyc b/ui/windows/__pycache__/devices_window.cpython-312.pyc
index ca01f31..601221d 100644
--- a/ui/windows/__pycache__/devices_window.cpython-312.pyc
+++ b/ui/windows/__pycache__/devices_window.cpython-312.pyc
Binary files differ
diff --git a/ui/windows/__pycache__/devices_window.cpython-314.pyc b/ui/windows/__pycache__/devices_window.cpython-314.pyc
new file mode 100644
index 0000000..e1a4868
--- /dev/null
+++ b/ui/windows/__pycache__/devices_window.cpython-314.pyc
Binary files differ
diff --git a/ui/windows/__pycache__/plot_window.cpython-312.pyc b/ui/windows/__pycache__/plot_window.cpython-312.pyc
index c2e3745..288e026 100644
--- a/ui/windows/__pycache__/plot_window.cpython-312.pyc
+++ b/ui/windows/__pycache__/plot_window.cpython-312.pyc
Binary files differ
diff --git a/ui/windows/__pycache__/plot_window.cpython-314.pyc b/ui/windows/__pycache__/plot_window.cpython-314.pyc
new file mode 100644
index 0000000..30b0a23
--- /dev/null
+++ b/ui/windows/__pycache__/plot_window.cpython-314.pyc
Binary files differ
diff --git a/ui/windows/__pycache__/settings_window.cpython-312.pyc b/ui/windows/__pycache__/settings_window.cpython-312.pyc
index 72f96e1..d596487 100644
--- a/ui/windows/__pycache__/settings_window.cpython-312.pyc
+++ b/ui/windows/__pycache__/settings_window.cpython-312.pyc
Binary files differ
diff --git a/ui/windows/__pycache__/settings_window.cpython-314.pyc b/ui/windows/__pycache__/settings_window.cpython-314.pyc
new file mode 100644
index 0000000..cc9691b
--- /dev/null
+++ b/ui/windows/__pycache__/settings_window.cpython-314.pyc
Binary files differ
diff --git a/ui/windows/__pycache__/signals_window.cpython-312.pyc b/ui/windows/__pycache__/signals_window.cpython-312.pyc
index 0526a85..1f6a3db 100644
--- a/ui/windows/__pycache__/signals_window.cpython-312.pyc
+++ b/ui/windows/__pycache__/signals_window.cpython-312.pyc
Binary files differ
diff --git a/ui/windows/__pycache__/signals_window.cpython-314.pyc b/ui/windows/__pycache__/signals_window.cpython-314.pyc
new file mode 100644
index 0000000..224e31e
--- /dev/null
+++ b/ui/windows/__pycache__/signals_window.cpython-314.pyc
Binary files differ