by AWS International — modified by Francisco Rivas

Tutorial AVS: Modificando GUI Renderer en tu Raspberry Pi

Francisco Rivas
Diseñando para la Voz
9 min readJan 3, 2019

--

A medida que Alexa va mejorando y evolucionando, se van incluyendo nuevas interfaces y con estas, nuevas funcionalidades. Entre ellas las Display Cards, que funcionan a través de la interfaz TemplateRuntime. Permiten dibujar en aquellos dispositivos con pantalla, texto e imágenes, por ejemplo, los controles de reproducción de audio (play, pause, forward, rewind, next, previous) o información estática como texto predefinido e imágenes. Por ejemplo, debajo, podemos ver una display card que se muestra como resultado de pedirle a Alexa que me contase algo de España. En este caso la Display Card se muestra en la web de Alexa.

Display Card — Alexa Web

¿Qué vamos a hacer en este tutorial?

Básicamente vamos a modificar la información que presenta la aplicación de ejemplo que viene con el SDK de AVS en una display card. Agregando un campo más y el payload a esta.

También, vamos a conocer, a grandes rasgos, cómo funciona la interfaz que nos permite utilizar las Display Cards desde la perspectiva de AVS y de una skill.

Pre-requisitos

  • Tener instalado el SDK de AVS en nuestra R-Pi. Si aún no lo tienes instalado, te sugiero seguir este excelente tutorial.
  • Por otro lado sugiero darle un vistazo a mi artículo anterior. Creo que sirve para tener una idea del tipo de modificación que vamos a hacer.

Algo de teoría primero

AVS expone un conjunto de interfaces y funcionalidades, por ejemplo, la interfaz AudioPlayer, permite controlar la reproducción de audio: nos permite adelantar, detener o resumir la reproducción. Una interfaz agrupa de forma lógica los mensajes en dos categorías. Directivas y Eventos. De forma general, podemos definirlos así:

  • Directivas: son los mensajes que se envían desde AVS en la nube a nuestro cliente indicándole qué hacer.
  • Eventos: son los mensajes que se envían desde nuestro cliente al AVS en la nube que le indican a este que algo ha sucedido, una solicitud de información, por ejemplo.

Existen varias interfaces: Alerts, AudioActivityTracker, AudioPlayer, BlueTooth, SpeechSynthesizer, TemplateRuntime, etc.

Nos interesa la interfaz TemplateRuntime, que es la encargada de proporcionar información (metadatos) a dispositivos con soporte visual. El formato en el que esta interfaz estructura los metadatos proporcionados es JSON.

La forma en la que se implementa la utilización de está interfaz es, a través de las Display Cards, como la que he mostrado mas arriba.

Directivas

TemplateRuntimetiene disponible dos directivas:

  • RenderPlayerInfo: Utilizada cuando la solicitud del usuario está asociada a la reproducción de audio.
  • RenderTemplate: Utilizada cuando la solicitud del usuario está asociada a texto estático, estas son las Display Cards.

Plantillas

Además de esto, AVS tiene disponible 5 plantillas que definen cómo y qué será mostrado en cada una. Cada plantilla tiene un payload con una estructura JSON específica.

  1. Now Playing: Se utiliza para mostrar los controles de audio y se asocia siempre a la directiva RenderPlayerInfo.
  2. BodyTemplate1: Se utiliza para mostrar solo texto. Por ejemplo, el mensaje de bienvenida o de despedida de una skill; información de interés para el usuario o simplemente texto obtenido de una consulta a una API. Se asocia siempre a la directiva RenderTemplate.
  3. BodyTemplate2: Se utiliza para mostrar texto y una imagen. Por ejemplo, una entrada de Wikipedia, alguna receta de cocina, etc. Se asocia a la directiva RenderTemplate.
  4. ListTemplate1: Se utiliza para mostrar calendarios y listas. Por ejemplo, listas de compras, listas de tareas. Se asocia a la directiva RenderTemplate.
  5. WeatherTemplate: Se utiliza para mostrar la información del clima. Se asocia a la directiva RenderTemplate.

TemplateRuntime: AVS y Skills

Desde el punto de vista de AVS

Cuando un usuario solicita algo al servicio, es posible satisfacer la solicitud y la aplicación encargada de responder a esta implementa Display Cards; AVS envía dos directivas Speak y Render. Como mencioné más arriba la directiva específica de Render depende del tipo de respuesta que se necesite, si es necesario incluir texto y/o una imagen sería RenderTemplate. El payload (JSON) que viene con la directiva RenderTemplate contiene el titulo, subtitulo, el icono de la skill, una imagen, etc.

Desde el punto de vista de las skills

La forma en la que utilizamos esta interfaz al momento de desarrollar nuestra skill, o incluso la forma en la que es utilizada por las funcionalidades básicas, es a través de las Display Cards.

El ASK SDK ofrece la posibilidad de crear las display cards, especificando el tipo de esta. Por ejemplo, utilizando el ASK SDK para Python, cuando se trata de la directiva RenderTemplate, tenemos las siguientes clases disponibles:

Esta permite utilizar la plantilla BodyTemplate1:

class SimpleCard(Card):
def __init__(self, title=None, content=None):
....

Esta permite utilizar la plantilla BodyTemplate2:

class StandardCard(Card):
def __init__(self, title=None, text=None, image=None):
.....

Si nos fijamos bien en los constructores, en el primer caso solo tenemos texto y en el segundo sí que podemos incluir una imagen.

A la práctica

Ya que conocemos un poco más sobre las plantillas y las directivas involucradas en ofrecernos contenido visual. Vamos a nuestro AVS de pruebas en nuestra R-Pi.

La modificación que vamos a hacer nos ayudará a hacer mejor depuración de errores ya que vamos a mostrar el texto de la tarjeta y el payload que recibimos.

Necesitamos una terminal desde la cual acceder al código fuente de la aplicación de ejemplo.

Veamos un ejemplo:

Ejecutamos la aplicación de ejemplo:

$ ./SampleApp/src/SampleApp ./Integration/AlexaClientSDKConfig.json 

Y le pedimos a Alexa que nos diga el clima en Madrid, por ejemplo, nos mostrará una tarjeta como esta:

#####################################################################     RenderTemplateCard
#-------------------------------------------------------------------
# Focus State : FOREGROUND
# Template Type : WeatherTemplate
# Main Title : Madrid, España
####################################################################

Podemos observar que TemplateType nos indica uno de los tipos de plantilla que mencioné mas arriba, WeatherTemplate. Si vemos la tarjeta que se dibuja en la web de Alexa, vemos algo como (sí, hace frío :) ):

Hagamos otra prueba, con una skill. Ejecutamos la aplicación de ejemplo:

$ ./SampleApp/src/SampleApp ./Integration/AlexaClientSDKConfig.json

Le pedimos a Alexa que abra Evangelio Hoy y nos lea el Evangelio. Veremos la siguiente Display Card:

#####################################################################     RenderTemplateCard
#-------------------------------------------------------------------# Focus State : FOREGROUND
# Template Type : BodyTemplate1
# Main Title : Evangelio Hoy
####################################################################

En este caso observamos que TemplateType muestra BodyTemplate1. Si la vemos en la web de Alexa:

En efecto, podemos confirmar que solo tiene texto y esto corresponde al tipo de plantilla BodyTemplate1.

Modificando GUI Renderer

Me resultaría interesante ver también el texto de la display card e incluso el JSON que corresponde a su generación en la terminal. Considero que puede ahorrar tiempo y puede ser útil para hacer troubleshooting. Por lo que decidí investigar un poco y finalmente modificar GuiRenderer.cpp, que es el componente que se encarga de dibujar las tarjetas.

Para saber el tag del campo que me interesa extraer del payload, lo que hice fue mirar los distintos JSON que recibo junto con la tarjeta cada vez que hago una solicitud. En este caso me interesa el tag textField.

Pasos

  1. Agregar una nueva constante que tiene el tag del texto que me interesa extraer del payload (JSON). En este caso textField.
  2. Agregar una nueva condición que verifica la existencia de ese tag en el payload y, en caso positivo, extraer el texto almacenándolo en una variable.
  3. Concatenar el contenido de textField al buffer que luego es mostrado en pantalla. Es una cadena de caracteres que va concatenando la información de la tarjeta.
  4. Recompilar SampleApp y probar.

1. Hay que modificar varias cosas. Voy a utilizar vim.

$ vim sdk-source/avs-device-sdk/SampleApp/src/GuiRenderer.cpp

En la primera sección del archivo se definen un conjunto de constantes, voy a agregar una mas para el campo textField:

/// Tag for find the field text in the payload of the RenderTemplate directive
static const std::string TEXT_FIELD_TAG("textField");

El nombre TEXT_FIELD_TAG es totalmente arbitrario, simplemente seguí un poco la convención de los demás. Lo que es importante es la referencia a textField.

2. Lo siguiente, es agregar la verificación de la existencia de este. Es importante mencionar que no todas las plantillas tienen este campo (textField) por lo que hay que gestionar este caso en la verificación.

std::string textField;if (templateType == "BodyTemplate1" || templateType == "BodyTemplate2") {    if (!jsonUtils::retrieveValue(payload, TEXT_FIELD_TAG, &textField)) {
ConsolePrinter::simplePrint("ERROR: Template JSON payload has no field text field");
return;
}
}

Primero verificamos que, en efecto, el TemplateType corresponde con alguna de las plantillas (BodyTemplate1 o BodyTemplate2) que tienen este campo y, de ser así, extraemos el texto. Lo guardamos en la variable textField que se define justo antes del condicional.

3. Concatenamos el contenido de textField al buffer, que es una cadena de caracteres que se mostrara en pantalla posteriormente. Adicionalmente agregamos la variable que contiene el payload (JSON).

// Storing the output in a single buffer so that the display is continuous.std::string buffer;
buffer += RENDER_TEMPLATE_HEADER;
buffer += "# Focus State : " + focusStateToString(focusState) + "\n";
buffer += "# Template Type : " + templateType + "\n";
buffer += "# Main Title : " + mainTitle + "\n";
buffer += "# Text Field : " + textField + "\n";
buffer += "# Payload : " + jsonPayload + "\n";

buffer += RENDER_FOOTER;

Salvamos los cambios y salimosESC :wq.

4. Recompilamos y probamos

Desde el directorio sdk-build

$ make SampleApp -j2                                                                                            
[ 32%] Built target AVSCommon
[ 34%] Built target SQLiteStorage
[ 39%] Built target ACL
[ 41%] Built target ADSL
[ 43%] Built target AFML
[ 45%] Built target RegistrationManager
[ 50%] Built target EqualizerImplementations
[ 52%] Built target ContextManager
[ 52%] Built target CapabilitiesDelegate
[ 54%] Built target PlaylistParser
[ 56%] Built target KWD
[ 56%] Built target SpeechEncoder
[ 58%] Built target AudioResources
[ 58%] Built target AudioPlayer
[ 58%] Built target Bluetooth
[ 58%] Built target Equalizer
[ 58%] Built target ExternalMediaPlayer
[ 60%] Built target InteractionModel
[ 63%] Built target Notifications
[ 65%] Built target PlaybackController
[ 65%] Built target SpeakerManager
[ 67%] Built target SpeechSynthesizer
[ 69%] Built target AVSSystem
[ 69%] Built target TemplateRuntime
[ 71%] Built target CBLAuthDelegate
[ 76%] Built target MediaPlayer
[ 78%] Built target CertifiedSender
[ 78%] Built target SENSORY
[ 80%] Built target AIP
[ 82%] Built target Settings
[ 86%] Built target Alerts
[ 89%] Built target ESP
[ 91%] Built target DeviceSettings
[ 91%] Built target KeywordDetectorProvider
[ 93%] Built target DoNotDisturbCA
[ 93%] Built target DefaultClient
Scanning dependencies of target SampleApp
[ 93%] Building CXX object SampleApp/src/CMakeFiles/SampleApp.dir/GuiRenderer.cpp.o
[ 93%] Linking CXX executable SampleApp

[100%] Built target SampleApp

Ahora podemos probar de nuevo, ejecutamos SampleApp y solicitamos a Alexa nos diga el clima en Madrid:

$ ./SampleApp/src/SampleApp ./Integration/AlexaClientSDKConfig.json

Podemos observar ahora lo siguiente:

#####################################################################     RenderTemplateCard
#-------------------------------------------------------------------# Focus State : FOREGROUND
# Template Type : WeatherTemplate
# Main Title : Madrid, España
# Text Field :
# Payload : {"type":"WeatherTemplate","token":"f775249c-43f1-44f5-96c6-a09c7bc9eba2","lowTemperature":{"value":"-1°","arrow":{"contentDescription":"Down arrow","sources":[{"widthPixels":88,"darkBackgroundUrl":"https://images-na.ssl-images-amazon.com/images/G/01/alexa/avs/gui/small/currentWeatherIcon/down_bb.png","size":"SMALL","heightPixels":80,"url":"https://images-na.ssl-images-amazon.com/images/G/01/alexa/avs/gui/small/currentWeatherIcon/down.png"},{"widthPixels":256,"darkBackgroundUrl":"https://images-na.ssl-images-amazon.com/images/G/01/alexa/avs/gui/medium/.......
####################################################################

Como podemos observar ahora tenemos algo mas de información, los campos Text Field y Payload. En este caso, como la plantilla WeatherTemplate no tiene el campo textField en el payload, queda vacío.

Ahora haciendo la prueba con la skill Evangelio Hoy, veremos como ahora textField sí tiene datos.

#####################################################################     RenderTemplateCard
#-------------------------------------------------------------------# Focus State : FOREGROUND
# Template Type : BodyTemplate1
# Main Title : Evangelio Hoy
# Text Field : Evangelio de hoy, jueves, 3 de enero de 2019. Lectura del santo Evangelio según san Juan:<break time='1s'/>....
# Payload : {"skillIcon":{"sources":[{"size":"SMALL","url":"https://s3.amazonaws.com/CAPS-SSE/echo_developer/b536/2723c98b0d824b049301c7d5f14ea1f2/APP_ICON?versionId=DFO2.C.FpuGWQl5y0BEaWVw9Q7YM9QEK&AWSAccessKeyId=AKIAJFEYRBGIHK2BBYKA&Expires=1546536276&Signature=1UDjJQI%2FZNjvjU5Qzv0ywlimDE4%3D"},{"size":"LARGE","url":"https://s3.amazonaws.com/CAPS-SSE/echo_developer/3ee9/5cd7572075a3412fbcfd0ffc98f1b55e/APP_ICON_LARGE?versionId=YLcQrDpHuYCphr_ExJTsLgAM5W19cMMW&AWSAccessKeyId=AKIAJFEYRBGIHK2BBYKA&Expires=1546536276&Signature=nMKaSG4El4uvp%2FxlLPX7%2Fm0RVrM%3D"}]},"type":"BodyTemplate1","token":"d8daac5e-6a5e-45d8-a011-3310518bcfbc","title":{"subTitle":"Evangelio Hoy","mainTitle":"Evangelio Hoy"},"textField":"Evangelio de hoy, jueves, 3 de enero de 2019. Lectura del santo Evangelio según san Juan:<break time='1s'/> ......

####################################################################

Aquí podemos ver el texto de la display card y ademas todo el payload. Especialmente util para hacer troubleshooting.

En este articulo

  • Hemos aprendido un poco sobre las interfaces que proporciona AVS, enfocándonos en las directivas de TemplateRuntime.
  • Revisamos, a grandes rasgos, las diferentes plantillas que TemplateRuntime tiene disponibles.
  • Revisamos, superficialmente, el funcionamiento de la interfaz desde la perspectiva de AVS y desde las skills.
  • Modificamos GuiRenderer.cpp para mostrar mas información en la display card que dibuja en la terminal.

Referencias

Muchas gracias por leer.

Escribió para Diseñando para la Voz, Francisco.

¿Estás trabajando en algún proyecto que utilice las display cards?, ¿Consideras necesaria alguna plantilla más?, ¿Estás desarrollando tu propio producto?. Nos encantaría saber sobre estos proyectos. Déjanos un comentario.

--

--

Francisco Rivas
Diseñando para la Voz

Alexa Developer | Coffee Enthusiast | Percussionist | Curious (life, tech) | Keen on learning