/* 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
*/
/* Theme: Heathcare - Modernize a heart-rate monitor.
An analog “heart rate” input is tracked; a nurse’s alert button
or remote web toggle triggers an emergency intervention mode.*/
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <uri/UriBraces.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <freertos/semphr.h>
#include "freertos/queue.h"
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
// Defining the WiFi channel speeds up the connection:
#define WIFI_CHANNEL 6
#define MAX_COUNT_SEM 5
WebServer server(80);
//define pins for LEDs, sensor and button
const int REDLED = 26;
const int GREENLED = 27;
const int BUTTON = 18;
const int SENSOR = 34;
const int WHITELED = 13;
TaskHandle_t heartbeat = NULL;
TaskHandle_t pressure = NULL;
TaskHandle_t emergency = NULL;
TaskHandle_t eventresponse = NULL;
// Declare semaphore and queue handles
SemaphoreHandle_t emergencySemaphore; // binary sem
SemaphoreHandle_t alertSemaphore; // counting sem
SemaphoreHandle_t mutex;
//QueueHandle_t dataQueue;
//int lastHeartrate = 0; // for when queue empty
bool redLedState = false;
bool greenLedState = false;
bool emergencyMode = false;
bool whiteLedState = false;
void sendHtml() {
String response = R"(
<!DOCTYPE html><html>
<head>
<title>MediConnect</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; }
h1 { margin-bottom: 1.2em; }
h2 { margin: 0; }
div { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: auto auto; grid-auto-flow: column; grid-gap: 1em; }
.btn { background-color: #5B5; border: none; color: #fff; padding: 0.5em 1em;
font-size: 2em; text-decoration: none }
.btn.OFF { background-color: #333; }
</style>
</head>
<body>
<h1>MediConnect</h1>
<div>
<h2>CRITICAL EMERGENCY</h2>
<a href="/toggle/1" class="btn REDLED_TEXT">REDLED_TEXT</a>
<h2>Patient Stable</h2>
<a href="/toggle/2" class="btn WHITELED_TEXT">WHITELED_TEXT</a>
</div>
</body>
</html>
)";
//displays the queue on the webpage
//response.replace("HR_VALUE", String(lastHeartrate));
response.replace("REDLED_TEXT", redLedState ? "ALERTED" : "OFF");
response.replace("WHITELED_TEXT", whiteLedState ? "STABLE" : "OFF");
server.send(200, "text/html", response);
}
// blinks green LED @1HZ to indicate system is running
void Heartbeat(void *arg){
while(1){
digitalWrite(GREENLED, HIGH);
vTaskDelay(1000/ portTICK_RATE_MS);
digitalWrite(GREENLED, LOW);
vTaskDelay(1000/ portTICK_RATE_MS);
}
}
//used AI to aid with converting application 4 code to arduino
//blood pressure sensor monitor task
void Pressure (void *arg){
const int Threshold = 180; // high heart rate warning
int sensorVal = 0;
bool alertGiven = false; //helps detect threshold crossing
while(1){
sensorVal = analogRead(SENSOR);
/*if(dataQueue){
//sends heartrate reading to queue
xQueueOverwrite(dataQueue, &sensorVal);
}*/
//detects threshold crossing
if(sensorVal > Threshold && !alertGiven){
xSemaphoreGive(alertSemaphore);
alertGiven = true;
}
if(sensorVal <= Threshold){
alertGiven = false;
}
vTaskDelay(pdMS_TO_TICKS(17)); //17ms delay
}
}
//polls button presses and includes debouncing
void Emergency(void *arg){
const TickType_t debounce = pdMS_TO_TICKS(50);
bool prevButtonState = HIGH;
bool curButtonState;
while(1){
curButtonState = digitalRead(BUTTON);
if(prevButtonState == HIGH && curButtonState == LOW){
vTaskDelay(debounce);
//button pressed
if(digitalRead(BUTTON) == LOW){
xSemaphoreGive(emergencySemaphore);
}
}
prevButtonState = curButtonState;
vTaskDelay(pdMS_TO_TICKS(20));
}
}
//waits on a semaphore to perform an action
void EventResponse(void *arg){
while(1){
//sensor alert event
if(xSemaphoreTake(alertSemaphore, pdMS_TO_TICKS(100)) == pdTRUE){
//mutex to protect console
xSemaphoreTake(mutex, portMAX_DELAY);
Serial.println("**ALERT** High Heart Rate Detected!");
xSemaphoreGive(mutex);
for(int i = 0; i < 15; i++){
digitalWrite(REDLED, HIGH);
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(REDLED, LOW);
vTaskDelay(pdMS_TO_TICKS(200));
}
}
//button press event
if(xSemaphoreTake(emergencySemaphore, pdMS_TO_TICKS(100)) == pdTRUE){
emergencyMode = !emergencyMode;
xSemaphoreTake(mutex, portMAX_DELAY);
Serial.printf("EMERGENCY MODE %s!\n", emergencyMode ? "ACTIVATED" : "DEACTIVATED");
xSemaphoreGive(mutex);
for(int i = 0; i < 8; i++){
digitalWrite(REDLED, HIGH);
vTaskDelay(pdMS_TO_TICKS(90));
digitalWrite(REDLED, LOW);
vTaskDelay(pdMS_TO_TICKS(90));
}
}
}
}
void setup(void) {
Serial.begin(115200);
pinMode(REDLED, OUTPUT);
pinMode(GREENLED, OUTPUT);
pinMode(WHITELED, OUTPUT);
pinMode(SENSOR, INPUT);
pinMode(BUTTON, INPUT_PULLUP);
// Create the semaphores
emergencySemaphore = xSemaphoreCreateBinary();
//if (emergencySemaphore)
xSemaphoreTake(emergencySemaphore, 0);
alertSemaphore = xSemaphoreCreateCounting(MAX_COUNT_SEM, 0);
mutex = xSemaphoreCreateMutex();
// Create a queue capable of containing 10 integers
/* dataQueue = xQueueCreate(10, sizeof(int));
if(dataQueue == NULL){
Serial.println("Failed to create queue!");
}*/
//used to answer questions
/* Serial.print("Free Heap: ");
Serial.println(xPortGetFreeHeapSize());*/
WiFi.begin(WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL);
Serial.print("Connecting to WiFi ");
Serial.print(WIFI_SSID);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
Serial.println(" Connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.on("/", sendHtml);
server.on(UriBraces("/toggle/{}"), []() {
String led = server.pathArg(0);
switch (led.toInt()) {
case 1:
if(!redLedState){
xSemaphoreTake(mutex, portMAX_DELAY);
Serial.print("CRITICAL EMERGENCY\n");
xSemaphoreGive(mutex);
}
redLedState = !redLedState;
digitalWrite(REDLED, redLedState);
break;
case 2:
if(!whiteLedState){
xSemaphoreTake(mutex, portMAX_DELAY);
Serial.print("Patient Stable\n");
xSemaphoreGive(mutex);
}
whiteLedState = !whiteLedState;
digitalWrite(WHITELED, whiteLedState);
break;
}
sendHtml();
});
server.begin();
//used to answer questions
/* Serial.print("Free Heap: ");
Serial.println(xPortGetFreeHeapSize());*/
xSemaphoreTake(mutex, portMAX_DELAY);
Serial.println("HTTP server started");
xSemaphoreGive(mutex);
xTaskCreatePinnedToCore(Heartbeat, "Heartbeat", 2048, NULL, 1, &heartbeat, 1);
xTaskCreatePinnedToCore(Pressure, "Pressure", 4096, NULL, 5, &pressure, 1);
xTaskCreatePinnedToCore(Emergency, "Emergency", 4096, NULL, 7, &emergency, 1);
xTaskCreatePinnedToCore(EventResponse, "EventResponse", 4096, NULL, 3, &eventresponse, 1);
}
void loop(void) {
server.handleClient();
delay(2);
}