Python×fitbit APIを使って心拍数をリアルタイム(?)にSpreadSheetsへ保存する！

# はじめに
fitbitを使い始めて1年近くになりますが、時計盤やアプリ開発ばかりでAPI周りをあまり触っていなかったことや、自身の研究のためもあり、今回はアクティビティデータのリアルタイム取得をやってみました。

データベースへ書き込んでも良かったのですが、知識がまだまだ赤ちゃんレベルなのでさくっとシートへ書き込みを選びました。(そろそろ勉強したい)

リアルタイムの後ろに(?)がついている理由は後ほど。。

# 実装ステップ
ざっくり分けて以下の手順で進めていきます。

#### 1. fitbitAPIの利用登録
#### 2. GoogleCloudPlatform(GCP)の利用登録
#### 3. APIを叩いてfitbitデータ取得
#### 4. シートに書き込み
#### 5. (簡単に)可視化


さっそく進めていきます！

※STEP1,2は素晴らしい記事がたくさんあるのでほとんどそちらを参考にさせて頂いています。


# STEP1: fitbitAPI利用登録

初っ端から他力本願ですが、[こちら](http://sakanaaas.hateblo.jp/entry/2017/03/19/215758)の記事を参考に、

- CLIENT_ID
- CLIENT_SECRET
- ACCESS_TOKEN
- REFRESH_TOKEN

を取得、設定してください。
ちなみに、自分のアプリ登録設定は以下の通りです。

※OAuth 2.0 Application Typeは*Personal*でないと、詳細なデータが取れないので注意してください！

|項目|入力内容|
|:-:|:-:|
|Application Name | HeartRate Watcher |
|Description | watch my heart rate |
|Application Website | http://localhost/ |
|Organization | personal|
|Organization Website | http://localhost/ |
|OAuth 2.0 Application Type | **Personal**|
|Callback URL | http://127.0.0.1:8080/|
|Default Access Type | Read-Only|


### このステップでやったこと

``` python:get_hr.py
import fitbit
from ast import literal_eval

CLIENT_ID     = "XXXXXX"
CLIENT_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
TOKEN_FILE    = "token.txt" #同一ディレクトリに.txtを作る

tokens = open(TOKEN_FILE).read()
token_dict = literal_eval(tokens)
access_token = token_dict['access_token']
refresh_token = token_dict['refresh_token']

def updateToken(token):
    f = open(TOKEN_FILE, 'w')
    f.write(str(token))
    f.close()
    return

authed_client = fitbit.Fitbit(CLIENT_ID, CLIENT_SECRET, access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN, refresh_cb=updateToken)
```

`authed_client`の承認が完了すれば、データの取得が好きなようにできます。

STEP1はこれで終了です。

# STEP2: GCPの諸設定
Pythonからスプレッドシートに書き込むため、こちらもAPI登録をします。

STEP2もとてもとても丁寧な解説が[こちら](https://tanuhack.com/operate-spreadsheet/#i-3)にあるため、読み進めながら設定をします。

### このステップでやったこと

``` python:get_hr.py
import gspread
import json
from oauth2client.service_account import ServiceAccountCredentials 

scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name('************.json', scope)
gc = gspread.authorize(credentials)
SPREADSHEET_KEY = '******************'
worksheet = gc.open_by_key(SPREADSHEET_KEY).sheet1
```

### 注意事項
以上の情報を登録できたら、好き方放題シートを読みまくり、書き込みまくることができます！

と言いたいところですが、**100秒あたり100件のリクエスト制限**があるので、意外と引っかかります。
セル値の取得や更新1つにつき1リクエストとみなされるので、使い回しできるところや余分な書き込みをなるべく避けるようにしましょう。（自分が上手くできてるかはわかりません。）

以上でSTEP2は終了です。


# STEP3: APIを叩いてデータ取得

色々な情報の登録が済んだらいよいよデータの取得です。

### 取得方法

``` python
data_sec = authed_client.intraday_time_series('activities/heart', 'YYYY-MM-DD', detail_level='1sec')
heart_sec = data_sec["activities-heart-intraday"]["dataset"]
```
取得したいデータの日付を`YYYY-MM-DD`, データ間隔を`detail_level`で設定します。
`detail_level`は [1sec, 1min, 15min] から選択できます。

### ちょっと変形
heart_secには`{time: mm:hh:ss, value:**}`の形で心拍数が入っています。
このままでも良いですが、日付情報を付け加えたかったので以下の通り少し変形させてから出力するようにしました。

```python
def get_heartrate_data(date):
	data_sec = authed_client.intraday_time_series('activities/heart', date, detail_level='1sec')
	heart_sec = data_sec["activities-heart-intraday"]["dataset"]
	for data in heart_sec:
		datetime = date + " " + data['time']
		data['datetime'] = datetime
		del data['time']
	return heart_sec
```
この後はシートへ投げるので、json形式のままにしておきます。

以上でSTEP3は終了です。

### その他情報を取得したい方へ
これまた丁寧に取得方法をまとめてくださっている方がいるので、心拍数以外も試してみたい方は是非[こちら](https://qiita.com/fujit33/items/27d3f3142cf02bb46436)を参考にしてみてください。

# STEP4: SpreadSheetsへ書き込み
最後のステップ、シートへ書き込みです。

投入したいデータのリストを作って、1つずつ投げていきます。
append()でうまいこと最新セルの1つ下に書き込んでくれるので、全て任せます。

``` python
headers = ['value', 'datetime'] # keys

def set_data_to_sheet(datas):
	for data in datas:
		column = []
		for header in headers:
			column.append(data[header])
		
		worksheet.append_row(column)
```

### 定期実行
以上の準備が完了したら、定期実行のためのjob()関数を作ります

```python

def get_latest_data(data):
	list_num = len(data)
	new_list = []
	for i in range(list_num-30, list_num):
		new_list.append(data[i])
	return new_list

def job():
	now = datetime.datetime.now()
	date = now.strftime('%Y-%m-%d')

	data = get_heartrate_data(date)
	latest_data = get_latest_data(data)
	set_data_to_sheet(latest_data)
```

日付設定、`get_latest_data`で最新のデータ30個を取得し、シートへ書き込むだけです。

後は、**リアルタイム**の取得を実現するため30秒に1回cronなどで定期実行させます。
fitbitAPIの制限が150アクセス/1Hなので少し余裕を持たせての設定をしています。


### 結果発表

**リアルタイムでの心拍数が取得可能!!**


とは、上手くはいかず以下のような結果になりました。。(1部抜粋)
![スクリーンショット 2020-06-03 0.20.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/588133/e290f718-924b-87e9-012a-0ac2fce2fb20.png)




30秒に一回更新される予想でしたが、最新情報の更新に約15分のディレイがあります。
ここで関係するのが、データのアップロードに関する仕様です。fitbitから直接クラウドには接続できないので、通信時には一旦スマホを介します。しかし、fitbitとスマホが約15分間隔でしか同期していないようなので、この問題が発生していると考えています。(常に同期はオンになっていますがバッテリー諸々との兼ね合いかと) 

このように、APIを用いての30秒間隔のリアルタイム取得は今のところ厳しそうなので次のように変更しました。

### リアルタイムからおよそだいたいリアルタイム(?)へ
状況を整理すると

- 最新情報へのアップデートは約15分に一回
- `heart_sec`を見ると約5秒に一回心拍数を記録（1secごとの設定のはずが頻度的に仕方ない）
- 即ち、1分に約12回記録


ということなので、最新情報がアップロードされるまでの間には、12×15=約180回分の記録が溜まっていることになります。

### 解決策は、、

それなら15分おきにcron実行で180個のデータを書き込みすればいい！

と思いましたが、ここで**100秒あたり100件のリクエスト制限**に引っかかります。

180個のデータを一気に書き込もうと挑戦しましたが制限を食らいました。(当たり前)
そこで、一回書き込むたびに1秒スリープという力技でなんとか制限をすり抜けました。(すごく時間がかかる)(データベースへ投げる方が圧倒的にスマート)


### STEP4 まとめ
結局のところ、即時のデータ取得と書き込みができなかったため、
「15分に1回、未アップロードの180個のデータを180秒かけてシートに書き込む」
というなんとも不格好なシステムとなりました。

ですが、シートには勝手にデータを記録してくれるため、何かしらの解析には使えそう?です。

# STEP5: 可視化
シートに書き込みさえすれば、すぐに可視化もできます。
と、いうか書き込まなくてもDataFrame化すればもっと簡単にグラフは作れるのでここは詳しくは割愛します。

例として、ある1日の心拍数24時間分をプロットしたものが以下のようになります。
睡眠が深い深夜~早朝は心拍数が低く、起床後に少しずつ上昇しているのがわかります。

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/588133/9591f6dd-56d9-65fc-a8cc-368cc2647db0.png)

ちゃんとデータベースで管理したら、カッコよく動的なグラフが作れると思うので、今後の課題です。

# おわりに

各種APIの設定をし、fitbitの情報を取得、シートに書き込み、可視化という一連の流れを終えました。

厳密にリアルタイムとはいきませんでしたが、15分間隔でも許容できるスマートな可視化システムや面白いアプリなどを作っていけたらと思います。

心拍数以外にも、カロリー、歩数、睡眠などなんでも記録が取れるので、fitbitユーザの方もそうでない方も是非試してみてください！