Photon simple telnet to serial fails

I hope I don’t shatter the image of Elites knowing everything! I am trying to use a Photon to create a super simple telnet (TPC port 23) to Serial “conduit” so I can use Putty or TeraTerm to telnet into the photon so all data sent goes to the Serial port and vice versa. The ultimate goal is to be able to turn this “feature” on and off.

So, the issue I have it that is works fine on the first telnet connection but once I close the Putty session, wait a few seconds and open another session, the app does not “see” that session and eventually the Photon goes into a blynking (vs breathing) cyan condition.

The code was compiled locally with the latest develop branch and tested on a Win7x64 PC:


// serial connection
int serialBaud = 9600;

// socket parameters
int serverPort = 23;

// start TCP servers
TCPServer server = TCPServer(serverPort);
TCPClient client;

char myIpString[24];

enum tnetState {DISCONNECTED, CONNECTED};
int telnetState = DISCONNECTED;

	
void setup() {
	Serial.begin(serialBaud); // open serial communications
	server.begin(); // begin listening for TCP connections

	IPAddress myIP = WiFi.localIP();
	sprintf(myIpString, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]);
	Spark.variable("devIP", myIpString, STRING);

}

void loop() {
	
	if (client.connected()) {
		telnetState = CONNECTED;
		// echo all available bytes back to the client
		int incomingByte = 0;
		while (client.available()) {	//Read incoming TCP data if available and copy to Serial port
			incomingByte = client.read();
			Serial.write(char(incomingByte));
		}
		if (incomingByte != 0)			//Make sure to flush outgoing serial data before looking at serial input
			Serial.flush();

		while (Serial.available() > 0) { 	//Read incoming serial data if available and copy to TCP port
			incomingByte = Serial.read();
			client.write((char)incomingByte); 	// write the char data to the client
		}
	}
	else {
		// if no client is yet connected, check for a new connection
		if (telnetState == CONNECTED) {		//If client WAS connected before, so stop() to close connection
			Serial.println("client.stop");	//Check for new connection on next loop()
			client.stop();
			telnetState = DISCONNECTED;
		}
		client = server.available();		//Update TCP client status - "client" is declared globally
	}
	
}

@mdma, @kennethlimcp, @BDub, and everyone else, thanks for your help! :grinning:

1 Like

Can anyone test this on a Core and/or Photon?

Once the code is flashed to a device, all you need to do is open a terminal/putty (or whatever) serial session to the device and a second putty (or whatever) telnet session (port 23) to the IP of the device (which is available on the devIP Spark.variable). When a key his pressed on the telnet session, it should appear in the serial terminal and vice versa. Now, if the telnet client (eg putty) is closed and you wait a few minutes and open a new telnet session then, in theory, typing stuff in the telnet session should show in the serial terminal.

I am totally snowed under for the next couple of days, but will look at this during the course of the week.

1 Like

When you close your TELNET PUTTY, do you see client.stop in your SERIAL PUTTY?

@BDub, no and that should not be the case cause it means client.connected() does not go false.

Perhaps this will make it easier to talk about:

void loop() {
    
    if (client.connected()) {
        telnetState = CONNECTED;
        
        // Process serial to TCP bridge
    }
    else {
        // If client WAS connected before, 
        // then call client.stop() to close connection
        // Check for new connection on next loop()
        if (telnetState == CONNECTED) {     
            Serial.println("client.stop");
            client.stop();
            telnetState = DISCONNECTED;
        }
        
        // If no client is yet connected, check for a new connection
        // Update TCP client status - "client" is declared globally
        client = server.available();        
    }
}

See how if it was connected, and then you closed the session wouldn’t client.connected() be false and force your Serial.println("client.stop");, and then on successive loops if a new client is .available() server.available(); will pass that to the client instance and your client.connected() will be true again on the next loop.

I was hoping you’d see “client.stop” in your SERIAL PUTTY or not, so we knew which direction to start looking. If you are not seeing it, I think client.connected() is not becoming false for some reason when you close the TELNET PUTTY.

@BDub, everything you describe was the idea. I even added (client != NULL) prior to the client.connected() test but that did not work. Your comments got me thinking however. I will add a timer so that if no text is received for way 1 minute, it will force a client.stop() and see if connection closes and I can reconnect a new telnet session as expected. This is good form anyway!

1 Like

Just for clarity and education - client is an instance (of TCPClient) and not a pointer, so it can never be NULL.

2 Likes

That would be my mistake then–I had told @peekay123 that I thought it could be NULL when the socket was not open.

I get the same results with my Photon @peekay123

I added the following to the top of the loop:

if(millis() % 1000 == 0)
  Serial.println("client.connected(): " + String(client.connected()));

It never goes back to 0/false (I’m using the term never pretty loosely here)

There’s a shorter way that’s a little less confusing:

if (client) {
   
}

or

if (client.connected())) {

}

I prefer the latter since it’s explicit about the nature of the test.

The reason the comparison with NULL works is that TCPClient has a bool operator so it’s implicitly converted to a bool value, and NULL is then also converted to bool (test for non-zero, which is false) and the two bool values compared.

So the comparison to NULL works, it’s just a bit confusing to read! :smile:

3 Likes

@mdma thanks for the clarification but it still doesn’t explain why client.connected() does not go false when the client disconnects. :flushed:

This is a nice idea, but will be problematic since it will not be executed every millisecond and won't always hit the 1000th boundaries.

Try this:

static uint32_t lastTime = 0;
static uint32_t now = 0;

now = millis();
if (now - lastTime > 1000UL) {
  lastTime = now;
  Serial.println("client.connected(): " + String(client.connected()));
}

Eureka, somewhat. So adding a timeout works perfectly. The socket closes after the timeout and when I start a new session, it works as expected. The “bad” news is a) it does not work with the latest develop branch so I used DEV to compile and flash OTA, and b) client.connected() does not go false as it should. FYI @mdma

Where did you add a timeout @peekay123?

Ok, so here is my final working code until someone fixes the client.connected bug(?)


#include "application.h"

// serial connection
int serialBaud = 9600;

// socket parameters
int serverPort = 23;

// start TCP servers
TCPServer server = TCPServer(serverPort);
TCPClient client;

char myIpString[24];

enum tnetState {DISCONNECTED, CONNECTED};
int telnetState = DISCONNECTED;

unsigned long activity_timeout = 0;
	
void setup() {
	Serial.begin(serialBaud); // open serial communications
	server.begin(); // begin listening for TCP connections

	IPAddress myIP = WiFi.localIP();
	sprintf(myIpString, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]);
	Spark.variable("devIP", myIpString, STRING);

}

void loop() {
	
	if (client.connected()) {
		if (telnetState == DISCONNECTED)
			Serial.println("client connected");	//Check for new connection on next loop()

		telnetState = CONNECTED;
		// echo all available bytes back to the client
		int incomingByte = 0;
		while (client.available()) {	//Read incoming TCP data if available and copy to Serial port
			incomingByte = client.read();
			Serial.write(char(incomingByte));
			activity_timeout = millis();
		}
		if (incomingByte != 0)			//Make sure to flush outgoing serial data before looking at serial input
			Serial.flush();

		else if (millis() - activity_timeout > 60000UL) {	// No data received so check 1 minute inactivity timeout
			Serial.println("client.stop");	//Check for new connection on next loop()
			client.stop();
			telnetState = DISCONNECTED;
		}

		while (Serial.available() > 0) { 	//Read incoming serial data if available and copy to TCP port
			incomingByte = Serial.read();
			client.write((char)incomingByte); 	// write the char data to the client
		}
	}
	else {
		// if no client is yet connected, check for a new connection
		if (telnetState == CONNECTED) {		//If client WAS connected before, so stop() to close connection
			Serial.println("client.stop");	//Check for new connection on next loop()
			client.stop();
			telnetState = DISCONNECTED;
		}
		client = server.available();		//Update TCP client status - "client" is declared globally
	}
	
}

I think I’m going to use elapsedMillis cause it so clean :wink:

2 Likes

Works well enough, quick and dirty lol It ran often enough to give me the info I was looking for

1 Like

There is no notification from WICED that a socket has been externally closed, so the only time we know a socket is closed is on the next read/write attempt.

1 Like

Would a peek() trigger it?

Not always, if the data being read is from the local buffer.