記事・メモ一覧

default eye-catch image.

RESTFul API 設計の掟

はじめに どうすれば、開発者に喜ばれるAPIを設計できるのか、RESTFulAPI設計のバイブル「Web API Design - Crafting Interfaces that developers love」をまとめてみます。 URLの設計方針 URLはリソースを表す URLはあくまでリソースを表す識別子とし名詞により表現すること リソースを複数形で表現すること。 リソースに対する操作を、動詞を使ったURLで表現しないこと 抽象的な名詞より具体的な名詞が良い より多くの意味を含もうとすると items といったボンヤリした単語になる。 items では、APIの利用者(開発者)にとって意味が分かりづらい。 例えば、以下のようなURLは適切。 /dogs/123 /dogs/123/age /dogs/123/color リソースに対する操作をHTTPメソッドで表現する リソースに対する操作をCRUDの4種類に分類すること CRUDを4種類のHTTPメソッドに対応させること。 操作HTTPメソッド CreatePOST ReadGET UpdatePUT DeleteDELETE リソースとHTTPメソッドの組み合わせは以下のような意味となる。 リソースPOSTcreateGETreadPUTupdateDELETEdelete /dogsdogを新規作成dog一覧を取得全てのdogを一括更新全てのdogを削除 /dogs/1234未定義ID=1234のdogを取得もしID=1234のdogがあれば更新もしID=1234のdogがあれば削除 リソース間の関連を簡潔に表現する 原理主義的に全てのリソースをURLに含めるとURLの階層が深くなりがちである。 /owners/954/dogs/123/red/runninng/park ... リソースとパラメータを分類することでリソースを表すURLの階層を浅く保つ パラメータはリスエストパラメータとして表現する /dogs?owner=954&color=&red&state=running&location=park エラーハンドリングをしっかりする API利用者にとってはAPIはブラックボックスとなる。API開発者は利用者に対してブラックボックス内部で発生した出来事を伝える義務がある。 さらにAPI開発者に対しても以下のようなメリットが生まれる TestFirstな開発を進めやすくなる プロダクトが世に出回ったとき発生したトラブルを追跡しやすくなる 以下の2つでエラーを表現する。 HTTPステータスコード HTTPレスポンス文字列 まず、以下の3つのステータスコードで済ませられないか検討する。 200 - OK 400 - Bad Request 500 - Internal Srver Error 必要であれば、以下から選んで上記に加える。 201 - Created 304 - Not Modified 404 - Not Found 401 - Unauthorized 403 - Forbidden 開発者向けメッセージ、利用者向けメッセージなどを含めるなど、レスポンス文字列を可能な限り充実させること。 バージョニング APIの更新による後方互換性を考慮し、URLにバージョンを含めること。例えば以下の通り。 api/v1/hoge/123 api/2016-06-12/hoge/123 api/hoge/123?v=1.0 旧APIをいつまで保守すべきかについて、以下のような方針を取ること。 少なくとも1個前は保守が必要 保守を停止することに対して開発者の反応を\"1サイクル\"見ること。 ここでいう\"1サイクル\"とは、開発対象による。モバイルアプリなら短いだろうしWebアプリなら長くなる。 リソース範囲を限定する方法 開発者はいつも全てのデータを必要としている訳ではない。以下の二つの戦略で取得したい範囲を限定できるべきである。 Partial Response戦略 あるデータについて常に全フィールドを返すのではなく、対応するデータのみ取得できる手段を提供すること。 リクエストパラメータに取得したいフィールドを付与することで実現すること。 Pagenation戦略 1回あたりの取得量を制限できる手段を提供すること。 例えば 100件のデータのうち、1回あたり20件を表示できるようにしたとき、page=3,limit=20 等をリクエストパラメータに付与できること。 複数のフォーマットをサポートする json、xml、csvなど複数のフォーマットをサポートする 以下のようなURL上の表現方法がある クエリパラメータに付与 : /api/v1/dogs/123?alt=json 拡張子 : /api/v1/dogs/123.json jsonフォーマットが最も普及しているためjsonをデフォルトとする jsonフォーマットを採用する場合、属性はJavaScriptの書式(CamelCase,オブジェクトタイプによる大文字小文字制御など)に従うこと 例外的な扱い HTTP Status Code を利用できない環境 HTTP Status Codeとして常に200=OKを返す レスポンスに StatusCode を表す属性を付与する PUT、DELETE等のHTTPメソッドが利用できない環境 全てをGETリクエストとする 動詞部分をクエリパラメータとする GET /api/v1/dogs?method=put&location=park など 認証 RESTfulAPIの認証で一般的なOAuth2.0を採用すること OAuth2.0と似て非なる認証を採用するのはN.G.なぜなら セキュリティ的に危険だから 開発者が慣れていないから 何度も呼び出さないといけないAPIにしない 開発者がどのようにAPIを使うか想像してAPIを設計すること 呼出回数を軽減することを心がけるkとお 例えば、ほとんどのケースである条件で絞り込んだ結果が求められるのに、必ずAPI経由で絞込みを行わせるなどは避ける 使われ方を想定したデフォルトを設定する

default eye-catch image.

FitbitAPI 日中の詳細なActivityデータを時系列に取得する

秒単位の消費カロリーなど詳細なデータを取得する Fitbit APIにより、秒単位の消費カロリーなど、詳細な分析を行うのに必要なデータを取得することもできる。 Access to the Intraday Time Series for personal use (accessing your own data) is available through the \"Personal\" App Type. 詳細なActivityデータの利用は個人的な利用に限られる ドキュメントの先頭に以下のような注意書きがあり、あくまで個人的な利用を想定しているようだ。商用利用を目的とする場合は api@fitbit.com を通じて許可を得る必要があるようだ。理由は、かなりのデータ量になるためリソースに対するチャージであったり、fitbitの真髄である「24時間秒単位の心拍数」を\"ヒドいアプリに応用されたくない\"というイメージ的なモノであったり、いろいろだと思う。 OAuth2 Authenticate grant flowの一番最初のフェーズにおいてWebアプリを登録する際に、アプリケーションタイプとして「Personal」を選択しないとデータを得られないようだ。 Access to the Intraday Time Series for personal use (accessing your own data) is available through the \"Personal\" App Type. 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を叩いても秒単位の心拍数データを得られなかった。秘伝のタレは簡単には渡してもらえないということか。ドキュメント上は以下のデータに限って詳細データを取得できる。 activities/calories activities/steps activities/distance activities/floors activities/elevation 諸々の注意事項 trackerは7日分のデータを蓄えることができる。それを超えると古いデータから順に失われる。 trackerが7日以上同期されていない場合、辻褄合わせが発生する。抜けた分について、概要は同期されている分だけ、詳細な歩数はゼロ、詳細なカロリーはBMR、EERから計算される。なお、失われた詳細な歩数はFitbitが用意する式によって計算できる。 現在の実装では、ユーザのタイムゾーンにおけるタイムスタンプ値のみ出力する。 trackerデータ欠損時のデータ補完アルゴリズム ちょっと難しくて訳せず... If a user had no Fitbit tracker data for the specific day then the greater of Logged Activities + BMR (for minutes when there is no activity) and the calories calculated from the EER for that day (if EER enabled for this user\'s profile) are taken. In case, there was some data from the tracker for the specific day, that data where available is used and for time where data is unavailable, the BMR is used. If the total is less than 20% greater than BMR then the EER (cals < EER * 0.8) is used. EER never used to calculate calories for today. Fitbitが採用するBMR算出式 身長、体重など簡単に取得できるデータからBMRを推定する計算する式がいくつか存在する。 その中で、Fitbitは MD Mifflin-St Jeor equation を採用している。 9.99 * weightKg + 6.25*heightCm - 4.92*ageYears + 5 (男性) 9.99 * weightKg + 6.25*heightCm - 4.92*ageYears - 161 (女性) 参考)他には以下のようなものがある。それぞれに関して資料を見つけた。 Harris–Benedict式 Owen式 Wang式 Cunningham式 日本人のための式? 母集団が脳梗塞急性期の患者だしn数が30と少ないので俯瞰するに留めるが、推定の精度について議論がある分野のようで、特に欧米人向けの推定式を日本人にそのままあてはめて良いか、という大きな議論があるようだ。 推定式平均値分散二乗平均平方根 Harris–Benedict 式1087980099 Mifflin 式101234829187 Owen 式129051573227 日本人のための簡易式128547083217 Wang 式120832631181 Cunningham 式117527216165 このペーパーの中では、Miffin式は、間接熱量計実測値よりも推定量が下振れする傾向を示している。 Fitbitが採用するEER(TEE,Total energy expenditure) EERとは、Estimated Energy Requirement の略。何のこっちゃであるが、括弧付きで TEE ,Total energy expenditure が付与されているところから、カロリー消費を推定する指標なのだろう。Fitbitは 米国CDC(疾病予防管理センター)のペーパー(An Easy Approach to Calculating Estimated Energy Requirements)のEER推定式を採用している。 式は以下である。 TEE = 864 - 9.72 x age (years) + 1.0 x (14.2 x weight(kg) + 503 x height (meters)) (男性) TEE = 387 - 7.31 x age (years) + 1.0 x (10.9 x weight(kg) + 660.7 x height (meters)) (女性) 取得方法 PHPを使ったOAuth2認証については過去エントリ(djchen/oauth2-fitbitを使ってPHPで簡単にFitbitのデータを取得する)を参考のこと。 詳細データのリソースURIは、過去エントリ(FitbitAPI Activityデータを時系列で取得する)で紹介した概要データのリソースURIに似ている。ただし、詳細データの取得を要求できるリソースは以下に限定される。 activities/calories activities/steps activities/distance activities/floors activities/elevation RESTFul APIは以下の通りである。 GET https://api.fitbit.com/1/user/-/[resource-path]/date/[date]/[date]/[detail-level].json GET https://api.fitbit.com/1/user/-/[resource-path]/date/[date]/1d/[detail-level].json GET https://api.fitbit.com/1/user/-/[resource-path]/date/[date]/[date]/[detail-level]/time/[start-time]/[end-time].json GET https://api.fitbit.com/1/user/-/[resource-path]/date/[date]/1d/[detail-level]/time/[start-time]/[end-time].json 概要データの取得に加え、[detail-level]という項目が追加されている。その値として「1min」か「15min」のいずれかを使用する。 実行例 せっかくなので、日課の1時間ランで絶賛カロリー消費中の時間帯について1分ごとの詳細データを取得してみた。 /1/user/-/activities/colories/date/2016-06-05/1d/1min/time/22:45/23:00.json Array ( [activities-calories] => Array ( [0] => Array ( [dateTime] => 2016-06-05 [value] => 129.13 ) ) [activities-calories-intraday] => Array ( [dataset] => Array ( [0] => Array ( [level] => 3 [mets] => 87 [time] => 22:45:00 [value] => 9.6848402023315 ) [1] => Array ( [level] => 3 [mets] => 88 [time] => 22:46:00 [value] => 9.7961597442627 ) [2] => Array ( [level] => 3 [mets] => 87 [time] => 22:47:00 [value] => 9.6848402023315 ) [3] => Array ( [level] => 3 [mets] => 87 [time] => 22:48:00 [value] => 9.6848402023315 ) [4] => Array ( [level] => 3 [mets] => 89 [time] => 22:49:00 [value] => 9.9074802398682 ) [5] => Array ( [level] => 3 [mets] => 74 [time] => 22:50:00 [value] => 8.2376804351807 ) [6] => Array ( [level] => 3 [mets] => 62 [time] => 22:51:00 [value] => 6.9018402099609 ) [7] => Array ( [level] => 3 [mets] => 66 [time] => 22:52:00 [value] => 7.347119808197 ) [8] => Array ( [level] => 3 [mets] => 64 [time] => 22:53:00 [value] => 7.1244797706604 ) [9] => Array ( [level] => 3 [mets] => 64 [time] => 22:54:00 [value] => 7.1244797706604 ) [10] => Array ( [level] => 3 [mets] => 64 [time] => 22:55:00 [value] => 7.1244797706604 ) [11] => Array ( [level] => 3 [mets] => 66 [time] => 22:56:00 [value] => 7.347119808197 ) [12] => Array ( [level] => 3 [mets] => 66 [time] => 22:57:00 [value] => 7.347119808197 ) [13] => Array ( [level] => 3 [mets] => 64 [time] => 22:58:00 [value] => 7.1244797706604 ) [14] => Array ( [level] => 3 [mets] => 64 [time] => 22:59:00 [value] => 7.1244797706604 ) [15] => Array ( [level] => 3 [mets] => 68 [time] => 23:00:00 [value] => 7.5697598457336 ) ) [datasetInterval] => 1 [datasetType] => minute ) )

default eye-catch image.

AWSにオレオレ証明書を登録する

この記事は「AWSでhttpsを使いたい」という要望に5分で応えるために書きます。難しい話は他のサイトを参考にしてください。 AWSでhttpsを使う際のポイントは以下 証明書はPEM形式でないとN.G.。Apacheで使う形式ではダメ。 2016年6月現在、AWSの各種ダッシュボードからは何故かアップロードできない。 AWS CLIを使ってコマンドラインから証明書をアップロードして登録する。 EC2、Beanstalkのロードバランサに対して、ポート番号443をリスナ登録する際に登録済み証明書を設定する。 前提として、AWS CLIがインストール済みであるものとします。まだの人は過去記事(IAM (Identity and Access Management)ユーザの追加と AWS CLIのセットアップ)を参考にしてインストールを済ませてください。 オレオレ証明書(PEM形式)の作成 サーバ証明書の作成 $ pwd /home/ikuty/cert $ openssl req -new -keyout certkey.pem -text -out cert.req -- 秘密鍵のパスワードを入力 Country Name (2 letter code) [AU]:JP State or Province Name (full name) [Some-State]:Tokyo Locality Name (eg, city) []:Minatoku Organization Name (eg, company) [Internet Widgits Pty Ltd]:OreOreCompany Organizational Unit Name (eg, section) []:Tech Common Name (eg, your name or your server\'s hostname) []:oreore.com Email Address []:ikuty@oreore.com Please enter the following \'extra\' attributes to be sent with your certificate request A challenge password []: An optional company name []: -- 次に証明書の自己署名。 $ openssl req -x509 -in cert.req -text -key certkey.pem -out cert.crt 上記の秘密鍵のパスワードを再入力 秘密鍵からパスワードを削除 $ openssl rsa -in certkey.pem -out certnokey.pem 上記の秘密鍵のパスワードを再入力 AWS CLIを使ってオレオレ証明書をアップロード ※改行は見易さのためです。コピペする時は1行にしてください。 $ pwd /home/ikuty/cert $ aws iam upload-server-certificate --server-certificate-name MyOreOreCertification --certificate-body file://cert.crt --private-key file://certnokey.pem 成功するとjsonが返ってくる。 { \"ServerCertificateMetadata\": { \"ServerCertificateId\": \"**********************\", \"ServerCertificateName\": \"MyOreOreCertification\", \"Expiration\": \"2016-07-11T06:10:41Z\", \"Path\": \"/\", \"Arn\": \"*********************************\", \"UploadDate\": \"2016-06-11T06:17:20.991Z\" } } 確認 EC2のロードバランサに443のリスナを追加する画面で、上で追加したMyOreOreCertificationを選択できるようになります。 Elastic Beanstalkのロードバランサでも同様に MyOreOreCertificationを証明書として443のリスナを追加できるようになります。

default eye-catch image.

IAM (Identity and Access Management)ユーザの追加と AWS CLIのセットアップ

IAMユーザを追加する AWSのIAMは、Identity and Access Managementの略だそうだ(最初「I am ..」かと思った)。 AWSリソース(EC2やRDS、S3など)に対する権限を持つユーザを1つのAWSアカウントに対して複数追加できる。IAMユーザは、AWS外からAWS CLIを使ってリソースを使う際のIDとしても利用できる。 ドキュメントにはこう書いてある。 AWS アカウントには 1 つ以上の IAM ユーザーを作成できます。組織に新入社員が加わった場合や、AWS への API 呼び出しを実行する必要がある新しいアプリケーションを使用する場合、IAM ユーザーを作成することがあります。 私のような素人Web屋には不要なレイヤーだなー、と思いつつ Windowsで言うところのAdministratorユーザみたいなのが欲しくなる。 いずれにせよ、AWSリソースにアクセスするためには、IAMリソースとしてユーザを追加する必要がある。本エントリではAWSにIAMリソースとしてユーザを追加する方法を記述する。 方法 IAMリソースを編集する画面は、AWSサービス一覧?から「セキュリテイ&アイデンティティ」というグループの中の「Identity&Access Management」を選ぶ。以下 IAMコンソールと呼ぶ。 IAMコンソールからユーザの作成を行うと作成したユーザ毎に以下が振られる。以下の情報は作成時にしか確認できないため作成時にメモっておく必要がある。(忘れたら再作成できる。)今後、AWS CLI等からユーザの紐付けを行う際に、これらの情報が必要となる。 Access Key Id Secret Access Key 次に今作成したユーザに権限を付与する。AWS風に言うと「IAMユーザに管理ポリシーをアタッチする」。Microsoft程ではないが、なかなかヒドイ日本語のセンスだと思う。 様々なポリシーが用意されているが、Everyone-フルコントロール的なのをイメージして「IAMFullAccess」をアタッチする。 AWS CLIのセットアップ 今作成したユーザを使って外部からAWSリソースにアクセスできるようにする。 AWS CLIはPythonのパッケージ管理ツールpipを使用するが、Python2.6.3以上を要求する。さくらVPSで実験してみた。(ちなみにさくらVPSの標準インストールなCentOS6に入っているPythonは2.6.3でギリギリ。以下のコマンドを実行すると古すぎると警告が出るから先に2.7に上げておくと良いと思う)。 $ curl \"https://bootstrap.pypa.io/get-pip.py\" -o \"get-pip.py\" $ sudo python get-pip.py $ sudo pip install awscli 成功したらAWS CLIのセットアップを実行する。 $ aws configure AWS Access Key ID [None]: *********** (上で作ったユーザの Access Key ID) AWS Secret Access Key [None]: *********** (上で作ったユーザの Secret Access Key) Default region name [None]: Default output format [None]: 今回はここまで。

default eye-catch image.

FitbitAPI Activityデータを時系列で取得する

API概要 ドキュメントはこちら。このAPIを使用することで、Fitbitが管理している各種Activityデータを時系列に取得できる。取得できるActivityデータは大きくactivity系とtracker系の2種類に分類され、それぞれ以下のように分類されている。 Fitbitデバイスの運動計測機能をtrackerと呼び、純粋に運動計測機能により取得したデータをtracker系で取得するようだ。また、その上位層として、手動入力値等の追加データを含むデータをactivity系で取得するようだ。 activity系 keymeans of value activities/calories消費カロリーの集計値。基礎代謝(BMR)とtracker系消費カロリー、および手動入力された消費カロリーの合計 activities/caloriesBMR基礎代謝(BMR)の集計値。Fitbitデバイスが計測したデータに基づき計算された値 activities/steps歩数 activities/distance距離 activities/floors縦方向の移動の階数表現 activities/elevation縦方向の移動距離 activities/minutesSedentary強度により4個に分類された運動強度のうち1レベル目(最弱)の運動時間 activities/minutesLightlyActive2レベル目の運動時間 activities/minutesFairlyActive3レベル目の運動時間 activities/minutesVeryActive4レベル目の運動時間(最強) activities/activityCaloriesSedentaryレベル以上の運動強度について1分毎に計算された消費カロリー。基礎代謝(BMR)や手動入力を含む。 tracker系 keymeans of value activities/tracker/caloriesFitbit trackerにより計測された基礎代謝を含む消費カロリー。手動入力は含まない。 activities/tracker/stepsFitbit trackerにより計測された歩数 activities/tracker/distanceFitbit trackerにより計測された距離 activities/tracker/floorsFitbit trackerにより計測された縦方向の移動の階数表現 activities/tracker/elevationFitbit trackerにより計測された縦方向の移動距離 activities/tracker/minutesSedentary強度により4個に分類された運動強度のうち1レベル目(最弱)の運動時間 activities/tracker/minutesLightlyActive2レベル目の運動時間 activities/tracker/minutesFairlyActive3レベル目の運動時間 activities/tracker/minutesVeryActive4レベル目の運動時間(最強) activities/tracker/activityCaloriesFitbit trackerにより計測されたSedentaryレベル以上の運動強度について1分毎に計算された消費カロリー。基礎代謝(BMR)を含む。手動入力は含まない。 Fitbit Charge/Fitbit Charge HR 保護カバー (Dark Purple) API詳細 RESTfulAPIの書式は2通りある。以下は取得日付が1日のみの場合。 GET /1/user/[user-id]/[resource-path]/date/[date]/[period].json 開始日時、終了日時を指定し、2日以上の時系列データを取得することもできる。 GET /1/user/[user-id]/[resource-path]/date/[base-date]/[end-date].json いずれの形式ともに、resource-pathとして、API概要にて示した key 値を指定する。 実行例 それでは実行例を紹介する。まず、今日(today, 2016/06/09) の activities/calories を取得してみる。 activities-calories-intraday という1日分の非常に詳細なデータも付いてくるが本エントリでは省略する。次回紹介。 /1/user/-/activities/calories/date/today/1d.json Array ( [activities-calories] => Array ( [0] => Array ( [dateTime] => 2016-06-09 [value] => 1082 ) ) [activities-calories-intraday] => Array ( [dataset] => Array ( … ) ) ) 次に、今日(today)と、1週間(1w) を合わせて activities/calories を取得してみる。すると、今日を末日とする1週間分の消費カロリーを取得できた。1w を指定した場合、1日分の詳細データ activities-calories-intraday は出力されなかった。 /1/user/-/activities/calories/date/today/1w.json Array ( [activities-calories] => Array ( [0] => Array ( [dateTime] => 2016-06-03 [value] => 2805 ) [1] => Array ( [dateTime] => 2016-06-04 [value] => 2653 ) [2] => Array ( [dateTime] => 2016-06-05 [value] => 2834 ) [3] => Array ( [dateTime] => 2016-06-06 [value] => 2734 ) [4] => Array ( [dateTime] => 2016-06-07 [value] => 2822 ) [5] => Array ( [dateTime] => 2016-06-08 [value] => 2652 ) [6] => Array ( [dateTime] => 2016-06-09 [value] => 1098 ) ) ) 基礎代謝(BMR)を期間を指定して取得してみる。一定値ではなかった!。Fitbitはtrackerから基礎代謝を計算で求めているということのようだ。すごいすごい。 /1/user/-/activities/tracker/calories/date/2016-06-05/2016-06-06.json Array ( [activities-caloriesBMR] => Array ( [0] => Array ( [dateTime] => 2016-06-05 [value] => 1603 ) [1] => Array ( [dateTime] => 2016-06-06 [value] => 1604 ) [2] => Array ( [dateTime] => 2016-06-07 [value] => 1594 ) [3] => Array ( [dateTime] => 2016-06-08 [value] => 1603 ) ) ) activities/tracker/calories を1日だけ取得してみる。today を指定すると1日分の詳細データが含まれていたが、期間指定により1日分を指定すると詳細データが含まれない。今回のケースでは、activities/calories と activities/tracker/calories は同一値(2834)である。 /1/user/-/activities/tracker/calories/date/2016-06-05/2016-06-05.json Array ( [activities-tracker-calories] => Array ( [0] => Array ( [dateTime] => 2016-06-05 [value] => 2834 ) ) )

default eye-catch image.

FitbitAPI Daily Activity Summary を取得する

API概要 ドキュメントはこちら。Daily Activity Summaryとは、その名の通り、Fitbitデバイスが取得しFitbitにストアされたデータに関して1日の活動にフォーカスして集計した統計情報だ。 用途にもよるだろうが、ほとんどのケースで、このDaily Activity Summaryを取得するだけで事が足りるのではないだろうか。 Summaryのレベルでさえ、ダッシュボードには表示されない詳細なデータが記録されている。ダッシュボードやアプリが、UI/UXを実現するために、どのようにデータを選別しているかがわかる。 注釈 ドキュメントには以下の記述がある。Fitbitの製品にはセンサや機能の違いによりいくつかバリエーションがあるが、スキーマはハイエンドに揃えていて、取得できないデータには0, null, falseなど、空データが入るようだ。 Daily summary data and daily goals for elevation (elevation, floors) only included for users with a device with an altimeter. リスエストヘッダに付与した「Accept-Language」によりデータの単位を設定できる。APIを介さず普通にダッシュボードにアクセスすると設定画面から単位を設定できるのだが、それとは異なる単位を指定できるということになる。 The Get Daily Activity Summary endpoint retrieves a summary and list of a user\'s activities and activity log entries for a given day in the format requested using units in the unit system which corresponds to the Accept-Language header provided. ドキュメントに実行例と今回の実行例を比較するとスキーマが大きく異なることがわかる。競争が激しい業界なのでスキーマのアップデートも速いのだろうか。このあたりはドキュメントに記述がなく将来に渡って現在のスキーマが存在し得るのか不安になる。 API詳細 RESTful APIは以下の通り。 GET https://api.fitbit.com/1/user/[user-id]/activities/date/[date].json パラメタは以下の通り。 parameter name explain sample user-id ユーザIDを取得する。\"-\"を指定するとログイン中のユーザ(通常、自分)となる。自分以外のIDを取得したいケースというのは...?。(Fitbitにはグループで実績を共有する概念があるのでグループメンバのIDを指定するのかも...詳細は不明)。 \"-\" date 取得したいデータの日付を指定する。フォーマットは\"yyyy-mm-dd\"。 2016-06-08 summaryオブジェクト key name value means activeScore不明 activityCaloriesFitbitが検出したActivityにより消費したカロリー caloriesBMR基礎代謝 caloriesOut直訳すると、プレミアムプラン会員の専属トレーナー(または自分)が設定した消費カロリー目標。 Calorie burn goal (caloriesOut) represents either dynamic daily target from the premium trainer plan or manual calorie burn goal. Goals are included to the response only for today and 21 days in the past. distancesdistanceオブジェクトの配列 elevation垂直方向の移動距離(?と思われる。) fairlyActiveMinutesまずまず運動している時間 [分] 運動強度は sedentary < light < fairly < veryActive floors垂直方向の移動距離を建物の階数に変換した数値。ダッシュボードではこちらが使われている。 heartRateZonesheartRateZoneオブジェクトの配列 lightlyActiveMinutes軽く運動している時間 [分] marginalCalories不明。あまり重要でないカロリー? restingHeartRate安静時心拍数 sedentaryMinutes座っている、静止している時間 [分] steps1日の歩数 veryActiveMinutes激しく運動している時間 [分] distanceオブジェクト key name value means activity距離を集計するActivity、total, tracker, loggedActivities, veryActive, moderatelyActive, lightlyActive, sedentaryActive distance距離 [km] heartRateZoneオブジェクト key name value means min心拍数ゾーンを定義する最低心拍数 [bmp] max心拍数ゾーンを定義する最大心拍数 [bmp] name心拍数ゾーンに付けられた名称 minutes1日のうち、該当心拍数ゾーンにあった時間 [分] caloriesOut1日のうち、該当心拍数ゾーンで消費したカロリー [Cal] 実行してみる OAuth2 Authenticate grant flow の実装例は前のエントリに書いた。このシリーズを始めてみて気づいたが、FitbitのAPIから得られるデータだけでは個人を識別できない。すなわち、APIから取得できるデータは個人情報ではない。管理側のアプリが個人識別情報とセットでストアして初めて個人情報になる。 データ項目についてはドキュメントを参照のこと。見慣れないデータも出てくるので新鮮だ。caloriesBMRとは基礎代謝だろうか。総消費カロリー(caloriesOut) と別のデータとなっている。 Array ( [activities] => Array ( ) [goals] => Array ( [activeMinutes] => 30 [caloriesOut] => 2722 [distance] => 8.05 [floors] => 10 [steps] => 10000 ) [summary] => Array ( [activeScore] => -1 [activityCalories] => 1380 [caloriesBMR] => 1594 [caloriesOut] => 2822 [distances] => Array ( [0] => Array ( [activity] => total [distance] => 12.61 ) [1] => Array ( [activity] => tracker [distance] => 12.61 ) [2] => Array ( [activity] => loggedActivities [distance] => 0 ) [3] => Array ( [activity] => veryActive [distance] => 7.65 ) [4] => Array ( [activity] => moderatelyActive [distance] => 0.83 ) [5] => Array ( [activity] => lightlyActive [distance] => 4.14 ) [6] => Array ( [activity] => sedentaryActive [distance] => 0 ) ) [elevation] => 54.86 [fairlyActiveMinutes] => 21 [floors] => 18 [heartRateZones] => Array ( [0] => Array ( [caloriesOut] => 1549.5324 [max] => 92 [min] => 30 [minutes] => 1155 [name] => 範囲外 ) [1] => Array ( [caloriesOut] => 646.17384 [max] => 128 [min] => 92 [minutes] => 157 [name] => 脂肪燃焼 ) [2] => Array ( [caloriesOut] => 538.62588 [max] => 156 [min] => 128 [minutes] => 59 [name] => 有酸素運動 ) [3] => Array ( [caloriesOut] => 0 [max] => 220 [min] => 156 [minutes] => 0 [name] => ピーク ) ) [lightlyActiveMinutes] => 158 [marginalCalories] => 904 [restingHeartRate] => 68 [sedentaryMinutes] => 580 [steps] => 15669 [veryActiveMinutes] => 69 ) ) 結論 FitbitAPIからDaily Activity Summary 実データを俯瞰してスキーマの定義をまとめた

default eye-catch image.

Authenticate code grant flow で FitbitAPI からデータを取得する例

Fitbit APIからデータを取得するためには OAuth2 をパスする必要がある。FitbitAPI は以下の2つのflowをサポートしている。 Authenticate grant flow implicit grant flow ドキュメントでは、サーバサイドコードからFitbitAPIにアクセスする場合、Authenticate grant flow が推奨されている。 このエントリでは、djchen/oauth2 というoauth2クライアントを利用して、Authenticate grant flow をパスするサンプルを作成してみる。 composerによるoauth2クライアント(djchen/oauth2)の導入手順 dev.fitbit.comへのサンプルアプリ登録 FitbitのOAuth2認証をパスするためのサンプル実装 Fitbitからサンプルデータの取得 composerによるoauth2クライアントの導入手順 phpスクリプトをブラウザから叩ける環境を用意する。VagrantでもVPSでも何でも。 ~/wwwが DocumentRoot となるように httpd を構成する。 $ pwd /home/ikuty/www 次に、composerを使ってOAuth2クライアントをインストールする。 $ composer require league/oauth2-client djchen/oauth2-fitbit dev.fitbit.comへのサンプルアプリ登録 これから作るサンプルアプリを dev.fitbit.com に登録する必要がある。 例えば以下のような登録を行う。 Application Name: Test Application Description: This is my first test. Application Web Site: http://hoge.com:8001 Organization: Personal Organization Web Site: https://ikuty.com/ OAuth2.0 Application Type: Personal Callback URL: http://hoge.com:8001/test.php Default Data Access: readonly すると、以下を発行してもらえる。 OAUth 2.0 Client ID Client Secret OAuth 2.0: Authorization URI OAUth 2.0: Access/Refresh Token Request URI FitbitのOAuth2認証をパスするためのサンプル実装 サンプル実装、といっても本家のサンプルをそのまま流用しただけだが…。 以下の Authenticate grant flow の流れの通りとなっている。 ClientID,ClientSecretを使用して認証コードを取得する 認証コード を accessToken に変換する accessToken の有効期限に達したら refreshToken を使って accessToken を更新する $ pwd /home/ikuty/www $ vi test.php 中身は以下 <?php require_once \'./vendor/autoload.php\'; use djchenOAuth2ClientProviderFitbit; use LeagueOAuth2ClientTokenAccessToken; $provider = new Fitbit([ \'clientId\' => \'{client_id}\', // 登録時に取得したclientId \'clientSecret\' => \'{clientSecret}\', //登録時に取得したclientSecret \'redirectUri\' => \'http://hoge.com:8002/test.php\' ]); session_start(); if(!isset($_GET[\'code\'])){ $authorizationUrl = $provider->getAuthorizationUrl(); $_SESSION[\'oauth2state\'] = $provider->getState(); header(\'Location: \'.$authorizationUrl); exit; } elseif ( empty($_GET[\'state\']) || ($_GET[\'state\'] != $_SESSION[\'oauth2state\'])){ unset($_SESSION[\'oauth2state\']); exit(\'Invalid state\'); } else { try { $forceToAuth = false; $needToRewrite = false; $lines = file(\'token.txt\',FILE_IGNORE_NEW_LINES); if (($lines == false) || (count($lines) == 0) || $forceToAuth) { echo \'authorization_code->\'; //ここで 認証コード と accessToken を交換する $accessToken = $provider->getAccessToken(\'authorization_code\',[\'code\'=>$_GET[\'code\']]); $needToRewrite = true; } else { echo \'existing AccessToken->\'; $_accessToken = $lines[0]; $_refreshToken = $lines[1]; $_expiredToken = $lines[2]; $accessToken = new AccessToken([\'access_token\'=>$_accessToken, \'refresh_token\'=>$_refreshToken, \'expires_in\'=>$_expiredToken]); // accessToken の有効期限に達したら refreshToken を使って新しい accessTokenを要求する if ($accessToken->hasExpired()) { echo \'refresh AccessToken->\'; $refreshToken = $accessToken->getRefreshToken(); $accessToken = $provider->getAccessToken(\'refresh_token\',[\'refresh_token\'=>$refreshToken]); $needToRewrite = true; } } if ($needToRewrite) { $file = fopen(\"token.txt\",\"wb\"); fputs($file, $accessToken->getToken()); fputs($file, \"n\"); fputs($file, $accessToken->getRefreshToken()); fputs($file, \"n\"); fputs($file, $accessToken->getExpires()); fputs($file, \"n\"); fputs($file, $accessToken->getResourceOwnerId()); fputs($file, \"n\"); fclose($file); } } catch (Exception $e){ } } 最初、「$accessToken->getToken()を保存しておいて次回利用時に使いまわす」具体的な方法が分からなかった。oauth2-client の AccessTokenクラスの実装を見ると、コンストラクタにアクセストークン等を渡してあげればインスタンス化できることがわかった。 アクセストークンの有効期限に達すると、hasExpired()メソッドがtrueを返すようになる。その場合、リフレッシュトークンを使って新しいアクセストークンを要求する。DBに保存しておいたアクセストークンを新しい値で上書きする。 Fitbitからサンプルデータの取得 RESTfulなAPIを指定することでデータが得られる。例えばユーザのプロフィールを取得するには以下の通りとする。厳密にAPIにパラメータを全て埋め込むタイプではなくログイン情報等のセッション情報も用いられるタイプ。以下では、user-idとして\"-\"を渡すとセッションにあるユーザIDが使われるようだ。 $request = $provider->getAuthenticatedRequest( \'GET\', Fitbit::BASE_FITBIT_API_URL . \'/1/user/-/profile.json\', $accessToken, [\'headers\' => [\'Accept-Language\' => \'ja_JP\'],[\'Accept-Locale\' => \'ja_JP\']] ); $response = $provider->getResponse($request); var_dump($response);