Visualizzazione post con etichetta Alexa. Mostra tutti i post
Visualizzazione post con etichetta Alexa. Mostra tutti i post

giovedì 31 gennaio 2019

Beta testing Alexa Skills

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

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();
---------------------------------------------------------------------------


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

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();
      
  },
};

===========================================

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





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

--------------------------------------------------------------
<?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)

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


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

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




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'
        }
    };
};
-------------------------------------------------------------------

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"
}
-------------------------------------------------------------------

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: {} }
-------------------------------------------------------------------

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


InvalidClientTokenId: The security token included in the request is invalid.

vuol dire che l'interfaccia Ask Cli non riesce ad auntenticarsi sui server Amazon.
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)

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

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();
  }));
}
--------------------------------------------------------------------

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();
  },
};
--------------------------------------------------------------------

Geologi

  E so anche espatriare senza praticamente toccare strada asfaltata