I’ve created a stopwatch with some nice features:
- It allows to Start, Stop and Resume.
- It allows to Reset.
- It allows to save lap values while running.
- It has hundredths of second precission.
- It continues running with restarts of Home Assistant.
- you don’t need to install any other package.
- It can be controlled manually through a Frontend card, or automatically by triggering with
input_boolean.start_stopwatch, input_boolean.reset_stopwatch and input_boolean.lap_stopwatch
.
v1.0: Initial version.
v1.1: I’ve changed the code to show only hundreds of second when the stopwatch is stopped.
v1.2: trigger simplification.
Note: After creating the stopwatch if it has Unavailable
or Unknown
in its state, press the reset button, to reset it to “00:00:00”.
Here is the code (it can be copied to the package folder):
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
- platform: state
entity_id: input_boolean.reset_stopwatch
from: "off"
to: "on"
- platform: state
entity_id: input_boolean.lap_stopwatch
from: "off"
to: "on"
- platform: state
entity_id: input_boolean.tictac_stopwatch
sensor:
- name: "Stopwatch"
state: >-
{% if is_state('input_boolean.reset_stopwatch','on') %}
{{ '00:00:00' }}
{% elif is_state('input_boolean.start_stopwatch','off') and is_state('input_boolean.lap_stopwatch','off') %}
{% set value = as_timestamp(now()) - state_attr('sensor.stopwatch','initial_time') + state_attr('sensor.stopwatch','elapsed_time') %}
{{ value|float|timestamp_custom("%H:%M:%S", False) + '.' + ((value|float*100)%100)|round(0)|string }}
{% elif 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("%H:%M:%S", False) |string }}
{% else %}
{{ states('sensor.stopwatch') }}
{% endif %}
icon: mdi:timer
attributes:
initial_time: >-
{% if is_state('input_boolean.start_stopwatch', 'on') and is_state_attr('sensor.stopwatch','running','off') %}
{{ as_timestamp(now()) }}
{% else %}
{{ state_attr('sensor.stopwatch','initial_time') }}
{% endif %}
elapsed_time: >-
{% if is_state('input_boolean.reset_stopwatch','on') %}
{{ 0 }}
{% elif is_state('input_boolean.start_stopwatch','off') and is_state('input_boolean.lap_stopwatch','off') %}
{{ as_timestamp(now()) - state_attr('sensor.stopwatch','initial_time') + state_attr('sensor.stopwatch','elapsed_time') }}
{% else %}
{{ state_attr('sensor.stopwatch','elapsed_time') }}
{% endif %}
running: >-
{{ states('input_boolean.start_stopwatch') }}
laps: >-
{% if is_state('input_boolean.reset_stopwatch','on') %}
{{[]}}
{% elif is_state('input_boolean.lap_stopwatch','on') 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("%H:%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
I’ve create two alternative Lovelace cards with animated seconds hand:
type: vertical-stack
cards:
- type: entity
entity: sensor.stopwatch
- type: horizontal-stack
cards:
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: button.start_stop
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: input_boolean.lap_stopwatch
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: input_boolean.reset_stopwatch
- type: horizontal-stack
cards:
- type: markdown
content: |-
**Laps:**
{% for i in range(state_attr('sensor.stopwatch','laps')|length) %}
{{ (i+1)|string + ': ' + state_attr('sensor.stopwatch','laps')[i]}}
{% endfor %}
- type: gauge
entity: sensor.template_tictac_stopwatch
min: 0
max: 1
needle: true
and this one:
type: vertical-stack
cards:
- type: horizontal-stack
cards:
- type: entity
entity: sensor.stopwatch
- show_name: true
show_icon: true
type: button
tap_action:
action: none
entity: sensor.template_tictac_stopwatch
show_state: false
icon_height: 40px
- type: horizontal-stack
cards:
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: button.start_stop
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: input_boolean.lap_stopwatch
- show_name: true
show_icon: true
type: button
tap_action:
action: toggle
entity: input_boolean.reset_stopwatch
- type: markdown
content: |-
**Laps:**
{% for i in range(state_attr('sensor.stopwatch','laps')|length) %}
{{ (i+1)|string + ': ' + state_attr('sensor.stopwatch','laps')[i]}}
{% endfor %}
If you use Mushroom integration, (you can install through HACS) it can be used for the card instead of previuos ones. In that case, you won’t need the button section of the stopwatch code, so you can safely remove and create several stopwatches without problems.
The card code with Mushrrom is:
type: vertical-stack
cards:
- type: entity
entity: sensor.stopwatch
- type: horizontal-stack
cards:
- type: custom:mushroom-template-card
primary: |-
{% if is_state('input_boolean.start_stopwatch','off') %}
{% if is_state('sensor.stopwatch','00:00:00') %}
Start
{% else %}
Resume
{% endif %}
{% else %}
Pause/Stop
{% endif %}
secondary: ''
icon: |-
{% if is_state('input_boolean.start_stopwatch','off') %}
{% if is_state('sensor.stopwatch','00:00:00') %}
mdi:play-circle-outline
{% else %}
mdi:pause-circle-outline
{% endif %}
{% else %}
mdi:stop-circle-outline
{% endif %}
entity: input_boolean.start_stopwatch
icon_color: |
{% if is_state('input_boolean.start_stopwatch','off') %}
{% if is_state('sensor.stopwatch','00:00:00') %}
green
{% else %}
orange
{% endif %}
{% else %}
red
{% endif %}
hold_action:
action: none
double_tap_action:
action: none
- type: custom:mushroom-template-card
primary: Lap
secondary: ''
icon: mdi:camera-outline
entity: input_boolean.lap_stopwatch
icon_color: |-
{% if is_state('input_boolean.start_stopwatch','off') %}
grey
{% else %}
blue
{% endif %}
hold_action:
action: none
double_tap_action:
action: none
- type: custom:mushroom-template-card
primary: Reset
secondary: ''
icon: mdi:close-circle-outline
entity: input_boolean.reset_stopwatch
icon_color: |-
{% if is_state('input_boolean.start_stopwatch','off') %}
{% if is_state('sensor.stopwatch','00:00:00') %}
grey
{% else %}
red
{% endif %}
{% else %}
red
{% endif %}
hold_action:
action: none
double_tap_action:
action: none
- type: horizontal-stack
cards:
- type: markdown
content: |-
**Laps:**
{% for i in range(state_attr('sensor.stopwatch','laps')|length) %}
{{ (i+1)|string + ': ' + state_attr('sensor.stopwatch','laps')[i]}}
{% endfor %}
- type: gauge
entity: sensor.template_tictac_stopwatch
min: 0
max: 1
needle: true
I hope anybody finds it useful.
Note: I recommend to remove all the entities created from recorder, as it’s useless to record them in database. To do that, add next lines to recorder
in configuration.yaml
:
recorder:
...
exclude:
entity_globs:
- automation.*stopwatch
- input_boolean.*stopwatch
- sensor.*stopwatch