Alexa-Skill für den Raspberry Pi mit Python

Vorgeschichte

Als Erweiterung für meine DIY-Hausautomatisierung* hatte ich schon längere Zeit den Wunsch, verschiedene Geräte auch per Sprachsteuerung ansprechen und steuern zu können. Da bei uns die Amazon Echo-Dots mittlerweile auch in fast jedem Raum Einzug gehalten haben, war die meiner Meinung nach perfekte Hardware eigentlich schon vorhanden. In meiner Vorstellung musste ich es daher „nur“ hinbekommen, dass die von Amazon übersetzten Sprachbefehle an meine Hausautomatisierung weitergeleitet und anschließend von mir per Script ausgewertet werden. Leider ist dies nicht ganz so einfach umzusetzen wie ich es mir gedacht habe.

 

zwingend nötige Vorbereitungen

Die Vorbereitungen um das gesetzte Ziel zu erreichen waren im Nachhinein betrachtet doch recht umfangreich. Ich musste mehrere, bereits online vorgestellte Lösungsansätze ausprobieren, um überhaupt zu verstehen, wie die ganze Sache funktioniert. Hierbei hatte ich das Problem, dass natürlich keine von diesen Anleitungen genau für meine DIY-Hausautomatisierung* passte. Ich wollte aber gern die Auswertung der Sprachbefehle selbst übernehmen, um direkt auf meine bereits vorhandenen Steuerdateien der DIY-Hausautomatisierung* zugreifen zu können. So könnte ich die Schaltbefehle für Einzel- und Gruppengeräte und die Logik hinter den Befehlen einfach weiterverwenden und müsste diese nicht aufwändig neu erstellen. Bevor es aber an das eigentliche Erstellen gehen konnte, musste die Raspberry Pi* und deren Netzwerkeinstellungen an die neue Aufgabe angepasst werden. Hierfür waren folgende Schritte, für welche ich externe Anleitungen verlinke und deshalb nicht weiter beschreibe, nötig:

Außerdem nutzte ich von der eben verlinkten Seite* das Python-Script und das Grundgerüst des Alexa-Skills, welches ich für die Verwendung mit meiner Hausautomatisierung natürlich noch anpassen musste. Nach dem Abarbeiten der Anleitung, erhielt ich aber zumindest schon einmal die gewünschten Sprachbefehle als Key-Words. Da diese vom Python-Script erfasst werden, meine DIY-Hausautomatisierung* aber größtenteils serverseitig auf PHP aufgebaut ist, musste ich mir eine kleine Schnittstelle basteln.

 

Von Python zu PHP

Die Schnittstelle für die Übergabe von Python an PHP ist dabei nicht mehr als eine PHP-Datei, welche die empfangenen Schlagwörter des Python-Scriptes entgegennehmen und weiterverarbeiten kann. Um dies realisieren zu können, importierte ich die requests-Bibliothek in das vorhandene Python-Script. Mit Hilfe des get-Befehls dieser Bibliothek ist es möglich das PHP-Script auszuführen und dabei die Schlagwörter per http-get-Parameter zu übergeben.

 

import logging
import os
import requests #<--- Einbinden der Requests Bibliothek


from flask import Flask
from flask_ask import Ask, request, session, question, statement
import RPi.GPIO as GPIO

app = Flask(__name__)
ask = Ask(app, "/")
logging.getLogger('flask_ask').setLevel(logging.DEBUG)

STATUSON = ["anzuschalten","anzumachen","anmachen","anschalten","an","einschalten","ein"] # alle Werte, die als Synonyme im Slot Type definiert wurden
STATUSOFF = ["auszuschalten","auszumachen","ausmachen","ausschalten","aus"]
STATUSTEMP = ["8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28"]

...
...
        
	#beim einschalten der Geräte
	if status in STATUSON:

		#Weitergabe an Webscript für Verarbeitung mit SHAS Homeautomation
		payload = {'geraet': geraet, 'zimmer': zimmer, 'status': status} #<--- bündeln der Parameter als Dictionary
		r = requests.get('http://127.0.0.1/PFAD_ZUM_ORDNER_DER_PHP_SKILLDATEI/SkillCommand.php', params=payload) #<--- Aufruf des PHP-Scriptes und Übergabe der Parameter
		#print (r.text)
		
		#Nach 'done' von der Rückgabe der Homepage (SkillCommand.php) suchen #<--- ab hier auswerten der Rückgabe des PHP Scriptes
		if (r.text.find('done') != -1):
			return statement(geraet + ' in ' + zimmer + ' wurde eingeschaltet')
		#Nach 'nodevice' von der Rückgabe der Homepage (SkillCommand.php) suchen
		elif (r.text.find('nodevice') != -1):
			return statement('Es wurde kein Gerät angegeben!')
		else:
			return statement('Befehl konnte nicht ausgeführt werden')		
		
	#beim ausschalten der Geräte
	elif status in STATUSOFF:

...
...

 

 

Das PHP-Script übernimmt die Schlüsselwörter und durchsucht die Ordnerstruktur meiner DIY-Hausautomatisierung* nach Übereinstimmungen. Hierbei wird in mehreren Ebenen vorgegangen. Zuerst wird geprüft, ob das angegebene Zimmer im Funktionsordner vorhanden ist. Da die Benennung der Ordner nicht nur den Namen der Räume enthält, sondern für das Sortieren der HTML-Anzeige Sortiermarker vor den eigentlichen Bezeichner gestellt wurden, wird hierbei jedoch nicht auf 100%ige Übereinstimmung, sondern auf das Vorhandensein innerhalb der Ordnerbezeichnung geprüft. Hat also der Ordner die Bezeichnung „B_Badezimmer“ und als Schlagwort für den Raum wurde „Bad“ übergeben, so wird dies als Übereinstimmung gewertet und der Ordner wird zur Suche nach einem passenden Gerät betreten. Um kein Fehlverhalten bei Groß- oder Kleinschreibung von Schlagwörtern und Ordnernamen zu provozieren, konvertierte ich beide Werte mittels strtolower()  in kleingeschriebene Zeichenketten.

 

<?php
$device			=   $_GET["geraet"]; 
$room			=   $_GET["zimmer"]; 
$command		=   $_GET["status"]; 

//Erkennung für Badezimmer verbessern
if($room == "badezimmer")
    $room = "bad";

//DEBUG: Ausgabe der empfangenen Variablen
//echo "Eingang: ".$device." in ".$room." ".$command;

//Versuche Raum zu ermitteln
$funktionDir = "../../PFAD_ZUM_FUNKTIONSORDNER/";

//Nur wenn ein Gerät angegeben wurde
if(strtolower($device) != "none" and $device != "")
{
    //Nur wenn eein Raum angegeben wurde
    if(strtolower($room) != "none" and $device != "")
    {
        //Funktionsordner Öffnen und alle darin befindlichen Ordner auslesen
        $action=opendir($funktionDir);									
        while($myRooms=readdir($action))
        {						
            //Schritt 1 nach entsprechenden Räumen durchsuchen
            if(preg_match("/".strtolower($room)."/", strtolower($myRooms)))
            {
                //DEBUG: gefundenen Raum anzeigen
                //echo "Raum: ".$room." als ".$myRooms." gefunden!";

...
...

 

Wurde der angegebene Raum gefunden, so wird dessen Ordner nach Übereinstimmungen mit dem angegebenen Gerät überprüft. Da ich in meiner DIY-Hausautomatisierung* verschiedene Gerätegruppen mit unterschiedlichen Funktionsumfängen definiert habe, musste ich an dieser Stelle darauf achten, dass ich für die jeweilige Geräteart auch das richtige Kommando ausführe. Der Vorteil: Da die aufgerufene Datei „Schaltung.php“ beim Schalten auch den Status der einzelnen Geräte aktualisiert, brauche ich mich in diesem PHP-Script nicht extra darum kümmern. Wird zum Beispiel eine Gruppenfunktion angesprochen, so werden nach dem Schalten auch alle Geräte als angeschaltet, respektive ausgeschaltet angezeigt.   

 

                //Zimmerordner Öffnen und alle darin befindlichen Ordner auslesen
                $action=opendir($funktionDir."/".$myRooms);									
                while($myDevices=readdir($action))
                {						
                    //Schritt 1 nach entsprechenden Geräten durchsuchen
                    //Prüfen ob alles anggeben wurde
                    if(strtolower($device) == "raum")
                    {
                        //nach groff für gruppen aus suchen
                        if(preg_match("/--GROFF_/", $myDevices) and !preg_match("/.png/", $myDevices))
                        {
                            //gefundenes Gerät anzeigen
                            echo "Gerät: ".$device." als ".$myRooms."/".$myDevices." gefunden!<br>";	
                                            

                            if($command == strtolower("ausschalten") or 
                            $command == strtolower("aus") or $command == strtolower("ausmachen"))
                            {
                                exec("wget --spider '127.0.0.1/.../Schaltung.php".
                                "?POS=GROFF&path=Funktion/".$myRooms."/".$myDevices."&dev=G'"); //" <-- for Syntax highlighting only 
                            }
                            
                            //Rückmelduung des Erfolges an das Python-Script
                            echo"done";
                        }//preg_match alles
                    }
                    //wenn $device != alles
                    else
                    {						
                        if(preg_match("/".strtolower($device)."/", strtolower($myDevices)) and (preg_match("/--G_/", $myDevices) or
                        preg_match("/--GI_/", $myDevices) or preg_match("/--GR_/", $myDevices)) and !preg_match("/.png/", $myDevices)) 
                        {
                            //DEBUG: gefundenes Gerät anzeigen
                            //echo "Gerät: ".$device." als ".$myRooms."/".$myDevices." gefunden!<br>";	
                        
                            //Gerätegruppe ermitteln
                            if (preg_match("/--G_/", $myDevices))
                                $devicegroup = "G";
                            
                            if (preg_match("/--GI_/", $myDevices))
                                $devicegroup = "GI";
                            
                            if (preg_match("/--GR_/", $myDevices))
                                $devicegroup = "GR";
                        
                            //Unterscheiden ob an, oder ausgeschalten werden soll
                            if($command == strtolower("einschalten") or $command == strtolower("ein") or 
                            $command == strtolower("anschalten") or $command == strtolower("an"))
                            {
                                exec("wget --spider '127.0.0.1/.../Schaltung.php".
                                "?POS=0&path=Funktion/".$myRooms."/".$myDevices."&dev=".$devicegroup."'"); //' <-- for Syntax highlighting only
                            }

                            if($command == strtolower("ausschalten") or $command == strtolower("aus") or 
                            $command == strtolower("ausmachen"))
                            {
                                exec("wget --spider '127.0.0.1/.../Schaltung.php".
                                "?POS=1&path=Funktion/".$myRooms."/".$myDevices."&dev=".$devicegroup."'"); //' <-- for Syntax highlighting only
                            }
                            
                            //Rückmelduung des Erfolges an das Python-Script
                            echo"done";
                        
                        }//preg_match devices 
                    }//else von alles
                }	
            }
        }

    }//room != none
    else
    {
        echo "noroom";
    }
}//device != none
else
{
    echo "nodevice";
}

?> 

 

Zu beachtende Besonderheiten / Nachteile

Diese Vorgehensweise hat zwar viele Vorteile, jedoch gibt es auch einige Besonderheiten und evtl. Nachteile welche es zu beachten gilt. So kann es zum Beispiel bei teilweise gleichen Gerätebezeichnungen zum versehentlichen Schalten von Geräten kommen. Dies resultiert aus dem Umstand, dass das PHP-Script auf übereinstimmende Teil-Strings testet. Sind zum Beispiel in einem Raum 2 Geräte mit identischen Teilbeschreibungen („Testleuchte“ und „Leseleuchte„) vorhanden, so wird  dies zum Schalten beider Geräte führen. Dieses Problem kann jedoch recht zügig durch das Umbenennen eines der Geräte behoben werden. Ein weiterer Nachteil ist, dass dieses zwischengeschobene Script die Ausführzeit der gewünschten Aktion verzögert. Der Grund hierfür ist, dass das Pyton-Script das PHP-Script ausführt, welches wiederum das PHP-Script der DIY-Hausautomatisierung* zum Aktualisieren und Schalten der Geräte aufruft. Um den Vorgang zu beschleunigen werde ich deshalb demnächst die Logik des PHP-Scriptes direkt in das Python-Script einbauen.

 

Integration der SHAS Heizungssteuerung   

Nachdem die Integration für die Schaltfunktionen innerhalb der DIY-Hausautomatisierung* abgeschlossen war, erweiterte ich das Python-Script nachträglich um die Bedienung meiner DIY-Heizungssteuerung. Hierzu fügte ich dem Python-Script ein weiteres Listenelement hinzu, welches alle einstellbaren Thermostat-Temperaturen beinhaltet.

STATUSTEMP = ["8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28"]

Außerdem erweiterte ich das Script um eine weitere Abfragebedingung, welche beim Vorhandensein einer durch den Alexa-Skill übergebenen Temperaturwertes betreten wird. Innerhalb dieser Bedingung wird dann direkt das bereits vorhandene PHP-Script für die Steuerung der Heizungsregelthermostate angesprochen. Dies hat auch hier den Vorteil, dass für die Aktualisierung der Anzeigefunktion innerhalb der DIY-Hausautomatisierung* kein weiterer Programmieraufwand nötig ist.

 

#beim Steuern der Heizung
elif temperatur in STATUSTEMP:

    #Weitergabe an Webscript für Verarbeitung mit SHAS Homeautomation
    payload = {'Temp': temperatur, 'Room': zimmer, 'Pairlock': 1}
    r = requests.get('http://127.0.0.1/.../Heizung_set.php', params=payload)
    #print ('#' + r.text + '#')	
    
    #Nach 'done' von der Rückgabe der Homepage (Heizung_set.php) suchen
    if (r.text.find('done') != -1):
        return statement(geraet + ' in ' + zimmer + ' wurde auf ' + temperatur + ' Grad eingestellt!')
    else:
        return statement('Der gewünschte Befehl konnte nicht ausgeführt werden')

 

Fazit

Auch wenn mich die Umsetzung einer Versionen mit AWS Lambda Integration sehr interessiert, so bin ich mit der aktuellen Version der Sprachsteuerung doch sehr zufrieden. Die Erkennung der Kommandos funktioniert problemlos und auch an die kurze Verzögerung bis zum Ausführen des Schaltvorgangs hat man sich mittlerweile gewöhnt. Wer also eine schnelle, schlanke und überschaubare Alexa Integration in aktuelle Projekte umsetzen möchte, ist mit dieser Variante sicherlich gut bedient.  

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert