summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
2026-09-20Persist dock layout to profiles; dedupe port names; Channels-tab Create buttonChristian Kolset
- Fix duplicated device names ("FT231X USB UART - FT231X USB UART (...)") — pyserial on Linux often reports the same udev model string twice for cheap FTDI/CH340-style adapters. Collapse it at the list_ports() source so every consumer (device auto-name, port picker) benefits. - Save Control Panel / Namespace dock area, floating geometry, visibility, size and order to .labui profiles via QMainWindow.saveState()/ restoreState(), round-tripped through the existing Profile.settings passthrough (self._settings) — no profile schema change needed. - Channels tab: replace the per-row Signal ⊕ button (and its pinned/ frozen-column overlay) with a single "Create Channel" button below the Namespace panel. Select one or more Signal/Channel rows (multi-select now supported) and click it — selected rows become the new Channel's sources, or an empty Channel is created if nothing's selected. Fixes a bug in the process: the multi/zero-source path defaulted to kind="expression" with no expression text, which fails to compile and silently dropped the channel; use kind="none" instead, which the user then edits in the Channels pane. NamespacePanel itself (ui/namespace_panel.py) goes back to a plain read-only reference widget — the create-channel button and its logic live entirely in ConfigWindow (config_window.py), so the shared dockable Namespace panel on the main window is unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20Make Channel the single source of truth for a reading's nameChristian Kolset
Device tab stays hardware-only (pins, wiring, may vary per workstation); the Channels window owns the profile-facing identity. Concretely: - Fix a real regression: SerialDevice generic-format Apply & Reconnect nulled out custom channel_names/units every time (no fallback, unlike every other optional field in reconfigure()), silently reverting to sig0/sig1/... ids. - Plot legend now live-tracks the current Signal/Channel name instead of freezing tr.label at add-time, so a rename shows up without re-adding the trace. - Add an inline quick-reconnect (combo + button, pre-selects the actual current source) directly on each Channel block in the Channels window, for the common single-source case — rewiring hardware no longer means touching every Plot/pipeline that referenced it. - Collapse the Channel ID field behind an "ID ▸" advanced toggle; channel_id is already auto-derived from the name (_slug_id) and rarely needs to be seen. - Devices tab's Signal "Name" column relabeled "Label" with a tooltip — de-emphasized now that Channel name is the real identity. - Move "create a Channel from this Signal" from a column on the Devices tab's signal table to a ⊕ button on each Signal row in the Channels tab's Namespace panel — the action now lives where the identity is created, not on the hardware tab. Only that instance opts in (on_create_channel); the read-only Namespace dock on the main window is unaffected. - Floor the Namespace panel's width at its columns' natural size so dragging the Channels-tab splitter narrower can't push the ⊕ column behind the horizontal scrollbar. - Breadcrumb log when a profile-saved Signal override can't be applied because the signal_id no longer exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20Fix Signal name reset and orphaned references on device reconfigureChristian Kolset
Apply & Reconnect rebuilt a device's whole Signal list from hardware defaults on every call, silently discarding user/profile-set names, units, and colors even when pins hadn't changed. Add merge_signal_metadata() and call it from ArduinoDevice.remap_pins() and SerialDevice.reconfigure() to reapply the same fields ProfileManager already treats as user overrides, keyed by signal_id. Also close a related gap: removing a pin that a Channel/Plot/Control still referenced left it silently dangling, and SignalProcessor never pruned its latest-value cache, so a derived Channel kept computing off a permanently frozen stale reading. Add SignalProcessor. on_device_reconfigured() to purge that cache, reference_registry. find_references() for a console breadcrumb, and live "missing source" indicators on Plot traces and Channel blocks reusing ControlWidget's existing stale/QSS pattern. Also fixes ControlWidget._write() still enqueuing writes to a channel that no longer exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19Redesign Signal/Channel naming and fix pin-remap breaking referencesChristian Kolset
Arduino analog Signal ids used to be the pin name itself, so fixing a wrong pin silently changed the id and orphaned any plot trace, control, or filter pipeline pointing at it. Analog Signal ids are now positional (ai0, ai1, ...) and decoupled from the underlying pin, matching how digital I/O already worked. More broadly, formalizes a Device -> Signal -> Channel -> Plot naming model (SignalConfig for physical device channels, Channel for derived/ computed ones) and adds core/reference_registry.py to propagate any Signal or Channel id rename through plot traces, controls, and filter pipelines instead of leaving them dangling. Signal ids are now editable in the Signals tab with the same propagation. .labui profile schema bumped to v2.0 with a transparent migration for old files, including remapping old pin-name Signal ids to the new positional ones. Removed six confirmed-dead files (two legacy device classes, four superseded top-level ui/ duplicates).
2026-08-26Commented out unfiltered stream. Now works with Adams python code.Christian Kolset
2026-08-17Fix control panel freeze and CM1 motor not movingChristian Kolset
ControlWidget._write() called dev.write_channel() directly on the GUI thread from Qt signal handlers — for CML (CoolMuscle), write() does 2-3 blocking serial round-trips, and a slider fires valueChanged on every drag tick, so each tick froze the whole app until the device responded or timed out. Replaced with a coalescing background dispatch: writes are keyed by (device, channel) in a dict instead of a plain FIFO queue, so re-writing the same channel while a write is still pending updates its value in place rather than piling up — a strict FIFO was observed backing up 30+ deep during a single drag, meaning the motor kept "moving" to seconds-old positions long after the slider stopped, which looked like a freeze even once off the GUI thread. Separately, the motor never actually moved because it was never enabled: per docs/CM1-C_ASCII_Command_Cheatsheet.md, a CM1-C motor stays disabled ("shaft free") until sent "(", and nothing in CMLLayer.write()'s VS/MA handling ever sent it — enable only existed as a separate M1_ME channel the generic MotorControl widget has no concept of. VS/MA now send enable unconditionally before the move sequence (a no-op if already enabled). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17Fix motion capture UI freezes: throttling, async camera open/scan, stale pollChristian Kolset
Several compounding freeze sources in plugins/motion_capture/: - tracker.py: _run_real had no frame-rate cap independent of the camera's configured fps (up to 120fps/2560x1440 was possible), and no bound on consecutive failed reads — a camera that "opens" (cv2 isOpened() can lie, seen with Windows MSMF/obsensor) but never actually delivers frames would get hammered forever. Added a 20Hz processing cap, a real test-read before committing to a camera, and a give-up-after-30-failures fallback to simulation. - tracker.py/device.py: cv2.VideoCapture() opening a real camera is a slow, blocking call — CameraTracker.start() used to do this synchronously on the caller's thread, which froze the whole app when ProfileManager.apply() called it during profile load. Now runs on the background thread; status reporting (CameraDevice) moved to an on_result callback since the caller can no longer learn the outcome synchronously. - window.py: the Camera View preview ran a 30Hz GUI-thread timer doing a full-frame copy + QImage/QPixmap rebuild every tick unconditionally (no visibility check) — dropped to 10Hz and skips entirely when hidden. Camera enumeration (list_cameras(), up to 8 sequential cv2.VideoCapture probes) also ran inline in the window constructor, blocking it open the same way camera_panel.py's CameraScanThread already avoided for the Add Device dialog — moved to the same async pattern here. config_window.py: separately, ConfigWindow._dev_poll (device status label refresh) was stopped in closeEvent and never restarted, since MainWindow reuses the same ConfigWindow instance across opens — invisible before because connect() was synchronous, but with the above change a device can sit in CONNECTING for a while, and a stale poll made that look stuck forever after reopening the window. Restart it in showEvent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17Add .labui file association — open by double-click, cross-platformChristian Kolset
Windows/Linux self-register via core/file_association.py (winreg / XDG .desktop+mime), triggered from a new Settings button since there's no installer to do it at install time. macOS gets it at build time via labui.spec's new BUNDLE() step (CFBundleDocumentTypes in Info.plist) — that step also makes mac builds a real .app bundle for the first time, not just a bare onedir folder. main.py gains a LabUIApplication subclass to catch the macOS QFileOpenEvent (buffered if it arrives before MainWindow exists) plus argv[1] handling for Windows/Linux. ProfileButton.load_path() extracted so both paths share the same load-and-apply logic as the existing file-dialog flow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13Channels tab drag-to-reorder, Namespace absolute references, fix stale ↵Christian Kolset
Channel ID renames Generalized the Plot pane drag-to-reorder (_PaneDragHandle/_PlotDropContainer -> _DragHandle/_DragReorderContainer) and reused it for the Channels tab's derived-channel list; reordering also updates SignalProcessor's evaluation order (list order matters for chained Channels) via new reorder_derived(). Namespace panel: added an ID column (bare channel_id, matching the actual channels.get(...) script convention) and fixed the Source column for Channels to show resolved absolute references (dev_id/ch_id for a Signal, bare id for a chained Channel) instead of the operation kind, so a chain is traceable by eye. Fixed a real bug this surfaced: renaming a Channel's ID never propagated to other Channels' sources, leaving dangling ("derived", old_id) references that silently stopped updating. Added SignalProcessor.rename_derived() to fix up sources plus carry the ring buffer/latest-value cache to the new key, wired to DerivedBlock's Apply. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13Configuration window UI reorg + Plot Builder polishChristian Kolset
Devices tab split into Physical Devices (left) / Signals (right); Channels tab now hosts the Namespace browser (left) alongside Channels (right, larger by default). Plot Builder panes gain drag-to-reorder (same mechanics as ControlPanel's widget list, adapted for the editable pane title via a dedicated grip icon), "+ Add Channel" moved beside Size for a more compact row, and Min/Max/Size/line-width spin boxes widened so values no longer overlap the up/down arrows. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13Updated configuration window design.Christian Kolset
- Split device tab into pysical devices and signals. - Replaced old Signals panel (under Channels tab) with namespace. to make Channel creation easier.
2026-08-13Re-detect serial device ports by identity when a profile loads on another ↵Christian Kolset
machine COM/tty numbers aren't stable across machines or replugs. Devices now save their USB serial number and OS description alongside the port; on profile load, if the saved port isn't present, ArduinoLayer.find_port_by_identity() looks for a match and remaps it. Falls back to the saved port (device shows ERROR) if nothing matches. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13Fixed loading profiles data retention and display.Christian Kolset
2026-08-13Added DSCUSB Protocol.Christian Kolset
2026-08-13Removed tracking of temp files and updated esp32_stringPot.inoChristian Kolset
- Removed log file and plugin enabled states. - Updated esp32_stringPot.ino: added time delay for config screen and group declaration together.
2026-08-11Finish multi-platform release restructuring (previous commit was incomplete)Christian Kolset
git add silently staged nothing from the previous attempt because one pathspec in that batch (scripts/release/repository, already moved) no longer existed — only the .gitignore-adjacent renames landed. This commit adds what was actually meant to go in: - core/updater.py: releases/download/updates-<platform>/ instead of a single shared updates/ tag. - labui.spec: bundles root.json from this platform's own tree. - scripts/release/repo_init.py, repo_release.py: auto-detect platform, operate inside scripts/release/<platform>/. - .gitignore: per-platform ignore rules (previous commit only carried the file renames, not this). - docs/building.md, scripts/release/README.md: rewritten for the per-platform flow. - Also picks up pre-existing local TUF re-signing (new snapshot/ timestamp signatures, bumped expiry/version) that was sitting uncommitted before this restructuring started. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11Restructure release infra for multiple platforms; migrate real win keysChristian Kolset
PyInstaller doesn't cross-compile and TUF metadata filenames aren't platform-prefixed, so a shared updates/ release and repo tree can't safely hold more than one OS's build — a second platform's publish would silently overwrite the first's trust root and metadata. - scripts/release/repo_init.py, repo_release.py: auto-detect platform (win/mac/linux) and operate inside scripts/release/<platform>/ instead of a flat scripts/release/ tree. - labui.spec: bundles root.json from this platform's own tree. - core/updater.py: points at releases/download/updates-<platform>/ instead of a single shared updates/ tag. - Moved the existing real keystore/repository/config (already used for a live Windows release) from the old flat path into scripts/release/win/ — same keys, same signed metadata, just relocated. No new keys generated, existing installs still trust it. - .gitignore updated for the per-platform paths, plus temporary rules covering the old flat path during migration. - docs/building.md, scripts/release/README.md: rewritten for the per-platform flow, including a note that Linux builds should happen on Linux/WSL2 (no cross-compiling) and that a bare .exe doesn't belong on the updates-<platform> release — only the TUF metadata + signed target archive do. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11Fix native-style Qt warning; add a few tooltips; trim Namespace hint textChristian Kolset
Root cause of "QFont::setPointSize: Point size <= 0 (-1)" on some dropdowns: the app never sets an explicit widget style, so it defaults to native windowsvista, whose popup code calls setPointSize() on fonts resolved from our QSS's pixel-based font-size (pointSize() is -1 by design for px fonts). Tried app.setStyle("Fusion") first — silences it, but Fusion doesn't render buttons/dropdowns to match the QSS the way the native style does. Kept native style, filtered just that one message via qInstallMessageHandler instead — it's cosmetic Qt console noise (the rejected setPointSize call is a no-op, doesn't affect rendering), not a real bug. Also adds a handful of tooltips (Control Editor's Check Syntax/Cancel/ Create Control, device Configure dialog's Close, main toolbar's View/ Configure/RUN/LOG/Settings, a few Control Panel widgets) from an in-progress app-wide tooltip pass, paused partway through by request — remaining buttons are being done manually. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11Added documentation to creating new devices and protocolsChristian Kolset
2026-08-11Minor UI fixesChristian Kolset
- Renamed LabDAQ to LabUI in main script - Removed dubplicate separator when plugins are enabled
2026-08-10Add dockable Namespace panel — live Python-namespace browserChristian Kolset
New right-docked panel (mirrors Control Panel's dock, opposite side) listing every Signal, Channel, and shared `vars` key currently in the profile, with live values and a double-click-to-copy reference snippet (vars.get(...) / channels.get(...)) for use in scripts. Closed by default — opt-in config tool, no collapse-strip nag like Control Panel gets since it's not needed mid-run. Also moves Configuration out of the View menu into its own toolbar button, and makes table row hover match the selection highlight color. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10Unify Signals/Channels around auto-naming, plot-by-channel, and shared varsChristian Kolset
Devices: auto-name from OS-reported port description (Device Manager / udev string) instead of a generic label; default Serial #Channels 1; consistent simulate-mode checkbox across backends; inline device rename. Channels: calibration/filters move out of Signals into a Channel "Operation" (adds Scale/Offset alongside the existing math kinds); single-physical-source Channels get an embedded Filters block; new Channels auto-name/auto-ID from their source and reject duplicate names/IDs on apply; "Create Channel" button on each Signal row; renamed "Sources" -> "Signals" in the Channels UI; Signals' READ/WRITE dir badge (was silently-broken IN/OUT) now reflects the real writable flag. Plot: default layout is one pane per Channel (not per device, not raw Signals) titled by channel name; "+ Add Channel" is a checkbox dialog instead of an inline dropdown; traces get up/down reorder; live unit changes (e.g. a Mark-10 gauge switching lb/kgF/N/ozF) propagate to an open pane's axis label without clobbering a manually-typed one. Control Panel: hardware assignment now picks from the same writable- Signals list instead of a separate device/channel combo with a hardcoded Arduino pin list; stale visual indicator when a control's target device/channel disconnects or disappears; documented the `vars` bridge (Control Panel On Action Script <-> Channel scripts) in both directions' help text and script templates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10esp32_stringPot firmware supports calibration from aruidno IDEChristian Kolset
2026-08-07Removed serial commands feature.Christian Kolset
2026-08-07Device Firmware updated.Christian Kolset
- Moved arduino file to sub-directory. - Updated esp32_stringPot low-pass filter from EMA to butterwoth.
2026-08-06Firmware update.Christian Kolset
- Renamed and moved arduino firmware into it's own sketch for arduino compiling interface. - Added esp32 firmware for stringpotentiometer.
2026-08-04ignoring vscode directoryChristian Kolset
2026-08-04Added plugin zip file creation instructionsChristian Kolset
2026-08-04Fixed version number to be v0 for pre-releaseChristian Kolset
2026-08-03Fix clipped/jagged simulated Arduino waveformsChristian Kolset
amp+offset spanned the full [0,5] clamp range edge-to-edge, so per-sample noise pushed past the rails on every peak/trough and got hard-clamped into a flat plateau. Shrinking amp gives ~8.7 sigma of headroom so the clamp stays a safety net instead of firing every cycle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03Fix acquisition pipeline stutter with many channelsChristian Kolset
Batches per-tick device readings into one signal emit instead of one per channel, evaluates derived channels once per tick instead of once per raw sample, and makes ChannelBuffer.window() cost scale with the window size instead of total buffer history. Strip chart X-range now scrolls on an independent ~30fps timer instead of only snapping when new data lands, which was the main visible cause of the stutter. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03Re-designed control widgets to be fully contrained.Christian Kolset
- Moved edit and X button to top with header of widget. - Replaced up/down button with drag-and-drop style headers.
2026-08-03Removed floating control panel feature. Floating can make windowChristian Kolset
disappear and no sidebar panel. Possible future implementation.
2026-08-02Merge branch 'feat/auto-updater'Christian Kolset
2026-08-02Remove Debug window featureChristian Kolset
Debug button, DebugWindow, and the stdout/stderr tee (core/debug_log.py) weren't wanted. developer_mode toggle stays — still gates device Simulation Mode checkboxes and verbose terminal logging. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02Merge origin/main: reconcile ConfigWindow consolidation with Debug window + ↵Christian Kolset
protocol updates origin/main (23 commits) added a Debug window/log system on top of the old separate Devices/Channels/Plot windows, plus protocol fixes (cml, mark10, modbus_rtu, scpi) and device/profile changes. Local main (3 commits) replaced the separate windows with a unified ConfigWindow + dock panel. Kept local's ConfigWindow/dock architecture and ported the Debug window onto it: new toolbar button + _open_debug(), gated by developer_mode same as origin's version. Deduped the two independent "developer mode" toggles that had collided in SettingsWindow (origin's General-tab checkbox gating Debug window + device sim-mode visibility, local's Advanced-tab checkbox setting verbose logging) into one General-tab checkbox that does both. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02Fix Settings button calling nonexistent _toggle_win methodChristian Kolset
Consolidation of Devices/Channels/Plot into ConfigWindow left the Settings button wired to a method that no longer exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02Consolidate Devices/Channels/Plot into unified ConfigWindowChristian Kolset
Replaces three separate floating windows with single tabbed ConfigWindow: - Tab 0: Devices (device cards, add/remove/configure) - Tab 1: Channels (signal table with Dir/IN/OUT badge + pipeline ⚙ per row; derived channels pane) - Tab 2: Plot (layout canvas + pane config) Adds is_output field to ChannelConfig for output channel direction badges. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01Major control panel improvements.Christian Kolset
- Added dedicated firmware file for arduino - Added View button to consolidate windows - Control Panel changes: - Changed name fron "Controls" to "Control Panel" - Blue side bar when Panel is hidden. - Supports "popping" out contorl panel - Settings > Advanced tab added with developer settings
2026-08-01Rename LabDAQ → LabUI and overhaul plugin systemChristian Kolset
Branding: - Rename app, window title, file extension (.labdaq → .labui), user data dirs (~/.labui/), spec file (labdaq.spec → labui.spec), and APP_NAME throughout all source, docs, and config files Plugin system: - Plugins no longer bundled in the PyInstaller build — installed at runtime by users via Settings → Plugins → Install Plugin (zip) - PluginManager now takes user_dir + extra_scan_dirs; user plugins live in ~/.labui/plugins/, dev scan additionally covers project plugins/ - install_from_zip / uninstall / is_user_installed added to PluginManager - vendor/ dir inside plugin zips: prepended to sys.path at load time so plugins can ship their own deps without requiring pip on end-user machine - source_url field in manifest.json: shown as Download button in the missing-plugins dialog when a profile requires an absent plugin - Frozen-app pip install now targets ~/.labui/plugin_packages/ using a real system Python (sys.executable is the exe in frozen builds) Profile loading: - Profile now stores plugins_manifest snapshot (id, name, version, source_url) alongside plugins_enabled - On load, missing or dep-broken plugins trigger MissingPluginsDialog before the rest of the profile is applied; user can install from zip or download via source_url in-dialog, or cancel the load - Plugin reconciliation only enables installed plugins — missing ones are not written to enabled.json UI: - Version label added to status bar (bottom-right, muted colour) - Settings → Plugins tab: Install Plugin… button, per-plugin Remove button for user-installed plugins, live list refresh after install/remove Docs: - New docs/building.md covers the full release pipeline - docs/plugin-development.md updated for new install flow, vendoring, source_url, profile behaviour, and distribution instructions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-31Add PyInstaller packaging and tufup-based auto-update pipelineChristian Kolset
Full pipeline, verified end-to-end against the real installed tufup 0.10.0 API (initial docs/summaries turned out inaccurate in places — e.g. the apply method is download_and_apply_update, not update; confirmed by inspecting installed package source directly rather than trusting docs alone): - core/version.py: single-source app version constant. - labdaq.spec: PyInstaller onedir build (must be onedir, not onefile — tufup replaces individual files in the install dir on update). Bundles ui/*.qss, plugins/ (needed for runtime plugin discovery), and repository/metadata/root.json once repo_init.py has produced one. Built and smoke-tested: the frozen exe launches and stays running. - scripts/release/{repo_init,repo_release}.py: maintainer-run release tooling using tufup.repo.Repository, manual local signing (keys never touch CI). Both actually run end-to-end during development of this feature against a real build, not just written and assumed correct. Longer expiration_days than tufup-example's CI-oriented defaults (targets/snapshot/timestamp 90d instead of 7d/7d/1d), since we're signing manually, not on an automated daily schedule — see scripts/release/README.md for the re-signing cadence this still requires even between releases. - core/updater.py: thin Client wrapper. Refuses to run outside a frozen build (getattr(sys, "frozen", False)) since there's no installed bundle for tufup to update in `python main.py` dev mode. Bootstraps the bundled root.json into the metadata cache dir on first run — tuf.ngclient.Updater loads root.json from local disk on construction, it does not fetch it remotely by design (the root of trust can't come from the same server being verified). - core/app_settings.py: factored out app_data_dir() (was inline in _settings_path()) so the updater's metadata/target cache dirs live in the same per-user location as settings.json, deliberately outside the install directory an update can replace/move. - Settings > General: version display + "Check for Updates" button, manual-only per discussion (no silent background network calls or surprise restarts for a lab-instrument-control app). Metadata/targets are hosted on this repo's "updates" GitHub Release — a fixed tag, not a normal per-version tag, because TUF's top-level metadata needs a stable URL across app versions. No such GitHub-Releases- hosting example exists in tufup or tufup-example; verified this by fetching tufup-example's actual GitHub Actions workflow file directly after a web search wrongly suggested one existed — the design here is ours, not copied from upstream. Not yet done, deliberately left for the user: running repo_init.py for real (generates production signing keys), and creating the actual "updates" GitHub Release. Both are irreversible-ish, security-sensitive, externally-visible actions outside what should happen without the user directly driving them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31updated requirements comments.Christian Kolset
2026-07-29Merge branch 'fix/control-channel-direction-filter'Christian Kolset
# Conflicts: # ui/control_editor.py
2026-07-29Merge branch 'fix/run-stop-log-master-switch'Christian Kolset
2026-07-29Merge branch 'feat/plugin-pip-install-prompt'Christian Kolset
2026-07-29Merge branch 'feat/mark10-unit-autodetect'Christian Kolset
2026-07-29Merge branch 'feat/device-naming'Christian Kolset
2026-07-29Merge branch 'feat/dev-debug-window'Christian Kolset
2026-07-29Add Developer Mode setting, Debug window, and gate simulate behind itChristian Kolset
New Settings > General > "Developer mode" checkbox (default on, so this repo's simulate-by-default workflow is unaffected out of the box) persisted through the existing core/app_settings.py load/save functions. Exposed to code that can't easily receive the settings dict via core.app_settings.is_developer_mode()/set_developer_mode(), a small in-memory cache MainWindow keeps in sync whenever settings are loaded or applied. Debug window (ui/windows/debug_window.py): minimal first pass per the plan — a live console. core/debug_log.py tees stdout/stderr into a ring buffer + Qt signal (installed once in main.py, before anything prints), so the window shows everything printed since app start, including from background poll threads, not just what's printed while it happens to be open. Its toolbar button is hidden unless developer mode is on. Simulate gating: every device's Simulation Mode checkbox/dropdown is now hidden when developer mode is off — ui/add_device_dialog.py (also covers the motion_capture camera panel, which shares this same checkbox rather than having its own), and the per-device config widgets in analog_input.py, arduino_device.py, digital_io.py, nidaqmx_device.py, serial_device.py. Hiding rather than force-clearing an already-simulating device's state — an existing simulated device keeps working if developer mode is turned off later; the option is just not offered again until it's back on. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29Offer to pip install missing plugin dependencies instead of just warningChristian Kolset
Builds on the previous branch's dependency check: the missing-deps QMessageBox is now a Yes/No prompt. Yes runs a blocking `sys.executable -m pip install <reqs>` (subprocess.run, output captured), shows a result dialog, and on success proceeds straight to enabling the plugin — no need to click Enable a second time. Blocking is a deliberate simplification, not an oversight: this codebase has no worker-thread/progress-dialog pattern for slow operations anywhere else, so a threaded installer would be inconsistent with everything else here. pip install is a one-time, infrequent action, unlike e.g. a serial connect that runs constantly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>