Automating Blinds with a Retrofitted External Motor

It’d be a real stretch to say I needed to automate the blinds in my apartment, but it was fun, and I’m loving the result. Blinds open automatically at sunrise and close at sundown. It’s nice to wake up to the sun!

I designed most of this setup myself, and wanted to share how I approached it. There was a lot of prior art (a particular shoutout to The Hookup’s Guide), but nothing that worked the way I was wanting. Guides I ran across either mounted a motor inside of a 2″ track (mine are 1″), or were for pull-cord style blinds.

I wanted a motor mounted on the outside of the track to drive the existing worm gear. I rent and don’t want to be kicking myself too hard when I move out. These are super easy to (un-)install.

There are few parts I’ll cover in this post:

  1. The 3D-printed mount design w/ assembly instructions
  2. The driver circuit PCB
  3. Driver firmware (esphome w/ cover component) + Integration with HomeAssistant
  4. Wiring tips

Bill of Materials

Before going into any detail, here’s a bill of materials (links may contain affiliate codes). If you’re into tinkering, you’ll probably have most of these parts already.

Driver

  1. Wemos D1 Mini ESP8266 dev board
  2. A4988 unipolar stepper driver
  3. 28BYJ stepper motor (important: converted to bipolar, see below)
  4. 1000 µF electrolytic capacitor
  5. LM2596 DC/DC buck converter
  6. Barrel jack
  7. 2A 12V power supply
  8. 4 Pin screw terminal
  9. Driver circuit PCB (Available on OSH Park). Gerber files on Github.

Mount

  1. A 3D printer
  2. eSun White PETG
  3. M3 screws, etc. for assembly (this kit has all but the M3*30 screws):
    1. 2x M3*30
    2. 3x M3*20
    3. 5x M3 nuts
    4. 6x M3 washers

3D-Printed Mount

The STLs are available on Thingiverse and PrusaPrinters. PrusaPrinters has gcode for the mounting arm, which requires support in some strategic locations.

The mount’s design is simple — it slides into the track and holds the motor in place. Angles and lengths are adjustable so that the motor can be moved into the appropriate position. It’s held together with some M3 screws.

The only piece of this that is not one-size-fits-all is the motor adapter. It connects the shaft of the stepper motor and the worm gear driver. While the 28BYJ end probably always looks the same, I had three different worm driver shafts across my apartment.

Assembly should be pretty self-evident. Let me know if not and I can elaborate.

Convert 28BYJ stepper to bipolar

28BYJ motors are unipolar steppers. In order to use them with the A4988 driver (which supports a higher supply voltage), they must be converted to bipolar. Fortunately, this is pretty simple. Just crack open the blue casing on the side and cut the center PCB trace. I used a small file to do this, but an X-Acto knife or small screwdriver would work just as well.

You’ll probably need to break some of the plastic legs on the casing to get it open, but it closes back up well enough without them. I also snipped the lead wire going to this trace to avoid future confusion.

Driver Board

The schematic for this board is pretty simple and has three main components: an ESP8266, an A4988 stepper driver, and a voltage regulator (I used an LM2596-based board).

This worked just fine on a perfboard, but because I wanted several of these, I used this project as an excuse to familiarize myself with Kicad and get some PCBs made from OSH Park (project available here, all kicad+gerber files are on Github). I was very happy with the result:

Assembly Instructions

  • Make sure to adjust the output voltage of the LM2596 to 3.3v! You’ll fry the D1 Mini if you don’t do this.
  • Soldering components should be pretty straightforward as they’re all through-hole.
  • I would recommend using headers for the A4988 and Wemos D1 Mini in case either gets damaged.
  • Most D1 Mini kits come with two sets of 8-pin headers. Since the A4988 is a 2×8 pin board as well, this works out perfectly.

Driver Firmware

For this project, I wanted to try out ESPHome. I’ve written my own firmware for almost all of my EPS8266 projects. It was fun while it lasted, but the thought of gluing MQTT libraries to crappy C++ code didn’t sound exciting this time.

ESPHome’s Template Cover component does the trick quite nicely. I’ve put the YAML definition on Github. Flashing the D1 Mini with generated firmware is pretty simple. I used this command:

(obviously you’ll need to have esphome and esptool installed).

You’ll also need a secrets.yaml that contains your wifi credentials and a setup password:

The meat of the definition is in the template .blinds.yaml, including pin mappings from the ESP8266 and the A4988. If you chose pins different from those shown in the schematic above, make sure to correct them.

Parameters that are specific to a particular installation are substituted. In addition to names, you may also want to adjust the target parameter in the definition. This controls how many turns the motor completes when transitioning from one step to another. You can also negate it if you need to invert the direction (although you can accomplish this by reversing wiring as well).

The stepper position and cover state should be persisted across reboots.

HomeAssistant Integration

ESPHome works really seamlessly with HomeAssistant. Just add an ESPHome integration (Configuration > Integrations > + > ESPHome) using the hostname or IP of the ESP8266 (if you’re having trouble finding this, it’ll appear in serial logs after flashing the firmware).

This adds the cover component and allows you to control the blinds:

To address a situation where I need to calibrate the position, I have these scripts:

Calling them will tell ESPHome to consider its current position as either open or closed. In combination with the up/down/stop action buttons, this will allow you to get your blinds into the desired state.

Wiring Tips

You can use any sufficiently large four-wire cable to connect the driver to the motor. Ethernet cable works really well. I used one twisted pair for each lead. Stranded ethernet + crimped header would be best, but I only had solid core ethernet, so I soldered some headers on and called it a day.

Here are some pictures that hopefully help as a wire guide:

Note that you can reverse the order in which they’re connected (1/2/3/4 -> 1/4/2/3) to invert the directions the stepper turns for open/close.

Updates

September 29, 2022

  • Significant updates to the ESPHome yaml definitions
    • Stepper position persisted across reboots
    • Adds pin definitions for MS1/MS2/MS3 on A4988 used to control step granularity. These need to be pulled LOW during operation.
    • By default D4 is used for MS1. D4 is also tied to the internal LED on the D1 Mini. I have this pin kept HIGH during idle so the LED stays off. It’s pulled LOW while the stepper is running, which has the nice side-effect of the LED lighting up while the motor is (or should be) running.

Links

Rescue Your Amazon Dash Buttons

Earlier this year, Amazon announced that they’ll discontinue Dash Buttons. I don’t know how successful Dash Buttons were for their intended use, but Home Automation hackers have loved (mis-)using them for everything from warming up their coffee pot to keeping track of bodily functions.

Unfortunately for us hackers, Amazon is an unforgiving god. Not only have they stopped selling Dash Buttons, but they’ve removed the part of their app used to set new ones up. Even worse, to any button unfortunate enough to connect to The Mothership, Amazon has promised to issue a firmware update that bricks the device. It is therefore critical that you set up your buttons in an environment where they will not be able to phone home.

Fortunately for us hackers, Amazon is not an infallible god. Versions of their dash button firmware built on May 2016 and earlier are vulnerable to a buffer overflow attack during the high-frequency audio setup (Hunz did some truly awesome reverse engineering work to find this).

We can exploit this vulnerability to complete the setup.

How-To

In the following section, I’ll go into more detail about how this works.

For now, let’s do this!

  1. Put Dash Button in setup mode by holding down the button until the LED flashes blue.
    1. Connect to the Amazon ConfigureMe WiFi network and visit http://192.168.0.1.
    2. You’ll see the button’s hardware (MAC) address. Block its Internet access in your router’s settings. If you don’t do this, the button will get an over-the-air update when it phones home and get bricked.
  2. While in setup mode, play this .wav file through some earbuds aimed at the Dash Button.
    • If the LED turns green, the exploit worked! Carry on to step (3).
    • If the LED turns off, you’re probably on a firmware version that fixed the vulnerability. Unfortunately, you’re out of luck if this is the case. (unless someone finds another vulnerability)
  3. Put the Button in setup mode again.
  4. Connect to the WiFi network it creates — Amazon ConfigureMe.
  5. Visit this URL*: http://192.168.0.1/?amzn_ssid=<wifi_network_name>&amzn_pw=<wifi_network_pw>. (obviously substituting wifi_network_name and wifi_network_pw for the desired values)

That’s it! It should work now.

[ * ]: Note that the setup AP is unsecured and this request is sent over HTTP. You’re sending this request in plaintext over the air. Don’t do this unless you’re comfortable with that. The buttons support a configuration flow that involves exchanging crypto keys, but this is far more involved. If there’s enough interest, I can write a script.

How this works

The Dash Button setup process has two high-level steps:

  1. Getting WiFi credentials from you.
  2. Exchanging a pair of secrets with The Mothership. Under normal operation, these are used to authenticate with Amazon when placing an order (it uses them to generate an HMAC secret).
    1. The button transfers a device secret baked into the firmware to Bezos HQ.
    2. The button retrieves and stores a customer secret from Amazon.

(1) is easy for us to fake. In fact, all we need is the final step from the previous section — visiting the URL with the amzn_ssid and amzn_pw parameters.

It’s (2) that’s keeping everyone from setting up their buttons. Amazon has stopped responding to these exchange requests, meaning the buttons will never complete the setup process unless we get creative.

The part of the firmware that handles high-frequency audio packets is vulnerable to a buffer overflow attack. From Hunz’s slides, you can see that it’s doing a memcpy without a length check:

Hunz put together some excellent scripts that pack an exploit payload in ARM assembly into an audio file.

I needed to do some reverse engineering of my own to find the function responsible for writing the customer secret to flash. I’d never so much as opened a disassembler, so this was a pretty fun challenge. Since IDA costs an arm, a leg, and the souls of any present and future offspring, I used Ghidra (I have no complaints, although a professional might). I put the archive file on Github.

The raw function that writes a customer secret to flash is at address 0x40FAA4:

It takes in a pointer and a length (which appears to always be 20). This function is normally wrapped in a bunch of code that calls Amazon’s servers, does validation, etc., but since we’ve got arbitrary code execution, we can just call it directly.

My exploit payload follows:

Since it doesn’t matter what the value of this secret is so long as we’re not intending to connect to Amazon, I pass an arbitrary address (0x400000 — the start of ROM) along with a length of 20 to the writeCustomerSecret function.

Then it sets the LED to green to indicate success. Firmware versions that have patched this vulnerability shut down when they receive the exploit.

Further fun

When starting on this effort, I soldered some magnet wire onto the UART test pads to get serial access. Hunz labels the pins in his talk:

Here’s a photo of my test rig:

The most interesting thing you can do with UART access is execute more code. The audio exploit payload is limited to a small number of instructions. Hunz supplied a payload that reads data from UART into SRAM and executes it (there may be a more convenient way to do this, but I’ve not dug in yet). Soldering onto these tiny pads is pretty challenging, so I modeled and 3D-printed a crude pogo pin fixture:

Pogo pin fixture. Connects to UART RX/TX pins. Gnd is connected separately.

I’d initially thought this was necessary in order to configure wifi credentials, but turns out you can do that using the setup AP, which is much easier.

You can’t do much of anything interesting in the serial console, but it at least appears that someone over at Amazon has a sense of humor:

Acknowledgements

  • Hunz’s reverse engineering work is incredible. If you’re into this sort of stuff at all (and if you’re reading this, you probably are), you’d very likely get a huge kick out of his 33c3 talk, which is where I learned about the audio vulnerability.

Future Work

The Dash Button hardware is pretty remarkable. It’s got a beefy microcontroller, (obviously) builtin wifi, a BLE chip, a microphone for the HFA audio configuration, and so on. I’ve not seen any official-looking estimates at the cost Amazon was paying for these things, but some have guessed in the ballpark of $20 — even at Amazon’s scale.

If we can find a way to flash custom firmware, they’d be perfect IoT buttons. As-is, they’re obviously a little janky. We’re monitoring networks for side-effects to trigger actions. This should theoretically be possible given that Amazon issues OTA firmware updates (the code that handles the OTA update process appears to be at 0x42391C).

I don’t know how much gas is left in my motivation-for-reading-assembly tank, but this is what I’d work on next with what’s left.

It’d be really neat if Amazon open-sourced an SDK. Yes, they’ve said they have a recycling program, but something tells me most buttons are gonna end up in the landfill.

Caveats

  1. Amazon fixed the buffer overflow vulnerability in a later version of the firmware, but almost every button I have uses the May 2016 version out of the box (there’s been ~1/20 exceptions).
  2. Buttons get OTA updates if they connect to the Internet. So if you have a button that’s been phoning home, in all likelihood it’s been patched.
  3. My setup triggers on 802.11 probe requests, meaning I don’t need (or want) the button to connect to a network, only to try. As far as I can tell, though, they will.

Donating

If this work has brought you happiness or utility, it’s more than enough for me to hear those words.

If you’re feeling especially generous, and are open to a charitable donation, that’d make me very happy. Here are some whose mission I support (in no particular order):

Webpack with Arduino and PlatformIO

Web development for embedded devices sucks. More often than not, it involves editing concatenated strings in a header file. Even some of the most popular web projects like Tasmota, WiFiManager, and esphome work like this.

If you’ve done any web development recently, it’s probably involved build tooling like Webpack. This makes it super easy to include external libraries, and to pack all of your assets (javascript, images, stylesheets) into an easy-to-distribute bundle. There’s also a wealth of incredible development tools like hot reloading, and support for alternative languages like Typescript.

Why is the experience so different when developing web applications for embedded devices?

To serve assets, you have these options:

  1. Embed the assets in the source code. This is a pain in the butt, but is a very straightforward approach and results in a complete firmware image.
  2. Side-load assets on, e.g., SPIFFS. This introduces an out-of-band step to get your project running.
  3. Serve assets from an external source. This introduces a point of failure, and requires Internet access.

(2) and (3) make for a much nicer development experience, but make for a crappy user experience. (1) is the reverse. It makes sense that developers optimize for their users.

There’s good news: we can have our cake and eat it. It just requires some simple build tooling. We can use Webpack as we normally would, have it generate a messy header file that our compiler will happily pack into a firmware image for us.

Example

I put together a skeleton example on Github, available here:

https://github.com/sidoh/webpack_with_platformio

Hopefully this serves as a useful quickstart, but I’ll give a more detailed overview below.

Moving Pieces

There are three high-level parts:

  1. The main PlatformIO application (./platformio.ini, ./lib, ./src).
  2. The web app (./web)
  3. A Webpack plugin to cram the web assets into a header file and a PlatformIO build script to call it (./.build_web.py)

./web is a self-contained Webpack application. We can iterate on it using the standard builtin development server. When we’re done, we can call the standard pio run --target upload to run the PlatformIO build. It’ll rebuild the web assets, pack them into a header file, and recompile our application.

The webserver is configured to dynamically serve the assets from the header file.

Result

Now that the build process is set up, we can focus on writing a great web app. Here’s an example of something I built for one of my projects, epaper_templates (I’ll talk about the new version in a future post at some point).

epaper_templates bitmap editor

This is a simple bitmap editor with local image importing, undo/redo stack, resizing, etc. Writing this in raw Javascript would be a giant pain in the butt. Doing it with React and the full richness of the Javascript developer community was an absolute delight. It took me a couple of evenings.

Continuous Integration

It’s pretty straightforward to get a build tool like TravisCI to build and distribute pre-compiled firmware images, complete with web assets. My epaper_templates project does this for tagged releases. Its .travis.yml may serve as a useful reference.

Downsides

This is, of course, not entirely free. Let’s discuss some of the costs:

  1. We’re embedding all of our assets in source. We’re obviously using progmem, but we still only have a couple of MBs of flash to play with. The above example is around 500 KB when it’s said and done.
  2. It’s is clearly more work than just putting stuff in header strings. If what you’re trying to do is simple, this is probably not the right approach for you.
  3. It adds dependencies to your build (e.g., node, npm).

Conclusion

When we have powerful tools, we can build really cool stuff without too much effort. Hopefully this is a useful reference for anyone wanting to use modern web development tooling in their embedded projects.