[Voicecontrol] Button für Fhemweb

Begonnen von schwatter, 14 März 2026, 17:25:25

Vorheriges Thema - Nächstes Thema

schwatter

Zitat von: Prof. Dr. Peter Henning am 08 April 2026, 03:17:00Ohne das jetzt ausprobiert zu haben: Das würde mit ziemlicher Sicherheit in zwei auf den FHEM-Server geöffneten Browserfenstern zweimal die Audiodatei abspielen. Aus dem Grund habe ich einen "Shared Worker" implementiert.

LG

pah

Morgen,

genau das macht es. Das ist der Rundumschlag. Zum differenzieren schicken wir dann
die fw_id mit. Mit der wird nur eine Nachricht in dem Fenster abgespielt, welches
gerade geöffnet ist. Dazu muss es noch etwas abgeändert werden.

Gruß schwatter

Prof. Dr. Peter Henning

Es ging mir weniger ums Differenzieren, als darum, dass nur einmal abgespielt wird. Aber gut, der Weg ist tatsächlich einfacher als mit dem SSE-Server.

Da ich verschiedene FHEM-Server habe, wird mir als Titel der Seite jeweils das letzte Oktett der Server-IP angezeigt. Und bei dem FHEM mit dem SSE-Server kommt noch ein farbiger Punkt dazu, der den Status der SSE-Verbindung anzeigt. Rot=tot, Grün=SSE steht, Orange=wartet.

Wir bräuchten also in fhemweb.js auch noch eine Statusvariable. Dann könnte man z.B. bei blockiertem Audio wenigstens einen roten Punkt o.ä. anzeigen lassen.

LG

pah

//------------------------------------------------------------------------------------------------------
// Dynamisch Titel "FHEM <letztes Oktett der Server-IP>" setzen
//------------------------------------------------------------------------------------------------------

function setFhemHeaderFromServerIP() {
    try {
        const serverIP = new URL(window.location.href).hostname;
        const lastOctet = serverIP.split(".").pop();
        const hdr = document.getElementById("hdr");
       
        if (hdr && ! document.getElementById("fhem-title")) {
            const container = document.createElement("div");
            container.id = "fhem-title-container";
            container.style.cssText = "display: inline-block; margin: 0; padding: 0; vertical-align: middle;";
           
            const title = document.createElement("span");
            title.id = "fhem-title";
            title.textContent = "FHEM " + lastOctet;
            title.style.cssText = "display:inline;color:#6d77e2;font-size:24px;font-weight:bold;" +
            "text-shadow:2px 2px #c5c5c5;margin-right:6px;";
           
            const indicator = document.createElement("span");
            indicator.id = "sse-status";
            indicator.style.cssText = "display:inline-block;width:5px;height:5px;margin-left:4px;" +
            "border-radius:50%;vertical-align:middle;";
           
            container.appendChild(title);
            container.appendChild(indicator);
           
            hdr.insertBefore(container, hdr.firstChild);
        }
    }
    catch (e) {
        console.warn("Fehler beim Ermitteln der Server-IP: ", e);
    }
}

//------------------------------------------------------------------------------------------------------
// setHeaderStatusIcon (Farbwechsel für Statussymbol)
//------------------------------------------------------------------------------------------------------

function setHeaderStatusIcon(status) {
    const indicator = document.querySelector("#sse-status");
    if (! indicator || ! window.sseStatusAllowed) return;
   
    // Alle Statusklassen entfernen
    indicator.classList.remove("ok", "error", "waiting");
   
    // Neue Statusklasse setzen
    if (status === "ok") {
        indicator.classList.add("ok");
        indicator.style.backgroundColor = "#00cc00";
        indicator.title = "SSE-Verbindung aktiv";
    } else if (status === "error") {
        indicator.classList.add("error");
        indicator.style.backgroundColor = "#cc0000";
        indicator.title = "SSE-Verbindung fehlgeschlagen";
    } else if (status === "waiting") {
        indicator.classList.add("waiting");
        indicator.style.backgroundColor = "#ffcc00";
        indicator.title = "Verbindungsaufbau läuft";
    } else {
        indicator.remove();
    }
}

schwatter

#77
Nabend,

1. In Fhemweb.js am Ende einfügen

// --- FHEM Universal Audio & Speech Backend ---
var fhemSelectedVoice = null;

function FW_audioControl(input) {
    if (!input || input.trim() === "") return;

    // --- fw_id aus [id] extrahieren ---
    var match = input.match(/^(.*)\s\[(.*)\]$/);
    var cleanInput = match ? match[1] : input;
    var targetId   = match ? match[2] : null;

    // eigene fw_id holen (body)
    var myId = (typeof $ !== 'undefined') ? $("body").attr("fw_id") : null;

    // nur ausführen wenn passende fw_id oder keine gesetzt
    if (targetId && myId && targetId !== myId) return;

    input = cleanInput.trim();

    // --- actualWindow Marker erkennen ---
    var onlyThisWindow = false;

    if (/\|actualWindow$/i.test(input)) {
        onlyThisWindow = true;
        input = input.replace(/\|actualWindow$/i, "").trim();
    }

    // wenn nur dieses Fenster → prüfen
    if (onlyThisWindow && !myId) return;


    // 1. Stimmen-Management (Innere Logik)
    var loadVoices = function() {
        if (typeof window.speechSynthesis === 'undefined') return;
        var voices = window.speechSynthesis.getVoices();
        if (voices.length > 0) {
            fhemSelectedVoice =
                voices.find(v => v.name.includes("Stefan")) ||
                voices.find(v => v.name.includes("Hans")) ||
                voices.find(v => v.lang.startsWith("de"));
        }
    };

    // Stimmen laden, falls noch nicht geschehen
    if (!fhemSelectedVoice) loadVoices();
    if (window.speechSynthesis && window.speechSynthesis.onvoiceschanged !== undefined && !fhemSelectedVoice) {
        window.speechSynthesis.onvoiceschanged = loadVoices;
    }

    // 2. Entscheidung: AUDIO-DATEI (.mp3, .wav, .ogg)
    if (/\.(mp3|wav|ogg)$/i.test(input)) {
        var url = '/fhem/www/audio/' + input;
        console.log('FHEM_AUDIO PLAY:', url);
        var a = new Audio(url);
        a.play().catch(e => console.log('Audio blocked:', e));
    }
   
    // 3. Entscheidung: SPRACHAUSGABE (TTS)
    else {
        console.log('FHEM_AUDIO SPEAK:', input);
       
        // Textreinigung
        var cleanText = input.replace(/_/g, ' ').trim();
        var utter = new SpeechSynthesisUtterance(cleanText);
        utter.lang = "de-DE";
       
        if (fhemSelectedVoice) {
            utter.voice = fhemSelectedVoice;
        }

        // Sprachausgabe starten
        window.speechSynthesis.speak(utter);
    }
}
   
// Optional: Einmaliges Vorab-Laden beim Start der Seite
if (window.speechSynthesis) {
    if (window.speechSynthesis.onvoiceschanged !== undefined) {
        window.speechSynthesis.onvoiceschanged = function() {
            var v = window.speechSynthesis.getVoices();
            if (v.length > 0 && !fhemSelectedVoice) {
                fhemSelectedVoice = v.find(s => s.name.includes("Stefan")) || v.find(s => s.lang.startsWith("de"));
            }
        };
    }
}

2. notify Beispiele

Aktuelles offenes Browserfenster
defmod n_audio_test notify Lampe01_Ez:on { FW_directNotify("#FHEMWEB:$FW_wname","FW_audioControl('ballhupe.mp3|actualWindow')","");; }
defmod n_audio_test notify Lampe01_Ez:on { FW_directNotify("#FHEMWEB:$FW_wname","FW_audioControl('Das ist ein Test|actualWindow')","");; }
Alle offenen Browser
defmod n_audio_test notify Lampe01_Ez:on { FW_directNotify("#FHEMWEB:$FW_wname","FW_audioControl('ballhupe.mp3')","");; }defmod n_audio_test notify Lampe01_Ez:on { FW_directNotify("#FHEMWEB:$FW_wname","FW_audioControl('Das ist ein Test')","");; }
EIn Statusicon ist noch nicht drin.

Gruß schwatter