Formulardaten an ESP per JSON senden und char-arrays zuweisen

Hi zusammen,

der folgende Sketch funktioniert schon fast, wie ich will.
Klar: Man könnte mit fetch API arbeiten statt Ajax aber ich möchte maximale Browser-Kompatibiliät.
Von daher soll das nicht Thema dieses Beitrags sein.

Jedenfalls will ich Formular-Daten per JSON an den ESP-webserver senden und dort dann die Werte entsprechenden char-Arrays zuweisen.

Wie gesagt: Funktioniert soweit.
Nur möchte ich nicht über den Key-Index parsen sondern den Key-Namen.

Hier der aktuelle Sketch.
Wie zum Henker komme ich an den Namen der JSON-Keys? Ich bin zu doof.

#include <WiFi.h>
#include <WiFiAP.h>
#include <WebServer.h>
WebServer server(80);
#include <Arduino_JSON.h>

char APssid[25] = "MyAccesspoint";
char APpassword[20] = "123456789";

char ssid[30]="";
char password[30]="";

const char indexPage[] PROGMEM = R"=====(
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
  </head>
    <body>
      <form>
        <p>SSID:</p>
        <input type="text" id="ssid" value="">
        <p>Passwort:</p>
        <input type="password" id="password" value="">
        <button type="button" onclick="setData()">Submit</button>
      </form>
    </body>
    <script>
      function setData() {

        const json_data = {};
        
        var ssid=document.getElementById("ssid").value;
        json_data["ssid"]=ssid;
        
        var password=document.getElementById("password").value;
        json_data["password"]=password;

        var http = new XMLHttpRequest();

        http.onreadystatechange = function () {
            if (this.readyState == 4 && this.status == 200) {
                var data=this.responseText;
                alert("Serverantwort:"+data);
            }
        };
        
        http.open('POST', '/setData', true);
        http.setRequestHeader('Content-Type', 'application/json');
        http.send(JSON.stringify(json_data));
      }
    </script>
  </html>
)=====";

void setup(){
  Serial.begin(115200);

  server.on("/", display_root);
  server.on("/setData", setData);

  WiFi.mode(WIFI_AP);
  WiFi.softAP(APssid, APpassword);
  server.begin(); 
}

void loop(){
  server.handleClient();
}

void display_root() {
  const char * httpType PROGMEM = "text/html";
  server.send_P(200,  httpType, indexPage);
}

void setData() {
  JSONVar myObject = JSON.parse(server.arg(0));

  if (JSON.typeof(myObject) == "undefined") {
    return;
  }

  JSONVar keys = myObject.keys();
  for (int i = 0; i < keys.length(); i++) {
    JSONVar value = myObject[keys[i]];
    // Das hier funktioniert
    switch (i) {
      case 0:
        strcpy(ssid, value);
        break;
      case 1:
        strcpy(password, value);
        break;
    }
    /* Aber ich möchte "sinngemäß" lieber sowas
    // Bin aber zu doof um an den Key zu kommen ;-(
    if (key=="ssid") {
      strcpy(ssid, value);
    } else if (key=="password") {
      strcpy(password, value);
    }
    */
  }

  Serial.println("SSID:" + (String)ssid + ", Password:" + (String)password);
  server.send(200, "text/plain", "OK");
}

Könnt ihr mich von der Leitung ziehen? Mein Hirn anstupsen?

Danke :wink:

Wenn Du nicht über id sondern einen Text parsen willst, musst Du wissen, wie der text lautet.
Den schmeisst in ein Array.
Du vergleichst den text inhaltlich mit jedem Arrayfeld.
(Alternativ if / elseif)
Bei match darfst den Inhalt tindern.
kannst mal son json zeigen?

Klaro:

{"ssid":"MeineSSID","password":"MeinPasswort"}

Grüßle

Na ist doch schick:

    switch (switchValue) {
      case "ssid":
        strcpy(ssid, value);
        break;
      case "key":
        strcpy(password, value);
        break;
    }

switchValue ist dann der Bezeichner. Den musste aus dem Json extrahieren.

1 Like

Da nimmst Du einfach die "richtige" ArduinoJason (installiert über die Bibliotheksverwaltung):

#define ARDUINOJSON_USE_LONG_LONG 0
#define ARDUINOJSON_USE_DOUBLE 0
#include <ArduinoJson.h>

... nutzt den Assistenten, wirfst ihm den Json-Text zum Fraße vor und bekommst am Ende - oh Wunder - diesen Schnipsel Code:

// char* input;
// size_t inputLength; (optional)

StaticJsonDocument<32> doc;

DeserializationError error = deserializeJson(doc, input, inputLength);

if (error) {
  Serial.print("deserializeJson() failed: ");
  Serial.println(error.c_str());
  return;
}

const char* ssid = doc["ssid"]; // "MeineSSID"
const char* password = doc["password"]; // "MeinPasswort"

Fertig!

1 Like

OK, danke euch beiden.
Dann werd ich wohl nun endgültig auf die andere ArduinoJSON umsteigen.
Die wurde mir schon mal vorgeschlagen :wink:

Wobei ich immer dachte, dass die offizielle Arduino library die Arduino_JSON.h ist.

Danke für eure Tipps.

ist wohl was anderes als <ArduinoJSON.h>.
Aber ...
Was passiert denn bei

JSONVar keys = myObject.keys();
for (int i = 0; i < keys.length(); i++) {
    JSONVar value = myObject[keys[i]];
    Serial.print(i); Serial.print(": "); Serial.println(keys[i]);
    if (strcmp(keys[i],"ssid") == 0) strcpy (ssid, value);
    if (strcmp(keys[i],"password") == 0) strcpy (password, value);
}

?
Nur geraten. Im Zweifelsfall kannst du ja keys[i] genauer anschauen.

1 Like

Yep, das wars (die Leitung).
keys[i] gibt natürlich den Keynamen aus.
Ich könnte grad schwören, dass hätte ich probiert.

Aber ich werd dann glaub echt auf die andere ArduinoJSON lib umsteigen.
Die ist auch deutlich besser dokumentiert.

Ok, der klappt so nicht ganz -- aber ich hatte ja schon ne Idee :wink:

const char txt[][10] = {"txt", "tyt"};
byte meinByte = 0;
void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
}

void loop()
{
  for (byte b = 0; b < 1; b++)
  {
    if (txt[b] == "txt")
      meinByte = 1;
  }
  switch (meinByte)
  {
    case 1:
      Serial.println("TXT");
      break;
    case 2:
      Serial.println("TYT");
      break;
  }
  delay(500);
}

Wenn Du jetzt txt[b] mit Deinem Json vergleichst .... :wink:

Danke trotzdem, ihr seid einfach spitze Leute!
Schönen Abend allen!

LG und bis zum nächsten Problem :see_no_evil:

@my_xy_projekt : du meinst sicher was mit strcmp ...

Warte ab :wink:
Du hast mir gestern nen Brocken hingelegt... :slight_smile:

@basementmedia

Darf ich einfach mal hinterfragen, warum du die Daten als JSON reinschicken willst?
Warum nicht als POST Parameter und den ESP die Parameter auswerten lassen?
Du willst "maximale Browser-Kompatibilität" ... dann setze nicht JavaScript für Sachen ein die du mit purem HTML auch lösen kannst.

So hab ich das bisher immer gemacht:

Auf HTML/Javascript-Seite

var send_str='ssid='+ssid+'&password='+password;
var xhr = new XMLHttpRequest();
xhr.open('POST', '/saveData', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = function () {
    // Ggf. Serverantwort auswerten
};
xhr.send(send_str);

Aber als ich dann gesehen habe, dass der Router meines Bruders im WLAN-Passwort ein "&" mit drin hat, hat das nicht mehr funktioniert... Oder wäre hier die Lösung gewesen, mit encodeURIcomponent den send-String zu verschlüsseln (oder mit base64) sodass das zusätzliche '&' (und ggf. weitere Sonderzeichen, die noch kommen könnten) wirkungslos wird?

Aber wie bekomm ich den verschlüsselten Query-String dann auf ESP-Seite wieder entschlüsselt?

Javascript brauch ich ja ohnehin.

Ein anderes Problem (warum ich dann auch JSON umgesprungen bin):
Wie speichere ich die hereinkommenden Daten (z.B. eben die Zugangsdaten) im SPIFFS?

Bisher hab ich das so gemacht (wahrscheinlich nicht die beste Lösung):

File file_test = SPIFFS.open(datei_test, "w");
if (file_test) {
  for (uint8_t i = 0; i < server.args(); i++) {
      file_test.println(server.argName(i) + "*" + server.arg(i));
  }
file_test.close();

Ich hab also quasi als Separator den * genommen.

Und beim wieder einlesen der Datei dann anhand des * zerhackt und die Daten auf Variablen zugewiesen:

File file_test = SPIFFS.open(datei_test, "r");
  char zeile[150];
  while (file_test.available()) {
    int l = file_test.readBytesUntil('\n', zeile, sizeof(buffer));
    zeile[l] = 0;
    char* token = "";
    char parameter_name[20];

    token = strtok(zeile, "*");
    int hck = 0;
    while (token) {
      switch (hck) {
        case 0:
          strcpy(parameter_name, token);
          break;
        case 1:
          if ((String)parameter_name == "ssid") { 
            byte lastChar = strlen(token) - 1;
            token[lastChar] = '\0';
            strcpy(ssid, token); 
          } else if ((String)parameter_name == "password") {
            byte lastChar = strlen(token) - 1;
            token[lastChar] = '\0';
            strcpy(password, token);
          } 
          break;
      }
      token = strtok(NULL, "*");
      hck++;
    }
  }
file_test.close(); 

Hier war dann aber das Problem, dass - wenn die ssid oder das Passwort einen "*" enthält - mein Kartenhaus wieder zusammenbricht.
Lange Rede, kurzer Sinn: Mir fehlt hier einfach noch die Erfahrung, wie man Daten aus HTML-Formularen am besten im SPIFFS speichert und beim Auslesen aus dem SPIFFS filletiert und in Variablen bunkert.

Und da war mir JSON recht sympathisch (funktioniert ja auch prima).

Zum Speichern der JSON-Daten im SPIFFS gibt es in der ArduinoJSON.h library sogar ein Beispiel:

Aber wenn ich mit dieser Lösung meine Browser-Kompatibilität wieder zunichte mache und ihr einen guten Tipp habt, wie man sowas besser macht, freu ich mich.

Danke schon mal.

Viele Grüße & schönen Tag

noch mal von vorne:

ganz normal ein Formular mit method POST ... pures HTML

edit.
Grad ausprobiert, das wird durch den browser encoded und der ESP kann das beim Auslesen selber decoden. Ich bekomm mein & im Fließtext wie ich es brauche.

Ja, diesen Weg kenn ich.
Aber bei einem normalen Formular muss ich ja eine action angeben. Und dann verlasse ich ja die Seite. Ich will aber in diesem Fall asynchron bleiben, d.h. die Abfrage abschicken, aber auf der Seite bleiben.

Oder gibt es auch eine Möglichkeit ein Formular abzuschicken OHNE dass ich danach die bestehende Seite auf dem Client verlasse?

Klingt schon mal gut :smiley:

früher hab ich da einfach einen unsichtbaren iframe gemacht und diesen als target angegeben.

<iframe name='i' style='display:none'></iframe>

Ja, die guten alten iframes :wink:
Aber dann hab ich halt keine Rückmeldung mehr vom Server. Die hab ich bei Ajax.

Aber mal anders gefragt:
Ist die JSON-Lösung nicht gut? JSON und Ajax sollten doch von so gut wie allen Browsern verstanden werden, d.h. damit hab ich doch maximale Kompatibiliät, meinst nicht?

Aber selbst wenn ich beim puren HTML - Form bleibe:

Wie machstn du das mit dem zerhacken der Daten und speichern im SPIFFS (und wieder auslesen)? Da ist die JSON-Lösung ja auch ganz praktisch.

Ich dachte ehrlich gesagt, dass ich die Sache mit dem JSON allgemein ganz gut gelöst habe ;-(

Edit:
Der Kollege hier macht für jeden Parameter eine eigene txt:

Save Data Input to SPIFFS with ESP32 and Arduino IDE | by Jingga Mutiara Windyarahma | Medium

Find ich aber ehrlich gesagt nicht so prickelnd.

ich kümmere mich nichts ums "zerhacken" ... ich lass mir vom ESP die fertigen Parameter und Inhalte geben.

JSON ist nichts schlechts,
JavaScript auch nicht
AJAX oder Fetch API auch nicht.

Aber für das was du aktuell am Browser machst - nämlich im Fehlerfall ein

alert("Serverantwort:"+data);

wäre mir das halt alles nicht wert.