In this
project, I put two servo motors, one for red flag and the other for white flag.
The voice command, e.g., hold up the red flag, is passed and upload to IoT
database through ASK/Lambda, then the ESP-01 module query the IoT database to
get the flag code, and finally attach the servo motors to react the up/down
flag action.
Please
note that this article does not attempt to tell you how to implement the smart
home things, but rather to share with you that we can extend our scope and
connect more things that support the REST API, such as Wikipedia, Google map,
Flickr, some government’s database and etc.
Red & White Flags Command
Our
intention is to design a flag game and one computer player (Alexa) that can
recognize the following commands.
Alexa, ask flag game,
Hold up the red flag.
Hold up the white flag.
Hold up the red flag and the white flag
together.
Put down the red flag.
Put down the white flag.
Put down the red flag and the white flag
together.
Don't hold up the red flag.
Don't put down the red flag.
Don't hold up the white flag.
Don't put down the white flag.
Don't hold up the red flag and the white
flag together.
Don't put down the red flag and the
white flag together.
|
ASK and AWS/Lambda
Program Framework
For more detail about how to implement Alexa Skill setp by step, please
refer to my previous project named ‘The first
Alexa Skill – Hello World’. Assume our APP (Alexa Skill) name is ‘light
config’.
ASK/Intent Schema
I expect that user can start the game by asking ‘flag game’ with different
combinations of commands to raise the red/white flag or put down the red/white
flag.
{
"intents": [
{
"slots": [
{
"name": "colorFlag",
"type": "COLOR_LIST"
},
{
"name": "invState",
"type": "INV_STATE"
}
],
"intent": "HoldUpFlagIntent"
},
{
"slots": [
{
"name": "colorFlag",
"type": "COLOR_LIST"
},
{
"name": "invState",
"type": "INV_STATE"
}
],
"intent": "PutDownFlagIntent"
},
{
"intent": "GameOverIntent"
}
]
}
|
Custom Slot
Two custom slot types were defined and enumerated below. The ‘INV_STATE’
variable is used to execute a negative command sentence, e.g., ‘don’t hold up
the red flag’ means to put down the red flag.
ASK/Sample Utterances
In order to improve the recognition of our new voice commands for Alexa,
which can trigger our custom intents successfully, we enumerate some possible
utterances.
HoldUpFlagIntent Hold up the {colorFlag} flag
HoldUpFlagIntent Hold up the red {colorFlag} flag and the {colorFlag} flag together
HoldUpFlagIntent {invState} hold up the {colorFlag} flag
HoldUpFlagIntent {invState} hold up the {colorFlag} flag and the {colorFlag} flag
together
PutDownFlagIntent Put down the {colorFlag} flag
PutDownFlagIntent Put down the {colorFlag} flag and the {colorFlag} flag together
PutDownFlagIntent {invState} put down the {colorFlag} flag
PutDownFlagIntent {invState} put down the {colorFlag} flag and the {colorFlag} flag
together
GameOverIntent game over
|
AWS/Lambda Script
Please refer to my previous project about ‘To access
ThingSpeak by making a HTTPS/GET request’, in which I utilize the
package proposed by Kathryn Hodge. First, you can download the Archive.zip
from her website and upload it to AWS/Lambda as a beginning. Second, you can
replace the index.js with the JavaScript described below. Please fill in your Channel
and APIKEY registered from ThingSpeak.
var request =
require("request");
// Route the incoming
request based on type (LaunchRequest, IntentRequest, etc.)
// The JSON body of the
request is provided in the event parameter.
exports.handler = function (event,
context) {
try {
console.log("event.session.application.applicationId=" +
event.session.application.applicationId);
if (event.session.new) {
onSessionStarted({requestId:
event.request.requestId}, event.session);
}
if (event.request.type === "LaunchRequest") {
onLaunch(event.request,
event.session,
function
callback(sessionAttributes, speechletResponse) {
context.succeed(buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === "IntentRequest") {
onIntent(event.request,
event.session,
function
callback(sessionAttributes, speechletResponse) {
context.succeed(buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === "SessionEndedRequest") {
onSessionEnded(event.request,
event.session);
context.succeed();
}
} catch (e) {
context.fail("Exception: " + e);
}
};
// Called when the session
starts.
function
onSessionStarted(sessionStartedRequest, session) {
}
// Called when the user
invokes the skill without specifying what they want.
function onLaunch(launchRequest,
session, callback) {
getWelcomeResponse(callback);
}
// Called when the user
specifies an intent for this skill.
function onIntent(intentRequest,
session, callback) {
var intent = intentRequest.intent;
var intentName = intentRequest.intent.name;
// dispatch custom intents to handlers
here
if (intentName == "HoldUpFlagIntent")
{
holdUpFlagHandler(intent, session, callback);
} else if (intentName == "PutDownFlagIntent")
{
putDownFlagHandler(intent, session, callback);
} else if (intentName === 'AMAZON.HelpIntent') {
getWelcomeResponse(callback);
} else if (intentName === 'GameOverIntent')
{
handleSessionEndRequest(callback);
} else if (intentName === 'AMAZON.StopIntent' || intentName ===
'AMAZON.CancelIntent') {
handleSessionEndRequest(callback);
} else {
throw "Invalid intent";
}
}
// Called when the user ends
the session.
function
onSessionEnded(sessionEndedRequest, session) {
}
function getWelcomeResponse(callback) {
var speechOutput = "Welcome! Let's play the red and white flag
game.";
var reprompt =
speechOutput;
var header = "Flag Game";
var shouldEndSession = false;
var flagSet = {"red":0, "white":0}; // init flag state
callback(flagSet,
buildSpeechletResponse(header, speechOutput, reprompt,
shouldEndSession));
}
function
handleSessionEndRequest(callback) {
const cardTitle = 'Session Ended';
const speechOutput = 'Thank you for playing the flag game. Have a nice
day!';
callback({}, buildSpeechletResponse(cardTitle, speechOutput, null,
true));
}
function holdUpFlagHandler(intent,
session, callback) {
var invState = intent.slots.invState.value?1:0;
var flagColor = intent.slots.colorFlag.value;
var flagSet = session.attributes;
flagColor.toLowerCase().split(' ').forEach(function(key){
flagSet[key] = invState?1:8; // 0,1,2:down
7,8,9:up to prevent single-bit transmission error
});
var flagVal = flagSet.red.toString()+flagSet.white.toString();
var speechOutput = "";
var reprompt = "";
var url = "https://api.thingspeak.com/update?api_key=APIKEY&field1="
+ flagVal;
request.get(url,
function(error, response, body) {
callback(flagSet,
buildSpeechletResponse("holdUpFlagHandler", speechOutput,
reprompt, false));
});
}
function putDownFlagHandler(intent,
session, callback) {
var invState = intent.slots.invState.value?1:0;
var flagColor = intent.slots.colorFlag.value;
var flagSet = session.attributes;
flagColor.toLowerCase().split(' ').forEach(function(key){
flagSet[key] = invState?8:1; // 0,1,2:down
7,8,9:up to prevent single-bit transmission error
});
var flagVal = flagSet.red.toString()+flagSet.white.toString();
var speechOutput = "";
var reprompt = "";
var url = "https://api.thingspeak.com/update?api_key=APIKEY&field1="
+ flagVal;
request.get(url, function(error, response, body) {
callback(flagSet,
buildSpeechletResponse("holdUpFlagHandler", speechOutput,
reprompt, false));
});
}
// ------- Helper functions
to build responses for Alexa -------
function buildSpeechletResponse(title,
output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: output
},
card: {
type: "Simple",
title: title,
content: output
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
};
}
function
buildSpeechletResponseWithoutCard(output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: output
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
};
}
function
buildResponse(sessionAttributes, speechletResponse) {
return {
version: "1.0",
sessionAttributes: sessionAttributes,
response: speechletResponse
};
}
function capitalizeFirst(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
|
Arduino
Board Connection
The pin definition of the servo motor is shown below.
We can define one servo as the right hand side (hold Red flag) and connect
its PWM pin to the Arduino pin 5. Similarily, we define another servo as the
left hand side (hold White flag) and connect its PWM pin to the Arduino pin 6,
as shown below.
Servo Test
#include <Servo.h>
Servo servoL; // pin6 (White flag)
Servo
servoR; //
pin5 (Red flag)
//
code[R][L]
void setColorFlag(char*
code) {
int udR = code[0]-48;
int udL = code[1]-48;
if ( udR<0 || udR>9 || udL<0 ||
udL>9 ) { // invalidate value
return;
}
int angR = udR<5?90:180;
int angL = udL<5?90:0;
Serial.println("Code>" +
String(code[0]) + String(code[1]) );
Serial.println("Code>" +
String(udR) + String(udL) );
servoR.attach(5);
servoL.attach(6);
servoR.write(angR);
servoL.write(angL);
delay(500);
servoR.detach();
servoL.detach();
}
void setup() {
}
void loop() {
if ( Serial.available() ) {
char ch = Serial.read();
switch (ch) {
case 'a': // red down white down
setColorFlag("11");
break;
case 'b': // red down white up
setColorFlag("18");
break;
case 'c': // red up white down
setColorFlag("81");
break;
case 'd': // red down white up
setColorFlag("88");
break;
}
}
}
|
Arduino
Program
In this example, we send an intermittent request to ThingSpeak every 5 seconds.
#include
<SoftwareSerial.h>
#include
<Servo.h>
SoftwareSerial ESP(3, 2); //
ESP8266 ESP-01: Tx, Rx
Servo servoL;
// pin6 (Red flag)
Servo servoR;
// pin5 (White flag)
bool
wifiState = false; // check wifi connection
bool
swState = false; // interrupt switch
void
sendATcmd(String cmd, unsigned int msDelay,String& espMsg) {
unsigned long timeout = millis()+msDelay;
Serial.print(">>ESP-01:
"+String(cmd)); // debug
ESP.print(cmd); //
send AT command to ESP-01
espMsg = "";
while ( ESP.available() ||
millis()<timeout ) {
while ( ESP.available() ) {
char ch = (char)ESP.read();
espMsg += ch;
}
}
}
bool
connectWAN(char* ssidName, char* passWord) {
bool status = false;
String recMsg;
String joinCmd =
"AT+CWJAP=\"" + String(ssidName) + "\",\""
+ String(passWord) + "\"\r\n";
sendATcmd("AT+RST\r\n",2000,recMsg); // reset
ESP-01
sendATcmd("AT+CWMODE=3\r\n",1000,recMsg); // config ESP-01 as AP+STA both
sendATcmd(joinCmd,1000,recMsg); // wait the
server to ack "OK"
for (int loop=0; loop<10; loop++ ) {
if ( ESP.find("OK") ) {
status = true;
Serial.println("join ESP-01
to WAN success ...");
break;
}
setDelay(1000);
}
return status;
}
// Alexa Channel used red/white flag game
void
queryThingSpeak(char* ipName,char* channelName, char* apiKey) {
String recMsg;
String tcpCmd =
"AT+CIPSTART=\"TCP\",\"" + String(ipName) +
"\",80\r\n";
String getCmd = "GET
/channels/" + String(channelName) +
"/feeds/last.json?key=" + String(apiKey) + "\r\n";
String cipCmd = "AT+CIPSEND=" +
String(getCmd.length()) + "\r\n";
sendATcmd(tcpCmd,1000,recMsg); // ask HTTP connection, and wait the server to ack
"OK"
sendATcmd(cipCmd,1000,recMsg); // start CIPSEND request, and wait the server to ack
"OK"
sendATcmd(getCmd,1000,recMsg); // send GET command
Serial.println("recMsg>" +
recMsg);
// get flagCode(RL)
from JSON
if ( recMsg.indexOf("+IP")>0
) {
char* code = recMsg.c_str();
char* p = NULL;
if ( (p=strrchr(code,':'))==NULL
&& (p=strrchr(code,';'))==NULL ) ;
else {
code
= (p+2);
*(p+4) = '\0';
}
setColorFlag(code);
}
}
// code[R][L]
void setColorFlag(char* code) {
int udR =
code[0]-48;
int udL =
code[1]-48;
if ( udR<0
|| udR>9 || udL<0 || udL>9 ) { //
invalidate value
return;
}
int angR =
udR<5?90:180;
int angL =
udL<5?90:0;
Serial.println("Code>" + String(code[0]) +
String(code[1]) );
Serial.println("Code>" + String(udR) + String(udL) );
servoR.attach(5);
servoL.attach(6);
servoR.write(angR);
servoL.write(angL);
delay(500);
servoR.detach();
servoL.detach();
}
void
setDelay(unsigned int ms) {
unsigned long timeout = ms+millis();
while( millis()<timeout ) {
checkSwitchButton();
}
}
bool
checkSwitchButton() {
if ( digitalRead(7)==LOW ) {
swState = !swState;
Serial.println("\nInterrupt:
Switch Button = " + String(swState) );
delay(300); //
debounce delay (depending on your behavior)
}
return swState;
}
void
setup() {
pinMode(7, INPUT_PULLUP); // set pin7 as the interrupt button
Serial.begin(115200);
ESP.begin(115200);
delay(1000);
// init color Flag
setColorFlag("88"); // up
setColorFlag("11"); // down
Serial.println("ESP-01 start
...");
Serial.println("try to connect
ESP-01 to WAN (through home AP) ...");
if ( (wifiState=connectWAN(SSID,PASSWORD))
) { // join ESP-01 to WAN home AP
Serial.println("press switch
button to start ...");
} else {
Serial.println("fail to join WAN
...");
}
}
void
loop() {
if ( wifiState && swState ) {
queryThingSpeak(
"184.106.153.149", // ThingSpeak IP
ChannelID, // Alexa Channel
ReadAPIKEY // read API-KEY
);
Serial.println("press button to
stop ...");
setDelay(1000);
}
setDelay(300); //
monitor hardware interrupt
}
|