diff --git a/fitbit.js b/fitbit.js index d671a4b..b11da7c 100644 --- a/fitbit.js +++ b/fitbit.js @@ -31,6 +31,12 @@ var CONSUMER_SECRET_PROPERTY_NAME = "fitbitConsumerSecret"; */ var PROJECT_KEY_PROPERTY_NAME = "projectKey"; +/** + * Sheet ID. + * @type {String} + * @const + */ +var CLIENT_SHEET_ID = "sheetId"; /** * Default loggable resources. @@ -44,8 +50,13 @@ var LOGGABLES = [ "activities/log/steps", "activities/log/distance", "activities/log/minutesSedentary", "activities/log/minutesLightlyActive", "activities/log/minutesFairlyActive", - "activities/log/minutesVeryActive", "sleep/timeInBed", - "sleep/minutesAsleep", "sleep/minutesAwake", "sleep/awakeningsCount", + "activities/log/minutesVeryActive", + "sleep/timeInBed", + "sleep/minutesAsleep", + "sleep/minutesAwake", + "sleep/awakeningsCount", + "sleep/efficiency", + "activities/heart", "body/weight", "body/bmi", "body/fat" ]; /** @@ -63,6 +74,9 @@ var PERIODS = [ "1d", "7d", "30d", "1w", "1m", "3m", "6m", "1y", "max" ]; */ var scriptProperties = PropertiesService.getScriptProperties(); + +var sheet_name = "Sheet1"; //Change this if you want to rename your sheet! + function refreshTimeSeries() { // if the user has never configured ask him to do it here @@ -74,7 +88,8 @@ function refreshTimeSeries() { Logger.log('Refreshing timeseries data...'); var user = authorize().user; Logger.log(user) - var doc = SpreadsheetApp.getActiveSpreadsheet() + var ss = SpreadsheetApp.openById(getSheetId()); + var doc = ss.getSheetByName(sheet_name); doc.setFrozenRows(2); // header rows doc.getRange("a1").setValue(user.displayName); @@ -141,12 +156,22 @@ function refreshTimeSeries() { if ( row_index != 0 ) { row_index++; } else { - row_index = findRow(date); + row_index = findRow(date, doc); } // Insert Date into first column - doc.getActiveSheet().getRange(row_index, 1).setValue(val["dateTime"]); + doc.getRange(row_index, 1).setValue(val["dateTime"]); // Insert value - doc.getActiveSheet().getRange(row_index, 2 + activity * 1.0).setValue(Number(val["value"])); + var formattedValue = Number(val["value"]); + + Logger.log("Title: " + title); + // This is heart rate data + if ( val.value["restingHeartRate"] !== undefined) { + + formattedValue = Number(val.value["restingHeartRate"]); + } + + doc.getRange(row_index, 2 + activity * 1.0).setValue(formattedValue); + Logger.log("Done with " + activity); } } } @@ -185,6 +210,17 @@ function getProjectKey() { return key; } +/** + * @return String Project key + */ +function getSheetId() { + var key = scriptProperties.getProperty(CLIENT_SHEET_ID); + if (key == null) { + key = ""; + } + return key; +} + /** * @param String Project key */ @@ -251,6 +287,7 @@ function saveConfiguration(e) { setConsumerKey(e.parameter.clientID); setConsumerSecret(e.parameter.consumerSecret); setProjectKey(e.parameter.projectKey); + scriptProperties.setProperty(CLIENT_SHEET_ID, e.parameter.sheetId); setLoggables(e.parameter.loggables); setPeriod(e.parameter.period); var app = UiApp.getActiveApplication(); @@ -265,7 +302,7 @@ function renderFitbitConfigurationDialog() { var doc = SpreadsheetApp.getActiveSpreadsheet(); var app = UiApp.createApplication().setTitle("Configure Fitbit"); app.setStyleAttribute("padding", "10px"); - app.setHeight('380'); + app.setHeight('460'); var helpLabel = app .createLabel("From here you will configure access to fitbit -- Just supply your own" @@ -293,17 +330,34 @@ function renderFitbitConfigurationDialog() { projectKey.setName("projectKey"); projectKey.setWidth("100%"); projectKey.setText(getProjectKey()); + + var sheetIdLabel = app.createLabel("Sheet ID:"); + var sheetId = app.createTextBox(); + sheetId.setName("sheetId"); + sheetId.setWidth("100%"); + sheet_id = getSheetId(); + if (!sheet_id) { + try { + sheet_id = SpreadsheetApp.getActiveSpreadsheet().getId(); + } + catch (e) { + Logger.log("Could not get the Sheet ID."); + } + } + sheetId.setText(sheet_id); var saveHandler = app.createServerClickHandler("saveConfiguration"); var saveButton = app.createButton("Save Configuration", saveHandler); - var listPanel = app.createGrid(6, 3); + var listPanel = app.createGrid(7, 2); listPanel.setWidget(1, 0, consumerKeyLabel); listPanel.setWidget(1, 1, consumerKey); listPanel.setWidget(2, 0, consumerSecretLabel); listPanel.setWidget(2, 1, consumerSecret); listPanel.setWidget(3, 0, projectKeyLabel); listPanel.setWidget(3, 1, projectKey); + listPanel.setWidget(4, 0, sheetIdLabel); + listPanel.setWidget(4, 1, sheetId); // add checkboxes to select loggables var loggables = app.createListBox(true).setId("loggables").setName("loggables"); @@ -315,8 +369,8 @@ function renderFitbitConfigurationDialog() { loggables.setItemSelected(parseInt(resource), true); } } - listPanel.setWidget(4, 0, app.createLabel("Resources:")); - listPanel.setWidget(4, 1, loggables); + listPanel.setWidget(5, 0, app.createLabel("Resources:")); + listPanel.setWidget(5, 1, loggables); var period = app.createListBox(false).setId("period").setName("period"); period.setVisibleItemCount(1); @@ -325,8 +379,8 @@ function renderFitbitConfigurationDialog() { period.addItem(PERIODS[resource]); } period.setSelectedIndex(PERIODS.indexOf(getPeriod())); - listPanel.setWidget(5, 0, app.createLabel("Period:")); - listPanel.setWidget(5, 1, period); + listPanel.setWidget(6, 0, app.createLabel("Period:")); + listPanel.setWidget(6, 1, period); // Ensure that all form fields get sent along to the handler saveHandler.addCallbackElement(listPanel); @@ -359,7 +413,7 @@ function getService() { .setProjectKey(getProjectKey()) .setCallbackFunction('fitbitAuthCallback') .setPropertyStore(PropertiesService.getScriptProperties()) - .setScope('activity') + .setScope('heartrate activity') .setTokenHeaders({ 'Authorization': 'Basic ' + Utilities.base64Encode(getConsumerKey() + ':' + getConsumerSecret()) }); @@ -424,15 +478,19 @@ function onInstall() { } // Find the right row for a date. -function findRow(date) { - var doc = SpreadsheetApp.getActiveSpreadsheet(); +function findRow(date, doc) { var cell = doc.getRange("A3"); - + var column = doc.getRange('A:A'); + var values = column.getValues(); // get all data in one call + var ct = 0; + // Find the first cell in first column which is either empty, // or has an equal or bigger date than the one we are looking for. - while ((cell.getValue() != "") && (cell.getValue() < date)) { - cell = cell.offset(1,0); + while ( (values[ct][0] != "") && values[ct][0] < date ) { + ct++; } + cell = cell.offset(ct, 0); + // If the cell we found has a newer date than ours, we need to // insert a new row right before that. if (cell.getValue() > date) { @@ -440,4 +498,4 @@ function findRow(date) { } // return only the number of the row. return (cell.getRow()); -} +} \ No newline at end of file diff --git a/readme.md b/readme.md index 00c003c..daded3f 100644 --- a/readme.md +++ b/readme.md @@ -1,17 +1,26 @@ -This little script runs in the Google App Script environment. +This little script runs in the Google App Script environment. -Specifically it runs in [Google Spreadsheets][0]. It lets you suck down your Fitbit data and the do all kinds of analysis. It's also an easy way to get started with the Fitbit API. +Specifically it runs in [Google Spreadsheets][0]. It lets you suck down your Fitbit data and the do all kinds of analysis. It's also an easy way to get started with the Fitbit API. This code is based on the work loghound did in [his project][3]. I have added support for heart rate figures that are produced by a Fitbit Charge HR. This version can also be scheduled using triggers. + +If you want to know how OAuth 2.0 works, have a look at [a sample][2]. Sadly to get started is a bit of a pain: 1. Create a new Google Spreadsheet. 2. Go to Tools-->Script Editor -3. Replace the template with fitbit.js & reload the spreadsheet -4. From the Fitbit menu that should appear, run the Configure option -5. Follow all the instructions given in the form that pops up -6. Run the "Authorize" menu option -- this will run through the oauth dance. -7. Run the 'Refresh fitbit Time Data" menu option to get your data -8. Profit! +3. Replace the template with fitbit.js. +4. Save your changes and reload your sheet. +5. From the Fitbit menu in your Spreadsheet, run the Configure option. If you don't see the option, run "onInstall" function in script editor and try again. +6. Follow the instructions given in the form that pops up. You'll have to set up a Fitbit dev account. +7. Run the "Authorize" menu option -- this will run through the OAuth dance. +8. Run the 'Refresh fitbit Time Data" menu option to get your data +9. Profit! + +Optional: +10. Set up a trigger for your script to run periodically. + - Script editor --> Resources --> All your triggers --> Add a new trigger for "refreshTimeSeries". [0]: http://drive.google.com [1]: https://github.com/loghound/Fitbit-for-Google-App-Script +[2]: https://github.com/googlesamples/apps-script-oauth2 +[3]: https://github.com/loghound/Fitbit-for-Google-App-Script \ No newline at end of file