Neue Features sowie Tipps & Tricks

Echo Show – Entwicklung von Skills für Multiple Interfaces
Kommentare

Für einen hohen Informationsgehalt sind reine Voice Interfaces ungeeignet. Deshalb verknüpft Amazon Echo Show Voice mit Graphical Interface. Wir zeigen, wie sich die Skillentwicklung durch neue Features geändert hat und was es zu beachten gibt.

Voice Interfaces sind klar im Trend, haben aber ihre Tücken: die Informationsvermittlung. Naturgemäß hat der Mensch eine selektive und begrenzte Auffassungsgabe. So sind bisherige Voice Interfaces für einen hohen Informationsgehalt eher ungeeignet. Das hat auch Amazon erkannt und vor wenigen Wochen einen Nachfolger des Amazon Echo in den USA herausgebracht. Das neue Amazon Echo Show ist das Resultat aus den Erfahrungen und Learnings, die Amazon und die Entwicklercommunity in den letzten Monaten bei der Skillentwicklung gemacht haben. Amazon Echo Show verknüpft nun Voice Interface mit Graphical Interface und kompensiert somit die genannten Probleme. Wie sich die Skillentwicklung durch die neuen Features ändert und was zu beachten ist, sehen wir uns anhand eines Beispiels genauer an.

Echo-Show-Herausforderung: fehlende Dokumentation

Wer sich in die Entwicklung von Skills unter Java mit dem neuen Echo Show stürzen möchte, wird zunächst einmal sehr irritiert sein. Es gibt kaum Dokumentation, keine Beispiele. Lediglich die Anmerkung im Committext des letzten Frameworkreleases gibt Aufschluss darüber, dass mit der Version 1.4 eine grundsätzliche Unterstützung von Echo Show vorhanden sein soll. Mit ein wenig Analyse des Frameworkcodes sind wir jedoch auch ohne Dokumentation dazu in der Lage, Skills mit Displayunterstützung zu implementieren.

Die Aufgabe: die Skillentwicklung veranschaulichen

Um die Skillentwicklung zu veranschaulichen, widmen wir uns einer einfachen Aufgabe. Wir wollen einen Skill entwickeln, mit dem man würfeln kann. Aber nicht nur das: Man kann sich auch noch aussuchen, wieviele Seiten der Würfel haben soll. Das Display wollen wir zum einen zur Selektion des Würfels nutzen, zum anderen, um das gewürfelte Ergebnis visuell darzustellen. Öffnen wir den Skill, wollen wir gleich mit dem Auswahlscreen begrüßt werden. Selektieren wir einen Würfel, soll es ein kurzes Sprach- und Displayfeedback geben. Anschließend können wir dem Skill sagen, dass er würfeln oder die Würfelauswahl erneut öffnen soll.

Vorbereitung

Die Entwicklung für Echo Show ist zurzeit nur für den US-Markt möglich. Wenn wir also einen neuen Skill in der Amazon Developer Konsole anlegen möchten, müssen wir English (U.S.) als Sprache wählen. Neu ist nun, dass unter Global Fields ein Feld „Render Template“ zu sehen ist. Dieses müssen wir aktivieren, damit wir die Display-Unterstützung bekommen (siehe Abbildung 1).

Abbildung 1 – Skillinformation

Abb. 1: Skillinformationen in der Amazon Developer Console für den Echo Show.

Soll der Skill auch Videocontent ausspielen, müssen wir zusätzlich Unterstützung für die Video-App aktivieren. Später gehen wir gesondert auf die Videocontent-Unterstützung ein. Gehen wir nun mit „next“ zum Interaction Model, sehen wir ebenfalls etwas Neues. Amazon weißt uns darauf hin, dass je ein Satz von Sprachintents in unserem Modell implementiert werden muss (Abbildung 2). Diese Intents sind für die Sprachsteuerung des Displays notwendig. Um die meisten dieser Intents müssen wir uns in unserer Skill-Logik nicht kümmern. Lediglich AMAZON.PreviousIntent und AMAZON.NextIntent sollten implementiert werden. AMAZON.PreviousIntent ist zudem auf den Back-Button des Displays gemappt. Wir müssen uns also im Vorfeld Gedanken darüber machen, wohin ein „back“ überhaupt führen soll. Führen wir „Add missing intents“ aus, werden entsprechende Intents unserem Modell hinzugefügt.

Abbildung 2 - Amazon Notification

Abb. 2: Amazon Notification über die Pflicht zu Sprachintents.

Für unsere Würfelfunktionalität brauchen wir zwei Intents. Zum einen müssen wir Alexa sagen können, dass wir einen Würfel auswählen möchten, zum anderen dass wir würfeln möchten. Die Steuerung der Touch-Funktionalität wird komplett im Skill abgebildet. Da wir keinerlei variable Informationen über Voice-Eingaben benötigen, sind keine Slots für Intents notwendig. Wir können also ohne den Beta Skill Builder arbeiten. Für das Modell (Listing 1) fügen wir zwei kurze Phrasen (Sample Utterances) ein. „chooseDice choose another dice“ und „rollDice roll”.

...
    	{
      		"intent": "chooseDice"
    	},
    	{
      		"intent": "rollDice"
    	}
...

Damit haben wir auf Skill-Konfigurationsseite vorerst alles getan was nötig ist. Wie wir Skilllogik allgemein in Java mit AWS- Lambda realisieren, kann online nachgelesen werden.

Wichtige Callbacks in der Skillentwicklung

Ein normaler Echoskill hat vier Callbackmethoden, die über das SpeechletV2-Interface implementiert werden (Listing 2).

public void onSessionStarted(SpeechletRequestEnvelope<SessionStartedRequest> requestEnvelope)

public SpeechletResponse onLaunch(SpeechletRequestEnvelope<LaunchRequest> requestEnvelope)

public SpeechletResponse onIntent(SpeechletRequestEnvelope<IntentRequest> requestEnvelope)
 
public void onSessionEnded(SpeechletRequestEnvelope<SessionEndedRequest> requestEnvelope)

Diese Callbacks sind rein für das Voice-Interface zuständig. Wir arbeiten aber zusätzlich mit einem Touchscreen. Wie bekommen wir also entsprechende Interaktionen vom User mit? Die Lösung finden wir im SpeechletRequestDispatcher. Hier werden nach Analyse des Request-JSON des Amazonendpoints, die Callbackmethoden entsprechender Interfaces aufgerufen. Neben dem SpeechletV2-Interface finden wir hier noch Display, System, PlaybackController und Audioplayer. Uns interessiert nur das Displayinterface. Es hat eine Callbackmethode, die im Fall eines Display Requests aufgerufen wird (Listing 3).

displaySpeechlet.onElementSelected(typeSpecificRequestEnvelope);

Implementieren wir in unserem Speechlet also neben SpeechletV2 ebenfalls das Display Interface und dessen Methoden, werden wir zukünftig auf Select Events reagieren können. Unser leerer Klassenkörper sollte nun also so wie in Listing 4 aussehen.

public class DiceSpeechlet implements SpeechletV2, Display {
	private static final Logger log = LoggerFactory.getLogger(DiceSpeechlet.class);

	@Override
	public SpeechletResponse onElementSelected(SpeechletRequestEnvelope<ElementSelectedRequest> requestEnvelope) {return null;}

	@Override
	public void onSessionStarted(SpeechletRequestEnvelope<SessionStartedRequest> requestEnvelope) {}

	@Override
	public SpeechletResponse onLaunch(SpeechletRequestEnvelope<LaunchRequest> requestEnvelope) {return null;}

	@Override
	public SpeechletResponse onIntent(SpeechletRequestEnvelope<IntentRequest> requestEnvelope) {return null;}

	@Override
	public void onSessionEnded(SpeechletRequestEnvelope<SessionEndedRequest> requestEnvelope) {}

}

Unsere Intents werden über die Callbackfunktion onIntent geregelt, unsere Touch-Events über onElementSelected. Die Funktionen der einzelnen Voice Interface Callbacks wurde schon im letzten Artikel ausführlich beschrieben.

Direktiven sind der Schlüssel zum Display

Wenn wir uns die SpeechletResponse ansehen, stellen wir fest, dass es keine speziellen Funktionen für das Display gibt. Display- und Videosteuerung erfolgt über sogenannte Direktiven. Diese Direktiven kommen z.B. auch in der Steuerung des neuen Dialogmodels, was sich zum jetzigen Zeitpunkt noch in der Betaphase befindet, zum Einsatz.

Um Inhalte auf dem Display ausspielen zu können, wird der Response eines Callbacks eine RenderTemplateDirective mitgegeben. Diese Direktive beinhaltet ein Template, das die Darstellung eines bestimmten Contents abbildet. Was genau der Amazonendpoint mit dieser Direktive macht, wissen wir leider nicht. Es kann sein, dass der Displayinhalt vorgerendert wird, es kann aber auch sein, dass der Displayinhalt in eine Echo Show verträgliche Beschreibungssprache konvertiert wird und dann auf dem Gerät gerendert wird. Anzunehmen ist jedoch, dass die Direktiven direkt ans Gerät weitergegeben werden. Entwickelt man mit AVS, werden Direktive vom Endpoint direkt an den AVS-Client weitergegeben, welcher sich dann um das Handling kümmern muss.

Render Templates und wie man sie benutzt

Es gibt zurzeit sechs verschiedene Templates die zur Darstellung von Content genutzt werden können. Zwei davon sind für Listendarstellungen gedacht, die anderen vier sind Bodytemplates und für generelle Content-Darstellung. Ein weiteres List Template, das von Amazon beschrieben wird, ist leider über das Java Skill Kit nicht verfügbar.

Die Templates sind durch ihren Aufbau für unterschiedliche Zwecke geeignet. Siehe hierzu auch Render Template Referenz. Für uns ist lediglich wichtig, dass bei den List Templates die Listenelemente per Touch selektiert werden können und auf der Skillseite Callbacks generieren.

Für unseren Würfelskill wollen wir nach dem Öffnen des Skills eine Liste mit unterschiedlichen, wählbaren Würfeln darstellen. Dazu verwenden wir das ListTemplate2. Ein List Template besteht aus einer Liste von Elementen der Klasse ListItem die je nach Template horizontal oder vertikal angeordnet werden. Mit dem ListTemplate2 haben wir eine horizontale Anordnung der Elemente. Das ListItem selber hat drei für uns wichtige Properties: TextContent, Image und Token.

Misslungene Ableitung von TripleTextContent

TextContent selbst wird von TripleTextContent abgeleitet und ist für die Textinhalte jedes Templates verantwortlich. Leider wird TextContent in jedem Template als innere Klasse abgeleitet. Wenn wir also in einer Klasse mit mehreren Templates arbeiten, werden wir für den ein oder anderen TextContent den vollen Paketpfad ausschreiben müssen. Wollen wir uns Hilfsfunktionen zum Erstellen vom TextContent für unterschiedliche Templates bauen, so müssen wir mit dynamischen Parametertypen und Reflections arbeiten. Das ist weniger schön.

Wie wir in der Methode sehen können (Listing 5), geht es hier explizit um „primary“ Text. TripleTextContent besteht aber aus drei Textteilen: Primary, Secondary und Tertiary. Da wir Texte formatieren können, wäre das im Prinzip nicht notwendig. Jedoch verbessert es die Übersicht, wenn Textteile über diese Art der Aufteilung strukturiert werden können.

public <T extends TripleTextContent> T createContentWithPrimaryText(Class<T> clazz,String string) {
		Object content = null;
		if (TripleTextContent.class.isAssignableFrom(clazz)) {
			try {
				content = clazz.newInstance();
				RichText richText = new RichText();
				richText.setText(string);
				((T)content).setPrimaryText(richText);
				return (T) content;
			} catch (InstantiationException | IllegalAccessException e) {
				//TODO: Hier ist das Kind eh schon in den Brunnen gefallen. 
			}
		}
		return null;
	}

Der Inhalt der einzelnen Textteile kann entweder Plaintext sein oder mit Richtext formatiert werden. Beim Richtext stehen uns einige HTML-ähnliche Tags zur Verfügung. Dazu zählen Line break <br/>, Bold <b>, Italics <i>, Underline <u> ,Font Sizes <font size="n"> und Actions <action value=“bezeichner“>. Mit letzterem können selektierbare Elemente in den Text eingefügt werden. Zu guter Letzt gibt es noch das Inline Images Tag, mit dem Bilder in den Text integriert werden können. Mit <img src='URL' width='n' height='m' alt='TEXT' /> werden Bilder von einer URL geladen, positioniert und auf entsprechende Größe skaliert.

Die Image-Klasse in Verwendung bei Render Templates

Wir wissen jetzt also, dass wir mit dem TextContent unsere Würfel beschreiben können. Weiterhin wollen wir für jede Würfelart ein Bild für das ListItem wählen. Dazu hat ListItem das Attribut image vom Type Image. image konnte ursprünglich über die Getter und Setter von largeSourceUrl und smallSourceUrl befüllt werden. Diese Attribute und deren Methoden sind indessen veraltet und als deprecated deklariert.

Ersetzt wurden sie durch die Property private List<ImageInstance> sources der man eine Liste von ImageInstance übergeben kann. ImageInstance selbst beinhaltet eine URL und optionale Größenangaben. Dem Attribut size kann man mit Hilfe eines Enum die Werte x-small, small, medium, large und x-large übergeben. Das hat den Hintergrund, dass sich der Renderprozess selbst aus der Imageliste das Bild mit der optimalen Größe heraussuchen soll. Der Defaultwert für size ist x-small. Für unsere Bedürfnisse reicht es, wenn wir dem ImageInstance Objekt eine URL geben und sie anschließend in die sources-Liste eintragen.

Wir haben nun ein Bild und einen Beschreibungstext in unserem ListItem. Wie aber können wir bei einem Touchcallback dieses Item eindeutig identifizieren? Hierfür hat jedes Template und jedes ListItem einen sogenannten „Token“. Dieser Token ist nichts anderes als ein Identifier (String), der bei einem Touchcallback übergeben wird. Namenskonventionen gibt es hier nicht. In unserem Fall tragen wir die Seitenzahl des Würfels als String ins Token Attribut ein.

Wenn wir nun für jeden unserer Würfel (4-seitig, 6-seitig, 8-seitig, etc.) ein ListItem erzeugt haben, tragen wir diese in eine Liste ein und übergeben diese wiederum über den Setter public void setListItems(List<ListItem> listItems) unserem Template. Für das Template haben wir noch die Möglichkeit, einen Titel, eine Hintergrundgrafik und das Verhalten des „Backbutton“ zu definieren. Der Backbutton kann die Zustände HIDDEN oder VISIBLE annehmen. Haben wir das Template nach unseren Wünschen angepasst, erzeugen wir eine Instanz von RenderTemplateDirective, übergeben ihr unser Template und das Ganze als Liste dem Setter public void setDirectives(List<Directive> directives) unseres Response Objekts.

Neues Sessionverhalten

An dieser Stelle ist es an der Zeit, sich Gedanken über die Session zu machen. Bisher war es so, dass wir mit newTellResponse eine Response erzeugt haben, die die Session beendet. Wollten wir im Dialog bleiben, mussten wir eine newAskResponse generieren. Nun haben wir allerdings ein Display, und es ist nicht unbedingt notwendig, aktiv auf eine Voice Response zu warten. Wir möchten aber auch nicht, dass der Skill beendet wird, wenn z.B. ein ListTemplate angezeigt wird und wir darauf warten, dass sich der Nutzer für ein Element entscheidet. Zumal ist es auch so, dass bei einer newAskResponse das Display dunkel überlagert wird.

Die Antwort auf dieses Problem ist eine neue Methode der SpeechletResponse Klasse. Mit public void setNullableShouldEndSession(final Boolean shouldEndSession) ist es möglich, das shouldEndSession Attribut auf null zu setzen. Tun wir dies, wird nach der Darstellung des Templates eine Zeit lang aktiv gelauscht. Erfolgt kein Sprachkommando, geht der Skill in einen passiven Modus und reagiert nur auf Touch Events. Wenn wir allerdings das Echo Show mit dem Wakeword aktivieren, befindet es sich erneut im aktuellen Skillkontext, und wir können wieder Sprachkommandos geben. Es ist also wichtig, an diesen Stellen shouldEndSession auf null zu setzen.

Umgang mit Touchcallbacks

Wenn wir unser Response Objekt nun im onLaunch Callback zurückgeben, werden wir beim Starten des Skills unsere Würfelauswahl angezeigt bekommen. Was wir feststellen können ist, dass unsere einzelnen Würfel selektierbar sind. Beim Touch auf einen Würfel im Skill wird unser public SpeechletResponse onElementSelected (SpeechletRequestEnvelope<ElementSelectedRequest> requestEnvelope) Callback aufgerufen. Aus dem requestEnvelope können wir nun über requestEnvelope.getRequest().getToken() den Token des selektierten Würfels abfragen. Den String, den wir bekommen haben, müssen wir nun noch in einen Integer konvertieren und anschließend für den Nutzer persistieren.

Hier benutzen wir den gleichen Mechanismus wie auch schon in unserem ersten Skill-Projekt. Wir nutzen eine HashMap mit der UserId als Key, die wir ebenfalls aus dem requestEnvelope erhalten. Entweder ist das Integer-Objekt direkt unser Value oder für den Fall, dass wir noch andere Daten persistieren wollen, bauen wir uns eine Klasse mit entsprechenden Attributen.

Um ein virtuelles Feedback auf die Würfelwahl zurückzugeben, nutzen wir diesmal eines der Body Templates. Sinnvoll wäre hier z.B. das BodyTemplate2, da wir dadurch Text mit einer Grafik kombinieren können. Weiterhin können wir einen optionalen Titel wählen. Die Properties werden wie auch schon bei ListTemplate über entsprechende Setter befüllt. Wichtig bei der Response ist, dass wir das shouldEndSession wieder auf null setzen.

Benutzerfreundlich: Hints

Zusätzlich zum Feedback, welchen Würfel der Nutzer gewählt hat, wollen wir ihm noch den Hinweis geben, welche Sprachkommandos an dieser Stelle zur Verfügung stehen. Dazu gibt es die sogenannte HintDirective, die optional zu einem RenderTemplate übergeben werden kann (Listing 6). Hints werden im unteren Bildschirmbereich in der Form von „Try: ……..“ dargestellt.

	HintDirective hintDirective = new HintDirective();
	PlainTextHint hint = new PlainTextHint();
	hint.setText("roll a die");
	hintDirective.setHint(hint);

Anschließend übergeben wir diese Direktive zusammen mit unserer RenderTemplateDirective der Response des Callbacks. Wählen wir nun einen Würfel, bekommen wir ein visuelles Feedback und einen Hinweis darauf, was wir an dieser Stelle sagen könnten.

Was nun noch fehlt, ist die Implementierung des Verhaltens zu den Intents „chooseDice“ und „rollDice“. Im onIntent-Callback reagieren wir auf diese Intents. Da diese keine Slots haben, müssen wir lediglich herausfinden, wie der Name des genutzten Intents ist. Seit dem SpeechLetV2 greifen wir auf das entsprechende Intent über requestEnvelope.getRequest().getIntent() zu und erhalten mittels getName() den Namen des Intents.  In einer Fallunterscheidung reagieren wir nun auf „chooseDice“ oder „rollDice“. Im ersten Fall geben wir erneut das am Anfang definierte ListTemplate im Response Objekt zurück.

Andere Templates, andere Aufgaben

Im Falle von „rollDice“ nutzten wir das einfache BodyTemplate1. Dieses Template ist für reinen Text inklusive eines Titels gedacht. Aus unserer HashMap holen wir uns dafür, über die UserId, die zuletzt eingetragene Anzahl der Würfelseiten und lassen uns auf dieser Basis eine Zufallszahl generieren, die den Wurf simuliert. Die entsprechend generierte Zahl lassen wir nun visuell als TextContent und auditiv als PlainTextOutputSpeech ausgeben. Wie überall setzen wir shouldEndSession wieder auf null damit die Session nicht beendet wird.

Mit diesen Komponenten ist unser Skill eigentlich schon voll funktionsfähig. Optisch könnte man über die Properties für das Hintergrundbild noch einiges verschönern und an jeder Stelle Hint-Direktiven platzieren.

Eine kleine Zugabe: Videos ausspielen

Was in unserem Demoskill keinen Sinn macht, ist Videocontent auszuspielen. Trotzdem wollen wir uns dazu kurz die Theorie anschauen. Videos werden bei einem Skill für Echo Show über die sogenannte VideoApp ausgespielt. Die VideoApp selbst ist kein Teil des Skills. Wird ein Video ausgespielt, kommen wir über den Backbutton zurück in unseren Skill zum zuletzt verwendeten Template.

Wollen wir die VideoApp benutzen, müssen wir dies in unseren „Skill Informationen“ angeben (Abbildung 1). Der Aufruf der VideoApp erfolgt wieder über eine Direktive: die LaunchDirective. Der LaunchDirective wird ein VideoItem übergeben, das wiederum die Attribute Source und Metadata beinhaltet. Mit Source übergeben wir die URL des Videos. Angesprochen werden die Videos über HLS. Decodiert wird H.264. In den Metadaten kann noch ein Title und Subtitle hinterlegt werden. Die LaunchDirective wird wieder der Response hinzugefügt (Listing 7).

	LaunchDirective launchDirective = LaunchDirective launchDirective = new LaunchDirective();
	VideoItem videoItem = new VideoItem();
	Metadata metadata = new Metadata();

	videoItem.setSource("https://testurl/video.mp3");
	metadata.setTitle("Testvideo");
	metadata.setSubtitle("Nur für Testzwecke!");
	videoItem.setMetadata(metadata);
	launchDirective.setVideoItem(videoItem);
	response.setDirectives(Arrays.asList(launchDirective));

Fazit

Die zurzeit unterstützte Funktionalität haben wir so ausführlich wie aktuell möglich beleuchtet. Abzuwarten bleibt, wann es erste offizielle Codebeispiele für das Java-Framework geben wird.

Die neuen Möglichkeiten des Echo Shows sollten unterstützend genutzt werden, jedoch nicht Mittelpunkt der Interaktion sein. Schließlich handelt es sich auch bei diesem Device in erster Linie noch immer um ein Voice Interface. Wie schon bei der Skillentwicklung ohne Display ist das A und O eines guten Skills weiterhin ein durchdachtes Kommunikationsmodell, das den Nutzer in den Fokus stellt. Auch Amazon Echo Show soll den Alltag erleichtern und nicht zu Frustration führen. In diesem Sinne, frohes Planen und Entwickeln!

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -