/* --------------------------------------------------------------
Application 5 + Application 4 Merged
Framework: Arduino on ESP32 with FreeRTOS
Notes:
- Application 5 base kept in Arduino
- Application 4 logic ported from ESP-IDF style into Arduino FreeRTOS style
- AI-added sections are clearly marked
COMMENT LABELS:
APP5 BASE = from the Arduino WiFi/webserver project
APP4 PORT = ported from Application 4
NEW MERGE = code added to correctly merge both codes, as well as any additional required code lines
SIMULATION WEBPAGE:
Start the simulation, and open http://localhost:9080
GENERAL AI USE DISCLAIMER:
ChatGPT was used to effectively organize comments and code blocks by using
the comment labels mentioned above. This was done to allow for visibility and
clarity regarding origin of code portions. ChatGPT also asisted with arduino
translation confirmation and similar. Any new code generated or lines where AI was
utilized otherwise in general, will still be marked for AI use. This overall disclaimer
was largely done to ensure a focus on relevant comments, and overall code clarity.
---------------------------------------------------------------*/
#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"
// APP5 BASE
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
#define WIFI_CHANNEL 6
WebServer server(80);
// APP4 PORT
const int LED_GREEN = 5;
const int LED_RED = 4;
const int BUTTON_PIN = 18;
const int SENSOR_PIN = 34;
#define SENSOR_THRESHOLD 3000
#define MAX_COUNT_SEM 300
SemaphoreHandle_t sem_button = NULL;
SemaphoreHandle_t sem_sensor = NULL;
SemaphoreHandle_t print_mutex = NULL;
SemaphoreHandle_t state_mutex = NULL;
volatile int SEMCNT = 0;
// NEW MERGE (Split alert state so manual and sensor alerts can behave differently)
volatile int latestSensorValue = 0;
volatile bool sensorAboveThreshold = false;
volatile bool manualAlertRequested = false;
volatile bool sensorAlertActive = false;
// NEW MERGE
void safePrint(const String &msg) {
if (print_mutex) {
xSemaphoreTake(print_mutex, portMAX_DELAY);
Serial.println(msg);
xSemaphoreGive(print_mutex);
} else {
Serial.println(msg);
}
}
// NEW MERGE (ChatGPT assisted with defining cases & confirming validity)
void setManualAlertState(bool s) {
xSemaphoreTake(state_mutex, portMAX_DELAY);
manualAlertRequested = s;
xSemaphoreGive(state_mutex);
}
void toggleManualAlertState() {
xSemaphoreTake(state_mutex, portMAX_DELAY);
manualAlertRequested = !manualAlertRequested;
xSemaphoreGive(state_mutex);
}
bool getManualAlertState() {
bool v;
xSemaphoreTake(state_mutex, portMAX_DELAY);
v = manualAlertRequested;
xSemaphoreGive(state_mutex);
return v;
}
void setSensorAlertState(bool s) {
xSemaphoreTake(state_mutex, portMAX_DELAY);
sensorAlertActive = s;
xSemaphoreGive(state_mutex);
}
bool getSensorAlertState() {
bool v;
xSemaphoreTake(state_mutex, portMAX_DELAY);
v = sensorAlertActive;
xSemaphoreGive(state_mutex);
return v;
}
bool getOverallAlertState() {
bool v;
xSemaphoreTake(state_mutex, portMAX_DELAY);
v = manualAlertRequested || sensorAlertActive;
xSemaphoreGive(state_mutex);
return v;
}
void updateSensorValue(int v, bool above) {
xSemaphoreTake(state_mutex, portMAX_DELAY);
latestSensorValue = v;
sensorAboveThreshold = above;
xSemaphoreGive(state_mutex);
}
int getSensorValue() {
int v;
xSemaphoreTake(state_mutex, portMAX_DELAY);
v = latestSensorValue;
xSemaphoreGive(state_mutex);
return v;
}
bool getSensorThresholdState() {
bool v;
xSemaphoreTake(state_mutex, portMAX_DELAY);
v = sensorAboveThreshold;
xSemaphoreGive(state_mutex);
return v;
}
// APP5 BASE & NEW MERGE
void sendHtml() {
bool overall = getOverallAlertState();
bool manual = getManualAlertState();
bool sensor = getSensorAlertState();
int sensorVal = getSensorValue();
bool threshold = getSensorThresholdState();
String sourceText = "Alert Source: None";
if (manual && sensor) sourceText = "Alert Source: Manual + Sensor";
else if (manual) sourceText = "Alert Source: Manual";
else if (sensor) sourceText = "Alert Source: Sensor";
// ChatGPT utilized for HTML contents in String response section.
String response = R"(
<!DOCTYPE html>
<html>
<head>
<title>ESP32 Emergency Alert System</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="refresh" content="1">
<style>
html { font-family: sans-serif; text-align: center; }
body { max-width: 700px; margin: 0 auto; padding: 20px; }
.card { border: 1px solid #ccc; border-radius: 12px; padding: 18px; margin: 16px 0; }
.status { font-size: 1.4em; font-weight: bold; margin: 10px 0; }
.normal { color: #1f7a1f; }
.alert { color: #b30000; }
.btn {
display: inline-block; background-color: #b30000; border: none; border-radius: 10px;
color: #fff; padding: 14px 22px; font-size: 1.2em; text-decoration: none; margin-top: 12px;
}
.sensor { font-size: 1.2em; }
.note { margin-top: 10px; font-size: 1.05em; }
</style>
</head>
<body>
<h1>ESP32 Emergency Alert System</h1>
<div class="card">
<div class="sensor">Sensor Value: SENSOR_VALUE</div>
<div class="note">Threshold: SENSOR_THRESHOLD_VALUE</div>
<div class="note">Sensor Condition: SENSOR_CONDITION</div>
</div>
<div class="card">
<div class="status ALERT_CLASS">ALERT_TEXT</div>
<div class="note">WEB_NOTE</div>
<div class="note">ALERT_SOURCE_TEXT</div>
<a href="/toggleAlert" class="btn">TOGGLE_BUTTON_TEXT</a>
</div>
</body>
</html>
)";
response.replace("SENSOR_VALUE", String(sensorVal));
response.replace("SENSOR_THRESHOLD_VALUE", String(SENSOR_THRESHOLD));
response.replace("SENSOR_CONDITION", threshold ? "ABOVE THRESHOLD" : "NORMAL");
response.replace("ALERT_CLASS", overall ? "alert" : "normal");
response.replace("ALERT_TEXT", overall ? "Emergency Alert Requested" : "No Emergency Alert Requested");
response.replace("WEB_NOTE", overall ? "The system is currently in ALERT mode." : "The system is currently in NORMAL mode.");
response.replace("ALERT_SOURCE_TEXT", sourceText);
response.replace("TOGGLE_BUTTON_TEXT", manual ? "Clear Manual Emergency Alert" : "Request Manual Emergency Alert");
server.send(200, "text/html", response);
}
// APP5 BASE & NEW MERGE
void handleToggleAlert() {
safePrint("Web button pressed: emergency alert toggle requested.");
xSemaphoreGive(sem_button);
server.sendHeader("Location", "/", true);
server.send(303, "text/plain", "");
}
// APP4 PORT
void heartbeat_task(void *pvParameters) {
while (1) {
digitalWrite(LED_GREEN, HIGH);
vTaskDelay(pdMS_TO_TICKS(1000));
digitalWrite(LED_GREEN, LOW);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// APP4 PORT & NEW MERGE
// Sensor can assert and clear its own alert automatically
void sensor_task(void *pvParameters) {
int lastAboveThreshold = 0;
while (1) {
int val = analogRead(SENSOR_PIN);
bool above = (val > SENSOR_THRESHOLD); // Sections utilizing "above" suggested by ChatGPT
updateSensorValue(val, above);
xSemaphoreTake(print_mutex, portMAX_DELAY);
Serial.print("Heart rate sensor reading: ");
Serial.println(val);
xSemaphoreGive(print_mutex);
if (above) {
if (SEMCNT < MAX_COUNT_SEM + 1) SEMCNT++;
if (lastAboveThreshold == 0) {
setSensorAlertState(true);
xSemaphoreGive(sem_sensor);
xSemaphoreTake(print_mutex, portMAX_DELAY);
Serial.println("Sensor condition crossed threshold: automatic alert asserted.");
xSemaphoreGive(print_mutex);
}
lastAboveThreshold = 1;
} else {
if (lastAboveThreshold == 1) {
setSensorAlertState(false);
xSemaphoreTake(print_mutex, portMAX_DELAY);
Serial.println("Sensor condition returned to manageable level: automatic alert cleared.");
xSemaphoreGive(print_mutex);
}
lastAboveThreshold = 0;
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// APP4 PORT
void button_task(void *pvParameters) {
int lastState = HIGH;
TickType_t lastPressTick = 0;
while (1) {
int state = digitalRead(BUTTON_PIN);
if (state == LOW && lastState == HIGH &&
(xTaskGetTickCount() - lastPressTick) > pdMS_TO_TICKS(50)) {
xSemaphoreGive(sem_button);
lastPressTick = xTaskGetTickCount();
xSemaphoreTake(print_mutex, portMAX_DELAY);
Serial.println("Physical emergency button pressed: intervention requested.");
xSemaphoreGive(print_mutex);
}
lastState = state;
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// APP4 PORT & NEW MERGE
// Button/web only toggle manual alert
void event_handler_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(sem_sensor, 0) == pdTRUE) {
SEMCNT--;
xSemaphoreTake(print_mutex, portMAX_DELAY);
Serial.println("Heart rate alert: abnormal reading detected.");
xSemaphoreGive(print_mutex);
}
if (xSemaphoreTake(sem_button, 0) == pdTRUE) {
toggleManualAlertState();
bool manual = getManualAlertState();
bool overall = getOverallAlertState();
xSemaphoreTake(print_mutex, portMAX_DELAY);
Serial.println(manual ? "Manual emergency intervention alert triggered."
: "Manual emergency intervention alert cleared.");
Serial.print("Overall alert state is now: ");
Serial.println(overall ? "ACTIVE" : "CLEAR");
xSemaphoreGive(print_mutex);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// NEW MERGE (Blink/Flash LED rather than single pulse like APP4)
// Red LED reflects combined alert state
void alert_led_task(void *pvParameters) {
while (1) {
if (getOverallAlertState()) {
digitalWrite(LED_RED, !digitalRead(LED_RED));
vTaskDelay(pdMS_TO_TICKS(250));
} else {
digitalWrite(LED_RED, LOW);
vTaskDelay(pdMS_TO_TICKS(50));
}
}
}
// APP5 BASE
void web_server_task(void *pvParameters) {
while (1) {
server.handleClient();
vTaskDelay(pdMS_TO_TICKS(2));
}
}
void setup(void) {
Serial.begin(115200);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_RED, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_RED, LOW);
analogReadResolution(12);
// APP4 PORT
sem_button = xSemaphoreCreateBinary();
sem_sensor = xSemaphoreCreateCounting(MAX_COUNT_SEM, 0);
print_mutex = xSemaphoreCreateMutex();
// NEW MERGE
state_mutex = xSemaphoreCreateMutex();
// APP5 BASE
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);
server.on("/toggleAlert", handleToggleAlert);
server.begin();
Serial.println("HTTP server started");
// APP4 PORT
xTaskCreatePinnedToCore(heartbeat_task, "heartbeat_task", 2048, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(sensor_task, "sensor_task", 4096, NULL, 2, NULL, 1);
xTaskCreatePinnedToCore(button_task, "button_task", 2048, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(event_handler_task, "event_handler_task", 4096, NULL, 2, NULL, 1);
// NEW MERGE
xTaskCreatePinnedToCore(alert_led_task, "alert_led_task", 2048, NULL, 2, NULL, 1);
// APP5 BASE refactored
xTaskCreatePinnedToCore(web_server_task, "web_server_task", 4096, NULL, 1, NULL, 0);
}
void loop(void) {
vTaskDelay(pdMS_TO_TICKS(1000));
}