Motorizing and automating horizontal window blinds tilt with a stepper motor and ESP board using ESPHome, to be controlled from Home Assistant, NodeRED, and with Philips Hue Dimmer switches. 3D printed parts are needed to at least mount the motor to the blinds, additionally, you can print enclosures for the parts.
My original setup is based on The Smart Home Hook Up guide, link below, so credits to the original setup from The Smart Home Hook Up.
We have 13 windows with these same horizontal blinds installed in between the window glasses. There is a rod that controls the tilt and then there is a string that controls the up and down position of the blinds. These blinds' main purpose is privacy and during summer reflecting sunlight and heat, without the blinds, it can get over 30 degrees celsius in those rooms which are in direct sunlight.
We also have separate light-blocking roller blinds on 4 of the bedrooms (some of these are motorized and automated as well), as during summer it can be quite bright outside through the "night".
Focus on the tilt action only
I decided to leave the string, the up and down control out, as we quite rarely use the strings to lift up the blinds, and also the force needed to pull the blinds up increases the higher you pull them and at the top, the pull strength on the larger windows can be quite significant.
Window sizes ranging from 1 x 1.5m, to 0.25 x 1.1m. Although not really accurate and not measuring torque at all, I tested with a luggage weight, that on average, the largest window needed a max "3kg" to lift the blinds all the way up. Although, to hold it up needed only about "0.2kg".
The original setup
About two years ago I motorized and automated some of the windows blinds, by following the instructions from The Smart Home Hook Up, the instructions and the video is still great (one of my favorite DIY guides for motorizing horizontal blinds). This setup has been really solid, only once I have had to replace one of my 3d printed adapters between the Motor and the tilt rod.
However for some reason, I have had some annoyances, mostly caused by the WiFi connection. The most annoying one is getting the ESP8266 connecting back to WiFi whenever it is disconnected (mostly happens when I have updated/rebooted my access points), and once they connected back, I have had to manually rotate the blinds back to sync, for which I needed to first open the window and roll the mechanism by hand.
My goal here was to still use the same components from The Smart Home Hook Up guide, and exact same wirings, but I wanted to try to replace the code with ESPHome. ESPHome doesn't mention direct support for the DRV8825 stepper driver, but it does support the A4988 stepper driver which is quite similar, with a slightly different pin setup. And while I was testing with both drivers the same code works just fine, naturally, you need to have the wiring done slightly differently for the motor (diagrams for both later in this post)
In the end, I just flashed my existing installed setups with the new ESPHome code, without a need to change any of the existing parts or wirings. I haven't really been able to do any long-term testing tests, but at least the connection problem that I had, seems to be totally gone .. the ESP connects right back from disconnects or after being powered off and on. Otherwise, the blinds have been working almost identically.
I tested these parts with the new ESPHome version of the setup:
- ESP8266 (D1 Mini, and NodeMCU)
- Stepper driver A4899 or DRV8825 (Note: pin layouts are slightly different)
- 28BYJ-45 stepper motor (5V) - converted to bipolar stepper
- MP1584EN Buck Converter
- 12V Power Supply
- 3D Printed parts: enclosure for the ESP and buck converter, adapter to attach the blind's axel to the motor. Mounting to attach the motor to the window frame and enclosure for the stepper driver.
- For wiring, I used an old cat6 ethernet cable
In my existing setup used NodeMCU and DRV8825, although now I did some additional test with D1 Mini and both drivers A4899 and DRV8825, just to confirm that both work.
Additionally, you can use some connectors and switches where needed.
Our windows are mostly in pairs so that there is a 1 m x 1.1m window and then a smaller 0.25m x 1.1m (ventilation) window next to it. I decided that in these rooms I would make one ESP control the tilt of both blinds simultaneously.
So one power supply, one ESP8826, one buck converter, two stepper drivers, and two motors to control two blinds.
Converting the 28BYJ - 48 - Stepper Motor to Bipolar
The 28BYJ is a 5-wire unipolar stepper motor and it comes with a ULN2003 driver. To get the most torque out of this motor, you can momentarily supply more voltage for it than designed, without burning it. But to use more than the 5V, a different driver than the ULN2003 is needed.
To use the DRV8825 or A4988, the motor needs to be converted to a bipolar motor. To convert the 28BYJ to bipolar you only need to cut the common voltage connection between the coils.
To do so carefully remove the blue cover from the motor and just scrape (cut) the center trace with a small screwdriver.
Place the cover back and you are ready to use the DRV8825 or A4988 drivers.
The Stepper Drivers
The stepper drivers include potentiometers which can be used to limit the current to the motor. If you run your motor for a long period, you should definitely limit the current as the motor will burn. However in this case the motor is normally run in small bursts, only a few seconds at a time, so there is time to cool down (as in the Hook Up guide, I didn't touch the potentiometers).
Also when the motors are not in use, they will be put to sleep, which is helpful in the case you need to adjust them manually with minimum resistance. There will still be some resistance from the motor itself, and in my case, I couldn't actually use the manual tilt rods anymore and ended up removing them altogether (there would have anyway been a huge syncing problem if I hadn't removed them). Removing the manual tilt rods also gave me a neat way to insert cables for the driver and motor, through the tilt rod hole.
Depending on the stepper driver you are going to use, you need to wire the motor to the driver in a slightly different way, make sure you use the correct diagram.
A voltage converter is used to convert the 12V from the power supply to 5V. The 5V is then directed to the ESP board and to the Stepper Driver (as their operating voltage).
The drivers reset and sleep pins are connected to keep the motors powered off (no holding torque, although as mentioned there is still some from the motor itself). The enable pin is then used to turn the motor on and off.
The driver's enable pin is connected to the ESP into a pin that controls the sleep in the ESPHome code. DIR (direction) pin is connected to a pin in ESP that controls the direction and similarly, the STEP pin is connected to an ESP pin that controls the stepping.
In my case, I noticed that I have 2 different versions of DVR8825, and the motor pins are slightly different on them. One with pin order B2, B1, A1, A2 and other ones with 2B, 1B, 1A, 2B .. you need to wire accordingly!
Don't know if there are similar variations of the A4988 as well.
If you have D1 Minis, the setup works pretty much the same, but you might be again using slightly different pins.
Remember to check the ESPHome code for the pins that you are using!
Wiring for two windows using two DRV8825 and two motors
This is my existing wiring from The Hook Up guide for two windows.
Check the pins you are using, and change them accordingly in the code.
esphome: name: livingroom-horizontal-blinds on_boot: priority: -100 then: - stepper.report_position: id: livingroom_stepper position: !lambda "return id(saved_position);" - stepper.set_target: id: livingroom_stepper target: !lambda "return id(saved_position);" - stepper.set_speed: id: livingroom_stepper speed: 500 steps/s - script.execute: update_cover_position esp8266: board: nodemcuv2 restore_from_flash: true # Enable logging logger: # Enable Home Assistant API api: services: - service: set_stepper_target variables: target: int then: - stepper.set_target: id: livingroom_stepper target: !lambda 'return target;' - script.execute: record_stepper_position - service: set_stepper_speed variables: speed: int then: - stepper.set_speed: id: livingroom_stepper speed: !lambda 'return speed;' - service: set_stepper_position variables: stepper_position: int then: - stepper.report_position: id: livingroom_stepper position: !lambda "return stepper_position;" - stepper.set_target: id: livingroom_stepper target: !lambda "return stepper_position;" # Tested values (one rotation) to fully close or open the blinds globals: - id: open_position type: int initial_value: '3050' # <-- Replace | "Closed to other direction" - id: middle_position type: int initial_value: '2025' # <--- Replace | Blinds Tilt Middle/Fully Open - id: closed_position type: int initial_value: '1000' # <--- Replace | Blinds Tilt Closed - id: saved_position type: int initial_value: '1000' # <--- Replace | Blinds Tilt Closed restore_value: true ota: password: !secret ota_password wifi: ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "Livingroom-Blinds-Hotspot" password: !secret ap_password captive_portal: # WEB PORTAL FOR TESTING #web_server: # port: 80 # version: 2 # include_internal: true # auth: # username: !secret web_user # password: !secret web_pass stepper: - platform: a4988 id: livingroom_stepper dir_pin: D6 step_pin: D7 sleep_pin: number: D5 inverted: yes # inverting since connected the Enable pin max_speed: 500 steps/s acceleration: inf deceleration: inf cover: - platform: template id: "livingroom_horizontal_blinds" device_class: blind name: "Livingroom Horizontal Blinds" has_position: true optimistic: false open_action: - logger.log: "Opening" - cover.template.publish: id: livingroom_horizontal_blinds current_operation: OPENING - stepper.set_target: id: livingroom_stepper target: !lambda "return id(open_position);" - while: condition: lambda: 'return id(livingroom_stepper).current_position < id(open_position);' then: - script.execute: update_cover_position - delay: 1000 ms - script.execute: update_cover_position - script.execute: record_stepper_position - cover.template.publish: id: livingroom_horizontal_blinds current_operation: IDLE close_action: - logger.log: "Closing" - cover.template.publish: id: livingroom_horizontal_blinds current_operation: CLOSING - stepper.set_target: id: livingroom_stepper target: !lambda "return id(closed_position);" - while: condition: lambda: 'return id(livingroom_stepper).current_position > id(closed_position);' then: - script.execute: update_cover_position - delay: 1000 ms - script.execute: update_cover_position - script.execute: record_stepper_position - cover.template.publish: id: livingroom_horizontal_blinds current_operation: IDLE position_action: - logger.log: "Setting position" - stepper.set_target: id: livingroom_stepper target: !lambda 'return (float(pos) * float( float(id(open_position)) - float(id(closed_position)) )) + float(id(closed_position));' - while: condition: lambda: 'return id(livingroom_stepper).current_position != ((float(pos) * float( float(id(open_position)) - float(id(closed_position)) )) + float(id(closed_position)));' then: - script.execute: update_cover_position - delay: 1000 ms - script.execute: update_cover_position - script.execute: record_stepper_position - cover.template.publish: id: livingroom_horizontal_blinds current_operation: IDLE stop_action: - logger.log: "Stopping" - cover.template.publish: id: livingroom_horizontal_blinds current_operation: IDLE - stepper.set_target: id: livingroom_stepper target: !lambda 'return id(livingroom_stepper).current_position;' - script.execute: update_cover_position - script.execute: record_stepper_position sensor: - platform: template name: "Livingroom stepper position" lambda: return id(livingroom_stepper).current_position; update_interval: 5s script: - id: update_cover_position then: - cover.template.publish: id: livingroom_horizontal_blinds position: !lambda 'return float( float(id(livingroom_stepper).current_position) - float(id(closed_position))) / float( float(id(open_position)) - float(id(closed_position)) );' - id: record_stepper_position then: - globals.set: id: saved_position value: !lambda 'return id(livingroom_stepper).current_position;'
3D printed parts
Your needs may be quite different based on what kind of mechanism you have and how the blinds are installed.
I needed an adapter in between the Step Motor and the blinds tilt rod, and a wall-mount for the motor so that the motor is directly lined up with the tilt rod.
These were some of my first designs with TinkerCad, so be easy on me .. the designs and accuracy could be better, and I have multiple times thought to revisit these once something breaks, but after 2 years everything has been working nicely (except for one adapter, that was a poor quality test print)
Links to TinkerCad:
I also made some enclosures for the other parts.
I have the "blinds adapter" attached directly between the tilt rod and the motor shaft, and the motor is attached to the window frame.
I know, I know .. these windows need some serious cleaning ..
Directly under the motor, I have an enclosure attached to the window frame for the stepper driver and for the connectors. Here you can see the cat6 cable going to the ESP32 and to the power supply.
I might have been able to hide the ESP8266 and buck converter inside the window frame, but I wanted to have easy access to them, so I made an enclosure and mounted it on top of the window (visually not the most pleasing option, I've heard). Some of the cables are running inside and around the frame and going to the blinds from the holes that were already in place for those manual tilt rods.
Pictures from the assembly and installation
Controlling the Tilt
We are using the Home Assistant mobile app and Philips Hue Dimmer switches and buttons to control the tilt of the blinds.
We have Philips Hue Buttons next to the doorways, that toggle the blinds to tilt open or close in that room. By the windows, we have the dimmer switches which can be used to toggle or control the tilt in 10% steps.
The logic I have built with NodeRED could perhaps be a bit simpler. There was some strange slowness and sometimes missed steps when controlling the tilt in 10% steps, I am guessing this is due to the update frequencies in the ESPHome code, but I haven't tested it yet. Instead, I created a number helper to store the tilt value, which then updates the blinds, and this bypasses the missed steps problem for now.