Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Internet Of SPO2 Things.
#include "M5StickCPlus.h"
#include <Wire.h>
// lcd
uint8_t brightnessLevel = 8;
uint8_t RotateSet = 1; // for right finger
// buttuns
#define BTN_A_PIN 37
#define BTN_B_PIN 39
#define BTN_ON LOW
#define BTN_OFF HIGH
uint8_t prev_btn_a = BTN_OFF;
uint8_t btn_a = BTN_OFF;
uint8_t prev_btn_b = BTN_OFF;
uint8_t btn_b = BTN_OFF;
// MAX30102
#include "MAX30105.h"
#include <movingAvg.h>
MAX30105 pox;
#define REPORTING_PERIOD_MS 1000
uint8_t HeartRate = 0;
uint8_t Spo2 = 0;
uint32_t tsLastReport = 0;
uint8_t Spo2Thresh = 90;
uint8_t Spo2ThreshExcept = 50; // 判定除外条件
uint8_t HeartRateThresh = 150;
uint8_t HeartRateThreshExcept = 30; // 判定除外条件
const uint32_t TH_FIN = 7000;
const int32_t TH_AMOUNT = 300;
const int32_t MIN_INIT = 9999999;
const int32_t MAX_INIT = 0;
// MAX30102: 表示する心拍数とSpO2の範囲(用途により適宜変更)
const uint32_t DISP_MIN_HR = 30;
const uint32_t DISP_MAX_HR = 180;
const uint32_t DISP_MIN_SPO2 = 70;
const uint32_t DISP_MAX_SPO2 = 100;
// MAX30102: 計算用
long l_time = millis();
int32_t before_ir_v = 0;
int32_t b_diff = 0;
long pulse_interval = -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;
movingAvg avgIr_v(30);
movingAvg avgRed_v(30);
movingAvg avgHR(3);
movingAvg avgSPO2(5);
// wifi
#include <WiFi.h>
const char ssid[1][16]={"INPUT_WIFI_SSID"};
const char ssidpass[1][32]={"INPUT_WIFI_PASSWORD"};
bool isWiFiConnected = false;
#define SSID_MAX 1
#define WIFI_CONNECT_RETRY 10
char macaddr[20];
WiFiClient client;
// current time (NTP)
const char* ntpServer = "ntp.jst.mfeed.ad.jp";
const long gmtOffset_sec = 9 * 3600;
const int daylightOffset_sec = 0;
static uint8_t conv2d(const char* p); // Forward declaration needed for IDE 1.6.x
uint8_t hh = conv2d(__TIME__), mm = conv2d(__TIME__ + 3), ss = conv2d(__TIME__ + 6); // Get H, M, S from compile time
uint32_t targetTime = 0; // for next 1 second timeout
// LINE
#include <ssl_client.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
// #define NOTIFY_PERIOD_MS 3 * 60 * 1000
#define NOTIFY_PERIOD_MS 5000
WiFiClientSecure clientSecure;
const char* lineHost = "notify-api.line.me";
const char* lineToken = "INPUT_LINE_TOKEN";
bool isNotifyOn = false;
uint32_t timeLastNotify = 0;
char LineMsgBuf[256];
// ambient
#include "Ambient.h"
// See https://ambidata.io/docs/getchannel/
const char* amUserKey = "INPUT_AMBIENT_KEY";
char amDevKey[20];
unsigned int amChannelId;
char amWriteKey[20];
Ambient am;
bool isAmbientConnected = false;
byte sec_count = 0;
// 初期化処理
void setup()
{
Serial.begin(115200);
M5.begin();
// I2Cのピン設定
Wire.begin(0,26); // for M5StickC Hat SDA=0,SCL=26
// buttons
pinMode(BTN_A_PIN, INPUT_PULLUP);
pinMode(BTN_B_PIN, INPUT_PULLUP);
// lcd
M5.Lcd.setRotation(RotateSet);
M5.Axp.ScreenBreath(brightnessLevel);
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0,0,2);
Serial.println("");
Serial.println("Initializing...");
M5.Lcd.println("Initializing...");
// wifi
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA); // Wi-FiのMACアドレスを取得する
sprintf(macaddr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// M5.Lcd.println(macaddr);
// Serial.println(macaddr);
for (int i = 0; i < SSID_MAX; i++) {
WiFi.begin(ssid[i],ssidpass[i]);
for (int j = 0; j < WIFI_CONNECT_RETRY; j++) {
if (WiFi.status() == WL_CONNECTED) {
isWiFiConnected = true;
Serial.printf("WiFi connected to %s.\n", ssid[i]);
break;
}
delay(500);
Serial.println('.');
}
if (isWiFiConnected) {
break;
}
else {
WiFi.disconnect( true, true ); //WiFi OFF, eraseAP=true
delay(1000);
}
}
// current time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
if (!isWiFiConnected) {
M5.Lcd.println("WiFi Error !");
}
else {
// for LINE setInsecureしないと、clientSecure.connect(lineHost, 443)でエラーになる。
clientSecure.setInsecure();
// ambient
strcpy(amDevKey, macaddr);
if (am.getchannel(amUserKey, amDevKey, amChannelId, amWriteKey, sizeof(amWriteKey), &client) == false) {
Serial.printf("Cannot get channelId. Please set DeviceKey (%s) to Ambient.\n", amDevKey);
while (true) {
delay(0);
}
}
Serial.printf("channelId: %d, writeKey: %s\r\n", amChannelId, amWriteKey);
am.begin(amChannelId, amWriteKey, &client);
M5.Lcd.println("Ambient Configured!");
}
// MAX30102初期化
Serial.println("Sensor Initializing...");
while(!pox.begin(Wire, I2C_SPEED_FAST)){
Serial.println(".");
}
Serial.println("Sensor Initialized!");
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
pox.setup(ledBrightness, diffmpleAverage, ledMode, diffmpleRate, pulse_intervalWidth, adcRange); //Configure sensor with these settings
// MAX30102 初期値を取得
before_ir_v = pox.getRed();
// MAX30102初期化
l_time = millis();
// 移動平均初期化
avgIr_v.begin();
avgRed_v.begin();
avgHR.begin();
avgSPO2.begin();
M5.Lcd.println("Sensor Configured");
delay(1000);
}
// 繰り返し処理
void loop()
{
// buttons
btn_a = digitalRead(BTN_A_PIN);
btn_b = digitalRead(BTN_B_PIN);
// Serial.printf("prev_btn_a = %d, btn_a = %d\n", prev_btn_a, btn_a);
// ボタンAが押されたとき
if(prev_btn_a == BTN_OFF && btn_a == BTN_ON) {
;
}
// ボタンAが離されたとき
if(prev_btn_a == BTN_ON && btn_a == BTN_OFF) {
if (isWiFiConnected) {
// sprintf(LineMsgBuf, "SOS!\n重症化しそうです。助けて。\nHeartRate(心拍数) = %d\nSPO2(血中酸素濃度) = %d\nごめんよメンフラハップ。", HeartRate, Spo2);
sprintf(LineMsgBuf, "SOS!\n重症化しそうです。助けて。\nSPO2(血中酸素濃度) = %d\nごめんよメンフラハップ。", Spo2);
// Serial.println(LineMsgBuf);
send2line(LineMsgBuf);
}
}
// ボタンBが押されたとき
if(prev_btn_b == BTN_OFF && btn_b == BTN_ON){
;
}
// ボタンBが離されたとき
if(prev_btn_b == BTN_ON && btn_b == BTN_OFF){
if (isWiFiConnected) {
isNotifyOn = isNotifyOn? false: true;
}
else {
isNotifyOn = false;
}
}
prev_btn_a = btn_a;
prev_btn_b = btn_b;
// MAX30102 計算&更新
updateData();
if (millis() - tsLastReport > REPORTING_PERIOD_MS) {
// Serial.printf("%d, %d\n", HeartRate, Spo2);
Serial.printf("%d\n", Spo2);
tsLastReport = millis();
refreshLcd();
if (isWiFiConnected) {
if (isNotifyOn) {
if (timeLastNotify == 0 || (millis() - timeLastNotify > NOTIFY_PERIOD_MS)) {
// 判定除外条件
if (Spo2 > Spo2ThreshExcept) {
// 判定
if (Spo2 < Spo2Thresh) {
sprintf(LineMsgBuf, "SOS!\n重症化しそうです。助けて。\nSPO2(血中酸素濃度) = %d\nごめんよメンフラハップ。", Spo2);
// Serial.println(LineMsgBuf);
send2line(LineMsgBuf);
timeLastNotify = millis();
}
}
}
}
if (sec_count > 0 && sec_count%5 == 0) {
Serial.println("send data to ambient");
am.set(1, HeartRate); // センサーデーターをセット、
am.set(2, Spo2); // センサーデーターをセット、
am.send(); // Ambientに送信する
}
}
sec_count++;
// Serial.printf("count = %d\n", sec_count);
}
}
// MAX30102 計算
void updateData() {
uint32_t red_v = pox.getIR();
uint32_t ir_v = pox.getRed();
if(red_v< TH_FIN || ir_v < TH_FIN) return;
double ir_v_dc = avgIr_v.reading(ir_v);
double red_v_dc = avgRed_v.reading(red_v);
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;
int32_t diff = before_ir_v - ir_v;
if(b_diff < TH_AMOUNT && diff > TH_AMOUNT){
pulse_interval = millis() - l_time;
l_time = millis();
double hr = (double)avgHR.reading(60000*1000/pulse_interval) / 1000.0;
int32_t ir_v_ac = max_ir_v-min_ir_v;
int32_t red_v_ac = max_red_v-min_red_v;
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;
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;
if(hr <= DISP_MAX_HR && hr >= DISP_MIN_HR && spo2 <= DISP_MAX_SPO2 && spo2 >= DISP_MIN_SPO2){
// Serial.printf("%lf, %lf\n", hr, spo2);
Serial.printf("%lf\n", spo2);
HeartRate = (int)hr;
Spo2 = (int)spo2;
}
}
before_ir_v = ir_v;
b_diff = diff;
}
// 時刻フォーマット
static uint8_t conv2d(const char* p) {
uint8_t v = 0;
if ('0' <= *p && *p <= '9')
v = *p - '0';
return 10 * v + *++p - '0';
}
// 現在時刻取得
void getCurrentTime(char *currentTime) {
// clock
struct tm timeinfo;
if (getLocalTime(&timeinfo, 1)) {
hh = timeinfo.tm_hour;
mm = timeinfo.tm_min;
ss = timeinfo.tm_sec;
}
else {
if (targetTime < millis()) {
// Set next update for 1 second later
targetTime = millis() + 1000;
// Adjust the time values by adding 1 second
ss++; // Advance second
if (ss == 60) { // Check for roll-over
ss = 0; // Reset seconds to zero
mm++; // Advance minute
if (mm > 59) { // Check for roll-over
mm = 0;
hh++; // Advance hour
if (hh > 23) { // Check for 24hr roll-over (could roll-over on 13)
hh = 0; // 0 for 24 hour clock, set to 1 for 12 hour clock
}
}
}
}
}
sprintf(currentTime, "%02d:%02d:%02d", hh, mm, ss);
}
// 液晶表示のリフレッシュ
void refreshLcd() {
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(10,2);
char currentTime[13];
getCurrentTime(currentTime);
M5.Lcd.printf("%s", currentTime);
M5.Lcd.setCursor(10,30);
M5.Lcd.printf("Notify : %s", (isNotifyOn?"ON":"OFF"));
// M5.Lcd.setCursor(10,60);
// M5.Lcd.printf("HEART : %d", HeartRate);
M5.Lcd.setCursor(10,90);
M5.Lcd.printf("SPO2 : %d", Spo2);
}
// LINE Notify送信
void send2line(String msg) {
Serial.println(msg);
if (!clientSecure.connect(lineHost, 443)) {
Serial.printf("send2line connection error.\n");
delay(2000);
return;
}
String query = String("message=") + msg;
String request = String("") +
"POST /api/notify HTTP/1.1\r\n" +
"Host: " + lineHost + "\r\n" +
"Authorization: Bearer " + lineToken + "\r\n" +
"Content-Length: " + String(query.length()) + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
query + "\r\n";
clientSecure.print(request);
while (clientSecure.connected()) {
String line = clientSecure.readStringUntil('\n');
if (line == "\r") {
break;
}
}
String line = clientSecure.readStringUntil('\n');
clientSecure.stop();
Serial.println(line);
Serial.printf("send2line sent\n");
delay(1000);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment