#include <WiFi.h>
#include <WebServer.h>
// This attribute keeps the variable in RTC memory so it isn't erased during deep sleep
// Note: RTC memory is limited. If chat history gets too long, it may behave unexpectedly.
RTC_DATA_ATTR char savedChat[2000] = "";
WebServer server(80);
String chatHistory = "";
const int LED_PIN = 2;
const int BOOT_BUTTON_PIN = 0; // Standard GPIO for the BOOT button
// Non-blocking LED variables
unsigned long lastBlinkTime = 0;
int blinkStep = 0;
// --- 3DS OPTIMIZED HTML ---
void handleRoot() {
String s = "<html><head><meta name='viewport' content='width=device-width, initial-scale=1'>";
s += "<style>";
s += "body{font-family:sans-serif; background:#fff; padding:10px;}";
s += ".box{text-align:left; background:white; padding:10px; border:2px solid #333; height:180px; overflow-y:scroll; font-size:16px; margin-bottom:10px;}";
s += "input[type=text]{width:60%; font-size:16px;}";
s += "input[type=submit]{font-size:16px; padding:5px;}";
s += "</style>";
s += "<script type='text/javascript'>";
s += "function updateChat() {";
s += " var x = new XMLHttpRequest();";
s += " x.onreadystatechange = function() {";
s += " if (this.readyState == 4 && this.status == 200) {";
s += " var c = document.getElementById('chat');";
s += " c.innerHTML = this.responseText;";
s += " c.scrollTop = c.scrollHeight;";
s += " }";
s += " };";
s += " x.open('GET', '/history', true); x.send();";
s += "}";
s += "function sendMsg() {";
s += " var mInput = document.getElementById('msg');";
s += " var m = mInput.value;";
s += " if(m === '') return false;";
s += " var c = document.getElementById('chat');";
s += " c.innerHTML += '<div><b>[Sending...]</b> ' + m + '</div>';";
s += " c.scrollTop = c.scrollHeight;";
s += " var x = new XMLHttpRequest();";
s += " x.open('GET', '/send?msg=' + encodeURIComponent(m), true);";
s += " x.send();";
s += " mInput.value = '';";
s += " return false;";
s += "}";
s += "setInterval(updateChat, 3000);";
s += "</script>";
s += "</head><body>";
s += "<h3>Public Chat</h3>";
s += "<div id='chat' class='box'>" + chatHistory + "</div>";
s += "<form onsubmit='return sendMsg()'>";
s += "<input type='text' id='msg' autocomplete='off'> ";
s += "<input type='submit' value='Send'>";
s += "</form></body></html>";
server.sendHeader("Connection", "close");
server.send(200, "text/html", s);
}
void handleHistory() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", chatHistory);
}
void handleSend() {
if (server.hasArg("msg")) {
String message = server.arg("msg");
if (message != "") {
chatHistory += "<div><b>></b> " + message + "</div>";
}
}
server.sendHeader("Connection", "close");
server.send(204, "text/plain", "");
}
void goToSleep() {
Serial.println("Saving chat and going to sleep...");
// Copy current chat to RTC memory before power down
strncpy(savedChat, chatHistory.c_str(), sizeof(savedChat) - 1);
// Set the BOOT button (GPIO 0) as the wake-up trigger
// Level 0 means it wakes when the button is pressed (pulled to GND)
esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0);
// Turn off LED and WiFi
digitalWrite(LED_PIN, LOW);
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
delay(500);
esp_deep_sleep_start();
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP);
// Restore chat history from RTC memory
chatHistory = String(savedChat);
// Flash LED to show we woke up
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
WiFi.mode(WIFI_AP);
WiFi.softAP("Google: 192.168.4.1", "");
WiFi.setSleep(false);
server.on("/", handleRoot);
server.on("/send", handleSend);
server.on("/history", handleHistory);
server.begin();
Serial.println("System Ready! Press and hold BOOT to sleep.");
}
void loop() {
server.handleClient();
// --- SLEEP BUTTON LOGIC ---
// Hold the BOOT button for 2 seconds to enter sleep
if (digitalRead(BOOT_BUTTON_PIN) == LOW) {
unsigned long pressStart = millis();
while (digitalRead(BOOT_BUTTON_PIN) == LOW) {
if (millis() - pressStart > 2000) {
goToSleep();
}
}
}
// --- LED LOGIC (Non-blocking) ---
int userCount = WiFi.softAPgetStationNum();
unsigned long currentMillis = millis();
if (userCount == 0) {
digitalWrite(LED_PIN, LOW);
blinkStep = 0;
}
else if (userCount == 1) {
if (blinkStep == 0 && currentMillis - lastBlinkTime >= 2500) {
digitalWrite(LED_PIN, HIGH); blinkStep = 1; lastBlinkTime = currentMillis;
}
else if (blinkStep == 1 && currentMillis - lastBlinkTime >= 100) {
digitalWrite(LED_PIN, LOW); blinkStep = 2; lastBlinkTime = currentMillis;
}
else if (blinkStep == 2 && currentMillis - lastBlinkTime >= 100) {
digitalWrite(LED_PIN, HIGH); blinkStep = 3; lastBlinkTime = currentMillis;
}
else if (blinkStep == 3 && currentMillis - lastBlinkTime >= 100) {
digitalWrite(LED_PIN, LOW); blinkStep = 0; lastBlinkTime = currentMillis;
}
}
else {
digitalWrite(LED_PIN, HIGH);
}
}