[I’ve called this Part 1. Let’s see if I get around to writing parts 2–4.]
While doing some overplanning in the last few weeks of the year, I built a few text files and spreadsheets. In the process, I decided that some sort of display would be worthwhile for keeping track of a few goals that I had developed. I thought about Dashboard widgets, but they seem to be waning. And then I thought about NerdTool/GeekTool, but those never worked very well for me when switching monitor resolutions, which I do often. And then a friend mentioned Übersicht, which I didn’t think I had heard of, but Brett Terpstra has written a couple of widgets. So, I’ve probably at least heard of it.
So, I played with some of the widgets from the gallery, and developed the beginnings of the heads up display that I wanted. But, one of the things I wanted was a way to track weight and exercise. I’ve been tracking my running for the last few years in RunKeeper, and I’ve been tracking my weight, off and on, in Weighbot. So, the first question was how to get the data out.
Weighbot presented a problem. It is mostly an iOS app, although it has a cloud backup service. The cloud backup service will let you download all your data in a CSV file from a website. This could probably be automated. But, my tests suggested that Weighbot usually did a backup immediately upon app launch. My usual usage pattern is that once a day I launch the app, enter my weight, and then close the app. The backup on launch, though, meant that the data set I downloaded was always missing today’s data point, unless I forced an additional backup. Then I realized, though, that RunKeeper could also track weight. I put a couple weeks worth of data points in, and I was off to the races.
RunKeeper has an API called HealthGraph that lets other apps access their data. I assumed that access to this API would be simple, like for Flickr or Forecast.io. But it’s a little more involved. First, you register, and they give you a Client ID and a Client Secret. You then use the Client ID to get RunKeeper to generate a Code. You then use the Client ID, Client Secret, and Code to generate an Access Token, which finally lets you access the data. Figuring all this out and making it work seamlessly is probably worthwhile if you are building an app for the masses, but it was quite a lot of trouble for a one-off data display. Luckily, this blog post walks you through the steps and provides the necessary snippets of code.
Here’s the code that downloads the necessary JSON blobs. It requires a bit of explanation. First, it gets the Access Token from a file called .runkeeper that I keep in my home directory. Then, it makes an initial call to the RunKeeper API to get a JSON blob of user information. In fact, with the exception of a User ID, this blob contains almost no actual user information. What it contains, instead, are pointers to the URLs to use to obtain the desired JSON blobs. In fact, these seem to be static strings, but the API documentation insists that they should not be hardcoded. Thus, the next two calls to the RunKeeper API use the information from the user blob to request the fitness_activities blob and the weight blob.
#!/usr/bin/python # coding=utf-8 from __future__ import division from __future__ import unicode_literals import json import urllib2 from os import environ # Get my Runkeeper Access Token try: with open(environ['HOME'] + '/.runkeeper') as rcfile: for line in rcfile: k, v = line.split(':') if k.strip() == 'access_token': AccessToken = v.strip() except (IOError, NameError): print "Failed to get Access Token" exit() # Get the user from Runkeeper try: userReq = urllib2.Request('http://api.runkeeper.com/user') userReq.add_header('Authorization', 'Bearer ' + AccessToken) userReq.add_header('Accept', 'application/vnd.com.runkeeper.User+json') userJsonString = urllib2.urlopen(userReq).read() user = json.loads(userJsonString); except (IOError, ValueError): print "Connection failure for user." exit() # Get the fitness activities from Runkeeper try: fitnessReq = urllib2.Request('http://api.runkeeper.com' + user['fitness_activities']) fitnessReq.add_header('Authorization', 'Bearer ' + AccessToken) fitnessReq.add_header('Accept', 'application/vnd.com.runkeeper.FitnessActivityFeed+json') fitnessJsonString = urllib2.urlopen(fitnessReq).read() fitness = json.loads(fitnessJsonString); except (IOError, ValueError): print "Connection failure for fitness activities." exit() # Get the weight from Runkeeper try: weightReq = urllib2.Request('http://api.runkeeper.com' + user['weight']) weightReq.add_header('Authorization', 'Bearer ' + AccessToken) weightReq.add_header('Accept', 'application/vnd.com.runkeeper.WeightSetFeed+json') weightJsonString = urllib2.urlopen(weightReq).read() weight = json.loads(weightJsonString); except (IOError, ValueError): print "Connection failure for weight data." exit()
It’s been a fun little project. If I were to write a second part of this blog post, it would be on extracting the running data for display (and retrieving my training plan from a text file for display, as well). If I were to write a third part, it would be on extracting the weight data, computing my weight goals, and creating a plot of both the data and the goals with Matplotlib. And if I were to write a fourth part, it would be on writing the HTML, CoffeeScript, and CSS to display everything as an Übersicht widget, which was by far the most painful part of the whole process. But I’m a professor, so I may leave those tasks as an exercise for the reader.