Hardware components | ||||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
|
I'm originally from Brazil. More specifically from Rio Grande do Sul state, a place known for its barbecues. It is a family tradition.
Since I moved to US, I had to adapt my tradition and start cooking on the regular gas grills. And first thing I've learned: you turn the beef only once to keep the meat juice.
Cool, but there was a problem. I reaaaaaaly like to enjoy the barbecue. As you can imagine, many times, when talking and joking with friends and family I simply lost the time. As a result, the meat would be too raw or too cooked. Not only that, my hands were normally full or dirty, so I could not use paper or my phone to track the time.
Then the idea came! Echo is the perfect barbecue pal. Not only it can play music for my guests, but guess what? It can keep track of my cooking for me.
So, as a good geek, I decided to develop this new skill to help me with it.
The SolutionActually, it is very simple. I tell Grill Pal when I am adding any food to the grill and how many minutes it should be there before turning or serving. Then, I can keep asking Grill Pal if it is time to turn or serve. That's it.
LimitationsUnfortunately, when this project was created, Echo did not offer any options for push notifications. When it is available, I will do another version, so Alexa can tell us when to turn, so we need to keep asking for it.
Amazon WS Lamba JS File
JavaScriptIt connects to an instance of Amazon Dynamo DB
'use strict';
console.log('Loading function');
let doc = require('dynamodb-doc');
let dynamo = new doc.DynamoDB();
var tableName = 'Grill_Pal';
exports.handler = function(event, context) {
try {
console.log("event.session.application.applicationId=" + event.session.application.applicationId);
console.log('Calling App');
if (event.session.new) {
onSessionStarted({
requestId: event.request.requestId
}, event.session);
}
getUserData(event, context, onUserDataLoad);
} catch (e) {
context.fail("Exception: " + e);
}
};
function onUserDataLoad(event, context, userData) {
console.log('After User load: ' + event.request.type);
if (event.request.type === "LaunchRequest") {
onLaunch(userData, event.request, event.session, context);
} else if (event.request.type === "IntentRequest") {
onIntent(userData, event.request, event.session, context);
} else if (event.request.type === "SessionEndedRequest") {
onSessionEnded(event.request, event.session);
context.succeed();
}
}
function getUserData(event, context, callback) {
// Check if the user ID has an entry on DB. If not, need to create new one.
// If exists, start a new barbecue.
console.log('Get User Data = ' + event.session.user.userId);
var params = {
TableName: tableName,
Key: {
userId: event.session.user.userId
}
};
dynamo.getItem(params, function(err, data) {
if (err) {
console.log('Error GET Handler: ' + err);
context.fail("Exception: " + err);
} else {
console.log('Got User Data' + JSON.stringify(data));
if (!data.Item) {
console.log('New User, creating data');
data = {
Item: {
userId: event.session.user.userId,
barbecues: []
}
}
} else {
console.log('Existing User');
}
callback(event, context, data);
}
});
}
function saveAndExit(userData, context, response) {
console.log('Saving: ' + JSON.stringify(userData));
var params = {
TableName: tableName,
Item: userData.Item
};
console.log('Saving: ' + JSON.stringify(params));
dynamo.putItem(params, function(err, data) {
if (err) {
console.log('Error Save Handler: ' + err);
context.fail("Exception: " + err);
} else {
context.succeed(response);
}
});
}
/**
* Called when the session starts.
*/
function onSessionStarted(sessionStartedRequest, session) {
console.log("onSessionStarted requestId=" + sessionStartedRequest.requestId +
", sessionId=" + session.sessionId);
}
/**
* Called when the user launches the skill without specifying what they want.
*/
function onLaunch(userData, launchRequest, session, context) {
console.log("onLaunch requestId=" + launchRequest.requestId +
", sessionId=" + session.sessionId);
var cardTitle = 'Grill Pal';
var cardText = 'Welcome to Grill Pal. Your barbecue personal assistant. Please, use the following commands: ' +
'\n - "ask Grill Pal to start a barbecue" = It will clean Alexa grill and start a new barbecue and reset all timers. ' +
'\n - "ask Grill Pal to add a beef to the grill for 5 and 10 minutes" = Start the timers for turning in 5 minutes and serving in 10 minutes ' +
'\n - "ask Grill Pal to turn food" = I will stop the timer before turning and start the second timer ' +
'\n - "ask Grill Pal is the food ready?" = And I will tell you if it is time to turn or serve the food ' +
'\n - "ask Grill Pal serve the food" = I will stop the timer for this food ';
var repromptText = "";
var sessionAttributes = {};
var shouldEndSession = true;
var speechOutput = "Hello, and welcome to Alexa Grill Pal. I am your personal grill assistant. " +
"My objective is to help controlling the time your food is on grill, so you can enjoy the barbecue with " +
"your family and friends. You can ask me to count the timer for as many food you want and I will keep different timers for " +
"each one of them for you.";
var response = buildSpeechletResponse(cardTitle, cardText, speechOutput, repromptText, shouldEndSession, sessionAttributes);
saveAndExit(userData, context, response);
}
/**
* Called when the user specifies an intent for this skill.
*/
function onIntent(userData, intentRequest, session, context) {
console.log("onIntent requestId=" + intentRequest.requestId +
", sessionId=" + session.sessionId);
var intentName = intentRequest.intent.name;
// Dispatch to your skill's intent handlers
if ("StartBarbecue" === intentName) {
startBarbecue(userData, intentRequest, session, context);
} else if ("YesNoIntent" === intentName) {
yesNo(userData, intentRequest, session, context);
} else if ("AddFoodToGrill" === intentName) {
addFood(userData, intentRequest, session, context);
} else if ("CheckFood" === intentName) {
checkFood(userData, intentRequest, session, context);
} else if ("TurnFood" === intentName) {
turnFood(userData, intentRequest, session, context);
} else if ("ServeFood" === intentName) {
serveFood(userData, intentRequest, session, context);
} else {
throw "Invalid intent";
}
}
/**
* Called when the user ends the session.
* Is not called when the skill returns shouldEndSession=true.
*/
function onSessionEnded(sessionEndedRequest, session) {
console.log("onSessionEnded requestId=" + sessionEndedRequest.requestId +
", sessionId=" + session.sessionId);
// Add cleanup logic here
}
function handleSessionEndRequest(callback) {
var cardTitle = "Session Ended";
var speechOutput = "Thank you for trying the Alexa Skills Kit sample. Have a nice day!";
// Setting this to true ends the session and exits the skill.
var shouldEndSession = true;
callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession, {}));
}
// ---------------- INTENTS ---------------------------------
function serveFood(userData, intent, session, context) {
console.log('Serve food' + JSON.stringify(intent));
var food = intent.intent.slots.FoodType.value.toLowerCase();
var ordinal = intent.intent.slots.Ordinal.value;
if (!ordinal) ordinal = 'first';
var number = getNumber(ordinal);
var curDate = new Date(intent.timestamp);
var cardTitle = 'Turn Food';
var cardText = '';
var repromptText = " " ;
var sessionAttributes = {};
var shouldEndSession = true;
var speechOutput = "";
var openBBQ = hasOpenBarbecue(userData);
if(openBBQ) {
var leng = userData.Item.barbecues.length -1;
var foodList = userData.Item.barbecues[leng].foods;
var selectFood;
// If Ordinal is asked, return only that entry. If not, reply with all entries
var i = 0;
for (i = 0; i< foodList.length; i++) {
if (foodList[i].type === food) {
if (foodList[i].index === number) {
selectFood = foodList[i];
break;
}
}
}
var foodName = getOrdinal(selectFood.index) + " " + selectFood.type;
if (selectFood.serveTime) {
var past = getInMinutes(new Date(selectFood.turnTime), curDate);
cardText = foodName + ' was served ' + getMinutesStr(past) + ' ago. ';
speechOutput = "Reading my notes, I understand you already served " + foodName + " " + getMinutesStr(past) + " ago. " ;
} else {
// Not turned yet, so make it
selectFood.serveTime = intent.timestamp;
cardText = foodName + ' timer stopped. Enjoy your meal';
speechOutput = foodName + ' timer stopped. Enjoy your meal';
}
} else {
console.log('No BBQ' );
// In case there is no barbecue:
cardText = 'There is no barbecue going on my records. Please, start a barbecue and add food to grill before asking for food timers.';
speechOutput = "Hey, according to my records, you did not start a barbecue yet. Please say: Alexa, ask Grill Pal to start a barbecue.";
}
var response = buildSpeechletResponse(cardTitle, cardText, speechOutput, repromptText, shouldEndSession, sessionAttributes);
saveAndExit(userData, context, response);
}
function turnFood(userData, intent, session, context) {
console.log('Turn food' + JSON.stringify(intent));
var food = intent.intent.slots.FoodType.value.toLowerCase();
var ordinal = intent.intent.slots.Ordinal.value;
if (!ordinal) ordinal = 'first';
var number = getNumber(ordinal);
var curDate = new Date(intent.timestamp);
var cardTitle = 'Turn Food';
var cardText = '';
var repromptText = " " ;
var sessionAttributes = {};
var shouldEndSession = true;
var speechOutput = "";
var openBBQ = hasOpenBarbecue(userData);
if(openBBQ) {
var leng = userData.Item.barbecues.length -1;
var foodList = userData.Item.barbecues[leng].foods;
var selectFood;
// If Ordinal is asked, return only that entry. If not, reply with all entries
var i = 0;
for (i = 0; i< foodList.length; i++) {
if (foodList[i].type === food) {
if (foodList[i].index === number) {
selectFood = foodList[i];
break;
}
}
}
var foodName = getOrdinal(selectFood.index) + " " + selectFood.type;
if (selectFood.turnTime) {
if (selectFood.serveTime) {
cardText += 'You already served ' + foodName + '. Please try again';
speechOutput += 'Ups. As I can see you already served ' + foodName + '. Are you sure you chose the right food?';
} else {
var past = getInMinutes(new Date(selectFood.turnTime), curDate);
cardText = foodName + ' was turned ' + getMinutesStr(past) + ' ago. ';
speechOutput = "Reading my notes, I understand you turned " + foodName + " " + getMinutesStr(past) + " ago. " ;
var toServe = selectFood.secondTimer - past;
if (toServe > 0) {
cardText += ' It will be ready to serve in ' + getMinutesStr(toServe)+ '. When you do, just say: Alexa, ask Grill Pal to serve ' + foodName + ".";
speechOutput += "It will be ready to serve in " + getMinutesStr(toServe) + ". When you do, just say: Alexa, ask Grill Pal to serve " + foodName + ".";
} else if (toServe > -2) {
cardText += ' It is time to serve it. When you do, just say: Alexa, ask Grill Pal to serve ' + foodName + ".";
speechOutput += "Actually It is time to serve it. When you do, just say: Alexa, ask Grill Pal to serve " + foodName + ".";
} else {
cardText += ' You had to serve it ' + getMinutesStr(-toServe) + ' ago. When you do, just say: Alexa, ask Grill Pal to serve ' + foodName + ".";
speechOutput += "Actually You had to serve it " + getMinutesStr(-toServe) + " ago. When you do, just say: Alexa, ask Grill Pal to serve " + foodName + ".";
}
}
} else {
// Not turned yet, so make it
selectFood.turnTime = intent.timestamp;
cardText = foodName + ' turned. It will be ready to serve in ' + getMinutesStr(selectFood.secondTimer) + '. When you do, just say: Alexa, ask Grill Pal to serve ' + foodName + ".";
speechOutput = 'OK, done! As you asked before, you should be able to serve ' + foodName + ' in ' + getMinutesStr(selectFood.secondTimer) + '. When you do, just say: Alexa, ask Grill Pal to serve ' + foodName + ".";
}
} else {
console.log('No BBQ' );
// In case there is no barbecue:
cardText = 'There is no barbecue going on my records. Please, start a barbecue and add food to grill before asking for food timers.';
speechOutput = "Hey, according to my records, you did not start a barbecue yet. Please say: Alexa, ask Grill Pal to start a barbecue.";
}
var response = buildSpeechletResponse(cardTitle, cardText, speechOutput, repromptText, shouldEndSession, sessionAttributes);
saveAndExit(userData, context, response);
}
function checkFood(userData, intent, session, context) {
console.log('Check food' + JSON.stringify(intent));
var food = intent.intent.slots.FoodType.value.toLowerCase();
var ordinal = intent.intent.slots.Ordinal.value;
var number = getNumber(ordinal);
var cardTitle = 'Check Food';
var cardText = '';
var repromptText = " " ;
var sessionAttributes = {};
var shouldEndSession = true;
var speechOutput = "";
var openBBQ = hasOpenBarbecue(userData);
if(openBBQ) {
var leng = userData.Item.barbecues.length -1;
var foodList = userData.Item.barbecues[leng].foods;
var selectFood = [];
// If Ordinal is asked, return only that entry. If not, reply with all entries
var i = 0;
for (i = 0; i< foodList.length; i++) {
if (foodList[i].type === food) {
if (number > 0 ) {
if (foodList[i].index === number) {
selectFood.push(foodList[i]);
}
} else selectFood.push(foodList[i]);
}
}
console.log('Aqui ' + selectFood.length);
var data = getResponseTextFromFood(selectFood, intent.timestamp);
cardText = data.cardText;
speechOutput = data.outputSpeech;
} else {
console.log('No BBQ' );
// In case there is no barbecue:
cardText = 'There is no barbecue going on my records. Please, start a barbecue and add food to grill before asking for food timers.';
speechOutput = "Hey, according to my records, you did not start a barbecue yet. Please say: Alexa, ask Grill Pal to start a barbecue.";
}
var response = buildSpeechletResponse(cardTitle, cardText, speechOutput, repromptText, shouldEndSession, sessionAttributes);
saveAndExit(userData, context, response);
}
function addFood(userData, intent, session, context) {
console.log('Add food' + JSON.stringify(intent));
var food = intent.intent.slots.FoodType.value.toLowerCase();
var firstTimer = intent.intent.slots.FirstTimer.value;
var secondTimer = intent.intent.slots.SecondTimer.value;
var cardTitle = 'Add Food';
var cardText = 'Adding ' + food + ' to Grill! I will keep the timers for ' + firstTimer +
' before turning it, and ' + secondTimer + ' before serving it';
var repromptText = " " ;
var sessionAttributes = {};
var shouldEndSession = true;
var speechOutput = "Starting the timers for " + food + " now. When you " +
"want to know if it is time to turn or serve, please just say: Alexa, ask Grill Pal how is the " + food;
var startTimeOpenBB = hasOpenBarbecue(userData);
if (!startTimeOpenBB) {
console.log('No BBQ started, starting one now');
// Has no barbecue running. Create one now
var bbq = {
startTime: intent.timestamp,
foods: []
}
userData.Item.barbecues.push(bbq);
}
var leng = userData.Item.barbecues.length -1;
var foodList = userData.Item.barbecues[leng].foods;
var newFood = {
type: food,
index: 1,
firstTimer: firstTimer,
secondTimer: secondTimer,
startTime: intent.timestamp
}
// If more than one entry of the same food is created, start using cardinals.
var i = 0;
for(i = foodList.length-1; i >= 0 ; i--) {
console.log(i);
if (foodList[i].type === food) {
console.log('Food Found');
newFood.index = foodList[i].index + 1;
var ordinal = getOrdinal(newFood.index);
speechOutput = "I see that you have another :" + food + " cooking already. Don't worry, I can keep track " +
"of them all. I will just start calling it " + ordinal + " " + food + " from now on. Starting the timers now. When you " +
"want to know if it is time to turn or serve, please just say: Alexa, ask Grill Pal how is the " + ordinal + " " + food;
break;
}
}
foodList.push(newFood);
var response = buildSpeechletResponse(cardTitle, cardText, speechOutput, repromptText, shouldEndSession, sessionAttributes);
saveAndExit(userData, context, response);
}
function startBarbecue(userData, intent, session, context) {
console.log('Start Barbecue' + JSON.stringify(intent));
var cardTitle = 'Start Barbecue';
var cardText = 'A new barbecue is started. When you are ready, start adding the food!';
var repromptText = "You can ask me things like add a new food to grill, turn a food, serve a food or how long one food is cooking.";
var sessionAttributes = {};
var shouldEndSession = true;
var speechOutput = "Ok, I just started a new barbecue for you. When you are ready, please, start adding new food to the grill and " +
"I will keep the time for your.";
var startTimeOpenBB = hasOpenBarbecue(userData);
if (startTimeOpenBB) {
console.log('Start BB - Ask Yes or No');
cardTitle = 'Start Barbecue';
cardText = 'You already a barbecue. Do you want to start over (and reset all timers) or keep the existing one?';
repromptText = "You can ask me things like add a new food to grill, turn a food, serve a food or how long one food is cooking.";
sessionAttributes = {step: 'StartBarbecue'};
shouldEndSession = false;
speechOutput = "As I can see, you already started a barbecue at " + startTimeOpenBB + ". Do you want me to start a new barbecue and " +
"forget all running timers? You can say Yes or No.";
var response = buildSpeechletResponse(cardTitle, cardText, speechOutput, repromptText, shouldEndSession, sessionAttributes);
saveAndExit(userData, context, response);
} else {
console.log('Start BB - Create new');
// No open found, so we should start new barbecue.
var bbq = {
startTime: intent.timestamp,
foods: []
}
userData.Item.barbecues.push(bbq);
response = buildSpeechletResponse(cardTitle, cardText, speechOutput, repromptText, shouldEndSession, sessionAttributes);
saveAndExit(userData, context, response);
}
}
function yesNo(userData, intent, session, context) {
console.log('YesNo')
// First, check what is the step saved on Session attributes
if (session.attributes) {
var step = session.attributes.step;
console.log('YesNo for step ' + step);
if (step) {
var answer = intent.intent.slots.Answer.value.toLowerCase();
console.log('YesNo - Answer ' + answer);
if ('StartBarbecue' === step) {
if ('yes' === answer) {
console.log('Start BB Yes')
var leng = userData.Item.barbecues.length -1;
userData.Item.barbecues[leng].endTime = intent.timestamp;
startBarbecue(userData, intent, session, context);
} else {
console.log('Start BB No')
var response = buildSpeechletResponse('Start Barbecue', 'We will continue use ', '', '', false, {});
saveAndExit(userData, context, response);
}
} else {
// Return nothing
}
} else {
// return nothing
}
}
}
// --------------- Helpers that build all of the responses -----------------------
function hasOpenBarbecue(userData){
var startTime = null;
var barbecues = userData.Item.barbecues;
console.log('Start BB - Barbecue List = ' + barbecues);
if (barbecues && barbecues.length > 0) {
console.log('Has old bb');
var bb = barbecues[barbecues.length-1];
if (!bb.endTime) {
// TODO: Check the date. If it more than 12 hours, consider it closed.
console.log('Has OPEN old bb');
return bb.startTime;
} else console.log('Has NO OPEN old bb');
} else console.log('Not have old bb');
}
function getOrdinal(number) {
switch (number) {
case 1:
return 'first';
case 2:
return 'second';
case 3:
return 'third';
case 4:
return 'fourth';
case 5:
return 'fifth';
case 6:
return 'sixth';
case 7:
return 'seventh';
case 8:
return 'eighth';
case 9:
return 'nineth';
case 10:
return 'tenth';
default:
return '';
}
}
function getNumber(ordinal) {
switch (ordinal) {
case 'first':
return 1;
case 'second':
return 2;
case 'third':
return 3;
case 'fourth':
return 4;
case 'fifth':
return 5;
case 'sixth':
return 6;
case 'seventh':
return 7;
case 'eighth':
return 8;
case 'nineth':
return 9;
case 'tenth':
return 10;
default:
return 0;
}
}
function getResponseTextFromFood(foodList, currTime) {
var cardText = '';
var outputT = '';
var curDate = new Date(currTime);
// 2016-04-27T13:21:56Z
var i = 0;
if(foodList.length === 0) {
console.log('No food');
return {
cardText: cardText,
outputSpeech: "I'm sorry, but I could not find any food with the name you said. Please, try again."
}
}
for (i = 0; i < foodList.length; i++) {
var food = foodList[i];
var total = getInMinutes(new Date(food.startTime), curDate);
var foodName = getOrdinal(food.index) + " " + food.type;
outputT += foodName + " is in the grill for " + getMinutesStr(total) + ". ";
if (!food.turnTime) {
var m = food.firstTimer - total;
console.log('No turn for ' + foodName + " - " + m );
// No turned yet
if (m > 0) {
console.log('Before turn '+ total + " - " + food.firstTimer);
// Still have time to turn
outputT += "You should turn it in " + getMinutesStr(food.firstTimer - total) + ". "
} else if (m === 0) {
console.log('Same time turn '+ foodName);
// About time to turn
outputT += "It is time to turn it. When you do, just say: Alexa ask Grill Pal to turn " + foodName;
} else {
console.log('After turn '+ foodName);
// Turn time passed
outputT += "You had to turn it " + getMinutesStr(total - food.firstTimer)+ " ago. Hurry up! When you do, just say: Alexa ask Grill Pal to turn " + foodName;
}
} else {
// Did the turn, so calculate the serving time
var afterTurn = getInMinutes(new Date(food.turnTime), curDate);
outputT += "You turned it " + getMinutesStr(afterTurn )+ " ago. ";
var m2 = food.secondTimer - afterTurn;
if (m2 > 0) {
console.log('Before serve ' +foodName);
// Still have time to serve
outputT += "You will be ready to serve in " + getMinutesStr(food.secondTimer - afterTurn) + ". "
} else if (m2 === 0) {
console.log('Same time serve ' +foodName);
// About time to turn
outputT += "It is time to serve. When you do, just say: Alexa ask Grill Pal to serve " + foodName;
} else {
console.log('After serve '+ foodName);
// Turn time passed
outputT += "You had to take it from grill " + getMinutesStr(afterTurn - food.firstTimer) + " ago. Hurry up! When you do, just say: Alexa ask Grill Pal to serve " + foodName;
}
}
}
return {
cardText: cardText,
outputSpeech: outputT
};
}
function getInMinutes(d1, d2) {
return Math.round(((d2-d1)/1000)/60);
}
function getMinutesStr(number) {
if (number <= 1) return number + " minute";
else return number + " minutes";
}
function buildSpeechletResponse(title, cardText, output, repromptText, shouldEndSession, attributes) {
return {
version: "1.0",
sessionAttributes: attributes,
response: {
outputSpeech: {
type: "PlainText",
text: output
},
card: {
type: "Simple",
title: title,
text: cardText
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
}
};
}
Comments