#!/usr/bin/python2.7 # Upload CSV data to FitBit # # CSV data is assumed to be in the form date, weight # date is in the form YYYY-MM-DD (all numeric, so Jan 1, 1970 is 1970-01-01) # if your date format is something different, adjust the date parsing to suit. # weight is treated as a floating-point number; units are determined by config # # This script takes one optional parameter, which is the start date for data to # post. The FitBit API is limited to 150 calls per hour, so if you've got more # than 150 datapoints to upload there's a good chance it'll do the first 150 # then exit with a rate-limit error. It will include the date for the data it # was trying to post; you can re-run the script at the top of the next hour # with this date as the sole parameter, and it'll pick up from where it left # off. Ideally the script would handle this, but the python library doesn't # pass back the rate-limiting headers that would allow you to figure out how # long to wait before running again, so I didn't bother. # # config file is $HOME/.api_keys (because I started out with a Perl library that # used this path). Contents: # # [fitbit] # fitbit_uploader_oauth_consumer_key=X # fitbit_uploader_oauth_shared_secret=Y # oauth_token=A # oauth_token_secret=B # unit_system=U # # set U appropriately per https://dev.fitbit.com/docs/basics/#units. # # X and Y you get by registering an application at # https://dev.fitbit.com/apps/new # # A and B are messier; the python-fitbit code comes with a gather_keys_cli.py # script which you can run with values X and Y as parameters; it'll open a # browser window where you authorise access, then it'll dump out a bunch of # values containing encoded_user_id and the two oauth_token values. Insert them # into .api_keys and you should be ready to go. # import ConfigParser import csv import datetime import logging import os import sys # http://python-fitbit.readthedocs.org/en/latest/ - you will need to download # and install this. import fitbit # should be generally safe to leave the rest of this script unmodified config = ConfigParser.SafeConfigParser() if not config.read(os.path.join(os.environ['HOME'], '.api_keys')): raise Error('Unable to read .api_keys file') if not config.items('fitbit'): raise ValueError('No [fitbit] section in .api_keys file') logging.basicConfig(level=logging.DEBUG) def upload_weight(date, weight): logging.debug('Posting weight data for {}'.format(date)) res = authd_client.body(date=date, data={u'weight': weight }) return res authd_client = fitbit.Fitbit( config.get('fitbit', 'fitbit_uploader_oauth_consumer_key'), config.get('fitbit', 'fitbit_uploader_oauth_shared_secret'), resource_owner_key=config.get('fitbit', 'oauth_token'), resource_owner_secret=config.get('fitbit', 'oauth_token_secret')) authd_client.system = config.get('fitbit', 'unit_system') # if a start date is specified, use it. if len(sys.argv) > 2: start_at_str = sys.argv[2] start_at_date = datetime.datetime.strptime(start_at_str, '%Y-%m-%d') with open(sys.argv[1], 'rb') as csvfile: csvreader = csv.reader(csvfile) for row in csvreader: if row[0] == 'Date': continue date = datetime.datetime.strptime(row[0], '%Y-%m-%d') if not start_at_date: start_at_date = date if date < start_at_date: logging.debug('{} is before {}, skipping'.format(date, start_at_date)) continue weight = row[1].replace('.0', '') if weight == '0': logging.warn('Skipping weight for {} as it is zero'.format(date)) continue try: res = upload_weight(date, weight) except fitbit.exceptions.HTTPTooManyRequests as e: logging.error('Rate limit exceeded when logging data for {}'.format(date)) raise b = res.get(u'body') if not b: raise ValueError('Body tag not found when logging data for {}'.format(date)) w = b.get(u'weight') if str(w) != str(weight): raise ValueError('{} != {} when logging data for {}'.format(w, weight, date))