Off-The-Shelf Hacker: Control Your Home Projects with Amazon Alexa

One of the great things about working with modern microcontrollers is that you can massively alter device behavior by simply modding the firmware and maybe adding a circuit or two.
We’ve been hacking a NodeMCU WiFi unit, a little 5-volt power supply, a passive infrared sensor (PIR) and a relay board into a backyard sensor light. This project uses MQTT messaging to control the light and possibly post a notice that an “intruder” has passed in front of the PIR sensor. The hardware and firmware work very well and I’ll soon cram the whole works into a standard plastic electrical box and install it at the back corner of my house.
I recently ran across an article about FauxmoESP, an Arduino library that works with the Amazon Alexa voice services. A little over an hour later, I was delighted to say “Alexa, turn on the dining room light” and poof, on went the dining room light. We’ll revert back to the yard sensor firmware shortly.
Let’s look at the details of this “temporary” exploratory build.
Add a Socket
Modifying the backyard light sensor project to try the Alexa library was easy and fast.
I wanted to keep the Alexa test simple, so I just unplugged the PIR sensor and set it aside on my desk. I then soldered some 18 gauge wire across the A/C input to the 5-volt power supply, so I could power a socket for the dining room light. The socket is wired in series with the relay normally-open (NO) contacts and the A/C line. Energize the relay, via the D1 general purpose input/output (GPIO) pin on the NodeMCU microcontroller and the light turns on. The D1 pin corresponds to GPIO5 in the code.

A/C outlet wires added to bottom of the input on the 5-volt power supply
Be extra careful when working with 120-volt A/C power and live bare contacts. An electrical shock is painful and possibly very bad for your health.
The contacts on the relay board are rated at 10 amps at 120-volts A/C. The hanging dining room light I used for the test uses three 7.5-watt LED bulbs. When the hardware gets moved out to the backyard and back under MQTT control, I’ll use two 150-watt incandescent outdoor floodlight bulbs. That’s 300-watts, which is below the capacity of the relay contacts. I may opt for a pair of 250-watt equivalent (32-watt actual) LED bulbs from Lowes. The $35 per bulb price is a little steep though. That would also be, at 64 watts, well within the capacity of the relay switch contacts. I’ll need the 18 gauge wire to hook the yard sensor device up to the floodlights.
That’s it for the hardware mods, now let’s look at the firmware.
Using the FauxmoESP Library
The FauxmoESP library is bundled into the last couple of Arduino IDE versions. I used the Arduino IDE version 1.8.7. 3.1.0 was the version of FauxmoESP.
Bring up the Arduino IDE and click “Include Library” under the “Sketch” tab. Slide over and click “Manage Libraries.” Type “fauxmo” into the search box and hit enter.
The FauxmoESP library should show up. Click the “install” button to use it in your sketches. Close down the “Manage Library” window to get back to the main Arduino IDE screen.
I used the fauxmoesp-basic sketch as the basis for my test. Choose “Examples” under the “File” main tab. Look way down in the “custom libraries” section for the fauxmoesp examples.
Much of the sketch was left unchanged. I changed the value of the “#define LED_BLUE” to 16 because that is the pin controlling one of the blue onboard LEDs. The value of the “#define LED_GREEN was changed to 5 to reflect NodeMCU pin that controls the relay board. A little further down in the code, the “#define ID_GREEN” was changed to “dining room light.” The words in this section correspond to what you will actually say when you ask Alexa to turn something on or off.
The code is pretty straightforward. I left the unused “lamps” so readers will have some context for my minimal changes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
#include #ifdef ESP32 #include #else #include #endif #include "fauxmoESP.h" // Rename the credentials.sample.h file to credentials.h and // edit it according to your router configuration #include "credentials.h" fauxmoESP fauxmo; // ----------------------------------------------------------------------------- #define SERIAL_BAUDRATE 115200 #define LED_YELLOW 4 #define LED_GREEN 5 #define LED_BLUE 16 #define LED_PINK 2 #define LED_WHITE 15 #define ID_YELLOW "yellow lamp" #define ID_GREEN "dining room light" #define ID_BLUE "blue lamp" #define ID_PINK "pink lamp" #define ID_WHITE "white lamp" // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // Wifi // ----------------------------------------------------------------------------- void wifiSetup() { // Set WIFI module to STA mode WiFi.mode(WIFI_STA); // Connect Serial.printf("[WIFI] Connecting to %s ", WIFI_SSID); WiFi.begin(WIFI_SSID, WIFI_PASS); // Wait while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(100); } Serial.println(); // Connected! Serial.printf("[WIFI] STATION Mode, SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); } void setup() { // Init serial port and clean garbage Serial.begin(SERIAL_BAUDRATE); Serial.println(); Serial.println(); // LEDs pinMode(LED_YELLOW, OUTPUT); pinMode(LED_GREEN, OUTPUT); pinMode(LED_BLUE, OUTPUT); pinMode(LED_PINK, OUTPUT); pinMode(LED_WHITE, OUTPUT); digitalWrite(LED_YELLOW, LOW); digitalWrite(LED_GREEN, LOW); digitalWrite(LED_BLUE, LOW); digitalWrite(LED_PINK, LOW); digitalWrite(LED_WHITE, LOW); // Wifi wifiSetup(); // By default, fauxmoESP creates it's own webserver on the defined port // The TCP port must be 80 for gen3 devices (default is 1901) // This has to be done before the call to enable() fauxmo.createServer(true); // not needed, this is the default value fauxmo.setPort(80); // This is required for gen3 devices // You have to call enable(true) once you have a WiFi connection // You can enable or disable the library at any moment // Disabling it will prevent the devices from being discovered and switched fauxmo.enable(true); // You can use different ways to invoke alexa to modify the devices state: // "Alexa, turn yellow lamp on" // "Alexa, turn on yellow lamp // "Alexa, set yellow lamp to fifty" (50 means 50% of brightness, note, this example does not use this functionality) // Add virtual devices fauxmo.addDevice(ID_YELLOW); fauxmo.addDevice(ID_GREEN); fauxmo.addDevice(ID_BLUE); fauxmo.addDevice(ID_PINK); fauxmo.addDevice(ID_WHITE); fauxmo.onSetState([](unsigned char device_id, const char * device_name, bool state, unsigned char value) { // Callback when a command from Alexa is received. // You can use device_id or device_name to choose the element to perform an action onto (relay, LED,...) // State is a boolean (ON/OFF) and value a number from 0 to 255 (if you say "set kitchen light to 50%" you will receive a 128 here). // Just remember not to delay too much here, this is a callback, exit as soon as possible. // If you have to do something more involved here set a flag and process it in your main loop. Serial.printf("[MAIN] Device #%d (%s) state: %s value: %d\n", device_id, device_name, state ? "ON" : "OFF", value); // Checking for device_id is simpler if you are certain about the order they are loaded and it does not change. // Otherwise comparing the device_name is safer. if (strcmp(device_name, ID_YELLOW)==0) { digitalWrite(LED_YELLOW, state ? HIGH : LOW); } else if (strcmp(device_name, ID_GREEN)==0) { digitalWrite(LED_GREEN, state ? HIGH : LOW); } else if (strcmp(device_name, ID_BLUE)==0) { digitalWrite(LED_BLUE, state ? HIGH : LOW); } else if (strcmp(device_name, ID_PINK)==0) { digitalWrite(LED_PINK, state ? HIGH : LOW); } else if (strcmp(device_name, ID_WHITE)==0) { digitalWrite(LED_WHITE, state ? HIGH : LOW); } }); } void loop() { // fauxmoESP uses an async TCP server but a sync UDP server // Therefore, we have to manually poll for UDP packets fauxmo.handle(); // This is a sample code to output free heap every 5 seconds // This is a cheap way to detect memory leaks static unsigned long last = millis(); if (millis() - last > 5000) { last = millis(); Serial.printf("[MAIN] Free heap: %d bytes\n", ESP.getFreeHeap()); } // If your device state is changed by any other means (MQTT, physical button,...) // you can instruct the library to report the new state to Alexa on next request: // fauxmo.setState(ID_YELLOW, true, 255); } |
Bringing up the example script also loads a file called “credentials.h.” You should see a file tab in the main Arduino IDE with that name. Click on the tab and change the SSID and PASS entries to match your local network WiFi access point values before trying to compile the code.
Compilation and Upload Challenges
A couple of areas slowed progress a bit.
First, there was an “Async” compile error. A quick trip to the internet revealed that the FauxmoESP library is dependent on the Async TCP library for the ESP8266. Go to the Async TCP library GitHub page and click the “Download” button at the top right. Select the “Download Zip” button and save the “ESPAsyncTCP-master.zip” file to a local directory. Again go to “Include Library” under the “Sketch” tab and click on “Add Zip Library.” Enter the location of the downloaded “ESPAsync” zip file and hit return. That fixes the compilation problem.
Next, the resultant binary file simply wouldn’t upload. I again searched the Web and found that you could just change the “IwIP variant” parameter, under the “Tools” tab from “v2 – low bandwidth” to “v2 – high bandwidth” and everything will work properly. After this change, the file uploaded to the NodeMCU device without any issues. Easy.
Overall, the whole process, including troubleshooting took about an hour and a half. Google and the other search engines are still your friends when trying to run down some obscure one-off problem.
Results And What’s Next
I’ve had the NodeMCU, relay board and power supply running on my dining room table for a few days and looks very reliable. Of course, you also have to have a working Alexa device on your local network and have it paired with the NodeMCU board. My Alexa Echo Dot is a generation 2 device. With everything plugged in I asked, “Alexa, find my devices” and waited for her to complete the discovery routine.
Note that I changed the “green lamp” to “dining room light” in the one define line. If you request that Alexa to “turn on the dining room lamp,” she will ask if you mean “dining room light,” which I found thoughtful. I replied “yes” and the light came on.
Interesting, after a few days, my wife even started using voice commands to turn on the light. Nice.
You can also control the NodeMCU device using the Alexa Android app, both over WiFi and 4/5G networks. The buttons and voice control work great remotely using the app.
I’ll revert back to the MQTT backyard sensor light firmware after finishing this story and put the gadget out in the yard for testing, soon. Time to order a new batch of NodeMCU boards.