/* ESP32 HTTP IoT Server Example for Wokwi.com
https://wokwi.com/projects/320964045035274834
To test, you need the Wokwi IoT Gateway, as explained here:
https://docs.wokwi.com/guides/esp32-wifi#the-private-gateway
Then start the simulation, and open http://localhost:9080
in another browser tab.
Note that the IoT Gateway requires a Wokwi Club subscription.
To purchase a Wokwi Club subscription, go to https://wokwi.com/club
*/
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <uri/UriBraces.h>
#include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/adc.h"
// Healthcare Theme Configuration
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
#define WIFI_CHANNEL 6
#define GREEN_LED GPIO_NUM_5 // System status LED
#define RED_LED GPIO_NUM_4 // Alert LED
#define BUTTON_PIN GPIO_NUM_18 // Nurse call button
#define POT_ADC_CHANNEL ADC1_CHANNEL_6 // Heart rate sensor
#define HEARTBEAT_LOWER_THRESHOLD 60
#define HEARTBEAT_UPPER_THRESHOLD 100
// FreeRTOS synchronization
SemaphoreHandle_t sem_button;
SemaphoreHandle_t sem_sensor;
SemaphoreHandle_t print_mutex;
SemaphoreHandle_t led_mutex;
WebServer server(80);
// Shared state variables
volatile bool green_led_state = false;
volatile bool red_led_state = false;
volatile bool system_alert_mode = false;
volatile int current_heart_rate = 0;
// Task handles for priority control
TaskHandle_t sensor_task_handle = NULL;
TaskHandle_t button_task_handle = NULL;
TaskHandle_t event_task_handle = NULL;
// Heartbeat task - indicates system is running
void heartbeat_task(void *pvParameters) {
while (1) {
xSemaphoreTake(led_mutex, portMAX_DELAY);
green_led_state = !green_led_state;
digitalWrite(GREEN_LED, green_led_state);
xSemaphoreGive(led_mutex);
vTaskDelay(pdMS_TO_TICKS(500)); // 1Hz blink (500ms on/off)
}
}
// Heart rate monitoring task
void sensor_task(void *pvParameters) {
int last_value = 0;
bool threshold_crossed = false;
while (1) {
int raw_value = adc1_get_raw(POT_ADC_CHANNEL);
current_heart_rate = raw_value / 21; // Scale to 0-195 bpm
// Rising edge detection for threshold crossing
bool current_threshold = (current_heart_rate > HEARTBEAT_UPPER_THRESHOLD ||
current_heart_rate < HEARTBEAT_LOWER_THRESHOLD);
if (current_threshold && !threshold_crossed) {
xSemaphoreGive(sem_sensor);
if (xSemaphoreTake(print_mutex, pdMS_TO_TICKS(100))) {
Serial.print("ALERT: Abnormal heart rate detected: ");
Serial.print(current_heart_rate);
Serial.println(" bpm");
xSemaphoreGive(print_mutex);
}
}
threshold_crossed = current_threshold;
vTaskDelay(pdMS_TO_TICKS(17)); // ~60Hz sampling as required
}
}
// Nurse call button task
void button_task(void *pvParameters) {
uint32_t last_press_time = 0;
const uint32_t debounce_time = 200; // 200ms debounce
while (1) {
if (digitalRead(BUTTON_PIN) == LOW) { // Active-low button
uint32_t now = millis();
if ((now - last_press_time) > debounce_time) {
xSemaphoreGive(sem_button);
last_press_time = now;
if (xSemaphoreTake(print_mutex, pdMS_TO_TICKS(100))) {
Serial.println("NURSE CALL: Button pressed - assistance requested");
xSemaphoreGive(print_mutex);
}
}
}
vTaskDelay(pdMS_TO_TICKS(10)); // 100Hz polling
}
}
// Event handling task
void event_handler_task(void *pvParameters) {
while (1) {
// Handle heart rate alerts
if (xSemaphoreTake(sem_sensor, pdMS_TO_TICKS(50))) {
system_alert_mode = true;
xSemaphoreTake(led_mutex, portMAX_DELAY);
digitalWrite(RED_LED, HIGH);
xSemaphoreGive(led_mutex);
vTaskDelay(pdMS_TO_TICKS(200));
xSemaphoreTake(led_mutex, portMAX_DELAY);
digitalWrite(RED_LED, LOW);
xSemaphoreGive(led_mutex);
vTaskDelay(pdMS_TO_TICKS(200));
}
// Handle nurse call button
if (xSemaphoreTake(sem_button, pdMS_TO_TICKS(50))) {
system_alert_mode = true;
// Triple blink pattern for nurse call
for (int i = 0; i < 3; i++) {
xSemaphoreTake(led_mutex, portMAX_DELAY);
digitalWrite(RED_LED, HIGH);
xSemaphoreGive(led_mutex);
vTaskDelay(pdMS_TO_TICKS(200));
xSemaphoreTake(led_mutex, portMAX_DELAY);
digitalWrite(RED_LED, LOW);
xSemaphoreGive(led_mutex);
vTaskDelay(pdMS_TO_TICKS(200));
}
// Return to normal after handling
system_alert_mode = false;
}
vTaskDelay(pdMS_TO_TICKS(10)); // Yield CPU
}
}
// Web server task
void web_server_task(void *pvParameters) {
while (1) {
server.handleClient();
vTaskDelay(pdMS_TO_TICKS(10)); // Yield CPU
}
}
void sendHtml() {
String statusClass = "normal";
String statusText = "Normal";
if (system_alert_mode) {
statusClass = "critical";
statusText = "CRITICAL ALERT!";
} else if (current_heart_rate > HEARTBEAT_UPPER_THRESHOLD) {
statusClass = "warning";
statusText = "High Heart Rate!";
} else if (current_heart_rate < HEARTBEAT_LOWER_THRESHOLD) {
statusClass = "warning";
statusText = "Low Heart Rate!";
}
String html = "<!DOCTYPE html><html>"
"<head>"
"<title>Patient Monitoring System</title>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
"<style>"
"html { font-family: sans-serif; text-align: center; }"
"body { display: inline-flex; flex-direction: column; margin: 20px; }"
"h1 { color: #2c3e50; }"
".status { "
"font-size: 1.5em; "
"margin: 20px; "
"padding: 20px;"
"border-radius: 10px;"
"box-shadow: 0 4px 8px rgba(0,0,0,0.1);"
"}"
".normal { background-color: #2ecc71; color: white; }"
".warning { background-color: #f39c12; color: white; }"
".critical { background-color: #e74c3c; color: white; animation: pulse 1s infinite; }"
".data { "
"background-color: #ecf0f1; "
"padding: 15px; "
"border-radius: 8px;"
"margin: 10px 0;"
"font-size: 1.2em;"
"}"
"button {"
"background-color: #3498db;"
"color: white;"
"border: none;"
"padding: 15px 30px;"
"font-size: 1.2em;"
"border-radius: 5px;"
"cursor: pointer;"
"margin: 10px;"
"transition: background-color 0.3s;"
"}"
"button:hover { background-color: #2980b9; }"
"@keyframes pulse {"
"0% { opacity: 1; }"
"50% { opacity: 0.7; }"
"100% { opacity: 1; }"
"}"
"</style>"
"</head>"
"<body>"
"<h1>Patient Monitoring System</h1>"
"<div class=\"data\">"
"<strong>Heart Rate:</strong> " + String(current_heart_rate) + " bpm"
"</div>"
"<div class=\"status " + statusClass + "\">"
"<strong>Status:</strong> " + statusText + ""
"</div>"
"<button onclick=\"location.reload()\">Refresh Status</button>"
"<button onclick=\"window.location.href='/nurse'\">Call Nurse</button>"
"</body>"
"</html>";
server.send(200, "text/html", html);
}
// Handle nurse call from web
void handleNurseCall() {
xSemaphoreGive(sem_button); // Simulate physical button press
sendHtml(); // Return to main page
}
void setup() {
Serial.begin(115200);
// Initialize GPIO
pinMode(GREEN_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Initialize ADC
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(POT_ADC_CHANNEL, ADC_ATTEN_DB_11);
// Create synchronization primitives
sem_button = xSemaphoreCreateBinary();
sem_sensor = xSemaphoreCreateCounting(10, 0); // Counting semaphore for up to 10 events
print_mutex = xSemaphoreCreateMutex();
led_mutex = xSemaphoreCreateMutex();
// Connect to WiFi
WiFi.begin(WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
Serial.println("\nConnected to WiFi!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// Configure web server routes
server.on("/", sendHtml);
server.on("/nurse", handleNurseCall);
server.begin();
Serial.println("HTTP server started");
// Create FreeRTOS tasks
xTaskCreate(heartbeat_task, "Heartbeat", 2048, NULL, 1, NULL);
xTaskCreate(sensor_task, "HeartRateSensor", 2048, NULL, 3, &sensor_task_handle);
xTaskCreate(button_task, "NurseButton", 2048, NULL, 4, &button_task_handle);
xTaskCreate(event_handler_task, "EventHandler", 2048, NULL, 5, &event_task_handle);
xTaskCreate(web_server_task, "WebServer", 4096, NULL, 2, NULL);
// Priority experiment (commented out by default)
// Uncomment to test priority effects:
// vTaskPrioritySet(heartbeat_task_handle, 1);
// vTaskPrioritySet(sensor_task_handle, 4);
// vTaskPrioritySet(button_task_handle, 5);
// vTaskPrioritySet(event_task_handle, 3);
}
void loop() {
// Not used - all functionality in FreeRTOS tasks
vTaskDelete(NULL); // Free up the loop task
}