Project tutorial

Scent-terrific Smart Candle © CC BY-NC

The Scent-terrific Smart Candle is made of wax, flickers like a real flame, emits scents though a wax warmer, and is controllable by Alexa!

  • 3,781 views
  • 0 comments
  • 34 respects

Components and supplies

11113 01
SparkFun Arduino Pro Mini 328 - 5V/16MHz
This is shown as retired on the Arduino.cc website, but you can still get them pretty easily. Both Sparkfun and Adafruit sell the 5V and 3V versions. You can also get cloned versions on Amazon. I bought a few clones for testing purposes, then moved to the "official" module for my product build.
×1
2821 01
Adafruit Feather HUZZAH with ESP8266 WiFi
I attempted to use a Huzzah breakout, but had problems getting my code to upload. Went with the feather instead.
×1
09590 01
LED (generic)
You'll need a red 3mm LED for the temp indicator. You'll need a yellow 5mm LED for each candle you plan to use. The Arduino Pro Mini has 6 PWM pins. You'll need for for the heating pad/transistor... leaving 5 for the candles.
×4
DS18B20 Temperature Sensor
Used to measure temperature of heating pad
×1
Adafruit Heating Pad
Used to heat the ramekin/wax cubes
×1
2 oz (1 in tall by 2.5 in wide) Ceramic Ramekin
Note - I purchased mine at Bed Bath Beyond for ~$2
×1
Adafruit TIP 120 Darlington Transistors
×1
Adafruit 5V Voltage Regulator (7805)
×1
12V 2A Power Supply
Don't skimp with a 12V 1A or a 9V power supply. You'll need 12V and 1.2 A to get the electric pad hot enough to warm the ramekin and melt the wax
×1
Adafruit 2.1 MM panel mount for DC connector
Input for the power supply.
×1
Assorted Resistors
You'll need resistors for the yellow LEDs (220), the Temperature Sensor (4.7K), and the transistor (10K)
×5
Momentary Push Button
You'll need this if you want to control the candles by touch as well as by voice.
×1
Candles
I used 2in wide by 4 in tall candles.... you can use whatever makes sense for your project.
×3
Heat Sinks
You'll need at least one for the Voltage Regulator. I went ahead and used 2 (one for the voltage regulator, the other for the transistor).
×1
Minwax® PolyShades (Bombay Mahogany)
Used to stain the PLA. Use the PolyShades for 3D prints.... looks much better than the regular stain.
×1
3d filament (PLA) - Black and Wood
I'm a fan of Hatchbox PLA, but use whatever you prefer. I couldn't find a Hatchbox Wood PLA, so I went with MG Chemicals Wood PLA.
×1
Capacitors 10 nF
Not required, but I added anyway (on each side of the voltage regulator)
×2
Fairchild semiconductor 1n4004. image
1N4007 – High Voltage, High Current Rated Diode
×1
Wax Cubes
Scented wax cubes to go into the wax melter
×1
LED panel mount
You will need one 3MM holder and a 5MM holder for each candle
×3
Adafruit Perma-Proto Half-sized Breadboard PCB
You can use any protoboard. I used this one because it was a logical migration from my breadboard solution.
×1

Necessary tools and machines

3drag
3D Printer (generic)
Drill
Used to make holes in the candles
09507 01
Soldering iron (generic)
You'll need an assortment of wires as well. I used both stranded and solid 22 gauge wire for the solution... the stranded was easier to fit into the cut-outs when I assembled the candle holder.
Crazy Glue

Apps and online services

Dp image kit 02
Amazon Alexa Alexa Skills Kit
Used to develop the Alexa Smart Home Skill
Ha 2up iot
Amazon Web Services AWS IoT
Used to create the certificates and create the IoT "thing"
Logo blue mos (1) gkow55hp00
Mongoose OS
Used to program the ESP8266
Ide web
Arduino IDE
Used to program the Pro Mini
Screen%20shot%202015 07 20%20at%206.10.26%20pm
Amazon Web Services AWS Lambda
Used to create the smart home skill and to create the registration webpage.

About this project

Introduction

The Scent-terrific Smart Candle (and Wax Warmer)

The Scent-terrific Smart Candle is a smart lighting and aromatherapy system for the home or office.

  • Scent-terrific Candles look like real candles because they are real candles.
  • They smell like real candles because a wax melter is built into the base.
  • They flicker like real candles!

Why Smart Candles

I love smart lighting. I've got seven wall switches, a smart bulb, and a smart plug (connected to a lamp) in my home. I've even built my own smart lamp.

Unfortunately, I haven't found the same innovation when it comes to other lighting apparatus. I haven't seen a smart night light on the market. I think smart lamps can be better.

Smart candles are the worse; most don't look like candles. They don't smell like candles. They aren't controllable by voice.

I've been thinking of building a smart candle system for a while; Hackster's "Alexa and Arduino Smart Home Challenge" seemed like a good reason to take my ideas and attempt to build something.

Design Considerations

One of the interesting aspects of the contest was the notion of taking a idea "from project to product". Using that as my guiding principle, I chose components and a final design based on the following factors:

  • Speed to Market - Do the components require special certifications from the FCC? How available are the components?
  • Cost of Components - How expensive/niche are the components? Can I substitutes components without a major impact on quality?
  • Cost to Manufacture - Can the components be easily manufactured (3D printed, soldered to a PCB, or wood-milled/manufactured)?

After heavy consideration, I landed on a solution that leveraged an Arduino Pro Mini, ESP8266 (S-12 module), Mongoose OS, and AWS IoT (more details in the appendix).

Interaction Diagram and Requirements

The main interaction model, using Alexa to provide voice control, is as follows:

Once I landed on a design, I started work on a prototype, with the following requirements:

  • Controllable via Alexa, Alexa App, and push button (on the device)
  • Looks like a candle - real wax and flickering light
  • Smells like a candle - must be able to melt wax in order to release a scent
  • Warning indicator if wax holder is too hot to touch
  • Quickly warm wax - scent should be released 10-15 mins from "turn on" request (note: most electric wax warmers take 30 mins to melt wax)

Breadboard Prototype

Inital Breadboard Prototype

Flame and Candles

I used yellow LEDs to simulate a flame (leveraging PWM pins to randomly vary the voltage to the LED, which causes the LED to "flicker").

I drilled 5/16 in. holes into 2 in. X 4 in. candles and inserted the LEDs inside to simulate a flame burning.

Prototype with LED in Candle (flickering)

Wax Melter and Temperature Control

I used an electric heating pad and a ceramic ramekin to meet the scent requirements.

A ds18b20 temperature sensor is used to measure the heating pad temperature. A transistor is used (along with a PWM pin) in order to control the temperature.

  • The Arduino keeps the ceramic ramekin "warm" (between 80-90 F) in order to reduce the wax melting time
  • The voltage (and current) to the heating pad increases when the candle activated. This allows the heating pad to reach a temperature between 130 - 140 F (wax melts around 130 F).
  • A red LED is used to indicate if the ramekin might be too hot to touch.

Voltage Challenges

The heating pad needs at least 12V at 1.2A to (a) get hot quickly, (b) get hot enough to melt the wax, (c) deal with heat loss between the pad, insulation tiles, and ceramic ramekin. [See linked datasheet for more info]

The Huzzah can only accept 4-6 V. The Pro Mini can, in theory, take up to 12V input, but that stresses the voltage regulator on the device (I blew out a voltage regulator on one of my test devices).

Ultimately, I used a 5V 1.5A voltage regulator (7805) to get a safe input voltage for the microcontrollers. I used a TIP 120 Darlington Transistor to handle the voltage switching for the heating pad (using a PWM pin to get more precise temperature control).

Wiring Diagram

Notes/Comments:

  • You will need a heat sink on the voltage regulator! There's a 7V gap between the input and output voltages. That equates to 8.4 Watts of power that the regulator is burning off (when the heating pad is on).
  • You may not need capacitors on the voltage regulator; I did just to be safe.

Building It - Smart Candle Software: ESP8266, AWS IoT, Mongoose OS

The Smart Candle uses two microcontrollers to provide fuctionality

  • ESP8266 - connects to AWS IoT (allowing the device to be controlled via Alexa)
  • Arduino Pro Mini - controls the LEDs and the heating pad

In order to build your own, you need to configure an ESP8266 to use Mongoose OS and AWS IoT.

Initial Set-up

Option 1 - Create your own AWS account and your own Alexa Skill

  • Find and save the custom endpoint for your AWS IoT devices (to do this, go to AWS IoT, then click "Settings").

Option 2 - Leverage my Alexa skill. To do this, you will need to

  • Request a temporary user on my account. You can request this by sending a message to me via the Hackster website.

Once you have the AWS CLI configured and have downloaded Mongoose OS:

  • Step A - Launch the Mongoose IDE. Plug in your ESP8266 device and select the correct COM. Choose Select.
  • Step B1 - Select the correct device ("ESP8266 w 4M Flash" if you are using a Huzzah)
  • Step B2 - Select the demo.js application and click FLASH
  • Step C - Enter your wifi creditials. Click SET, then click DONE.
  • Step D1 - Click the "Device Configuration" option on the left menu.
  • Step D2 - Check the MQTT box. Enter your AWS IoT Custom Endpoint, Username, Password, Region, and AWS Policy. Click "Provision with AWS IOT"
  • Step E1 - Once the update is complete, click on the "Device Files" menu option.
  • Step E2 - Find the init.js file. Cut the code from this file and replace it with the init.js code from the code repository below. Click "SAVE and REBOOT"

At this point, your ESP8266 is now configured for use with the AWS IoT cloud. This is necessary in order for the candle to interact with Alexa (using my code below).

Notes/Comments:

  • You do not need to understand AWS IoT and Shadows to create this, but it may help (especially as you look at the code and understand the "desired" and "reported" states).

Building It - Smart Candle Software: Arduino Pro Mini

The Pro Mini controls the functions of the smart candle. Building this is straight forward.

First, solder headers onto the Pro Mini (Sparkfun has a great getting started guide). From there, upload the code to the Pro Mini (check the code section for the Arduino code).

The wiring is documented in the Fritzing diagram. You can wire up to 5 "candles" with the code.

Building it - Smart Candle Device

I decided to build a three candle holder for my second prototype.

Step 1 - Prepare the Candles

  • Drill a 5/16 in hole in the bottom of each candle. The hole should be about 3 in deep (three-fourths as long as the candle). Start with a small drill bit and progressively work up to a 5/16 in drill bit. [Note: Be sure to keep the wick in the candle as you make the holes].
  • Next, burn the candle for 10-15 mins to achieve a melted look.
  • Note - you may want to drill a hole all the way through; this will allow for more light to shine through.

Step 2 - Create the Physical Candle Holder

I used 3D printing to create the candle holder (see section below for STL files):

  • "Wooden" candle holder and bottom - Wood PLA filament was used, with a coat of walnut stain to get the color (0.2 mm layer levels)
  • LED Insert - Black PLA filament, with a 5mm LED holder.
  • Candle Holder Saucer- Black PLA filament. The LED insert snaps onto the saucer.

Notes/Comments:

  • You will need to sand the wooden pieces with 180 grit sandpaper before applying the stain. The stain will take approximately 4-6 hours to dry. You may need two or three coats, depending on the look you are going for.
  • The candle holder has cut-outs for the 3 mm temperature LED, 2.1 mm DC mount, and power button.

Step 3 - Final Soldering and Assembly

Step 3.1 - Solder the micro-controllers to a protoboard

  • Solder the microcontrollers, voltage regulator, capacitors, transistor, and resistor for the temperature sensor to the protoboard.
  • Solder a connection between the A0 Arduino pin and pin 15 of the Huzzah.
  • Solder connections to the 5v and 12v rails as indicated in the Fritzing diagram
  • Solder female headers to the base so that the protoboard can be glued to the candle holder base.

Step 3.2 - Attache the heading pad to the base

  • Attached a 2 X 3 length of Peel and Stick tile as a base for the ramekin and heating pad.
  • Fold the heating pad in half and use crazy glue to attach the heating pad to the tile.

Step 3.3 - Assemble the remaining components, but do not solder them to the protoboard yet

  • Make sure the temperature sensor wires are long enough to reach across the candle holder (left to right = about 8 inches).
  • Solder the resistors to the candle LEDs and ensure that the wire is long enough to go through the candle LED holder to the bottom of the candle holder.
  • Solder a 3-4 in length of wire to the red LED (temperature indicator). Do the same for the button and the DC panel mount.

Step 3.4 - Assemble the LED holders

  • Insert the LEDs into a 5mm LED panel holder. Insert the holder onto the top of the 3D-printed LED holder.
  • Snap the LED holder onto the 3D printer cut-out.
  • Use a dab of crazy glue to keep everything attached.

Step 3.5 - Assemble, then solder the remaining components to the protoboard

  • Slowly soldered all the components together, making sure to use heat shrink (to present shorts). Shorten wire as needed.
  • Finally, used crazy glue to ensure all the 3D printed components stay together.

The final build, before adding the candles, looked like so:

Final assembly, before adding the candles.

Step 3.6 - Add the candles and test with the button

  • Place the candles into their holders.
  • Plug in the 12V plug
  • Turn the light on and off with the button.

Step 3.7 - Test with Alexa

If you are using an account provided my me, then you will be able to control the device through Alexa

  • Ask Alexa to 'Discover devices"
  • Alexa will scan for 20-30 seconds. Afterwards, she should tell you that a device has been added for you.
  • Go to the Alexa app to name the device.

Note - See the next section if you plan to build your own Alexa skill.

Building it - Alexa Smart Home Skill (if you plan to build your own skill)

An overview of building Alexa Smart Home skills can be found here: https://developer.amazon.com/docs/smarthome/understand-the-smart-home-skill-api.html

If this is your first smart home skill, then I suggest you first read the documentation and create a few "hello world" skills. Once you're ready, you can use my code to build your own Alexa Skill to control your smart candle.

The Alexa skill responds to the following directives:

Notes/Comments:

I used Login With Amazon for authentication. You can follow the example here: https://developer.amazon.com/docs/smarthome/authenticate-an-alexa-user-account-linking.html

I am in the process of building a "Device Cloud" for the Scent-terrific Smart Candle, so my code uses the user token to authenticate the user and retrieve the user_id.

The user_id is then used to either (a) discover devices or (b) confirm the user_id corresponds to the device_id.

If you are replicating this skill, you can skip some of the complexity and hard-code values instead. I've left the code as-is, with comments to where you can either hard-code or access a "device cloud" (a DynamoDB table).

Next Steps

Here are a few of the next steps needed to fully realize this project as a product.

Next Prototype (v3)

  • Add rechargeable battery, in order to operate LEDs in case of power outage
  • Allow for wax warmer to be operated independently from candle LEDs
  • Add Power Control functionality for LEDs (dim/brighten)
  • Create phone app that allows the user to configure the Smart Candle (connect to wifi, schedule on/off, and timer)
  • Add reset button on Smart Candle to erase wifi credentials

Things to think about:

  • Investigate moving to an ESP 32. It has more pins and BTE/BLE functionality, though that may be overkill (it is also higher cost and is new to development community)
  • Think about ways to reduce voltage requirements. Custom ramekins (1/2 in. height) and smaller heating pads will help
  • Look into better insulation of the heating apparatus. A redesign of the candle base will help

Path to Minimally Viable Product

  • Design/build PCB with SMT components
  • Create 2-5 additional models/options for candle holders
  • Look at manufacturing the candles with pre-existing holes
  • Manufacture ramekins (correct size) and candles (pre-existing holes) to spec
  • Think through candle base manufacturing (need more efficient options at scale)

Future Thinking

  • Add remote control functionality
  • Allow multiple light color options (using an RGB LED)
  • Add replenishment options for wax cubes (use Amazon Dash for auto reorder).

Appendix: Design Options

Note: This is a longer explanation of considered options. Added to the appendix for anyone who is interested.

One of the interesting aspects of the contest was the notion of taking a idea "from project to product". Using that as my guiding principle, I chose components and a final design based on the following factors:

  • Speed to Market - Do the components require special certifications from the FCC? How available are the components?
  • Cost of Components - How expensive/niche are the components? Can I substitutes components without a major impact on quality?
  • Cost to Manufacture - Can the components be easily manufactured (3D printed, soldered to a PCB, or wood-milled/manufactured)?

I ultimately came down to two design ideas:

Option 1: Hub and Spoke

Components:

  • Raspberry Pi to connect to AWS IoT
  • RFM69HCW modules to connect from Raspberry Pi to Smart Candle Modules

Pros:

  • RFM69HCW module is already FCC certified, and can easily be soldered to a breadboard.
  • Raspberry Pi are cheap ($10 retail)
  • Clear documentation/examples on connectivity between modules

Cons:

  • Would require a "hub" - which doesn't make sense if a user only had 2-3 candle modules.
  • Would have to write logic to connect/reset Wifi for the hub AND logic to pair the candle modules to the hub.

Option 2: Direct Wifi Connection to AWS IoT

Components:

  • ESP8266, leveraging Mongoose OS, to connect to AWS IoT

Pros:

  • No hub used (only need logic to connect/reset to wifi).
  • ESP8266 Modules are cheap, but finicky.
  • Some of the ESP8266 modules are FCC certified.

Cons:

  • A large number of candles could impact a user's wifi connection.
  • ESP8266s can be power hogs (though less of an issue for this implementation, as the smart candles would be plugged in).

Final Design

I ultimately decided on Option 2, for the following reasons;

  • ESP8266s are cheap, easily mounted to PCBs, generally available, and FCC certified (ESP-12S)
  • Most users would only have 2-3 smart candles in a home - so a hub doesn't make sense
  • Good documentation on using Mongoose OS to connect an ESP8266 device to AWS IoT

Code

Arduino Pro Mini code (candle.ino)C/C++
This is the code to be installed on the Arduinio Pro Mini. Controls the LEDs and Heating Pad
//This is the code that is installed on the Arduino Pro Mini for the Smart Candle

//Define the pins for the LEDs
#define FLICKER_LED_PIN1 5
#define FLICKER_LED_PIN2 6
#define FLICKER_LED_PIN3 9
#define FLICKER_LED_PIN4 10
#define FLICKER_LED_PIN5 11

#define PAD_PIN 3 //heating pad
#define TEMP_PIN 2 //temperature sensor
#define VOLTAGE_PIN 0


#include <OneWire.h>
#include <DallasTemperature.h>

OneWire oneWire(TEMP_PIN);
DallasTemperature sensors(&oneWire);

int state = 0;
long waitTime = 60000; // in Milliseconds
int lowPower = 64; //25%
int highPower = 255; //100%
float changeFactor = 0.1;
int safeTempLimit = 105;
int lowMin = 80;
int lowMax = 90;
int highMin = 110;
int highMax = 140;

void setup() {
  Serial.begin(115200);

  //set pin mode for LEDs, temp sensors, and heating pad
  pinMode(FLICKER_LED_PIN1, OUTPUT);
  pinMode(FLICKER_LED_PIN2, OUTPUT);
  pinMode(FLICKER_LED_PIN3, OUTPUT);
  pinMode(FLICKER_LED_PIN4, OUTPUT);
  pinMode(FLICKER_LED_PIN5, OUTPUT);
  pinMode(PAD_PIN, OUTPUT);
  pinMode(TEMP_PIN, OUTPUT);
  sensors.begin();



  //Get Initial Settings; if the voltage is 0, then the LEDs and Heating pad are off
  if (analogRead(VOLTAGE_PIN) < 500) {
    state = 0;
  } else {
    state = 1;
  }
  heatingpad(); //function to determine the voltage that needs to go to heating pad - using PWM to control temperature
}

//Sets the initial values to control flicker of LEDs
int flicker_random_low_start = 0;
int flicker_random_low_end = 0;
int flicker_random_high = 0;
int flicker_random_speed_start = 0;
int flicker_random_speed_end = 0;

//Sets the initial values to determine how ofter to check temperature and regulate voltage to heating pad
long msCounter = 0;

void loop() {

  if (analogRead(VOLTAGE_PIN) < 500) {
    state = 0;
  } else {
    state = 1;
  }

  flicker(FLICKER_LED_PIN1);
  flicker(FLICKER_LED_PIN2);
  flicker(FLICKER_LED_PIN3);
  flicker(FLICKER_LED_PIN4);
  flicker(FLICKER_LED_PIN5);

  //If the wait time has pasted, call the heatingpad function
  if (millis() - msCounter > waitTime) {
    msCounter = millis();
    heatingpad();
  }
}


//Heatingpad function either makes the heating pad hot, or keeps it warm
//The Heating pad needs to stay around 90 F in order to reduce the time it takes to get hot and warm wax
void heatingpad() {
  sensors.requestTemperatures();
  float temp = sensors.getTempFByIndex(0);

  if (state > 0) {
    keepHot(temp);
  } else {
    keepWarm(temp);
  }

}


//The Flicker function randomizes the flicked of the LEDs

void flicker(int FLICKER_LED_PIN) {

  // the start of the flicker (low)
  static int flicker_low_min = 200;
  static int flicker_low_max = 240;

  // the end value of the flicker (high)
  static int flicker_high_min = 230;
  static int flicker_high_max = 256;

  // delay between each low-high-low cycle
  // low->high |flicker_hold| high->low
  static int flicker_hold_min = 40; // milliseconds
  static int flicker_hold_max = 80; // milliseconds

  // delay after each low-high-low cycle
  // low->high->low |flicker_pause| low->high...
  static int flicker_pause_min = 100; // milliseconds
  static int flicker_pause_max = 200;  // milliseconds

  // delay low to high and high to low cycle
  static int flicker_speed_min = 900; // microseconds
  static int flicker_speed_max = 1000; // microseconds
  if (state > 0) {

    // random time for low
    flicker_random_low_start = random(flicker_low_min, flicker_low_max);
    flicker_random_low_end = random(flicker_low_min, flicker_low_max);

    // random time for high
    flicker_random_high = random(flicker_high_min, flicker_high_max);

    // random time for speed
    flicker_random_speed_start = random(flicker_speed_min, flicker_speed_max);
    flicker_random_speed_end = random(flicker_speed_min, flicker_speed_max);

    // low -> high
    for (int i = flicker_random_low_start; i < flicker_random_high; i++) {
      analogWrite(FLICKER_LED_PIN, i);
      delayMicroseconds(flicker_random_speed_start);
    }

    // hold
    delay(random(flicker_hold_min, flicker_hold_max));

    // high -> low
    for (int i = flicker_random_high; i >= flicker_random_low_end; i--) {
      analogWrite(FLICKER_LED_PIN, i);
      delayMicroseconds(flicker_random_speed_end);
    }

    // pause
    delay(random(flicker_pause_min, flicker_pause_max));
  }
  else  {
    analogWrite(FLICKER_LED_PIN, 0);

  }
}


//This function checks the temperature and attempts to keep the Heating Pad WARM: between 110 - 140 F degrees
//Wax melts around 130 F
void keepWarm(float temp) {
  Serial.println(temp);
  if (temp > lowMax) {
    lowPower = lowPower - lowPower * changeFactor;
  }

  if (temp < lowMin) {
    lowPower = lowPower + lowPower * changeFactor;
  }
  if (lowPower < 64) { //we should not drop below this level to keep system warm
    lowPower = 64;
  }
  analogWrite(PAD_PIN, lowPower);
}

//This function checks the temperature and attempts to keep the Heating Pad HOT: between 80 - 90 F degrees
void keepHot(float temp) {
  Serial.println(temp);
  if (temp > highMax) {
    highPower = highPower - highPower * changeFactor;
  }

  if (temp < highMin) {
    highPower = highPower + highPower * changeFactor;

  }
  if (highPower > 255) {
    highPower = 255; 
  }
  analogWrite(PAD_PIN, highPower);
}
Alexa Smart Home Skill code (index.js)JavaScript
This is the main function used to control the Smart Home Devices
//This is the Alexa Smart Home skill code used to control Smart Candles
//Users need to understand the following concepts before creating/modifying this skill:
// - AWS IoT Shadows: https://docs.aws.amazon.com/iot/latest/developerguide/iot-device-shadows.html
// - AWS IoT: https://docs.aws.amazon.com/iot/latest/developerguide/iot-thing-management.html
// - Alexa Smart Home Skill Development: https://developer.amazon.com/docs/smarthome/understand-the-smart-home-skill-api.html
//Please reach out to Darian Johnson (@darianbjohnson via Twitter) for questions


var AWS = require('aws-sdk');
var requestCall = require('request');

//Config variables for IoT Thing
const config = {};
config.IOT_BROKER_ENDPOINT = "XXX.iot.XXX.amazonaws.com";  // REST API endpoint for you IoT Devices
config.IOT_BROKER_REGION = "XXX";  // the region where you build your IoT Thing; for example, us-east-1 for the N. Virginia region

exports.handler = function (request, context) {

    //used when you ask Alexa to discover your devices
    if (request.directive.header.namespace === 'Alexa.Discovery' && request.directive.header.name === 'Discover') {
        var token = request.directive.payload.scope.token;

        //You can comment out the validateUser call if you plan to hard code your ThingName(s)
        validateUser(request, context, token, (userId) => {
            handleDiscovery(request, context, userId);
        });
    }

    //used when you ask Alexa to turn on/off your smart devices
    else if (request.directive.header.namespace === 'Alexa.PowerController') {
        if (request.directive.header.name === 'TurnOn' || request.directive.header.name === 'TurnOff') {
            log("DEBUG:", "TurnOn or TurnOff Request", JSON.stringify(request));
            handlePowerControl(request, context);
        }
    }

    //used when the Alexa App attempts to determine the state of a device
    else if (request.directive.header.namespace === 'Alexa') {
        if (request.directive.header.name === 'ReportState') {
            handleReportState(request, context);
        }
    }

    else{
        var responseHeader = request.directive.header;
        responseHeader.messageId = responseHeader.messageId + "-R";
        responseHeader.name = "ErrorResponse";
        var response = {
            context: contextResult,
            event: {
                header: responseHeader,
                endpoint: {
                    "endpointId": request.directive.endpoint.endpointId
                },
                payload: {
                    "type": "INVALID_DIRECTIVE",
                    "message": "That command is not valid for this device."
                }
            }
        };
        log("DEBUG", "ERROR ", JSON.stringify(response));
        context.succeed(response);
    }

    function handleDiscovery(request, context, userId) {
        //getApplicances passes in a userId (from the validateUser function) and returns all devices
        //associated with a userID. You can remove this call and hardcode the return values.
        //See https://github.com/alexa/alexa-smarthome/blob/master/sample_messages/Discovery/Discovery.response.json
        //for an example of a hardcoded response.

        getApplicances(userId, (errCode, endpoints) => {
            var payload = {
                "endpoints": endpoints
            };
            var header = request.directive.header;
            header.name = "Discover.Response";
            log("DEBUG", "Discovery Response: ", JSON.stringify({ header: header, payload: payload }));
            context.succeed({ event: { header: header, payload: payload } });
        });
    }

    function log(message, message1, message2) {
        console.log(message + message1 + message2);
    }

    //
    function handlePowerControl(request, context) {

        var endpointId = request.directive.endpoint.endpointId; // get device ID for requested device
        var requestMethod = request.directive.header.name; //used to determine if device should be on or off
        var requestToken = request.directive.endpoint.scope.token; // get user token pass in request
        var powerResult;
        var desiredState;

        if (requestMethod === "TurnOn") {
            powerResult = "ON";
            desiredState = true;
        }
        else if (requestMethod === "TurnOff") {
            powerResult = "OFF";
            desiredState = false;
        }

        //this updates the IoT ShadowThing, based on the desired state (on or off)
        handleDevice(endpointId, desiredState, (result) => {

            var d = new Date();
            var isoD = d.toISOString();
            var contextResult = {
                "properties": [
                    {
                        "namespace": "Alexa.PowerController",
                        "name": "powerState",
                        "value": powerResult,
                        "timeOfSample": isoD,
                        "uncertaintyInMilliseconds": 50
                    }, 
                    {
                        "namespace": "Alexa.EndpointHealth",
                        "name": "connectivity",
                        "value": {
                            "value": "OK"
                        },
                        "timeOfSample": isoD,
                        "uncertaintyInMilliseconds": 0
                    }
                ]
            };
            var responseHeader = request.directive.header;
            var responseEndpoint = request.directive.endpoint;
            responseHeader.namespace = "Alexa";
            responseHeader.name = "Response";
            responseHeader.messageId = responseHeader.messageId + "-R";
            var response = {
                context: contextResult,
                event: {
                    header: responseHeader,
                    endpoint: responseEndpoint,
                    payload: {}
                }

            };
            //log("DEBUG", "Alexa.PowerController ", JSON.stringify(response));
            //context.succeed(response);

            //Send and error back if the Shadow Device Reported state is not updated in 6 seconds
            var timeoutObj = setTimeout(() => {
                console.log('timeout beyond time');
                handleError(request, context);
            }, 7000);


            //Check the Shadow Every 0.3 sec to see if the update has occured; if so, send success message
            var intervalObj = setInterval(() => {
                getDeviceStatus(endpointId,(results)=>{
                    if (results === powerResult){
                        log("DEBUG", "Alexa.PowerController ", JSON.stringify(response));
                        context.succeed(response);
                    }
                });
            }, 300);
        });
    }

    //this lets the Alexa app know if the device is on or off   
    function handleReportState(request, context) {
        var endpointId = request.directive.endpoint.endpointId;
        getDeviceStatus(endpointId, (powerResult) => {

            if (powerResult === "ERR") {
                handleError(request, context);
            }
            else {

                var d = new Date();
                var isoD = d.toISOString();
                var contextResult = {
                    "properties": [
                        {
                            "namespace": "Alexa.PowerController",
                            "name": "powerState",
                            "value": powerResult,
                            "timeOfSample": isoD, 
                            "uncertaintyInMilliseconds": 50
                        }
                    ]
                };

                var responseHeader = request.directive.header;
                responseHeader.messageId = responseHeader.messageId + "-R";
                responseHeader.name = "StateReport";
                var response = {
                    context: contextResult,
                    event: {
                        header: responseHeader,
                        payload: {}
                    }
                };
                log("DEBUG", "ReportState ", JSON.stringify(response));
                context.succeed(response);
            }

        });
    }

    //this handles errors if the device is unreachable
    function handleError(request, context) {
        var responseHeader = request.directive.header;
        responseHeader.messageId = responseHeader.messageId + "-R";
        responseHeader.name = "ErrorResponse";
        var response = {
            context: contextResult,
            event: {
                header: responseHeader,
                endpoint: {
                    "endpointId": request.directive.endpoint.endpointId
                },
                payload: {
                    "type": "ENDPOINT_UNREACHABLE",
                    "message": "Unable to reach device because it appears to be offline."
                }
            }
        };
        log("DEBUG", "ERROR ", JSON.stringify(response));
        context.succeed(response);

    }

    //this function is needed if you plan to authenticate against a device cloud
    //You can remove/ignore this function if you plan to hard code values
    function validateUser(request, context, token, callback) {
        if (token === 'access-token-from-skill') {
            callback('test1');
        } else {
            var amznProfileURL = 'https://api.amazon.com/user/profile?access_token=';
            amznProfileURL += token;
            requestCall(amznProfileURL, function (error, response, body) {
                if (error) {
                    var responseHeader = request.directive.header;
                    responseHeader.messageId = responseHeader.messageId + "-R";
                    responseHeader.name = "ErrorResponse";
                    var response = {
                        context: contextResult,
                        event: {
                            header: responseHeader,
                            payload: {
                                "type": "INVALID_AUTHORIZATION_CREDENTIAL",
                                "message": "The authorization credential provided by Alexa is invalid. Disable and re-enable the skill."
                            }
                        }
                    };
                    log("DEBUG", "ERROR ", JSON.stringify(response));
                    context.succeed(response);
                } else {
                    if (response.statusCode == 200) {
                        var parsedBody = JSON.parse(body);
                        console.log(parsedBody.user_id);
                        callback(parsedBody.user_id);
                    } else {
                        var responseHeader = request.directive.header;
                        responseHeader.messageId = responseHeader.messageId + "-R";
                        responseHeader.name = "ErrorResponse";
                        var response = {
                            context: contextResult,
                            event: {
                                header: responseHeader,
                                payload: {
                                    "type": "EXPIRED_AUTHORIZATION_CREDENTIAL",
                                    "message": "The authorization credential provided by Alexa has expired. Disable and re-enable the skill."
                                }
                            }
                        };
                        log("DEBUG", "ERROR ", JSON.stringify(response));
                        context.succeed(response);
                    }
                }
            });
        }
    
    }
};

//this function returns all the devices associated with an authenticaed user (listed in a DynamoDB table)
//You can remove/ignore this function if you plan to hard code values
function getApplicances(userId, callback) {

    var params = {
        TableName: "WaxOn_Appliances",
        IndexName: "userId-index",
        KeyConditionExpression: "#userId = :userId",
        ExpressionAttributeNames: {
            "#userId": "userId"
        },
        ExpressionAttributeValues: {
            ":userId": userId
        }
    };

    var docClient = new AWS.DynamoDB.DocumentClient();

    var endpoints = [];

    var p = new Promise((resolve, reject) => {
        docClient.query(params, function (err, data) {
            if (err) {
                console.log(err);
                reject(endpoints);
            } else {
                data.Items.forEach(function (item) {
                    var endpointDetails = {};
                    endpointDetails.endpointId = item.applianceId;
                    endpointDetails.description = item.description;
                    endpointDetails.friendlyName = item.friendlyName;
                    endpointDetails.displayCategories = [item.displayCategories];
                    endpointDetails.manufacturerName = "WaxOn Smart Candle - DIY";
                    endpointDetails.capabilities =
                        [
                            {
                                "type": "AlexaInterface",
                                "interface": "Alexa",
                                "version": "3"
                            },
                            {
                                "interface": "Alexa.PowerController",
                                "version": "3",
                                "type": "AlexaInterface",
                                "properties": {
                                    "supported": [{
                                        "name": "powerState"
                                    }],
                                    "retrievable": true,
                                    "proactivelyReported": false
                                    /*"proactivelyReported": true, "retrievable": true */
                                }
                            }
                        ];
                    endpoints.push(endpointDetails);
                });

                resolve(endpoints);
            }
        });
    });

    p.then(value => {

        if (value.length < 0) {
            callback(0, null);
        }
        else {
            callback(1, value);
        }

    }, reason => {
        console.log(reason); // Error!
        callback(-1, null);
    });
}

//This function updates the IoT Shadow Thing to turn the desired state on or off
//the ESP8266 will update the ShadowThing to reflect the "reported" state as on/off
//after it recieves the ShadowThing Update
function handleDevice(endpointId, desiredState, callback) {
    // update AWS IOT thing shadow
    AWS.config.region = config.IOT_BROKER_REGION;

    var param = { "on": desiredState }

    var paramsUpdate = {
        "thingName": endpointId,
        "payload": JSON.stringify(
            {
                "state":
                    {
                        "desired": param
                    }
            }
        )
    };

    var iotData = new AWS.IotData({ endpoint: config.IOT_BROKER_ENDPOINT });

    iotData.updateThingShadow(paramsUpdate, function (err, data) {
        if (err) {
            console.log("an error");
            callback("not ok");
        }
        else {
            console.log("success");
            callback("ok");
        }
    });

}

//This function returns the current state (on/off) of the device
function getDeviceStatus(endpointId, callback) {
    // update AWS IOT thing shadow
    AWS.config.region = config.IOT_BROKER_REGION;

    var params = {
        "thingName": endpointId
    };

    var iotData = new AWS.IotData({ endpoint: config.IOT_BROKER_ENDPOINT });

    iotData.getThingShadow(params, function (err, data) {
        if (err) {
            console.log(err);
            callback("ERR");
        }
        else {
            var results = JSON.parse(data.payload)
            var deviceState = results.state.reported.on;
            if (!deviceState) {
                callback("OFF");
            } else {
                callback("ON");
            }
        }
    });

}
ESP8266 Code (connects to AWS Iot) - (init.js)JavaScript
This code is installed on the ESP8266 via Mongoose OS
//Code that is installed on ESP8266 via Mongoose OS


load("api_aws.js");
load('api_gpio.js');

let ledPin = 0; //led indicator on ESP8266 device
let buttonPin = 2; //button
let prominiPin = 15; //connected to Pro Mini to indicate if the LED should be on or off
let state = { on: false, counter: 0 };  // device state: shadow metadata

GPIO.set_mode(ledPin,GPIO.MODE_OUTPUT);
GPIO.set_mode(prominiPin,GPIO.MODE_OUTPUT);


// Upon startup, report current actual state, "reported"
// When cloud sends us a command to update state ("desired"), do it
AWS.Shadow.setStateHandler(function(data, event, reported, desired, reported_metadata, desired_metadata) {
  if (event === AWS.Shadow.CONNECTED) {
    AWS.Shadow.update(0, {reported: state});  // Report device state
  } else if (event === AWS.Shadow.UPDATE_DELTA) {
    for (let key in state) {
      if (desired[key] !== undefined) state[key] = desired[key];
    }
    AWS.Shadow.update(0, {reported: state});  // Report device state
  }
  print(JSON.stringify(reported), JSON.stringify(desired));
  if (!reported.on){
    GPIO.write(ledPin,1); 		//ESP8266 Light off
    GPIO.write(prominiPin,0); 	//Candle off
  }else{
    GPIO.write(ledPin,0);		//ESP8266 Light on
    GPIO.write(prominiPin,1); 	//Candle on
  }
}, null);

// On a button press, update press counter via the shadow
GPIO.set_button_handler(buttonPin, GPIO.PULL_UP, GPIO.INT_EDGE_NEG, 200, function() {
  state.on = !state.on;
  AWS.Shadow.update(0, {desired: {on: state.on, counter: state.counter + 1}}); //update ShadowThing to turn on/off device (desired state)
}, null);

Custom parts and enclosures

Candle Insert Holder
This "clips" to the LED holder and is used to hold the candle in the base
Candle Base Top
The main candle holder. I've also uploaded the Fusion 360 version (so that you can add a different name or design to the front).
Candle Base Top (Fusion 360 file)
candle_holder_eS4Jul1JWZ.f3d
LED Holder
Holds the LED
Candle Holder Bottom
The bottom of the candle holder

Schematics

Breadboard Diagram
Details on the wiring of the smart candle module.
Smartcandle bb3 sccpw8cog8
VUI diagram
High level voice interface between Alexa and the device
Vui large zanbykllur

Comments

Similar projects you might like

Hygge Home - Alexa Smart Bath

Project tutorial by J Howard

  • 5,356 views
  • 2 comments
  • 18 respects

Rampiot - Cool Smart Lock

Project tutorial by Robinson Mesino

  • 3,695 views
  • 1 comment
  • 23 respects

Amazon Alexa / Arduino YÚN Smart Home Light Sample

Project showcase by Noctuvigilus

  • 3,242 views
  • 0 comments
  • 9 respects

BOFF - Alexa Enabled Open Smart Fan

Project showcase by Stephen Harrison

  • 7,693 views
  • 6 comments
  • 28 respects

Animated Smart Light with Alexa and Arduino

Project tutorial by Bruno Portaluri

  • 3,655 views
  • 9 comments
  • 23 respects

Arduino-Powered Smart Light (Works with Amazon Echo)

Project tutorial by Tinker Project

  • 14,748 views
  • 7 comments
  • 29 respects
Add projectSign up / Login