summaryrefslogtreecommitdiff
path: root/devices/device_registry.py
blob: 8ed68869bddbd47b37fc50d3215d5978964f13fb (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
"""
devices/device_registry.py

Auto-discovers and manages all BaseDevice subclasses.
Drop a new .py file in devices/ and it appears automatically.
"""

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:
    def __init__(self):
        self._classes:   Dict[str, Type[BaseDevice]] = {}
        self._instances: Dict[str, BaseDevice]       = {}
        self._discover()

    # ── Discovery ────────────────────────────────────────────────────────

    def _discover(self):
        path    = Path(__file__).parent
        package = "devices"
        skip    = {"base_device", "device_registry"}

        for _, mod_name, _ in pkgutil.iter_modules([str(path)]):
            if mod_name.startswith("_") or mod_name in skip:
                continue
            try:
                mod = importlib.import_module(f"{package}.{mod_name}")
                for name, obj in inspect.getmembers(mod, inspect.isclass):
                    if issubclass(obj, BaseDevice) and obj is not BaseDevice:
                        self._classes[name] = obj
            except Exception as e:
                print(f"[Registry] Could not load {mod_name}: {e}")

    # ── Instance management ──────────────────────────────────────────────

    def add_instance(self, device: BaseDevice) -> None:
        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._classes.keys())

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

    def create(self, class_name: str, device_id: str, **kw) -> BaseDevice:
        cls = self._classes.get(class_name)
        if not cls:
            raise ValueError(f"Unknown device class: {class_name}")
        dev = cls(device_id=device_id, **kw)
        self._instances[device_id] = dev
        return dev

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