/*
Application 5
Caden Kohlmeier
Due: 7/20/25
AI Use: AI was used to documentation, coding correction, generation of pin definitions, as well as clarifying comments and application 4 was compared with the web based server example provided by wokwi to create a document that fits the criteria. AI also used to proofread my analysis questions and format into markdown
*/
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <uri/UriBraces.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"
// WiFi Configuration
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
#define WIFI_CHANNEL 6
// Pin Definitions - Fixed with proper GPIO types
#define LED_GREEN (gpio_num_t)27
#define LED_RED (gpio_num_t)26
#define BUTTON_PIN GPIO_NUM_18
#define POT_ADC_CHANNEL ADC1_CHANNEL_6
// Web Server LEDs
#define LED1 26
#define LED2 27
// Constants
#define AVG_WINDOW 10
#define SENSOR_THRESHOLD 2048
#define MAX_COUNT_SEM 10
// Global Variables
WebServer server(80);
bool led1State = false;
bool led2State = false;
// FreeRTOS Synchronization Primitives
SemaphoreHandle_t sem_button;
SemaphoreHandle_t sem_HB_sensor;
SemaphoreHandle_t print_mutex;
int SEMCNT = 0;
// Web Server HTML Response
void sendHtml() {
String response = R"(
<!DOCTYPE html><html>
<head>
<title>ESP32 Heart Monitor & Web Server</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; color: #333; }
h2 { margin: 0; }
.controls { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: auto auto; grid-auto-flow: column; grid-gap: 1em; margin: 20px; }
.status { background-color: #f0f0f0; padding: 20px; margin: 20px; border-radius: 10px; }
.btn { background-color: #5B5; border: none; color: #fff; padding: 0.5em 1em;
font-size: 2em; text-decoration: none; border-radius: 5px; }
.btn.OFF { background-color: #333; }
.btn:hover { opacity: 0.8; }
</style>
</head>
<body>
<h1>ESP32 Heart Monitor & Control Panel</h1>
<div class="status">
<h3>System Status</h3>
<p>Heart Rate Monitor: Active</p>
<p>Emergency Button: Armed</p>
<p>System Heartbeat: Running</p>
</div>
<div class="controls">
<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);
}
//Task 1: Blink Green LED to show system ON - Priority 1
void heartbeat_task(void *pvParameters) {
bool green_led_state = false;
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1) {
gpio_set_level(LED_GREEN, green_led_state);
green_led_state = !green_led_state;
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
}
}
//Task 2: Print Raw Sensor Value - Priority 2
void sensor_task(void *pvParameters) {
int pot_readings[AVG_WINDOW] = {0};
int idx = 0;
int sum = 0;
static bool above_thresh = false;
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1) {
int val = adc1_get_raw(POT_ADC_CHANNEL);
//static variable retained through program run
sum -= pot_readings[idx];
pot_readings[idx] = val;
sum += val;
idx = (idx + 1) % AVG_WINDOW;
int avg = sum / AVG_WINDOW;
if (xSemaphoreTake(print_mutex, portMAX_DELAY)) {
printf("Raw Value: %d\n", val);
xSemaphoreGive(print_mutex);
}
if (val > SENSOR_THRESHOLD && !above_thresh) { //if raw value is above sensorthresh and above_thresh is not false (true)
above_thresh = true; //then above threshold is true
if (SEMCNT < MAX_COUNT_SEM + 1) SEMCNT++; // DO NOT REMOVE THIS LINE... SEMCT: global counter that tracks # of given, MAX_COUNT_SEM: max capacity of 10 and prevent semaphore overflow
xSemaphoreGive(sem_HB_sensor); // Signal
} else if (val <= SENSOR_THRESHOLD) {
above_thresh = false;
}
if (avg >= SENSOR_THRESHOLD) {
if (xSemaphoreTake(print_mutex, portMAX_DELAY)) {
printf("ALERT: Current HB Average of %d exceeds a Threshold Value of %d\n", avg, SENSOR_THRESHOLD);
xSemaphoreGive(print_mutex);
}
}
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(100)); //vTaskDelay(pdMS_TO_TICKS(1)); will dramatically starve the green LED
}
}
//Task 3: Button Task That signals when a button is pressed and a debounce is executed (100ms)
void button_task(void *pvParameters) {
static TickType_t prev_button_press = 0;
static bool last_button_state = true; //Generated by Claude for a true single button press design; track previous state
while (1) {
int state = gpio_get_level(BUTTON_PIN);
if (state == 0 && last_button_state == true) { //if (state == 0){
TickType_t now = xTaskGetTickCount();
if ((now - prev_button_press) > pdMS_TO_TICKS(100)) {
prev_button_press = now;
xSemaphoreGive(sem_button); //signal event
}
}
last_button_state = state; //generated by Claude for true single button press; update state tracker
vTaskDelay(pdMS_TO_TICKS(10)); // Do Not Modify This Delay!
}
}
//Task 4: Event Handler Task - Priority 3
void event_handler_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(sem_HB_sensor, 0)) {
SEMCNT--; // DO NOT MODIFY THIS LINE
xSemaphoreTake(print_mutex, portMAX_DELAY);
printf("Sensor event: Threshold Rate Has Been Exceeded!\n");
xSemaphoreGive(print_mutex);
gpio_set_level(LED_RED, 1);
vTaskDelay(pdMS_TO_TICKS(17));
gpio_set_level(LED_RED, 0);
}
if (xSemaphoreTake(sem_button, 0)) {
xSemaphoreTake(print_mutex, portMAX_DELAY);
printf("Emergency Alert Button Activated-Report To Patient Immediately\n");
xSemaphoreGive(print_mutex);
gpio_set_level(LED_RED, 1);
vTaskDelay(pdMS_TO_TICKS(300)); //vTaskDelay(pdMS_TO_TICKS(3000)); //increase the delay to 3s to miss button presses
gpio_set_level(LED_RED, 0);
}
vTaskDelay(pdMS_TO_TICKS(10)); // Idle delay to yield CPU
}
}
//Task 5: WiFi Web Server Task - Priority 5
void webserver_task(void *pvParameters) {
while (1) {
server.handleClient();
vTaskDelay(pdMS_TO_TICKS(2)); // Small delay to yield CPU
}
}
void setup() {
// Initialize Serial
Serial.begin(115200);
// Configure heartbeat output LEDs - FIXED VERSION
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_RED, OUTPUT);
// Configure web server LEDs
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
// Configure input button - FIXED VERSION
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Configure ADC
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(POT_ADC_CHANNEL, ADC_ATTEN_DB_11);
// Initialize WiFi
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());
// Setup web server routes
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:
led2State = !led2State;
digitalWrite(LED2, led2State);
break;
}
sendHtml();
});
server.begin();
Serial.println("HTTP server started");
//Sync primitives
sem_button = xSemaphoreCreateBinary();
sem_HB_sensor = xSemaphoreCreateCounting(MAX_COUNT_SEM, 0);
print_mutex = xSemaphoreCreateMutex();
// Create tasks
xTaskCreate(heartbeat_task, "heartbeat", 2048, NULL, 1, NULL);
xTaskCreate(sensor_task, "sensor", 2048, NULL, 2, NULL);
xTaskCreate(event_handler_task, "event_handler", 2048, NULL, 3, NULL);
xTaskCreate(button_task, "button", 2048, NULL, 4, NULL);
xTaskCreate(webserver_task, "webserver", 4096, NULL, 5, NULL);
}
void loop() {
server.handleClient();
delay(2);
}