[Project] Controlling exterior Venetian blind with tilt with Shelly2.5

Hi guys,
I just wanted to share a little project I have been working on.
I have an exterior venetian blind with a single motor which controls both position and tilt (when button down pressed, the blind first tilts to closed then starts moving down, when button up pressed, the blind first tilts open then starts moving up).

I wanted to use the time_based cover in ESPHome to track the position, which is accurate enough.
My blind also has built in endstops, which I cannot read directly in ESPHome, but there is a workaround using the power draw detected by Shelly2.5. This is why the time_based cover has “has_built_in_endstop” set to true, to be able to “synchronize” the blind position with the position tracked by “time_based” cover in case it gets inaccurate over time, this way each time the blind is fully open or closed, the position gets synchronized and the cover component is stopped based on the detected power draw.
Unfortunatelly time_based cover doesn’t support tilt, so what you are actually controlling via the buttons or HA is the template cover, which synchronizes position state with the time_based cover and controlls it, but also adds the tilt function. I meassured that it takes around 1s to tilt open/close the blind, to synchronize the position (without a need for some custom component tracking the tilt status based on the previous movement of the blind) it just opens/closes the tilt for 1s to make sure it’s either fully open/closed, then it applies the percentage of tilt you requested (in worst case this means the blind will move up/down a bit).

Features

  • Support for HW buttons connected to Shelly2.5 - short press used for tilt (activates the blind momentarily), long press for opening/closing the blind (latches in the on state until second press in either direction)
  • Position of the cover tracked using time_based cover
  • Tilt controlled by template_cover - tilt position synchronizes first to fully open/closed, the moving to specified position
  • has_built_in_endstop enabled to allow the blind to move to fully open/closed position to synchronize the software time based position.
  • Endstops of the blind detected by power draw from the Shelly2.5

Just for completeness my blind is based on the Somfy WT motor.
It is not perfect, but works good enough, altough any improvements or fixed you might think about are welcome :slight_smile: For example I don’t like the idea of the lambda used on the template_cover to check the position that quickly, I think it slows it down a bit, but a delay cannot be added because it looks like this lambda is evaluated in the main loop and if I add a delay, it freezes the whole device.

The code

substitutions:
  device_name: Living Room Cover
  
esphome:
  name: living_room_blind
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_pass
  fast_connect: true
  power_save_mode: none
  manual_ip:
    static_ip: 10.10.3.25
    gateway: 10.10.3.1
    subnet: 255.255.255.0


  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${device_name}
    password: !secret ap_pass


# Enable logging
logger:
  level: DEBUG
  esp8266_store_log_strings_in_flash: False

# Enable Home Assistant API
api:
  password: !secret api_pass

ota:
  password: !secret api_pass
  
i2c:
  sda: GPIO12
  scl: GPIO14

globals:
  - id: cover_moving #This is for the HW buttons to determine if a press should stop the blind or start moving it (this could probably be done without this var by reading id(time_cover).current_operation)
    type: bool
    initial_value: "0"

cover:
  - platform: time_based # Time based cover to track the position of the blind based on the time (unfortunatelly this doesn't support tilt)
    name: ${device_name} - Time Cover
    internal: True
    id: time_cover
    has_built_in_endstop: True # The controller in my blind automatically stops the motor in it's endpoints, this is set to True to be able to synchronize the zero position based on time with the actual position when the blind is either fully closed or open
                               # The power meter in the Shelly2.5 is then used to determine when the blind has stopped, sending a "cover.stop" action to this cover.
    open_action:
      - globals.set:
          id: cover_moving
          value: "true"
      - script.execute: detect_endpoint
      - switch.turn_off: motor_down
      - switch.turn_on: motor_up
    open_duration: 54sec # Set the correct time for your specific blind

    close_action:
      - globals.set:
          id: cover_moving
          value: "true"
      - script.execute: detect_endpoint
      - switch.turn_off: motor_up
      - switch.turn_on: motor_down
    close_duration: 53sec # Set the correct time for your specific blind

    stop_action:
      - globals.set:
          id: cover_moving
          value: "false"
      - script.stop: detect_endpoint
      - switch.turn_off: motor_up
      - switch.turn_off: motor_down
      
  - platform: template # Template cover which synchronizes position with the time_cover, but also supports tilt.
    name: ${device_name}
    id: template_cover
    lambda: |-
      if (id(template_cover).current_operation != id(time_cover).current_operation)
      {
        id(template_cover).current_operation = id(time_cover).current_operation;
      }
      return id(time_cover).position;
    has_position: true
    assumed_state: True
    open_action: 
      - cover.open: time_cover
    close_action:
      - cover.close: time_cover
    stop_action:
      - cover.stop: time_cover
    position_action:
      - cover.control:
          id: time_cover
          position: !lambda |-
            return pos;
    tilt_action:
      - lambda: |-
          if (tilt == 1) {
            auto call1 = id(time_cover).make_call();
            call1.set_command_open();
            call1.perform();
            delay(1000);
            auto call2 = id(time_cover).make_call();
            call2.set_command_stop();
            call2.perform();
            
          } else if (tilt == 0) {
            auto call1 = id(time_cover).make_call();
            call1.set_command_close();
            call1.perform();
            delay(1000);
            auto call2 = id(time_cover).make_call();
            call2.set_command_stop();
            call2.perform();
            
          } else {
            if (tilt > 0.5) {
              auto call1 = id(time_cover).make_call();
              call1.set_command_open();
              call1.perform();
              delay(1000);
              auto call2 = id(time_cover).make_call();
              call2.set_command_stop();
              call2.perform();
              delay(500);
              auto call3 = id(time_cover).make_call();
              call3.set_command_close();
              call3.perform();
              delay(1000 - (tilt*1000) + 50);
              auto call4 = id(time_cover).make_call();
              call4.set_command_stop();
              call4.perform();
              
            }
            if (tilt <= 0.5) {
              auto call1 = id(time_cover).make_call();
              call1.set_command_close();
              call1.perform();
              delay(1000);
              auto call2 = id(time_cover).make_call();
              call2.set_command_stop();
              call2.perform();
              delay(500);
              auto call3 = id(time_cover).make_call();
              call3.set_command_open();
              call3.perform();
              delay(tilt*1000 + 200);
              auto call4 = id(time_cover).make_call();
              call4.set_command_stop();
              call4.perform();
              
            }
          }
          
          
          id(template_cover).tilt = tilt;
          id(template_cover).publish_state();

script:
  - id: detect_endpoint # Used to detect the endpoint of the blind based on the power draw, the blind automatically stops in it's endpoints, this might not be needed, but I don't like the idea of leaving the relay on when "has_built_in_endstop" is used on the cover.
    then:
      - delay: 5sec # Delay to wait for the power sensor to read the value
      - wait_until:
          sensor.in_range:
            id: power_down
            below: 20
      - wait_until:
          sensor.in_range:
            id: power_up
            below: 20
            
      - cover.stop: template_cover
        
        
switch:
  - platform: gpio
    pin: 4
    name: ${device_name} - Motor UP
    id: motor_up
    interlock: [motor_down]
    interlock_wait_time: 100ms
    restore_mode: always off
  - platform: gpio 
    pin: 15
    name: ${device_name} - Motor DOWN
    id: motor_down
    interlock: [motor_up]
    interlock_wait_time: 100ms
    restore_mode: always off
    
binary_sensor:
  - platform: gpio  # Physical button on the wall to move the blind UP
    pin: 5
    name: ${device_name} - Button UP
    on_press:
      then:
        - if:
            condition:
              lambda: 'return !id(cover_moving);'
            then:
              - cover.open: template_cover

    on_click:
      - min_length: 1ms
        max_length: 999ms
        then:
          - cover.stop: template_cover

          
  - platform: gpio # Physical button on the wall to move the blind DOWN
    pin: 13
    name: ${device_name} - Button DOWN
    on_press:
      then:
        - if:
            condition:
              lambda: 'return !id(cover_moving);'
            then:
              - cover.close: template_cover
              
    on_click:
      - min_length: 1ms
        max_length: 999ms
        then:
          - cover.stop: template_cover
          
        
sensor:          

  - platform: ade7953
    voltage:
      name: ${device_name} - Voltage
    current_a:
      name: ${device_name} - Current Down
      internal: True
    current_b:
      name: ${device_name} - Current Up
      internal: True
    active_power_a:
      name: ${device_name} - Power Down
      id: power_down
    active_power_b:
      name: ${device_name} - Power Up
      id: power_up
      filters:
        - multiply: -1
    update_interval: 5s
2 Likes

Has there been some change in ESPHome API? Cause I’m not able to compile because of position_action: not being present on Template Cover with EspHome 1.14.5

You need to use v1.15.0-dev. For some reason they still haven’t added it to the v1.14.x.

Thanks, already found out and the config works nicely. I’ll try modifying it so it does not have to reset the tilt every time. Maybe just once a while if there are like five consecutive tilt commands. It would make it much nice just adding tilt to previous state. Will post the results back.

No problem, I’m glad it works. Well I proposed something like this for the time based cover, although there are some culprits, you would have to check the state of the previous tilt not only by the tilt, but the position aswell, for example: if your blind is currently at position 20 (almost closed) and tilt at 50 (45 degrees) and you set the position to 40, this will also “reset” the state of your tilt because by moving the motor up, it will first open the tilt and then start moving up, so you would have to tell the cover component to now update its state to tilt fully open, same goes the opposite direction, if you move the blind down, the tilt is now fully closed, so you would have to not only track previous tilt position but also if the position was changed or not and by how much (in rare cases if I just set the position from 50 to 49, this wouldn’t even fully close the tilt so you can’t just rely on basic logic - when moved down = set tilt to 0, when moved up = set tilt to 100, you would have to track if the time of the position movement was larger than tilt time from fully open to close and vice versa). This is why I added the tilt sync for every tilt change to make sure it is at the right angle.

1 Like

Hi,

Are you still using this solution or you make any improvement on it? Do you have this tipe of blinds?

It would be great to see the working on a video.

Hi, yes this is exactly the type I have and I still use the same solution. It has been working great so far so no need to change it. Tomorrow when it’s light outside I can shoot a video.

That would be awesome.

Found another solution too: A utility script for controlling venetian blinds with Shelly in Home Assistant | by Lukas Weiss | Medium

If you have a little time can you shoot a short video about working?

Hi madrian, did you get your external venetian blinds working with Home Assistant? I also bought the same blinds as you and was only informed 2 days ago, way into installation, from Shelly and the blind manufacturer that it would not work, even though they both told me it would months ago. :upside_down_face:

I hope you made it work and maybe I could get some assistance from you on this? Would really appreciate this. We can chat offline if easier.

Have a nice weekend.

Hi,
have you tried my solution posted as the original post? You just flash the Shelly 2.5 with Esphome and use the provided script. I have the same external blinds as madrian and this solution has been working fine for me for more than two years. It supports both closing and opening as well as tilt.

Hi, thanks for your response. I am using the Shelly Pro 2PM and will wire it using ethernet cable to the Home Assistant server and have the physical blind switch wires to it to manually be able to control the blind. So I trying to find out how to resolve this issue. :slight_smile: Do you think your approach will also work for the Shelly Pro 2PM?

I think it should. If the Pro 2PM has two inputs for switches and two outputs to connect each direction of the motor, than you should be able to do that. You will probably just have to change the GPIO pins in my script for the specific ones for the 2PM

That be great if possible. I am trying to understand how this all will work as I am not yet experienced enough with this kind of setup. Would it be possible to chat with you offline? I am happy to compensate for your time.

Now that I have some more time in the evening I checked the details of the Pro 2PM and it is a bit more complicated. The Shelly 2.5 I am using uses just simple GPIO pins as input and output, which you can easily read and control. However the Pro 2PM uses the SN74HC595B shift register, this basically is a chip to expand your GPIOs (with 3 GPIOs on the main chip, you can get up to 8 more GPIOs), but it needs to be controlled differently. I found some scattered info, there is currently no GPIO guide for the Pro 2PM neither for ESPHome, nor for Tasmota, but the pinout seems to be the same as for Shelly Pro 1.
This is the current pinout Shelly Pro 2PM DIN Relay (PRO2PM) Configuration for Tasmota . However it seems that it’s not complete. You can see that you can read the switches with GPIO38 and GPIO39, this is where you would get info if the button was pressed, so you would modify my script to this:

binary_sensor:
  - platform: gpio  # Physical button on the wall to move the blind UP
    pin: GPIO38
    name: ${device_name} - Button UP
    on_press:
      then:
        - if:
            condition:
              lambda: 'return !id(cover_moving);'
            then:
              - cover.open: template_cover

    on_click:
      - min_length: 1ms
        max_length: 999ms
        then:
          - cover.stop: template_cover

          
  - platform: gpio # Physical button on the wall to move the blind DOWN
    pin: GPIO39
    name: ${device_name} - Button DOWN
    on_press:
      then:
        - if:
            condition:
              lambda: 'return !id(cover_moving);'
            then:
              - cover.close: template_cover
              
    on_click:
      - min_length: 1ms
        max_length: 999ms
        then:
          - cover.stop: template_cover

Now to the hard part, to be able to control the motors, you need to configure the SN74HC595 expander. If the Pro 1 is the same as 2PM (here is the Pro 1 pinout: Shelly Pro 1 | ESPHome-Devices , you should be able to do that with this config:

sn74hc595:
  - id: 'sn74hc595_hub'
    data_pin: GPIO13
    clock_pin: GPIO14
    latch_pin: GPIO4

Now you would modify my script again, the switch: part to something like this:

switch:
  - platform: gpio
    pin:
      sn74hc595: sn74hc595_hub
      # Use pin number 0  - possible values 0-7 (needs to be tested which one works)
      number: 0
      inverted: false
    name: ${device_name} - Motor UP
    id: motor_up
    interlock: [motor_down]
    interlock_wait_time: 100ms
    restore_mode: always off
  - platform: gpio 
    pin:
      sn74hc595: sn74hc595_hub
      # Use pin number 1 - possible values 0-7 (needs to be tested which one works)
      number: 1
      inverted: false
    name: ${device_name} - Motor DOWN
    id: motor_down
    interlock: [motor_up]
    interlock_wait_time: 100ms
    restore_mode: always off

However I am not sure if the pin number is actually 0 and 1, you will have to do some tests, might be 0-7.

So this is basic settings for reading buttons and controlling motors. Now there is one more aspect which is the LAN, since this is not available by default in ESPHome, which mainly works with WIFI.
To set up LAN you would need something like:

ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO0_IN
  phy_addr: 0

  # Optional manual IP
  manual_ip:
    static_ip: 10.0.0.42
    gateway: 10.0.0.1
    subnet: 255.255.255.0

However I would first use Wifi, which is known to work, try everthing, adding the GPIO expander and see if you can control the motors and read the switches, if this works I would then move to the LAN part.

As I said, there is no guarantee that this will work. I don’t have the Pro 2PM on hand so I can’t test it.
Of course first small obstacle might be that you first need to flash the ESPHome firmware, which needs to be done with the FTDI adapter which you connect to the programming pins on the side of the Pro 2PM, then you have to figure out if these settings work or not.

Another solution would be to simply use this for something else and buy the Shelly 2.5, put it in the box underneath your blind switches and connect everything there. This script is tested for Shelly 2.5, so all you would have to do is flash the ESPHome with FTDI, upload this script and change the time it takes to open/close the blind and change the time it takes to tilt open/close.

If you want to chat about it directly, we can do that, however I am not sure if I will be able to provide any more info without actually testing it on the device. Also my time lately is a bit limited since I have a newborn baby so I had to slightly set aside my HA projects.

Thanks so much and congratulations! I know how this feels. :slight_smile: I really appreciate the time you spend assisting me on the issue. I would be happy to talk with you offline, I also are happy to send you a Pro 2PM to investigate further. Is there a way I can PM you on this forum, or how could I contact you without sharing your details here? Looking forward to your response!

I am using variation of this code with my blinds, but I have slight issue with correct reporting of closed state to home assistant.
For example when blinds are set to be closed, in closed position it’s still reporting moving state and only after another action it’s reported correctly as closed.
Do you have an idea what should be changed, so that closed state is reported correctly?
I have Z90 blinds with Somfy WT motor controlled with Shelly Plus 2PM.
Thanks.

It is there any update for Shelly 2PM plus?

This is more up to date. Youl will need to modify the GPIOs. Blakadder has a list I think.

I use a heavily modified Jerry script at the moment for Shelly 2.5 for Swiss style (Physical Interlocked Up-Down open-momentary-closed) buttons that allows a hard pushed(closed) down button override to stop an automation that makes the blinds open at dawn. I only have one Shelly 2PM Plus deployed and it is stock firmware for a simple roller outside blind for a terrace.

Two weeks after easter I will roll out the bruxy70 with my override feature on all my Shelly 2.5.