Forum: Mikrocontroller und Digitale Elektronik Absturz beim ESP8266


von Sirdus (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe ein Problem und kann mir selber nicht so recht helfen.
Auf dem ESP läuft ein Web-Server welcher eine Seite zur Verfügung 
stellt. Auf dieser Seite kann man div. Schaltperioden einstellen. Das 
ist eine ganz einfach Wochenschaltuhr. Jetzt habe ich aber das Problem, 
dass mir der ESP unter EDGE abstürzt. Beim Firefox funktioniert es. 
Firefox ist aber auch nicht ganz die Lösung, da hier das Stellen der 
Uhrzeit nicht geht.

Was kann hier die Ursache des Problems sein und was kann man dagegen 
tun.
Wäre schön wenn mich einer bei der Fehlersuche unterstützen könnte.

VG

von 60/40 (Gast)


Lesenswert?

Gefühlt ein Pufferüberlauf im Parser.

von Stefan F. (Gast)


Lesenswert?

Der ESP8266 hat nur etwa 5 kB im Stack frei, wenn man mehr belegt stürzt 
er ab.

Anderer Ansatz: Kommentiere mal alle seriellen Ausgaben aus. Wenn die zu 
lange dauern macht er nämlich einen Watchdog Reset.

Mir ist noch etwas aufgefallen:
1
  server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
2
3
    ...
4
5
      int i = 0;
6
      while (true)
7
      {
8
        myjson = myjson + String((char)data[i]);
9
        if (data[i - 1] == ']' && data[i] == '}')
10
          break;
11
        i++;
12
      }

Wenn du die erwarte Zeichenfolge "]}" nicht empfängst, dann hängst du 
hier in einer Endlosschleife fest die über das Ende von data hinaus 
läuft. Du ignorierst sogar den Wert von len.

von Εrnst B. (ernst)


Lesenswert?

Du benutzt doch eh schon den AsyncWebserver. Da hast du doch garkeine 
Notwendigkeit, die gesamte Antwort im RAM zusammenzubasteln, sondern 
könntest die Dateien z.B. einfach vom PROGMEM oder SPIFFS rausstreamen, 
und per Templating-Option deine dynamischen Werte einsetzen.

https://github.com/me-no-dev/ESPAsyncWebServer#send-large-webpage-from-progmem-containing-templates

Das hier:
1
    String head = String(header_html);
2
    String fn = get_jsdate_fn();
3
    String table = get_html_table();
4
    String rem = String(rem_body);
5
    String foot = String(footer_html);
6
    String resp = head + fn + table + rem+ foot;
ist schon sehr verschwenderisch... erstmal alles aus dem PROGMEM in den 
RAM kopieren, und dann (über mehrere Zwischenschritte in den 
String-Additionen, die auch alle im RAM liegen) nochmal eine 
Komplett-Kopie der gesamten Antwort im RAM zurechtlegen...

Wenn dir das Templating zu kompliziert ist:
1
String resp = String(header_html);
2
resp += get_jsdate_fn();
3
resp += get_html_table();
4
resp += String(rem_body);
5
resp += String(footer_html);
ist zumindest etwas sparsamer.

von Stefan F. (Gast)


Lesenswert?

Soweit ich dem Beispielprojekt der Bibliothek entnehmen kann ist wohl 
vorgesehen, dass onRequestBody() unter Umständen mehrmals mit Fragmenten 
des Bodies aufgerufen wird, bis die "total" Anzahl von Bytes erreicht 
sind.

Dein Code geht aber davon aus, dass das  gesamte JSON Dokument in einem 
Rutsch an onRequestBody() übergeben wird.

von Sirdus (Gast)


Lesenswert?

Hallo Stefan F. die seriellen Ausgaben hatte ich schon deaktiviert, 
leider ohne Erfolg. Ich denke dein zweiter Tipp mit dem nichtempfangen 
der Zeichenkette und dem damit verbundenen Überlauf ist schon mal ein 
Punkt.

@ Εrnst B

>Wenn dir das Templating zu kompliziert ist:
1
String resp = String(header_html);
2
3
resp += get_jsdate_fn();
4
resp += get_html_table();
5
resp += String(rem_body);
6
resp += String(footer_html);
7
ist zumindest etwas sparsamer.

Diesen Teil habe ich so übernommen, der Rest sprengt meine Kenntnisse 
;-)

@Stefan F.
>Soweit ich dem Beispielprojekt der Bibliothek entnehmen kann ist wohl
>vorgesehen, dass onRequestBody() unter Umständen mehrmals mit Fragmenten
>des Bodies aufgerufen wird, bis die "total" Anzahl von Bytes erreicht
>sind.

>Dein Code geht aber davon aus, dass das  gesamte JSON Dokument in einem
>Rutsch an onRequestBody() übergeben wird.

Okay wie würde man es den lösen?

von Stefan F. (Gast)


Lesenswert?

Sirdus schrieb:
> Okay wie würde man es den lösen?

Die empfangenen Bytes sammeln bis len+len+...+len den Wert von total 
erreicht hat. Erst dann auswerten. Aber nicht bis zur Position von "]}" 
auswerten, sondern von 0 bis total.

von Sirdus (Gast)


Lesenswert?

Hallo,

ich habe mal eine serielle Ausgabe in den Code eingebaut und sehe einen 
Unterschied zwischen Firefos und Edge

1
  // save file
2
  server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
3
    state = STARTUP;
4
    String myjson;
5
    if (request->url() == "/submit")
6
    {
7
    
8
       for (size_t i = 0; i < total; i++) {
9
        Serial.write(data[i]);
10
        myjson = myjson + String((char)data[i]);
11
       }
12
13
    save_config_file(DEFAULT_CONFIG_FILE, myjson);
14
    int r = load_config(start_time, stop_time, status, DEFAULT_CONFIG_FILE);
15
16
    String resp = String(header_html);
17
18
    resp += get_jsdate_fn();
19
    resp += get_html_table();
20
    resp += String(rem_body);
21
    resp += String(footer_html);  
22
    } });
23
24
  enable_ap();
25
  
26
}

Auf der Console sehe ich jetzt folgendes.

Firefox:
{"start_time":[450,450,450,450,450,480,480],"stop_time":[840,840,840,840 
,840,720,720],"status":1,1,1,1,1,0,0]}
123341151169711411695116105109101345891525348445253484452534844525348445 
253484452564844525648934434115116111112951161051091013458915652484456524 
844565248445652484456524844555048445550489344341151169711611711534589149 
44494449444944494448444893125


Edge:
{"start_time":[450,450,450,450,450,480,480],"stop_time":[840,840,840,840 
,840,720,720],"status":1,1,1,1ointR␅123341151169711411695116105109101345 
891525348445253484452534844525348445253484452564844525648934434115116111 
112951161051091013458915652484456524844565248445652484456524844555048445 
5504893443411511697116117115345891494449444944491111051101168210185,1,0, 
0]}␀␀\␃�␅=␅␟␅Mozilla/5.0  (Windows NT 10.0; Win64; x64) 
AppleWebKit/537.36 (KHTML, like Gecko) 
Chrome/106.0444944484448931250092313056153157711112210510810897475346483 
240871051101001111191153278843249484648593287105110545259321205452413265 
112112108101871019875105116475351554651543240757284777644321081051071013 
27110199107111413267104114111109101474948544648

Die "Zahlen" kommen aus einer Print ausgabe beim Datei schreiben.
1
 
2
bool save_config_file(String f_name, String Json)
3
{
4
  File file = SPIFFS.open(f_name, "w");
5
  if (!file)
6
  {
7
    Serial.println("Error opening file for writing");
8
    return false;
9
  }
10
11
  if (!file.print(Json))
12
  {
13
    return false;
14
  }
15
16
  file.close();
17
/////////////////////////////////
18
File file2 = SPIFFS.open(f_name, "r");
19
20
  if (!file2)
21
  {
22
    Serial.println("Failed to open file for reading");
23
    return false;
24
  }
25
26
  int i = 0;
27
  while (file2.available())
28
  {
29
    Serial.print(file2.read()); -----> erzeugt die Zahlen in der Ausgabe!
30
  }
31
  file2.close();
32
33
/////////////////////////////////////
34
35
  return true;
36
}

Wenn ich es richtig sehe, wird beim Edge irgendwie die Datei geschrieben 
bevor die Funktion fertig ist. Man erkennt aber auch den Rest in der 
Ausgabe.
.."status":1,1,1,1 xxxx ,1,0,0]}

von Εrnst B. (ernst)


Lesenswert?

Sirdus schrieb:
> Wenn ich es richtig sehe, wird beim Edge irgendwie die Datei geschrieben
> bevor die Funktion fertig ist. Man erkennt aber auch den Rest in der
> Ausgabe.

Wie Stefan geschrieben hat:
Es ist nicht garantiert, dass du nur einen einzigen Aufruf von 
"handleUpload" bekommst, da können auch mehrere kommen, jeder mit einem 
Teil der Daten. Erst wenn der Aufruf mit "final=true" kommt, sind die 
Daten vollständig übertragen.

mit "file_data" hast du ja schon angefangen, dir diese Daten 
zusammenzusammeln. Aber: Du darfst das nur beim ersten Aufruf auf 
""(leer) setzen.
z.B:
1
  server.on("/handleupload", HTTP_POST, [](AsyncWebServerRequest *request)  
2
    { file_data=""; request->send(200); },
3
      handleUpload);
oder alternativ am Ende der Datenübertragung, also im "if (final) ..."

Vorsicht: Ist keine 100%ige Lösung, wenn Zwei Uploads auf die URL 
gleichzeitig passieren, zerschießt es dir den Variableninhalt.

von Stefan F. (Gast)


Lesenswert?

Sirdus schrieb:
> for (size_t i = 0; i < total; i++)
> Auf der Console sehe ich jetzt folgendes:
(ein bisschen richtiges gefolgt von Zufallsdaten)

> Wenn ich es richtig sehe, wird beim Edge irgendwie die Datei
> geschrieben bevor die Funktion fertig ist

Weil dein Code immer noch davon ausgeht, das gesamte JSON Dokument in 
einem Rutsch zu empfangen. Darauf kannst du dich aber nie verlassen. 
Beim TCP Protokoll können Datenströme immer in unerwartete Paketgrößen 
zerlegt werden.  Du wirst mit dem anderen Browser genau das gleiche 
Problem bekommen wenn das JSON Dokument größer als 4 kB ist. Das ist 
nämlich die maximale zulässige Paketgröße bei Ethernet. Doch darauf 
kannst du dich nicht verlassen, da jedes beteiligte Programm und Gerät 
den Datenstrom in kleinere Stücke zerlegen darf.

Die Funktion onRequestBody() wird mehrmals (!) mit folgenden Parametern 
aufgerufen:

data:  Zeiger auf die empfangenen Bytes des aktuellen Fragmentes
len:   Anzahl der empfangenen Bytes in data
index: Position innerhalb der Gesamtgröße
total: Gesamte Anzahl erwarteter Bytes (wie im Content-Length header)

Angenommen dein Request Body besteht auf 3 Stücken, dann könnten sie so 
aufgeteilt sein:
1
Erster Aufruf:  len=128, index=0,   total=300
2
Zweiter Aufruf: len=64,  index=128, total=300
3
Dritter Aufruf: len=108, index=192, total=300

Wenn len+index=total ist, hast du das letzte Fragment empfangen.

Du möchtest alle Fragmente nacheinander verketten, und erst ganz zum 
Schluss das JSON Dokument weiter verarbeiten. Nicht bei jedem Aufruf von 
onRequestBody().

von Sirdus (Gast)


Lesenswert?

Hallo,

@ Stefan F. vielen Dank für deinen letzten Beitrag, das hat mir wirklich 
geholfen das Problem zu verstehen.

Ich habe nun versucht das irgendwie umzusetzten aber so recht geht es 
leider nicht.
1
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
2
    
3
    if (index = 0){
4
      myjson ="";
5
    }
6
    
7
    if (request->url() == "/submit")
8
    {
9
      Serial.print("len ");
10
      Serial.println(len);
11
      Serial.print("idx ");
12
      Serial.println(index);
13
      Serial.print("tot ");
14
      Serial.println(total);
15
16
      for (int i = 0; i < len; i++){
17
           myjson = myjson + String((char)data[i]);
18
19
      }
20
    
21
      cnt = len + index;
22
23
      if (cnt == total) 
24
       {
25
          Serial.println();
26
          printf("%s\n", myjson.c_str());
27
          Serial.println();
28
          save_config_file(DEFAULT_CONFIG_FILE, myjson);
29
          int r = load_config(start_time, stop_time, status, DEFAULT_CONFIG_FILE);
30
31
          String resp = String(header_html);
32
33
          resp += get_jsdate_fn();
34
          resp += get_html_table();
35
          resp += String(rem_body);
36
          resp += String(footer_html);  
37
          state = STARTUP;
38
          enable_ap();
39
       }     
40
 
41
    } });
42
}

damit bekomme ich folgende Print Ausgaben
Firefox
1
len 115
2
idx 0
3
tot 115
4
5
{"start_time":[450,450,450,450,450,480,480],"stop_time":[1320,960,1320,1080,1140,720,720],"status":[1,1,1,1,0,0,0]}

Edge
1
len 103
2
idx 0
3
tot 115
4
len 12
5
idx 0
6
tot 115

Leider ist Index immer 0 ?

von J. S. (jojos)


Lesenswert?

Mal die Compiler Warnungen einschalten.

if (index = 0){

von Stefan F. (Gast)


Lesenswert?

Sirdus schrieb:
> Ich habe nun versucht das irgendwie umzusetzten aber so recht geht es
> leider nicht.

Dein gezeigter Quelltext ist jedenfalls der richtige Ansatz.

J. S. schrieb:
> Mal die Compiler Warnungen einschalten.
> if (index = 0){

Das wird es sein.

von Sirdus (Gast)


Lesenswert?

Hallo,

if (index = 0){
jetzt hab ich mich aber wieder angestellt.

Vielen Dank nochmal für die tolle Hilfe.

von Sirdus (Gast)


Angehängte Dateien:

Lesenswert?

Ein Problem habe ich noch und zwar beim Stellen der Uhr.
1
 // load selected file for pump timing
2
  server.on("/updatertc", HTTP_GET, [](AsyncWebServerRequest *request){
3
    state = STARTUP;
4
    uint16_t yr;
5
    uint8_t mont, dy, hrs, minu, sec;
6
      
7
    String thisy = request->getParam("y")->value();
8
    String thismo = request->getParam("mo")->value();
9
    String thisd = request->getParam("d")->value();
10
    String thish = request->getParam("h")->value();
11
    String thism = request->getParam("m")->value();
12
    String thiss = request->getParam("s")->value();
13
   
14
    yr = thisy.toInt(); mont = thismo.toInt(); dy = thisd.toInt();
15
    hrs = thish.toInt(); minu = thism.toInt(); sec = thiss.toInt();
16
17
    Serial.printf("%d,%d,%d\n", yr, mont, dy);
18
    Serial.printf("%d,%d,%d\n", hrs, minu, sec);
19
20
    delay(300);
21
22
#ifdef DEBUG
23
   // Serial.printf("%d\n",yr);
24
#endif
25
    rtc.adjust(DateTime(yr, mont, dy, hrs, minu, sec));
26
    int r = load_config(start_time, stop_time, status, DEFAULT_CONFIG_FILE);
27
    String head = String(header_html);
28
    String fn = get_jsdate_fn();
29
    String table = get_html_table();
30
    String rem = String(rem_body);
31
    String foot = String(footer_html);
32
    String resp = head + fn + table + rem+ foot;
33
    request->send_P(200, "text/html", (resp).c_str());
34
    delay(10);
35
    });

Leider funktioniert das nicht unter Firefox, Edge macht keine Probleme.
Ich sehe mit Firefox keine Print-Ausgabe. Es scheint als ob die Funktion 
nicht aufgerufen wird. Im Debugfesnter vom Firefox sehe ich 
NS_BINDING_ABORTED

Kann mir hier noch einer einen Tipp geben?

von Stefan F. (Gast)


Lesenswert?

Mache mal eine Debug Ausgabe ganz am Anfang der Funktion um zu sehen ob 
sie überhaupt aufgerufen wird.

Kontrolliere mit Wireshark, wie dein Mikrocontroller den HTTP Request 
beantwortet. Du kann mit Wireshark auch gut herausfinden, worin sich die 
Requests deiner beiden Browser unterscheiden.

von Sirdus (Gast)


Angehängte Dateien:

Lesenswert?

Stefan F. schrieb:
> Mache mal eine Debug Ausgabe ganz am Anfang der Funktion um zu sehen ob
> sie überhaupt aufgerufen wird.
1
server.on("/updatertc", HTTP_GET, [](AsyncWebServerRequest *request){
2
    Serial.printf("debug");

--> keine Ausgabe

Habe es mit Wireshark aufgezeichnet, sprengt aber meinen Stack...
In der LOG sieht man erst den Firefox, dann Edge.
Beim Firefox fehlt irgendwie die Anfrage.

von Sebastian (Gast)


Lesenswert?

Sirdus schrieb:
> Beim Firefox fehlt irgendwie die Anfrage.

Vorher den Cache geleert?

LG, Sebastian

von Sirdus (Gast)


Lesenswert?

Sebastian schrieb:
> Vorher den Cache geleert?

gerade getestet, ohne Erfolg.

von Stefan F. (Gast)


Lesenswert?

Sirdus schrieb:
> keine Ausgabe
> 123.png

> Habe es mit Wireshark aufgezeichnet, sprengt aber meinen Stack...

Wieso ist doch gut. Du hast genau das gezeigt, was ich sehen wollte.

Dass ein Browser wiederholt versucht, das /favicon.ico zu laden ist ganz 
normal. Dass dein µC mit darauf mit einem Error antwortet wenn er dieses 
Bild nicht bereitstellen kann ist auch OK (wobei dann ein anderer 
Errorcode üblich wäre, aber egal).

Was hier aber völlig fehlt ist der Aufruf /updaterc durch den Firefox 
Browser. Kein Wunder dass dein Mikrocontroller nicht reagiert. Du hast 
offenbar einen Fehler im HTML oder Javascript der Webseite. Hänge mal 
den Seitenquelltext als HTML Datei an. Mit dem Browser abspeichern, ich 
will sehen wie die Seite aus seiner Sicht aussieht. Falls sie 
irgendwelche Javascripte als separate Datei includiert, dann hänge diese 
bitte auch an.

von Sirdus (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Stefan F.

anbei die HTML Seite

von Stefan F. (Gast)


Lesenswert?

Die letzte Zeile der Funktion muss weg, ich habe sie auskommentiert:
1
        function updateRtc(){
2
          ...
3
          var http = new XMLHttpRequest();
4
          http.open("GET", url+"?"+params, true);
5
          http.onreadystatechange = function(){
6
          if(http.readyState == 4 && http.status == 200) {
7
            alert("Time updated");
8
            location.reload();
9
          }}
10
          http.send(null);
11
          //location.reload();
12
        }

Erklärung: Mit http.onreadystatechange richtest du eine Call-Back 
Funktion ein, der aufgerufen werden soll, wenn der Request beantwortet 
wurde. Da du aber schon vorher das Fenster neu lädst, ist der 
Eventhandler nicht mehr Verfügbar. Der Request wird vom Browser 
abgebrochen.

Schau dir auch mal deine submitFunction an, die ist auch nicht in 
Ordnung:
1
        function submitFunction() {
2
            ...
3
            let jsonData = { ... };
4
            let xhr = new XMLHttpRequest();
5
            let url = "/submit";
6
            xhr.open("POST", url, true);
7
            xhr.setRequestHeader("Content-Type", "application/json");
8
            var data = JSON.stringify(jsonData);
9
            xhr.send(data);
10
            alert("Daten gespeichert!");
11
            location.reload();
12
        }

Bei xhr.open() hast du mit dem Parameter true festgelegt, dass der 
Request asynchron ausgeführt werden soll. Direkt nach xhr.send() gibst 
du aber schon eine Erfolgsmeldung aus, völlig egal ob der Request 
überhaupt erfolgreich beantwortet wurde. Du musst an dieser Stelle auch 
wie oben eine Call-Back Funktion benutzen, um die Antwort auszuwerten.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.