Wednesday, April 23, 2014

Arduino Controlled Light

I mentioned the reasoning behind this project in a previous post, please read it if you can't figure out why anyone would ever want to do this.

Basically, the idea is to have an arduino controlled room light.  The primary goal is to be able to turn the light on and off based on a fixed time table from the moment the light switch is flipped.  A second goal is to fit the components for the build in the same space the light switch would have occupied (basically one side of a dual-gang box).

The first thing you will realize, given the second requirement above, is that a standard arduino will not fit in the space available.  At the time I was planning this, there were several mini arduino compatibles which were a possible fit.  But then I found this article on using a BlinkM MinM as a tiny arduino.  Given the simple goals of this project I should only need a single output pin to control a relay.  The MinM gives me 2 I/O pins and an RGB LED to play with.  Just what the doctor ordered.

I found myself with an extra pin that I wasn't using so I decided to extend the functionality by allowing a visitor to disable the timer.  I toyed with the idea of putting a button on this I/O pin and maybe making the user click in a specific sequence to disable the timer but I quickly discarded the idea.  What else can be done with a single pin?  That's when I remembered the 1-wire family of devices by Maxim Integrated (formerly Dallas Semiconductor).  I decided to use my last pin for a 1-wire iButton reader.

Components I ended up using were:
Here's a sketch of the circuit 



The idea is that the circuit is not even powered until the light switch is turned on.  Once the switch is turned on, it supplies power to the USB power adapter which powers the MinM and the green LED on the iButton reader.  When the MinM powers up it sets pin D to high turning on the relay and the light that's attached to it.  The arduino sketch takes care of turning the relay/light on and off according to a schedule, driving the RGB LED built into the MinM and reading the iButton serial number if present (every iButton has a unique serial number).

I removed the CATV connecter from the Decora filler and made the hole larger until I could fit the iButton reader in its place.  I also glued an angle header to the Decora faceplate so that I could attach and remove the MinM as needed to reprogram it (the MinM has press-fit header holes which keep it securely attached without soldering).  A small hole is drilled in the faceplate that lines up with the LED on the MinM allowing it to shine through.

Decora insert from the back


Decora insert from the front

Everything that will be stuffed in the gang box

After the hardware was sorted out, I wrote an arduino sketch to bring everything together.  Using the OneWire library made everything pretty straight forward.  Since there is no security requirement, the code doesn't actually check the iButton serial number, just the fact that an iButton was connected.

I actually spent much more time playing with the built in RGB LED.  The ATtiny85 that powers the MinM does not support PWM on all of the rgb pins so I wrote a function that fades the LEDs from one color to another in a specified time period.  I use it to provide a nice fade-in/fade-out effect.

Below is the code that is currently running on the light.  Although I program for a living, this is my first Arduino sketch.


#include <OneWire.h>

const int buttonPin = 2;
const int relayPin =  0;
const int redPin =  3;
const int grnPin =  4;
const int bluPin =  1;
const int pollInterval = 100;
const unsigned int minuteMillis = 60000;
const int startReminders = 15;   // minutes until we start flashing reminders

unsigned int elapsedMinutes = 0;          // minutes since start of timing
unsigned int elapsedMillis = 0;           // milliseconds of current second

int reminderElapsed = -1;        // milliseconds since this reminder started

// Serial iButton
OneWire ds(buttonPin);  // on pin 10
byte addr[8];
byte infiniteLives = 0;

// # of items in the reminderDuration array
byte reminderCount = 10;
// When reminders start, we will switch light off for given number of milliseconds
int reminderDuration[] = { 2000, 2000, 2000, 2000, 2000, 3000, 3000, 3000, 3000, 3000 };

void setup() {
 pinMode(relayPin, OUTPUT);
 pinMode(redPin, OUTPUT);
 pinMode(grnPin, OUTPUT);
 pinMode(bluPin, OUTPUT);
 pinMode(buttonPin, INPUT);
 // Switch light ON when powered up
 digitalWrite(relayPin, HIGH);
}

void loop() {
 delay(pollInterval);
 elapsedMillis = elapsedMillis + pollInterval;

 if(infiniteLives == 1) {
   // iButton key previously verified, slow blink
   rgbTransition(0, 0, 0, 0, 0, 99, 500); // fade blue on
   rgbTransition(0, 0, 99, 0, 0, 0, 500); // fade blue off
   delay(5000);
   return;
 }

 // check if iButton is present
 if ( !ds.search(addr)) {
     ds.reset_search();
 } else if ( OneWire::crc8( addr, 7) != addr[7]) {
   rgbTransition(0, 0, 0, 99, 0, 99, 50); // fade purple on
   rgbTransition(99, 0, 99, 0, 0, 0, 50); // fade purple off
   rgbTransition(0, 0, 0, 99, 0, 99, 50); // fade purple on
   rgbTransition(99, 0, 99, 0, 0, 0, 50); // fade purple off
 } else {
   // Acknowledge valid iButton
   rgbTransition(0, 0, 0, 0, 99, 0, 50); // fade green on
   rgbTransition(0, 99, 0, 0, 0, 0, 50); // fade green off
   rgbTransition(0, 0, 0, 0, 99, 0, 50); // fade green on
   rgbTransition(0, 99, 0, 0, 0, 0, 50); // fade green off

   // Turn on lights (in case they were off)
   digitalWrite(relayPin, HIGH);

   infiniteLives = 1;
   ds.reset();
   return;
 }

 if(reminderElapsed >= 0)
   reminderElapsed = reminderElapsed + pollInterval;

 elapsedMillis = elapsedMillis % minuteMillis;
 // Another minute has passed
 if(elapsedMillis == 0) {
   elapsedMinutes++;

   if(elapsedMinutes >= startReminders) {
     if(elapsedMinutes >= startReminders+reminderCount) {
       // Leave light off
       digitalWrite(relayPin, LOW);
       // "Evil" blink (red)
       rgbTransition(0, 0, 0, 75, 0, 0, 500);
       rgbTransition(75, 0, 0, 0, 0, 0, 500);
     } else {
       if(reminderDuration[elapsedMinutes - startReminders] > 0) {
         // start the reminder timer
         reminderElapsed = 0;
         digitalWrite(relayPin, LOW);
       }
       // "Danger" blink
       rgbTransition(0, 0, 0, 75, 75, 0, 500); // fade red+green on
       rgbTransition(75, 75, 0, 0, 0, 0, 500); // fade red+green off
     }
   } else {
     // "All's Well" blink
     rgbTransition(0, 0, 0, 0, 75, 0, 250); // fade green on
     rgbTransition(0, 75, 0, 0, 75, 75, 250); // fade blue on
     rgbTransition(0, 75, 75, 0, 0, 75, 250); // fade green off
     rgbTransition(0, 0, 75, 0, 0, 0, 250); // fade blue off
   }
 }

 if(reminderElapsed >= 0) {
   if(reminderElapsed == reminderDuration[elapsedMinutes - startReminders])
   {
     // disable the reminder timer
     reminderElapsed = -1;
     digitalWrite(relayPin, HIGH);
   }
 }
}
// PWM transition from rgb1 -> rgb2 in 'duration' milliseconds
// rgb values must be 0-99
void rgbTransition(char r1, char g1, char b1, char r2, char g2, char b2, int duration)
{
 for(int step = 0;step < duration;step++) {
   // Each millisecond is a step in the transition
   char rOn = r1+(r2-r1)*float(step)/float(duration);
   char gOn = g1+(g2-g1)*float(step)/float(duration);
   char bOn = b1+(b2-b1)*float(step)/float(duration);
   for(byte i=0;i<3;i++) {
     if(rOn>0)
       digitalWrite(redPin, HIGH);
     if(gOn>0)
       digitalWrite(grnPin, HIGH);
     if(bOn>0)
       digitalWrite(bluPin, HIGH);

     byte j = 0;
     while(j<100) {
       if(rOn == j)
         digitalWrite(redPin, LOW);
       if(gOn == j)
         digitalWrite(grnPin, LOW);
       if(bOn == j)
         digitalWrite(bluPin, LOW);
       j = j + 1;
     }
   }
 }
 digitalWrite(redPin, LOW);
 digitalWrite(grnPin, LOW);
 digitalWrite(bluPin, LOW);
}

No comments:

Post a Comment