Halloween light from dusk till dawn

Fang Jin
5 min readNov 2, 2023

I had a halloween light toy bought couple of years back for $5. It can last couple of days with the battery (three AAs). I rarely used it, because it only got a switch to turn a red led light on and off. Pretty basic, but the problem is that no one would remember to turn it off in the morning. So the battery can run out really quick.

This year halloween time, I modified it a bit so that it can do some RGB lights and I want it to use the battery a bit more responsibly, so it can last a bit longer, hopefully a week or two without attendance.

Most of the Xmas lights with the timer are like that, you just plug them in and they’ll do their thing for the whole winter. Of course here we need to work with battery, but you get the idea.

Prototype the idea

Since I had a Pro Micro chip with me at that moment, I just used it on a breadboard along with a RGB light and a light sensor and then installed it under the original PCB.

And I laid down the basic idea in the following firmware:

void loop(void)
{
if (isDark())
{
lightUp();
}

sleep();
}

In the loop, the light sensor makes a reading. And if it’s dark enough, it then performs a light dance, such as going red, green and blue couple of times, and then it goes to sleep for a few seconds before waking up to take the next loop. I will explain all these functions one by one next.

Normally you would run the loop every milli second in a typical firmware. But here I slow it down, you could think the sleep as sort of delay(8000) for 8 seconds. The difference is that delay can not save any current while the microcontroller is running, whereas sleep can reduce the current consumption to minimum.

Lights up

I’m really not any light engineer, so the lightUp function only does some basic led flash with three colors wired on A1 , A2 and A3 pins.

#define LED_R A3
#define LED_G A2
#define LED_B A1

void flash(uint8_t pin, int start, int stop)
{
digitalWrite(pin, HIGH);
delay(start);
digitalWrite(pin, LOW);
delay(stop);
}

void lightUp(void)
{
flash(LED_R, 2000, 10);
flash(LED_G, 2000, 10);
flash(LED_B, 2000, 10);
flash(LED_R, 2000, 10);
flash(LED_G, 2000, 10);
flash(LED_B, 2000, 10);
}

When the light dance starts, it will take approximately 12 seconds to finish.

Is dark now?

As you have noticed, I don’t want to flash the light all the time, especially in the day time, because there’s just no point. Therefore, I added a light sensor wired to the pin 8.

#define LIGHT_PIN (8)
#define LIGHTON_THRESHOLD (1)

int readLight()
{
int light = analogRead(LIGHT_PIN);
Serial.println(light);
return light;
}

bool isDark()
{
return readLight() < LIGHTON_THRESHOLD;
}

A LIGHTON_THRESHOLD is required to decide whether it’s dark enough so that it flashes only when the light reading is below that that threshold. After some experiment, I have to set the threshold extremely low, such as 1, the lowest number. So essentially it’s pitch black considering the device is installed inside a closure where in general it’s not that bright even in the day time.

Sleep after the show

In order to save battery usage, the device needs to sleep whenever it can. For instance, wake up -> do the light dance -> sleep 8 seconds -> wake up again.

#include <LowPower.h>

void sleep()
{
// Idle, a lighter version of sleep
// LowPower.idle(SLEEP_8S, ADC_OFF, TIMER4_OFF, TIMER3_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART1_OFF, TWI_OFF, USB_OFF);

// Power Down, deep sleep
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}

I used the LowPower library and I experimented with both the idle and power down version of the sleep.

With no sleep, it operates at 23mA while the code is running in the loop. The current reduces to 11mA with the idle sleep and 3mA with the power down sleep; and when the light dance starts, the overall current jumps to 16mA.

You might wonder why 16mA is even less than 23mA. That’s because the dance show runs 12 seconds and sleeps for 8 seconds, therefore there’s a 0.6 factor to discount the total consumption. If I were turn the lights always on, it’ll consume 27mA.

Ok, with the consumption rate measured and the battery capacity given, we can calculate how long the device can run. For my 400mAh LiPo battery, it’ll last 400mAh / 16mA = 25hr. If the dark time is about 8hr a day, it’ll last 25 / 8 = 3.1days. But if I use the AA batteries that comes with it instead of my LiPo battery, it’ll last 2850mAh / 16mA / 8 = 22 days. I think this would do.

Pushing further

There’s couple of improvements that I made along the way. One of them is that I don’t want to turn the lights on for 8 hours. Because no one is going to watch them after 2am in the morning. Because I don’t have an extra timer available for me to know the real hour, remember we try to sleep as much as possible, I set up a counter manually to keep track of time.

#define MAX_CYCLES (360)

int counter = MAX_CYCLES;

void loop(void)
{
if (counter > 0)
{
if (isDark())
{
lightUp();
counter--;
}
else
{
heartBeat(LED_G);
counter = MAX_CYCLES;
}
}
else
{
if (!isDark())
{
counter = MAX_CYCLES;
}
else
{
heartBeat(LED_R);
}
}

sleep();
}

Say I only want to light up the lights for MAX_CYCLES times a day. A counter is reset to this max number whenever it starts the day by sensing the light. Once it hits the darkness, it’ll start the count down. And when the countdown reaches zero, it will stop the show for the day. In order for me to “debug” or monitor it, I also add a heartBeat function.

void heartBeat(uint8_t pin)
{
flash(pin, 50, 10);
}

This heartBeat function flashes briefly for 50ms so that I can tell whether the unit is waiting to start the count down or has already finished the count down by flashing green and red respectively.

Ok, with this change, I basically reduces the 8hr a day to 2hr a day, therefore the number of days it can last should in theory increases by four. With the LiPo battery pack, it should be able to run for 3 * 4 = 12 days and with the AA battery pack, it should be able to run for 22 * 4 = 88 days. Now if I leave the device unattended, it should function for the whole holiday season :)

--

--

Fang Jin
Fang Jin

Written by Fang Jin

Front-end Engineer, book author of “Designing React Hooks the Right Way” and "Think in Recursion"

No responses yet