Temperaturføler med ESP32, DS18B20 og OLED-skærm

From munkjensen.net/wiki

Indledning

Dette projekt beskriver en ESP32-baseret temperaturmåler, der bruger en DS18B20-sensor til at måle temperaturer, f.eks. i et vildmarksbad hvor 0 °C til ~42 °C er normalt. Temperaturen vises på en OLED-skærm og kan ses i en web-browser.

Projektet inkluderer nem konfiguration af opkobling til Wi-Fi og der er mulighed for, at sende data til en MQTT-server.

Indkøbsliste

Byggevejledning

1. Tilslutninger:

  - DS18B20:  
    - VCC (rød)  → 3.3V på ESP32  
    - GND (sort) → GND på ESP32  
    - Data (gul) → GPIO 4 på ESP32  
    - Tilføj en 4,7kΩ pull-up modstand mellem Data og VCC.
  - OLED-skærm (I2C):  
    - VCC → 3.3V på ESP32  
    - GND → GND  på ESP32  
    - SDA → GPIO 21 på ESP32  
    - SCL → GPIO 22 på ESP32  

2. Strømforsyning:

  Tilslut ESP32 og OLED til 5V strømforsyning via HLK-PMxx-modulet.

3. Montering:

  - Montér komponenterne på prototype-PCB.  
  - Placér hele opsætningen i Sonoff IP66 vandtæt etui for beskyttelse mod vejr og støv.

Kode til ESP32

Kopier denne kode til Arduino IDE -->
/* README plz...
   Benyt "uPesy ESP32 Wroom DevKit" som board model til kompilering.
   Jeg har oplevet, at andre har fejl der får LED til at blinke for hurtigt !?!
*/
#include <WiFi.h>
#include <WebServer.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <PubSubClient.h>
#include <EEPROM.h>

// === PIN-konfigurationer ===
#define ONE_WIRE_BUS   4  // Pin til DS18B20 temperatursensoren.
#define SCREEN_WIDTH 128  // Skærmens bredde (for OLED).
#define SCREEN_HEIGHT 64  // Skærmens højde (for OLED).
#define OLED_RESET    -1  // Reset pin til OLED (sættes til -1, da vi ikke bruger en fysisk reset pin).
#define EEPROM_SIZE 4096  // Størrelsen af EEPROM (kan gemme op til 4096 byte).
#define RST_PIN       13  // Pin for RST-knappen.
#define LED_BUILTIN    2  // Pin for LED.

// === Globale objekter ===
OneWire oneWire(ONE_WIRE_BUS);        // Initialiserer OneWire-bus til DS18B20.
DallasTemperature sensors(&oneWire);  // Opretter et objekt til at håndtere temperaturmålinger.
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);  // OLED-skærm til at vise informationer.
WebServer server(80);                 // Webserver på port 80 til at håndtere HTTP-anmodninger.
WiFiClient espClient;                 // WiFi klient til MQTT.
PubSubClient mqttClient(espClient);   // MQTT-klient, der bruger WiFi klienten.

// === Globale variabler ===
unsigned long lastCommsCheck = 0;          // Tidspunkt for sidste Comms-tjek.
unsigned long CommsCheckInterval = 60000;  // Tidsinterval for Comms-tjek (60 sekunder) angivet i millisekunder.
unsigned long lastLEDCheck = 0;            // Tidspunkt for sidste LED-tjek.
unsigned long LEDCheckInterval = 2000;     // Tidsinterval for LED-tjek (2 sekunder) angivet i millisekunder.
unsigned long lastMQTTpublish = 0;         // Tidspunkt for sidste MQTT-publish.
unsigned long MQTTPublishInterval = 15000; // Tidsinterval for MQTT publish (15 sekunder) angivet i millisekunder.
bool debugEnabled = false;                 // Skal der udskriver til seriel port?
int updateInterval = 15000;                // Variabel til 15 sekunders opdateringsinterval angivet i millisekunder.

// === Netværksindstillinger ===
char ssid[32] = "";      // SSID til WiFi netværket.
char password[32] = "";  // WiFi adgangskode.

// === MQTT-indstillinger ===
char mqtt_server[128] = "";    // MQTT-serverens IP eller domæne.
int mqtt_port = 1883;          // MQTT-port (standard 1883).
char mqtt_user[128] = "";      // MQTT-brugernavn.
char mqtt_password[128] = "";  // MQTT-adgangskode.
char mqtt_clientID[128] = "";  // MQTT-clientID.

void setup() { // Initiel programmatiske konfigurations kode. 
  pinMode(RST_PIN, INPUT);        // Initialiser RST_PIN som et INPUT.
  pinMode(LED_BUILTIN, OUTPUT);   // Initialiser den blå LED.
  pinMode(RST_PIN, INPUT_PULLUP); // Aktiver intern pull-up modstand.
  digitalWrite(LED_BUILTIN, LOW); // Sluk den blå LED.
  Serial.begin(115200);           // Initialiserer seriel kommunikation til debugging.
  EEPROM.begin(EEPROM_SIZE);      // Initialiserer EEPROM for at gemme konfigurationer.
  sensors.begin();                // Starter temperatursensoren.
  lastCommsCheck = millis();      // Sætter første tidsværdi af variablen.
  lastLEDCheck = millis();        // Sætter første tidsværdi af variablen.
  lastMQTTpublish = millis();     // Sætter første tidsværdi af variablen.
  
  // OLED-initialisering
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Tjekker om OLED-skærmen er korrekt tilsluttet
    Serial.println(F("OLED fejl!"));
    for (;;);  // Stopper programmet, hvis skærmen ikke kan initialiseres
  }
  display.clearDisplay();  // Rydder skærmen
  display.setTextSize(1);  // Sætter tekststørrelse
  display.setTextColor(WHITE);  // Sætter tekstfarve til hvid

  // Tjek om RST-knappen er trykket under opstart
  if (digitalRead(RST_PIN) == LOW) {  // Hvis pin er LOW
    Serial.println("RST-knappen trykket. Nulstiller WiFi og MQTT konfiguration.");
    delay(1000);
    resetConfig();  // Nulstil konfiguration
  }
  
  readConfig();                   // Læs gemte konfigurationer fra EEPROM
  if (strlen(ssid) == 0 || strlen(password) == 0) { // Hvis ingen konfiguration er gemt, start konfigurationsmode
    displayMessage("WiFi konfig mangler.\n\nStarter konfig WiFi.");
    setupConfigServer();          // Starter konfigurationsserveren, så brugeren kan konfigurere WiFi og MQTT
    } else {
    // WiFi-forbindelse
    connectWiFi();                // Forbinder til WiFi, hvis SSID og adgangskode er gemt
  }

  // Webserver-ruter
  server.on("/", handleRoot);                   // Root URL, der viser temperaturdata
  server.on("/temperature", handleTemperature); // URL til at sende temperaturdata til JavaScript opdatering af handleRoot
  server.on("/config", handleConfig);           // URL til at konfigurere WiFi og MQTT
  server.on("/reset", handleReset);             // URL til at nulstille WiFi
  server.begin();  // Starter webserveren

  if (strlen(mqtt_server) > 0) { // start kun MQTT, hvis "mqtt_server" er konfigureret og gemt.
    // Hent MAC-adressen
    String mac = WiFi.macAddress();                         // Eksempel: "24:6F:28:1A:2B:3C"
    Serial.println(mac);
    mac.replace(":","");                                    // fjern kolon'er
    String lastSixChars = mac.substring(mac.length() - 6);  // Gem de sidste 6 tegn - Resultat: "2B:3C"
    String mqtt_clientID = "hottub-" + lastSixChars;        // Generer klient-id baseret på de sidste 6 bogstaver og tal wifi mac adresse
    mqttClient.setServer(mqtt_server, mqtt_port);
    if (!mqttClient.connected()) {                          // HVis MQTT ikke er forbundet,
      reconnectMQTT();                                      // Forsøg at oprette forbindelse til MQTT.
    }

    // Serial.println(String("mqtt_server   = ") + mqtt_server);
    // Serial.println(String("mqtt_port     = ") + mqtt_port);
    // Serial.println(String("mqtt-klientID = ") + mqtt_clientID);
    // Serial.println(String("mqtt_user     = ") + mqtt_user);
    // Serial.println(String("mqtt_password = ") + mqtt_password);
  }

  Serial.println(" - Debug beskeder kan aktiveres på seriel port med 'debug on' eller 'debug off'(standard).");
  Serial.println(" - Systemet kan nulstilles ved at sende 'nullify system' på seriel porten.");
}

void loop() { // Her placeres koden der skal eksekveres kontinuerligt
  server.handleClient();                                   // Lad webserveren behandle indkommende HTTP-anmodninger.
  sensors.requestTemperatures();                           // Anmoder om temperaturmåling.
  float tempC = sensors.getTempCByIndex(0);                // Hente temperatur fra den første sensor (index 0).
  updateOledScreen(tempC);                                 // Opdaterer OLED-skærmen.
  
  if (millis() - lastMQTTpublish >= MQTTPublishInterval) { // Send MQTT publish hvis det er tid til det (hver 15. sekunder)
    if (strlen(mqtt_server) > 0) {                         // Send kun MQTT, hvis mqtt_server er angivet
      if (mqttClient.connected()) {                        // Send temperaturdata til MQTT, hvis forbindelsen er aktiv
        sendTemperatureToMQTT(tempC);
      }
      lastMQTTpublish = millis();                          // Opdater lastMQTTpublish variablen til nu.
    }
  }
  
  if (millis() - lastCommsCheck >= CommsCheckInterval) {   // Tjek forbindelser hvis det er tid til det (hver 60. sekunder)
    if (WiFi.status() != WL_CONNECTED) {                   // Hvis WiFi ikke er forbundet,
      connectWiFi();                                       // Forsøg at oprette forbindelse til WiFi.
    }
    if (strlen(mqtt_server) > 0) {                         // Tjek kun MQTT, hvis mqtt_server er angivet
      if (!mqttClient.connected()) {                       // HVis MQTT ikke er forbundet,
        reconnectMQTT();                                   // Forsøg at oprette forbindelse til MQTT.
      }
    }
    lastCommsCheck = millis();                             // Opdater lastCommsCheck variablen til nu.
  }

  if (millis() - lastLEDCheck >= LEDCheckInterval) {      // Blinke funktion til indikation af at loop() køre

    // Serial.print(millis());
    // Serial.print(" - ");
    // Serial.print(lastLEDCheck);
    // Serial.print(" = ");
    // Serial.println(millis() - lastLEDCheck);
    // Serial.println("");

    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Læs den nuværende tilstand af LED_BUILTIN og sæt den modsatte værdi.
    lastLEDCheck = millis();                              // Opdater tidspunktet for LED status ændring
  }

  if (Serial.available() > 0) {                           // Håndter seriel kommando linje
    handleSerialInput();
  }
}

void handleSerialInput() { // Håndter seriel kommando linje
  String input = Serial.readStringUntil('\n');  // Læs indtil linjeskift
  input.trim();  // Fjern eventuelle mellemrum eller linjeskift

  if (input == "debug on") {
    debugEnabled = true;
    Serial.println("Debug mode aktiveret.");
  } 
  else if (input == "debug off") {
    debugEnabled = false;
    Serial.println("Debug mode deaktiveret.");
  } 
  else if (input == "nullify system") {
    Serial.println("Systemet nulstilles om 42 sekunder!");
    Serial.println("SLUK HVIS DET VAR EN FEJL-KOMMANDO!");
    int seconds =42;
    while (seconds > 0) {
      Serial.print("Tid tilbage: ");
      Serial.print(seconds);
      Serial.println(" sekunder");
      delay(1000);  // Vent 1 sekund
      seconds--;
    }
    resetConfig();
  } 
  else {
    Serial.println("--");
    Serial.println("Ugyldig kommando!");
    Serial.println("--");
    Serial.println("'debug on' eller 'debug off' tænder / slukker for debug beskeder.");
    Serial.println("'nullify system' nulstiller til fabriksinstillinger.");
  }
}

void setupConfigServer() { // Starter soft-AP (WiFi access point) til konfiguration af systemet
  String apSSID = "Hottub_Config";  // SSID for AP
  
  // Generer en tilfældig kode mellem 100000000 og 999999999
  long randomCode = random(100000000, 1000000000);
  String apPassword = String(randomCode);  // Konverter tallet til en streng
  
  // Start softAP
  WiFi.softAP(apSSID.c_str(), apPassword.c_str());
  
  // Vent et øjeblik for at sikre, at access pointet er startet
  delay(1000);
  
  // Print IP-adresse for AP
  Serial.println("Soft-AP startet.");
  
  server.on("/config", handleConfig);  // Håndterer konfigurationsanmodninger
  server.begin();  // Starter webserveren
  Serial.println("#############################");
  Serial.println("Konfigurationsserver startet.");
  Serial.println("SSID: " + apSSID);
  Serial.println("Kode: " + apPassword);
  Serial.println("Tilgå http://"+ WiFi.softAPIP().toString() + "/config for at konfigurere.");
  
  // Vist information om konfigurationsserveren på OLED-skærmen
  display.clearDisplay();   // Rydder skærmen før opdatering
  display.setCursor(0, 0);  // Sætter cursoren i øverste venstre hjørne
  display.print("- Config WiFi online!");
  display.setCursor(0, 10);
  display.print("SSID: " + apSSID);
  display.setCursor(0, 19);
  display.print("Kodeord:");
  display.setTextSize(2);    // Sætter skriftstørrelsen til 2 (standard er 1)
  display.setCursor(4, 28);
  display.print(apPassword); // Vis den tilfældige kode
  display.setTextSize(1);    // Sætter skriftstørrelsen til 1 (standard er 1)
  display.setCursor(0, 45);
  display.print(" IP:  " + WiFi.softAPIP().toString()); // Vist IP-adressen på soft-AP mode
  display.setCursor(0, 55);
  display.print("http://<IP>/config");
  display.display();        // Vis information på OLED.

  // Vi stopper her og venter på, at brugeren gemmer en gyldig konfiguration
  while (strlen(ssid) == 0 || strlen(password) == 0) {
    delay(1000);  // Fortsæt med at vente, mens vi holder serveren aktiv
    server.handleClient();  // Håndter klientanmodninger under ventetid
  }
  
  // Når en gyldig konfiguration er gemt, afslut setup og gå videre til WiFi-forbindelse
  displayMessage("Konfiguration gemt. Genstarter ESP32.");
  delay(1000);
  ESP.restart();  // Genstart for at anvende den gemte konfiguration
}

void connectWiFi() { // Tilslutter til det konfigurerede Wireless netværk
  if (strlen(ssid) == 0 || strlen(password) == 0) {
    displayMessage("Ugyldig WiFi config");
    return;
  }

  displayMessage("Forbinder til WiFi...");
  WiFi.begin(ssid, password);
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 10) {
    delay(1000);
    attempts++;
    displayMessage("Forbinder: Forsøg " + String(attempts));
    Serial.println("Forbinder til WiFi: Forsøg " + String(attempts));
  }

  if (WiFi.status() != WL_CONNECTED) {
    display.clearDisplay();  // Rydder skærmen før visning
    display.setCursor(0, 0);
    display.println("WiFi fejlede");
    
    // Vis det aktuelle SSID på linje 2
    display.setCursor(0, 16);
    display.print("SSID: ");
    display.print(ssid);  // Vist det aktuelle SSID
    
    display.display();  // Opdaterer skærmen
    Serial.println("Kunne ikke forbinde til WiFi ved opstart.");
  } else {
    displayMessage("WiFi forbundet!");
    Serial.println(WiFi.localIP());
  }
}

void reconnectMQTT() { // Forsøger at oprette forbindelse til MQTT-serveren
  int attempts = 0;
  while (!mqttClient.connected() && attempts < 3) {
    if (mqttClient.connect(mqtt_clientID, mqtt_user, mqtt_password)) {
      mqttClient.publish("hottubsensor/temperature", "Forbundet til MQTT");  // Send en testmeddelelse
    } else {
      Serial.print("FEJL, RC=");
      // RE 5 er f.eks. MQTT_CONNECT_UNAUTHORIZED - se https://pubsubclient.knolleary.net/api for flere fejl beskeder.
      Serial.print(mqttClient.state());
      Serial.println(" venter 1420 msek.");
      delay(1420);  // Vent lidt før et nyt forsøg
      attempts++;
    }
  }
  
  // Hvis MQTT-forbindelsen mislykkes efter flere forsøg
  if (!mqttClient.connected()) {
    Serial.println("Kunne ikke forbinde til MQTT efter 3 forsøg.");
  }
}

void sendTemperatureToMQTT(float temp) { // Sender aktuel temperatur til MQTT server
  if (!mqttClient.connected()) {
    return;  // Send kun, hvis MQTT er forbundet
  }
  char tempStr[10];                 // Midlertidig buffer
  dtostrf(temp, 6, 2, tempStr);     // Konverterer float til string med 2 decimaler
  mqttClient.publish("hottubsensor/temperature", tempStr);
  debugEnabled ? (void)Serial.println("MQTT publish: " + String(tempStr)) : (void)0;
}

void updateOledScreen(float temp) { // Opdatrerer OLED skærm med information under normal drift
  display.clearDisplay();   // Rydder skærmen før opdatering
  display.setCursor(0, 0);  // Sætter cursoren i øverste venstre hjørne
  
  // Vist temperatur på den første linje
  display.setTextSize(3);   // Sætter skriftstørrelsen til 3 (standard er 1)
  // display.print(" ");       // lav et mellemrum
  display.print(temp);      // Vis temperatur
  //display.print(" ");       // lav et mellemrum
  display.write(247);       // 247 er ASCII-koden for '°' i Adafruit GFX
  display.print("C");
  display.setTextSize(1);   // Gå tilbage til normal skriftstørrelse for de næste linjer
  if (debugEnabled) {
    char tempStr[10];                 // Midlertidig buffer
    dtostrf(temp, 6, 2, tempStr);     // Konverterer float til string med 2 decimaler
    Serial.println("OLED update: " + String(tempStr));
  }

  // Vist IP-adresse på den næste linje
  display.setCursor(0, 28);  // Sætter cursoren på næste linje
  display.print("IP:   ");
  display.print(WiFi.localIP());  // Vist IP-adresse
  debugEnabled ? (void)Serial.println(String("OLED update: IP = ") + WiFi.localIP().toString()) : (void)0;
  
  // Vist WiFi status på næste linje
  display.setCursor(0, 41);  // Sætter cursoren på den næste linje
  display.print("WiFi: ");
  if (WiFi.status() == WL_CONNECTED) {
    display.print(WiFi.SSID()); // Vis SSID for aktuel WiFi
    debugEnabled ? (void)Serial.println("OLED update: SSID: " + WiFi.SSID()) : (void)0;
  } else {
    display.print("Ikke forbundet");
    debugEnabled ? (void)Serial.println("OLED update: WiFi: Ikke forbundet") : (void)0;
  }

  // Vis MQTT informationer på næste linje
  display.setCursor(0, 53);  // Sætter cursoren på næste linje
  display.print("MQTT: ");
  // Vis kun MQTT status , hvis mqtt_server er angivet
  if (strlen(mqtt_server) > 0) {
    // Hvis MQTT er aktiveret, vis status, ellers "Ikke forbundet"
    display.print(mqttClient.connected() ? "Forbundet" : "Ikke forbundet");
    debugEnabled ? (void)Serial.println("OLED update: MQTT: " + mqttClient.connected() ? "Forbundet" : "Ikke forbundet") : (void)0;
  } else {
    display.print("Ikke i brug.");
    debugEnabled ? (void)Serial.println("OLED update: MQTT: Ikke i brug.") : (void)0;
  }

  display.display();  // Opdaterer skærmen med de nye data
}

void displayMessage(String message) { // Skriver tekst ca. midt på OLED skærmen
  display.clearDisplay();             // Rydder skærmen
  display.setCursor(0, 24);           // Sætter cursoren ca. midt på skærmen
  display.println(message);           // Vist besked på skærmen
  display.display();                  // Opdaterer skærmen
}

void handleRoot() { // Root URL, der viser temperaturdata
  sensors.requestTemperatures();  // Anmoder om temperaturmåling
  float tempC = sensors.getTempCByIndex(0);  // Henter temperatur fra den første sensor (index 0)
  String response;

  if (tempC == -127.0) {
    response = "<p id='temperature' class='error'>Fejl: Ingen sensor fundet</p>\n";
  } else {
    response = "<div class='meter-container'><meter id='temperatur' value='" + String(tempC) + "' min='0' max='45' low='5' high='37' optimum='40'></meter></div>";
    response += "<p id='temperature' class='temperature'>Aktuel temperatur: " + String(tempC) + " °C</p>\n";
  }

  String ipAddress = WiFi.localIP().toString();

  response += "<div class='info'>\nGå til <a href='http://" + ipAddress + "/reset'>" + ipAddress + "/reset</a> for at factory resette.<br />\n";
  response += "Gå til <a href='http://" + ipAddress + "/config'>" + ipAddress + "/config</a> for at ændre konfig.\n</div>\n";

  // Tilføjer HTML, CSS og JavaScript for dark mode og dynamisk temperatur-opdatering
  String htmlResponse = "<!DOCTYPE html>\n<html lang='da'>\n<head>\n";
  htmlResponse += "<meta charset='UTF-8'>\n";
  htmlResponse += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>\n";
  htmlResponse += "<title>Hottub Temperatur</title>\n";
  htmlResponse += "<style>\n";
  htmlResponse += "body { font-family: Arial, sans-serif; background-color: lightgray; margin: 0; padding: 0; color: darkslategray; text-align: center; transition: background-color 0.3s, color 0.3s; }\n";
  htmlResponse += "h1 { color: green; font-size: 2rem; }\n";
  htmlResponse += ".meter-container { margin-top: 20px; }\n";
  htmlResponse += "meter { width: 80%; max-width: 400px; height: 20px; }\n";
  htmlResponse += ".temperature, .error { font-size: 1.2rem; margin-top: 10px; }\n";
  htmlResponse += ".error { color: red; }\n";
  htmlResponse += ".info { color: silver; font-size: 1rem; margin-top: 20px; }\n";
  htmlResponse += ".info.dark-mode { color: dimgray; }\n";
  htmlResponse += "a, a:visited { color: silver; text-decoration: underline; }\n";
  htmlResponse += "a.dark-mode, a.dark-mode:visited { color: dimgray; text-decoration: underline; }\n";
  htmlResponse += "button { padding: 10px 20px; margin-top: 20px; cursor: pointer; background-color: green; color: white; border: none; border-radius: 5px; font-size: 1rem; }\n";
  htmlResponse += "button:hover { background-color: darkgreen; }\n";
  htmlResponse += "body.dark-mode { background-color: black; color: green; }\n";
  htmlResponse += "body.dark-mode a { color: dimgray; }\n";
  htmlResponse += "</style>\n";
  htmlResponse += "</head>\n<body>\n";
  htmlResponse += "<h1>Temperaturmåling</h1>\n";
  htmlResponse += response;
  htmlResponse += "<button id='themeToggle' onclick='toggleDarkMode()'>Dark Mode</button>\n";
  htmlResponse += "<script>\n";
  
  // Dark mode javascript funktion.
  htmlResponse += "function toggleDarkMode() {\n";
  htmlResponse += "  document.body.classList.toggle('dark-mode');\n";
  htmlResponse += "  const isDarkMode = document.body.classList.contains('dark-mode');\n";
  htmlResponse += "  localStorage.setItem('darkMode', isDarkMode);\n";
  htmlResponse += "  document.getElementById('themeToggle').textContent = isDarkMode ? 'Light Mode' : 'Dark Mode';\n";
  htmlResponse += "  // Toggler dark-mode for .info elementerne\n";
  htmlResponse += "  document.querySelectorAll('.info').forEach(function(el) {\n";
  htmlResponse += "    el.classList.toggle('dark-mode', isDarkMode);\n";
  htmlResponse += "  });\n";
  htmlResponse += "  // Toggler dark-mode for <a> elementerne\n";
  htmlResponse += "  document.querySelectorAll('a').forEach(function(el) {\n";
  htmlResponse += "    el.classList.toggle('dark-mode', isDarkMode);\n";
  htmlResponse += "  });\n";
  htmlResponse += "}\n";
  htmlResponse += "if (localStorage.getItem('darkMode') === 'true') {\n";
  htmlResponse += "  document.body.classList.add('dark-mode');\n";
  htmlResponse += "  document.getElementById('themeToggle').textContent = 'Light Mode';\n";
  htmlResponse += "  // Tilføjer dark-mode til .info elementerne ved sideindlæsning, hvis dark mode er aktivt\n";
  htmlResponse += "  document.querySelectorAll('.info').forEach(function(el) {\n";
  htmlResponse += "    el.classList.add('dark-mode');\n";
  htmlResponse += "  });\n";
  htmlResponse += "  // Tilføjer dark-mode til <a> elementerne ved sideindlæsning, hvis dark mode er aktivt\n";
  htmlResponse += "  document.querySelectorAll('a').forEach(function(el) {\n";
  htmlResponse += "    el.classList.add('dark-mode');\n";
  htmlResponse += "  });\n";
  htmlResponse += "}\n";
  
  // Javascript funktion til at opdatere temperatur dynamisk.
  htmlResponse += "function fetchTemperature() {\n";
  htmlResponse += "  fetch('/temperature').then(response => response.text()).then(data => {\n";
  htmlResponse += "    document.getElementById('temperature').innerHTML = data;\n";
  htmlResponse += "  });\n";
  htmlResponse += "}\n";
  htmlResponse += "setInterval(fetchTemperature, " + String(updateInterval) + ");\n";
  
  htmlResponse += "</script>\n";
  htmlResponse += "</body>\n</html>";

  server.send(200, "text/html", htmlResponse);
}

void handleTemperature() { // URL til at sende temperaturdata til JavaScript opdatering af handleRoot
  sensors.requestTemperatures();
  float tempC = sensors.getTempCByIndex(0);
  if (tempC == -127.0) {
    server.send(200, "text/html", "<p class='error'>Fejl: Ingen sensor fundet</p>");
  } else {
    server.send(200, "text/html", "Aktuel temperatur: " + String(tempC) + " °C");
  }
}

void handleReset() { // Håndterer web kald til /reset (websiden som kan bruges til at nulstille systemet)
  String input = server.arg("reset_input");  // Få input fra formularen
  
  // Tjek om input er "reset", uanset store/små bogstaver
  if (input.equalsIgnoreCase("reset")) {
    server.send(200, "text/plain", "System nulstilles og genstartes.");  // Vist besked om nulstilling
    resetConfig();  // Nulstil konfiguration.
    delay(3000);    // Vent tre sekunder.
    ESP.restart();  // Genstart ESP32.
  } else {
    // Hvis input ikke er korrekt, send en fejlbesked
    String htmlResponse = "<!DOCTYPE html>\n<html lang='da'>\n<head>\n";
    htmlResponse += "<meta http-equiv='refresh' content='15'>\n";
    htmlResponse += "<meta charset='UTF-8'>\n";
    htmlResponse += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>\n";
    htmlResponse += "<title>Hottub Temperatur</title>\n";
    htmlResponse += "</head>\n<body style='color: red; text-align: center;'>\n";
    htmlResponse += "<h1>Skriv 'reset' i tekstboksen for, at nulstille systemet.</h1>\n";
    htmlResponse += "<form method='POST' action='/reset'>\n";
    htmlResponse += "<input type='text' name='reset_input' placeholder='Skriv her' required>\n";
    htmlResponse += "<input type='submit' value='Nulstil'>\n";
    htmlResponse += "</form>\n";
    htmlResponse += "</body>\n</html>";
    server.send(400, "text/html", htmlResponse);  // Send fejlmeddelelse hvis input er forkert
  }
}

void handleConfig() { // Håndterer web kald til /config (webside hvor systemet kan konfigureres)
  // Hvis det er en POST-anmodning (brugeren indsender data)
  if (server.method() == HTTP_POST) {
    // Læs konfigurationsdata fra webformularen og gem dem i variablerne
    strncpy(ssid, server.arg("ssid").c_str(), sizeof(ssid));
    strncpy(password, server.arg("password").c_str(), sizeof(password));
    strncpy(mqtt_server, server.arg("mqtt_server").c_str(), sizeof(mqtt_server));
    mqtt_port = server.arg("mqtt_port").toInt();  // Konverterer MQTT-porten fra tekst til integer
    if (mqtt_port <= 0 || mqtt_port > 65535) {  // Validering af portnummer
      mqtt_port = 1883;  // Standard MQTT-port, hvis den er ugyldig
    }
    strncpy(mqtt_user, server.arg("mqtt_user").c_str(), sizeof(mqtt_user));
    strncpy(mqtt_password, server.arg("mqtt_password").c_str(), sizeof(mqtt_password));
    
    // Gem konfigurationen i EEPROM
    saveConfig();
    
    // Bekræftelse og information
    server.send(200, "text/plain", "Konfiguration gemt. Genstart ESP32.");
    delay(1000);
    ESP.restart();  // Genstart for at anvende den nye konfiguration
  } else {
    // Returner HTML-formular for konfiguration
    String html = "<!DOCTYPE html><html lang='da'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>Konfiguration</title>";
    html += "<style>";
    html += "body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f9; color: #333; }";
    html += "h1 { text-align: center; color: #4CAF50; }";
    html += ".container { max-width: 600px; margin: 20px auto; padding: 20px; background-color: white; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); }";
    html += "input[type='text'], input[type='password'], input[type='number'] { width: 100%; padding: 12px; margin: 8px 0; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }";
    html += "input[type='submit'] { background-color: #4CAF50; color: white; padding: 14px 20px; border: none; border-radius: 4px; cursor: pointer; width: 100%; }";
    html += "input[type='submit']:hover { background-color: #45a049; }";
    html += "label { font-weight: bold; margin-top: 10px; display: block; }";
    html += ".info { text-align: center; font-size: 14px; color: #777; margin-top: 20px; }";
    html += "</style>";
    html += "</head><body>";
    html += "<div class='container'>";
    html += "<h1>Konfiguration</h1>";
    html += "<form method='POST'>";
    html += "<label for='ssid'>SSID:</label><input id='ssid' name='ssid' type='text' required><br>";
    html += "<label for='password'>Password:</label><input id='password' name='password' type='password' required><br>";
    html += "<label for='mqtt_server'>MQTT Server:</label><input id='mqtt_server' name='mqtt_server' type='text'><br>";
    html += "<label for='mqtt_port'>MQTT Port:</label><input id='mqtt_port' name='mqtt_port' type='number' value='1883' min='1' max='65535'><br>";
    html += "<label for='mqtt_user'>MQTT User:</label><input id='mqtt_user' name='mqtt_user' type='text'><br>";
    html += "<label for='mqtt_password'>MQTT Password:</label><input id='mqtt_password' name='mqtt_password' type='password'><br>";
    html += "<input type='submit' value='Gem Konfiguration'>";
    html += "</form>";
    html += "<div class='info'>Undlad MQTT-information, hvis den ikke skal bruges.</div>";
    html += "</div></body></html>";

    server.send(200, "text/html", html);  // Sender formularen som HTML
  }
}

void resetConfig() { // Nulstiller al konfigurations data
  memset(ssid, 0, sizeof(ssid));
  memset(password, 0, sizeof(password));
  memset(mqtt_server, 0, sizeof(mqtt_server));
  memset(mqtt_user, 0, sizeof(mqtt_user));
  memset(mqtt_password, 0, sizeof(mqtt_password));
  mqtt_port = 1883;  // Default port

  // Initialiser EEPROM med den ønskede størrelse
  if (!EEPROM.begin(EEPROM_SIZE)) {
    Serial.println("EEPROM initialization failed.");
    return;
  }

  // Rydder EEPROM ved at skrive 0xFF til hver byte (fjerner gamle data)
  for (int i = 0; i < EEPROM_SIZE; i++) {
    EEPROM.write(i, 0xFF);  // Write 0xFF to each byte
  }
  
  Serial.println("Skriver til alle adresser.");
  
  saveConfig();  // Funktion til at gemme den opdaterede (tomme) konfiguration til EEPROM

  display.clearDisplay();  // Rydder skærmen
  display.setTextSize(2);   // Sætter skriftstørrelsen til 2
  display.println("WiFi og MQTT");
  display.println(" nulstillet.");
  display.println("Genstarter!");
  Serial.println("WiFi og MQTT nulstillet. Genstarter ESP32.");
  delay(3000);
  ESP.restart();  // Restart the ESP32
}

void saveConfig() { // Gemmer konfigurationen i EEPROM
  EEPROM.put(0, ssid);
  EEPROM.put(32, password);
  EEPROM.put(64, mqtt_server);
  EEPROM.put(128, mqtt_port);
  EEPROM.put(132, mqtt_user);
  EEPROM.put(164, mqtt_password);
  EEPROM.commit();  // Bekræft gemt data
}

void readConfig() { // Læs konfigurationen fra EEPROM
  EEPROM.get(0, ssid);
  EEPROM.get(32, password);
  EEPROM.get(64, mqtt_server);
  EEPROM.get(128, mqtt_port);
  EEPROM.get(132, mqtt_user);
  EEPROM.get(164, mqtt_password);
}

Diverse links

Afslutning

Dette projekt giver dig en robust ESP32-baseret temperaturmåler med webinterface og mulighed for MQTT-dataoverførsel. Fejlhåndteringen sikrer, at enheden fungerer stabilt, selv under netværksproblemer. Du kan nemt nulstille Wi-Fi og MQTT-konfiguration via webinterfacet.