Forum: Mikrocontroller und Digitale Elektronik ESP8266: Client Server Kommunikation - http.get() liefert connection refused


von Rainer B. (hobbyduino)


Lesenswert?

Hallo,
ich habe zwei WEMOS D1 mini pro (version 1) im Einsatz. Der eine hat 
einen DHT22 Sensor und der andere muss sich dort die Temperatur holen, 
um davon abhängig ein Relais zu schalten.
Beide Geräte melden sich im selben WLAN Netz an. Über die jeweils 
eigenen Webseiten kann ich das auch gut überprüfen - funktioniert also. 
Auf dem DHT22 Server habe ich auch dafür gesorgt, dass der 
Temperaturwert alleine übertragen wird (also unabhängig von der eigenen 
Webseite). Funktioniert auch im Browser.
Nun möchte ich am Client die Werte erfragen und komme einfach nicht 
dahinter, wie ich den "connection refused" Error wegbekomme und die 
Daten übernehmen kann. Ich habe hier nur einen Teil des gesamten 
Programm- Codes eingefügt in der Hoffnung, dass es noch halbwegs 
verständlich ist und alle relevanten Libs enthalten sind.
Wie der Code funktionieren soll:
Wenn die Wifi- Verbindung steht, wird ein Ticker mit 15 Sekunden- 
Laufzeit gestartet der lediglich ein Flag setzt, dass anschließend im 
Loop()- Zweig ausgelesen wird. Von dort wird eine Funktion aufgerufen, 
um einen http- Get- Aufruf zum Server zu starten. Zum Schluss wird das 
Flag wieder zurückgesetzt.
1
#include <ESP8266WiFi.h>
2
#include <Ticker.h>
3
#include <ESP8266HTTPClient.h>
4
5
WiFiEventHandler wifiConnectHandler;
6
7
const char* serverNameTemperatur = "http://192.168.178.96/KorrigierteTemperatur";
8
const char* serverNameHeatIndex = "http://192.168.178.96/KorrigierterHeatIndex/";
9
Ticker CallServerTimer;
10
float ServerTemperature = 0;
11
float ServerHeatIndex = 0;
12
bool CallServer = false;
13
14
void SetBit2CallServer(){
15
16
  CallServer = true;
17
}
18
19
String httpGETRequest(const char* serverName) {
20
  WiFiClient client;
21
  HTTPClient http;
22
  // MIt diesem Aufruf habe ich auch rumexperimentiert - ohne Erfolg
23
  // http.begin(client, "192.168.178.96", 80, "/KorrigierterHeatIndex");
24
  
25
  http.begin(client, serverName);
26
  int httpResponseCode = http.GET();
27
  
28
  String payload = "--"; 
29
  
30
  if (httpResponseCode>0) {
31
    Serial.print("HTTP Response code: ");
32
    Serial.println(httpResponseCode);
33
    payload = http.getString();
34
  }
35
  else {
36
    Serial.print("Error code: ");
37
    Serial.println(httpResponseCode);
38
    payload = "Error calling the temperature server. Errorcode: " + String(httpResponseCode);
39
  }
40
  // Free resources
41
  http.end();
42
  client.stop();
43
  return payload;
44
}
45
46
void onWifiConnect(const WiFiEventStationModeGotIP& event) 
47
{
48
  Serial.println("Connected to Wi-Fi.");
49
  // Call DHT22 Server every 15 sec 
50
   CallServerTimer.attach(15,SetBit2CallServer);
51
}
52
void setup() {
53
....
54
55
  wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
56
57
...
58
}
59
loop() {
60
...
61
62
  if (CallServer == true){
63
    const char* HttpResult;
64
    char* Position;
65
    HttpResult = httpGETRequest(serverNameTemperatur).c_str();
66
    Position = strstr(HttpResult, "Error");
67
    if (Position != NULL){
68
      ServerTemperature = atof(HttpResult);
69
    }
70
71
    HttpResult = httpGETRequest(serverNameHeatIndex).c_str();
72
    Position = strstr(HttpResult, "Error");
73
    if (Position != NULL){
74
      ServerHeatIndex = atof(HttpResult);
75
    }
76
        
77
    CallServer = false;
78
  }
79
  ...
80
  
81
}

Ich bin gespannt, wo ihr hoffentlich mein Problem findet. Danke im 
Voraus!

von Rainer B. (hobbyduino)


Lesenswert?

Hmm ...  irgendwie scheint die Frage aus dem Blickwinkel geraten zu 
sein. Oder ist sie unter einer falschen Rubrik einsortiert? Falls ja, 
bitte ich darum, sie umzuhängen.
Danke!

von Stefan F. (Gast)


Lesenswert?

Schau dir https://www.arduino.cc/en/Reference/WiFiClient

Die benutzt den client falsch. Der erste Parameter muss ein Objekt von 
der Klasse IPAddress sein:

> IPAddress server(192,168,178,96);
> client.connect(server, 80)

Nachdem die Verbindung aufgebaut ist, sendest du den HTTP Request, 
gefolgt von einer Leerzeile.

> client.println("GET /KorrigierteTemperatur HTTP/1.0");
> client.println();

Dann wird der Server antworten:

1. HTTP/1.0 200 OK
2. Mehrere Header Zeilen, die du nicht brauchst
3. Eine Leerzeile
4. Das angeforderte Dokument
5. Der Server schließt die Verbindung

Was mir an dem Arduino Beispiel nicht so gut gefällt ist, dass es die 
Zeilenumbrüche vermutlich nicht korrekt formatiert. Es sollte \r\n sein, 
also besser:

> client.print("GET /KorrigierteTemperatur HTTP/1.0\r\n\r\n");

anstelle von println().

Falls dein Webserver das alte HTTP 1.0 nicht unterstützt, empfehle ich 
dir Kapitel 10 aus 
http://stefanfrings.de/mikrocontroller_buch/Einstieg%20in%20die%20Elektronik%20mit%20Mikrocontrollern%20-%20Band%202.pdf, 
da erkläre ich HTTP 1.1

von Stefan F. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Die benutzt den client falsch

Korrektur: Du benutzt den client falsch.

von Rainer B. (hobbyduino)


Lesenswert?

Herzlichen Dank für den Tipp. Ich probier das morgen aus!

von hobbydunino (Gast)


Lesenswert?

Hmm - hat eine Weile gedauert, bin einen großen Schritt weiter aber noch 
nicht am Ende - Danke Stefan!

Ich habe meine Funktion folgendermaßen geändert:
1
String httpGETRequest(const char* serverName) {
2
3
  WiFiClient myclient;
4
  IPAddress server(192,168,178,96);
5
  const char* host = "192.168.178.96";
6
  String payload = "--";
7
8
  if (!myclient.connect(server, 80)) {
9
    payload = "connection failed";
10
    return payload;
11
  }
12
  myclient.print("GET /KorrigierterHeatIndex HTTP/1.0\r\n\r\n");
13
  myclient.println();
14
15
  int Count=0;
16
  while (myclient.connected()){
17
    if (myclient.available()){
18
      Count = Count + 1;
19
      payload = myclient.readStringUntil('\n');
20
      Serial.print("Zeile ");
21
      Serial.println(Count);
22
      Serial.println(payload);
23
    }
24
    else {
25
      if (Count > 0) {
26
        myclient.stop();
27
        Serial.println("unavailable -> gestoppt");
28
      }
29
    }
30
    // Serial.println("still available");
31
  }
32
  Serial.println("Getrennt");
33
  // myclient.stop();
34
  return "Das wars";
35
}

Die Monitor-Ausgabe verwirrt mich aber:
1
Zeile 1
2
HTTP/1.0 200 OK
3
Zeile 2
4
Content-Length: 5
5
Zeile 3
6
Content-Type: text/plain
7
Zeile 4
8
Connection: close
9
Zeile 5
10
11
Zeile 6
12
24.81
13
unavailable -> gestoppt
14
Getrennt
15
Payload nach dem Call: Das wars

In Zeile 4 kommt bereits ein "Connection: close"
danach kommen aber noch zwei Zeilen und in der Zeile 6 die eigentliche 
Nutzantwort. Und nur durch die Abfrage, ob der Client noch verfügbar ist 
und bereits geantwortet hat (Count > 0) schaffe ich es, aus der 
Endlosschleife while (myclient.connected()){...} herauszukommen. Ohne 
diesen else- Zweig, der Client bekommt den "Connection: close" offenbar 
nicht mit, bleibt mein Code für ewig hier hängen. Mir stellt sich 
deshalb auch die Frage, ob ich auf der Server-Seite alles richitg 
gemacht habe.
Dort habe ich eine globale Variable
1
 // Create an asynchronous web server on port 80.
2
AsyncWebServer server(80);
 definiert und im Setup() folgendermaßen auf die Client-Abfrage 
reagiert:
1
server.on("/KorrigierteTemperatur", HTTP_GET, [](AsyncWebServerRequest *request){
2
    request->send_P(200, "text/plain", readCorrectedTemp().c_str());
3
  });
Ist das richtig oder muss das dann auch anders aussehen? Kann hier auch 
ein Grund dafür liegen, dass die while (myclient.connected()){...}- 
Schleife nicht abgebrochen wird?

Danke noch mal - auch im Voraus :-) wenn du noch eine Idee hast!

von Stefan F. (Gast)


Lesenswert?

hobbydunino schrieb:
> myclient.print("GET /KorrigierterHeatIndex HTTP/1.0\r\n\r\n");
>  myclient.println();

Damit hast du insgesamt drei Zeilenumbrüche, da ist einer zu viel.

> In Zeile 4 kommt bereits ein "Connection: close"

Das heißt nicht, dass die Verbindung geschlossen wurde. Das ist nur ein 
Text. Damit teilt der Server dem Client mit, dass er die Verbindung nach 
der Antwort schließen wird.

Beim HTTP 1.1 Protokoll dürfte sie offen blieben. Aber das bringt andere 
Komplexitäten mit sich.

Ich kenne mich mit Arduino nicht gut aus, deswegen kann ich dir nicht 
sagen, ob du Server-Seitig alles richtig gemacht hast. Dein 
Übertragungsprotokoll sieht jedenfalls gut aus.

Generell ist es keine gute Idee, schon als Anfang beide seiten der 
Kommunikation selbst zu programmieren. Weil dann genau die Unsicherheit 
entsteht, die du gerade hast. Installiere auf deinen PC irgendeinen HTTP 
Server (ich mag den von Apache), von dem du Textdateien abrufst. Dann 
bist du sicher, dass der Server OK ist.

Deinen eigenen Server kannst du gut mit dem Programm "curl" überprüfen. 
Benutze den Parameter -v, dann gibt curl dir die ganze Kommunikation im 
Klartext aus.

von MagIO2 (Gast)


Lesenswert?

Der Server schickt ja auch mit der letzten Header-Zeile 2 
Zeilenumbrüche. Das ist Teil des HTTP-Protokolls. Und danach kommen 
genau die "Content-Length" an Bytes. Damit könntest du die 
Endlos-Schleife in eine Schleife mit definiertem Ende umwandeln.

Dafür könnte man z.B. eine State-Machine nutzen:
Status 0 -> Lese Header-Zeilen, wenn eine Leer-Zeile kommt, dann Wechsel 
in Status 1
Status 1 -> Lese solange Zeilen, bis Content-Length bytes empfangen 
wurden, dann Wechsel in Status 2
Status 2 -> Fertig ;o)

Man könnte natürlich auch sowas, wie myclient.connected() noch mit 
aufnehmen und bei einem disconnect den Status auf 3 -> Error setzen

( So eine Art Pseudo-Code, hab schon lange kein C mehr gemacht ;o)

status=0;
gelesen=0;
zulesen=0;
while (status<2) {
  if (!myclient.connected()) {
    status=2;
  }
  case( status ) {
    0:
       // code zum lesen einer Zeile
       if( /* wenn zeile mit "Content-Length:" anfängt */ ) ) { 
zulesen=/* String nach int konvertieren */ }
       if( zeilenlänge = 0 ) { status=1; }
       break;
    1:
       // code zum Lesen des Content
       gelesen += /* Länge der aktuellen Zeile */
       if( gelesen == zulesen ) { status = 2; }
       break;
  }
}

// Abhängig davon, ob status == 1 oder == 2 geht es dann weiter mit
// Fehlerbehandlung oder mit sonstigem Code

von hobbydunino (Gast)


Lesenswert?

'tschuldigung...
die server.on routine heißt natürlich
1
server.on("/KorrigierterHeatIndex", HTTP_GET, [](AsyncWebServerRequest *request){
2
    request->send_P(200, "text/plain", readCorrectedHeatIndex().c_str());
3
  });

von Stefan F. (Gast)


Lesenswert?

Der Content-Length Header ist bei HTTP 1.0 Protokoll optional. Deswegen 
würde ich mich nicht darauf verlassen, dass er vorhanden ist.

Selbst wenn er vorhanden ist, kann die Verbindung vorzeitig abbrechen. 
Das darf nicht dazu führen, dass sich der Client in einer endlosen 
Warteschleife aufhängt.

Soweit ich sehe, passiert das bei der while Schleife des TO (und bei der 
von MagIO2) auch nicht, also alles gut. Ich wollte nur mal drauf 
hinweisen, denn den Fehler habe ich selbst mal gemacht. Er fiel erst 
Wochen nach der Inbetriebnahme auf und stiftete daher schlechte Laune.

von hobbydunino (Gast)


Lesenswert?

Danke für Euer Feedback. Habe die dritte Leerzeile beim Serveraufruf 
entfernt. Ob es was damit zu tun hat oder nicht - egal jetzt geht er 
nicht mehr in die Endlosschleife. Somit funktioniert der Code jetzt 
erstmal so weit, dass ich den Temperaturwert herausfiltern und damit 
weiterarbeiten kann. Ganz herzlichen Dank noch mal - auch dass ihr Euch 
am Wochenende darum kümmert!!

von Sascha W. (sascha-w)


Lesenswert?

Du könntest für den Zweck auf ganz auf TCP/HTTP verzichten.
Leg deinen 15s Timer auf dem Server an und sende deinen Temperaturwert 
per UDP an die IP des Client. Dann bekommst du am Client genau das eine 
Paket was ausschließlich deine Nutzdaten enthält.

Sascha

von Hobbyduino (Gast)


Lesenswert?

War ein paar Tage offline... Danke für den Tipp. Hatte die Idee auch 
schon, nachdem ich mich auf der Seite von Stefan umgesehen habe

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.