Gunosy Tech Blog

Gunosy Tech Blogは株式会社Gunosyのエンジニアが知見を共有する技術ブログです。

Fitbitのカスタムレポートを作成してLINEに通知する

本記事は、Gunosy Advent Calendar 2020 21日目の記事です。

昨日の記事はUT@mocyuto さんの「既存システムをkubernetesに移行して大きくコストカットした話」でした。

はじめに

こんにちは。SRE部の板谷(@SItaya5)です。 今日の記事では業務でなく、プライベートで取り組んだことを紹介していきたいと思います!

みなさん、Fitbit使ってますか?私は愛用しています! Fitbitはフィットネス用のトラッカーで、歩数はもちろん、睡眠の時間や質、心拍数、そして夏に発売されたSenseシリーズでは皮膚温まで計測できるようになりました(私はまだ持っていませんが、、)。 日々のアクティビティ管理や体調の変化を把握するためには欠かせないツールとなっています。

そんなFitbitですが、APIが公開されており、自分のデータにアクセスすることができます(https://www.fitbit.com/jp/dev)。 そしてこのAPIを利用して、週次でカスタムレポートを作成することにしました(メールでの週次レポートがサポートされていますが、もっといろいろなデータが見れたらいいなと思っていました)。 今回は歩数に焦点を当て、週次のデータに加えて以下のデータが通知されるようにしました。

  • その年の最も歩いた日Top5とその日の歩数
  • Fitbitを使い始めてから最も歩いた日Top5とその日の歩数

また、通知先はメールではなくLINEにすることにしました。

といった点を考慮して作成したものがFitbit-activity-notifierです。

github.com

この記事ではFitbit-activity-notifierを作るまでに必要だったことや、実装の内容について紹介します。

必要なもの

  • Fitbitアカウント
  • LINEアカウント
  • AWSアカウント(ローカルで動かす場合などは不要です)

Fitbitのデータを取得するためのトークンを発行する

https://dev.fitbit.com/にアクセスします。 ログインしていない状態だと「Log In」ボタンが表示されるので、そちらからFitbitのアカウントでログインします。

まずは「REGISTER AN APP」のタブに遷移し、アプリを登録します。

登録するに当たって必要な項目がいくつかあるので、入力します。 気をつける項目としては以下の通りです。

項目 設定値 備考
OAuth 2.0 Application Type Personal Personalを設定することで個人のデータにアクセスすることができます
Callback URL http://127.0.0.1:8080/ 後続作業の手間を減らすためにこの値に設定しておく

登録が完了するとOAuth 2.0 Client IDClient Secretが発行されます。 なお、項目名にもある通り認可にはOAuth2.0が採用されています。

続いて認可のプロセスです。 登録したアプリの管理画面から「OAuth 2.0 tutorial page」のリンクに遷移することでブラウザから進めることもできますが、やや煩雑なので別の手順を紹介します。 以下のリポジトリのスクリプトをクローンして使用します(後ほど紹介するPythonのスクリプトでもこのリポジトリに含まれるライブラリを使用してFitbitからデータを取得します)。

github.com

リポジトリ直下に配置されているgather_keys_oauth2.pyを使用します(前の手順でCallback URLを指定したのはこのスクリプトで指定されている値と合わせるためです)。 前の手順で取得した、OAuth 2.0 Client IDとClient Secretを引数に渡して実行するだけです。

$ python gather_keys_oauth2.py client_id client_secret

するとブラウザが起動し、以下の画面が表示されるので、必要な項目にチェックを入れ、「許可」を押します。

ブラウザに「You are now authorized to access the Fitbit API!」と表示され、コンソールに以下のようなトークンなどが表示されれば成功です!

access_token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
expires_in = 28800
refresh_token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
scope = ['weight', 'heartrate', 'activity', 'profile', 'nutrition', 'location', 'sleep', 'social', 'settings']
token_type = Bearer
user_id = XXXXXX
expires_at = 1607978494.153829

試しにAPIを叩いてみましょう。 APIに関しては以下のドキュメントかSwagger UIが参考になります。

例として12/6から12/12までの歩数を取得してみます。 ここではActivity Time SeriesというAPIをcurlで実行します。 エンドポイントはhttps://api.fitbit.com/[user-id]/[resource-path]/date/[base-date]/[end-date].jsonで提供されているので、ここに必要なパラメータを当てはめていきます。

$ curl -i -H "Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  https://api.fitbit.com/1/user/-/activities/steps/date/2020-12-06/2020-12-12.json

"Authorization: Bearer xxxxxxxx"のように、先程取得したaccess_tokenをヘッダーで渡します。 また、[user-id]には-で現在ログインしているユーザーを意味し、[resource-path]にactivities/stepsを設定することでアクティビティとして歩数が指定されます(もちろん歩数以外のデータも取得できます)。

成功すると以下のようなレスポンスが取得できます。

{
  "activities-steps": [
    {
      "dateTime": "2020-12-06",
      "value": "11440"
    },
    {
      "dateTime": "2020-12-07",
      "value": "16258"
    },
    {
      "dateTime": "2020-12-08",
      "value": "10739"
    },
    {
      "dateTime": "2020-12-09",
      "value": "6998"
    },
    {
      "dateTime": "2020-12-10",
      "value": "12204"
    },
    {
      "dateTime": "2020-12-11",
      "value": "7413"
    },
    {
      "dateTime": "2020-12-12",
      "value": "15030"
    }
  ]
}

LINE Notifyのアクセストークンを発行する

シンプルにLINEにメッセージを送るだけならLINE Notifyがおすすめです。 これはLINEのトークンをヘッダーに指定し、https://notify-api.line.me/api/notifyにPOSTするだけでメッセージを送ることができる機能です(他にもGithubなどの外部サービスと連携して通知を受け取ることもできるようです)。 上記のページからLINEアカウントでログインし、マイページにトークン発行ボタンがあります。

通知先として選択できるのは個人宛てかLINEグループ宛てのどちらかです。 トークン名を入力して発行します(ここで入力した値が通知の際に表示されます)。 画面に表示されるのでメモりましょう。このときしか確認できませんので気を付けてください。

PythonでFitbitのデータを取得する

ここからが本題です。

ところでPythonを選定した理由ですが、ライブラリが存在するためです(先程の認可で紹介したものと同じリポジトリです)。

github.com

pipでインストールすれば使用できます。

$ pip install fitbit

ライブラリのおかげでかなり楽に実装することができました。 ドキュメントも用意されています。

python-fitbit.readthedocs.io

ライブラリの使用方法

まず、認証は以下のように行います。

import fitbit

authd_client = fitbit.Fitbit('<client_id>', '<client_secret>',
                             access_token='<access_token>', 
                             refresh_token='<refresh_token>', 
                             refresh_cb=update_token)

引数で渡しているものは、アプリの登録や認可の手順で発行したものです。 私の実装では、client_idclient_secretSystems Manager パラメータストアで、access_tokenrefresh_tokenS3のファイルから取得しています(なぜS3なのか、引数のrefresh_cb=update_tokeは何なのか、などの詳細は後ほど)。

データの取得にはこのauthd_clientを使用します。 先程curlで実行した例を今度はPythonで実行してみると以下のようになります。

steps_data = authd_client.time_series('activities/steps',
                          base_date= '2020-12-06',
                          end_date= '2020-12-12')

ここで取得できるデータはcurlのときと同様、以下の形式のdictionaryです。

{"activities-steps": [{ "dateTime": "2020-12-06", "value": 11440 }, {...}, {...}, ...]}

activities-stepsというキーに対し、バリューはdateTimevalueから成るdictionaryのリストです。 必要なデータを取得し、いい感じに抽出・加工すればカスタムレポートのできあがりです。

実装にあたってのハマりどころ

とは言ってもハマりどころもあったのでまとめておきます。

トークンの更新処理

もちろんトークンには有効期限があります。上記の手順では8時間(28800秒)に設定されます。 もっと長く設定できるようですが、期限切れの対応を考慮しておく必要があることには変わりません。

トークンを再発行するために必要なのがrefresh_tokenです。 これにより、access_tokenが再発行されますが、その際にrefresh_tokenも再発行されます。 そのため、この2つのトークンはスクリプトから都度参照できる場所に保管しておく必要があります。 Fitbit-activity-notiferでは、その保管場所としてS3を採用しており、そこにトークンが記載されたテキストを置いています(初回実行時前に手で置く必要があります)。 そしてそのトークンを書き換えるupdate_tokenという関数を定義しました。 先程のPythonでの認証を再掲します。

authd_client = fitbit.Fitbit('<client_id>, '<client_secret>',
                             access_token='<access_token>', 
                             refresh_token='<refresh_token>', 
                             refresh_cb=update_token)

このとき関数update_tokenrefresh_cbの引数として渡します。 update_tokenは以下のように実装しています。

import boto3

REFRESH_CB_BUCKET_NAME = os.environ["REFRESH_CB_BUCKET_NAME"]
REFRESH_CB_FILE_NAME = os.environ["REFRESH_CB_FILE_NAME"]

tmp_file_name = '/tmp/token.txt'

s3 = boto3.resource('s3')
refresh_cb_bucket = s3.Bucket(REFRESH_CB_BUCKET_NAME)

def update_token(token):

    f = open(tmp_file_name, 'w')
    f.write(str(token))
    f.close()
    refresh_cb_bucket.upload_file(tmp_file_name, REFRESH_CB_FILE_NAME)

    return

引数で受け取っているtokenは、access_tokenrefresh_tokenなどを含んだdictionaryです。 ローカルに作成したファイルにtokenを書き込み、それをS3にアップロードします。

データ取得前の認証の流れをまとめると以下の通りです。

  • S3に格納されたファイルから、access_tokenrefresh_tokenを取得。
  • トークンを基に、authd_clientを作成。
  • その際に、トークンの期限が切れていたらupdate_tokenを呼び出す。
リクエストで取得できるデータの上限

メソッドtime_seriesbase_dateend_dateと渡せばその期間のデータを取得できますが、1回のリクエストで取得できるデータの上限は1095日分(約3年分)です。 今回のように Fitbitを使い始めてからの全データを集計対象にしたい場合は、この点を考慮して実装する必要があります。

ここでは都度tmp_start_datetmp_end_dateを定義し、1095日分以上のデータがあっても取得処理を分割することで全データを取得しています。 具体的な実装についてはこちらをご覧ください。

Fitbit-activity-notifier

最後に、作成したFitbit-activity-notifierの紹介で締めたいと思います。

なんとなくTerraformも作ってみました。大きく分けるとLambdaCloudWatch Eventsに関するリソースが定義してあります。 デフォルトではCloudWatch Eventsで毎週日曜日にレポートが送られるように設定してあります。

データ取得の処理やLINEへの通知はlambda_function.pyに記載しています(Lambdaを使用することを前提としていますが、他の環境で実行する場合は適宜読み替えてください)。 いろいろと書いてありますが、ほとんどの部分は取得したデータの抽出・加工処理です。

ちなみに作成されるカスタムレポートはこんな感じです!

まとめ

この記事ではFitbit-activity-notifierの内容をベースに、PythonでFitbitのAPIを叩く方法や、必要な事前準備について紹介しました。

今回は歩数だけを取り扱いましたが、別のデータも活用して、より有益なカスタムレポートを模索中です。 FItbitをお持ちの方はぜひ試してみてはいかがでしょうか。 まだお持ちでない方はサンタさんにお願いしましょう🎅

明日はIoriSさんの記事になります。お楽しみに!

参考