初心者でも分かるFitbitとGASを連携してスプレッドシートでデータを可視化する方法

チュートリアル

こんにちは!今回は、FitbitとGoogle Apps Script(GAS)を連携して、Fitbitのデータをスプレッドシートで可視化する方法を初心者向けに解説していきます。コードブロックを使って丁寧に説明するので、プログラミングが苦手な方でも安心してついてきてくださいね。


こちらの記事もおすすめ

リフレッシュトークンを使用したアクセストークンの更新方法
OAuth 2.0認証を使用するWebアプリケーションでは、アクセストークンの有効期限が切れた場合に、リフレッシュトークンを使用して新しいアクセストークンを取得する必要があります。本記事では、Pythonを使用してリフレッシュトークンを使用...
Fitbitの期間を指定してデータを取得/可視化する
こんにちは!今回は、Fitbitから期間を指定してデータを取得し、取得したデータを可視化する方法について解説します。Fitbitは、ウェアラブルデバイスやアプリを通じて健康関連のデータを収集・管理するプラットフォームです。この記事では、Py...

まず始めに、Fitbitのアクセストークンなどの情報を保存するためのJSONファイルをGoogle Driveに作成します。ファイル名はfitbit-token.jsonとし、以下の内容を記述してください。


{
  "access_token": "XXXXXX",
  "expires_in": 28800,
  "refresh_token": "TTTTT",
  "scope": "cardio_fitness respiratory_rate temperature location social activity settings oxygen_saturation sleep nutrition heartrate electrocardiogram profile weight",
  "token_type": "Bearer",
  "user_id": "ZZZZZ"
}

実際のアクセストークンなどの情報は、Fitbitの開発者ポータルで取得したものに置き換えてください。

次に、GASのコードエディタで新しいプロジェクトを作成し、以下のようにコードを記述していきます。


const CONFIG_FILE = "fitbit-token.json";

この行で、先ほど作成した設定ファイルの名前を定数として定義しています。

設定ファイルの読み込みと保存

まずは、設定ファイルを読み込んで、アクセストークンなどの情報を取得する関数を作成します。


function loadConfig() {
  try {
    const file = DriveApp.getFilesByName(CONFIG_FILE).next();
    const content = file.getBlob().getDataAsString();
    return JSON.parse(content);
  } catch (error) {
    console.log(`設定ファイル ${CONFIG_FILE} が見つかりません。`);
    throw error;
  }
}

function saveConfig(config) {
  const file = DriveApp.getFilesByName(CONFIG_FILE).next();
  const content = JSON.stringify(config, null, 2);
  file.setContent(content);
}

loadConfig関数は、設定ファイルを読み込んでJSONをパースし、設定情報を返します。saveConfig関数は、設定情報をJSONに変換して設定ファイルに保存します。

認証ヘッダーの作成

次に、FitbitのAPIにアクセスするために必要な認証ヘッダーを作成する関数を定義します。


function createAuthHeader(accessToken) {
  return { Authorization: "Bearer " + accessToken };
}

この関数は、アクセストークンを受け取り、認証ヘッダーを返します。

アクセストークンの更新

アクセストークンには有効期限があるため、定期的に更新する必要があります。以下の関数で、アクセストークンを更新します。


function refreshAccessToken(config) {
  const url = "https://api.fitbit.com/oauth2/token";
  const data = {
    grant_type: "refresh_token",
    refresh_token: config.refresh_token,
  };
  const headers = {
    Authorization: "Basic " + config.client_id + ":" + config.client_secret,
    "Content-Type": "application/x-www-form-urlencoded",
  };

  const options = {
    method: "post",
    payload: data,
    headers: headers,
  };

  const response = UrlFetchApp.fetch(url, options);
  const responseData = JSON.parse(response.getContentText());

  if ("errors" in responseData) {
    console.log(`アクセストークンの更新に失敗しました: ${responseData.errors[0].message}`);
    return false;
  }

  config.access_token = responseData.access_token;
  config.refresh_token = responseData.refresh_token;
  saveConfig(config);
  return true;
}

この関数は、設定情報からリフレッシュトークンを取得し、FitbitのAPIにPOSTリクエストを送信してアクセストークンを更新します。更新に成功した場合、新しいアクセストークンとリフレッシュトークンを設定情報に保存し、trueを返します。

トークンの有効期限のチェック

APIレスポンスからトークンの有効期限をチェックする関数を作成します。


function isTokenExpired(responseData) {
  if ("errors" in responseData) {
    for (const error of responseData.errors) {
      if (error.errorType === "expired_token") {
        console.log("アクセストークンの有効期限が切れています。");
        return true;
      }
    }
  }
  return false;
}

この関数は、APIレスポンスにエラーが含まれている場合、エラーの種類を確認し、トークンの有効期限が切れている場合はtrueを返します。

APIリクエストの送信

次に、FitbitのAPIにリクエストを送信する関数を定義します。


function makeApiRequest(url, headers) {
  const options = {
    headers: headers,
  };

  const response = UrlFetchApp.fetch(url, options);
  const responseData = JSON.parse(response.getContentText());

  if (isTokenExpired(responseData)) {
    const config = loadConfig();
    if (refreshAccessToken(config)) {
      headers = createAuthHeader(config.access_token);
      return UrlFetchApp.fetch(url, { headers: headers });
    } else {
      console.log("アクセストークンの更新に失敗したため、リクエストを中止します。");
      throw new Error("Failed to refresh access token.");
    }
  }

  return response;
}

この関数は、URLと認証ヘッダーを受け取り、APIにリクエストを送信します。レスポンスを解析し、トークンの有効期限が切れている場合は、アクセストークンを更新してから再度リクエストを送信します。

心拍数データの取得

Fitbitから心拍数データを取得する関数を作成します。


function getHeartRate(date = "today", period = "1d") {
  const url = `https://api.fitbit.com/1/user/-/activities/heart/date/${date}/${period}.json`;
  const config = loadConfig();
  const headers = createAuthHeader(config.access_token);
  return makeApiRequest(url, headers);
}

この関数は、日付と期間を指定して、その期間の心拍数データをFitbitのAPIから取得します。

データのスプレッドシートへの追加

取得した心拍数データをスプレッドシートに追加する関数を作成します。


function appendDataToSheet(sheetId, sheetName, date, data) {
  const sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName);
  const lastRow = sheet.getLastRow();
  const dataArray = data.map(item => {
    const [hours, minutes, seconds] = item.time.split(':');
    const dateTime = new Date(date);
    dateTime.setHours(hours);
    dateTime.setMinutes(minutes);
    dateTime.setSeconds(seconds);
    return [dateTime, item.value];
  });
  sheet.getRange(lastRow + 1, 1, dataArray.length, dataArray[0].length).setValues(dataArray);
}

この関数は、スプレッドシートのIDとシート名、日付、心拍数データを受け取り、データをスプレッドシートに追加します。データは、日時と心拍数の組み合わせで追加されます。

メイン関数の定義

最後に、メイン関数を定義します。


function main() {
  const config = loadConfig();

  const targetDate = "2024-03-20";
  const response = getHeartRate(targetDate);
  const data = JSON.parse(response.getContentText());
  const heartRateData = data["activities-heart-intraday"].dataset;
  appendDataToSheet(SHEET_ID, SHEET_NAME, targetDate, heartRateData);
}

この関数は、設定ファイルを読み込み、指定した日付の心拍数データを取得し、スプレッドシートに追加します。

可視化結果

以上が、FitbitとGASを連携してスプレッドシートでデータを可視化する方法の解説でした。コードブロックを使って丁寧に説明したので、初心者の方でも理解しやすいと思います。ぜひ、このコードを参考にして、自分のFitbitデータを可視化してみてくださいね!

全体コード


// スプレッドシートのID
var SPREADSHEET_ID = 'XXXXXXXXXX';
const CONFIG_FILE = "fitbit-token.json";
const SHEET_ID = "YYYYYYYY";
const SHEET_NAME = "ZZZZZZZZZZZZZ";

// ---------------------------------------------------------

function loadConfig() {
  try {
    const file = DriveApp.getFilesByName(CONFIG_FILE).next();
    const content = file.getBlob().getDataAsString();
    return JSON.parse(content);
  } catch (error) {
    console.log(`設定ファイル ${CONFIG_FILE} が見つかりません。`);
    throw error;
  }
}

function saveConfig(config) {
  const file = DriveApp.getFilesByName(CONFIG_FILE).next();
  const content = JSON.stringify(config, null, 2);
  file.setContent(content);
}

function createAuthHeader(accessToken) {
  return { Authorization: "Bearer " + accessToken };
}

function refreshAccessToken(config) {
  const url = "https://api.fitbit.com/oauth2/token";
  const data = {
    grant_type: "refresh_token",
    refresh_token: config.refresh_token,
  };
  const headers = {
    Authorization: "Basic " + config.client_id + ":" + config.client_secret,
    "Content-Type": "application/x-www-form-urlencoded",
  };

  const options = {
    method: "post",
    payload: data,
    headers: headers,
  };

  const response = UrlFetchApp.fetch(url, options);
  const responseData = JSON.parse(response.getContentText());

  if ("errors" in responseData) {
    console.log(`アクセストークンの更新に失敗しました: ${responseData.errors[0].message}`);
    return false;
  }

  config.access_token = responseData.access_token;
  config.refresh_token = responseData.refresh_token;
  saveConfig(config);
  return true;
}

function isTokenExpired(responseData) {
  if ("errors" in responseData) {
    for (const error of responseData.errors) {
      if (error.errorType === "expired_token") {
        console.log("アクセストークンの有効期限が切れています。");
        return true;
      }
    }
  }
  return false;
}

function makeApiRequest(url, headers) {
  const options = {
    headers: headers,
  };

  const response = UrlFetchApp.fetch(url, options);
  const responseData = JSON.parse(response.getContentText());

  if (isTokenExpired(responseData)) {
    const config = loadConfig();
    if (refreshAccessToken(config)) {
      headers = createAuthHeader(config.access_token);
      return UrlFetchApp.fetch(url, { headers: headers });
    } else {
      console.log("アクセストークンの更新に失敗したため、リクエストを中止します。");
      throw new Error("Failed to refresh access token.");
    }
  }

  return response;
}

function getHeartRate(date = "today", period = "1d") {
  const url = `https://api.fitbit.com/1/user/-/activities/heart/date/${date}/${period}.json`;
  const config = loadConfig();
  const headers = createAuthHeader(config.access_token);
  return makeApiRequest(url, headers);
}

function appendDataToSheet(sheetId, sheetName, date, data) {
  const sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName);
  const lastRow = sheet.getLastRow();
  const dataArray = data.map(item => {
    const [hours, minutes, seconds] = item.time.split(':');
    const dateTime = new Date(date);
    dateTime.setHours(hours);
    dateTime.setMinutes(minutes);
    dateTime.setSeconds(seconds);
    return [dateTime, item.value];
  });
  sheet.getRange(lastRow + 1, 1, dataArray.length, dataArray[0].length).setValues(dataArray);
}

function main() {
  const config = loadConfig();

  const targetDate = "2024-03-20";
  const response = getHeartRate(targetDate);
  const data = JSON.parse(response.getContentText());
  const heartRateData = data["activities-heart-intraday"].dataset;
  appendDataToSheet(SHEET_ID, SHEET_NAME, targetDate, heartRateData);
}

コメント

タイトルとURLをコピーしました