こんにちは!今回は、FitbitとGoogle Apps Script(GAS)を連携して、Fitbitのデータをスプレッドシートで可視化する方法を初心者向けに解説していきます。コードブロックを使って丁寧に説明するので、プログラミングが苦手な方でも安心してついてきてくださいね。
こちらの記事もおすすめ
まず始めに、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 API を使って GAS からスプレッドシートにデータ送信してデータベース化成功!!
Pythonベースのコードを claude さんが秒でGASに変換してくれたのでめちゃ助かりました! pic.twitter.com/9jXHL0ZAjb
— Maki@Sunwood AI Labs. (@hAru_mAki_ch) March 21, 2024
以上が、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);
}
コメント