client.read() Problem

Hallo Leute,

folgendes Problem: Ein Arduino Uno (Ethernet) fungiert als Client und soll sich mit einem Server verbinden (auf einen bestimmten Port). Ist der Arduino verbunden beginnt der Server zyklische Daten an den Client, also den Arduino zu senden (momentan aller 2 Sekunden). Die Daten die der Server sendet sind einige Statusdaten und werden Insgesamt als String gesendet und durch einen Delimiter (:slight_smile: getrennt:

Beispiel-String vom Server: 12 V:1 A:3 h

char antwort;

...

while(client.connected())
  {
    while(client.available())
    {
      antwort = client.read();
      Serial.write(antwort);
    }

...

Lass ich mir die ankommenden Daten anzeigen funktioniert das auch wunderbar. Mein problem liegt viel mehr in der Weiterverarbeitung.

Wie man aus dem Beispiel erkennt kommen folgende Daten:
12 V
1 A
3 h

Quasi immer Wert und Einheit. Diese Daten sollen nun Anhand des Delimiters (:slight_smile: getrennt werden und bestenfalls in ein Mehrdimensionales Array und String Array geschrieben werden. Immer wenn neue Daten ankommen sollen diese wieder auseinander genommen werden und die alten überschrieben werden, sodass man diese dann immer weiterverarbeiten kann.

Ich habe schon einiges Versucht, bin aber noch nicht annähernd zum Ziel gekommen. kan von euch jemand mit einem konkreten Code helfen? Danke für eure Mühe!

Warum willst du einen fertigen Sketch ?
Du liest mit antwort = client.read(); ein.

Mach ein Char Array aus antwort draus und verwalte einen Index.
char antwort [maxlen];
Dann brauchst du noch ein Ende Zeichen der markiert wenn ein Telegramm zu Ende ist.

Das Zerlegen (Parsen) des Strings geht seht gut mit strtok, in der Funktion kannst du die Delimiters definieren.

Ein bisschen Eigeninitiative sollte doch gerade bei einem Hobby Projekt der Antrieb sein.

bestenfalls in ein Mehrdimensionales Array und String Array geschrieben werden

Mach es anders: In ein char Array einlesen (d.h. ein C String) und dann in Integer konvertieren

Und die Einheiten kannst du weglassen. Die sind unnötig. Einfach so:

12,1,3

Buchstaben kann man bei Bedarf voranstellen. Das braucht aber nur wenn die Telegramme mal unterschiedlich abgebaut sind. Dann kann man daran fest-stellen was die Zahl sein soll.

Wobei die Einheiten die Konvertierungs-Funktionen auch nicht stören.

Hier habe ich gerade erst gezeigt wie man sowas von einer SD Karte einlesen kann:
http://forum.arduino.cc/index.php?topic=287092.msg2010578#msg2010578

Das Vorgehen hier ist fast identisch. Lediglich zur Wandlung dann atoi() statt strtoul() verwenden.

Eine andere Möglichkeit ist das in einem Rutsch einzulesen (statt Zahl für Zahl) und erst dann zu Parsen. Hier mal für eine Eingabe von Serial (und der String auch mit '#' abgeschlossen):

const int SERIAL_BUFFER_SIZE = 21;
char serialBuffer[SERIAL_BUFFER_SIZE];

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  if (readSerial())
    parseSerial();
}

bool readSerial()
{
  static byte index;

  while (Serial.available())
  {
    char c = Serial.read();

    if (c == '#')
    {
      serialBuffer[index] = '\0';
      index = 0;
      return true;
    }
    else if (c >= 32 && index < SERIAL_BUFFER_SIZE - 1)
    {
      serialBuffer[index++] = c;
    }

  }
  return false;
}

void parseSerial()
{
  int volt = atoi(strtok(serialBuffer, ","));
  int current = atoi(strtok(NULL, ","));
  int time = atoi(strtok(NULL, ","));

  Serial.println(volt);
  Serial.println(current);
  Serial.println(time);
  Serial.println();
}

Ich habe hier 3 mal strtok() per Hand gemacht. Das geht wenn weiß dass man drei Zahlen erwartet. Aber man kann auch strtok() einfach in einer Schleife machen:

void parseSerial()
{
  const byte expectedValues = 3;
  int values[expectedValues];

  byte index = 0;
  char* ptr = strtok(serialBuffer, ",");
  while (ptr != NULL && index < expectedValues)
  {
    values[index++] = atoi(ptr);
    ptr = strtok(NULL, ",");
  }

  for (int i = 0; i < index; i++)
    Serial.println(values[i]);

  Serial.println();
}

Wie gesagt wichtig, ist dass du den String irgendwie mit einem Endzeichen markierst. Das wird hier abgefragt:

if (c == '#')

Ein weiteres Komma ist auch eine Option. Du musst lediglich die Abfrage anpassen

rudirabbit:
Warum willst du einen fertigen Sketch ?
Du liest mit antwort = client.read(); ein.

Mach ein Char Array aus antwort draus und verwalte einen Index.
char antwort [maxlen];
Dann brauchst du noch ein Ende Zeichen der markiert wenn ein Telegramm zu Ende ist.

Das Zerlegen (Parsen) des Strings geht seht gut mit strtok, in der Funktion kannst du die Delimiters definieren.

Ein bisschen Eigeninitiative sollte doch gerade bei einem Hobby Projekt der Antrieb sein.

Hab ich schon probiert, das wollte nicht wie ich mir dachte. Als Ausgabe erschienen nur kryptische Zeichen. Kannst du mal ein Stück Code schreiben so wie du dachtest?

@Serenifly:

Naja, die Einheiten sollen dran bleiben. Die Daten werden im Anschluss auf einem Display angezeigt.

Ich dachte mir das in etwa so:
Dieser Code liest 10 Zeichen in einen Sring ein, den du später parsen kannst.
Der Code hat aber einen gewaltigen Nachteil, denn er erkennt den Anfang nicht.
Deshalb musst du eine Ende Zeichen auf den Server einführen, z.b ein '#'
Als Ende Marke könntest du auch das 'h' nehmen.

char datareceive[10];


boolean getdata()
{
while(client.available())
 {
	 
	  char c=client.read();
	  if (index<=9)	datareceive[index++]=c;  
		   
		else
                      {
                        datareceive[index]='\0';
			index=0;
						
                       return true;
						     
	                }

 
 }
return false;
	
}




void loop()
{
 
while(client.connected()) { 
if (getdata)  Serial.write(datareceive); 
                          }
}

Aus:

 if (index<=9)	datareceive[index++]=c;

wird dann dies

 if (index<=9 && c!='#' ) datareceive[index++]=c;

Dieser Code würde dann wenn ich richtig gedacht habe solange Daten in den String schreiben bis das '#' oder bis 10 Zeichen erreicht sind.
Die genaue Länge und die Ende Marke weiß ich nicht das müstest du anpassen. Wie gesagt als Ende Marke könnte auch das 'h' funktionieren.

Als Ausgabe bekommst du einen String z.b. "12 V:1 A:3 h"
Wenn da bei dir so funktioniert, geht es mit dem Parsen weiter.
Versuche es erstmal bis hier.

tooony:
Naja, die Einheiten sollen dran bleiben. Die Daten werden im Anschluss auf einem Display angezeigt.

Die kannst du ja danach wieder einfügen. So in der Art:

lcd.setCursor(...);
lcd.print(volt), lcd.print(" V");

Das ist völlig flexibel

Du kannst meinen Code aber doch ganz einfach so anpassen, dass er gleich mit C-Strings umgeht. Schau dir mal strtok() an:
http://www.cplusplus.com/reference/cstring/strtok/

strtok() liefert dir jeweils einen Zeiger auf den Anfang des nächsten Teil-Strings und terminiert diesen

Also einfach parseSerial() leicht ändern:

void parseSerial()
{
  char* volt = strtok(serialBuffer, ":"));       //Delimiter kann man hier anpassen
  char* current = strtok(NULL, ":");
  char* time = strtok(NULL, ":");
}

Und schon hast du Zeiger auf drei Teil-Strings. Und man muss sie nicht mal extra abspeichern

Wie gesagt, du bist hier sehr flexibel, da Auslesen und Verarbeiten getrennt ist. Solange das Endzeichen da ist (und der Puffer groß genug ist), wird alles eingelesen. Dann muss man nur die Parse-Funktion seinen Wünschen anpassen.

Hallo Leue,

mittlerweile bin ich mit meinem Problem weitergekommen, aber das nächste wartet schon. Das Aufnehmen der Daten über client.read und das anschließende parsen funktioniert mittlerweile. Hier erstmal der aktuelle Stand:

  while(client.connected())
  {
    while(client.available())
    {
      //client.write("READ:VOLT?(@0-15)");
      antwort[x] = client.read();
      if(antwort[x] == '!' || antwort[x] == '\r') { 
        antwort[x] = '\0';       
        ptr = strtok(antwort, delimiter);
        //strncpy(length_str,antwort,4);
        length_str[0] = antwort[0];
        length_str[1] = antwort[1];
        length_str[2] = antwort[2];
        length_str[3] = antwort[3];
        length_str[4] = '\0';
        length = atoi(length_str)*1;
        
        if(x == length-1) {
          Serial.write("Laenge stimmt\n"); 
          /*
          for (x = 5; antwort[x] != 0; x++) { 
            if( (antwort[x] == ':')) { 
              antwort[x] = '\n'; 
            } 
          }*/
           Serial.write(antwort);         
           while(ptr != NULL) {
                //Serial.write(ptr);
                //Serial.write("\n");  
        	// naechsten Abschnitt erstellen
         	ptr = strtok(NULL, delimiter);
                strcat(ausgabe, ptr);      
          }
          strcat(ausgabe, ptr);
          out = ausgabe;
          genie.WriteStr (0,out);
          //Serial.write(temp);   
          x=0; 
          y=0;        
        } else {
          Serial.write("Packet-Laenge stimmt nicht\n");
          Serial.write("Auf neue Daten warten...\n");
        } // IF length
        
        //Serial.write(antwort);
      } else {
        x++;
      } // antwort[x]

Neu hinzugekommen ist eine Längenprüfung, d.h. im ankommendem String steht zu beginn die Länge. Diese ist dann für die anschlieende Ausgabe nicht relevant.

Im nächsten Schritt sollen die geparsten Werte auf einem 4D Systems Display ausgegeben werden. Und genau da liegt das Problem.

Mit der Funktion

genie.WriteStr (0,out);

wird ein String auf dem Display ausgegeben. Berachte ich das ganze gleichzeitig im Serial Monitor erscheint aber nichts nennenswertes.

Theoretisch wäre das Parsen an dieser Stelle auch wieder überflüssig, da man alle Delimiter durch einen "\n" erzetzten könnte. Die einzelnen herausgeparsten Werte sollen untereinander ausgegeben werden, also immer auf einer neuen Zeile.

Hat jemand schon Erfahrungen diesbezüglich? Kann jemand helfen? Danke schonmal im voraus!

Gruß

tooony

tooony:
Hat jemand schon Erfahrungen diesbezüglich? Kann jemand helfen? Danke schonmal im voraus!

Anbei sende ich Dir mal einen Code zum Sammeln von Zeichen und Trennen der Parameter als Funktion "handleChar":

void setup() {
  Serial.begin(9600);
}

char* handleChar(char c)
{
  #define PARAMSIZE 11   // Maximale Parameterlänger + '\0'
  char paramTrenner=':'; // Trennzeichen zwischen Parametern
  static byte paramLen=0;// Zählt empfangene Zeichen für den Parameter
  static char str[PARAMSIZE]; // Empfangener Parameter-String
  char* result=NULL;
  if (c>=' ' && c!=paramTrenner)
  {
    if (paramLen<PARAMSIZE-1)
    {
      if (paramLen==0) memset(str,0,sizeof(str));
      str[paramLen]=c;
      paramLen++;
    }
  }
  else
  {
    if (paramLen>0) result=str;
    paramLen=0;
  }
  return result;
}

void loop() {
  if (Serial.available())
  {
    char* parameter= handleChar(Serial.read());
    if (parameter != NULL) Serial.println(parameter);
  }
}

Der Aufruf in der loop kann leicht von Serial.available()/Serial.read() auf Client.available()/Client.read() umgeschrieben werden.

Die Funktionsweise ist wie folgt:
Immer wenn Du ein Zeichen auslesen kannst, übergibst Du es zur Verarbeitung an die Funktion "handleChar", die einen char-Pointer als Rückgabewert liefert:

  • Entweder ist der Rückgabewert ein NULL Pointer, dann ist der Parameter noch unvollständig
  • Oder der Rückgabewert ist von NULL verschieden, dann ist es ein empfangener String-Parameter

Parameter werden entweder durch ein einzelnes Trennzeichen getrennt, das in der Funktion definiert werden kann und von mir mit ':' vordefiniert ist. Oder die Trennung erfolgt durch ein Steuerzeichen, z.B. ein Zeilenende-Zeichen. Empfangene Parameter werden erst mit einer Mindestlänge von 1 zurückgeliefert, also Parameter-Strings als "Leerstring" mit Länge 0 werden unterdrückt und nicht zurückgeliefert.

Vielleicht kannst Du damit etwas anfangen.

jurs:
Vielleicht kannst Du damit etwas anfangen.

Danke für deine Hilfe und diese super Funktion! Aber leider liegt das Problem wo anders:

Wie die Daten ankommen, also mit dem ":" getrennt weißt du ja schon. Davon sind die ersten 4 Byte für eine Längenprüfung.

Alle anderen Parameter sollen dann mit der Funktion genie.WriteStr() ausgegeben werden. Und bei dieser Ausgabe liegt das Problem. Theoretisch müsste man jeden ":" durch eine neue Zeile ("\n") ersetzten. Quasi wäre das parsen an sich nicht zwingend notwendig (so im nachhinein).

Fakt ist aber, dass die einzelnen Parameter trotzdem nicht auf einer neuen Zeile erscheinen. Wenn ich aber zum Beispiel folgendes manuell mache:

genie.WriteStr(0, "Halle\nTest1\nTest2\n");

Dann wird alles untereinander ausgegebn, so wie es soll.

tooony:
aber, dass die einzelnen Parameter trotzdem nicht auf einer neuen Zeile erscheinen. Wenn ich aber zum Beispiel folgendes manuell mache:

genie.WriteStr(0, "Halle\nTest1\nTest2\n");

Dein "genie" Objekt hat nur eine "WriteStr" Funktion aber kein "WriteChar" zur Ausgabe einzelner Zeichen? In dem Fall kannst Du natürlich auch ein einzelnes Zeichen wie das Newline-Zeichen als String ausgeben. Ich weiß nicht, wofür der erste Parameter im Funktionsaufruf steht, aber vielleicht hängst Du die Ausgaben einfach hintereinander:

if (parameter != NULL)
{
  genie.WriteStr(0,parameter); // Parameter ausgeben
  genie.WriteStr(0,"\n");          // und ein "New Line" hinterhersenden
}

Die genie-Funktion ist wie folgt definiert:

genie.WriteStr(uint16_t, char*)

Der Int Wert beschreibt dabei nur das Display-Feld wo die Ausgabe stattfindet.