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
}
}
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.
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!
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!
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
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
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.