Contents
- Rationale
- Planning
- Getting the Data
- Controlling the LEDs
- Providing Power
- Fabricating the Signage
- Housing the Electronics
- Key Learnings
If you are only interested in specific aspects of this project, feel free to use the table of contents above to jump to the appropriate section. If you are just getting started with embedded electronics, you may want to read all the way through, as it might give you ideas about how to plan your project before jumping in to execution.
RationaleWhat it Is: This tutorial is about how to build a battery powered, internet-connected NeoPixel LED sign to display up-to-date information using Arduino and the ESP8266. It is an extension of my tutorial on how to parse XML data on the ESP, but while that tutorial focused mostly on the code, this is covers how to actually fabricate the signage, drive the LEDs, and tidy up the circuitry a bit.
What It Looks Like:
Why I Built It: My wife owns a store in Portland called 11:11 supply, which sits in front of a busy street car stop. Customers are curious to browse while they wait, but are nervous they might miss their bus, so I wanted to prototype a low cost internet-connected sign that would display when the next bus was about to arrive, so customers wouldn't worry as much while shopping.
Making Progress: I've done a fair bit of breadboard prototyping, but this is the first project where I've actually gone the extra step of making things compact, self-contained, and durable, and hidden away all the ugly wires, which is a big step for me!
Prototype,Improve: This is still very much a prototype, but one that's solid enough that I feel I could display it somewhere to see how people use it, how it holds up over time, how various lighting conditions effect it, how much battery it consumes, etc.
PlanningI knew that in order to build the project, I needed to do the following:
- make sure the data I need is available
- select the right microcontroller
- connect to the internet with that microcontroller
- get the API data
- display that data visually
- provide powersignage
- providing power
- hide away the electronics neatly
However, rather than think of the system as a whole, I approached each of these steps piecemeal, moving from one stage to the next. And while this helped me compartmentalize each task so the project didn't seem so daunting, it also meant that I didn't anticipate some of the concerns I would have to deal with further down the road. I wish I had stopped to sketch out a birds eye view of the entire project - what components I would need, what the form factor would be, how it would be powered, how it would be wired, what the best way to get and display the data would be, before I started building.
Getting the DataIs it available? First I had to know if I could get the data I needed. Most good transit authorities provide open-source access to their data via what's called an API. A quick search for "[your transit authority name] API" in Google should lead you quickly to whether you can get the data you need. What is an API? It stands for Application Programming Interface, and it's an easy(ish) way to make requests to a web server and get information back in a formatted manner.
My search result yielded this:
Seems promising so far! The developer resources page tells me how to get started. First I need to register for an app id, then I can access their various services. But do they have the info I need?
Arrivals at a stop identified by location ID sounds like exactly what I need. Clicking on the Arrivals V2 linkshows me all the data that is accessible when I query this web service. I want to make sure they have information on how long before the next bus arrives at a specific stop.
I find:
So I know at what time the next bus is scheduled to arrive. Perfect! Almost...What I didn't take in to account was that my sign would display how many minutes away the bus was, and what this API was giving me was the arrival time, which means that I would also need to know the current time of day, and then do the math to get the number of minutes. Sounds easy...if your microcontroller has a way of telling time.
Testing the DataTrimet makes it really easy to see the data you need (thanks, Trimet!). Once you have an appID, you can just pull the data up in a web browser. For me, that web address looked like:
https://developer.trimet.org/ws/V1/arrivals/locIDs/5901/appID/[my_app_id]
where `5901` is the bus stop number outside my wife's store, and `[my_app_id]` is the ID I got when I registered with Trimet. The results from trimet look like this:
Tracking down the `estimated` parameter, I realized this wasn't a number of minutes from now, or even a readable time, but a really long number. Having dealt with this before, I realized my data was coming back as a UTC timestamp which is a universal convention for telling time based on the number of minutes that have passed since January 1, 1970 (don't ask me...). That meant to get the number of minutes til the next arrival, I'd have to get the current UTC Timestamp, subtract it from the estimated arrival time, and convert that to a readable format.
Selecting the Right MicrocontrollerGreat, so the data I need is available. Now I need a device that can get that data regularly. I have a few criteria:
- It should be cheap - I don't want to sacrifice an expensive laptop just to get the bus times.
- It should be small - ideally this thing sits inside the signage and is not visible.
- It should be low power - I may have to run this off battery power, and I want that battery to last a while so I'm not constantly switching them out.
- It should be able to connect to the internet easily - I don't want to have to add a bunch of modules and components to the microcontroller. Also, it should be WiFi, I don't want to have to run a bunch of cables and I want to experiment with mounting the sign in different places to see where it works best.
- It should be able to control an LED Strip or LCD Display - It's not enough to have the data, I need to show it somehow to others.
- It should be easy to program and prototype with - I don't really want to have to use an FTDI programmer or learn a new language. I'm somewhat familiar with Arduino, so I want to program in that, plug the microcontroller in through USB to my computer, upload my code, and debug it with a serial monitor.
My starting point for most microcontroller projects is an Arduino Uno because I have a couple of them, they're relatively flexible reliable, and cheap, and documentation for how to hook them up and program them with various sensors is readily available. But it's rarely the best microcontroller for a finished product. And in this case, it doesn't have WiFi out of the box. But the ESP8266 has a lot of the same features as an Uno, plus it's cheaper, smaller, and many form factors include WiFi.
I'm a big fan of Adafruit because they provide good support and have lots of online tutorials for the products they sell, so I started with a Feather Huzzah.
Using the Microcontroller to Retrieve the DataI'm familiar with Arduino, but not so much NodeMCU + Lua, which the Adafruit Feather ships with by default. Luckily Adafruit provides a great tutorial about how to set up the Feather for use with Arduino.
From there, I needed to get the Feather to connect to the internet, retrieve the data, and get it into a readable format. I've covered this process in detail in a previous post, so if you're interested in learning how to connect to WiFi with an ESP8266 and retrieve and parse XML data, read this first, the continue!
Displaying the Data with LEDs11:11 Supply prides itself on its analog tools, so I knew anything too "techy" like a monitor or an LCD character / number display wouldn't fit the aesthetic of the store, so I decided to display the estimated arrival times using LEDs. I've worked with LEDs before, and I knew dealing with a bunch of wiring for individual LEDs would involve a lot of wiring and soldering and resistors and such, so I decided my sign would have to accommodate LED strips. Adafruit's NeoPixel library works with any WS2812B compatible LED strip, so I actually purchased my LEDs from Amazon to save a bit of money.
I bought a large strip of LEDs, but I only needed 9, one to represent each minute away the bus was. The nice thing about these LED strips is you can easily cut them down wherever the cut marks / copper pads are, and just solder up wires to the end. These LEDs came with a waterproof casing - I just tossed that aside. These LEDs were the 60 per meter variety, and I was hoping that would give me enough spacing between each LED so that when one number of the sign was illuminated, its light wouldn't "bleed" into the next number.
In the diagram below you can see how I wired up the LEDs for testing.
You'll notice there's a capacitor between the negative and positive leads of the battery pack - this helps to smooth out voltage as power can fluctuate a bit. I didn't use a capacitor in my final project, because I was only powering a small number of LEDs, but it's still a good idea.
Also, You can probably get away fine with a couple of AAs to power this few LEDs. This was just the only battery holder I had available at the time. Plan on about 60 milliamps per white LED (20mA each for R, G, B).
For now, I am powering the microcontroller itself via USB from my laptop so I can update code quickly and debug using the serial monitor.
I made a standalone LED code sketch to test the lights, so that if there was anything wrong, I would know it was a problem with the lights specifically and not the WiFi code I'd written earlier. It's pretty straightforward:
#include <Adafruit_NeoPixel.h>
#define PIN 15 //microcontroller pin connected to the center pin (DO) of the LED strip
#define N_LEDS 9 //total number of LEDs
int ledsToShow = 5; //number of LEDs to light up at one time
int brightness = 100; //on a scale of 0 - 255
bool showSingleLight = false; //when false, light up all LEDs up to the `ledsToShow`. When true, only light a single light.
Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_LEDS, PIN, NEO_GRB + NEO_KHZ800); //initialize the neopixel library
void setup() {
strip.begin(); //initialize the LED strip
strip.setBrightness(brightness);
for (uint16_t i = 0; i < N_LEDS; i++) { //loop through each light in the strip, and set its color to white (255,255,255) or off (0,0,0)
if (showSingleLight){ //if we just want to highlight one light at a time
if (i==ledsToShow){
strip.setPixelColor(i, strip.Color(255, 255, 255)); //white
strip.show();
} else {
strip.setPixelColor(i, strip.Color(0, 0, 0)); //off
strip.show();
}
} else { //if we want to highlight any LED less than or equal to the number of minutes away
if (i<ledsToShow){
strip.setPixelColor(i, strip.Color(255, 255, 255)); //white
strip.show();
} else {
strip.setPixelColor(i, strip.Color(0, 0, 0)); //off
strip.show();
}
}
}
}
void loop(){
}
First we include the NeoPixel library. You can add it via `Tools > Manage Libraries` and typing `NeoPixels`:
Then we choose which pin on the Feather will control the LEDs. I chose 15. You can usually see all the available pins for any microcontroller by doing a Google Image search for "[YOUR_MICROCONTROLLER_NAME] Pinout". This can be especially useful if you've already covered up the markings on your microcontroller by adding headers, as I did.
These diagrams can be a bit confusing. The number we will reference is the `IO` number - so in this case, the 7th one down from the top right set of copper circles is IO15.
The `N_LEDS` variable is how many LEDs total are in my strip - 9. And ledsToShow is how many of those 9 LEDs I want lit up at a time to test. I'm going to only show 5 for now.
`brightness` is a variable from 0 to 255, with 0 being no brightness, and 255 being blindingly bright (provided you're giving your LED strip enough power). 100 is good for testing, though I encourage you to test your LEDs in all lighting conditions that will be present wherever you plan on using them. I was initially testing at night and early morning, and found that in daylight, I wished I had set the brightness higher, or possibly even included a rotary dial so that I could adjust the brightness.
I included a `showSingleLight` variable to test whether it looked better to highlight all the LEDs up to the `ledsToShow` value, or whether it was better to show just the individual LED representing the number of minutes away the bus was (I ultimately opted to keep this value set to `false`).
`strip.begin();` initializes the LED strip, and `for (uint16_t I = 0; I < N_LEDS; i++) {}` creates a loop that will go through each LED and specify whether to turn the light off:
strip.setPixelColor(i, strip.Color(0, 0, 0));
or on/white:
strip.setPixelColor(i, strip.Color(255, 255, 255));
You'll also see a `loop` function - this has to be included in Arduino or your sketch will throw errors, but it doesn't actually do anything here.
Providing PowerI assumed that to be small and unobtrusive, the sign would need to run on battery power, and the battery pack for the LEDs working fine, so I stuck with that. I also knew the ESP8266 would run fine off of 3.3V, so a little LiPo battery would work well, be able to be recharged, and not risk any damage to the controller.
Had I planned better, I would have stopped to consider how often these would need to be charged / replaced, and the logistics of who would handle this, whether I could get by with just one power source instead of two separate ones, potentially putting the board into sleep mode when it wasn't in use, etc.. It turns out, the store has outlet boxes up near the ceiling for DC power that could provide power without the need for any maintenance, so the final version will need to use that. And I've made a note to get a better understanding of how to manage power!
Fabricating the signageI knew the LEDs alone wouldn't fit the aesthetic of the store, and would be too bright, so I decided to cover them with a thin diffuse material - I had some thin white card stock lying around, and again, in the dark this seemed to diffuse the lights nicely, but in the daylight, at the current brightness setting, it was a little too opaque.
On top of that I wanted a solid black face plate. My initial thought was to use acrylic, but I was worried that the lights might bleed through into each number / dot. Also, I wasn't sure if I could get access to a laser cutter to cut everything out, but I do have an inexpensive Curio Vinyl cutter home, and thought that if I chose a softer material, like a thick card stock, I might be able to cut it with that.
Behind the non-illuminated letters, I wanted to try a natural wood, as 11:11 is full of that kind of material. I had some cheap, thin, birch balsa wood at home which I knew would be easy to work with, so I decided to give that a shot.
Here's what everything would eventually look like:
Trying to cut these materials with the Curio didn't work at all. I love that machine, but even the deep cut blade made a mess of the thick black card stock, and even the thinnest, softest balsa wood doesn't quite cut all the way through.
I eventually was able to use the laser cutter at the amazing Make+Think+Code lab at PNCA where I teach workshops (thanks to the awesome Megan McKissack and MTC!).
The card stock definitely didn't allow light to bleed through, but it also wasn't very durable. If you look closely at the photo you can see how easy it was to scuff up the black (plus I was careless and got some glue on there). In retrospect, I wish I would have gone with acrylic.
Also, I would highly recommend to do your laser cutting first, and then cut down your material to the size you need. I came in to the lab with all the material cut down to the size I wanted, and, because it's a little hard to position the laser, things ended up being off center a number of times and I had to start over.
Once the card stock was cut, I just laid the balsa wood down behind the letters, cutting it with an exacto knife. I made little strips between where each LED would be - one, to prevent light bleed, and two, to support the led strip so I could glue it up more securely. Here's what it looks like on the back side:
Now it was time to lay out all my components. I realized quickly that I had not planned at all for how things would fit on to this signage. I started by taping in the LED strip to hold it in place and test the brightness / alignment of the LEDs with the cut holes.
You'll notice a few differences at this point from my previous setup:
A new microcontroller. I quickly realized the big long header pins I had soldered to my Feather Huzzah would make the microcontroller really hard to fit into my case and work with, and I wanted to keep the microcontroller as is for future projects, so I ended up switching to a Sparkfun ESP8266 Thing Dev that I found which had no header pins attached. The Thing Dev is extremely similar to the Huzzah - the main differences to me were:
- the pinout is different. Instead of the `USB` pin we use `5V` here, and instead of pin 15, we use pin 5.
- it has a nice little on off switch
- it has some handy status lights (though because they interfere with the color of my LED strip, I wish it didn't in this case)
A new battery pack. I wanted an easy on off switch, and I didn't want to overpower my small number of lights. This is a 3XAA battery pack. I attached a JST connector to it so that the pack could be easily disconnected to swap out the batteries.
No more capacitor. I'll be honest - I was just being lazy here...
Speaking of lazy...it sort of pained me to do things this way, but I've seen the work of enough Makers who I admire using heaps of glue, solder, and tape in a prototype to give myself permission to be a little ugly here.
I used hot glue to tack down the wires, and adhesive velcro strips to hold down the battery packs (so they'd still be serviceable). I didn't really position the board well - it's hard to get the power switch, and the JST connector is nowhere near the edge of whatever box I'd use to contain the sign.
In the final version of this I'd love to lay the wires out much more cleanly, laser cut some foam to keep all the components in place, and 3D print a box to provide access to all the ports and switches you'd need to actually maintain this thing. As it is, the whole sign will have to be taken down from wherever it is being displayed.
Here's how it looks from the back of the sign:
Plan the entire system before you start building.
It sounds so obvious now that I write it, but think first about what components you're going to use, what tools you have vs need to buy, how much they will cost, how your device will be accessed, serviced, how durable it needs to be, etc., Figure out what code you can borrow, what libraries you need, and what code you will need to write. Consider the environment where your device will live - what is the aesthetic, what is the lighting like, what is the internet situation and availability of power. There's a LOT to think about, and I wouldn't expect anyone to get it 100% right the first time, but doing as much planning as possible up front will save you headaches down the road.
Understand your data as well as the capabilities of your microcontroller
Had I known up front that I would need my microcontroller to know the time, or do so much time conversion, I might've done all the data parsing via my own custom web service rather than on the microcontroller itself. And I might've either hooked up a real time clock, or chosen a different microcontroller entirely like the Raspberry Pi Zero W.
Pay close attention to your power requirements.
This can make a difference between your device running forever, and your device catching fire. I should've taken the time to figure out how to power the whole thing off one power source (if anyone has advice on that, please let me know!).
Start big, then cut down to size
Starting with a full sheet of acrylic or matte board or balsa or whatever you're using means you 'll have nice straight edges, and plenty of wiggle room. Do your laser cutting or CNC routing, then lay out all your electrical components to make sure they'll fit, and then cut things down to size.
Consider your environment
I should've stopped to ask if the device could be plugged in, instead of assuming it needed to be portable and cable-less. It would've meant a lot less hassle for the store. I also should've thought about the lighting - in bright light, the material I used to diffuse the lights was way too thick.
ConclusionWhile there's a LOT I would do differently on this project, I'm proud to finally have built something that doesn't have a million wires hanging out everywhere for all to see! And I'm excited to see whether this device will be helpful to people in the store!
Comments