Home Electronics Mechanical Random

Alexa controlled ceiling light

Using a reprogrammed Sonoff Basic to add Alexa control to the LED ceiling light in Lucas' bedroom.

I picked this lamp up from Amazon UK for £12 on Prime, because there was a pendant light in there, but after putting a cabin bed in, he ended up sharing his bed with the lampshade.
The light itself is nice - an aluminium housing with frosted plastic window and LED tape as the light source. It's earthed, has a sensible mounting bracket, and plenty of heatsink for the LEDs.

I reprogrammed an old Sonoff to use the ESPAlexa library for Alexa integration, the WiFi UDP library to link it to my own home automation stuff, and monitor the spare IO pin on the programming header for manual override.
Although I could have extended the IO pin across the room, in the past I've found that switching noise and lightning can trigger pins wired like this. To avoid this interference, I added an optoisolator, so it takes a couple of milliamps to trigger the input pin.

Watch on YouTube:

The LED light fitting: https://www.amazon.co.uk/gp/product/B075T29JWV
Right now (4/4/2019) it's not on Prime, but sometimes it is!

The ESP8266 source code:

/* Smarter light fitting... Andy, ymmv.co.uk.  
 *  This is the code I put together for my son's bedroom light.  It allows Alexa control, 
 *  accepts commands from the custom house IoT server, has a manual override via the spare
 *  pin on the original Sonoff Basic (ESP8266 based units with the 5 pin header) and reports
 *  state changes to my own homebrew home automation system.
 *  Just delete the bits that mention IoT to just get Alexa control :)
 */

 // wifi
#include <ESP8266WiFi.h>
const char* ssid = "AccessPointName"
const char* password = "WiFiPassword";

// Alexa
#define ESPALEXA_ASYNC // bugfix
//#define ESPALEXA_DEBUG
#include <Espalexa.h>
Espalexa espalexa;

// Sonoff Basic (Original 8266)
#define RELAY_1 12
#define LED_1 13
#define BUTTON 14  // External button - additional GPIO on old style sonoff.
// #define BUTTON 0  // Internal button on old style sonoff basic.

// This is just for my IoT system - scheduled control and change reporting.
#include <WiFiUDP.h>
WiFiUDP UDP;
#define IOT_PORT  6502
#define IOT_SERVER  "10.14.0.100"
#define IOT_DEVICE_TYPE 0x82 // Generic light
#define IOT_ADDR 3
const int PACKET_SIZE = 8;
byte rxBuf[PACKET_SIZE];

// General state...
bool wifiConnected;
bool currentState = false;    // Power on state of the relay false; = off.
bool lastState = !currentState; // Trigger an update on first loop.  

void setup() {
  pinMode(RELAY_1, OUTPUT);
  pinMode(LED_1, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);
  digitalWrite(RELAY_1, currentState);  
  digitalWrite(LED_1, LOW);  
     
  Serial.begin(115200);
  // Initialise wifi connection
  wifiConnected = connectWifi();
  
  if(!wifiConnected){
    Serial.println("Rebooting ESP");
    delay(2000);
    ESP.restart();
  }

  UDP.begin(6502);

  // Advertise "Lucas Ceiling" as a light on the network.  When Alexa updates it, call 
  // stateChanged().  If you put an Echo and a light in the same room in the Alexa app,
  // you can just refer to it as "light" on that Echo, so Lucas can say "Alexa, Turn the
  // light off" and it only turns his light off.
  espalexa.addDevice("Lucas Ceiling", stateChanged);
  espalexa.begin();
}

void loop() {
  espalexa.loop();  // process pending Alexa requests.

  if (getPacket()) doUDP;  // My IoT: process pending UDP messages.

  if (digitalRead(BUTTON) == false ) {  // Check the button state.
    currentState = !currentState;
    updateOutput();
    while (digitalRead(BUTTON) == false ) delay(50); // debounce
  }

  if (currentState != lastState) updateOutput(); // Change light state if updated by Alexa or UDP.
  
  delay(5); // Give the WiFi subsystem some time to do things.

}

void stateChanged(uint8_t value){ // We got a message from Alexa.
  Serial.print("New Value ");
  Serial.println(value);
  currentState = (value > 50);  // If brightness is > 50%, set the state to on.
}


void updateOutput(){
  // set the relay.
  digitalWrite(RELAY_1, currentState);
  digitalWrite(LED_1, !currentState);
  
  // All changes are reported back to my IoT server
  if (lastState != currentState) sendUDPLightChange(IOT_ADDR, currentState);
  lastState = currentState;
}

bool connectWifi(){  // Fairly standard WiFi connect.
  bool state = true;
  int i = 0;
  
  WiFi.mode(WIFI_STA);  // Station mode (no soft-ap)
  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 > 60){
      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;
}

bool getPacket() {  // My IoT: Has a packet arrived?
  int packetSize;
  packetSize = UDP.parsePacket();
  if (packetSize == 0) { // If there's no response (yet)
    return false;
  };
  if ( packetSize > PACKET_SIZE ) { // Only read first PACKET_SIZE bytes, else
    packetSize = PACKET_SIZE;       //  we might overflow the buffer.
  };

  UDP.read(rxBuf, packetSize);
  if (rxBuf[2] == (packetSize & 0x00ff)) { // Does the size match the protocol we expect?
    Serial.println("Packet Received");     //  Check my website for details of the protocol.
    return true;
  } else {
    Serial.println("Incorrect packet size");
    return false;
  }
}

void doUDP(){ // My IoT: If a packet arrived, it's in rxBuf.  Only look for on/off messages.
  if ((rxBuf[0] == IOT_DEVICE_TYPE) && ((rxBuf[1] == IOT_ADDR) || (rxBuf[1] == 0xFF))){
    if (rxBuf[3] == 0x00) currentState = false;
    if (rxBuf[3] == 0x01) currentState = true;
  }
}

void sendUDPLightChange(char id, bool state) // My IoT stuff...  Report a change.
{  // This is used for updating a house map, and indicates people are home, etc, 
   //  to be used for logic in decision making processes like "someone is up, should
   //  the heating be on?  Can the alarm that's set for 3 minutes time be muted?"
  char data[4];
  data[0] = 0x82;  // Generic light.
  data[1] = id;    // This specific light
  data[2] = 4;     // 4 bytes.
  if (state)
  {
    data[3] = 0x21; // Turned on.
  } else {
    data[3] = 0x20; // Turned off.
  }
  UDP.beginPacket(IOT_SERVER, IOT_PORT); 
  for (int i = 0; i<4; i++){
    UDP.write(data[i]);
  }
  UDP.endPacket();
}