FS / json Datenaustausch mit struct

Moin,

ich möchte meine MQTT-Topic-Tree gerne in einem json-File ablegen.
Gibt es eine komfortable Möglichkeit den json-File mit einem z.B. eigenen struct zu verbinden?
Oder macht man das in guter alter Handarbeit?

Gruß
Pf@nne

Ich möchte ein multidimensionales Array bestehend aus char[20] erzeugen.
Das MultiArray soll zur Aufnahme eines TopicTrees dienen:

/*
  [0] mqttDeviceName 
   ║
   ╠═[1] WiFiConfig
   ║  ╠═[11] WEBserver
   ║  ║  ╠═[111] Username
   ║  ║  ╚═[112] Password
   ║  ╠═[12] Accesspoint
   ║  ║  ╠═[121] SSID
   ║  ║  ╚═[122] Password
   ║  ╠═[13] WiFi
   ║  ║  ╠═[131] SSID
   ║  ║  ╠═[132] Password
   ║  ║  ╚═[133] IP
   ║  ╚═[14] MQTT
   ║     ╠═[141] Server
   ║     ╠═[142] Port
   ║     ╚═[143] Status
   ║     
   ╠═[2] Control
   ║  ╠═[21] Status
   ║  ║  ╠═[211] WiFi
   ║  ║  ╚═[212] MQTT
   
*/

Ich habe versucht das Array so zu definieren:

  typedef char topicField[20];
  typedef topicField MyArray[5][10][5];
  MyArray publish;

Ziel soll es sein auf die verschiedenen Ebenen direkten Zugriff zu haben.

publish[1][2][2] = "xx"  //WiFiConfig/Accesspoint/Password

Der Compiler meckert jetzt:

libraries\ESP8266_Basic\ESP8266_Basic.cpp.o:(.bss.publish+0x0): multiple definition of `publish’

sketch\ESP8266_template.ino.cpp.o:c:\users\admin\appdata\local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\xtensa-lx106-elf\include\c++\4.8.2/functional:2027: first defined here

libraries\ESP8266_Basic\ESP8266_Basic_webServer.cpp.o:(.bss.publish+0x0): multiple definition of `publish’

sketch\ESP8266_template.ino.cpp.o:c:\users\admin\appdata\local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\xtensa-lx106-elf\include\c++\4.8.2/functional:2027: first defined here

collect2.exe: error: ld returned 1 exit status

exit status 1
Fehler beim Kompilieren.

Wie definiert man das Array richtig?
Würde auch eine Definition ohne Festlegung der Feldanzahl gehen?

Du willst einen Array mit 5x5x10x20 = 5000 Bytes für diese eine Struktur verbraten?
Halte ich für keine gute Idee.

publish[1][2][2] = "xx"  //WiFiConfig/Accesspoint/Password

Das funktioniert nicht und würde vom Compiler angemeckert, wenn er publish erkennen würde.
strcpy oder strncpy wären geeignet den Transfer zu erledigen.

Ohne deinen Kode ganz zu posten kann ich zu den Fehlern nichts sagen.

Du willst einen Array mit 5x5x10x20 = 5000 Bytes für diese eine Struktur verbraten?
Halte ich für keine gute Idee.

Daher ja die Frage, wie ich das Ganze dynamisch definiere und nur das verbrauche was auch tatsächlich benötigt wird.

z.B. mit folgendem Inhalt:

  [0] mqttDeviceName 
   ║
   ╠═[1] "WiFiConfig"
   ║  ╠═[11] "WEBserver"
   ║  ║  ╠═[111] "Username"
   ║  ║  ╚═[112] "Password"
   ║  ╠═[12] "Accesspoint"
   ║  ║  ╠═[121] "SSID"
   ║  ║  ╚═[122] "Password"
   ║  ╠═[13] "WiFi"
   ║  ║  ╠═[131] "SSID"
   ║  ║  ╠═[132] "Password"
   ║  ║  ╚═[133] "IP"
   ║  ╚═[14] "MQTT"
   ║     ╠═[141] "Server"
   ║     ╠═[142] "Port"
   ║     ╚═[143] "Status"
   ║     
   ╠═[2] "Control"
   ║  ╠═[21] "Status"
   ║  ║  ╠═[211] "WiFi"
   ║  ║  ╚═[212] "MQTT"

Ebene 0 kann entfallen....

Ich möchte mit der BaumStruktur flexibel sein, nur die maximale Tiefe würde ich festlegen wollen.
4 Ebenen sollten reichen.

Was meint er den mit "multiple definition of `publish'"?

publish[1][2][2] = "xx"  //WiFiConfig/Accesspoint/Password

Ist noch nicht im Code enthalten, war nur sinngemäß gemeint.

Wäre natürlich mit strcpy zu verwenden.

Du kannst structs innerhalb von structs anlegen.

Dynamischer Speicher ist eine Option, aber bei der Rate mit der du Leichtsinnsfehler machst, auch nicht ganz unproblematisch. Wenn man da nicht aufpasst hat man auch schnell Speicherlecks

Pfanne:
Daher ja die Frage, wie ich das Ganze dynamisch definiere und nur das verbrauche was auch tatsächlich benötigt wird.

Da könntest du ein Array von char* benutzen, dann kommst du auf 500 Bytes plus alle verwendenten Zeichenketten.
Wäre mir immer noch zu viel Overhead, aber es ist ja dein Programm.

Pfanne:
Was meint er den mit “multiple definition of `publish’”?

Was wird der Compiler damit wohl sagen wollen? publish ist mehr als einmal definiert.
Da du deinen Kode für dich behältst, kann ich dir die Zeilen nicht sagen.

Pfanne:
Ist noch nicht im Code enthalten, war nur sinngemäß gemeint.

Der Punkt ist, das Statement hat keinen Sinn.

Dynamischer Speicher ist eine Option, aber bei der Rate mit der du Leichtsinnsfehler machst, auch nicht ganz unproblematisch. Wenn man da nicht aufpasst hat man auch schnell Speicherlecks

Och menno, ich üb doch noch....... :roll_eyes:

struct in struct habe ich schon, das läuft auch.
Nur wäre ein direkter Ebenenzugriff von Vorteil (publish[1][2][2]), da man das besser in eine Schleife packen könnte.

  typedef struct TWEBserver{
    char webUser[40];
    char webPassword[40];
  }; 
  typedef struct TAccesspoint{
    char apName[40];
    char apPassword[40];
  }; 
  typedef struct TWiFi{
    char wifiSSID[40];
    char wifiPSK[40];
    char wifiIP[20];
  }; 
  typedef struct TMQTT{
    char mqttServer[20];
    char mqttPort[6];
    char mqttDeviceName[20];
 char mqttStatus[20];
  }; 
  typedef struct TWiFiConfig{
    TWEBserver WEBserver;
 TAccesspoint Accesspoint;
 TWiFi WiFi;
 TMQTT MQTT;
  }; 
  
  typedef struct TStatus{
    char WiFi[0];
    char MQTT[0];
  }; 
  typedef struct TControl{
    TStatus Status;
  };


  typedef struct Tpublish{
    TWiFiConfig WiFiConfig;
 TControl Control;
  };

Wie würdest du denn an die Sache rann gehen?

Vielleicht beschreibe ich nochmal was ich möchte:

  • Ich möchte die o.g. Baumstruktur möglicht dynamisch anlegen, statisch geht aber auch, dann müsste man den type eben bei jeder Erweierung mit anpassen. Das ist nicht schlimm.
  • Der Zugriff auch die einzelnen Felder der Zweige soll möglichst intitiv zu erreichen sein.
    Daher habe ich mich für die Zweignummern entschieden.
  • Der Baum soll in geschachtelten Schleifen durchlaufen werden können, das ist für das Durchsuchen des Baums wichtig
  typedef char* topicField;
  typedef topicField MyArray[5][10][5];
  
  MyArray publishTopic;

void setup() {
  strcpy(publishTopic[1][2][3], "Hello World");
}

void loop() {
  Serial.println(publishTopic[1][2][3]);
}

das läßt sich compilieren, führt aber zum Reset im ESP8266

Das ist oft so, wenn man auf einen NULL-Pointer schreibt. :wink:

Bei der obigen Definition wäre

  publishTopic[1][2][3] = "Hello World";

richtig. Bringt aber die Problematik, dass du nicht mehr entscheiden kannst (durch Betrachtung des Pointers), ob deine Strings dynamisch oder statisch angelegt sind. Kein Problem wenn du nur eine Sorte von Speicher - in diesem Fall also statischen - verwendest.

Warum kann ich denn jetzt topicField (char*) "direkt mit = beschreiben?

Wie könnte ich den jetzt die Tiefe noch dynamisieren?

Weil es nur ein Zeiger ist. Und das String Literal zerfällt auch zu einem Zeiger auf das erste Element (wie Arrays auch). Deshalb geht die Zuweisung.

Du musst hier wirklich verstehen was im Hintergrund abläuft. Und auf keinen Fall denken dass Code korrekt ist nur weil er kompiliert. C/C++ erlaubt es dir wild im Speicher rumzuschreiben. Das machst die Sprache mächtig aber auch sehr gefährlich.

Einen Array von Pointern auf Strukturen kannst du auch benutzen, dann musst du die Strukturen halt dynamisch (new/malloc) anlegen, eine verkettete Liste von Strukturen geht genauso (und spart Platz, da die Pointer nicht für alle Strukturen vorher angelegt werden müssen).

Die Struktur im header:

ESP8266_Basic_data.h

  typedef char* topicField;  
  typedef topicField TtopicE1[2];
  typedef topicField TtopicE2[2][2];
  typedef topicField TtopicE3[2][4][3];
  
  typedef struct TpublishTopic{
    TtopicE1 E1;
    //TtopicE2 E2;
    //TtopicE3 E3;	
  }publishTopic = {"1","2"};

Sollte für meinen Gebrauch erstmal reichen.

Wie kann ich die denn jetzt gleich beim Erstellen befüllen?
So geht es wohl nicht.

Aktuell mache ich die Zuweisung der Werte im Konstruktor einer eigenen Class.
Das funktioniert auch soweit, sieht für mich aber irgendwie nur nach einem work arround aus.

.h

  typedef char* topicField;  
  typedef topicField TtopicE1[2];
  typedef topicField TtopicE2[2][2];
  typedef topicField TtopicE3[2][4][3];
  
  typedef struct TpublishTopic{
    TtopicE1 E1;
    //TtopicE2 E2;
    //TtopicE3 E3;	
  };
  
class topic{
public:
  topic();
  TpublishTopic publishTopic;
  
private:
};

.cpp

  #include <ESP8266_Basic_data.h>

topic::topic(){ 
  publishTopic = {"1","2"};
}

Das müsste doch aber auch irgendwie im Header gehen?

Das müsste doch aber auch irgendwie im Header gehen?

Auch hier kommt dir wieder C++11 entgegen. Da kann man nicht-statische und nicht-const Variablen auch einfach im Header initialisieren.

Ohne das (also in klassischem C++) hat man zwei Optionen:
1.) Ein struct im Header als extern deklarieren und in der .cpp Datei definieren
2.) Das struct mit dem Konstruktor der Klasse initialisieren.

Und Wunder oh Wunder, wenn man mit Google nach "initialize struct in header" sucht, findet man alle drei Lösungen an einer Stelle:

Die zweite Antwort von unten. Von "Joseph Mansfield". Wobei "mystruct" deiner Klasse entspricht.
Die extern Version ist ganz unten, wobei ich davon abrate. Braucht man heutzutage nicht mehr.

Das wird sogar "uniform initialization" angesprochen. Ein weiteres C++11 Feature dass den Unterschied zwischen () und {} bei Konstruktor-Aufrufen unnötig macht.

Du musst aber unbedingt verinnerlichen, dass die Konstruktion "Variable = { }" zusammen bei der Definition geschehen muss. Wenn du denkst dass man das aufteilen kann liegt dass nur daran dass in C/C++ Definition und Deklaration zwei verschiedene Dinge sind. Mit "extern" macht man eine Variable nur bekannt. Sie existiert da noch nicht! Die andere Möglichkeit das zu umgehen sind eben Initialisierungslisten in Konstruktoren. Damit kann man sogar Konstanten und Referenzen initialisieren deren Wert sofort bei der Definition zugewiesen muss. Initialisierungslisten sind bei deinen Klassen schon ein paar mal über den Weg gelaufen, aber richtig verstanden hast du das noch nicht. Das ist eine ganz wichtige Sache in C++, vor allem bei dem was du machst! Es gibt dafür im Netz auch massig Anleitungen. Auch auf Deutsch.

Also kurzum: Verlasse dich auf C++11 und mache die Initialisierung im Header, aber versuche auch die anderen Optionen zu verstehen. Sonst verstehst du viel nicht-C++11 Code nicht

Und Wunder oh Wunder, wenn man mit Google nach "initialize struct in header" sucht, findet man alle drei Lösungen an einer Stelle:

Das Problem ist nicht meine Gogglefaulheit, sondern vielmehr die Unkenntniss über die richtigen Suchbegriffe. Ich versuche im Vorwege schon selber zu schauen, trete dann aber oft auf der Stelle, daher nochmal mein Dank an deine Geduld!

  struct TMyStruct{
    char* Field01;
    char* Field02;
  };
  
  TMyStruct MyStruct = {"Field01", "Field02"};
  int AA = 2;
 
   struct point            /* deklariert den Strukturtyp point */
  {
    int x;
    int y;
  } pkt1 = { 20, 40 };    /* definiert die Variable pkt1 und initialisiert
                             ihre beiden Felder (x = 20, y = 40) */

Die Beispiele funktionieren, ich habe jetzt aber noch das Problem, dass durch die definition einer "echten" Variablen im Header diese doppelt aufgerufen wir, da ich diesen Header noch in anderen Headern verwende.

Wie kann ich die doppelte definition der Variablen und die damit verbundene Fehlermeldung umgehen?
Ist es denn überhaupt sinnvoll Variablen dierekt im Header zu definieren?
Oder solle das lieber im Konstuktor einer Klasse passieren?

libraries\ESP8266_Basic\ESP8266_Basic.cpp.o:(.data.pkt1+0x0): multiple definition of `pkt1'

sketch\ESP8266_template.ino.cpp.o:(.data.pkt1+0x0): first defined here

libraries\ESP8266_Basic\ESP8266_Basic.cpp.o:(.data.AA+0x0): multiple definition of `AA'

sketch\ESP8266_template.ino.cpp.o:(.data.AA+0x0): first defined here

libraries\ESP8266_Basic\ESP8266_Basic.cpp.o:(.data.MyStruct+0x0): multiple definition of `MyStruct'

sketch\ESP8266_template.ino.cpp.o:(.data.MyStruct+0x0): first defined here

libraries\ESP8266_Basic\ESP8266_Basic_webServer.cpp.o:(.data.pkt1+0x0): multiple definition of `pkt1'

sketch\ESP8266_template.ino.cpp.o:(.data.pkt1+0x0): first defined here

libraries\ESP8266_Basic\ESP8266_Basic_webServer.cpp.o:(.data.AA+0x0): multiple definition of `AA'

sketch\ESP8266_template.ino.cpp.o:(.data.AA+0x0): first defined here

libraries\ESP8266_Basic\ESP8266_Basic_webServer.cpp.o:(.data.MyStruct+0x0): multiple definition of `MyStruct'

sketch\ESP8266_template.ino.cpp.o:(.data.MyStruct+0x0): first defined here

collect2.exe: error: ld returned 1 exit status

exit status 1
Fehler beim Kompilieren.

sondern vielmehr die Unkenntniss über die richtigen Suchbegriffe

Ich würde ja nichts sagen wenn da unbekannte Begriffe vorkämen. Aber es sind lediglich "struct", "Header" und "Initialisierung"

Die Beispiele funktionieren, ich habe jetzt aber noch das Problem, dass durch die definition einer "echten" Variablen im Header diese doppelt aufgerufen wir, da ich diesen Header noch in anderen Headern verwende.

Dann musst du das doch in der .cpp Datei definieren. Entweder indem du die Variable im Header als extern deklarierst und in der .cpp Datei definierst. Oder du machst es per Konstruktor und Initialisierungsliste. Beachte dass bei letzterer Option kein = dort steht

Ich würde ja nichts sagen wenn da unbekannte Begriffe vorkämen. Aber es sind lediglich “struct”, “Header” und “Initialisierung”

Hab ein weinig Nachsticht, ich bin doch stest bemüht… :slight_smile:

Ich hab das jetzt im Konstruktor gelöst, bei der Definition im Header machen mich die geschweiften Klammern irre. Im Header kann ich jedes item direkt ansprechen, das ist übersichtlicher.

.h

#pragma once
  
  const int e1 = 2;
  const int e2 = 4;
  const int e3 = 3;
  
  typedef char* topicField; 
  typedef struct TtopicE1{
    topicField item[e1];
	int count;
  };
  typedef struct TtopicE2{
    topicField item[e1][e2];
	int count;
  };
  typedef struct TtopicE3{
    topicField item[e1][e2][e3];
	int count;
  };
 
  typedef struct Tpub{
    TtopicE1 E1;
    TtopicE2 E2;
    TtopicE3 E3;	
  };
 
class topic{
public:
  topic();
  Tpub pub; 
private:
};

.cpp

#include <ESP8266_Basic_data.h>

topic::topic(){ 

// MQTT subcribe TopicTree struct


/* MQTT publish TopicTree struct

  [x] mqttDeviceName 
   ¦
   ¦-[0] WiFiConfig
   ¦  ¦-[00] WEBserver
   ¦  ¦  ¦-[000] Username
   ¦  ¦  +-[001] Password
   ¦  ¦-[01] Accesspoint
   ¦  ¦  ¦-[010] SSID
   ¦  ¦  +-[011] Password
   ¦  ¦-[02] WiFi
   ¦  ¦  ¦-[020] SSID
   ¦  ¦  ¦-[021] Password
   ¦  ¦  +-[022] IP
   ¦  +-[03] MQTT
   ¦     ¦-[030] Server
   ¦     ¦-[031] Port
   ¦     +-[032] Status
   ¦     
   ¦-[1] Control
   ¦  ¦-[10] Status
   ¦  ¦  ¦-[100] WiFi
   ¦  ¦  +-[101] MQTT   
*/

  pub.E1.count = e1;
  pub.E2.count = e2;
  pub.E3.count = e3;
  
  pub.E1.item[0] = "WiFiConfig";
  pub.E2.item[0][0] = "WEBserver";
  pub.E3.item[0][0][0] = "Username";
  pub.E3.item[0][0][1] = "Password";
  pub.E2.item[0][1] = "Accesspoint";
  pub.E3.item[0][1][0] = "SSID";
  pub.E3.item[0][1][1] = "Password";
  pub.E2.item[0][2] = "WiFi";
  pub.E3.item[0][2][0] = "SSID";
  pub.E3.item[0][2][1] = "Password";
  pub.E3.item[0][2][2] = "IP";
  pub.E2.item[0][3] = "MQTT";
  pub.E3.item[0][3][0] = "Server";
  pub.E3.item[0][3][1] = "Port";
  pub.E3.item[0][3][2] = "Status";
  
  pub.E1.item[1] = "Control";
  pub.E2.item[1][0] = "Status";
  pub.E3.item[1][0][0] = "WiFi";
  pub.E3.item[1][0][1] = "MQTT";

}

Ist das jetzt eine wirre Lösung oder würde der Profi das ähnlich machen?
Ich habe auch kurz über einen NodeTree mit *left unf *right Zeigern nachgedacht.
Hab aber außer Platzersparnis noch nicht ganz den Vorteil gesehen.