TCPClient Connectivity & Long Timeout Killing UX

Hey There Photonators,

Like many of you I’m developing a product that needs a fluid user experience.

I’m having an issue with 5 second timeouts with the photon when TCPClient.connect is unable to connect in the case of a bad IP, or a non listening server that is killing my UX. I know the company is going through growing pains but to have a TCPClient on a WiFi product that does not have a good a robust interface is pretty disappointing.

With derivations of the following code I observe the following connection cases:

Case 1: TCP Connect

  • W/ Server Listening No Issue
  • W/o Server Listening 5 second hang
  • Bad IP 5 second hang

Case 2: WiFi Ping Default Retry of 5

  • W/ Server Listening No Issue
  • W/o Server Listening No Issue
  • Bad IP 5 second hang

Case 3: WiFi Ping Retry of 1

  • W/ Server Listening No Issue
  • W/o Server Listening No Issue
  • Bad IP 1 Second Hang

I think i’ve seen this issue in a few other threads, but I’d really like to find a working solution for products that will be in a variety of different Wireless Environments, and encourage finding a long term solution that adds value rather than takes away value

A 1 Second Delay is alot better than 5 Seconds for my application but it seems arbitrary and long.

Could the same code that causes ping() to default to a 5 second delay cause a TCP.connect() to also be 5 seconds? Can this be reduced, or can control be given to the user to choose the timeout?

Maybe this is an issue that can be avoided all together through clever application code? Has anyone solved this problem and come up with a fluid connection routine?

Nearly at my whits end here and pretty frustrated any help or wisdom would be much appreciated :slight_smile:

//adapted from @Hootie81 & @jon1977 in community.particle.io
#include "application.h"
//#include "dnsclient.h"

SYSTEM_MODE(AUTOMATIC);
SYSTEM_THREAD( ENABLED );

int serverPort = 8330;
uint32_t lastTime;
const char replymsg[60] = "TheInMsg and then a whole lot more characters than before";
String clientmsg = String("mymsg 2 and then a whole lot more characters than before");
char inmsg[512];
String myInStr;
char myIpString[24];
//IPAddress server(192, 168, 1, 80);//server's ip address
char * server = "playbeemo.com";
IPAddress serverIP;
IPAddress pingGoogle(8,8,8,8);
IPAddress pingBad(169,254,0,0);
bool complete;

TCPClient client;
//DNSClient dns;

bool initialConnection = false;
char outmsg[50];

void setup()
{
  Serial.begin(115200);
  pinMode(D6,OUTPUT);
  pinMode(D7,OUTPUT);
  pinMode(D3,INPUT);
  delay(1000);

  while(!Serial.available()){Particle.process();}
  log("Serial Connected");
  while (!WiFi.ready()) {
    log("Wifi Not Ready...");
    Particle.process();
    WiFi.connect();
    while(WiFi.connecting()) {Particle.process();}
  }
  log("Wifi Now Ready...");
  log("Resolve Server IP...");
  serverIP = WiFi.resolve( server );
  log("Got: " + String(serverIP));
  //log("Starting DNS Server");
  //log("DNS IP: "+ String(WiFi.dhcpServerIP()));
  //dns.begin(dnsServerIP);

}


void loop() {
  log("---------GO LOOP-------");
  pingIPs();
  log("Calling Open:");
  open();

  log("Checking If Connected");
  if (initialConnection && client.connected()) {

    lastTime = millis();
    get();
  }

  log("Calling Close:");
  close();
  log("---------END LOOP-------");
  log("\r\n\r\n\r\n\r\n\r\n");
  delay(5000);

}//loop

void log(String message){
  //Super Debug Mode Will Try Both Serial And WiFi-zle if it's turn
  //We will default to serial always for zeee robust debugging
    Serial.print(millis());
    Serial.print("|^|\t");
    Serial.println(message);
    if ( initialConnection ){
      client.print(millis());
      client.print("|^|\t");
      client.println(message);
    }
}

void pingIPs(){
  //delay(1000);
  int rtryCount = 1;
  log("Pinging with rtry Count "+String(rtryCount));
  int rpingGate = WiFi.ping(serverIP,rtryCount);
  log("Ping Beemo: "+ String(rpingGate));
  //delay(1000);
  int rpingGoog = WiFi.ping(pingGoogle,rtryCount);
  log("Ping Google: "+ String(rpingGoog));
  //delay(1000);
  int rpingBad = WiFi.ping(pingBad,rtryCount);
  log("Ping Bad: "+ String(rpingBad));
}

void open(){

  log("Trying To Connect...");
  initialConnection = client.connect( server, serverPort);
  if (initialConnection) {
    log("Got Connected...");
  }
  else{
    log("Not Connected...");
  }
}

void close(){
  log("Stopping Client");
  if (initialConnection){
    log("Closing...");
    client.stop();
    log("Closed");
  }
}

void get(){
  while ((!client.available()) && (millis() - lastTime < 1)) {
    //Particle.process();
    log("Client Not Available");
  }//wait for response
  log("Get Msg");
  in(inmsg,10);//5-10 pure trial and error
  log("Got: ");log(inmsg);
}

void in(char *ptr, uint8_t timeout) {
  int pos = 0;
  unsigned long lastdata = millis();
  while ( client.available() || (millis()-lastdata < timeout)) {
    if (client.available()) {
      char c = client.read();
      lastdata = millis();
      ptr[pos] = c;
      pos++;
    }//if (client.available())
  }//while ( client.available() || (millis()-lastdata < timeout))
  client.read();
}//void in(char *ptr, uint8_t timeout)

Doing a bit of digging It looks like some WICED timeouts are defined in:

Are these WICED Timeouts causing the long connection times?

/************************************************************************
 * Default WICED networking timeouts in milliseconds */
#define WICED_ALLOCATE_PACKET_TIMEOUT             (2000)
#define WICED_TCP_DISCONNECT_TIMEOUT              (3000)
#define WICED_TCP_BIND_TIMEOUT                    (3000)
#define WICED_TCP_SEND_TIMEOUT                    (3000)
#define WICED_TCP_ACCEPT_TIMEOUT                  (3000)
#define WICED_UDP_BIND_TIMEOUT                    (3000)
#define WICED_NTP_REPLY_TIMEOUT                   (5000)
#define WICED_TLS_RECEIVE_TIMEOUT                 (5000)
#define WICED_TLS_TRANSMIT_TIMEOUT                (5000)
#define WICED_DHCP_IP_ADDRESS_RESOLUTION_TIMEOUT (15000)
#define WICED_AUTO_IP_ADDRESS_RESOLUTION_TIMEOUT (15000)

Are you doing the pings as a necessary part of the process, or just for testing? I mean, if connect worked without long delays in various failure conditions would that be sufficient or do you actually also need ping?

No it’s not directly nessicary, but as a means to reduce blocking code it can help to ping a server and see if it exists before trying to connect to it. This code is basically my debugging routine, and isn’t related to my application directly.

It appears that there are two fixed timeouts of 5 seconds that can affect you here. One is the DNS timeout, and the other is the actual TCP connect timeout. Neither of these can be made significantly shorter, because it actually could take that long for the operation to occur. I have a few ideas for things to try out, but it may take a few days because they’re kind of non-trivial to implement and test.

Good information! Thanks for looking into this for me

In theory it might take 5, 10, or 20 seconds in a worst case for any TCP connection to occur, but why wouldn’t we expose the timeout like on WiFi.ping( ‘particle.io’, timeout) that way it lets the developer more control of their application

Right now i’m moving towards an MDNS configuration the idea being that its easier to setup services or know if they exist when they are being broadcast but this is more work.

As a proof-of-concept I wrote some code that implements an asynchronous version of TCPClient connect(). It takes a callback function, and prevents blocking loop() during connect(). It works surprisingly well, at least on the Photon. It does not work at all on the Electron. I’m also not convinced this is a good idea. Still, it’s interesting and the code is here on Github:

2 Likes

Very Nice Solution! This presents a good way for users with a high speed event loop to wait for a connection, or to poll continuously for an external server if it exists or not. From a developer’s point of view the more ways we have to connect to the photon the stronger the ecosystem will be a fit for various purposes.

This will go along way towards getting my hardware field ready :slight_smile:

Thanks So Much Rick!

1 Like