移動中の心拍数(位置情報付き)をモバイル回線経由で記録する。丸1日動かせるくらいの低消費電力で。

# はじめに
山登りやサイクリングで移動中に、心拍、標高、気温、気圧などのデータを定期的に取得してGPS位置情報付きで保存できたら、楽しそうだと思いませんか？
集めたデータを見返し、同じペースでも気圧が低いと心拍数が上がりやすいな、などの気づきを得たり、
どの地点でペースが上がりすぎていたか可視化し、次回以降のペース配分に活かしたり。

そこで第一弾として、移動中の心拍数を位置情報付きで記録するシステムを作りました。
下記エントリーにインスパイアされています。
[M5Stack\+心拍センサー\+GPSで移動中の心拍数を記録する – Ambient](https://ambidata.io/samples/m5stack/m5stack_pulse_gps/)

ここではマイコンとスマホをテザリングで接続しています。
しかし、山登りやサイクリングは長丁場ですから、バッテリーが丸一日は持ってほしい。テザリングでは消費電力が気になりますね。
そこでBLE通信を使います。スマホとマイコンのBLE接続は、LINE Thingsを利用して簡単に実現できます。

※ Arduino系統に触るのは今回が初めてです。指摘や情報提供いただけるとたいへん喜びます。

# 概要
## 構成図
<img src="https://docs.google.com/drawings/d/e/2PACX-1vRHzibwScqC3hKT2vVs8oPvcs9_BfjrgvZlvoQWMRt9iGLcT_cg5OgVRdjFcEocZzRKZtkFD0X7R8QO/pub?w=1239&amp;h=520">

## 動いているところ
マジックテープを指に巻き、心拍センサの受光部を押し当てています。
GPSセンサが点滅して衛生信号の受信を示しています。
![ezgif.com-resize.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154885/c39fa61e-ebac-03b2-cc80-1081b07a07b2.gif)

## 結果
移動中、1分おきに計測した心拍数を地図上にマッピングできました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154885/bdbd94eb-a666-b8dc-c578-f0f6bafa5ad5.png)
脈が速いほど、マーカーの色が暖色になります。
スタート直後(水色マーカー)は脈が遅く、その後ダッシュしてみたためオレンジ色に。
以降は歩いているため緑色で落ち着いています。
高層ビル街なのでGPSは多少誤差がありますね。

## 消費電力
13時~21時にかけてスマホとESP32をBLEで繋ぎっぱなしにしましたが、スマホのバッテリの減り具合は普段とほとんど変わらず。
マイコンの電源に使用したモバイルバッテリの残目盛り(4段階)は全く減っていませんでした。
10000mAhあれば一週間は持ちそうです。

BLEで消費電力を抑える目的は達成です。

# 使ったもの
## ハードウェア
* スマホ
    * LINEアプリインストール済
* マイコンボード(ESP32)
    * [ESPr® Developer 32](https://www.switch-science.com/catalog/3210/)
* 心拍センサ
    * [PulseSensor - World Famous Electronics](https://www.switch-science.com/catalog/1135/)
* GPSセンサ
    * [GPS受信機キット 1PPS出力付き 「みちびき」3機受信対応](http://akizukidenshi.com/catalog/g/gK-09991/)

## サービス
* BLE経由のWebへのデータ送信
    * [LINE Things 自動通信機能](https://engineering.linecorp.com/ja/blog/line-things-automatic-communication/)
* LINE Botのホスティング
    * [ZEIT](https://zeit.co/)
* データ記録・可視化
    * [Ambient](https://ambidata.io/)

# 作り方
## ESP32側
ESPr® Developer 32に書き込んだコードです。
> https://gist.github.com/TakeshiMasukawa/2165c5b4a05bbc90914293620053dc0e

以下、手順の解説です。

### LINE Thingsを利用したBLE通信
[LINE Things Messageを使ってみよう \#linethings \#linedc \- Qiita](https://qiita.com/n0bisuke/items/7f3da133a2363b549e3e)を参考にしました
BLE通信に関わるコードは[ESP32の場合](https://qiita.com/n0bisuke/items/7f3da133a2363b549e3e#esp32%E3%81%AE%E5%A0%B4%E5%90%88)のgistをベースにしています。

変更点は以下の通り。

* 削除したコード
    * ボタン押下時にスマホにNotify送信する処理
    * LEDを光らせる処理
* 追加したコード
    * 1分おきにセンサデータをスマホにNotify送信する処理

送信処理のスケジューリングにはTickerを使います。

#### Tickerのインクルード

```c:ESP32HeartRateWithGPS.ino
// Schedule notify
#include <Ticker.h>
```

#### Tickerのセットアップ
`setup()`内
6000msごとにデータ送信処理をコール

```c:ESP32HeartRateWithGPS.ino
  // Schedule notify
  ticker.attach_ms(60000, sendData);
```

#### データ送信処理
BLE Notifyに乗せられるユーザデータのペイロードは20Byte。
GPSデータだけでいっぱいになってしまうため、データを3回に分けて送ります。
順番は次のとおりです。

1. データ送信開始のシグナルを送る
  - サーバはこのシグナルを受け取ったタイミングをデータの記録時刻とします。
2. GPS信号が正しく受信できていた場合、位置情報を"{緯度},{経度}"の形式で送る
3. 1分間の心拍数を送る

センサデータは最初にまとめて取得しますが、Notifyは5秒ずつ間隔を空けて送信し、サーバ側の負荷を分散します。

```c:ESP32HeartRateWithGPS.ino
void sendData() {
  Serial.println("send start");
  notifyCharacteristic->setValue("start");
  notifyCharacteristic->notify();
  const int BPM = pulseSensor.getBeatsPerMinute();;
  if (gps.location.isValid())
  {
    char coordinate[20];
    sprintf(coordinate,"%f,%f",gps.location.lat(),gps.location.lng());
    delay(5000);
    notifyCharacteristic->setValue(coordinate);
    notifyCharacteristic->notify();
  }
  delay(5000);
  char sendData[10];
  sprintf(sendData,"%d;",BPM);
  notifyCharacteristic->setValue(sendData);
  notifyCharacteristic->notify();
}
```

### 心拍センサ
[Ambient 心拍モニター](https://ambidata.io/doc/heartrate.html)を参考にしました。

#### 防水処理
雨や汗からセンサ基盤を守るために、♡マークがある側に付属の保護シールを、反対側にはホットグルーを盛ります。

こちら側に保護シールを貼り、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154885/ee92465a-eb8c-a3ec-e1ec-d4fcd2b7fe0b.png)

反対側にはホットグルーを盛って、付属のマジックテープシールを貼ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154885/9dc73876-ca7c-6451-804e-6506f7f37631.png)


#### ESP32との接続
アナログ通信(ADC)を利用します。
次のように接続しました。

|用途|センサ側|ESP32側|
|---|---|---|
|電源 (3~5V)|赤い線|3V3 (3.3V)|
|GND |黒い線|GND|
|通信|紫の線|IO34 (ADC1_CH6)|

ESP32のアナログ入力については[ESP-WROOM-32に関するTIPS](https://trac.switch-science.com/wiki/esp32_tips#ADC%E3%82%A2%E3%83%8A%E3%83%AD%E3%82%B0%E5%85%A5%E5%8A%9B)を参照ください。
WiFiやBLEをONにすると、アナログ入力に対応しているピンのうちADC2回路を使うものが全て使えなくなるので注意しましょう。
私はここでだいぶハマりました。

入力に使用するピンを指定します。

```c:ESP32HeartRateWithGPS.ino
#define HR_PIN 34
```

生データをシリアルプロッタで見るとこんな具合です。

![Screenshot from Gyazo](https://gyazo.com/0954eff595ae0bce16aacf7aecf8486e/raw)

これを心拍数に変換するためにライブラリPulseSensorPlaygroundを利用します。


#### ソースコード
ライブラリは、[公式版](https://github.com/WorldFamousElectronics/PulseSensorPlayground) (ESP32未対応)ではなく、[Ambientによる改良版](https://github.com/AmbientDataInc/PulseSensorPlayground)を利用します。
公式版では"Added support for ESP32 #103"というPRが2019/06にマージされていますが、大嘘です。
コンパイルが通るようになっただけで、`getBeatsPerMinute()`が心拍数を返してくれません (2019/08現在)。

* PulseSensorPlaygroundのインクルード

includeの前に`USE_ARDUINO_INTERRUPTS`をdefineします。
こうしないとコンパイルが通りません。

```c:ESP32HeartRateWithGPS.ino
// Heart rate
#define USE_ARDUINO_INTERRUPTS true
#include <PulseSensorPlayground.h>
```
* セットアップ

`setup()`内で生データをライブラリに渡すよう設定します。

```c:ESP32HeartRateWithGPS.ino
  // Heart rate library settings
  pulseSensor.analogInput(HR_PIN);
  pulseSensor.setSerial(Serial);
  pulseSensor.setOutputType(OUTPUT_TYPE);
  pulseSensor.setThreshold(THRESHOLD);
  pulseSensor.begin();
```
あとはデータが欲しいタイミングで`pulseSensor.getBeatsPerMinute()`すると、直近の分間BPMを取得できます。

### GPSセンサ

#### GPSの受信
センサに電源を投入するとランプが光ります。さらにGPS信号の受信に成功するとランプが点滅します(冒頭の動画参照)。
屋内だとなかなか点滅しないことがあります。その場合は信号を受信しやすい窓際に置いてみてください。
屋外で使用する場合はまず大丈夫です。

#### ESP32との接続
UARTによるシリアル通信を使用します。
通信にはESP32に標準搭載されているハードウェアシリアルを使います。
詳しくは[Arduino\-ESP32 Serial通信 \- Qiita](https://qiita.com/T-YOSH/items/0ce41136c535c723affb)を参照ください。

今回はUART2を使いました。
UART0はArduino IDEからのコード書き込みと競合します。またUART1はピンをプログラムで指定する必要があり、面倒です。

次のように接続します。

|用途|センサ側|ESP32側|
|---|---|---|
|電源 (3.8V~12V)|5V|VOUT|
|GND |GND|GND|
|通信|TXD|IO16 (U2RXD)|

GPSセンサからのデータ送信のみ利用するため、センサのRXDおよび1PPSピンは使用しません。
HW初心者すぎて、TXDとTXD、RXDとRXDを繋いで「データが取れない！」と頭を抱えていたのは秘密です。

生データはこんな具合です。（画像は[Device Plusさん](https://deviceplus.jp/hobby/entry060/)からの借り物です）
![image.png](https://deviceplus.jp/wp-content/uploads/2017/05/c74249062b826385ff23cee2e813090f.png)

これを緯度、経度に変換するためライブラリTinyGPS++を利用します。


#### ソースコード

* TinyGPS++のインクルード

```c:ESP32HeartRateWithGPS.ino
// GPS
#include <TinyGPS++.h>
```

* シリアル通信セットアップ
    * UART2用に`Serial2`という変数が用意されています。

`setup()`内

```c:ESP32HeartRateWithGPS.ino
  // Set up serial for GPS
  Serial2.begin(9600);// GPS
```

* センサデータをライブラリに渡す

`loop()`内

```c:ESP32HeartRateWithGPS.ino
  // Send GPS raw data to library
  while (Serial2.available() > 0) {
    if (gps.encode(Serial2.read())) {
      break;
    }
  }
```

あとはデータが欲しいタイミングで、`gps.location.lat()`で緯度を、,`gps.location.lng()`で経度を取得できます。

## サーバ側
LINE Things 自動通信機能のバックエンドとして使用したコードです。

> https://github.com/TakeshiMasukawa/Sensor-data-with-GPS-coordinates-tracker

[LINE Things Messageを使ってみよう \#linethings \#linedc \- Qiita](https://qiita.com/n0bisuke/items/7f3da133a2363b549e3e#step3-line-botnodejs%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88%E3%82%8B)のコードをベースにしています。

以下、各部を解説します。

### Ambientとの接続
Ambientのnode.jsライブラリの使い方は次を参照ください。
[node\.jsライブラリー ambient\-lib – Ambient](https://ambidata.io/refs/node-js/)

### 受信データのデコード
ESP32からは文字列データが送られます。
ascii変換されており、そのままでは扱えないのでデコードします。

`handleEvent()`内

``` server.js
    // 受信データをデコード
    const buffer = new Buffer.from(thingsData.bleNotificationPayload, 'base64');
    const data = buffer.toString('ascii');
```

### 受信データのキャッシュ&送信
ESP32からは3回に分けてデータが送られてきます。
それぞれをAmbientへの送信データにセットし、全て受信したら送信します。
1回目の受信時刻をデータ取得時刻としてセットしています。
緯度、経度データは`lat`、`lng`にセットするとAmbientに認識してもらえます。

`handleEvent()`内

``` server.js
    if (data === "start") {
      // データ送信開始信号
      sendData.created = Date.now();
    } else if (data.includes(',')) {
      // GPSデータ
      const cordinates = data.split(',');
      sendData.lat = cordinates[0];
      sendData.lng = cordinates[1];
    } else if (data.includes(";")) {
      // 心拍データ
      const BPM = data.split(';')[0];
      sendData.d1 = BPM;
      await ambientSendAsync(sendData);
      // 次の受信に備えてデータをリセット
      sendData = {};
      ...
    }
```

### LINEメッセージ送信機能

心拍数が上がりすぎている場合は、LINEメッセージで警告する機能をつけてみました。
アウトドア活動のアシスタントになってくれます。

`handleEvent()`内

``` server.js
      if (parseInt(BPM) > heartRateLimit) {
        message = `心拍数が上がりすぎています。ペースを落としましょう。BPM: ${BPM}`;
      }
```

登山やサイクリングで、ペースを一定に保つのに使えそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154885/6abdd3d6-3afc-0319-29c4-7343b14f02c1.png)

# 今後の展望
今回はGPSセンサでの緯度経度と併せて、心拍数のみを記録しました。
他にも、GPSセンサの標高を取ってみたり、気圧センサを追加してみれば、登山の記録としてさらに一段深い考察ができそうです。
温度・湿度センサを組み合わせれば、メッセージで熱中症の警告を送るなどと、より高機能なアシスタントになってくれるでしょう。

実はこの製作はお盆休みの富士登山計画に向けて始めたのですが、実装が間に合わなかった上に雨天中止になってしまいました。
次に登山するときはぜひこれを携行して、記録をつけてみようと思います。