summaryrefslogtreecommitdiff
path: root/plugins/base_plugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/base_plugin.py')
-rw-r--r--plugins/base_plugin.py133
1 files changed, 133 insertions, 0 deletions
diff --git a/plugins/base_plugin.py b/plugins/base_plugin.py
new file mode 100644
index 0000000..0eeb25f
--- /dev/null
+++ b/plugins/base_plugin.py
@@ -0,0 +1,133 @@
+"""
+plugins/base_plugin.py
+
+Abstract base class for all LabDAQ plugins.
+
+To create a plugin:
+ 1. Create a directory under plugins/ e.g. plugins/my_plugin/
+ 2. Add manifest.json with plugin metadata
+ 3. Add plugin.py with a class subclassing LabPlugin
+ 4. Enable in Settings → Plugins
+
+manifest.json schema:
+ {
+ "plugin_id": "my_plugin", required — unique snake_case id
+ "name": "My Plugin", required
+ "version": "1.0.0",
+ "description": "What this does.",
+ "author": "Name",
+ "entry_point": "plugin.MyPlugin" module.ClassName relative to plugin dir
+ }
+
+Integration points (all optional — override only what you need):
+
+ get_toolbar_actions() → list of PluginAction toolbar buttons in main window
+ get_devices() → list of BaseDevice auto-added to registry + engine
+ get_filter_classes() → {name: FilterBase cls} custom signal pipeline filters
+ get_settings_widget() → QWidget | None shown in Settings > Plugins
+ get_save_state() → dict persisted in .labdaq profiles
+ apply_save_state(dict) restore from profile
+"""
+
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from typing import Any, Callable, Dict, List, Optional, Type
+
+from PyQt6.QtWidgets import QWidget
+
+
+# ── Context ───────────────────────────────────────────────────────────────────
+
+@dataclass
+class PluginContext:
+ """Core app objects handed to each plugin on load."""
+ registry: Any # devices.device_registry.DeviceRegistry
+ engine: Any # core.acquisition.AcquisitionEngine
+ processor: Any # core.signal_processor.SignalProcessor
+ main_window: Any # ui.main_window.MainWindow (QMainWindow)
+
+
+# ── Toolbar action descriptor ─────────────────────────────────────────────────
+
+@dataclass
+class PluginAction:
+ """Describes one toolbar button contributed by a plugin."""
+ label: str
+ callback: Callable[[bool], None] # receives checked state
+ icon: str = ""
+ tooltip: str = ""
+ checkable: bool = False
+
+
+# ── Base class ────────────────────────────────────────────────────────────────
+
+class LabPlugin(ABC):
+ """
+ Abstract base for all LabDAQ plugins.
+
+ Subclass this, set the metadata properties, and override whatever
+ integration hooks your plugin needs.
+ """
+
+ # ── Metadata ──────────────────────────────────────────────────────────
+
+ @property
+ @abstractmethod
+ def plugin_id(self) -> str:
+ """Unique identifier matching manifest plugin_id."""
+
+ @property
+ @abstractmethod
+ def name(self) -> str:
+ """Human-readable display name."""
+
+ @property
+ def version(self) -> str:
+ return "1.0.0"
+
+ @property
+ def description(self) -> str:
+ return ""
+
+ @property
+ def author(self) -> str:
+ return ""
+
+ # ── Lifecycle ─────────────────────────────────────────────────────────
+
+ def on_load(self, context: PluginContext) -> None:
+ """Called when the plugin is enabled. Store context for later use."""
+
+ def on_unload(self) -> None:
+ """Called when the plugin is disabled. Release all resources."""
+
+ # ── Integration hooks ─────────────────────────────────────────────────
+
+ def get_toolbar_actions(self) -> List[PluginAction]:
+ """Toolbar buttons added to the main window when plugin is enabled."""
+ return []
+
+ def get_devices(self) -> list:
+ """BaseDevice instances contributed by this plugin.
+ They are automatically added to the DeviceRegistry and AcquisitionEngine."""
+ return []
+
+ def get_filter_classes(self) -> Dict[str, Type]:
+ """Custom signal filters: {filter_type_name: FilterBase subclass}.
+ Registered in SignalProcessor.FILTER_CLASSES when plugin loads."""
+ return {}
+
+ def get_settings_widget(self) -> Optional[QWidget]:
+ """Plugin-specific settings panel shown in Settings > Plugins."""
+ return None
+
+ # ── Profile persistence ───────────────────────────────────────────────
+
+ def get_save_state(self) -> Dict[str, Any]:
+ """Return a JSON-serialisable dict, saved with the .labdaq profile."""
+ return {}
+
+ def apply_save_state(self, state: Dict[str, Any]) -> None:
+ """Restore plugin state when a profile is loaded."""