ESP32 WEBSERVER

mit CallMeBot API für Telegram und WhatsApp Verbindungsnachricht

    Vorlage:
        https://randomnerdtutorials.com/esp32-web-server-gauges/
    CallMeBot:
        https://www.callmebot.com/blog/telegram-text-messages/
        https://www.callmebot.com/blog/whatsapp-messages-from-esp8266-esp32/

Image

Beschreibung

In diesem Projekt wird ein Webserver mit einem ESP32 über WiFi realisiert. Als Vorlage wurde hier das Random Nerd Tutorial Projekt gewählt. Dieses wurde um zwei Erweiterungen ergänzt. Erstens die Zeitausgabe die mit einem SSD1306 128x64 I2C Display dargestellt wird und zweitens das versenden einer Nachricht auf Telegram oder WhatsApp mit der CallMeBot API wenn der ESP32 sich mit dem WLAN verbindet.

Komponenten

Hardware:
    ESP32
    DHT22
    SSD1306 Display
    4.7kΩ Wiederstand
    Verbindungskabel
Software:
    IDE: VSCode PlatformIO oder Arduino
    API: CallMeBot API
Programmiersprache:
    C/C++

Pinbelegung

    DHT22:
        DATA = D5 + 3V3(4.7K Ohm)
        VCC = 3V3
        GND = GND
    SSD1306:
        SCL = D21
        SDA = D22
        VCC = 3V3
        GND = GND

PlatformIO.ini

    ; PlatformIO Project Configuration File
    ;
    ;   Build options: build flags, source filter
    ;   Upload options: custom upload port, speed and extra flags
    ;   Library options: dependencies, extra library storages
    ;   Advanced options: extra scripting
    ;
    ; Please visit documentation for the other options and examples
    ; https://docs.platformio.org/page/projectconf.html

    [env:esp32dev]
    platform = espressif32
    board = esp32dev
    framework = arduino
    upload_speed = 921600
    monitor_speed = 115200
    lib_deps = 
        plageoj/UrlEncode@^1.0.1
        esphome/AsyncTCP-esphome@^2.1.3
        esphome/ESPAsyncWebServer-esphome@^3.2.0
        arduino-libraries/Arduino_JSON@^0.2.0
        arduino-libraries/NTPClient@^3.2.1
        adafruit/Adafruit GFX Library@^1.11.9
        adafruit/Adafruit SSD1306@^2.5.10
        beegee-tokyo/DHT sensor library for ESPx@^1.19
        ciniml/WireGuard-ESP32@^0.1.5

C++ SourceCode

    // Driver from AZ Delivery: https://cdn.shopify.com/s/files/1/1509/1638/files/ch340.zip?v=1683899825

    /* LINUX
    Bekannte Probleme bei Verbindungsversuch unter Linux.

        --- A fatal error occurred: Could not open /dev/ttyUSB0, the port doesn’t exist ---

    Mit dem Befehl "sudo chown your_username /dev/ttyUSB0" kann dieser behoben werden.
    */

    /*Bibliotheken*/
    #include <Arduino.h>
    #include <WiFi.h>
    #include <AsyncTCP.h>
    #include <ESPAsyncWebServer.h>
    #include "SPIFFS.h"
    #include <Arduino_JSON.h>
    #include <Wire.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>
    #include <WiFiUdp.h>
    #include <NTPClient.h>
    #include <DHTesp.h>
    #include <HTTPClient.h>
    #include <UrlEncode.h>
    #include <time.h>

    /*Initialisierung des SSD1306 Display*/
    #define SCREEN_WIDTH 128 // OLED display width, in pixels
    #define SCREEN_HEIGHT 64 // OLED display height, in pixels
    #define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
    #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3C for 128x64, 0x3D for 128x32
    Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

    /*Ändern für WLAN Name und Passwort*/
    const char* ssid = " ";
    const char* password = " ";

    /*CallMeBot Verifikationsdaten für WhatsApp*/
    const String phoneNumber = " ";
    const String apiKey = " ";

    char wochentage[7][12] = {"So","Mo", "Di", "Mi", "Do", "Fr", "Sa"};

    /*Timer Variablen*/ 
    unsigned long lastTime = 0;
    unsigned long timerDelay = 10000;
    const int Sommerzeit = 7200;
    const int Winterzeit = 3600;

    /*Objekt Erstellung*/
    WiFiUDP ntpUDP;
    NTPClient timeClient(ntpUDP, "pool.ntp.org");
    DHTesp dht;
    HTTPClient http;
    WiFiClient client;
    AsyncWebServer server(80);
    AsyncEventSource events("/events");
    JSONVar readings;

    /*
    Methode sendMessage():
      CallMeBot(WhatsApp / Telegram) Konfiguration
    */ 
    void sendMessage (String message){
      // Daten die zur Verifikation gesendet werden
      String whatsapp = "http://api.callmebot.com/whatsapp.php?phone=" + phoneNumber + "&apikey=" + apiKey + "&text=" + urlEncode(message);
      String telegram = "http://api.callmebot.com/text.php?user=[@TELEGRAM_USERNAME]&text=" + urlEncode(message) + "&html=no&links=no";
      http.begin(client, telegram);
      //http.begin(client, whatsapp);

      // Header
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");

      // HTTP Post request senden
      int httpResponseCode = http.POST(telegram); // Hier telegram oder whatsapp auswählen für Endpunkt
      if (httpResponseCode == 200) {
        Serial.println("Message sent successfully");
      } else {
        Serial.println("Error sending the message");
        Serial.print("HTTP response code: ");
        Serial.println(httpResponseCode);
      }
      http.end();
    }

    // Get Sensor Readings and return JSON object
    String getSensorReadings(){
      readings["humidity"] = String(dht.getHumidity()); 
      readings["temperature"] = String(dht.getTemperature());
      String jsonString = JSON.stringify(readings);
      return jsonString;
    }

    /*Ausgabe für SSD1306 Display*/ 
    void localResponse(){
       float hum = dht.getHumidity();
       float tmp = dht.getTemperature();

       if (isnan(hum)||isnan(tmp)){         // Abfrage ob DHT22 Sensordaten gelesen werden können
        Serial.print("Keine Sensorwerte");  // Serial Monitor Ausgabe wenn keine DHT22 Sensordaten verfügbar sind
       } else {                             // Wenn Sensordaten vorhanden Ausgabe über SSD1306 Display
         oled.setTextSize(0);

         oled.setCursor(0,45);
         oled.print("Temp: ");
         oled.setCursor(35,45);
         oled.print(tmp);
         oled.print(" C");

         oled.setCursor(0,55);
         oled.print("Hum: ");
         oled.setCursor(35,55);
         oled.print(hum);
         oled.print(" %");

         oled.display();
         }

       }

    void ntpTimer(){
      // Deklarierung der Ganzzahl Variablen für Tag, Stunde, Minute, Sekunde
      int aktuellerTag = timeClient.getDay();
      int aktuelleStunde = timeClient.getHours();
      int aktuelleMinute = timeClient.getMinutes();
      int aktuelleSekunde = timeClient.getSeconds();

      String formattedTime = timeClient.getFormattedTime();

      int middle_string = ((6 * formattedTime.length()));

      timeClient.update();

      char timestamp[20];
      sprintf(timestamp, "%s, %2d:%2d:%2d", wochentage[aktuellerTag], aktuelleStunde, aktuelleMinute, aktuelleSekunde);

      oled.clearDisplay();
      oled.setTextSize(2);
      oled.setTextColor(SSD1306_WHITE);
      oled.setCursor(((SCREEN_WIDTH / 2) + ( - (middle_string))), ((SCREEN_HEIGHT / 2) / 2));
      oled.print(formattedTime);
      oled.setTextSize(1);
      oled.setCursor(0,0);
      oled.print(wochentage[aktuellerTag]);

      oled.setTextSize(1);
      oled.setCursor(0,45);
      oled.print("");

      // Aufruf der Methode localResponse(), syncronisiert sich mit der ntpTimer() Methode (Zeitausgabe)
      localResponse();

      oled.display();
    }

    /*
    Methode initSPIFFS():
      Initialisierung SPIFFS = Serial Peripheral Interface Flash File System
    */
    void initSPIFFS() {
      if (!SPIFFS.begin()) {
        Serial.println("An error has occurred while mounting SPIFFS");
      }
      Serial.println("SPIFFS mounted successfully");
    }

    /*
    Methode initWiFi():
      Ausgabe über SSD1306 Display bei Verbindung.
      Initialisierung WiFi.
      Wenn WiFi verbunden ist dann senden der Nachricht über Telegram oder Whatsapp.
    */ 
    void initWiFi() {
      oled.clearDisplay();
      oled.setTextSize(1);
      oled.setTextColor(SSD1306_WHITE);
      oled.setCursor(0,0);
      oled.print("Verbindet mit SSID");
      oled.setCursor(64 ,20);
      oled.print(ssid);
      oled.display();

      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid, password);
      Serial.print("Verbindet mit WiFi ...");
      while (WiFi.status() != WL_CONNECTED) {
        Serial.print('.');
        delay(1000);
      }

      if (WiFi.status() == WL_CONNECTED){
        sendMessage("ESP32 /" + WiFi.localIP().toString() + " ist Online"); // Die Nachricht die gesendet wird wenn der ESP32 sich mit dem WLAN verbindet.
       }

      oled.clearDisplay();
      oled.setTextSize(1);
      oled.setTextColor(SSD1306_WHITE);
      oled.setCursor(0,0);
      oled.print("Server gestartet");
      oled.setCursor(0,20);
      oled.print("IP-Adresse");
      oled.setCursor(0,30);
      oled.print(WiFi.localIP().toString());
      oled.display();
      delay(10000); // 10 sek
      oled.clearDisplay();

      Serial.println(WiFi.localIP());
    }

    void setup() {

      // SSD1306 Display
      if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)){
        Serial.println(F("SSD1306 allocation failed"));
        for(;;);
      }

      Serial.begin(115200);
      initWiFi();
      initSPIFFS();
      timeClient.begin();
      timeClient.setTimeOffset(Sommerzeit);
      dht.setup(5, DHTesp::DHT22); // DHT22 auf D5

      // Web Server Root URL
      server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(SPIFFS, "/index.html", "text/html");
      });

      server.serveStatic("/", SPIFFS, "/");

      // Request for the latest sensor readings
      server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){
        String json = getSensorReadings();
        request->send(200, "application/json", json);
        json = String();
      });

      events.onConnect([](AsyncEventSourceClient *client){
        if(client->lastId()){
          Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
        }
        // send event with message "hello!", id current millis
        // and set reconnect delay to 1 second
        client->send("hello!", NULL, millis(), 10000);
      });
      server.addHandler(&events);

      // Start server
      server.begin();
    }

    void loop() {
      if ((millis() - lastTime) > timerDelay) {
        // Send Events to the client with the Sensor Readings Every 10 seconds
        events.send("ping",NULL,millis());
        events.send(getSensorReadings().c_str(),"new_readings" ,millis());
        lastTime = millis();
      }
      if(WiFi.status() == WL_CONNECTED){
        ntpTimer();
      } 
    }

Erklärung zum Sourcecode

Da es sich hier speziell um eine Erweiterung handelt würde ich zur genauen Beschreibung auf das Original von Random Nerd Tutorial verweisen. Die Erklärung die hier folgt beinhaltet nur meinen erweiterten Sourcecode.

Erweiterungen

Aufbau