Automated Curtain Alarm System
My NAIT capstone. An embedded wake-up rig that opens the curtains on schedule with ESP32, steppers, LEDs, and audio.
بسم الله الرحمن الرحيم
Phone alarms don't work on me. I turn them off half-asleep and don't remember doing it, and by the time I'm actually awake I've already lost the morning. I've known that about myself for years. What finally pushed me to build something was noticing that daylight was the one thing I couldn't override in that half-conscious state. If the curtains were open before I was up, I was up. So I built a rig that opens them for me.
This was my capstone project at NAIT for the Computer Engineering Technology diploma. I worked on it over the course of two semesters, and the goal was to prove out the full stack, from PCB design to mobile app, in a way that would actually function in a real room and not just on a bench.
The mechanical side
The curtains hang on a standard rod, so the mechanical problem was: how do you pull the whole rod's worth of curtains open and closed reliably, without ripping the rod out of the wall the first time something binds? The answer I landed on was a timing belt clamped to the rod, driven by a stepper motor on each end. Two motors instead of one so the belt doesn't have to fight torsion when the curtains are heavy, and steppers instead of DC because I wanted position control without adding encoders.
For current control I ran a pair of TMC2209 drivers. They're the standard choice for 3D printers, and there's a reason. They handle stepper current cleanly, they're cheap, and they have sensorless load detection which I ended up not using but liked knowing was there. A limit switch on each end tells the ESP32 when the curtain has hit the end of its travel, so it stops cleanly and the belt doesn't skip teeth trying to go past.
The embedded side
The whole loop runs on an ESP32-WROOM. I picked the ESP32 because it has WiFi baked in, plenty of GPIO for the motor drivers and the peripherals, and I already had a couple sitting on my desk from an earlier project. Firmware is in MicroPython because I knew most of the tuning was going to happen by trial and error, and reflashing C every time I wanted to adjust a delay would've killed my patience. MicroPython lets me push a change over the serial console in a couple of seconds.
Around the motor loop I added a WS2812 LED strip for a slow warm-up light, and an I2S amp driving a small speaker off an SD card, so the wake sequence has some shape to it instead of being either silent or a blast of noise. There's a Xamarin Forms mobile app I wrote that pushes commands over MQTT if I want to trigger the system manually from bed. And there's an ATmega328-based IR remote on the nightstand as an offline fallback for when the WiFi is having a bad day. Schedules live in a MySQL database on the backend and sync down to the ESP32 over WiFi.
What actually took the longest
I figured the tricky part would be the motion control. It wasn't. Steppers with proper drivers are boring in the best possible way once they're wired up, and the limit switches took an afternoon to calibrate. The real time sink was the sequencing.
My first version of the wake sequence had the audio, the LEDs, and the curtains all firing at once, and it felt awful. Getting hit with sound while a light strobed on and the room got brighter didn't feel like waking up, it felt like a fire alarm. I ended up staggering everything. LEDs come on first, dim, warm up over about a minute. Then the curtains start moving, slowly for the first half of the travel so the change in light is gradual, and faster for the second half. Audio comes in last, quiet at first, then rises. I spent a couple of weeks nudging those timings before the sequence stopped feeling artificial.
The other thing that got me was motor noise, and not the audible kind. Electrical noise from the stepper drivers was leaking onto the shared power rail and messing with the ESP32's ADC readings, which caused the limit-switch debouncer to fire false positives every few cycles. Took me a while to trace it, because on a scope the switching looked clean when the motors weren't moving. The fix wasn't glamorous. Better decoupling on the driver side, a proper split between analog and digital grounds on the custom PCB, and a ferrite bead on the 5 V line to keep the noise from wandering. Standard hygiene, but I hadn't designed for it up front, so I had to redo the board once I saw the problem. Lesson noted for future PCBs.
What I'd change in a v2
A light sensor on the outside of the window would let the system dim the LEDs and skip the audio ramp on days there's already sun coming in. The Xamarin app is fine but the framework has been in maintenance mode for a while now, so if I picked this up again I'd rewrite it in React Native or Flutter. And I'd probably swap the MySQL backend for Supabase, since that's my default now for anything that needs auth plus a database without the operations overhead.
Salaam.