Sleep tracking help

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:
image
image

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