Cura - A Health Emergency Alert for Seniors We Care

1 13445 Medium

Cura is a wearable device that detects the wearer’s health emergency issue and sends an SMS alert to the wearer’s emergency contact.

projectImage

Things used in this project

 

Hardware components

HARDWARE LIST
1 DFRobot 6 DOF Sensor - MPU6050
1 Arduino UNO
1 ProtoCentral Electronics ProtoCentral Pulse Oximeter & Heart Rate Sensor based on MAX30100
1 Espressif ESP8266 ESP-12E
1 Battery, 3.7 V
1 Jumper wires (generic)
1 SparkFun Pushbutton switch 12mm

Software apps and online services

 

Arduino IDE

 

Twilio SMS Messaging API

Hand tools and fabrication machines

 

Soldering iron (generic)

Story

 

Story

 

My gradmother love having nightwalks, yet her health condition is deteriorating at the age of 90. She once accidently fell in the field during a nightwalk. My family and I spent a night searching for her and eventually called the police to find her. My parents were worried about her and told her that she must have a companion in order to have a nightwalk.

 

During last weekend at LA Hacks, I shared this story with my friends in the same team. After brainstorming, we thought, why not build a wearable device that could detect the elderly wearer's health emergency and send alert to his or her family and friends? Here’s how Cura comes.

projectImage

Research

 

During my research, I found that not only my grandma, but also a lot of the seniors out there, share the concerns of healthy emergency issues. The two major types of emergency are falling and arrhythmia (unusual heart rate), caused by deteriorating body and organs.

projectImage

According to CDC [https://www.cdc.gov/injury/features/older-adult-falls/index.html#:~:text=Falls%20are%20common%20and%20costly,death%20in%20this%20age%20group], about 36 million falls are reported among older adults each year, resulting in 3 million emergency department treatments and more than 32, 000 deaths. And according to NHANES, 70% of older adults have hypertension, causing stroke, heart failure, and even sudden death.

 

Moreover, a research by BMC Emergency Medicine suggests that 60 minutes after a traumatic injury is the greatest chance of survival if given medical attention. This proves our idea can be life-saving to seniors by reporting their health emergency in time, so we start building it.

 

Health Emergency Detection

 

We use Arduino Uno as our main board (since Nano R3 is somehow incompatible with higher versions of MacOS, we tried), along with MPU6050 accelerometer, pulse sensor, ESP-8266 12-e WiFi modulel, pushbutton, LED bulb, and 3.7V battery.

 

All the code and wire diagrams are at the bottom of this page. MPU6050 and pulse sensor requires soldering first.

projectImage

Detect Fall

 

We use the accelerometer to detect fall, since fall is a "lower version" of free fall, which gives us a smaller gravitational acceleration than usual (G < 9.8 - falling threshold). Some projects use DPS310 to detect fall by measuring a decrease in the wearer's altitude, but we found that inaccurate (e.g. going downhills or staircases) and not as intuitive as gravitational acceleration.

 

We make the fall detection more accurate and avoids wrong alert by incorporating code from this project. The accelerometer must receives a lower G when falling, a reverse spike in G when hit the ground, and no change in G for a time period, suggesting the user has fallen and is lying still on the ground.

 

Detect arrhythmia

 

We use the pulse sensor to measure the wearer's heart rate. As research results suggest, a heart rate higher than 200 or lower than 27 beats per minute (BPM) is life threatening. Taking into account that the wearer may be exercising or sleeping, a heart rate at this danger range is abnormal regardless of the wearer's state of movement.

 

Emergency button

 

Since emergency casued by stroke and heart attack may not have a obvious change in heart rate, we add a emergency button that allows the wearer to send alert directly when feeling uncomfortable.

 

When emergency is triggered by either of the three above, the emergency LED blub turns on. The light signals potential people around the wearers is in emergency, not to confuse with the belief that the wearer is sleeping or just laying down.

 

Send SMS alert with location

 

Once an emergency is triggered, Arduino Uno outputs a HIGH pin to ESP8266, which then sends a SMS to the wearer's emergency contact with the wearer's location.

projectImage

TwilioSMSAPI

 

Twilio supports sending SMS via WiFi connection using ESP8266. We follow this doc to implement this feature. https://www.twilio.com/docs/sms/tutorials/how-to-send-sms-messages-esp8266-cpp

UnwiredLabGeolocationAPI

 

Through research, we find WiFi positioning system (WPS) allows us to get the wearer's current location using information from the connected network. UnwiredLab provides a Geolocation API that utilizes WPS to return the location. Given the location in the SMS, the emergency contact can find the wearer before the wearer's sign of life worsens.

 

Demo

Future works

 

-Use Arduino Nano R3 and ESP8266-01 to make the device size smaller

 

-Add a 3D printed shell to the hardware components

 

-Press the emergency button again to cancel the emergency and send another SMS

 

-Add health features like steps and exercise time measured by accelerometer

 

-Add a monitor or implement Blynk app to see health data, past emergency alerts, and edit emergency contacts

 

Hope this article helps you. Happy building!

Schematics

 

MPU6050 wire

projectImage

Pulse sensor wire

projectImage

ESP8266 12E wire

projectImage

Code

 

Arduino Uno health emergency detection

Arduino

 

This code is uploaded to Arduino Uno to detect:
1. Fall measured by accelerometer
2. Unusual heart rate measured by pulse sensor
3. Emergency button pushed

Once one of three above is triggered, the code sends HIGH output to the WiFi module and the LED bulb.

I incorporated fall detection code from https://create.arduino.cc/projecthub/Technovation/health-band-a-smart-assistant-for-the-elderly-0fed12 since it gives accurate result by monitoring a 3-stage change in G.

CODE
#define USE_ARDUINO_INTERRUPTS true // Set-up low-level interrupts for most acurate BPM math
#include <PulseSensorPlayground.h>
#include <Wire.h>

const int PulseWire = 0; // 'S' Signal pin connected to A0
// const int LED13 = 13;          // The on-board Arduino LED
int Threshold = 550; // Determine which Signal to "count as a beat" and which to ignore
PulseSensorPlayground pulseSensor;

const int MPU_addr = 0x68; // I2C address of the MPU-6050
int16_t AcX, AcY, AcZ, Tmp, GyX, GyY, GyZ;
float ax = 0, ay = 0, az = 0, gx = 0, gy = 0, gz = 0;
const int buttonPin = 6;
const int ledPin = 3;

// int data[STORE_SIZE][5]; //array for saving past data
// byte currentIndex=0; //stores current data array index (0-255)
boolean fall = false;     // stores if a fall has occurred
boolean trigger1 = false; // stores if first trigger (lower threshold) has occurred
boolean trigger2 = false; // stores if second trigger (upper threshold) has occurred
boolean trigger3 = false; // stores if third trigger (orientation change) has occurred

byte trigger1count = 0; // stores the counts past since trigger 1 was set true
byte trigger2count = 0; // stores the counts past since trigger 2 was set true
byte trigger3count = 0; // stores the counts past since trigger 3 was set true
int angleChange = 0;

// variable for storing the pushbutton status
int buttonState = 0;
int ledState = 0;

int fall_message_ouput = 7;

void setup()
{
  Wire.begin();
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x6B); // PWR_MGMT_1 register
  Wire.write(0);    // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  Serial.begin(9600);
  
  // set output pins to send fall alert to ESP8266
  pinMode(fall_message_ouput, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(0, OUTPUT);
  digitalWrite(11, HIGH);
  
  // set input pin of button and output pin of led
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);

  // Configure the PulseSensor object, by assigning our variables to it
  pulseSensor.analogInput(PulseWire);
  // pulseSensor.blinkOnPulse(LED13);       // Blink on-board LED with heartbeat
  pulseSensor.setThreshold(Threshold);

  if (pulseSensor.begin())
  {
    Serial.println("PulseSensor object created!");
  }
}

void loop()
{
  // read the state of the pushbutton value
  buttonState = digitalRead(buttonPin);
  // check if the pushbutton is pressed.
  // if it is, the buttonState is HIGH
  if (buttonState == HIGH)
  {
    ledState = !ledState;
  }
  if (ledState == HIGH)
  {
    // turn LED on
    digitalWrite(ledPin, HIGH);
  }
  else
  {
    // turn LED off
    digitalWrite(ledPin, LOW);
  }

  // heartbeat sensor
  int myBPM = pulseSensor.getBeatsPerMinute(); // Calculates BPM

  if (pulseSensor.sawStartOfBeat())
  {                                             // Constantly test to see if a beat happened
    Serial.println("Heartbeat  BPM: " + myBPM); // If true, print a message                       // Print the BPM value
  }
  if (myBPM > 170)
  { // high heart rate detection
    Serial.println("High Heartrate Alert: Your heart rate is above 200!");
    digitalWrite(fall_message_ouput, HIGH);
    digitalWrite(ledPin, HIGH);
    delay(1000);
    digitalWrite(fall_message_ouput, LOW);
  }
  if (myBPM < 27)
  { // high heart rate detection
    Serial.println("Low Heartrate Alert: Your heart rate is below 27!");
    digitalWrite(fall_message_ouput, HIGH);
    digitalWrite(ledPin, HIGH);
    delay(1000);
    digitalWrite(fall_message_ouput, LOW);
  }

  // accelerator
  mpu_read();
  // 2050, 77, 1947 are values for calibration of accelerometer
  //  values may be different for you
  ax = (AcX - 2050) / 16384.00;
  ay = (AcY - 77) / 16384.00;
  az = (AcZ - 1947) / 16384.00;

  // 270, 351, 136 for gyroscope
  gx = (GyX + 270) / 131.07;
  gy = (GyY - 351) / 131.07;
  gz = (GyZ + 136) / 131.07;

  // calculating Amplitute vactor for 3 axis
  float Raw_AM = pow(pow(ax, 2) + pow(ay, 2) + pow(az, 2), 0.5);
  int AM = Raw_AM * 10; // as values are within 0 to 1, I multiplied
                        // it by for using if else conditions
                        // Serial.println(analogRead(A1));
  // if(digitalRead(A3) == HIGH)
  //{
  //   Serial.println("A3: SOS"+digitalRead(A3));
  // }

  Serial.println(AM);
  // Serial.println(PM);
  // delay(5);

  if (trigger3 == true)
  {
    trigger3count++;
    // Serial.println(trigger3count);
    if (trigger3count >= 10)
    {
      angleChange = pow(pow(gx, 2) + pow(gy, 2) + pow(gz, 2), 0.5);
      // delay(10);
      Serial.println(angleChange);
      if ((angleChange >= 0) && (angleChange <= 10))
      { // if orientation changes remains between 0-10 degrees
        fall = true;
        trigger3 = false;
        trigger3count = 0;
        Serial.println(angleChange);
      }
      else
      { // user regained normal orientation
        trigger3 = false;
        trigger3count = 0;
        Serial.println("TRIGGER 3 DEACTIVATED");
      }
    }
  }
  if (fall == true)
  { // in event of a fall detection
    Serial.println("FALL DETECTED");
    digitalWrite(fall_message_ouput, HIGH);
    digitalWrite(ledPin, HIGH);
    delay(1000);
    digitalWrite(fall_message_ouput, LOW);
    fall = false;
    // exit(1);
  }
  if (trigger2count >= 6)
  { // allow 0.5s for orientation change
    trigger2 = false;
    trigger2count = 0;
    Serial.println("TRIGGER 2 DECACTIVATED");
  }
  if (trigger1count >= 6)
  { // allow 0.5s for AM to break upper threshold
    trigger1 = false;
    trigger1count = 0;
    Serial.println("TRIGGER 1 DECACTIVATED");
  }
  if (trigger2 == true)
  {
    trigger2count++;
    // angleChange=acos(((double)x*(double)bx+(double)y*(double)by+(double)z*(double)bz)/(double)AM/(double)BM);
    angleChange = pow(pow(gx, 2) + pow(gy, 2) + pow(gz, 2), 0.5);
    Serial.println(angleChange);
    if (angleChange >= 30 && angleChange <= 400)
    { // if orientation changes by between 80-100 degrees
      trigger3 = true;
      trigger2 = false;
      trigger2count = 0;
      Serial.println(angleChange);
      Serial.println("TRIGGER 3 ACTIVATED");
    }
  }
  if (trigger1 == true)
  {
    trigger1count++;
    if (AM >= 12)
    { // if AM breaks upper threshold (3g)
      trigger2 = true;
      Serial.println("TRIGGER 2 ACTIVATED");
      trigger1 = false;
      trigger1count = 0;
    }
  }
  if (AM <= 2 && trigger2 == false)
  { // if AM breaks lower threshold (0.4g)
    trigger1 = true;
    Serial.println("TRIGGER 1 ACTIVATED");
  }
  // It appears that delay is needed in order not to clog the port
  delay(100);
}

void mpu_read()
{
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_addr, 14, true); // request a total of 14 registers
  AcX = Wire.read() << 8 | Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
  AcY = Wire.read() << 8 | Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ = Wire.read() << 8 | Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  Tmp = Wire.read() << 8 | Wire.read(); // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  GyX = Wire.read() << 8 | Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  GyY = Wire.read() << 8 | Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  GyZ = Wire.read() << 8 | Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
}

WiFi module sends SMS with location

Arduino

 

This code is uploaded to ESP8266 12E to
1. Get the wearer's location triangulated by WiFi position system using UnwiredLab Geolocation API
2. Send SMS with the location to the wearer's emergency contact using Twilio SMS API

Both actions are triggered when ESP8266 receives pin input from Arduino Uno.

CODE
/*
 * Twilio SMS and MMS on ESP8266 Example.
 */

#include <Wire.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ESP8266WebServer.h>

#include "twilio.hpp"

// Use software serial for debugging?
#define USE_SOFTWARE_SERIAL 0

// Print debug messages over serial?
#define USE_SERIAL 1

//
// UnwiredLab Geolocation API info
//
// your network SSID (name) & network password
char myssid[] = "UCLA_WEB";
char mypass[] = "";

// unwiredlabs Hostname & Geolocation Endpoint url
const char *host = "us1.unwiredlabs.com";
String endpoint = "/v2/process.php";
const int httpsPort = 443;

const char *fingerprint = "41 3F 95 84 0A E3 74 F6 2B C3 19 27 C9 67 F3 0C 38 D4 6F B6";

// UnwiredLabs API_Token. Signup here to get a free token https://unwiredlabs.com/trial
String geolocation_api_key = "pk.73d4083cea6ff7527b49262ee534f8f1";
String jsonString = "{\n";

// Variables to store unwiredlabs response
double latitude = 0.0;
double longitude = 0.0;
double accuracy = 0.0;

int GPIO = 5;

// Your network SSID and password
const char *ssid = "UCLA_WEB";
const char *password = "";

// Find the api.twilio.com SHA1 fingerprint, this one was valid as
// of July 2020. This will change, please see
// https://www.twilio.com/docs/sms/tutorials/how-to-send-sms-messages-esp8266-cpp
// to see how to update the fingerprint.
const char fingerprint[] = "72 1C 17 31 85 E2 7E 0D F9 D4 C2 5B A0 0E BD B7 E2 06 26 ED";

// Twilio account specific details, from https://twilio.com/console
// Please see the article:
// https://www.twilio.com/docs/guides/receive-and-reply-sms-and-mms-messages-esp8266-c-and-ngrok

// If this device is deployed in the field you should only deploy a revocable
// key. This code is only suitable for prototyping or if you retain physical
// control of the installation.
const char *account_sid = "ACb63f3d2be8ed9c87832c49302c30a636";
const char *auth_token = "63bee244f52b6f923f0b9b719598c530";

// Details for the SMS we'll send with Twilio.  Should be a number you own
// (check the console, link above).
String to_number = "+12134682703";
String from_number = "+15205025995";
String user_name = "Jeffrey";
String message_body = "Cura - " + user_name + " alerts a health emergency!!! He's at latitude: " + String(loc_longitude, 6) + ", longitude: " + String(loc_latitude, 6) + ". Call 911 if you need any emergent medical help.";

// The 'authorized number' to text the ESP8266 for our example
String master_number = "+12134682703";

// Optional - a url to an image.  See 'MediaUrl' here:
// https://www.twilio.com/docs/api/rest/sending-messages
String media_url = "";

// Global twilio objects
Twilio *twilio;
ESP8266WebServer twilio_server(8000);
twilio = new Twilio(account_sid, auth_token, fingerprint);

//  Optional software serial debugging
#if USE_SOFTWARE_SERIAL == 1
#include <SoftwareSerial.h>
// You'll need to set pin numbers to match your setup if you
// do use Software Serial
extern SoftwareSerial swSer(14, 4, false, 256);
#else
#define swSer Serial
#endif

/*
 * Callback function when we hit the /message route with a webhook.
 * Use the global 'twilio_server' object to respond.
 */
void handle_message()
{
#if USE_SERIAL == 1
  swSer.println("Incoming connection!  Printing body:");
#endif
  bool authorized = false;
  char command = '\0';

  // Parse Twilio's request to the ESP
  for (int i = 0; i < twilio_server.args(); ++i)
  {
#if USE_SERIAL == 1
    swSer.print(twilio_server.argName(i));
    swSer.print(": ");
    swSer.println(twilio_server.arg(i));
#endif

    if (twilio_server.argName(i) == "From" and
        twilio_server.arg(i) == master_number)
    {
      authorized = true;
    }
    else if (twilio_server.argName(i) == "Body")
    {
      if (twilio_server.arg(i) == "?" or
          twilio_server.arg(i) == "0" or
          twilio_server.arg(i) == "1")
      {
        command = twilio_server.arg(i)[0];
      }
    }
  } // end for loop parsing Twilio's request

  // Logic to handle the incoming SMS
  // (Some board are active low so the light will have inverse logic)
  String response = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
  if (command != '\0')
  {
    if (authorized)
    {
      switch (command)
      {
      case '0':
        digitalWrite(LED_BUILTIN, LOW);
        response += "<Response><Message>"
                    "Turning light off!"
                    "</Message></Response>";
        break;
      case '1':
        digitalWrite(LED_BUILTIN, HIGH);
        response += "<Response><Message>"
                    "Turning light on!"
                    "</Message></Response>";
        break;
      case '?':
      default:
        response += "<Response><Message>"
                    "0 - Light off, 1 - Light On, "
                    "? - Help\n"
                    "The light is currently: ";
        response += digitalRead(LED_BUILTIN);
        response += "</Message></Response>";
        break;
      }
    }
    else
    {
      response += "<Response><Message>"
                  "Unauthorized!"
                  "</Message></Response>";
    }
  }
  else
  {
    response += "<Response><Message>"
                "Look: a SMS response from an ESP8266!"
                "</Message></Response>";
  }

  twilio_server.send(200, "application/xml", response);
}

//
// Get current location from UnwiredLab Geolocation API
//
void getLocation()
{
  char bssid[6];
  DynamicJsonBuffer jsonBuffer;

  // WiFi.scanNetworks will return the number of networks found
  int n = WiFi.scanNetworks();
  Serial.println("scan done");

  if (n == 0)
  {
    Serial.println("No networks available");
  }
  else
  {
    Serial.print(n);
    Serial.println(" networks found");
  }

  // now build the jsonString...
  jsonString = "{\n";
  jsonString += "\"token\" : \"";
  jsonString += geolocation_api_key;
  jsonString += "\",\n";
  jsonString += "\"id\" : \"saikirandevice01\",\n";
  jsonString += "\"wifi\": [\n";
  for (int j = 0; j < n; ++j)
  {
    jsonString += "{\n";
    jsonString += "\"bssid\" : \"";
    jsonString += (WiFi.BSSIDstr(j));
    jsonString += "\",\n";
    jsonString += "\"signal\": ";
    jsonString += WiFi.RSSI(j);
    jsonString += "\n";
    if (j < n - 1)
    {
      jsonString += "},\n";
    }
    else
    {
      jsonString += "}\n";
    }
  }
  jsonString += ("]\n");
  jsonString += ("}\n");
  Serial.println(jsonString);

  WiFiClientSecure client;

  // Connect to the client and make the api call
  Serial.println("Requesting URL: https://" + (String)Host + endpoint);
  if (client.connect(Host, 443))
  {
    Serial.println("Connected");
    client.println("POST " + endpoint + " HTTP/1.1");
    client.println("Host: " + (String)Host);
    client.println("Connection: close");
    client.println("Content-Type: application/json");
    client.println("User-Agent: Arduino/1.0");
    client.print("Content-Length: ");
    client.println(jsonString.length());
    client.println();
    client.print(jsonString);
    delay(500);
  }

  // Read and parse all the lines of the reply from server
  while (client.available())
  {
    String line = client.readStringUntil('\r');
    JsonObject &root = jsonBuffer.parseObject(line);
    if (root.success())
    {
      latitude = root["lat"];
      longitude = root["lon"];
      accuracy = root["accuracy"];

      Serial.print("Latitude = ");
      Serial.println(latitude, 6);
      Serial.print("Longitude = ");
      Serial.println(longitude, 6);
      Serial.print("Accuracy = ");
      Serial.println(accuracy);

      loc_latitude = latitude;
      loc_longitude = longitude;
      loc_accuracy = accuracy;
    }
  }

  Serial.println("closing connection");
  Serial.println();
  client.stop();
}

/*
 * Setup function for ESP8266 Twilio Example.
 *
 * Here we connect to a friendly wireless network, set the time, instantiate
 * our twilio object, optionally set up software serial, then send a SMS
 * or MMS message.
 */
void setup()
{
  pinMode(GPIO, INPUT);
  Wire.begin(9); // 9 here is the address(Mentioned even in the master board code)

  WiFi.begin(ssid, password);
  //        twilio = new Twilio(account_sid, auth_token, fingerprint);

#if USE_SERIAL == 1
  swSer.begin(115200);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    swSer.print(".");
  }
  swSer.println("");
  swSer.println("Connected to WiFi, IP address: ");
  swSer.println(WiFi.localIP());
#else
  while (WiFi.status() != WL_CONNECTED)
    delay(1000);
#endif
}

/*
 *  In our main loop, we listen for connections from Twilio in handleClient().
 */
void loop()
{
  twilio_server.handleClient();

  if (digitalRead(GPIO) == HIGH)
  {
    Serial.println("Health emergency detected!");
    getLocation();

    // Response will be filled with connection info and Twilio API responses
    // from this initial SMS send.
    String response;
    bool success = twilio->send_message(
        to_number,
        from_number,
        message_body,
        response,
        media_url);

    // Set up a route to /message which will be the webhook url
    twilio_server.on("/message", handle_message);
    twilio_server.begin();

    // Use LED_BUILTIN to find the LED pin and set the GPIO to output
    pinMode(LED_BUILTIN, OUTPUT);

#if USE_SERIAL == 1
    swSer.println(response);
#endif
  }
}

The article was first published in hackster, April 21, 2022

cr: https://www.hackster.io/jeffreythecoder/cura-a-health-emergency-alert-for-seniors-we-care-ee08d1

author: jeffreythecoder

License
All Rights
Reserved
licensBg
1