Google Fit: Dataset; Aggregate Step Count

Google Fit: Dataset; Aggregate Step Count

Aggregates data of Step Count and Distance (meters) in Google Fit. Multiple data sets from multiple sources (including manual input) can be aggregated. Note that the results of simultaneous measurement with multiple devices are all added up.

2019-10-09 (C) Questetra, Inc. (MIT License)
https://support.questetra.com/addons/google-fit-dataset-aggregate-step-count/

Configs
  • A: Select OAuth2 Config Name (at [OAuth 2.0 Setting]) *
  • B: Select DATETIME DATA for Start of Aggregation Range *
  • C: Select DATETIME DATA for End of Aggregation Range *
  • D: Select NUMERIC DATA for Step Count (update) *
  • E: Select NUMERIC DATA for Distance (update)
Script
// (c) 2019, Questetra, Inc. (the MIT License)

//// == OAuth2 Setting example ==
// Authorization Endpoint URL:
//  "https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force"
// Token Endpoint URL:
//  "https://accounts.google.com/o/oauth2/token"
// Scope:
//  "https://www.googleapis.com/auth/fitness.activity.read https://www.googleapis.com/auth/fitness.location.read"
// Client ID:
//  ( from https://console.developers.google.com/ )
// Consumer Secret:
//  ( from https://console.developers.google.com/ )
//  *Redirect URL of Webapp OAuth-Client-ID: "https://s.questetra.net/oauth2callback"
//  *Note: Activate your Google Fitness API


//////// START "main()" ////////
main();
function main(){ 

//// == Config Retrieving / 工程コンフィグの参照 ==
const oauth2  = configs.get( "conf_OAuth2"  ) + ""; // required
const dataIdB = configs.get( "conf_DataIdB" ) + ""; // required
const dataIdC = configs.get( "conf_DataIdC" ) + ""; // required
const dataIdD = configs.get( "conf_DataIdD" ) + ""; // required
const dataIdE = configs.get( "conf_DataIdE" ) + ""; // not required
// 'java.lang.String' (String Obj) to javascript primitive 'string'


//// == Data Retrieving / ワークフローデータの参照 ==
if( engine.findDataByNumber( dataIdB ) === null ){
  throw new Error( "\n AutomatedTask UnexpectedDatetimeError:" +
                   " Datetime {B} is null \n" );
}
if( engine.findDataByNumber( dataIdC ) === null ){
  throw new Error( "\n AutomatedTask UnexpectedDatetimeError:" +
                   " Datetime {C} is null \n" );
}
const strStart  = engine.findDataByNumber( dataIdB ) + "";
const dateStart = new Date( strStart );
const strEnd    = engine.findDataByNumber( dataIdC ) + "";
const dateEnd   = new Date( strEnd );
engine.log( " AutomatedTask Datetime: Start: " + strStart + " " + engine.getTimeZoneId() );
engine.log( " AutomatedTask Datetime: End:   " + strEnd   + " " + engine.getTimeZoneId() );
// com.questetra.bpms.core.event.scripttask.WorkflowEngine (R2300)


//// == Calculating / 演算 ==
// const numOffsetMin = engine.getTimeZoneOffsetInMinutes() - 0; // "540" "-480" etc
// // com.questetra.bpms.core.event.scripttask.WorkflowEngine (R2300)
// const numStartTimeMillis = dateStart.getTime() - numOffsetMin * 60000; // milli sec
// const numEndTimeMillis   = dateEnd.getTime()   - numOffsetMin * 60000; // milli sec
// Questetra Nashorn "getTime()" always uses UTC for time representation.
const numStartTimeMillis = dateStart.getTime(); // milli sec
const numEndTimeMillis   = dateEnd.getTime(); // milli sec
const numDurationMillis  = numEndTimeMillis - numStartTimeMillis;
engine.log( " AutomatedTask Datetime: (s-milli): " + numStartTimeMillis + " (UTC)" );
engine.log( " AutomatedTask Datetime: (e-milli): " + numEndTimeMillis   + " (UTC)" );
const dateNow = new Date();
const numNowMillis = dateNow.getTime();
engine.log( " AutomatedTask Runtime Now (milli): " + numNowMillis       + " (UTC)" );
if( numEndTimeMillis > numNowMillis ){
  engine.log( " AutomatedTask DatetimeWarning:" +
              " Time range includes future" );
}

/// obtain OAuth2 Access Token
const token   = httpClient.getOAuth2Token( oauth2 );

/// post Request for "com.google.step_count.delta" Google Fit REST API v1
// https://developers.google.com/fit/rest/v1/reference/users/dataset/aggregate
let requestObj = {};
    requestObj.aggregateBy = [];
    requestObj.aggregateBy[0] = {};
    requestObj.aggregateBy[0].dataTypeName = "com.google.step_count.delta";
    requestObj.endTimeMillis   = numEndTimeMillis;
    requestObj.startTimeMillis = numStartTimeMillis;
    requestObj.bucketByTime = {};
    requestObj.bucketByTime.durationMillis = numDurationMillis;
let apiRequest = httpClient.begin(); // HttpRequestWrapper
apiRequest = apiRequest.bearer( token );
apiRequest = apiRequest.body( JSON.stringify( requestObj ), "application/json" );
const apiUri = "https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate";
engine.log( " AutomatedTask Trying: POST " + apiUri );
const response = apiRequest.post( apiUri );
const responseCode = response.getStatusCode() + "";
engine.log( " AutomatedTask ApiResponse: Status " + responseCode );
if( responseCode !== "200"){
  throw new Error( "\n AutomatedTask UnexpectedResponseError: " +
         responseCode + "\n" + response.getResponseAsString() + "\n" );
}
const responseStr = response.getResponseAsString() + "";
const responseObj = JSON.parse( responseStr );
engine.log( responseStr );
let intStepCount = 0;
if( responseObj.bucket[0].dataset[0].point[0] == undefined ){
  engine.log( " AutomatedTask ApiResponse: StepCount VALUE undefined" );
}else{
  intStepCount = responseObj.bucket[0].dataset[0].point[0].value[0].intVal;
  engine.log( " AutomatedTask ApiResponse: StepCount " +  intStepCount );
}

/// post Request2 for "com.google.distance.delta" Google Fit REST API v1
let requestObj2 = {};
    requestObj2.aggregateBy = [];
    requestObj2.aggregateBy[0] = {};
    requestObj2.aggregateBy[0].dataTypeName = "com.google.distance.delta";
    requestObj2.endTimeMillis   = numEndTimeMillis;
    requestObj2.startTimeMillis = numStartTimeMillis;
    requestObj2.bucketByTime = {};
    requestObj2.bucketByTime.durationMillis = numDurationMillis;
let apiRequest2 = httpClient.begin(); // HttpRequestWrapper
apiRequest2 = apiRequest.bearer( token );
apiRequest2 = apiRequest.body( JSON.stringify( requestObj2 ), "application/json" );
const apiUri2 = "https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate";
engine.log( " AutomatedTask Trying: POST " + apiUri );
const response2 = apiRequest.post( apiUri2 );
const responseCode2 = response2.getStatusCode() + "";
engine.log( " AutomatedTask ApiResponse: Status " + responseCode2 );
if( responseCode !== "200"){
  throw new Error( "\n AutomatedTask UnexpectedResponseError: " +
         responseCode2 + "\n" + response2.getResponseAsString() + "\n" );
}
const responseStr2 = response2.getResponseAsString() + "";
const responseObj2 = JSON.parse( responseStr2 );
engine.log( responseStr2 );
let fpDistance = 0;
if( responseObj2.bucket[0].dataset[0].point[0] == undefined ){
  engine.log( " AutomatedTask ApiResponse: Distance VALUE undefined" );
}else{
  fpDistance = responseObj2.bucket[0].dataset[0].point[0].value[0].fpVal;
  engine.log( " AutomatedTask ApiResponse: Distance " +  fpDistance );
}


//// == Data Updating / ワークフローデータへの代入 ==
engine.setDataByNumber( dataIdD, new java.math.BigDecimal( intStepCount ) );
if ( dataIdE !== "" ){ 
  engine.setDataByNumber( dataIdE, new java.math.BigDecimal( fpDistance ) );
}


} //////// END "main()" ////////

Download

Capture

Notes

  1. If walking (activity) is ongoing, it may be excluded from counting.
  2. Walking data that intersects with the time window will be aggregated.

See also

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: