summaryrefslogtreecommitdiff
path: root/plugins/example
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-05-07 13:02:34 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-06-03 09:50:17 -0600
commitdeb3ad65d25d4c3167a3cbeca2ac19b5c45b0627 (patch)
tree131eebcabfab2d0a19e52aec21a21ad0a24775fa /plugins/example
parent2b9943ba0d28449a590acfa8a41555b174b6ba84 (diff)
Added plugin/add-on system
Plugins live in plugins/<name>/ 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 <noreply@anthropic.com>
Diffstat (limited to 'plugins/example')
-rw-r--r--plugins/example/__pycache__/plugin.cpython-312.pycbin0 -> 3549 bytes
-rw-r--r--plugins/example/manifest.json8
-rw-r--r--plugins/example/plugin.py105
3 files changed, 113 insertions, 0 deletions
diff --git a/plugins/example/__pycache__/plugin.cpython-312.pyc b/plugins/example/__pycache__/plugin.cpython-312.pyc
new file mode 100644
index 0000000..c31b466
--- /dev/null
+++ b/plugins/example/__pycache__/plugin.cpython-312.pyc
Binary files differ
diff --git a/plugins/example/manifest.json b/plugins/example/manifest.json
new file mode 100644
index 0000000..a5af87d
--- /dev/null
+++ b/plugins/example/manifest.json
@@ -0,0 +1,8 @@
+{
+ "plugin_id": "example",
+ "name": "Example Plugin",
+ "version": "1.0.0",
+ "description": "Skeleton showing all plugin integration points. Safe to delete.",
+ "author": "Your Name",
+ "entry_point": "plugin.ExamplePlugin"
+}
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