Per sviluppare Alexa Skills puo' essere necessario un programma di beta testing. Cio' e' possibile con l'unica (e non banale) limitazione che tutti i beta tester siano registrati come developer presso Amazon
Per permettere ad altri sviluppare a fare da beta tester si puo' usare questo metodo
https://developer.amazon.com/it/blogs/post/Tx2EN8P2AHAHO6Y/How-to-Add-Beta-Testers-to-Your-Skills-Before-You-Publish
anche se non e' attualmente funzionante in Italia sembra che nel futuro il beta testing seguira' invece la procedura indicata da questo link
https://developer.amazon.com/it/blogs/alexa/post/42e7de5c-f7ef-4e3e-8391-c61fe24f6caa/improve-skill-quality-with-the-new-beta-testing-tool-for-alexa-skills-beta
Visualizzazione post con etichetta Alexa. Mostra tutti i post
Visualizzazione post con etichetta Alexa. Mostra tutti i post
giovedì 31 gennaio 2019
mercoledì 23 gennaio 2019
Persistenza delle variabili in Alexa Skills
La persistenza delle variabili in Alexa Skills con NodeJS si ottiene tramite PersistantAttributes che si appoggia su DynamoDB in modo trasparente
In questa prima fase viene settata una variabile (per semplicita' statica) in modo persistente. Se non e' settata crea una nuova stringa. Importante impostare la chiamata come asincrona
Da notare che le variabili sono relative ad un determinato account per cui ogni utente ritrovera' le proprie impostazioni
---------------------------------------------------------------------------
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
async handle(handlerInput) {
const attributesManager = handlerInput.attributesManager;
var speechText = "Benvenuto nella skill ";
const attributes = await attributesManager.getPersistentAttributes() || {};
if (Object.keys(attributes).length === 0) {
attributes.nameValue = "Luca";
attributesManager.setPersistentAttributes(attributes);
await attributesManager.savePersistentAttributes();
}else {
speechText = "Benvenuto ${attributes.nameValue.toString()}";
}
return handlerInput.responseBuilder
.speak(speechText)
.withShouldEndSession(false)
.getResponse();
},
};
---------------------------------------------------------------------------
Con questo codice si puo' richiamare il valore della variabile da altra intent
---------------------------------------------------------------------------
const attributesManager = handlerInput.attributesManager;
const attributes = await attributesManager.getPersistentAttributes()
var speechOutput = attributes.nameValue.toString();
---------------------------------------------------------------------------
Per salvare i dati e' necessario impostare il nome della tabella in cui sono salvate le variabili e la possibilita' di creare in automatico la tabella ove non esista
---------------------------------------------------------------------------
const skillBuilder = Alexa.SkillBuilders.standard();
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequestHandler,
YesHandler,
geo,
HelpHandler,
ExitHandler,
SessionEndedRequestHandler
)
.addErrorHandlers(ErrorHandler)
.withTableName('alexa-data')
.withAutoCreateTable(true)
.lambda();
---------------------------------------------------------------------------
In questa prima fase viene settata una variabile (per semplicita' statica) in modo persistente. Se non e' settata crea una nuova stringa. Importante impostare la chiamata come asincrona
Da notare che le variabili sono relative ad un determinato account per cui ogni utente ritrovera' le proprie impostazioni
---------------------------------------------------------------------------
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
async handle(handlerInput) {
const attributesManager = handlerInput.attributesManager;
var speechText = "Benvenuto nella skill ";
const attributes = await attributesManager.getPersistentAttributes() || {};
if (Object.keys(attributes).length === 0) {
attributes.nameValue = "Luca";
attributesManager.setPersistentAttributes(attributes);
await attributesManager.savePersistentAttributes();
}else {
speechText = "Benvenuto ${attributes.nameValue.toString()}";
}
return handlerInput.responseBuilder
.speak(speechText)
.withShouldEndSession(false)
.getResponse();
},
};
---------------------------------------------------------------------------
Con questo codice si puo' richiamare il valore della variabile da altra intent
---------------------------------------------------------------------------
const attributesManager = handlerInput.attributesManager;
const attributes = await attributesManager.getPersistentAttributes()
var speechOutput = attributes.nameValue.toString();
---------------------------------------------------------------------------
Per salvare i dati e' necessario impostare il nome della tabella in cui sono salvate le variabili e la possibilita' di creare in automatico la tabella ove non esista
---------------------------------------------------------------------------
const skillBuilder = Alexa.SkillBuilders.standard();
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequestHandler,
YesHandler,
geo,
HelpHandler,
ExitHandler,
SessionEndedRequestHandler
)
.addErrorHandlers(ErrorHandler)
.withTableName('alexa-data')
.withAutoCreateTable(true)
.lambda();
---------------------------------------------------------------------------
venerdì 18 gennaio 2019
Sviluppare Alexa Skills: Echo od emulatore??
Questa e' una cosa che ho imparato a spese mie: gli emulatori di Alexa, anche quello ufficiale di Amazon, non hanno lo stesso comportamento e funzionalita' dei dispositivi Echo
Per esempio gli emulatori hanno problemi sono le skills di flash briefing od con il reprompt
Quando si sviluppa e' quindi sempre meglio utilizzare un dispositivo Echo reale in modo da non perdere la testa pensando di aver fatto un errore nel codice di una skill quando invece il problema e' il dispositivo
Per esempio gli emulatori hanno problemi sono le skills di flash briefing od con il reprompt
Quando si sviluppa e' quindi sempre meglio utilizzare un dispositivo Echo reale in modo da non perdere la testa pensando di aver fatto un errore nel codice di una skill quando invece il problema e' il dispositivo
Alexa Skills: Reprompt e YesNoIntent
Per avere una maggiore interazione utente Alexa puo' terminare la propria frase con una domanda e rimanere in attesa di una risposta da parte dell'utente
Questa fase e' gestita tramite la direttiva reprompt e si possono usare le BuildIntents Yes e No quando e' sufficiente che l'utente interagisca in modo affermativo o negativo
Per esempio partendo da un custom intent si puo' aggiungere il reprompt
===========================================
const geo = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return (request.type === 'IntentRequest'
&& request.intent.name === 'geo');
},
async handle(handlerInput) {
const response = await httpGet();
//console.log("Risposta "+response);
const { intent } = handlerInput.requestEnvelope.request;
const activity = intent.slots.luogo.value;
var speechOutput = response;
if (activity == "firenze")
{
speechOutput = "_______________";
//console.log("Speechout firenze"+ speechOutput+data);
}
if (activity == "milano")
{
speechOutput = "_______________";
}
console.log(activity);
return handlerInput.responseBuilder
.speak(speechOutput)
.reprompt("Vuoi la spiegazione del messaggio?")
.getResponse();
},
};
===========================================
"name": "AMAZON.YesIntent",
"samples": []
}
Questa fase e' gestita tramite la direttiva reprompt e si possono usare le BuildIntents Yes e No quando e' sufficiente che l'utente interagisca in modo affermativo o negativo
Per esempio partendo da un custom intent si puo' aggiungere il reprompt
===========================================
const geo = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return (request.type === 'IntentRequest'
&& request.intent.name === 'geo');
},
async handle(handlerInput) {
const response = await httpGet();
//console.log("Risposta "+response);
const { intent } = handlerInput.requestEnvelope.request;
const activity = intent.slots.luogo.value;
var speechOutput = response;
if (activity == "firenze")
{
speechOutput = "_______________";
//console.log("Speechout firenze"+ speechOutput+data);
}
if (activity == "milano")
{
speechOutput = "_______________";
}
console.log(activity);
return handlerInput.responseBuilder
.speak(speechOutput)
.reprompt("Vuoi la spiegazione del messaggio?")
.getResponse();
},
};
===========================================
Si aggiunge quindi il buildinten Yes
-------------------------------------------------
{"name": "AMAZON.YesIntent",
"samples": []
}
-------------------------------------------------
a questo punto per gestire la eventuale risposta positiva si aggiunge alla lambda
--------------------------------------------------
const YesHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest'
&& request.intent.name === 'AMAZON.YesIntent'
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak("Speigazione")
.getResponse();
},
};
--------------------------------------------------
si deve infine aggiungere lo YesHandler
---------------------------------------------------
const skillBuilder = Alexa.SkillBuilders.standard();
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequestHandler,
YesHandler,
geo,
HelpHandler,
ExitHandler,
SessionEndedRequestHandler
)
.addErrorHandlers(ErrorHandler)
.lambda();
venerdì 7 dicembre 2018
UserId su Alexa Skills
Non ho trovato da solo il modo di richiedere ad Alexa l'identificativo dell'utente che utilizza la skill (serve per poi fornire risposte personalizzate basate su DB)...e' venuto incontro il forum
https://forums.developer.amazon.com/questions/193367/userid-alexa.html
la soluzione e'
const userId = handlerInput.requestEnvelope.context.System.user.userId
attenzione che l'userId mostrato non e' relativo all'account Amazon dell'utente. Ogni skill genera un proprio UserID legato all'account Amazon (quest'ultimo rimane sempre mascherato)..se uno stesso utente usa due skill differenti gli UserId che si leggeranno da Alexa risulteranno differenti
https://forums.developer.amazon.com/questions/193367/userid-alexa.html
la soluzione e'
const userId = handlerInput.requestEnvelope.context.System.user.userId
attenzione che l'userId mostrato non e' relativo all'account Amazon dell'utente. Ogni skill genera un proprio UserID legato all'account Amazon (quest'ultimo rimane sempre mascherato)..se uno stesso utente usa due skill differenti gli UserId che si leggeranno da Alexa risulteranno differenti
Alexa Flash Briefing Skill
Un modo rapido di creare una Alexa Skill e' quella di utilizzare un feed RSS mediante la Flash Briefing. Di fatto non e' necessaria nessuna programmazione di un backend lambda ma e' sufficiente creare un file RSS di un certo formato su un sito disponibile in HTTPS ed indicare il link
il feed puo' essere testuale (quindi letto dalla voce di Alexa) oppure un audio MP3
--------------------------------------------------------------
il feed puo' essere testuale (quindi letto dalla voce di Alexa) oppure un audio MP3
--------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8" ?> <rss version="2.0"> <channel> <ttl>30</ttl> <item> <guid>urn:uuid:1335c695-cfb8-4ebb-abbd-80da344efa6b</guid> <title>Prova di riunione veloce</title> <description> prova di trasmissione di Luca Innocenti </description> <pubDate>2018-12-07T09:30:00.0Z</pubDate> </item> </channel> </rss>
--------------------------------------------------------------La cosa interessante e' che una volta che l'utente si iscrive al flash briefing non e' necessario richiamare la skill ma questa viene attivata ogni volta che vengono richieste le notizie (per esempio "Alexa dammi le notizie" fa partire SkyTG24 ma prima viene proposto il Flash Briefing)Esiste un limite di 4000 carattere per la parte descrizione
giovedì 6 dicembre 2018
Alexa Skill Code Generator
Uno strumento per avere uno scheletro completo di una funzione Lambda partendo da uno schema di Alexa e' dato CodeGenerator
E' sufficiente copiare il file Json derivante da Alexa Console ed avere la funzione Lambda (anche troppo dettagliata)
E' sufficiente copiare il file Json derivante da Alexa Console ed avere la funzione Lambda (anche troppo dettagliata)
Permssi DynamoDB per Alexa Skill
Prima di poter utilizzare DynamoDB in connessione con una Skill si devono settare i permessi del database tramite IAM. Si va quindi in IAM Roles e si ha la lista delle proprie skill. Si clicca su quella di interesse. Di default c'e' solo il permesso per l'uso di Cloudwatch.
Si puo' procedere cliccando su Attach Policy e spuntando AmazonDynamoDBFullAccess
Altrimenti si puo' usare un approccio piu' granulare creando una inline policy e selezionando quanto di interesse
Si puo' procedere cliccando su Attach Policy e spuntando AmazonDynamoDBFullAccess
Altrimenti si puo' usare un approccio piu' granulare creando una inline policy e selezionando quanto di interesse
Alexa Dev Day Milano 5 dicembre
Partecipazione alla giornata per sviluppatori Alexa a Milano. Un paio di commenti: posto prestigiosissimo, pensavo che fosse piu' utile
mercoledì 5 dicembre 2018
Deploy Alexa Skill con Ask Cli
Per poter effettuare il deploy di una skill ad Amazon ad ASK Cli e' necessario impostare le proprie credenziali, Si procede con
ask init --aws-setup
a questo punto vengono richieste la Access Key ID e la Secret Access Key che si recuperano dalla console AWS sotto Security Credentials/Access Keys
per poter dialogare con la skill, una volta effettuato il deploy, si usa
ask simulate -l it-IT -t "testo del messaggio"
se la skill e' in inglese si dovra' usare en-US. Nello switch -t si inserisce invece il testo del parlato
ask init --aws-setup
a questo punto vengono richieste la Access Key ID e la Secret Access Key che si recuperano dalla console AWS sotto Security Credentials/Access Keys
per poter dialogare con la skill, una volta effettuato il deploy, si usa
ask simulate -l it-IT -t "testo del messaggio"
se la skill e' in inglese si dovra' usare en-US. Nello switch -t si inserisce invece il testo del parlato
martedì 4 dicembre 2018
Alexa Address API
Esempio di API Address di Alexa
------------------------------------------------------------
{
"interactionModel": {
"languageModel": {
"invocationName": "indirizzo",
"intents": [
{
"name": "GetAddressIntent",
"slots": [],
"samples": [
"dove vivo"
]
},
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
}
],
"types": []
}
}
}
------------------------------------------------------------
------------------------------------------------------------
/* eslint-disable no-console */
const Alexa = require('ask-sdk-core');
const messages = {
WELCOME: 'Benvenuto, cosa vuoi?',
WHAT_DO_YOU_WANT: 'Cosa vuoi chiedere?',
NOTIFY_MISSING_PERMISSIONS: 'Abilita i permessi sulla app Alexa',
NO_ADDRESS: 'Non è stato impostato un indirizzo',
ADDRESS_AVAILABLE: 'Ecco il tuo indirizzo completo: ',
ERROR: 'Errore.',
LOCATION_FAILURE: 'C\'è un errore.Riprova',
GOODBYE: 'Arriverderci',
UNHANDLED: 'This skill doesn\'t support that. Please ask something else.',
HELP: 'Chiedimi dove vivo?',
STOP: 'Stop',
};
const PERMISSIONS = ['read::alexa:device:all:address'];
const LaunchRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
return handlerInput.responseBuilder.speak(messages.WELCOME)
.reprompt(messages.WHAT_DO_YOU_WANT)
.getResponse();
},
};
const GetAddressIntent = {
canHandle(handlerInput) {
const { request } = handlerInput.requestEnvelope;
return request.type === 'IntentRequest' && request.intent.name === 'GetAddressIntent';
},
async handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
const consentToken = requestEnvelope.context.System.user.permissions
&& requestEnvelope.context.System.user.permissions.consentToken;
if (!consentToken) {
return responseBuilder
.speak(messages.NOTIFY_MISSING_PERMISSIONS)
.withAskForPermissionsConsentCard(PERMISSIONS)
.getResponse();
}
try {
const { deviceId } = requestEnvelope.context.System.device;
const deviceAddressServiceClient = serviceClientFactory.getDeviceAddressServiceClient();
const address = await deviceAddressServiceClient.getFullAddress(deviceId);
console.log('Address successfully retrieved, now responding to user.');
let response;
if (address.addressLine1 === null && address.stateOrRegion === null) {
response = responseBuilder.speak(messages.NO_ADDRESS).getResponse();
} else {
const ADDRESS_MESSAGE = `${messages.ADDRESS_AVAILABLE + address.addressLine1}, ${address.stateOrRegion}, ${address.postalCode}`;
response = responseBuilder.speak(ADDRESS_MESSAGE).getResponse();
}
return response;
} catch (error) {
if (error.name !== 'ServiceError') {
const response = responseBuilder.speak(messages.ERROR).getResponse();
return response;
}
throw error;
}
},
};
const SessionEndedRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);
return handlerInput.responseBuilder.getResponse();
},
};
const UnhandledIntent = {
canHandle() {
return true;
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(messages.UNHANDLED)
.reprompt(messages.UNHANDLED)
.getResponse();
},
};
const HelpIntent = {
canHandle(handlerInput) {
const { request } = handlerInput.requestEnvelope;
return request.type === 'IntentRequest' && request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(messages.HELP)
.reprompt(messages.HELP)
.getResponse();
},
};
const CancelIntent = {
canHandle(handlerInput) {
const { request } = handlerInput.requestEnvelope;
return request.type === 'IntentRequest' && request.intent.name === 'AMAZON.CancelIntent';
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(messages.GOODBYE)
.getResponse();
},
};
const StopIntent = {
canHandle(handlerInput) {
const { request } = handlerInput.requestEnvelope;
return request.type === 'IntentRequest' && request.intent.name === 'AMAZON.StopIntent';
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(messages.STOP)
.getResponse();
},
};
const GetAddressError = {
canHandle(handlerInput, error) {
return error.name === 'ServiceError';
},
handle(handlerInput, error) {
if (error.statusCode === 403) {
return handlerInput.responseBuilder
.speak(messages.NOTIFY_MISSING_PERMISSIONS)
.withAskForPermissionsConsentCard(PERMISSIONS)
.getResponse();
}
return handlerInput.responseBuilder
.speak(messages.LOCATION_FAILURE)
.reprompt(messages.LOCATION_FAILURE)
.getResponse();
},
};
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequest,
GetAddressIntent,
SessionEndedRequest,
HelpIntent,
CancelIntent,
StopIntent,
UnhandledIntent,
)
.addErrorHandlers(GetAddressError)
.withApiClient(new Alexa.DefaultApiClient())
.lambda();
Per la skill deve essere abilitato il permesso nella parte Permission di Alexa Console
Si devono abilitare i permessi di localizzazione su https://alexa.amazon.it
(attenzione : per ricevere una risposta di deve inserire l'indirizzo di ogni dispositivo all'interno della app Alexa, non esiste un geocoding basato su IP o su GPS
------------------------------------------------------------
{
"interactionModel": {
"languageModel": {
"invocationName": "indirizzo",
"intents": [
{
"name": "GetAddressIntent",
"slots": [],
"samples": [
"dove vivo"
]
},
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
}
],
"types": []
}
}
}
------------------------------------------------------------
------------------------------------------------------------
/* eslint-disable no-console */
const Alexa = require('ask-sdk-core');
const messages = {
WELCOME: 'Benvenuto, cosa vuoi?',
WHAT_DO_YOU_WANT: 'Cosa vuoi chiedere?',
NOTIFY_MISSING_PERMISSIONS: 'Abilita i permessi sulla app Alexa',
NO_ADDRESS: 'Non è stato impostato un indirizzo',
ADDRESS_AVAILABLE: 'Ecco il tuo indirizzo completo: ',
ERROR: 'Errore.',
LOCATION_FAILURE: 'C\'è un errore.Riprova',
GOODBYE: 'Arriverderci',
UNHANDLED: 'This skill doesn\'t support that. Please ask something else.',
HELP: 'Chiedimi dove vivo?',
STOP: 'Stop',
};
const PERMISSIONS = ['read::alexa:device:all:address'];
const LaunchRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
return handlerInput.responseBuilder.speak(messages.WELCOME)
.reprompt(messages.WHAT_DO_YOU_WANT)
.getResponse();
},
};
const GetAddressIntent = {
canHandle(handlerInput) {
const { request } = handlerInput.requestEnvelope;
return request.type === 'IntentRequest' && request.intent.name === 'GetAddressIntent';
},
async handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
const consentToken = requestEnvelope.context.System.user.permissions
&& requestEnvelope.context.System.user.permissions.consentToken;
if (!consentToken) {
return responseBuilder
.speak(messages.NOTIFY_MISSING_PERMISSIONS)
.withAskForPermissionsConsentCard(PERMISSIONS)
.getResponse();
}
try {
const { deviceId } = requestEnvelope.context.System.device;
const deviceAddressServiceClient = serviceClientFactory.getDeviceAddressServiceClient();
const address = await deviceAddressServiceClient.getFullAddress(deviceId);
console.log('Address successfully retrieved, now responding to user.');
let response;
if (address.addressLine1 === null && address.stateOrRegion === null) {
response = responseBuilder.speak(messages.NO_ADDRESS).getResponse();
} else {
const ADDRESS_MESSAGE = `${messages.ADDRESS_AVAILABLE + address.addressLine1}, ${address.stateOrRegion}, ${address.postalCode}`;
response = responseBuilder.speak(ADDRESS_MESSAGE).getResponse();
}
return response;
} catch (error) {
if (error.name !== 'ServiceError') {
const response = responseBuilder.speak(messages.ERROR).getResponse();
return response;
}
throw error;
}
},
};
const SessionEndedRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);
return handlerInput.responseBuilder.getResponse();
},
};
const UnhandledIntent = {
canHandle() {
return true;
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(messages.UNHANDLED)
.reprompt(messages.UNHANDLED)
.getResponse();
},
};
const HelpIntent = {
canHandle(handlerInput) {
const { request } = handlerInput.requestEnvelope;
return request.type === 'IntentRequest' && request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(messages.HELP)
.reprompt(messages.HELP)
.getResponse();
},
};
const CancelIntent = {
canHandle(handlerInput) {
const { request } = handlerInput.requestEnvelope;
return request.type === 'IntentRequest' && request.intent.name === 'AMAZON.CancelIntent';
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(messages.GOODBYE)
.getResponse();
},
};
const StopIntent = {
canHandle(handlerInput) {
const { request } = handlerInput.requestEnvelope;
return request.type === 'IntentRequest' && request.intent.name === 'AMAZON.StopIntent';
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(messages.STOP)
.getResponse();
},
};
const GetAddressError = {
canHandle(handlerInput, error) {
return error.name === 'ServiceError';
},
handle(handlerInput, error) {
if (error.statusCode === 403) {
return handlerInput.responseBuilder
.speak(messages.NOTIFY_MISSING_PERMISSIONS)
.withAskForPermissionsConsentCard(PERMISSIONS)
.getResponse();
}
return handlerInput.responseBuilder
.speak(messages.LOCATION_FAILURE)
.reprompt(messages.LOCATION_FAILURE)
.getResponse();
},
};
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequest,
GetAddressIntent,
SessionEndedRequest,
HelpIntent,
CancelIntent,
StopIntent,
UnhandledIntent,
)
.addErrorHandlers(GetAddressError)
.withApiClient(new Alexa.DefaultApiClient())
.lambda();
------------------------------------------------------------
Per la skill deve essere abilitato il permesso nella parte Permission di Alexa Console
Si devono abilitare i permessi di localizzazione su https://alexa.amazon.it
(attenzione : per ricevere una risposta di deve inserire l'indirizzo di ogni dispositivo all'interno della app Alexa, non esiste un geocoding basato su IP o su GPS
venerdì 30 novembre 2018
Testare Lambda NodeJS in locale per Alexa Skills
Per testare una Lambda function per Alexa Skills non e' strettamente necessario utilizzare Amazon AWS con l'editor on line
per esempio si parte dallo scaricare il progetto Basic Starter da SkillTemplates (ask new --template --url https://skilltemplates.com/templates.json)
Si crea quindi nella root del progetto (dove e' contenuta la directory Lambda per capirsi) una directory test e si inseriscono i seguenti tre files
test.js
-------------------------------------------------------------------
var lambda = require('../lambda/custom/index.js');
var context = require('./context.js');
var mockEvent = require('./event.json');
var mockContext = new context();
function callback(error, data) {
if(error) {
console.log('error: ' + error);
} else {
console.log(data);
}
}
lambda.handler(mockEvent, mockContext, callback);
-------------------------------------------------------------------
context.js
-------------------------------------------------------------------
module.exports = function() {
return {
succeed: function(result) {
console.log(JSON.stringify(result, null,'\t') );
},
fail: function(err) {
console.log(JSON.stringify(err, null,'\t') );
},
done: function(err, result) {
//console.log("CONTEXT DONE:", err, result);
},
functionName: 'local_functionName',
awsRequestId: 'local_awsRequestId',
logGroupName: 'local_logGroupName',
logStreamName: 'local_logStreamName',
clientContext: 'local_clientContext',
identity: {
cognitoIdentityId: 'local_cognitoIdentityId'
}
};
};
-------------------------------------------------------------------
{
"session": {
"new": false,
"sessionId": "mock_sessionId",
"application": {
"applicationId": "mock_applicationId"
},
"attributes": {},
"user": {
"userId": "mock_userId"
}
},
"request": {
"type": "LaunchRequest",
"requestId": "mock_requestId",
"locale": "en-US",
"timestamp": "2017-09-19T11:46:23Z"
},
"context": {
"AudioPlayer": {
"playerActivity": "IDLE"
},
"System": {
"application": {
"applicationId": "mock_applicationId"
},
"user": {
"userId": "mock_userId"
},
"device": {
"supportedInterfaces": {}
}
}
},
"version": "1.0"
}
-------------------------------------------------------------------
se si lancia il file test.js con
node.js
si ha come riposta
-------------------------------------------------------------------
{ version: '1.0',
response:
{ outputSpeech:
{ type: 'SSML',
ssml: '<speak>Hello there. What is your name?</speak>' },
reprompt: { outputSpeech: [Object] },
shouldEndSession: false,
card:
{ type: 'Simple',
title: 'Example Card Title',
content: 'Example card body content.' } },
userAgent: 'ask-node/2.0.5 Node/v10.4.1',
sessionAttributes: {} }
-------------------------------------------------------------------
per esempio si parte dallo scaricare il progetto Basic Starter da SkillTemplates (ask new --template --url https://skilltemplates.com/templates.json)
Si crea quindi nella root del progetto (dove e' contenuta la directory Lambda per capirsi) una directory test e si inseriscono i seguenti tre files
test.js
-------------------------------------------------------------------
var lambda = require('../lambda/custom/index.js');
var context = require('./context.js');
var mockEvent = require('./event.json');
var mockContext = new context();
function callback(error, data) {
if(error) {
console.log('error: ' + error);
} else {
console.log(data);
}
}
lambda.handler(mockEvent, mockContext, callback);
-------------------------------------------------------------------
context.js
-------------------------------------------------------------------
module.exports = function() {
return {
succeed: function(result) {
console.log(JSON.stringify(result, null,'\t') );
},
fail: function(err) {
console.log(JSON.stringify(err, null,'\t') );
},
done: function(err, result) {
//console.log("CONTEXT DONE:", err, result);
},
functionName: 'local_functionName',
awsRequestId: 'local_awsRequestId',
logGroupName: 'local_logGroupName',
logStreamName: 'local_logStreamName',
clientContext: 'local_clientContext',
identity: {
cognitoIdentityId: 'local_cognitoIdentityId'
}
};
};
-------------------------------------------------------------------
event.json
-------------------------------------------------------------------{
"session": {
"new": false,
"sessionId": "mock_sessionId",
"application": {
"applicationId": "mock_applicationId"
},
"attributes": {},
"user": {
"userId": "mock_userId"
}
},
"request": {
"type": "LaunchRequest",
"requestId": "mock_requestId",
"locale": "en-US",
"timestamp": "2017-09-19T11:46:23Z"
},
"context": {
"AudioPlayer": {
"playerActivity": "IDLE"
},
"System": {
"application": {
"applicationId": "mock_applicationId"
},
"user": {
"userId": "mock_userId"
},
"device": {
"supportedInterfaces": {}
}
}
},
"version": "1.0"
}
-------------------------------------------------------------------
node.js
si ha come riposta
-------------------------------------------------------------------
response:
{ outputSpeech:
{ type: 'SSML',
ssml: '<speak>Hello there. What is your name?</speak>' },
reprompt: { outputSpeech: [Object] },
shouldEndSession: false,
card:
{ type: 'Simple',
title: 'Example Card Title',
content: 'Example card body content.' } },
userAgent: 'ask-node/2.0.5 Node/v10.4.1',
sessionAttributes: {} }
-------------------------------------------------------------------
questo e' il file che normalmente Lambda passa a Alexa per poi essere trasformato in messaggio vocale ed spedito al dispositivo Alexa
In pratica viene generata l'intent LaunchRequest (event.json) della funzione Lambda che corrisponde a
-------------------------------------------------------------------
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const speechText = 'Hello there. What is your name?';
const repromptText = 'Can you tell me your name?';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(repromptText)
.withSimpleCard('Example Card Title', "Example card body content.")
.getResponse();
},
};
-------------------------------------------------------------------
giovedì 29 novembre 2018
Alexa Ask Cli ed Address API
Per installare l'interfaccia di programmazione di Alexa a linea di comando (senza utilizzare gli editor Web online di Alexa ed Aws Lambda) si puo' usare Ask Cli che si installa tramite npm con
npm install -g ask-cli
verra' al termine richiesto di loggardi con la propria user e password su Amazon tramite un finestra di autenticazione web
Nel caso in cui ask risponda con un messaggio di errore del tipo
vuol dire che l'interfaccia Ask Cli non riesce ad auntenticarsi sui server Amazon.
ask new --template --url https://skilltemplates.com/templates.json
(altrimenti se si crea una skill ex novo
ask new --skill-name test --lambda-name test
)
npm install -g ask-cli
verra' al termine richiesto di loggardi con la propria user e password su Amazon tramite un finestra di autenticazione web
Nel caso in cui ask risponda con un messaggio di errore del tipo
InvalidClientTokenId: The security token included in the request is invalid.
La soluzione e' riprocedere con il comando
ask init
(attenzione se non gia' configurato a questo punto verra' chiesto di inserire le proprie credenziali.
Su alcuni SO la procedura funziona senza problemi...in altri casi si deve usare ask init --no-browser a causa di un errore di Unhandled promise rejection)
(attenzione se non gia' configurato a questo punto verra' chiesto di inserire le proprie credenziali.
Su alcuni SO la procedura funziona senza problemi...in altri casi si deve usare ask init --no-browser a causa di un errore di Unhandled promise rejection)
A questo punto si inizia con l'esempio
ask new --template --url https://skilltemplates.com/templates.json
(altrimenti se si crea una skill ex novo
ask new --skill-name test --lambda-name test
)
Dal menu si sceglie Device Location API Starter
si entra quindi nella nuova directory del template e si inserisce
ask deploy
ogni volta che e' stato modificato il codice in locale per effettuare l'upload della skill su Amazon si deve effettuare il deploy. Si puo' effettuare anche un deploy parziale della sola funzione Lambda con
ask lambda upload
Le Address API richiedono una autorizzazione esplicita da parte dell'utente per l'utilizzo. Si deve quindi andare su https://alexa.amazon.it cliccare Le tue Skill, Skill attivate e cercare device-location-api-starter attivare la Skill con l'autorizzazione
a questo punto si e' presentato un problema. La skill era priva di endpoint perche' non e' stata creata la corrispondente funziona Lambda. Ho quindi proceduto a creare una Lambda copiandoci poi sopra il file index.js che ho trovato nel template
----------------------------------------------
/* eslint-disable func-names */
/* eslint-disable no-console */
const Alexa = require('ask-sdk-core');
const APP_NAME = "Location Starter"
const messages = {
NOTIFY_MISSING_PERMISSIONS: 'Please enable device location permissions in the Amazon Alexa app.',
NO_ADDRESS: 'It looks like you don\'t have an address set. You can set your address from the companion app.',
ERROR: 'Uh Oh. Looks like something went wrong.'
};
const DEVICE_LOCATION_PERMISSION = 'read::alexa:device:all:address';
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const speechText = `Hello, welcome to ${APP_NAME}. You can ask about device address by saying what is my location`;
const repromptText = 'What would you like to know?';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(repromptText)
.withSimpleCard(APP_NAME, speechText)
.getResponse();
},
};
const DeviceLocationIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'DeviceLocationIntent';
},
async handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
try {
const { deviceId } = requestEnvelope.context.System.device;
const deviceAddressServiceClient = serviceClientFactory.getDeviceAddressServiceClient();
const address = await deviceAddressServiceClient.getFullAddress(deviceId);
let response;
if (address == undefined || (address.addressLine1 === null && address.stateOrRegion === null)) {
response = responseBuilder.speak(messages.NO_ADDRESS).getResponse();
return response;
} else {
const completeAddress = `${address.addressLine1}, ${address.stateOrRegion}, ${address.postalCode}`;
const response = `Your complete address is, ${completeAddress}`;
return handlerInput.responseBuilder
.speak(response)
.withSimpleCard(APP_NAME, response)
.getResponse();
}
} catch (error) {
console.log(JSON.stringify(error));
if (error.statusCode == 403) {
return responseBuilder
.speak(messages.NOTIFY_MISSING_PERMISSIONS)
.withAskForPermissionsConsentCard([DEVICE_LOCATION_PERMISSION])
.getResponse();
}
console.log(JSON.stringify(error));
const response = responseBuilder.speak(messages.ERROR).getResponse();
return response;
}
},
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speechText = 'You can ask for the address';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse();
},
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'
|| handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speechText = 'Goodbye!';
return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
},
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);
return handlerInput.responseBuilder.getResponse();
},
};
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`Error handled: ${error.message}`);
return handlerInput.responseBuilder
.speak('Sorry, I can\'t understand the command. Please say again.')
.reprompt('Sorry, I can\'t understand the command. Please say again.')
.getResponse();
},
};
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequestHandler,
DeviceLocationIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler
)
.addErrorHandlers(ErrorHandler)
.withApiClient(new Alexa.DefaultApiClient())
.lambda();
----------------------------------------------
questo e' il file skill.json. Evidenziato in giallo la richiesta dei permessi
----------------------------------------------
{
"manifest": {
"publishingInformation": {
"locales": {
"en-US": {
"summary": "Sample Short Description",
"examplePhrases": [
"Alexa open location starter",
"Alexa ask location starter about my adrees"
],
"keywords": [
"keyword1",
"keyword2",
"keyword3"
],
"name": "device-location-api-starter",
"description": "Sample Full Description",
"smallIconUri": "https://s3.amazonaws.com/cdn.dabblelab.com/img/skill-template-default-sm.png",
"largeIconUri": "https://s3.amazonaws.com/cdn.dabblelab.com/img/skill-template-default-lg.png"
}
},
"isAvailableWorldwide": true,
"testingInstructions": "No special instructions.",
"category": "EDUCATION_AND_REFERENCE",
"distributionCountries": []
},
"apis": {
"custom": {
"endpoint": {
"sourceDir": "lambda/custom"
}
}
},
"manifestVersion": "1.0",
"permissions": [
{
"name": "alexa::devices:all:address:full:read"
}
],
"privacyAndCompliance": {
"allowsPurchases": false,
"locales": {
"en-US": {
"termsOfUseUrl": "http://dabblelab.com/terms",
"privacyPolicyUrl": "http://dabblelab.com/privacy"
}
},
"isExportCompliant": true,
"containsAds": false,
"isChildDirected": false,
"usesPersonalInfo": false
}
}
}
----------------------------------------------
Di default non viene assunto l'indirizzo del dispositivo con quello configurato nel profilo Amazon. Si deve settare in modo esplicito la posizione nella app sul telefono Alexa Amazon per ogni dispositivo
venerdì 23 novembre 2018
Problemi di Alexa
un po' di problemi con Alexa...alcuni esempi
"funzionale" viene interpretato come "funziona le"
"cfr" (pronunciato ciefferre) viene interpretato come "c'è fede"
"centro" alcune volte viene interpretato come "cento"
"sesto" viene intepretato come "6o"
questi problemi sono debuggabili solo attraverso la Alexa Developer Console tramite il file JSON che traduce il parlato nel file interpretato
"funzionale" viene interpretato come "funziona le"
"cfr" (pronunciato ciefferre) viene interpretato come "c'è fede"
"centro" alcune volte viene interpretato come "cento"
"sesto" viene intepretato come "6o"
questi problemi sono debuggabili solo attraverso la Alexa Developer Console tramite il file JSON che traduce il parlato nel file interpretato
lunedì 19 novembre 2018
HTTP Async request in Alexa
Per fare interagire Alexa con server esterni la cosa piu' semplice e' usare il modulo http perche' e' un built-in e non necessita di supporto esterno. Si dichiara l'uso all'inizio della Lambda function
--------------------------------------------------------------------
const Alexa = require('ask-sdk');
var http = require('http');
--------------------------------------------------------------------
si crea poi una funzione di richiesta http. Attenzione: questa funzione e' di tipo asincrono e si deve inserire la Promise per fare in modo che il risultato entri nello scope della funzione principale
--------------------------------------------------------------------
function httpGet() {
return new Promise(((resolve, reject) => {
var options = {
host: '80.211.xxxx,xxxx', //solo ip o nome dominio ..niente http od https prima
port: 80,
path: '/dpc.php',
method: 'GET',
};
const request = http.request(options, (response) => {
response.setEncoding('utf8');
let returnData = '';
response.on('data', (chunk) => {
returnData += chunk;
});
response.on('end', () => {
resolve(returnData);
console.log("returnData "+returnData);
});
response.on('error', (error) => {
reject(error);
});
});
request.end();
}));
}
--------------------------------------------------------------------
--------------------------------------------------------------------
const Alexa = require('ask-sdk');
var http = require('http');
--------------------------------------------------------------------
si crea poi una funzione di richiesta http. Attenzione: questa funzione e' di tipo asincrono e si deve inserire la Promise per fare in modo che il risultato entri nello scope della funzione principale
--------------------------------------------------------------------
function httpGet() {
return new Promise(((resolve, reject) => {
var options = {
host: '80.211.xxxx,xxxx', //solo ip o nome dominio ..niente http od https prima
port: 80,
path: '/dpc.php',
method: 'GET',
};
const request = http.request(options, (response) => {
response.setEncoding('utf8');
let returnData = '';
response.on('data', (chunk) => {
returnData += chunk;
});
response.on('end', () => {
resolve(returnData);
console.log("returnData "+returnData);
});
response.on('error', (error) => {
reject(error);
});
});
request.end();
}));
}
--------------------------------------------------------------------
Il custom intent che deve gestire la funzione asincrona sopra indicata deve essere modificato ponendo async nell'handler e deve aspettare il ritorno della funzione httpGet prima di procedere in modo da popolare in modo corretto la variabile response
--------------------------------------------------------------------
const geo = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return (request.type === 'IntentRequest'
&& request.intent.name === 'geo');
},
async handle(handlerInput) {
const response = await httpGet();
const { intent } = handlerInput.requestEnvelope.request;
const activity = intent.slots.luogo.value;
var speechOutput = response;
if (activity == "firenze")
{
//speechOutput = "firenze si trova in toscana";
}
if (activity == "milano")
{
speechOutput = "milano si trova in lombardia"
}
return handlerInput.responseBuilder
.speak(speechOutput)
.getResponse();
},
};
--------------------------------------------------------------------
Iscriviti a:
Post (Atom)
Debugger integrato ESP32S3
Aggiornamento In realta' il Jtag USB funziona anche sui moduli cinesi Il problema risiede nell'ID USB della porta Jtag. Nel modulo...
-
In questo post viene indicato come creare uno scatterplot dinamico basato da dati ripresi da un file csv (nel dettaglio il file csv e' c...
-
La scheda ESP32-2432S028R monta un Esp Dev Module con uno schermo TFT a driver ILI9341 di 320x240 pixels 16 bit colore.Il sito di riferiment...
-
Questo post e' a seguito di quanto gia' visto nella precedente prova Lo scopo e' sempre il solito: creare un sistema che permet...