Hello folks
I have also taken a close look at this and I think we are hitting a race around condition that is dependent on the order the triggers are processed by the template and the automations. It seems that by the time a template trigger is processed the input booleans may (or may not have changed). In addition when the ‘reset’ automation is processed it launches several triggers (start/stop and tactic) into the template which will be processed in due course (with no guarantee on the order). This is also compounded by the tic tac triggers that can arrive during the reset process. I added extra lap entries and observed up to two residual tactic triggers processed after the reset started (one in mid flight, one triggered by the reset automation).
I tried adding a wait on trigger for the reset process (triggered from the “00:00” status of the stopwatch sensor but it wasn’t quite there. I also considered a ‘resetting’ state to allow all the trigger to work through.
Below is my version of the code which takes account of the trigger type inside of the template as well as extra checks on sensor attributes. I looks like a bit of overkill but does seem to work. Ideally the template code would only use template variables and trigger details (to avoid scheduling race around issues), but did not succeed.
The code seems to work for me so try out and see if it works for you.
input_boolean:
start_stopwatch:
# It triggers stopwatch to start/stop(pause)
name: Start/Stop Stopwatch
# initial: off
reset_stopwatch:
# It triggers stopwatch to reset
name: Reset
# initial: off
tictac_stopwatch:
# Pendulum of the stopwatch
name: TicTac
# initial: off
lap_stopwatch:
# It triggers stopwatch to show lap time
name: Lap
icon: mdi:camera-outline
# initial: off
template:
- trigger:
# Stopwatch sensor with Start, Stop/Pause, Reset and Lap features. Hundreds of second precission
- platform: state
entity_id: input_boolean.start_stopwatch
id: start_stop_pause
from:
- 'on'
- 'off'
to:
- 'off'
- 'on'
- platform: state
entity_id: input_boolean.reset_stopwatch
from: "off"
to: "on"
id: reset
- platform: state
entity_id: input_boolean.lap_stopwatch
from: "off"
to: "on"
id: lap
- platform: state
entity_id: input_boolean.tictac_stopwatch
id: tictac
from:
- 'on'
- 'off'
to:
- 'off'
- 'on'
sensor:
- name: "Stopwatch"
state: >-
{% if (trigger.id =='reset') %}
{{ '00:00' }}
{# RESET THE STOPWATCH SENSOR #}
{% elif (trigger.id =='start_stop_pause') and (trigger.to_state.state == 'off') and is_state('input_boolean.start_stopwatch','off') and is_state('input_boolean.lap_stopwatch','off') and is_state_attr('sensor.stopwatch','running','on') %}
{% set value = as_timestamp(now()) - state_attr('sensor.stopwatch','initial_time') + state_attr('sensor.stopwatch','elapsed_time') %}
{{ value|float|timestamp_custom("%M:%S", False) + '.' + ((value|float*100)%100)|round(0)|string }}
{# STOP / PAUSE STOPWATCH #}
{% elif (trigger.id =='tictac') and is_state('input_boolean.start_stopwatch','on') and is_state_attr('sensor.stopwatch','running','on') %}
{% set value = as_timestamp(now()) - state_attr('sensor.stopwatch','initial_time') + state_attr('sensor.stopwatch','elapsed_time') %}
{{ value|float|timestamp_custom("%M:%S", False) |string }}
{# UPDATE DUE TO THE TIC TAC KEEPS DISPLAY MOVING #}
{% else %}
{{ states('sensor.stopwatch') }}
{# IGNORE ALL OTHER TRIGGERS #}
{% endif %}
icon: mdi:timer
attributes:
initial_time: >-
{% if (trigger.id =='reset') %}
{{ 0 }}
{% elif (trigger.id =='start_stop_pause') and (trigger.to_state.state == 'on') and is_state('input_boolean.start_stopwatch', 'on') and is_state_attr('sensor.stopwatch','running','off') %}
{{ as_timestamp(now()) }}
{# INITIAL TIME OF THE CURRENT RUNNING SESSION #}
{% else %}
{{ state_attr('sensor.stopwatch','initial_time') }}
{# IGNORE ALL OTHER TRIGGERS #}
{% endif %}
elapsed_time: >-
{% if (trigger.id =='reset') %}
{{ 0 }}
{# RESET ELAPSED TIME ATTRIBUTE #}
{% elif (trigger.id =='start_stop_pause') and (trigger.to_state.state == 'off') and is_state('input_boolean.start_stopwatch','off') and is_state('input_boolean.lap_stopwatch','off') and is_state_attr('sensor.stopwatch','running','on')%}
{{ as_timestamp(now()) - state_attr('sensor.stopwatch','initial_time') + state_attr('sensor.stopwatch','elapsed_time') }}
{# # STOPWATCH STOPPED/PAUSED - update the elapsed time (first run it is zero, if paused and restarted it added the last run period #}
{# IGNORE ALL OTHER TRIGGERS #}
{% else %}
{{ state_attr('sensor.stopwatch','elapsed_time') }}
{% endif %}
running: >-
{{ states('input_boolean.start_stopwatch') }}
laps: >-
{% if (trigger.id =='reset') %}
{{[]}}
{% elif (trigger.id =='lap') and is_state_attr('sensor.stopwatch','running','on') %}
{% set data = namespace(laps=state_attr('sensor.stopwatch','laps')) %}
{% set value = as_timestamp(now()) - state_attr('sensor.stopwatch','initial_time') + state_attr('sensor.stopwatch','elapsed_time') %}
{% set data.laps = (data.laps + [value|float|timestamp_custom("%M:%S", False) + '.' + ((value|float*100)%100)|round(0)|string]) %}
{{ data.laps }}
{% else %}
{{ state_attr('sensor.stopwatch','laps')}}
{% endif %}
- trigger:
# Numeric conversion of input_boolean.tictac_stopwatch to show as a gauge in Frontend
- platform: state
entity_id: input_boolean.tictac_stopwatch
sensor:
- unique_id: tictac_stopwatch
name: >-
{% if states('input_boolean.tictac_stopwatch') == 'on' %}
Tic
{% else %}
Tac
{% endif %}
state: >-
{% if is_state('input_boolean.tictac_stopwatch','on') %}
{{ 1 }}
{% else %}
{{ 0 }}
{% endif %}
icon: >-
{% if states('input_boolean.tictac_stopwatch') == 'off' %}
mdi:clock-time-nine
{% else %}
mdi:clock-time-three-outline
{% endif %}
# Start/Stop(Pause) button
- button:
- unique_id: 'start_stop_stopwatch'
name: Start/Stop
icon: >-
{% if states('input_boolean.start_stopwatch') == 'off' %}
mdi:play-circle-outline
{% else %}
mdi:stop-circle-outline
{% endif %}
press:
service: input_boolean.toggle
target:
entity_id: input_boolean.start_stopwatch
automation:
- id: tic_tac_stopwatch
alias: "Tic Tac Stopwatch"
description: "It toggles input_boolean.tictac_stopwatch every second"
trigger:
- platform: time_pattern
seconds: /1
condition:
- condition: state
entity_id: input_boolean.start_stopwatch
state: 'on'
action:
- service: input_boolean.toggle
target:
entity_id: input_boolean.tictac_stopwatch
mode: single
- id: reset_stopwatch
alias: "Reset Stopwatch"
description: "It reset input_booleans when input_boolean.reset_stopwatch is set to on"
trigger:
- platform: state
entity_id: input_boolean.reset_stopwatch
from: "off"
to: "on"
action:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.start_stopwatch
- service: input_boolean.turn_off
target:
entity_id: input_boolean.tictac_stopwatch
- service: input_boolean.turn_off
target:
entity_id: input_boolean.reset_stopwatch
mode: single
- id: lap_stopwatch
alias: "Lap Stopwatch"
description: "It turns off input_boolean.lap_stopwatch when it is turned on"
trigger:
- platform: state
entity_id: input_boolean.lap_stopwatch
from: "off"
to: "on"
action:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.lap_stopwatch
mode: single```