Introduction
This project can use voice command to:
- Turn on/off one, a range or all of LED strip
- Change color of one, a range or all of LED strip
- Change effect (blink, changing color or normal) of one, a range or all of LED strip
This project is an expansion of the project Amazon Echo – DIY Project with PHPoC
Every step is almost the same, there are only some difference on interaction model configuration and source code.
You can do step by step on that link. I only show the differences in this article.
Things Used In This Project
- 1 x Amazon Alexa Echo Dot
- 1 x PHPoC Blue
- 14 x PHPoC 4-Port Relay Expansion Board http://www.phpoc.com/smart_expansion_board.php
- 14 x Adafruit RGB LED Strip https://www.adafruit.com/product/285
RGB LED strip required 12V power source. Therefore we need to use relay to control the LED .
Each strip need 3 relays to control three colors lines.
I use 14 strips, so I need 42 relays.
Each PHPoC 4-Port Relay Expansion Board has 4 relays.
In order to simplify the wiring and source code, I use 14 Expansion Boards. Each board connect to one LED strip.
Step 2: Creating and configuring Alexa Skill (on Alexa Service)
- In “Interaction Model”
- Intent Schema
Code:{ "intents": [ { "slots": [ { "name": "ColorState", "type": "COLOR_STATE" }, { "name": "LedState", "type": "LED_STATE" }, { "name": "FromLed", "type": "LED_ID" }, { "name": "ToLed", "type": "LED_ID" }, { "name": "Effect", "type": "EFFECT_STATE" } ], "intent": "LedStrip" } ] }
- Custom Slot Types
- COLOR_STATE
Code:red green blue cyan yellow magenta white
- LED_STATE
Code:on off
- LED_ID
Code:one two three four five six seven eight night ten eleven twelve thirteen fourteen all
- EFFECT_STATE
Code:normal blink color
- COLOR_STATE
- Sample Utterances[/i][/b]
Code:LedStrip {FromLed} {ColorState} LedStrip {FromLed} to {ToLed} {ColorState} LedStrip number {FromLed} {ColorState} LedStrip number {FromLed} to number {ToLed} {ColorState} LedStrip led number {FromLed} {ColorState} LedStrip led number {FromLed} to number {ToLed} {ColorState} LedStrip led number {FromLed} to led number {ToLed} {ColorState} LedStrip change color to {ColorState} LedStrip change number {FromLed} to {ColorState} LedStrip change number {FromLed} to number {ToLed} to {ColorState} LedStrip change led number {FromLed} to {ColorState} LedStrip change led number {FromLed} to number {ToLed} to {ColorState} LedStrip change led number {FromLed} to led number {ToLed} to {ColorState} LedStrip change led from number {FromLed} to number {ToLed} to {ColorState} LedStrip {FromLed} {LedState} LedStrip {LedState} {FromLed} LedStrip {FromLed} to {ToLed} {LedState} LedStrip turn {LedState} {FromLed} LedStrip turn {LedState} number {FromLed} LedStrip turn {LedState} led number {FromLed} LedStrip turn {FromLed} {LedState} LedStrip turn led number {FromLed} {LedState} LedStrip turn {LedState} number {FromLed} to number {ToLed} LedStrip turn {LedState} led number {FromLed} to number {ToLed} LedStrip turn {LedState} led from number {FromLed} to number {ToLed} LedStrip turn number {FromLed} to number {ToLed} {LedState} LedStrip turn led number {FromLed} to number {ToLed} {LedState} LedStrip turn led from number {FromLed} to number {ToLed} {LedState} LedStrip {Effect} LedStrip {FromLed} {Effect} LedStrip {Effect} {FromLed} LedStrip {Effect} number {FromLed} LedStrip {Effect} led number {FromLed} LedStrip {Effect} {FromLed} to {ToLed} LedStrip {Effect} number {FromLed} to {ToLed} LedStrip {Effect} number {FromLed} to number {ToLed} LedStrip {Effect} led number {FromLed} to {ToLed} LedStrip {Effect} led number {FromLed} to number {ToLed} LedStrip number {FromLed} {Effect} LedStrip {FromLed} to {ToLed} {Effect} LedStrip number {FromLed} to {ToLed} {Effect} LedStrip number {FromLed} to number {ToLed} {Effect} LedStrip led number {FromLed} {Effect} LedStrip led number {FromLed} to {ToLed} {Effect} LedStrip led number {FromLed} to number {ToLed} {Effect} LedStrip effect {Effect} LedStrip effect {FromLed} {Effect} LedStrip effect {Effect} {FromLed} LedStrip effect {Effect} number {FromLed} LedStrip effect {Effect} led number {FromLed} LedStrip effect {Effect} {FromLed} to {ToLed} LedStrip effect {Effect} number {FromLed} to {ToLed} LedStrip effect {Effect} number {FromLed} to number {ToLed} LedStrip effect {Effect} led number {FromLed} to {ToLed} LedStrip effect {Effect} led number {FromLed} to number {ToLed}
- Intent Schema
Step 3: Writing source code to handle data from Alexa Skill (on AWS Lambda)
This code will handle "intent" from Alexa Skill and extract the necessary data, format it in json format and publish on a MQTT topic.
<index.js>
Code:
/** * This sample demonstrates a simple skill built with the Amazon Alexa Skills Kit. * The Intent Schema, Custom Slots, and Sample Utterances for this skill, as well as * testing instructions are located at http://amzn.to/1LzFrj6 * * For additional samples, visit the Alexa Skills Kit Getting Started guide at * http://amzn.to/1LGWsLG */ var mqtt = require('mqtt'); // 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); /** * Uncomment this if statement and populate with your skill's application ID to * prevent someone else from configuring a skill that sends requests to this function. */ /* if (event.session.application.applicationId !== "amzn1.echo-sdk-ams.app.[unique-value-here]") { context.fail("Invalid Application ID"); } */ 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) { console.log("onSessionStarted requestId=" + sessionStartedRequest.requestId + ", sessionId=" + session.sessionId); } /** * Called when the user launches the skill without specifying what they want. */ function onLaunch(launchRequest, session, callback) { console.log("onLaunch requestId=" + launchRequest.requestId + ", sessionId=" + session.sessionId); // Dispatch to your skill's launch. getWelcomeResponse(callback); } /** * Called when the user specifies an intent for this skill. */ function onIntent(intentRequest, session, callback) { console.log("onIntent requestId=" + intentRequest.requestId + ", sessionId=" + session.sessionId); var intent = intentRequest.intent, intentName = intentRequest.intent.name; // Dispatch to your skill's intent handlers if ("LedStrip" === intentName) { setLightInSession(intent, session, callback); } else if ("AMAZON.HelpIntent" === intentName) { getWelcomeResponse(callback); } else if ("AMAZON.StopIntent" === intentName || "AMAZON.CancelIntent" === intentName) { handleSessionEndRequest(callback); } 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 } // --------------- Functions that control the skill's behavior ----------------------- function getWelcomeResponse(callback) { // If we wanted to initialize the session to have some attributes we could add those here. var sessionAttributes = {}; var cardTitle = "Welcome"; var speechOutput = "Welcome to P H P o C. How can I help you?"; // If the user either does not reply to the welcome message or says something that is not // understood, they will be prompted again with this text. var repromptText = "How can I help you?"; var shouldEndSession = false; callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession)); } function handleSessionEndRequest(callback) { var cardTitle = "Session Ended"; var speechOutput = "Thank you for trying the P h p o c example. Have a nice day!"; // Setting this to true ends the session and exits the skill. var shouldEndSession = true; callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession)); } /** * Sets the led in the session and prepares the speech to reply to the user. */ function setLightInSession(intent, session, callback) { var cardTitle = intent.name; var colorStateRequest = intent.slots.ColorState; var ledFromRequest = intent.slots.FromLed; var ledToRequest = intent.slots.ToLed; var ledStateRequest = intent.slots.LedState; var effectRequest = intent.slots.Effect; var repromptText = ""; var sessionAttributes = {}; var shouldEndSession = true; var speechOutput = ""; if(colorStateRequest || ledFromRequest || ledToRequest || ledStateRequest || effectRequest) { var colorState = colorStateRequest.value; var ledFrom = ledFromRequest.value; var ledTo = ledToRequest.value; var ledState = ledStateRequest.value; var effect = effectRequest.value; if (colorState === undefined) colorState = ""; if (ledFrom === undefined) ledFrom = 0; if (ledTo === undefined) ledTo = 0; if (ledState === undefined) ledState = ""; if (effect === undefined) effect = ""; var responseAlexa = ""; if(colorState != "") { responseAlexa = "Ok, changing color of led strip "; if(ledFrom != 0 && ledTo !=0) responseAlexa += "from number " + ledFrom + " to number " + ledTo + " to " + colorState; else if(ledFrom != 0) responseAlexa += "number " + ledFrom + " to " + colorState; else responseAlexa += " to " + colorState; } else if(ledState != "") { responseAlexa = "Ok, turning "; if(ledFrom != 0 && ledTo !=0) responseAlexa += ledState + " led strip from number " + ledFrom + " to number " + ledTo; else if(ledFrom != 0) { if(ledFrom == "all") responseAlexa += ledState + " all led strip"; else responseAlexa += ledState + " led strip number " + ledFrom; } } else if(effect != "") { responseAlexa = "Ok, change effect of led "; if(ledFrom != 0 && ledTo !=0) responseAlexa += "from number " + ledFrom + " to number " + ledTo + " to " + effect; else if(ledFrom != 0) responseAlexa += "number " + ledFrom + " to " + effect; else responseAlexa = "Ok, change effect to " + effect; } else { responseAlexa = "Please try again"; } if (ledFrom === "all") ledFrom = 15; var paramsUpdate = ' {"color" : "' + colorState + '", "from" : ' + ledFrom + ', "to" : ' + ledTo + ', "state" : "' + ledState + '", "effect" : "' + effect + '"}'; //Update var mqttpromise = new Promise( function(resolve,reject){ var client = mqtt.connect({port:1883,host:'iot.eclipse.org'}) client.on('connect', function() { // When connected // publish a message to a topic client.publish('phpoc/alexa/ledstrip', paramsUpdate) client.end() resolve('Done Sending'); }); }); mqttpromise.then( function(data) { console.log('Function called succesfully:', data); sessionAttributes = createcolorStateAttributes(colorState); speechOutput = responseAlexa; repromptText = responseAlexa; callback(sessionAttributes,buildSpeechletResponse( cardTitle, speechOutput, repromptText, shouldEndSession)); }, function(err) { console.log('An error occurred:', err); } ); } else { speechOutput = "Please try again"; repromptText = "Please try again"; callback(sessionAttributes,buildSpeechletResponse( cardTitle, speechOutput, repromptText, shouldEndSession)); } } function createcolorStateAttributes(colorState) { return { colorState: colorState }; } // --------------- Helpers that build all of the responses ----------------------- function buildSpeechletResponse(title, output, repromptText, shouldEndSession) { return { outputSpeech: { type: "PlainText", text: output }, card: { type: "Simple", title: "SessionSpeechlet - " + title, content: "SessionSpeechlet - " + output }, reprompt: { outputSpeech: { type: "PlainText", text: repromptText } }, shouldEndSession: shouldEndSession }; } function buildResponse(sessionAttributes, speechletResponse) { return { version: "1.0", sessionAttributes: sessionAttributes, response: speechletResponse }; }
Step 5: Writing source code on PHPoC to handle commands and control the devices.
This code subscribe a MQTT topic and handle MQTT message which contains LED state, effect and LED numbers.
<task0.php>
PHP Code:
<?php
if(_SERVER("REQUEST_METHOD"))
exit; // avoid php execution via http request
include_once "/lib/sd_spc.php";
include_once "/lib/sn_dns.php";
include_once "/lib/sn_json.php";
include_once "/lib/vn_mqtt.php";
define("RED", 1);
define("GREEN", 2);
define("BLUE", 4);
define("YELLOW", RED | GREEN);
define("CYAN", GREEN | BLUE);
define("MAGENTA", RED | BLUE);
define("WHITE", RED | GREEN | BLUE);
define("EFFECT_NORMAL", 1);
define("EFFECT_BLINK", 2);
define("EFFECT_COLOR", 3);
define("EFFECT_DOMINO", 4);
define("ON", 1);
define("OFF", 0);
function led_strip_get_tick()
{
while(($pid = pid_open("/mmap/st0", O_NODIE)) == -EBUSY)
usleep(500);
if(!pid_ioctl($pid, "get state"))
pid_ioctl($pid, "start");
$tick = pid_ioctl($pid, "get count");
pid_close($pid);
return $tick;
}
function get_color_code($color_name)
{
$color_name = strtoupper($color_name);
if($color_name == "RED")
return RED;
if($color_name == "GREEN")
return GREEN;
if($color_name == "BLUE")
return BLUE;
if($color_name == "YELLOW")
return YELLOW;
if($color_name == "CYAN")
return CYAN;
if($color_name == "MAGENTA")
return MAGENTA;
if($color_name == "WHITE")
return WHITE;
return 0;
}
spc_reset();
spc_sync_baud(115200);
//$host_name = "test.mosquitto.org";
$host_name = "iot.eclipse.org";
//$host_name = "broker.hivemq.com";
//$host_name = "broker.mqttdashboard.com";
//$host_name = "[112.171.138.90]";
$port = 1883;
mqtt_setup(0, "PHPoC-MQTT Sub Example", $host_name, $port);
$will = "";
$username = "";
$password = "";
mqtt_connect(true, $will, $username, $password);
$out_topics = array(array("phpoc/alexa/ledstrip", 0));
if(mqtt_state() == MQTT_CONNECTED)
mqtt_subscribe($out_topics);
$in_topic = "";
$in_content = "";
$is_retain = 0;
$led_from = 0;
$led_to = 0;
$led_effect = array("normal", "normal", "normal", "normal", "normal", "normal", "normal", "normal", "normal", "normal", "normal", "normal", "normal", "normal");
$led_color = array(WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE);
$led_state = array(OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF);
$update = true;
while(1)
{
if(mqtt_state() == MQTT_DISCONNECTED)
while(mqtt_reconnect() == false);
if(mqtt_loop($in_topic, $in_content, $is_retain))
{
//TODO , procees the received publish packet here
if($is_retain == 1)
echo "<<a stale message\r\n";
echo "<<topic:$in_topic\r\n";
echo "<<content: $in_content\r\n";
$color = json_text_value(json_search($in_content, "color"));
$from = (int)json_search($in_content, "from");
$to = (int)json_search($in_content, "to");
$state = json_text_value(json_search($in_content, "state"));
$effect = json_text_value(json_search($in_content, "effect"));
if($from > 15 || $to > 15)
continue;
if($from == 15)
{
$led_from = 1;
$led_to = 14;
}
else if($from != 0)
{
$led_from = $from;
$led_to = $to;
}
if($color != "")
{
$color_value = get_color_code($color);
if($from != 0 && $to == 0)
$led_color[$from - 1] = $color_value;
else if($from != 0 && $to != 0)
{
for($i = $led_from; $i <= $led_to; $i++)
{
$led_color[$i - 1] = $color_value;
}
}
else if($from == 0 && $to == 0)
{
for($i = 1; $i <= 14; $i++)
{
$led_color[$i - 1] = $color_value;
}
}
}
if($state != "" && $led_from != 0)
{
$state_value = ($state == "on") ? ON : OFF;
if($led_to == 0)
{
$led_state[$led_from - 1] = $state_value;
$led_effect[$led_from - 1] = "normal";
}
else
{
for($i = $led_from; $i <= $led_to; $i++)
{
$led_state[$i - 1] = $state_value;
$led_effect[$i - 1] = "normal";
}
}
}
if($effect != "")
{
if($from != 0 && $to == 0)
{
$led_effect[$from - 1] = $effect;
if($effect != "normal")
$led_state[$from - 1] = ON;
}
else if($from != 0 && $to != 0)
{
for($i = $led_from; $i <= $led_to; $i++)
{
$led_effect[$i - 1] = $effect;
if($effect != "normal")
$led_state[$i - 1] = ON;
}
}
else if($from == 0 && $to == 0)
{
for($i = 1; $i <= 14; $i++)
{
$led_effect[$i - 1] = $effect;
if($effect != "normal")
$led_state[$i - 1] = ON;
}
}
}
$update = true;
}
$curr_time = led_strip_get_tick();
for($i = 0; $i < 14; $i++)
{
$effect = $led_effect[$i];
switch($effect)
{
case "normal":
//$update = true;
break;
case "blink":
if(($curr_time - $update_time) > 1500)
{
//if($led_state[$i] == ON)
$led_state[$i] = ($led_state[$i] + 1) % 2;
$update = true;
}
break;
case "color":
if(($curr_time - $update_time) > 1500)
{
$led_color[$i] = ($led_color[$i] + 1) % 8;
$update = true;
}
break;
case "domino":
break;
}
}
if($update)
{
for($i = 1; $i <= 14; $i++)
{
if($led_state[$i - 1] == OFF)
{
spc_request($i, 4, "set 0 output low"); usleep(10);
spc_request($i, 4, "set 1 output low"); usleep(10);
spc_request($i, 4, "set 2 output low"); usleep(10);
}
else
{
$color = $led_color[$i - 1];
$state = ($color & RED) ? "high" : "low";
spc_request($i, 4, "set 0 output $state"); usleep(10);
$state = ($color & GREEN) ? "high" : "low";
spc_request($i, 4, "set 1 output $state"); usleep(10);
$state = ($color & BLUE) ? "high" : "low";
spc_request($i, 4, "set 2 output $state"); usleep(10);
}
}
$update_time = led_strip_get_tick();
$update = false;
}
}
//mqtt_unsubscribe("sensors/temperature");
mqtt_disconnect();
?>