Go Down

Topic: No response from volumio server with WiFiClient (from ESP8266WiFi) (Read 224 times) previous topic - next topic

arjena

I am trying to send api commands to my Raspberry-pi running volumio. When done from a browser this works fine. (http://volumio.local/api/v1/getstate returns a json formatted text as it should).
When I use the BasicHTTPClient example (using the ESP8266HTTPClient lib ) and connect to volumio.local I get the expected response. But this lib uses the String class and since every other page here warns against using this (ok, I'm exaggerating) because of memory fragmentation, I'm trying to re-write the code using only String free libs.
So I tried using the WiFi client example from the ESP8266WiFi lib. This reads the incoming stream char by char, so no String class involved. But, when I connect to volumio.local there is no response. Every other address I throw at it gives me at least "HTTP/1.1 400 Bad Request" or something similar. But when I connect to volumio.local it connects fine, there is no response whatsoever.
Code is below (even though it is the unaltered code from the example, stripped of non-important Serial.prints). Of course to do anything useful the "Hello from..." should be replaced with something like
"GET /api/v1/getstate". But that also gets me no reply whatsoever.
Now I know this appears to be more of a problem with volumio than with the esp8266/Arduino code, but since every other server (local or not) gets me a reply, I'm wondering if there is any way to find out what the actual reply from volumio could be. Because since every other way I connect to volumio gets me the right reply (browser, curl from the terminal, esp8266 with ESP8266HTTPClient lib) there must be something different in the way volumio returns the data compared to all the other server I tried. Is there any way to find out what goes wrong?
The log files from volumio show every attempt at access from browser(s) and curl, but nothing from the esp...

Code: [Select]
/*
    This sketch establishes a TCP connection to a "quote of the day" service.
    It sends a "hello" message, and then prints received data.
*/

#include <ESP8266WiFi.h>

#ifndef STASSID
#define STASSID "ssid"
#define STAPSK  "pass"
#endif

const char* ssid     = STASSID;
const char* password = STAPSK;

//const char* host = "moode.local";
//const char* host = "homeServer.local";
const char* host = "volumio.local";
//const uint16_t port = 3000;
const uint16_t port = 80;

void setup() {
  Serial.begin(115200);

  // We start by connecting to a WiFi network

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
}

void loop() {
  Serial.print("connecting to ");
  Serial.print(host);
  Serial.print(':');
  Serial.println(port);

  // Use WiFiClient class to create TCP connections
  WiFiClient client;
  if (!client.connect(host, port)) {
    Serial.println("connection failed");
    delay(5000);
    return;
  }

  // This will send a string to the server
  Serial.println("sending data to server");
  if (client.connected()) {
    client.println("hello from ESP8266");
  }

  // wait for data to be available
  unsigned long timeout = millis();
  while (client.available() == 0) {
    if (millis() - timeout > 5000) {
      Serial.println(">>> Client Timeout !");
      client.stop();
      delay(60000);
      return;
    }
  }

  // Read all the lines of the reply from server and print them to Serial
  Serial.println("receiving from remote server");
  // not testing 'client.connected()' since we do not need to send data here
  while (client.available()) {
    char ch = static_cast<char>(client.read());
    Serial.print(ch);
  }

  // Close the connection
  Serial.println();
  Serial.println("closing connection");
  client.stop();

  delay(3000); // execute once every 5 minutes, don't flood remote service
}

PaulS

Quote
I'm wondering if there is any way to find out what the actual reply from volumio could be.
The reply from the server should be the same regardless of what made the request, so use your favorite browser, which is smart enough to know that it needs to make a GET request.

A server just sits there, parsing data that arrives on the port it is listening to, and passing it to the proper script/application, returning whatever output the script/application generates to the requesting client.

If you make a request that the server doesn't understand (like "Hello from oblivious"), it is free to not respond at all. If you make a request it can't handle (like "GET me/some/ice/cream HTTP/4.6"), it will generate a reply.
The art of getting good answers lies in asking good questions.

arjena

That is (of course) all true. But as I tried to explain, when the commands are issued from a (any) browser (on multiple platforms) I get a reply from the Pi running Volumio. Even when the command I send is rubbish.
When I use the HTTPClient lib I get a reply. But when I use the Client instance from the ESP8266 lib I get nothing from Volumio, but all the expected results from any other server. So it seems the culprit is on the server (Volumio) side. Maybe it sends a NULL as the first character, so my esp stops listening before it starts reading.
Though if that was the case, I think I should be able to see a connection attempt in the Pi logs, but there is none. I can see all the attempt I made from browsers or from the esp with the HTTPClient lib. 
So my question is actually twofold: Is there a way to see if the Pi gets the GET request at all, and if so, what it's reply is.
I have by the way now changed my sketch to still use the HTTPClient lib, but I now read the stream directly avoiding the readString from the above mentioned example. Now there are no more Strings in my sketch, but HTTPClient.cpp is riddled with Strings so I'm not sure I totally avoid them. But it gets me the reply I expect. Still would like to understand why the other method works on everything except Volumio...

PaulS

Quote
But as I tried to explain, when the commands are issued from a (any) browser (on multiple platforms) I get a reply from the Pi running Volumio. Even when the command I send is rubbish.
There is a fundamental disconnect in this statement, that is at the crux of your problem.

When you use a browser, you are asking some software to do something with some data you provide. That software KNOWS that the only thing the server can understand is a GET request. So that is what it makes, and the server knows how to deal with a GET request.

It does NOT know what to do with "hello from ...", so it simply discards it. As it should.

What you are doing, and what the browser is doing, are two very different things.
The art of getting good answers lies in asking good questions.

arjena

Again true, when I send the code as in the example above it's up to the server to respond or not. All other servers I tried respond even to this rubbish, Volumio does not. But when I change the request to (what I think is) the proper GET request (not near my programming computer now, so I don't have an example of the exact syntax I use but is is something like "GET /api/v1/getstate") there is still no response. The same command with curl works just fine. As you probably already get, I'm not a very experienced coder so I don't even know if curl adds something to the command I type so the Volumio server understands. 
Maybe I put too much emphasis on the fact that it works from a browser and I should have posted the code with the GET string in stead of the original example.

Can you point me to the proper syntax to replace the "Hello from..." string with a proper GET command? 

arjena

OK, it is working now. The correct string was indeed "GET /api/v1/getstate". I already tried adding \n and \r\n at the end of that but that did not help.
What did help was adding another client.println() after the GET line. Still no idea why this works and \r\n does not, but at least I got it working.

PS: Paul, I proved your sig to be so right. Reading back my question was not as clear as it could have been. Thanks for pointing me in the right direction.

PaulS

Quote
What did help was adding another client.println() after the GET line. Still no idea why this works and \r\n does not, but at least I got it working.
Can you post your complete code?

The complete GET request should have looked like:
Code: [Select]
GET /api/v1/getstate HTTP/1.n
where n is 0 or 1. (1 requires that you also send a Host header; 0 does not).
The art of getting good answers lies in asking good questions.

arjena

I'll post the complete code tonight (not at home right now), but I tried the GET command with both the HTTP/1.0, HTTP/1.1 and no HTTP/1.n at all. And all those combinations with and without the \r\n at the end. But in the end it only works when I add the extra client.println(). Then I get the expected response, Volumio seems to ignore the HTTP/1.n
After I got the response (the first successful try was without the HTTP/1.n) I put code in place to filter out the header. Tonight I will comment out that code and see if I get different replies with or without the HTTP/1.n addition. In my search for how to solve this I saw protocol requires the HTTP/1.n addition. I'm curious why Volumio seems to ignore that but does require the extra client.println().
Without access to my actual code, I'm pretty sure the first working version is identical to the code posted above, except for:
Code: [Select]
   client.println("hello from ESP8266");
which is now:
Code: [Select]
   client.println("GET /api/v1/getstate");
client.println();

arjena

For the complete (working) code see below.
I tried several options, HTTP/1.0, HTTP1.1 etc. Makes no difference, reply is the same. HTTP/1.1 works whether I add a host or not. Taking out the line client.println() results in no reply (Timeout).
Funny thing, when I add a space (" ") after ...getstate I get no reply. I always assumed whitespace was ignored.
Btw, the according to the documentation the only difference between print and println is that println adds a carriage-return and a newline. But client.print("\r\n") does not work, while client.println() does.

Code: [Select]
/*
    This sketch establishes a TCP connection to a "quote of the day" service.
    It sends a "hello" message, and then prints received data.
*/

#include <ESP8266WiFi.h>

#ifndef STASSID
#define STASSID "SSID"
#define STAPSK  "pass"
#endif

const char* ssid     = STASSID;
const char* password = STAPSK;

const char* host = "keukenmuziek.local";
const uint16_t port = 80;
void setup() {
  Serial.begin(115200);

  // We start by connecting to a WiFi network

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  /* Explicitly set the ESP8266 to be a WiFi-client, otherwise, it by default,
     would try to act as both a client and an access-point and could cause
     network-issues with your other WiFi-devices on your WiFi-network. */
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  Serial.print("connecting to ");
  Serial.print(host);
  Serial.print(':');
  Serial.println(port);

  // Use WiFiClient class to create TCP connections
  WiFiClient client;
  if (!client.connect(host, port)) {
    Serial.println("connection failed");
    delay(5000);
    return;
  }

  // This will send a string to the server
  Serial.println("sending data to server");
  if (client.connected()) {
    client.println("GET /api/v1/getstate");
    client.println();
  }

  // wait for data to be available
  unsigned long timeout = millis();
 
  while (client.available() == 0) {
    if (millis() - timeout > 1000) {
      Serial.println(">>> Client Timeout !");
      client.stop();
      delay(1000);
      return;
    }
  }

  // Read all the lines of the reply from server and print them to Serial
  Serial.println("receiving from remote server");
  // not testing 'client.connected()' since we do not need to send data here
   while (client.available()) {
    char ch = static_cast<char>(client.read());
    Serial.print(ch);
  }
  // Close the connection
  Serial.println();
  Serial.println("closing connection");
  client.stop();

  delay(5000); // execute once every 5 seconds, don't flood remote service
}

Go Up