Alexa Skill con funciones Lambda

II Jornadas de intercambio de experiencias en FPCloud

Lorenzo González  

CIPFP Batoi

14/05/2024

Índice

  • Motivación

  • Alexa Skill

  • AWS Lambda

  • Creando un Alexa Skill

  • El problema

  • AWS API Gateway

  • AWS DynamoDB

  • AWS SQS

  • Juntando todo

Motivación

Home Assistant

Conectar Alexa con

mi sistema domótico local

Hay diversas formas pero ninguna es barata, segura, privada y versátil

+

Ventajas

  • Barata: 0€ ya que es suficiente con la capa gratuita de AWS
  • Segura: No tengo que abrir puerto de mi router
  • Privada: No tengo ningún servidor intermedio que puede conocer mis comunicaciones con Alexa
  • Versatil: Puedo acceder a todas las funciones de Alexa

Desventajas

  • Lo tengo que mantener yo.

Alexa Skill

Funcionamiento de Alexa

Tipos de Skills

  • Juegos
  • Música
  • Vehículos
  • Smart Home
  • Etc.

Skill: Aplicación que añade funciones personalizadas a Alexa

Smart Home Skill

Alexa, enciende la bombilla

Petición

 

{
    "directive": {
        "header": {
            "messageId": "74a44945-4b40-47dd-aaff-5050387ffcc9",
            "namespace": "Alexa.PowerController",
            "name": "TurnOn",
            "payloadVersion": "3",
            "correlationToken": "SUdTVEs6AAE6AAlNTM4LTdhYzQtNGE4Ni1iNGM0LWFhOGVjODJkMTNmYyJ9"
        },
        "endpoint": {
            "scope": {
                "type": "BearerToken",
                "token": "Atz4vhHtITm3IF93x-1A_tGqtp2zXcd0677DIGS-yvq5jnokEYHFbdhPPnTExoFKTTQ"
            },
            "endpointId": "DEVICE-1",
            "cookie": {}
        },
        "payload": {}
    }
}

 

Respuesta Éxito

 

{
    "event": {
        "header": {
            "namespace": "Alexa",
            "name": "Response",
            "messageId": "b0611318-483c-4625-8538-47401f04a2f0",
            "correlationToken": "SUdTVEs6AAE6AAlNTM4LTdhYzQtNGE4Ni1iNGM0LWFhOGVjODJkMTNmYyJ9",
            "payloadVersion": "3"
        },
        "endpoint": {
            "scope": {
                "type": "BearerToken",
                "token": "Atz4vhHtITm3IF93x-1A_tGqtp2zXcd0677DIGS-yvq5jnokEYHFbdhPPnTExoFKTTQ"
            },
            "endpointId": "DEVICE-1"
        },
        "payload": {}
    },
    "context": {
        "properties": [
            {
                "namespace": "Alexa.PowerController",
                "name": "powerState",
                "value": "ON",
                "timeOfSample": "2024-05-06T09:12:47.019Z",
                "uncertaintyInMilliseconds": 500
            }
        ]
    }
}

 

Respuesta Error

 

{
  "event": {
    "header": {
      "namespace": "Alexa",
      "name": "ErrorResponse",
      "messageId": "b0611318-483c-4625-8538-47401f04a2f0",
       "correlationToken": "SUdTVEs6AAE6AAlNTM4LTdhYzQtNGE4Ni1iNGM0LWFhOGVjODJkMTNmYyJ9",
      "payloadVersion": "3"
    },
    "endpoint":{
      "endpointId": "DEVICE-1"
    },
    "payload": {
      "type": "FIRMWARE_OUT_OF_DATE",
      "message": "El firmware del dispositivo es la versión 3.4 pero debe ser como mínimo la 4.1"
    }
  }
}

 

Código básico

 

exports.handler = function (request, context) {
    if (request.directive.header.namespace === 'Alexa.PowerController') { 
        
         const endpointId=request.directive.endpoint.endpointId;

         if (request.directive.header.name === 'TurnOn') {
            //Realizar la acción
            return {} //Retornar un OK
         }

         if (request.directive.header.name === 'TurnOff') {
            //Realizar la acción
            return {} //Retornar un OK

         }
    }
} 

 

Conceptos

Dispositivo inteligente a controlar

Directiva: Lo que queremos que haga el dispositivo

Uttertance: La frase que le decimos a Alexa

Interface: Agrupa varias directivas

Display Category: El tipo de dispositivo

Interfaces, directivas, utterances

Alexa.PowerController

      TurnOn: Alexa, enciende la luz del comedor.

      TurnOff: Alexa, apaga la luz del comedor.

 

Alexa.ColorController

      SetColor: Alexa, pon la luz del comedor en azul.

 

Alexa.BrightnessController

      SetBrightness: Alexa, establece el brillo del comedor al 50%

      AdjustBrightness: Alexa, baja el brillo del comedor un 5%

Directivas obligatorias

Alexa.Discovery

      Discover

Al activar el skills para saber que dispositivos hay

Alexa.Authorization

      AcceptGrant

 

Autenticarse en el Skill

AWS Lambda

Lambda

  • Son funciones que se ejecutan al producirse un evento
  • No hay que levantar un ordenador (Serverless)
  • Escalabilidad automática
  • Pago por uso
  • Hay que definir el evento que las desencadena

 

exports.handler = function (request, context) {

    const response=hacerAlgo(request)
    return response;
} 

 

  • Una frase en Alexa
  • Una petición HTTP
  • WebSocket
  • Paso del tiempo

Creando un Skill

Amazon Developer

  • Entrar en "Developer Console"

Alexa Skills Kit

  • Crear el Skill en "Alexa Skills Kit" de tipo "Smart Home"

Skill ID

  • Guardar el "Skill ID"

Security Profile

  • Entrar en "Login with Amazon" y "Create a New Security Profile"

Account Linking

  • Volver al Skill a "Account Linking" 

 y poner los datos del "Security Profile" y viceversa

Función Lambda

  • Crear la función Lambda

Añadir un desencadenador

  • Agregar el desencadenador "Alexa"

Id de la habilidad

  • En "Id de la habilidad" pegar el "Skill ID"

ARN Función Lambda

  • Volver al Skill y en "Default endpoint" poner el ARN de la función lambda

Código

  • Pegar el código fuente 
  • Pulsar en el botón "Deploy"

Alexa App

  • Ir a la App de Alexa a "Skills y juegos"
  • Y abajo del todo está el botón de "Mis Skills".
  • Activar el Skill

El Problema

Espectativas vs realidad

Realidad

Espectativas

AWS API Gateway

API Gateway

  • Es un servidor Websockets (también de HTTP)
  • No hay que levantar un ordenador (Serverless)
  • Escalabilidad automática
  • Pago por uso
  • Hay que definir a que función Lambda llama para que se ejecute la tarea

API Gateway

Código base

 

export const handler = async (request,context) => {
  const wsRequest = JSON.parse(event.body);

  connectionId=request.requestContext.connectionId

  switch (request.requestContext.routeKey) {
    case "$connect":
      break;
    case "$disconnect":
      break;
    case "$default":
      break;
    default:
      break;
  }
   
  return {
    statusCode: 200,
    body: JSON.stringify("success")
  };
}

 

connectionId:

Permite enviar y recibir datos del cliente de WebSockets

Problema

Interconectar los 2 sistemas

AWS DynamoDB

DynamoDB

  • Es un servidor de base de datos NoSQL (Como MongoDB)
  • No hay que levantar un ordenador (Serverless)
  • Escalabilidad automática
  • Muy baja latencia
  • Pago por uso
  • Se replican los datos en las zonas de disponibiliad

Crear Tabla

  • Hay que crear la tabla, indicando la clave de partición

Código

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
  DynamoDBDocumentClient,PutCommand,UpdateCommand,DeleteCommand,
} from "@aws-sdk/lib-dynamodb";


const client = new DynamoDBClient({});
const dynamo = DynamoDBDocumentClient.from(client);
const tableName = "Conexiones";
let endpointId="DEVICE-1";
let connectionId="XPiAWf-9Dwe5oECHSw=";


//Insertar
await dynamo.send(new PutCommand({
    TableName: tableName,
    Item: {
            endpointId:endpointId,
            connectionId:connectionId
        }
}));

//Leer
const dynamoDbResponse=await dynamo.send(new GetCommand({
	TableName: tableName,
    	Key: {
        	endpointId: endpointId
      	}        
    }));
connectionId = dynamoDbResponse.Item.connectionId;

Guardar el connectionId

Al crear y cerrar la conexión, se guarda el connectionId en DynamoDB

Skill envía directiva de Alexa

Cuando llega una petición de Alexa , el Skill obtiene el connectionId de DynamoDB para poder enviar la petición por Websockets al dispositivo inteligente

Código

import {
    ApiGatewayManagementApiClient,
    PostToConnectionCommand,
} from "@aws-sdk/client-apigatewaymanagementapi";


const url = "https://vkkogyu5555.execute-api.eu-west-1.amazonaws.com/production";
const apiClient = new ApiGatewayManagementApiClient({
    endpoint: url,
});
    
async function  sendToWebSocket(connectionId, data) {

    return apiClient.send(new PostToConnectionCommand({
        ConnectionId: connectionId,
        Data: JSON.stringify(data, null, 2)
    }));

}

 

AWS SQS

Simple Queue Service

Simple Queue Service

  • Es un servidor de colas (Como RabbitMQ vs ActiveMQ)
  • No hay que levantar un ordenador (Serverless)
  • Escalabilidad automática
  • Pago por uso
  • FIFO sin duplicidades de mensajes
  • Se replican los datos en las zonas de disponibiliad

Simple Queue Service

Código

const sqsClient = new SQSClient({});
const sqsQueueUrl = "https://sqs.eu-west-1.amazonaws.com/5555552080/AlexsaResponse.fifo";


//Enviar un mensaje
await sqsClient.send(new SendMessageCommand({
    QueueUrl: sqsQueueUrl;,
    MessageBody: {
        "message":"Hola mundo"
    },
    MessageGroupId:"Alexa"
})); 


//Recibir un mensaje
let sqsResponse = await sqsClient.send(new ReceiveMessageCommand({
    QueueUrl: sqsQueueUrl,
    MaxNumberOfMessages: 1,
    WaitTimeSeconds:2
}));

 

Simple Queue Service

La cola se usa para enviar la respuesta del dispositivo que está en la Lambda de WebSocket a la Lambda del Skill

Recopilando

Cuando el dispositivo se conecta por WebSocket al API Gateway , se guarda  conexión en DynamoDB

El Skill de Alexa usa el identificador de la conexión para enviar el mensaje al dispositivo a través de API Gateway

Se una una cola para enviar la respuesta del dispositivo al Skill de Alexa

¡Juntando todo!

¡Ya funciona nuestro Skill!

pero

aun no

hemos terminado

Latencia

Es el tiempo que se tarda

desde que se da la orden a Alexa

hasta que se responde

¡Es el mayor problema

en un Skill!

Causas de la latencia

  • Arranque en frío de la función Lambda (Cold Start)

  • Ejecución de la función Lambda

  • Comunicación entre servidores

Tiempo de

Arranque en frio

  • Usar lenguajes de programación que tarden poco en arrancar. Ej: NodeJS

  • Que el código fuente sea pequeño ya que hay que descargarlo desde S3. Cuidado con node_modules

  • El tamaño de la RAM y CPU que sea pequeño

  • Usando CloudWatch Events de forma que llame cada 5 minutos a la función para que no se descargue. ¡Cache!

Ejecución de la función Lambda

  • No es un problema ya que se hace muy poco procesamiento

Comunicación entre servidores

  • La comunicación entre servidores aumenta mucho la latencia

  • Envíar solo la petición y responde rápidamente "nada" a Alexa

  • DynamoDB almacena todo en SSD

  • No usar comunicaciones extremadamente lentas como MQTT (IoT Core)

Medición de la latencia

Programación Local

En vez de usar el pequeño IDE de la función lambda , podemos programar en local y subir el código de AWS

  • Usar nuestro IDE favorito
  • Uso de Git
  • TDD (aws-sdk-client-mock)
  • Etc.

Ventajas

aws lambda update-function-code 
  --function-name AlexaSkill 
  --zip-file fileb://../dist/lambda.zip

Subir el código a AWS

Para terminar

  • Gestionar si varios clientes de WebSocket se conectan

  • Websocket:Timeout de desconexión

  • Usar CloudWatch para ver console.log()

  • TODO: Gestionar peticiones simultanea a Alexa

  • TODO: SAM o CloudFormation

  • TODO: Lambda-Less

Gracias

por vuestra atención

¿Preguntas?

https://slides.com/logongas/alexa-skill-con-funciones-lambda

l.gonzalezgascon@edu.gva.es

Alexa Skill con funciones Lambda

By Lorenzo Gonzalez Gascón

Alexa Skill con funciones Lambda

  • 51