Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Offline-Sprachsteuerung für VDR und Kodi mit Snips und Fhem
#1
Hallo zusammen,

ich wollte euch mal zeigen wie ich mit snips und fhem eine offline-Sprachsteuerung für VDR und Kodi gebastelt habe.

Erstmal ein paar allgemeine Worte zu Snips.
Komplett offline ist es nicht. Alles was Snips an Sprache erkennen soll, muss vorher trainiert werden. Das passiert in der Snips-Console / auf dem Snips-Server. Der trainierte Assistent wird dann auf dem Snips-Raspi installiert, ab dann geht es offline. Online bei der Konfiguration, Offline zur Laufzeit.
Die Spracherkennung ist gut.
Für das Hotword (Hey Snips) gibt es einen Parameter "sensitivity" fürs fine-tuning.
Im Zusammenspiel mit dem fhem-Modul ist es super um im smart-home Geräte zu steuern.
Es ist aber kein Sprachassistent, der auf alle Fragen eine Antwort hat, so wie es z.B. die Alexa-Werbung immer vorgaukelt. Wobei man sich sowas z.T. zurecht basteln kann, man muss dem Snips nur alle Fragen beibringen und dann die Antworten selbst definieren/programmieren. Ich habe z.B. einfache Abfragen nach Tag und Uhrzeit drin, s.u.
Wer schon immer Lust auf Sprachsteuerung hat, aber keine Lust, dass jeder Sprachfetzen einmal über den großen Teich wandert, der soll sich Snips mal näher anschauen.

Ich mache damit:
- Licht, Heizung und ein paar Funksteckdosen steuern (mehr Geräte habe ich nicht in fhem, aber theoretisch könnte man alles steuern, fhem ist ja sehr flexibel)
- ein eigener Intent um Artikel auf meine Einkaufsliste zu setzen
- VDR + Kodi

Mit folgenden Sprachbefehlen kann ich VDR/Kodi steuern:
- VDR einschalten (WOL Paket senden)
- VDR ausschalten
- Kodi ein/ausschalten (Wechsel zwischen VDR/Kodi)
- lauter,leiser,Lautstärke maximal,Ton an/aus,setze die Lautstärke auf {numerischerWert}
- Sender hoch/runter
- umschalten auf {Sendername}
- letzter Sender
- aufnehmen (aktuelle Sendung aufnehmen)
- in Aufnahmen/Kodi: play,pause,stop,vor/zurück spulen/springen

Die Befehle, die als shortcut angelegt sind, müssen auch exakt so gesprochen werden. Bei den anderen Befehlen gehen auch verschiedene Formulierungen, z.B. umschalten auf Sender, schalte um zu Sender; oder VDR einschalten, schalte den VDR ein
Ich habe es so gebaut, dass bei Erkennung des Hotwords die Lautstärke von VDR/Kodi auf die Hälfte des aktuellen Werts gesetzt wird und nach dem Sprachbefehl wieder zurück (bzw. auf den neuen Lautstärke-Wert) gesetzt wird


benötigte Hardware:
- raspi 3 (ältere raspis sind zu schwach für Spracherkennung)
- SD Karte
- Netzteil
- Mikrofon (ich habe das PS3Eye genommen, ansonsten habe ich öfter was vom reSpeaker gelesen)
- Lautsprecher (ich habe ganz billige USB-powered genommen, die sind zwar kacke, aber das ist egal weil die Audio-Ausgabe von snips eh nicht so toll ist, aber auch das ist egal weil da eh nur kurze Rückmeldungen kommen)


Ich hab das jetzt natürlich für unseren easyVDR gemacht, denke aber es sollte mit jeder VDR-Distri funzen, denn SVDRP sollte doch überall gleich sein.
Auf dem VDR habe ich den PChanger aufgebohrt. Der meldet immer an fhem ob gerade VDR oder Kodi läuft. In der program-changer.sh habe ich ganz am Ende diese Zeilen eingefügt.
/usr/share/easyvdr/program-changer/program-changer.sh:
Code:
##### custom-script starten wenn vorhanden und ausgewähltes Programm als Parameter übergeben
custom_file=/usr/share/easyvdr/program-changer/program-changer-custom.sh
if [ -f $custom_file ]; then
 $custom_file ${name_button[$startindex]}
fi

Hier auch gleich eine Bitte an euch bzw. direkt an den Bleifuss: kann die Änderung dauerhaft in den PChanger übernommen werden?
Dann mus das bei neuen Versionen/Installationen nicht immer nachgearbeitet werden. Und bei den Leuten, die kein Bock auf die Geschichte hier haben, schadet es nicht weil da kein Custom-Script vorhanden ist.

Das Custom-Script sieht so aus. IP-Adresse vom fhem-Rechner einsetzen, hier wird in fhem das currentMediaDevice gesetzt
/usr/share/easyvdr/program-changer/program-changer-custom.sh:
Code:
#!/bin/bash

case $1 in
   none)
       curl http://192.168.x.y:8088/fhem?cmd=set%20currentMediaDevice%20none
       ;;
   KODI)
       curl http://192.168.x.y:8088/fhem?cmd=set%20currentMediaDevice%20Kodi
       ;;
   Vdr-Frontend)
       curl http://192.168.x.y:8088/fhem?cmd=set%20currentMediaDevice%20VDR
       ;;
esac

Startup-Hook einfügen
in /usr/share/vdr/after-vdr-hooks habe ich eine Datei "98_set_fhem" erstellt, Inhalt:
Code:
#! /bin/bash

# das script setzt in fhem den VDR als aktuelles device
/usr/share/easyvdr/program-changer/program-changer-custom.sh Vdr-Frontend

Shutdown-Hook einfügen
in /usr/share/vdr/shutdown-hooks habe ich eine Datei "98_set_fhem" erstellt, Inhalt:
Code:
#! /bin/bash

# das script setzt in fhem kein aktuelles device
/usr/share/easyvdr/program-changer/program-changer-custom.sh none


Snips installieren:
Ich habe es installiert nach dem Artikel in der ct 11/2019, aber das hier ist genau das gleiche:
https://docs.snips.ai/getting-started/qu...spberry-pi
Bei Step 5, den Befehl "sam install assistant" erstmal nicht ausführen. Erstmal die github-Anleitung lesen, die gleich kommt.
Hier muss dann in der Snips-Console die fhem-App hinzugefügt werden, dann auf "Deploy Assistant", da kann dann auch der entsprechende Befehl "sam install assistant ..." kopiert werden.


Fhem-Installation setze ich jetzt einfach mal vorraus, ansonsten eben das hier:
https://wiki.fhem.de/wiki/Raspberry_Pi

Snips-Modul in fhem installieren:
https://forum.fhem.de/index.php?topic=89548.0
https://github.com/Thyraz/Snips-Fhem
Ein Feature, die Shortcuts, hat es leider nicht in die Anleitung geschafft, ist aber hier im Forum dokumentiert:
https://forum.fhem.de/index.php/topic,89...#msg835728


In fhem wird dann folgendes konfiguriert (formatiert für die fhem.cfg, aber der erfahrene fhem-Nutzer weiß natürlich, dass man nicht direkt in der fhem.cfg editieren soll).

Code:
# currentMediaDevice freigeben, damit es vom VDR aus gesetzt werden kann
# da gibt es auch andere Wege, siehe fhem-wiki zu csrfToken
define WEBapi FHEMWEB 8088 global
attr WEBapi allowfrom 192.168.x.y|127.0.0.1        # ip vom vdr
attr WEBapi csrfToken none

define allowedWEB allowed
attr allowedWEB allowedCommands set
attr allowedWEB allowedDevices currentMediaDevice
attr allowedWEB validFor WEBapi

# MQTT device
define SnipsMQTT MQTT 192.168.x.y:1883            #ip und Port vom snips-raspi

# Snips device
define Snips SNIPS SnipsMQTT Wohnküche
attr Snips IODev SnipsMQTT
attr Snips room Wohnküche
attr Snips shortcuts Welcher Tag ist heute={return "es ist " . qx(date +%A) . " der " . qx(date +%D);;}\
Wieviel Uhr ist es={return "es ist " . qx(date +%R);;}\
Wieviel Uhr haben wir={return "es ist " . qx(date +%R);;}\
Ton aus={MediaAction("mute")}\
Ton an={MediaAction("unmute")}\
lauter={MediaAction("vol+")}\
leiser={MediaAction("vol-")}\
Lautstärke maximum={MediaAction("volmax")}\
Lautstärke maximal={MediaAction("volmax")}\
play={MediaAction("play")}\
pause={MediaAction("pause")}\
stop={MediaAction("stop")}\
vor spulen={MediaAction("FastFwd")}\
zurück spulen={MediaAction("FastRew")}\
vor springen={MediaAction("skip+")}\
zurück springen={MediaAction("skip-")}\
Kanal hoch={MediaAction("Channel+")}\
Kanal runter={MediaAction("Channel-")}\
Letzter Sender={MediaAction("PrevChannel")}\
aufnehmen={MediaAction("record")}\
weiter={MediaAction("next")}\
zurück={MediaAction("prev")}\

attr Snips verbose 5

define ntfy_snips_listening notify Snips:listening_wohnkueche.* {snipsListening($EVTPART1)}

# dummy devices, werden als variablen genutzt
define VDR.Volume dummy
define Kodi.Volume dummy
define currentMediaDevice dummy

# VDR device, ich habe es als WOL-Device definiert, damit kann den VDR auch per Sprache einschalten. Man könnte auch ein dummy-device nehmen.
define VDR WOL 00:11:22:AA:BB:CC 192.168.x.y        # mac und ip vom vdr
attr VDR userattr snipsChannels:textField-long
attr VDR interval 60
attr VDR room Snips,Wohnküche
attr VDR snipsChannels a er de={sendSVDRP("CHAN Das Erste HD")}\
zdf={sendSVDRP("CHAN ZDF HD")}\
vox={sendSVDRP("CHAN VOX")}\
rtl={sendSVDRP("CHAN RTL Television")}\
rtl2={sendSVDRP("CHAN RTL2")}\
sat 1={sendSVDRP("CHAN SAT.1")}\
pro 7={sendSVDRP("CHAN ProSieben")}\
kabel 1={sendSVDRP("CHAN kabel eins")}\
sport 1={sendSVDRP("CHAN SPORT1")}\
eurosport={sendSVDRP("CHAN EUROSPORT 1")}\
Nickelodeon={sendSVDRP("CHAN Nickelodeon/Nicknight")}\
comedy central={sendSVDRP("CHAN VIVA/COMEDY CENTRAL")}\
ntv={sendSVDRP("CHAN ntv")}\
en vier und zwanzig={sendSVDRP("CHAN N24")}\
euronews={sendSVDRP("CHAN euronews (D)")}\
arte={sendSVDRP("CHAN arte HD")}\
Phoenix={sendSVDRP("CHAN Phoenix HD")}\
3Sat={sendSVDRP("CHAN 3Sat HD")}\
ServusTV={sendSVDRP("CHAN ServusTV HD")}\
WDR={sendSVDRP("CHAN WDR Köln HD")}\
SWR={sendSVDRP("CHAN SWR BW HD")}\
ha er={sendSVDRP("CHAN hr-fernsehen HD")}\
beh er={sendSVDRP("CHAN BR Fernsehen Süd HD")}\
en de er={sendSVDRP("CHAN NDR Niedersachsen HD")}\
er beh beh={sendSVDRP("CHAN rbb Berlin")}\
em de er={sendSVDRP("CHAN MDR Sachsen")}\
a er de alpha={sendSVDRP("CHAN ARD-alpha")}\
tagesschau vier und zwanzig={sendSVDRP("CHAN tagesschau24")}\
wann={sendSVDRP("CHAN ONE HD")}\
ZDF_neo={sendSVDRP("CHAN ZDF_neo HD")}\
ZDFinfo={sendSVDRP("CHAN ZDFinfo")}\
KIKA={sendSVDRP("CHAN KIKA HD")}\
er en ef={sendSVDRP("CHAN RNF HD")}\
en vier und zwanzig Doku={sendSVDRP("CHAN N24 Doku")}\
kabel eins Doku={sendSVDRP("CHAN kabel eins Doku")}\
de max={sendSVDRP("CHAN DMAX")}\
Sat.1 Gold={sendSVDRP("CHAN Sat.1 Gold")}\
sechs={sendSVDRP("CHAN SIXX")}\
Pro 7 max={sendSVDRP("CHAN ProSieben MAXX")}\
Nitro={sendSVDRP("CHAN Nitro")}\
RTLplus={sendSVDRP("CHAN RTLplus")}\
Welt der Wunder={sendSVDRP("CHAN Welt der Wunder")}\
Super RTL={sendSVDRP("CHAN Super RTL")}\
TOGGO={sendSVDRP("CHAN TOGGO plus")}\
Disney={sendSVDRP("CHAN Disney SD")}\
Tele 5={sendSVDRP("CHAN Tele 5")}\

attr VDR snipsMapping GetOnOff:currentVal=currentMediaDevice:state,valueOn=VDR\
SetOnOff:cmdOn=on,cmdOff={sendSVDRP("hitk power")}\
SetNumeric:currentVal={getVdrVolume()},cmd={setVdrVolume($VALUE)},minVal=0,maxVal=255,step=50,type=Lautstärke\
MediaControls:cmdPlay={sendSVDRP("hitk play")},cmdPause={sendSVDRP("hitk pause")},cmdStop={sendSVDRP("hitk stop")},cmdFwd={sendSVDRP("hitk FastFwd")},cmdBack={sendSVDRP("hitk FastRew")}
attr VDR snipsName Fernseher,vau de er
attr VDR snipsRoom Wohnküche
attr VDR useUdpBroadcast 192.168.x.255            # broadcast-ip
attr VDR webCmd :


# Kodi device
define Kodi KODI 192.168.x.y tcp            # ip von Kodi/vdr
attr Kodi room Snips,Wohnküche
attr Kodi snipsMapping GetOnOff:currentVal=currentMediaDevice:state,valueOn=Kodi\
SetOnOff:cmdOn={fhem("trigger VDR_Kodi Kodi");;return "Ok.";;},cmdOff={fhem("trigger VDR_Kodi VDR");;return "Ok.";;}\
SetNumeric:currentVal=volume,cmd={fhem("set Kodi volume " . $VALUE);fhem('set Kodi.Volume -1');},minVal=0,maxVal=100,step=20,type=Lautstärke\
MediaControls:cmdPlay=play,cmdPause=pause,cmdStop=stop,cmdFwd=next,cmdBack=prev
attr Kodi snipsName Kodi
attr Kodi snipsRoom Wohnküche
attr Kodi updateInterval 60

# device um zwischen VDR und Kodi umzuschalten, um es in der fhem-GUI anzuzeigen und um es programmatisch zu triggern
define VDR_Kodi dummy
attr VDR_Kodi alias Kodi / VDR
attr VDR_Kodi devStateIcon {'<div></div>'}
attr VDR_Kodi room Wohnküche
attr VDR_Kodi webCmd Kodi:VDR

# ip vom vdr
define Kodi_An notify VDR_Kodi.Kodi { GetFileFromURL("http://192.168.x.y/index.php?xbmc=start");; fhem("set currentMediaDevice Kodi");; }
define VDR_An notify VDR_Kodi.VDR  { GetFileFromURL("http://192.168.x.y/index.php?xbmc=stop");; fhem("set currentMediaDevice VDR");; }

Ihr müsst die IP-Adressen anpassen, einfach nach "192.168" suchen, habe alle Stellen entsprechend kommentiert.
Evtl. müsst ihr den Raumname anpassen, die Attribute room und snipsRoom, bei mir findet alles in der "Wohnküche" statt (auch das notify "listening_wohnkueche" nicht vergessen).

Beim VDR-device müsst ihr das Attribut snipsChannels anpassen. Damit man das nicht alles schreiben muss, habe ich ein Script geschrieben, das die channels.conf ausliest, den String zusammenbaut und in einer Textdatei (snipsChannels.txt) ablegt. Aus der Textdatei kann man dann die benötigten Zeilen kopieren und in fhem in der device-Ansicht im Attribut-Editor einfügen.
Für jeden Sender wird eine Zeile angelegt in der Form
<SENDERNAME>={sendSVDRP("CHAN <SENDERNAME>")}
oder
<SENDERNAME>={sendSVDRP("CHAN <SENDERNUMMER>")}

Der Teil vor dem = ist der gesprochene Sendername, das müsst ihr anpassen.
Da muss man z.T. auch kreativ sein, z.B. wird aus ARD "a er de" oder aus RBB wird "er beh beh". Beim Sender ONE ging nur "wann", bei SIXX ging nur "sechs".
Bei Zahlen im Sendername muss manchmal ein Leerzeichen dazwischen (sat 1), manchmal nicht (rtl2).
Orientiert euch an meinen Beispielen, ansonsten hilft hier nur try and error.

Aufruf:
Parameter1: Pfad zur channels.conf (erforderlich)
Parameter2: num (optional, um in der Ausgabe die Sendernummer zu erhalten)
Bsp.:
snipsChannels.sh /etc/vdr/channels.conf [num]

Script ist im Anhang, Endung txt entfernen.


Dann muss eine 99_myUtils.pm angelegt werden.
https://wiki.fhem.de/wiki/99_myUtils_anlegen
Hier werden folgende Funktionen abgelegt:

Code:
sub sendSVDRP($;$) {
    my ($data, $closeSocket) = @_;
    $closeSocket //= 1;
    
    use IO::Socket::INET;
    
    my $ip = InternalVal("VDR","IP","");
    my $port = 6419;
    
    if ($ip) {
        my $socket = new IO::Socket::INET (
            PeerHost => $ip,
            PeerPort => $port,
            Proto => 'tcp',
        ) or die "ERROR in Socket Creation : $!\n";

        # send command
        Log 1,("sending SVDRP: " . $data);
        $socket->send($data . "\r\n");
        $socket->send("quit\r\n");

        if ($closeSocket)
        {
            Log 1,("closing socket");
            $socket->close();
        }
        else
        {
            Log 1,("returning socket");
            return $socket;
        }
    }
    else {
        Log 1,("no vdr-ip found");
    }
}

sub setVdrVolume($) {
    my ($vol) = @_;
    
    my $new = $vol - 5;
    if ($new < 0) { $new = 0; }
    
    my $cmd = "volu " . $new . "\r\n hitk volume" . ($vol == 0 ? "-" : "+");
    sendSVDRP($cmd);
    
    fhem('set VDR.Volume -1');
}

sub getVdrVolume() {
    my ($line, $search, $pos);
    my $doLoop = 1;
    my $vol = ReadingsVal("VDR.Volume", "state", -1);

    if ($vol == -1)
    {
        my $socket = sendSVDRP("volu", 0);

        while ($doLoop)
        {
            $line = $socket->getline();
            if (defined $line && length($line) > 0)
            {
                Log 1,(trim($line));

                if (substr($line, 0, 3) eq "221") { $doLoop = 0; }

                if (substr($line, 0, 3) eq "250")
                {
                    $search = "Audio volume is";
                    $pos = index($line, $search);
                    if ($pos != -1)
                    {
                        $vol = int(trim(substr($line, $pos + length($search))));
                        $doLoop = 0;
                    }
                }
            }
        }

        Log 1,("closing socket");
        $socket->close();
    }
    
    return $vol;
}


sub snipsListening($) {
    my ($event) = @_;

    my $currentDev = ReadingsVal("currentMediaDevice", "state", "");
    Log 1,("snipsListening: event: " . $event . ", currentDev: " . $currentDev);

    my $vol = -1;

    if ($currentDev)
    {
        if ($event eq "1")
        {
            # snips hört zu...

            if ($currentDev eq "VDR")
            {
                $vol = getVdrVolume();
                Log 1,("snipsListening, VDR, event=1: " . $vol);
                if ($vol > -1)
                {
                    fhem('set VDR.Volume ' . $vol);
                    sendSVDRP("volu " . int($vol / 2));
                }
            }
            elsif ($currentDev eq "Kodi")
            {
                $vol = ReadingsVal("Kodi", "volume", -1);
                Log 1,("snipsListening, Kodi, event=1: " . $vol);
                if ($vol > -1)
                {
                    fhem('set Kodi.Volume ' . $vol);
                    fhem('set Kodi volume ' . int($vol / 2));
                }
            }
        }
        elsif ($event eq "0")
        {
            # snips hört nicht mehr zu

            $currentDev = ReadingsVal("currentMediaDevice", "state", "");
            $vol = ReadingsVal($currentDev . ".Volume", "state", -1);
            Log 1,("snipsListening: event=0: " . $vol);
            if ($vol > -1)
            {
                if ($currentDev eq "VDR")
                {
                    sendSVDRP("volu " . $vol);
                }
                elsif ($currentDev eq "Kodi")
                {
                    fhem('set Kodi volume ' . $vol);
                }
                fhem('set ' . $currentDev . '.Volume -1');
            }
        }
    }
}


sub MediaAction($) {
    my ($action) = @_;

    use Switch;

    my $currentDev = ReadingsVal("currentMediaDevice", "state", "");

    Log 1,("MediaAction: action=$action, currentDev=$currentDev");

    if ($currentDev)
    {
        if ($currentDev eq "VDR")
        {
            switch ($action)
            {
                case ["play", "pause", "stop", "FastFwd", "FastRew", "Channel+", "Channel-", "PrevChannel", "record", "next", "prev"]
                {
                    my $cmd = "hitk " . $action;
                    sendSVDRP($cmd);
                }
                case ["vol+", "vol-", "volmax"]
                {
                    my $vol = -1;
                    
                    if ($action eq "volmax")
                    {
                        $vol = 255;
                    }
                    else
                    {
                        $vol = getNewVolume($action, $currentDev, 50, 0, 255);
                    }
                    
                    if ($vol > -1)
                    {
                        setVdrVolume($vol);
                    }
                }
                case ["mute"]
                {
                    sendSVDRP("volu " . ReadingsVal("VDR.Volume", "state", -1) . "\r\n hitk mute");
                    fhem('set VDR.Volume -1');
                }
                case ["unmute"]
                {
                    sendSVDRP("hitk mute");
                }
                case ["skip+"]
                {
                    sendSVDRP("hitk yellow");
                }
                case ["skip-"]
                {
                    sendSVDRP("hitk green");
                }
            }
        }
        elsif ($currentDev eq "Kodi")
        {
            switch ($action)
            {
                case ["play", "pause", "stop", "next", "prev"]
                {
                    fhem('set Kodi ' . $action);
                }
                case ["vol+", "vol-", "volmax"]
                {
                    my $vol = -1;
                    
                    if ($action eq "volmax")
                    {
                        $vol = 100;
                    }
                    else
                    {
                        $vol = getNewVolume($action, $currentDev, 20, 0, 100);
                    }
                    
                    if ($vol > -1)
                    {
                        fhem('set Kodi volume ' . $vol);
                        fhem('set Kodi.Volume -1');
                    }
                }
                case ["mute", "unmute"]
                {
                    fhem('set Kodi mute');
                }
                case ["skip+"]
                {
                    fhem("set Kodi exec stepforward");
                    fhem("set Kodi exec stepforward");
                }
                case ["skip-"]
                {
                    fhem("set Kodi exec stepback");
                    fhem("set Kodi exec stepback");
                }
            }
        }
    }
    
    return "Ok.";
}


sub getNewVolume($$$$$) {
    my ($action, $dev, $delta, $min, $max) = @_;

    my $vol = ReadingsVal($dev . ".Volume", "state", -1);
    if ($vol > -1)
    {
        if ($action eq "vol-") { $delta *= -1; }
        $vol += $delta;
        if ($vol < $min) { $vol = $min; }
        if ($vol > $max) { $vol = $max; }
    }
    return $vol;
}


In der Funktion sendSVDRP() muss evtl. der Port angepasst werden.
In der Funktion MediaAction() müsst ihr evtl. Anpassungen machen.
Bei den cases "skip+" und "skip-" (vor/zurück springen) wird für den VDR per SVDRP die gelbe bzw. grüne Taste gedrückt ("hitk yellow" bzw. "hitk green"). wenn das bei euch anders belegt ist, müsst ihr es anpassen.
Bei Kodi wird jeweils 2x "stepforward" bzw. "stepback" ausgeführt, weil mein Kodi so eingestellt ist, dass bei zweimal links/rechts drücken eine Minute gesprungen wird (einmal drücken springt 10 Sekunden).


So, ich hoffe ich habe es halbwegs verständlich rüber gebracht und habe nix vergessen.
Viel Spass beim nachbauen.


Gruß
chicco


Angehängte Dateien
.txt   snipsChannels.sh.txt (Größe: 978 Bytes / Downloads: 0)
Mein VDR:
M3N78-EM, Athlon II X2 240e, 2x1GB RAM G.Skill, Technisat CableStar HD 2 + Mystique CaBiX-C2, Atric-Einschalter
easyVDR 3.5
TV: Samsung RU7099 43"
Zitieren


Nachrichten in diesem Thema
Offline-Sprachsteuerung für VDR und Kodi mit Snips und Fhem - von chicco3 - 07.10.2019, 02:21

Gehe zu:


Benutzer, die gerade dieses Thema anschauen: 1 Gast/Gäste