I’ve had a project floating around in my head for a number of years…
We have a two-car garage with a nice white door that faces the street. I would like to use it as a canvas for something more interesting. My first thought was to build/buy/adapt a laser projector, and while I think that would be a fun project, it would unfortunately involve aiming laser light back towards the garage, which isn’t really the safest thing in the world. I’d also need to put the projector out in the rain someplace, so despite the whole “pew pew” lasers thing, I shelved it.
I’d also considered using some addressable LEDs, but for the first ones were pretty pricey, and I hadn’t figured out how I wanted to control them.
Recently, the project jelled (gelled?), and here’s the plan:
- A 5 meter (2.73 fathom) strip of addressable WS2812 RGB LEDs, with 60 LEDs/meter so a total of 300 individual LEDs. This will be mounted under the front eave of the garage facing down and back towards the house. I chose these because they are the cheapest decent addressable LEDs available and they are fairly ubiquitous, which means you can find libraries to drive them for most microcontrollers. Which is good, because they have strange timing requirements.
- A 20 Amp 5 Volt power supply. At full brightness each LED takes about 60 mA, and 0.06 * 300 = 18, which give me a bit of headroom. That’s about 90 watts to the LEDs, and these are pretty efficient, so yeah, that’s a lot of light. I had considered going with the strips that have 144 LEDs/meter, but they are a lot pricier and those would require 0.06 * 144 * 5 = 44 amps of power, which makes it less like a lighting project and more like a welding one.
- A ESP8266 wireless microcontroller. These are really hard to beat; you get a microcontroller with a decent number of inputs and a full 802.11 wireless stack; it can function either as a wireless client that hooks up to your house system, or it can function as a hotspot on its own. And it’s cheap. I went with the Adafruit Huzzah because it comes on a nice board that can be driven by 5 volts, and because Adafruit doesn’t sell cheap stuff that breaks. And it’s still less than $10. Oh, and it uses the Arduino IDE.
- A light sensor, so that I can use this as general lighting during the night. Sensor TBD.
- A passive infrared sensor, so I can ramp the LEDs up to full brightness when somebody shows up. Sensor TBD.
The hardware part is straightforward; it will just be a matter of getting all the parts and hooking them up. I haven’t settled on my mounting approach for the strip, but I think it will probably be 3/4″ electrical conduit, as it is very straight, very rigid, cheap, and has decent ways to mount it to walls. That also lets me twist it around to adjust the light.
As for the software, that gets a little more interesting. The ESP will serve up a web page where you can choose your lighting scheme (all on, specific colors, a rainbox effect, etc.), and I’m planning on coding that directly in HTML since I didn’t like any of the libraries that I found. For the LEDs, I’m taking a different approach.
The existing libraries are written to run on Arduinos, which have very little memory, so you need to be very small and optimal. That leads to code that looks like this:
// Input a value 0 to 255 to get a color value.
// The colours are a transition r – g – b – back to r.
static uint32_t Wheel(Adafruit_NeoPixel& strip, byte WheelPos) {
WheelPos = 255 – WheelPos;
if(WheelPos < 85) {
return strip.Color(255 – WheelPos * 3, 0, WheelPos * 3);
}
if(WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 – WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 – WheelPos * 3, 0);
}
static void Rainbow(Adafruit_NeoPixel& strip, uint8_t wait) {
uint16_t i, j;
for(j=0; j<256; j++) {
for(i=0; i<strip.numPixels(); i++) {
strip.setPixelColor(i, Wheel(strip, (i+j) & 255));
}
strip.show();
delay(wait);
}
Honestly, that is just awful; the animation is written right at the metal, and this approach doesn’t integrate well into the ESP because the web server can’t handle any requests while we are stuck in one of these loops. Luckily, the ESP has a lot more memory than the Arduino, and I can afford to spend that on some much-needed software abstractions. So, using the skills I apply at work when I’m writing C# I asked myself, “Self, what sort of library would I build if I had a bit of memory to spare?”. And this is what I came up with:
A Chunk is a series of RGB pixels that acts as a pixel source. Let’s assume that it has three pixels and is set to “Red Green Blue”.
A Mapper knows how to map a chunk onto the RGB strip. It is pretty simple; it just does the following:
for (int i = 0; I < strip.numPixels(); i++)
{
strip.setPixel(i, chunk.getNextPixel);
}
The Mapper maps the chunk onto the strip until the end of the strip, so if our strip had 9 pixels, it would end up with “Red Green Blue Red Green Blue Red Green Blue”.
That gives me a simple static mapping, but chunk has one more feature; you can set the offset at which it will start sourcing pixels. So, if I write something like:
for (int offset = 0; offset < chunk.numPixels(); offset++)
{
chunk.setOffset(offset);
mapper.renderAndShow(chunk);
}
That gives me a chaser feature; every time through the loop, the chunk shifts one spot to the right, and the chunk wraps around.
I can also use this with chunks that are larger than the strip, and animate the offset back and forth to control the portion of the chunk that is shown.
The Blender class is used to create a chunk that blends two colors together; pass it the first color, the second color, and the number of steps for the blend, and it generates a chunk that implements the blend.
The following code generates a 180-pixel blend across 6 colors:
RGBColor red(255, 0, 0);
RGBColor yellow(255, 255, 0);
RGBColor green(0, 255, 0);
RGBColor cyan(0, 255, 255);
RGBColor blue(0, 0, 255);
RGBColor magenta(255, 0, 255);
Blender blender(180);
blender.addBlend(red, yellow, 30);
blender.addBlend(yellow, green, 30);
blender.addBlend(green, cyan, 30);
blender.addBlend(cyan, blue, 30);
blender.addBlend(blue, magenta, 30);
blender.addBlend(magenta, red, 30);
pChunk = blender.getChunk();
It is much much much easier to understand than the code I started with, and very easy to modify.
And finally, there is an Animator class that makes it easy to drive all of these from the loop() method. Give it a minimum and maximum offset, how often to modify the offset (so you can do slow animations), and the increment to add to the offset, and then just call run() every loop and it will handle the animation for you.
I’m pretty pleased with the current implementation, but it’s not quite good enough to easily implement a Larson Scanner, which is a definite requirement. I think I can do it with two chunks that are a lot bigger than the strip, but it would be inefficient. Perhaps if the chunks were sparse, with blank spaces at each end.