What we’re building:
It’s a great project to get started with learning about IoT, and Alexa.
Hardware
- Particle Photon micro-controller
- Adafruit Featherwing mini-relay (https://learn.adafruit.com/mini-relay-featherwings/overview)
- Amazon Alexa (Dot in my case…)
- Optional for some extra holiday cheer: Adafruit NeoPixel Stick (https://www.adafruit.com/product/1426)
Circuit Diagram
Fritzing file can be found here: https://github.com/dvas0004/SmartMote/blob/master/SmartMote_v1.fzz
Notes, tips and tricks about the Hardware
- The mini relay uses 3V – so we connect this to the 3.3V output pin on the particle photon
- The NeoPixels require 5V to power them – so we connect this to the VIN pin on the photon, which – despite it’s name – OUTPUTS about 5V when the photon is powered via a mini-USB cable
- Regarding the relay, you need to cut ONE wire from the christmas tree’s lights. One end of that wire goes into “COM” while the other end goes into “NO”
Photon Code
// This #include statement was automatically added by the Particle IDE.
#include <neopixel.h>
// From particle documentation: enable OS system threading
SYSTEM_THREAD(ENABLED);
// This is where the NeoPixel Stick is plugged in
int led = D2;
// This is where the mini-relay "set" and "unset" pins are connected respectively
int RELAY_SET = D3;
int RELAY_UNSET = D4;
// Even though my stick has 8 LEDS, the last two wouldn't work if unless I used 10
int NUMBER_OF_LEDS = 10;
// From Adafruit documentation : initialize the stick. Note the use of "SK6812RGBW"
// you may need to experiment with the last value - this is the one that worked for me
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMBER_OF_LEDS, led, SK6812RGBW);
// Thread setup. "continue_thread" is used as a flag to control the thread
bool continue_thread = false;
Thread thread("ledThread", threadFunction);
// Next we go into the setup function.
void setup() {
// First, declare all of our pins. This lets our device know which ones will be used for outputting voltage, and which ones will read incoming voltage.
pinMode(led,OUTPUT); // Our LED pin is output (lighting up the NeoPixel)
pinMode(RELAY_SET, OUTPUT);
pinMode(RELAY_UNSET, OUTPUT);
// We are also going to declare a Particle.function so that we can turn the NeoPixel LEDs on and off from the cloud.
Particle.function("led",ledToggle);
// We do the same for the relay
Particle.function("relay",relayToggle);
// Make sure the NeoPixel is blank when we start
strip.begin();
strip.clear();
strip.show();
}
// Next is the loop function...
void loop() {
// In this case we actually dont do anything here - all the work is done by the thread
}
void threadFunction(void *param) {
strip.begin();
int counter = 0;
while(true) {
// TODO: cleanup the code below - there's some repetition with two "switch" statements that can be removed
if (continue_thread) {
// FADE IN the LEDs, and cycle between red, green and blue
for( int a = 0; a < 150; a = a + 5 ) {
for( int l = 0; l < NUMBER_OF_LEDS; l = l + 1 ) {
int r = 0;
int g = 0;
int b = 0;
switch (counter){
case(0):
r = a;
break;
case(1):
g = a;
break;
case(2):
b = a;
break;
}
//GRBW
strip.setPixelColor((uint16_t)l, (uint8_t)g, (uint8_t)r, (uint8_t)b, (uint8_t)0);
}
strip.show();
delay(100);
}
// FADE OUT the LEDs, and cycle between red, green and blue
for( int a = 150; a >= 0; a = a - 5 ) {
for( int l = 0; l < NUMBER_OF_LEDS; l = l + 1 ) {
int r = 0;
int g = 0;
int b = 0;
switch (counter){
case(0):
r = a;
break;
case(1):
g = a;
break;
case(2):
b = a;
break;
}
//GRBW
strip.setPixelColor((uint16_t)l, (uint8_t)g, (uint8_t)r, (uint8_t)b, (uint8_t)0);
}
strip.show();
delay(100);
}
counter = (counter + 1)%3;
} else {
// If the control variable "continue_thread" is set to False, - clear the neopixels
for( int b = 0; b < NUMBER_OF_LEDS; b = b + 1 ) {
strip.setPixelColor((uint16_t)b, (uint8_t)0, (uint8_t)0, (uint8_t)0, (uint8_t)0);
}
strip.show();
delay(1000);
}
}
}
// Define our relayToggle function, which we can call through the particle cloud
int relayToggle(String command) {
if (command=="on"){
digitalWrite(RELAY_UNSET, LOW);
digitalWrite(RELAY_SET, HIGH);
return 1;
}
else if (command=="off"){
digitalWrite(RELAY_SET, LOW);
digitalWrite(RELAY_UNSET, HIGH);
return 0;
}
return -1;
}
// Define our ledToggle function, which we can call through the particle cloud
int ledToggle(String command) {
if (command=="on") {
if (!continue_thread){
continue_thread=true;
}
return 1;
}
else if (command=="off") {
continue_thread=false;
strip.begin();
return 0;
}
else {
return -1;
}
}
Code can be found online: https://github.com/dvas0004/SmartMote/blob/master/SmartMote.ino
Notes, tips and tricks about the Photon Code
- You must include the “neopixel” library using the photon IDE
- For some reason, I needed to use a white lie and pass “10” instead of “8” as the number of LEDs on my NeoPixel stick in the “Adafruit_NeoPixel” constructor, otherwise the last two LEDs didn’t work
- In the same constructor, make sure to set the correct model – in my case above “SK6812RGBW”. If your LEDs dont light up, light up in the wrong order, refuse to switch off, get the wrong colors and so on – this is the parameter to play around with (after checking for hardware issues….). It’s a good idea to read through the NeoPixel Uberguide for more details:
- I used threading here ( hence the SYSTEM_THREAD(ENABLED) ), because I wanted to cycle the NeoPixels through red, green and blue as shown in the video below, while fading in and out. In the meantime I wanted the main thread (“loop”) to be free for other functions i’ll add later, and for retrieving instructions from the Particle Cloud API. It’s a good idea to review this post to get a good idea about threading:
At this stage of the process, you should be able to login to your particle cloud console, and in the “my devices” section, click the relevant photon and you should see two functions: “led” and “relay“. If you have the neopixels, type in “on”/”off” in the “led” function argument and the LEDs should come on/off. Same for the relay – you should hear a little “click” when the relay changes state – and the featherwing onboard LED changes color
Adding Voice Control – Enter Amazon Alexa
This is actually pretty straightforward if you know one particular GOTCHA: you have to follow the below instructions while in the “US East (N. Virginia)” zone. No other zone will work, even if your Alexa is set to use the “English – UK” language (as opposed to “English – US”).
With that out of the way, developing the skill is actually quite straightforward, simply follow the tutorial written here:
https://github.com/alexa/alexa-smarthome/wiki/Build-a-Working-Smart-Home-Skill-in-15-Minutes
When it came to the actual lambda code, I changed the following:
- The “SAMPLE_APPLIANCES” array was trimmed down to just one “appliance” – the particle photon. And because I wanted to say “Switch on the Christmas Tree”, the “friendlyName” also needed to be changed, resulting in something like this:
SAMPLE_APPLIANCES = [
{
"applianceId": "endpoint-001",
"manufacturerName": "Sample Manufacturer",
"modelName": "Smart Switch",
"version": "1",
"friendlyName": "Christmas Tree",
"friendlyDescription": "Christmas Tree switch that can only be turned on/off",
"isReachable": True,
"actions": [
"turnOn",
"turnOff"
],
"additionalApplianceDetails": {
"detail1": "For simplicity, this is the only appliance",
"detail2": "that has some values in the additionalApplianceDetails"
}
}
]
Integrating AWS to Particle Cloud
The last bit is allowing our lambda function to actually send commands to our particle photon – which we’ll do via the Particle Cloud API. Remember those “led” and “relay” functions? Those functions are very conveniently exposed via a REST API by Particle. Read up on the API here:
https://docs.particle.io/reference/device-cloud/api/
TL;DR: at least get your API token from the Particle Build web IDE on the ‘Settings’ page.
So we’d like our AWS Lambda function to call the ‘led’ and ‘relay’ functions from the Particle Cloud API with the “on” or “off” argument. We need to modify the lambda code as follows
- First, import the python “requests” module at the top of the file:
from botocore.vendored import requests
- Right after, define some constants we need to communicate with our particle:
#particle cloud
auth_token='<from the WEB IDE Settings page>'
header={'Authorization': 'Bearer ' + auth_token}
particleID='<from the My Devices page in Particle Cloud>'
- Then, define a function which will call the “led” and “relay” functions on the Particle Cloud, for our particular photon:
def callParticleFunction(on_off):
functionName='relay' url='https://api.particle.io/v1/devices/{}/{}'.format(particleID, functionName)
data={'arg':on_off}
requests.post(url, headers=header, data=data)
# same thing for LED function
functionName='led' url='https://api.particle.io/v1/devices/{}/{}'.format(particleID, functionName)
requests.post(url, headers=header, data=data)
- Last, we need to call the above function with the “on” parameter whenever we get a “Turn On” request from Alexa, and a similar call for “off”. In our lambda, find the functions “handle_non_discovery(request)“, and call our function in the appropriate if clause, like so:
def handle_non_discovery(request):
request_name = request["header"]["name"]
if request_name == "TurnOnRequest":
callParticleFunction('on')
# ... rest of pre-existing code ....
elif request_name == "TurnOffRequest":
callParticleFunction('off')
# ... rest of pre-existing code ....
We do the same thing for v3 alexa requests, in the “handle_non_discovery_v3(request)” function:
def handle_non_discovery_v3(request):
request_namespace = request["directive"]["header"]["namespace"]
request_name = request["directive"]["header"]["name"]
if request_namespace == "Alexa.PowerController":
if request_name == "TurnOn":
value = "ON"
callParticleFunction('on')
else:
callParticleFunction('off')
value = "OFF"
# ... rest of pre-existing code ....
Done!
Don’t forget to follow the Amazon instructions and “discover” your devices, and if you run into problems always have a look at the AWS CloudFront logs from the Lambda function to see what exactly is tripping up
You must be logged in to post a comment.