#ifdef ESP32
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
#else
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <Hash.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#endif
#include <Wire.h>
#include "MAX30105.h"
#include <movingAvg.h>
MAX30105 particleSensor;
//------ Wifi AP情報
const char* ssid = "<SSID>";
const char* password = "<パスワード>";
//------ 定数定義
const uint32_t TH_FIN = 7000; //指が置いてあるかどうかの閾値
const int32_t TH_AMOUNT = 250; //パルスの起点となる検出変化量
const int32_t MIN_INIT = 9999999; //最小値の初期値
const int32_t MAX_INIT = 0; //最大値の初期値
//表示する心拍数とSpO2の範囲(用途により適宜変更)
const uint32_t DISP_MIN_HR = 30;
const uint32_t DISP_MAX_HR = 200;
const uint32_t DISP_MIN_SPO2 = 70;
const uint32_t DISP_MAX_SPO2 = 100;
//瞬間心拍数の許容範囲(デフォルトは表示範囲と一緒)
const uint32_t MAX_MOMENT_HR = DISP_MAX_HR;
const uint32_t MIN_MOMENT_HR = DISP_MIN_HR;
//ノイズと判断するカットレート 0.3 移動平均の+-30%の変化を認める
// ex ) 移動平均70拍 : 49~91の範囲外はカットオフする
const double NOISE_CUTOFF_RATE = 0.3;
//HRの移動平均データのリセットするカットオフの連続回数
const uint32_t MAX_CUTOFF_COUNT = 5;
//パルス検出保持変数
long l_time = millis(); //最後のパルス検出時間の保持
int32_t before_ir_v = 0; //最後のir_v値保持
int32_t b_diff = 0; // 最後の差の保持
long pulse_interval = -1; // パルス間隔時間
//1パルス中の生データの最大・最小値の保持
int32_t min_ir_v = MIN_INIT ,max_ir_v = MAX_INIT;
int32_t min_red_v= MIN_INIT ,max_red_v = MAX_INIT;
//生データの変化値の保持
int32_t diff = 0;
//移動平均値(IR_DC、RED_DC) デフォルトは30(大体20~50の範囲で指定)サンプル
movingAvg avgIr_v(30);
movingAvg avgRed_v(30);
//移動平均値(心拍数は直近3ビート、SPO2は5ビートの平均)
movingAvg avgHR(3);
movingAvg avgSPO2(5);
//連続カットオフ回数
//MAX_CUTOFF_COUNT回連続でカットオフされたらavg_HRをリセットする
int32_t cutoff_c = 0;
//グラフ描記用のサーバー
AsyncWebServer server(80);
void setup()
{
Wire.begin(0,2); // I2Cのピン設定
Serial.begin(115200);
Serial.println("");
Serial.println("Initializing...");
// センサー初期化
while(!particleSensor.begin(Wire, I2C_SPEED_FAST)){
Serial.print(".");
}
Serial.println("OK!");
byte ledBrightness = 0x1F; //Options: 0=Off to 255=50mA
byte diffmpleAverage = 8; //Options: 1, 2, 4, 8, 16, 32
byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
int diffmpleRate = 400; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
int pulse_intervalWidth = 411; //Options: 69, 118, 215, 411
int adcRange = 4096; //Options: 2048, 4096, 8192, 16384
particleSensor.setup(ledBrightness, diffmpleAverage, ledMode, diffmpleRate, pulse_intervalWidth, adcRange); //Configure sensor with these settings
//初期値として一回読んでおく(ir_vをパルス計測に用いる)
before_ir_v = particleSensor.getRed();
//パルス最終取得時間の初期化
l_time = millis();
//移動平均ライブラリの初期化
avgIr_v.begin();
avgRed_v.begin();
avgHR.begin();
avgSPO2.begin();
// Initialize SPIFFS
if(!SPIFFS.begin()){
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
// IPアドレス http://XXX.XXX.XXX.XXX/index.html にアクセス
Serial.println(WiFi.localIP());
// グラフ描記のルート index.html
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html");
});
//心拍数
server.on("/hr", HTTP_GET, [](AsyncWebServerRequest *request){
char hr_st[10];
if(avgHR.getCount()>0){
dtostrf((double)avgHR.getAvg() / 1000.0, 0, 2,hr_st);
}else{
sprintf(hr_st,"0.00");
}
request->send_P(200, "text/plain", hr_st);
});
//SPO2
server.on("/spo2", HTTP_GET, [](AsyncWebServerRequest *request){
char spo2_st[10];
if(avgSPO2.getCount()>0){
dtostrf((double)avgSPO2.getAvg() / 1000.0, 0, 2,spo2_st);
}else{
sprintf(spo2_st,"0.00");
}
request->send_P(200, "text/plain", spo2_st);
});
//簡易ECG 高速で読みだせばEGCグラフも描記できるかも ※未実装
server.on("/ecg", HTTP_GET, [](AsyncWebServerRequest *request){
char ecg_st[10];
sprintf(ecg_st,"%d",diff);
request->send_P(200, "text/plain", ecg_st);
});
// Start server
server.begin();
}
void loop(
{
//逆になっている。センサーが逆を返しているので各自チェック
uint32_t red_v = particleSensor.getIR();
uint32_t ir_v = particleSensor.getRed();
//指を置いているか
if(red_v< TH_FIN || ir_v < TH_FIN) return;
//移動平均値(IR_DC RED_DCの算出)
double ir_v_dc = avgIr_v.reading(ir_v);
double red_v_dc = avgRed_v.reading(red_v);
//IRとREDのACを求める為に最大値・最初値の更新
if(ir_v<min_ir_v) min_ir_v = ir_v;
if(ir_v>max_ir_v) max_ir_v = ir_v;
if(red_v<min_red_v) min_red_v = red_v;
if(red_v>max_red_v) max_red_v = red_v;
//パルス検出にはir_vを利用。前回との差分(変化値)を求める
diff = before_ir_v - ir_v;
//TH_AMOUNT以上の変化がある場合、パルス起点と推定
if(b_diff < TH_AMOUNT && diff > TH_AMOUNT){
//1パルスの時間を計算
pulse_interval = millis() - l_time;
l_time = millis();
//推定されるパルスの瞬間心拍数の計算
double hr_r = 60000.0/pulse_interval;
boolean f;
if(avgHR.getCount()>=3){
//移動平均avgHRでhr_rの妥当性を判断(+-NOISE_CUTOFF_RATEの範囲内にあるか)
double hr_avg = (double)avgHR.getAvg() / 1000.0;
f = hr_r > hr_avg*(1.0-NOISE_CUTOFF_RATE) && hr_r < hr_avg*(1.0+NOISE_CUTOFF_RATE);
}else{
//移動平均がない場合はしゅんかん定義の心拍数の範囲内かどうか
f = hr_r >= (double)MIN_MOMENT_HR && hr_r <= (double)MAX_MOMENT_HR;
}
if(f){
//連続カットオフカウントのリセット
cutoff_c = 0;
//1パルスの時間より心拍数の計算。avgHRは整数のみなので1000倍して1000で割り小数保持
double hr = (double)avgHR.reading(hr_r * 1000.0 ) / 1000.0;
//SPO2の計算
//IR・REDのACを求める(振幅最大-振幅最小)
int32_t ir_v_ac = max_ir_v-min_ir_v;
int32_t red_v_ac = max_red_v-min_red_v;
// R = (AC_RED / DC_RED) / (AC_IR / DC_IR)の計算式より
double red_div = double(red_v_ac)/red_v_dc;
double ir_div = double(ir_v_ac)/ir_v_dc;
double R = red_div / ir_div;
// SPO2 = -45.060*R^2 + 30.354*R + 94.845 これはspo2_algorithm.cppにあったのを準用
// 乗除1000は小数保持のため
double spo2 = (double)avgSPO2.reading((-45.060*R*R + 30.354*R + 94.845)*1000.0) / 1000.0;
//最大値・最小値の初期化
min_ir_v = MIN_INIT;
max_ir_v = MAX_INIT;
min_red_v = MIN_INIT;
max_red_v = MAX_INIT;
//心拍数とSPO2の表示(範囲は定義で変更可)
if(hr <= DISP_MAX_HR && hr >= DISP_MIN_HR && spo2 <= DISP_MAX_SPO2 && spo2 >= DISP_MIN_SPO2){
Serial.print(hr);
Serial.print(",");
Serial.println(spo2);
}
}else{
//瞬間心拍範囲外・カットオフ
cutoff_c++;
if(cutoff_c >= MAX_CUTOFF_COUNT){
avgHR.reset();
//Serial.println("avgHR reset");
}
}
}
//パルス検出用値の保持
before_ir_v = ir_v;
b_diff = diff;
}