4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

始めに

私は普段からFitbitを愛用しています。(愛機はFitbit Versa 4

Fitbitは健康とフィットネスを追跡するためのデバイスとサービスを提供する会社です。
Fitbitの製品ラインアップには、アクティビティトラッカーやスマートウォッチがあり、歩数、心拍数、睡眠の質、消費カロリーなどのデータを専用のデバイスで取得し管理しています。

このFitbitのアクティビティデータを使ってなにか出来ないかと思い、まずはAPIからのデータ取得方法の手順を調べたのでメモします。

Fitbit開発者アカウントの作成

簡単に流れを説明すると以下の流れです。

  1. https://accounts.fitbit.com/signupにアクセスして、fitbit.comアカウントの登録
  2. Fitbit.comのアカウントが登録できたら、https://dev.fitbit.com/appsへアクセスしてAIPを利用するアプリケーションを作成

ダッシュボードへログイン

Fitbit.comのアカウントでダッシュボードにログインできることを確認。

dev.fitbit.comへログイン

Fitbit.comと同じアカウントでdev.fitbit.comへログインできることを確認。

スクリーンショット 2024-06-13 6.51.56.png

アプリケーションを作成

「Register a new app」をクリック

スクリーンショット 2024-06-13 6.51.56.png

とりあえず、「Redirect URL」以外のURL入力欄はhttp://localhostと入力しておきます。

image.png

「I have read and agree to the terms of service」をチェックしてピンクの「Register」ボタンを押すと、以下のページが表示されます。

スクリーンショット 2024-06-14 12.50.58.png

左下の「OAuth2.0 Tutorial」をクリックして、アクセス・トークン等の発行を行います。

App Settings

「Client ID」は自動的に表示されているのでそのままにしておきます。

「Application Type」は「Client (for client-side access)」を選択します。

スクリーンショット 2024-06-14 12.53.51.png

Getting an Access Token

Step 1: Generate PKCE and State Values

アクセス・トークンを発行するために必要な情報を生成します。
「Generate」ボタンを押します。(2箇所)

生成される文字列は一時的なものなので控えておく必要はありません。

スクリーンショット 2024-06-14 12.56.15.png

Step 2: Display Authorization Page

APIで取得を認める項目の選択です。
デフォルトでは全てにチェックが入っているので取得を認めない項目からチェックを外します。

The permission scopes to request access to. Only request what your app needs.

アクセスを要求する権限のスコープ。アプリに必要なものだけをリクエストしてください。

スクリーンショット 2024-06-14 13.19.14.png

権限のチェックができたら「Authorization URL」をクリックします。

自動で認可ページが開きます。
「すべて許可する」をチェックし、「許可」ボタンを押します。

スクリーンショット 2024-06-13 8.35.00.png

すると、別タブでRedirect URLで指定したhttp://localhost:8000/~が開き「このサイトにアクセス出来ません」と表示されていると思います。
閉じずにURLをコピー してください。

スクリーンショット 2024-06-13 8.36.43.png

Step 3: Handle the Redirect

先ほどのページに戻り、下の画像の赤枠のところにコピーしたURLを貼り付けます。
URLからAuthorization CodeとStateを抽出して表示してくれます。
いずれの値も控えておく必要はありません。

スクリーンショット 2024-06-14 13.00.36.png

Step 4: Get Tokens

上記のURLを元にリクエストを自動生成してくれています。
「SUBMIT REQUEST」のボタンを押します。
下のテキストエリアにエラーが表示されていなければOKです。

スクリーンショット 2024-06-14 13.09.53.png

Access TokenとRefresh Tokenは必ず控えてください。

Access TokenはAPIを実行するのに使います。
このTokenは8時間の寿命なので、期限が切れた場合はRefresh Tokenを使ってAccess Tokenの再発行を行います。

Access TokenとRefresh Tokenは人に見せないようにしてください。
Access TokenまたはRefresh Tokenが流出した可能性がある場合、ここで作成したアプリを削除してください。

Step 5: Check Scopes

この後のステップは、profile取得のAPIが通るかのテストとRefresh Tokenを使って再発行をするチュートリアルなので、不要なら飛ばしてください。
Profileはそもそも権限を与えていない場合はエラーになるので、試す場合はご留意ください。
また、Refresh Tokenを使って再発行をすると、Access TokenとRefresh Tokenともに更新されるので、更新後の値を控えておいてください。

Access User Data

これまでの手順で作成したAceess TokenとAPI Endpoint URLが自動でセットされているので、「SUBMITREQUEST」を押します。
APIエンドポイントからユーザープロファイルのデータが取得され「Respose」欄に表示されればOKです。

スクリーンショット 2024-06-14 13.13.29.png

Refresh Tokens

Access Tokenは8時間の寿命なので、期限が切れた場合はRefresh Tokenを使ってAccess Tokenの再発行を行います。

Refresh Tokenが自動でセットされているので、「SUBMITREQUEST」を押します。
Refresh Tokenを用いて、Access Tokenが更新されました。(古いAccess Tokenは使えなくなります)

スクリーンショット 2024-06-14 13.16.00.png

NodejsからAPIを叩いて情報を取得

さて、本題のFitbitAPIでデータ取得をするアプリケーションを自作します。
今回はNode.jsでFitbit APIを利用して「Get Heart Rate Time Series by Date」を取得する簡単なサーバーサイドアプリケーションを作成します。

Node.jsプロジェクトの初期設定

app.jsを作成します。

mkdir fitbit-nodejs
cd fitbit-nodejs
npm init -y

必要なパッケージのインストール

npm install axios dotenv

環境変数の設定

.env
CLIENT_ID=
ACCESS_TOKEN=
REFRESH_TOKEN=

いずれもこれまでで取得した値で埋めてください。
client_idはアプリの登録ページで、登録したアプリをクリックすると確認できます。

データ取得スクリプトの作成

プロジェクトのルートにindex.jsファイルを作成し、以下のコードを追加します。これにより、指定された日付に基づいて心拍数データを取得します。

require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');

async function refreshAccessToken() {
    const url = 'https://api.fitbit.com/oauth2/token';
    const clientId = process.env.CLIENT_ID;
    const refreshToken = process.env.REFRESH_TOKEN;

    const body = new URLSearchParams();
    body.append('grant_type', 'refresh_token');
    body.append('client_id', clientId);
    body.append('refresh_token', refreshToken);

    const config = {
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    };

    try {
        const response = await axios.post(url, body.toString(), config);
        const { access_token, refresh_token } = response.data;

        updateEnvFile(clientId, access_token, refresh_token);
        console.log('Access token and refresh token updated.');
        return access_token;
    } catch (error) {
        console.error('Error refreshing token:', error.response.data);
    }
}

function updateEnvFile(clientId, accessToken, refreshToken) {
    const envPath = path.join(__dirname, '.env');
    const envContents = `CLIENT_ID=${clientId}\nACCESS_TOKEN=${accessToken}\nREFRESH_TOKEN=${refreshToken}\n`;

    fs.writeFileSync(envPath, envContents, 'utf8');
    console.log('.env file has been updated with new tokens.');
}

async function getHeartRate(date = 'today', period = '1d') {
    const accessToken = await refreshAccessToken();
    const url = `https://api.fitbit.com/1/user/-/activities/heart/date/${date}/${period}.json`;

    try {
        const response = await axios.get(url, {
            headers: {
                'Authorization': `Bearer ${accessToken}`
            }
        });
        return response.data;
    } catch (error) {
        console.error('Error fetching heart rate data:', error.response.data);
    }
}

(async () => {
  const heartRateData = await getHeartRate();  // 今日の1日分の心拍データを取得
  console.log(JSON.stringify(heartRateData));
})();

取得結果

{
	"activities-heart": [
		{
			"dateTime": "2024-06-13",
			"value": {
				"customHeartRateZones": [],
				"heartRateZones": [
					{
						"caloriesOut": 2111.9742600000004,
						"max": 109,
						"min": 30,
						"minutes": 1427,
						"name": "Out of Range"
					},
					{
						"caloriesOut": 88.36310999999998,
						"max": 133,
						"min": 109,
						"minutes": 13,
						"name": "Fat Burn"
					},
					{
						"caloriesOut": 0,
						"max": 163,
						"min": 133,
						"minutes": 0,
						"name": "Cardio"
					},
					{
						"caloriesOut": 0,
						"max": 220,
						"min": 163,
						"minutes": 0,
						"name": "Peak"
					}
				],
				"restingHeartRate": 62
			}
		}
	],
	"activities-heart-intraday": {
		"dataset": [
			{
				"time": "00:00:00",
				"value": 62
			},
			{
				"time": "00:01:00",
				"value": 61
			},
...長いので割愛
			{
				"time": "16:34:00",
				"value": 78
			},
			{
				"time": "16:35:00",
				"value": 74
			},
			{
				"time": "16:36:00",
				"value": 76
			},
			{
				"time": "16:37:00",
				"value": 76
			},
			{
				"time": "16:38:00",
				"value": 80
			}
		],
		"datasetInterval": 1,
		"datasetType": "minute"
	}
}

最後に

APIからデータ取得ができたので、なにをしようかな。これから考えます。

この記事は個人の見解であり所属する組織を代表しません。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?