Heimautomatisierung mit Alexa und Raspberry Pi — Teil 6

Überarbeitung der Lambda-Funktion

Joachim Baumann
Oct 6, 2020 · 5 min read

Einleitung (Links einfügen)

Eigentlich läuft unsere Heimautomatisierung mit Alexa und unserem Raspberry, den wir in den Teilen 1–5 unserer Artikelserie gebaut haben, sehr zuverlässig und ohne Probleme.

Es gab allerdings die Einschränkung unserer Implementierung, nur einen Rückgabewert zurückliefern zu können. Für die meisten Geräte und Alexa-Interfaces ist das völlig ausreichend, es gibt aber einige wenige, die mehr als einen Rückgabewert brauchen. Dies sind aktuell die Alexa.Speaker-, Alexa.ThermostatController- und die Alexa.Cooking-Interfaces.

Während das Alexa.Cooking-Interface vermutlich nur für wenige interessant sein dürfte, sind sowohl das Alexa.Speaker- als auch das Alexa.ThermostatController-Interface für fortgeschrittene Anwendungen sehr interessant.

Deshalb überarbeiten wir in diesem Artikel unserer Serie die Lambda-Funktion so, dass sie in der Lage ist, Alexa-konform mehrere Rückgabewerte im korrekten Format zurückzuliefern.

Der Ansatzpunkt

In unserer Lambda-Funktion rufen wir die Funktion handleControl() dann auf, wenn eine Direktive von Alexa kommt (siehe Teil 5). In dieser Funktion senden wir die Details des Aufrufs an die MQTT-Instanz und erzeugen im Erfolgsfall eine Nachricht, die wir im letzten Schritt an Alexa zurücksenden.

Listing 1: Unser Ansatzpunkt für die Erzeugung der Nachricht mit mehreren Rückgabewerten

Genau bei dieser Erzeugung der Nachricht liegt unser Ansatzpunkt. Wenn wir hier anstatt nur eines Rückgabewertes alle benötigten erzeugen, dann funktioniert die Rückmeldung nicht nur für einzelne Werte, sondern für alle existierenden Alexa-Interfaces.

AWS Lambda: Wechsel zwischen Implementierungen

Bevor wir uns in die notwendigen Änderungen stürzen, noch ein Hinweis auf eine sehr geschickte Funktionalität von AWS Lambda, die es uns erlaubt, die Implementierung unserer Lambda-Funktion sehr einfach und schnell auszutauschen.

Unsere Lambda-Funktion exportiert die aufzurufende Funktionalität über die Variable exports.handler. Voreingestellt ist die Verwendung der Variable handler aus der Datei index.js, die wir bisher einfach verwendet haben. Im folgenden Bild ist der Bereich in der Lambda Management Console von AWS dargestellt, in dem wir diese Einstellung ändern können (oranger Kasten).

Bild 1: Die Auswahl des Handlers in AWS Lambda

Durch Änderung auf eine andere Datei oder eine andere Variable können wir damit sehr schnell eine andere Implementierung wählen und testen. So können Sie einfach die Datei index.js in eine Datei index_neu.js kopieren und als Handler index_neu.handler eintragen.

Die Implementierung

Damit wir die Antwort korrekt erzeugen können, brauchen wir zuerst die Menge der möglichen Fähigkeiten (capabilities) und ihrer Eigenschaften, für die wir Werte zurückliefern können, müssen dann entscheiden, für welche dieser Eigenschaften Alexa tatsächlich eine Antwort braucht, und wir müssen entscheiden, ob wir einen “echten” (von Alexa in der Direktive mitgelieferten) Wert zurückliefern, oder ob wir einfach einen “beliebigen”, sinnvollen Wert zurückgeben.

Die Menge aller Fähigkeiten unserer Geräte steht uns bereits zur Verfügung. Die Variable endpoints enthält diese Information für die Gerätesuche von Alexa (Discovery-Phase). Wir müssen also nur den richtigen Endpoint für den Request heraussuchen. Dies geht relativ einfach dadurch, dass im Request die Endpoint-ID des angesprochenen Gerätes mitgeschickt wird. Wenn wir die Endpunktdefinition haben, können wir uns durch die Liste der Fähigkeiten arbeiten und für jede Eigenschaft entscheiden, ob wir diese für unsere Antwort benötigen.

Die Implementierung dieses ersten Teils sehen wir in Listing 2.

Listing 2: Die Iteration durch die Eigenschaften aller Fähigkeiten unseres Endpunkts

Wir definieren zuerst eine lokale Funktion checkId(), die eine übergebene Endpunkt-Definition gegen die aus der Alexa-Direktive stammenden Endpoint-ID prüft und bei Übereinstimmung den Wert true zurückliefert. Diese verwenden wir, um mit der Methode find(), duch die Liste der Endpunktdefinitionen zu iterieren, bis wir die richtige Definition gefunden haben (endpoints.endpoints.find(checkId)). Da Alexa uns nur Endpoint-IDs liefert, die wir vorher in der Gerätesuche definiert haben, und wir auf die exakt gleiche Gerätedefinition zugreifen, können wir uns darauf verlassen, dass tatsächlich eine Definition gefunden wird. Wir greifen auf die Liste der Fähigkeiten zu und iterieren über jede Fähigkeit (capabilities.forEach(…)). Wenn der in der Direktive übergebene Namespace dem in der Fähigkeit definierten Interface entspricht, arbeiten wir uns durch alle zugehörigen Eigenschaften (capability.properties.supported.forEach()).

Zur Erinnerung: Im Prinzip wäre die korrekte Vorgehensweise, die Werte bei unseren Geräten zu setzen und die Antworten der Geräte an Alexa zurückzuliefern. Die grundlegend asynchrone Art der Kommunikation über MQTT, die wir gewählt haben, macht dies aber sehr schwierig. Da die dadurch gewonnene Genauigkeit in den meisten Fällen nicht benötigt wird, würden wir durch ein alternatives und komplexeres Vorgehen, das eine synchrone Kommunikation implementiert, kaum etwas gewinnen. Deshalb geben wir sinnvolle, plausible Werte zurück (interessanterweise gehen auch viele kommerzielle Hersteller diesen Weg).

Für jede der Eigenschaften haben wir jetzt drei verschiedene Möglichkeiten:

  • Verwendung eines sinnvollen, künstlichen Wertes
  • Verwendung des Wertes, der im Payload der Direktive mitgeschickt wurde
  • Ignorieren dieser Eigenschaft

Wir implementieren die Varianten in der angegebenen Reihenfolge. Dies ermöglicht uns, von Alexa geschickte Werte im Zweifelsfall mit unseren eigenen zu überschreiben und damit noch mehr Flexibilität zu erhalten.

Verwendung eines sinnvollen, künstlichen Wertes

Hierzu erweitern wir das assoziative Feld acceptableValues, das wir auch bisher verwendet hatten, um eine künstliche Antwort zurückzuliefern. Anstatt eines einzigen Wertes für jede benutzen wir jetzt ein weiteres assoziatives Feld, das für jede von Alexa aufgerufene Direktive die Werte für die verschiedenen Eigenschaften der gewählten Fähigkeit enthält.

Listing 3: Die Abbildung von Direktive auf künstliche Rückgabewerte

Die Einträge, die wir aus dem letzten Artikel der Serie kennen, sehen sehr ähnlich aus. Anstelle des Wertes selbst haben wir jetzt den Eintrag im Feld mit dem Namen der zugehörigen Eigenschaft.

Interessant sind die drei neuen Einträge für die Direktiven SetVolume, AdjustVolume und SetMute. Diese sind Direktiven, die dem Alexa.Speaker-Interface zugeordnet sind. Alle drei erwarten 2 Rückgabewerte, und wir definieren für SetVolume und SetMute die beiden Werte, die nicht über den Payload mitgeliefert werden, und für AdjustVolume beide Werte.

Verwendung des Wertes, der im Payload der Direktive mitgeschickt wurde

Im nächsten Schritt versuchen wir für die Eigenschaften, die nicht bereits über diese Abbildung einen Rückgabewert erhalten haben, den Wert aus dem Payload der Direktive zu extrahieren. Im Prinzip haben wir dies auch bisher getan, es gibt aber gerade bei dem Alexa.Speaker-Interface (und wahrscheinlich bei anderen) eine zusätzliche kleine Herausforderung. Während die Eigenschaft muted heißt, lautet der Name des Wertes im Payload der Direktive mute. Unverständlich, aber hierfür führen wir ein zusätzliches Feld propertyDirectiveMapping ein, das diese Abbildung liefert.

Ignorieren der Eigenschaft

Sofern wir über keine der beiden Vorgehensweisen einen Rückgabewert für die Eigenschaft erhalten, gehen wir davon aus, dass kein Antwortwert notwendig ist und ignorieren die Eigenschaft für die Antwort an Alexa.

Die Implementierung

Mit diesen Betrachtungen sieht die vollständige Implementierung wie folgt aus:

Listing 4: Die vollständige Implementierung

Zusammenfassung

In diesem Artikel haben wir die Lambda-Funktion so verallgemeinert, dass sie auf alle von Alexa geschickten Direktiven eine sinnvolle Antwort liefern kann. Diese Implementierung ist trotz ihrer höheren Komplexität erstaunlicherweise kaum länger als die simplere Variante aus dem letzten Artikel der Serie. Durch Erweiterung der beiden Felder acceptableValues und propertyDirectiveMapping ist sie beliebig erweiter- und anpassbar und damit auch für die Zukunft mit Alexa gut gerüstet.

Die vollständige Lambda-Funktion findet sich auf Github.

Vielen Dank fürs Lesen und ich freue mich auf Feedback. Die weiteren Artikel dieser Serie, genau wie andere interessante Artikel, erscheinen im Blog der Digital Frontiers und werden auch auf unserem Twitter-Account angekündigt.

Digital Frontiers — Das Blog

Dies ist das Blog der Digital Frontiers GmbH & Co.

Digital Frontiers — Das Blog

Dies ist das Blog der Digital Frontiers GmbH & Co. KG (http://www.digitalfrontiers.de). Hier veröffentlichen wir zu Themen, die uns interessieren und bewegen.

Joachim Baumann

Written by

Management Consultant and Managing Director @dxfrontiers

Digital Frontiers — Das Blog

Dies ist das Blog der Digital Frontiers GmbH & Co. KG (http://www.digitalfrontiers.de). Hier veröffentlichen wir zu Themen, die uns interessieren und bewegen.