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]: 今回はここまで。
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 ) ) )
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 実データを俯瞰してスキーマの定義をまとめた
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);