Inconsistency between time trigger and condition?

Post the full automation, please, or at least the full trigger and condition blocks.

Is there a way to tell? The trace merely shows that the condition failed, not every step in the calculation.

Sure:

alias: Dehumidifier Pump Control
trigger:
  - at: input_datetime.dehumidifier_pump_pause_until
    platform: time
  - entity_id: sensor.151732605496952_tank
    platform: state
  - entity_id: input_number.dehumidifier_pump_threshold
    platform: state
  - event: start
    platform: homeassistant
condition:
  - condition: template
    value_template: |-
      {{now().timestamp() >=
      state_attr('input_datetime.dehumidifier_pump_pause_until', 'timestamp')}}
action:
  - if:
      - above: input_number.dehumidifier_pump_threshold
        condition: numeric_state
        entity_id: sensor.151732605496952_tank
    then:
      if:
        condition: state
        entity_id: switch.dehumidifier_pump_switch
        state: "off"
      then:
        - service: switch.turn_on
          target:
            entity_id: switch.dehumidifier_pump_switch
        - data:
            timestamp: "{{ now().timestamp() + 1*60 }}"
          service: input_datetime.set_datetime
          target:
            entity_id: input_datetime.dehumidifier_pump_pause_until
    else:
      if:
        condition: state
        entity_id: switch.dehumidifier_pump_switch
        state: "on"
      then:
        - service: switch.turn_off
          target:
            entity_id: switch.dehumidifier_pump_switch
        - data:
            timestamp: "{{ now().timestamp() + 10*60 }}"
          service: input_datetime.set_datetime
          target:
            entity_id: input_datetime.dehumidifier_pump_pause_until

(I have reformatted this slightly. For some reason the YAML copy/paste had the if/then/else out of order. I know it doesn’t technically make a difference, but it certainly makes it more difficult to read…)
Note that this is not the only automation that adjusts input_datetime.dehumidifier_pump_pause_until.

Add variables before the condition and store the two sides in separate variables. This should show in traces.

I changed the automation to:

condition:
  - condition: or
    conditions:
      - condition: template
        value_template: >-
          {{now().timestamp() >=
          state_attr('input_datetime.dehumidifier_pump_pause_until',
          'timestamp')}}
      - condition: template
        value_template: "{{trigger.platform == 'time'}}"

…which works around the behavior while making it visible in the trace if this behavior occurs.

By manually doing a service call to update pause_until I’m able to trigger this behavior:

service: input_datetime.set_datetime
data:
  timestamp: "{{ now().timestamp() + 1 }}"
target:
  entity_id: input_datetime.dehumidifier_pump_pause_until

Will do…

I changed it to the following:

condition:
  - condition: or
    conditions:
      - condition: template
        value_template: >-
          {% set now_timestamp = now().timestamp() %}
          {% set pause_timestamp =
          state_attr('input_datetime.dehumidifier_pump_pause_time', 'timestamp')
          %}
          {{ now_timestamp >=  pause_timestamp }}
      - condition: template
        value_template: "{{trigger.platform == 'time'}}"

…but the trace does not have any information about the variables.

Am I misunderstanding something?

If you mean adding a variables action - I can’t do so because this is the condition block of the automation, which is run before any actions.

I can try turning the condition block into an initial if/else return in the automation actions I suppose, though that’s a more major change to the automation.

Suggestions?

No, not the action, just add variables before condition section. You have to do it through YAML, there’s no UI for it.

1 Like

I think what Artur meant was something like:

condition:
  - variables:
      now_timestamp: "{{ now().timestamp() }}"
      pause_timestamp: "{{state_attr('input_datetime.dehumidifier_pump_pause_time', 'timestamp') }}"
  - condition: or
    conditions:
      - condition: template
        value_template: >-
          {{ now_timestamp >=  pause_timestamp }}
      - condition: template
        value_template: "{{trigger.platform == 'time'}}"
1 Like

Oh interesting!

I didn’t know you could define variables in the condition block. This appears to be undocumented. Is it something I can rely on, or should I just use it for debugging?

I’ll try that and get back to you.

I’m not sure this is correct. I meant like that:

variables:
  now_timestamp: "{{ now().timestamp() }}"
  pause_timestamp: "{{state_attr('input_datetime.dehumidifier_pump_pause_time', 'timestamp') }}"
condition:
  - condition: or
    conditions:
      - condition: template
        value_template: >-
          {{ now_timestamp >=  pause_timestamp }}
      - condition: template
        value_template: "{{trigger.platform == 'time'}}"

It is documented, but in unexpected place.

Please make sure to read a thread you’re commenting before saying something unneeded. Thank you.

I ran a few tests. What I’ve found is that the time trigger ignores fractions of seconds and will fire as soon as possible after the integer seconds are matching now()

Here’s an example where I set the trigger time to a timestamp that ended in 0.99 seconds. In my automation I rendered now() and set it to a variable to see what it displayed. Here are the results:

input_timestamp: 1724988756.99
now_timestamp: 1724988756.001632

I tried this many times. The value for now() was always between 0.001 to 0.002 seconds after the integer value of the desired time trigger. It made no difference what fractions of seconds were specified on the entity I used to trigger. I’d expect the 0.001-0.002 delay to change depending on how fast your host hardware is and what other processes are currently running. But the takeaway is that the trigger clearly ignores fractions of a second.

Therefore, comparing now() to the desired trigger time is a bad idea unless you remove the fractions of seconds from the trigger time.

3 Likes

It only reports the result of the entire template. Converting the Jinja2 variables to script variables will report their values in the trace, if you want more visibility, but it won’t ultimately change the template’s result.

The issue you’re occasionally encountering might be due to how fractional seconds are handled (or not handled) by the trigger. What is the smallest unit of time in the input_datetime’s value? Minutes or seconds?

EDIT
Ninja’d by meckaneck

1 Like

Agreed about the unexpected place. That being said, that seems to be documenting script-global variables, not condition-scope variables. I can’t find any documentation on condition-scope variables…

Actually now that I try it it condition-scope variables don’t appear to work (or I have the syntax wrong):

condition:
  - variables:
      now_timestamp: "{{now().timestamp()}}"
      pause_timestamp: >-
        {{state_attr('input_datetime.dehumidifier_pump_pause_time',
        'timestamp')}}
  - condition: or
    conditions:
      - condition: template
        value_template: "{{now_timestamp >= pause_timestamp}}"
      - condition: template
        value_template: "{{trigger.platform == 'time'}}"

Automation is unavailable: Unexpected value for condition: ‘None’. Expected and, device, not, numeric_state, or, state, sun, template, time, trigger, zone @ data[0]

Moving the variables to script-global does work:

variables:
  now_timestamp: "{{now().timestamp()}}"
  pause_timestamp: >-
        {{state_attr('input_datetime.dehumidifier_pump_pause_time',
        'timestamp')}}
condition:
  - condition: or
    conditions:
      - condition: template
        value_template: "{{now_timestamp >= pause_timestamp}}"
      - condition: template
        value_template: "{{trigger.platform == 'time'}}"

…however I’d be concerned with this approach with ordering (are variables defined in this manner guaranteed to be evaluated after the trigger?)

Aha: great insight! That matches what I’m seeing with script-global variables:

now_timestamp: 1725023103.001939
pause_timestamp: 1725023103.99

And that explains the observed bug quite nicely; thank you for chasing that down. Anything that was setting the input_datetime to an integer # of seconds would end up “fine” - which some but not all changes to the pause time were doing. Hence the inconsistency. Time for a bug report on github?

The more I look at it, the more I think this is a bug. Looking at the code here, there is a separate logic chain depending on if the time trigger is an input_datetime compared to a sensor with a timestamp device class.

When the trigger is a sensor, the listener is set to the sensor’s state, which carries along with it fractions of a second, if any.

When the trigger is an input_datetime only the hours, minutes, and seconds are used to set the listener. My guess is that was done because, via the UI, an input_datetime can only be set to 1-second resolution. I think it was overlooked that an input_datetime can contain microsecond accuracy if set by an action call.

Maybe I have misread the code but the smallest unit of time monitored by a Time Trigger appears to be seconds (i.e. the clock’s minimum resolution is 1 second). Fractional seconds are ignored.

Sort of like if its resolution was hours, then fractional hours would be ignored so it would trigger at 13:00 for a sensor or input_datetime’s value of 13:15.

In other words, it’s working exactly as designed. To accommodate fractional seconds, it would need to reduce the clock resolution to milliseconds or microseconds … and I doubt that would fly in an Architecture discussion.

1 Like

You don’t need infinite precision for this.

Just do what most other timers do when presented with a target time, and wake up a) no earlier than the target time and b) as close to the target as possible.

a) is the key here.

This matches e.g. what Linux does (interrupted syscalls notwithstanding).

If you’re planning to make that change, to the Time Trigger’s underlying code, you can propose it in the Architecture repository. If accepted, you can proceed to submit a Pull Request in the Core repository.

Otherwise you can create a Feature Request in the community forum and hope a volunteer developer agrees with your proposal and takes on the project.

I’m seeing that fractional seconds work in a time trigger already, as long as it uses a sensor entity (must be sensor domain with timestamp device class) that has a state that does include fractional seconds. Which, they normally don’t, because the code for a sensor with device class timestamp strips fractional seconds also.

But if you override a sensor entity’s state (via developer tools → states), it IS possible to get fractional seconds in a sensor entity. So I did that, using a datetime that ends with 0.9 seconds and when the automation fired, now() rendered 0.0033 seconds after the intended trigger:

now_timestamp: 1725041230.903311
trigger_timestamp: 1725041230.9

So, it DID trigger at 0.9 seconds, it was just fairly difficult to get all the right things in place.

I agree this warrants an architecture discussion. There are a few things that should change if fractional seconds are desired to be supported:

  • Update time triggers to use microseconds when an input_datetime entity is selected
  • Update input_datetime helpers to allow specifying them with microseconds, both in yaml and the UI. They can already hold microseconds if set via an action, although it is only in the timestamp attribute. The state itself does not reflect microseconds.
  • Update timestamp sensors to not strip off microseconds

If the decision is made to not support microseconds in sensors or triggers, then the following should change:

  • update input_datetime helpers so that microseconds are stripped off when using the input_datetime.set_datetime action
  • Update the state of buttons and events so that they don’t use microseconds (for consistency across all entities)

I’ll probably make a post later if nobody else gets to it before then.