From deb3ad65d25d4c3167a3cbeca2ac19b5c45b0627 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Thu, 7 May 2026 13:02:34 -0600 Subject: Added plugin/add-on system Plugins live in plugins// with a manifest.json and a LabPlugin subclass. Enabled/disabled in Settings > Plugins tab; state persists in plugins/enabled.json and in .labdaq profiles (loading a profile enables/disables plugins to match). Plugins can contribute toolbar buttons, BaseDevice instances (auto-wired into acquisition pipeline), custom signal filter classes, a settings widget, and profile state via get_save_state/apply_save_state. Load errors now report the exact failing stage (file not found, class missing, on_load crash). Includes example plugin skeleton and docs/plugin-development.md. Co-Authored-By: Claude Sonnet 4.6 --- plugins/example/plugin.py | 105 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 plugins/example/plugin.py (limited to 'plugins/example/plugin.py') diff --git a/plugins/example/plugin.py b/plugins/example/plugin.py new file mode 100644 index 0000000..e5fbb8e --- /dev/null +++ b/plugins/example/plugin.py @@ -0,0 +1,105 @@ +""" +plugins/example/plugin.py + +Skeleton plugin — shows every integration point. +Copy this directory, rename it, update manifest.json, and fill in your logic. +""" + +from __future__ import annotations +from typing import Any, Dict, List, Optional, Type + +from PyQt6.QtWidgets import QLabel, QWidget + +# LabDAQ imports available at runtime +from plugins.base_plugin import LabPlugin, PluginAction, PluginContext + + +class ExamplePlugin(LabPlugin): + + # ── Metadata ────────────────────────────────────────────────────────── + + @property + def plugin_id(self) -> str: + return "example" + + @property + def name(self) -> str: + return "Example Plugin" + + @property + def version(self) -> str: + return "1.0.0" + + @property + def description(self) -> str: + return "Skeleton showing all plugin integration points." + + @property + def author(self) -> str: + return "Your Name" + + # ── Lifecycle ───────────────────────────────────────────────────────── + + def on_load(self, context: PluginContext) -> None: + self._ctx = context + # e.g. start a background thread, open a camera, etc. + + def on_unload(self) -> None: + # Release resources — called when user disables plugin + pass + + # ── Toolbar ─────────────────────────────────────────────────────────── + + def get_toolbar_actions(self) -> List[PluginAction]: + return [ + PluginAction( + label = "Example", + icon = "🔌", + tooltip = "Open example plugin window", + checkable = True, + callback = self._on_toolbar_click, + ) + ] + + def _on_toolbar_click(self, checked: bool): + # Open / close your plugin window here + pass + + # ── Devices ─────────────────────────────────────────────────────────── + + def get_devices(self) -> list: + # Return BaseDevice instances — they are auto-added to the registry + # and AcquisitionEngine so their channels appear as normal signals. + # + # Example (uncomment and adapt): + # from devices.analog_input import AnalogInputDevice + # return [AnalogInputDevice("my_plugin_ai", num_channels=2, simulate=True)] + return [] + + # ── Custom filters ──────────────────────────────────────────────────── + + def get_filter_classes(self) -> Dict[str, Type]: + # Return {type_name: FilterBase subclass} for custom pipeline filters. + # + # Example: + # from .my_filter import MyFilter + # return {"my_filter": MyFilter} + return {} + + # ── Settings widget ─────────────────────────────────────────────────── + + def get_settings_widget(self) -> Optional[QWidget]: + # Return a QWidget shown in Settings > Plugins when this plugin is enabled. + lbl = QLabel("No settings for this plugin.") + lbl.setObjectName("traceSource") + return lbl + + # ── Profile persistence ─────────────────────────────────────────────── + + def get_save_state(self) -> Dict[str, Any]: + # Return JSON-serialisable dict saved with .labdaq profiles. + return {} + + def apply_save_state(self, state: Dict[str, Any]) -> None: + # Restore plugin state when a profile is loaded. + pass -- cgit v1.2.3