<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/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) &gt; 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 &lt; 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))
</pre></body></html>