Which WEB server to use?

Hello,

As a rather new developer in Arduino and it must be +25 year since I lasted used C++ I do have sort of frustration if I choose the right WEB server for my ESP32C6 solution.

Here is the challenge I seem to have: I have 8 curtain motors (Somfy Glydea 35 RTS) which I control from an ESP32-C6 board (Sparkfun Quick). Of the 8 ESP32-C6 cards, 5 works perfect after 3 years or something. So that leaves the 3 that still kind of works, however the response from them is always delayed, between 1 to 3 seconds, and sometimes it just fails. It is not often it fails, that is maybe once every 6-9 months, however the delay is a bit more frustrating. Now all 8 cards run the exact same compiled software. Well except one of the 3 above which I kind of hammering with different examples of web server to see if that changes the challenge.

Se it like this: Either one has direct response (the 5 working ESP32-C6) or the command sent is delayed with 1-3 seconds. For me there is no reason for this strange behavior, the same kind of ESP-card and compiled software (with the exception that one of the remaining 3 is some sort of development work on - it does however never change this delay behavior). But I still wonder if my very old knowledge in C++ is the reason this happens? Or if for some reason I somehow managed to set the ESP32-C6 into some sort of low power CPU setting (no need, it is powered over 230v outlets into the Glydea motor, which converts it to 3.3v which I feed direct into ESP32-C6 pin for 3.3v - and it has more than enough to drive the card, I even tried a stabilizing solution, no change).

So what I would love is for someone to check my code, and tell me what I might improve - or if I should change web server (webserver.h - but I have tested the Async version also - no difference)? Any ideas or comments are welcome - and please do remember it is a looooong time since I used C++, consider me a beginner (heck I have used IBM Mainframe Assembler for just a few years ago so I seem to know that better, although I am actually more of a SQL person nowdays).

#include <WiFi.h>
#include <WebServer.h>

#include <EEPROM.h>
#define EEPROM_SIZE 1

#define GPIO_OPEN 2
#define GPIO_CLOSE 3
#define GPIO_MYPOSITION 18

#define CURTAIN_UNKNOWN 0
#define CURTAIN_CLOSE 1
#define CURTAIN_OPEN 2
#define CURTAIN_MYPOSITION 3
byte curtain_status = 0;

// -------------------------------------------------------------------

const char* WIFI_NAME = "xxxx";
const char* WIFI_PASSWORD = "xxx";

#define SERVERPORT 80
WebServer server(SERVERPORT);

#define WEBPAGESIZE 500
char webpage[WEBPAGESIZE];

// -------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(2000);

  EEPROM.begin(EEPROM_SIZE);
  curtain_status = EEPROM.read(0);

  pinMode(GPIO_CLOSE, INPUT);
  pinMode(GPIO_OPEN, INPUT);
  pinMode(GPIO_MYPOSITION, INPUT);

  Serial.print("Connecting to ");
  Serial.println(WIFI_NAME);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print(" done!\nSignal strength: ");
  Serial.println(String(WiFi.RSSI()));
  Serial.print("Webserver started at http://");
  Serial.println(WiFi.localIP());

  server.on("/", handlewebpage_root);
  server.on("/status", handlewebpage_status);
  server.on("/restart", handlewebpage_restart);
  server.on("/open", handlewebpage_open);
  server.on("/close", handlewebpage_close);
  server.on("/myposition", handlewebpage_myposition);
  server.on("/changedirection", handlewebpage_changedirection);
  server.on("/storemyposition", handlewebpage_storemyposition);
  
  server.begin();
}

// -------------------------------------------------------------------
void loop() {

  server.handleClient();

//  delay(1);

}

// -------------------------------------------------------------------
void handlewebpage_root() {

//  Serial.println("Called root");

  SendHTML();
}

// -------------------------------------------------------------------
void handlewebpage_open() {

//  Serial.println("Called open");

  curtain_status = CURTAIN_OPEN;
  EEPROM.write(0, curtain_status);
  EEPROM.commit();

  pinMode(GPIO_OPEN, OUTPUT);
  pinMode(GPIO_MYPOSITION, INPUT);
  pinMode(GPIO_CLOSE, INPUT);
  digitalWrite(GPIO_OPEN, LOW);
  delay(200);
  pinMode(GPIO_OPEN, INPUT);

  SendHTML();
}

// -------------------------------------------------------------------
void handlewebpage_close() {

//  Serial.println("Called close");

  curtain_status = CURTAIN_CLOSE;
  EEPROM.write(0, curtain_status);
  EEPROM.commit();

  pinMode(GPIO_OPEN, INPUT);
  pinMode(GPIO_MYPOSITION, INPUT);
  pinMode(GPIO_CLOSE, OUTPUT);
  digitalWrite(GPIO_CLOSE, LOW);
  delay(200);
  pinMode(GPIO_CLOSE, INPUT);

  SendHTML();
}

// -------------------------------------------------------------------
void handlewebpage_myposition() {

//  Serial.println("Called myposition");

  curtain_status = CURTAIN_MYPOSITION;
  EEPROM.write(0, curtain_status);
  EEPROM.commit();

  pinMode(GPIO_OPEN, INPUT);
  pinMode(GPIO_MYPOSITION, OUTPUT);
  pinMode(GPIO_CLOSE, INPUT);
  digitalWrite(GPIO_MYPOSITION, LOW);
  delay(200);
  pinMode(GPIO_MYPOSITION, INPUT);

  SendHTML();
}

// -------------------------------------------------------------------
void handlewebpage_changedirection() {

//  Serial.println("Called changedirection");

  pinMode(GPIO_OPEN, OUTPUT);
  pinMode(GPIO_MYPOSITION, OUTPUT);
  pinMode(GPIO_CLOSE, OUTPUT);
  digitalWrite(GPIO_OPEN, LOW);
  digitalWrite(GPIO_MYPOSITION, LOW);
  digitalWrite(GPIO_CLOSE, LOW);
  delay(4200);
  pinMode(GPIO_OPEN, INPUT);
  pinMode(GPIO_MYPOSITION, INPUT);
  pinMode(GPIO_CLOSE, INPUT);
  delay(500);
  pinMode(GPIO_MYPOSITION, OUTPUT);
  digitalWrite(GPIO_MYPOSITION, LOW);
  delay(2500);
  pinMode(GPIO_MYPOSITION, INPUT);
  
  SendHTML();
}

// -------------------------------------------------------------------
void handlewebpage_storemyposition() {

//  Serial.println("Called storemyposition");

  pinMode(GPIO_OPEN, INPUT);
  pinMode(GPIO_MYPOSITION, OUTPUT);
  pinMode(GPIO_CLOSE, INPUT);
  digitalWrite(GPIO_MYPOSITION, LOW);
  delay(4500);
  pinMode(GPIO_MYPOSITION, INPUT);
  
  SendHTML();
}

// -------------------------------------------------------------------
void handlewebpage_restart() {

//  Serial.println("Called restart");

  snprintf(webpage, WEBPAGESIZE, "<!DOCTYPE HTML><html>Restart</html>");
  server.send(200, "text/html", FPSTR(webpage));

  esp_restart();
}

// -------------------------------------------------------------------
void handlewebpage_status() {

//  Serial.println("Called status");

  if (curtain_status == CURTAIN_OPEN) { snprintf(webpage, WEBPAGESIZE, "<!DOCTYPE HTML><html>Open</html>"); }
  if (curtain_status == CURTAIN_CLOSE) { snprintf(webpage, WEBPAGESIZE, "<!DOCTYPE HTML><html>Close</html>"); }
  if (curtain_status == CURTAIN_MYPOSITION) { snprintf(webpage, WEBPAGESIZE, "<!DOCTYPE HTML><html>MyPosition</html>"); }

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

// -------------------------------------------------------------------
void SendHTML() {

//  Serial.println("Called SendHTML");

  snprintf(webpage, WEBPAGESIZE, " \
  <!DOCTYPE html> <html> \
  <head><title>Curtain Control</title></head> \
  <body> \
  <h1><a href=""/"">Curtain Web Server v5.1</a></h1> \
  <a href=\"/open\">Open</a><br> \
  <a href=\"/close\">Close</a><br> \
  <a href=\"/myposition\">My Position</a><br> \
  <a href=\"/changedirection\">Change Direction</a><br> \
  <a href=\"/storemyposition\">Store My Position</a><br> \
  <a href=\"/restart\">Restart</a><br> \
  <a href=\"/status\">Status\n</a> \
  </body></html>");

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

And for reference I have updated Arduino to Arduino IDE 2.3.8 - no errors/warnings when compiling.

Well, I have no idea what might help you right now, based on that code and the problem description.

Assuming all the boards have the same code, connect to the same WiFi network, and none of them have hardware issues, there shouldn't be any differences in behavior. If you say these slowdowns happen rarely (every 6-9 months is "rare" for me), they seem more like transient issues than anything structural.
For example, have you checked the WiFi signal strength on each board? And have you checked for radio interference (e.g., other WiFi networks, perhaps a neighbor's, or other devices that emit radio frequencies)?

PS: aside from that, though I understand that changing strategy now is probably complex for you, I would have structured things differently. I'd have used UDP for the communication rather than the much more "heavy" and relatively complicated HTTP. Thus delegating "manual" (or automatic..) command management to a local application on a PC, or a tablet, or even another Arduino acting just as the "master" (i.e. the only one that will and can expose a web server to control the USP packets to be sent to the devices).

Ah sorry for being a bit unclear:

The three ESP32-C6 boards always responses with a delay.
And then once in a while they simply never responds, that is every 6-9 months.

Regarding wifi interference: Well that could be an issue, if it was not for... Let me explain: I have four (4) windows, and each window has TWO (2) Somfy Glydea Motors, with an ESP32-C6 board attached to each motor. Now the three (3) boards that is giving me the challenge, they are either to inside or the outside curtain on three (3) different windows. So there is no obvious solution to wifi interference, since well if one of the two cards on each window is disturbed by the other, then it should be one of the two in some kind of random. It is never random, it is always the same three (3) motors that are delayed.

I have been thinking of running only four (4) boards in total, however they will be on different potentials (earth) since one motors power supply will power one card, which in turn will control two motors, and that will not work in my mind. Call med old-school or something...

Edit: All eight ESP32-C6 are in fact controlled from Home Assistant, which sends CURL commands for requested function. The original thought was to do ZigBee software solution and that is why I choose the ESP32-C6 board - well then I realized that ZigBee and WiFi most likely would not play nice together...

Edit2: Just to be more specific: When I installed all 8 ESP32-C6 cards, they ALL responded direct. It was over time that first one ESP32-C6 card started to behave with delay, then thw 2nd and since half a year ago the third one. So say at least 6 months before the first one started to delay requests, another year or so for the 2nd. So in the end, all 8 will I think go into this delay behavior... It is just a question of time....

At this point (excluding other issues connected to Home Assistant manager, and/or your WiFi Access point and/or router) I can't think of anything except some kinda hardware failure of ESP32 wiFi chip that's somehow "degrading".
To test this, buy a couple of other ESP32s (I recommend getting them from another store/seller, hopefully from a different batch...) and swap them out for the currently "faulty" ones to see if they work properly or not.
This "degradation" seems pretty strange to me, but from your data and your answers (that exclude interference or anything else I tried) I can't find any other explanations.

The ESP32 does not have a real EEPROM, but rather a flash EEPROM.
assuming that you chance your curtain setting once a day, after about 3 yrs you are going beyond the guaranteed life cycle (a 1000 writes if i am correct) of the EEPROM cell.

Don't know if that is the cause but it is just not right to use the flash EEPROM the way you do.

That would be in line with the expectation. Easy test would be to change the EEPROM address and see what happens.

docdoc: I concur, so I ordered a bunch (10) the other day - considering it has been some time from the original order they will be from different batch for sure :slight_smile:

Deva_Rishi: So How do I store the "current" curtain position so it survives a reboot after power-failure and such funny happenings? For reference the curtains in question is moved multiple times per 24h, so it is very safe to say it is well beyond the 1000 writes...

Edit: Normal operation is that both inner and outer curtain move on the same time, so if it is a wrote EEPROM challenge, then both ESP32-C6 should behave more or less the same? And they don't.

This seems like a possible explanation, congratulations! I had missed this detail: that the code continuously writes to EEPROM! I know the reliability limit for the ESP32 EEPROM is between 10,000 and 100,000 writes (on the same cell), but I actually believe that some "degradation" caused by the use of EEPROM is possible.

Question about the EEPROM issue then: Since the delay is from the response before the WEB server responds back to CURL (in the normal case of Home Assistant controlling it all), or at least that is what I can see when I use it on a USB cable to my PC running Arduino IDE serial port monitor and well the delayed response when I give a CURL request from HA - the delay is always there, how is the EEPROM part of that delay? I read/write the EEPROM when in a request - not just for presenting the WEB page? Do I make any sense about what I am asking?

Guaranteed write cycles is one thing, how many actual cycles before degradation occurs is something else.

either a true (SPI) EEPROM or a support IC that has one or a remote storage.

It is of course also a matter of 'why do you need it ?' You could simply get the curtains to calibrate when the ESPs are powered up.

If the delay is always there also on the pages that do not write to the EEPROM, then it probably isn't the EEPROM causing the issue.

There is no way for me to know from the Somfy Glydea Motor where the curtain is at. There is no feedback from that motor that I could use (nowdays Somfy has a ZigBee implementation, but 8 motors for 800 usd (each, that is 6400 usd just to go ZigBee) - and I already have kind-of-control thru the ESP32-C6). So I have to guestimate where it is....

If I use the /status request (does not read/write the EEPROM at all) - the delay is there, just is if I use /open (which writes the EEPROM). The only time the EEPROM is read is at boot to see where the curtain is.

On old threads the solution was to periodically "move" the written area, e.g. by adding an "offset" and copying the data from one area to the next one.
I see you just use a single byte, so first of all make sure to write the byte only when the status changes (and not each time you call that function). Something like:

...
void handlewebpage_open() {
  if (curtain_status != CURTAIN_OPEN) {
    EEPROM.write(0, CURTAIN_OPEN);
    EEPROM.commit();
  }
  curtain_status = CURTAIN_OPEN;
...

Then, add a variable to point to the current address. To avoid writing that value on a fixed position (that voids the target of not using always the same byte), you could fill the "old" position with a conventional value, like 255 (add it as a "#define CURTAIN_EMPTY 255").
On program start just search for the first "non-255" byte address and store it on a global variable, e.g. "int currentAddress". Then add a function to write a new value, like "EepromWrite(byte value)" where you just write CURTAIN_EMPTY on "currentAddress", then add 1 to "currentAddress", check its value to keep it between the memory boundary (e.g. mod 4096), and finally write the "value" to that new "currentAddress".
This way you have access to the same EEPROM byte once in 4096 state changes...

Then the EEPROM is not at fault.

It is only about writing.

Anyway there must be some other cause.

Fully opening or closing it at boot should give you more accurate information. I am assuming you have switches at the extremes.

I will definitely look into this - very large thanks!

For the record, or something, the Somfy Glydea motor has three positions: Open, Close and My Position (only when curtain is at stop/not moving). Now My Position doubles as "Stop" if the curtain is moving - it is so to speak the same "button" or command in this case, dual purpose.

(And I can not for the name of it find a quote button in this forum - where is it?)

When you select a section of text you want to quote, it should show up by itself right next to the text.

Back to the issue at hand. Can you confirm that it is the callback that is called with a delay or that the transmission (send() ) is taking longer to be executed than expected.

You page is never longer than 512 bytes i guess, but shouldn't your char-array be null terminated ?

More common practice is to declare a local String and fill that up with the webpage and just send that.

found another little thing

It isn't stored in PROGMEM, in fact you've just modified it, but then don't use the PROGMEM function.

There could just be an issue in the router they are all connecting to.
Give that a power cycle so all units get a new IP address assigned.

I know I am old - but this I missed completely - okay :slight_smile: Big thanks for pointing my in the right direction!

489 bytes for the moment - can not say it is an issue. However null I thought was handled by the compiler for C++ but that might not be the case for Arduino? Well I added \0 - it can not hurt as I see this.

PROGMEM - You completely lost me here, I thought I do not use that keyword, so what am I missing?

My router is OPNSense running on ProxMox (which also happens to host Home Assistant plus some more stuff)). I have restarted OPNSense just w week ago, and ProxMox two weeks ago (both updated to latest level and so on). Made no difference, and well I guess I reboot OPNSense at least each month (although this month it is more like 7 times?). And do remember that I had this challenge around 3 years now.

Question on that: Would turning on, in Arduino IDE that is, "erase all flash when uploading sketch" do any difference here? I can easily test it I guess - I'll be back :wink:

Thanks - implemented! The move of the EEPROM I will have to look at later today.

Well guess what, it is a bit to early to be happy, but two out of three now works. The third has not been updated yet with erase flash turned on (it just works for the moment - don't ask why). I just updated all 8 before turning on "erase all flash". And well status was close to the same, except 6 worked direct, and two was delayed as usually. So thoose two got the erase flag set, and they directly started to work as expected.

Now there is that anomaly of the card that used to work with delay, that has not got the erase flag in Arduino IDE set, but works for the moment after latest flash (v5.2 after docdoc's suggestion about reducing EEPROM writes). I will let that card "ride on" for the moment, to see if it needs another flash. I just needs to see if this is a erase flash thing or something to do with the flashing in it self?! I'll be back...