Arduino-Based Curtain Automation, Mk 2

In 2012, I built a controller so that I can open and close my curtain from the command line! It worked ok for a while, but eventually I rebuilt it from scratch. The description of the first build is here, or read on for the description of Curtain Mk 2.


My apartment has a 16' wide floor-to-ceiling window, and a correspondingly-huge curtain. I wanted to put the curtain under software control so I could do things like, for example, have it automatically open in the morning to help me get out of bed.

Things that didn't work.

My first approach was to buy an Add-a-Motor Model 80 Drapery and Blind Controller, as a starting point. It looked like this would do most of what I wanted: open and close the curtain by pulling on the rope. All that was left was a small matter of hooking up ethernet to its toggle button, right?

Well, it wasn't that simple; I ended up having to discard all of its circuitry and re-use just its electric motor, gearbox and mounting hardware. Downsides of this approach were:

  1. The motor is REALLY REALLY LOUD.

    Since one of my goals for this project was to have my curtain automatically open in the morning so that the daylight subtly reminds me to get out of bed without an actual alarm going off, this is a problem, since the noise of the thing would often wake me up.

    I somewhat mitigated that by putting the whole mess inside a larger box and lining the inside of it with Ballistic Sound Dampening sheets, which is a dense-rubber-and-aluminum sticky sheet designed for layering inside your car door panel to prevent rattling when you have a ludicrously deafening sound system. It helped, but not enough.

  2. The gearbox is made of plastic gears, and is a piece of shit.

    Once I got the thing working, it worked for a couple of years, but eventually it catastrophically self-destructed, stripping a gear and cracking its own case while torquing itself off the wall.

    So I bought another one and replaced it, and that one stripped a gear the very next day. Since they're almost $100, I wasn't about to keep buying a new one every couple of months.

  3. The mechanism is timer-based.

    That means that if for some reason, opening the curtain and closing the curtain don't take the same amount of time -- e.g., when the shitty plastic gears are slipping at random -- the curtain won't close all the way.

So, you know, I don't really recommend the Add-a-Motor. Unfortunately there doesn't seem to be any comparable product on the market that isn't an integrated curtain track that costs more than $4,000 -- and can only be remote controlled using mysterious, proprietary systems that mostly rely on IR.

Starting over.

Still a-glow from the flawless victory of my recent payphone conversion project (making a note here: huge success) I decided to start over from scratch. The new plan went a little something like this:

  1. Arduino Uno;
  2. Ethernet Shield;
  3. Motor Shield;
  4. Stepper motor, 179oz-in -- nope, that wasn't strong enough;
  5. 227:1 Metal Gearmotor 25Dx56L mm LP 12V (23 RPM, 240oz-in, 1.1a stall);
  6. Two Hall effect sensors to determine when the curtain is at the left end or right end of the rail.

Hall effect sensors are those magnet detectors that you see on door alarms. They trigger when a strong magnet is nearby, and with a reasonably strong magnet, that means "within about an inch". (Pro tip: you need 2, so buy 10. Don't be like me.)

The Adafruit Motor Shield and its software are really easy to use, and support a variety of motors. First I tried a stepper motor, but it wasn't nearly strong enough; even when double-stepping it, I could still stop it with one finger. So I got the above Pololu electric motor, with an internal metal gearbox, and it's... almost strong enough. It can open the curtain just fine, but when closing it, it really struggles at the end (as the curtain gets bunched up and harder to compress) and sometimes it stalls out.

So I may switch to this one instead, which has more torque, but sadly, is only about half as fast (378:1, 14 RPM, 320oz-in, 1.1a stall). With the current motor, it takes about 90 seconds to open the curtain. With the slower/stronger one, that would be about two and a half minutes...

I haven't found another motor that is both higher speed and torque, and that has a stall current under 1.2a (which is what the Motor Shield requires).

Update: I took a chance on this one, which has a too-high stall current (33 RPM, 210oz-in, 2.1a stall) and it was working great -- for about a month and a half until it stripped an internal gear. Sigh.

Also: huge thanks to Limor for advice on what parts to use! I wouldn't have even known where to begin shopping for motors or how to quantify my requirements otherwise. Adafruit is an awesome company and you should buy all your stuff there.

The hardware.

I used an Arduino proto board to keep all of my connectors removable and in the same place: I attached an 8-pin header to it and routed everything through it. I also added a pair of LEDs to it to make it easier to debug the positioning of sensors: I needed to be able to see whether the sensor was triggering from across the room, while I was standing on a ladder...

So my Arduino pin usage looks like:

  1. LED 1;
  2. LED 2;
  3. Unused: Ethernet Shield usurps it;
  4. Sensor 1;
  5. Sensor 2;
  6. Push button.

The LEDs are wired directly, so that leaves three sets of cables that go into the header:

  1. Push-button A;
  2. Push-button B;

  3. Motor A;
  4. Motor B;

  5. Sensor power;
  6. Sensor ground;
  7. Sensor 1 detect;
  8. Sensor 2 detect.

The Hall effect sensors have three pins, but they can share power and ground, so two sensors means four wires. I just ran Cat5 behind the curtain track, with a fork for the first sensor.

To mount them, I cut up small pieces of proto-board, soldered on the sensors and the wires, and then glopped a generous serving of epoxy over it all, since I knew I was going to be twisting and fussing with them a lot while mounting them, and I didn't want any chance of one of the wires breaking off.

right now they're stuck to the ceiling with foam-tape, but eventually I'll epoxy them in place, once I'm certain the positioning is correct.

One difficulty was actually finding a place to put them that didn't interfere with the motion of the curtain. Even though the curtain hangs below the track on hooks, the fabric of the curtain actually extends above the track, so zip-tying the cable to the track's bolts was making it get caught up. I ended up having to drill new holes in the ceiling just to mount the cable. Nothing says good times like standing on a ladder and drilling into concrete above your head...

Turning the wheel and pulling on the cord was another difficulty...

The old Add-a-Motor had a track that the box slid on to, and a set-pin to hold it in place, so you could adjust the tension on the cord. That was a reasonable system, so I decided to re-use that by bolting my new motor contraption into the old box. My first attempt at that, of just screwing the new motor housing to the old plastic box, brought us back to the same old problem: everything about the Add-a-Motor is a plastic piece of shit. So of course it flexed too much and didn't work right.

I then stripped away most of the plastic box and screwed some small steel plates to it to stiffen it, then mounted the motor to that. That'll hold it...

I also re-used the rubber gripper-wheel from the Add-a-Motor, after sandwiching it between two new wheels, because why not.

See that pipe strap holding the motor to the bracket? Yeah, that was a bad idea. Don't do that. On my first attempt at this, I tightened down that strap too much, and one of the screw-heads very slightly dented the motor casing. You know what happens to electric motors that have dents in them? They no longer turn. So don't do that.

The software.

It's only a minor update of the software I wrote last time, updated to understand the two sensors (giving it three possible states: open, closed and middle, rather than the two states it had last time.) Also some LED-blinking niceties to represent the various states and error conditions.

It listens for connections on an Ethernet port, and reacts to commands like OPEN, CLOSE and TOGGLE by turning the motor on and off until it sees the status of the sensors change. And as a safety feature, if the motor has been running for more than a couple of minutes without a sensor being triggered, it panics and stops the motor anyway.

There is also a push-button on the side of the device that does the same thing as the TOGGLE command, for those times when I want to open or close the curtain without using the computer to do it.

The code is pretty straightforward, but if you're writing your own command-line-driven server for an Arduino Ethernet, this might be a good starting point.

Arduino source: curtain.ino

And here's a Perl script for talking to that server from the command-line, so you can type things like "curtain open":

Cmdline interface:

An aside about the Arduino Ethernet library and how it sucks.

Can I just take a moment to say again how completely absurd the Arduino DHCP situation is? Let me enumerate its sins:

  1. You have to hardcode your MAC address into your source! SERIOUSLY? If I had multiple devices and I wanted them to function simultaneously, I would have to edit the source code for each one of them before deploying? (And don't tell me it would break the bank to include 48 bits of NVRAM on the Ethernet board -- I'll pay the extra $0.001.)

    I mean, how does anything work at all? Honestly, even talking about this is embarrassing. I feel bad for the people who allowed this situation to exist. I feel like even discussing this is like pointing out someone's big honking booger, like, is this really necessary, and can we just not?

  2. If the Ethernet shield is not plugged in, the software does not detect that, and Ethernet.begin() simply hangs forever and your device will not boot. So have fun debugging the software in an ethernet-less test environment.

  3. If the Ethernet cable is not plugged in, Ethernet.begin() stalls for two minutes waiting for a response. Because apparently link-detect is too hard. And not only is that timeout unreasonably long, but there is no way to adjust it.

  4. Should your device boot up with no DHCP lease... forget about it, you can't get one later either, unless you're willing to periodically risk that two minute timeout.

  5. It will never renew your lease, ever. So if your router decides to give your IP to someone else, because you've never renewed it... well, I guess you get to walk over there and power-cycle your device.

  6. If you google around for a minute, you'll find some references to an "EthernetDHCP" library that purports to fix many of these problems -- Asynchronous operation! Configurable timeouts! Actually renewing leases! This is like some Star Trek science fictional future crazy-talk here! -- but...

    ...wait for it...

    ...that software is from like 2013, which was three years ago, so of course it no longer compiles, its features were never re-integrated back into the standard Ethernet library, and it now appears to be abandonware.

  7. Oh yeah, and though it's not particularly Ethernet related, wow, the Mac really is a piece of shit. I mean, it's written in Java, so complaining about how relentlessly terrible it is really feels like kicking a puppy. A puppy with no hind legs. But wow is it bad.

    It has innumerable failings, but it's greatest sin is not bothering to notice when the file loaded into it has changed on disk (because, say, you're using an external editor) and automatically re-reading it, like every other IDE in the world does. This is related to its second-greatest sin, which is that it does not use standard Mac text-areas, which means that the built-in editor does not implement Emacs keybindings, so editing code within the Arduino app is like dictating your code over the telephone.

    But I digress.

Would you like to come up and see my automata?

At this point there's a fair amount of home-brewed automation in my apartment. My receiver (a Denon X2000) has ethernet, so I wrote a Perl script to speak the AVR/AVC control protocol. This means I can change the volume and input sources from the computer without getting out of my chair: sweet! (Some people might have used an IR remote control for this. How pedestrian.)

My current video projector (an Optoma X600) does not do anything sensibly straightforward like just have a web server in it for remote control, but there is a Net::PJLink Perl library that knows how to talk to it, so I can power it on and off from the web. Sweet.

So, combine the two of these with a properly-configured Griffin PowerMate, and I've got a big button sitting on my desk that adjusts the volume when I turn it, and toggles the projector on and off when I smack it. Turning the projector on also pauses iTunes and switches to the proper input on the tuner; and turning it off does the reverse. (Important: use the the PowerMate 2.1 software. The 3.0 software totally shit the bed.)

This is especially handy for those times when I fall asleep watching TV, because when I wake up and stumble toward the bedroom, I can hit the big button to turn off the projector without having to wake up enough to focus my eyes on a remote!

Turning on the projector also closes the curtain -- and, I have a cron job that opens the curtain in the morning to remind me that daylight exists.

© 2012-2016 Jamie Zawinski <>