Introduction: On Time Intelligent Light Switch

This project is an intelligent dimmer for the bathroom. A motion detector starts the light. Adafruit IO + IFTTT sets the brightness and time the light remains on. Bonus feature connects with your calendar to pulsate the light as a reminder to get moving. The first part of the demo will showcase the tools and parts needed to construct the project. The second section focuses on the the circuit layout and the software (Arduino code + Adafruit IO + IFTTT). The third is the overview of building and assembling the hardware.

Step 1: Parts, Tools, Supplies

Step 2: Circuit Diagram and Code

The Circuit consists of two separate sides. The AC power components which run on a 5 volt logic provided by the AC-DC Power Supply. (This includes the HUZZAH drawing power through the USB port) and the sensors which use the 3.3 volt logic provided by the Adafruit Feather HUZZAH.

The Arduino code below connects the Arduino to the sensors, drives the relay + dimmer modules, and connects the Adafruit IO features. You will need to add in your own details for the Wifi (WLAN_SSID + WLAN_PASS) and Adafruit IO (AIO_USERNAME + AIO_KEY)

// Adding Adafruit IO connection
#include <esp8266wifi.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

// Adding Dimmer 
#include "hw_timer.h" 

// Info for PIR Motion Sensor
int motionPin = 5;              // choose the input pin (for PIR sensor)
int motionState = LOW;          // we start, assuming no motion detected
int val = 0;                    // variable for reading the pin status

// info For 2 button for dimmer
int button1 = 0;                 // first button at pin 0
int button2 = 16;                // second button at pin 16
int brightness = 100;

// Info for Relay         
int relayPin = 14;                 // first button at pin 4

// Info for RobotDYN Dimmer         
const byte zcPin = 12;
const byte pwmPin = 13;  

byte fade = 1;
byte state = 1;
byte tarBrightness = 255;
byte curBrightness = 0;
byte zcState = 0; // 0 = ready; 1 = processing;

/************************* WiFi Access Point *********************************/

#define WLAN_SSID       "Network"
#define WLAN_PASS       "Network Password"

************************* Adafruit.io Setup *********************************/

#define AIO_SERVER      "io.adafruit.com"
#define AIO_SERVERPORT  1883                   // use 8883 for SSL
#define AIO_USERNAME    "User"
#define AIO_KEY         "Key"

************ Global State (you don't need to change this!) ******************
Create an ESP8266 WiFiClient class to connect to the MQTT server.
WiFiClient client;
// or... use WiFiFlientSecure for SSL
//WiFiClientSecure client;

// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);

****************************** Feeds ***************************************

//Setup a feed called 'photocell' for publishing.
// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>
//Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/on-time-light-switch.photocell");</feedname></username>

// Setup a feed called 'brightime' for publishing.
// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>
//Adafruit_MQTT_Subscribe brightime_slider = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/on-time-light-switch.brightime");</feedname></username>

// Setup a feed called 'brightime' for publishing.
// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>
//Adafruit_MQTT_Publish brightime_guage = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/on-time-light-switch.brightime");</feedname></username>

// Setup a feed called 'onoff' for subscribing to changes.
Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/on-time-light-switch.onoff");


// Setup a feed called 'onoff' for publishing.
//Adafruit_MQTT_Publish onoffbutton = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/on-time-light-switch.onoff");

// Setup a feed called 'dimmer' for publishing.
// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>
Adafruit_MQTT_Publish dimmer_guage = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/on-time-light-switch.on-time-dimmer");</feedname></username></p><p>// Setup a feed called 'dimmer_slider' for subscribing to changes.
Adafruit_MQTT_Subscribe dimmer_slider = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/on-time-light-switch.on-time-dimmer");

/************************* SETUP SETUP SETUP *********************************

// Bug workaround for Arduino 1.6.6, it seems to need a function declaration
// for some reason (only affects ESP8266, likely an arduino-builder bug).
void MQTT_connect();</p><p>void setup() {
    
   //PIR Sensor Setup
  pinMode(motionPin, INPUT);      // declare sensor as INPUT

// Two button setup
  pinMode(button1,INPUT_PULLUP);   // Set pin 7 to be in input mode
  pinMode(button2,INPUT_PULLUP);   // Set pin 8 to be in input mode
  

//  Relay Setup
  pinMode(relayPin, OUTPUT);      // declare relay as OUTPUT

    Serial.begin(115200);

 // Connect to WiFi access point.
  Serial.println(); Serial.println();
  Serial.print("Connecting to ");
  Serial.println(WLAN_SSID);
  WiFi.begin(WLAN_SSID, WLAN_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("WiFi connected");
  Serial.println("IP address: "); Serial.println(WiFi.localIP());


// Setup MQTT subscription for onoff & slider feed.
  mqtt.subscribe(&onoffbutton);
  mqtt.subscribe(&dimmer_slider);

// Dimmer Board  Setup
  pinMode(zcPin, INPUT_PULLUP);
  pinMode(pwmPin, OUTPUT);
  attachInterrupt(zcPin, zcDetectISR, RISING);    // Attach an Interupt to Pin 2 (interupt 0) for Zero Cross Detection
  hw_timer_init(NMI_SOURCE, 0);
  hw_timer_set_func(dimTimerISR);
}</p><p>uint32_t x=0;

/************************* LOOP LOOP LOOP *********************************/
void loop() {
  // Ensure the connection to the MQTT server is alive (this will make the first
  // connection and automatically reconnect when disconnected).  See the MQTT_connect
  // function definition further below.
  MQTT_connect();

  // this is our 'wait for incoming subscription packets' busy subloop
  // try to spend your time here
    Adafruit_MQTT_Subscribe *subscription;
  while ((subscription = mqtt.readSubscription(5000))) {
    // Check if its the onoff button feed
    if (subscription == &onoffbutton) {
      Serial.print(F("On-Off button: "));
      Serial.println((char *)onoffbutton.lastread);
      
      if (strcmp((char *)onoffbutton.lastread, "ON") == 0) {
        digitalWrite(relayPin, LOW); 
      }
      if (strcmp((char *)onoffbutton.lastread, "OFF") == 0) {
        digitalWrite(relayPin, HIGH); 
      }
    }
    
    // Slider feed > Dimmer Brightness
    if (subscription == &dimmer_slider) {
      Serial.print(F("Slider: "));
      Serial.println((char *)dimmer_slider.lastread);
      uint16_t sliderval = atoi((char *)dimmer_slider.lastread);  // convert to a number 
      tarBrightness=sliderval;      // connecting the Adafruit IO slider to the dimmer brightness
    }
  }
 
  // ping the server to keep the mqtt connection alive
  // NOT required if you are publishing once every KEEPALIVE seconds
  /*
  if(! mqtt.ping()) {
    mqtt.disconnect();
  }
  */
  

// PIR Motion read-write 
  val = digitalRead(motionPin);  // read input value
  if (val == HIGH) {            // check if the input is HIGH
    digitalWrite(relayPin, HIGH);  // turn RELAY ON
    if (motionState == LOW) {
      // we have just turned on
        Serial.print("Motion ON");
      // We only want to print on the output change, not state
      motionState = HIGH;
    }
  } else {
    digitalWrite(relayPin, LOW); // turn RELAY OFF
    if (motionState == HIGH){
      // we have just turned of
        Serial.print("Motion OFF");
      // We only want to print on the output change, not state
      motionState = LOW;
    }
 }
 
  // put your main code here, to run repeatedly:
    if (Serial.available()){
        int val = Serial.parseInt();
        if (val>0){
          tarBrightness =val;
          Serial.println(tarBrightness);
        }
        
    }
}

/************************* END OF LOOP *********************************/
void dimTimerISR() {
    if (fade == 1) {
      if (curBrightness > tarBrightness || (state == 0 && curBrightness > 0)) {
        --curBrightness;
      }
      else if (curBrightness < tarBrightness && state == 1 && curBrightness < 255) {
        ++curBrightness;
      }
    }
    else {
      if (state == 1) {
        curBrightness = tarBrightness;
      }
      else {
        curBrightness = 0;
      }
    }
    
    if (curBrightness == 0) {
      state = 0;
      digitalWrite(pwmPin, 0);
    }
    else if (curBrightness == 255) {
      state = 1;
      digitalWrite(pwmPin, 1);
    }
    else {
      digitalWrite(pwmPin, 1);
    }
    
    zcState = 0;
}

void zcDetectISR() {
  if (zcState == 0) {
    zcState = 1;
  
    if (curBrightness < 255 && curBrightness > 0) {
      digitalWrite(pwmPin, 0);
      
      int dimDelay = 30 * (255 - curBrightness) + 400;//400
      hw_timer_arm(dimDelay);
    }
  }
}

// Function to connect and reconnect as necessary to the MQTT server.
// Should be called in the loop function and it will take care if connecting.
void MQTT_connect() {
  int8_t ret;

  // Stop if already connected.
  if (mqtt.connected()) {
    return;
  }

  Serial.print("Connecting to MQTT... ");


  uint8_t retries = 3;
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
       Serial.println(mqtt.connectErrorString(ret));
       Serial.println("Retrying MQTT connection in 5 seconds...");
       mqtt.disconnect();
       delay(5000);  // wait 5 seconds
       retries--;
       if (retries == 0) {
         // basically die and wait for WDT to reset me
         while (1);
       }
  }
  Serial.println("MQTT Connected!");
}



Step 3: Building It

Wire up the Motion sensor to the Feather.

Mount the AC connected units (Power Supply, Relay, and Dimmer) to the plastic frame to lock them in place and keep the open wiring safely tucked away.

Wire everything together according to the circuit diagram above. Test.

The Motion Sensor is mounted through a thick strip of styrene which sets right inside the standard rectangular lighting switch opening.

Once you have everything connected and running, I recommend using a little hot glue to lock some of the essential boards and wires in place so that they have ample clearance from each other and that they don't come loose over time. Let it rip.

Step 4: Video

A video to talk through the project, it's components, and some of its functions.

Step 5: Next Steps..

The final components of this projects ended up a little more complex than I originally intended. My next stage of work for this project is to try and condense everything so that It can become a drop-in replacement for a standard home light switch. Stay Tuned for future updates.