New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cover single button control #1268
Comments
There are actually 2 parts to it:
Controlling relay from the cover is probably a good use case for another cover:
- platform: single_button
control_output: garage_opener_relay
control_delay: 1500ms # for when need to stop and change direction
closed_endstop: closed_endstop
close_timeout: 16s
open_endstop: ... # optional
open_timeout: 16s Controlling the cover from a switch is more generic, and can be used as an additional method to control any cover, e.g: switch:
- platform: cover_remote
control_switch: garage_opener_switch
cover: garage_door
cover:
- platform: template
id: garage_door
... |
This is definitely something that needs to be simplified for the end-users. I agree that a new Something I would like to add to your setup an additional |
I would also recommend using config variables that are already in use by other components, such as, but not limited to:
|
@NeilDuToit92 thanks for you input and pointing me to existing config names, better be consistent with existing naming.
The garage door opener that I have has the obstruction bean built-in. So if garage door is being closed and there's something in the way, it will stop the door. And as per the state diagram above it would time out, so the Is this what you had in mind? Or did you mean actually connecting the beam to the ESP? |
I meant connecting the beam directly to ESP - Where I live almost none of the door motors/controllers has this functionality built in. Our motors only stop if there is physically something stopping the door and there is too much strain on the motor - and even this isn't tuned correctly by a lot of installers. Not having a beam is fine when you can see the door you are closing when you are in radio range, but the moment you add remote control to it, it becomes a necessity to be able to add a beam in case something is in the way. Edit: A lot of our sliding gates have a beam built-in, but not the doors. |
Thinking about possible scenarios of obstruction with an additional obstruction sensor:
Scenario 1 is possible with an automation on the obstruction sensor, e.g: binary_sensor:
platform: gpio
pin: D1
on_press:
cover.stop: my_cover Scenario 2 however will require the proposed Here's an updated proposed schema taking into consideration suggested renames and obstruction sensor: cover:
- platform: single_button
output: garage_opener_relay # relay that controls garage door movement
off_delay: 200ms # how long to keep the relay engaged
binary_sensor: button # external button for controlling the garage door with
reverse_delay: 1500ms # for when need to stop and change direction
closed_endstop: closed_endstop
close_duration: 16s # used for timeout
open_endstop: ... # optional
open_duration: 16s # used for timeout
obstruction_sensor: ... # optional Alternatively can implement obstruction detection in a more generic way within binary_sensor:
platform: gpio
pin: D1
on_press:
lambda: id(my_cover).obstructed(true);
on_release:
lambda: id(my_cover).obstructed(false); |
I created my own custom garage door component (not built to be able to use as an external component although I have been considering changing that). One thing to note that would need to be accounted for is how the opener responds to button presses while in motion. The one door I have this currently built for has the following state progression: |
I have another use case for a gate which has the following state progression: opening -> closing -> opening I.e pressing the button changes direction immediately (no stopping). So there are 3 possible use cases (including @nuttytree’s), which means the single_button needs to be flexible enough to configure different state transitions. One way to solve it is to create an abstraction for the state machine and have different implementations of it so that the the right one can be chosen for the single_button in yaml. I still don’t have a clear idea how to best design this. Will need to think about this some more. |
I'm thinking that first step is to create generic code for a state machine, so that it can be easily configured (in C code). I'm thinking something along these lines: struct StateTransition
{
BYTE from_state;
BYTE trigger;
BYTE to_state;
};
class StateMachine
{
public:
StateMachine(const StateTransition transitions[], int transitionCount, BYTE initialState = 0);
BYTE current_state();
bool is_transition_allowed(BYTE trigger);
BYTE transition(BYTE trigger);
private:
const StateTransition* _transitions;
int _transitionCount;
BYTE _currentState;
}; And then the actual configuration for the state machine can be done like this: const extern BYTE GATE_STATE_UNKNOWN = 0;
const extern BYTE GATE_STATE_CLOSED = 1;
const extern BYTE GATE_STATE_OPENING = 2;
const extern BYTE GATE_STATE_OPEN = 3;
const extern BYTE GATE_STATE_CLOSING = 5;
const extern BYTE GATE_STATE_WAITING_HOLD = 6;
const extern BYTE GATE_STATE_OPEN_HOLD = 7;
const extern BYTE GATE_TRIGGER_UNKNOWN = 0;
const extern BYTE GATE_TRIGGER_PRESS = 1;
const extern BYTE GATE_TRIGGER_TIMEOUT = 2;
static const StateTransition gate_state_transitions[] =
{
// from_state trigger to_state
{ GATE_STATE_CLOSED, GATE_TRIGGER_PRESS, GATE_STATE_OPENING },
{ GATE_STATE_OPENING, GATE_TRIGGER_PRESS, GATE_STATE_CLOSING },
{ GATE_STATE_OPENING, GATE_TRIGGER_TIMEOUT, GATE_STATE_OPEN },
{ GATE_STATE_OPEN, GATE_TRIGGER_PRESS, GATE_STATE_CLOSING },
{ GATE_STATE_OPEN, GATE_TRIGGER_TIMEOUT, GATE_STATE_WAITING_HOLD },
{ GATE_STATE_CLOSING, GATE_TRIGGER_TIMEOUT, GATE_STATE_CLOSED },
{ GATE_STATE_CLOSING, GATE_TRIGGER_PRESS, GATE_STATE_OPENING },
{ GATE_STATE_WAITING_HOLD, GATE_TRIGGER_PRESS, GATE_STATE_OPEN_HOLD },
{ GATE_STATE_WAITING_HOLD, GATE_TRIGGER_TIMEOUT, GATE_STATE_OPEN },
{ GATE_STATE_OPEN_HOLD, GATE_TRIGGER_PRESS, GATE_STATE_CLOSING },
};
auto stateMachine = new StateMachine(gate_state_transitions, sizeof(gate_state_transitions)/sizeof(StateTransition), GATE_STATE_CLOSED); |
I know such abstractions don't exist, but sketching this here in case it's actually possible: state_machine:
name_prefix: GATE_
states:
- CLOSED
- OPENING
- OPEN
- CLOSING
- WAITING_HOLD
- OPEN_HOLD
triggers:
- PRESS
- TIMEOUT
transitions:
- from: CLOSED
trigger: PRESS
to: OPENING
- from: OPENING
trigger: PRESS
to: CLOSING
- from: OPENING
trigger: TIMEOUT
to: OPEN
...
on_transition:
then:
- lambda: |-
// x.from_stage, x.trigger, x.to_state
switch (x.to_state) {
case GATE_STATE_OPEN:
id(gate_cover).current_operation = esphome::cover::COVER_OPERATION_IDLE;
id(gate_cover).position = esphome::cover::COVER_OPEN;
id(gate_cover).publish_state();
break;
case GATE_STATE_OPENING:
id(gate_cover).current_operation = esphome::cover::COVER_OPERATION_OPENING;
id(gate_cover).position = esphome::cover::COVER_OPEN;
id(gate_cover).publish_state();
break;
...
if (x.to_state == GATE_TRIGGER_TIMEOUT) {
// TODO: start timeout
} And use it by something like this binary_sensor:
...
on_press:
then:
- state_machine.trigger: PRESS |
Could use the new select:
- platform: state_machine
id: sm
internal: true
options:
- CLOSED
- OPENING
- OPEN
- CLOSING
triggers:
- PRESS
- TIMEOUT
transitions:
- from: CLOSED
trigger: PRESS
to: OPENING
- ..
initial_option: CLOSED |
The timeout trigger can be built-in, so can configure transitions that happen automatically after certain timeout, e.g: transitions:
- from: OPENING
trigger: TIMEOUT
to: OPEN
timeout: 8s |
Then can have a specialised cover:
- platform: state_machine
state_machine_select: sm
states: # maps state machine states to the cover states
closed: CLOSED
opening: OPENING
open: OPEN
closing: CLOSING And to drive the relay from the cover define additional triggers on the state machine: select:
- platform: state_machine
...
triggers:
- PRESS
- TIMEOUT
- OPEN
- CLOSE
- STOP And configure cover to fire necessary triggers: cover:
- platform: state_machine
...
open_trigger: OPEN
stop_trigger: STOP
close_trigger: CLOSE Or could just use a |
When configuring a state machine, should be able to specify optional actions to call on transitions, e.g: transitions:
- from: CLOSED
trigger: OPEN # fired when opening the cover from HA
to: OPENING
action:
then:
- output.turn_on: relay # this will "press" the button to physically control the door/gate
- delay: 8s # with this, we don't really need a separate `timeout` attribute on each trigger
- lambda: 'id(sm).transition("TRIGGER_TIMEOUT");' Callback for when transitioned to a new state is already in |
I managed to implement a state machine text sensor. I wanted to create a general purpose state machine which can be used to model logic of different single button covers and yet allows using it for other purposes too. Here's a very basic example: text_sensor:
- platform: state_machine
initial_state: CLOSED
states:
- CLOSED
- name: OPENING
on_enter:
- delay: 3s
- state_machine.transition: TIMEOUT
- name: CLOSING
on_enter:
- delay: 3s
- state_machine.transition: TIMEOUT
- CLOSING
inputs:
- name: PRESS
transitions:
- CLOSED -> OPENING
- OPENING -> CLOSING
- CLOSING -> OPENING
- OPEN -> CLOSING
- name: TIMEOUT
transitions:
- OPENING -> OPEN
- CLOSING -> CLOSED
action:
- logger.log: "Timeout reached" The The The Also sometimes you want to have a conditional action in a state. For that I've added - name: OPEN
on_enter:
- if:
condition:
state_machine.transition:
from: OPENING
then:
- logger.log: "Will be able to HOLD in 6s"
- delay: 6s
- state_machine.transition: TIMEOUT
- name: WAITING_HOLD
on_enter:
- logger.log: "Waiting for HOLD"
- delay: 4s
- state_machine.transition: TIMEOUT
- name: OPEN_HOLD
inputs:
- name: PRESS
transitions:
...
- WAITING_HOLD -> OPEN_HOLD
- OPEN_HOLD -> CLOSING
- name: TIMEOUT
transitions:
...
- OPEN -> WAITING_HOLD
- WAITING_HOLD -> OPEN In the next few days I'll get the code cleanup up and ready for use as an external component. |
Uploaded a working version: https://github.com/muxa/esphome-state-machine/tree/dev Usage: - source:
type: git
url: https://github.com/muxa/esphome-state-machine
ref: dev |
Closing this issue as now with the above mentioned state machine component I can model any logic of gates and door controllers with relative ease. |
@muxa do you have an example of your component with the single button cover control? |
@Tuckie yes, here are some examples:
|
Thank you so much! I've ordered some M5stack relay modules that I plan on using. |
Hi @muxa, thanks for the single button and contact sensor code . .. . it's blowing my mind through ahahaa. I would like some help with a very similar single button sketch if possible. |
@Douganatornz can you describe your garage door control setup in full? I.e. do you have a secondary control? (e.g. original garage remote), do you have a contact sensor to detect when the garage door is fully closed or open?, etc. |
@muxa What I would like to do using this new ESPHome code is to show states CLOSED, OPENING, OPEN, CLOSING. Thanks |
@Douganatornz I'd like to transfer this discussion to https://github.com/muxa/esphome-state-machine/discussions |
Describe the problem you have/What new integration you would like
I have a garage door that's controlled with a single button (a typical garage door control): press to start/stop, press again to move in opposite direction). The button is on a ESP8266 WiFi wall switch with a relay that shorts the original garage door button to control it remotely. This part was easy to connect.
I wanted to have a reliable way to control the
cover
with the single button as well as from Home Assistant, so that the state of the cover is always correct.Please describe your use case for this integration and alternatives you've tried:
I have implemented this using a
cover
withtemplate
platform with quite a bit of C code that implements a state machine. I've also added a contact sensor to detect when the garage door is fully closed.Here's the state machine diagram:
Here is the configuration and the code that I've got so far: https://gist.github.com/muxa/9d0b1be8ea7c3daed5a0d4f0db058e4f
It's working pretty well.
Additional context
What I'd like to do is to create a component to encapsulate this functionality (this will be a learning curve for me), publish it as an external component and eventually perhaps get it added the ESPHome core.
However I'd like to get some feedback as to what approach to take here. I'm thinking of a new "meta" cover platform, which controls another cover, e.g:
Would love some feedback and guidance for architecting this.
The text was updated successfully, but these errors were encountered: