Hey folks, I am not at all a programmer, but have played with Arduinos and such off and on over the years, and have generally managed to string bits and pieces I find around the net together to get something fairly simple done.
My most recent project is also my largest. I've built a Little Free Library, and have an Arduino in the attic driving 5 WS2812b LED Strips to light the thing up at night. I've been using the FastLED library, which is clearly quite powerful, and has been very useful, if a bit tough to figure out at times.
All that said, what I have now is essentially a finished, and working project. The lights do what I want them to do, and I'm satisfied with the results.
However, I expect that my code isn't exactly clean or efficient, and I'm probably doing things in ham-fisted ways in places.
What I'd like is, in the interest of getting better at this stuff, and learning, for people to look through things, and let me know about those things that could be handled better, done differently, made more efficient, more cleanly, etc.
I don't really need tips like using 'a += b' instead of 'a = a + b', those are the things I think I'll get better at with time, and I have intentionally done a lot here in a longhand method, just to make it clearer to me as I am reviewing it myself, and trying to track things down. (Similarly, I know I over-commented on everything, but it was useful for me)
What I'm looking for more, is overall structural stuff. "This whole thing should be a separate function that you call this way, so it's not taking up cycles." or "I don't get why you're calling this thing repeatedly instead of just doing this. . . "
Thanks in advance for any tips an advice.
Context: The Library has 5 LED strips. There are 4 in the corners of the main cabinet (Front Left & Right, Back Left & Right), and then a smaller strip in the attic section. The goal is for the lights not to come on at all during the day (via the photoresistor). At night, I'd like it to run a pattern of light (currently watery blue) for the first could hours after sunset, then go dark. As long as it's dark out, if the doors are opened, the front LEDs will come up to white, to illuminate the books. Once the door is closed, the white light lingers for a time, before fading back to the idle pattern/darkness. I hope that all makes sense.
// Updated - 11/27/22 - 5:16PM
#include <FastLED.h> // FastLED library.
#define NUM_LEDS_FRONT 21 // Number of LEDs for the Front strips
#define NUM_LEDS_BACK 22 // Number of LEDs for the Back strips
#define NUM_LEDS_ATTIC 8 // Number of LEDs for the Attic Strip
#define NUM_LEDS_MAX 22 // Largest of the above numbers (used for setting a temporary array of colors)
#define LED_PIN_FRONT_LEFT 13 // Pin for the Front Left Strip
#define LED_PIN_FRONT_RIGHT 12 // Pin for the Front Right Strip
#define LED_PIN_BACK_LEFT 11 // Pin for the Back Left Strip
#define LED_PIN_BACK_RIGHT 10 // Pin for the Back Right Strip
#define LED_PIN_ATTIC 9 // Pin for the Attic Strip
#define LED_TYPE WS2812B // LED Strip type
#define SWITCH_PIN_LEFT 4 // Pin for the Left Door Switch
#define SWITCH_PIN_RIGHT 3 // Pin for the RIght Door Switch
#define SWITCH_PIN_ATTIC 2 // Pin for the Attic Switch
#define PHOTO_PIN A0 // Analog Pin for the Photoresistor
#define SWITCH_DEBOUNCE 250 // How long to wait between sampling the door switches
#define PHOTO_SAMPLE_TIME 30 // How often to sample the photoresistor (in seconds)
#define PHOTO_DARK 100 // Light Level at which it's now dark enough to turn on the lights
#define DUSK_TIMER 7200000 // How long to keep the backlight on when it first gets dark.
#define LINGER_TIMER 20000 // How long to keep the lights on after the doors get closed
#define FADE_STEP 1 // A multiplier used to set how fast the fades happen.
CRGB ledsWhite [NUM_LEDS_MAX]; // Array that is just bright white, used to fill in the front/attic when the doors are opened.
CRGB ledsTemp [NUM_LEDS_MAX]; // Temporary Array used to copy the pattern colors to all of the other arrays.
CRGB ledsFrontLeft[NUM_LEDS_FRONT]; // Array for the Front Left LEDs
CRGB ledsFrontRight[NUM_LEDS_FRONT]; // Array for the Front Right LEDs
CRGB ledsBackLeft[NUM_LEDS_BACK]; // Array for the Back Left LEDs
CRGB ledsBackRight[NUM_LEDS_BACK]; // Array for the Back Right LEDs
CRGB ledsAttic[NUM_LEDS_ATTIC]; // Array for the Attic LEDs
int switchLeft = 1; // To track the status of the Left Door switch
int switchRight = 1; // To track the status of the Right Door switch
int switchAttic = 1; // To track the status of the Attic Door switch
bool doorMain = false; // To track when the main doors are open.
bool doorAttic = false; // To track when the attic door is open.
//
bool itsDark = false; // Is it dark now?
bool itsDusk = false; // Is it dusk now?
bool lightLinger = false; // Are we lingering?
unsigned long timeNow = millis(); // What time is it now?
unsigned long lastLight = millis(); // Used to store the last point in time when photoAvg is greater than PHOTO_DARK
unsigned long lastOpened = millis(); // Used to store the last time a Main cabinet door was opened.
uint8_t activeBright = 255; // How bright should the backlight be while in standby mode during dusk?
uint8_t idleBright = 128; // How bright should the lights be at dusk with the doors closed?
uint8_t blendAmountMain = 0; // Used to blend the pattern to white for the Front and Attic lights
uint8_t blendAmountAttic = 0; // U
int mainBright = 0; // Used to store the starting brightness of the Main Cabinet
int atticBright = 0; // Used to store the starting brightness of the Attic
//#############################################
//################### SETUP ###################
//#############################################
void setup() {
delay( 3000 ); // Power-up safety delay
FastLED.addLeds<LED_TYPE, LED_PIN_FRONT_LEFT, GRB>(ledsFrontLeft, NUM_LEDS_FRONT); // Adding Front Left LEDs
FastLED.addLeds<LED_TYPE, LED_PIN_FRONT_RIGHT, GRB>(ledsFrontRight, NUM_LEDS_FRONT); // Adding Front Right LEDs
FastLED.addLeds<LED_TYPE, LED_PIN_BACK_LEFT, GRB>(ledsBackLeft, NUM_LEDS_BACK); // Adding Back Left LEDs
FastLED.addLeds<LED_TYPE, LED_PIN_BACK_RIGHT, GRB>(ledsBackRight, NUM_LEDS_BACK); // Adding Back Right LEDs
FastLED.addLeds<LED_TYPE, LED_PIN_ATTIC, GRB>(ledsAttic, NUM_LEDS_ATTIC); // Adding Attic LEDs
FastLED.setCorrection(TypicalPixelString); // Color Correction
FastLED.setBrightness(255); // Setting Overall max brightness
fill_solid(ledsWhite, NUM_LEDS_MAX, CHSV(45,64,255));
pinMode(SWITCH_PIN_LEFT, INPUT_PULLUP); // Setting up the pins for the Left Door switch
pinMode(SWITCH_PIN_RIGHT, INPUT_PULLUP); // Setting up the pins for the Right Door switch
pinMode(SWITCH_PIN_ATTIC, INPUT_PULLUP); // Adjusting the RTC to match the Date/Time of the computer as the sketch gets uploaded
Serial.begin(115200); // Starting Serial Communications
Serial.println("Setup Complete"); // Checkpoint text
}
//######################################################
//################### SET BRIGHTNESS ###################
//######################################################
void setBright(bool lightLinger){
int mainFadeDir = 0; // Used to store which way we're fading.
int atticFadeDir = 0; // Used to store which way we're fading.
//##### Main Brightness #####
if (doorMain) { // If the main doors are open
mainFadeDir = 1; // Fade Up
} else if ((!doorMain) && (lightLinger)) { // Or if the main doors are closed, but we're lingering
mainFadeDir = 1; // Fade Up
} else { // Otherwise
mainFadeDir = -1; // Fade down
}
// Serial.print("Main Fade Direction: ");
// Serial.println(mainFadeDir);
mainFadeDir = mainFadeDir * FADE_STEP; // Increses/Decreases the Fade Direction based on the Fade Step
mainBright = mainBright + mainFadeDir; // Add the current brightness to the fade direction
if (mainBright <= idleBright) { // If brightness is less than target
mainBright = idleBright; // Reset to target
} else if (mainBright >= activeBright) { // Or if it's maxed out
mainBright = activeBright; // Keep it maxed out.
}
blendAmountMain = map(mainBright, idleBright, activeBright, 0, 255); // Sets Blend Amount to always be in the 0-255 range, but based on the difference between the Idle & Active brightnesses)
if (blendAmountMain <= 0) { // If blend amount is less than 0
blendAmountMain = 0; // Reset to 0
} else if (blendAmountMain >= 255) { // Or if it's maxed out
blendAmountMain = 255; // Keep it maxed out.
}
// Serial.print("Main Fade Dir: ");
// Serial.println(mainFadeDir);
// Serial.print("Main Brightness: ");
// Serial.println(mainBright);
// Serial.print("Blend Amount: ");
// Serial.println(blendAmountMain);
//##### Attic Brightness #####
if (doorAttic) { // If the main doors are open
atticFadeDir = 1; // Fade Up
} else if ((!doorAttic) && (lightLinger)){ // Or if the main doors are closed, but we're lingering
atticFadeDir = 1; // Fade Up
} else { // Otherwise
atticFadeDir = -1; // Fade down
}
atticBright = (atticBright + (atticFadeDir * FADE_STEP)); // Add the current brightness to the fade direction
if (atticBright <= idleBright) { // If brightness is less than target
atticBright = idleBright; // Reset to target
} else if (atticBright >= activeBright) { // Or if it's maxed out
atticBright = activeBright; // Keep it maxed out.
}
blendAmountAttic = map(atticBright, idleBright, activeBright, 0, 255); // Sets Blend Amount to always be in the 0-255 range, but based on the difference between the Idle & Active brightnesses)
if (blendAmountAttic <= 0) { // If blend amount is less than 0
blendAmountAttic = 0; // Reset to 0
} else if (blendAmountAttic >= 255) { // Or if it's maxed out
blendAmountAttic = 255; // Keep it maxed out.
}
// Serial.print("Attic Brightness: ");
// Serial.println(atticBright);
}
//######################################################
//################### LIGHT PATTERNS ###################
//######################################################
void lightsWatery(uint8_t brightness, int timeOffset) {
// Serial.print("Watery Brightness = ");
// Serial.println(brightness);
unsigned long t = ((millis()/5) + timeOffset); // Sets a variable to use as time with the noise functions, including an offset from the function calls, so they don't all have to be in sync
for (int i = 0; i < NUM_LEDS_MAX; i++) { // Step through each LED in the strip
uint8_t noiseA = inoise8(i * 10 + 20, t); // Creating and constraining Noise A
noiseA = constrain(noiseA, 0, 255);
uint8_t noiseB = inoise8(i * 5 + 50, 2.8*t); // Creating and constraining Noise B
noiseB = constrain(noiseB, 0, 255);
uint8_t noiseC = inoise8(i * 20 - 10, 3.7*t); // Creating and constraining Noise C
noiseC = constrain(noiseC, 0, 255);
uint8_t ledHue = map(noiseA, 50, 190, 130, 160); // Mapping and constraining Hue based on Noise A
ledHue = constrain(ledHue, 130, 160);
uint8_t ledSat = map(noiseB, 50, 190, 230, 255); // Mapping and constraining Saturation based on Noise B
ledSat = constrain(ledSat, 230, 255);
uint8_t ledVal = map(noiseC, 50, 190, 128, 255); // Mapping and constraining Value based on Noise C
ledVal = constrain(ledVal, 128, 255); // Modulating backVal by the brightnessBack
float brightPct = (brightness/255.0); // Converting brightness to a percentage to be multiplied on backVal
ledVal = (brightPct*ledVal); // Attenuate the Light Value by the Brightness Percent
ledsTemp[i] = CHSV(ledHue, ledSat, ledVal); // Set the temporary Array
}
}
//#############################################
//################### PHOTO ###################
//#############################################
void photo(){
static int photoIndex = 0; // Used to save photo readings into the next slot in the array
static int photoReadings[10] = {500,500,500,500,500,500,500,500,500,500}; // Array to hold the 10 most recent photoresistor samples
int photoLevel = analogRead(A0); // Temporarily stores the photoresistor sample level
int photoSum = 0; // Used to total the contents of the array
static int photoAvg; // Average of the 10 light samplees stored in photoReadings[]
photoReadings[photoIndex] = photoLevel; // Insert that reading into the next slot of the array
for(int i=0; i<10; i++){ // Total up the current contents of the array
photoSum = (photoSum + photoReadings[i]);
}
photoAvg = photoSum/10; // Average the total of the array
photoSum = 0; // Reset the total of the array, so it recounts from 0 next time it rolls through photoIndex++; // Move the index up so the next time we go through, we put the new reading in the next slot.
if (photoAvg < PHOTO_DARK) { // If the average light is below a threshold
itsDark = true; // It's Dark!
// Serial.print("It's Dark! - ");
// Serial.println(photoAvg);
} else { // Otherwise
itsDark = false; // It ain't!
// Serial.print("It's Bright! - ");
// Serial.println(photoAvg);
}
// Serial.print("Photo Readings: ");
// for(int j=0; j<10; j++){
// Serial.print(photoReading[j]);
// Serial.print(", ");
// }
// Serial.println();
// Serial.print("Photo Average: ");
// Serial.println(photoAvg);
if (photoIndex >= 10){ // reset the index to 0 to continue the rolling 10 average
photoIndex = 0;
}
}
//############################################
//################### LOOP ###################
//############################################
void loop () {
timeNow = millis(); // What time is it right now?
//#### Sample the Photoresistor and keep time as long as it's bright
EVERY_N_SECONDS(PHOTO_SAMPLE_TIME){ // Only sample the lighting conditions every 30 seconds or so)
photo(); // Run the Photoresistor Function
if (!itsDark){ // If it's not dark
lastLight = millis(); // Store the last time it was light out
}
}
if ((timeNow - lastLight) < DUSK_TIMER) { // If the last time it was light is longer ago than the Dusk Timer
itsDusk = true; // It's Dusk!
// Serial.println("It's Dusk!");
} else { // Otherwise
itsDusk = false; // It ain't!
}
//#### Sample the Switches ####
EVERY_N_MILLISECONDS(SWITCH_DEBOUNCE){ // Debounce the switches
switchLeft = digitalRead(SWITCH_PIN_LEFT); // Sample the Left Door Switch
switchRight = digitalRead(SWITCH_PIN_RIGHT); // Sample the Right Door Switch
switchAttic = digitalRead(SWITCH_PIN_ATTIC); // Sample the Attic Door Switch
//
if ((switchLeft == LOW) || (switchRight == LOW)){ // If one or the other front door is open
doorMain = true;
// Serial.println("Main Door Open");
} else {
doorMain = false;
}
if (switchAttic == LOW){ // If the attic door is open
doorAttic = true;
// Serial.println("Attic Door Open");
} else {
doorAttic = false;
}
}
//#### Record the last time the door was open ###
if ((itsDark) && (doorMain)){ // If it's dark and either the Left or Right Door is open
lastOpened = millis(); // As long as a door is open, this is constantly updating to the current time.
}
//#### Are we in Linger mode? ####
if ((((millis() - lastOpened) < LINGER_TIMER)) && (mainBright >= activeBright)) { // If the last time the doors were opened is less than the Linger Timer
lightLinger = true; // Then we be lingerin'
// Serial.println("Lingering. . . . ");
} else { // Otherwise
lightLinger = false; // Don't Linger
}
//#### Idle Brightness depends on whether it's Dusk or not.
if (itsDusk) { // If it's dusk
idleBright = 128; // Then when the doors aren't open, things should still glow.
} else { // Otherwise
idleBright = 0; // Turn them off
}
//#### Set the Brightness ####
setBright(lightLinger); // Run the setBright Function, and tell it if we're lingering or not.
//####Set the light patterns ####
//## Front Lights ##
lightsWatery(mainBright, 0); // set the temporary LED Array with some colors, and send the current brightness value
for (int i = 0; i < NUM_LEDS_FRONT; i++){ // Step through the LEDs one by one
ledsFrontLeft[i] = blend(ledsTemp[i], ledsWhite[i], blendAmountMain); // Copy the colors from Temp Array (and/or blend to white) into the real LED Array.
}
lightsWatery(mainBright, 6742); // set the temporary LED Array with some colors, and send the current brightness value
for (int i = 0; i < NUM_LEDS_FRONT; i++){ // Step through the LEDs one by one
ledsFrontRight[i] = blend(ledsTemp[i], ledsWhite[i], blendAmountMain); // Copy the colors from Temp Array (and/or blend to white) into the real LED Array.
}
//## Back Lights ##
lightsWatery(mainBright, 12475); // set the temporary LED Array with some colors, and send the current brightness value
for (int i = 0; i < NUM_LEDS_BACK; i++){ // Step through the LEDs one by one
ledsBackLeft[i] = ledsTemp[i]; // And copy the color from the Temp Array to the real LED Array
}
lightsWatery(mainBright, 18736); // set the temporary LED Array with some colors, and send the current brightness value
for (int i = 0; i < NUM_LEDS_BACK; i++){ // Step through the LEDs one by one
ledsBackRight[i] = ledsTemp[i]; // And copy the color from the Temp Array to the real LED Array
}
//## Attic Lights ##
lightsWatery(atticBright, 0); // set the temporary LED Array with some colors, and send the current brightness value
for (int i = 0; i < NUM_LEDS_ATTIC; i++){ // Step through the LEDs one by one
ledsAttic[i] = blend(ledsTemp[i], ledsWhite[i], blendAmountAttic); // Copy the colors from Temp Array (and/or blend to white) into the real LED Array.
}
FastLED.show(); // Aziz, Light!
// Serial.println("boop");
// delay(2000);
}