fitbitは心拍数や消費カロリー、歩数や睡眠等多数のライフログを残す事が可能な活動量計です。便利なアプリもありますが、ここではPythonでfitbitのAPIを叩く事によってこれらのデータをプログラムで扱えるようにする方法を紹介します。
こんにちは。wat(@watlablog)です。今回はfitbitから情報を取得するために、面倒なAPIの設定方法からPythonコード例までをまとめます!
fitbitとは?
電池の持ちが良いフィットネス用スマートウォッチ
fitbit(フィットビット)とは、腕時計型の活動量計で、時計の裏の心拍数センサーで腕の血管から心拍数を計測する機能を基本に、歩数、消費カロリー、睡眠時間といったデータを自動で記録してくれます。フィットネスに特化しています。
僕はMacbook ProやiPhone、iPadをWindows機とは別に持っており、図らずとも準Apple信者のようになっていますが、fitbitを数年間使ってきた事で未だにApple Watchは購入していません。
fitbit購入当時のApple Watchはまだ心拍数センサーの精度の口コミ評価が微妙だったのと、電池の持ちが圧倒的にfitbit優位であった事を覚えています。
最新のApple Watchも充電は1日、2日に1回(1時間ほど)程度でしょうか?
そんな中、fitbitはバッテリー持続時間が1週間弱あります!
僕はIonicを2年ほど着けていますが、これだけ年数が経った今もそれほど劣化を感じず、毎日のシャワーの10〜20分間程度充電器に繋ぐだけでずっと使い続けられています(充電のために外す事が無い)。
fitbit公式は睡眠の分析(レム睡眠やノンレム睡眠、睡眠スコア等)でApple Watchと差別化しているようですが、僕の場合は電池の持ちで決めました。
Apple Watchと違ってLINEやメールの返信等はできませんが、読む事くらいはできる(既読は付きませんが)ので通常のスマートウォッチとしても使え、価格もApple Watchより安いのでオススメの製品です。
最近は以下のような製品が発売されており、僕もIonicの次は最新を買おうと思います。
Pythonで情報を取得できる
fitbitはAPI(Application Programming Interface)を叩いて情報を取得する事が出来ます。Pythonで出来るというのが非常に良いと思ったので、今回は記事を書きました(Python以外でも可能ですが)。
プログラム的に情報を取得する事ができるという事は、分析をカスタマイズしたり自動化する事も可能になります。
Pythonは統計分析や機械学習に強い言語なので、その他のAPI(Twitter、Google…)と組み合わせて活用しても面白い事ができるかなと思っています。
APIと言えば「PythonでGmailのAPIを通してメールを送信する方法」でGoogleメールを操作した事がありますが、今回はどうでしょう。
それでは以下より本題に入りましょう!
この記事の目標
fitbitのAPI(ここではWeb APIを意味しています)を使うためには、アプリの登録が必要です。
そしてOAuth2.0による認証を経てtoken(トークン)の発行も必要です。
このtoken、セキュリティを高めるために期限が短く設定されていますが、プログラムを作る上で毎回手動で更新するのはナンセンスですので、以下赤字で示している期限切れのtokenファイルを自動で更新する仕組みも必須と思います。
これらの枠組みを構築した上でfitbitからの情報をPythonで加工したりプロットしたりする事でストレス無く使えるようになるでしょう。
そのため本記事では以下を目標とします。
- fitbitにログインしてアプリを登録する
- OAuth2.0の認証を受けてtokenを発行する
- 期限切れtokenを自動更新するPythonコードを作る
- fitbitから情報を取得してmatplotlibで表示する
ちなみにfitbit APIで使われているOAuth2.0は世界中で使われている認証システムです。色々なAPIで使われています。アクセストークン、リフレッシュトークンを使ってセキュリティを高めているとの事ですが、正直僕は雰囲気で使っています。
記事を書きながら以下の書籍を見つけたのでポチって勉強しようと思います。
動作環境
本記事ではWindowsとMacで動作検証をしています。
検証した環境を以下に示します。
Windows | OS | Windows10 64bit |
---|---|---|
CPU | 2.4[GHz] | |
メモリ | 4[GB] |
Mac | OS | macOS Catalina 10.15.7 |
---|---|---|
CPU | 1.4[GHz] | |
メモリ | 8[GB] |
fitbitでWeb APIを利用するための準備
fitbitへログイン
まずはfitbitにログインをする事が必要です。以下の公式ページからログインを行います。
fitbit公式:https://www.fitbit.com/jp/dev
上のページから、さらに以下のLog inに進みます。
僕の場合はGoogleに支配されていますので、Googleアカウントで続けました。
アプリの登録
ログインしたら、続いてアプリの登録を行います。
ログイン先のページにManageという項目があると思いますが、Register An Appをクリックします。
ページが以下に切り替わります。最初は何を書けば良いか不明だと思いますが、
こちらに例を示します。Application Name等にはfitbitという単語を入れてはいけない等の制約があります。
Webサイトは結構適当です。当ブログのURLを書きました。ブログや自分のホームページを持っていない人はどうするんだろう??SNSのアカウントページでも良いのかな??
OAuth 2.0 Application TypeはPersonalにしていないと、詳細な時系列データを取得できないようなので注意。
Redirect URLはこの表と同じで良いそうです。
Application Name | watlab_App |
---|---|
Description | Application for training using fitbit data. |
Application Website URL | https://watlab-blog.com/ |
Organization | WATLAB |
Organization Website URL | https://watlab-blog.com/ |
Terms of Service URL | https://watlab-blog.com/ |
Privacy Policy URL | https://watlab-blog.com/ |
OAuth 2.0 Application Type | Personal |
Redirect URL | http://127.0.0.1:8080/ |
Default Access Type | Read Only |
OAuth 2.0の認証
以上を入力して次に進むと、確認画面が表示されます。ここで、さらにOAuth 2.0 tutorial pageに進みます。ここからは認証の作業です。
※Pythonプログラムの時点でどうしても心拍数の詳細が取得できない問題が発生していましたが、上でPersonal設定にしたはずがServerになっていました。「Edit Application Setting」で変更する事で解決しましたのでメモします。その場合、token.txtも作り直しが必要でした。
ちなみにfitbit公式からOAuthに関して以下の公式リファレンスが出ています。参考までに。
公式リファレンス:https://dev.fitbit.com/build/reference/web-api/oauth2/
次に、Authorization Code Flowにチェックを付け、Select Scopesに全てチェックを付けます。Expires Inはデフォルトにしました。
次にその下に書いてあるURLをクリックします。
URLをクリックすると、認証させるデータを選択する場面が表示されます。プログラムの詳細はまだ決まっていないので、ここは全て許可で良いと思います。
「このサイトにアクセスできません」と怒られるがそのままでよし。このページのURLが重要なだけです。
トークンの発行
許可が終了したら、次はtokenの発行を行います。
先ほどのアクセスできなかったページのURL欄に記載の「code=」の次から「#」までの間の文字列をCode欄にコピペします。
Terminalやコマンドプロンプトで生成されたコードを実行します。
実行が成功すると、access_tokenやrefresh_tokenが記載されたテキストが生成されるので、これを「token.txt」というファイルにしてプログラム実行ディレクトリに置きます。
※Pythonの辞書型と同じフォーマットであるため、Pythonコード内で容易に参照可能です。
この時点で何かエラーメッセージが出る場合はおそらく上記手順にミスがあるか、既にコードが期限切れになっています。ページ自体を更新してからやりなおしてみて下さい。
fitbitライブラリのインストール(pip install)
PythonでfitbitのAPIを叩くために、pip installでfitbitをインストールしておきます。僕はAnaconda等を使っていないので、以下のようにインストールしました。
1 2 3 4 5 |
# Windows python -m pip install fitbit # Mac pip3 install fitbit |
これで準備は完了です!それではPythonコードを書いていきましょう。
fitbitAPIを使って情報を取得するPythonコード
公式リファレンス
fitbitからは様々な情報を取得する事が出来ます。一つ一つブログで紹介するには膨大な記事になってしまうことと、公式リファレンスにしっかりまとまっていることから、以下の公式リファレンスのリンクをメモしておきます。
fitbit.Fitbit:https://python-fitbit.readthedocs.io/en/latest/
まずはコードに書いて実行後、printで中身のフォーマットを確認し、リストで抽出するのか辞書型でキーを引くのかといった詳細の抽出方法を決定するのが基本の使い方だと思います。
例えば、get_devices()メソッドではデバイスの情報を取得できそうですが、printで中身を見てみると、.get_devices()[0]['battery']とすればバッテリーの残存レベル(Highとか)の情報を取得できることがわかります。
ここまでわかれば、あとは自力で欲しい情報を持って来れそうです。
tokenを自動更新するコード
僕のOAuthに対する理解はまだ曖昧ですが、tokenは期限切れまでの期間が短く、refresh tokenを使ってたった一度だけ期限の更新が可能…という理解をしました。
refresh tokenはtoken更新後再発行されますが、これを先ほど作成したtoken.txt内に書き込む処理を入れておくと、コード実行時に毎回自分でaccess_tokenとrefresh_tokenを記入しなおす事が無くなります。
この辺のコードはググると多くの方が同一のコードを使っていました。今回僕も更新に使うファイル書き換え関数は以下のサイト様を参考にさせて頂きました。
- ブログを書くまでが「PythonからFitbit APIを使ってデータを取得する(OAuth2)」
- サボテンパイソン「[fitbit] 3. PythonでFitbit APIを使って心拍数データを取得してmatplotlibで表示」
def updateToken()をfitbit.Fitbit()のrefresh_cbで使うのがキーポイント(この使い方は自分だけだとわからなかった…先人のサイト様に感謝)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import fitbit from ast import literal_eval # tokenファイルを上書きする関数 def updateToken(token): f = open(TOKEN_FILE, 'w') f.write(str(token)) f.close() return # ユーザ情報の定義 CLIENT_ID = '******' CLIENT_SECRET = '********************************' TOKEN_FILE = "token.txt" # ファイルからtoken情報を読み込む tokens = open(TOKEN_FILE).read() token_dict = literal_eval(tokens) access_token = token_dict['access_token'] refresh_token = token_dict['refresh_token'] # .FitbitでClient情報を取得 # refresh_cbに関数を定義する事で期限切れのtokenファイルを自動更新する client = fitbit.Fitbit(CLIENT_ID, CLIENT_SECRET, access_token = access_token, refresh_token = refresh_token, refresh_cb = updateToken) # access_tokenとrefresh_tokenの確認 print('access_token=', access_token) print('refresh_token=', refresh_token) |
上記コードの使い方は、
- CLIENT_ID
- CLIENT_SECRET
にApp登録時に生成されるユーザ固有の情報を登録し(ここもファイルから呼び出しても良い)実行するだけ。access_tokenとrefresh_tokenを単純に確認するだけのコードです。
一定時間経過後(例えば翌日とか)に再度コードを動かす事で、token.txtが更新され、print(refresh_token)の結果が変わっている事が確認できるでしょう。
fitbitのAPIを使う操作は今後全てこのコードを基本として構成します!
fitbitから心拍数を時系列データで取得するコード
指定日の心拍数プロットまでの全コード
以下のコードはTODAYで指定した日付の心拍数データを取得してmatplotlibでプロットするまでの全コードです。
読みやすさの観点からdef get_heart_rate()関数を別途作っています。この関数ではintrady_time_seriesで時系列データを取得し、Pandasのデータフレームに変換する所までを行なっています。
引数としてdetail_levelがありますが、これはfitbit側の仕様で1sec, 1min, 15minのデータ間隔が指定できます。
さらに、matplotlibでそのままプロットしてしまうと横軸が細かくなり過ぎてしまうため、.xaxis.set_major_locatorと.xaxis.set_major_formatterを使ってx軸の間隔を指定しています。
今回はHourLocatorで0時から24時までを6時間間隔で設定していますが、MinutesLocatorで分間隔にしたりも可能です。そして、表示フォーマットは%Hの他にも%Mや%Sで分や秒にも対応。お好みで変更してみて下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
import fitbit from ast import literal_eval import pandas as pd from matplotlib import pyplot as plt from matplotlib import dates as mdates # tokenファイルを上書きする関数 def updateToken(token): f = open(TOKEN_FILE, 'w') f.write(str(token)) f.close() return # 心拍数を取得する関数 def get_heart_rate(date, detail_level): # heart rateを1[s]単位で取得してpandas DataFrameに変換する hr = client.intraday_time_series(resource='activities/heart', base_date=date, detail_level=detail_level)['activities-heart-intraday']['dataset'] hr = pd.DataFrame.from_dict(hr) return hr # ユーザ情報の定義 CLIENT_ID = '******' CLIENT_SECRET = '********************************' TOKEN_FILE = "token.txt" # ファイルからtoken情報を読み込む tokens = open(TOKEN_FILE).read() token_dict = literal_eval(tokens) access_token = token_dict['access_token'] refresh_token = token_dict['refresh_token'] # .FitbitでClient情報を取得 # refresh_cbに関数を定義する事で期限切れのtokenファイルを自動更新する client = fitbit.Fitbit(CLIENT_ID, CLIENT_SECRET, access_token = access_token, refresh_token = refresh_token, refresh_cb = updateToken) # 日付を指定して心拍数を取得する関数を実行(detail_level='1sec'/'1min'/'15min') TODAY = '2021-05-26' hr = get_heart_rate(date=TODAY, detail_level='1sec') print(hr) # ここからグラフ描画------------------------------------- # フォントの種類とサイズを設定する。 plt.rcParams['font.size'] = 14 plt.rcParams['font.family'] = 'Times New Roman' # 目盛を内側にする。 plt.rcParams['xtick.direction'] = 'in' plt.rcParams['ytick.direction'] = 'in' # グラフの上下左右に目盛線を付ける。 fig = plt.figure() ax1 = fig.add_subplot(111) ax1.yaxis.set_ticks_position('both') ax1.xaxis.set_ticks_position('both') # 軸のラベルを設定する。 ax1.set_xlabel('Time [h]') ax1.set_ylabel('Heart Rate [bpm]') # スケールの設定をする。 ax1.xaxis.set_major_locator(mdates.HourLocator(byhour=range(0, 24, 6))) ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H')) # データプロットの準備とともに、ラベルと線の太さ、凡例の設置を行う。 ax1.plot(pd.to_datetime(hr['time']), hr['value'], label=TODAY, lw=1) ax1.legend() # レイアウト設定 fig.tight_layout() # グラフを表示する。 plt.show() plt.close() # --------------------------------------------------- |
Pandasデータに対しmdatesでDateFormatterを使う時に、pd.to_datetime()で時系列データに変換しないと機能しません。僕はしばらくこのFormatterにはまったのでご注意を。
以下が結果。秒単位の心拍数が取得されています。
ちなみにfitbitのアプリ上から同じ日の心拍数データを取得すると以下のような絵になります。アプリでは毎分('1min')しか見れないみたいですが、データはクラウド上に毎秒ストレージされているみたいです(今では当たり前の技術ですが、結構すごい)。
ちなみにこの日は運動不足解消のためのランニングをしたりしていたので明らかに有酸素運動やピーク傾向が見れる領域があります。ラインが途切れているのはシャワー兼充電タイム。
…日常生活でも階段を登ったりはしているけど、ちょっと平均高い?
fitbitでデータ分析して大きな病気を未然に防ぐ事もできそうですね。気をつけねば!
fitbitから指定日の消費カロリーを取得するコード
心拍数と同様に、消費カロリーを取得するコードを以下に示します。
心拍数の時のコードをテンプレートにdef get_calories()を作成し、forループで指定日の消費カロリーを取得します。
DayLocator, Formatterの部分は日付データ用に変更してあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
import fitbit from ast import literal_eval import pandas as pd from matplotlib import pyplot as plt from matplotlib import dates as mdates # tokenファイルを上書きする関数 def updateToken(token): f = open(TOKEN_FILE, 'w') f.write(str(token)) f.close() return # 消費カロリーを取得する関数 def get_calories(date): # caloriesを1[d]単位で取得してpandas DataFrameに変換する cal = client.time_series(resource='activities/calories', base_date=date, period='1d')['activities-calories'][0]['value'] return cal # ユーザ情報の定義 CLIENT_ID = '******' CLIENT_SECRET = '********************************' TOKEN_FILE = "token.txt" # ファイルからtoken情報を読み込む tokens = open(TOKEN_FILE).read() token_dict = literal_eval(tokens) access_token = token_dict['access_token'] refresh_token = token_dict['refresh_token'] # .FitbitでClient情報を取得 # refresh_cbに関数を定義する事で期限切れのtokenファイルを自動更新する client = fitbit.Fitbit(CLIENT_ID, CLIENT_SECRET, access_token = access_token, refresh_token = refresh_token, refresh_cb = updateToken) # 日付を連番で作成 DAYS = pd.date_range("2021-05-22", periods=7, freq="D") # 設定した日付で消費カロリーを取得する cals = [] for i in range(len(DAYS)): cal = int(get_calories(date=DAYS[i])) cals.append(cal) print(cals) # ここからグラフ描画------------------------------------- # フォントの種類とサイズを設定する。 plt.rcParams['font.size'] = 14 plt.rcParams['font.family'] = 'Times New Roman' # 目盛を内側にする。 plt.rcParams['xtick.direction'] = 'in' plt.rcParams['ytick.direction'] = 'in' # グラフの上下左右に目盛線を付ける。 fig = plt.figure() ax1 = fig.add_subplot(111) ax1.yaxis.set_ticks_position('both') ax1.xaxis.set_ticks_position('both') # 軸のラベルを設定する。 ax1.set_xlabel('Day [d]') ax1.set_ylabel('Heart Rate [bpm]') # スケールの設定をする。 ax1.xaxis.set_major_locator(mdates.DayLocator(bymonthday=None, interval=1)) ax1.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d')) # データプロットの準備とともに、ラベルと線の太さ、凡例の設置を行う。 ax1.plot(pd.to_datetime(DAYS), cals, label='Calories', lw=1, marker='o') print(type(DAYS), type(cals[0])) # レイアウト設定 fig.tight_layout() fig.legend() # グラフを表示する。 plt.show() plt.close() # --------------------------------------------------- |
下図の結果が消費カロリー取得結果です。このコードの欠点はforループを使っている事で、1時間に150回しか情報を取得できない事です。
例えば1年分(365日)取得しようとすると、一度には無理で3時間かかる事になります。
まとめ
本記事ではfitbitの概要とAPIを使うための準備として、アプリの登録からOAuth2.0を使った認証、Pythonコード例を紹介しました。
実用的にPythonコードでfitbitの情報を取得するために、tokenの自動更新が必要な事がわかりました。
心拍数や消費カロリーを取得する方法を把握しましたが、同じような手法でその他情報も容易にGetできるようになったと思います。
今後はその他WATLABブログの内容とコラボさせて面白い開発ができると良いと思っています。
fitbit愛好家としては念願のfitbit情報取得ができました!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!