Ziel

Im Folgenden wird beschrieben, wie man einen Skill erstellt, der Alexa ertüchtigt, die gewünschten Kommandos zu verstehen. Alexa soll auf Kommandos wie "Schalte den Fernseher ein" oder "Wähle Programm ARD" erkennen und darauf reagieren können. Die Reaktion soll zunächst eine sprachliche Quittierung der Kommandos sein. Die Verbindung zum ESP32 erfolgt später, wenn die Voraussetzungen für die Verbindungsaufnahme hergestellt wurden.

In­halts­ver­zeich­nis

Alexa-Objekt-Struktur

Anmeldung / Account einrichten

Skill erstellen

Neuen Skill anlegen

Skill ergänzen

Invocation Name (Aufrufname)

Skill Modell

Erster Test

Skill bearbeiten

LaunchRequest

Intent anpassen

Slot Types

HelloWorld-Intent modifizieren

Code zum Intent anpassen

Intent zur Kanalauswahl anlegen

Download

Hinweis: Amazon hat sein Programmiermodell mehrfach geändert. Die folgende Beschreibung basiert auf dem Stand vom Januar 2024.

zurück


Alexa-Objekt-Struktur

Um einen Skill zu entwickeln, muss man wissen, wie er aufgebaut ist.

Objekt-Struktur

Ein Skill kann mehre Funktionalitäten (Intent) besitzen. In dem obigen Beispiel wäre die eine den Fernseher ein- und ausschalten und eine andere die Programmwahl. Es können auch total andere Funktionalitäten eingebunden werden, z.B. eine Abfrage nach der aktuellen Außentemperatur. Sinn macht auch die Installation eines Intents, der erklärt, wie der Skill zu bedienen ist.

Jedem Intent sind eine oder mehrere Phrasen (Utterance) zugeordnet, die die Funktionalität auslösen. Die Phrasen Utterances sollten innerhalb eines Skills eindeutig sein. Bei deren Definition macht es Sinn, viele Varianten zu hinterlegen. Andere Leute werden andere Sprachgewohnheiten haben.

Utterances können Parameter enthalten (Slot). Dies erlaubt es, eine Phrase zu modifizieren. Es wäre sehr umständlich, für die Programmwahl für jeden Fernsehkanal einen eigenen Intent anzulegen. Stattdessen kann man die Kanalangabe als Argument in den Sprachbefehl einfügen. Slots sind einem Slot Type zugeordnet. Ein Slot Type ist eine Enumeration der möglichen Worte, die als Argument benutzt werden können. Es gibt auch Slot Types, die eine freie Textangabe ermöglichen.

Im obigen Beispiel könnte man sich also überlegen, ob man mit den Skill weitere Geräte ein- oder ausschalten möchte. In diesem Fall müsste auch das Gerät als Slot eingefügt werden. Der Slot Type enthielte z.B. dann die Elemente Fernseher, TV, Hintergrundbeleuchtung, Waschmaschine, ... Bevor man also anfängt, einen Skill zu entwickeln, sollte man sich Gedanken über eine sinnvolle Strukturierung machen.

Leider ist es nicht einfach möglich einen Intent auszulösen, indem man die zugehörige Utterance ausspricht. Benutzerdefinierte Skills (Costum Skill) sind im Alexa-Grundzustand nicht aktiv. Man muss sie erst aktivieren. Dazu dient der Skill Invocation Name z.B. ullis service. Man aktiviert den Skill, indem man Alexa dazu auffordert, z.B. "alexa starte ullis service".

Anmeldung / Account einrichten

Skills werden online im Browser über die Amazon Developer Console entwickelt. Dazu muss man einen entsprechenden Account anlegen. Wer bereits einen Account für den Amazon-Shop besitzt, kann den benutzen und dessen Anmeldedaten verwenden.

Die Amazon Developer Console ruft man über den diesen Link auf: https://developer.amazon.com/alexa/console/ask

Anmeldung Man Landet auf einer Seite, mit der man anmelden kann oder ein neues Konto einrichten kann.

Nach dem Einloggen erscheint eine Liste der bereits vorhandenen Skills. Wenn man beginnt, ist diese Liste naturgemäß leer.

Übersicht über die Skills

Skill erstellen

Neuen Skill anlegen

In der Übersicht der Skills gibt es die Schaltfläche Skill erstellen, mit der die Neuanlage eines Skills gestartet wird. Es erscheint eine Seite, auf der der Name des Skills und das Gebietsschema festgelegt werden muss. Der Name ist nahezu und hat keine Relevanz für die Funktionalitäten.

Skill erstellen Schritt 1: Namen und Gebietsschema festlegen

Im in den folgenden Schritten wird die Vorlage für den Skill definiert und erstellt. Als erstes muss der type of experience festgelegt werden. Es muss Other ausgewählt werden.

Skill erstellen Schritt 2a: Themenbereich (Experience) festlegen

Danach muss das Skill-Modell festgelegt werden. Hier wird Custom ausgewählt.

Skill erstellen Schritt 2b: Modell festlegen

Im nächsten Schritt wird der Hosting Service bestimmt. Hier wird festgelegt, in welcher Programmiersprache der Code (AWS Lambda Funktion) hinterlegt werden soll und wo dieses Code ausgeführt werden soll. Es muss Alexa-hosted (Python)3) ausgewählt werden. Es sollte nicht vergessen werden, im unteren Teil die Hosting Region zu bestimmen.

Skill erstellen Schritt 2c: Hosting-Dienst festlegen

3) Die Verwendung der Alexa-hosted Varianten ist am einfachsten. Node.js scheidet allerdings aus, weil hier keine synchrone Netzwerk-Aufrufe möglich sind. Damit sind keine Rückmeldungen aus dem Netz (sprich vom ESP32) möglich. Man kann den Code auch im Visual Studio mit C# entwickeln. Aber dazu muss man separate Tools installieren und sich mit den Lambda-Funktionen gut auskennen. Auch für Python-Laien, wie mich, ist es dank des ausführlichen Templates nicht besonders schwierig, den Code zu ergänzen.

Als nächstes wird die Vorlage ausgewählt. Start from Scratch ist hier die richtige Wahl.

Skill erstellen Schritt 3: Vorlage festlegen

Damit sind alle Parameter festgelegt. Es erscheint eine Seite, in der alle Daten noch einmal angezeigt werden:

Skill erstellen Schritt 4: Angaben überprüfen

Die Schaltfläche Create Skill löst die Erstellung des Skills aus. Das dauert eine Weile. Die Developer Konsole versucht aus den bisher angegebenen Daten eine gültigen Skill zu erstellen. Wenn dies auf Grund inkonsistenter Angaben nicht möglich ist, erscheint eine Fehlermeldung. Achtung: Sie erscheint nur kurz.

Skill erstellen Schritt 5: Anweisungen für den nächsten Schritt

Skill ergänzen

Der gerade angelegte Skill ist noch nicht fertig. Es fehlen essentielle Information. Diese müssen über den Skill-Editor erfasst werden.

Skill erstellen Schritt 7: Skill-Editor

Invocation Name (Aufrufname)

Zunächst einmal muss festgelegt werden, wir der Skill gestartet werden soll, z.B. "alexa starte ullis service".

Skill erstellen Schritt 11: Invocation Name (Aufrufname) festlegen

Für den Aufrufnamen gibt es eine Reihe von Einschränkungen, die auf der rechten Seite erläutert werden.

Wenn der Name eingegeben wurde, sollte der Skill über die Schaltfläche Build Skill neu erstellt werden. Auch das dauert wieder einige Zeit.

Skill Modell

Es lohnt sich an dieser Stelle, schon einmal einen Blick auf das Skill-Modell zu werfen:

Skill Modell

Auf der linken Seite des Reiters Build findet man die Elemente des Skills. Unter Interaction Model / Intents findet man die in der Vorlage definierten Intents. Man sieht, dass einige der Intents obligatorisch vorhanden sein müssen. Klickt man einen der Intents an, sieht man dessen Eigenschaften, hier die Aktivierungsphrasen:

Skill Modell

Erster Test

Jetzt wird es Zeit für den ersten Test. In der oberen Reiterleiste finden man den Reiter Test.

Reiter

Der Alexa-Simulator ist standardmäßig ausgeschaltet. Man muss ihn zunächst einschalten:

Simulator einschalten

Anschließend kann man per Tastatur oder Mikrophon mit Alexa "reden". Ich habe nacheinander "starte ullis service" und "hallo" eingegeben. Alexa zeigt die programmierten Antworten an und liest sie auch vor. Das Ganze ist etwas gewöhnungsbedürftig. Die Eingaben müssen in Deutsch erfolgen, die Ausgaben erfolgen jedoch in Englisch. Aber das wird jetzt als nächstes geändert.

Test der Vorlage

Skill bearbeiten

Zunächst ein Blick auf den Code. Dazu wird der Reiter angeklickt. Es erscheint eine Seite mit dem Python-Code, der Lambda-Funktion.

Code editieren

Der Code besteht im Wesentlichen aus zwei Komponenten. Für jeden Intent gibt es eine Handler-Klasse. Hier das Beispiel für den HelloWorldIntent.

class HelloWorldIntentHandler(AbstractRequestHandler):
    """Handler for Hello World Intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_intent_name("HelloWorldIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speak_output = "Hello World!"

        return (
            handler_input.response_builder
                .speak(speak_output)
                # .ask("add a reprompt if you want to keep the session open for the user to respond")
                .response
        )

Als zweites gibt eine Funktion, über die die Handler registriert werden:

# The SkillBuilder object acts as the entry point for your skill, routing all request and response
# payloads to the handlers above. Make sure any new handlers or interceptors you've
# defined are included below. The order matters - they're processed top to bottom.


sb = SkillBuilder()

sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(HelloWorldIntentHandler())
sb.add_request_handler(HelpIntentHandler())
sb.add_request_handler(CancelOrStopIntentHandler())
sb.add_request_handler(FallbackIntentHandler())
sb.add_request_handler(SessionEndedRequestHandler())
sb.add_request_handler(IntentReflectorHandler()) # make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers

sb.add_exception_handler(CatchAllExceptionHandler())

lambda_handler = sb.lambda_handler()

Dieser Code wir in den nächsten Schritten angepasst und ergänzt werden.

Hinweis: Der Editor für die Lambda-Funktion erkennt Syntax-Fehler nicht zuverlässig und die Fehlersuche ist schwierig. Es empfiehlt sich den Code mit einem externen Syntax-Checker zu überprüfen. ExtendsClass Free Online Toolbox for developers bietet eine Reihe solcher Tools an, auch einen Python-Checker.

LaunchRequest

Der LaunchRequest-Intent wird ausgeführt, wenn der Skill gestartet wird. In der Vorlage wird der englische Text "Welcome, you can say Hello or Help. Which would you like to try?" ausgegeben. Es ist relativ einfach diesen Text zu ändern in "Ich bin bereit, den Fernseher zu bedienen." Außerdem wurde in die Antwort das Element ask hinzugefügt mit dem gleichen Text. Das bewirkt, dass der Text wiederholt wird, wenn der Anwender keine weiteren Kommandos gibt.

class LaunchRequestHandler(AbstractRequestHandler):
    """Handler for Skill Launch."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool

        return ask_utils.is_request_type("LaunchRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speak_output = "Ich bin bereit, den Fernseher zu bedienen."

        return (
            handler_input.response_builder
                .speak(speak_output)
                .ask(speak_output)
                .response
        )

Wenn ein Teil des Codes geändert wurde, muss er über die Schaltfläche Save (rechts oben) zunächst gespeichert werden und über die Schaltfläche Deploy neu als Lambda-Funktion bereit gestellt werden. Letzteres dauert wieder eine Weile. Der Skill muss jedoch nicht neu erstellt werden.

Hat man dies gemacht, kann man erneut Testen. Alexa antwortet nun mit dem neu eingefügten Text.

Geänderter Text

Die Antworten der anderen Intents können analog angepasst werden.

Intent anpassen

Slot Types

Der HelloWorld-Intent ist zwar ganz nett, aber für das Projekt nicht zu gebrauchen. Er wird so angepasst, dass er auf "Fernseher an/aus" reagiert. Dazu wird zunächst ein Slot Type benötig, in dem an und aus als Parameterausprägungen für dieses Kommando hinterlegt sind.

Auf der linken Seite des Reiters Build findet man unter Assets / Slot Types die Liste der definierten Slot Types. Diese Liste ist leer.

Code editieren

Es wird ein neuer Typ mit Namen SlotTypeOnOff und den Werten an und aus angelegt.

Code editieren

Hinweis: Zumindest im Alexa-Simulator wird nicht abgeprüft, ob eingegebene Werte zum Slot Type passen.

HelloWorld-Intent modifizieren

Nun kann der eigentliche Intent in Angriff genommen werden. Im Reiter Build findet man die Liste der Intents unter Interaction Model / Intents. Klickt man HelloWorldIntent an, wird dieser zum Bearbeiten aufgerufen.

Der Name des Intents wird geändert in TvOnOffIntent und die bestehenden Utterances werden gelöscht (Papierkorb-Symbol rechts). Dann werden neue Phrasen erfasst. Dazu gibt man den einfach in die Textbox mit dem "+"-Zeichen ein. Slots werden mit einer geschweiften Klammer eingeleitet. Tippt man diese, erscheint ein Box zu Definition des Slots. Erwartet wird ein Name. Hier wurde OnOffSlot gewählt.

Code editieren

Im Beispiel wurden zwei Utterances angelegt. Nachdem dies geschehen ist, müssen den Slots noch ein Slot Type zugewiesen werden. Dies geschieht im unterem Teil des Dialogs:

Code editieren

Damit sind die Änderungen des Intents und des Skill-Modells abgeschlossen. Damit sie wirksam werden, muss der Skill neu erstellt werden (Schaltfläche Build Skill rechts oben).

Code zum Intent anpassen

"Hello World" funktioniert nicht mehr! Der Code der Lambda-Funktion muss entsprechend der gemachten Änderungen angepasst werden. Der Code für den Intent-Handler wurde wie folgt umgeschrieben

01: class TvOnOffIntentHandler(AbstractRequestHandler):
02:     """Handler for On Off Intent."""
03:     def can_handle(self, handler_input):
04:         # type: (HandlerInput) -> bool
05:         return ask_utils.is_intent_name("TvOnOffIntent")(handler_input)
06: 
07:     def handle(self, handler_input):
08:         # type: (HandlerInput) -> Response
09: 
10:         slots = handler_input.request_envelope.request.intent.slots
11:         state = slots['OnOffSlot']
12:         speak_output = "Fernseher " + state.value
13: 
14:         return (
15:             handler_input.response_builder
16:                 .speak(speak_output)
17:                 .ask("Was soll ich noch tun?")
18:                 .response
19:         )

Zeile 01: Der Name des Handlers wurde geändert von HalloWorldIntentHandler auf TvOnOffIntentHandler. Der Name kann beliebig gewählt werden.

Zeile 05: Der Name des Intents ist nun TvOnOffIntent.

Zeile 10, 11: Der Wert des Slots wird ermittelt.

Zeile 12: Die Sprachausgabe wurde angepasst.

Zeile 17: Das ask-Element bewirkt, dass die Session nicht nach der Ausführung des Kommandos beendet wird.
01: sb = SkillBuilder()
02: 
03: sb.add_request_handler(LaunchRequestHandler())
04: sb.add_request_handler(TvOnOffIntentHandler())
05: sb.add_request_handler(HelpIntentHandler())
06: sb.add_request_handler(CancelOrStopIntentHandler())
07: sb.add_request_handler(FallbackIntentHandler())
08: sb.add_request_handler(SessionEndedRequestHandler())
09: sb.add_request_handler(IntentReflectorHandler()) 
10: 
11: sb.add_exception_handler(CatchAllExceptionHandler())
12: 
13: lambda_handler = sb.lambda_handler()

Zeile 04: Der Name des Handlers ist TvOnOffIntentHandler.

Intent zur Kanalauswahl anlegen

Der bisher erstellte Skill ist nun theoretisch in der Lage, den Fernseher an- und abzuschalten. Es soll aber auch der Kanal ausgewählt werden können.

Es macht Sinn, mit der Anlage eines Slot Types zu beginnen, der die Namen der Kanäle definiert:

Code editieren

In dem Beispiel wurde der Slot Type SlotTypeTvChannel mit einigen Kanalbezeichnungen angelegt. Für ARD und ZDF wurden die Synonyme Erstes und Zweites definiert.

Als nächstes kann der Intent angelegt werden:

Code editieren

Der Name des Intents (TvChannelIntent) muss vergeben und die Utterances festgelegt werden. Es wird der Slot TvChannelSlot als Platzhalter für den gewünschten Kanal benutzt. Der Slot Type für diesen Slot ist der oben angelegte SlotTypeTvChannel.

Wenn das gemacht wurde, muss der Skill neu erstellt werden (Schaltfläche Build Skill rechts oben).

Nun muss noch die Lambda-Funktion ergänzt werden. Ein neuer Handler muss erstellt werden. Dazu kopiert man den TvOnOffIntentHandler und modifiziert in entsprechend:

class TvChannelIntentHandler(AbstractRequestHandler):
    """Handler for On Off Intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_intent_name("TvChannelIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response

        slots = handler_input.request_envelope.request.intent.slots
        channel = slots['TvChannelSlot']
        speak_output = "Programm " + channel.value + " wurde eingestellt"

        return (
            handler_input.response_builder
                .speak(speak_output)
                .ask("Was soll ich noch tun?")
                .response
        )

Der Handler-Liste muss noch um diesen neuen Handler ergänzt werden (unten im Code):

...
sb.add_request_handler(TvOnOffIntentHandler())
sb.add_request_handler(TvChannelIntentHandler())
sb.add_request_handler(HelpIntentHandler())
...

Nach den Änderungen muss der Code gespeichert und als Lambda-Funktion registriert werden (Schaltflächen Save und Deploy rechts oben).

Nun sollte man testen, ob alles soweit funktioniert. Hier das Protokoll des Tests:

Code editieren

Download

Das ZIP-Archiv Alaxa-ESP32.ZIP enthält im Order 1. Alexa Skill anlegen einen Export des gesamten Skills (ullis service skill version 1.zip). In Skill-Export sind auch die Lambda-Funktionen enthalten.

zurück