From 6d2102ab8b79b1553478a1085b6321fca357c97a Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 19 Oct 2015 20:09:44 -0600 Subject: [PATCH 1/2] Better command-line argument parsing --- README.md | 9 +++++++-- fitsync.py | 55 +++++++++++++++++++++++++++++++++--------------------- 2 files changed, 41 insertions(+), 23 deletions(-) mode change 100644 => 100755 fitsync.py diff --git a/README.md b/README.md index 3f2e0b8..0966a27 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ This script opens a browser window where you can log in to your Google account a ## Usage -Run `fitsync.py` to download data from Fitbit and upload to Google, +Use the `patch` command to download data from Fitbit and upload to Google, - $ python fitsync.py + $ python fitsync.py patch Use the `delete` command to remove data from Google, @@ -52,6 +52,11 @@ Use the `get` command to see the data stored in Google, $ python fitsync.py get +There are a few options supported, + + $ python fitsync.py --help + + ## See your data To view weight data on [Google Fit](https://fit.google.com), diff --git a/fitsync.py b/fitsync.py old mode 100644 new mode 100755 index 625f314..bdb03cc --- a/fitsync.py +++ b/fitsync.py @@ -1,7 +1,11 @@ +#!/usr/bin/env python + import httplib2 import sys import time import yaml +import argparse +import logging import fitbit from apiclient.discovery import build @@ -14,17 +18,20 @@ POUNDS_PER_KILOGRAM = 2.20462 TIME_FORMAT = "%a, %d %b %Y %H:%M:%S" - -def GetFitbitClient(): - credentials = yaml.load(open('fitbit.yaml')) +def GetFitbitClient(filename): + logging.debug("Creating Fitbit client") + credentials = yaml.load(open(filename)) client = fitbit.Fitbit(**credentials) + logging.debug("Fitbit client created") return client -def GetGoogleClient(): - credentials = Storage('google.json').get() +def GetGoogleClient(filename): + logging.debug("Creating Google client") + credentials = Storage(filename).get() http = credentials.authorize(httplib2.Http()) client = build('fitness', 'v1', http=http) + logging.debug("Google client created") return client @@ -57,12 +64,23 @@ def GetDataSourceId(dataSource): def main(): - fitbitClient = GetFitbitClient() + parser = argparse.ArgumentParser("Transfer Fitbit weight data to Google Fit") + parser.add_argument("command", choices=('patch', 'get', 'delete'), help="What to do") + parser.add_argument("-d", "--debug", action="count", default=0, help="Increase debugging level") + parser.add_argument("-g", "--google-creds", default="google.json", help="Google credentials file") + parser.add_argument("-f", "--fitbit-creds", default="fitbit.yaml", help="Fitbit credentials file") + args = parser.parse_args() + + debugLevel = logging.WARNING - (args.debug * 10) + logging.basicConfig(level=max(debugLevel, 0)) + logging.root.name = "fitsync" + + fitbitClient = GetFitbitClient(args.fitbit_creds) devices = fitbitClient.get_devices() (scale,) = (device for device in devices if device['type'] == 'SCALE') - fitbitBodyweight = fitbitClient.get_bodyweight(period='30d') + fitbitBodyweight = fitbitClient.get_bodyweight(period='1m') fitbitWeightLogs = fitbitBodyweight['weight'] fitbitWeightLogTimes = [log['logId'] / 1000 for log in fitbitWeightLogs] @@ -72,7 +90,7 @@ def main(): googleWeightLogs = [FitbitWeightToGoogleWeight(log) for log in fitbitWeightLogs] - googleClient = GetGoogleClient() + googleClient = GetGoogleClient(args.google_creds) dataSource = dict( type='raw', @@ -121,12 +139,8 @@ def main(): set(point['startTimeNanos'] for point in left['point']) - set(point['startTimeNanos'] for point in right['point'])) - command = 'patch' - if len(sys.argv) > 1: - command = sys.argv[1] - # Get weight dataset. - if command == 'get': + if args.command == 'get': data = GetData() numpoints = 0 for point in data['point']: @@ -136,22 +150,22 @@ def main(): readableTime = time.strftime(TIME_FORMAT, time.localtime(startTimeSecs)) weightKgs = float(fpVal) weightLbs = float(fpVal) * POUNDS_PER_KILOGRAM - print "%.1f lbs ( %.2f kgs ), %s" % (weightLbs, weightKgs, readableTime) + print("%.1f lbs ( %.2f kgs ), %s" % (weightLbs, weightKgs, readableTime)) numpoints += 1 - print "Total %d points (in Google Fit)" % numpoints + print("Total %d points (in Google Fit)" % numpoints) # Delete weight dataset. - elif command == 'delete': + elif args.command == 'delete': dataPrior = GetData() googleClient.users().dataSources().datasets().delete( userId='me', dataSourceId=dataSourceId, datasetId=datasetId).execute() dataPost = GetData() - print "Deleted %d points (from Google Fit)" % PointsDifference(dataPrior, dataPost) + print("Deleted %d points (from Google Fit)" % PointsDifference(dataPrior, dataPost)) # Upload weight dataset. - elif command == 'patch': + elif args.command == 'patch': dataPrior = GetData() googleClient.users().dataSources().datasets().patch( userId='me', @@ -164,10 +178,8 @@ def main(): point=googleWeightLogs, )).execute() dataPost = GetData() - print "Added %d points (to Google Fit)" % PointsDifference(dataPost, dataPrior) + print("Added %d points (to Google Fit)" % PointsDifference(dataPost, dataPrior)) - else: - print "bad command" def PointInData(startTimeNanos, data): @@ -179,3 +191,4 @@ def PointInData(startTimeNanos, data): if __name__ == '__main__': main() + From 4f02093bc922b94fb3169f06394afd00110dbc00 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 20 Oct 2015 13:12:44 -0600 Subject: [PATCH 2/2] Fix timezone problems --- fitsync.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/fitsync.py b/fitsync.py index bdb03cc..1754de9 100755 --- a/fitsync.py +++ b/fitsync.py @@ -6,6 +6,9 @@ import time import yaml import argparse import logging +import datetime +import dateutil.tz +import dateutil.parser import fitbit from apiclient.discovery import build @@ -35,13 +38,21 @@ def GetGoogleClient(filename): return client +dawnOfTime = datetime.datetime(1970, 1, 1, tzinfo=dateutil.tz.tzutc()) + +def epochOfFitbitLog(logEntry, tzinfo): + logTimestamp = "{} {}".format(logEntry["date"], logEntry["time"]) + logTime = dateutil.parser.parse(logTimestamp).replace(tzinfo=tzinfo) + return (logTime - dawnOfTime).total_seconds() + def nano(val): """Converts a number to nano (str).""" return '%d' % (val * 1e9) -def FitbitWeightToGoogleWeight(fitbitWeightLog): - logSecs = fitbitWeightLog['logId'] / 1000 +def FitbitWeightToGoogleWeight(fitbitWeightLog, tzinfo): + logSecs = epochOfFitbitLog(fitbitWeightLog, tzinfo) + logWeightLbs = fitbitWeightLog['weight'] logWeightKg = logWeightLbs / POUNDS_PER_KILOGRAM return dict( @@ -77,17 +88,20 @@ def main(): fitbitClient = GetFitbitClient(args.fitbit_creds) + userProfile = fitbitClient.user_profile_get() + tzinfo = dateutil.tz.gettz(userProfile['user']['timezone']) + devices = fitbitClient.get_devices() (scale,) = (device for device in devices if device['type'] == 'SCALE') fitbitBodyweight = fitbitClient.get_bodyweight(period='1m') fitbitWeightLogs = fitbitBodyweight['weight'] - fitbitWeightLogTimes = [log['logId'] / 1000 for log in fitbitWeightLogs] + fitbitWeightLogTimes = [epochOfFitbitLog(log, tzinfo) for log in fitbitWeightLogs] minLogNs = nano(min(fitbitWeightLogTimes)) maxLogNs = nano(max(fitbitWeightLogTimes)) - googleWeightLogs = [FitbitWeightToGoogleWeight(log) + googleWeightLogs = [FitbitWeightToGoogleWeight(log, tzinfo) for log in fitbitWeightLogs] googleClient = GetGoogleClient(args.google_creds) @@ -131,7 +145,8 @@ def main(): dataSourceId=dataSourceId, datasetId=datasetId).execute() #insert empty 'point' when there is nothing - if 'point' not in ret: ret['point']=[] + if 'point' not in ret: + ret['point']=[] return ret def PointsDifference(left, right):