ben hier nu dagen aan het sukkelen en vind het probleem niet.

ik heb een ESP die 2 paginas genereerd, eentje is vast geschreven in het programma (const char), de 2de moet dynamisch gegenereerd worden (grafieken). die 2de gaat corrupt als er meer dan 1872 karakters in zitten.
dus ik heb IDENTIEK dezelfde html code in die const char gestoken met 1875 karakters. de const char werkt en de string versie niet

als de ESP start en ik surf naar "http://ipadres/" dan krijg ik de correcte grafiek
als ik surf naar "http://ipadres/GRAPH" is het corrupt.
als ik 1 regel weghaal en minder dan 1873 karakters heb, werken beide

dus 1872chars in die html: OK, alles werkt en correct (foto van browser in view-source)

1873chars in die html, gaat die in het midden corrupt. de corruptie is 95% van de tijd altijd op dezelfde plaats. als ik daar dus 2 regels wissel, die black met die 60,407, dan zal de 60,407 regel corrupt zijn op dezelfde plaats, namelijk "filltext"

de webserver zelf is met ESPAsyncwebserver. die aanvaard geen strings in de send_P dus laat ik een procedure een string schrijven en die string zet ik om met c_str().

omdat ik dacht dat het hier misschien mis loopt, heb ik een serial.println gemaakt van die String en van die String.c_str().
in serial monitor is de data 2x hetzelfde, niks corrupt.

dit is het testprogramma met 2x dezelfde HTML erin, alles van berekenen en verwerken is er volledig uit. de loop doet enkel een print in seriel monitor van de data



//V9a: added graph in the system, get it by http://192.168.1.x/GRAPH.=> corrupted data
//V9b: delete many routines to find the corruption

#define versie "Versie 9b"
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

String newHostname = "serialHTML8";

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

// Create an Event Source on /events
AsyncEventSource events("/events");

String IP;

//################################################# Initialize ...
void connectWifi(){
  //WiFi.setOutputPower(5);
  String wifi = "wifiloods";
  ssid = "***********";
  const char* password = "************";
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password,11);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! trying backup network...");
    wifi = "wifihuis";
    ssid = "************";
    const char* password = "***********";
    WiFi.begin(ssid, password);
    //delay(5000);
    while (WiFi.waitForConnectResult() != WL_CONNECTED) {
      Serial.println("Connection Failed! Rebooting...");
      delay(5000);
      ESP.restart();
    }
  }
  IP = WiFi.localIP().toString();
  Serial.println(IP);
}


String Printgraph() {
  String ptr = "<canvas id='myCanvas' width='1440' height='410'\n";  //dynamisch??
  ptr += "style='border:1px solid #d3d3d3;'>Your browser does not support the canvas element.</canvas>\n";
  ptr += "<script>var canvas = document.getElementById('myCanvas');var ctx = canvas.getContext('2d');";
  ptr += "ctx.fillStyle = '#FFFFFF';ctx.fillRect(0,0,1440,410);ctx.fillStyle = '#F0F0F0';ctx.fillRect(0,50,1440,50);ctx.fillRect(0,150,1440,50);ctx.fillRect(0,250,1440,50);ctx.fillRect(0,350,1440,50);ctx.beginPath(); ";
  ptr += "ctx.strokeStyle = 'red';  ctx.moveTo(0,300);  ";
  ptr += "\n   ctx.moveTo(1,300);";
  ptr += "\n   ctx.lineTo(100,286); ";
  ptr += "\n ctx.stroke(); ";//ctx.font = '20px Arial'; ";
  ptr += "\n ctx.fillStyle = 'red';ctx.fillText('Usage 08/12/2025 (2668 Wp - total 3250Wh)',30,40); ";
  ptr += "\n ctx.beginPath();ctx.strokeStyle = 'blue';  ctx.moveTo(0,300);   "; 
  ptr += "\n   ctx.moveTo(1,400);";
  ptr += "\n   ctx.lineTo(1300,310); ";
  ptr += "\n ctx.stroke(); ";
  ptr += "\n ctx.font = '12px Arial'; ";
  ptr += "\n ctx.fillText('60V',10,55); ";
  ptr += "\n ctx.fillText('50V',10,105); ";
  ptr += "\n ctx.fillText('40V',10,155); ";
  ptr += "\n ctx.fillText('30V',10,205); ";
  ptr += "\n ctx.fillText('20V',10,255); ";
//draw tekst on X axle
  ptr += "\n ctx.stroke();";
  ptr += "\n ctx.fillStyle = 'black';";
  ptr += "\n ctx.fillText('\\'',60,407); ";
  ptr += "\n ctx.fillText('\\'',120,407); ";
  ptr += "\n ctx.fillText('\\'',180,407); ";
  ptr += "\n ctx.fillText('\\'',240,407); ";
  ptr += "\n ctx.fillText('\\'',300,407); ";
  ptr += "\n ctx.fillText('\\'',360,407); ";
  ptr += "\n ctx.fillText('\\'',420,407); ";
  ptr += "\n ctx.fillText('\\'',480,407); ";
  ptr += "\n ctx.fillText('\\'',540,407); ";
  ptr += "\n ctx.fillText('\\'',600,407); ";
  ptr += "\n ctx.fillText('\\'',660,407); ";
  ptr += "\n ctx.fillText('\\'',720,407); ";
  ptr += "\n ctx.fillText('\\'',780,407); ";
  ptr += "\n ctx.fillText('\\'',840,407); ";
  ptr += "\n ctx.fillText('\\'',900,407); ";
  ptr += "\n ctx.fillText('\\'',960,407); ";
  ptr += "\n ctx.fillText('\\'',1020,407); ";
  ptr += "\n ctx.fillText('\\'',1080,407); ";
  ptr += "\n ctx.fillText('\\'',1140,407); ";
  ptr += "\n ctx.fillText('\\'',1200,407); ";
  ptr += "\n ctx.fillText('\\'',1260,407); ";
  ptr += "\n ctx.fillText('\\'',1320,407); ";
  ptr += "\n ctx.fillText('\\'',1380,407); ";
  ptr += "\n ctx.fillText('\\'',1440,407); ";
  ptr += "\n ctx.fillText('60',54,410); ";
  ptr += "\n ctx.fillText('120',110,410); ";
  ptr += "\n ctx.fillText('180',170,410); ";
  ptr += "\n ctx.fillText('240',230,410); ";
  ptr += "\n ctx.fillText('300',290,410); ";
  ptr += "\n ctx.fillText('360',350,410); ";
 // ptr += "\n ctx.fillText('420',410,410); ";
  ptr += "\n ctx.stroke(); ";
  ptr += "\n</script>";
  ptr += "test";
  return ptr;
}

//################################################# HTML code
const char index_html[] PROGMEM = R"rawliteral(
<canvas id='myCanvas' width='1440' height='410'
style='border:1px solid #d3d3d3;'>Your browser does not support the canvas element.</canvas>
<script>var canvas = document.getElementById('myCanvas');var ctx = canvas.getContext('2d');ctx.fillStyle = '#FFFFFF';ctx.fillRect(0,0,1440,410);ctx.fillStyle = '#F0F0F0';ctx.fillRect(0,50,1440,50);ctx.fillRect(0,150,1440,50);ctx.fillRect(0,250,1440,50);ctx.fillRect(0,350,1440,50);ctx.beginPath(); ctx.strokeStyle = 'red';  ctx.moveTo(0,300);  
   ctx.moveTo(1,300);
   ctx.lineTo(100,286); 
 ctx.stroke(); 
 ctx.fillStyle = 'red';ctx.fillText('Usage 08/12/2025 (2668 Wp - total 3250Wh)',30,40); 
 ctx.beginPath();ctx.strokeStyle = 'blue';  ctx.moveTo(0,300);   
   ctx.moveTo(1,400);
   ctx.lineTo(1300,310); 
 ctx.stroke(); 
 ctx.font = '12px Arial'; 
 ctx.fillText('60V',10,55); 
 ctx.fillText('50V',10,105); 
 ctx.fillText('40V',10,155); 
 ctx.fillText('30V',10,205); 
 ctx.fillText('20V',10,255); 
 ctx.stroke();
 ctx.fillstyle = 'black';
 ctx.fillText('\'',60,407); 
 ctx.fillText('\'',120,407); 
 ctx.fillText('\'',180,407); 
 ctx.fillText('\'',240,407); 
 ctx.fillText('\'',300,407); 
 ctx.fillText('\'',360,407); 
 ctx.fillText('\'',420,407); 
 ctx.fillText('\'',480,407); 
 ctx.fillText('\'',540,407); 
 ctx.fillText('\'',600,407); 
 ctx.fillText('\'',660,407); 
 ctx.fillText('\'',720,407); 
 ctx.fillText('\'',780,407); 
 ctx.fillText('\'',840,407); 
 ctx.fillText('\'',900,407); 
 ctx.fillText('\'',960,407); 
 ctx.fillText('\'',1020,407); 
 ctx.fillText('\'',1080,407); 
 ctx.fillText('\'',1140,407); 
 ctx.fillText('\'',1200,407); 
 ctx.fillText('\'',1260,407); 
 ctx.fillText('\'',1320,407); 
 ctx.fillText('\'',1380,407); 
 ctx.fillText('\'',1440,407); 
 ctx.fillText('60',54,410); 
 ctx.fillText('120',110,410); 
 ctx.fillText('180',170,410); 
 ctx.fillText('240',230,410); 
 ctx.fillText('300',290,410); 
 ctx.fillText('360',350,410); 
 ctx.stroke(); 
</script>test)rawliteral";


void webserverhandle() {
  // Handle Web Server
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", index_html); //deze werkt correct

  });

  server.on("/GRAPH", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", Printgraph().c_str()); //deze is corrupt

  });
  server.begin();
}

void setup() {
    Serial.begin(115200);
  connectWifi();
  webserverhandle();
}

void loop() {
  Serial.println(Printgraph());         //als string is alles OK
  Serial.println(Printgraph().c_str()); //dezelfde string met c_str() via serial.println werkt ook correct
  delay(5000);

}

Geen ervaring mee, maar als ik zoek in de documentatie dan staat er een specifiek stuk opgenomen voor “grote content”. Helpt het als je het conform deze manier doet?

Send large webpage from PROGMEM:

const char index_html[] PROGMEM = "..."; // large char array, tested with 14k
request->send_P(200, "text/html", index_html);

Pakketgrootte kan gelimiteerd zijn door bijv. de MTU size...

Weet je zeker dat je genoeg RAM hebt voor de dynamische HTML-generatie?

Je maar een C++ String door steeds '+=' te gebruiken. Iedere keer moet er een nieuwe, langere string op de heap gemaakt worden, voordat de oudere vrijgegeven kan worden.
En door fragmentatie past het wellicht net niet helemaal lekker.

Geen idee wat er gebeurt als dat niet meer past, maar het zal niet fraai zijn.

maar het werk wel door diezelfde string in serial.println te zetten, en ook door die dan nog eens om te zetten naar chars.

in een ander esp programma maakt ik een html van 8600 karakters op dezelfde manier en die werkt feilloos. zelfde ESP, 5x zo lang.
ook daar maakt ik lange strings voor logfiles (die ik in ram houd) en daar heb ik nog 20.9kB vrij van de 30kB ram. het valt dus allemaal nog goed mee.

de strings maak ik aan in het subroutine en doe dan een return. de string bestaat dan niet meer in ram

Op woensdag 10 december 2025 15:10:04 schreef Arco:
Pakketgrootte kan gelimiteerd zijn door bijv. de MTU size...

diezelfde string staat eronder in const char en die raakt wel door. zou de mtu beperkt zijn, zou het bij beide falen.
de originele html file in die const char was zelf veeeeel groter

Op woensdag 10 december 2025 15:05:40 schreef rudig76:
Geen ervaring mee, maar als ik zoek in de documentatie dan staat er een specifiek stuk opgenomen voor “grote content”. Helpt het als je het conform deze manier doet?

Send large webpage from PROGMEM:

const char index_html[] PROGMEM = "..."; // large char array, tested with 14k
request->send_P(200, "text/html", index_html);

dat doe ik ook. die const char uit het 2de deel is zo opgebouwd. maar dan zit ik met een vast bepaalde html code en ik moet ze dynamisch kunnen geneneren uit een tabel.

const char index_html[] PROGMEM = ....

zie alleen het verschil niet waarom de ene wel 1875 karakters kan weergeven, en de andere corrupt gaat, met beide chars.

in andere ESP programmas gebruik ik de ESP8266WebServer.h met veeeeeeeeeeeeeeeeeeeeeel grotere strings. maar in deze situatie moet ik ook data kunnen toevoegen, vandaar die async webserver

Een string zelf als return value van een subroutine is nooit een goed idee (dat moet dan via de stack)
Beter alles in die sub afhandelen, of een pointer als return geven.

Op woensdag 10 december 2025 16:29:39 schreef Arco:
Een string zelf als return value van een subroutine is nooit een goed idee (dat moet dan via de stack)
Beter alles in die sub afhandelen, of een pointer als return geven.

Ik denk ook dat het in String Printgraph() fout gaat, de variable "ptr" gaat out of scope en je string is dan (potentieel) stuk? Of gaat door copy-elision de boel nog goed?

Ander moet je het zoeken in het buffer management van de html server? In de code van request->send_P() zelf dus.

Op woensdag 10 december 2025 15:49:15 schreef fcapri:

in een ander esp programma maakt ik een html van 8600 karakters op dezelfde manier en die werkt feilloos. zelfde ESP, 5x zo lang.

Heb je dit programma al in een andere ESP geprobeerd?

Mogelijkheid 1:
String ptr wordt hier gebruikt om de HTML pagina op te bouwen. Telkens als je een stuk data toevoegt wordt de string groter en moet de heap dus een groter stuk geheugen leveren. Het vorige stuk kan dan nog niet vrijgegeven worden want dat moet nog naar de nieuwe buffer gecopiëerd worden. Dan krijg je al snel fragmentatie. Dat kun je oplossen door aan het begin al de juiste hoeveelheid data voor de string te reserveren. dmv ptr.reserve(2000)

Maar het kan ook zijn dat het hier fout gaat:


server.on("/GRAPH", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", Printgraph().c_str()); //deze is corrupt

server.on krijgt een pointer naar de data. En die data verdwijnt zodra server.on afgelopen is. De data is dan gecopieerd naar de xmit buffer als het goed is. Maar bij lange berichten past dat niet en dan wordt de data in 2 stukken verstuurd. Maar de data is er niet meer tegen de tijd dat het tweede blok wordt verstuurd.
Dat kan je oplossen door de string van PrintGraph() naar een static String variabele te copieren.

PS: Een string (of een andere grote data structuur) aanmaken in een functie en dan als return value terug geven is tegenwoordig geen enkel probleem meer. Vroeger wel, want toen werd de hele string eerst lokaal aangemaakt en vervolgens gecopiëeerd. Maar tegenwoordig doen alle compilers aan "return value optimisation", en dat houdt in dat er geen tussen-variabele wordt aangemaakt, dus de string wordt direct in de return variabele opgebouwd.

PS2:
Je gebruikt asyncWebserver(). Op zich niks mis mee. Maar Async betekent wel dat de interface non-blocking is. Dwz dat de SendP funktie niet wacht tot de data verstuurd is, maar direct terugkomt zodat je tijdens transmit nog andere dingen kan doen. Dat gaat alleen goed als je zeker weet dat de data niet verandert voordat alles verstuurd is.

[Bericht gewijzigd door deKees op (10%)]

Op woensdag 10 december 2025 16:52:17 schreef benleentje:
[...]Heb je dit programma al in een andere ESP geprobeerd?

ja, 2 ESP8266, de ene op de hardware aangesloten met een INA228, vandaag en blanke ESP8266 genomen en had identiek hetzelfde probleem.
heb dan alles wat niet nodig was om html te genereren eruit gegooid en nog bleef de html op exact dezelfde plaats corrupt gaan.

Op woensdag 10 december 2025 19:20:05 schreef deKees:
Mogelijkheid 1:
String ptr wordt hier gebruikt om de HTML pagina op te bouwen. Telkens als je een stuk data toevoegt wordt de string groter en moet de heap dus een groter stuk geheugen leveren. Het vorige stuk kan dan nog niet vrijgegeven worden want dat moet nog naar de nieuwe buffer gecopiëerd worden. Dan krijg je al snel fragmentatie. Dat kun je oplossen door aan het begin al de juiste hoeveelheid data voor de string te reserveren. dmv ptr.reserve(2000)

ptr.reserver(50000) l geprobeerd (ook kleinere waardes). heb zelf eens met char ptr[5000] geprobeerd en daar telkens
strcpy(ptr,"");
strcat(ptr,"<canvas id='myCanvas' width='1440' height='410'\n");
strcat(ptr, "style='border:1px solid #d3d3d3;'>Your browser does not support the canvas element.</canvas>\n");
strcat(ptr, "<script>var canvas = document.getElementById('myCanvas');var ctx = canvas.getContext('2d');");

gedaan, dat werkte wel maar is beperkt in geheugen.
daarna kwam ik dan op het idee om heel die opbouw daarna in een serial.println te zetten en daar is de data wel goed. het is dus zuiver die webserver die in de fout gaan.
mijn espwebserver.h die ik elders gebruik heeft daar geen ellende mee.

ik moet hier bij async blijven omdat ik tijdens de werking constant data veranderd in de basis html code (niet deze testhtml code). dit kan ik niet doen met een gewone webserver.

ga morgen eens proberen of ik de 2de webserver er ook bij kan zetten zodat die enkel de graph kan afwerken. weet niet of er 2servers tegelijk kunnen draaien

Probeer het zo eens:


void webserverhandle() {

  // Buffer for response message html message
  static String Response;

  // Handle Web Server
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", index_html); //deze werkt correct

  });

  server.on("/GRAPH", HTTP_GET, [](AsyncWebServerRequest * request) {
    Response = Printgraph();
    request->send_P(200, "text/html", Response.c_str()); //deze was corrupt

  });
  server.begin();
}

Door de html in een static String te schrijven blijft de data geldig totdat je een nieuwe response genereert.

met je opmerking van gisteren had ik ook al gedacht om mijn hele string eens in een globale variabele te steken. en dat ging ik ook eens bekijken.
ik dacht dat ik String ptr; al eens globaal had gezeteen paar dagen geleden en ook niks oploste.
ik heb je recente code eens geprobeerd en idd, de tekst is zonder corruptie. is die static dan de oplossing? want globale string deed het ook niet

ik had dit al gevolgd wat 'nickgammon' daar allemaal al poste, niet met strings maar met die chars
https://forum.arduino.cc/t/can-a-function-return-a-char-array/63405/5

Op woensdag 10 december 2025 19:20:05 schreef deKees:

server.on krijgt een pointer naar de data. En die data verdwijnt zodra server.on afgelopen is. De data is dan gecopieerd naar de xmit buffer als het goed is. Maar bij lange berichten past dat niet en dan wordt de data in 2 stukken verstuurd. Maar de data is er niet meer tegen de tijd dat het tweede blok wordt verstuurd.
Dat kan je oplossen door de string van PrintGraph() naar een static String variabele te copieren.

het is eigenlijk wel heel raar dat er net in het midden wat corruptie is, en de rest van de html data gewoon werd weergegeven. van de 1875karakters was de corruptie altijd vanaf karakter 949.
heb ik 1872karakters, is er geen corruptie op het 949ste.
heb ik 1873karakters dan is 950 corrupt.

zouden ze ergens in die library zeggen: heb je meer dan 1872chars, splits die dan per blokken van 950

Het gaat fout in deze regel omdat je een pointer naar de interne buffer van een tijdelijke string doorgeeft:

request->send_P(200, "text/html", Printgraph().c_str());

Printgraph() geeft een string terug.
Deze string wordt direct daarna vernietigd, maar send_P() probeert de data later te versturen.

Gevolg: de pointer verwijst naar geheugen dat niet meer geldig is, waardoor de HTML corrupt binnenkomt.

Probeer dit eens:


server.on("/GRAPH", HTTP_GET, [](AsyncWebServerRequest *request) {
    String html = Printgraph();
    request->send(200, "text/html", html.c_str());
});

Ah, ik moet leren eerst alles te lezen alvorens een reactie te schrijven.

Op donderdag 11 december 2025 10:37:21 schreef hardbass:
Gevolg: de pointer verwijst naar geheugen dat niet meer geldig is, waardoor de HTML corrupt binnenkomt.

Verduidelijking:

er wordt verwezen naar geheugen dat vrijgegeven is. Het KAN gebeuren(*) dat het systeem dat geheugen even later voor iets anders gebruikt. Puur toeval dat het "soms" werkt.

Onder Linux hebben ze tegenwoordig libraries met malloc/free die bewust niet de oude data laten staan, bewust "bekende data" net voorbij jou malloc stukje zetten en controleren dat dat er later nog staat. De bedoeling is dat het dan "direct fout gaat" als je het verkeerd doet. En niet dat je een tijd aan het zoeken bent, van "het werkt toch?" voor de kleinere voorbeelden.

(*) En kennelijk gebeurt dat "vaak" als je 1875 karakters verstuurt en "zelden" als je 1872 karakters verstuurt.

Op donderdag 11 december 2025 10:37:21 schreef hardbass:
Ah, ik moet leren eerst alles te lezen alvorens een reactie te schrijven.

Ik ook geloof ik.

Toch is dit een typisch probleem wat door dit soort dingen veroorzaakt wordt. Ik zou het in deze hoek zoeken.

Andere optie is dat er een "wilde pointer" rond hangt. Dus dat niet DIT stukje geheugen vrijgegeven is en hergebruikt wordt, maar andersom dat er een ander stukje code een free doet, maar daarna toch de pointer nog gebruikt (om te schrijven!).

Absoluut.

Dit is ook wel een vervelende eigenschap van c++ op embedded. Al kan het met c ook fout gaan, maar het valt me wel op dat er bij c++ makkelijker 'onopvallend' fout gaat.

Eerlijk gezegd doe ik op embedded nooit de objecten met dynamisch gedrag gebruiken, zoals in dit geval de String. Niet alleen vanwege lifetime issues, maar ook heb ik liever iedere variabele van tevoren ergens geclaimd. Zo weet je zeker dat je tijdens runtime nooit zonder geheugen zit. Dan kan je ook veel makkelijker grenzen stellen aan het project. Bijvoorbeeld: we accepteren maximaal 3 clients tegelijk. Het geheugen voor deze 3 clients is altijd geclaimd, ook al is er maar 1 verbonden.

In deze specifieke situatie doe ik liever dit:


size_t GetSomeString(char* buffer, size_t bufferSize);

void Test()
{
   char buffer[1024];
   size_t length = GetSomeString(buffer, sizeof(buffer));
}

Er zijn trouwens wel heel handige usecases die met C++ wel kunnen:
(even een hoop details weg gelaten, oa timeout afhandeling)


// Interface abstraction
class IMutex
{
public:
    virtual ~IMutex() = default;
    virtual bool Take() const = 0;
    virtual bool Give() const = 0;
};

// RAII lock helper
#define LOCK(mutex) ContextLock lock(mutex)

class ContextLock
{
    const IMutex& mutex;

public:
    ContextLock(const IMutex& mutex) noexcept
        : mutex(mutex)
    {
        mutex.Take();
    }

    ~ContextLock() noexcept
    {
        mutex.Give();
    }
};


// ----------------------------------------------------------------------
// Usage example
// ----------------------------------------------------------------------

Mutex someMutex;

void SomeCode()
{
    LOCK(someMutex);

    // Do work here.
    // The mutex is automatically released when leaving scope,
    // even if the function returns early or throws (if exceptions were enabled).
}

[Bericht gewijzigd door hardbass op (36%)]

De static zorgt ervoor dat de String blijft bestaan en een volgende keer opnieuw gebruikt kan worden, ook al is het een local variabele. Ook de data blijft ongemoeid en kan door de server op de achtergrond worden verstuurd zolang je geen nieuw request binnenkrijgt -en dus een nieuwe response genereert- voordat alles gedaan is.

Met een global variabele zou het net zo goed moeten werken.

het is eigenlijk wel heel raar dat er net in het midden wat corruptie is, en de rest van de html data gewoon werd weergegeven.

Tja, de buffer met de data is niet meer gekoppeld aan een variabele en wordt dus vrijgegeven voor andere toepassingen / functies. Welke dat zijn en welk effect dat heeft op de data is dan moeilijk te voorspellen.

Je hoeft de string niet met += aan elkaar te knopen. C++ kan een string over meerdere source regels verdelen door verschillende stukken een eigen set quotes te geven. De string eindigt dan bij de eerste punt-comma buiten de quotes. Dat scheelt een flink aantal dure memory operaties.

Dat ziet er dan zo uit:


String Printgraph() {
  String ptr = "<canvas id='myCanvas' width='1440' height='410'\n"   //dynamisch??
  "style='border:1px solid #d3d3d3;'>Your browser does not support the canvas element.</canvas>\n"
  "<script>var canvas = document.getElementById('myCanvas');var ctx = canvas.getContext('2d');" 
  "ctx.fillStyle = '#FFFFFF';ctx.fillRect(0,0,1440,410);ctx.fillStyle = '#F0F0F0';ctx.fillRect(0,50,1440,50);ctx.fillRect(0,150,1440,50);ctx.fillRect(0,250,1440,50);ctx.fillRect(0,350,1440,50);ctx.beginPath(); " 
  "ctx.strokeStyle = 'red';  ctx.moveTo(0,300);  " 
  "\n   ctx.moveTo(1,300);" 
  "\n   ctx.lineTo(100,286); " 
  "\n ctx.stroke(); " //ctx.font = '20px Arial'  " 
  "\n ctx.fillStyle = 'red';ctx.fillText('Usage 08/12/2025 (2668 Wp - total 3250Wh)',30,40); " 
  "\n ctx.beginPath();ctx.strokeStyle = 'blue';  ctx.moveTo(0,300);   "  
  "\n   ctx.moveTo(1,400);" 
  "\n   ctx.lineTo(1300,310); " 
  "\n ctx.stroke(); " 
  "\n ctx.font = '12px Arial'; " 
  "\n ctx.fillText('60V',10,55); " 
  "\n ctx.fillText('50V',10,105); " 
  "\n ctx.fillText('40V',10,155); " 
  "\n ctx.fillText('30V',10,205); " 
  "\n ctx.fillText('20V',10,255); " 
//draw tekst on X axle
  "\n ctx.stroke();" 
  "\n ctx.fillStyle = 'black';" 
  "\n ctx.fillText('\\'',60,407); " 
  "\n ctx.fillText('\\'',120,407); " 
  "\n ctx.fillText('\\'',180,407); " 
  "\n ctx.fillText('\\'',240,407); " 
  "\n ctx.fillText('\\'',300,407); " 
  "\n ctx.fillText('\\'',360,407); " 
  "\n ctx.fillText('\\'',420,407); " 
  "\n ctx.fillText('\\'',480,407); " 
  "\n ctx.fillText('\\'',540,407); " 
  "\n ctx.fillText('\\'',600,407); " 
  "\n ctx.fillText('\\'',660,407); " 
  "\n ctx.fillText('\\'',720,407); " 
  "\n ctx.fillText('\\'',780,407); " 
  "\n ctx.fillText('\\'',840,407); " 
  "\n ctx.fillText('\\'',900,407); " 
  "\n ctx.fillText('\\'',960,407); " 
  "\n ctx.fillText('\\'',1020,407); " 
  "\n ctx.fillText('\\'',1080,407); " 
  "\n ctx.fillText('\\'',1140,407); " 
  "\n ctx.fillText('\\'',1200,407); " 
  "\n ctx.fillText('\\'',1260,407); " 
  "\n ctx.fillText('\\'',1320,407); " 
  "\n ctx.fillText('\\'',1380,407); " 
  "\n ctx.fillText('\\'',1440,407); " 
  "\n ctx.fillText('60',54,410); " 
  "\n ctx.fillText('120',110,410); " 
  "\n ctx.fillText('180',170,410); " 
  "\n ctx.fillText('240',230,410); " 
  "\n ctx.fillText('300',290,410); " 
  "\n ctx.fillText('360',350,410); " 
 // "\n ctx.fillText('420',410,410); " 
  "\n ctx.stroke(); " 
  "\n</script>" 
  "test";

  return ptr;
}

En dat is precies het ernstigste 'gebrek' aan C: die ellendige ongedefinieerdheid en vaagheid van geheugenallocatie. Iets waar Pascal geen last van heeft: tijdens compilatie gaat de compiler al zeiken dat je dingen met geheugen wilt doen die niet kunnen. Veel betere boundary checks. Als je echt wilt, kun je de boel nog steeds stuk krijgen, maar dan moet je dat bewust en moedwillig doen.

Op donderdag 11 december 2025 11:08:15 schreef hardbass:
maar ook heb ik liever iedere variabele van tevoren ergens geclaimd. Zo weet je zeker dat je tijdens runtime nooit zonder geheugen zit.

ik meet spanningen in functie van de tijd met een accutester. ik start de test in deze html pagina. via events voeg ik regels toe in die kader (spanning, stroom, ....). werkt nu niet want de tester hangt niet aan een accu.

op het einde kopieer ik al die regels, steek die in excel met een CSV splitser en maak ik een grafiek. dit is omslachtig terwijl die ESP dat voor mij ook in html kan maken.

elke 10sec steek ik die waarden in een array en daar kan ik op het einde van de test, als ik wil, een grafiek laten maken. ik weet niet hoelang de test geduurd heeft, dus het aantal waardes kan verschillen (de array zal op einde allemaal nullen hebben, en dan is de html string ook korter).

dan moet die voor alle spanningen een string maken die eruit ziet als

ctx.beginPath();ctx.strokeStyle = 'red';
ctx.moveTo(0,180);
ctx.lineTo(1,182);
ctx.lineTo(2,184);
ctx.lineTo(3,186);
ctx.lineTo(4,188);
ctx.lineTo(5,190);
ctx.lineTo(6,192);
ctx.lineTo(7,194);
ctx.lineTo(8,196);
ctx.lineTo(9,198);
ctx.lineTo(10,200);
ctx.lineTo(11,201);
ctx.lineTo(12,201);
ctx.lineTo(13,201);
ctx.lineTo(14,201);
ctx.lineTo(15,201);
ctx.lineTo(16,202);
ctx.lineTo(17,202);
ctx.lineTo(18,202);
ctx.lineTo(19,202);
ctx.lineTo(20,202);
ctx.lineTo(21,203);
ctx.lineTo(22,203);
ctx.lineTo(23,203);
ctx.lineTo(24,203);
...

en ziet er dan zo uit. ik weet de data niet op voorhand, maar weet wel dat er altijd 600 zijn. mijn array is dus 600 waardes
diezelfde excel grafiek door mijn esp getekent:

de grootste ellende is dat die stand alone draait, geen servers, geen andere opslag. ALLES moet in ram blijven en lokaal genereren. als ik de tester uitschakel, is alles weg. dus de html generatie moet ik erna wel opslaan.
het bespaard me een hoop werk om 100den regels te kopieren naar excel, de waardes splitsen, grafiek maken, saven op hard disk....
nu run ik de test (pc, gsm,... iets dat op html kan) en kan de eindgrafiek lokaal opslaan zonder excel nodig te hebben

Op donderdag 11 december 2025 12:17:26 schreef deKees:
Je hoeft de string niet met += aan elkaar te knopen. C++ kan een string over meerdere source regels verdelen door verschillende stukken een eigen set quotes te geven. De string eindigt dan bij de eerste punt-comma buiten de quotes. Dat scheelt een flink aantal dure memory operaties.

ah, ga ik eens proberen

Ik ken je precieze situatie niet, maar als je op dit gebied wilt groeien, kan het helpen om eens te kijken naar een modern frontend-framework zoals React of Angular.

Hoe ik het zelf aanpak:

Je ontwikkelt de frontend (de webpagina) gewoon met HTML en JavaScript. Deze bestanden host je op de ESP zelf via een eenvoudige file-webserver, iets wat door ESP-IDF standaard wordt ondersteund. De bestanden kunnen bijvoorbeeld op een SD-kaart of in een aparte flash-partitie staan. Je zou dit eventueel met een kleine FTP-server toegankelijk kunnen maken, zodat je de frontend eenvoudig kunt updaten.

Vervolgens maak je een paar HTTP-endpoints beschikbaar op de ESP. De webpagina haalt via JavaScript de benodigde data op vanuit deze endpoints.

Op deze manier hoeft de ESP veel minder werk te doen: de rendering vindt volledig plaats in de browser van de gebruiker, niet op de microcontroller. De ESP levert alleen de data aan.

Maargoed, dit is wel een redelijk traject als je er nog niet mee bekend bent. (Al kan je met AI vrij snel een demo in elkaar flansen)

Waarom je gegevens niet in een database stoppen (MariaDB?)? Doe ik met alle gegevens van mijn weerstation en de fijnstof sensor. Een PHP pagina haalt dan de nodige info op via een stored procedure. De PHP pagina maakt er dan de nodige tabellen van. Het invoeren in de database gebeurd ook via een PHP pagina en een stored procedure. De query library heb ik nooit deftig werkende gekregen.

Via HTML zou ik de HTML niet constant opnieuw schrijven. Wat niet wijzigt, zou ik niet telkens verzenden. Word je HTML string een stuk korter en er worden veel minder gegevens verzonden. Ik zou alleen de gegevens wijzigen met websockets (gegevens verzonden in JSON formaat). Dat is ook wat andere webpagina's doen om de server belasting lager te houden. Pas ik onder andere toe om deze webpagina op te bouwen. Elke seconde een update.

Dit is met een ESP32. Maar hetzelfde doe ik met een andere webpagina op een ESP8266 voor de kerstverlichting.

Hier wijzigen uiteraard minder gegevens. De efemeriden worden opgevraagd in JSON formaat .
https://api.sunrise-sunset.org/json?lat=51.000000&lng=4.600000&date=20…

RAW

{"results":{"sunrise":"7:40:03 AM","sunset":"3:38:34 PM","solar_noon":"11:39:18 AM","day_length":"07:58:31","civil_twilight_begin":"7:02:28 AM","civil_twilight_end":"4:16:08 PM","nautical_twilight_begin":"6:19:48 AM","nautical_twilight_end":"4:58:49 PM","astronomical_twilight_begin":"5:39:25 AM","astronomical_twilight_end":"5:39:12 PM"},"status":"OK","tzid":"UTC"}

@ hardbass: niet echt, ik bouw de hardware om ebike batterijen te testen. de software errond boeit me eigenlijk weinig. het is handig dat ik een grafiek heb hoe goed een accu is/blijft.

nu was dat omslachtig door de data op mijn pc naar excel te kopieren, in kolommen steken, grafiek uit maken, .... ik heb de data ook niet meer nodig erna, dus hoef het niet in cloud of server te steken.

mijn esp kan me dezelfde grafiek geven zoals mijn excel en dat is prima voor mij.
mijn excel geeft de seconden weer (tot 4750), mijn esp geeft het weer in minuten. nog net was leesbaarder.
als die accu het 78min volhouden is dat ongeveer 78km dat ze op eco kan halen

@buckfast beekeeper: ik heb de data erna ook niet meer nodig. een afdruk van de grafiek in het archief en klaar. als ik de accu 2jaar later nog eens test zie ik de evolutie.
het hoofdprogramma werkt zoals het uwe. ik bouw de layout op, en daarna komt er elke 5sec een nieuwe regel met meetwaardes.

hier maak ik zo een regel in html code en stuur die naar elke client die deze website open heeft, krijgt dan zo een nieuwe regel erbij

  tekst =  String(getal) + ";&#9; " + String(spanning) + ";&#9;"  + String(stroom) + ";&#9;" + String(watt, 0) + ";&#9; " + String(Ah) + ";&#9;" + String(Wh) + String("<br>");
  tekst.replace('.', ',');
  events.send(String(tekst).c_str(), "serialdata", millis());

na de test (1-2uur) maak ik een grafiek, bereken de Ah waarde, bereken de Ri en de data hoef ik niet meer. dat is die eindgrafiek dan die 1x gebouwd wordt

mijn boiler werkt ook op die manier, 1x de pagina basic opbouwen en daarna met events zet ik alles correct. veranderd 1 client iets, dan krijgen alle andere meekijkende clients ook een update
hier bv zal elke minuut het blauwe stukje tekst aangepast worden, de layout niet meer (tenzij je F5 drukt). haal je een schakelaar om, idem, enkel de status van die ene switch wordt doorgestuurd

ik kan die programmeren dat die bv elke dag van 12h tot 16h opwarmt (zo begon het programma), maar die is nu veel slimmer, die zal zelf aanspringen als er overschot is van de P1 meter. en als er om 14h niet genoeg opgewarmd is (tijd of temperatuur), warmt die x minuten zelf bij. dat bepaald die zelf, ik heb er geen omkijken meer naar. blijkbaar beslist die vandaag om nog 147min bij te warmen nu. die 34°C zal er voor iets tussen zitten :-)

Waarom afdrukken en dan jaren later gaan vergelijken? Met een database, een PHP pagina en 2 stored procedures moet je niks meer afdrukken en wegbergen. Eenvoudig een grafiek maken par dag, per maand, per jaar, over de gehele levensduur, ... Daar hebben ze databases voor uitgevonden. MariaDB kan ook op een RPi als ik me niet vergis. Bij mij draait het op een Synology NAS.

In een wat verder verleden moesten we maandelijks oplijsten hoeveel pleziervaart er was. Dat was zoeken in 4 boeken. Dat werd dan doorgemaild naar een administratief persoon die het moest verwerken van 4 locaties. Ik heb daar een Access database voor gemaakt. De eerste dag van de maand drukte ze op een knop en de gegevens werden in de door hen gebruikte Excel aangepast. Wanneer is een schip het laatst geweest? Naam invullen en op een knop drukken en je had een lijst met data. Geen boeken meer die in het archief verdwenen als ze vol waren.

Ik snap het hoor, gewoon gebruiken wat voor jou goed werkt.