ESP32 AsyncWebserver mit 2 physikalischen Buttons

Zur Optimierung eine ansonsten schon funktionierenden Projekts, möchte ich einen Assyncchronen Webserver installieren. Ich habe dazu auf Youtube folgendes Beispiel gefunden, was im Grunde das leistet, was ich benötige:

Allerdings möchte ich (da halt fertig) nicht auf die In-/Output-Pins verzichten, die ich aktuell eingerichtet habe:
Input-Pins: 2,4,5,15
Output-Pins: 12,13,25,26
Also habe ich den Code testweise auf ein baugleiches Board (AZ-Delivery ESP32 Dev Kit C V4) installiert und getestet.
Interessanterweise flimmert immer Switch1 (egal welchen pin ich darauf lege), Switch 2 funktioniert entweder oder je nach pin auf Switch 1, geht dann auch mal gar nichts.
hier mal mein Code:

#ifdef ESP32  // ESP32 libraries
#include <WiFi.h>
#include <AsyncTCP.h>
#else  // ESP8266 libraries
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#include "settings.h"

//******Enter your network credentials************
// daten in settings.h
//const char* WIFI_SSID = "Your Network WIFI_SSID";
//const char* WIFI_PASS = "Your network WIFI_PASS";
//************************************************

//Define Status
const char *PARAM_INPUT_1 = "state";
const char *PARAM_INPUT_2 = "state2";

//**********Pin Assignments***********
const int relay1 = 25;  
const int switch1 = 5;  
const int relay2 = 26;  
const int switch2 = 15;  
//************************************

//*********************Variables declaration**********************
int relay_1_status = LOW;        // the current status of relay1
int switch_1_status;             // the current status of switch1
int last_switch_1_status = LOW;  // Last status of switch1
int relay_2_status = LOW;        // the current status of relay2
int switch_2_status;             // the current status of switch2
int last_switch_2_status = LOW;  // Last status of switch2
//****************************************************************

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 500;   // the debounce time; increase if the output flickers

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>NodeMCU based Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #F63E36; border-radius: 34px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
input:checked+.slider {background-color: #3CC33C}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>
</head>
<body>
<h2>NodeMCU based Web Server</h2>
%BUTTONPLACEHOLDER%
%BUTTONPLACEHOLDER2%
<script>
function toggleCheckbox(element)
{
var xhr = new XMLHttpRequest();
if(element.checked)
{
xhr.open("GET", "/update?state=1", true);
}
else
{
xhr.open("GET", "/update?state=0", true);
}
xhr.send();
}
 
function toggleCheckbox2(element)
{
var xhr2 = new XMLHttpRequest();
if(element.checked)
{
xhr2.open("GET", "/update?state2=1", true);
}
else
{
xhr2.open("GET", "/update?state2=0", true);
}
xhr2.send();
}
 
setInterval(function ( )
{
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function()
{
if (this.readyState == 4 && this.status == 200)
{
var inputChecked;
var outputStateM;
 
if( this.responseText == 1)
{
inputChecked = true;
outputStateM = "ON";
}
else
{
inputChecked = false;
outputStateM = "OFF";
}
document.getElementById("output").checked = inputChecked;
document.getElementById("outputState").innerHTML = outputStateM;
}
}
xhttp.open("GET", "/state", true);
xhttp.send();
 
var xhttp2 = new XMLHttpRequest();
xhttp2.onreadystatechange = function()
{
if (this.readyState == 4 && this.status == 200)
{
var inputChecked2;
var outputStateM2;
 
if( this.responseText == 1)
{
inputChecked2 = true;
outputStateM2 = "ON";
}
else
{
inputChecked2 = false;
outputStateM2 = "OFF";
}
document.getElementById("output2").checked = inputChecked2;
document.getElementById("outputState2").innerHTML = outputStateM2;
}
};
xhttp2.open("GET", "/state2", true);
xhttp2.send();
 
}, 1000 ) ;
</script>
</body>
</html>
)rawliteral";

// Replaces placeholder with button section in your web page
String processor(const String &var) {
  //Serial.println(var);
  if (var == "BUTTONPLACEHOLDER") {
    String buttons1 = "";
    String outputStateValue = outputState();
    buttons1 += "<h4>Device 1 - Status <span id=\"outputState\"><span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";
    return buttons1;
  }

  if (var == "BUTTONPLACEHOLDER2") {
    String buttons2 = "";
    String outputStateValue2 = outputState2();
    buttons2 += "<h4>Device 2 - Status <span id=\"outputState2\"><span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox2(this)\" id=\"output2\" " + outputStateValue2 + "><span class=\"slider\"></span></label>";
    return buttons2;
  }
  return String();
}

String outputState() {
  if (digitalRead(relay1)) {
    return "checked";
  } else {
    return "";
  }
  return "";
}
String outputState2() {
  if (digitalRead(relay2)) {
    return "checked";
  } else {
    return "";
  }
  return "";
}

void setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(relay1, OUTPUT);
  digitalWrite(relay1, LOW);
  pinMode(switch1, INPUT_PULLDOWN);

  pinMode(relay2, OUTPUT);
  digitalWrite(relay2, LOW);
  pinMode(switch2, INPUT_PULLDOWN);

  // Connect to Wi-Fi
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/html", index_html, processor);
  });

  // Send a GET request
  server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request) {
    String inputMessage;
    String inputParam;

    // GET input1 value on <ESP_IP>/update?state=<inputMessage>
    if (request->hasParam(PARAM_INPUT_1)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value();
      inputParam = PARAM_INPUT_1;
      digitalWrite(relay1, inputMessage.toInt());
      relay_1_status = !relay_1_status;
    } else {
      inputMessage = "No message sent";
      inputParam = "none";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", "OK");

    String inputMessage2;
    String inputParam2;

    if (request->hasParam(PARAM_INPUT_2)) {
      inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
      inputParam2 = PARAM_INPUT_2;
      digitalWrite(relay2, inputMessage2.toInt());
      relay_2_status = !relay_2_status;
    } else {
      inputMessage2 = "No message sent";
      inputParam2 = "none";
    }
    Serial.println(inputMessage2);
    request->send(200, "text/plain", "OK");
  });

  // Send a GET request to <ESP_IP>/state
  server.on("/state", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", String(digitalRead(relay1)).c_str());
  });

  server.on("/state2", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", String(digitalRead(relay2)).c_str());
  });
  // Start server
  server.begin();
  Serial.println("Setup erledigt");
}

void loop() {
  int reading1 = digitalRead(switch1);
  /*if (reading1 != last_switch_1_status) {
    Serial.println("Switch 1 gedrückt");
    lastDebounceTime = millis();  // reset the debouncing timer
  }*/

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading1 != switch_1_status) {
      switch_1_status = reading1;
      if (switch_1_status == HIGH) {
        relay_1_status = !relay_1_status;
        Serial.println("Switch 1 gedrückt");
    lastDebounceTime = millis();
      }
    }
  }

  int reading2 = digitalRead(switch2);
 /*if (reading2 != last_switch_2_status) {
    Serial.println("Switch 2 gedrückt");
    lastDebounceTime = millis();
  }*/

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading2 != switch_2_status) {
      switch_2_status = reading2;
      if (switch_2_status == HIGH) {
        relay_2_status = !relay_2_status;
        Serial.println("Switch 1 gedrückt");
    lastDebounceTime = millis();
      }
    }
  }
  // set the LED:
  digitalWrite(relay1, relay_1_status);
  digitalWrite(relay2, relay_2_status);

  // save the reading. Next time through the loop, it'll be the lastButtonState:
  last_switch_1_status = reading1;
  last_switch_2_status = reading2;
}

Wie Ihr seht, arbeite ich mit INPUT_PULLDOWN.
Aber auch Versuche mit externen Pullup-Widerständen funktionierten nicht.
Probeweise habe ich den den ganzen Async-Teil mal aus dem Code rausgenommen:

//**********Pin Assignments***********
const int relay1 = 25;  // D0 Pin of NodeMcu, change it if you are using ESP32
const int switch1 = 5;  // D6 Pin of NodeMcu, change it if you are using ESP32
const int relay2 = 26;  // D1 Pin of NodeMcu, change it if you are using ESP32
const int switch2 = 15;  // D5 Pin of NodeMcu, change it if you are using ESP32

//*********************Variables declaration**********************
int relay_1_status = LOW;        // the current status of relay1
int switch_1_status;             // the current status of switch1
int last_switch_1_status = LOW;  // Last status of switch1
int relay_2_status = LOW;        // the current status of relay2
int switch_2_status;             // the current status of switch2
int last_switch_2_status = LOW;  // Last status of switch2

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 500;   // the debounce time; increase if the output flickers
void setup() {
  Serial.begin(115200);

  pinMode(relay1, OUTPUT);
  digitalWrite(relay1, LOW);
  pinMode(switch1, INPUT_PULLDOWN);

  pinMode(relay2, OUTPUT);
  digitalWrite(relay2, LOW);
  pinMode(switch2, INPUT_PULLDOWN);

}

void loop() {
  // put your main code here, to run repeatedly:
int reading1 = digitalRead(switch1);
  /*if (reading1 != last_switch_1_status) {
    Serial.println("Switch 1 gedrückt");
    lastDebounceTime = millis();  // reset the debouncing timer
  }*/

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading1 != switch_1_status) {
      switch_1_status = reading1;
      if (switch_1_status == HIGH) {
        relay_1_status = !relay_1_status;
        Serial.println("Switch 1 gedrückt");
    lastDebounceTime = millis();
      }
    }
  }

  int reading2 = digitalRead(switch2);
 /*if (reading2 != last_switch_2_status) {
    Serial.println("Switch 2 gedrückt");
    lastDebounceTime = millis();
  }*/

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading2 != switch_2_status) {
      switch_2_status = reading2;
      if (switch_2_status == HIGH) {
        relay_2_status = !relay_2_status;
        Serial.println("Switch 1 gedrückt");
    lastDebounceTime = millis();
      }
    }
  }
  // set the LED:
  digitalWrite(relay1, relay_1_status);
  digitalWrite(relay2, relay_2_status);

  // save the reading. Next time through the loop, it'll be the lastButtonState:
  last_switch_1_status = reading1;
  last_switch_2_status = reading2;
}

...das funktioniert dann problemlos.
Wo habe ich meinen Denkfehler?

Hallo, und willkommen,
damit wir deinen Sketch richtig lesen können und dieser sauber vom Rest getrennt wird, solltest du den in Code-Tags setzen.

...ich war noch bei der Korrektur :wink:

Ok, alles klar.
Was ich eben gesehen habe, du arbeitest mit "PullDown-Widerstände" ?
Soweit mir das bekannt ist, gibt es die auf dem ESP8266 nicht. Zumindest nicht die internen Widerstände (INPUT_PULLDOWN).

Da solltest du allerdings eine Fehlermeldung erhalten.

Wie geschrieben benutze ich einen ESP32 Dev Kit C V4 (von AZ-Delivery) nach meinen Informationen sind für die 4 INPUT-pins PULLDOWN-Widerstände eingerichtet.
Wie auch geschrieben hat aber auch ein Versuch mit externen PULLUP-Widerständen nicht funktioniert.
...und (auch schon gepostet) ohne den Assync-server funktioniert es ja.
Ausserdem funktionieren die Buttons bei meinem Original-Projekt ja auch solange ich nicht die (synchrone) server-client Option nutze (danach wartet der server halt auf den client und es loopt nicht mehr)

Ok, dann solltest du besser nicht ein Beitrag posten, der einen ESP8266 zeigt.
Das irritiert.
Was den Assync-Server betrifft, habe ich keine Idee, da können andere helfen.

Ok, jetzt verstehe ich es,
meine Formulierung:

..auf ein baugleiches Board (AZ-Delivery ESP32 Dev Kit C V4)...

ist missverständlich, nicht baugleich zum ESP8266 aus dem Video sondern bauchleich zum Board dass ich in meinem Original-Projekt nutze.
Den link von dem ich den Code zu posten halte ich ansonsten für eine unbedingt erforderliche Form des Respekts gegenüber dem Ersteller.

Ok, alles klar. Jetzt auch für mich. Sorry...:wink:

Demo-Code zwei On/off-Schalter mit ESPUI

#include <ESPUI.h>
#include <SafeString.h>

cSF(myLabel_SS, 512);
cSF(multiPurp_SS, 512);

#if defined(ESP32)
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif

const char *home_ssid     = "your SSID";
const char *home_password = "your password";

uint16_t statusLabel_ID;
uint16_t switch1_ID;
uint16_t switch2_ID;

byte switch1State;

unsigned long MyTestTimer = 0;                   // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED = 2;     // onboard-LEDESP32 / ESP8266


void defineGUI() {
  statusLabel_ID = ESPUI.label("Label-Title", ControlColor::Turquoise, "0");

  switch1_ID     = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, Control::noParent, &switch1CallBack);
  switch2_ID     = ESPUI.addControl(ControlType::Switcher, "Switch two", "", ControlColor::None,     Control::noParent, &switch1CallBack);  
}


void switch1CallBack(Control* sender, int value){
    switch (value)  {
      
    case S_ACTIVE:
        Serial.print("Switch 1 Active:");
        break;

    case S_INACTIVE:
        Serial.print("Switch1 Inactive");
        break;
    }

    Serial.print(" ");
    Serial.println(sender->id);
}


void switch2CallBack(Control* sender, int value){
    switch (value)  {
      
    case S_ACTIVE:
        Serial.print("Switch 1 Active:");
        break;

    case S_INACTIVE:
        Serial.print("Switch1 Inactive");
        break;
    }

    Serial.print(" ");
    Serial.println(sender->id);
}


void ChangeSwitch1Position() {
  switch1State = ESPUI.getControl(switch1_ID)->value.toInt();
  switch1State = !switch1State; 
  
  ESPUI.updateSwitcher(switch1_ID, switch1State);  
}


void setLabelText() {

  // anzuzeigenden Text zusammensetzen
  myLabel_SS  = "Label";
  myLabel_SS += " Zeile 1:";
  myLabel_SS += "\n";  //   "\n" macht Zeilen-Umbruch => nachfolgender Text landet in Zeile darunter

  myLabel_SS += " Zeile 2:";
  myLabel_SS += "\n";  //   "\n" macht Zeilen-Umbruch => nachfolgender Text landet in Zeile darunter

  myLabel_SS += " Zeile 3:";
  myLabel_SS += millis();
  myLabel_SS += "\n";  //   "\n" macht Zeilen-Umbruch => nachfolgender Text landet in Zeile darunter

  // Webseite mit dem Inhalt der SafeString-Variable  myTuerStatus_SS updaten
  ESPUI.updateControlValue(statusLabel_ID, myLabel_SS.c_str() );
}



void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  
  ESPUI.setVerbosity(Verbosity::VerboseJSON);
  PrintFileNameDateTime();
  connectToWiFi();
  defineGUI();

  ESPUI.begin("I am the website created by the ESPUI-Demo");
}


void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 500);

  if ( TimePeriodIsOver(MyTestTimer, 1000) ) {
    setLabelText();
    ChangeSwitch1Position();
  }
}



void connectToWiFi() {

  // try to connect to existing network
  WiFi.begin(home_ssid, home_password);
  Serial.print("\n\nTry to connect to existing network");
  Serial.print(" named #");
  Serial.print(home_ssid);
  Serial.println("#");

  uint8_t timeout = 10;

  // Wait for connection, 5s timeout
  do {
    BlinkHeartBeatLED(OnBoard_LED, 100);
    delay(500);
    Serial.print(".");
    timeout--;
  } while (timeout && WiFi.status() != WL_CONNECTED);

  // not connected -> create hotspot
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print("\n\n no connection to SSID #");
    Serial.print(home_ssid);
  }

  Serial.print("IP address: ");
  Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
  Serial.println("type this IP-adress into your browser to connect to the GUI of your ESP");
}


void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


/* Erklärung der Webseiten-Definition
  statusLabel_ID = ESPUI.label("Türenstatus", ControlColor::Turquoise, "0");
  // statusLabel_ID     : number that is used to identfy the GUI-element
  // .label             : the type of the GUI-element
  // "Number of Clicks" : the text that describes the GUI-element
  // ControlColor::Turquoise : explains ITSELF
  // "0" initial VALUE shown on the element (will be updated on runtime)
*/

Hallo,
ganz verstanden habe ich Dich nicht.
Du hast eine Anwendung bei der Du mittels "Schalter" ein Relais ein und aus schaltest.
schalter ein > Relais ein / Schalter aus Relais aus
jetzt willst Du zusätzlich über eine Webseite die Relais ebenfalls schalten können. wie auf deinem Eingangspost abgebildet mit "Software-Schalter"

Ich denke mal das wird so nichts werden weil Du es mit zwei statischen Zuständen zu tun hast , einmal der "Hardware Schalter" und zum zweiten der "Sofware Schalter" . Nach welcher Logik sollte das Relais schalten. Du kannst das ja mal mit zwei Schaltern versuchen. Je einen Schalter an einen pin und dann eine LED schalten. Das wird nix werden, vermutlich wird die LED "flackern" Wenn Du allerdings zwei Taster nimmst dann kannst Du mit beiden verodert eine Variable toggeln.

Was Du meiner Meinung nach machen kannst ist, du nutzt einen "Taster" und toggelst damit eine Statusvariable. Auf der Webseite nutzt Du einen Button wenn du den drückst wird das mit einer "1" gesendet.
Auf dem ESP kannst Du dann die beiden Signale , HW-Taster und Button verodern und eine Statusvariable für das Relais damit toggeln. Im response für die Webseite überträgst Du dann den Status ebenfalls um die Farbe des Buttons , oder den Text zu ändern.

Nachtrag
ich habe gerade gesehen für die Hardware Taster machst Du das ja , da toggelst Du

Das kann schon funktionieren.
Man muss halt nicht den Zustand auswerten, sondern den Flankenwechsel und diesen mit dem aktuellen Zustand vergleichen.

Hallo,
@HotSystems ich habs später auch gesehen , bei den HW macht er es so. Ich bin aber nicht sicher ob er das auch auf der Webseite macht. Deshalb meine Idee da buttons zu nehmen. dieses xhr im script Teil ist etwas unbekannt für mich.
Button click ... fetch api in einem form mittels post wäre so meine Variante.

Ja, ok...seinen Sketch habe ich nicht danach durchsucht.
Das wird allgemein auch etwas umfangreicher.

Zumal eine Webseite im LittleFS doch deutlich übersichtlicher ist.

@StefanL38
Danke für den Code,
leider zeigt der Code den gleichen Effekt, Switch 1 "flimmert".

lass mal den Webserver weg.

Hast du einen für dich funktionierenden Sketch wo die Ausgänge auf deine Eingänge richtig reagieren?

Kannst du das mal posten?

Also ich würde das Wort "flimmern" benutzen wenn der Switch mindestens 4 bis 10 mal pro Sekunde die Stellung wechselt.

In meinem Demo-Code wird hier

Einmal pro Sekunde vom Programm die Schalterstellung gewechselt.
Als Demo-Code wie man das Schalterstellung wechseln vom Code aus macht
ohne den Switch auf der Internetseite zu betätigen.

Nochmal hier zur Erklärung;
Mein Originalprojekt (eine Gartenbewässerung mit lcd2004 display und RTC-Clock[falls WIFI inaktiv ist muss der programmierte Zeitplan trotzdem funktinieren]) funktioniert so weit mit einem "synchronen" Webserver, das ergibt nur 3 kleine Problemchen:

  • wenn ich die bei aufgerufener WIFI-Seite den Button vor Ort drücke wird die Seite nicht automatisch aktualisiert
  • es gibt einen bekannten bug bezüglicht client.connected(), dieser meldet häufug immer noch true, obwohl die client-server-Verbindung getrennt ist. Da "klemmt" mein code dann teilweise mehrere Minuten in der Schleife; in der Zeit haben die Buttons vor Ort dann keine Funktion
  • ...eine Folge aus den beiden vorherigen Punkten, wenn ich über einen zweiten Client verbinde, funktionert der möglicherweise auch nicht und zeigt auch nicht den aktuellen Status an.
    All diese Problemchen hätte ich mit einem asynchronen Webserver abfangen können.

Noch einmal explizit die Frage:

wechselt der switch eins in meinem Demo-Code 1 mal pro Sekunde regelmäßig die Stellung?

oder

wechselt switch 1 unregelmäßig die Stellung ?
Wenn es unregelmäßig ist
beschreibe diese Unregelmäßigkeit so präzise wie möglich

ah, ok....
das habe ich übersehen.
Es sah halt genau so aus wie beim AssyncWebserver, da flimmert es halt auch nur ca. 1x pro Sekunde, was aber nur an diversen delays liegt.

wie gesagt, dein Code scheint das zu tun was er soll