FHEMWEB/VoiceControl: Web-STT & Hardware-Wakeword

VoiceControl – Sprachsteuerung via Browser und Atom Echo s3r
Diese Lösung ermöglicht eine flexible Sprachsteuerung für FHEM. Sprache wird in Text (Speech-to-Text) umgewandelt und als Reading STT im Device global bereitgestellt. Dieses Reading kann anschließend zentral (z. B. über notify oder DOIF) ausgewertet werden.
Unterstützt werden nur Chromium-basierte Browser (Chrome, Edge, Fully Browser). Firefox hat leider kein Backend.
Es gibt zwei unterschiedliche Wege zur Spracherfassung:
- Weg 1️⃣ (Software): Browser-basierte Komplettlösung
- Weg 2️⃣ (Hybrid): Hardware-Wakeword + Browser-Spracherkennung
Hilfe
- Forenthread zum VoiceControl Sprachsteuerung
Dort befinden sich in Post #1 auch alle benötigten Dateien.
Funktionen
Grundprinzip
- Sprache → Speech-to-Text
- Ergebnis → Reading
STTim Deviceglobal - Zentrale Logik verarbeitet Befehle
Betriebsarten
- Push-to-Talk (nur Browser)
- Always-On mit Wakeword
- Hardware-Wakeword (Hybrid)
Rückmeldungen
- Sprachausgabe (TTS)
- Visuelle Bubble im Browser
- Optional gezielte Rückmeldung per Client-ID
Weg 1️⃣: Browser-Lösung (voicecontrol.js)
Das Script nutzt die Google Web Speech API. Unterstützt werden Chromium-basierte Browser (Chrome, Edge, Fully Browser). Firefox wird aktuell nicht unterstützt.
Bedienung
Push-to-Talk
- Button gedrückt halten (~450 ms)
- Direkt sprechen (kein Wakeword nötig)
- Befehl wird sofort verarbeitet
Always-On
- Kurzer Klick aktiviert Dauerbetrieb
- Wakeword erforderlich (Standard: „James“)
- Nach Aktivierung permanentes Mithören
Wakeword
- Standard:
james - Anpassbar im Script:
const wakewords = ["james"];
Ablauf:
- „James“ sagen
- System antwortet „Ja?“
- Zeitfenster (~6 Sekunden) für Befehl
- Danach automatische Verarbeitung oder Abbruch
Installation
Datei kopieren
{ Svn_GetFile('contrib/voicecontrol.js', 'www/pgm2/voicecontrol.js') }
Einbindung
attr WEBphone JavaScripts pgm2/voicecontrol.js
Hinweis (HTTP ohne HTTPS)
Chrome benötigt Freigabe für Mikrofon:
chrome://flags/#unsafely-treat-insecure-origin-as-secure- Eigene URL hinzufügen
- Auf
Enabledsetzen
Weg 2️⃣: Hybrid-Lösung mit Browser + Atom Echo (voicecontrol_echo.js)
Diese Variante kombiniert Hardware und Software:
- Wakeword-Erkennung erfolgt auf dem ESP (Atom Echo s3r)
- Die eigentliche Sprachverarbeitung (Speech-to-Text) erfolgt im Browser
Funktionsweise
- Wakeword wird auf dem ESP erkannt
- FHEM erzeugt Event (z. B.
james_detected) - Browser empfängt Event via WebSocket
- Speech-to-Text startet im Browser
- Ergebnis wird an FHEM übertragen
Aktivierung
- Kurzer Klick auf Button aktiviert/deaktiviert System
- Baut WebSocket-Verbindung zu FHEM auf
- Kein Push-to-Talk-Modus
Installation
Datei kopieren
Die Datei voicecontrol_echo.js ist hier zu finden: VoiceControl Sprachsteuerung und muss nach www/pgm2/voicecontrol_echo.js kopiert werden.
Die echo_s3r.yaml ist für den Atom Echo s3r. Die Konfuguration ist weiter unten beschrieben.
Einbindung
attr WEBtablet JavaScripts pgm2/voicecontrol_echo.js
Konfiguration im Javascript
const DEVICE = "atom_echos3r_9888e00f4280";
const TRIGGER = "james_detected";
const FHEM_IP = "192.168.1.76:8085";
DEVICE→ FHEM-Device des ESPTRIGGER→ Event bei WakewordFHEM_IP→ FHEM-Server
Konfiguration in der Yaml
Damit sich der ESP mit eurem MQTT-Server und WLAN verbindet, müssen folgende Stellen angepasst werden.
wifi:
ssid: "YOUR_SSID"
password: "YOUR_PW"
fast_connect: true
mqtt:
broker: 192.168.1.76
port: 1884
username: "YOUR_USERNAME"
password: "YOUR_PW"
topic_prefix: atom_echo
Wakeword (ESP / ESPHome)
Das Wakeword wird direkt auf dem ESP definiert:
- Umsetzung über ESPHome
- Eine große Auswahl an Wakewords sind hier zu finden:
https://github.com/TaterTotterson/microWakeWords
Hier ein Beispiel, wie das Wakeword definiert wird.
micro_wake_word:
id: mww
microphone: echo_mic
models:
- model: "https://github.com/TaterTotterson/microWakeWords/raw/main/microWakeWordsV2/james.json"
id: james_model
probability_cutoff: 0.6
Kommunikation
- WebSocket-Verbindung zu FHEM
- Lauscht auf Device-Events
- Automatischer Reconnect bei Abbruch
Ablauf nach Wakeword
- System sagt „Ja?“
- Browser startet SpeechRecognition
- Nutzer spricht Befehl
- Befehl wird verarbeitet
- Rückmeldung „Erledigt“
Zentrale Auswertung (Logik)
Beide Wege schreiben in:
global:STT
Die Verarbeitung erfolgt zentral über ein notify.
Beispiel: notify
defmod n_VoiceControl notify global:STT:.* {\
# 1. VORBEREITUNG\
my ($cleanEvent, $clientId) = $EVENT =~ /^(.*)\s\[(.*)\]$/;;\
$cleanEvent //= $EVENT;;\
$clientId //= "unknown";;\
\
my %lightRooms = (\
"esszimmer" => { dev => "Lampe01_Ez", label => "Licht Esszimmer" },\
"küche" => { dev => "Deckenlampe_Kue", label => "Licht Küche" },\
"wohnzimmer" => { dev => "Lampe06_Dek", label => "Licht Wohnzimmer" }\
);;\
\
my %vacRooms = (\
"arbeitszimmer" => "Arbeitszimmer",\
"badezimmer" => "Badezimmer",\
"esszimmer" => "Esszimmer",\
"flur" => "Flur",\
"küche" => "Küche",\
"wohnzimmer" => "Wohnzimmer"\
);;\
\
my $onRegEx = '\b(an|ein|einschalten|starte|aktivier|aktiviere)\b';;\
my $offRegEx = '\b(aus|ausschalten|stop|stoppe|beende|deaktivier|deaktiviere)\b';;\
\
my @commands = split(/\s*(?:und|dann|,)\s*/, lc($cleanEvent));;\
\
# 2. DER BEFEHLS-LOOP\
foreach my $cmd_part (@commands) {\
\
$cmd_part =~ s/^\s+|\s+$//g;;\
\
my $is_on = ($cmd_part =~ /$onRegEx/) ? 1 : 0;;\
my $is_off = ($cmd_part =~ /$offRegEx/) ? 1 : 0;;\
\
$cmd_part =~ s/\b(ich|brauche|mach|bitte|kannst du|würdest du|mal|doch|den|das|die|im|in der)\b//g;;\
\
# --- INTENT: STAUBSAUGER ---\
if ($cmd_part =~ /(reinige|sauge|putze|staubsauger|roboter)/) {\
my @found = grep { $cmd_part =~ /\b$_\b/ } keys %vacRooms;;\
if (@found) {\
fhem("set MQTT2_valetudo_FlusteredUnequaledFish clean_segment " . join(",", map { $vacRooms{$_} } @found));;\
} else {\
fhem("set MQTT2_valetudo_FlusteredUnequaledFish start");;\
}\
next;;\
} elsif ($cmd_part =~ /(lade|aufladen|dock|station|home)/) {\
fhem("set MQTT2_valetudo_FlusteredUnequaledFish charge");;\
next;;\
}\
\
# --- INTENT: FERNSEHER ---\
if ($cmd_part =~ /(fernseher|tv|vuplus)/) {\
fhem("set VuPlus " . ($is_off ? "off" : "on"));;\
next;;\
}\
\
# --- INTENT: AMBIENTE ---\
if ($cmd_part =~ /ambiente/) {\
if ($cmd_part =~ /(\d+)/) {\
my $b = ($1 > 255 ? 255 : ($1 < 1 ? 1 : $1));;\
fhem("set LampeSzeneAlle brightness $b");;\
} else {\
fhem("set LampeSzeneAlle " . ($is_off ? "off" : "on"));;\
}\
next;;\
}\
\
# --- INTENT: AMBILIGHT ---\
if ($cmd_part =~ /ambilight/) {\
system("sshpass -p 'GEHEIMESPASSWORT' ssh -o StrictHostKeyChecking=no root\@192.168.1.46 '/usr/share/hyperhdr/scripts/hyperhdr_toggle.sh'");;\
next;;\
}\
\
# --- INTENT: LICHT ---\
my ($lightRoom) = grep { $cmd_part =~ /\b$_\b/ } keys %lightRooms;;\
if ($lightRoom || $cmd_part =~ /(licht|lampe)/) {\
my $dev = $lightRooms{$lightRoom}{dev} // "LampeSzeneAlle";;\
fhem("set $dev " . ($is_off ? "off" : "on")) if ($is_on || $is_off);;\
next;;\
}\
\
# --- HILFE (DYNAMISCH) ---\
if ($cmd_part =~ /(hilfe|kommandos|übersicht)/) {\
\
my $h = '<div style="text-align:left;;min-width:250px;;font-family:sans-serif;;">';;\
$h .= '<b>Befehlsübersicht:</b><br><br>';;\
\
# Licht\
$h .= '<u>Licht</u><br>';;\
for my $k (sort keys %lightRooms) {\
$h .= "• ".$lightRooms{$k}{label}." an/aus<br>";;\
}\
\
# Staubsauger\
$h .= '<br><u>Staubsauger</u><br>';;\
$h .= "• Sauge [Raum]<br>";;\
$h .= " Räume: ".join(", ", map { ucfirst($_) } sort keys %vacRooms)."<br>";;\
$h .= "• Lade Roberto<br>";;\
\
# Sonstiges\
$h .= '<br><u>Sonstiges</u><br>';;\
$h .= "• Fernseher an/aus<br>";;\
$h .= "• Ambiente [an|aus|1-255]<br>";;\
$h .= "• Ambilight<br>";;\
\
$h .= '</div>';;\
\
$h =~ s/'/\\"/g;;\
\
my $js = "if((document.querySelector('input[name=\"fw_id\"]')||{}).value==='$clientId'){FW_okDialog('$h')}";;\
\
FW_directNotify("#FHEMWEB:$_", $js, "")\
for devspec2array("TYPE=FHEMWEB");;\
\
next;;\
}\
}\
}
Unterschiede der Wege
| Feature | Browser | Hybrid (Atom Echo S3) |
|---|---|---|
| Wakeword | Browser | ESP (Hardware) |
| Push-to-Talk | Ja | Nein |
| Always-On | Ja (bedingt) | Ja |
| Architektur | Software | Software + Hardware |
| Stabilität | Sehr stabil | Sehr stabil (Wakeword extern) |
FireOS mit Fully Browser (Plus)
Damit die Spracherkennung funktioniert:
- Apps installieren:
Google Speech Recognition & Synthesis
- FireOS:
„Tastatur und Sprache“ → „Text-to-Speech“
- Fully Browser:
Enable JavaScriptInterface (PLUS) aktivieren