0
votes

I'm new to alexa, nodejs and coding in general but i am currently trying to create a skill to find machine states from my dynamodb table using date and time.

I currently have set up my skill to get alexa and lambda to understand my slot values but im not sure how i can use these values for a dynamodb query and have alexa call out the state for that corresponding time

My table is setup with a primary and sort key which are date and time and i have a third column for machine state.

I'm not sure if i should set up a custom slot for machine state as it would be easy to do since there are only 4 possible states.

Here is the code i currently have, please feel free to clean parts up or explain how you came to my solution.

const awsSDK = require('aws-sdk');
const updatedincident = 'updatedincident';
const docClient = new awsSDK.DynamoDB.DocumentClient();

var AWSregion = 'us-east-1';  // us-east-1
var AWS = require('aws-sdk');
var dbClient = new AWS.DynamoDB.DocumentClient();
AWS.config.update({
    region: "'us-east-1'"
});

const params = {
    TableName: "updatedincident",
    Key:{ date: "2018-03-28",
      time: "04:23",
      state: "Blocked Primary"
    }
};

let GetMachineStateIntent = (context, callback) => {    
  var params = {
    TableName: "updatedincident",
    Key: {
      date: "2018-03-28",
      time: "04:23",
      state: "Blocked Primary"
    }
  };
  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 your database",
          endSession: false
       });
    } else {
       console.log('loaded data item:\n' + JSON.stringify(data.Item, null, 2));
       // assuming the item has an attribute called "state"..
       sendResponse(context, callback, {
          output: data.Item.state,
          endSession: false
       });
    }
  });
};


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, I can tell you about the status of machines at different times. what data are you looking for?",
        endSession: false
      });
  }
    else if (request.type === "IntentRequest") {
      if (request.type === "IntentRequest" 
      // make sure the name of the intent matches the one in interaction model
   && request.intent.name == "GetMachineStateIntent") {
    var dateSlot = request.intent.slots.Date != null ?
                   request.intent.slots.Date.value : "unknown date";
    var timeSlot = request.intent.slots.Time != null ?
                   request.intent.slots.Time.value : "unknown time";
                   
    // respond with speech saying back what the skill thinks the user requested
    sendResponse(context, callback, {
       output: "You wanted the machine state at " 
              + timeSlot + " on " + dateSlot,
       endSession: false
    });
    
    
    var ConfirmationHandlers = Alexa.CreateStateHandler(states.CONFIRMATIONMODE, {
    'YesIntent': function () { 
        this.emit("GetMachineStateIntent"); 
    }, 
    'AMAZON.NoIntent': function () { 
        this.response.speak(GetMachineStateIntent); 
        this.emit(':responseReady'); 
    } 
    });


}
      let options = {};         
      if (request.intent.name === "GetMachineStateIntent") {
        GetMachineStateIntent(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 incidents that have happened or states of machines in the past",
          reprompt: "what can I help you with?",
          endSession: false
        });
      }
      else {
        sendResponse(context, callback, {
          output: "I don't know that one! please try again!",
          endSession: false
        });
      }
    }
    else if (request.type === "SessionEndedRequest") {
      sendResponse(context, callback, ""); // no response needed
    }
    else {
      // an unexpected request type received.. just say I don't know..
      sendResponse(context, callback, {
          output: "I don't know that one! please try again!",
          endSession: false
      });
    }
  } 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");
    }
  }
};

Update ************

Response im getting currently in Skill I/O in alexa

	"request": {
		"type": "IntentRequest",
		"requestId": "amzn1.echo-api.request.c515c39e-4ce1-4f28-97ed-30536fa593b9",
		"timestamp": "2018-05-15T08:55:25Z",
		"locale": "en-GB",
		"intent": {
			"name": "GetMachineStateIntent",
			"confirmationStatus": "NONE",
			"slots": {
				"Time": {
					"name": "Time",
					"value": "04:23",
					"confirmationStatus": "NONE"
				},
				"Date": {
					"name": "Date",
					"value": "2018-03-28",
					"confirmationStatus": "NONE"
				}
			}
		},
		"dialogState": "STARTED"
	}
}
1

1 Answers

1
votes

A couple of observations:

First In the branch of code that handles the GetMachineStateIntent you had added code to create state handlers but they weren't properly wired. At best that code would do nothing, at worst it might actually cause some problems. Remove that.

    // take the following lines of code out
    var ConfirmationHandlers = Alexa.CreateStateHandler(states.CONFIRMATIONMODE, {
    'YesIntent': function () { 
        this.emit("GetMachineStateIntent"); 
    }, 
    'AMAZON.NoIntent': function () { 
        this.response.speak(GetMachineStateIntent); 
        this.emit(':responseReady'); 
    } 
    }); 

Second The query parameters that you are passing to your DynamoDB query are hard-coded. That means you are always going to get the same result. You need to pass the values of the slots your receive in the intent into the parameters for the query.

  var params = {
    TableName: "updatedincident",
    Key: {
      date: "2018-03-28",
      time: "04:23",
      state: "Blocked Primary"
    }
  };

Those are hard-coded. You only need to specify the primary key (date) and the sort key ('time'), so you can remove the state. And for date and time you have to change the values to be dynamically passed in from the dateSlot and timeSlot.


Third In the branch of code that handles requests of type IntentRequest you are handling the GetMachineStateIntent twice and the code is a bit redundant. Rewrite that as follows:

   ...
} else if (request.type === "IntentRequest") {
  if (request.intent.name === "GetMachineStateIntent") {
    GetMachineStateIntent(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 incidents that have happened or states of machines in the past",
      reprompt: "what can I help you with?",
      endSession: false
    });
  }
  else {
    sendResponse(context, callback, {
      output: "I don't know that one! please try again!",
      endSession: false
    });
  } 
} else if (request.type === "SessionEndedRequest") {

Fourth This is the most difficult to explain. When you query the machine state, you will be providing a date and a time but presumably, the machine state might not be stored in the database with a time stamp that matches the time value in your query exactly. So you have to do a query that is essentially the equivalent of "what is the machine state on date X, at the most recent time before or equal to Y"

It is that "most recent time before or equal to Y" part that is tricky. You have to create a query on your table that expresses that and you will also have to change the way you store timestamps in the table, from string, to a numeric format so that you can easily express this inequality.

I'm going to show here just how to pass the dateSlot and timeSlot to make the query but I recommend you look into getting this to work (perhaps ask specific questions if you get stuck).


Here's your code with the modifications I mentioned:

const awsSDK = require('aws-sdk');
const updatedincident = 'updatedincident';
const docClient = new awsSDK.DynamoDB.DocumentClient();

var AWSregion = 'us-east-1';  // us-east-1
var AWS = require('aws-sdk');
var dbClient = new AWS.DynamoDB.DocumentClient();
AWS.config.update({
    region: "'us-east-1'"
});

let GetMachineStateIntent = (context, callback, dateSlot, timeSlot) => {    
  var params = {
    TableName: "updatedincident",
    KeyConditionExpression: '#d = :dVal and #t < :tVal',
    ExpressionAttributeValues: {
       ':dVal': dateSlot,
       ':tVal': timeSlot
    },
    ExpressionAttributeNames: {
       '#d': 'date',
       '#t': 'time'
    },
    ScanIndexForward: false // gets values in reverse order by time 
  };
  dbClient.query(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 your database",
          endSession: false
       });
    } else {
       let dataItem = data.Items[0];           
console.log('loaded data item:\n' + JSON.stringify(dataItem, null, 2));
       // assuming the item has an attribute called "state"..
       sendResponse(context, callback, {
          output: dataItem.state,
          endSession: false
       });
    }
  });
};


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, I can tell you about the status of machines at different times. what data are you looking for?",
        endSession: false
      });
    } else if (request.type === "IntentRequest") {
      if (request.intent.name === "GetMachineStateIntent") {
        var dateSlot = request.intent.slots.Date != null 
             ? request.intent.slots.Date.value : null;
        var timeSlot = request.intent.slots.Time != null
             ? request.intent.slots.Time.value : null;
        // pass the slot values to the GetMachineStateIntent function
        GetMachineStateIntent(context, callback, dateSlot, timeSlot);
      } 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 incidents that have happened or states of machines in the past",
          reprompt: "what can I help you with?",
          endSession: false
        });
      }
      else {
        sendResponse(context, callback, {
          output: "I don't know that one! please try again!",
          endSession: false
        });
      }
    }
    else if (request.type === "SessionEndedRequest") {
      sendResponse(context, callback, ""); // no response needed
    }
    else {
      // an unexpected request type received.. just say I don't know..
      sendResponse(context, callback, {
          output: "I don't know that one! please try again!",
          endSession: false
      });
    }
  } 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");
    }
  }
};