loudest.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # -*- coding: utf-8 -*-
  2. """
  3. Loudest-Strategie für Arbitration.
  4. Wählt den Satellite mit dem höchsten Audio-Level.
  5. Dies ist die Standard-Strategie und entspricht dem Prinzip
  6. "wer am lautesten gehört wird, ist am nächsten".
  7. """
  8. from typing import Any
  9. from trixy_core.arbitration.candidate import ArbitrationCandidate
  10. from trixy_core.arbitration.strategies.base import ArbitrationStrategy
  11. class LoudestStrategy(ArbitrationStrategy):
  12. """
  13. Strategie: Höchstes Audio-Level gewinnt.
  14. Diese Strategie wählt den Satellite aus, bei dem das Wakeword
  15. am lautesten erkannt wurde. Dies ist typischerweise der Satellite,
  16. der dem Sprecher am nächsten ist.
  17. Beispiel:
  18. ```python
  19. strategy = LoudestStrategy()
  20. candidates = [
  21. ArbitrationCandidate(satellite_id="kitchen", audio_level=0.7),
  22. ArbitrationCandidate(satellite_id="living_room", audio_level=0.85),
  23. ]
  24. winner = strategy.select(candidates)
  25. # winner.satellite_id == "living_room"
  26. ```
  27. """
  28. def __init__(self, min_audio_level: float = 0.1) -> None:
  29. """
  30. Initialisiert die Strategie.
  31. Args:
  32. min_audio_level: Minimales Audio-Level für Gültigkeit
  33. """
  34. self.min_audio_level = min_audio_level
  35. @property
  36. def name(self) -> str:
  37. return "loudest"
  38. @property
  39. def description(self) -> str:
  40. return "Wählt den Satellite mit dem höchsten Audio-Level"
  41. def calculate_score(
  42. self,
  43. candidate: ArbitrationCandidate,
  44. context: dict[str, Any] | None = None,
  45. ) -> float:
  46. """
  47. Score basiert nur auf Audio-Level.
  48. Args:
  49. candidate: Der Kandidat
  50. context: Nicht verwendet
  51. Returns:
  52. Audio-Level als Score (0.0-1.0)
  53. """
  54. candidate.score_details = {
  55. "audio_level": candidate.audio_level,
  56. }
  57. return candidate.audio_level
  58. def select(
  59. self,
  60. candidates: list[ArbitrationCandidate],
  61. context: dict[str, Any] | None = None,
  62. ) -> ArbitrationCandidate | None:
  63. """
  64. Wählt Kandidaten mit höchstem Audio-Level.
  65. Args:
  66. candidates: Liste der Kandidaten
  67. context: Nicht verwendet
  68. Returns:
  69. Kandidat mit höchstem audio_level oder None
  70. """
  71. valid = self.filter_valid(candidates)
  72. if not valid:
  73. return None
  74. # Scores berechnen
  75. for candidate in valid:
  76. candidate.score = self.calculate_score(candidate, context)
  77. # Höchsten Score auswählen
  78. winner = max(valid, key=lambda c: c.score)
  79. return winner
  80. def validate_candidate(self, candidate: ArbitrationCandidate) -> bool:
  81. """Prüft ob Audio-Level über Minimum liegt."""
  82. base_valid = super().validate_candidate(candidate)
  83. return base_valid and candidate.audio_level >= self.min_audio_level