/* 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 <freertos/semphr.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
// Defining the WiFi channel speeds up the connection:
#define WIFI_CHANNEL 6
#define LED_STATUS 26 // Green: system alive indicator
#define LED_ALERT 27 // Red: critical vital sign alert
#define NURSE_BUTTON 18 // Manual nurse call button
#define HEART_RATE_PIN 34 // GPIO34: simulated heart rate sensor (ADC1_CH6)
// Maximum number of unhandled critical heart rate events before the
// counting semaphore saturates. Sized for 30s at 100ms polling = 300 events.
#define MAX_COUNT_SEM 300
#define CRITICAL_HEART_RATE_THRESHOLD 3000
SemaphoreHandle_t sem_nurse_call; // Binary semaphore: nurse button press signal
SemaphoreHandle_t sem_heart_rate; // Counting semaphore: queued critical heart rate events
SemaphoreHandle_t console_mutex; // Mutex: protects shared UART console output
WebServer server(80);
const int LED1 = 26;
const int LED2 = 27;
bool led1State = false;
bool led2State = false;
void status_indicator_task(void *pvParameters) {
bool led_status = false;
TickType_t xLastWakeTime = xTaskGetTickCount();
TickType_t period_LastWakeTime = xTaskGetTickCount();
while (1) {
if (!led1State) {
digitalWrite(LED_STATUS, led_status);
led_status = !led_status;
} else {
digitalWrite(LED_STATUS, true);
}
TickType_t xNow = xTaskGetTickCount();
xSemaphoreTake(console_mutex, portMAX_DELAY);
Serial.printf("Monitor online — status period: %lu ms\n",
(xNow - period_LastWakeTime) * portTICK_PERIOD_MS);
xSemaphoreGive(console_mutex);
period_LastWakeTime = xNow;
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
}
}
void heart_rate_sensor_task(void *pvParameters) {
bool was_above_threshold = false;
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1) {
int raw_bpm = analogRead(HEART_RATE_PIN);
if (xSemaphoreTake(console_mutex, portMAX_DELAY) == pdTRUE) {
Serial.printf("Heart rate sensor reading: %d\n", raw_bpm);
xSemaphoreGive(console_mutex);
}
bool is_critical = raw_bpm > CRITICAL_HEART_RATE_THRESHOLD;
if (is_critical && !was_above_threshold) {
if (xSemaphoreTake(console_mutex, portMAX_DELAY) == pdTRUE) {
Serial.println("ALERT: Heart rate exceeded critical threshold!");
xSemaphoreGive(console_mutex);
}
xSemaphoreGive(sem_heart_rate);
}
was_above_threshold = is_critical;
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(100));
}
}
void nurse_call_task(void *pvParameters) {
while (1) {
int state = digitalRead(NURSE_BUTTON);
if (state == 0) {
vTaskDelay(pdMS_TO_TICKS(50));
if (digitalRead(NURSE_BUTTON) == 0) {
xSemaphoreGive(sem_nurse_call);
xSemaphoreTake(console_mutex, portMAX_DELAY);
Serial.println("Nurse call button pressed — manual alert triggered.");
xSemaphoreGive(console_mutex);
}
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void event_handler_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(sem_heart_rate, 0)) {
xSemaphoreTake(console_mutex, portMAX_DELAY);
Serial.println("!!! Critical heart rate event — patient requires attention!");
xSemaphoreGive(console_mutex);
digitalWrite(LED_ALERT, HIGH);
vTaskDelay(pdMS_TO_TICKS(300));
digitalWrite(LED_ALERT, LOW);
}
if (xSemaphoreTake(sem_nurse_call, 0)) {
xSemaphoreTake(console_mutex, portMAX_DELAY);
Serial.println("!!! Nurse call received — dispatching staff to patient room.");
xSemaphoreGive(console_mutex);
digitalWrite(LED_ALERT, HIGH);
vTaskDelay(pdMS_TO_TICKS(300));
digitalWrite(LED_ALERT, LOW);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void server_task(void *pvParameters) {
while (true) {
server.handleClient();
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void sendHtml() {
String response = R"(
<!DOCTYPE html><html>
<head>
<title>ESP32 Web Server Demo</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>ESP32 Web Server</h1>
<div>
<h2>LED 1</h2>
<a href="/toggle/1" class="btn LED1_TEXT">LED1_TEXT</a>
<h2>LED 2</h2>
<a href="/toggle/2" class="btn LED2_TEXT">LED2_TEXT</a>
</div>
</body>
</html>
)";
response.replace("LED1_TEXT", led1State ? "ON" : "OFF");
response.replace("LED2_TEXT", led2State ? "ON" : "OFF");
server.send(200, "text/html", response);
}
void setup(void) {
Serial.begin(115200);
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
pinMode(NURSE_BUTTON, INPUT_PULLUP);
analogReadResolution(12);
analogSetPinAttenuation(HEART_RATE_PIN, ADC_11db);
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());
// Create sync primitives
sem_nurse_call = xSemaphoreCreateBinary();
sem_heart_rate = xSemaphoreCreateCounting(MAX_COUNT_SEM, 0);
console_mutex = xSemaphoreCreateMutex();
server.on("/", sendHtml);
server.on(UriBraces("/toggle/{}"), []() {
String led = server.pathArg(0);
Serial.print("Toggle LED #");
Serial.println(led);
switch (led.toInt()) {
case 1:
led1State = !led1State;
digitalWrite(LED1, led1State);
break;
case 2:
xSemaphoreGive(sem_nurse_call);
xSemaphoreTake(console_mutex, portMAX_DELAY);
Serial.println("Nurse call button pressed remotely via web interface — manual alert triggered.");
xSemaphoreGive(console_mutex);
break;
}
sendHtml();
});
server.begin();
Serial.println("HTTP server started");
xTaskCreatePinnedToCore(status_indicator_task, "status", 2048, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(heart_rate_sensor_task, "sensor", 2048, NULL, 4, NULL, 1);
xTaskCreatePinnedToCore(nurse_call_task, "nurse_button", 2048, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(event_handler_task, "event_handler", 2048, NULL, 2, NULL, 1);
xTaskCreatePinnedToCore(server_task, "sever_handler", 4096, NULL, 2, NULL, 0);
}
void loop(void) {
}