Week-long hack: ESP8266 touchscreen WiFi light controller and clock

A couple of months ago I picked up cheap WiFi-controlled LED bulbs (one among dozens of very similar devices), after seeing them at a friend’s place.  This turned out to be an excuse to play with the ESP8266, which has inspired several hacks.

I was overall very happy with these bulbs: decent Android and iOS apps and, compared to fancier solutions (e.g., Philips Hue or Belkin WeMo), they do not require any proprietary base stations, and you can’t beat the price!  However, switching off the lights before falling asleep involved hunting for the phone, opening the app, and waiting for it to scan the network; not an ideal user experience.  I was actually missing our old X10 alarm clock controller (remember those?), so I decided to make one from scratch, because… why not?

Although the X10 Powerhouse controller’s faux-wood styling and 7-segment LED had a certain… charm, I decided to go more modern and use a touchscreen.  I also designed a 3D printed enclosure with simple geometric shapes and used it as a further excuse to play with 3D print finishing techniques.  Here is the final result:

ESP Clock

And here it is in action:

If this seems interesting, read on for details.  The source code for everything is available on GitHub. Edit: You can also check the Hackaday.io project page for occasional updates.

Component selection. There are several boards with the ESP8266, most of them using the ESP-12 module. I decided to go with the SparkFun Thing (which directly incorporates the ESP chip), as it also includes a LiPo charge controller.  Perhaps overkill for battery backup, but nice to have.  If you do use the charge controller, then the price is very reasonable (e.g., an Adafruit ESP breakout and Micro-LiPo combo will cost about the same–although flash is 4x larger and the ESP-12 module is FCC-approved). Also, it’s a very nice board for experimentation and it’s become my go-to ESP board: nice header layout, and the easiest to program (tip: instead of fiddling with the DTR jumper on the Thing, just cut your DTR wire and insert a pin header pair: once esptool starts uploading, just pull the pin and.. done!).

For the display, modules with a parallel interface were out of the question, since the ESP does not have enough pins. After some googling, I found Digole’s IPS touchscreen, which incorporates a PIC MCU and can connect over UART, I2C, or SPI (selectable via a solder jumper). There are several users that really like Digole’s display modules and, particularly their older models, seem quite popular. The display itself is very nice.  However, touchscreen support appears to be relatively recent and isn’t that great (more later).  It is also a bit on the expensive side, the firmware is not upgradeable (so you’re basically stuck with whatever version your module comes loaded with — I got one with an older version that has some bugs with 18-bit color support), and manufacturing quality could have been a bit better (mine had poor reflow).  Still, for prototype experimentation, this isn’t a bad module and the company is generally helpful to customer inquiries.

I also picked up a DS3231 RTC module off of Amazon, but I ended up not using it; periodically synchronizing with an NTP server is more than good enough.

Total cost.  The first version of this device comes to about $45 including everything: SparkFun Thing ($15), touchscreen (highest cost at $21.50), and 500mAh LiPo cell ($8.50 off eBay). However, in retrospect, it could be done for much less: about $13 total (!) if you skip the LiPo (and charge controller), using a $5-6 ESP module instead, and also get a much cheaper ILI9341 touchscreen module (not IPS, but just $7 off eBay; I have one on the way from China). This does not include plastic filament (maybe a dollar?), paint (assuming you have these already, doesn’t use much), and labor.

3D-printed enclosure. I mocked a couple of profiles in 2D CAD to see what I like, and then did the actual design in OpenSCAD, which is my go-to CAD tool, because… code! (Who has time for point-and-click? :)  It’s a fairly standard affair, with simple geometric shapes, designed in multiple pieces for printing.

openscad

The picture above shows the parts in their printing orientation. The standoffs are conical to eliminate the need for supports (alternatively, I could have printed them as separate parts, but getting them inserted is too fiddly, especially in tight spaces like this). The cylindrical sections (middle right) are support ribs which I ABS-glued to the main enclosure’s vertices. They serve two purposes. First, to hold the endcaps in place (the ribs are slightly shorter than the enclosure). Second, to provide some extra support (after gluing, rib layers are perpendicular to enclosure layers). Printing them separately may be unnecessary overkill (there is also a version of the enclosure and ribs in one piece, which requires some extra support to print, but not much), but gluing them is easy enough so… why not. The little clip (top right) is for holding the LiPo cell in place, and is also glued inside the main enclosure.  It’s printed separately to eliminate the need for support (and, at the prototyping stage, also make it a little easier to try different battery sizes, without having to re-print the whole thing or do separate test-prints).

One part of the design that does require a lot of support is the opening for the display.  I entertained the idea of printing the main enclosure in three vertical sections (and gluing them together), but eventually decided against it. Printing that successfully took a bit of trial and error.  I use both Cura and Slic3r.  For most parts I used Slic3r (mainly because it produces smoother outer perimeters, and also integrates better with Octoprint).  However, for the life of me, I haven’t managed to print supports that break off easily.  Even with the new pillar mode, most parts are fine, except one part somewhere that’s just too close to the print to separate!  Cura, on the other hand, always does an excellent job with supports.

Finally, when designing cases like this, one of the (many!) things I like about open-source hardware is that I can download the PCB layout and get precise component positions and dimensions; no calipers and almost-there test prints!  Sometimes it’s the little things…

You will note, however, that there are holes to insert hex nuts, which are visible from the outside and would have been rather ugly (you don’t see exposed fasteners in “real” products).  Which brings me to the next trick.

Friction welding: dos and don’ts.  I first heard about friction welding through Make magazine’s excellent article on 3D print post-processing and since then it has become one of my favorite techniques. I’ve seen a few tutorials on YouTube about friction welding plastics using a rotary tool.  However, at least the ones I found, seem to be from people who recently learned the technique themselves and are excited to share it.  I wish Make: had placed more emphasis on this (it would have saved me several failed attempts), but do not skip step 2c (pre-heating the surfaces); this is crucial!  And, no matter what you do, do not immediately press the spinning, cold filament onto the cold pieces (as some of these tutorials appear to suggest), since you’ll most likely gouge them.  An alternative I’ve found to pre-heating with a heat gun is to use friction itself to do the preheating. Initially, just barely touch the spinning filament onto the plastic surfaces (not on the metal).  Without applying any pressure, wait until you see a tiny bit of plastic start flowing.  Only then gradually increase the pressure and start moving the filament, to keep a consistent flow.  Also, if blobs form on the tip of the filament, it’s best to stop and lightly spin it against some sandpaper to clean them off.

Embedding nuts with friction welding. Using friction welding to embed nuts is a trick I came upon by accident.  When I was building my Kossel-based printer, I overtighened the screws holding the rods to the effector, stripping the cutouts for the nylocs.  I was too lazy/impatient to print another effector, so I just quickly filled the gaps using friction welding.  I’m still using that effector, which has held the nuts very nicely for over a year (and after having taken the effector apart several times, to tweak various things).

I now regularly use this technique to also hold magnets and, generally, anything inserted that needs to stay put.  Superglue is the easiest, but it develops stress cracks and invariably fails over time (and, if you’re thinking threadlocker, don’t: it will craze the plastic, especially ABS).  Next easiest is using a soldering iron to press the nuts/items into the plastic, which is I use very often (I regularly design all my holes undersized and do this anyway).  However: (i) you can’t use it on non-metal items; (ii) you can’t use it on magnets (the necessary heat will demagnetize them); (iii) if you don’t have a steady hand, you may loosen the hole enough to cause the part to fall out eventually, even if it seems fine at first.  Friction welding takes a bit more time, but it’s the best solution I’ve found so far and it’s also very easy after just a little bit of  practice.  I haven’t tried threaded inserts yet. The McMaster-Carr “heat-set inserts for plastics” (it appears their site does not support direct linking!?) that Werner Berry uses look really nice and I’ve been itching to try them, but that’s another piece of hardware I need to keep around.

Another nice thing is that you can use this trick to embed blind nuts that are not visible from the outside.  This is a trick that is rather obvious (once you’ve done all the above :).  First, insert the nuts (I used the soldering iron) and make sure the surface is flat (lightly file, if necessary):

insert1 insert2

Make sure that the fastener axis is oriented properly (if not, adjust).  Then, fully thread the holes from the outside.  Use a proper tap (not a screw) to cut threads, especially for finer pitches.  Do not skip this step (more later).

insert3 insert4

Finally, apply molten plastic, starting from the outside (i.e., touching the perimeter of the hole, plastic-to-plastic) and working your way towards the center.  Once you’re done (if you do it right, you should end up with a very clean-looking plug, without any gouges or streaks), lightly file to make the surface flat.

weld1 weld2

Done! Now the nuts are not visible from the outside, and you have a very clean finish.  Additional advantages of this approach: you do not need easy access to the hole from the fastener side (in this enclosure it would have been very difficult to insert the nuts and/or tap the holes from the inside), and you can use a regular taper or plug tap (rather than a bottom tap).

3D-print finishing. Although I often like the surface finish of 3D printed layers, in this case I wanted a smoother, more “product-like” finish.  Some time ago I bought some XTC-3D and this was a good opportunity to play with it a little more.  Overall, XTC works very well; especially on organic/curved shapes, you’re pretty much done after applying. Do follow the instructions about applying very thin coats (it will even out, even if it does not look like it at first).  However, in this case (no pun intended) there were two issues. First, I used an older printer (a Solidoodle 2; my Kossel is not yet set up for long ABS prints) which has significant banding. XTC is good, but it’s not magic; I did some initial sanding (and cleaning with denatured alcohol) before applying the XTC resin.  Second, on large flat surfaces, you will get some minor unevenness and some tiny bubbles here and there. Light sanding (with a sanding block!) will address most of these issues, but in some places you may need to use a little filler.  One-part Bondo spot putty is sufficient for this.  Apply it generously, and after it is dry, sand most of it off (it sands very easily).  Do wait for it to set, though.  Especially on thick coats, the manufacturer’s recommended set time (25 minutes) may not be sufficient; rule of thumb is to wait until it turns light pink everywhere and then wait some more.

bondo1 bondo2

All things considered, XTC-3D works great (unfortunately, I forgot to take pictures after applying just the XTC-3D). It definitely beats sanding (substantially reducing it), as well as two-part body fillers (which I haven’t used with prints, but I’ve used in another project a long time ago).  And for smaller surfaces or organic shapes, you’re pretty much done after applying.

Spray painting. I’m very new at this; I did it once in the past (again for this project) and, surprisingly, it had gone very smoothly.  I still don’t know why (maybe too much false confidence?), but it’s always the second time that gets you burned, isn’t it?  To cut the long story short, I learned about the difference between lacquers and enamels (simplifying, the first just dry by evaporation, the second cure by reacting with air), got distracted by paint chemistry (if you’re curious look, e.g., here or here, and if you’re really curious try this), and found the following paint compatibility chart, which is worth it’s weight in gold:

paint-compatibility
(original credit and image)

Furthermore, in the past I had used Krylon, which is not available at big box stores (we have one two blocks from home) so I decided to try Rustoleum instead.  Although people are often happier with Rustoleum (and, these days, they’re also cheaper), for the life of me I couldn’t get an even spray with their nozzles. Maybe they work well on large items like chairs and tables, or maybe it’s my (lack of) technique, but on this small enclosure I couldn’t get even coverage, and always got spots with too much paint (not enough to cause drips, but enough to affect the surface finish). More importantly, Rustoleum takes forever to dry and, if you’re doing your spraying in all sorts of weird places with temporary setups (we live in an apartment), that’s an issue.

So, I wiped it all off (tip perhaps worth sharing: I found that, at least if the paint hasn’t completely cured, white spirit works well and it doesn’t attack the plastic at all), went to an auto parts store, and got some Krylon.  I think their newer non-rotating nozzles spray a bit more like a firehose (just have to live with overspray), but other than that, the second attempt went pretty well.

I chose a satin finish both because I like it, and also because it’s a bit more forgiving with improper spraying distance (you can err on keeping the nozzle too far from the surface, and it won’t have an ill effect, within reason). Skipping the intermediate steps (nothing to be proud of :), here is the end result — not bad for a rookie:

paint3 paint2

paint1 paint4

Putting it together. The last bits were easy: soldering headers on the Thing (whatever fits in the enclosure, some straight and some raised right-angle pins) and on the display module.  Also, the right-angle JST header soldered onto the Thing wouldn’t work in this enclosure (the LiPo wire collides with the endcap), so I desoldered it and replaced it with a vertical JST header.  Finally, I had to solder a wire to the reset pads on the Digole module (the reset signal is not broken out, but it’s accessible through an unpopulated reset pushbutton).

digole

 

After fiddling with the screws (long nose pliers and balldrive Allen keys FTW!) and wires, the mechanical assembly was done — whewww!

Epic fail(s). So far I’ve omitted an epic fail from the story.  The enclosure shown above is actually the second attempt.  The first one ended up in disaster, all within a couple of hours.  The first attempt was printed in PLA.  First fail and lesson: PLA really does melt under the sun, and it takes less than you’d think.  I sprayed the endcaps first and temporarily set them down on a cardboard on top of a metal outdoor table under the sun.  In the few minutes it took me to spray the first coat on the main enclosure, the encaps had seriously warped!  You can see this in the picture on the left (and that is after I spent half an hour re-shaping them with a temperature-controlled hot air gun at low heat!).  The second fail was even worse: when I did the first attempt, I did not have an M2 tap, so I decided to use M2 screws (and oversize tap diameter).  Unfortunately, this does not cut the threads properly, and the screws still meet substantial resistance.  Since the nuts will never be perfectly aligned, when inserting the screws from the inside what happened was what you see on the left photo. Doh!

fail1 fail2

So, definitely use a tap to properly cut threads (or, make the holes really oversize, and make sure you clean any molten plastic if you use a soldering iron). Furthermore, measuring your fastener lengths twice and hand-tightening them is not a bad idea either.

You may also notice that the finish here is a little glossier; that’s what happens when you over-apply paint and/or spray from too close.

Finally, to top it all off, I hadn’t realized that the standoffs for the RTC module where on the wrong side (double-doh!) and when test-fitting it also turned out that the wires I had crimped were a couple of cm too short. Oh well, it had been a while since I had an epic fail like this! :)

Protocol sniffing. On to the software part.  First thing was to reverse-engineer the WiFi bulbs’ protocol.  It appears that, although there are several variants of the hardware that look identical, not all of them run the same protocol (e.g., see links in sniffing notes on GitHub).  I’m not even sure all are made my the same OEM (FWIW, MAC vendor lookup on my bulbs says Hi-flying Electronics). Of course, none of these protocols are published, but all of them are very similar and quite simple.  In my case, since I’m running OpenWRT on our router, I just installed ngrep and sniffed the iOS app’s traffic.  I’m pretty sure it’s possible to sniff traffic even if you don’t have access to the router (but I didn’t have to find out).  Edit: Root access on the router makes sniffing much easier (otherwise you’ll probably need a sniffer on your tablet/phone).

For on and off commands, I can just copy them verbatim.  For commands to set color, the structure is easy to figure out.  First is an opcode byte, followed by RGBW values (the bulbs have both RGB as well as warm-white LEDs, and it seems you can turn on either one or the other), a constant(?), and a checksum byte. Nothing too fancy.

The iOS app uses UDP broadcast for bulb discovery (that protocol is also easy to figure out).  This step does take some time (and was one of the annoyances with the user experience, since this information is not cached by the iOS app).  However, after that, all communication happens over TCP.  To keep things simple, I decided to skip the device discovery step (at least for now), and just assign fixed hostnames/IPs to the bulbs.

Firmware.  The firmware is fairly standard stuff. It’s written using the ESP port of Arduino (many thanks, @igrr et al!), and it currently occupies about 70% of the Thing’s flash.

First, the display driver and UI code. Touch handling uses a combination of interrupts to detect the first touch, and then polling and debouncing to detect finger down/move/up events, and update the UI accordingly (this is probably the most complex bit here, and it’s actually pretty simple).  While at it, I did a gratuitous rewrite of the Digole library, inspired by a very cool hack I had seen.  Then NTP client, WiFi bulb client, and a webserver for configuring the device over a web browser.  Settings are stored in “EEPROM” (which, on the ESP, is just a sector of the flash memory).  The web UI is pretty simple for now:

web-screen

Arduino on ESP has a great set of libraries for networking stuff, which makes all this quite easy! I’m using basic Bootstrap and Knockout.js to make it look a little pretty. I decided to write a proper HTML5 frontend and a simple REST API (using the excellent ArduinoJson library). However, upon first boot, the device has no Internet access.  If it fails to connect to WiFi, the firmware will switch the device to AP mode, so initial configuration can be done over WiFi.  Since the flash chip is not large enough to store Bootstrap and Knockout locally, there is a separate, minimal UI (not shown) that uses regular HTML forms (no AJAX) and just allows setting the SSID and password.

One problem (that I eventually worked around, rather than solved) was getting the Digole module to talk back to the ESP.  I2C was a fail (and it wasted me a couple of days; still not sure if the problem is on the display’s end or with the Arduino’s clock stretching implementation, or something else) and SPI I didn’t really try. I finally got UART to work (except that you can’t turn off the ESP’s 78Kbaud boot messages, hence the need for accessing the reset signal on the Digole).  The downside is that now reflashing the firmware is a PITA (I have to fiddle inside the case, to disconnect the display and connect my FTDI), but that happens relatively infrequently (the display stuff is mostly done, and the network stuff I test on a spare Thing first).

Conclusion. After all this, I think the result is not bad for a completely home-made device. Could I have gotten a used Chumby for a comparable price (they go for about $60 used), or just used an old/cheap Android tablet (and perhaps just 3D print a stand)?  Aside from the Chumby service’s ups and downs… sure, but where’s the fun in that? :)  Also, there is no way to reduce the cost of these alternatives down to $13.

What’s next? Well, you may have noticed there is a zipcode setting. That’s for weather information (planning to use OpenWeatherMaps, which returns reasonably-sized responses — parsing anything more than 1KB, maybe 2KB, is probably a bit iffy).  Also, a web UI to control the lamps would be nice (the REST API endpoints are there, just need to get around to writing and refactoring the HTML bits).  Maybe adapt the whole thing to a cheaper display module (as discussed in the beginning; I’ve already started a port of the ucglib library to the ESP, but need an actual device to finish it). Finally, one could perhaps re-write it in Lua (NodeMCU?) with support for pluggable modules (a-la true Chumby). That probably won’t be me, though; by that time, I’m pretty sure a new hack will have “distracted” me. :)