Eigenen Alexa Skill programieren und auf Raspi-Webserver hosten

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, die 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, verschleiert und gefahrvoll. Bevor ich mich da 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.

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:



Der Ablauf ist also der Folgende: Wollen wir einen eigenen Skill, dann müssen wir: Der eigene Skill wird dann mit "Alexa, sage [Invocation name] [intent] [slots]" aufgerufen. Bzw. auf deutsch "Alexa, sage [Skillname] [Verb] [Parameter]", zum Beispiel: "Alexa sage Display Schalte Steckdose eins an". Hier ist "Display" der Skill, "Schalte Steckdose" der Intent und "eins" sowie "an" die Parameter.

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:



Mein Display-Skill hat also bisher die Intents: Hallo und Wetter haben keine Parameter, Zeigen muss ein Parameter mitgegeben werden, nämlich die Screen-Nr., die gezeigt werden soll. Und Schalte hat zwei Parameter, die Steckdosennr. und ob an oder aus.

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: Ist der Skill auf der amazon Developer Seite fertig gebuidelt, dann wird uns die Amazon Cloud jede Alexa-Anweisung für unseren Skill als JSON-Objekt an unserem Endpoint, also unserem angegeben PHP-Script abliefern.

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.aaaaaaaaaaaaaaaaaaaa"},"device":{"deviceId":"amzn1.ask.device.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","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:



Jetzt wo der Rahmen steht, wird es in Zukunft nicht schwierig sein, meinen Skill um weitere Intents und damit Funktionen zu erweitern. Der Anfang war zwar ein bisschen holprig, aber jetzt weiß man ja, wie der Hase läuft.