Merge upstream (removes a newline I added)
This commit is contained in:
@@ -40,9 +40,9 @@ This script opens a browser window where you can log in to your Google account a
|
|||||||
|
|
||||||
## Usage
|
## 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,
|
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
|
$ python fitsync.py get
|
||||||
|
|
||||||
|
There are a few options supported,
|
||||||
|
|
||||||
|
$ python fitsync.py --help
|
||||||
|
|
||||||
|
|
||||||
## See your data
|
## See your data
|
||||||
|
|
||||||
To view weight data on [Google Fit](https://fit.google.com),
|
To view weight data on [Google Fit](https://fit.google.com),
|
||||||
|
|||||||
+53
-26
@@ -1,8 +1,14 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import httplib2
|
import httplib2
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import yaml
|
import yaml
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import datetime
|
||||||
|
import dateutil.tz
|
||||||
|
import dateutil.parser
|
||||||
|
|
||||||
import fitbit
|
import fitbit
|
||||||
from apiclient.discovery import build
|
from apiclient.discovery import build
|
||||||
@@ -15,27 +21,38 @@ POUNDS_PER_KILOGRAM = 2.20462
|
|||||||
|
|
||||||
TIME_FORMAT = "%a, %d %b %Y %H:%M:%S"
|
TIME_FORMAT = "%a, %d %b %Y %H:%M:%S"
|
||||||
|
|
||||||
|
def GetFitbitClient(filename):
|
||||||
def GetFitbitClient():
|
logging.debug("Creating Fitbit client")
|
||||||
credentials = yaml.load(open('fitbit.yaml'))
|
credentials = yaml.load(open(filename))
|
||||||
client = fitbit.Fitbit(**credentials)
|
client = fitbit.Fitbit(**credentials)
|
||||||
|
logging.debug("Fitbit client created")
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
def GetGoogleClient():
|
def GetGoogleClient(filename):
|
||||||
credentials = Storage('google.json').get()
|
logging.debug("Creating Google client")
|
||||||
|
credentials = Storage(filename).get()
|
||||||
http = credentials.authorize(httplib2.Http())
|
http = credentials.authorize(httplib2.Http())
|
||||||
client = build('fitness', 'v1', http=http)
|
client = build('fitness', 'v1', http=http)
|
||||||
|
logging.debug("Google client created")
|
||||||
return client
|
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):
|
def nano(val):
|
||||||
"""Converts a number to nano (str)."""
|
"""Converts a number to nano (str)."""
|
||||||
return '%d' % (val * 1e9)
|
return '%d' % (val * 1e9)
|
||||||
|
|
||||||
|
|
||||||
def FitbitWeightToGoogleWeight(fitbitWeightLog):
|
def FitbitWeightToGoogleWeight(fitbitWeightLog, tzinfo):
|
||||||
logSecs = fitbitWeightLog['logId'] / 1000
|
logSecs = epochOfFitbitLog(fitbitWeightLog, tzinfo)
|
||||||
|
|
||||||
logWeightLbs = fitbitWeightLog['weight']
|
logWeightLbs = fitbitWeightLog['weight']
|
||||||
logWeightKg = logWeightLbs / POUNDS_PER_KILOGRAM
|
logWeightKg = logWeightLbs / POUNDS_PER_KILOGRAM
|
||||||
return dict(
|
return dict(
|
||||||
@@ -58,22 +75,36 @@ def GetDataSourceId(dataSource):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
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)
|
||||||
|
|
||||||
|
userProfile = fitbitClient.user_profile_get()
|
||||||
|
tzinfo = dateutil.tz.gettz(userProfile['user']['timezone'])
|
||||||
|
|
||||||
devices = fitbitClient.get_devices()
|
devices = fitbitClient.get_devices()
|
||||||
(scale,) = (device for device in devices if device['type'] == 'SCALE')
|
(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']
|
fitbitWeightLogs = fitbitBodyweight['weight']
|
||||||
fitbitWeightLogTimes = [log['logId'] / 1000 for log in fitbitWeightLogs]
|
fitbitWeightLogTimes = [epochOfFitbitLog(log, tzinfo) for log in fitbitWeightLogs]
|
||||||
|
|
||||||
minLogNs = nano(min(fitbitWeightLogTimes))
|
minLogNs = nano(min(fitbitWeightLogTimes))
|
||||||
maxLogNs = nano(max(fitbitWeightLogTimes))
|
maxLogNs = nano(max(fitbitWeightLogTimes))
|
||||||
|
|
||||||
googleWeightLogs = [FitbitWeightToGoogleWeight(log)
|
googleWeightLogs = [FitbitWeightToGoogleWeight(log, tzinfo)
|
||||||
for log in fitbitWeightLogs]
|
for log in fitbitWeightLogs]
|
||||||
|
|
||||||
googleClient = GetGoogleClient()
|
googleClient = GetGoogleClient(args.google_creds)
|
||||||
|
|
||||||
dataSource = dict(
|
dataSource = dict(
|
||||||
type='raw',
|
type='raw',
|
||||||
@@ -114,7 +145,8 @@ def main():
|
|||||||
dataSourceId=dataSourceId,
|
dataSourceId=dataSourceId,
|
||||||
datasetId=datasetId).execute()
|
datasetId=datasetId).execute()
|
||||||
#insert empty 'point' when there is nothing
|
#insert empty 'point' when there is nothing
|
||||||
if 'point' not in ret: ret['point']=[]
|
if 'point' not in ret:
|
||||||
|
ret['point']=[]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def PointsDifference(left, right):
|
def PointsDifference(left, right):
|
||||||
@@ -122,12 +154,8 @@ def main():
|
|||||||
set(point['startTimeNanos'] for point in left['point']) -
|
set(point['startTimeNanos'] for point in left['point']) -
|
||||||
set(point['startTimeNanos'] for point in right['point']))
|
set(point['startTimeNanos'] for point in right['point']))
|
||||||
|
|
||||||
command = 'patch'
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
command = sys.argv[1]
|
|
||||||
|
|
||||||
# Get weight dataset.
|
# Get weight dataset.
|
||||||
if command == 'get':
|
if args.command == 'get':
|
||||||
data = GetData()
|
data = GetData()
|
||||||
numpoints = 0
|
numpoints = 0
|
||||||
for point in data['point']:
|
for point in data['point']:
|
||||||
@@ -137,22 +165,22 @@ def main():
|
|||||||
readableTime = time.strftime(TIME_FORMAT, time.localtime(startTimeSecs))
|
readableTime = time.strftime(TIME_FORMAT, time.localtime(startTimeSecs))
|
||||||
weightKgs = float(fpVal)
|
weightKgs = float(fpVal)
|
||||||
weightLbs = float(fpVal) * POUNDS_PER_KILOGRAM
|
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
|
numpoints += 1
|
||||||
print "Total %d points (in Google Fit)" % numpoints
|
print("Total %d points (in Google Fit)" % numpoints)
|
||||||
|
|
||||||
# Delete weight dataset.
|
# Delete weight dataset.
|
||||||
elif command == 'delete':
|
elif args.command == 'delete':
|
||||||
dataPrior = GetData()
|
dataPrior = GetData()
|
||||||
googleClient.users().dataSources().datasets().delete(
|
googleClient.users().dataSources().datasets().delete(
|
||||||
userId='me',
|
userId='me',
|
||||||
dataSourceId=dataSourceId,
|
dataSourceId=dataSourceId,
|
||||||
datasetId=datasetId).execute()
|
datasetId=datasetId).execute()
|
||||||
dataPost = GetData()
|
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.
|
# Upload weight dataset.
|
||||||
elif command == 'patch':
|
elif args.command == 'patch':
|
||||||
dataPrior = GetData()
|
dataPrior = GetData()
|
||||||
googleClient.users().dataSources().datasets().patch(
|
googleClient.users().dataSources().datasets().patch(
|
||||||
userId='me',
|
userId='me',
|
||||||
@@ -165,10 +193,8 @@ def main():
|
|||||||
point=googleWeightLogs,
|
point=googleWeightLogs,
|
||||||
)).execute()
|
)).execute()
|
||||||
dataPost = GetData()
|
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):
|
def PointInData(startTimeNanos, data):
|
||||||
@@ -180,3 +206,4 @@ def PointInData(startTimeNanos, data):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user