Eigenen Alexa Skill programieren und auf Raspi-Webserver hosten
In meinen letzten Projekten habe ich ja ein LC-Display mit vier Zeilen zu zwanzig Zeilen an einen Raspberry Pi Zero gelötet, eine Fernbedienungs-IR-LED eingebaut und das Ganze in ein ansprechendes, mit dem 3D-Drucker selbst designtes und gedrucktes Gehäuse gepackt.Das Ganze ist dafür gedacht, auf meinem Schreibtisch zu stehen, so dass ich wichtige und interessante Informationen immer im Blick habe: den Status meines Web-Servers, Wetter, Börse, was immer ich will.
Kurzum: Das Always-On-Display steckt endlich im eigenen Gehäuse, ist per Infrarot-Fernbedienung steuerbar und zeigt auch brav alle Infos an.
Aber wäre es nicht eine feine Sache, wenn man das Display auch mit der Alexa auf dem amazon Echo Dot, den man sowieso schon hat, steuern könnte und wenn einem Alexa die Daten auch vorlesen würde, wenn man gerade nicht am Computer sitzt, sondern auf dem Sofa?
Das kann man schon haben. Doch vor das Genießen der Faulheit hat das Universum den Fleiß gestellt. Heute geht es darum, wie man einen eigenen Skill für Alexa schreibt und diesem auf dem eigenen Server hostet. Die AWS-Services wäre evtl. einfacher zu nutzen, aber das Kostenkonzept ist mir zu kompliziert, intransparent und birgt finanzielle Gefahren, die ich nicht einzugehen gedenke. Bevor ich mich da in die seitenlangen Geschäftsbedingungen und Tarife reinfuchse (und evtl. dann doch am Ende eine böse Überraschung erleben) schreibe und hoste ich mir das lieber selbst.
Als Webserver kann ja dann auch gleich der Raspi im Alway-On-Display herhalten - der hat eh fast nichts zu tun und Speicher ist doch auch noch genügend frei. Wichtig ist bei dem Homeserver-Konzept, so will ich das jetzt mal nennen, dass der Homeserver auch von außen erreichbar ist. Das heißt: er braucht unbedingt eine IPv4-Adresse, am besten eine feste oder man geht den Umweg, neue dynamische IP-Adressen, die dem eigenen Internetzugang zugewiesen werden, umgehend an einen Dynamic DNS-Server zu melden.
Da feste IP bei meinem Provider extra kostet, bin ich den Weg mit dem Melden von neuen dynamischen IP-Adressen gegangen. Und das kann der Raspberry Pi ja auch gleich mit erledigen. Natürlich ist außerdem für die Sicherheit des Raspi zu sorgen, damit es hier kein Einfallstor für Hacker ins eigene Netzwerk gibt. Aber das alles zu beschreiben, würde den Rahmen des Artikels sprengen und würde am eigentlichen Thema vorbei gehen.
Zu allererst gilt es aber, grob die Architektur der Alexa-Modells und der Skills zu verstehen. Ich habe das hier mal kurz skizziert:
Nein, ich erwarte jetzt nicht, dass ihr mein Gekrakel auf Anhieb versteht. Darum habe ich das hier Schritt für Schritt erklärt:
- Alexa wartet auf ihr Schlagwort ("Alexa", "Echo" oder "Computer"
- Ab da nimmt sie alles mit den Mikrofonen auf
- Die Spracheingabedatei wird in die Amazon-Cloud geschickt
- Der Amazon-Server macht die Spracheingabe zu Text und ermittelt den zuständigen Skill
- Der Text wird dem Skill übergeben und der generiert einen Antworttext
- Aus dem Antworttext macht die Sprachsynthese wieder eine Sounddatei, die an unsere Alexa zurückgeschickt wird
- Alexa gibt die Sprachausgabe wider
- einen bei Amazon als Developer definieren
- Amazon sagen, wo unser Skill zu finden ist (AWS oder eigener HTTPS-Server). Wir wählen den eigenen Server.
- den Skill näher definieren mit:
-
- Invocation name = Aufrufname, auf den unser Skill hört
- Intent = Absichtsverb (durch die Utterances können wir hier Alternativ-Verben formulieren)
- optional: Slots = Parameter, die mitgegeben werden. Diese können bestimmte Werte haben, die wir auch definieren (nebst Alternativen)
- Den eigenen Skill mit "sage" oder "frage" in der Alexa-Anweisung benennen
- Die Anweisung als JSON-Objekt mit unserem HTTPS-Server entgegennehmen, verarbeiten und die Antwort als JSON-Objekt zurückgeben.
Wie man einen eigenen Skill unter developer.amazon.com/alexa anlegt und diesen dann mit den entsprechenden Werten füllt, habe ich in diesem Video anhand meine Always-On-Displays beispielhaft erklärt:
- hallo: Hallo sagen
- wetter: Wettervorhersage vorlesen
- zeige [screen]: AOD auf anderen Screen schalten
- schalte [steckdose] [an_aus]: eine 433 MHz-Steckdose an oder ausschalten
Am einfachsten und übersichtlichsten ist, die Intents so zu nennen, wie sie auch gesagt werden müssen und für die Parameter sprechende Namen zu wählen.
Mein Skill kann also z. B. auf folgende Alexa-Anweisungen antworten:
- Alexa, sage Display Hallo
- Alexa, sage Display Wetter
- Alexa, frage Display Wie wird das Wetter
- Alexa, sage Display Zeige eins
- Alexa, sage Display Zeige Screen eins
- Alexa, sage Display Zeige Directory
- Alexa, sage Display Zeige Wetter (zeigt das Wetter auf dem AOD an und liest es nicht vor)
- Alexa, sage Display Schalte Steckdose eins ein
- Alexa, sage Display Schalte Schreibtischlampe aus (Schreibtischlampe ist Alternative für eins)
- Alexa, sage Display Schalte eins aus
Dies tut sie als Raw-Post-Data, die wir in PHP folgendermaßen erfassen können:
[unser-skill.php]
...
$rawdata = file_get_contents('php://input');
$file = fopen ("/home/pi/debug/php-raw-post.txt", "w");
fwrite ($file, $rawdata);
fclose ($file);
Der Inhalt sieht dann so aus:
pi@raspberrypi:~/debug $ cat raw-post.txt
{"version":"1.0","session":{"new":true,"sessionId":"amzn1.echo-api.session.970c186e-4c20-492d-a67b-856903ed0e7e","application":{"applicationId":"amzn1.ask.skill.xxx-xxx-xxx-xxx-xxx"},"user":{"userId":"amzn1.ask.account. yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"}},"context":{"System":{"application":{"applicationId":"amzn1.ask.skill.zzz-zzz-zzz-zzz-zzz"},"user":{"userId":"amzn1.ask.account.aaaaa aaaaa aaaaa aaaaa"},"device":{"deviceId":"amzn1.ask.device.bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb","supportedInterfaces":{}},"apiEndpoint":"https:// api.eu.amazonalexa.com", "apiAccessToken":"ddd.ddddd-eeeeeeeeeeeeeeeeeeeee _ eeeeeeeeeeeeeeeeeeeeeeee-eeeee_eeeee"}},"request":{"type":"IntentRequest","requestId": "amzn1.echo-api.request.ffff-ffff-ffff-ffff-ffffffff", "timestamp":"2019-03-04T11:02:07Z","locale":"de-DE","intent":{"name":"hallo","confirmationStatus":"NONE"}}}
...
amzn1.echo-api.request.xxxx","timestamp":"2019-03-04T10:50:48Z","locale":"de-DE","intent":{"name": "schalten","confirmationStatus": "NONE","slots":{"an_aus":{"name": "an_aus","value":"aus","resolutions": {"resolutionsPerAuthority": [{"authority": "amzn1.er-authority.echo-sdk.amzn1.ask.skill.xxxx.an_aus","status":{"code":"ER_SUCCESS_MATCH"},"values":[{"value":{"name":" aus","id":"xxxx"}}]}]},"confirmationStatus": "NONE","source":"USER"},"steckdose":{"name": "steckdose","value": "testgeraet","resolutions": {"resolutionsPerAuthority": [{"authority": "amzn1.er-authority.echo-sdk.amzn1.ask.skill. xxxx.steckdose","status":{"code":"ER_SUCCESS_MATCH"},"values":[{"value":{"name": "eins","id":" xxxx "}}]}]},"confirmationStatus": "NONE","source":"USER"}}}}}
Nun können wir in $rawdata die Stellen suchen, die wir benötigen - in den beiden oberen Beispielen habe ich diese einmal gelb markiert.Es gibt auch eine nützliche Dokumentationsseite mit den Übergabewerten. Der Link scheint sich allerdings häufiger zu ändern und mit Umleitungsseiten scheint es amazon auch nicht so genau zu nehmen. Wenn ihr also hier einen 404er bekommt, sucht nach dem Titel "Request and Response JSON Reference".
Die ganzen mitgegebenen IDs können wir gut dafür gebrauchen, zu schauen, ob der Aufruf berechtigt ist. Ich habe einfach alles, was nicht von meinem Alexa-Endgerät oder von meinem Amazon-Account kommt, geblockt. Aber da ich den Skill nicht gepublisht habe, sollte sowieso niemand den Skill verwenden können außer mir - aber sicher ist sicher.
Die oben gelb markierten Werte sind die eigentlich wichtigen. Hiermit entscheiden wir, was wie zu tun ist und basteln und damit einen Antworttext zusammen, den wir als Resonse auf den HTTPS-Request von amazon-Server zurückgeben. Er sieht im einfachsten Fall so aus:
$out='{"version":"1.0","sessionAttributes":{"key":"value"},"response":{"outputSpeech":{"type":"PlainText","text":"'.$text.'",
"playBehavior": "REPLACE_ENQUEUED"}, "shouldEndSession":true}}';
exit ($out);
Wobei die Variable $text unser Anwort-Text ist, auch hier sollte man Umlaute als ae, oe, ue und ss schreiben, sonst kommt Alexa beim vorlesen ins Stolpern.Wie das fertige System reagiert, habe ich mit ein paar zusätzlichen Erklärungen hier noch mal beispielhaft gezeigt: