/*
Real Time Systems Spring 2026 - Application 6
Victor Valentin
Underwater Pressurized Locater Devices
***AI USE DISCLAIMER***
Claude AI was used within this project to write the sendHTML function
prompted to create the HTML based off the given code to function as desired
It was also prompted to check for outstanding errors within the project
https://claude.ai/share/63e0b290-ac4f-467d-8298-f379e0eb96df
Gemini AI was used within this project to understand proper wiring and
library usage for the BMP180 sensor. It was also used to learn how to write
interrupts.
https://gemini.google.com/share/55880cbf44ae
*/
#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"
#include "math.h"
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085.h>
// Wifi
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
// Defining the WiFi channel speeds up the connection:
#define WIFI_CHANNEL 6
// Web Server
WebServer server(80);
// Define GPIO pins for peripherals
#define emergencyLED 26
#define warningLED 27
#define statusLED 14
#define recallLED 12
#define recallButton 18
#define luxSensor 34
// Barometric Pressure Sensor uses I2C
// SCL on 22, SDA on 21
// Thresholds
#define crushDepth 90000
#define warningDepth 65000
#define lowVisibility 50
#define MAX_COUNT_SEM 3 // Limit a large backlog of LED flashes
// Synchronization Primitives
SemaphoreHandle_t xRecallSem;
SemaphoreHandle_t xRemoteRecallSem;
SemaphoreHandle_t xEmergencySem;
SemaphoreHandle_t xWarningSem;
SemaphoreHandle_t xStateMutex;
SemaphoreHandle_t xLogMutex;
QueueHandle_t xLuxQueue;
QueueHandle_t xPressureQueue;
// Create the pressure sensor object - Gemini AI
Adafruit_BMP085 bmp;
// State of LEDs/readings used in webserver
volatile bool emergencyLEDState = false;
volatile bool warningLEDState = false;
volatile bool statusLEDState = false;
volatile bool recallState = false;
volatile int lastLuxReading = 0;
volatile int lastPressureReading = 0;
// Webpage sendHTML created using Claude AI
// https://claude.ai/share/63e0b290-ac4f-467d-8298-f379e0eb96df
void sendHtml() {
// Snapshot shared state under mutex so the HTML is self-consistent
bool snap_emergency, snap_warning, snap_status, snap_recall;
int snap_pressure, snap_lux;
xSemaphoreTake(xStateMutex, portMAX_DELAY);
snap_emergency = emergencyLEDState;
snap_warning = warningLEDState;
snap_status = statusLEDState;
snap_recall = recallState;
snap_pressure = lastPressureReading;
snap_lux = lastLuxReading;
xSemaphoreGive(xStateMutex);
// Derive status strings
bool snap_atCrush = snap_pressure > crushDepth;
bool snap_atWarning = snap_pressure > warningDepth;
bool snap_lowVis = snap_lux < lowVisibility;
String html = R"rawhtml(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="refresh" content="1">
<title>Abyssal Pressure Monitor</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@700&display=swap');
:root {
--bg: #020d18;
--panel: #050f1e;
--border: #0a2540;
--teal: #00e5d4;
--red: #ff2244;
--amber: #ffaa00;
--dim: #0e2235;
--text: #a8d8e8;
--subtext: #2e6080;
--crush: #ff2244;
--warning: #ffaa00;
--safe: #00e5d4;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
background-image:
radial-gradient(ellipse at 50% 0%, #00304830 0%, transparent 70%),
repeating-linear-gradient(0deg, transparent, transparent 80px, #00182808 80px, #00182808 81px);
color: var(--text);
font-family: 'Share Tech Mono', monospace;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem 1rem;
gap: 1.5rem;
}
h1 {
font-family: 'Orbitron', sans-serif;
font-size: clamp(1.1rem, 4vw, 1.8rem);
letter-spacing: 0.25em;
color: var(--teal);
text-shadow: 0 0 20px #00e5d480;
text-align: center;
}
.subtitle {
color: var(--subtext);
font-size: 0.75rem;
letter-spacing: 0.15em;
text-align: center;
}
/* ── Alert Banner (crush/recall) ── */
.alert-banner {
width: 100%;
max-width: 580px;
padding: 0.85rem 1.5rem;
border: 2px solid var(--border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--panel);
}
.alert-banner.crush { border-color: var(--crush); animation: pulse-red 1s infinite alternate; }
.alert-banner.warn { border-color: var(--warning); animation: pulse-amb 1.5s infinite alternate; }
.alert-banner.recall { border-color: var(--teal); animation: pulse-teal 1.5s infinite alternate; }
@keyframes pulse-red { from { box-shadow: 0 0 6px #ff224430; } to { box-shadow: 0 0 22px #ff224490; } }
@keyframes pulse-amb { from { box-shadow: 0 0 6px #ffaa0030; } to { box-shadow: 0 0 22px #ffaa0090; } }
@keyframes pulse-teal { from { box-shadow: 0 0 6px #00e5d430; } to { box-shadow: 0 0 22px #00e5d490; } }
.banner-label { color: var(--subtext); font-size: 0.7rem; letter-spacing: 0.1em; }
.banner-value { font-family: 'Orbitron', sans-serif; font-size: 1rem; margin-top: 0.2rem; }
.banner-value.crush { color: var(--crush); text-shadow: 0 0 8px #ff224460; }
.banner-value.warn { color: var(--warning); text-shadow: 0 0 8px #ffaa0060; }
.banner-value.recall { color: var(--teal); text-shadow: 0 0 8px #00e5d460; }
.banner-value.normal { color: var(--subtext); }
/* ── Sensor Readings ── */
.sensor-grid {
width: 100%;
max-width: 580px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.sensor-card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 4px;
padding: 1rem 1.2rem;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.sensor-label { color: var(--subtext); font-size: 0.7rem; letter-spacing: 0.12em; }
.sensor-value {
font-family: 'Orbitron', sans-serif;
font-size: 1.6rem;
letter-spacing: 0.04em;
}
.sensor-value.danger { color: var(--crush); text-shadow: 0 0 10px #ff224455; }
.sensor-value.warn { color: var(--warning); text-shadow: 0 0 10px #ffaa0055; }
.sensor-value.safe { color: var(--teal); text-shadow: 0 0 10px #00e5d455; }
.sensor-unit { font-size: 0.7rem; color: var(--subtext); }
/* ── Status Flags ── */
.flag-row {
width: 100%;
max-width: 580px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.75rem;
}
.flag {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.75rem 0.5rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
font-size: 0.68rem;
letter-spacing: 0.08em;
color: var(--subtext);
text-align: center;
}
.flag-dot {
width: 14px; height: 14px;
border-radius: 50%;
background: var(--dim);
border: 1px solid var(--subtext);
}
.flag-dot.active-red { background: var(--crush); border-color: var(--crush); box-shadow: 0 0 10px var(--crush); }
.flag-dot.active-amber { background: var(--warning); border-color: var(--warning); box-shadow: 0 0 10px var(--warning); }
.flag-dot.active-teal { background: var(--teal); border-color: var(--teal); box-shadow: 0 0 10px var(--teal); }
.flag-text { color: var(--text); }
/* ── LED Grid ── */
.led-section-label {
width: 100%;
max-width: 580px;
font-size: 0.7rem;
letter-spacing: 0.15em;
color: var(--subtext);
border-bottom: 1px solid var(--border);
padding-bottom: 0.3rem;
}
.led-grid {
width: 100%;
max-width: 580px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.75rem;
}
.led-card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.9rem 0.5rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.55rem;
}
.led-name { font-size: 0.62rem; letter-spacing: 0.1em; color: var(--subtext); text-align: center; }
.led-dot {
width: 26px; height: 26px;
border-radius: 50%;
border: 2px solid var(--dim);
background: var(--dim);
}
.led-dot.on-teal { background: var(--teal); border-color: var(--teal); box-shadow: 0 0 14px var(--teal); }
.led-dot.on-red { background: var(--crush); border-color: var(--crush); box-shadow: 0 0 14px var(--crush); }
.led-dot.on-amber { background: var(--warning); border-color: var(--warning); box-shadow: 0 0 14px var(--warning); }
.led-state { font-size: 0.7rem; color: var(--text); }
/* ── Recall Button ── */
.btn-row { width: 100%; max-width: 580px; display: flex; gap: 1rem; }
.recall-btn {
flex: 1;
padding: 0.9rem;
background: transparent;
border: 2px solid;
font-family: 'Orbitron', sans-serif;
font-size: 0.8rem;
letter-spacing: 0.12em;
cursor: pointer;
border-radius: 4px;
text-decoration: none;
text-align: center;
text-transform: uppercase;
transition: background 0.2s, box-shadow 0.2s;
}
.recall-btn.initiate {
border-color: var(--crush);
color: var(--crush);
}
.recall-btn.initiate:hover {
background: #ff224415;
box-shadow: 0 0 16px #ff224440;
}
.recall-btn.stop {
border-color: var(--teal);
color: var(--teal);
}
.recall-btn.stop:hover {
background: #00e5d415;
box-shadow: 0 0 16px #00e5d440;
}
.footer {
color: var(--subtext);
font-size: 0.62rem;
letter-spacing: 0.1em;
text-align: center;
}
/* depth bar decoration */
.depth-bar {
width: 100%;
max-width: 580px;
height: 4px;
border-radius: 2px;
background: linear-gradient(to right, var(--teal) 0%, var(--warning) 60%, var(--crush) 100%);
opacity: 0.35;
}
</style>
</head>
<body>
<h1>⚓ ABYSSAL PRESSURE MONITOR</h1>
<p class="subtitle">ESP32 FREERTOS — AUTO REFRESH 1s</p>
<div class="depth-bar"></div>
<!-- Crush Depth Banner -->
<div class="alert-banner CRUSH_BANNER_CLASS">
<div>
<div class="banner-label">CRUSH DEPTH STATUS</div>
<div class="banner-value CRUSH_VALUE_CLASS">CRUSH_STRING</div>
</div>
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 11v-4m0 8h.01"/>
</svg>
</div>
<!-- Recall State Banner -->
<div class="alert-banner RECALL_BANNER_CLASS">
<div>
<div class="banner-label">RECALL STATE</div>
<div class="banner-value RECALL_VALUE_CLASS">RECALL_STRING</div>
</div>
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
<path d="M3 3v5h5"/>
</svg>
</div>
<!-- Sensor Readings -->
<div class="sensor-grid">
<div class="sensor-card">
<div class="sensor-label">BAROMETRIC PRESSURE</div>
<div class="sensor-value PRESSURE_CLASS">PRESSURE_VALUE</div>
<div class="sensor-unit">Pa • WARN > WARN_DEPTH / CRUSH > CRUSH_DEPTH</div>
</div>
<div class="sensor-card">
<div class="sensor-label">AMBIENT LUX LEVEL</div>
<div class="sensor-value LUX_CLASS">LUX_VALUE</div>
<div class="sensor-unit">ADC counts • LOW VIS < LOW_VIS_THRESHOLD</div>
</div>
</div>
<!-- Status Flags -->
<div class="flag-row">
<div class="flag">
<div class="flag-dot WARN_DEPTH_DOT"></div>
<div class="flag-text">WARNING DEPTH</div>
<div>WARN_DEPTH_STR</div>
</div>
<div class="flag">
<div class="flag-dot CRUSH_DEPTH_DOT"></div>
<div class="flag-text">CRUSH DEPTH</div>
<div>CRUSH_DEPTH_STR</div>
</div>
<div class="flag">
<div class="flag-dot LOW_VIS_DOT"></div>
<div class="flag-text">LOW VISIBILITY</div>
<div>LOW_VIS_STR</div>
</div>
</div>
<!-- LED Status -->
<div class="led-section-label">◉ LED INDICATOR STATUS</div>
<div class="led-grid">
<div class="led-card">
<div class="led-name">EMERGENCY</div>
<div class="led-dot EMERG_DOT_CLASS"></div>
<div class="led-state">EMERG_STATE</div>
</div>
<div class="led-card">
<div class="led-name">WARNING</div>
<div class="led-dot WARN_DOT_CLASS"></div>
<div class="led-state">WARN_STATE</div>
</div>
<div class="led-card">
<div class="led-name">STATUS</div>
<div class="led-dot STATUS_DOT_CLASS"></div>
<div class="led-state">STATUS_STATE</div>
</div>
<div class="led-card">
<div class="led-name">RECALL</div>
<div class="led-dot RECALL_DOT_CLASS"></div>
<div class="led-state">RECALL_LED_STATE</div>
</div>
</div>
<!-- Recall Control Button -->
<div class="btn-row">
<a href="/ack" class="recall-btn RECALL_BTN_CLASS">RECALL_BTN_LABEL</a>
</div>
<div class="depth-bar"></div>
<p class="footer">© ESP32 FREERTOS ABYSSAL MONITOR — ALL DEPTHS NOMINAL</p>
</body>
</html>
)rawhtml";
// ── String substitutions ──────────────────────────────────────────────────
// Crush depth banner
html.replace("CRUSH_BANNER_CLASS", snap_atCrush ? "crush" : (snap_atWarning ? "warn" : ""));
html.replace("CRUSH_VALUE_CLASS", snap_atCrush ? "crush" : (snap_atWarning ? "warn" : "normal"));
html.replace("CRUSH_STRING", snap_atCrush ? "CRUSH DEPTH EXCEEDED" : (snap_atWarning ? "WARNING DEPTH" : "DEPTH NOMINAL"));
// Recall banner
html.replace("RECALL_BANNER_CLASS", snap_recall ? "recall" : "");
html.replace("RECALL_VALUE_CLASS", snap_recall ? "recall" : "normal");
html.replace("RECALL_STRING", snap_recall ? "RECALL ACTIVE" : "STANDBY");
// Sensor values
html.replace("PRESSURE_VALUE", String(snap_pressure));
html.replace("PRESSURE_CLASS", snap_atCrush ? "danger" : (snap_atWarning ? "warn" : "safe"));
html.replace("LUX_VALUE", String(snap_lux));
html.replace("LUX_CLASS", snap_lowVis ? "warn" : "safe");
// Status flags
html.replace("WARN_DEPTH_DOT", snap_atWarning ? "active-amber" : "");
html.replace("WARN_DEPTH_STR", snap_atWarning ? "ACTIVE" : "CLEAR");
html.replace("CRUSH_DEPTH_DOT", snap_atCrush ? "active-red" : "");
html.replace("CRUSH_DEPTH_STR", snap_atCrush ? "ACTIVE" : "CLEAR");
html.replace("LOW_VIS_DOT", snap_lowVis ? "active-amber" : "");
html.replace("LOW_VIS_STR", snap_lowVis ? "ACTIVE" : "CLEAR");
// Sensor unit threshold labels
html.replace("WARN_DEPTH", String(warningDepth));
html.replace("CRUSH_DEPTH", String(crushDepth));
html.replace("LOW_VIS_THRESHOLD", String(lowVisibility));
// LEDs
html.replace("EMERG_DOT_CLASS", snap_emergency ? "on-red" : "");
html.replace("EMERG_STATE", snap_emergency ? "ON" : "OFF");
html.replace("WARN_DOT_CLASS", snap_warning ? "on-amber" : "");
html.replace("WARN_STATE", snap_warning ? "ON" : "OFF");
html.replace("STATUS_DOT_CLASS", snap_status ? "on-teal" : "");
html.replace("STATUS_STATE", snap_status ? "ON" : "OFF");
html.replace("RECALL_DOT_CLASS", snap_recall ? "on-teal" : "");
html.replace("RECALL_LED_STATE", snap_recall ? "ON" : "OFF");
// Recall toggle button — always active, label/style flips with state
html.replace("RECALL_BTN_CLASS", snap_recall ? "stop" : "initiate");
html.replace("RECALL_BTN_LABEL", snap_recall ? "■ STOP RECALL" : "⚠ INITIATE RECALL");
server.send(200, "text/html", html);
}
void IRAM_ATTR buttonISR();
void setup(void) {
Serial.begin(115200);
// Initialize pins as output/input
pinMode(recallLED, OUTPUT);
pinMode(statusLED, OUTPUT);
pinMode(emergencyLED, OUTPUT);
pinMode(warningLED, OUTPUT);
pinMode(recallButton, INPUT_PULLUP);
pinMode(luxSensor, INPUT);
// All LEDs start off
digitalWrite(recallLED, LOW);
digitalWrite(statusLED, LOW);
digitalWrite(emergencyLED, LOW);
digitalWrite(warningLED, LOW);
// Initialize the sensor - Gemini AI
if (!bmp.begin()) {
Serial.println("Could not find BMP180 sensor. Check wiring!");
while (1) {}
}
Serial.println("BMP180 Sensor Initialized.");
// Connect to 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());
// Synchronization primitives
xRecallSem = xSemaphoreCreateBinary();
xRemoteRecallSem = xSemaphoreCreateBinary();
xLogMutex = xSemaphoreCreateMutex();
xStateMutex = xSemaphoreCreateMutex();
xEmergencySem = xSemaphoreCreateCounting(MAX_COUNT_SEM, 0);
xWarningSem = xSemaphoreCreateCounting(MAX_COUNT_SEM, 0);
xPressureQueue = xQueueCreate(10, sizeof(int));
xLuxQueue = xQueueCreate(10, sizeof(int));
// Create interrupt for button
attachInterrupt(digitalPinToInterrupt(recallButton), buttonISR, FALLING);
// Create Tasks
xTaskCreatePinnedToCore(status_led_task, "Status LED", 1024, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(emergency_led_task, "Emergency LED", 1024, NULL, 2, NULL, 1);
xTaskCreatePinnedToCore(warning_led_task, "Warning LED", 1024, NULL, 2, NULL, 1);
xTaskCreatePinnedToCore(pressure_reading_task, "Pressure Reading", 2048, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(lux_reading_task, "LUX Reading", 2048, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(server_recall_task, "Server Recall Task", 1024, NULL, 4, NULL, 1);
xTaskCreatePinnedToCore(recall_task, "Recall Task", 1024, NULL, 4, NULL, 1);
xTaskCreatePinnedToCore(sensor_handler_task, "Event Handler", 4096, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(web_server_task, "Web Server", 4096, NULL, 1, NULL, 0);
}
void loop(void) {
vTaskDelay(portMAX_DELAY);
}
// Sends HTML to web server continuously, pinned to core 0
void web_server_task(void *parameter){
server.on("/", sendHtml);
server.on("/ack", [](){
xSemaphoreGive(xRemoteRecallSem);
server.sendHeader("Location", "/", true);
server.send(302, "text/plain", "");
});
server.begin();
// Mutex protected print
xSemaphoreTake(xLogMutex, portMAX_DELAY);
Serial.println("HTTP server started");
xSemaphoreGive(xLogMutex);
while (1) {
server.handleClient();
vTaskDelay(2 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
// Blink Status LED, 4 Second Period (2 On, 2 Off), Soft Deadline
void status_led_task(void *parameter){
bool led_state = false;
while(1){
led_state = !led_state;
digitalWrite(statusLED, led_state);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
statusLEDState = led_state;
xSemaphoreGive(xStateMutex);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
// Blink Emergency LED, 1 second on 0.3 off, Soft Deadline
void emergency_led_task(void *parameter){
while(1){
// Counting Semaphore given by sensor handler task
if(xSemaphoreTake(xEmergencySem, portMAX_DELAY)){
digitalWrite(emergencyLED, HIGH);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
emergencyLEDState = true;
xSemaphoreGive(xStateMutex);
vTaskDelay(1000);
digitalWrite(emergencyLED, LOW);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
emergencyLEDState = false;
xSemaphoreGive(xStateMutex);
vTaskDelay(300);
}
}
vTaskDelete(NULL);
}
// Blink Warning LED, 1 second on 0.3 off, Soft Deadline
void warning_led_task(void *parameter){
while(1){
// Counting Semaphore given by sensor handler task
if(xSemaphoreTake(xWarningSem, portMAX_DELAY)){
digitalWrite(warningLED, HIGH);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
warningLEDState = true;
xSemaphoreGive(xStateMutex);
vTaskDelay(1000);
digitalWrite(warningLED, LOW);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
warningLEDState = false;
xSemaphoreGive(xStateMutex);
vTaskDelay(300);
}
}
vTaskDelete(NULL);
}
// Button ISR, gives recall semaphore
void IRAM_ATTR buttonISR(){
// Debouncing parameters
static TickType_t last_isr_time = 0;
TickType_t isr_time = xTaskGetTickCountFromISR();
// Debounce
if((isr_time - last_isr_time) > pdMS_TO_TICKS(100)){
last_isr_time = isr_time;
BaseType_t xHigherPrioTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xRecallSem, &xHigherPrioTaskWoken);
portYIELD_FROM_ISR(xHigherPrioTaskWoken);
}
}
// ISR button initated recall, Hard Deadline, Recall LED must update within 10ms.
void recall_task(void *parameter){
while(1){
// Recall Semaphore given by ISR
if(xSemaphoreTake(xRecallSem, portMAX_DELAY)){
// If recall off turn on, else turn off
if(!recallState){
// Mutex protected print
xSemaphoreTake(xLogMutex, portMAX_DELAY);
Serial.println("Recall initated by device.");
xSemaphoreGive(xLogMutex);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
recallState = true;
digitalWrite(recallLED, HIGH);
xSemaphoreGive(xStateMutex);
}
else{
// Mutex protected print
xSemaphoreTake(xLogMutex, portMAX_DELAY);
Serial.println("Recall stopped by device.");
xSemaphoreGive(xLogMutex);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
recallState = false;
digitalWrite(recallLED, LOW);
xSemaphoreGive(xStateMutex);
}
}
vTaskDelay(10);
}
vTaskDelete(NULL);
}
// Server initiated recall, Hard deadline
void server_recall_task(void *parameter){
while(1){
// Remote Recall Sem given from web server task
if(xSemaphoreTake(xRemoteRecallSem, portMAX_DELAY)){
if(!recallState){
// Mutex protected print
xSemaphoreTake(xLogMutex, portMAX_DELAY);
Serial.println("Recall initated remotely.");
xSemaphoreGive(xLogMutex);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
recallState = true;
digitalWrite(recallLED, HIGH);
xSemaphoreGive(xStateMutex);
}
else{
// Mutex protected print
xSemaphoreTake(xLogMutex, portMAX_DELAY);
Serial.println("Recall stopped remotely.");
xSemaphoreGive(xLogMutex);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
recallState = false;
digitalWrite(recallLED, LOW);
xSemaphoreGive(xStateMutex);
}
}
vTaskDelay(10);
}
}
// Reads barometric pressure sensor every 1500ms and queues value received, Hard Deadline
void pressure_reading_task(void *parameter){
const TickType_t period = pdMS_TO_TICKS(1500); // e.g. 1500 ms period
TickType_t lastWakeTime; // initialize last wake time
lastWakeTime = xTaskGetTickCount();
while(1){
// Pressure read using bmp library functions, transferred to sensor handler via queue
int pressureReading = bmp.readPressure();
xQueueSend(xPressureQueue, &pressureReading, 0);
// Delay with no drift, 1500ms period
vTaskDelayUntil(&lastWakeTime, period);
}
vTaskDelete(NULL);
}
// Reads lux sensor every 2500ms and queues value received, Hard Deadline
void lux_reading_task(void *parameter){
const TickType_t period = pdMS_TO_TICKS(2500); // e.g. 2500 ms period
TickType_t lastWakeTime; // initialize last wake time
lastWakeTime = xTaskGetTickCount();
while(1){
// Analog raw lux reading, transferred to sensor handler via queue
int rawLuxReading = analogRead(luxSensor);
xQueueSend(xLuxQueue, &rawLuxReading, 0);
// Delay with no drift, 2500ms period
vTaskDelayUntil(&lastWakeTime, period);
}
vTaskDelete(NULL);
}
// Handles values received from sensor reading tasks, Hard Deadlines
void sensor_handler_task(void *parameter){
int rawLuxReading;
int pressureReading;
while(1){
// Handles queued pressure readings
if(xQueueReceive(xPressureQueue, &pressureReading, 0)){
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
lastPressureReading = pressureReading;
xSemaphoreGive(xStateMutex);
// Mutex protected printing
xSemaphoreTake(xLogMutex, portMAX_DELAY);
Serial.printf("Barometric Pressure: %d\n", pressureReading);
if(pressureReading > crushDepth){
Serial.println("EMERGENCY! BELOW CRUSH LEVEL! RECALL IMMEDIATELY TO PREVENT HULL DAMAGE");
xSemaphoreGive(xEmergencySem);
} else if(pressureReading > warningDepth){
Serial.println("WARNING! Nearing crush depth, recall recommended");
xSemaphoreGive(xWarningSem);
}
xSemaphoreGive(xLogMutex);
}
// Handles queue lux readings
if(xQueueReceive(xLuxQueue, &rawLuxReading, 0)){
// Convert raw lux into actual
float Vmeasure = 0.;
float Rmeasure = 0.;
int lux = 0;
Vmeasure = rawLuxReading / 4095.0 * 3.3;
if (Vmeasure >= 3.29) Vmeasure = 3.28;
Rmeasure = 2000 * Vmeasure / (3.3 - Vmeasure);
lux = (int)((pow((50 * pow(10, 3.7)) / Rmeasure, 1.0 / 0.7))/10);
// Mutex protected state change
xSemaphoreTake(xStateMutex, portMAX_DELAY);
lastLuxReading = lux;
xSemaphoreGive(xStateMutex);
// Mutex protected print
xSemaphoreTake(xLogMutex, portMAX_DELAY);
Serial.printf("Current Lux Level: %d\n", lux);
if(lux < lowVisibility){
Serial.println("Warning: Visibility Low");
xSemaphoreGive(xWarningSem);
}
xSemaphoreGive(xLogMutex);
}
// Delay 10ms, Hard deadlines, check queues often (checked more often than queues are populating)
vTaskDelay(pdMS_TO_TICKS(10));
}
vTaskDelete(NULL);
}