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 www/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 www/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. Ein Device wird in der Mappingtabelle eingetragen.
Beispiel:
"esszimmer:licht|lampe|deckenlampe" => { dev => "Lampe01_Ez", label => "Licht Esszimmer", cmdOn => "on", cmdOff => "off" }
Aufschlüsselung:
"hauptkeyword:Filter1|Filter3|Filter3" => { dev => "Devicename", label => "Übersichtname", cmdOn => "on", cmdOff => "off" }
Beispiel: notify
defmod n_VoiceControl notify global:STT:.* {\
# --- VORBEREITUNG ---\
my ($cleanEvent, $clientId) = $EVENT =~ /^(.*)\s\[(.*)\]$/;;\
$cleanEvent //= $EVENT;;\
$clientId //= "unknown";;\
\
# --- KONFIGURATION: Zentrales Mapping ---\
my %smartHomeDevices = (\
"esszimmer:licht|lampe|deckenlampe" => { dev => "Lampe01_Ez", label => "Licht Esszimmer", cmdOn => "on", cmdOff => "off" },\
"esszimmer:aquarium" => { dev => "Aquarium_Aktor", label => "Aquarium", cmdOn => "on", cmdOff => "off" },\
"küche" => { dev => "Deckenlampe_Kue", label => "Licht Küche", cmdOn => "on", cmdOff => "off" },\
"wohnzimmer" => { dev => "Lampe06_Dek", label => "Licht Wohnzimmer", cmdOn => "on", cmdOff => "off" },\
"fernseher|tv" => { dev => "VuPlus", label => "Fernseher", cmdOn => "on", cmdOff => "off" },\
"rechner|pc" => { dev => "PC_Aktor", label => "PC", cmdOn => "on", cmdOff => "off" },\
"garage|tor" => { dev => "Garagentor_Aktor", label => "Garagentor öffnen/schließen", cmdOn => "open", cmdOff => "close" },\
"kaffee" => { dev => "Kaffeemaschine", label => "Kaffeemaschine", cmdOn => "on", cmdOff => "off" },\
"roberto" => { dev => "MQTT2_valetudo_FlusteredUnequaledFish", label => "Lade Roberto", cmdOn => "charge" },\
"ambiente" => { dev => "LampeSzeneAlle", label => "Zentrales Licht", type => "dimmer", cmdOn => "on", cmdOff => "off" },\
"sauge|reinige|putze|staubsauger|roboter" => { dev => "MQTT2_valetudo_FlusteredUnequaledFish", label => "Staubsauger", type => "vacuum" }\
);;\
\
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|öffne|öffnen|auf|hoch|lade)\b';;\
my $offRegEx = '\b(aus|ausschalten|stop|stoppe|beende|deaktivier|deaktiviere|schließe|schließen|zu|runter)\b';;\
\
my @commands = split(/\s*(?:und|dann|,)\s*/, lc($cleanEvent));;\
\
# --- 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;;\
\
# --- AMBILIGHT ---\
if ($cmd_part =~ /ambilight/) {\
system("sshpass -p '1431Fhem1982' ssh -o StrictHostKeyChecking=no root\@192.168.1.46 '/usr/share/hyperhdr/scripts/hyperhdr_toggle.sh'");;\
next;;\
}\
\
# --- GENERISCHE STEUERUNG ---\
my $match = 0;;\
# Sortierung stellt sicher, dass 'dimmer/vacuum' Vorrang haben\
foreach my $key (sort { ($smartHomeDevices{$b}{type}//"") cmp ($smartHomeDevices{$a}{type}//"") } keys %smartHomeDevices) {\
\
my ($main, $must) = split(/:/, $key);;\
\
# Prüfe Hauptwort\
if ($cmd_part =~ /\b($main)\b/i) {\
# Prüfe Pflichtwort falls vorhanden\
next if ($must && $cmd_part !~ /\b($must)\b/i);;\
\
my $d = $smartHomeDevices{$key};;\
\
# A) Staubsauger\
if (($d->{type} // "") eq "vacuum") {\
my @found = grep { $cmd_part =~ /\b$_\b/ } keys %vacRooms;;\
fhem(@found ? "set $d->{dev} clean_segment ".join(",", map { $vacRooms{$_} } @found) : "set $d->{dev} start");;\
}\
# B) Dimmer (Kompakt & korrigiert)\
elsif (($d->{type} // "") eq "dimmer") {\
$cmd_part =~ /(\d+)/ \
? fhem("set $d->{dev} brightness " . int(($1 > 100 ? 100 : $1) * 2.55)) \
: ($is_on || $is_off) && fhem("set $d->{dev} " . ($is_off ? "off" : "on"));;\
}\
# C) Standard An/Aus\
else {\
my $fhem_cmd = $is_off ? ($d->{cmdOff} // "off") : ($d->{cmdOn} // "on");;\
fhem("set $d->{dev} $fhem_cmd") if ($is_on || $is_off);;\
}\
\
$match = 1;; last;;\
}\
}\
next if $match;;\
\
# --- HILFE ---\
if ($cmd_part =~ /(hilfe|kommandos|übersicht)/) {\
my $h = '<div style="text-align:left;;min-width:250px;;font-family:sans-serif;;"><b>Befehlsübersicht:</b><br><br>';;\
my %seen;;\
for my $k (sort keys %smartHomeDevices) {\
my $d = $smartHomeDevices{$k};;\
next if $seen{$d->{label}}++;; \
my $suffix = ($d->{cmdOn} && $d->{cmdOff} && $d->{label} !~ /(an\/aus|öffnen\/schließen)/i) ? " (an/aus)" : "";;\
$suffix = " [0-100%]" if ($d->{type} // "") eq "dimmer";;\
$h .= "• $d->{label}$suffix<br>";;\
}\
$h .= '<br><u>Staubsauger Räume</u><br>• '.join(", ", map { ucfirst($_) } sort keys %vacRooms).'<br></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");;\
}\
}\
}
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