Search code examples
androidhttpurlconnectionarduino-unohome-automation

Android HttpUrlConnection with Arduino Uno


Happy new year all :)

I am working on a home automation project using Arduino Uno and Android application what can communicate through my TP-Link router.

I am having a java.io.IOException: unexpected end of stream on Connection once I send a query to the Arduino and I'm not being able to solve it.

01-01 16:51:47.771 10592-11256/com.projects.mahmoudmahdi.etherdroid E/EtherDroid: java.io.IOException: unexpected end of stream on Connection{192.168.1.215:80, proxy=DIRECT@ hostAddress=192.168.1.215 cipherSuite=none protocol=http/1.1} (recycle count=0)
    at com.android.okhttp.internal.http.HttpConnection.readResponse(HttpConnection.java:210)
    at com.android.okhttp.internal.http.HttpTransport.readResponseHeaders(HttpTransport.java:80)
    at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:904)
    at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:788)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:443)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:388)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:501)
    at com.projects.mahmoudmahdi.etherdroid.MainActivity$commandArduino.doInBackground(MainActivity.java:50)
    at com.projects.mahmoudmahdi.etherdroid.MainActivity$commandArduino.doInBackground(MainActivity.java:31)
    at android.os.AsyncTask$2.call(AsyncTask.java:295)
    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)
Caused by: java.io.EOFException: \n not found: size=0 content=...
    at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:200)
    at com.android.okhttp.internal.http.HttpConnection.readHeaders(HttpConnection.java:220)
    at com.android.okhttp.internal.http.HttpConnection.readResponse(HttpConnection.java:199)
    at com.android.okhttp.internal.http.HttpTransport.readResponseHeaders(HttpTransport.java:80) 
    at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:904) 
    at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:788) 
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:443) 
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:388) 
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:501) 
    at com.projects.mahmoudmahdi.etherdroid.MainActivity$commandArduino.doInBackground(MainActivity.java:50) 
    at com.projects.mahmoudmahdi.etherdroid.MainActivity$commandArduino.doInBackground(MainActivity.java:31) 
    at android.os.AsyncTask$2.call(AsyncTask.java:295) 
    at java.util.concurrent.FutureTask.run(FutureTask.java:237) 
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
    at java.lang.Thread.run(Thread.java:818) 

My Arduino Sketch:

#include <UIPEthernet.h>


int relay = 2;

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };   //physical mac address
byte ip[] = { 192, 168, 1, 215 };                      // ip in lan (that's what you need to use in your browser. (F("192.168.1.200"))
byte gateway[] = { 192, 168, 1, 10 };                   // internet access via router
byte subnet[] = { 255, 255, 255, 0 };                  //subnet mask

EthernetServer server(80);                             //server port

String readString;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);

  pinMode(relay, OUTPUT);

  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();
  Serial.print(F("server is at "));
  Serial.println(Ethernet.localIP());
}


void loop() {
  // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    //clearing string for next read
    readString = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        //read char by char HTTP request
        if (readString.length() < 100) {
          //store characters to string
          readString += c;
          Serial.print(c);
        }

        //if HTTP request has ended
        if (c == '\n') {

          Serial.println(readString); //print to serial monitor for debuging

          client.println("HTTP/1.1 200 OK"); //send new page
          client.println("Content-Type: text/html");

          //stopping client
          client.stop();

          //controls the Arduino if you press the buttons
          if (readString.indexOf("?relay1on") > 0) {
            digitalWrite(relay, LOW);
            Serial.println("relay is on");
          }
          if (readString.indexOf("?relay1off") > 0) {
            digitalWrite(relay, HIGH);
            Serial.println("relay is off");
          }
        }
      }
    }
  }
}

My Android Code:

package com.projects.mahmoudmahdi.etherdroid;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

import java.net.HttpURLConnection;
import java.net.URL;

public class MainActivity extends AppCompatActivity implements OnClickListener {

    private class commandArduino extends AsyncTask<URL, Void, Integer> {
        @Override
        protected Integer doInBackground(URL... url) {

            int response = 0;

            if (isNetworkAvailable()) {

                // params comes from the execute() call: params[0] is the url.
                try {
                    HttpURLConnection urlConnection = (HttpURLConnection) url[0].openConnection();
                    urlConnection.setRequestMethod("GET");

                    urlConnection.connect();

                    switch (urlConnection.getResponseCode()) {
                        case HttpURLConnection.HTTP_OK:
                            Log.d("RESPONSE", "**OK**");
                            break; // fine, go on
                        case HttpURLConnection.HTTP_GATEWAY_TIMEOUT:
                            Log.d("RESPONSE", "**gateway timeout**");
                            break;// retry
                        case HttpURLConnection.HTTP_UNAVAILABLE:
                            Log.d("RESPONSE", "**unavailable**");
                            break;// retry, server is unstable
                        default:
                            Log.d("RESPONSE", "**unknown response code**.");
                            break; // abort
                    }

                    urlConnection.disconnect();
                    Log.d("RESPONSE", "Aborting download of dataset.");
                } catch (Exception e) {
                    Log.e("EtherDroid", "STACKTRACE");
                    Log.e("EtherDroid", Log.getStackTraceString(e));
                }
            }
            return response;
        }
    }

    static boolean relay1 = false;
    commandArduino cmdAdn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button Relay1 = (Button) findViewById(R.id.relay1);

        Relay1.setOnClickListener(this);


    }

    private boolean isNetworkAvailable() {
        boolean available = false;
        /** Getting the system's connectivity service */
        ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

        /** Getting active network interface  to get the network's status */
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();

        if (networkInfo != null && networkInfo.isAvailable())
            available = true;

        /** Returning the status of the network */
        return available;
    }

    public void onClick(View thisView) {
        cmdAdn = new commandArduino();
        switch (thisView.getId()) {
            case R.id.relay1:
                if (!relay1) {
                    relay1 = true;
                    try {
                        cmdAdn.execute(new URL("http://192.168.1.215/?relay1on"));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    Toast.makeText(getApplicationContext(), "relay1 on", Toast.LENGTH_SHORT).show();
                } else {
                    relay1 = false;
                    try {
                        cmdAdn.execute(new URL("http://192.168.1.215/?relay1off"));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    Toast.makeText(getApplicationContext(), "relay1 off", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
}

Solution

  • In general terms, you're missing:

    1. Send data in the response
    2. Giving time for the socket to send data before closing
    3. Reading Android's response before closing the connection.

    More in detail, make sure to send some data after establishing the connection:

    client.println("HTTP/1.1 200 OK"); //send new page
    client.println("Content-Type: text/html");
    client.println("Connection: close");  // the connection will be closed after completion of the response
    client.println();
    client.println("<!DOCTYPE HTML>");
    client.println("<html>");
    client.println("</html>");
    
    // Give it time to close
    delay(1);
    client.close();
    

    Reference: https://www.arduino.cc/en/Tutorial/WebServer , here's the full example:

    void loop() {
      // listen for incoming clients
      EthernetClient client = server.available();
      if (client) {
        Serial.println("new client");
        // an http request ends with a blank line
        boolean currentLineIsBlank = true;
        while (client.connected()) {
          if (client.available()) {
            char c = client.read(); // NOTE: You're missing this in your code
            Serial.write(c);
            // if you've gotten to the end of the line (received a newline
            // character) and the line is blank, the http request has ended,
            // so you can send a reply
            if (c == '\n' && currentLineIsBlank) {
              // send a standard http response header
              client.println("HTTP/1.1 200 OK");
              client.println("Content-Type: text/html");
              client.println("Connection: close");  // the connection will be closed after completion of the response
              client.println("Refresh: 5");  // refresh the page automatically every 5 sec
              client.println();
              client.println("<!DOCTYPE HTML>");
              client.println("<html>");
              // output the value of each analog input pin
              for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
                int sensorReading = analogRead(analogChannel);
                client.print("analog input ");
                client.print(analogChannel);
                client.print(" is ");
                client.print(sensorReading);
                client.println("<br />");
              }
              client.println("</html>");
              break;
            }
            if (c == '\n') {
              // you're starting a new line
              currentLineIsBlank = true;
            } else if (c != '\r') {
              // you've gotten a character on the current line
              currentLineIsBlank = false;
            }
          }
        }
        // give the web browser time to receive the data
        delay(1);
        // close the connection:
        client.stop();
        Serial.println("client disconnected");
        Ethernet.maintain();
      }
    }
    

    Also, consider using json instead of html for Arduino's response. To do that, you'll need to change:

    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: application/json");
    client.println("Connection: close");
    client.println();
    client.println("{\"status\":\"ok\"}"); // Or whatever you want to change