# -*- coding: utf-8 -*- """ Capability-Registry für Satellites. Verwaltet die Fähigkeiten von Satellites und ermöglicht das Filtern nach bestimmten Capabilities. """ from __future__ import annotations import logging from dataclasses import dataclass, field from datetime import datetime from enum import Enum, auto from typing import Any, Callable class CapabilityState(Enum): """Zustand einer Capability.""" UNKNOWN = auto() # Noch nicht geprüft AVAILABLE = auto() # Verfügbar UNAVAILABLE = auto() # Nicht verfügbar DEGRADED = auto() # Eingeschränkt verfügbar CHECKING = auto() # Wird gerade geprüft @dataclass class CapabilityInfo: """Informationen über eine Capability.""" id: str # z.B. "audio.input" name: str # z.B. "Audio-Eingang" description: str = "" category: str = "" # z.B. "audio", "display", "sensor" version: str = "" # Capability-Version required_features: list[str] = field(default_factory=list) metadata: dict[str, Any] = field(default_factory=dict) @dataclass class Capability: """ Eine konkrete Capability eines Satellites. Enthält Informationen über Verfügbarkeit und spezifische Konfiguration. """ info: CapabilityInfo state: CapabilityState = CapabilityState.UNKNOWN satellite_id: str = "" last_checked: datetime | None = None error_message: str = "" parameters: dict[str, Any] = field(default_factory=dict) @property def id(self) -> str: """Capability-ID.""" return self.info.id @property def is_available(self) -> bool: """Prüft ob Capability verfügbar ist.""" return self.state == CapabilityState.AVAILABLE @property def is_degraded(self) -> bool: """Prüft ob Capability eingeschränkt ist.""" return self.state == CapabilityState.DEGRADED def update_state( self, state: CapabilityState, error: str = "", ) -> None: """Aktualisiert den Zustand.""" self.state = state self.error_message = error self.last_checked = datetime.now() def to_dict(self) -> dict[str, Any]: """Konvertiert zu Dictionary.""" return { "id": self.info.id, "name": self.info.name, "category": self.info.category, "state": self.state.name, "satellite_id": self.satellite_id, "last_checked": ( self.last_checked.isoformat() if self.last_checked else None ), "error": self.error_message, "parameters": self.parameters, } class CapabilityRegistry: """ Registry für Satellite-Capabilities. Verwaltet welche Capabilities auf welchen Satellites verfügbar sind und ermöglicht Suche nach Fähigkeiten. Beispiel: registry = CapabilityRegistry() # Capability registrieren registry.register( satellite_id="sat-001", capability=Capability( info=CapabilityInfo("audio.input", "Mikrofon"), state=CapabilityState.AVAILABLE, ) ) # Satellites mit Capability finden satellites = registry.find_satellites_with("audio.input") """ def __init__(self, logger: logging.Logger | None = None) -> None: """ Initialisiert die Registry. Args: logger: Logger-Instanz """ self.logger = logger or logging.getLogger(__name__) # satellite_id -> {capability_id -> Capability} self._capabilities: dict[str, dict[str, Capability]] = {} # capability_id -> CapabilityInfo (Definitionen) self._definitions: dict[str, CapabilityInfo] = {} # Callbacks self._on_change: list[Callable[[str, Capability], None]] = [] @property def satellite_count(self) -> int: """Anzahl registrierter Satellites.""" return len(self._capabilities) @property def capability_count(self) -> int: """Anzahl definierter Capability-Typen.""" return len(self._definitions) def define_capability(self, info: CapabilityInfo) -> None: """ Definiert einen Capability-Typ. Args: info: Capability-Definition """ self._definitions[info.id] = info self.logger.debug(f"Capability definiert: {info.id}") def get_definition(self, capability_id: str) -> CapabilityInfo | None: """Holt Capability-Definition.""" return self._definitions.get(capability_id) def list_definitions(self) -> list[CapabilityInfo]: """Listet alle Capability-Definitionen.""" return list(self._definitions.values()) def register( self, satellite_id: str, capability: Capability, ) -> None: """ Registriert eine Capability für einen Satellite. Args: satellite_id: Satellite-ID capability: Capability-Instanz """ if satellite_id not in self._capabilities: self._capabilities[satellite_id] = {} capability.satellite_id = satellite_id self._capabilities[satellite_id][capability.id] = capability self.logger.debug( f"Capability registriert: {capability.id} für {satellite_id}" ) # Callbacks for callback in self._on_change: try: callback(satellite_id, capability) except Exception as e: self.logger.error(f"Callback-Fehler: {e}") def unregister( self, satellite_id: str, capability_id: str | None = None, ) -> bool: """ Entfernt Capability(s) für einen Satellite. Args: satellite_id: Satellite-ID capability_id: Optional: Spezifische Capability Returns: True wenn entfernt """ if satellite_id not in self._capabilities: return False if capability_id: # Spezifische Capability entfernen if capability_id in self._capabilities[satellite_id]: del self._capabilities[satellite_id][capability_id] return True return False else: # Alle Capabilities des Satellites entfernen del self._capabilities[satellite_id] return True def update_state( self, satellite_id: str, capability_id: str, state: CapabilityState, error: str = "", ) -> bool: """ Aktualisiert den Zustand einer Capability. Args: satellite_id: Satellite-ID capability_id: Capability-ID state: Neuer Zustand error: Optionale Fehlermeldung Returns: True wenn aktualisiert """ cap = self.get(satellite_id, capability_id) if not cap: return False cap.update_state(state, error) # Callbacks for callback in self._on_change: try: callback(satellite_id, cap) except Exception as e: self.logger.error(f"Callback-Fehler: {e}") return True def get( self, satellite_id: str, capability_id: str, ) -> Capability | None: """ Holt spezifische Capability. Args: satellite_id: Satellite-ID capability_id: Capability-ID Returns: Capability oder None """ sat_caps = self._capabilities.get(satellite_id, {}) return sat_caps.get(capability_id) def get_all(self, satellite_id: str) -> list[Capability]: """ Holt alle Capabilities eines Satellites. Args: satellite_id: Satellite-ID Returns: Liste der Capabilities """ return list(self._capabilities.get(satellite_id, {}).values()) def get_available(self, satellite_id: str) -> list[Capability]: """Holt alle verfügbaren Capabilities eines Satellites.""" return [ cap for cap in self.get_all(satellite_id) if cap.is_available ] def has_capability( self, satellite_id: str, capability_id: str, require_available: bool = True, ) -> bool: """ Prüft ob Satellite eine Capability hat. Args: satellite_id: Satellite-ID capability_id: Capability-ID require_available: Muss verfügbar sein Returns: True wenn vorhanden (und verfügbar) """ cap = self.get(satellite_id, capability_id) if not cap: return False if require_available: return cap.is_available return True def has_all_capabilities( self, satellite_id: str, capability_ids: list[str], require_available: bool = True, ) -> bool: """Prüft ob Satellite alle angegebenen Capabilities hat.""" return all( self.has_capability(satellite_id, cid, require_available) for cid in capability_ids ) def has_any_capability( self, satellite_id: str, capability_ids: list[str], require_available: bool = True, ) -> bool: """Prüft ob Satellite mindestens eine der Capabilities hat.""" return any( self.has_capability(satellite_id, cid, require_available) for cid in capability_ids ) def find_satellites_with( self, capability_id: str, require_available: bool = True, ) -> list[str]: """ Findet Satellites mit einer bestimmten Capability. Args: capability_id: Gesuchte Capability require_available: Muss verfügbar sein Returns: Liste von Satellite-IDs """ result = [] for sat_id in self._capabilities: if self.has_capability(sat_id, capability_id, require_available): result.append(sat_id) return result def find_satellites_with_all( self, capability_ids: list[str], require_available: bool = True, ) -> list[str]: """Findet Satellites mit allen angegebenen Capabilities.""" result = [] for sat_id in self._capabilities: if self.has_all_capabilities(sat_id, capability_ids, require_available): result.append(sat_id) return result def find_satellites_with_any( self, capability_ids: list[str], require_available: bool = True, ) -> list[str]: """Findet Satellites mit mindestens einer der Capabilities.""" result = [] for sat_id in self._capabilities: if self.has_any_capability(sat_id, capability_ids, require_available): result.append(sat_id) return result def get_by_category( self, satellite_id: str, category: str, ) -> list[Capability]: """Holt Capabilities einer Kategorie.""" return [ cap for cap in self.get_all(satellite_id) if cap.info.category == category ] def on_change( self, callback: Callable[[str, Capability], None], ) -> None: """Registriert Callback für Änderungen.""" self._on_change.append(callback) def get_summary(self) -> dict[str, Any]: """Liefert Zusammenfassung der Registry.""" total_caps = sum( len(caps) for caps in self._capabilities.values() ) available_count = sum( sum(1 for c in caps.values() if c.is_available) for caps in self._capabilities.values() ) return { "satellite_count": self.satellite_count, "definition_count": self.capability_count, "total_capabilities": total_caps, "available_capabilities": available_count, "satellites": { sat_id: { "capabilities": len(caps), "available": sum(1 for c in caps.values() if c.is_available), } for sat_id, caps in self._capabilities.items() }, } def __contains__(self, satellite_id: str) -> bool: return satellite_id in self._capabilities def __len__(self) -> int: return self.satellite_count # Standard-Capability-Definitionen STANDARD_CAPABILITIES = [ CapabilityInfo( id="audio.input", name="Audio-Eingang", description="Mikrofon für Sprachaufnahme", category="audio", ), CapabilityInfo( id="audio.output", name="Audio-Ausgang", description="Lautsprecher für Sprachausgabe", category="audio", ), CapabilityInfo( id="audio.music", name="Musik-Wiedergabe", description="Hochwertige Musikwiedergabe", category="audio", ), CapabilityInfo( id="display.screen", name="Bildschirm", description="Visuelle Anzeige", category="display", ), CapabilityInfo( id="display.led", name="LED-Anzeige", description="LED-Statusanzeige", category="display", ), CapabilityInfo( id="sensor.motion", name="Bewegungssensor", description="Erkennt Bewegungen", category="sensor", ), CapabilityInfo( id="sensor.temperature", name="Temperatursensor", description="Misst Raumtemperatur", category="sensor", ), CapabilityInfo( id="network.wifi", name="WLAN", description="WiFi-Konnektivität", category="network", ), CapabilityInfo( id="network.bluetooth", name="Bluetooth", description="Bluetooth-Konnektivität", category="network", ), ]