2
votes

I'm trying to create a skill with Alexa to read data from my DynamoDB table using either the scan or query function (or both).

The columns in my table are date, time and film name.

I'm new to this but I've managed to link my Lambda function to Alexa. I have also created a separate Lambda function that will recall data from my table when i configure test events, so when I input a specific date it will recall the corresponding film and time. However now I want to implement this into Alexa and am not sure how.

Here is my current code

console.log('Loading function');

var AWSregion = 'us-east-1';  // us-east-1
var AWS = require('aws-sdk');
var dclient = new AWS.DynamoDB.DocumentClient();

var getItems = (event, context, callback)=>{
    
    dclient.get(event.params,(error,data)=>{
        if(error){
            callback(null,"error occurerd");
        }
        else{
            callback(null,data);
        }
    });
};

exports.handler = getItems;

exports.handler = (event, context, callback) => {
    try {

        var request = event.request;

        if (request.type === "LaunchRequest") {
            context.succeed(buildResponse({
                speechText: "Welcome to H.S.S.M.I skill, what would you like to find",
                repromptText: "I repeat, Welcome to my skill, what would you like to find",
                endSession: false
            }));
        }
        else if (request.type === "IntentRequest") {
            let options = {};         


            if (request.intent.name === "cinema") {
            } else if (request.intent.name === "AMAZON.StopIntent" || request.intent.name === "AMAZON.CancelIntent") {
                options.speechText = "ok, good bye.";
                options.endSession = true;
                context.succeed(buildResponse(options));
            }
             else if (request.intent.name === "AMAZON.HelpIntent") {
                options.speechText = "My skill will read your table depending on what is asked. For example, you can ask what about a specific date. Please refer to skill description for all possible utterences.";
                options.repromptText = "What is the data sign you want to know  about today? If you want to exit from my  skill please say stop or cancel.";
                options.endSession = false;
                context.succeed(buildResponse(options));
            }
            else {
                context.fail("Unknown Intent");
            }
        }

        else if (request.type === "SessionEndedRequest") {
            options.endSession = true;
            context.succeed();
        }
        else {
            context.fail("Unknown Intent type");
        }




    } catch (e) {

    }


};

function buildResponse(options) {
    var response = {
        version: "1.0",
        response: {
            outputSpeech: {
                "type": "SSML",
                "ssml": `<speak><prosody rate="slow">${options.speechText}</prosody></speak>`
            },

            shouldEndSession: options.endSession
        }
    };

    if (options.repromptText) {
        response.response.reprompt = {
            outputSpeech: {
                "type": "SSML",
                "ssml": `<speak><prosody rate="slow">${options.repromptText}</prosody></speak>`
            }
        };
    }

    return response;
}

function readDynamoItem(params, callback) {
    
    var AWS = require('aws-sdk');
    AWS.config.update({region: AWSregion});
    var dynamodb = new AWS.DynamoDB();
    console.log('reading item from DynamoDB table');

    dynamodb.scan(params, function(err, data) {
        if (err) console.log(err, err.stack); // an error occurred
        else{
            console.log(data); // successful response
            callback(JSON.stringify(data));
        }
    });
    var docClient = new AWS.DynamoDB.DocumentClient();
    //Get item by key
    docClient.get(params, (err, data) => {
        if (err) {
            console.error("Unable to read item. Error JSON:", JSON.stringify(err, null, 2));
        } else {
            console.log("GetItem succeeded:", JSON.stringify(data, null, 2));

            callback(data.Item.message);  // this particular row has an attribute called message

        }
    });

}

///////////////////////////////////////////////////////////////////////////////

And here is my DBHandler

var AWS = require('aws-sdk');
AWS.config.update({
    region: "'us-east-1'"
});

let docClient = new AWS.DynamoDB.DocumentClient();

var table = "Cinema";

let getItems = (Id,callback) => {   
  
    var params = {
        TableName: "cinema",
        Key: {
            "date": "2018-01-04",
            "filmname": "rugrats"
        }
    };

    docClient.get(params, function (err, data) {
        callback(err, data);
    });

};
module.exports = {
    getItems
};

I can launch the app and I have a Lambda function that works on its own when I configure the test event to find a movie from a certain date but I cannot get it to work with Alexa.

Can anyone help me or indicate where I'm going wrong

UPDATE*************

Here is how my intent schema is setup

{
  "intents": [
    {
      "slots": [
        {
          "name": "sincedate",
          "type": "AMAZON.DATE"
        }
      ],
      "intent": "date"
    },
    {
      "intent": "AMAZON.CancelIntent"
    },
    {
      "intent": "AMAZON.HelpIntent"
    },
    {
      "intent": "AMAZON.StopIntent"
    },
    {
      "slots": [
        {
          "name": "sincedate",
          "type": "AMAZON.DATE"
        }
      ],
      "intent": "cinema"
    },
    {
      "intent": "MyIntent"
    }
  ]
}
1

1 Answers

1
votes

Part 1 - Permissions

One possible reason why you may not be able to read from DynamoDB in your skill is because of permissions.

You should double check the IAM role that you assigned to your skill lambda to make sure that it has permissions to read from DynamoDB.

Some references:

Part 2 - The actual Skill handler Lambda

I re-read your question and I'm confused about the part where you talk about setting up a second Lambda to read data from Dynamo. You shouldn't have two Lambda functions - just one that will handle requests from Alexa and in that function you should return your response to Alexa, after making a call to Dynamo.

Now, to the specifics. In your first code snippet, you have:

exports.handler = getItems;

exports.handler = (event, context, callback) => {
    // here you have your handler to handle alexa responses 
}

One thing that stands out immediately is that you are first setting the handler to getItems and then resetting back to the handler that is supposed to respond to the Alexa.

The other thing that I'm guessing is happening is that sometimes the skill works, like when you first launch it, and probably if you say Help but in other cases it doesn't, like when you send it the "cinema" intent.

This is because the entry point from the Alexa request to your skill is the exports.handler which is basically defined as a function with three parameters (it's like the void main(int argc,char *argv[]) of a c program).

The first parameter - event is the input to your skill. Alexa will supply information here such as the request type, if it's an intent, the intent name, session information etc.

The second and third parameters - context and callback are what you use to return control from your lambda function, depending on the node runtime. For Note v4 and newer, you use callback, for older versions you use context.

You can use something like this to send a success response back:

if(typeof callback === 'undefined') {
     context.succeed("successful response message");
} else {
     callback(null, "successful response message");
}

And something like this to send a failure response

if(typeof callback === 'undefined') {
     context.fail("failure response message");
} else {
     callback("failure response message", null);
}

Putting it all together, here's a basic Lambda handler that always responds to your skill invocations:

function sendResponse(context, callback, responseOptions) {
  if(typeof callback === 'undefined') {
    context.succeed(buildResponse(responseOptions));
  } else {
    callback(null, buildResponse(responseOptions));
  }
}

function buildResponse(options) {
  var alexaResponse = {
    version: "1.0",
    response: {
      outputSpeech: {
        "type": "SSML",
        "ssml": `<speak><prosody rate="slow">${options.output}</prosody></speak>`
      },
      shouldEndSession: options.endSession
    }
  };
  if (options.repromptText) {
    alexaResponse.response.reprompt = {
      outputSpeech: {
        "type": "SSML",
        "ssml": `<speak><prosody rate="slow">${options.reprompt}</prosody></speak>`
      }
    };
  }
  return alexaResponse;
}

exports.handler = (event, context, callback) => {
  try {
    var request = event.request;
    if (request.type === "LaunchRequest") {
      sendResponse(context, callback, {
        output: "welcome to my skill. what do you want to find?",
        endSession: false
      });
    }
    else if (request.type === "IntentRequest") {
      let options = {};         
      if (request.intent.name === "cinema") {
        // this is where we will wire up the dynamo call
        // for now, just send a simple response and end the session
        sendResponse(context, callback, {
          output: "cinema not implemented yet!",
          endSession: true
        });
      } else if (request.intent.name === "AMAZON.StopIntent" || request.intent.name === "AMAZON.CancelIntent") {
        sendResponse(context, callback, {
          output: "ok. good bye!",
          endSession: true
        });
      }
      else if (request.intent.name === "AMAZON.HelpIntent") {
        sendResponse(context, callback, {
          output: "you can ask me about films",
          reprompt: "what can I help you with?"
          endSession: false
        });
      }
      else {
        sendResponse(context, callback, {
          output: "I don't know that one! Good bye!",
          endSession: true
        });
      }
    }
    else if (request.type === "SessionEndedRequest") {
      sendResponse(context, callback, ""); // no response needed
    }
    else {
      // un unexpected request type received.. just say I don't know..
      sendResponse(context, callback, {
          output: "I don't know that one! Good bye!",
          endSession: true
      });
    }
  } catch (e) {
    // handle the error by logging it and sending back an failure
    console.log('Unexpected error occurred in the skill handler!', e);
    if(typeof callback === 'undefined') {
       context.fail("Unexpected error");
    } else {
       callback("Unexpected error");
    }
  }
};

Getting to this point, the skill should be functional and it should be able to handle all your requests. Assuming that you have configured your interaction model correctly and that the cinema intent gets sent to your skill, then here's how you can use dynamo client to respond with data from a table.

var AWSregion = 'us-east-1';  // us-east-1
var AWS = require('aws-sdk');
var dbClient = new AWS.DynamoDB.DocumentClient();

let handleCinemaIntent = (context, callback) => {    
  let params = {
    TableName: "cinema",
    Key: {
        "date": "2018-01-04",
        "filmname": "rugrats"
    }
  };
  dbClient.get(params, function (err, data) {
    if (err) {
       // failed to read from table for some reason..
       console.log('failed to load data item:\n' + JSON.stringify(err, null, 2));
       // let skill tell the user that it couldn't find the data 
       sendResponse(context, callback, {
          output: "the data could not be loaded from Dynamo",
          endSession: true
       });
    } else {
       console.log('loaded data item:\n' + JSON.stringify(data.Item, null, 2))
       // assuming the item has an attribute called "message"..
       sendResponse(context, callback, {
          output: data.Item.message,
          endSession: true
       });
    }
  });
};


function sendResponse(context, callback, responseOptions) {
  if(typeof callback === 'undefined') {
    context.succeed(buildResponse(responseOptions));
  } else {
    callback(null, buildResponse(responseOptions));
  }
}

function buildResponse(options) {
  var alexaResponse = {
    version: "1.0",
    response: {
      outputSpeech: {
        "type": "SSML",
        "ssml": `<speak><prosody rate="slow">${options.output}</prosody></speak>`
      },
      shouldEndSession: options.endSession
    }
  };
  if (options.repromptText) {
    alexaResponse.response.reprompt = {
      outputSpeech: {
        "type": "SSML",
        "ssml": `<speak><prosody rate="slow">${options.reprompt}</prosody></speak>`
      }
    };
  }
  return alexaResponse;
}

exports.handler = (event, context, callback) => {
  try {
    var request = event.request;
    if (request.type === "LaunchRequest") {
      sendResponse(context, callback, {
        output: "welcome to my skill. what do you want to find?",
        endSession: false
      });
    }
    else if (request.type === "IntentRequest") {
      let options = {};         
      if (request.intent.name === "cinema") {
        handleCinemaIntent(context, callback);
      } else if (request.intent.name === "AMAZON.StopIntent" || request.intent.name === "AMAZON.CancelIntent") {
        sendResponse(context, callback, {
          output: "ok. good bye!",
          endSession: true
        });
      }
      else if (request.intent.name === "AMAZON.HelpIntent") {
        sendResponse(context, callback, {
          output: "you can ask me about films",
          reprompt: "what can I help you with?"
          endSession: false
        });
      }
      else {
        sendResponse(context, callback, {
          output: "I don't know that one! Good bye!",
          endSession: true
        });
      }
    }
    else if (request.type === "SessionEndedRequest") {
      sendResponse(context, callback, ""); // no response needed
    }
    else {
      // un unexpected request type received.. just say I don't know..
      sendResponse(context, callback, {
          output: "I don't know that one! Good bye!",
          endSession: true
      });
    }
  } catch (e) {
    // handle the error by logging it and sending back an failure
    console.log('Unexpected error occurred in the skill handler!', e);
    if(typeof callback === 'undefined') {
       context.fail("Unexpected error");
    } else {
       callback("Unexpected error");
    }
  }
};