Remote Health Checking System

by rmchu in Circuits > Arduino

80 Views, 2 Favorites, 0 Comments

Remote Health Checking System

IMG_3564.png

This is my teaching case for my project which is a remote health checking or monitoring system. It is a system that helps the user check and keep up on their health by collecting data on their pulse, body temperature and the gas around them as it can also effect ones health, especially for people who live in places with poor air quality. This system can be setup in the users home, at anywhere that is a accessible to them where they can use whenever they desire, whether that's a few times a week or everyday, and it is user controlled using a GUI created using Python on the Raspberry Pi. Additionally, there are certain thresholds, that when crossed, the user will be notified using IFTTT. I have created this project as a way to address issues with healthcare accessibility, due to factors such as cost and distance. This system is a simple way to check ones health without needing to go to a healthcare facility for a simple checkup, this can also be suitable for people who are more health conscious and want to monitor their health at home. Though it should obviously be created to a larger scale to be able to implement it in the real world, this is just a rundown version of the idea. The sensors can be changes or added to as there are various ways to measure health, the code will just have to be altered to suit the sensors.

Supplies

Hardware

  • Arduino Nano 33 IoT
  • Raspberry Pi
  • Biometric sensor (MAX30102) For pulse
  • Contactless Infrared Thermophile Sensor (TMP007) Body temperature
  • Gas Sensor (MQ2) Gas
  • Buzzer


Software

  • Application to remotely control Raspberry Pi (If needed) e.g TigerVNC, VNC Viewer
  • Arduino IDE installed on Raspberry Pi
  • Python IDE on Raspberry Pi e.g Thonny, PyCharm

Hardware Setup

Screenshot 2024-04-18 at 3.08.39 pm.png

Connect the hardware to the required connections. The biometric and temperature sensor will need to connect to the SCL and SDA pins, as well as ground and power (3.3V), the gas sensor will connect to pin A0, ground and power, and the buzzer to pin 8 and ground.

The Arduino connects to the Raspberry Pi's USB port to create a serial communication.

Arduino Program

Screenshot 2024-05-30 at 1.47.42 am.png

Going on the Raspberry Pi, we will first start programming the Arduino and the sensors.

I have downloaded the SparkFun MAX3010x library for the biometric sensor and the Adafruit TMP007 library for the temperature sensor, and I have also included the Wire.h library for the I2C communications. These will aid us in setting up the sensors, just by referring to the example sketches in these libraries, we can easily set everything up. We define all the variables that we will be using, such as pulse, temperature etc. and initialise the sensors, as shown in the photo.

I have created separate methods for each sensor reading, as mentioned before, I referred to the example sketches to figure out how to program the sensors. The biometric sensor will continuously collect data when called, while the temperature and gas sensors will have a gap in between each reading. When there is no finger detected on the biometric, the buzzer method will be called.

Additionally, there is a reset sensor method created that will reset the sensor every time the program starts so that we remove remaining data from the previous reading. There is also a check sensor function that is used for fault tolerance, in the case of any hardware and connection issues.

In the main loop, it's set up so that the check sensor method will be called every 5 seconds, a switch case is used so that each function can be called depending on the user interacting with the GUI. Each case will call on a sensor method to collect data.


Arduino Code

//Include libraries
#include <Wire.h>
#include "Adafruit_TMP007.h"
#include "MAX30105.h"
#include "heartRate.h"

#define MQ2pin A0
#define buzzerPin 8

MAX30105 particleSensor;
Adafruit_TMP007 tmp007;
const byte RATE_SIZE = 4;
byte rates[RATE_SIZE];
byte rateSpot = 0;
long lastBeat = 0;
float beatsPerMinute;
int beatAvg;
float sensorValue;
float objt;

void setup() {
Serial.begin(115200);
pinMode(buzzerPin, OUTPUT);

// Initialize sensors
if (!tmp007.begin())
{
Serial.println("TMP007 not found!");
while (1);
}

//Setup Biometric Sensor
if (!particleSensor.begin(Wire, I2C_SPEED_FAST))
{
Serial.println("MAX30105 was not found. Please check wiring/power.");
while (1);
}
Serial.println("Place your index finger on the sensor with steady pressure.");

particleSensor.setup();
particleSensor.setPulseAmplitudeRed(0x0A);
particleSensor.setPulseAmplitudeGreen(0);
}

void loop() {
//Check for proper hardware connection
static unsigned long lastCheck = 0;
const unsigned long checkInterval = 5000; // Check every 5 seconds

if (millis() - lastCheck >= checkInterval) {
lastCheck = millis();
checkSensors();
}

if (Serial.available() > 0) {
char command = Serial.read();
//Switch case for each sensor
switch (command) {
case 'R':
resetSensor();
break;
case 'P':
heartrate();
break;
case 'T':
temp();
break;
case 'G':
gas();
break;
default:
break;
}
}
}

//Method for measuring pulse
void heartrate() {

long irValue = particleSensor.getIR();

if (checkForBeat(irValue)) {
long delta = millis() - lastBeat;
lastBeat = millis();

beatsPerMinute = 60 / (delta / 1000.0);

if (beatsPerMinute < 255 && beatsPerMinute > 20)
{
rates[rateSpot++] = (byte)beatsPerMinute;
rateSpot %= RATE_SIZE;

beatAvg = 0;
for (byte x = 0; x < RATE_SIZE; x++)
beatAvg += rates[x];
beatAvg /= RATE_SIZE;
}
}

Serial.print("Avg BPM=");
Serial.println(beatAvg);
//If finger is not on the sensor, trigger buzzer
if (irValue < 50000) {
Serial.println("No finger?");
buzz();
}
}
//Read temperature method
void temp() {
float objt = tmp007.readObjTempC();
Serial.print("Temperature=");
Serial.println(objt);
delay(4000);
}
//Read gas method
void gas() {
sensorValue = analogRead(MQ2pin);
//If readings are invalid or there are issues with sensor
if (sensorValue < 0 || sensorValue > 1023) {
Serial.println("Gas sensor not found or reading invalid.");
} else {
Serial.print("Gas=");
Serial.println(sensorValue);
}
delay(2000);

}
//Buzzer method
void buzz()
{
digitalWrite(buzzerPin, HIGH);
delay(1000);
digitalWrite(buzzerPin, LOW);
}
//Reset sensor when restarting program
void resetSensor()
{
particleSensor.shutDown(); // Power down the sensor
delay(100); // Wait for the sensor to fully power down
particleSensor.wakeUp(); // Power up the sensor
particleSensor.setup(); // Reinitialize the sensor
lastBeat = 0;
rateSpot = 0;
for (byte i = 0; i < 4; i++) rates[i] = 0;
beatAvg = 0;
sensorValue = 0;
objt = 0;
}
//Check if sensors are correctly setup
void checkSensors() {
// Check TMP007 sensor
float temperature = tmp007.readObjTempC();
if (isnan(temperature) || temperature < -40.0 || temperature > 150.0) {
Serial.println("TMP007 not found!");
}

// Check MAX30105 sensor by reading a value
if (!particleSensor.safeCheck(500)) {
Serial.println("MAX30105 was not found. Please check wiring/power.");
}
}


GUI Login & Input Pages

Screenshot 2024-05-30 at 1.06.17 am.png
Screenshot 2024-05-30 at 1.06.49 am.png

Now, we program the other GUI pages of the program which is fairly simple. We start with the Login page, the first page the user will see. This page simply contains labels and input boxes for the user to input their login details which are setup in the code, there is error handling as well for if the login details are incorrect that will display a warning box. In the input page, it is similar, it prompts the user for their weight and height and there is user input handling for when the inputs are not valid such as negative values, string values or the user not completing the form.

Login Class

from PyQt5.QtWidgets import QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout, QMessageBox
#Login page
class LoginPage(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self): #Ask for username and password
self.username_label = QLabel('Username', self)
self.username_input = QLineEdit(self)

self.password_label = QLabel('Password', self)
self.password_input = QLineEdit(self)
self.password_input.setEchoMode(QLineEdit.Password)

self.login_button = QPushButton('Login', self)
self.login_button.clicked.connect(self.check_login)

layout = QVBoxLayout()
layout.addWidget(self.username_label)
layout.addWidget(self.username_input)
layout.addWidget(self.password_label)
layout.addWidget(self.password_input)
layout.addWidget(self.login_button)

self.setLayout(layout)
self.setWindowTitle('Login Page')

def check_login(self):
username = self.username_input.text()
password = self.password_input.text()
#set up user name and password and validate
if username == 'user' and password == 'pass':
self.parent().login_successful()
else:
QMessageBox.warning(self, 'Error', 'Incorrect username or password')


Input Class

from PyQt5.QtWidgets import QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout, QMessageBox,QSizePolicy,QApplication  
from PyQt5.QtCore import Qt
#Starting input page
class InputPage(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.name = QLabel('Welcome, Rachel!', self)
self.name.setAlignment(Qt.AlignCenter) # Center align the text
self.name.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) # Ensure the label expands to fit content
#Prompt user for weight and height
self.weight_label = QLabel('Weight (kg):', self)
self.weight_input = QLineEdit(self)

self.height_label = QLabel('Height (cm):', self)
self.height_input = QLineEdit(self)
#Submit
self.submit_button = QPushButton('Submit', self)
self.submit_button.clicked.connect(self.submit_data)

layout = QVBoxLayout()
weight_layout = QHBoxLayout()
height_layout = QHBoxLayout()

weight_layout.addWidget(self.weight_label)
weight_layout.addWidget(self.weight_input)

height_layout.addWidget(self.height_label)
height_layout.addWidget(self.height_input)

layout.addWidget(self.name)
layout.addLayout(weight_layout)
layout.addLayout(height_layout)
layout.addWidget(self.submit_button)

self.setLayout(layout)
self.setWindowTitle('Enter Weight and Height')

def submit_data(self):
weight = self.weight_input.text()
height = self.height_input.text()

# Validate input data
if not weight or not height:
QMessageBox.warning(self, 'Error', 'Please enter both weight and height')
return
try:
weight = float(weight)
height = float(height)
except ValueError:
QMessageBox.warning(self, 'Error', 'Please enter valid numbers for weight and height')
return

if weight <= 0 or height <= 0:
QMessageBox.warning(self, 'Error', 'Please enter valid numbers for weight and height')
return


self.parent().input_complete(weight, height)


GUI Summary Page & Main Class

Screenshot 2024-05-30 at 1.12.23 am.png

The summary page is the final page that is displayed after the health checking is completed, this simply contains the date and time of the check and all the final data collected.

The Main class is the main controller where all the other classes are put together and where the program entry point is to run it. It determines the flow of the program, containing objects of each class and methods to transition between pages from login to summary. There is a centre window method to determine the centre of the device screen to display the GUI there.

Summary Class

from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QPushButton, QApplication
from datetime import datetime
import time
#Summary page
class SummaryPage(QWidget):
def __init__(self, weight, height, sensor_data):
super().__init__()
self.weight = weight
self.height = height
self.sensor_data = sensor_data
self.initUI()

def initUI(self):
layout = QVBoxLayout()
#Show data collected
current_datetime = time.strftime("%Y-%m-%d %H:%M:%S")
date_label = QLabel(f"Date and Time: {current_datetime}", self)

weight_label = QLabel(f"Weight: {self.weight} kg", self)
height_label = QLabel(f"Height: {self.height} cm", self)

pulse_label = QLabel(f"Average Pulse: {self.sensor_data.get('pulse', 'N/A')} BPM", self)
temp_label = QLabel(f"Average Temperature: {self.sensor_data.get('temperature', 'N/A')} °C", self)
gas_label = QLabel(f"Average Gas Level: {self.sensor_data.get('gas', 'N/A')}", self)
#Close button
finish_button = QPushButton('Finish', self)
finish_button.clicked.connect(self.close_program)

layout.addWidget(date_label)
layout.addWidget(weight_label)
layout.addWidget(height_label)
layout.addWidget(pulse_label)
layout.addWidget(temp_label)
layout.addWidget(gas_label)
layout.addWidget(finish_button)

self.setLayout(layout)
self.setWindowTitle('Summary Page')

def close_program(self):
QApplication.instance().quit()


Main Class

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget
from Login import LoginPage
from inputpage import InputPage
from healthmonitoring import HealthMonitorGUI
from summarypage import SummaryPage
#Main class to set all the classes up
class MainApplication(QMainWindow):
def __init__(self):
super().__init__()
self.login_page = LoginPage()
self.setCentralWidget(self.login_page)
self.login_page.setParent(self)
self.setWindowTitle('Health Checking System')
self.resize(400, 300) # Resize window
self.center_window()
self.show()
#Login to input
def login_successful(self):
self.input_page = InputPage()
self.setCentralWidget(self.input_page)
self.input_page.setParent(self)
self.input_page.show()
#Health monitor
def input_complete(self, weight, height):
self.weight = weight
self.height = height
self.sensor_data = {}
self.health_monitor_gui = HealthMonitorGUI()
self.setCentralWidget(self.health_monitor_gui)
self.health_monitor_gui.show()
#Summary page
def display_summary(self):
self.summary_page = SummaryPage(self.weight, self.height, self.sensor_data)
self.setCentralWidget(self.summary_page)
self.summary_page.show()
#Center window
def center_window(self):
frame_geo = self.frameGeometry()
screen_center = QDesktopWidget().availableGeometry().center()
frame_geo.moveCenter(screen_center)
self.move(frame_geo.topLeft())

if __name__ == '__main__':
app = QApplication(sys.argv)
main_app = MainApplication()
sys.exit(app.exec_())


GUI Health Monitoring Page

Screenshot 2024-05-30 at 1.08.35 am.png
Screenshot 2024-05-30 at 1.12.03 am.png

Now, we create the GUI set up starting with the main health monitoring page the GUI is designed using Qt. First importing the needed libraries sys, serial, time, threading, requests and PyQt5.

Setting up the serial connection to the Arduino, we need the port name of the Arduino on the Raspberry Pi, this is usually in my case dev/ttyACM0 or dev/ttyACM1 as well as the baud rate which is 115200. We then declare all the variables we need, the IFTTT details and the thresholds.

Now creating our functions, we start with the IFTTT sending function using a web request and verifying if it's successful or not. Then there is a read data function that will be applied to all the sensors as how the program will read the data being read, it will reset the sensor by calling 'R' and decoding the data coming through the serial communication. Checking for error messages, which will cause an error window to pop up, and if not, the data will be parsed between the '=' sign and the last read data will be the final data given to the use, the reading will go for 30 seconds for each sensor. The program then checks if the final data goes over the thresholds, and if so, the IFTTT notification will trigger using the function.

In the Main window, we set up the GUI design using simple labels and buttons to display the results to the user, and allowing them to interact with each button to start a reading. There are also 3 boolean variables for each sensor to track if the user has completed each sensor reading.

The methods for each sensor are very similar, threading is used to ensure that each function gets properly processed one at a time, this is used for each function. As mentioned before, the read sensor data function gets used in the functions and the final data gets displayed onto the GUI and stored, as well as error handling if no final data gets return.

In the finish method, when the user wants to finish the reading, if not all the sensors get read and completed, a warning box will appear not allowing the user to continue. If all is completed, the summary page is shown.


Health Monitoring Class

import sys
import serial
import time
import threading
import requests
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget, QMessageBox
from PyQt5.QtCore import QTimer

# Configure the serial connection to the Arduino
ser = serial.Serial('/dev/ttyACM0', 115200) # Adjust the serial port as needed
time.sleep(2) # Wait for the serial connection to initialize
IFTTT_KEY = 'diV95Kkwlv38KjPMKlUS4T'
IFTTT_EVENT_NAME = 'sensor_trigger'
IFTTT_URL = f"https://maker.ifttt.com/trigger/{IFTTT_EVENT_NAME}/with/key/{IFTTT_KEY}"

# Thresholds for notifications
TEMP_THRESHOLD_HIGH = 39.0
TEMP_THRESHOLD_LOW = 26.0
HEART_RATE_THRESHOLD_HIGH = 185
GAS_THRESHOLD = 400

class SensorError(Exception):
pass
#Send IFTTT notification
def send_ifttt_notification(value1):
payload = {"value1": value1}
response = requests.post(IFTTT_URL, json=payload)
if response.status_code == 200:
print("IFTTT notification sent successfully")
else:
print("Error sending IFTTT notification")
#read data received from the arduino
def read_sensor_data(command, duration=30, interval=1):
readings = []
start_time = time.time()
ser.write('R'.encode())
time.sleep(1)

while time.time() - start_time < duration:
ser.write(command.encode())
line = ser.readline().decode('utf-8').strip()
print(line) # Debug: Print the raw data received
try:
if 'TMP007 not found!' in line:
raise SensorError("TMP007 sensor not found. Please check the connection.")
elif 'MAX30105 was not found' in line:
raise SensorError("MAX30105 sensor not found. Please check the connection.")
elif 'Gas sensor not found' in line:
raise SensorError("Gas sensor not found. Please check the connection")
elif '=' in line:
label, value_str = line.split('=')
reading = float(value_str)
last_valid_reading = reading # Append the reading to the list
else:
print("Error in reading") # Debug: Indicate if '=' is missing
except SensorError as e:
return str(e)
except (ValueError, IndexError) as e:
print(f"Error processing line: {line} ({e})") # Debug: Print errors in processing
continue
#if reading crosses threshold, send notification
if last_valid_reading is not None:
if command == 'T' and (last_valid_reading > TEMP_THRESHOLD_HIGH or average < TEMP_THRESHOLD_LOW):
send_ifttt_notification(f"Temperature alert: {last_valid_reading:.2f} °C")
elif command == 'P' and last_valid_reading > HEART_RATE_THRESHOLD_HIGH:
send_ifttt_notification(f"Heart rate alert: {last_valid_reading:.2f} BPM")
elif command == 'G' and last_valid_reading > GAS_THRESHOLD:
send_ifttt_notification(f"Gas level alert: {last_valid_reading:.2f}")
return last_valid_reading
else:
return None
#Main window for GUI
class HealthMonitorGUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Health Monitoring System")
self.setGeometry(100, 100, 400, 300)

self.label_pulse = QLabel("Average Pulse: N/A", self)
self.label_temp = QLabel("Average Body Temperature: N/A", self)
self.label_gas = QLabel("Average Gas Level: N/A", self)

self.button_pulse = QPushButton("Measure Pulse", self)
self.button_temp = QPushButton("Measure Body Temperature", self)
self.button_gas = QPushButton("Measure Gas Levels", self)
self.button_finish = QPushButton("Finish", self)

self.button_pulse.clicked.connect(self.measure_pulse)
self.button_temp.clicked.connect(self.measure_temp)
self.button_gas.clicked.connect(self.measure_gas)
self.button_finish.clicked.connect(self.finish)

layout = QVBoxLayout()
layout.addWidget(self.label_pulse)
layout.addWidget(self.button_pulse)
layout.addWidget(self.label_temp)
layout.addWidget(self.button_temp)
layout.addWidget(self.label_gas)
layout.addWidget(self.button_gas)
layout.addWidget(self.button_finish)

container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)

self.pulse_reading_done = False
self.temp_reading_done = False
self.gas_reading_done = False

#Set threading to ensure proper running of each method of the sensors
def measure_pulse(self):
threading.Thread(target=self._measure_pulse).start()
#Start measuring pulse
def measure_pulse(self):
pulse_avg = read_sensor_data('P', duration=30) #measure for 30 seconds
if isinstance(pulse_avg, str): #If issues with system, show error
self.show_error(pulse_avg, self.measure_temp)
elif pulse_avg is not None and pulse_avg != 0:
self.label_pulse.setText(f"Average Pulse: {pulse_avg:.2f} BPM")
self.pulse_reading_done = True
else:
self.show_error("Pulse data could not be collected. Do you want to retry?")
self.parent().sensor_data['pulse'] = pulse_avg

def measure_temp(self):
threading.Thread(target=self._measure_temp).start()
#Measure temperature
def measure_temp(self):
temp_avg = read_sensor_data('T')
if isinstance(temp_avg, str):
self.show_error(temp_avg, self.measure_temp)
elif temp_avg is not None and temp_avg != 0:
self.label_temp.setText(f"Average Body Temperature: {temp_avg:.2f} °C")
self.temp_reading_done = True
else:
self.label_temp.setText("Failed to read temperature data.")
self.parent().sensor_data['temperature'] = temp_avg

def measure_gas(self):
threading.Thread(target=self._measure_gas).start()
#Measure gas
def measure_gas(self):
gas_avg = read_sensor_data('G')
if isinstance(gas_avg, str):
self.show_error(gas_avg, self.measure_temp)
if gas_avg is not None and gas_avg != 0:
#gas_avg = round(gas_avg, 2)
self.label_gas.setText(f"Average Gas Level: {gas_avg:.2f}")
self.gas_reading_done = True
else:
self.label_gas.setText("Failed to read gas data.")
self.parent().sensor_data['gas'] = gas_avg
#Method to show error box
def show_error(self, message, retry_function):
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setText(message)
msg.setWindowTitle("Error")
msg.setStandardButtons(QMessageBox.Retry | QMessageBox.Close)
ret = msg.exec_()
if ret == QMessageBox.Retry: #Prompt user if they want to retry or quit
retry_function()
elif ret == QMessageBox.Close:
QApplication.quit()

#Finish to go to next page
def finish(self):
if self.pulse_reading_done and self.temp_reading_done and self.gas_reading_done:
self.parent().display_summary()
else: #If all sensors are not read, show warning
QMessageBox.warning(self, "Incomplete Readings", "Please complete all readings before finishing.")

Running the System

Now that everything is programmed and complete, we can run it by running the Arduino code, then running the Python one. Clicking on each button to read from the sensors should result to the readings should be displayed in the terminal and the final reading on the GUI, and everything should work as expected. We can exit out of the Arduino IDE as the program is now stored in the Arduino until it gets overridden.


This is part of an assignment submitted to Deakin University, School of IT, Unit SIT210/730 - Embedded Systems Development