はじめに
山登りやサイクリングで移動中に、心拍、標高、気温、気圧などのデータを定期的に取得してGPS位置情報付きで保存できたら、楽しそうだと思いませんか?
集めたデータを見返し、同じペースでも気圧が低いと心拍数が上がりやすいな、などの気づきを得たり、
どの地点でペースが上がりすぎていたか可視化し、次回以降のペース配分に活かしたり。
そこで第一弾として、移動中の心拍数を位置情報付きで記録するシステムを作りました。
下記エントリーにインスパイアされています。
M5Stack+心拍センサー+GPSで移動中の心拍数を記録する – Ambient
ここではマイコンとスマホをテザリングで接続しています。
しかし、山登りやサイクリングは長丁場ですから、バッテリーが丸一日は持ってほしい。テザリングでは消費電力が気になりますね。
そこでBLE通信を使います。スマホとマイコンのBLE接続は、LINE Thingsを利用して簡単に実現できます。
※ Arduino系統に触るのは今回が初めてです。指摘や情報提供いただけるとたいへん喜びます。
概要
構成図
動いているところ
マジックテープを指に巻き、心拍センサの受光部を押し当てています。
GPSセンサが点滅して衛生信号の受信を示しています。
結果
移動中、1分おきに計測した心拍数を地図上にマッピングできました。
脈が速いほど、マーカーの色が暖色になります。
スタート直後(水色マーカー)は脈が遅く、その後ダッシュしてみたためオレンジ色に。
以降は歩いているため緑色で落ち着いています。
高層ビル街なのでGPSは多少誤差がありますね。
消費電力
13時~21時にかけてスマホとESP32をBLEで繋ぎっぱなしにしましたが、スマホのバッテリの減り具合は普段とほとんど変わらず。
マイコンの電源に使用したモバイルバッテリの残目盛り(4段階)は全く減っていませんでした。
10000mAhあれば一週間は持ちそうです。
BLEで消費電力を抑える目的は達成です。
使ったもの
ハードウェア
- スマホ
- LINEアプリインストール済
- マイコンボード(ESP32)
- 心拍センサ
- GPSセンサ
サービス
- BLE経由のWebへのデータ送信
- LINE Botのホスティング
- データ記録・可視化
作り方
ESP32側
ESPr® Developer 32に書き込んだコードです。
https://gist.github.com/TakeshiMasukawa/2165c5b4a05bbc90914293620053dc0e
以下、手順の解説です。
LINE Thingsを利用したBLE通信
LINE Things Messageを使ってみよう #linethings #linedc - Qiitaを参考にしました
BLE通信に関わるコードはESP32の場合のgistをベースにしています。
変更点は以下の通り。
- 削除したコード
- ボタン押下時にスマホにNotify送信する処理
- LEDを光らせる処理
- 追加したコード
- 1分おきにセンサデータをスマホにNotify送信する処理
送信処理のスケジューリングにはTickerを使います。
Tickerのインクルード
// Schedule notify
# include <Ticker.h>
Tickerのセットアップ
setup()
内
6000msごとにデータ送信処理をコール
// Schedule notify
ticker.attach_ms(60000, sendData);
データ送信処理
BLE Notifyに乗せられるユーザデータのペイロードは20Byte。
GPSデータだけでいっぱいになってしまうため、データを3回に分けて送ります。
順番は次のとおりです。
- データ送信開始のシグナルを送る
- サーバはこのシグナルを受け取ったタイミングをデータの記録時刻とします。
- GPS信号が正しく受信できていた場合、位置情報を"{緯度},{経度}"の形式で送る
- 1分間の心拍数を送る
センサデータは最初にまとめて取得しますが、Notifyは5秒ずつ間隔を空けて送信し、サーバ側の負荷を分散します。
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 心拍モニターを参考にしました。
防水処理
雨や汗からセンサ基盤を守るために、♡マークがある側に付属の保護シールを、反対側にはホットグルーを盛ります。
反対側にはホットグルーを盛って、付属のマジックテープシールを貼ります。
ESP32との接続
アナログ通信(ADC)を利用します。
次のように接続しました。
用途 | センサ側 | ESP32側 |
---|---|---|
電源 (3~5V) | 赤い線 | 3V3 (3.3V) |
GND | 黒い線 | GND |
通信 | 紫の線 | IO34 (ADC1_CH6) |
ESP32のアナログ入力についてはESP-WROOM-32に関するTIPSを参照ください。
WiFiやBLEをONにすると、アナログ入力に対応しているピンのうちADC2回路を使うものが全て使えなくなるので注意しましょう。
私はここでだいぶハマりました。
入力に使用するピンを指定します。
# define HR_PIN 34
生データをシリアルプロッタで見るとこんな具合です。
これを心拍数に変換するためにライブラリPulseSensorPlaygroundを利用します。
ソースコード
ライブラリは、公式版 (ESP32未対応)ではなく、Ambientによる改良版を利用します。
公式版では"Added support for ESP32 #103"というPRが2019/06にマージされていますが、大嘘です。
コンパイルが通るようになっただけで、getBeatsPerMinute()
が心拍数を返してくれません (2019/08現在)。
- PulseSensorPlaygroundのインクルード
includeの前にUSE_ARDUINO_INTERRUPTS
をdefineします。
こうしないとコンパイルが通りません。
// Heart rate
# define USE_ARDUINO_INTERRUPTS true
# include <PulseSensorPlayground.h>
- セットアップ
setup()
内で生データをライブラリに渡すよう設定します。
// 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を参照ください。
今回は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さんからの借り物です)
これを緯度、経度に変換するためライブラリTinyGPS++を利用します。
ソースコード
- TinyGPS++のインクルード
// GPS
# include <TinyGPS++.h>
- シリアル通信セットアップ
- UART2用に
Serial2
という変数が用意されています。
- UART2用に
setup()
内
// Set up serial for GPS
Serial2.begin(9600);// GPS
- センサデータをライブラリに渡す
loop()
内
// 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のコードをベースにしています。
以下、各部を解説します。
Ambientとの接続
Ambientのnode.jsライブラリの使い方は次を参照ください。
node.jsライブラリー ambient-lib – Ambient
受信データのデコード
ESP32からは文字列データが送られます。
ascii変換されており、そのままでは扱えないのでデコードします。
handleEvent()
内
// 受信データをデコード
const buffer = new Buffer.from(thingsData.bleNotificationPayload, 'base64');
const data = buffer.toString('ascii');
受信データのキャッシュ&送信
ESP32からは3回に分けてデータが送られてきます。
それぞれをAmbientへの送信データにセットし、全て受信したら送信します。
1回目の受信時刻をデータ取得時刻としてセットしています。
緯度、経度データはlat
、lng
にセットするとAmbientに認識してもらえます。
handleEvent()
内
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()
内
if (parseInt(BPM) > heartRateLimit) {
message = `心拍数が上がりすぎています。ペースを落としましょう。BPM: ${BPM}`;
}
登山やサイクリングで、ペースを一定に保つのに使えそうです。
今後の展望
今回はGPSセンサでの緯度経度と併せて、心拍数のみを記録しました。
他にも、GPSセンサの標高を取ってみたり、気圧センサを追加してみれば、登山の記録としてさらに一段深い考察ができそうです。
温度・湿度センサを組み合わせれば、メッセージで熱中症の警告を送るなどと、より高機能なアシスタントになってくれるでしょう。
実はこの製作はお盆休みの富士登山計画に向けて始めたのですが、実装が間に合わなかった上に雨天中止になってしまいました。
次に登山するときはぜひこれを携行して、記録をつけてみようと思います。