/* --------------------------------------------------------------
Assignment: Appliaction #5
Class: EEE 4775 Real-Time System
Author: Minh Hoang Nguyen Ho
UCF ID: 5182391
Email: [email protected]
School: University Of Central Florida
Environment: Healthcare
Website Link: http://localhost:9080
AI Use: I Used AI To Help Create The HTML Code For The
Website As Well As To Help Determine How To Implement
FreeRTOS In The Arduino Frame Work Alongside The Web Page
---------------------------------------------------------------*/
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
#define WIFI_CHANNEL 6
WebServer server(80);
const int REDLED = 26;
const int GREENLED = 27;
const int BLUELED = 4;
const int BUTTON = 18;
const int POT = 34;
volatile bool alertState = false;
volatile bool emergencyMode = false;
volatile bool statusEnabled = true;
volatile int latestHeartRate = 0;
SemaphoreHandle_t binarySemaphore;
SemaphoreHandle_t countingSemaphore;
SemaphoreHandle_t serialMutex;
const int SENSOR_THRESHOLD = 3000;
// Prints Messages To The Serial Monitor Using A Mutex
void printMessage(const String &msg) {
if (xSemaphoreTake(serialMutex, portMAX_DELAY) == pdTRUE) {
Serial.println(msg);
xSemaphoreGive(serialMutex);
}
}
// Makes HTML Page
void sendHtml() {
String response = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<title>Healthcare Monitor</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="refresh" content="2">
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin: 20px;
background: #f3f4f6;
color: #1f2937;
}
h1 {
color: #1d4ed8;
margin-bottom: 20px;
}
.card {
background: white;
border-radius: 14px;
padding: 16px;
margin: 14px auto;
max-width: 440px;
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
.status {
font-size: 22px;
font-weight: bold;
margin: 10px 0;
}
.normal {
color: #15803d;
}
.critical {
color: #dc2626;
}
.modeOn {
color: #2563eb;
}
.modeOff {
color: #6b7280;
}
.btn {
display: inline-block;
margin: 8px;
padding: 12px 18px;
font-size: 17px;
font-weight: bold;
text-decoration: none;
color: white;
border-radius: 10px;
background: #2563eb;
}
.btn.green {
background: #059669;
}
</style>
</head>
<body>
<h1>Healthcare Monitor</h1>
<div class="card">
<p class="status">Heart Rate: HEART_TEXT BPM</p>
</div>
<div class="card">
<p class="status ALERT_CLASS">Patient State: ALERT_TEXT</p>
<p class="status MODE_CLASS">Emergency Mode: MODE_TEXT</p>
<p class="status">System Status: STATUS_TEXT</p>
</div>
<div class="card">
<a class="btn" href="/toggleEmergency">Change Emergency Mode</a>
<a class="btn green" href="/toggleStatus">Toggle System Status</a>
</div>
</body>
</html>
)rawliteral";
response.replace("HEART_TEXT", String(latestHeartRate));
response.replace("ALERT_TEXT", alertState ? "CRITICAL CONDITION" : "NORMAL CONDITION");
response.replace("MODE_TEXT", emergencyMode ? "ON" : "OFF");
response.replace("STATUS_TEXT", statusEnabled ? "ACTIVE" : "DISABLED");
response.replace("ALERT_CLASS", alertState ? "critical" : "normal");
response.replace("MODE_CLASS", emergencyMode ? "modeOn" : "modeOff");
server.send(200, "text/html", response);
}
// Keeps The Web Server Running
void WebServerTask(void *pvParameters) {
while (true) {
server.handleClient();
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// Continuously Reads The Sensor And Updates The Heart Rate And Alert State
void SensorMonitorTask(void *pvParameters) {
bool lastAboveThreshold = false;
while (true) {
// Obtain ADC Reading And Converts
int currentSensorValue = analogRead(POT);
int heartRate = (currentSensorValue * 180) / 4095;
latestHeartRate = heartRate;
// Update Current Status
bool currentAboveThreshold = (currentSensorValue > SENSOR_THRESHOLD);
alertState = currentAboveThreshold;
// Gives The Counting Semaphore When The Sensor Crosses The Threshold
if (currentAboveThreshold && !lastAboveThreshold) {
xSemaphoreGive(countingSemaphore);
}
// Prints A Message When The Condition Returns To Normal
if (!currentAboveThreshold && lastAboveThreshold) {
printMessage("NORMAL CONDITION");
}
// Prints The Current Heart Rate Reading
printMessage(String("Heart Rate: ") + heartRate);
lastAboveThreshold = currentAboveThreshold;
// Waits
vTaskDelay(pdMS_TO_TICKS(17));
}
}
// Monitors The Button
void ButtonWatchTask(void *pvParameters) {
bool buttonHandled = false;
while (true) {
int currentButtonState = digitalRead(BUTTON);
// Checks If The Button Was Pressed
if (currentButtonState == LOW && !buttonHandled) {
vTaskDelay(pdMS_TO_TICKS(50));
if (digitalRead(BUTTON) == LOW) {
xSemaphoreGive(binarySemaphore);
buttonHandled = true;
// Waits Until The Button Is Released
while (digitalRead(BUTTON) == LOW) {
vTaskDelay(pdMS_TO_TICKS(10));
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
// Resets The Button State
if (currentButtonState == HIGH) {
buttonHandled = false;
}
// Waits
vTaskDelay(pdMS_TO_TICKS(20));
}
}
// Handles The Two Events
void EventResponseTask(void *pvParameters) {
bool redLedState = false;
TickType_t lastBlinkTime = 0;
while (true) {
// Toggles Emergency Mode
if (xSemaphoreTake(binarySemaphore, 0) == pdTRUE) {
emergencyMode = !emergencyMode;
digitalWrite(BLUELED, emergencyMode ? HIGH : LOW);
printMessage(String("Emergency Mode ") + (emergencyMode ? "ON" : "OFF"));
}
// Prints A Critical Condition Message
while (xSemaphoreTake(countingSemaphore, 0) == pdTRUE) {
printMessage("CRITICAL CONDITION");
}
// Blinks The Red LED While The Alert State Is Active
if (alertState) {
if ((xTaskGetTickCount() - lastBlinkTime) >= pdMS_TO_TICKS(300)) {
redLedState = !redLedState;
digitalWrite(REDLED, redLedState ? HIGH : LOW);
lastBlinkTime = xTaskGetTickCount();
}
} else {
// Turns The Red LED Off When The Alert State Is Not Active
redLedState = false;
digitalWrite(REDLED, LOW);
}
vTaskDelay(pdMS_TO_TICKS(20));
}
}
// Controls The Green Light
void HeartbeatTask(void *pvParameters) {
while (true) {
if (statusEnabled) {
digitalWrite(GREENLED, HIGH);
vTaskDelay(pdMS_TO_TICKS(1000));
digitalWrite(GREENLED, LOW);
vTaskDelay(pdMS_TO_TICKS(1000));
} else {
// Keeps The Green Light Off When System Status Is Disabled
digitalWrite(GREENLED, LOW);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
}
void setup() {
Serial.begin(115200);
pinMode(REDLED, OUTPUT);
pinMode(GREENLED, OUTPUT);
pinMode(BLUELED, OUTPUT);
pinMode(BUTTON, INPUT_PULLUP);
pinMode(POT, INPUT);
digitalWrite(REDLED, LOW);
digitalWrite(GREENLED, LOW);
digitalWrite(BLUELED, LOW);
// Creates The Binary Semaphore Counting Semaphore And Mutex
binarySemaphore = xSemaphoreCreateBinary();
countingSemaphore = xSemaphoreCreateCounting(10, 0);
serialMutex = xSemaphoreCreateMutex();
WiFi.begin(WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL);
Serial.print("Connecting To WiFi ");
Serial.print(WIFI_SSID);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
Serial.println(" Connected!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
server.on("/", sendHtml);
// Toggles Emergency Mode From The Website
server.on("/toggleEmergency", []() {
xSemaphoreGive(binarySemaphore);
server.sendHeader("Location", "/");
server.send(303);
});
// Toggles The System Status From The Website
server.on("/toggleStatus", []() {
statusEnabled = !statusEnabled;
server.sendHeader("Location", "/");
server.send(303);
});
server.begin();
Serial.println("HTTP Server Started");
// Creates Tasks
xTaskCreatePinnedToCore(WebServerTask, "WebServerTask", 4096, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(SensorMonitorTask, "SensorMonitorTask", 4096, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(ButtonWatchTask, "ButtonWatchTask", 4096, NULL, 4, NULL, 1);
xTaskCreatePinnedToCore(EventResponseTask, "EventResponseTask", 4096, NULL, 2, NULL, 1);
xTaskCreatePinnedToCore(HeartbeatTask, "HeartbeatTask", 2048, NULL, 1, NULL, 1);
}
void loop() {
vTaskDelay(portMAX_DELAY);
}