Arduino Nano <- ESP8266 ESP-01S -> SoftwareSerial - JSON

Guten Abend!

Ich finde 1000fach Hilfestellungen zum Thema, leider komme dennoch nicht weiter, und frage nun um Rat.

Von einem ESP8266 ESP01-S sende ich eine empfangene JSON-Kette über TX und empfange diese am Arduino über SoftwareSerial.

Problem ist, das die Verarbeitung am Arduino nicht richtig funktioniert. Ein rohes Serial.write(ESPserial.read()); laboriert tadellos, sobald ich die Daten verarbeiten möchte, jedoch folgendes Szenario: das Programm verarbeitet die Daten teilweise fehlerlos, dann "hängt" es teilweise recht lange und tut gar nichts (gibt weder am Seriellen Monitor etwas aus, noch reagieren die BuiltIn LEDs, wie im Code angewiesen, und hin und wieder gibt parseObject einen Fehler aus (JSON Parsing failed).

Ich bin mir relativ sicher, dass ich Fehler darin begehe, wie ich die Daten Seriell empfange bzw. diese verarbeite.

Von der Hardware betrachtet würde ich behaupten, für mich Laien, das alles in Ordnung ist (Indikator hier, das Serial.write() mir eben den korrekten Empfang bestätigt, solange wie das Programm nichts anderes zu tun bekommt).

Vielen Dank für Tipps und Hilfe / und Kritikpunkte.

Programm auf dem ESP-01S:

// http://arduino.esp8266.com/stable/package_esp8266com_index.json
// Node MCU 1.0 ESP 12E Module
// ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>

// WIFI
const char* ssid  = "XXX";
const char* password = "XXX";

// JSON
#include <ArduinoJson.h>

// Programm Variables
int PIXEL_DATA_SIZE = 7;
int requestperiod = 5000;

// Stuff
unsigned long time_now = 0;

void setup()                                   
{
    //Serial.begin(115200);
    Serial.begin(9600);
    
    //Serial.println();
    //Serial.print("Verbinde zu ");
    //Serial.println(ssid);           // Kontrollelement im seriellen Monitor

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid,password);      // Die WLAN Verbindung wird mit der oben definierten SSID und dem zugehörigen Passwort hergestellt
    
    while (WiFi.status() != WL_CONNECTED)  // Solange noch keine WLAN-Verbindung besteht....
    {
      delay(500);
      //Serial.print(".");                    // ... sollen Punkte ausgegeben werden. Die Punkte dienen als Kontrollelement.
      digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
      digitalWrite(LED_BUILTIN, LOW);    // turn the LED off 
    }

     //Serial.println(" ");
     //Serial.println("WiFi verbunden");   // Kontrollelement im seriellen Monitor

}

void loop() {

    if(millis() > time_now + requestperiod)
    {
        time_now = millis();
        getStatus();
    }
}


void getStatus() {
  HTTPClient http;
  http.begin("http://192.168.2.106:80/arduino/XXX");
  
  int httpCode = http.GET();

  if (httpCode > 0) {

    //Serial.print("Request OK ");
    //Serial.println(millis());

    String response = http.getString();
    http.end();
    
    Serial.println(response);
    delay(10);

    const size_t bufferSize = JSON_OBJECT_SIZE(PIXEL_DATA_SIZE) + 40;
    DynamicJsonBuffer jsonBuffer(bufferSize);
    
    JsonObject& root = jsonBuffer.parseObject(response);
    requestperiod = root["requestperiod"];
    
  }
  
  http.end();
}

Programm auf dem Arduino:

// Basic serial communication with ESP8266
// Uses serial monitor for communication with ESP8266
//
//  Pins
//  Arduino pin 2 (RX) to ESP8266 TX
//  Arduino pin 3 to voltage divider then to ESP8266 RX
//  Connect GND from the Arduiono to GND on the ESP8266
//  Pull ESP8266 CH_PD HIGH
//

// JSON
// ArduinoJson - Version: 5.13.2
#include <ArduinoJson.h>
#include <ArduinoJson.hpp>

int whitePIN = 11;

// Programm Variables
int PIXEL_DATA_SIZE = 7;
int PIXELdelay;
int PIXELwhite0;
int PIXELred0;
int PIXELgreen0;
int PIXELblue0;
int PIXELmode = 1;

#include <SoftwareSerial.h>
SoftwareSerial ESPserial(2, 3); // RX | TX
 
void setup() 
{
    Serial.begin(9600); // communication with the host computer

    // Start the software serial for communication with the ESP8266
    ESPserial.begin(9600);  
    
    pinMode(whitePIN, OUTPUT);
}
 
void loop() 
{

    //analogWrite(whitePIN, PIXELwhite0);
    //Serial.print("white: ");
    //Serial.println(PIXELwhite0);
  
    // listen for communication from the ESP8266
    while ( ESPserial.available() == 0 )   {
        Serial.println("available loop");
    }
    String json;
    //strcpy(json, ESPserial.readString());
    
    json = ESPserial.readString();
    Serial.println(json);
    
    const size_t bufferSize = JSON_OBJECT_SIZE(PIXEL_DATA_SIZE) + 40;
    DynamicJsonBuffer jsonBuffer(bufferSize);
    JsonObject& root = jsonBuffer.parseObject( json );
        
    
    /*
    PIXELmode = root["pixelmode"];
    PIXELdelay = root["delay"];
    PIXELwhite0 = root["white0"];    
    PIXELred0 = root["red0"];
    PIXELgreen0 = root["green0"];
    PIXELblue0 = root["blue0"];
    */
    
    if (!root.success()) {

          for (int i=0; i <= 3; i++) {
              digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
              digitalWrite(LED_BUILTIN, LOW);    // turn the LED off 
          }
          
          Serial.println("JSON Parsing failed");
          //delay(5000);
          
    } else {

        //Serial.println(json);

        PIXELwhite0 = root["white0"];
        analogWrite(whitePIN,PIXELwhite0);
    } 
}

Rumgerate:
Wie stabil läuft der Arduino wenn du ihn mit einem Terminal-Programm zügig den JSON schickst?

So richtig kann ich nicht helfen, daher nur Themen die du vermutlich nicht hören willst:
-Ich würde eher kein Software-Serial verwenden
-Wenn schon dann die empfangenen Bytes einzlnen in ein char array sammeln bis zum Ende-Zeichen, und dann erst das JSON parsen
-Warum überhaupt den Arduino, warum reicht der ESP nicht?

  • Wenn Arduino und ESP nebenenander sind, I2C verwenden.

Wenn ich Deinen Code richtig verstehe, hängt der Arduino per Softserial am Serial vom ESP8266. Richtig?

Dann sind auf dem ESP solche Zeilen, wie

Serial.println(response);

eher kontraproduktiv, wenn Du irgendwann ein JSON schicken willst.

Ich mache sowas lieber mit Arduino und Wemos D1 mini pro (früher auch mit NodeMCU), die ich über Pegelwandler und I2C koppel oder überlege, ob Du den Arduino überhaupt noch brauchst. Der ESP8266 ist der leistungsfähigere MC. Er hat nur nicht so viele IO. Da gibt es aber I2C-Erweiterungen dazu.

Gruß Tommy

Hi

Wie stabil läuft der Arduino wenn du ihn mit einem Terminal-Programm zügig den JSON schickst?

Der Arduino läuft stabil, ich habe die Tage zum Test auch einmal andere Programme drauf, er tut was er soll. Im Prinzip tut er auch was er soll, wenn ich ihm die JSON-Kette direkt serviere ohne das er diese Seriell empfangen muss.

-Wenn schon dann die empfangenen Bytes einzlnen in ein char array sammeln bis zum Ende-Zeichen, und dann erst das JSON parsen
-Warum überhaupt den Arduino, warum reicht der ESP nicht?

  • Wenn Arduino und ESP nebenenander sind, I2C verwenden.

zu 1. darf ich ganz blöd fragen, wie genau ermittle ich das Ende-Zeichen? auf diese Varianten bin ich auch bereits im Netz gestoßen (ich habe noch nie mit den Seriellen Schnittstellen eigenhändig agiert, ich habe schon gemerkt, das ich mir hier noch wissen aneignen muss... Taste mich da eben nun ran.

zu 2. Weil ich weit aus mehr Pins benötige, und im Weiteren um zu lernen wies funktioniert :slight_smile:

zu 3. einen Logic-Leven Konverter habe ich mir gestern im Amazon geordert, danke für die Bestätigung!

Wenn ich Deinen Code richtig verstehe, hängt der Arduino per Softserial am Serial vom ESP8266. Richtig?

Korrekt!

eher kontraproduktiv, wenn Du irgendwann ein JSON schicken willst.

Okay, und wieso? Denn ich schicke hiermit ja genau die vom Server gelieferte JSON-Antwort an den Arduino (und die kommt ja "quasi" auch an). Was kann ich hier optimieren?

Ich mache sowas lieber mit Arduino und Wemos D1 mini pro (früher auch mit NodeMCU), die ich über Pegelwandler und I2C koppel oder überlege, ob Du den Arduino überhaupt noch brauchst. Der ESP8266 ist der leistungsfähigere MC. Er hat nur nicht so viele IO. Da gibt es aber I2C-Erweiterungen dazu.

Auch interessant! Ist mir nach absenden des Posts auch eingefallen ... exakt dieses gleiche Prozedere, habe ich ebenfalls auf einem NodeMCU hier welchen ich vor einigen Monaten getestet habe, und das läuft tadellos (eben ohne die Serielle Kommunikation). Nun kommt eben die Stelle mit dem Seriellen hinzu, daher versteife ich mich hier gerade drauf, das der Fehler in der Verarbeitung liegt. Da werde ich aber mal den Tipp von noiasca nun primär nachgehen.

(Ich könnte jetzt natürlich auch einfach den NodeMCU... aber das ist nicht meine Intention, dadurch werde ich nicht klüger)

Grüße und Danke
Marc

icrew:
zu 1. darf ich ganz blöd fragen, wie genau ermittle ich das Ende-Zeichen?

Bei Zeichenketten sind die Zeichen CR 0x0D, LF 0x0A oder EOT 0x04 beliebt. Ein neues Zeichen wird mit dem Ende-Zeichen verglichen, bei Ungleichheit gespeichert, bei Gleichheit die gewünschte Aktion ausgeführt.

Der Typ String steht in diesem Forum im Verdacht, für Instabilitäten zu sorgen, ein Feld vom Typ char gilt als besser.

nimm mal das Tutorial von Robin2 in die Hand und optimiere deinen Zeichenempfang.
http://forum.arduino.cc/index.php?topic=396450

Wie stabil läuft der Arduino wenn du ihn mit einem Terminal-Programm zügig den JSON schickst?

Der Arduino läuft stabil, ich habe die Tage zum Test auch einmal andere Programme drauf, er tut was er soll. Im Prinzip tut er auch was er soll, wenn ich ihm die JSON-Kette direkt serviere ohne das er diese Seriell empfangen muss.

ich meinte nicht die Stabilität mit einem anderen Programm oder einer hardcoded JSON, sondern wenn aktuelle das Programm drauf ist z.B. vom PC aus den Arduino dauerhaft befeuern.

darf ich ganz blöd fragen, wie genau ermittle ich das Ende-Zeichen?

ergänzend zu den obigen Antworten, in deinem Fall könnt auch das abschließende } aus dem JSON das Ende-Zeichen darstellen. Kommt drauf an ob der ESP am Ende überhaupt den Linefeed schickt.

zur I2C Sache:
ich finde das stabiler, schneller, und für den ESP passender und am Arduino sparst dir den SoftSerial. Wenn du diesen Weg gehen willst lies dich ein wenig in I2C ein. Wenn du dann ein Beispiel brauchst melde dich explizit dazu, ich denke einige von uns haben hiezu Beispiele.

Einen schönen Samstag wünsche ich, klasse, das es Regnet... bleibt mehr Zeit hierfür :-))

Lieben Dank für eure weiteren Antworten, diesen werde ich direkt auch einmal weiter nachgehen.

Nach einigem herumprobieren und testen, habe ich aktuell ein Szenario hinbekommen, welches scheinbar zunächst einmal tut was es soll. Ob es das sinnvoll tut, wage ich dezent zu bezweifeln.

// Basic serial communication with ESP8266
// Uses serial monitor for communication with ESP8266
//
//  Pins
//  Arduino pin 2 (RX) to ESP8266 TX
//  Arduino pin 3 to voltage divider then to ESP8266 RX
//  Connect GND from the Arduiono to GND on the ESP8266
//  Pull ESP8266 CH_PD HIGH
//

// JSON
// ArduinoJson - Version: 5.13.2
// Aktuelle Version nicht kompatibel
#include <ArduinoJson.h>
#include <ArduinoJson.hpp>

// KSQ PINS 750ma
int whitePIN = 11;

// KSQ PINS 350ma
// int redPIN = 10;
// int greenPIN = 9;
// int bluePIN = 6;

// Programm Variables
int PIXEL_DATA_SIZE = 7;
int PIXELdelay;
int PIXELwhite0;
int PIXELred0;
int PIXELgreen0;
int PIXELblue0;
int PIXELmode = 1;

#include <SoftwareSerial.h>
SoftwareSerial ESPserial(2, 3); // RX | TX

// Serial-Variables
char ser_json[128]; // Store received chars
boolean save = false; // Store or not to store the Serial.read chars
String json = ""; // The json String to parse
 
void setup() 
{
    Serial.begin(9600);
    ESPserial.begin(9600);  
    pinMode(whitePIN, OUTPUT);
}
 
void loop() 
{

    // Inkrement für char array
    int i;

    // listen for communication from the ESP8266
    while ( ESPserial.available() > 0 ) {
      
        char ser_read = ESPserial.read();
        //char inChar = (char)ser_read;
      
        // Find Start-char
        if(ser_read=='{') {
            save = true;
        }
        // Store char
        if(save==true) {
            ser_json[i] = ser_read;
            i++;
        }
        // Find End-char
        if(ser_read=='}') {
            save = false;
        }
      
        // Halte die Reihenfolge ein
        Serial.println(i);
        
        // String komplett? Parse JSON-String!
        if(ser_read=='}')
        {
            json = ser_json;
            ser_json[128] = "";              
    
            const size_t bufferSize = JSON_OBJECT_SIZE(PIXEL_DATA_SIZE) + 40;
            DynamicJsonBuffer jsonBuffer(bufferSize);
            JsonObject& root = jsonBuffer.parseObject( json );        
                
            if (!root.success()) {
        
                  for (int i=0; i <= 3; i++) {
                      digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
                      digitalWrite(LED_BUILTIN, LOW);    // turn the LED off 
                  }
            } else {
                PIXELwhite0 = root["white0"];
                analogWrite(whitePIN,PIXELwhite0);
            }
        }
    }
    
    /*
    PIXELmode = root["pixelmode"];
    PIXELdelay = root["delay"];
    PIXELwhite0 = root["white0"];    
    PIXELred0 = root["red0"];
    PIXELgreen0 = root["green0"];
    PIXELblue0 = root["blue0"];
    */
    
 
}

Das ermitteln des eigentlichen Zeilenendes hat noch nicht so recht zum Erfolg geführt, obgleich ich auch ESP-Seitig diese auch mal direkt gesendet habe (CR-NL) usw. Hier bin ich noch weiter dran. Danke für die Hinweise auch hierzu!

Ich merke das meinen Synapsen so langsam klarer wird, worums im Detail geht, aber es dauert noch. Zeichensatz, ASCII, Byte - jo, das wird schon noch!

Wie gesagt, die obere Variante tut schon einmal was ich erwarte, seit einer halben Stunde ununterbrochen ohne Aussetzer zu haben, die angeschlossenen KSQ werden mittels PWM gesteuert, aus dem Wert eines JSON-Objekts. Prima.

Konkrete Frage: das Programm läuft nur fehlerfrei ab, wenn ich explizit die Zeile Serial.println(i); bestehen lasse. Lösche ich diese, so landet konsequent nur das End-Zeichen meines Strings in "String json" -> }

I2C ist heute auch angekommen, ich werde die Widerstände heute noch ausbauen, und gegen diesen ersetzen.

Grüßle
Marc

also dein int i ... ich weis nicht... das ist doch dein Zeichenzähler ... wird wohl kaum mal negativ werden.
uint16_t wäre wohl sinnvoller.

Schlimmer ist, dass diese lokale Variable nicht initialisiert wird. Da ausser i++ und Serial.print nichts damit gemacht wird, ist auch static keine Lösung.

Hi

michael_x:
Schlimmer ist, dass diese lokale Variable nicht initialisiert wird. Da ausser i++ und Serial.print nichts damit gemacht wird, ist auch static keine Lösung.

Erklärung für den TO:
Variablen in Funktionen, Die nur local zur Verfügung stehen, werden bei JEDEM Durchlauf neu erzeugt.
Dabei wird aber NUR der Speicherplatz für diese Variable bestimmt - als Wert wird der im Speicher Stehende genommen.
Die Variable kann also mit einem beliebigem Wert 'auftauchen'.
Bei static wird die Variable nur 1x erzeugt - der Platz bleibt also 'bis in alle Ewigkeiten bestehen' - die Initialisierung ist aber immer noch 'was bereits dort stand'.
Da im setup() im Normalfall schon irgendwelche Routinen initialisiert werden, ist davon auszugehen, daß in diesem Speicherbereich bereits 'was gemacht wurde' - also auch keine gute Idee, der Variable KEINEN Startwert mitzugeben.
Gerade bei static ist's kein Problem, dort als Startwert 0 einzutragen - so hast Du geordnete Verhältnisse.

MfG