Kommunikation Esp8266 zu SPS

Nabend allerseits,
ich bin aktuell dran, kleine "Datenstationen" mit einem ESP8266 bzw. Wemos D1 zu bauen und die Daten zu einer 1500er SPS zu schaufeln:
Diese erfassen aktuell über einen BME280 und BH1750 Temperatur, Feuchtigkeit, Druck sowie Beleuchtungsstärke. Die Kommunikation läuft Netzwerkintern über eine TCP Verbindung. Die SPS ist hierbei der Server, die ESP´s Clients.

Das Senden der Daten (aktuell arbeite ich hier mit Hercules von HW) klappt eigentlich problemlos. Aktuell im Testbetrieb baut der ESP eine Verbindung zu dem Terminal Programm auf und sendet die Daten aktuell noch im Klartext.

Da ich nun jedoch auch Daten zum ESP senden möchte, nervt jedoch eine Tatsache:
Der ESP baut die Verbindung auf, sendet die Daten und schließt die Verbindung dann. Sie wird erstwieder aufgebaut, wenn neue Daten gesendet werden sollten. Das heißt, dass ich nur Daten senden kann, wenn der ESP grade gesendet hat. Da ich jedoch nicht sekündlich Messe/Daten sende, bekomm ich so eine ziemliche Verzögerung rein, wenn ich dann erst senden kann.

Ich war es bei solch einer Kommunikation bisher (Im Industriellen Umfeld) immer gewohnt, dass die Verbindung aufrecht gehalten wird bis sie explizit geschlossen wird.

Habe ich hierzu eine Möglichkeit, dies zu erreichen?

Die Alternative wäre die Daten über OPC UA hin und her zu schaufeln, jedoch scheint es hier ESP/Arduino seitig keine Möglichkeit zu geben, diese als OPC Client arbeiten zu lassen.

/* Einbinden von benötigten Bibliotheken*/

#include <ESP8266WiFi.h>              // Library für Wifi vom ESP8266
#include <Wire.h>                     // Library für I2C/SPI

#include <BH1750.h>                   // Library für BH1750 Beleuchtungssensor
#include <Adafruit_BME280.h>          // ibrary für Bosch BME280 Temp/Humid./Druck Sensor
#include <Adafruit_Sensor.h>          // Library/Definitionen für allgemeine Sensoren/Physikalische Einheiten

/* Setzen der W-Lan Parameter zur Verbindung*/

W_LAN Daten...

/* Globale Variablen Deklaration */

float rLux = 0;                  // Beleuchtungsstärke in [lux]
float rDruck = 0.0;              // Druck in [hPa]
float rTemp = 0.0;               // Temperatur in [C°]
float rHumid = 0.0;              // relative Luftfeuchtigkeit [%]
float rAltitude = 0.0;           // Höhe 
long rssi = WiFi.RSSI();         // Wifi Signalstärke
int iDaten = 0;
String string = "";
/* Normalniveau (Druck) definieren*/

#define SEALEVELPRESSURE_HPA (1013.25) //Normaldruck US-Standardatmhosphäre

/* Definieren von "lightmeter" ??? */
BH1750 lightMeter;

/* Definieren von BME280           */
Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

void setup() {
//## WIFI/Serial ##//
Serial.begin (9600);           // Serielle Kommunikation mit 9600 Baud aufbauen
Wire.begin();                  // I2C Bus Verbindung aufbauen

//BME280 @ 0x76
//BH1750 @ 0x23

lightMeter.begin();            // Funktion für BHE1750 Beleuchtungssensor aufrufen


WiFi.begin(ssid, password);    //W-Lan Verbindung zu vorher definiertem Partner Aufbauen
Serial.println(WiFi.localIP());


while (WiFi.status() != WL_CONNECTED) {               //Solange keine Verbindung aufgebaut ist, in WHILE-Schleife Verbleiben und "LOOP" nicht ausführen ## FF ##
delay(500);
Serial.print(".");
}

/* Auto Reconnect */
//WiFi.setAutoReconnect(true);
//WiFi.persistent(true);

Serial.println(WiFi.localIP());


//## BME280 ##//
unsigned status;             // Statuswort BME280 

status = bme.begin();

}


void loop() {

WiFiClient client;                                    //Client Verbindung aufbauen
if (!client.connect(host, serverPort)) {              //Muss aus Gründen wohl nochmal im Loop Teil bearbeitet werden, anders keine TCP Verbindung möglich
Serial.print("X");
return;
}

delay(1000);

  //## Einlesen der Sensorwerte ##//

  //BH1750 Beleuchtung
  rLux = lightMeter.readLightLevel();

  //BME280 Temp, Druck....
  rDruck = bme.readPressure();
  rTemp = bme.readTemperature();
  rHumid = bme.readHumidity();
  rAltitude = bme.readAltitude(SEALEVELPRESSURE_HPA);

  /* Senden der Werte über TCP an CPU 1510 NUR TEST*/

  client.print("Beleuchtungsstaerke ");
  client.print(rLux);
  client.println(" lx");

  client.print("Temperatur ");
  client.print(rTemp);
  client.println(" C");

  client.print("relative Luftfeuchte ");
  client.print(rHumid);
  client.println(" %");

  client.print("Luftdruck ");
  client.print(rAltitude);
  client.println(" m");

  /* Lesen der Daten von Server */

  iDaten = client.available();
  string = client.read();
  Serial.print("Anzahl Daten:");
  Serial.println(iDaten);
  Serial.print(string);

//client.flush();                                     //
//client.stop();                                      //

  delay(5000);                                        //Delay nach senden von Daten
}

Besten Dank und einen guten Rutsch

Also liegt es an den ESP, wann sie Daten vom Server haben wollen.
Die ESP müssen also häufiger nach Daten fragen als sie bisher Daten an den Server senden,
oder du musst dein Server-Client Konzept generell ändern.

Das verstehe ich jetzt nicht.
Der Server sendet keine Daten autonom an den Slave. Der Slave fordert Daten an und bekommt sie dann vom Server.

Grüße Uwe

wenn die SPS eine Datentransfer zum ESP initieren können soll, dann brauchst du am ESP neben dem Client auch einen Server.

Die delays im loop stören dann auch. Bau das um mit timer auf Basis von millis().

Vieleicht machst mal ein Blockschaltbild und zeichnest welche Komponenten du verwendest und wo welcher Datentransfer mit welchem Protokoll stattfinden soll.

Hallo,
wenn Du ja bisher SPS gewohnt bist dann kennst Du ja die FB´s "Send" und "Fetch" , die das da abwickeln . Die bauen auch immer eine TCP Verbindung neu auf und beende die wieder. Es gibt ja Fälle wo nur selten ein Aufruf erfolgt und da macht das ja keinen Sinn das stehen zu lassen. Natürlich gibt es Fälle wo die Verbindung stehen bleibt. Die gesamte Dezentrale E/A wir ständig offen sein. Send sendet dabei einfach was und Fetch geht was holen.

Im Prinzip kannst Du das jetzt ebenso machen .
Derzeit machst Du wenn man so will einen Fetch bei dem Du auch noch Daten senden kannst und bekommst zusätzlich Daten zurück. Eigentlich ist es immer so das der Client eine Verbindung zum Server aufbaut und der Server antwortet.

Ich denke eventuell ist es einfacher wenn Du den ESP zum Server machst und die SPS ist Client. Die SPS hat zwei verschieden Aufträge. Einen "Send" der sendet oft die Daten an den ESP und einen "Fetch" der die Daten von dem Sensor seltener holt.
Unterscheiden kann der ESP beide Pakete entweder über die Länge oder Du baust eine zusätzliche Kennung mit ein. Im ersten Fall ließt der ESP die Daten ein und schließt die Verbindung wieder. Im zweiten Fall ließt er die Daten ein, eigentlich nur die Kennung, erkennt das er antworten muss, macht das und schließt dann die Verbindung.

Zudem solltest Du die Daten mit einem einzigen client.print() versenden, dann gibts nur ein Datenpaket. Was Du mal probieren könntest wenn Du die Daten als Struct versendest könnte ich mit vorstellen das die Daten direkt als binär Werte , nicht ACSII Werte in Deinem DB landen.

die Struct dann aber anlegen mit festen Datenlängen
also z.B int16_t für einen 16 bit Integer

Nachtrag
Ups ich glaube da habe ich mich zu weit aus dem Fenster gelehnt , im Moment wüsste ich gar nicht wie das gehen sollte eine Struct versenden.
client.write() ?? sollte gehen

Aber nur, wenn gleiche Variablen Größe, MSB/LSB, Alignment usw.Sonst wird es mehr Aufwand.

Gruß Tommy

Ja den Aufwand aus ASCII Werte wieder Binärwerte machen ist in der SPS sicher auch doof. Ich bin da jetzt nicht mehr so drin, ist ja inzwischen schon 8 Jahre her. Auf jeden Fall solltest Du die ganzen Texte weglassen die da mit übertragen werden. Das ist ja völlig unnötig, letztlich wissen doch beide Seiten wo was steht.
Zudem in irgendeinem SPS Forum wird sowas schon mal jemand gemacht haben. Da solltest Du mal auf die Suche gehen.

Hallo allerseits,
Danke erstmal für die zahlreichen Antworten.

Vielleicht erklär ich etwas genauer, was ich eigentlich bezwecken will:
Ich will eine einfache TCP Socket Verbindung zwischen jeweils einem ESP und der SPS Aufbauen und über diese die Daten austauschen. Vll. ist Client/Server hier garnicht so passend. Gemeint habe ich damit eigentlich, dass der entsprechende ESP die Verbindung aktiv aufbaut, die SPS passiv, also auf die Verbindung wartet.
Wenn die Verbindung besteht (und bestehen bleibt), können beide entsprechend nach Bedarf Daten senden. Anfordern will ich hier eigentlich nichts.

Das Problem bei meinem obigen Code ist, dass die Verbindung immer nur kurz aufgebaut wird, der ESP Daten sendet, und die Verbindung dann wieder abgebaut wird. Und dann kann ich von der SPS halt nichts senden.

Vielleicht hab ich hier ESP seitig auch die falschen Funktionen genutzt um mein Ziel zu erreichen.

"Send" "Fetch" sagen mir SPS seitig tatsächlich 0. Bisher hab ich immer mit TSEND / TRCV (C) gearbeitet.
Natürlich sende ich die Daten nachher fest strukturiert rüber, das decodieren von ASCII Werten ist SPS seitig aber tatsächlich relativ einfach.

Beste Grüße

Hallo,
oh je , kann gut sein das die tatsächlich jetzt TSEND /TRCV heißen und ich noch was älteres im Kopf hatte.

Dennoch für Dich kommen in Verbindung mit dem ESP zwei Beispiele in Frage auf die Du aufbauen solltest.
als Client
Beispiele/ ESP286WiFI/WIFIClient
Beispiele/ ESP286WiFI/WIFIClientBasic

im Wesentlichen hast Du Dich ja da angelehnt, aber irgendwann musst Du die Verbindung schließen mit client.stop. In der Zeit muss der Server dann antworten.

Für einen Server
dann gab es mal ein Beispiel für einen einfachen TCP Server , das hab ich aber nicht mehr gefunden. Es gibt eine Beispiel
WifiManaulWebServer

da müsste dann aber der ganze Kram für HTTP raus genommen werden. Ich habe da mal in meiner Bastelkiste gesucht und was gefunden. Das möchte ich aber gerne noch mal testen. Wenn Du Interesse hast mach ich das heute Abend.

Du kannst auch beides auf dem ESP machen Server und Client. Das geht auch. Dann kann eigentlich jeder was senden wann immer er will.

Hallo,
ich hab das getestet und ein bisschen abgespeckt. Getestet hab ich das mit dem "Paket Sender" Tool
Der ESP läuft als Server, d.h die SPS muss eine Anfrage senden in der auch Daten enthalten sein können. Der ESP antwortet mit drei simulierten Werten die durch ein ";" 0x3b getrennt sind. Wenn da jetzt ein ESP Client die Daten empfangen würde, ging das recht einfach mit strtok(). Die einzelnen Werte hab ich jetzt mal je 6 Zeichen lang gemacht, führende Leerstellen sind mit 0x20 aufgefüllt.

sprintf(buffer, "%6.2f;%6d;%6d", wert1, wert2, wert3);

eventuell musst Du das anpassen.
Je nach Netzlast in meinem Heimnetz dauert das zwischen 20 und 100 ms, also es geht recht schnell.

/*TCP Server Beispiel
   Wert können von einem TCP -Client
   gesendet auch werden.Der Server sendet
   Messdaten als Response zuück

   Beispiel
   Client sendet eine Anfrage und baut die Verbindung auf (request)
   Server antwortet mit Istwerten (response)
   
*/

#include <ESP8266WiFi.h>

IPAddress staticIP(192, 168, 178, 20);  // eigene IP
IPAddress gateway(192, 168, 178, 1);  // Fritzbox Heimnetz
IPAddress subnet(255, 255, 255, 0);

const char* ssid = "xxxxx";
const char* password = "yyyyy";

unsigned int localPort = 4210; // local Port to listen

float wert1 = 0;
int wert2 = 0;
int wert3 = 0;


// Instanzen erstellen
WiFiServer TCPserver(localPort);


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  wificonnect();
  TCPserver.begin();
  Serial.printf("TCP Server started listening at IP %s, Port %d\n", WiFi.localIP().toString().c_str(), localPort);
}

void loop() {
  // put your main code here, to run repeatedly:
  if (WiFi.status() != WL_CONNECTED) {
    wificonnect();
    delay(1000);
  }
  read_werte();
  tcp_server();
}

void wificonnect() {
  WiFi.persistent(false);   // daten nicht in EEprom
  WiFi.mode(WIFI_STA);
  Serial.printf("Connecting to %s ", ssid);

  WiFi.config(staticIP, gateway, subnet);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" connected");
  Serial.print("local IP:");
  Serial.println(WiFi.localIP());

}

void read_werte() {
  static uint32_t altzeit = 0;

  if (millis() - altzeit > 3000) {
    altzeit = millis();
    // Simulation Testwerte
    wert1 = (200 + random(40)) / 10.0;
    wert2++;
    wert3 = 100 + wert2;
    Serial.printf("Messwerte %6.2f;%6d;%6d\n", wert1, wert2, wert3);
  }
}

void tcp_server() {
  int packSize = 0;
  uint32_t altzeit;

  WiFiClient client = TCPserver.available();
  if (client) {

    if (client.connected()) {
      Serial.print("Connect to client :");
      Serial.println(client.remoteIP());
      char buffer[50]; buffer[0] = ('\0'); int i = 0;
      altzeit = millis();
      while (!client.available() && millis() - altzeit < 100); delay(1);
      while (client.available() && i <= sizeof(buffer) - 2) {
        char s = client.read();
        buffer[i] = s;
        i++;
      }
      buffer[i] = ('\0');
      packSize = strlen(buffer);
      Serial.printf("Received %d bytes\n", packSize);
      Serial.println(buffer);

// Antwort senden
      sprintf(buffer, "%6.2f;%6d;%6d", wert1, wert2, wert3);
      Serial.println(buffer);
      client.print(buffer);

      client.stop();
    }

  }
}
1 Like

Hallo,
vielen Dank für die ausführliche Antwort! Ich werd mir den Code mal in Ruhe genau anschauen. Insbesondere die einzelnen Befehle.
Es ist also der Normalfall, dass die TCP Verbindung immer wieder auf und abgebaut wird?

Ich kenn es halt so, dass die Verbindung ständig bestehen bleibt.
Aber wiegesagt, dass ist die SPS/Industrie Welt. Beim letzten Projekt bin ich dadurch auch auf einen Wert von ~10-20ms gekommen. Die Geschwindigkeit ist hie rnatürlich völlig irrelevant.
Das ist auch völlig Wertungsfrei gemeint, ist rein interessenshalber und man sollte ja auch mal über den Tellerrand blicken.

Besten Dank nochmal, ich geb Rückmeldung!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.