Suche
Alexa trifft LEGO

Alexa Skills entwickeln: Sprachbefehle für LEGO-Mindstorms-Roboter

Vadim Kantor, Boris Kantor

©Shutterstock / maxuser

Vor 75 Jahren hat der Science-Fiction-Schriftsteller Isaac Asimov nicht nur den Begriff Robotik erfunden, sondern auch drei Robotergesetze formuliert. Dem zweiten Asimovschen Gesetz gemäß muss ein Roboter den menschlichen Befehlen gehorchen. In unserem Artikel befassen wir uns mit der technischen Fragestellung: Wie gut kann ein Roboter im Jahr 2017 auf Befehle von Menschen hören? Zwei intelligente Maschinenwesen, Alexa und LEGO, treffen aufeinander, um uns bei der Beantwortung dieser Frage zu helfen.

Wie entwickelt man eigene Alexa Skills? Dieser Artikel zeigt anhand des Beispiels Lego Mindstorms, wie sich Skills für Alexa für die Roboter-Steuerung und -Kommunikation erstellen lassen. Kommen Sie mit auf die Reise! 

Alexa trifft Lego

Nachdem endlich die Schulferien begonnen hatten, habe ich mir überlegt, einen eigenen Amazon-Echo-Ersatz auf meinem Raspberry Pi mit einem USB-Mikrofon und einem Lautsprecher zu bauen. Mit einer Anleitung auf GitHub ging das schnell und problemlos. Bis auf die Anmeldung beim Amazon Alexa Voice Service konnte ich alles alleine erledigen. Dann bat ich meinen Vater um seine Unterstützung. Nachdem plötzlich der Geist im Raspberry Pi auf das Wort Alexa reagierte, stellten wir ihm sofort alle möglichen Fragen und waren mit unserer neuen allwissenden Sprachassistentin erst einmal zufrieden. Als ich schließlich voller Begeisterung „Alexa, was ist LEGO?“ ausrief, sagte Alexa, sie habe keine Antwort auf diese Frage. Immer wieder stellte ich diese und ähnliche Fragen über LEGO Mindstorms, EV3. Jedes Mal antwortete Alexa hilflos: „Da bin ich mir leider nicht sicher!“ oder „Das weiß ich leider nicht!“ bis sie endlich kapitulierte und zugab „Entschuldigung, das weiß ich nicht. Aber lass uns Freunde bleiben.“. „Papa, das kann nicht sein, dass Alexa LEGO nicht kennt. Jedes Kind weißt, was LEGO ist!“, meinte ich enttäuscht. Papa versuchte Alexa zu verteidigen und erklärte mir, woran das Problem vermutlich liegen könnte. Er meinte, Alexa Voice Service setze die Suchmaschine Bing ein und Alexa könne nur das beantworten, was durch Bing in aufbereiteter Form zur Verfügung gestellt wird.

Das Lexikonwissen ist nicht das einzige Kriterium der Intelligenz einer Sprachassistentin. Nicht weniger wichtig ist, dass die gestellten Fragen oder Aufgabestellungen korrekt im Sinne der Spracherfassung verstanden werden, und bei Unklarheiten und Mehrdeutigkeiten durch einen möglichst natürlichen Dialog mit dem Benutzer präzisiert werden können. In der letzten Zeit gab es viele Berichte über verschiedene Vergleichstests der persönlichen Sprachassistenten von Google, Amazon, Apple und Microsoft. So behauptet beispielsweise die amerikanische Consulting-Firma Stone Temple aufgrund ihres Testes mit ca. 5 000 unterschiedlichen Testfragen, Google Assistant verstehe Fragen besser und antworte sachbezogener als seine Konkurrenten. Gemäß einer deutschen Studie von Digitaldienstleister Diva-e gemeinsam mit der Hochschule Aalen im Juni 2017 ist Alexa unter den digitalen Assistenten die Schlaueste im Lande, vor allem aufgrund der permanent steigenden Anzahl an Drittentwicklern, die Applikationen für den Assistenten programmieren und so Alexa immer schlauer machen.

„Schlauer machen? Du meinst damit wahrscheinlich Alexa Skills?“, unterbrach ich Papa. „Kann ich mit meinen eigenen Skills Alexa beibringen, was LEGO ist?“ „Mehr noch, mit Alexa Skills kannst du sogar deinen LEGO-Roboter über Sprachbefehle steuern.“ „Papa, das ist doch cool! Lass uns gleich damit anfangen!“

Alexa, was sind Alexa Skills?

Mit Alexa Skills erweitert die Sprachassistentin ihre Fähigkeiten. So kann man per Sprachanfragen entweder an bestimmte Informationen kommen oder ein Gerät steuern. Vereinfacht gesagt, sind Skills für Alexa nichts anderes als Apps. Standardmäßig beherrscht Alexa nur wenige Fähigkeiten, wie Wettervorhersage, Nachrichtenwiedergabe, Abspielen von Musik oder Bestellungen bei Amazon. Die Anzahl der Skills nimmt seit 2015 in den Vereinigten Staaten und seit dem Marktstart am 26. Oktober 2016 in Deutschland rapide zu. Amazon verzeichnet aktuell mehr als 15 000 Alexa Skills. Allein in Deutschland existieren mittlerweile über 1 500 verschiedene Skills. Diese kann der Alexa-Nutzer aus dem Skills Store von Amazon über eine Alexa-App nachladen.

Lesen Sie auch: „Alexa Skills sind die neuen Apps“ – Interview mit Dr. Adam Giemza & Marek Wester

Die Voraussetzung für die Entwicklung eines eigenen Skills ist der Besitz eines kostenlosen Amazon-Developer-Accounts. Mein Vater legte einen Developer Account an, und wir begannen mit der Entwicklung eines eigenen Skills. Amazon stellt für die Entwicklung das Alexa Skills Kit (ASK) zur Verfügung. ASK ist eine Sammlung von Self-Service APIs, Tools, Dokumentationen und Codebeispielen. Es existieren unterschiedliche Typen von Skills: Custom Skills und Smart Home Skills. Custom Skills ermöglichen einen Dialog mit dem Nutzer und erlauben dem Entwickler eine gewisse Freiheit bei der Auswahl der Schlüsselwörter. Skills werden durch einen Invocation Name (Rufnamen), z. B. LEGO, aktiviert. Wenn man also „Alexa, starte LEGO“ sagt, erkennt Alexa das Wort LEGO und ruft den Skill auf. Smart Home Skills benötigen hingegen keine Rufnamen und sind leichter zu programmieren. Sie verfügen allerdings über einen bereits existierenden Befehlssatz, wie Einschalten oder Ausschalten.

Für die Entwicklung eines benutzerdefinierten Skills sollte man sich zuerst einen Ablaufplan überlegen: Welche Schlüsselwörter kann der Benutzer verwenden und welche Reaktionen werden durch sie ausgelöst? Der Alexa-Client – sei es Echo oder ein anderes Gerät mit Alexa Voice Service – sendet die Anforderungen zur Verarbeitung an die Alexa-Service-Plattform. Die Verarbeitung erfolgt anhand des im Skill vordefinierten Alexa Interaction Models. Im Model werden im JSON-Format Satztypen – die so genannten Intents (Absichten) – und ihre Variablen (Slots) festgelegt. Listing 1 zeigt ein Beispiel des Models.

{"intents": [
  {"intent": "AMAZON.StopIntent"},
  {"intent": "turnIntent",
    "slots": [{"name": "direction", "type": "DIRECTION_TYPE"},
      {"name": "angle", "type": "AMAZON.NUMBER"}]
  },
  {"intent": "moveIntent",
    "slots": [{"name": "direction", "type": "DIRECTION_TYPE"},
      {"name": "distance", "type": "AMAZON.NUMBER"}]
}

Dieses Beispiel hat ein Array von drei Intents: ein Built-in Intent AMAZON.StopIntent zum Stoppen der Konversation und zwei für das Fahren- und Abbiegen-Kommando. Das Intent mit der ID turnIntent hat die Slots direction für eine Richtung und angle für die Angabe eines Drehwinkels in Grad. Das Intent moveIntent hat ebenso zwei Slots.

Listing 2 zeigt, wie Satzvariationen, so genannte Utterances, den Intents mit einem oder mehreren Slots zugeordnet werden können.

turnIntent nach {direction} drehen
turnIntent fahre nach {direction}
turnIntent drehe dich um {angle} Grad
moveIntent fahre {direction} {distance} Zentimeter
...

Im Model sollte man für jeden Slot einen Typ eingeben. Dabei stehen zahlreiche vordefinierte Typen wie AMAZON.DURATION, AMAZON.TIME auch für die deutschsprachigen Skills zur Verfügung. Außerdem gibt es die Möglichkeit, einen eigenen Slot-Typ als eine Liste von erwarteten Werten zu definieren. In unserem Beispiel kann die Werteliste vom Slot-Typ DIRECTION_TYPE aus links, rechts, geradeaus, rückwärts usw. bestehen. Nachdem ein Model definiert ist, kann man mit der Skill-Programmierung anfangen. Dafür muss man entweder eine AWS-Lambda-Funktion erstellen oder einen Web Service für die Kommunikation mit Alexa bereitstellen, der JSON Requests auswertet und eine entsprechende JSON-Response generiert.

Alexa, fahre Roboter geradeaus! Stopp! Kran nach links! Runter! Greif’s!

Für unseren Skill haben wir eine AWS-Lambda-Funktion geschrieben. Eine Lambda-Funktion kann in Java, Node.js, C# oder Python programmiert werden. Wir entschieden uns für Java, da ich schon immer gerne Java lernen wollte. „Wie werden wir es eigentlich programmieren?“, überlegte sich mein Vater, während ich schon ungeduldig über seine Schulter auf den Bildschirm schaute. „Zuerst müssen wir eine stabile Schnittstelle zwischen dem Alexa Skill und dem Roboter entwickeln. Unser Skill sollte idealerweise von keinen technischen Details des Roboters wissen, das gilt auch umkehrt: der Roboter weiß nichts über Alexa.“ Das verstand ich nicht: „Wieso das? Wir möchten doch erreichen, dass Alexa den Roboter kennt.“ Papa meinte, unser erster Entwurf vom Interaction Model (Listing 1) sei nicht optimal, denn das Model weise eine enge Kopplung zwischen Roboter und Alexa auf. Wir hätten bei jedem neuen Kommando oder jeder Änderung an einem vorhandenen Kommando das Model und die entsprechende Skill-Implementierung anpassen müssen. Besser wäre es, wenn der Roboter dem Backend selbst mitteilt, wie der Mensch ihn steuert. Die Aufgabe unseres Alexa Skills wäre es zu erfahren, welche Roboter der Mensch steuern und welche Befehle er dabei benutzen kann.

Bei der Umsetzung dieser Idee stießen wir allerdings sofort auf ein technisches Problem: den Slot-Typ AMAZON.LITERAL für eine Spracheingabe, den wir hätten nutzen können, um ein beliebiges Kommando vom Benutzer aufzunehmen, gibt es zurzeit nur für Skills in Englisch. Außerdem bietet die Alexa-Plattform keine Möglichkeit, Werte von einem Slot-Typ programmatisch zur Laufzeit zu ändern.

Mehr zum Thema: Grundlagen der Spracherkennung – so funktionieren Alexa, Cortana, Siri & Co

Ich starrte meinen Papa ganz frustriert an: „Und jetzt? Wir können unsere Idee nicht umsetzen? Es muss doch eine Lösung geben!“ Während mein Vater verzweifelt in der Dokumentation von Alexa stöberte, experimentierte ich am Alexa Interaction Model herum. „Das funktioniert doch! Wir müssen einfach unser Kommando-Intent als eine Kette von mehreren Slots definieren: ein Slot pro Wort. In die Werteliste von Slots tragen wir irgendwas ein. Das scheint keine Rolle zu spielen, denn Alexa kann fast alle Wörter erkennen, auch wenn sie nicht explizit in der Werteliste stehen.“ Wir erstellten also unser neues Model (Listing 3).

CommandIntent Gerät {deviceName} {action} {option} {value}
{"intents": [
  {"intent": "AMAZON.StopIntent"},
  {"intent": "CommandIntent",
    "slots": [{"name": "deviceName","type": "DEVICE_NAME_TYPE"},
      {"name": "action","type": "ACTION_NAME"}
      {"name": "option","type": "OPTION_NAME"}
      {"name": "value", "type": "AMAZON.NUMBER"}]
  }
}

Da die benutzerdefinierten Slots keine leere Werteliste enthalten dürfen, mussten wir bei den Slot-Typen DEVICE_NAME_TYPE, ACTION_TYPE und OPTION_TYPE beliebige Zeichenketten eintragen. Damit Alexa mehrstellige Nummern nicht als einzelne Wörter erkennt, definierten wir als letzten Slot {value} vom Typ AMAZON.NUMBER. Damit unsere Skill-Implementierung nicht allzu viel Zeit in Anspruch nimmt, legten wir einfachheitshalber folgende konventionale Regeln für die Kommandos fest:

  1. Ein Kommando besteht aus einem Satz mit maximal vier Slots.
  2. Der Benutzer muss immer mit dem Wort „Gerät“ anfangen, danach wird der Name des Geräts erwartet, der das Kommando ausführen muss.
  3. Das Verb in der Infinitivform an der zweiten Stelle ist ein Aktionsname, z. B. fahren, drehen, umdrehen oder einschalten.
  4. An der dritten Stelle kann ein Nebenwort für eine Option des Kommandos stehen, z. B. links oder langsam.
  5. Ein numerischer Parameter mit optionaler Einheitsangabe kann nur an letzter Stelle kommen, z. B. einhundertfünfundsiebzig oder dreihundertzehn Grad.

Mit dem Skill Builder definierten wir ein Dialog-Model (Abb. 1). Das Dialog-Model ist ein erweitertes Interaction Model. Mit einem Dialog-Model versetzt Alexa Voice Service den Benutzer in die Lage, eine Konversation mit Alexa zu führen, damit Alexa durch mehrere Dialogschritte alle erforderlichen Slot-Werte vom Intent erlangen kann (Abb. 2).

Alexa Skills entwickeln

Abb. 1: Definieren eines Dialogs in Skill Builder

Alexa Skills entwickeln

Abb. 2: Dialog in mehreren Schritten mit Alexa

Damit Alexa Skill unsere Lambda-Funktion aufruft, muss eine Klasse implementiert werden, die die abstrakte Klasse com.amazon.speech.speechlet.lambda.SpeechletRequestStreamHandler erweitert (Listing 4). Die Umsetzung der Logik der Lambda-Funktion für unseren Alexa Skill erfolgt in der Klasse Alexa2LegoSpeechlet, die das Interface com.amazon.speech.speechlet.SpeechletV2 implementiert. Das Interface SpeechletV2 hat vier Methoden:

  • void onSessionStarted(SpeechletRequestEnvelope<SessionStartedRequest> requestEnvelope)
  • SpeechletResponse onLaunch(SpeechletRequestEnvelope<LaunchRequest> requestEnvelope)
  • SpeechletResponse onIntent(SpeechletRequestEnvelope<IntentRequest> requestEnvelope
  • void onSessionEnded(SpeechletRequestEnvelope<SessionEndedRequest> requestEnvelope)
public class Alexa2LegoSpeechletRequestStreamHandler extends SpeechletRequestStreamHandler {
  private static final Set<String> supportedApplicationIds;
  static {
    supportedApplicationIds = new HashSet<>();
    supportedApplicationIds.add(System.getenv("alexa_app_id"));
  }
  public Alexa2LegoSpeechletRequestStreamHandler() {
    super(new Alexa2LegoSpeechlet(), supportedApplicationIds);
  }
}

Die Callback-Methode onSessionStarted wird aufgerufen, wenn der Skill aktiviert wird. Die Methode onLaunch ist für die erste Response von Alexa vorgesehen. onSessionEnded wird am Ende der Session aufgerufen. Sobald Alexa ein Intent zuordnen kann, ruft sie die Methode onIntent auf. In der Methode sind die Interaktionen mit dem Benutzer implementiert.

private static final String COMMAND_INTENT = "CommandIntent";
@Override
public SpeechletResponse onIntent(SpeechletRequestEnvelope<IntentRequest> speechletRequestEnvelope) {
  SpeechletResponse response = null;
  IntentRequest intentRequest = speechletRequestEnvelope.getRequest();
  Session session = speechletRequestEnvelope.getSession();
  Intent intent = intentRequest.getIntent();
  String intentName = getIntentName(intent);
  if (intentName != null) {
  switch (intentName) {
  case COMMAND_INTENT:
    DialogState dialogState = intentRequest.getDialogState();
    if (DialogState.STARTED.equals(dialogState) || DialogState.IN_PROGRESS.equals(dialogState)) {
      response = getDialogResponse(intent, dialogState, session);
    } else if (DialogState.COMPLETED.equals(dialogState)) {
      response = handleCommandIntent(intent);
    }
    break;
  ...
  }
}
private SpeechletResponse handleCommandIntent(final Intent intent) throws Alexa2LegoException {
  String deviceName = intent.getSlot(SLOT_DEVICENAME).getValue();
  String action = intent.getSlot(SLOT_ACTION).getValue();
  String option = intent.getSlot(SLOT_OPTION).getValue();
  String value = intent.getSlot(SLOT_VALUE).getValue();
  Alexa2LegoCommand command = generateEv3Command(deviceName, action, option, value);
  sendCommand(deviceName, command);
  ...
  return getResponse(deviceName, command),
}

In Listing 5 wird die Behandlung von CommandIntent in der Methode onIntent gezeigt. Da es sich bei CommandIntent um ein Dialog-Model handelt, wird die Methode onIntent gegebenenfalls nach jedem Dialogschritt aufgerufen. Ist der Dialog mit dem Status DialogState.COMPLETED durch Alexa abgeschlossen, wird die Methode handleCommandIntent aufgerufen, die das Kommando an das entsprechende Gerät sendet. Der Rückgabewert von onIntent ist vom Typ SpeechletResponse. Die SpeechletResponse sieht neben einer Sprachausgabe auch eine so genannte Card Response vor. Die Cards sind visuelle Komponenten, die auf der Alexa-Webanwendung https://alexa.amazon.com, in der Alexa-App für mobile Geräte oder auf dem Display von Echo Show erscheinen.

Alexa, was ist EV3Dev?

Für unsere Idee, Alexa einen LEGO-Roboter per Sprachbefehle steuern zu lassen, brauchen wir natürlich auch einen Roboter. Wir haben zuhause einen LEGO-Mindstorms-EV3-Home-Edition-Bausatz, und ich habe mir von der Schule noch ein Education Set ausgeliehen, damit wir die Steuerung von einem Roboter mit mehreren Prozessoren, so genannten EV3-Bricks, ausprobieren können. Bei der Steuerungseinheit EV3-Brick handelt es sich um einen programmierbaren 32-bit-ARM9-Mikrocontroller von Texas Instruments mit einem auf Linux basierenden Betriebssystem. Die Taktrate beträgt 300 MHz und die Größe des Hauptspeichers ist 64 MB. Der Brick hat 16 MB Flash-Speicher, der sich mit einer Micro-SD-Karte auf bis zu 32 GB erweitern lässt. Als Kommunikations- und Programmierschnittstelle verfügt EV3 über einen USB-2.0-Anschluss und ein Funkmodul, das Bluetooth verwendet. An den USB-2.0-Anschluss lässt sich ein WiFi-Dongle zur WLAN-Kommunikation anschließen. Für den Anschluss von Aktoren (große und mittlere Servomotoren) an den Brick stehen vier Ausgangsports (A bis D) und für Sensoren (z. B. Farbsensor, Berührungssensor, Ultraschallsensor oder Gyrosensor) vier Eingänge (1 bis 4) zur Verfügung. Während ich aus den LEGO-Bausteinen einen Autokran mit zwei EV3-Bricks – einen für die Bewegung des Autos und einen für den Kran – baute, beschäftigte sich mein Vater mit der Installation von EV3Dev auf den Bricks.

Der Anwender kann das Verhalten seines zusammengebauten LEGO-EV3-Roboters über verschiedene Anwendungen programmieren. Bei der auf LabView basierten Standardsoftware, die man kostenlos von der Mindstorms-Webseite herunterladen kann, setzt der Anwender in Form eines Ablaufdiagramms die miteinander verbundenen Programmierblöcke zusammen. Leider gibt es keine Möglichkeit, die Programmierblöcke mit einem eigenen Quellcode zu erweitern. Zur Programmierung der EV3-Roboter stehen außerdem zahlreiche andere Umgebungen und Frameworks in unterschiedlichen Programmiersprachen bereit. Für Java-Programmierung von EV3 ist das Open-Source-Projekt LeJOS EV3 sehr bekannt.

LeJOS

Die Abkürzung LeJOS steht für LEGO Java Operating System. Das Open-Source-Projekt stellt den Entwicklern eine Firmware für den EV3-Brick und ein Java-Framework mit einem LeJOS-EV3-Plug-in für Eclipse zur Verfügung. Die Firmware benutzt die schlanke Java-SE-Embedded-Laufzeitumgebung, die Oracle zur Unterstützung der LeJOS-Community zum Download bereitgestellt hat. Aktuell gibt es EV3-Java-SE für die Java Version 8. LeJOS wird von einer bootbaren Micro-SD-Karte gestartet. Die auf dem EV3 vorhandene LEGO-Software wird nicht verändert. LeJOS unterstützt alle Sensoren und Aktoren des gesamten LEGO-Mindstorms-Ökosystems und stellt dafür grundlegende Funktionen zum Auslesen von Sensordaten und Ansteuerung von Aktoren zur Verfügung. Des Weiteren bietet LeJOS vielseitige Möglichkeiten, verschiedene Robotikkonzepte umzusetzen.

Ralph Hempel, ein Senior Manager der LEGO Group, hat Ende 2014 das Open-Source-Projekt Ev3Dev ins Leben gerufen, um basierend auf Debian Linux ein Betriebssystem für verschiedene, mit Lego Mindstorms kompatible Plattformen zu schaffen. Dafür wurde ein Low-Level-Driver-Framework für die Steuerung von Sensoren und Motoren entwickelt. Ähnlich wie LeJOS wird Ev3Dev von einer Micro-SD-Karte aus gestartet, und es ist keine Anpassung der nativen LEGO Firmware erforderlich. Ev3Dev unterstützt einige Programmiersprachen out of the Box: Python, JavaScript (Node.js), Go, C++, Java und andere. Bei der Unterstützung von Java handelt es sich um das Projekt ev3dev-lang-java.

Kurz nachdem mein Vater das System Ev3Dev auf zwei Micro-SD-Karten installiert hatte, war ich auch mit dem Bauen unseres Roboters fertig (Abb. 3). Wir steckten zuerst die erste Karte in einen der beiden LEGO-Bricks – und voilà: Unser Brick war zum Programmieren bereit.

Für die Programmierung des Roboters schrieben wir den Code in JavaScript (Node.js). Leider steht für den ARM9-Prozessor des Mindstorms EV3 nur die alte Node-Version 0.10 zur Verfügung. Mit dem Befehl sudo npm install ev3dev-lang installieren wir das ev3dev-lang-Paket.

Alexa Skills entwickeln - Abb. 3: Der zu steuernde LEGO-Kran

Abb. 3: Der zu steuernde LEGO-Kran

EV3 ist ein Ding

Wir beabsichtigten, für die Kommunikation zwischen Alexa und unserem Roboter das Konzept Internet of Things anzuwenden. Zum Einsatz kommt der Amazon-IoT-Dienst. Mit AWS IoT kann die Anwendung Geräte überwachen, steuern und verfolgen, auch dann, wenn sie nicht verbunden sind. Der Service unterstützt HTTP, MQTT und WebSockets. Die Kommunikation ist durch TLS gesichert. AWS IoT umfasst mehrere Dienste: Ein Device Gateway für die bidirektionale Kommunikation, eine Device Registry zur Registrierung und Nachverfolgung von Geräten und Device Shadows für die Möglichkeit, bei den Anwendungen Daten von Geräten einfach abzufragen und Befehle zu senden. Außerdem gehört eine Rule Engine zur Filterung oder Transformation von gesendeten Daten und zur Weiterleitung von Daten an die anderen Amazon-Cloud-Services dazu. Dadurch können z. B. Gerätedaten in einer DynamoDB-Datenbank abgelegt werden. Amazon stellt für die Kommunikation zwischen den Geräten und dem AWS-IoT-Backend IoT-SDKs für mehrere Programmiersprachen zur Verfügung. Nach der Anmeldung bei AWS IoT legten wir für die AWS-Region EU-WEST-1 ein neues Thing namens EV3Crane an und bekamen anschließend eine Identifikationsnummer unseres Dings – eine eindeutige ARN-Nummer.

Danach ließen wir für das Gerät ein X.509-Zertifikat mit einem privaten Schlüssel erstellen, den wir von der AWS-IoT-Seite herunterluden und zusammen mit einem Root-CA für AWS IoT auf unseren EV3-Brick kopierten. Dann fügten wir in AWS IoT zum erstellten Zertifikat eine Policy hinzu, die dem Gerät die Connect-, Receive-, Publish- und Subscribe-Methoden erlaubt. Hinterher widmeten wir uns der Programmierung unseres Krans. Dafür legten wir ein Node.js-Projekt an. Mit npm init wurde package.json erzeugt, und mit dem Befehl npm install aws-iot-device-sdk –save wurde das AWS IoT Device SDK zum Projekt hinzugefügt. Dann erstellten wir eine Konfigurationsdatei config.js (Listing 6) mit aws_iot_host von unserer IoT-Instanz.

var config = {
  aws_region: 'eu-west-1',
  aws_iot_host: 'xxxxxxxxxxxxx.iot.eu-west-1.amazonaws.com',
  thingName: 'EV3Crane'
};
module.exports = config;

Listing 7 zeigt, wie eine Shadow-Instanz für unser Gerät erstellt wird. Die Shadow-Instanz vereinfacht die Kommunikation zwischen unserem Device und dem IoT-Backend.

var awsIot = require('aws-iot-device-sdk');
var config = require('./config.js');
var shadow = awsIot.thingShadow({
  keyPath: './certs/private.pem.key',9
  certPath: './certs/certificate.pem.crt',
  caPath:  './certs/root-CA.crt'),
  region:    config.aws_region,
  host:      config.aws_iot_host,
  clientId:  config.thingName,
  protocol: 'mqtts'
});

Für die Kommunikation mit dem IoT Shadow setzten wir das sichere MQTTS-Protokoll ein. MQ Telemetry Transport (MQTT) ist ein schlankes Messaging-Protokoll, das auf mobile Anwendungen zugeschnitten ist und sich im Umfeld von IoT als Standardprotokoll etabliert hat. MQTT kennt drei Quality-of-Service(QoS)-Stufen: 0, 1 und 2. Jede Stufe steht für bestimmte Garantien, auf deren Einhaltung man sich verlassen kann. Für unseren Zweck setzen wir QoS der Stufe 0 ein, die lediglich garantiert, dass eine Nachricht höchstens einmal ankommt, allerdings nicht, dass sie überhaupt ankommt. Für unseren Fall ist aber der Ressourcenverbrauch erst einmal wichtiger als die Zuverlässigkeit.

shadow.on('connect', function () {
  registerThing();
});
shadow.on('message', function (topic, payload) {
  handleMessage(topic, payload);
});
function registerThing() {
  shadow.register(config.thingName, {}, function () {
    shadow.update(config.thingName, sendRegistrationReport());
  });
  shadow.subscribe(config.thingName);
}
function sendRegistrationReport () {
  return {state: {reported: { device: {
    deviceName: "Kran",
    serialNumber: "XXXXXX",
    commands: crane.getCommands()}}}};

Listing 8 demonstriert zwei Callback-Methoden und die Registrierungsfunktion unseres IoT-Device. Die Funktion registerThing() wird aufgerufen, sobald eine Verbindung zwischen unserem Roboter und AWS IoT hergestellt wird. Sie sendet dem IoT-Backend einen RegistrationReport mit einer Liste aller verfügbaren Befehle, die das Gerät ausführen kann. Außerdem wird das IoT-Topic für eingehende Nachrichten mit auszuführenden Kommandos subskribiert. Beim Ankommen einer Nachricht wird die Callback-Funktion handleMessage() aufgerufen. Danach implementierten wir die einzelnen Command-Methoden des Roboters: turnRight(), turnLeft(), moveUp() etc. Listing 9 veranschaulicht eine mögliche Implementierung des Kommandos turnLeft zur Krandrehbewegung gegen den Uhrzeigersinn mit einem Motor und einem Gyrosensor für einen bestimmten Winkel.

var ev3dev = require('ev3dev-lang');
var motorA = newev3dev.Motor(ev3dev.OUTPUT_A);
var gyroSensor = new ev3dev.GyroSensor(ev3dev.INPUT_4);
var cancellationMotorA;
turnLeft: function (angle) {
  if (angle!==null) {
    gyroReset();
    cancellationMotorA = setInterval(function () {
    motorA.start(motorA.maxSpeed, motorA.stopActionValues.brake);
    }, 100);
  } else{
  motorA.runForDistance(-180, motorA.maxSpeed, motorA.stopActionValues.brake);
  }
}
gyroSensor.registerEventCallback(function (error, ultraInfo) {
  if (error) throw error;
  if (ultraInfo.lastValue < -angle) {
    clearInterval(cancellationMotorA);
    stopMotor(motorA);
    gyroReset();
  }}, function (userData) {
    var curValue = gyroSensor.getValue(0);
    var changed = curValue < userData.lastValue;
    userData.lastValue = curValue;
    return changed;
  }, false, {lastValue: 0});

Als wir unseren Code auf EV3Crane kopiert hatten, loggten wir uns per SSH ein und starteten das JavaScript-Programm. Nach einem kurzen Augenblick sahen wir im Shadow-Bereich von AWS IoT einen Statusreport unseres ersten EV3-Geräts mit all seinen Befehlen (Abb. 4).

Alexa Skills entwickeln - Abb. 4: Statusreport des IoT-Device

Abb. 4: Statusreport des IoT-Device

Im Alexa Skill programmierten wir einen Alexa2LegoIotClient (Listing 10). Er sollte Kommandonachrichten an die angemeldeten EV3-Geräte schicken. Anhand des Gerätenamens aus dem Kommando ermittelt Alexa2LegoIotClient den Adressaten der Nachrichten.

private Map<String, AWSIotMqttClient> iotClients = new HashMap<>();
private Map<String, AWSIotDevice> iotDevices = new HashMap<>();

public Alexa2LegoIotClient(String clientEndpoint, String clientId, String awsAccessKeyId, String awsSecretAccessKey,	List<Ev3Device> ev3Devices) {
for (Ev3Device ev3Device : ev3Devices) {
AWSIotMqttClient iotClient = new AWSIotMqttClient(clientEndpoint, clientId, awsAccessKeyId, awsSecretAccessKey);
AWSIotDevice iotDevice = new AWSIotDevice(ev3Device.getIotName());
iotDevices.put(ev3Device.getDeviceName(), iotDevice);
deviceMap.put(ev3Device.getDeviceName(), ev3Device.getIotName());
iotClient.attach(iotDevice);
iotClients.put(ev3Device.getDeviceName(), iotClient);
  ... 
}
}

public void sendCommand(String deviceId, Alexa2LegoCommand command) throws Alexa2LegoException {
AWSIotMqttClient iotClient = iotClients.get(deviceId);
if (iotClient == null) {
throw new Alexa2LegoException("ioT-Client " + deviceId + " cannot be found");
}
Alexa2LegoIotMessage message = new Alexa2LegoIotMessage(deviceMap.get(deviceId),
AWSIotQos.QOS0,command.toJson());

iotClient.publish(message, PUBLISH_TIMEOUT);
...
}

Damit unser Alexa Skill vor einer Interaktion mit dem Benutzer erfährt, welche Geräte für die Steuerung vorhanden sind und über welche Sprachkommandos sie verfügen, benötigten wir einen Service, der unserem Skill diese Information zur Verfügung stellt. Dazu haben wir die Rule Engine in AWS IoT verwendet und eine Regel definiert, die die Registrierungsnachrichten von allen Geräten in Amazon DynamoDB abspeichert. Amazon DynamoDB ist ein NoSQL-Datenbankservice der Amazon-Cloud. Unser Alexa Skill kann auf die Daten in der DynamoDB zugreifen und sie dem Benutzer bereitstellen. Dafür legten wir in der DynamoDB-Konsole eine neue Tabelle ev3devices mit einem Hashkey thing an (Abb. 5). Danach haben wir eine Regel definiert, die ausgeführt wird, sobald AWS IoT eine Updatenachricht für eine unserer Shadow-Instanzen enthält.

Alexa Skills entwickeln - Abb. 5: Die Tabelle „ev3devices“ in DynamoDB führt alle Geräte samt ihrer Sprachkommandos auf

Abb. 5: Die Tabelle „ev3devices“ in DynamoDB führt alle Geräte samt ihrer Sprachkommandos auf

Die Regel enthält folgendes Rule Query Statement: SELECT * FROM ‚$aws/things/+/shadow/update‘. Das +-Zeichen sorgt dafür, dass die Regel für alle Shadow-Instanzen gilt. Für die Regelaktion wählten wir INSERT A MESSAGE INTO A DYNAMODB TABLE und konfigurierten sie so, dass die Registrierungsnachricht in die Tabelle thing unter dem Gerätenamen als Schlüsselwert (Hash Key Value) abgespeichert wird.

Für den Zugriff unserer AWS-Lambda-Funktion auf die Amazon-IoT- und DynamoDB-Services legten wir im AWS-Bereich IDENTITY AND ACCESS MANAGEMENT (IAM) einen User an und ordneten ihn einer Usergruppe zu, die die entsprechenden Berechtigungen für den Zugriff auf unsere AWS-IoT- und Amazon-DynamoDB-Objekte hat. IAM generiert für unseren User eine AccessKey-ID und einen SecretAccessKey, die wir in der Konfiguration von Environment Variables der AWS-Lambda-Funktion hinzufügen.

Nachdem wir mit dem Gerät EV3Crane fertig waren, widmeten wir uns dem zweiten Teil unseres Roboters. Das zweite Gerät nannten wir EV3Engine. Mit ihm ging alles schnell und analog zu EV3Crane. Abbildung 6 zeigt schematisch den Gesamtablauf unseres Skills Alexa2Lego:

  • 1. Ein EV3-Gerät meldet sich beim AWS-IoT-Registrierungsdienst an und sendet an AWS IoT einen Report in einem festgelegten JSON-Format.
  • 2. Der Report mit allen Kommandos des Geräts wird in Amazon DynamoDB persistiert.
  • 3. Der Benutzer startet eine Interaktion mit Alexa und ruft den Alexa2Lego-Skill auf.
  • 4.–5. Alexa startet eine Skill-Session und ruft das Alexa2LegoSpeechlet auf.
  • 6. Der Skill fragt die Tabelle ev3devices in unserer AmazonDB-Instanz ab und liefert Informationen über alle angemeldeten EV3-Geräte und deren Kommandos.
  • 7.–8. Der Skill gibt dem Benutzer eine Sprachantwort zurück und sendet eine textuelle Karte mit den Kommandos an Alexa-App oder Alexa-Webanwendung.
  • 9.–12. Der Benutzer gibt Alexa ein Kommando. Ist das Kommando vollständig und kann es ausgeführt werden, wird es weiter an AWS-IoT-Service übergegeben. Ist das Kommando nicht vollständig, startet Alexa einen Dialog, um die erforderlichen Parameter des Kommandos zu vervollständigen.
  • 13. AWS IoT publiziert die Nachricht mit dem Befehl und seinen Parametern im Topic des jeweiligen EV3-Geräts, und das Gerät führt das Kommando aus.
Alexa Skills entwickeln - Abb. 6: Gesamtablauf des Alexa2Lego-Skills

Abb. 6: Gesamtablauf des Alexa2Lego-Skills

Fazit

In diesem Artikel haben wir am Beispiel eines LEGO-Roboters gezeigt, wie man ein IoT-fähiges Gerät per Alexa mit Sprachbefehlen steuern kann. Dafür entwickelten wir einen Alexa Skill, der verschiedene Amazon-Cloud-Dienste (AWS Lambda, AWS IoT und Amazon DynamoDB) für die Interaktion mit Benutzern, Kommunikation mit Geräten und Implementierung einer Device Registry nutzt. Für die Umsetzung von Steuerungsbefehlen auf einem LEGO-Roboter kam die EV3Dev-Plattform zum Einsatz. Auf GitHub findet man den Quellcode unseres Roboters und des Alexa Skills.

LESETIPP:

„Wir werden Sprachinteraktion zwischen Mensch und Maschine bald als einen ganz normalen Prozess erleben“

Geschrieben von
Vadim Kantor
Vadim Kantor
Vadim Kantor ist seit über zwanzig Jahren als selbstständiger IT-Berater, Softwarearchitekt und Entwickler tätig. Zu seinen Schwerpunkten gehört Java/JEE-Architektur.
Boris Kantor
Boris Kantor
Boris Kantor ist in der siebten Klasse des Kaiserin-Friedrich-Gymnasiums in Bad Homburg. In seiner Freizeit programmiert er gerne in JavaScript und baut LEGO-Roboter. Seit 2015 nimmt er erfolgreich an der World Robot Olympiad teil.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.