-
-
Save nomissbowling/207e712a88551ffef325a4401bd540f1 to your computer and use it in GitHub Desktop.
Internet Of SPO2 Things.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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