Inconsistency between time trigger and condition?

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.