Files
mission-control-v2/client/hermes-voice/main.py
T
Hitonabi e19831f7d5 Feat: Hermes Voice Client (Wake Word + STT + Vision + TTS)
Windows-Desktop-Script: Energy VAD + Whisper Wake Detection,
faster-whisper STT, mss Screen Capture, MC2 Vision/Chat API,
Edge TTS Ausgabe, pystray System Tray. Kein Account nötig —
Wake Word frei konfigurierbar via WAKE_WORDS in config.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 22:02:52 +02:00

130 lines
3.9 KiB
Python

"""
Hermes Voice Client
Wake Word → STT → Screenshot → Hermes (MC2) → TTS
Läuft als System-Tray-App im Hintergrund.
Aktivierung: "Hey Jarvis" (konfigurierbar in config.py)
"""
import threading
import sys
from PIL import Image, ImageDraw
import pystray
from audio_input import wait_for_wake_word, record_speech, transcribe
from audio_output import speak, play_ding, stop_speaking
from screen_capture import capture_screen
from ai_client import ask
from config import SCREENSHOT_ON_QUERY
# ── Status ───────────────────────────────────────────────────────────────
class State:
IDLE = "idle" # wartet auf Wake Word
LISTENING = "listen" # nimmt Sprache auf
THINKING = "think" # wartet auf MC2-Antwort
SPEAKING = "speak" # gibt Antwort aus
_state = State.IDLE
_stop_event = threading.Event()
_tray_icon: pystray.Icon | None = None
# ── Tray Icon ────────────────────────────────────────────────────────────
COLORS = {
State.IDLE: "#22c55e", # grün
State.LISTENING: "#eab308", # gelb
State.THINKING: "#3b82f6", # blau
State.SPEAKING: "#a855f7", # lila
}
def _make_icon(color: str) -> Image.Image:
img = Image.new("RGBA", (64, 64), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.ellipse([4, 4, 60, 60], fill=color)
return img
def _set_state(state: str):
global _state
_state = state
if _tray_icon:
_tray_icon.icon = _make_icon(COLORS[state])
labels = {
State.IDLE: "Hermes — bereit (Hey Jarvis)",
State.LISTENING: "Hermes — hört zu…",
State.THINKING: "Hermes — denkt…",
State.SPEAKING: "Hermes — spricht",
}
_tray_icon.title = labels[state]
# ── Hauptloop ────────────────────────────────────────────────────────────
def _voice_loop():
print("[Hermes] Bereit. Sage 'Hey Jarvis' zum Aktivieren.")
while not _stop_event.is_set():
_set_state(State.IDLE)
# 1. Auf Wake Word warten
detected = wait_for_wake_word(_stop_event)
if not detected:
break
# 2. Aktivierungs-Ding + Aufnahme starten
play_ding()
_set_state(State.LISTENING)
audio = record_speech()
# 3. Transkribieren
text = transcribe(audio)
if not text:
continue
print(f"[STT] {text!r}")
# 4. Screenshot machen
screenshot = capture_screen() if SCREENSHOT_ON_QUERY else None
# 5. MC2 fragen
_set_state(State.THINKING)
response = ask(text, screenshot)
print(f"[Hermes] {response!r}")
# 6. Antwort sprechen
_set_state(State.SPEAKING)
speak(response)
print("[Hermes] Beendet.")
# ── System Tray ──────────────────────────────────────────────────────────
def _on_quit(icon, item):
_stop_event.set()
stop_speaking()
icon.stop()
def _on_mute(icon, item):
stop_speaking()
def main():
global _tray_icon
loop_thread = threading.Thread(target=_voice_loop, daemon=True)
loop_thread.start()
menu = pystray.Menu(
pystray.MenuItem("Hermes Voice", None, enabled=False),
pystray.Menu.SEPARATOR,
pystray.MenuItem("Sprechen stoppen", _on_mute),
pystray.MenuItem("Beenden", _on_quit),
)
_tray_icon = pystray.Icon(
"hermes-voice",
icon=_make_icon(COLORS[State.IDLE]),
title="Hermes — bereit (Hey Jarvis)",
menu=menu,
)
_tray_icon.run()
if __name__ == "__main__":
main()