Monthly Archives: December 2014

Building an Exercise and Weight Display: Part 1

[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.

Then, I was off to the races. Starting by stealing some API access code from Dr. Drang, I used my new Access Token to create some Python code that downloads my run data and my weight data. Basically, you request a URL with a couple extra headers (including the Access Token) in the HTTP request, and you get back JSON blobs of data. I then wrote code that processed those blobs into a graph and extracted the bits of raw data that I wanted to display. At that point, I needed to get the raw data over to Übersicht to format for display. I puzzled for a bit about how to do this, and I wound up with an obvious solution—another JSON blob. Since Übersicht uses a JavaScript-related language called CoffeeScript, I assumed that processing the JSON would be straightforward, and it was.

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()

Each call to ‘json.loads()’ parses a JSON blob and turns it into a Python dictionary. The structure of these dictionaries (JavaScript Objects) is explained in the HealthGraph API documentation. Here is the documentation of the fitness_activities blob, and here is the documentation for the weight blob. There is no real error checking, and your mileage may vary, etc.

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.

Bits and Pieces

  • Blogging 101 wasn’t for me. I sortof knew that it was for new bloggers, but I thought that I could make it work. I couldn’t or didn’t. The assignments didn’t appeal to me. Besides, November (and the first half of December) is a busy time in academia. So, it didn’t really happen. So it goes.

  • But, I’ve given up social media for Advent. So, maybe while I’m absent from Twitter I’ll blog more. Maybe. Do people give things up for Advent? Not that I know of. But I have to take periodic breaks from social media or it starts to make me crazy. Advent and Lent seemed like one way to do it; both are seasons of waiting and preparation.

  • If you miss me that much, though, I’ve decided that Instagram doesn’t count. (Mostly because I follow only a few people on Instagram and the volume of posts is light.) Will I blog more often or Instagram more often? Yes. Maybe.

  • Every December for the last several years I’ve hit Instapaper zero. It happened by accident the first time or two—Winter Break is conducive to reading all the things—but for the last couple of years it has been intentional. And as I started making my way through my queue this year, I finally read Ta-Nehisi Coates’s piece on reparations from last June. Wow. There is so much about the history of racism in the United States that I was never taught. The piece was widely lauded, so I’m late to the party, but it’s probably the best thing I’ve read this year.

  • On a lighter note, I’m a long time fan of Merlin Mann, and hence a long time listener to his podcast with Dan Benjamin, Back to Work. If I may be so bold, it is a lot of nonsense punctuated by moments of brilliance. And in the most recent episode, a little after 1:27, Merlin says, “The problem is you still have hope about email. That is hell. Hell is believing that there is hope in email. The only thing that can actually help you is to realize that it is completely hopeless.” My name is Allen, and I have an email problem.

  • And today, after finishing up that episode, I was listening to this fantastic interview of Ira Glass by Alec Baldwin. So, so good. I don’t listen to Alec Baldwin’s show very often, but maybe I should. (I wrote a whole post about his interview of Billy Joel back in 2012.)