Were testing out the TCP client functionality and running into a problem once the packet rate gets increased with the read saying there is no data. I've tested this on a Boron and an Argon.
Setup:
TCP client on particle side
TCP server in python to echo packet back
Observations:
packet rate of 1Hz has no issues
packet rate of 4Hz will sometimes cause the read to stop working
packet rate of 10Hz will cause the read to stop working within 5s to 1 min
when the read stops working (available() returns 0), the send still functions correctly and the server gets the packets to echo back
when the read stops working (available() returns 0) and I shut down the server, the particle does not detect that the connection has closed and continues to send packets
while the read is working correctly (available() returns > 0) and I shut down the server, the particle does detect that the connection has closed
/*
* Project myProject
* Author: Your Name
* Date:
* For comprehensive documentation and examples, please visit:
* https://docs.particle.io/firmware/best-practices/firmware-template/
*/
// Include Particle Device OS APIs
#include "Particle.h"
// Let Device OS manage the connection to the Particle Cloud
SYSTEM_MODE(SEMI_AUTOMATIC);
#if Wiring_WiFi
STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));
#endif
// Run the application and system concurrently in separate threads
SYSTEM_THREAD(DISABLED);
// Show system, cloud connectivity, and application logs over USB
// View logs with CLI using 'particle serial monitor --follow'
SerialLogHandler logHandler(LOG_LEVEL_INFO);
byte server[] = { X, X, X, X };
TCPClient wifiClient;
const int wifiClientPort{7150};
void wifiSetup()
{
Log.info("STARTING WIFI SETUP");
Particle.disconnect();
waitUntil(Particle.disconnected);
WiFi.connect(WIFI_CONNECT_SKIP_LISTEN);
waitUntil(WiFi.ready);
Log.info("WIFI setup DONE");
}
void wifiTcpClient()
{
Log.info("WIFI TCP CLIENT START");
while(!wifiClient.connect(server, wifiClientPort))
{
Log.info("WIFI TCP CLIENT CONNECTION FAILED -- SLEEP 1s");
delay(1000);
}
waitUntil(wifiClient.connected);
Log.info("WIFI TCP CLIENT CONNECTED");
}
void wifiTcpRead()
{
static int count{0};
static u_int8_t buf[40];
static bool packetRead{false};
count = wifiClient.available();
if(count >= 40)
{
count = wifiClient.read(buf, 40);
if(count != 40)
{
Log.info("DID NOT READ 40!!!");
while(1)
{
delay(10000);
}
}
}
count == 40 ? packetRead = true : packetRead = false;
if(packetRead)
{
Log.info("WIFI PACKET READ = %s", buf);
packetRead = false;
}
}
void wifiTcpWrite()
{
static int packetCount{0};
static char sendBuf[40] = "WIFI SENDING PACKET NUM";
static unsigned long lastPacketTime{0};
static const unsigned long packetInterval{75};
if(!wifiClient.connected())
{
Log.info("WIFI TCP CONNECTION DROPPED -- NO WRITE");
while(1)
{
delay(10000);
}
}
if(millis() - lastPacketTime >= packetInterval)
{
snprintf(sendBuf, sizeof(sendBuf), "WIFI SENDING PACKET NUM=%d", ++packetCount);
wifiClient.write((byte*)sendBuf, 40);
Log.info("WIFI SENT PACKET = %s", sendBuf);
lastPacketTime = millis();
}
}
// setup() runs once, when the device is first turned on
void setup() {
wifiSetup();
wifiTcpClient();
}
// loop() runs over and over again, as quickly as it can execute.
void loop() {
wifiTcpRead();
wifiTcpWrite();
delay(1);
}
Server code
import socket
import sys
# Create a TCP/IP socket
wifiSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind the socket to the port
wifi_server_address = ('X.X.X.X', 7150)
print ('WIFI starting up on %s port %s' % wifi_server_address)
wifiSock.bind(wifi_server_address)
# Listen for incoming connections
wifiSock.listen(1)
while True:
# Wait for a connection
print ('waiting for a WIFI connection')
wifi_connection, wifi_client_address = wifiSock.accept()
try:
print ('WIFI connection from', wifi_client_address)
# Receive the data in small chunks and retransmit it
while True:
wifi_data = wifi_connection.recv(40)
print ('WIFI received "%s"' % wifi_data)
if wifi_data:
print ('WIFI sending data back to the WIFI client')
ret = wifi_connection.send(wifi_data)
if(ret != 40):
exit(-1)
finally:
# Clean up the connection
connection.close()
WIFI received "b'WIFI SENDING PACKET NUM=391\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'"
WIFI sending data back to the WIFI client
WIFI received "b'WIFI SENDING PACKET NUM=392\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'"
WIFI sending data back to the WIFI client
WIFI received "b'WIFI SENDING PACKET NUM=393\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'"
WIFI sending data back to the WIFI client
WIFI received "b'WIFI SENDING PACKET NUM=394\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'"
WIFI sending data back to the WIFI client
WIFI received "b'WIFI SENDING PACKET NUM=395\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'"
WIFI sending data back to the WIFI client
WIFI received "b'WIFI SENDING PACKET NUM=396\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'"
WIFI sending data back to the WIFI client
WIFI received "b'WIFI SENDING PACKET NUM=397\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'"
WIFI sending data back to the WIFI client
WIFI received "b'WIFI SENDING PACKET NUM=398\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'"
There is no guarantee that a 40-byte write in TCP will be received as a 40-byte read. It's stream protocol, and the boundaries are not preserved. Also you cannot simply wait until 40 bytes is available. I believe you should always read whatever is available and accumulate it into your own buffer to find the frame boundaries.
Also closing in TCPClient is confusing because the underlying TCP protocol is a two-step close that allows for a half-open connection (data flowing in one direction only). However the Arduino TCPClient implementation doesn't expose this and it can behave unpredictably. Also, because the FIN can be in the last data packet, if there's outstanding data to be read, you may not get the close notification until you've read it. The exact behavior is unpredictable, however, and sometimes you can get a close before you've read all of the data.
It's true that it's not guaranteed, but if anything came through I'd get 40 bytes eventually. In the case of 10hz, that never happens because the read buffer is empty.
As an update, we switched to a different platform and are able to run the above ported code at 100Hz with no issues. The connection issue is also resolved.