Custom Componenet: Oura Ring sleep data sensor

exactly this ^. I didn’t want them to overlap.

How’s your experience been with it so far? I noticed a few things that I want to account for but minor.

Pretty good so far :slight_smile: … nice work!

I have been updating the original component code to migrate to Oura API v2 and also start adding more sensors. The objective is to have all Oura API data on the code. For now, only readiness is added (in addition to the previously existing sleep), but I will be adding more over the next couple of days/weeks.

The new component requires a different configuration, and has breaking changes. So if you want to use it, you may want to completely reinstall it from scratch until it’s ready.

This currently sits on oura-v2 branch. It will be merged after adding all other sensors and data points. Until then, assume the code is experimental and can change at any time.

If you have ideas on what you want to see on the component, feel free to add an issue; or if you want to improve the logic and code, PRs are welcomed (specially if previously aligned on an issue).

2 Likes

Both the activity sensor and the readiness sensors have been implemented on the oura-v2 branch and the code is using Oura API v2.

If anyone is willing to try the component and see if it works well, check how easy it is to install, and report any issues - I would highly appreciate it.

I have tested it and it seems to work for me. However, do note that I am still adding commits to that branch, so it could be broken at times (if I push a wrong commit).

I will use the feedback also to develop the other sensors too for other endpoints.

I have now fully published the new version of the Oura component.

Summary of changes include:

  • Migration from Oura API v1 to v2.
  • Addition of sensors for all API endpoints, including: activity, bedtime, heart rate, readiness, sessions, sleep, sleep periods sleep score, workouts.
  • More configuration options for each of the sensor.

Warning: This version includes breaking changes. To note a few of the most important:

  • Configuration users Personal Tokens and no longer supports or accepts OAuth. This should allow for simpler configuration, though.
  • Legacy sleep sensor is now under sleep. Most variables are still there, but some have changed or moved to other sensors. This has been done following the changes of the API. For example, sleep score is now on the sleep score sensor instead of the sleep sensor. You may need to rewrite some of your views or derived template sensors.

For more information, refer to the GitHub documentation. If you have suggestions on what to improve or find bugs, please report them in the GitHub component. PRs are also welcomed if aligned on a GitHub issue first.

Any comments on things working great is also welcomed here.

1 Like

@webosa @akeslo I see you are using charts to display data. If you have any recommendations of 1-2 useful charts to add to the documentation that are compatible with the sample documentation on derived sensors, I am happy to add it. Or, if you prefer, you can send a pull request too. That might be useful to others, so happy to show an example in the documentation! Thanks!

3 Likes

I stopped using a personal token and started using the client secret instead from my Oura app web page (Oura on the web), together with the client id on the same page: the custom component started working.

Hi @nitobuendia - thank you for all your work on this integration.

I’m new to Home Assistant and trying to get this integration to work for my Oura ring and I’m having difficulty. I suspect I’m missing something obvious but I can’t see what it is.

  • I have created an Oura personal token
  • I have cloned the current repo and copied the contents of custom_component/oura/ folder into the custom_component/oura/
  • I have added a basic config to /config/configuration.yaml and restarted.

I’m still not able to see any Oura entities. I spent a few hours on this yesterday and I’m not sure what I’m doing wrong.

I’m unable to find any useful logs.

Is anyone able to point me in the right direction to get this working? Thank you!

Image 2023-01-13 at 8.45.01 AM

Image 2023-01-13 at 8.48.44 AM

@phillcoxon Replied to you on the Github issue. Given the secreenshot, I think my comment number (1) is the main explanation.

Thank you @nitobuendia - you were 100% right. The sensor: heading was missing in front of the configuration. As someone very new to Home Assistant that wasn’t immediately obvious.

So the following config now works:

sensor:
  - platform: oura
    access_token: !secret oura_api_token
    scan_interval: 7200 # 2h = 2h * 60min * 60 seconds
    sensors:
      readiness: {}
      sleep:
        name: sleep_data
        max_backfill: 3
        monitored_dates:
          - yesterday
          - monday
          - tuesday
          - wednesday
          - thursday
          - friday
          - saturday
          - sunday
          - 8d_ago # Last week, +1 to compare to yesterday.

Thank you!

1 Like

Here’s what I managed to get working:

type: custom:apexcharts-card
graph_span: 7d
now:
  show: true
header:
  show: true
  title: Sleep Stats
  show_states: true
  colorize_states: true
series:
  - entity: sensor.oura_sleep
    data_generator: |
      var days = ['8d_ago', '7d_ago', '6d_ago', '5d_ago',
        '4d_ago', '3d_ago', '2d_ago', 'yesterday'];
      var res = Object.keys(entity.attributes)
          .map((day, index) => {
            var data = entity.attributes[day]
            return days.includes(day)
              ? [new Date(data.day).getTime(), data.total_sleep_duration_in_hours]
              : undefined;
      });
      return res.sort().filter(x=>x);

and

type: custom:apexcharts-card
graph_span: 1d
now:
  show: true
header:
  show: true
  title: Heart Stats
  show_states: true
  colorize_states: true
series:
  - entity: sensor.oura_heart_rate
    data_generator: |
      var days = ['8d_ago', '7d_ago', '6d_ago', '5d_ago',
        '4d_ago', '3d_ago', '2d_ago', 'yesterday'];
      var res = Object.keys(entity.attributes)
          .map((day, index) => {
            var data = entity.attributes[day]
            return days.includes(day)
              ? data.map((v, i) => [new Date(v.timestamp).getTime(), v.bpm])
              : undefined;
      });
      res = res.reduce((a, v) => v ? a.concat(v) : a, []);
      return res;

(there’s HR data for yesterday only) but I can’t get this template sensor to show anything:

sensor:
- platform: template
  sensors:
    - name: "Sleep Breath Average Yesterday"
      unique_id: sleep_breath_average_yesterday
      unit_of_measurement: bpm
      state: >
        {{ states('sensor.oura_sleep.attributes.yesterday.average_breath') }}
      icon: "mdi:lungs"

(the sensors created by the integration work, although as just numbers in the History Graph)
EDIT: This entity seems to have survived the deletion of a previous version.

I’m currently digging into templating as well. Does this work if you use it instead?

{{ state_attr('sensor.oura_sleep', 'yesterday')["average_breath"] }}

Also, if it’s helpful, here’s some testing of accessing array data in the `Developer Tools → Template" editor. I’m starting to get my head around it now.

--
sensors.oura_sleep array: {{ states.sensor.oura_sleep.attributes }}

--

sensor.oura_sleep array keys; {{ states.sensor.oura_sleep.attributes.keys() }}

--

yesterday array keys: {{ states.sensor.oura_sleep.attributes.yesterday.keys() }}

--

yesterdy average_breath {{ states.sensor.oura_sleep.attributes.yesterday.average_breath }}

--

yesterday state_attr: {{ state_attr('sensor.oura_sleep', 'yesterday') }}

0d_ago: state_attr: {{ state_attr('sensor.oura_sleep', '0d_ago') }}

yesterday average_breath: {{ state_attr('sensor.oura_sleep', 'yesterday')["average_breath"] }}

{% set obs=  state_attr("sensor.oura_sleep", "yesterday") %}
yesterday average_breath: {{ obs["average_breath"] }}

@amb007 - I can not get the sensors template example that @nitobuendia provided in GitHub to work at all. @nitobuendia - are you able to provide more detail about that template example? If it still works? Where it should be included etc?

What I have been able to get to work is this:

sensor:
  - platform: template
    sensors:
      sleep_breath_average_yesterday:
        friendly_name: "Sleep Breath Average Yesterday"
        unique_id: sleep_breath_average_yesterday
        unit_of_measurement: bpm
        value_template: "{{ state_attr('sensor.oura_sleep', 'yesterday').average_breath  }}"

      sleep_resting_heart_rate_yesterday:
        friendly_name: "Sleep Resting Heart Rate Yesterday"
        unique_id: sleep_resting_heart_rate_yesterday
        unit_of_measurement: bpm
        value_template: "{{ state_attr('sensor.oura_sleep', 'yesterday').average_heart_rate  }}"

@phillcoxon The most likely explanation is that your sensor is not called sensor.oura_sleep. Given your configuration above, the sensor is likely called sensor.sleep_data. You can play around the template code in Developer Tools > Template. Copy/paste there what you have on your value_template, see the error and edit until it works.

The dates should be fine as you’re using yesterday and that’s added by default and in your case.

The second most likely reason is available attributes. Your configuration does not have monitored_attributes configure and therefore only includes the default attributes. As per documentation, this includes average_breath , average_heart_rate , awake_duration_in_hours , bedtime_start_hour , bedtime_end_hour , day , deep_sleep_duration_in_hours , in_bed_duration_in_hours , light_sleep_duration_in_hours , lowest_heart_rate , rem_sleep_duration_in_hours , total_sleep_duration_in_hours . In your case, you’re reading average_breath and average_heart_rate - both of which are included, hence why I say this is not the first hypothesis.

A third reason could be that the way to set up template sensors has changed. You can see how the new structure is. FWIW, I have updated the GitHub documentation too.

@akeslo

Thank you so much for putting this together. Do you have a sample image on how this may look like? I don’t currently have the custom:apexcharts-card, but happy to install if needed. Should I add this to the documentation or do you prefer to send a PR yourself?


Are you using the configuration shared here? If yes, I think the sensor name is wrong.

Your code has sensor.oura_sleep:

{{ states('sensor.oura_sleep.attributes.yesterday.average_breath') }}

However, your configuration is naming the sensor sensor.oura_sleep_metrics.

 - platform: oura
    access_token: XXXXREMOVEDXXXXX
    scan_interval: 7200
    sensors:
     sleep:
       name: oura_sleep_metrics
       max_backfill: 0
       monitored_dates:
        - 0d_ago
        - 1d_ago
        - 7d_ago 

Secondly, the template is asking for yesterday attribute. However, you’re not loading yesterday in the config and using 1d_ago instead.

I think this would work work if that’s still your setup:

{{ states('sensor.oura_sleep_metrics.attributes.1d_ago.average_breath') }}

A third reason could be that the way to set up template sensors has changed. You can see how the new structure is. Overall, the new template I use is closer to this:

{{ (state_attr('sensor.oura_sleep_metrics', '1d_ago') or {}).get('average_breath') }}

FWIW, I have updated the GitHub documentation too.

2 Likes

Here is how I combined yesterday’s daytime data of oura_heart_rate (it gets no older data, perhaps a bug in the custom integration: older daytime data are missing but they are retrievable directly via the V2 API) with older nightime data of oura_sleep (it does not have as late data as the other sensor).

type: custom:apexcharts-card
graph_span: 4d
now:
  show: true
header:
  show: true
  title: Heart Stats
  show_states: true
  colorize_states: true
series:
  - entity: sensor.oura_heart_rate
    data_generator: |
      var days = ['8d_ago', '7d_ago', '6d_ago', '5d_ago',
        '4d_ago', '3d_ago', '2d_ago', 'yesterday'];
      var res = Object.keys(entity.attributes)
        .map((day, index) => {
          var data = entity.attributes[day];
          return days.includes(day)
            ? data.map((v, i) => v.bpm
              ? [new Date(v.timestamp).getTime(), v.bpm]
              : undefined)
              .filter(x => x)
            : undefined;
        });
      res = res.reduce((a, v) => v ? a.concat(v) : a, [])
        .sort(([t1,v1], [t2, v2]) => t1 - t2);// console.log(res);
      return res;
  - entity: sensor.oura_sleep_periods
    data_generator: |
      var days = ['8d_ago', '7d_ago', '6d_ago', '5d_ago',
        '4d_ago', '3d_ago', '2d_ago', 'yesterday'];
      var res = Object.keys(entity.attributes)
        .map((day, index) => {
          var data = entity.attributes[day];
          return days.includes(day)
            ? data.reduce((a, v) => {
                var start = new Date(v.heart_rate.timestamp).getTime();
                var int_ms = v.heart_rate.interval * 1000;
                var res2 = v.heart_rate.items
                  .map((w, j) => w
                    ? [start + int_ms * j, w]
                    : undefined)
                  .filter(x => x);
                return a.concat(res2);
              }, [])
            : undefined;
        });
      res = res.reduce((a, v) => v ? a.concat(v) : a, [])
          .sort(([t1,v1], [t2, v2]) => t1 - t2);
      return res;

Your config also needs:

      sleep_periods:
        max_backfill: 3
        monitored_variables:
          - average_breath
          - average_heart_rate
          - bedtime_start_hour
          - bedtime_end_hour
          - day
          - total_sleep_duration_in_hours
          - type
          - heart_rate
        monitored_dates:
          - yesterday
          - 2d_ago
          - 3d_ago
          - 4d_ago
          - 5d_ago
          - 6d_ago
          - 7d_ago
          - 8d_ago

(similar for heart_rate sensor).

And the picture looks like this (needs polishing, I need to learn the chart):
image

Question for the experts: Can I store old data into a template sensor so that I do not have to keep re-fetching old data?

Thank you @nitobuendia - the new documentation & derived sensors example has helped a lot. I believe you may be right that the issue was either sensor naming or mixing the old / new template structures. I’m up and running and making progress now.

I’m going to spend a bit of time testing and creating data graphs and then I’d like to work towards submitting an example configuration, derived sensors and apexchart-card examples that can be included as examples in the repository. I think it would be great to have a number of examples that people can copy and paste and have something working immediately vs spending hours of our time (and yours) trying to debug newbie mistakes.

Does that sound like a good idea to you? An example-configs folder that people can submit their own working configs, derived sensors, charts?

1 Like