44
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NSSOLAdvent Calendar 2018

Day 18

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

Last updated at Posted at 2018-12-18

TL;DR

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

はじめに

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

suimin_man.png

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

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

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

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

身の回りのログの取得

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

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

睡眠のライフログ

この企画のために温め続けてきた睡眠ログ
Sleep Cycle alarm clockにてデータを蓄積してきました。
アプリ上取得できているのは、

  • 快眠度
  • 就寝時刻
  • 睡眠時間
  • 起床時間
  • いびき
  • 天気
  • 気圧
  • 月齢
    などです。
IMG_B8A26EE28447-1.jpeg

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

image.png

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

日常生活のライフログ

iPhoneのヘルスケア

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

IMG_7398ADDDD86D-1.jpeg

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

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f383139302f65633539643432642d666562322d396332342d663538372d3662656238313862323863342e6a706567.jpeg

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


~/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の作成から


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を含めて、どういう値が取得できるかがわかるかと思います。


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

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で記録されているため、歩数に着目してデータを取得したいときは、


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

下記XMLに同梱されていたAppleWatchからの平均脈拍
image.png

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

部屋の気温、湿度の取得(Nature Remo)

どこのご家庭にでも最低一台はあるNature Remoを利用します。
我が家でもKickStartで購入したNatureRemoと最近追加したminiの二台体制で動いています。
hp_remomini.jpg出展 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://home.nature.global/

アクセスしてログインをすれば、下記許可画面が出ます。
image.png

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

curl -X GET "https://api.nature.global/1/devices" -H "accept: application/json" -k --header "Authorization: Bearer <アクセストークン>"

結果がわかりにくいので、jqで視認性を上げます。

[
  {
    "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で温度情報を取得するにはこういった形になります、

# -*- 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"])

実行結果

$ python get_data.py
22.39

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

おわりに

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

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

image.png

参考

44
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?