Lorenzo González
14/05/2024
Motivación
Alexa Skill
AWS Lambda
Creando un Alexa Skill
El problema
AWS API Gateway
AWS DynamoDB
AWS SQS
Juntando todo
Hay diversas formas pero ninguna es barata, segura, privada y versátil
+
Tipos de Skills
Skill: Aplicación que añade funciones personalizadas a Alexa
Alexa, enciende la bombilla
{ "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": {} } }
{ "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 } ] } }
{ "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" } } }
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 } } }
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
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%
Alexa.Discovery
Discover
Al activar el skills para saber que dispositivos hay
Alexa.Authorization
AcceptGrant
Autenticarse en el Skill
exports.handler = function (request, context) { const response=hacerAlgo(request) return response; }
y poner los datos del "Security Profile" y viceversa
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
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;
Al crear y cerrar la conexión, se guarda el connectionId en DynamoDB
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
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) })); }
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 }));
La cola se usa para enviar la respuesta del dispositivo que está en la Lambda de WebSocket a la Lambda del Skill
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
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!
Tiempo de
node_modules
En vez de usar el pequeño IDE de la función lambda , podemos programar en local y subir el código de AWS
Ventajas
aws lambda update-function-code --function-name AlexaSkill --zip-file fileb://../dist/lambda.zip
Subir el código a AWS
https://slides.com/logongas/alexa-skill-con-funciones-lambda
l.gonzalezgascon@edu.gva.es