default eye-catch image.

External Network Accessを使ってSnowflakeとFitbitAPIを繋いでみた話

FitbitはAPIがしっかり整備されていて、OAuth2 endpoint経由でデータが取り放題。 せっかくなので、話題のExternal Network Access(2023年12月現在 PuPr)を試してみようと思う。 つまり、FitbitAPI→Snowflakeをやってみようと思う。 Fitbit APIを使用するにはOAuth2.0 Authorizationを通す必要がある。 Snowflakeの公式にOAuth2.0 Endpoint経由でGoogle翻訳APIと連携する段取りが書かれていて、 それをそのままFitbit APIのものに差し替えるだけで動いた。 外部ネットワークアクセスの例 外部ネットワークアクセスについては以下。 [clink implicit=\"false\" url=\"https://docs.snowflake.com/ja/developer-guide/external-network-access/creating-using-external-network-access#label-creating-using-external-access-integration-network-rule\" imgurl=\"https://www.snowflake.com/wp-content/uploads/2017/01/snowflake-logo-color-300x69.png\" title=\"外部アクセス統合の作成と使用\" excerpt=\"特定の外部ネットワークロケーションへのアクセスを有効にするには、外部ロケーションのリストと使用を許可されるシークレットのリストを指定する外部アクセス統合を作成します。UDF の作成時、あるいは CREATEFUNCTION または CREATEPROCEDURE でプロシージャを作成する際に、 EXTERNAL_ACCESS_INTEGRATIONS 句を使用してこの統合を参照することで、ハンドラーコードが外部ロケーションとの認証コードにシークレットを使用できるようになります。\"] 2016年6月に書いた記事。phpで検証をしていた。 この辺りからバッテリーがダメになる度に新しいFitbit Charge(1,2,3)を買って溜めてきた。 この間、FitbitがGoogleに買われてしまったり、スマホアプリが大幅に変わったり、色々あった。 基本的な機能はずっと動いているので、7年分のデータが溜まっているんじゃないかな、と期待。 [clink url=\"https://ikuty.com/2016/06/07/fitbitapi-authenticate-grant-flow/\"] Fitbit API側の準備 OAuth2連携に必要な情報を dev.fitbit.com から取得する必要がある。 Authorization Code Grant Flow with PKCE こちらを参考にさせていただいた。 [clink implicit=\"false\" url=\"https://www.zenryoku-kun.com/post/fitbit-api#register-app\" imgurl=\"https://www.zenryoku-kun.com/home/sakura-400w.jpg\" title=\"FitbitのWeb APIを実行する方法\" excerpt=\"Fitbit Sense2を購入しました。はじめてのスマートウォッチです。Fitbitデバイスでは、心拍数や歩数等、収集したデータをWeb APIで取得することが可能です。さっそく使って遊んでみようと思ったら、Web APIの認証がなかなか通らない、、、ドキュメントはとても充実しているのですが、OAuth2.0の認証パターンがImplicit Grant Flowの場合、Authorization Code Grant Flowの場合、PKCEを使う場合、、、などなど、情報量がとにかく多く混乱してしまいました。何はともあれ、何とか認証を通して、こんな感じで歩数などのアクティビティ情報や、心拍数や血中酸素濃度(SpO2)を取得することが出来ました。\"] 以下を準備すればOK。 access-token refresh-token client-id Snowflakeでリソース作り Snowsightでポチポチとリソースを作っていく。 USE ROLE SYSADMIN; -- 外部ロケーションを表すネットワークルールの作成 -- CREATE OR REPLACE NETWORK RULE fitbit_apis_network_rule MODE = EGRESS TYPE = HOST_PORT VALUE_LIST = (\'api.fitbit.com\'); -- 外部ロケーションとの認証に必要なOAuth認証情報を保持するセキュリティ統合の作成 -- CREATE OR REPLACE SECURITY INTEGRATION fitbit_api_oauth TYPE = API_AUTHENTICATION AUTH_TYPE = OAUTH2 OAUTH_CLIENT_ID = \'\' OAUTH_CLIENT_SECRET = \'\' OAUTH_TOKEN_ENDPOINT = \'https://api.fitbit.com/oauth2/token\' OAUTH_AUTHORIZATION_ENDPOINT = \'https://www.fitbit.com/oauth2/authorize\' ENABLED = TRUE; -- セキュリティ統合に含まれる認証情報を表すシークレットの作成 -- CREATE OR REPLACE SECRET fitbit_api_oauth_token TYPE = oauth2 API_AUTHENTICATION = fitbit_api_oauth OAUTH_REFRESH_TOKEN = \'\'; 最後に外部アクセス統合を作成する。 ストレージ統合や、Notification統合など、統合の作成にはACCOUNTADMINが必要で、 同様に外部アクセス統合の作成にはACCOUNTADMINが必要とのこと。 USE ROLE ACCOUNTADMIN; CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION fitbit_apis_access_integration ALLOWED_NETWORK_RULES = (fitbit_apis_network_rule) ALLOWED_AUTHENTICATION_SECRETS = (fitbit_api_oauth_token) ENABLED = TRUE; 外部ロケーション(ネットワーク)にアクセスするUDFsを書くロールを作成する。 UDFsを書く際に、シークレットを参照する必要がある。 UDFsを書けるロールにシークレットのREAD権限を付与しておく必要がある。 以下、そのままでは SECURITYADMINがDB・スキーマに触れないので環境により修正が必要。 USE ROLE USERADMIN; CREATE OR REPLACE ROLE ikuty_fitbitapi_developer; USE ROLE SECURITYADMIN; USE SCHEMA IKUTY_DB.PUBLIC; GRANT READ ON SECRET IKUTY_DB.PUBLIC.fitbit_api_oauth_token TO ROLE ikuty_fitbitapi_developer; GRANT USAGE ON INTEGRATION fitbit_apis_access_integration TO ROLE ikuty_fitbitapi_developer; GRANT ROLE ikuty_fitbitapi_developer TO role SYSADMIN; 本体の実装 PythonでOAuth2 Endpoint経由でFitbit APIにGETリクエストを投げるFunctionを書く。 最初、トークンのexpire時のrefreshを自力で書いていたが、get_oauth_access_token(\'cred\')により、 自動的にrefreshしてくれていることに気づいた。 use role sysadmin; use schema IKUTY_DB.PUBLIC; CREATE OR REPLACE FUNCTION fitbit_python() RETURNS STRING LANGUAGE PYTHON RUNTIME_VERSION = 3.8 HANDLER = \'hello_fitbit\' EXTERNAL_ACCESS_INTEGRATIONS = (fitbit_apis_access_integration) PACKAGES = (\'snowflake-snowpark-python\',\'requests\') SECRETS = (\'cred\' = fitbit_api_oauth_token ) AS $$ import _snowflake import requests import json def hello_fitbit(): with requests.Session() as s: access_token = _snowflake.get_oauth_access_token(\'cred\') url = \"https://api.fitbit.com/1/user/-/activities/steps/date/today/1m.json\" res = s.get(url,headers={\"Authorization\": \"Bearer \" + access_token}) res_data = res.json() return res_data $$; 実行結果は以下。1日毎の歩数を1ヶ月分取得できた(恥...)。 select parse_json(fitbit_python()); { \"activities-steps\": [ { \"dateTime\": \"2023-11-23\", \"value\": \"15570\" }, { \"dateTime\": \"2023-11-24\", \"value\": \"5392\" }, { \"dateTime\": \"2023-11-25\", \"value\": \"8993\" }, { \"dateTime\": \"2023-11-26\", \"value\": \"10525\" }, { \"dateTime\": \"2023-11-27\", \"value\": \"6371\" }, { \"dateTime\": \"2023-11-28\", \"value\": \"2713\" }, { \"dateTime\": \"2023-11-29\", \"value\": \"9252\" }, { \"dateTime\": \"2023-11-30\", \"value\": \"0\" }, { \"dateTime\": \"2023-12-01\", \"value\": \"7947\" }, { \"dateTime\": \"2023-12-02\", \"value\": \"11265\" }, { \"dateTime\": \"2023-12-03\", \"value\": \"8557\" }, { \"dateTime\": \"2023-12-04\", \"value\": \"2366\" }, { \"dateTime\": \"2023-12-05\", \"value\": \"7985\" }, { \"dateTime\": \"2023-12-06\", \"value\": \"8109\" }, { \"dateTime\": \"2023-12-07\", \"value\": \"6852\" }, { \"dateTime\": \"2023-12-08\", \"value\": \"3707\" }, { \"dateTime\": \"2023-12-09\", \"value\": \"12640\" }, { \"dateTime\": \"2023-12-10\", \"value\": \"7122\" }, { \"dateTime\": \"2023-12-11\", \"value\": \"7190\" }, { \"dateTime\": \"2023-12-12\", \"value\": \"8034\" }, { \"dateTime\": \"2023-12-13\", \"value\": \"5228\" }, { \"dateTime\": \"2023-12-14\", \"value\": \"2861\" }, { \"dateTime\": \"2023-12-15\", \"value\": \"6785\" }, { \"dateTime\": \"2023-12-16\", \"value\": \"11720\" }, { \"dateTime\": \"2023-12-17\", \"value\": \"11021\" }, { \"dateTime\": \"2023-12-18\", \"value\": \"0\" }, { \"dateTime\": \"2023-12-19\", \"value\": \"11021\" }, { \"dateTime\": \"2023-12-20\", \"value\": \"0\" }, { \"dateTime\": \"2023-12-21\", \"value\": \"2703\" }, { \"dateTime\": \"2023-12-22\", \"value\": \"3336\" }, { \"dateTime\": \"2023-12-23\", \"value\": \"7497\" } ] } 結論 PuPrのExternal Network Accessを使用して、FitbitAPI→Snowflakeが出来ることを確認した。 (途中、自動的にトークンをrefreshしてくれている、と書いたが、何度かExpireさせないと良くわからない。) 相手がOAuth2.0ならとても簡単に繋ぐことができると思う。 次は、せっかくなのでSiS(Streamlit in Snowflake)で可視化してみたりしたい。

default eye-catch image.

筋トレ強度を上げて1週間、記録を振り返る

筋トレ強度を上げて1週間経過した 有酸素運動中心のシェイプアップに、強度の高い筋トレを追加した。 1週間経過し、早速、体重・体脂肪率グラフに変化があった。 順調に減少していた体重が68kgまで戻り、体脂肪率が減少しはじめた。15.5%から14.5%へ約1%の減少。こうやってレコーディングを続けていくと身体の変わり目の時、体重、体脂肪率の傾向が急激に変化する瞬間があることに気づく。身体の水分量とか様々なバランスが変化する瞬間なのかもしれない。 消費カロリーが減った? ちょっと気になって半年分の時系列消費カロリーをプロットしてみた。特に消費カロリーが落ちて体重が増えた、ということではないようだ。左端の異常値はtrackerの誤動作ではなくインフルエンザにかかったときの記録。1600付近の値は、Fitbit Charge HRを付けなかったときの記録。 とにかく炭水化物の摂取を避けているので、2500もあればカロリー収支はマイナスになっていると思う。3000消費するとフラフラする。5月から7月にかけて一気に体重を落とした範囲では2800カロリーを目安としていた。カロリー収支に問題はないはずなので、このまま2800を維持して体重、体脂肪率の変化を追ってみる。

default eye-catch image.

FitbitAPI long range restingHeartRate plot as health index .

If the sympathetic nervous system is stimulated, the points of restingHeartRate rise directly.I think \"restingHeartRate\" will reveal the index of relaxing myself. Long term observation of restingHeartRate may specify mental or physical rhythms. So, I scracthced the script to retrieve the long term \"restingHeartRate\" from the Fitbit Heartrate API to plot its trend. The restingHeartRate itself may not be userful, but relatively useful The point of restingHeartRate itself (75,82,..) may not be useful to observe the trend of individulas. The uptrend of restingHeartRate will warn that sympathetic nervous system is continuously stimulated, and show the necessity of resting, then avoid from streath. So it\'s important the standard of restingHeartRate and it\'s important to relate the point and physical or mental conditions. Index for rest Vice versa, if this index get the uptrend, agressively rest. Plot example As below, the x axis show the date, between 2015/08 and 2016/06, about 10months. the y axis show the point of the restingHeartRate retrieved from Fitbit Heartrate api.

default eye-catch image.

OAuth2 implicit grant flow で FitbitAPI の認証をパスするサンプル

これまで、OAuth2 Authenticate code grant flow で FitbitAPI の認証をパスしてデータを取得する事例を書いてきた。implicit grant flow では認証をパスできないのか調べてみた。 Fitbit API の認証を implicit grant flowでパスする際の特徴は以下の通り。 implicit grant flow でFitbitAPI の認証をパスするには、アプリケーションタイプを Client として登録する必要がある。 access tokenの消費期限はユーザが決定する。 refresh tokenを使ってaccess tokenを更新する場合、ユーザの承認が必要となる。 本エントリにて、implicit grant flow で FitbitAPI の認証する方法を step by step で追ってみる。 Authenticate code grant flow と implicit grant flow の違いについて、↓が参考になりました。What is the difference between the 2 workflows? When to use Authorization Code flow? Authenticate code grant flow Authenticate code grant flow は以下の流れだった。 ClientID, ClientSecret から認可コードを取得する 認可コードと accessToken を交換する accessTokenを取得するためには ClientID, ClientSecret を保持しておく必要があった。 本家から図を転載する。 implicit grant flow 対して、implicit grant flow は以下のような流れとなる。 まず、本家からの図の転載。 まとめると次の通り。 ClientIDを使って accessToken を取得する https://www.fitbit.com/oauth2/authorize?state=fx2D6zGcbNVltC15sQlxh4wP8U8HH53%2B &scope=activity+heartrate+location+profile+settings+sleep+social+weight &response_type=token &client_id=xxxxxx &redirect_uri=http%3A%2F%2Fhoge.com%3A8002%2Ftest.php ブラウザ上で認証画面が表示される。この画面はブラウザである必要があり埋め込んでスルーしたりすることはできない。 許可すると、指定したコールバックURLがパラメータ付きで呼び出される。(当然伏字です) http://hoge.com:8002/test.php#scope=weight+location+social+settings+heartrate+sleep+activity+profile &state=fx2D6zGcbNVltC15sQlxh4wP8U8HH53%252B &user_id=xxxxx &token_type=Bearer &expires_in=69483 &access_token=eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NjY5MzMyMzcsInNjb3BlcyI6InJ3ZWkgc nBybyByaHIgcmxvYyByc2xlIHJzZXQgcmFjdCByc29jIiwic3ViIjoiM1FaVllYIiwiYXVkIjoiMjI3V EZKIiwiaXNzIjoiRml0Yml0IiwidHlwIjoiYWNjZXNzX3Rva2VuIiwiaWF0IjoxNDY2ODYzNzU0fQ.oy 0Px-mEauh5Jgw1yS8PF94U37tUW2Q35fbCHKcDFHU ここで得られたaccess_tokenを使って、データアクセスAPIを呼び出す。その際、クエリに含まれるaccess_tokenをAPIにアクセスする度に利用する。クエリに付けるのではなくBASIC認証のヘッダとして付与する。 URLのクエリからaccess_tokenを取得しBASIC認証で利用する。 GET https://api.fitbit.com/1/user/-/profile.json Authorization: Bearer eyJhbGciOiJIUz******************************* ******BybyByaHIgcmxvYyByc2xlIHJzZXQgcmFjdCByc29jIiwic3ViIjoiM1FaVll YIiwiYXVkIjoiMjI3VEZKIiwiaXNzIjoiRml0Yml0IiwidHlwIjoiYWNjZXNzX3Rva2 V**************************************eLtCF0V6IISPinTxy_ZgCLQl1tB0 rEMeqVk4 access_token を使いまわしていると、いずれ access_token の消費期限に到達する。 消費期限に到達した access_token を使ってデータ取得を行うと、以下のようなレスポンスが返ってくる。 { \"errors\": [ { \"errorType\": \"expired_token\", \"message\": \"Access token expired: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzAzNDM3MzUsInNjb3BlcyI6Indwcm8gd2xvYyB3bnV0IHdzbGUgd3NldCB3aHIgd3dlaSB3YWN0IHdzb2MiLCJzdWIiOiJBQkNERUYiLCJhdWQiOiJJSktMTU4iLCJpc3MiOiJGaXRiaXQiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4iLCJpYXQiOjE0MzAzNDAxMzV9.z0VHrIEzjsBnjiNMBey6wtu26yHTnSWz_qlqoEpUlpc\" } ] } ここで、Authenticate code grant flow と同様に refresh_token を使って access_token を更新する必要があるのだが、ドキュメントに記述があるように、implicit grant flow では refresh_token を取得するために client_id を使って再度認証する必要がある。 client_id はアプリケーションにストアすべきデータではなく、ユーザによる操作が必要。 Unlike the Authorization Code Grant Flow, the refresh tokens are not issued with the Implicit Grant flow. Refreshing a token requires use of the client secret, which cannot safely be stored in distributed application code. When the access token expires, users will need to re-authorize your app. また、以下に記述があるように、implicit grant flow における access_token の消費期限は Authenticate code grant flow のそれよりも長めに設定される。アプリケーション側で予め消費期限を設定できるが、最終的には認証画面においてユーザが消費機嫌を決めること値が決まる。 Access tokens from the Implicit Grant Flow are longer lived than tokens from the Authorization Code Grant flow. Users may specify the lifetime of the access token from the authorization page when an application uses the Implicit Grant flow. The access token lifetime options are 1 day, 1 week, and 30 days. Applications can pre-select a token lifetime option, but the user ultimately decides.

default eye-catch image.

FitbitAPI 1分毎の歩数を24時間、1ヶ月分 3次元プロットしてみる

前回、1分毎の消費カロリーを24時間 1ヶ月分 3次元プロットしてみた。 データを眺めていてもイメージがわかないので、前回と同様に歩数データもプロットしてみる。 Rawデータの3次元プロット X軸が日にち、Y軸が00:00:00からの経過時間(分)、Z軸が歩数である。 データはFitbitのtrackerが計測したもので、心拍数と同様に、1分あたり小数点のデータが入っている。 あくまでセンサーが出力した数値がそのまま出てくるのだろうと推測される。 (積分すると、ちゃんと1日の歩数になる。サンプリンク周期の方を1分に固定したために歩数の方が小数点になっているんだろう) 値が0から170-180までのレンジにあることが興味深い。 ジムのマシンでガンガン飛ばしているとき、マシンの数値は75回転/分から80回転/分程度。 1回転するのに2歩必要なのだから、150歩から160歩ということになる。 ちょっと多いような気もするが、だいたいそんなもん。 消費カロリーの3次元プロットと比べると、カロリーを消費していない時間帯にも割と歩いていることがわかる。 消費カロリーがフラットだが、歩数が立ち上がっている領域は、心拍数が低い領域である。 心拍数を上げずに歩いてもカロリーは消費されないことがわかる。 積分値の3次元プロット trackerが出力したRawデータを積分して、累積値を3次元プロットしてみる。 消費カロリーの積分プロットは、基礎代謝のおかげて傾きがゼロになることはないのだが、 座り仕事が続くと、歩数に関しては傾きがゼロになり、上記の図のように階段状になる。 気をぬくと、24時間フラットな図になるだろう。 基礎代謝の分を除けば、概ね、消費カロリーと歩数は相関がありそう(計算してないけど)。 万歩計はとても安くて揃えやすいから、歩数を健康の目標にすることが多いのだろうね。

default eye-catch image.

FitbitAPI 24時間消費カロリーを1か月分 3次元プロットしてみる

ふと、そのようなことを思いついた。 Fitbitのtrackerは、消費カロリー、歩数、移動距離、階数、上下移動距離を24時間1分間隔で保存している。 心拍数は「1秒間隔」というエクストリームな保存の仕方だが、アクティビティは常識的な詳細度だ。 1分間隔で保存された各値を積分すると1日分の値になる。 各値と、累積値をそれぞれプロットしてみる。 Rawデータの3次元プロット APIから返されるデータをそのまま3次元プロットしてみた。 心拍数が安静時心拍数に近い領域では消費カロリー値がゼロに近い。たぶんこれが基礎代謝。 日中(ほとんどPC作業...orz)、やはりゼロに近い(1から3の間)。 歩いたり、ジムで運動したときにオレンジの領域がポツポツ現れる。 累積値の3次元プロット 当たり前だが、1日の消費カロリーは瞬間消費カロリーの積分だ。 各値の積分値が1日分の値になるはずだ。1分毎の累積値を計算してプロットしてみた。 ジムで1時間程度の有酸素運動を行うとき、傾きが一気にきつくなり最終的に2700から2800程度に到達する。 サボると2400程度に留まる。 上記は、傾きが小さい(基礎代謝が低い)のを、なんとか運動で補正してカロリーを消費している図。 消費カロリーには基礎代謝が積分で効いてくるので、基礎代謝の上昇量(値)から想像する以上に消費カロリーは上昇することになる(と思う)。 筋肉無いのは悪だ。

default eye-catch image.

FitbitAPI 1ヶ月分の24時間心拍数を3次元プロットしてみる

ふと、タイトルのようなことを思い立った。 詳細度=1分間隔で1日分の心拍数を取得する 月初から月末まで繰り返す X軸=日にち、Y軸=0時0分からの経過時間、Z軸=心拍数となるようにデータを格納する gnuplotのsplotコマンドで格納したデータをプロットする Fitbit API の呼出し制限 思い立つこと20分、コードを作成しAPIサーバからガシガシデータを取得していたのだが、ある時点からデータが取れなくなった。 以下のようなメールがFitbitから届いた。 Hello XXXXXXX, This is an automated courtesy notice. No action is required. Your application, Test Php Application, exceeded the Fitbit API client+viewer rate limit for the users listed below. HOGEHOGE (Hit: 2016/06/18 18:08:38, Reset time: 2016/06/18 18:59:59) If you receive this notice regularly and would like to discuss potentially more efficient ways to use the Fitbit API, please contact api@fitbit.com. Best regards, The Fitbit API team 1分刻みの24時間データを31日分、5回ほどのTry&Errorでリミットに達した。APIから取得したデータをファイルに書き込んでみると、20KBくらいになる。jsonのオーバーヘッドがあるとしても1MBは取っていないはずだ。1時間後にはリミッターが解除されるので、あくまで攻撃対策なのだろうか。 実際、サービスを作るとしたらもっと高頻度にAPIを叩くと思うので、ちょっと大風呂敷広げている割にはリミッターが早いな、という印象。 ちなみに、石狩リージョンのさくらvpsからFitbitAPIに上記のような叩き方をした場合、ターンアラウンドタイムは5秒くらい。結構長い。都度つど取って良いデータではなく、1度とったら使いまわせるように保存しておかないとダメだ。 プロットしてみる プロットしてみた。gnuplotで時系列データを扱うのは割と面倒。 set grid xtics ytics mxtics mytics set datafile separator \',\' set size square set xdata time set timefmt \'%Y-%m-%d\' set format x \'%d\' set xrange [\'2016-05-01\':\'2016-05-31\'] set yrange [0:1440] set zrange [40:200] set xtics 345600 set mxtics 2 set xlabel \"day\" set ylabel \"minutes elapsed [min]\" set zlabel \"heart rate [bpm]\" set terminal png set view 60,30,1,1 set output \"hoge.png\" set ticslevel 0.5 splot \'hoge.csv\' using 1:2:3 w d lc palette title \'Heart Rate every day\' 結果 なんとなく絵になるグラフが出てきただけで、思っていたのと違うな。 手前から奥へ0時0分からの時間経過を表すが、大陸棚みたいになっている部分が睡眠時間。経験的に大陸棚が浅いと体調が悪い。心肺機能を高めると安静時心拍数が下がる(大陸棚が深くなる)ので、結果として体調が良くなる効果がある(と思う)。 奥の方でオレンジ色がポツポツ立ち上がっているのは、週4でジムに通って1時間の有酸素運動をしている部分。有酸素運動1時間というのは慣れても楽な運動ではないのだが、1日のうちの割合でいったら大した率ではないことがわかる。こうして体積を俯瞰してみると、ジムで運動することによる心拍数の増加時間の体積は無視できるような大きさに過ぎない。あぁ、悲しいなぁ。 大陸に上がってから、日によってベースとなる高度が異なることがわかる。例えば12日あたりを境にベースとなる高度が下がっている。カレンダーを遡ってみると、実はこの日は慢性的なストレスを抱える原因になっている出来事にある程度の区切りがついた日でした!すげぇ、心拍数。すげぇ。 心拍数の推移を体積として俯瞰すると、こうして何事もなかったかのようにやり過ごした心身に対するストレスを自覚するきっかけになる(かもしれない)。 ということで、とても面白いので、もう少しFitbitAPIで遊んでみます。

default eye-catch image.

FitbitAPI 詳細な心拍数データを時系列で取得する

FitbitAPI 詳細な心拍数データを時系列で取得する おそらく、Fitbitに蓄えられているデータのうち最も利用価値が高いであろう、詳細な心拍数データを取得してみる。ドキュメントの先頭に以下の記述があり、Personal としてアプリを登録した場合にのみ利用できる。 Access to the Intraday Time Series for personal use (accessing your own data) is available through the \"Personal\" App Type. このデータを使うアプリケーションは、ユーザにとってFitbitに代えがたい価値を提供しなければならない。としている。心拍数データがあってもなくても良いようなクソアプリには使うなよ、ということだろう。非営利目的ならOKとのこと。24時間心拍数を計測可能なウェラブルデバイスが何に応用できるのか、Fitbitもこうして一般に門戸を開いて研究中ということか。超絶便利で画期的なアプリやシステムを考案したら、case-by-caseで商用利用可能の許可が下りるそうだ。 Fitbitという会社の価値は24時間心拍数データの保存にあるということはわかった。 Access to the Intraday Time Series for all other uses is currently granted on a case-by-case basis. Applications must demonstrate necessity to create a great user experience. Fitbit is very supportive of non-profit research and personal projects. Commercial applications require thorough review and are subject to additional requirements. Only select applications are granted access and Fitbit reserves the right to limit this access. To request access, email api@fitbit.com. 実際、データ量が大きいので、現実的な問題としてリソース負荷も大きな理由の一つだろう。 API概要 RESTfulAPI は以下の通り用意されている。何故か dateとend-dateを両方指定できる書式があるが、以下の通り 1日以上のデータを取得することはできない。これはimplicitに\"超絶凄いアプリを考え付いたら複数日分取得できるようにするよ\"ということなのかもしれない。 If your application has the appropriate access, your calls to a time series endpoint for a specific day (by using start and end dates on the same day or a period of 1d), the response will include extended intraday values with a one-minute detail level for that day. GET /1/user/-/activities/heart/date/[date]/[end-date]/[detail-level].json GET /1/user/-/activities/heart/date/[date]/[end-date]/[detail-level]/time/[start-time]/[end-time].json GET /1/user/-/activities/heart/date/[date]/1d/[detail-level].json GET /1/user/-/activities/heart/date/[date]/1d/[detail-level]/time/[start-time]/[end-time].json parameterformatmeanings dateyyyy-MM-ddデータを取得したい日付 end-dateyyyy-MM-ddデータを取得したい日付(dateと同じである必要がある...) start-timeHH:mm(オプション)取得時間を範囲指定する場合、開始時刻を指定できる end-timeHH:mm(オプション)取得時間を範囲指定する場合、終了時刻を指定できる detail-level1sec or 1minデータを1秒区切りで取得するか1分区切りで取得するか。1日は24*60*60=86400秒だ。巨大なレスポンスとなる。 解像度と精度 脈波から心拍数を推測しているとはいえ、心拍数はだいたい1分間に60から120くらいの数値だ。1秒に1回か2回、1Hzか2Hzか、そんな程度の頻度で出現するデータだ。「1秒毎の心拍数」というのは一体どう解釈すれば良いのだろうか。 実際に計測しているのは腕の血流量だが、もしかしたら血流量の変動は心拍数を推測する以上に高頻度に変動する量なのかもしれない。実際、本エントリの末尾に「1秒毎の心拍数」をプロットして貼ってみたが、同一時間でかなりの幅をもっている。単なる歪みなのか、それとも...。detail-level = 1sec を許す fitbit の狙いが知りたい。 レスポンス概要 activities-heart-intradayの下にdatasetという名前で、時刻と心拍数のセットが永遠に繰り返される。 実行例 レスポンスが巨大すぎて実行結果をWordPressのエディタに貼り付けたら編集できなくなった。。代わりにデータをプロットしたので貼っておく。 本当に24時間分の心拍数を1秒区切りで返してくる。ときどき1.5秒になったりする。APIサーバの応答は早い。APIサーバとクライアントの間のネットワーク帯域に依存しそう。PHPがjsonをパースして配列に格納する処理の時間も無視できなさそう。 こんなデータを24時間記録するのにバッテリーが5日も持つなんて!面白くないが最初の2分を載せてみる。こんな調子で24時間心拍数データを記録できるFitbitChargeHRに感謝! [activities-heart-intraday] => Array ( [dataset] => Array ( [0] => Array ( [time] => 00:00:05 [value] => 73 ) [1] => Array ( [time] => 00:00:20 [value] => 74 ) [2] => Array ( [time] => 00:00:30 [value] => 76 ) [3] => Array ( [time] => 00:00:45 [value] => 76 ) [4] => Array ( [time] => 00:00:50 [value] => 74 ) [5] => Array ( [time] => 00:01:05 [value] => 74 ) [6] => Array ( [time] => 00:01:10 [value] => 73 ) [7] => Array ( [time] => 00:01:20 [value] => 72 ) [8] => Array ( [time] => 00:01:30 [value] => 74 ) [9] => Array ( [time] => 00:01:40 [value] => 75 ) [10] => Array ( [time] => 00:01:55 [value] => 75 )

default eye-catch image.

FitbitAPI 心拍数ゾーン毎の滞在時間を取得する

一般的に運動の強度を心拍数の下限と上限の範囲で定義することが多いようだ。Fitbitでは心拍数ゾーンが定義されていて、各ゾーンでの滞在時間数を取得できるようになっている。 API概要 書式は2通り用意されている。 GET /1/user/[user-id]/activities/heart/date/[date]/[period].json GET /1/user/[user-id]/activities/heart/date/[base-date]/[end-date].json parameterformatmeaning dateyyyy-MM-ddperiodパラメータを用いて期間指定する場合にその最終日を指定する period1d, 7d, 30d, 1w, 1m定義された識別子により範囲指定を行う。 base-date]yyyy-MM-dd取得範囲を指定する場合の開始日 end-date]yyyy-MM-dd取得範囲を指定する場合の終了日 他のAPIと同様に1か月を超えて範囲を設定することはできない。 レスポンスのスキーマ heartRateZoneオブジェクト parameterformatmeaning caloriesOutXXX.XXXX該当心拍数ゾーンにおける消費カロリー 有効桁数が考慮されている(と思う) maxXXX該当心拍数ゾーンにおける最大心拍数 minXXX該当心拍数ゾーンにおける最小心拍数 minutesXXX該当心拍数ゾーンにおける滞在時間 nameXXX該当心拍数ゾーンを識別する名称Out of Range,Fat Burn,Cardio,Peakが定義されているようだ。このうち、Fat Burnには脂肪燃焼ゾーン、Cardioには有酸素運動ゾーン、のように日本語訳が付けられている。 restingHeartRate安静時心拍数 その他 parameterformatmeaning dateTimeyyyy-MM-ddデータの日時 restingHeartRate安静時心拍数 実行例 customHeartRateZones という空のオブジェクトが返ってきた。どこかで設定できるのだろうか。 Array ( [activities-heart] => Array ( [0] => Array ( [dateTime] => 2016-06-14 [value] => Array ( [customHeartRateZones] => Array ( ) [heartRateZones] => Array ( [0] => Array ( [caloriesOut] => 1480.75044 [max] => 92 [min] => 30 [minutes] => 1137 [name] => Out of Range ) [1] => Array ( [caloriesOut] => 824.94048 [max] => 128 [min] => 92 [minutes] => 221 [name] => Fat Burn ) [2] => Array ( [caloriesOut] => 585.03432 [max] => 156 [min] => 128 [minutes] => 67 [name] => Cardio ) [3] => Array ( [caloriesOut] => 0 [max] => 220 [min] => 156 [minutes] => 0 [name] => Peak ) ) [restingHeartRate] => 69 ) ) )

default eye-catch image.

FitbitAPI 体重-体脂肪率グラフを作ってみる

せっかく生データを取得できるようになったので、記念にグラフを作ってみることにする。3ヶ月分のデータを2軸のグラフとしてプロットしてみたい。 方針は以下の通り。 認証 djchen/oauth2-fitbitを使用しOAuth2をパスする 過去エントリを参照のこと 取得 BMI/体重/(体脂肪率)取得APIを叩く 期間は3ヶ月分とする。APIを3回叩くことになる。 出力 gnuplotで描画するため、csvとする 体重、体脂肪率はFitbitAPIから得たデータを加工なしにそのまま使う 日付についてFitbitのyyyy-MM-ddからgnuplotのyyyy/MM/ddに変換する 描画 gnuplot。学生のとき以来だな^^ sakura vps の CentOS6 に yum で入れる。便利になったものだ。 gnuplot sakura vpsにgnuplotをインストール。当然 X11とかないのでバッファに出力するだけ。 $ pwd /home/ikuty $ sudo yum install gnuplot gnuplotスクリプトは以下の通り。やはり単にプロットするだけなら R より GnuPlot の方が楽で良い。 set xdata time set timefmt \"%Y/%m/%d\" set datafile separator \",\" set terminal png set output \"plot.png\" set y2tics set y2range [12:22] set multiplot set xlabel \"date\" set ylabel \"weight[kg]\" set y2label \"fat[%]\" set grid plot \"plot.csv\" using 1:2 with lp axes x1y1 replot \"plot.csv\" using 1:3 with lp axes x1y2 外部ファイルを読み込む。 $ gnuplot < gnu.plot 結果 Arialフォントが無いので文字がとても汚いが、グラフ専用ツールなので形にはなる。 朝晩2回計測しているのだがWiththingsとの連携だと1日のうち最後に計測した値だけがFitbitにストアされる。なので、以下のグラフは夜の体重、体脂肪率のグラフということになる。それではハッキリ言って意味がない。 ※体脂肪率が下がっていないのに体重が落ちているのは、筋肉量が落ちている、ということ。カロリー収支のマイナス幅が大きすぎて筋肉量も減らしてしまっているのかも。