esphome / feature-requests

ESPHome Feature Request Tracker
https://esphome.io/
406 stars 26 forks source link

Time-based position calculation for Roller Shutter Blinds #739

Open santibur06 opened 4 years ago

santibur06 commented 4 years ago

Describe the problem you have/What new integration you would like I'm using the time-based cover component to control my window's Roller Shutter Blinds and it works perfectly with the exception of the position calculation which seems to be a bit more complex to calculate for roller shutter blinds.

So right now the component calculates position like this:

Pretty simple, at least that's how I understand it works.

So, position should also mean how far up the cover is from the bottom of the window, so 50% would mean my window is half covered.

That's where this component is not compatible with roller shutter blinds... Say the cover is all the way down, motor has stopped rotating on its own cause it reached the bottom. Now I issue the up command and it starts rotating, it is moving up, but segment by segment, and it takes it like 5 seconds (in my case) to actually start uncovering the window. So that's 5 seconds I lose but the component is calculating the position, so by the time my window is 50% uncovered, the component thinks the cover is at 70% or more.

So yeah, the actual position of the cover may be 70% but it is not covering 30% of my window, it's actually covering 50%.

I would really like to have a way tell the component how long it takes the cover from position 0% to actually start moving up and uncovering my window.

It would work something like this:

0% - Cover all the way down 10% - Cover has rolled up a bit and light can pass by it, but it's still covering the entire window. 11% - Cover is not covering the entire window anymore, it actually started moving up. 50% - Cover is more or less covering 50% of my window. 100% - Cover is all the way up.

Please describe your use case for this integration and alternatives you've tried: This would not only be useful for Roller Shutters but also for some garage covers that show the same behavior.

Additional context I'm using Sonoff T1, 3 gang to control the shutters and aside from the issue mentioned above, it works excellent.

glmnet commented 4 years ago

What about a roller_shutter_delay ? The thing is, when cover goes down 100% it will start counting this roller shutter delay, we can accumulate this in the tilt state

Is the roller stopping by itself? or is ESPHome commanding it to stop? can you share your yaml?

When cover goes up, if it is shuttered then we un shuttered it first, consume the shutter delay time and then we start updating the cover position.

Also related to https://github.com/esphome/feature-requests/issues/664 but not exactly the same

santibur06 commented 4 years ago

roller has built-in end stops so it stops by itself.

so, based on what you propose, position would stay at 0% while the cover is going up during that roller_shutter_delay right?

what do you think about using 10% of movement for that shutter delay?

0-10%: Cover goes up, consuming the _roller_shutterdelay. 11-100%: Cover goes up consuming the _(openduration - roller shutter delay)

That way you would still see position being updated whenever the cover is moving and you would also be able to set the cover to 10% and let some light come in while the window is still 100% covered.

Don't know if I'm explaining myself correctly..

here's my full yaml:

substitutions:
  devicename: "8282400e6d"
  upper_devicename: "8282400e6d"

  cover_name: "8282400e6d"

esphome:
  name: $devicename
  platform: ESP8266
  board: esp01_1m

wifi:
  networks:
    - ssid: !secret wifi_ssid
      password: !secret wifi_password
    - ssid: !secret wifi2_ssid
      password: !secret wifi2_password
    - ssid: !secret wifi3_ssid
      password: !secret wifi3_password
  reboot_timeout: 0s
  # fast_connect: true

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: $upper_devicename
    password: !secret wifi_password

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

# enabling web server
# web_server:
#   port: 80

status_led:
  pin:
    number: GPIO13
    inverted: yes

binary_sensor:
  - platform: gpio
    id: button_open
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: True
    on_press:
      - cover.open: my_cover

  - platform: gpio
    id: button_stop
    pin:
      number: GPIO09
      mode: INPUT_PULLUP
      inverted: True
    on_press:
      - cover.stop: my_cover
    on_multi_click:
      - timing:
          - ON for at most 0.5s
          - OFF for at most 0.5s
          - ON for at most 0.5s
          - OFF for at most 0.5s
          - ON for at most 0.5s
          - OFF for at most 0.5s
          - ON for at most 0.5s
          - OFF for at least 0.2s
        then:
          - switch.turn_on: sonoff_restart

  - platform: gpio
    id: button_close
    pin:
      number: GPIO10
      mode: INPUT_PULLUP
      inverted: True
    on_press:
      - cover.close: my_cover

switch:
  - platform: restart
    name: $upper_devicename Reboot
    id: sonoff_restart

  - platform: gpio
    pin: GPIO12
    interlock: &interlock [open_cover, close_cover]
    restore_mode: always off
    id: open_cover
  - platform: gpio
    pin: GPIO4
    interlock: *interlock
    restore_mode: always off
    id: close_cover

cover:
  - platform: time_based
    name: $cover_name
    id: my_cover
    open_action:
      - switch.turn_on: open_cover
    open_duration: 10s
    close_action:
      - switch.turn_on: close_cover
    close_duration: 10s
    stop_action:
      - switch.turn_off: open_cover
      - switch.turn_off: close_cover
glmnet commented 4 years ago

And what is the difference between that and what you already have?

santibur06 commented 4 years ago

Right now if I set the position to 50% it will unroll (the delay) and then go up but since already consumed the delay it stops at around 30% kf the window

santibur06 commented 4 years ago

What I'm saying is that while we unshutter it we update position but from 0 to 10% max so we can also control the unshuttering from HA. Right now it will say like 20% when it finishes unshuttering. By maxing it at 10% (or even 5%) we'll get the cover stop at more or less half the window and ir will say 50%

santibur06 commented 4 years ago

Hi @glmnet, just wondering if you had time to check this out.

Thanks!

alexbarcelo commented 4 years ago

Oh I am not the only one having those issues for roller covers!

Let me throw my idea here, just to see if it makes sense for this context:

ESPhome has a powerful system of filters. Right now, the code for filters is focused on sensors and using that outside the sensors seems to be hackish (I tried and half-succeeded with workarounds). However, it makes sense --in my head, at least-- to abstract those filter things into a C++ class that can be used from other components. I opened the #755 to discuss that specific refactoring, but the specifics of the consequences and potential for the cover component is more on-topic here.

To fix some ideas, let me put an example of how I would imagine my proposal. Let's start with the yaml definition:

cover:
  - platform: time_based
    name: "My Fancy Cover"
    #(... open, close action and durations ...)
    open_filters: &common_filters
      - calibrate_polynomial:
          degree: 2
          datapoints:
            - 0.0 -> 0.0
            - 0.1 -> 0.15
            - 0.5 -> 0.75
            - 1.0 -> 1.0
    close_filters: *common_filters
    # conceivable case: open and close need different filters

Which would translate to: "when 10% of time has passed, the cover is opened 15%. When 50% time has passed, the cover is opened 75%".

Calibrate polynomial seems the most useful filter right now. I have an implementation of a calibrate_spline filter, which uses spline fitting (piecewise polynomial functions). It seems to be working, I will deploy that in my ESP cover control and polish it.

At C++ level, I imagine reusing the same code geneartion that is instantiating filters but instead of instantiating them into the filter_list_ attribute of the sensor component, instantiate a FilterChain instance which will have the filter_list_ and its appropriate methods (add_filters, set_filters, a new transform_value, etc.). That FilterChain will be used also from time based covers that are using the open_filters and close_filters configuration entries.

Does it make sense? I know that it seems overkill, but note that roller_shutter_delay is not a silver-bullet as the linear speed is not constant: typically those motor have constant rotation speed, which means that the linear speed is greater when there is more cover rolled (because radius increases) and in my experiments it was quite noticeable.

santibur06 commented 3 years ago

hey guys @glmnet @alexbarcelo, just wondering if you were able to take a look at this, I'm struggling with my shutters, position is totally off and the feature is useless in my setup. Do you guys know of any workaround I could put in place to fix this in the meantime? I'm sorry for asking but I would implement this code fix myself if I had any idea on where to start, it's been a while since I don't touch code other than yaml... thanks!

muxa commented 3 years ago

@alexbarcelo I like your idea of using filters to create a custom "curve" for time/position relation ship. I'm in the same boat as @santibur06 - my blinds take a few seconds to start moving after issuing a command. Have you been successful in implementing your idea?

I think being able to specify this filter for opening and closing independently would be useful. In my case when the blinds are rotating to close there's a spot where they are kind of stuck for a second or so, and then rapidly move to the right position, so it would be great to be able to account for that.

muxa commented 3 years ago

Also to not depend on the refactoring required (#755) for implementing your idea, I was thinking to simply build the non-liner config into the time_based cover. e.g something like:


cover:
  - platform: time_based
    name: "Balcony Louvres"

    open_action:
      - switch.turn_on: btn_open
    open_duration: 11s
    open_curve:
            - 0.0 -> 0.0
            - 0.4 -> 0.0
            - 0.9 -> 1.0
            - 1.0 -> 1.0

    close_action:
      - switch.turn_on: btn_close
    close_duration: 10s
    close_curve:
            - 0.0 -> 0.0
            - 0.1 -> 0.0
            - 0.3 -> 0.3
            - 0.4 -> 0.3
            - 0.42 -> 0.4
            - 0.8 -> 1.0
            - 1.0 -> 1.0

    stop_action:
      - switch.turn_on: btn_stop

Alternatively the curve can be configured using time (to make configuration more intuitive):

    close_duration: 10s
    close_curve:
            # 0 -> 0.0 implied 
            - 1s -> 0.0
            - 3s -> 0.3
            - 4s -> 0.3
            - 4.2s -> 0.4
            - 8s -> 1.0
            # 10s -> 1.0 implied

@alexbarcelo what do you think?

alexbarcelo commented 3 years ago

@muxa "to simply build the non-liner config into the time_based cover" means to have certain redundancy of code, as the filters (as they are now) are not meant to be used outside the list of filters in a sensor.

Either you use those filters from outside a sensor --which will be troublesome, as the includes at C level are only granted when there are sensor components, thus making things a little bit tricky, at least in my limited knowledge of the code generation. And filters are instantiated by sensor and within a list and there's some implicit constraints here and there.

Or you add more code in the cover component, making it a bit redundant and repeating yourself --meaning more documentation and more code to maintain for two things that are very similar and behave in a very similar fashion.

I may have missed something. I was waiting for any opinion and directions from members of ESPHome, before attempting a clean PR for that (I have some hackish ugly fragile workaround for my use case, but I need to start from scratch if I want to share that). Maybe some of my assumptions are bogus? Maybe I am being overzealous on DRY matters?

muxa commented 3 years ago

@alexbarcelo I was coming with my suggestion from the premise of keeping changes isolated, but at the expense of potential code duplication.

@OttoWinter can you please comment on a preferred approach?

OttoWinter commented 3 years ago

Code duplication for these components is fine with me. We likely won't have to touch these code areas much anyway (and if so, it's not hard to update).

The only major thing to think about here is user experience. If there are 10 different cover platforms doing essentially the same thing, it can be confusing for the user to choose the right one (a lot of this also comes down to good docs).

Why use sensor filters though here? The backend already has support for tilt (so the backend can report how far the individual elements are tilted). Can't we just tell the device "this is how long it takes to tilt the elements", then:

alexbarcelo commented 3 years ago

Ok, that makes sense. Let me clean up my code and I'll be back with a PR in a few days.

I am not sure I am following you regarding the tilt thing. My covers don't physically "tilt" (not as I understand the word, non-native speaker here, maybe I am misunderstanding things). And what you are describing doesn't fit my problems: "varying speed covers" (typical roller shutter ones). Maybe it would be enough for some use cases, but I will argue that my approach solves both my use case and this other use case. Hope it makes sense.

Regarding the user experience: I will do the PR with changes on the timed based cover, as it is something that only makes sense for time based covers and is completely optional (no breaking changes). So I hope that this would not be confusing to the user --they will look into the time based cover and decide which features do they want.

glmnet commented 3 years ago

leaving out of discussion tilt at all... the time -> position function makes sense on it's own, e.g. for covers which are rolling, assume a constant rotation speed of motor, the more it rolled up, the faster the cover actually goes up as the roll becomes thicker. This is just too much of details for myself, and I'm just saying that it makes sense, I wouldn't care setting up this for myself let alone implementing it.

As for the position vs time and the limits, e.g. totally open = 100%? Imagine your cover opens fully but the motor keeps winding it for a while even though you don't see the window more open, then you should set your time to the time it is fully open, not the time the motor stops, this will have the effect of the position being reported correctly at 100%. The same will be when closing the cover...

Lets imagine a cover which closes fully at 2:00 and then 15 more seconds to shut light down completely, I'd setup the close time so the position 0% = CLOSED is reached in 2:00 ... then the motor will continue shutting light down for additional 15 seconds... then this time will be consumed in the tilt state (we need to give 15 seconds time to tilt).

This is the same I wrote in the second post here, mostly. I see Otto says first decrease tilt, then decrease position, this might make sense for one type of cover, where first decreasing position and then tilt might make sense for other types of covers. I believe both can be supported without much difference.

For the decrease position then tilt, these covers apply: https://www.youtube.com/watch?v=IT06zwM87Xw For the tilt then position I'm not really sure

micronen commented 3 years ago

@alexbarcelo Have you implemented this PR yet? I'm dealing with the same issue, and can look into it, if you like.

hmahnken commented 3 years ago

Actually the idea of a non-linear time-based cover would be very useful for controlling motorized valves, since opening the valve is almost linear but closing it is pretty far from linear behaviour. What is your progress on this issue? :-)

alexbarcelo commented 3 years ago

I have a somewhat usable proof of concept of non-linear (a linear piecewise function) actuation. It works non-linearly, however, I have messed things and there is some inverted evaluation or something fishy. I had my hands full with my day job and some other urgent-er DIY projects, so my implementation is on "marination".

I will try to allocate some free time to clean up my code, fix the fishy math and do a PR. Probably I would complete that in a week or so :crossed_fingers:

chatziko commented 2 years ago

+1 on this feature, it would be quite useful.

gromar88 commented 2 years ago

Any luck with the code @alexbarcelo ? I'm having the same problem with controlling shutter on specific level.

sim6680 commented 2 years ago

+1 on this feature also for me, it would be very useful indeed. thanks a lot!!

alexbarcelo commented 2 years ago

I added some discussion on my just-pushed PR, but maybe it is better to have it here given that the discussion has been already brewing for a while:

I have no idea how to address the restore mechanism. My first approach is to have position the real position of the cover and use a time_position to track the time. However, the component tracks the time_position, but this attribute is not stored in the restore structure, so it cannot be set. This means that the restore does not work as intended.

The decision to use position and time_position also do not sit well with the idea of the time_based cover that open and close can have diferent durations. Maybe my approach is wrong? Maybe I have to throw this and start from scratch? For my scenario of a "time symmetric" cover it works good enough, but that may not be flexible enough.

I feel like I was trying to attain a "minimal changes approach" and ended up with a botched incomplete mess.

lednicazar commented 2 years ago

What is the status of the "calibration" implementation?? that will be great!!!

Bascht74 commented 2 years ago

Yes, I would like to see somthing like this for my german shutters...

luca-iodice commented 1 year ago

could be cool to port this feature as it is implemented in tasmota. Unfortunately I can't convert shutters and buttons to esphome because of the poor implementation that they have in comparison to tasmota.

alexbarcelo commented 1 year ago

@luca-iodice maybe not the users here are familiar with Tasmota (I myself have never flashed nor used it). Can you elaborate on how does Tasmota handle covers? Or point to a documentation / source code that discusses it?

Bascht74 commented 1 year ago

https://tasmota.github.io/docs/Blinds-and-Shutters/

Bascht74 commented 1 year ago

It is really nice, you can different types of shutters and - most important - a calibration. Old video here: https://www.youtube.com/watch?v=Z-grrvnu2bU

I have "normal" shutters, but I need at least a calibration as possible via ShutterSetHalfway.