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'