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