Serielle Daten auswerten und an ThingSpeak

Hallo in die Runde,

für ein kleines Projekt möchte ich gerne folgende Werte über die seriellen Schnittstelle einlesen, zerstückeln und dann bei Thingspeak hochschieben.

#00 200428173601 428 379 2000 80 2640 R--?
Hinter dem beiden "-- " kommt noch ein CR und LF

Habe mir überall was zusammen gesucht und nach besten Gewissen und Wissen zusammengeschustert.
Läuft auch soweit rund nur leider nicht über mehrere Tage :frowning:

Hier der Code dazu:

//  Libraries

#define debug                              // debug ein/aus
#include <EthernetENC.h>                   // Ethernet shield
#include <ThingSpeak.h>                    // Thinkspeak  

//  SETTINGS - SETTINGS

byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0xFB, 0x80 }; // MAC
IPAddress ip(192, 168, 0, 15);                       // Feste IP
EthernetClient client;                               // Ethernet Library als Cleint initialisieren
unsigned long myChannelNumber = xxx;             // ThingSpeak Channel
const char * myWriteAPIKey = "xxx";     // ThingSpeak WriteAPI
int f_druck = 0;
int f_percent = 0;
int f_height = 0;
int f_volume = 0;
String daten = "";                                                    // eingelesene serielle daten
String html_daten = "no data to display !";                           // html webseitendaten
String key = "";                                                      // #00 Erkennung

//SETUP - SETUP

void setup() {

  Serial.begin(9600);                                // Serielle Kommunikation starten
  Serial.setTimeout(1500);                           // RX Buffer Timeout
  pinMode(9, OUTPUT);                                // OnBoard LED auf Output
  Serial.println("");                                // CR über Serial
  Ethernet.begin(mac, ip);                           // Ethernet Verbindung mit fester IP initialisieren
#ifdef debug
  Serial.print("Arudino gestartet. IP: ");          // IP des Arduinos ausgeben
  Serial.println(Ethernet.localIP());               // IP des Arduinos ausgeben
#endif
  ThingSpeak.begin(client);                         // Initialize ThingSpeak
}

// LOOP - LOOP

void loop() {
  while (Serial.available() > 0)  {                                   // checken ob mehr als ein Zeichen im Serial Buffer is
    digitalWrite(9, HIGH);                                            // led on
    delay(500);                                                       // etwas warten
    digitalWrite(9, LOW);                                             // led off
    delay(1500);                                                      // etwas warten
    daten = Serial.readString();                                      // liest ankommende daten in "daten"
#ifdef debug
    Serial.println(" ");
    Serial.println(daten.length());                                   //wie viele Zeichen wurden gelesen
    Serial.print("Inhalt :");
    Serial.println(daten);                                            //anzeigen der eingelesenen Daten
#endif
    key = daten.substring(0, 3);                                      //erkennen ob mit #00 beginnt
    if (key == "#00")  {
      html_daten = daten.substring(3, 44);                            // Daten selektieren
      digitalWrite(9, HIGH);
      delay(100);
      digitalWrite(9, LOW);
      delay(100);
      digitalWrite(9, HIGH);
      delay(100);
      digitalWrite(9, LOW);
      delay(100);
      digitalWrite(9, HIGH);
      delay(100);
      digitalWrite(9, LOW);
      auswertung();                                                     // --> Auswertung


      ThingSpeak.setField(1, f_percent);                                    // Feld 1 mit Prozent
      ThingSpeak.setField(2, f_druck);                                      // Feld 2 mit Druckwert
      ThingSpeak.setField(3, f_height);                                     // Feld 3 mit Fuellhoehe
      ThingSpeak.setField(4, f_volume);                                     // Feld 4 mit Fuellmenge
      int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);       // write to the ThingSpeak channel
#ifdef debug
      if (x == 200) {
        Serial.println("Channel update successful.");
      }
      else {
        Serial.println("Problem updating channel. HTTP error code " + String(x));
      }
#endif
    }
  }
}

void auswertung(void)  {
  String field;
  uint8_t field_len;


  field = daten.substring(31, 34);                                  // prozente
  field_len = field.length() + 1;
  char field_char1[field_len];
  field.toCharArray(field_char1, field_len);
  f_percent = atoi(field_char1);

  field = daten.substring(26, 30);                                  // fuellhoehe
  field_len = field.length() + 1;
  char field_char2[field_len];
  field.toCharArray(field_char2, field_len);
  f_height = atoi(field_char2);

  field = daten.substring(36, 40);                                  // fuellmenge
  field_len = field.length() + 1;
  char field_char3[field_len];
  field.toCharArray(field_char3, field_len);
  f_volume = atoi(field_char3);

  field = daten.substring(17, 20);                                  // druckwert
  field_len = field.length() + 1;
  char field_char4[field_len];
  field.toCharArray(field_char4, field_len);
  f_druck = atoi(field_char4);

}

Das Ganze läuft auf einem Nano mit Ethernet Shield ENC28J60
Was mit schon aufgefallen ist, dass der serielle RX Buffer teils "kleiner" wird.
Heipt es kommt nciht der ganze String an, daher auch meine Abfrage nach den eingelesenen Zeichen. Das wurde besser durch den höheren Timeout kommt aber immer noch vor.

Bin für jeden Tipp und Anregung dankbar :slight_smile:

Zusammenfassung

Dieser Text wird ausgeblendet

Der API-Key gehört hier nicht gepostet

... und nachdem ich mal auf den Code geschaut hab, wird Dir der Speicher durch die Verwendung von String knapp, was dann zu dem von Dir geschilderten führt...
Die Verwendung von delay() in der routine

  while (Serial.available() > 0)  {                                   // checken ob mehr als ein Zeichen im Serial Buffer is
    digitalWrite(9, HIGH);                                            // led on
    delay(500);                                                       // etwas warten
    digitalWrite(9, LOW);                                             // led off
    delay(1500);      

macht es nicht besser.

Hi,

API KEY raus geschmissen - Verstanden :slight_smile:

Aber was sagt mir die Info, dass die Verwendung von String meinen Speicher knapp macht.
Was gibt es für Alternativen?
Gibt es nen Beispiel wo ich mich dran lang hangeln kann?
Das ich kein Profi bin ist wohl ersichtlich ^^

Danke & Gruss
Peff

String funktioniert auf den 8-Bit-AVRs mit wenig RAM nicht so gut. Der Speicher wird möglicherweise fragmentiert. Alternativ verwendet man den Typ char als Feld.

Wie häufig kommt die Zeichenkette über die serielle Schnittstelle?

jede Minute. Mir würde es aber reichen wenn man sie alle 5min verarbeiten würde, also nach ThingSpeak hochzuladen.

Du hast zwei Fehler gemacht:

  1. Bei while müssen alle Daten schon eingelesen sein, was normalerweise nicht der Fall ist.
  2. Wegen delay wird das Einlesen verzögert.

Glücklicherweise heben sich die Fehler gegenseitig auf, weil nach 2 Sekunden und 9600 Baud auch das letzte Zeichen sicher eingelesen ist.

Das könnte man besser machen, aber mit dem beschriebenen Fehler hat das nichts zu tun.

O.K. also das while gegen if getauscht .

Das mit dem delay hatte ich gelesen, dass es sicherer sei.

Nun müsste ich aber erst mal Grundsätzlich meinen String Geschichte in Char umfrekeln.
Um das stabiler zu bekommen.

Habe schon hier und da quer gelesen, aber nichts gefunden woraus ich mir was zusammensetzen könnte :frowning:
Hast Du vielleicht ein Beispiel?

Ach das ist nicht schwer.
Erster Einstieg:
https://www.wikinger-tommy.de/arduino/tut_zeichenketten.html

Taste Dich langsam ran.

Meine Beispiele sind nicht so gut wie das von Tommy.

Wenn Du zunächst mit dem Einlesen und der Ausgabe auf dem seriellen Monitor anfängst, kann ich Dir helfen, da ich EthernetENC und ThingSpeak nicht kenne.

Klar, genauso habe ich ja auch mit der jetzigen Variante angefangen.

Habe mich schon ein wenig in das Char Thema rein gelesen.

Das Beispiel 5 von hier geht ja schon in die richtige Richtung.
Serial Input Basics - updated
Mir ist nur gerade nicht klar wie ich den Datenstrang ohne Trennzeichen, sondern nach Stellen im Strang zerlege.

Vielleicht etwas viel an Kram drumrum, aber schau die Funktion readSerial() und teileBuf() an - das ist die Grundlage nach der Du alles auflösen kannst.

Rustikal:

    if ( puffer[0] == '#' && puffer[1] == '0' && puffer[2] == '0' ) {

Eleganter:

if ( !strncmp(puffer, "#00", 3) ) { // bei Gleichheit Rückgabewert 0

So ungefähr :slightly_smiling_face:

ja, das wäre dann ja die Sicherheitsfrage ob die Auswertung stattfinden soll oder nicht.
Also ob die Daten beginnen.

Woraufhin ich dann ja die Daten selektiere und die Auswertung starte.
Und genau da klemmt es gerade bei mir....
Hiermit

  field = daten.substring(31, 34);                                  

kann ich ja schön die Stelle angeben wo meine Daten stecken.
Und damit die Variable befüllen

Das kannst Du auch mit dem, was ich Dir versucht habe zu zeigen.

Der Einstieg bei mir wäre

das Teilen des Puffers und dann kommt jeder Wert einzeln raus:

15:09:00.999 -> Start... 
15:09:00.999 -> Ausgabe
15:09:00.999 -> #00
15:09:00.999 -> 200428173601
15:09:00.999 -> 428
15:09:00.999 -> 379
15:09:00.999 -> 2000
15:09:00.999 -> 80
15:09:00.999 -> 2640

Sowas ähnliches machst Du auch.
Steht unten am Ende rauskopiert voin Dir...

char teststring[] = "#00 200428173601 428 379 2000 80 2640 R--\r\n";

unsigned int f_druck = 0;
unsigned int f_percent = 0;
unsigned int f_height = 0;
unsigned int f_volume = 0;

void setup() {
  Serial.begin(115200);
  Serial.println(F("Start... "));
  teileBuf(teststring);

}

void loop()
{

}

void teileBuf(char *buf)               // Teilt den Puffer
{
  char *a;
  char *b;
  unsigned int c = 0;
  unsigned int d = 0;
  unsigned int e = 0;
  unsigned int f = 0;
  unsigned int g = 0;


  a = strtok(buf, " ");                // Übernehme #00
  b = strtok(NULL, " ");               // Übernehme ID?

  c = atoi(strtok(NULL, " "));
  d = atoi(strtok(NULL, " "));
  e = atoi(strtok(NULL, " "));
  f = atoi(strtok(NULL, " "));
  g = atoi(strtok(NULL, " "));
  Serial.println(F("Ausgabe"));
  Serial.println(a);
  Serial.println(b);
  Serial.println(c);
  Serial.println(d);
  Serial.println(e);
  Serial.println(f);
  Serial.println(g);

  /*
    f_percent = atoi(field_char1);
    f_height = atoi(field_char2);
    f_volume = atoi(field_char3);
    f_druck = atoi(field_char4);
  */
}

Oh man, der Wald und die Bäume.
Du selektiert nach den Leerzeichen.
Ich habe mich so auf Trennzeichen fixiert, dass ich Space nicht "gesehen" habe.
Danke Dir!

Naja, Du hättest ja auch zählen können.

Mach das doch - musst nur auf die Eigenheiten achten.
Ich hab da 2640 raus.

[edit - Code kommentiert]

// Forensketch CharArray aufteilen II
// https://forum.arduino.cc/t/serielle-daten-auswerten-und-an-thingspeak/871680/16

char teststring[] = "#00 200428173601 428 379 2000 80 2640 R--\r\n";

void setup() {
  Serial.begin(115200);
  Serial.println(F("Start... "));
  // Einmalige Ausgabe
  // übergeben wird das Array, sowie Anfang und Ende des Ausschnitt
  Serial.println(teileBuf(teststring, 33, 36)); 
}

void loop()
{

}

uint16_t teileBuf(char *buf, const uint8_t von, const uint8_t bis) // Teilt den Puffer
{
  uint16_t lokalZahl=0;                 // lokale Hilfsvariable
  char lokalBuf[bis - von] = {"\0"};    // Größe wird aus der Anzahl der Zeichen gebildet
  for (uint8_t i = von; i <= bis; i++)  // Position im CharArray ...
  { 
    lokalBuf[i - von] = buf[i];         // ... Zeichen übertragen ...
  }

  Serial.print(F("Ausgabe in Funktion: "));
  Serial.println(lokalBuf);
  lokalZahl=atoi(lokalBuf);             // ... in Zahl wandeln ...
  return lokalZahl;                     // ... und zurück geben
}

Dank Deines Beispiels und dem Example 5 von hier Link bin ich nun schon ein ganzes Stück weiter und verstehe auch -fast- alles was ich da so verbreche.
Habe nun alle relevanten Daten in Variablen und schaue mal das ich Thingspeak und Ethernet reinbaue.

Nochmal besten Dank soweit.
Hoffen wir mal die Mühe lohnt und es lauft mit dem Weg stabiler als über ReadString