Skip to content
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

Feature request: Option for @state_trigger to only trigger once #89

Closed
tchef69 opened this issue Nov 11, 2020 · 19 comments
Closed

Feature request: Option for @state_trigger to only trigger once #89

tchef69 opened this issue Nov 11, 2020 · 19 comments

Comments

@tchef69
Copy link

tchef69 commented Nov 11, 2020

I have an automation which turns on/off some exterior lights with a lux-sensor as input.
But the automation gets triggered every time the lux value changes and is above 50.
I have not found an option to make it only trigger once, and then not trig again until after the trigger-statement has been false.

@state_trigger( "int(sensor.lux_outside_the_garage) > 50" )
def automate_exterior_lights_off_in_morning():
    if pyscript.ext_light_off_done == 'off' and input_boolean.exterior_lights_wanted_state == 'on':
        input_boolean.exterior_lights_wanted_state = 'off'
        pyscript.ext_light_off_done = 'on'

One option is of course to add an extra condition , like this:

@state_trigger( "int(sensor.lux_outside_the_garage) > 50 and int(sensor.lux_outside_the_garage.old) <= 50")
@dlashua
Copy link
Contributor

dlashua commented Nov 11, 2020

Another option that allows you to intentionally set when the function will trigger, and when it will be allowed to trigger again.

@state_trigger( "int(sensor.lux_outside_the_garage) > 50" )
def automate_exterior_lights_off_in_morning():
    task.unique('exterior_lights', kill_me=True)
    if pyscript.ext_light_off_done == 'off' and input_boolean.exterior_lights_wanted_state == 'on':
        input_boolean.exterior_lights_wanted_state = 'off'
        pyscript.ext_light_off_done = 'on'
    task.wait_until(state_trigger="int(sensor.lux_outside_the_garage) < 50")

@dlashua
Copy link
Contributor

dlashua commented Nov 11, 2020

A third option: create a binary sensor template in Home Assistant for lux > 50 and use that in your state_trigger.

Or a fourth option, use a global variable as a binary sensor template of sorts and do it all in pyscript... (untested)...

TRIGGER_STATE = None

@time_trigger("startup")
@state_trigger("sensor.lux_outside_the_garage")
def automate_exterior_lights_off_in_morning(value=None):
    global TRIGGER_STATE

    if int(value) > 50:
        if TRIGGER_STATE != 'on':
            if pyscript.ext_light_off_done == 'off' and input_boolean.exterior_lights_wanted_state == 'on':
                input_boolean.exterior_lights_wanted_state = 'off'
                pyscript.ext_light_off_done = 'on'
        TRIGGER_STATE = 'on'
    else:
        TRIGGER_STATE = 'off'

@tchef69
Copy link
Author

tchef69 commented Nov 12, 2020

Thanks for your good solution. The last one was more what I had in mind.
Would it be possible to build in your last solution as an option in the trigger, to keep the automation clean ? Like:

@state_trigger( "int(sensor.lux_outside_the_garage) > 50", armOnFalse=true)
def automate_exterior_lights_off_in_morning():
    ...

I think that this kind of problem only occurs when you have a state that's not binary like lux and temperature.

@dlashua
Copy link
Contributor

dlashua commented Nov 12, 2020

@craigbarratt can speak to weather a feature like this should be in PyScript. I agree that it's useful. It can even affect binary states when there is an "or" in the condition.

In your particular case, it seems you may already be keeping track of this and it could be handled with a state_active()?

@state_trigger( "int(sensor.lux_outside_the_garage) > 50" )
@state_active("pyscript.ext_light_off_done == 'off' and input_boolean.exterior_lights_wanted_state == 'on'")
def automate_exterior_lights_off_in_morning():
        input_boolean.exterior_lights_wanted_state = 'off'
        pyscript.ext_light_off_done = 'on'

Deciding how to express this cleanly in syntax is the most difficult part, in my opinion. "arm_on_false" isn't particularly easy to understand. A different kwarg, perhaps? Or, a "pseudo variable" like "last_eval" available to state_active... so that this would work:

@state_trigger('int(sensor.lux) > 50')
@state_active('not last_eval')
def a():
  light.exterior.turn_on()

Or maybe a different decorator entirely to indicate this functionality?

@craigbarratt
Copy link
Member

Thanks for the suggestion, and all the different ideas. It seems the first option

@state_trigger( "int(sensor.lux_outside_the_garage) > 50 and int(sensor.lux_outside_the_garage.old) <= 50")

is the cleanest approach until we add a new feature.

I'm warming up to the idea of adding a new optional argument to @state_trigger, but I'm struggling with finding an informative parameter name that isn't too long. Perhaps it could be state_was_false?

Another issue is how to handle start up - specifically, how is the first trigger handled when state_was_false is True? How about this:

  • if state_check_now=False (which is default), then the first trigger succeeds; it doesn't need to previously be False
  • if state_check_now=True, then the trigger is checked at startup; if that check is False then the next trigger will succeed. If it's True immediately, we ignore it and continue to wait until the trigger is False before the next trigger succeeds.

That way you get get either behavior (ie, requiring False first or not) by combining state_check_now with state_was_false.

Do we also need a state_hold_false parameter so that the False value has to be held for that long; any earlier True triggers will be ignored? Or could we just use state_hold to mean both True and False hold times? I'd rather avoid adding more parameters unless they are really essential.

This is sounding a bit complicated....

@craigbarratt
Copy link
Member

craigbarratt commented Nov 12, 2020

Perhaps we could just have a single new parameter state_hold_false, default None? It seems easier to explain: "the trigger expression must be False for this amount of time in seconds before a positive value causes a trigger." The default of None is easy to understand - same behavior as currently with no requirement the expression be previously False. A value of 0 means it does have to be False, immediately prior to the trigger. A value >0 means it has to be False for at least that amount of time prior to the trigger.

@dlashua
Copy link
Contributor

dlashua commented Nov 12, 2020

I'm rolling this state_hold_false=None/0/1+ around. I like the name because it works just like state_hold, but for the "false" side of the trigger. It covers every use case I can think of, as well.

@tchef69
Copy link
Author

tchef69 commented Nov 13, 2020

When I started to use Home Assistant and discovered this fantastic pyscript-integration (Thank you so much for this !) a couple of weeks ago, I was surprised that triggers re-trigger on every value change. I think this happens both in yaml-automations and in pyscript with @state_trigger.

To me, the function of a trigger means something that triggers on an event and then don't re-triggers as long as the condition is still true. If it retriggers then it's a more of a condition than a trigger.

Question: Do you have a forum for pyscript, or is it correct to lift this kind of questions as an github-issue ?

Thanks so much for your support !

@craigbarratt
Copy link
Member

Github issues are definitely a good place to post feature requests, and this discussion is definitely appropriate for a Github issue.

No, there isn't a forum for pyscript. I haven't thought about creating a forum, nor do I know where a good place would be to host it. As more people start using pyscript, a forum could be helpful. Suggestions are welcome.

@github392
Copy link

Can't we just use the normal community forum but with a subcategory like AppDaemon

https://community.home-assistant.io/c/third-party/appdaemon/21

In that way it will also create more buzz in the normal HA community.

@dlashua
Copy link
Contributor

dlashua commented Nov 13, 2020

I think this happens both in yaml-automations and in pyscript with @state_trigger.

Yes and no.

If you write your YAML automation like this, it'll trigger over and over again. Additionally, this is currently what a pyscript state_trigger (as you have written it, there are other forms) is the equivalent to in Home Assistant.

- alias: triggers over and over
  trigger:
    - platform: state
      entity_id: sensor.lux
  condition:
    - platform: template
      value_template: "{{ states('sensor.lux')|int > 50 }}"

But when you use a template trigger, it will only trigger when the "True/False" state of the entire template changes from False to True. I think most people would write this automation this way:

- alias: only triggers once
  trigger:
    - platform: template
      value_template: "{{ states('sensor.lux')|int > 50 }}"

This alternate syntax is also available:

- alias: only trigger once alt
  trigger:
    - platform: numeric_state
      entity_id: sensor.lux
      above: 50

@tchef69
Copy link
Author

tchef69 commented Nov 13, 2020

Ah, yes, you are correct. The following trigger (Numeric state trigger), triggers only one time when the value passes below 50, which is also described in the documentation: ".....fires if the value is changing from above to below or from below to above the given threshold."

automation:
  - alias: Test of numeric_state-below automation
    trigger:
    - platform: numeric_state
      entity_id: sensor.lux_outside_the_garage
      below: 50

Could it be good for similarity with the yaml-config to have a matching @numeric_state_trigger() ? But on the other hand, yaml has a lot of different special triggers (sun, tag .... )

@Nxt3
Copy link

Nxt3 commented Nov 13, 2020

I'd like to chime in and say I also found this confusing. If the trigger continues triggering after it reaches the met condition--what's the difference between @state_trigger and @state_active? Seems like they behave the same in this case.

@dlashua
Copy link
Contributor

dlashua commented Nov 13, 2020

@state_active does not listen for state changes. An example:

@state_trigger('input_boolean.test_1 = "on"')
@state_active('input_boolean.test_2 = "on"')
def a():
  whatever

If test_1 turns on while test_2 is off, this does not run the function. If test_2 THEN turns on, this still does not run the function because it only starts the trigger process on a change to test_1. So, in order to run the function, test_1 must turn on while test_2 is already on.

Put another way, @state_trigger, when it contains a comparison (like "==") is, in Home Assistant speak, both a trigger and a condition. It "triggers" on any entity mentioned in @state_trigger and then has a condition on the entire @state_trigger statement being True.

When you use @state_trigger without a comparison (i.e. @state_trigger('input_boolean.test_1')) then there is no condition and the function will run, unless, of course, you also have @state_active, which adds a condition.

@state_trigger('input_boolean.test_1') means watch test_1 for changes.

@state_trigger(input_boolean.test_1 == "on") is essentially a shortcut for:

@state_trigger('input_boolean.test_1')
@state_active('input_boolean.test_1 == "on"')

It watches test_1 AND checks a condition when there is a change.

@craigbarratt
Copy link
Member

Can't we just use the normal community forum but with a subcategory like AppDaemon

https://community.home-assistant.io/c/third-party/appdaemon/21

In that way it will also create more buzz in the normal HA community.

Sounds like a good idea. How do I create a 3rd-party forum?

@dlashua
Copy link
Contributor

dlashua commented Nov 14, 2020

@craigbarratt I think a moderator has to do it for you. I'll see if I can figure out who that might be.

craigbarratt added a commit that referenced this issue Nov 15, 2020
…_until()``

This requires the trigger expression to be ``False`` for at least that period
(including 0) before a successful trigger.  Proposed by @tchef69 (#89).
@tchef69
Copy link
Author

tchef69 commented Nov 15, 2020

Thank you so much ! Very nice and clear documentation too !

@dlashua
Copy link
Contributor

dlashua commented Nov 15, 2020

@craigbarratt this looks great!! I'll pull master and get some of my automations using this for a nice full test.

@craigbarratt
Copy link
Member

Closing this since it's now supported, but discussion of startup case continue in #95.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants