I’m about 1 month into using Home Assistant so I’m a bit of a n00b and I’ve been struggling to figure this one out. ChatGPT and Claude have me running in circles getting part of the way there only for something to not work and then they go off the rails trying to suggest fixes. So I’m hoping somebody can give me some guidance on how to do this.
I would like to use Home Assistant to help track my sleep. I have already figured out the mechanism to tell whether I am asleep or not, but I need help figuring out how to track that over time and display it in the dashboard the way I’m envisioning. I’m also open to other ideas if you know of a better way to do this.
I want to be able to quickly glance at my phone in the middle of the night and see how long I’ve been asleep up to that point, and then when I wake up in the AM I want to know the total elapsed time I was asleep for that sleep session.
Ideally I end up with a dashboard card that looks like this when I go to bed:
Start: 10:00pm Tuesday Jan 28
End: ongoing
Elapsed: 0h 1m and counting
like this when I check in the middle of the night:
Start: 10:00pm Tuesday Jan 28
End: ongoing
Elapsed: 4h 10m and counting
and like this if I wake up at 7:05am:
Start: 10:00pm Tuesday Jan 28
End: 7:05am Wednesday Jan 29
Elapsed: 9h 5m
- To facilitate this, I created a template sensor called
sensor.sleep_state
that looks at 2 physical sensors and evaluates whether I am asleep
, awake
, or limbo
. Asleep and awake are self-explanatory. Limbo
is basically a 5min delay I added to account for short bathroom breaks in the middle of the night, where I don’t want the sensor to change to awake
until I’ve been in ‘limbo’ for more than 5 minutes…and visa versa to make sure it doesn’t change to asleep
until my bed sensors have detected me in bed for > 5min to avoid resetting the state anytime I sit on the bed briefly during the day.
- For each sleep session I want to record start/end/elapsed time and have these records somewhere that I can access them long term and use them as inputs for a graph to show averages or trends. All times should be local timezone.
Sleep Session Start
= the time and date that the sensor last changed to asleep
.
Sleep Session End
= the time and date that the sensor changed to awake
after an ongoing sleep session has started. While the sleep session is active, the dashboard can show ongoing
. Once the session has ended, the dashboard should show the end time/date.
Sleep Session Total
= running total of how long I’ve been asleep, displayed in hours/minutes (e.g. 8h 44m
). Should include the running total time the sensor is in asleep
state and/or limbo
state. While a sleep session is active, the value should update once a minute in the dashboard. Once the session has ended the value doesn’t need to update anymore until the next session starts.in hours and minutes
- Once a sleep session has ended, the dashboard card should remain as is until the next sleep session starts, at which point everything should reset:
Start
changes to the new start time
End
goes back to ongoing
Elapsed
resets to 0h 0m
and begins counting up again
Sorry for the long post and thanks in advance if you made it this far!
I think you should go about this by setting up another sensor to hold the data you have, i.e. sensor.sleep_tracking
that holds the data for your tracking.
Template sensors can do this, for example by setting up a time_pattern
trigger that triggers every minute.
Another route you can take is using the pyscript
integration (Pyscript: Python Scripting for Home Assistant — hacs-pyscript 1.6.1 documentation) to set them up. It may be easier to correctly format everything and all the automating should be able to be done in a single file.
Between these two, I would say you’re mainly picking between either the robustness and support of a template sensor (as they are natively supported), whereas pyscript may be a bit more flexible.
Depending on how fancy you want things on the frontend, a simple markdown card is enough to display the data from the sensor. Otherwise, you can have a look at some custom cards as well.
I finally got this at least part of this working after a lot of back and forth with Claude AI and trial and error. It took 4 template sensors and 2 automations, and a dashboard entities card. Now I can see the current/last sleep session in the dashboard like I wanted. It seems to be working so far but I haven’t extensively tested it yet.
Since I have no coding experience or actual understanding of YAML can anyone take a look and let me know if there is any glaring problem or if this can be done in a better way?
I’m also not sure if doing it the way I have, will allow me to track the long term stats I wanted - to be able to plot the sleep sessions over time. How could I do that?
template:
- sensor:
- name: "trevorlahey Sleep State"
state: >
{% if is_state('input_boolean.trevorlahey_sleep_state_force_asleep', 'on') %}
asleep
{% elif is_state('binary_sensor.trevorlahey_bed_tilt_sensor', 'on') %}
awake
{% elif is_state('binary_sensor.sleepnumber_where_the_magic_happens_trevorlahey_is_in_bed', 'off') %}
{% if (states.binary_sensor.sleepnumber_where_the_magic_happens_trevorlahey_is_in_bed.last_changed | as_timestamp) < (now() - timedelta(minutes=5)).timestamp() %}
awake
{% else %}
limbo
{% endif %}
{% elif is_state('binary_sensor.trevorlahey_bed_tilt_sensor', 'off') and is_state('binary_sensor.sleepnumber_where_the_magic_happens_trevorlahey_is_in_bed', 'on') %}
{% if (states.binary_sensor.sleepnumber_where_the_magic_happens_trevorlahey_is_in_bed.last_changed | as_timestamp) < (now() - timedelta(minutes=5)).timestamp() %}
asleep
{% else %}
limbo
{% endif %}
{% else %}
limbo
{% endif %}
icon: >
{% if is_state('sensor.trevorlahey_sleep_state', 'asleep') %}
mdi:sleep
{% elif is_state('sensor.trevorlahey_sleep_state', 'awake') %}
mdi:sleep-off
{% else %}
mdi:help-network
{% endif %}
# Sleep Session Start Time
- name: "sleep_session_start"
state: >
{% if states('sensor.trevorlahey_sleep_state') == 'asleep' %}
{{ states.sensor.trevorlahey_sleep_state.last_changed }}
{% elif states('sensor.sleep_session_start') != 'unknown' and
states('sensor.sleep_session_start') != 'null' %}
{{ states('sensor.sleep_session_start') }}
{% else %}
null
{% endif %}
attributes:
formatted: >
{% if states('sensor.sleep_session_start') != 'unknown' and
states('sensor.sleep_session_start') != 'null' %}
{{ as_timestamp(states('sensor.sleep_session_start')) | timestamp_custom('%I:%M%p %a %b %-d') | replace('AM', 'am') | replace('PM', 'pm') }}
{% else %}
No session
{% endif %}
# Sleep Session End Time
- name: "sleep_session_end"
state: >
{% if states('sensor.trevorlahey_sleep_state') == 'awake' and
states('sensor.sleep_session_start') != 'unknown' and
states('sensor.sleep_session_start') != 'null' %}
{{ states.sensor.trevorlahey_sleep_state.last_changed }}
{% elif states('sensor.sleep_session_end') != 'unknown' and
states('sensor.sleep_session_end') != 'null' %}
{{ states('sensor.sleep_session_end') }}
{% else %}
null
{% endif %}
attributes:
formatted: >
{% if states('sensor.trevorlahey_sleep_state') in ['asleep', 'limbo'] and
states('sensor.sleep_session_start') != 'unknown' and
states('sensor.sleep_session_start') != 'null' %}
ongoing
{% elif states('sensor.sleep_session_end') != 'unknown' and
states('sensor.sleep_session_end') != 'null' %}
{{ as_timestamp(states('sensor.sleep_session_end')) | timestamp_custom('%I:%M%p %a %b %-d') | replace('AM', 'am') | replace('PM', 'pm') }}
{% else %}
No session
{% endif %}
# Sleep Session Total Duration
- name: "sleep_session_total"
state: >
{% set start = states('sensor.sleep_session_start') %}
{% if start != 'unknown' and start != 'null' %}
{% if states('sensor.trevorlahey_sleep_state') in ['asleep', 'limbo'] %}
{% set duration = (as_timestamp(now()) - as_timestamp(start)) | int %}
{% else %}
{% set end = states('sensor.sleep_session_end') %}
{% if end != 'unknown' and end != 'null' %}
{% set duration = (as_timestamp(end) - as_timestamp(start)) | int %}
{% else %}
{% set duration = 0 %}
{% endif %}
{% endif %}
{{ duration }}
{% else %}
0
{% endif %}
attributes:
formatted: >
{% set duration = states('sensor.sleep_session_total') | int(0) %}
{% set hours = (duration / 3600) | int %}
{% set minutes = ((duration % 3600) / 60) | int %}
{{ hours }}h {{ minutes }}m
2 automations:
automation sleep_tracking:
- alias: "Reset Sleep Session on New Sleep"
trigger:
- platform: state
entity_id: sensor.trevorlahey_sleep_state
to: "asleep"
action:
- service: homeassistant.update_entity
target:
entity_id:
- sensor.sleep_session_start
- sensor.sleep_session_end
- sensor.sleep_session_total
- alias: "Update Sleep Session Total"
trigger:
- platform: time_pattern
minutes: "/1"
condition:
- condition: state
entity_id: sensor.trevorlahey_sleep_state
state:
- "asleep"
- "limbo"
action:
- service: homeassistant.update_entity
target:
entity_id: sensor.sleep_session_total
and an dashboard card:


type: entities
title: Sleep Session
show_header_toggle: false
entities:
- type: attribute
entity: sensor.sleep_session_start
attribute: formatted
name: Start
icon: mdi:clock-start
- type: attribute
entity: sensor.sleep_session_end
attribute: formatted
name: End
icon: mdi:clock-end
- type: attribute
entity: sensor.sleep_session_total
attribute: formatted
name: Total
icon: mdi:clock