#include "esp_camera.h"
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
// ======== Camera Pin Configuration for AI-Thinker ESP32-CAM ========
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
// ======== WiFi Access Point Credentials ========
const char* ssid = "ESP32_CAM_AP";
const char* password = "12345678";
// ======== Create AsyncWebServer on port 80 ========
AsyncWebServer server(80);
// ======== Dashboard Page HTML in PROGMEM ========
const char driver_dashboard_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>WakeGuard - Driver Dashboard</title>
<style>
body { margin: 0; font-family: Arial, sans-serif; background: #4b4b5a; color: #fff; }
.container { display: flex; height: 100vh; }
.sidebar { width: 250px; background: #6e6e81; padding: 20px; }
.content { flex: 1; display: flex; flex-direction: column; padding: 20px; }
.driver-info, .metrics { background: #3a3a46; padding: 20px; margin-bottom: 20px; }
.video-container { flex: 1; background: #1f1f28; display: flex; align-items: center; justify-content: center; }
img { max-width: 100%; max-height: 100%; object-fit: contain; }
h2 { margin-top: 0; }
</style>
</head>
<body>
<div class="container">
<div class="sidebar">
<h2>WakeGuard</h2>
<nav>
<p><a href="#" style="color: #fff;">All Drivers</a></p>
<p><a href="#" style="color: #fff;">Alerts</a></p>
</nav>
</div>
<div class="content">
<div class="driver-info">
<h2>Driver Information</h2>
<p><strong>Name:</strong> <span id="driver-name">Loading...</span></p>
<p><strong>ID:</strong> <span id="driver-id">Loading...</span></p>
<p><strong>State:</strong> <span id="driver-state">Loading...</span></p>
</div>
<div class="metrics">
<h2>Real-Time Metrics</h2>
<p><strong>Eye Aspect Ratio:</strong> <span id="earValue">0.00</span></p>
<p><strong>Drowsiness Level:</strong> <span id="drowsyValue">0%</span></p>
</div>
<div class="video-container">
<img id="videoStream" src="/stream" alt="Live Stream"/>
</div>
</div>
</div>
</body>
</html>
)rawliteral";
// ======== MJPEG Stream Handler using Chunked Response ========
void streamHandler(AsyncWebServerRequest *request) {
request->beginChunkedResponse("multipart/x-mixed-replace; boundary=frame", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
static camera_fb_t *fb = nullptr;
static size_t framePart = 0;
static String header;
if (index == 0) { // Start of new frame
if (fb) {
esp_camera_fb_return(fb);
fb = nullptr;
}
fb = esp_camera_fb_get();
if (!fb) return 0;
header = "--frame\r\nContent-Type: image/jpeg\r\nContent-Length: " + String(fb->len) + "\r\n\r\n";
framePart = 0;
}
// Send header
if (framePart == 0 && index < header.length()) {
size_t bytesToSend = header.length() - index;
bytesToSend = bytesToSend > maxLen ? maxLen : bytesToSend;
memcpy(buffer, header.c_str() + index, bytesToSend);
if (index + bytesToSend == header.length()) framePart++;
return bytesToSend;
}
// Send frame data
if (framePart == 1) {
size_t frameIndex = index - header.length();
size_t remaining = fb->len - frameIndex;
if (remaining == 0) {
framePart++;
return 0;
}
size_t bytesToSend = remaining > maxLen ? maxLen : remaining;
memcpy(buffer, fb->buf + frameIndex, bytesToSend);
return bytesToSend;
}
// Send footer
if (framePart == 2) {
String footer = "\r\n";
size_t footerIndex = index - header.length() - fb->len;
if (footerIndex >= footer.length()) return 0;
size_t bytesToSend = footer.length() - footerIndex;
bytesToSend = bytesToSend > maxLen ? maxLen : bytesToSend;
memcpy(buffer, footer.c_str() + footerIndex, bytesToSend);
return bytesToSend;
}
return 0;
});
}
void setup() {
Serial.begin(115200);
// ======== Camera Configuration ========
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 15;
config.fb_count = 1;
if (esp_camera_init(&config) != ESP_OK) {
Serial.println("Camera init failed!");
return;
}
// ======== Set up WiFi AP ========
WiFi.softAP(ssid, password);
Serial.print("AP IP: ");
Serial.println(WiFi.softAPIP());
// ======== Set up Web Server Routes ========
server.on("/driver_dashboard", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", driver_dashboard_html);
});
server.on("/stream", HTTP_GET, streamHandler);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->redirect("/driver_dashboard");
});
server.begin();
}
void loop() {
// Keep empty for async operation
}