Beeminder Forum

Oura integration

I have an Oura ring I’ve been using for quite a while now. The thing I pay most attention to is the temperature data, actually, because it gives me a window into my hormonal cycles, but it was also useful when I was working through some sleep problems. And it’s the least offensive step-tracker ever. In fact, it’s like, past “inoffensive” and into “maybe actively nice” to wear.

I haven’t paid much attention to the recovery and readiness metrics. It’s kind of like, uh, I mean, it matches what I already know? Like “I didn’t get enough sleep and Oura says my readiness is 67.” I don’t know. I can tell when I’m in garbage physical state.

Anyway, I set up a Beeminder goal this year at something like a 10% increase over last year’s average daily step count. That’s a rate of 9320 steps per day. It’s been mostly quite easy to stay ahead of, and I now have an autoratchet of 2 days on it, so it periodically trims off extra safety buffer. I’m looking forward to seeing what my actual average over the year winds up being!

Ok, but on to the tech. So Oura has a nice api and in addition to an Oauth2 implementation for people who want to build a client app (perhaps in Beeminder’s future?), they have a nice interface for generating personal auth tokens. You can generate multiple ones, name them, and revoke them individually, so that’s pretty nice if you want to fool around with your own scripts, but also if someone else doesn’t want to deal with OAuth2, but you want to use their stuff, you can make a PAT specific for that use, and then you’ve got control to revoke just that one token without messing up your other scripts or whatever.

Ok, so it’s a nice and simple api. It was pretty straightforward grabbing steps and syncing them with Beeminder.

#! /usr/bin/ruby
# A script to fetch my steps from Oura and update my beeminder step goal
# If run with no arguments it fetches the last 5 days from Oura, and updates
# beeminder datapoints.

require 'httparty'

OPAT = "REDACTED"
OURL= "https://api.ouraring.com/v1/"
BPAT = "REDACTED"
BURL= "https://www.beeminder.com/api/v1/"
SID = 24*60*60

stepurl = BURL+"users/b/goals/step2021"
now = Time.now
params = {
  start: (now - SID*5).strftime('%F'), 
  end: now.strftime('%F')
} 

headers = {"Authorization": "Bearer #{OPAT}"}
activity = HTTParty.get(OURL+"activity", headers: headers, query: params).parsed_response["activity"]
# sleep = HTTParty.get(OURL+"sleep", headers: headers, query: params).parsed_response["sleep"]

# transform the data into date,value pairs
activity.map!{|act| 
  [Date.parse(act["summary_date"]), act["steps"]]
}

data = HTTParty.get(stepurl+"/datapoints.json", query: {auth_token: BPAT, count: 6, sort: "daystamp"}).parsed_response

# check if Beeminder has an existing datapoint before adding our data
activity.each {|date,steps|
  datapt = data.select{|dat| dat["daystamp"] == date.strftime('%Y%m%d')}.first
  if datapt == nil
    # add a datapoint
    resp = HTTParty.post(stepurl + "/datapoints.json", body: {
      auth_token: BPAT, 
      value: steps, 
      daystamp: date.strftime('%Y%m%d'), 
      comment: "From Oura. Entered at #{Time.now.iso8601}"
    })
  elsif datapt && datapt["value"] < steps
    # ok, but there is a datapoint and it has less steps than oura 
    resp = HTTParty.put(stepurl + "/datapoints/#{datapt["id"]}.json", body: {
      auth_token: BPAT,
      value: steps,
      daystamp: date.strftime('%Y%m%d'),
      comment: "#{datapt["comment"]}; Up:#{Time.now.strftime('%H:%M')}"
    })
  end
}

The hard part about this actually turned out to be getting my mac (running Big Sur) to run the dang thing in a cron job.

First I had to add cron to the ‘Full Disk Access’ permission in the Privacy settings of ‘Security & Privacy’ in System Preferences. Otherwise cron can’t actually run programs on a Mac any longer? It seems like their attempts to keep me safe from myself are making my mac less appealing as a tool for programming… anyway.

The second problem with running this script from cron was related to my ruby environment and the gems that were available etc. I have rbenv installed controlling my ruby version, but when cron runs it doesn’t load my .rc and so it was executing the script using the system ruby which didn’t have my httparty gem installed. So this is what my cron entry wound up looking like in order to run the script:

50 * * * * /bin/zsh -c 'export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH"; ruby $HOME/bin/fetchstepsoura.rb'

6 Likes

Hmmmm. One of my friends (who does triathalons) is a big fan of her Oura; hearing that it has a pleasant API is a huge plus in its favor. Once my fitbit bites the dust, I might head Oura-wards. Bookmarking this thread for if that ever happens! :slight_smile:

1 Like

@bee Nice write-up! It seems that since 10 days ago, it is now required for laggards to get a $6 a month membership to use the app[0]. It is not clear to me if the ring is usable without the app. I.e. would we be able to get the data out using the API?

[0] https://support.ouraring.com/hc/en-us/articles/4409231414163-Upgrading-from-Gen2-to-Gen3

1 Like

If you already own an Oura Ring, you do not need the membership to use the app.

From the FAQs:

Can I continue to use my Generation 2 Oura Ring without an Oura Membership?

Yes. If you’re currently a Gen2 Oura Ring member and decide to stick with your Gen2 ring without upgrading to the new Gen3 ring, we’ll continue to support your product with routine software and firmware updates for the foreseeable future. You will not be charged a monthly membership fee. Please note that Oura Membership only applies to the Gen3 ring.

You’ll still have access to all the same features currently available on your Gen2 ring with the exception of one change to our Moment feature. You’ll still have access to unguided Moment sessions, but due to technical limitations, guided audio Moment sessions will be removed from the Gen2 experience. All existing guided audio sessions will be available in the new Explore tab for Gen3 users. Due to the technological differences between Gen2 and Gen3, as we continue to release new features, many of them may not be available on Gen2 due to limitations in its hardware components.

And given this wording, I assume the API will still be accessible:

You’ll still have access to all the same features currently available on your Gen2 ring

1 Like