MATLABでWebアクセス：TANITA Health Planet編

# はじめに


昨年の終わりに体重計をTANITAの物に買い換えて，『データがBluetoothでスマホに転送されるしTANITAのサーバでも記録してくれるし楽チンで良いねぇ』などと喜んでおりました．そして，データもたまってきて，自分でも加工してみたくなってようやく気がつきました．『データをダウンロードする機能がない！』




サポートにも問い合わせたのですが，そのような機能はないとのこと．なんとかならないかと調べてゆくと，あるじゃないですか，Webサーバの API が．というわけで，さっそくTANITAのWeb サービス「Health Planet ヘルスプラネット」から既存のデータを取得して遊んでみました．


# 環境・使用プログラム


Windows10 (64bit)




Google Chrome 84.0.4147.89 （64ビット）




MATLAB R2020a


# 準備


以下，アカウントは既に持っていて，サーバ上にデータが存在しているものとします．




[「Health Planet ヘルスプラネット」](https://www.healthplanet.jp/)にログインしたら，バナー右上のメニューから「登録情報の確認・変更 」をクリック．続いて「サービス連携」に進みます．出てきた画面の中程「アプリ連携」にある，「アプリケーション開発者の方はこちら」をクリックすると「APIの設定」という画面に遷移しますので，「新規登録」ボタンを押して必要情報を入力後「規約に同意して登録する」をクリックします．すると，Client ID，Client secretの各トークンと，リダイレクトで使われるDomain名（登録時に自分で入力します）が表示されますので，これらを記録しておきます．

![image_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541000/19010268-f88f-4686-d4c0-0640f6d55d5c.png)

  
# MATLABでアクセス
## パラメータの設定


MATLABでWebサービスにアクセスするための設定を行います．設定と言っても，変数やパラメータの値をセットするだけです．（ご参考までに，ヘルスプラネットのWeb API の仕様は[こちら](https://www.healthplanet.jp/apis/api.html)にあります．OAuth2.0に準拠しているそうですよ．）




下の設定で `scope` とあるのは取得する情報の種類で，体組成計の情報の場合には "`innerscan"` となります．（他に，血圧計だとか活動量計などがあるようです．）また，`response_type` は `"code"` のみが許されている値だそうです．



```matlab:Code
baseURI = "https://www.healthplanet.jp";  

client_id     = "ご自身のトークンを使ってください．";
client_secret = "ご自身のトークンを使ってください．";
redirect_uri  = "https://localhost";
scope = "innerscan";
response_type = "code";
```

## アクセス許可


送信するURLを生成して，関数 `web()`を使って送信します．Webブラウザ上でユーザーにアクセス許可を求める関係で，`webwrite`では上手くいかないようです．



```matlab:Code
oauth_auth = sprintf( ...
  "%s/oauth/auth?client_id=%s&redirect_uri=%s&scope=%s&response_type=%s", ...
  baseURI, client_id, redirect_uri, scope, response_type);

web(oauth_auth);
```


送信が上手くいくと，既定のWebブラウザー上にアクセス許可画面が現れます．ユーザーが「アクセスを許可」をクリックすると「コード発行画面」に遷移し，アクセスコードが表示されます．後でアクセストークンを取得する際にこのコード（アクセスコード）を使うので，どこかにコピペしておきます．

![image_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541000/b2a1c3a4-4e0b-a8c3-ab96-d10625dd1152.png)
![image_2s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541000/807d67af-5131-19cf-5954-a2b78c810b96.png)


## 認証


アクセスコードをプログラムのコードにコピペするのは面倒なので，入力ダイアログを用意してそこにコピペするようにします．このようにして取得したアクセスコードを送信することで，サーバーからアクセストークンを取得します．送信には関数 `webwrite`を使用します．



```matlab:Code
code = inputdlg("アクセスコードを入力してくださいな");
code = string(code);
 
tokens = webwrite(baseURI+"/oauth/token.?", ...
  "client_id",client_id, ...
  "client_secret", client_secret, ...
  "redirect_uri", redirect_uri, ...
  "code", code, ...
  "grant_type", "authorization_code");
```

## データの要求と取得


リクエストの送信とデータの受信には，再び関数 `webwrite` を使います．データはJSON形式で受け取ることにするので，`webwrite`への最初の引数が `"https://www.healthplanet.jp/status/innerscan.json?"`となります．また，パラメータ `"tag" に`は受け取る情報の種類を示すコードを指定します．「体重」と「体脂肪率」に対応するコードはそれぞれ 6021 と 6022です．その他のパラメータについては，[APIの仕様](https://www.healthplanet.jp/apis/api.html)を参考にしてください．




一度に取得できるデータは３ヶ月分という制限があるため，ここでは３回に分けてデータ授受を行っています．



```matlab:Code
data1 = webwrite(baseURI+"/status/innerscan.json?", ...
  "access_token",tokens.access_token, ...
  "date",1, ...
  "from","20191201000000", ...
  "to",  "20200229000000", ...
  "tag", "6021,6022");

data2 = webwrite(baseURI+"/status/innerscan.json?", ...
  "access_token",tokens.access_token, ...
  "date",1, ...
  "from", "20200301000000", ...
  "to",   "20200531000000", ...
  "tag","6021,6022");

data3 = webwrite(baseURI+"/status/innerscan.json?", ...
  "access_token",tokens.access_token, ...
  "date",1, ...
  "from", "20200601000000", ...
  "to",   "20200831000000", ...
  "tag",  "6021,6022");
```



以上でWebアクセスは完了．なんとも簡単ですね．遠い昔にPerlでCGIやSSI（サーバーサイドインクルード）を書いていた頃のことをほのかに思い出しました．あのころの苦労はいったい・・・


## データ処理


ここから先は完全にMATLABオンリーの世界．取得した変数 data1, data2, data3 はそれぞれ構造体で，その中の data というフィールドにほしい情報が構造体配列として格納されています．




<details><summary> `data1.data` の中身</summary><div>



|フィールド|date|keydata|model|tag|
|:--:|:--:|:--:|:--:|:--:|
|1|'202002280615'|'73.90'|'01000151'|'6021'|
|2|'202002280615'|'20.40'|'01000151'|'6022'|
|3|'202002270549'|'73.60'|'01000151'|'6021'|
|4|'202002270549'|'20.30'|'01000151'|'6022'|
|5|'202002260549'|'73.75'|'01000151'|'6021'|
|6|'202002260549'|'20.50'|'01000151'|'6022'|

</div>
</details>

構造体配列のそれぞれの要素のフィールド `keydata` に，それぞれの `tag` に対応したデータが格納されているのがわかります．フィールド `model` は測定を行った製品名を表すコードですので，ここでは使用しません．データは全て文字配列として格納されていますね．




取得した３つのデータから `data` フィールドを取り出して結合して，それをテーブル変数に変換します（この過程でフィールド `model` は除去します）．



```matlab:Code
% data フィールドの抽出並びに結合
data = [data1.data; data2.data; data3.data];

% テーブル形へ変換
dataTbl = struct2table(data);
dataTbl = dataTbl(:,["date","keydata","tag"]);
```

<details><summary> `dataTbl` の中身</summary><div>

| |date|keydata|tag|
|:--:|:--:|:--:|:--:|
|1|'202002280615'|'73.90'|'6021'|
|2|'202002280615'|'20.40'|'6022'|
|3|'202002270549'|'73.60'|'6021'|
|4|'202002270549'|'20.30'|'6022'|
|5|'202002260549'|'73.75'|'6021'|
|6|'202002260549'|'20.50'|'6022'|

</div></details>


このままでは，`keydata` が「体重」「体脂肪率」と繰り返されているので，これを別々の列にします．これを簡単にやってくれる便利な関数 `unstack` があります．「体重」「体脂肪率」に対応する列名を「`WGT`」「`BFR`」とします．



```matlab:Code
dataTbl = unstack(dataTbl,"keydata","tag", "NewDataVariableNames",["WGT","BFR"]);
```

<details><summary> 並べ替え後の`dataTbl` の中身</summary><div>

| |date|WGT|BFR|
|:--:|:--:|:--:|:--:|
|1|'202002280615'|'73.90'|'20.40'|
|2|'202002270549'|'73.60'|'20.30'|
|3|'202002260549'|'73.75'|'20.50'|
|4|'202002250546'|'74.40'|'20.30'|
|5|'202002240806'|'74.05'|'20.30'|
|6|'202002210735'|'73.40'|'20.00'|

</div></details>


あとは，各変数の型を調整してからテーブルをタイムテーブルに変換，データを日付でソートし，一週間移動平均データを加えます．



```matlab:Code
% 変数の型の調整
dataTbl.date = datetime(dataTbl.date,"InputFormat","yyyyMMddHHmm");
dataTbl.WGT = double(string(dataTbl.WGT));
dataTbl.BFR = double(string(dataTbl.BFR));

% テーブルからタイムテーブルへ
dataTbl = table2timetable(dataTbl);

% データを時間軸でソート
dataTbl = sortrows(dataTbl);

% 一週間移動平均データを付加
dataTbl.WGT_mav = movmean(dataTbl.WGT,7);
dataTbl.BFR_mav = movmean(dataTbl.BFR,7);
```


<details><summary> 最終的な `dataTbl` の中身</summary><div>![figure_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541000/8feabc55-0b6b-9061-33a1-1da2431f8b11.png)


| |date|WGT|BFR|WGT_mav|BFR_mav|
|:--:|:--:|:--:|:--:|:--:|:--:|
|1|2019/12/03 00:00:00|73.1000|19.2000|73.4625|20.5500|
|2|2019/12/03 06:55:00|73.0500|21.1000|73.3000|20.5200|
|3|2019/12/04 23:11:00|74.0000|20.3000|73.2667|20.4833|
|4|2019/12/05 05:51:00|73.7000|21.6000|73.2429|20.6286|
|5|2019/12/06 05:57:00|72.6500|20.4000|73.3500|20.8857|
|6|2019/12/07 08:30:00|73.1000|20.3000|73.3929|20.7714|

</div></details>

# お絵かき


せっかくなので，お絵かきをします．ひとつめは，体重と体脂肪の変化をそれぞれ１週間移動平均とともにプロットした物です．計量は毎朝起床直後に行っているのですが，日々のばらつきが大きいのでスムージングをかけたいとずっと思っていたのです．（TANITAさんは，安定した軽量のために朝食後２時間経過時を推奨してますが，普通はそんな時刻に計量できませんってば．）


<details><summary>お絵かきその１　MATLAB　コード</summary><div>

```matlab:Code
clf;
subplot(2,1,1);
lh0 = plot(dataTbl.date, dataTbl.WGT,'o-','MarkerSize',4,'color',[0.7 0.9 1]*0.8);
lh0.MarkerFaceColor = lh0.Color;

lh1 = line(dataTbl.date, dataTbl.WGT_mav, 'LineWidth',2,'color',[0.2 0.7 0]);
ylabel("体重 [kg]");
title("体重");
grid on;

subplot(2,1,2);
lh2 = plot(dataTbl.date, dataTbl.BFR,'o-','MarkerSize',4,'color',[0.7 0.9 1]*0.8);
lh2.MarkerFaceColor = lh2.Color;

lh3 = line(dataTbl.date, dataTbl.BFR_mav, 'LineWidth',2,'color',[1 0.5 0]);
ylabel("体脂肪率 [%]");
title("体脂肪率");
grid on;
```
</div></detail>

![figure_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541000/54554e4a-a03b-0c95-3ba6-1e031ef954ad.png)


二つ目は，体重と体脂肪率のクロスプロット．願わくば体重・体脂肪率ともに下がっていってほしいのですが，そう素直には変化してゆかないみたいです．一般に，運動を始めると初期は体脂肪の落ちよりも体重の増加が目立ち，そのうち体脂肪が落ちてくるなどと言われていますがどうでしょう．

<details><summary>お絵かきその２　MATLAB　コード</summary><div>

```matlab:Code
clf;

tmpDays = days(dataTbl.date-dataTbl.date(1));
sh = scatter(dataTbl.WGT_mav, dataTbl.BFR_mav,[],tmpDays,'filled');
set(gca,'Box','on');
xlabel("体重 [kg]");
ylabel("体脂肪率 [%]");

tmpDates = datetime(["2020/1/1","2020/2/1","2020/3/1","2020/4/1","2020/5/1","2020/6/1", "2020/7/1"]);
tmpDatesIdx = arrayfun(@(x) find(dataTbl.date > x, 1, 'first'), tmpDates);

bh= colorbar;
bh.Ticks = tmpDatesIdx;
bh.TickLabels = string(tmpDates);

grid on;
title("体重ー体脂肪率の関係の推移");
```
</div></details>

![figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541000/634845e8-00c6-f962-9e6e-3978ed6fc6b0.png)