registry.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. # -*- coding: utf-8 -*-
  2. """
  3. Capability-Registry für Satellites.
  4. Verwaltet die Fähigkeiten von Satellites und ermöglicht
  5. das Filtern nach bestimmten Capabilities.
  6. """
  7. from __future__ import annotations
  8. import logging
  9. from dataclasses import dataclass, field
  10. from datetime import datetime
  11. from enum import Enum, auto
  12. from typing import Any, Callable
  13. class CapabilityState(Enum):
  14. """Zustand einer Capability."""
  15. UNKNOWN = auto() # Noch nicht geprüft
  16. AVAILABLE = auto() # Verfügbar
  17. UNAVAILABLE = auto() # Nicht verfügbar
  18. DEGRADED = auto() # Eingeschränkt verfügbar
  19. CHECKING = auto() # Wird gerade geprüft
  20. @dataclass
  21. class CapabilityInfo:
  22. """Informationen über eine Capability."""
  23. id: str # z.B. "audio.input"
  24. name: str # z.B. "Audio-Eingang"
  25. description: str = ""
  26. category: str = "" # z.B. "audio", "display", "sensor"
  27. version: str = "" # Capability-Version
  28. required_features: list[str] = field(default_factory=list)
  29. metadata: dict[str, Any] = field(default_factory=dict)
  30. @dataclass
  31. class Capability:
  32. """
  33. Eine konkrete Capability eines Satellites.
  34. Enthält Informationen über Verfügbarkeit und
  35. spezifische Konfiguration.
  36. """
  37. info: CapabilityInfo
  38. state: CapabilityState = CapabilityState.UNKNOWN
  39. satellite_id: str = ""
  40. last_checked: datetime | None = None
  41. error_message: str = ""
  42. parameters: dict[str, Any] = field(default_factory=dict)
  43. @property
  44. def id(self) -> str:
  45. """Capability-ID."""
  46. return self.info.id
  47. @property
  48. def is_available(self) -> bool:
  49. """Prüft ob Capability verfügbar ist."""
  50. return self.state == CapabilityState.AVAILABLE
  51. @property
  52. def is_degraded(self) -> bool:
  53. """Prüft ob Capability eingeschränkt ist."""
  54. return self.state == CapabilityState.DEGRADED
  55. def update_state(
  56. self,
  57. state: CapabilityState,
  58. error: str = "",
  59. ) -> None:
  60. """Aktualisiert den Zustand."""
  61. self.state = state
  62. self.error_message = error
  63. self.last_checked = datetime.now()
  64. def to_dict(self) -> dict[str, Any]:
  65. """Konvertiert zu Dictionary."""
  66. return {
  67. "id": self.info.id,
  68. "name": self.info.name,
  69. "category": self.info.category,
  70. "state": self.state.name,
  71. "satellite_id": self.satellite_id,
  72. "last_checked": (
  73. self.last_checked.isoformat()
  74. if self.last_checked else None
  75. ),
  76. "error": self.error_message,
  77. "parameters": self.parameters,
  78. }
  79. class CapabilityRegistry:
  80. """
  81. Registry für Satellite-Capabilities.
  82. Verwaltet welche Capabilities auf welchen Satellites
  83. verfügbar sind und ermöglicht Suche nach Fähigkeiten.
  84. Beispiel:
  85. registry = CapabilityRegistry()
  86. # Capability registrieren
  87. registry.register(
  88. satellite_id="sat-001",
  89. capability=Capability(
  90. info=CapabilityInfo("audio.input", "Mikrofon"),
  91. state=CapabilityState.AVAILABLE,
  92. )
  93. )
  94. # Satellites mit Capability finden
  95. satellites = registry.find_satellites_with("audio.input")
  96. """
  97. def __init__(self, logger: logging.Logger | None = None) -> None:
  98. """
  99. Initialisiert die Registry.
  100. Args:
  101. logger: Logger-Instanz
  102. """
  103. self.logger = logger or logging.getLogger(__name__)
  104. # satellite_id -> {capability_id -> Capability}
  105. self._capabilities: dict[str, dict[str, Capability]] = {}
  106. # capability_id -> CapabilityInfo (Definitionen)
  107. self._definitions: dict[str, CapabilityInfo] = {}
  108. # Callbacks
  109. self._on_change: list[Callable[[str, Capability], None]] = []
  110. @property
  111. def satellite_count(self) -> int:
  112. """Anzahl registrierter Satellites."""
  113. return len(self._capabilities)
  114. @property
  115. def capability_count(self) -> int:
  116. """Anzahl definierter Capability-Typen."""
  117. return len(self._definitions)
  118. def define_capability(self, info: CapabilityInfo) -> None:
  119. """
  120. Definiert einen Capability-Typ.
  121. Args:
  122. info: Capability-Definition
  123. """
  124. self._definitions[info.id] = info
  125. self.logger.debug(f"Capability definiert: {info.id}")
  126. def get_definition(self, capability_id: str) -> CapabilityInfo | None:
  127. """Holt Capability-Definition."""
  128. return self._definitions.get(capability_id)
  129. def list_definitions(self) -> list[CapabilityInfo]:
  130. """Listet alle Capability-Definitionen."""
  131. return list(self._definitions.values())
  132. def register(
  133. self,
  134. satellite_id: str,
  135. capability: Capability,
  136. ) -> None:
  137. """
  138. Registriert eine Capability für einen Satellite.
  139. Args:
  140. satellite_id: Satellite-ID
  141. capability: Capability-Instanz
  142. """
  143. if satellite_id not in self._capabilities:
  144. self._capabilities[satellite_id] = {}
  145. capability.satellite_id = satellite_id
  146. self._capabilities[satellite_id][capability.id] = capability
  147. self.logger.debug(
  148. f"Capability registriert: {capability.id} für {satellite_id}"
  149. )
  150. # Callbacks
  151. for callback in self._on_change:
  152. try:
  153. callback(satellite_id, capability)
  154. except Exception as e:
  155. self.logger.error(f"Callback-Fehler: {e}")
  156. def unregister(
  157. self,
  158. satellite_id: str,
  159. capability_id: str | None = None,
  160. ) -> bool:
  161. """
  162. Entfernt Capability(s) für einen Satellite.
  163. Args:
  164. satellite_id: Satellite-ID
  165. capability_id: Optional: Spezifische Capability
  166. Returns:
  167. True wenn entfernt
  168. """
  169. if satellite_id not in self._capabilities:
  170. return False
  171. if capability_id:
  172. # Spezifische Capability entfernen
  173. if capability_id in self._capabilities[satellite_id]:
  174. del self._capabilities[satellite_id][capability_id]
  175. return True
  176. return False
  177. else:
  178. # Alle Capabilities des Satellites entfernen
  179. del self._capabilities[satellite_id]
  180. return True
  181. def update_state(
  182. self,
  183. satellite_id: str,
  184. capability_id: str,
  185. state: CapabilityState,
  186. error: str = "",
  187. ) -> bool:
  188. """
  189. Aktualisiert den Zustand einer Capability.
  190. Args:
  191. satellite_id: Satellite-ID
  192. capability_id: Capability-ID
  193. state: Neuer Zustand
  194. error: Optionale Fehlermeldung
  195. Returns:
  196. True wenn aktualisiert
  197. """
  198. cap = self.get(satellite_id, capability_id)
  199. if not cap:
  200. return False
  201. cap.update_state(state, error)
  202. # Callbacks
  203. for callback in self._on_change:
  204. try:
  205. callback(satellite_id, cap)
  206. except Exception as e:
  207. self.logger.error(f"Callback-Fehler: {e}")
  208. return True
  209. def get(
  210. self,
  211. satellite_id: str,
  212. capability_id: str,
  213. ) -> Capability | None:
  214. """
  215. Holt spezifische Capability.
  216. Args:
  217. satellite_id: Satellite-ID
  218. capability_id: Capability-ID
  219. Returns:
  220. Capability oder None
  221. """
  222. sat_caps = self._capabilities.get(satellite_id, {})
  223. return sat_caps.get(capability_id)
  224. def get_all(self, satellite_id: str) -> list[Capability]:
  225. """
  226. Holt alle Capabilities eines Satellites.
  227. Args:
  228. satellite_id: Satellite-ID
  229. Returns:
  230. Liste der Capabilities
  231. """
  232. return list(self._capabilities.get(satellite_id, {}).values())
  233. def get_available(self, satellite_id: str) -> list[Capability]:
  234. """Holt alle verfügbaren Capabilities eines Satellites."""
  235. return [
  236. cap for cap in self.get_all(satellite_id)
  237. if cap.is_available
  238. ]
  239. def has_capability(
  240. self,
  241. satellite_id: str,
  242. capability_id: str,
  243. require_available: bool = True,
  244. ) -> bool:
  245. """
  246. Prüft ob Satellite eine Capability hat.
  247. Args:
  248. satellite_id: Satellite-ID
  249. capability_id: Capability-ID
  250. require_available: Muss verfügbar sein
  251. Returns:
  252. True wenn vorhanden (und verfügbar)
  253. """
  254. cap = self.get(satellite_id, capability_id)
  255. if not cap:
  256. return False
  257. if require_available:
  258. return cap.is_available
  259. return True
  260. def has_all_capabilities(
  261. self,
  262. satellite_id: str,
  263. capability_ids: list[str],
  264. require_available: bool = True,
  265. ) -> bool:
  266. """Prüft ob Satellite alle angegebenen Capabilities hat."""
  267. return all(
  268. self.has_capability(satellite_id, cid, require_available)
  269. for cid in capability_ids
  270. )
  271. def has_any_capability(
  272. self,
  273. satellite_id: str,
  274. capability_ids: list[str],
  275. require_available: bool = True,
  276. ) -> bool:
  277. """Prüft ob Satellite mindestens eine der Capabilities hat."""
  278. return any(
  279. self.has_capability(satellite_id, cid, require_available)
  280. for cid in capability_ids
  281. )
  282. def find_satellites_with(
  283. self,
  284. capability_id: str,
  285. require_available: bool = True,
  286. ) -> list[str]:
  287. """
  288. Findet Satellites mit einer bestimmten Capability.
  289. Args:
  290. capability_id: Gesuchte Capability
  291. require_available: Muss verfügbar sein
  292. Returns:
  293. Liste von Satellite-IDs
  294. """
  295. result = []
  296. for sat_id in self._capabilities:
  297. if self.has_capability(sat_id, capability_id, require_available):
  298. result.append(sat_id)
  299. return result
  300. def find_satellites_with_all(
  301. self,
  302. capability_ids: list[str],
  303. require_available: bool = True,
  304. ) -> list[str]:
  305. """Findet Satellites mit allen angegebenen Capabilities."""
  306. result = []
  307. for sat_id in self._capabilities:
  308. if self.has_all_capabilities(sat_id, capability_ids, require_available):
  309. result.append(sat_id)
  310. return result
  311. def find_satellites_with_any(
  312. self,
  313. capability_ids: list[str],
  314. require_available: bool = True,
  315. ) -> list[str]:
  316. """Findet Satellites mit mindestens einer der Capabilities."""
  317. result = []
  318. for sat_id in self._capabilities:
  319. if self.has_any_capability(sat_id, capability_ids, require_available):
  320. result.append(sat_id)
  321. return result
  322. def get_by_category(
  323. self,
  324. satellite_id: str,
  325. category: str,
  326. ) -> list[Capability]:
  327. """Holt Capabilities einer Kategorie."""
  328. return [
  329. cap for cap in self.get_all(satellite_id)
  330. if cap.info.category == category
  331. ]
  332. def on_change(
  333. self,
  334. callback: Callable[[str, Capability], None],
  335. ) -> None:
  336. """Registriert Callback für Änderungen."""
  337. self._on_change.append(callback)
  338. def get_summary(self) -> dict[str, Any]:
  339. """Liefert Zusammenfassung der Registry."""
  340. total_caps = sum(
  341. len(caps) for caps in self._capabilities.values()
  342. )
  343. available_count = sum(
  344. sum(1 for c in caps.values() if c.is_available)
  345. for caps in self._capabilities.values()
  346. )
  347. return {
  348. "satellite_count": self.satellite_count,
  349. "definition_count": self.capability_count,
  350. "total_capabilities": total_caps,
  351. "available_capabilities": available_count,
  352. "satellites": {
  353. sat_id: {
  354. "capabilities": len(caps),
  355. "available": sum(1 for c in caps.values() if c.is_available),
  356. }
  357. for sat_id, caps in self._capabilities.items()
  358. },
  359. }
  360. def __contains__(self, satellite_id: str) -> bool:
  361. return satellite_id in self._capabilities
  362. def __len__(self) -> int:
  363. return self.satellite_count
  364. # Standard-Capability-Definitionen
  365. STANDARD_CAPABILITIES = [
  366. CapabilityInfo(
  367. id="audio.input",
  368. name="Audio-Eingang",
  369. description="Mikrofon für Sprachaufnahme",
  370. category="audio",
  371. ),
  372. CapabilityInfo(
  373. id="audio.output",
  374. name="Audio-Ausgang",
  375. description="Lautsprecher für Sprachausgabe",
  376. category="audio",
  377. ),
  378. CapabilityInfo(
  379. id="audio.music",
  380. name="Musik-Wiedergabe",
  381. description="Hochwertige Musikwiedergabe",
  382. category="audio",
  383. ),
  384. CapabilityInfo(
  385. id="display.screen",
  386. name="Bildschirm",
  387. description="Visuelle Anzeige",
  388. category="display",
  389. ),
  390. CapabilityInfo(
  391. id="display.led",
  392. name="LED-Anzeige",
  393. description="LED-Statusanzeige",
  394. category="display",
  395. ),
  396. CapabilityInfo(
  397. id="sensor.motion",
  398. name="Bewegungssensor",
  399. description="Erkennt Bewegungen",
  400. category="sensor",
  401. ),
  402. CapabilityInfo(
  403. id="sensor.temperature",
  404. name="Temperatursensor",
  405. description="Misst Raumtemperatur",
  406. category="sensor",
  407. ),
  408. CapabilityInfo(
  409. id="network.wifi",
  410. name="WLAN",
  411. description="WiFi-Konnektivität",
  412. category="network",
  413. ),
  414. CapabilityInfo(
  415. id="network.bluetooth",
  416. name="Bluetooth",
  417. description="Bluetooth-Konnektivität",
  418. category="network",
  419. ),
  420. ]