ESPHome | HA | Motorizing Horizontal Blinds (Tilt action)

Motorized horizontal blinds (based on the Smart Home Hook Up guide), converted to ESPHome.

ESPHome | HA | Motorizing Horizontal Blinds (Tilt action)

Overview

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.

0:00
/
Using the Hue Dimmer Switch to tilt the blinds, this switch is normally attached next to the window.
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.
Automated Motorized Window Blinds (Horizontal Blinds) – The
Check out The Smart Home Hook Up guide for the original build. 

Background

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".

Roller blind
Less than 2 hours of "dark", and even that is not really dark, but just twilight.

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.

Goal

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.

Parts

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.

0:00
/
Testing D1 Mini and the A4899 with ESPHome, (experimenting with stepper motor acceleration and deceleration, however, didn't use it in the final code)
Parts used in my original setup, and which are still in use.

Additionally, you can use some connectors and switches where needed.

Dupont connectors
Power Plug and a switch

The build

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.

Wiring Diagrams

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.

⚠️
Always confirm your components have the same pin order.

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.
Wiring with DRV8825 stepper Driver
Wiring with A4988 stepper driver

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.

My current setup for two windows

ESPHome Code

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:

Mount for the motor and adapter for the blinds

I also made some enclosures for the other parts.

Enclosure for NodeMCU, Buck Converter with a power switch and plug for the power cord. This design had interchangeable wall elements for different needs I had.
Enclosure for the DRV8825 stepper driver and the connections

Installation

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 ..
This has been in place for about 2 years and is in daily use (looks like it would need some cleaning though). The adapter had to be changed once, I had used a test printed adapter, so the tilt rod hole didn't hold anymore.

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.

The driver has its cooler sticking out of the enclosure, which also helps to keep it in place.

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 button switch is used to toggle the blinds
The Hue dimmer switch

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.

Node-RED