I have used the UART-Bridge example to create my own serial-WiFi converter.
But the code for the serial handler is overly complex since it has the option of specifying multiple simultaneous clients, which we do not need. So there is an array of clients and handling around it and I want to simplify the code for a single client.
Here is what I have now:
StartSerialServer is called from setup().
HandleSerialServer is called in loop().
void StartSerialServer()
{
//start UART.
Serial.begin(ESPConf.baud);
SerialDebug.println("Swapping pins for Serial, use 7/10 for Rx/Tx!");
Serial.swap(); //Put the serial pins on 7 (Rx) and 10 (Tx) to avoid boot data to reach SS
// Instantiate and start TCP serial server on port TCP_PORT
tcpServer = new WiFiServer(ESPConf.tcpport);
tcpServer->begin();
tcpServer->setNoDelay(true);
SerialDebug.println("Serialserver ready!");
}
void HandleSerialServer()
{
uint8_t i;
char buf[1024]; //Buffer for serial server
unsigned int bytesAvail, bytesIn;
//check if there are any new clients
if (tcpServer->hasClient())
{
for (i = 0; i < MAX_SRV_CLIENTS; i++)
{
//find free/disconnected spot
if (!tcpServerClients[i] || !tcpServerClients[i].connected())
{
if (tcpServerClients[i]) tcpServerClients[i].stop();
tcpServerClients[i] = tcpServer->available();
SerialDebug.print("New client: "); SerialDebug.println(i);
delay(1);
continue;
}
}
//no free/disconnected spot so reject
WiFiClient tcpServerClient = tcpServer->available();
tcpServerClient.stop();
SerialDebug.println("New client rejected: ");
}
// Check for TCP client data ------
for (i = 0; i < MAX_SRV_CLIENTS; i++)
{
if (tcpServerClients[i] && tcpServerClients[i].connected())
{
//get data from the telnet client and push it to the UART
while ((bytesAvail = tcpServerClients[i].available()) > 0)
{
bytesIn = tcpServerClients[i].readBytes(buf, min(sizeof(buf), bytesAvail));
if (bytesIn > 0)
{
SerialSS.write(buf, bytesIn);
delay(0);
}
}
}
}
//check UART for data
while ((bytesAvail = SerialSS.available()) > 0)
{
bytesIn = SerialSS.readBytes(buf, min(sizeof(buf), bytesAvail));
if (bytesIn > 0)
{
//push UART data to all connected telnet clients
for (i = 0; i < MAX_SRV_CLIENTS; i++)
{
if (tcpServerClients[i] && tcpServerClients[i].connected())
{
tcpServerClients[i].write((uint8_t*)buf, bytesIn);
delay(0);
}
}
}
}
}
I do not really understand what is happening in the loops above and would rather simplify the lot by:
Only allowing one client to connect
Use this client without the extra loops and stuff.
Can someone please suggest how this can be simplified?
And how can one check if the single client is still connected?
And how can one check if the single client is still connected?
"the single client" is like the next person in line at a ticket booth. The Arduino will handle the next client in the line. It has no way of knowing if the client is one that it has handled before, or not. It will happily sell tickets to My Fair Lady to the client, regardless of how many tickets the client has already bought.
The hardware is capable of handling multiple simultaneous clients. The simplest approach is to NOT mess with that capability.
Use this client without the extra loops and stuff.
What "extra loops and stuff"? There is a loop to deal with the maximum number of simultaneous connections. You should NOT mess with that loop.
For some reason when I connect a PuTTY TCP client to my ESP I get the debug messages:
New client: 0
New client: 1
New client rejected:
The first line is expected but not the following two...
It seems like the connection logic is flawed such that it goes through the MAX_SRV_CLIENTS (in my case 2) slots even though there is only one connection attempt.
And the final 3 lines inside the if (tcpServer->hasClient()) block seems to always execute and print the reject message...
BosseB:
For some reason when I connect a PuTTY TCP client to my ESP I get the debug messages:
New client: 0
New client: 1
New client rejected:
The first line is expected but not the following two...
It seems like the connection logic is flawed such that it goes through the MAX_SRV_CLIENTS (in my case 2) slots even though there is only one connection attempt.
And the final 3 lines inside the if (tcpServer->hasClient()) block seems to always execute and print the reject message...
the tcp releases the sockets after some time. if the same telnet client application connects second time it is a new client. the first connection dies after some time after the client closed the connection.
Notice that these reports all are printed in rapid succession when the PuTTY program makes its connection to the serial server.
So there is no connect, then wait a while, then a new connect and finally a third connect when the allowed client count has been exhausted.All happens within a single second when I connect using PuTTY.
I probably have to write my own client so I can state for a fact that only a single operation is performed during connect.
you are on Windows? try the Windows command line telnet client. if you are on linux change to port 2323 and try telnet client (on port 23 it tries to negotiate settings)
Maybe this github code for an UART-bridge is a better example?
It does not use an array of clients, just a single one, hence no arrays to scan and keep track of.
But otherwise it seems to work similarly to the example I started from...
QUESTION:
Is it possible to configure the tcp server to allow only one client connection at a time?
Is it possible to configure the tcp server to allow only one client connection at a time?
The Arduino only HANDLES one connection at a time, like a ticket seller. What you appear to be wanting to do is to do away with the line of clients wanting to buy tickets. Why?
OK, let me explain:
The TCP server is linking a serial (RS232) wire going to a measuring system to the client sitting at the same network and doing so via TCP sockets.
The serial communication paradigm (one server, one client, one wire) needs to be preserved, so this is why I want to secure that only one client is connected at a time and that this client can send commands to the equipment and receive results either immediately (in human terms but still some milliseconds later) or at a later time when the measurement completes (this can take some 10s of seconds or even minutes).
Meanwhile we do not want to allow a second client to connect and mess with the ongoing activity. Neither by pushing new commands into an already started sequence nor by reading off the results that was really destined for the client who commanded the action to begin with. We cannot modify the protocol over the serial wire either.
The reason we want the WiFi link here is that we want to use Android devices as well as PC:s as masters and do away with the RS232 wires. But the measuring devices cannot be modified themselves so we want to employ the RS232/TCP bridge here.
Coming back to the example supplied with the Arduino IDE, it is named WiFiTelnetToSerial and is located below ESP8266WiFi in the examples list.
I used this as the template for my relaying application.
Now I have to ask about something I feel is not correct in the code for the loop() function where it checks for new TCP clients:
void loop() {
uint8_t i;
//check if there are any new clients
if (server.hasClient()){
for(i = 0; i < MAX_SRV_CLIENTS; i++){
//find free/disconnected spot
if (!serverClients[i] || !serverClients[i].connected()){
if(serverClients[i]) serverClients[i].stop();
serverClients[i] = server.available();
Serial1.print("New client: "); Serial1.print(i);
continue; // <=== DOES THIS REALLY WORK??? Will take the next i in the loop...
}
}
//no free/disconnected spot so reject
WiFiClient serverClient = server.available();
serverClient.stop();
}
//check clients for data
for(i = 0; i < MAX_SRV_CLIENTS; i++){
if (serverClients[i] && serverClients[i].connected()){
if(serverClients[i].available()){
//get data from the telnet client and push it to the UART
while(serverClients[i].available()) Serial.write(serverClients[i].read());
}
}
}
//check UART for data
if(Serial.available()){
size_t len = Serial.available();
uint8_t sbuf[len];
Serial.readBytes(sbuf, len);
//push UART data to all connected telnet clients
for(i = 0; i < MAX_SRV_CLIENTS; i++){
if (serverClients[i] && serverClients[i].connected()){
serverClients[i].write(sbuf, len);
delay(1);
}
}
}
}
The continue I have marked in the code for the connection sequence seems to just continue the loop even when a free spot has been found and the new client assigned its spot in the array of clients. Then when the loop is exhausted it will reach the reject step, which seems very odd to me.
In actual fact the code should break out of the loop entirely and continue after the exit of the
if (server.hasClient()){
block altogether, but there is no such way in the code...
Or am I mis-reading it totally?
How could I exit the if block when a client spot has been found?
Then later down it uses a one-sentence TCP read to Serial write operation:
Will this handle the case of data arriving faster than the Serial device can send them out and not choke and be losing data?
And can it handle binary data, i.e. can it be expected to transfer NULL bytes?
In my derived code I am using a buffer of bytes and the Serial.write uses the count of read chars to send them:
// Check for TCP client data ------
for (i = 0; i < MAX_SRV_CLIENTS; i++)
{
if (tcpServerClients[i] && tcpServerClients[i].connected())
{
//get data from the telnet client and push it to the UART
while ((bytesAvail = tcpServerClients[i].available()) > 0)
{
bytesIn = tcpServerClients[i].readBytes(buf, min(sizeof(buf), bytesAvail));
if (bytesIn > 0)
{
Serial.write(buf, bytesIn); //Send bytes that were read from TCP
delay(0);
}
}
}
}