Using TCP to communicate between an Android and Photon without cloud connection

Hello all,

I’m looking to eventually query my Photon from my Android phone without requiring the cloud connection to be operational.

I’m attempting to use a TCP connection to communicate between my Android phone and the Photon (where the phone is a wireless hotspot not connected to the internet).

Whenever I attempt to get them to connect to one another, I can get the client (Android app) to connect to the server (Photon), but I can’t get the Photon to find that client.available() == true, but have already confirmed that client.connected() == true, previously. The TCPClient helper class also seems to get stuck after the initial message is sent to the Photon.

This is my Photon firmware:

SYSTEM_MODE(MANUAL);

TCPServer server = TCPServer(23);
TCPClient client;

void setup() {
      WiFi.on();
      WiFi.setCredentials("AndroidAP", "password");
      WiFi.connect();
    
      // Make sure your Serial Terminal app is closed before powering your device
      Serial.begin(9600);
      // Now open your Serial Terminal, and hit any key to continue!
    
    // start listening for clients
      server.begin();
}

void loop() {
    if (client.connected()) {
        // echo all available bytes back to the client
        while (client.available() > 0) {
          server.write("200");
          Serial.println("200");
          Serial.println(WiFi.SSID());
          Serial.println(WiFi.localIP());
    
        }
        
    } else {
        // if no client is yet connected, check for a new connection
        client = server.available();
    }
}

This is my MainActivity.java:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    private TCPClient mTcpClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Button button = (Button) findViewById(R.id.send_button);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // connect to the server
                new connectTask().execute("message1");
            }
        });
    }

    public class connectTask extends AsyncTask<String,String,TCPClient> {

        @Override
        protected TCPClient doInBackground(String... message) {

            //we create a TCPClient object and
            mTcpClient = new TCPClient(new TCPClient.OnMessageReceived() {
                @Override
                //here the messageReceived method is implemented
                public void messageReceived(String message) {
                    //this method calls the onProgressUpdate
                    publishProgress(message);
                    Log.d("Message", message);
                }
            });
            mTcpClient.run();

            return null;
        }

        @Override
        protected void onProgressUpdate(String... values) {
            super.onProgressUpdate(values);
            Log.d("values", values[0]);
        }
    }
}

and my TCPClient helper class:

import android.util.Log;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

public class TCPClient {

        private String serverMessage;
        public static final String SERVERIP = "192.168.43.157"; //your Photon (formerly computer) IP address
        public static final int SERVERPORT = 23;
        private OnMessageReceived mMessageListener = null;
        private boolean mRun = false;

        PrintWriter out;
        BufferedReader in;

        /**
         *  Constructor of the class. OnMessagedReceived listens for the messages received from server
         */
        public TCPClient(OnMessageReceived listener) {
            mMessageListener = listener;
        }

        /**
         * Sends the message entered by client to the server
         * @param message text entered by client
         */
        public void sendMessage(String message){
            if (out != null && !out.checkError()) {
                out.println(message);
                out.flush();
            }
        }

        public void stopClient(){
            mRun = false;
        }

        public void run() {

            mRun = true;

            try {
                //here you must put your computer's IP address.
                InetAddress serverAddr = InetAddress.getByName(SERVERIP);

                Log.e("TCP Client", "C: Connecting...");

                //create a socket to make the connection with the server
                Socket socket = new Socket(serverAddr, SERVERPORT);

                try {

                    //send the message to the server
                    out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);

                    Log.e("TCP Client", "C: Sent.");

                    Log.e("TCP Client", "C: Done.");

                   // Code never seems to be able to execute past this point...

                    //receive the message which the server sends back
                    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                    //in this while the client listens for the messages sent by the server
                    while (mRun) {
                        serverMessage = in.readLine();

                        if (serverMessage != null && mMessageListener != null) {
                            //call the method messageReceived from MyActivity class
                            mMessageListener.messageReceived(serverMessage);
                        }
                        serverMessage = null;

                    }

                    Log.e("RESPONSE FROM SERVER", "S: Received Message: '" + serverMessage + "'");

                } catch (Exception e) {

                    Log.e("TCP", "S: Error", e);

                } finally {
                    //the socket must be closed. It is not possible to reconnect to this socket
                    // after it is closed, which means a new socket instance has to be created.
                    socket.close();
                }

            } catch (Exception e) {

                Log.e("TCP", "C: Error", e);

            }

        }

        //Declare the interface. The method messageReceived(String message) will must be implemented in the MyActivity
        //class at on asynckTask doInBackground
        public interface OnMessageReceived {
            public void messageReceived(String message);
        }
}

The TCPClient seems to get stuck waiting for the response from the Photon.

Has anyone run into these problems before, or got a potential solution? Or maybe even have a past project that they would be willing to share?

Many thanks!

I may be misreading your code, but it looks like your Photon code only sends a response back after it receives data from the phone, because it’s in a while(client.available()) loop. Available means there are bytes waiting to be read that have been sent from the other side, not that the connection is open.

In your Android code, it looks like it creates the reader and writers, then waits for data using in.readLine(). There won’t ever be any data, because the Photon is waiting for data to be sent to it from the phone first.

Thanks for the speedy response @rickkas7!

I’m guessing you mean these lines of code:

//send the message to the server
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);

//receive the message which the server sends back
in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 

I’m pretty new to using TCPClient and Server, but don’t we want the client to send a message to the server, and then receive whatever message the server sends back?

For anyone else who comes along and is looking to do something similar, I eventually got this code to work.

TCPClient.java

import android.util.Log;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

import static android.R.id.message;

public class TCPClient {

        private String serverMessage;
        public static String buttonPushed;
        public static final String SERVERIP = "192.168.43.157"; //your Photon (formerly computer) IP address
        
        public static final int SERVERPORT = 23;
        private OnMessageReceived mMessageListener = null;
        private boolean mRun = false;

        PrintWriter out;
        BufferedWriter out1;
        OutputStreamWriter out2;
        OutputStream out3;
        BufferedReader in;

        /**
         *  Constructor of the class. OnMessagedReceived listens for the messages received from server
         */
        public TCPClient(OnMessageReceived listener) {
            mMessageListener = listener;
        }

        /**
         * Sends the message entered by client to the server
         * @param message text entered by client
         */
        public void sendMessage(String message){
            if (out != null && !out.checkError()) {
                out.println(message);
                Log.d("TCP Client", "Message: " + message);
                out.flush();
            }
        }

        public void stopClient(){
            mRun = false;
        }

        public void run() {

            mRun = true;

            try {
                //here you must put your computer's IP address.
                InetAddress serverAddr = InetAddress.getByName(SERVERIP);

                Log.e("TCP Client", "C: Connecting...");

                //create a socket to make the connection with the server
                Socket socket = new Socket(serverAddr, SERVERPORT);

                try {
                    //send the message to the server

                    out3 = socket.getOutputStream();
                    out2 = new OutputStreamWriter(socket.getOutputStream());
                    out1 = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                    out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
                    Log.e("TCP Client", "C: out3 = " + out3);
                    Log.e("TCP Client", "C: out2 = " + out2);
                    Log.e("TCP Client", "C: out1 = " + out1);
                    Log.e("TCP Client", "C: out0 = " + out);

                    sendMessage(buttonPushed); //this was the key
                    Log.e("TCP Client", "C: Sent.");

                    Log.e("TCP Client", "C: Done.");

                    //receive the message which the server sends back
                    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    Log.e("TCP Client", "C: received = " + in);
                    Log.e("TCP Client", "C: run = " + mRun);
                    //in this while the client listens for the messages sent by the server
                    while (mRun) {
                        Log.e("TCP Client", "C: I got to the while loop!");

                        serverMessage = in.readLine();

                        Log.e("TCP Client", "C: serverMessage = " + serverMessage);
                        new TCPClient(mMessageListener);
                        stopClient();
                        if (serverMessage != null && mMessageListener != null) {
                            //call the method messageReceived from MyActivity class
                            mMessageListener.messageReceived(serverMessage);
                        } else {
                            serverMessage = null;
                        }
                    }

                    Log.e("TCP Client", "C: run = " + mRun);

                    Log.e("RESPONSE FROM SERVER", "S: Received Message: '" + serverMessage + "'");
                } catch (Exception e) {
                    Log.e("TCP", "S: Error", e);
                } finally {
                    //the socket must be closed. It is not possible to reconnect to this socket
                    // after it is closed, which means a new socket instance has to be created.
                    socket.close();
                    Log.d("TCP Client", "Socket closed.");
                    serverMessage = null;
                }

            } catch (Exception e) {
                Log.e("TCP", "C: Error", e);
            }

        }

        //Declare the interface. The method messageReceived(String message) will must be implemented in the MyActivity
        //class at on asynckTask doInBackground
        public interface OnMessageReceived {
            public void messageReceived(String message);
        }
}

MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

//    private ListView mList;
//    private ArrayList<String> arrayList;
//    private MyCustomAdapter mAdapter;
    private TCPClient mTcpClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Button button = (Button) findViewById(R.id.send_button);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // connect to the server
                TCPClient.buttonPushed = "Get range 1";
                new connectTask().execute("");
            }
        });



    }

    public class connectTask extends AsyncTask<String,String,TCPClient> {

        @Override
        protected TCPClient doInBackground(String... message) {

            //we create a TCPClient object and
            mTcpClient = new TCPClient(new TCPClient.OnMessageReceived() {
                @Override
                //here the messageReceived method is implemented
                public void messageReceived(String message) {
                    //this method calls the onProgressUpdate
                    publishProgress(message);
                    Log.d("Message", message);
                }
            });
            mTcpClient.run();

            return null;
        }

        @Override
        protected void onProgressUpdate(String... values) {
            super.onProgressUpdate(values);
            Log.d("values", values[0]);
            //in the arrayList we add the messaged received from server
//            arrayList.add(values[0]);
            // notify the adapter that the data set has changed. This means that new message received
            // from server was added to the list
//            mAdapter.notifyDataSetChanged();
        }
    }
}

Photon code:
SYSTEM_MODE(MANUAL);

TCPServer server = TCPServer(23);
TCPClient client;

void setup() {
      WiFi.on();
      WiFi.setCredentials("AndroidAP", "password");
      WiFi.connect();
    
      // Make sure your Serial Terminal app is closed before powering your device
      Serial.begin(9600);
      // Now open your Serial Terminal, and hit any key to continue!
    
    // start listening for clients
      server.begin();
}

void loop() {
    if (client.connected()) {
        // echo all available bytes back to the client
        while (client.available() > 0) {
          server.write("200\n");
          Serial.println("200");
          Serial.println(WiFi.SSID());
          Serial.println(WiFi.localIP());
    
        }
        
    } else {
        // if no client is yet connected, check for a new connection
        client = server.available();
    }
}

Thank you for the code, it is really helpful.
Have you ever tried to use the phone to setup a soft-ap on your own and then submit the Wifi network credentials in order to start the tcp connection?

I have, tons of times (but not with an iOS device and some very new Androids ;-))

Can you confirm me that you are using this example?

https://docs.particle.io/reference/firmware/photon/#complete-example

What i need to know is the following:
Assume i claim the device and in a later stage (at the client home for example) i use

void setup()
{
    WiFi.listen();
}

void loop()
{
          //read sensors and TCP/IP server
}

Does it mean that everytime i power on the device it will wait on 192.168.0.1 for wifi credentials or will it automatically use the previous one? I mean, should i handle it with an if or not?

Would you be so kind to share those example? As i stated around the forum i’m working in a cloud off environment for now

Yes, using the complete exampe or a slightly adapted version of it.
But I would not enter Listening Mode (WiFi.listen()) unconditionally.

Either your device has no WiFi creds stored anyway, then it will enter Listening Mode by default without calling WiFi.listen() or you add some external trigger (e.g. button) to cause the execution of WiFi.listen() only on demand.

That is clear to me. So are you saying that if the environment i’m in is “new” to the photon i’ll still enter in listening mode?

Nope, the device can store up to five sets of WiFi credentials. If none of these slots is populated (e.g. with a brand new device, after a WiFi.clearCredentials() or after a reset via the SETUP button) the device will by default enter LM.
But if you have at least one set of creds stored already it won’t. But you can enter LM via holding down SETUP for 3sec, or via code under whatever condition you want.

This would be one way to enter LM when your device can’t connect to WiFi for at least one minute

... // preperation code for SoftAP as in sample implementation

SYSTEM_THREAD(ENABLED)
SYSTEM_MODE(SEMI_AUTOMATIC)

void setup() {
  WiFi.connect();                    // try to connect 
  if (!waitFor(WiFi.ready, 60000))   // if that's not possible for 60+ sec
    WiFi.listen();                   // enter Listening Mode
}

So since you are using SYSTEM_THREAD should i use waitUntil on server.begin or will this work anyway?

void setup()
{
    WiFi.connect();                    // try to connect 
	if (!waitFor(WiFi.ready, 30000))   // if that's not possible for 30+ sec
		WiFi.listen();                 // enter Listening Mode
    // Make sure your Serial Terminal app is closed before powering your device
    Serial.begin(9600);
    // Now open your Serial Terminal, and hit any key to continue!

    // start listening for clients
    server.begin();
}

listen should be blocking but the documentation is saying

setup() is executed immediately regardless of the system mode, which means setup typically executes before the Network or Cloud is connected. Calls to network-related code will be impacted and may fail because the network is not up yet

Yes, since you need WiFi connected for your communication, I’d have a waitUntil(WiFi.ready) just before that call - or any other means of making sure your device is actively connected.

WiFi.listen() in itself is not blocking. Only in conjunction with SYSTEM_MODE(AUTOMATIC) and in absence of SYSTEM_THREAD(ENABLED) it will put your device into a mode where user application code is not executed.
But in the code I’ve written above, user application code will just keep running in parallel with SoftAP.

void setup()
{
    WiFi.connect();                    // try to connect 
	if (!waitFor(WiFi.ready, 30000))   // if that's not possible for 30+ sec
		WiFi.listen();                 // enter Listening Mode

    // Make sure your Serial Terminal app is closed before powering your device
    Serial.begin(9600);
    // Now open your Serial Terminal, and hit any key to continue!

    // start listening for clients
    waitUntil(Wifi.Ready);
    server.begin();
}

In this case is the application loop started right after the setup() or due to SYSTEM_THREAD(ENABLED) and SYSTEM_MODE(MANUAL) (or semi) the behaviour is different?

That's probably because you already have valid WiFi credentials stored. Is this not what you want?

I haven’t tried it yet, i was wondering how SYSTEM_THREAD(ENABLED) and SYSTEM_MODE(MANUAL) (or semi) are changing the execution time of loop().

Normally it starts right after setup(), what happens in this case? are those instructions changing the default behavior?

setup() will always be executed before loop() irrespective of SYSTEM_MODE or threading.
But SYSTEM_THREAD(ENABLED) will decouple the user code (setup() and loop()) from the system tasks (unless when accessing shared resources simultaneously).

In this case for example do i have to call Particle.process() (MANUAL_MODE)?
What i’ve understood is that if i don’t use the cloud it is not needed, otherwise yes

@itsmaxdd, if you have SYSTEM_THREAD(ENABLED) then you shouldn’t have to call Particle.process() which simply allows the system firmware to do housekeeping including non-Cloud stuff. Calling Particle.process() isn’t bad anyway.

Hey man. Can you add comments to your code so I can fully understand it. I’m new to programming so please bear with me. Thanksssssssss.