Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Nick Stanley
Published © GPL3+

Bed Underglow

Motion activated bed lighting, including an ambient light sensor, relaxing color scenes, and Adafruit.io control.

IntermediateWork in progress8 hours2,423
Bed Underglow

Things used in this project

Hardware components

Photon
Particle Photon
×1
RGB Diffused Common Anode
RGB Diffused Common Anode
×1
PIR Motion Sensor (generic)
PIR Motion Sensor (generic)
×1
Photo resistor
Photo resistor
×1
Power MOSFET N-Channel
Power MOSFET N-Channel
×3
Capacitor 10 µF
Capacitor 10 µF
×2
Linear Regulator (7805)
Linear Regulator (7805)
×1

Software apps and online services

adafruit.io

Story

Read more

Schematics

Schematic

Code

BedRGBGlow.ino

C/C++
Main entry point
/*
 * BedRGBGlow.ino
 * Nick Stanley
 *
 * PIR activated LED strip. Turns on for 1 minute to a yellow color
 * whenever motion is detected. Lights will not come on if the room
 * is already bright, as determined by a photocell. Also features a
 * relaxing scene that will run for 10 minutes, shifting between 
 * blues and greens.
 */
#include "application.h"
#include "RGBLED.h"
#include "Adafruit_IO_Particle/Adafruit_IO_Particle.h"
#include "Adafruit_IO_Particle/Adafruit_IO_Client.h"

//Pin Definitions
#define PIN_PWM_RED         D0
#define PIN_PWM_GREEN       D1
#define PIN_PWM_BLUE        D2
#define PIN_PIR_SENSOR      D4
#define PIN_PHOTOCELL       A0
#define PIN_PHOTOCELL_PWR   A5
#define PIN_ONBOARD_LED     D7

//Constants
#define DELAY_TIME          20
#define BED_MOTION_TIME     10000
#define BED_COLOR_TIME      60000
#define ONE_SHOT            true
#define PHOTOCELL_THRESHOLD 700
#define AIO_KEY             "0000000000000000000000000000000000000000"

//Timers
Timer BedMotionTimer(BED_MOTION_TIME, BedLightsOff, ONE_SHOT);
Timer BedColorTimer(BED_COLOR_TIME, BedLightsOff, ONE_SHOT);

//Blue-green color scene
bool RampColors = false;
//Colors
RGB_LED LEDStrip(PIN_PWM_RED, PIN_PWM_GREEN, PIN_PWM_BLUE, COMMON_CATHODE);
Color clr_BedGlow;
Color clr_Off;
Color clr_Green;
Color clr_Blue;
Color clr_Current;
uint32_t AIOColorValue;

// Create an Particle TCPClient class to connect to the AIO server.
TCPClient client;

// Create an Adafruit IO Client instance.
Adafruit_IO_Client AIOClient = Adafruit_IO_Client(client, AIO_KEY);

// Finally create instances of Adafruit_IO_Feed objects, one per feed.
Adafruit_IO_Feed AIOColorFeed = AIOClient.getFeed("bedcolor");

/*
 * BedLightsOnTimed
 *
 * Turns the bed lights on to 
 * RGB (10,10,10) whenever motion
 * is detected. Will not trip if
 * photocell is above the desired threshold
 */
void BedLightsOnTimed()
{
    //Check if it's already bright enough
    digitalWrite(PIN_PHOTOCELL_PWR, HIGH);
    if(analogRead(PIN_PHOTOCELL) > PHOTOCELL_THRESHOLD)
    {
        digitalWrite(PIN_PHOTOCELL_PWR, LOW);
        return;
    }
    digitalWrite(PIN_PHOTOCELL_PWR, LOW);
    noInterrupts();//don't trip again until expired
    //Motion triggered LED
    LEDStrip.Set(clr_BedGlow);
    //Turn off on timer expire
    BedMotionTimer.start();
}
/*
 * BedLightsOff()
 *
 * Turn off lights, stop color scene
 * and re-enable interrupts
 */
void BedLightsOff()
{
    RampColors = false;
    interrupts();
    LEDStrip.Set(clr_Off);
}
/*
 * BedColorsOn()
 *
 * Turn off the lights, disable interrupts,
 * and set flag for loop to run scene. Stop
 * on timer expire
 */
void BedColorOn(const char* e, const char* args)
{
    noInterrupts();
    RampColors = true;
    BedColorTimer.start();
}
/*
 * ColorSetup()
 *
 * Initializes Color structs for use later
 * in the program. Uses standard decimal or
 * hexidecimal RGB notation
 */
void ColorSetup()
{
    clr_Off.red =   0;
    clr_Off.green = 0;
    clr_Off.blue =  0;
    
    clr_BedGlow.red =   10;
    clr_BedGlow.green = 10;
    clr_BedGlow.blue =  10;
    
    clr_Green.red =   0x00;
    clr_Green.green = 0xFF;
    clr_Green.blue =  0x00;
    
    clr_Blue.red =   0x00;
    clr_Blue.green = 0x00;
    clr_Blue.blue =  0xFF;
    
    clr_Current = clr_Off;
}
/*
 * setup()
 * 
 * System entry point. Assign pins, attach
 * inetrrupts and cloud functions
 */
void setup() 
{
    LEDStrip.Init();
    //Input
    pinMode(PIN_PIR_SENSOR, INPUT);
    //Output
    pinMode(PIN_ONBOARD_LED, OUTPUT);
    pinMode(PIN_PHOTOCELL_PWR, OUTPUT);
    //Colors
    ColorSetup();
    //Motion Sensor Interrupt
    attachInterrupt(PIN_PIR_SENSOR, BedLightsOnTimed, RISING);
    //Colors cloud activation
    Particle.subscribe("BedColors", BedColorOn, MY_DEVICES);
    //
    BedLightsOff();
    // Initialize the Adafruit IO client class
    AIOClient.begin();
}
/*
 * loop()
 *
 * If we're running the color scene, loop through that
 * Otherwise, control is handdled by interrupts and 
 * cloud subscriptions
 */
void loop() 
{
    if(RampColors) //Blue-green relaxing scene
    {
        clr_Current = LEDStrip.Ramp(clr_Current, clr_Green, DELAY_TIME);
        delay(DELAY_TIME);
        clr_Current = LEDStrip.Ramp(clr_Current, clr_Blue, DELAY_TIME);
    }
    else //Set color from Adafruit.io
    {
        FeedData latest = AIOColorFeed.receive();
        if (latest.isValid())
        {
            latest.ulongValue(&AIOColorValue);
            LEDStrip.Set(AIOColorValue);
        }
    }
}

RGBLED.h

C/C++
RGB LED support functions
/*
 * RGBLED.h
 * Nick Stanley
 *
 * RGB LED control functions. Allows the use of standard RGB notations
 * or Color structs to control common cathode OR anode setups
 */
#include "application.h"

#define COMMON_CATHODE true
#define COMMON_ANODE false

#define R_MASK 0x00FF0000
#define G_MASK 0x0000FF00
#define B_MASK 0x000000FF

struct Color
{
    byte red;
    byte green;
    byte blue;
};

class RGB_LED
{
    public:
        RGB_LED(int r, int g, int b, bool c);       //Standard setup
        void Init();                                //Initialize outputs and quick test
        void Set(byte r, byte g, byte b);           //Set color via 3 bytes
        void Set(Color c);                          //Set color using Color struct
        void Set(uint32_t i);                       //Set color using a 32 bit integer, containing r, g, b values
        Color Ramp(Color c1, Color c2, int d);      //Linear color change from c1 to c2, with a specified delay time inbetween changes
        Color UIntToColor(uint32_t);
        uint32_t ColorToUInt(Color c);
    private:
        int pin_r;      //Red pin
        int pin_g;      //Green pin
        int pin_b;      //Blue pin
        bool common;    //Common Anode/cathode
};

RGBLED.cpp

C/C++
RGB LED support functions
/*
 * RGBLED.cpp
 * Nick Stanley
 *
 * RGB LED control functions. Allows the use of standard RGB notations
 * or Color structs to control common cathode OR anode setups
 */
#include "RGBLED.h"


//Constructors
//Save pins, select common cathode or anode
RGB_LED::RGB_LED(int r, int g, int b, bool c)
{
    pin_r = r;          //red pin
    pin_g = g;          //green pin
    pin_b = b;          //blue pin
    common = c;         //common anode/cathode
}

//Initialize
//Set pin modes, check primary colors
void RGB_LED::Init()
{
    //PWM Output pins
    pinMode(pin_r, OUTPUT);
    pinMode(pin_g, OUTPUT);
    pinMode(pin_b, OUTPUT);
    //R-G-B demo
    this->Set(0xff, 0x00, 0x00);
    delay(1000);
    this->Set(0x00, 0xff, 0x00);
    delay(1000);
    this->Set(0x00, 0x00, 0xff);
    delay(1000);
    this->Set(0x00, 0x00, 0x00);
}


//Set
//Set the color, swap values if using common anode
//Apply brigness if necessary
void RGB_LED::Set(byte r, byte g, byte b)
{
    //Commom cathode acts the way we think, 0 = off, 255 = on
    if(common == COMMON_CATHODE)
    {
        analogWrite(pin_r, r);
        analogWrite(pin_g, g);
        analogWrite(pin_b, b);
    }
    else //common anode, backwards to our thinking, 255 = off, 0 = on
    {
        analogWrite(pin_r, 255 - r);
        analogWrite(pin_g, 255 - g);
        analogWrite(pin_b, 255 - b);
    }
}
void RGB_LED::Set(Color c)
{
    this->Set(c.red, c.green, c.blue);
}
void RGB_LED::Set(uint32_t i)
{
    //First byte is brighness, next is red, green, then blue
    //mask off the part we want and move it over
    byte r, g, b;
    r  = (i & 0x00FF0000) >> 4;
    g  = (i & 0x0000FF00) >> 2;
    b  = (i & 0x000000FF);
    this->Set(r, g, b);
}

//UIntToColor
//
Color RGB_LED::UIntToColor(uint32_t u)
{
    Color retrun;
    //Mask off the bits we want, shift to lowest bits
    retrun.red =   (u & R_MASK) >> 4;
    retrun.green = (u & G_MASK) >> 2;
    retrun.blue =  (u & B_MASK);
    return retrun;
}

//ColorToUInt
//
uint32_t RGB_LED::ColorToUInt(Color c)
{
    //Move the colors to proper bit position and smash them together
    return (c.red << 4) | (c.green << 2) | c.blue;
}
//Ramp
//Transitions color linearly from C1 to C2
Color RGB_LED::Ramp(Color c1, Color c2, int d)
{
    Color retrun;               //Our end color (Hopefully == c2, but with rounding who knows)
    int16_t d_r, d_g, d_b, big; //differential values
    float i_r, i_g, i_b;        //incremental values
    
    //Find the differences between each color
    d_r = c2.red - c1.red;
    d_g = c2.green - c1.green;
    d_b = c2.blue - c1.blue;
    
    //The largest difference will determine our step amount
    big = max(abs(d_r), max(abs(d_g), abs(d_b)));
    
    //No difference, better leave before div/0
    if(big == 0)
    {
        retrun.red =   0;
        retrun.green = 0;
        retrun.blue =  0;
        return retrun;
    }
    
    //Start at C1
    i_r = c1.red;
    i_g = c1.green;
    i_b = c1.blue;
    for(int i = 0; i <= big; ++i)
    {
        //Move it up a tad
        //Smallest increment should be big/big = 1
        i_r += d_r / big;
        i_g += d_g / big;
        i_b += d_b / big;
        //be sure it hasn't gone out of bounds, in case of rounding
        i_r = constrain(i_r, min(c1.red, c2.red), max(c1.red, c2.red));
        i_g = constrain(i_g, min(c1.green, c2.green), max(c1.green, c2.green));
        i_b = constrain(i_b, min(c1.blue, c2.blue), max(c1.blue, c2.blue));
        //Update
        this->Set(i_r, i_g, i_b);
        //Delay (all the math above might help too)
        delay(d);
    }
    //We hopefully ended up at C2, but math and rounding is funny that way, so just in case
    retrun.red = i_r;
    retrun.green = i_g;
    retrun.blue = i_b;
    return retrun;
}

Credits

Nick Stanley
5 projects • 13 followers
Software Engineer - System Integrator - Maker of Things

Comments