自分の睡眠をエンジニアリングしたい（データ取得編）

# TL;DR
自分の睡眠に関して分析したい。どういうときに寝れて、寝れないのか。
スマホアプリ、iPhone自身のデータ、部屋においてあるセンサーの情報を使って分析したい。
今回はその始まりとして、身の回りにあるデータをどう取得するかについて記載した。

# はじめに
この記事は [ NSSOL Advent Calendar 2018 ](https://qiita.com/advent-calendar/2018/nssol)の18日目となります。
普段はインフラエンジニアをやっていて、OpenStackやDocker、自動化周辺を中心にしています。今回はいい機会なので普段の業務とは違うことをしようと思い、ちょうど夫婦ふたりとも寝れないと文句をいうことが多いので、家庭ハックとその先にある良い仕事を目指して睡眠について分析したいと考えました。

<img width="300" alt="suimin_man.png" src="https://qiita-image-store.s3.amazonaws.com/0/8190/2ac51298-e6da-1c41-54fa-ede6dfbaed6d.png">


犯罪者のほぼ100%が水を飲んでいるように、**エンジニアのほぼ100%が睡眠を取っている**ようです。
そんな人間が生きていくために必要な睡眠ですが、~~ストレスで、~~夜暑くて眠れないや朝寒くて起きれないなど常に悩みがつきまとってきます。

近年ではIoTだスマート家電だ！と様々な場所で言われているように、身の回りの生活にある情報を使ってよりよく寝るための生活環境づくりを目指そうと思います。
今回情報を取得する対象とするのは、下記3つとなります。**iPhoneとNature Remo編で記事を分ければよかったと後悔**しているところです。

* Sleep Cycle alerm Clock
* iPhoneのヘルスケア
* Nature Remo

※本記事は分析するためのデータをどう集めて行くかという内容になります。


# 身の回りのログの取得
睡眠に関連するかもしれないログデータはどういうものがあるでしょうか。
こういった情報をどう取得するかを身の回りのものを使って考えてみます。

* 睡眠時間
* 就寝時間
* 起床時間
* 寝室の室温
* 寝室の湿度
* 部屋の明るさ
* 騒音
* 一日の消費カロリー
* ~~ストレス~~


## 睡眠のライフログ
この企画のために温め続けてきた睡眠ログ
[Sleep Cycle alarm clock](https://itunes.apple.com/jp/app/sleep-cycle-alarm-clock/id320606217?mt=8)にてデータを蓄積してきました。
アプリ上取得できているのは、

* 快眠度
* 就寝時刻
* 睡眠時間
* 起床時間
* いびき
* 天気
* 気圧
* 月齢
などです。

<img width="250" alt="IMG_B8A26EE28447-1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/8190/6fc41a63-9fde-0130-afa3-f12cfdb628c7.jpeg">

ここまで取れてれば、分析がいらないのではないかと思うほどです。
しかし、天気や気圧といった情報はあくまで住んでいる場所の天気ですので、実際の室温や湿度ではない点に注意です。またアプリ上のグラフではいろいろなパラメータについて出力されていますが、CSVファイルに出力できるのは、就寝時間、起床時間、快眠度くらいとなります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/8190/61fe2447-8f1f-9945-4058-0931c6cebaa1.png)



※睡眠ログを取りたいだけなら、Sleep CycleよりPillowを使ったほうが詳細にエクスポートできそうです。

## 日常生活のライフログ
### iPhoneのヘルスケア
iPhoneユーザにはお馴染みの**ヘルスケア**アプリの情報を利用します。
このヘルスケア情報ですが、睡眠時間からApple Watchや、体重計といった周辺デバイスの連携をすることで心拍や体脂肪率までまとめて管理することができます。(Androidユーザはすみません）

<img width="250" alt="IMG_7398ADDDD86D-1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/8190/d4c828fa-a84a-834d-5df1-bd503858e6da.jpeg">

全然使ったことがありませんでしたが、このデータはXMLで書き出すことができます。
そのためには下記のボタンを押します。ただ、このボタンを押すと結構時間がかかります。

<img width="375" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f383139302f65633539643432642d666562322d396332342d663538372d3662656238313862323863342e6a706567.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/8190/da23d91e-51fe-e01b-e814-b2cf6138e028.jpeg">


特に何も考えず書き出したものがこちら（ファイル名は変えてあります）
(日本語名ディレクトリ、ファイル名で吐き出されるとは思わなかった。)

```shell

~/Desktop/Appleヘルスケア書き出し » ls -lh AppleHealth.xml                             
-rw-r--r--@ 1 onodes  staff   736M 12 16 21:35 AppleHealth.xml

~/Desktop/Appleヘルスケア書き出し » wc AppleHealth.xml                                 
 2409032 45758198 771788148 AppleHealth.xml
```
**736MB**...そりゃ時間がかかる。

ちなみにファイルの中身はこのような形のXMLです。このあたりはApple Watchを使った心拍数が記録されているみたいです。

```
<Record type="HKQuantityTypeIdentifierHeartRateVariabilitySDNN" sourceName="Apple Watch" sourceVersion="5.1.1" device="&lt;&lt;HKDevice: 0x22cd250&gt;, name:Apple Watch, manufacturer:Apple, model:Watch, hardware:Watch2,4, software:5.1.1&gt;" unit="ms" creationDate="2018-12-13 12:11:43 +0900" startDate="2018-12-13 12:10:41 +0900" endDate="2018-12-13 12:11:42 +0900" value="97.0947">
  <HeartRateVariabilityMetadataList>
   <InstantaneousBeatsPerMinute bpm="66" time="12:10:43.14"/>
   <InstantaneousBeatsPerMinute bpm="77" time="12:10:43.92"/>
   <InstantaneousBeatsPerMinute bpm="85" time="12:10:44.63"/>
   <InstantaneousBeatsPerMinute bpm="82" time="12:10:45.36"/>
```

このXMLには何が記載されているのか、Pythonを使って調べてみます。下記をjupyter notebookを使って見ていきます。

まずはDataFrameの作成から

```python

from lxml import objectify
import pandas as pd
from pandas import DataFrame
from dateutil.parser import parse
from datetime import datetime
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

parser = argparse.ArgumentParser()
headers = ['type', 'unit', 'startDate', 'endDate', 'value']
parsed = objectify.parse(open('AppleHealth.xml'))
root=parsed.getroot()
data = [({k: v for k, v in elem.attrib.items() if k in headers})
            for elem in root.Record]
df = DataFrame(data)
```


このDataFrameの中を見てみましょう。
大体193万件の何らかのデータが入っています。最初がBMIか...:relaxed: 冷静に数字を見ると悲しい気持ちになったので :relaxed:  します。

|  | endDate | startDate | type | unit | value |
|--:|:--|:--|:--|:--|--:|
| 0 | 2016-09-26 21:10:44 +0900 | 2016-09-26 21:10:44 +0900 | HKQuantityTypeIdentifierBodyMassIndex | count | :relaxed: |
| 1 | 2016-09-26 21:05:36 +0900 | 2016-09-26 21:05:36 +0900 | HKQuantityTypeIdentifierBodyMassIndex | count | :relaxed:  |
| 2 | 2016-09-26 22:14:35 +0900 | 2016-09-26 22:14:35 +0900 | HKQuantityTypeIdentifierBodyMassIndex | count | :relaxed:  |
| 3 | 2016-09-26 22:26:48 +0900 | 2016-09-26 22:26:48 +0900 | HKQuantityTypeIdentifierBodyMassIndex | count | :relaxed:  |
| 4 | 2016-09-26 23:21:06 +0900 | 2016-09-26 23:21:06 +0900 | HKQuantityTypeIdentifierBodyMassIndex | count | :relaxed:  |
|||||
|1931273|	2018-12-13 18:14:59 +0900	|2018-12-13 18:13:55 +0900|	HKQuantityTypeIdentifierHeartRateVariabilitySDNN	|ms|	35.131|

typeに着目することでBMIを含めて、どういう値が取得できるかがわかるかと思います。

```python

df.index = pd.to_datetime(df['startDate']) #startDateをindexにしてしまいます。
```

```python

df['type'].unique()
```

```
array(['HKQuantityTypeIdentifierBodyMassIndex',
       'HKQuantityTypeIdentifierHeight',
       'HKQuantityTypeIdentifierBodyMass',
       'HKQuantityTypeIdentifierHeartRate',
       'HKQuantityTypeIdentifierOxygenSaturation',
       'HKQuantityTypeIdentifierBodyFatPercentage',
       'HKQuantityTypeIdentifierStepCount',
       'HKQuantityTypeIdentifierDistanceWalkingRunning',
       'HKQuantityTypeIdentifierBasalEnergyBurned',
       'HKQuantityTypeIdentifierActiveEnergyBurned',
       'HKQuantityTypeIdentifierFlightsClimbed',
       'HKQuantityTypeIdentifierDietaryFatTotal',
       'HKQuantityTypeIdentifierDietaryFatSaturated',
       'HKQuantityTypeIdentifierDietarySodium',
       'HKQuantityTypeIdentifierDietaryCarbohydrates',
       'HKQuantityTypeIdentifierDietaryFiber',
       'HKQuantityTypeIdentifierDietaryEnergyConsumed',
       'HKQuantityTypeIdentifierDietaryProtein',
       'HKQuantityTypeIdentifierDietaryVitaminA',
       'HKQuantityTypeIdentifierDietaryVitaminC',
       'HKQuantityTypeIdentifierDietaryVitaminE',
       'HKQuantityTypeIdentifierDietaryCalcium',
       'HKQuantityTypeIdentifierDietaryIron',
       'HKQuantityTypeIdentifierDietaryThiamin',
       'HKQuantityTypeIdentifierDietaryRiboflavin',
       'HKQuantityTypeIdentifierAppleExerciseTime',
       'HKQuantityTypeIdentifierRestingHeartRate',
       'HKQuantityTypeIdentifierVO2Max',
       'HKQuantityTypeIdentifierWalkingHeartRateAverage',
       'HKQuantityTypeIdentifierHeartRateVariabilitySDNN'], dtype=object)
```

これらの値を使うことで、日々の活動量や身体的ライフログを取得できます。
例えば歩数はHKQuantityTypeIdentifierStepCountで記録されているため、歩数に着目してデータを取得したいときは、

```python

step_count = df[df['type'] == 'HKQuantityTypeIdentifierStepCount'].copy()
step_count['value']  = step_count['value'].astype(float)
step_count_sum = step_count.groupby(pd.TimeGrouper(freq='D')).sum()
step_count.loc["2018-10-01":].plot()
```
下記歩数
![image.png](https://qiita-image-store.s3.amazonaws.com/0/8190/60c9499d-1f09-4f08-ac9a-94e7a0d4b290.png)

下記XMLに同梱されていたAppleWatchからの平均脈拍
![image.png](https://qiita-image-store.s3.amazonaws.com/0/8190/bee39f13-3c48-090f-b722-cf1fc0a7abe3.png)

実際に出力してみると、本当だろうか？という数字が多々あったので、外れ値を除外する処理を入れる必要があると思います。

## 部屋の気温、湿度の取得(Nature Remo)
どこのご家庭にでも最低一台はある[Nature Remo](https://nature.global/)を利用します。
我が家でもKickStartで購入したNatureRemoと最近追加したminiの二台体制で動いています。
![hp_remomini.jpg](https://qiita-image-store.s3.amazonaws.com/0/8190/6c9b8ed8-4952-d98e-958f-96966090dee9.jpeg)出展 https://nature.global/

Nature Remoは取り上げられる内容としてスマートリモコンの機能が挙げられます。
「アレクサ、エアコンをつけて」→NatureRemoから赤外線が出ることで、エアコンや各種赤外線コントロールが可能な機器を操作することができます。
一方で、Nature Remoには各種センサーが搭載されているので、今回はそれを使うことで部屋の環境をモニタリングする準備をします。

### Nature Remoのセンサー
はじめはどのセンサーも使うことが出来なかったですが、最近では搭載されているセンサーを使うことができるようになっています。
* 温度
* 湿度
* 人感
* 照度
* ノイズ(これ使えるのかよくわかっていない）

Nature Remoとminiでは赤外線送信は一緒ですが、搭載しているセンサーの差があります。
miniでは温度センサーしか搭載していないとのことです。

### Nature Remoのセンサー情報の取得
Nature Remoのセンサーからの情報はNature RemoのAPIを通して取得することができます。
大きく分けてCloud APIとLocal APIの二種類がありますが、センサー情報を取得するにはCloud APIを利用する必要があります。

https://developer.nature.global/

#### アクセストークンの発行
トークンの発行のために、下記にアクセスしてログインする必要があります。
https://home.nature.global/

アクセスしてログインをすれば、下記許可画面が出ます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/8190/80d40619-475f-57e4-c378-977440e27640.png)

アクセストークンを取得後アクセスコードを利用して、下記でアクセスしてみます。

```shell
curl -X GET "https://api.nature.global/1/devices" -H "accept: application/json" -k --header "Authorization: Bearer <アクセストークン>"
```
結果がわかりにくいので、jqで視認性を上げます。

```json
[
  {
    "name": "Remo-bed",
    "id": "XXXX",
    "created_at": "2018-06-23T05:43:11Z",
    "updated_at": "2018-12-17T12:55:56Z",
    "firmware_version": "Remo-mini/1.0.84-gc801a2d",
    "temperature_offset": 0,
    "humidity_offset": 0,
    "users": [
      {
        "id": "XXX",
        "nickname": "onodes",
        "superuser": true
      }
    ],
    "newest_events": {
      "te": {
        "val": 23,
        "created_at": "2018-12-17T15:41:15Z"
      }
    }
  },
  {
    "name": "Remo-room",
    "id": "XXXX",
    "created_at": "2017-05-30T11:25:23Z",
    "updated_at": "2018-12-16T22:47:11Z",
    "firmware_version": "Remo/1.0.39-g41c1188",
    "temperature_offset": 0,
    "humidity_offset": 0,
    "users": [
      {
        "id": "XXXX",
        "nickname": "onodes",
        "superuser": true
      }
    ],
    "newest_events": {
      "hu": {
        "val": 20,
        "created_at": "2018-12-17T15:19:39Z"
      },
      "il": {
        "val": 37.2,
        "created_at": "2018-12-17T13:13:18Z"
      },
      "te": {
        "val": 22.39,
        "created_at": "2018-12-17T09:39:51Z"
      }
    }
  }
]
```

上にあるremo-bedがNature remo mini, remo-roomがNature Remoになっています。
現時点ではNature Remoをがリビングにあるため、寝室と入れ替える必要がありますが、この中の"hu", "il", "te"を取得することで、部屋の室温など各種情報を取得することができます。

* hu → humidity
* il → illumination
* te → temperature

試しにPythonで温度情報を取得するにはこういった形になります、

```python
# -*- coding: UTF-8 -*-

import requests
import json

headers = {
        "Authorization": "Bearer <アクセストークン>",
        "accept": "application/json"
        }

response = requests.get("https://api.nature.global/1/devices", headers=headers)
json_data = response.json()

print(json_data[1]["newest_events"]["te"]["val"])

```

実行結果

```shell
$ python get_data.py
22.39
```

Nature Remo側では過去のデータを蓄積はしていないようなので、Advent Calendarとは別にこれらのデータを蓄積する仕組みを作成していきます。

# おわりに

これまで、睡眠ログの取得、iPhone内にある各種データの抽出、Nature Remoを使った家庭環境の可視化についてやってきました。
取得はしたもののまだ断片的な情報なので、データの蓄積と相関について行っていきたいと考えています。

手書きメモですが、分析してうまく行けば、制御部も作って見ようかと思います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/8190/55a35ba8-92c3-b340-593d-df1899be2356.png)

### 参考
* https://qiita.com/masato/items/72cf6cad5aa3179d64cc