summaryrefslogtreecommitdiff
path: root/daq_system/devices/device_registry.py
blob: e407bc37fe5651d80aff701305290fb8ad368fca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
"""
devices/device_registry.py

Discovers, instantiates, and manages all device modules.
Add new devices by dropping a .py file into the devices/ directory.
"""

import importlib
import inspect
import pkgutil
from pathlib import Path
from typing import Dict, List, Optional, Type

from devices.base_device import BaseDevice, DeviceInfo


class DeviceRegistry:
    """Central registry for all DAQ device modules."""

    def __init__(self):
        self._device_classes: Dict[str, Type[BaseDevice]] = {}
        self._instances: Dict[str, BaseDevice] = {}
        self._auto_discover()

    # ------------------------------------------------------------------ #
    #  Discovery                                                           #
    # ------------------------------------------------------------------ #

    def _auto_discover(self):
        """Scan the devices/ package for BaseDevice subclasses."""
        devices_path = Path(__file__).parent
        package = "devices"

        for _, module_name, _ in pkgutil.iter_modules([str(devices_path)]):
            if module_name.startswith("_") or module_name in ("base_device", "device_registry"):
                continue
            try:
                module = importlib.import_module(f"{package}.{module_name}")
                for name, obj in inspect.getmembers(module, inspect.isclass):
                    if issubclass(obj, BaseDevice) and obj is not BaseDevice:
                        self._device_classes[name] = obj
            except Exception as e:
                print(f"[DeviceRegistry] Failed to load {module_name}: {e}")

    def register_class(self, cls: Type[BaseDevice]) -> None:
        """Manually register a device class (for testing / runtime plugins)."""
        self._device_classes[cls.__name__] = cls

    # ------------------------------------------------------------------ #
    #  Instance management                                                 #
    # ------------------------------------------------------------------ #

    def create_device(self, class_name: str, device_id: str, **kwargs) -> Optional[BaseDevice]:
        """Instantiate a device by class name with a unique device_id."""
        cls = self._device_classes.get(class_name)
        if cls is None:
            raise ValueError(f"Unknown device class: {class_name}")
        instance = cls(device_id=device_id, **kwargs)
        self._instances[device_id] = instance
        return instance

    def add_instance(self, device: BaseDevice) -> None:
        """Register a pre-built device instance."""
        self._instances[device.info.device_id] = device

    def remove_instance(self, device_id: str) -> None:
        dev = self._instances.pop(device_id, None)
        if dev:
            try:
                dev.disconnect()
            except Exception:
                pass

    def get_instance(self, device_id: str) -> Optional[BaseDevice]:
        return self._instances.get(device_id)

    def all_instances(self) -> List[BaseDevice]:
        return list(self._instances.values())

    def available_classes(self) -> List[str]:
        return list(self._device_classes.keys())

    def get_class(self, class_name: str) -> Optional[Type[BaseDevice]]:
        return self._device_classes.get(class_name)

    def __len__(self):
        return len(self._instances)