From 781b625aa5d804acbbf8a531bdcc3163d0555272 Mon Sep 17 00:00:00 2001 From: John Tantalo Date: Mon, 15 Dec 2014 08:10:06 -0800 Subject: [PATCH] copy weight data from fitbit to google --- .gitignore | 2 + README | 3 + auth_google.py | 17 ++++++ fitsync.py | 142 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 16 ++++++ 5 files changed, 180 insertions(+) create mode 100644 README create mode 100644 auth_google.py create mode 100644 fitsync.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index db4561e..edb4c63 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ docs/_build/ # PyBuilder target/ +google.json +fitbit.yaml diff --git a/README b/README new file mode 100644 index 0000000..7b5c3f0 --- /dev/null +++ b/README @@ -0,0 +1,3 @@ +Fitsync is a python app that syncs data between Fitbit and Google Fit. + +Currently, it only copies weight data from Fitbit to Google. diff --git a/auth_google.py b/auth_google.py new file mode 100644 index 0000000..ceec9c2 --- /dev/null +++ b/auth_google.py @@ -0,0 +1,17 @@ +import sys + +from oauth2client.file import Storage +from oauth2client.client import OAuth2WebServerFlow +from oauth2client.tools import run_flow, argparser + +def main(): + client_id = sys.argv[1] + client_secret = sys.argv[2] + scope = sys.argv[3] + flow = OAuth2WebServerFlow(client_id, client_secret, scope) + storage = Storage('google.json') + flags = argparser.parse_args([]) + run_flow(flow, storage, flags) + +if __name__ == '__main__': + main() diff --git a/fitsync.py b/fitsync.py new file mode 100644 index 0000000..49e0ead --- /dev/null +++ b/fitsync.py @@ -0,0 +1,142 @@ +import httplib2 +import sys +from time import time as now +import yaml + +import fitbit +from apiclient.discovery import build +from oauth2client.file import Storage +from oauth2client.client import OAuth2Credentials +from googleapiclient.errors import HttpError + + +POUNDS_PER_KILOGRAM = 2.20462 + + +def GetFitbitClient(): + credentials = yaml.load(open('fitbit.yaml')) + client = fitbit.Fitbit(**credentials) + return client + + +def GetGoogleClient(): + credentials = Storage('google.json').get() + http = credentials.authorize(httplib2.Http()) + client = build('fitness', 'v1', http=http) + return client + + +def nano(val): + """Converts a number to nano (str).""" + return '%d' % (val * 1e9) + + +def FitbitWeightToGoogleWeight(fitbitWeightLog): + logSecs = fitbitWeightLog['logId'] / 1000 + logWeightLbs = fitbitWeightLog['weight'] + logWeightKg = logWeightLbs / POUNDS_PER_KILOGRAM + return dict( + dataTypeName='com.google.weight', + endTimeNanos=nano(logSecs), + startTimeNanos=nano(logSecs), + value=[dict(fpVal=logWeightKg)], + ) + + +def GetDataSourceId(dataSource): + projectNumber = Storage('google.json').get().client_id.split('-')[0] + return ':'.join(( + dataSource['type'], + dataSource['dataType']['name'], + projectNumber, + dataSource['device']['manufacturer'], + dataSource['device']['model'], + dataSource['device']['uid'])) + + +def main(): + fitbitClient = GetFitbitClient() + + devices = fitbitClient.get_devices() + (scale,) = (device for device in devices if device['type'] == 'SCALE') + + fitbitBodyweight = fitbitClient.get_bodyweight(period='30d') + fitbitWeightLogs = fitbitBodyweight['weight'] + fitbitWeightLogTimes = [log['logId'] / 1000 for log in fitbitWeightLogs] + + minLogNs = nano(min(fitbitWeightLogTimes)) + maxLogNs = nano(max(fitbitWeightLogTimes)) + + googleWeightLogs = [FitbitWeightToGoogleWeight(log) + for log in fitbitWeightLogs] + + googleClient = GetGoogleClient() + + dataSource = dict( + type='raw', + application=dict(name='fitsync'), + dataType=dict( + name='com.google.weight', + field=[dict(format='floatPoint', name='weight')] + ), + device=dict( + type='scale', + manufacturer='unknown', + model='unknown', + uid=scale['id'], + version=scale['deviceVersion'], + ) + ) + + dataSourceId = GetDataSourceId(dataSource) + + # Ensure datasource exists for the device. + try: + googleClient.users().dataSources().get( + userId='me', + dataSourceId=dataSourceId) + except HttpError, error: + if not 'DataSourceId not found' in str(error): + raise error + # Doesn't exist, so create it. + googleClient.users().dataSources().create( + userId='me', + body=dataSource) + + datasetId = '%s-%s' % (minLogNs, maxLogNs) + + command = 'patch' + if len(sys.argv) > 1: + command = sys.argv[1] + + # Get weight dataset. + if command == 'get': + print googleClient.users().dataSources().datasets().get( + userId='me', + dataSourceId=dataSourceId, + datasetId=datasetId).execute() + + # Delete weight dataset. + if command == 'delete': + googleClient.users().dataSources().datasets().delete( + userId='me', + dataSourceId=dataSourceId, + datasetId=datasetId).execute() + print "deleted data" + + # Upload weight dataset. + if command == 'patch': + print googleClient.users().dataSources().datasets().patch( + userId='me', + dataSourceId=dataSourceId, + datasetId=datasetId, + body=dict( + dataSourceId=dataSourceId, + maxEndTimeNs=maxLogNs, + minStartTimeNs=minLogNs, + point=googleWeightLogs, + )).execute() + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5f5c972 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +PyYAML==3.11 +fitbit==0.1.2 +google-api-python-client==1.3.1 +httplib2==0.9 +oauth2client==1.4.3 +oauthlib==0.7.2 +pyasn1==0.1.7 +pyasn1-modules==0.0.5 +python-dateutil==2.3 +requests==2.5.0 +requests-oauthlib==0.4.2 +rsa==3.1.4 +simplejson==3.6.5 +six==1.8.0 +uritemplate==0.6 +wsgiref==0.1.2