#include <WiFi.h>
#include <WebServer.h>
#include <OneWire.h>
#include <Preferences.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// Wi-Fi Access Point configuration
const char *apSSID = "Jean & Sizwe";
const char *apPassword = "12345678";
WebServer server(80);
// Preferences for saving limits
Preferences preferences;
bool isAuthenticated = false;
float currentTemperature = 0.0;
// RGB LED pins (Common Anode)
const int redPin = 21;
const int greenPin = 22;
const int bluePin = 23;
// Temperature limits
float lowerLimit = 27.0;
float upperLimit = 30.0;
// Buzzer pin
const int buzzerPin = 5;
// Optional fan
//const int fanPin = 19;
//bool isFanOn = false;
unsigned long lastFlashTime = 0;
bool redState = false;
bool buzzerState = false;
// Function to update RGB LED based on temperature
void updateRGBLED() {
unsigned long currentMillis = millis();
if (currentTemperature < lowerLimit) {
// Blue ON - Normal cool temperature, no buzzer
digitalWrite(redPin, HIGH);
digitalWrite(greenPin, HIGH);
digitalWrite(bluePin, LOW);
digitalWrite(buzzerPin, LOW);
}
else if (currentTemperature > upperLimit) {
// Flashing Red + Buzzer ON continuously
if (currentMillis - lastFlashTime >= 500) {
lastFlashTime = currentMillis;
redState = !redState;
digitalWrite(redPin, redState ? LOW : HIGH);
}
// keep buzzer ON whenever above upper limit
digitalWrite(buzzerPin, (currentMillis / 500) % 2);
digitalWrite(greenPin, HIGH);
digitalWrite(bluePin, HIGH);
}
else {
// Green ON - normal temperature
digitalWrite(redPin, HIGH);
digitalWrite(greenPin, LOW);
digitalWrite(bluePin, HIGH);
digitalWrite(buzzerPin, LOW);
}
}
// ----------------- Working DS18B20 Temperature Read -----------------
float readTemperature() {
sensors.requestTemperatures(); // send the command to get temperatures
float tempC = sensors.getTempCByIndex(0); // read first sensor
Serial.print("Temperature: ");
Serial.print(tempC);
Serial.println(" °C");
return tempC;
}
// -------------------------------------------------------------------
// Root page with graph
void handleRoot() {
if (isAuthenticated) {
String message = "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><title>ESP32 Temp Monitor</title>";
message += "<style>";
message += "body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin:0; padding:20px; }";
message += "h1 { text-align:center; color:#333; }";
message += "canvas { display:block; margin:20px auto; border:2px solid #000; background:#fff; }";
message += "p { text-align:center; font-size:18px; }";
message += "a { text-decoration:none; color:#0066cc; }";
message += "a:hover { text-decoration:underline; }";
message += "header, footer { background-color: lightgreen; padding:10px; text-align:center; }";
message += "footer { margin-top:20px; font-size:14px; }";
message += "</style></head><body>";
message += "<header><h1>ESP32 Temperature Monitoring System</h1></header>";
message += "<canvas id='temperatureCanvas' width='800' height='400'></canvas>";
message += "<p id='temperature'>Fetching temperature...</p>";
message += "<p><a href='/settings'>Settings</a> | <a href='/logout'>Logout</a></p>";
message += "<script>";
message += "var ctx = document.getElementById('temperatureCanvas').getContext('2d');";
message += "ctx.font='16px Arial'; ctx.fillStyle='black';";
message += "var temperatureData=[];";
message += "function drawGraph(){";
message += "ctx.clearRect(0,0,800,400);";
message += "let minTemp=0, maxTemp=50;";
message += "let scaleY=350/(maxTemp-minTemp);";
message += "if(temperatureData.length>0){";
message += "ctx.beginPath();";
message += "ctx.moveTo(0,400-(temperatureData[0]-minTemp)*scaleY);";
message += "for(let i=1;i<temperatureData.length;i++){";
message += "let x0=(i-1)*35, y0=400-(temperatureData[i-1]-minTemp)*scaleY;";
message += "let x1=i*35, y1=400-(temperatureData[i]-minTemp)*scaleY;";
message += "let cx=(x0+x1)/2;";
message += "ctx.quadraticCurveTo(x0,y0,cx,(y0+y1)/2);}";
message += "ctx.lineTo((temperatureData.length-1)*35,400-(temperatureData[temperatureData.length-1]-minTemp)*scaleY);";
message += "ctx.strokeStyle='red'; ctx.lineWidth=2; ctx.stroke();";
message += "ctx.fillStyle='blue';";
message += "for(let i=0;i<temperatureData.length;i++){";
message += "let x=i*35, y=400-(temperatureData[i]-minTemp)*scaleY;";
message += "ctx.beginPath(); ctx.arc(x,y,4,0,2*Math.PI); ctx.fill();}";
message += "ctx.fillStyle='black';";
message += "for(let y=minTemp; y<=maxTemp; y+=5){";
message += "let yPos=400-(y-minTemp)*scaleY; ctx.fillText(y.toFixed(0),5,yPos);}";
message += "ctx.fillText('Temperature (°C)',20,30); ctx.fillText('Time (s)',700,380);}";
message += "}";
message += "function updateTemperature(){";
message += "fetch('http://192.168.4.1/temperature')";
message += ".then(response=>response.json())";
message += ".then(data=>{";
message += "document.getElementById('temperature').innerText='Current Temperature: '+data.temperature+' °C';";
message += "temperatureData.push(data.temperature); if(temperatureData.length>20) temperatureData.shift();";
message += "drawGraph();})";
message += ".catch(err=>console.error('Fetch error:',err));}";
message += "updateTemperature();";
message += "setInterval(updateTemperature,1000);";
message += "</script>";
message += "<footer><p>© 2025 ESP32 Project.</p></footer>";
message += "</body></html>";
server.send(200,"text/html",message);
} else handleLoginPage();
}
// -------------------- Remaining functions (login, settings, API) --------------------
void handleLoginPage() {
String loginPage = "<html><head><title>Login</title><style>";
loginPage += "body { font-family: Arial; background-color:#f4f4f4; display:flex; justify-content:center; align-items:center; height:100vh; }";
loginPage += "form { background:white; padding:20px; border-radius:8px; box-shadow:0 2px 10px rgba(0,0,0,0.1); width:300px; }";
loginPage += "input[type='text'], input[type='password'] { width:100%; padding:10px; margin:10px 0; border:1px solid #ccc; border-radius:4px; }";
loginPage += "input[type='submit'] { background-color:#0066cc; color:white; border:none; padding:10px; border-radius:4px; cursor:pointer; width:100%; }";
loginPage += "input[type='submit']:hover { background-color:#0055a5; }";
loginPage += "</style></head><body>";
loginPage += "<form method='POST' action='/login'><h1>Login</h1>";
loginPage += "<input type='text' name='username' placeholder='Username' required><br>";
loginPage += "<input type='password' name='password' placeholder='Password' required><br>";
loginPage += "<input type='submit' value='Login'></form></body></html>";
server.send(200,"text/html",loginPage);
}
void handleLogin() {
if(server.arg("username")=="admin" && server.arg("password")=="1234") {
isAuthenticated = true;
server.sendHeader("Location","/");
server.send(302,"text/plain","");
} else handleLoginPage();
}
void handleLogout() {
isAuthenticated = false;
server.sendHeader("Location","/");
server.send(302,"text/plain","");
}
void handleSettings() {
if(isAuthenticated){
String message="<html><head><title>Settings</title><style>";
message += "body { font-family: Arial; background-color:#f4f4f4; padding:20px; }";
message += "input[type='number'] { padding:5px; width:100px; }";
message += "input[type='submit'] { padding:10px; background-color:#0066cc; color:white; border:none; border-radius:4px; cursor:pointer; }";
message += "</style></head><body>";
message += "<h1>Settings</h1>";
message += "<form method='POST' action='/update_limits'>";
message += "<label>Lower Limit:</label><input type='number' step='0.1' name='lower_limit' value='" + String(lowerLimit) + "'><br>";
message += "<label>Upper Limit:</label><input type='number' step='0.1' name='upper_limit' value='" + String(upperLimit) + "'><br>";
message += "<input type='submit' value='Update Limits'></form><p><a href='/'>Back</a></p></body></html>";
server.send(200,"text/html",message);
} else handleLoginPage();
}
void handleUpdateLimits() {
if(server.hasArg("lower_limit")) {
lowerLimit = server.arg("lower_limit").toFloat();
preferences.putFloat("lowerLimit", lowerLimit);
}
if(server.hasArg("upper_limit")) {
upperLimit = server.arg("upper_limit").toFloat();
preferences.putFloat("upperLimit", upperLimit);
}
server.sendHeader("Location","/settings");
server.send(302,"text/plain","");
}
void handleTemperature() {
currentTemperature = readTemperature();
updateRGBLED();
String temperatureJSON="{\"temperature\":"+String(currentTemperature)+"}";
server.send(200,"application/json",temperatureJSON);
}
// ------------------------- Setup & Loop -------------------------
void setup() {
Serial.begin(115200);
// Start DallasTemperature
sensors.begin();
WiFi.softAP(apSSID, apPassword);
Serial.println("Access Point started");
Serial.print("IP address: "); Serial.println(WiFi.softAPIP());
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
//pinMode(fanPin, OUTPUT);
pinMode(buzzerPin,OUTPUT);
digitalWrite(redPin, HIGH);
digitalWrite(greenPin, HIGH);
digitalWrite(bluePin, HIGH);
//digitalWrite(fanPin, LOW);
digitalWrite(buzzerPin,LOW);
preferences.begin("settings", false);
lowerLimit = preferences.getFloat("lowerLimit", 27.0);
upperLimit = preferences.getFloat("upperLimit", 30.0);
server.on("/", handleRoot);
server.on("/login", HTTP_POST, handleLogin);
server.on("/logout", handleLogout);
server.on("/settings", handleSettings);
server.on("/update_limits", HTTP_POST, handleUpdateLimits);
server.on("/temperature", handleTemperature);
server.begin();
Serial.println("Web server started");
}
void loop() {
server.handleClient();
updateRGBLED();
// Read and update temperature every second
static unsigned long lastTempRead = 0;
if (millis() - lastTempRead >= 1000) {
lastTempRead = millis();
currentTemperature = readTemperature(); // read sensor
updateRGBLED(); // update LED based on new temperature
Serial.print("Current Temperature: ");
Serial.print(currentTemperature);
Serial.println(" °C");
}
}