We all had our projects during one of the many lockdowns and an ‘Alexa Enabled Secret Bar’ was mine! This project started out with building two industrial sideboards for the corner of my dining room. The question: what to do with dead space in the corner? The answer: a pop up unit that made full use of that space. And hence, the idea for an Alexa Enabled Secret Bar was born.
In this 3 part series of posts, I’m going to give you a good idea on how to build your own version of an Alexa enabled secret bar. But, at the end of the day, this is just a stepper motor hooked up to a WIFI enabled controller. So I am sure there are many other projects you could do with this method. Automated blinds, window/door openers, extending shelve….you get the idea! I’d be really interested to hear about any project to get up to.
If you haven’t already, check out the video of the finished project. I started writing this post but it has quickly grown! So I have split this into 3 separate posts:
- The Mechanics
- The Electronics
- The Firmware & Software
Due to the code, this one has turned out pretty long. If you’re not interested in reading any of the below and just want to skip on to downloading some code – then click here!
Part 3A: Firmware
I’m sure someone will correct me on this point but I am calling the code that I put on the microcontroller: the firmware. As apposed to the Amazon Alexa App which I am calling: ‘the software’. Feel free to disagree with me on this point :). From a “Firmware” point of view, there are 6 keys bits:
- Defining our pins and values
- The all important set up
- LED Strip Light Control
- Stepper Motor Control (& Limit Switch)
- Touch Switch Control
- The final Loop
Hopefully by now you have read my ‘How to Control a Stepper Motor from Alexa‘ post which goes into detail on how to upload the code to your ESP32 using the Arduino IDE so I’m going to skip over that here.
Defining our Pins and Key Values
So, lets start with the basics and make sure we include the Wifi, ESPAlexa and AccelStepper Libraries:
/* Alexa controlled Bar Project Aug 2020
* Created by Wayne Morton https://MechApe.co.uk, with a lot of work from Aircoookie and Mike McCauley
* Code in public domain
* Feel free to use, modify, share or completely discard!
*/
#ifdef ARDUINO_ARCH_ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Espalexa.h> //awesome lib by Aircoookie
#include <AccelStepper.h> //awesome lib by Mike McCauley at http://www.airspayce.com/mikem/arduino/AccelStepper/
Next we need to start defining the pins we have used in our electronics wiring for the LEDs and stepper motor driver:
//pins used
#define dirPin 2
#define stepPin 4
#define sleep 17
#define homingSwitch 16 //Push to make button
const int redPin = 27; //was 14 - My LED Strip must be defective!
const int greenPin = 14; //was 27 - My LED Strip must be defective!
const int bluePin = 26;
const int whitePin = 25;
long homePosition=-1;
int state = HIGH; //is the bar open (High) or closed (Low)
// Create a new instance of the AccelStepper class:
#define motorInterfaceType 1
AccelStepper stepper = AccelStepper(motorInterfaceType, stepPin, dirPin);
Whilst we are talking about the LEDs, lets set up some constants for the PWM channels for the ESP32. Note that the ESP32 deals with PWM controls slightly to, say, the Arduinos. On an Arduino, you can simply write something like analogWrite(126)
which will output a PWM signal with a 50% duty. The ESP32 has an inbuilt LED controller which can be used to output PWM signals and just needs a little more setting up. Random Nerds have a great tutorial on this if you want any more info.
//Setting the PWM info
const int redChannel = 0; //Note that the channels start from 0
const int greenChannel = 1;
const int blueChannel = 2;
const int whiteChannel = 3;
const int freq = 5000; //5KhZ
const int resolution = 8; // Bit resolution 2^8 = 256
Great, now lets deal with the touch pin. The below are the final values I ended up with, but it’s highly likely you’ll need to have a play with these numbers to suit your set up. For example, it’s likely you’ll need to play with the “touchTrigger” value. Once you have your system set up and working, you can read a live value for your touch pin using something like Serial.println(touchReading);
. This will print the current value to the serial monitor.
//touch pin (toggle) details
#define touchPin 13 //touch pin for open / close
int touchReading; //the current state of the touch pin
int touchPrevious = 0; //the previous reading from the touch pin
int touchTrigger = 5; // trigger value
long timer = 0; // the last time the bar was toggled
long debounce = 5000; // the debounce time, increase if the output flickers
The final step is getting things ready for ESPAlexa. The excellent ESPAlexa library is focused around smart lighting, but that doesn’t mean it can’t be used for other devices like stepper motors. In my case, I just modified the default ESPAlexa code to suit my needs. Hence you will notice reference to firstLightChanged(uint8_t brightness)
in my code when I’m referring to the Stepper Motor. My other functions are colourChange(EspalexaDevice* dev);
for the LED strip RGB values and whiteChange(EspalexaDevice* dev);
for the Warm White LEDs. This means I have 3 “lights” that appear in my Alexa App: the stepper, the RGB and the Warm White. If you prefer, you can group the RGB and Warm White into one function in ESPAlexa and hence only have 2 “lights” in your Alexa App.
// values
boolean connectWifi();
//callback functions
void firstLightChanged(uint8_t brightness);
void colourChange(EspalexaDevice* dev);
void whiteChange(EspalexaDevice* dev);
// Change this!!
const char* ssid = "SSID";
const char* password = "PASSWORD";
boolean wifiConnected = false;
Espalexa espalexa;
EspalexaDevice* TheBar; //this is optional
EspalexaDevice* colourBar;
EspalexaDevice* whiteBar;
The all important set up
Great, now we can get into things. First things first, lets boot up the serial monitor so we know what our ESP32 is doing. Next, remember how I said the ESP has an LED controller that needs a bit of set up? Well let’s get that up and running with the values we started earlier:
void setup()
{
Serial.begin(115200);
delay(1000); //allow time for the serial monitor
//set PWM functionalities
ledcSetup(redChannel, freq, resolution);
ledcSetup(greenChannel, freq, resolution);
ledcSetup(blueChannel, freq, resolution);
ledcSetup(whiteChannel, freq, resolution);
// attach the PWM channel to the GPIO to be controlled
ledcAttachPin(redPin, redChannel);
ledcAttachPin(greenPin, greenChannel);
ledcAttachPin(bluePin, blueChannel);
ledcAttachPin(whitePin, whiteChannel);
//Ensure LEDs are off at startup
ledcWrite(redChannel, 0);
ledcWrite(greenChannel, 0);
ledcWrite(blueChannel, 0);
ledcWrite(whiteChannel, 0);
Next we want to connect to WiFi and set up our ESPAlexa devices. The ESPAlexa library provides us with several device types to chose from. Notice in the below code how the Bar (stepper motor), colour and white lights are all set up slightly differently and some have device types defined? This means that the Alexa app will give us different options for each:
- The Bar (stepper motor) is the default simple device with a simple percentage scale 0-100%. Later, I have coded only 2 states: 0% means close the bar and 100% means open the bar. Anything in between will do nothing.
- The “color” – other than being spelt wrong – is a device type that allows control of the RGB to turn some pre-set colours. This is neat as it allows basic colours without having to exit the Alexa app – for example with the Hue lighting app.
- The white light is similar to the stepper motor and is a basic dimmable device with a percentage 0-100%. The difference here is that the LEDs will work throughout the scale – not just for 0% and 100%. For example, 50% sets the white LEDs to half the brightness. There was no need to specifically define the device here since the default is also dimmer – but hopefully it shows the idea.
// Initialise wifi connection
wifiConnected = connectWifi();
if(wifiConnected){
// Define your devices here.
TheBar = new EspalexaDevice("The Bar", firstLightChanged); //define 'device[0]' as the device with friendly name 'The Bar'
espalexa.addDevice(TheBar); //add the device to ESP Alexa
colourBar = new EspalexaDevice("Bar Colour Light", colourChange, EspalexaDeviceType::color); //color
espalexa.addDevice(colourBar);
whiteBar = new EspalexaDevice("Bar White Light", whiteChange, EspalexaDeviceType::dimmable); //basic dimamable device
espalexa.addDevice(whiteBar);
espalexa.begin();
} else
{
while (1) {
Serial.println("Cannot connect to WiFi. Please check data and reset the ESP.");
delay(2500);
}
}
Great, the final step of our set up is to make sure our stepper motor is ready to go and is in the “closed” position at start up. This means we always know the initial state of our device. We’ll get into the motorHome() function later in this post.
pinMode(sleep, OUTPUT);
pinMode(homingSwitch, INPUT);
delay(5000); //5s wait
motorHome(); //home the motor on startup so we start from the base
LED Strip Light Control
Since the ESPAlexa library was built for controlling lights, controlling the LEDs is relatively easy. In fact, since we have already set up our PWM controls for the ESP, it is only 1 line for code for each LED:
ledcWrite(redChannel, d->getR()*d->getPercent()*0.01);
For troubleshooting, you can also output the percentage of each LED to the serial monitor. So our complete colourChange
function would look something like:
void colourChange (EspalexaDevice* d){
Serial.print("The brightness % is...");
Serial.println(d->getPercent());
Serial.print("The red value is...");
Serial.println(d->getR()*d->getPercent()*0.01);
Serial.print("The green value is...");
Serial.println(d->getG()*d->getPercent()*0.01);
Serial.print("The blue value is...");
Serial.println(d->getB()*d->getPercent()*0.01);
ledcWrite(redChannel, d->getR()*d->getPercent()*0.01);
ledcWrite(greenChannel, d->getG()*d->getPercent()*0.01);
ledcWrite(blueChannel, d->getB()*d->getPercent()*0.01);
}
And our whiteChange
function would look like:
void whiteChange (EspalexaDevice* d){
Serial.print("The white value is...");
Serial.println(d->getW()*d->getPercent()*0.01);
ledcWrite(whiteChannel, d->getPercent());
}
Stepper Motor Control (& Limit Switch)
There are 3 functions which control the stepper motor: motorHome
, barOpen
and barClose
. Then finally, we have a firstlightChanged
function which is the ESPAlexa handler function. If you wanted, you could definitely simplify these into fewer functions.
motorHome function
motorHome
is a function which basically reverses the stepper motor until it hits a switch. This way, the “zero” or “home” point is known. In my case this is the bar closed position. Knowing this point allows the system to not destroy itself when, say the power get cut or the stepper motor skips. In addition to moving the stepper motor, we can also change the LED light colours to indicate that the bar is moving.
Hopefully all the comments in the below code are enough to help you understand what’s going on:
void motorHome(){
Serial.println("Homing Motor");
ledcWrite(whiteChannel, 0);
ledcWrite(redChannel, 255); //set the LED lights to Red - red always means danger right?!
ledcWrite(greenChannel, 0);
ledcWrite(blueChannel, 0);
stepper.setMaxSpeed(25000); //initial speed should be low for homing
stepper.setAcceleration(500); //motor acceleration
digitalWrite(sleep, LOW); //turn on the stepper driver
while (digitalRead(homingSwitch)==LOW) { //if the switch is open, loop through this
stepper.moveTo(homePosition); //move to the current home position
homePosition--; //decrease the home position by 1
stepper.runSpeedToPosition(); //stepper.runSpeedToPosition() move the motor without acceleration
//delay (1); //wait 10ms for things to settle (uncomment if needed)
}
stepper.setCurrentPosition(0); //set the current position as home
Serial.println("Homing Complete");
digitalWrite(sleep, HIGH); //Can now turn off the stepper driver since bar is supported on frame
state = LOW;
delay (5000);
//turn off the leds
ledcWrite(whiteChannel, 0);
ledcWrite(redChannel, 0);
ledcWrite(greenChannel, 0);
ledcWrite(blueChannel, 0);
whiteBar -> setValue(0); //Tell Alexa the white lights are off
colourBar -> setValue(0); //Tell Alexa the colour lights are off
delay (5000); //wait 5s
}
barOpen function
The barOpen
function is exactly what it says on the tin – move the stepper motor until the bar is open (a known number of turns). Here, you will have to do a bit of experimenting with your set up. My “open” position turned out to be 1700000 number of steps (well, microsteps). Like the homing function, we will also play with the LEDs so it is obvious that the bar is opening. Since we utilised the AccelStepper library, the stepper motor speed and acceleration can be controlled and the values have been set here.
void barOpen () {
Serial.println("Bar opening");
ledcWrite(whiteChannel, 255); //turn on the white LED lights
whiteBar -> setValue(255); //tell Alexa the white lights are on
stepper.setMaxSpeed(30000); //motor speed
stepper.setAcceleration(1000); //motor acceleration
digitalWrite(sleep, LOW); //turn on the stepper driver
stepper.moveTo(1700000); //top position of lift
stepper.runToPosition();
digitalWrite(sleep, HIGH); //Can now turn off the stepper driver - plenty of friction in the screw
state = HIGH;
}
barClose function
Now we have a home position and an open position, the last step is to close the bar. In actual fact, the below code is not really needed. The reason is, every time we want to close the bar, we could just home it again until it reached a zero point. In any case, it is a very simple bit of code:
void barClose () {
Serial.println("Bar closing");
digitalWrite(sleep, LOW); //Turn on the stepper driver
stepper.moveTo(0); //move the stepper motor to the home position
stepper.runToPosition();
digitalWrite(sleep, HIGH); //turn off the stepper driver
//turn off the lights
state = LOW;
}
firstlightChanged function
Why is it called firstlightChanged
? Well, like I mentioned earlier, ESPAlexa library is focused around smart lighting, but that doesn’t mean it can’t be used for other devices like stepper motors. In my case, I just modified the default ESPAlexa code to suit my needs. All this code does is call up the functions we have discussed earlier and lets Alexa know the new state of the device.
void firstLightChanged(uint8_t brightness) {
//Serial.print("Device 1 changed to ");
//do what you need to do here
switch (brightness) {
case 255: //switch is on
Serial.println("The bar is opening!");
barOpen();
break;
case 0: //switch is off
Serial.println("Last orders, the bar is closing");
motorHome();
//barClose();
break;
default: //optional - all other cases
break;
}
}
Touch Switch Control (And the main loop!)
Fantastic, we are almost there. The last thing is to integrate a touch switch. I wanted this to run in the main loop of the code so that if there was every any problems with wifi/Alexa etc. – I would always still have a physical button to press. This means I should always be able to get to my whiskey wifi or not!
The method I used for the touch switch is to read the value of the touch, save that value, then read it again. We then compare those values to a set value (the “touch trigger” point). If the values are less than the trigger point, do nothing. If they are greater than that value, then the button is being pressed and we can then open the bar, play with lights etc. In addition to these, we add a debouce check – this ensures that our output doesn’t flicker between states.
void loop()
{
espalexa.loop();
delay(1);
touchReading = touchRead(touchPin);
//Serial.println(touchReading);
if(touchReading < touchTrigger && touchPrevious < touchTrigger && millis() - timer > debounce){
if (state == HIGH) {
firstLightChanged(0);
TheBar -> setValue(0); //tell Alexa the bar is closed
//whiteChange(0);
ledcWrite(whiteChannel, 0);
whiteBar -> setValue(0); //Tell Alexa the white lights are off
}
else {
//whiteChange(255); //turn on the white light
ledcWrite(whiteChannel, 255);
whiteBar -> setValue(255); //tell Alexa the white lights are on
firstLightChanged(255); //open the bar
TheBar -> setValue(255); //tell Alexa the bar is open
}
timer = millis();
}
touchPrevious = touchReading;
}
Part 3B: The Software
This is where you can get creative. Using the Alexa app, you can set up several different ways to impress your friends. For example, I’ve set up 2 routines on my system:
- “Alexa, open the bar!” – Alexa replies with “no problem, I got this” and then sets The Bar “light” to on. Hence, the bar opens.
- “Alexa, last orders!” – Alexa replies with “quickly, get those last drinks in” then waits a few minutes before closing the bar.
I won’t go into the details of how to set up these routines since there are millions of blog posts out there and lots of information from Amazon themselves. Check out this webpage for starters.
Final Thoughts! Is ESPAlexa still going for you?
Unfortunately, as of March 2022, I think the ESPAlexa library has a few issues. There has been a lot of issues reported on the GitHub page and not too much movement on these. Mine seems to work most of the time. However, it occasionally doesn’t. Luckily, all is not lost! When the ESPAlexa drops out, my bar can still be accessed through the capacitive touch button. So I can still get to my whiskey! Cheers!
The Final Code
/* Alexa controlled Bar Project March 2022
* Created by Wayne Morton https://MechApe.co.uk, with a lot of work from Aircoookie and Mike McCauley
* Code in public domain
* Feel free to use, modify, share or completely discard!
*/
#ifdef ARDUINO_ARCH_ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Espalexa.h> //awesome lib by Aircoookie
#include <AccelStepper.h> //awesome lib by Mike McCauley at http://www.airspayce.com/mikem/arduino/AccelStepper/
//pins used to connect my hardware
#define dirPin 2
#define stepPin 4
#define sleep 0
#define homingSwitch 16 //Push to make button
// values
boolean connectWifi();
long homePosition=-1;
int state = HIGH; //is the bar open (High) or closed (Low)
//callback functions
void firstLightChanged(uint8_t brightness); //this is the fuction for ESPAlexa
// Create a new instance of the AccelStepper class:
#define motorInterfaceType 1
AccelStepper stepper = AccelStepper(motorInterfaceType, stepPin, dirPin); //since we are using an external motor driver
// Wifi Details
const char* ssid = "SSID"; //add in your WiFi details here
const char* password = "PASSWORD"; //add in your WiFi detailed here
boolean wifiConnected = false;
Espalexa espalexa;
EspalexaDevice* TheBar; //this is the name of our device for ESPAlexa
void setup()
{
Serial.begin(115200); //useful for trouble shooting your code
delay(1000); //allow time for the serial monitor
// Initialise wifi connection
wifiConnected = connectWifi();
if(wifiConnected){
// Define your devices here.
TheBar = new EspalexaDevice("The Bar", firstLightChanged); //sets 'device[0]' as the 'TheBar' device with friendly name 'The Bar'
espalexa.addDevice(TheBar); //add the device to ESP Alexa
espalexa.begin();
} else
{
while (1) {
Serial.println("Cannot connect to WiFi. Please check data and reset the ESP.");
delay(2500);
}
}
pinMode(sleep, OUTPUT);
pinMode(homingSwitch, INPUT);
delay(10000); //10s wait
motorHome(); //home the motor on startup so we start from the base
}
void loop(){
espalexa.loop();
delay(1);
}
//our callback functions
void firstLightChanged(uint8_t brightness) {
Serial.print("Device 1 changed to ");
//do what you need to do here
switch (brightness) {
case 255: //switch is on
Serial.println("The bar is opening");
barOpen();
break;
case 0: //switch is off
Serial.println("Last orders, the bar is closing");
barClose();
break;
default: //optional - all other cases
break;
}
}
void motorHome(){
Serial.println("Homing Motor");
stepper.setMaxSpeed(1000); //initial speed should be low for homing
digitalWrite(sleep, HIGH); //turn on the stepper driver
while (digitalRead(homingSwitch)==LOW) { //if the switch is open, loop through this
stepper.moveTo(homePosition); //move to the current home position
homePosition--; //decrease the home position by 1
stepper.runSpeedToPosition(); //move the motor without acceleration
delay (10); //wait 10ms
}
stepper.setCurrentPosition(0);
Serial.println("Homing Complete");
digitalWrite(sleep, LOW); //Can now turn off the stepper driver since bar is supported on frame
state = LOW;
}
void barOpen () {
Serial.println("Bar opening");
stepper.setMaxSpeed(4000); //motor speed
stepper.setAcceleration(100); //motor acceleration
digitalWrite(sleep, HIGH); //turn on the stepper driver
stepper.moveTo(20000); //top position of lift
stepper.runToPosition();
state = HIGH;
}
void barClose () {
Serial.println("Bar closing");
stepper.moveTo(0);
stepper.runToPosition();
digitalWrite(sleep, LOW); //turn off the stepper driver
state = LOW;
}
// connect to wifi – returns true if successful or false if not
boolean connectWifi(){
boolean state = true;
int i = 0;
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
Serial.println("Connecting to WiFi");
// Wait for connection
Serial.print("Connecting...");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (i > 20){
state = false; break;
}
i++;
}
Serial.println("");
if (state){
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
else {
Serial.println("Connection failed.");
}
return state;
}