TCP comms between arduinos

So I’m having trouble with a home monitoring project I’m working on. The project uses a Mega 2560 with Ethernet Shield 2 as a base station and three MKR WiFi 1010 as wireless peripheral devices. The base station’s job is to monitor six different aspects of my home and send me an email if it discovers a problem with any of them. Three of the aspects are monitored over hard wire connections to the Mega analog pins or interrupt pins. The other three are monitored wirelessly over my LAN.

Before I introduced the peripheral code, so just monitoring the three native pins, the system was stable and worked flawlessly for years.

The WiFi units, however, have never been stable. It works but crashes in different ways. When the base station (Mega 2560) boots up it successfully finds each of the three WiFi devices, connects to them and verifies that the device is a device it wants to be connected to. The base station then polls each device sequentially every ten seconds to see what their states are. By poll I mean it connects to the device, reads its status and disconnects.

It seems to work for a while, but seemingly at random one or more of the connections fails (usually only one). 10 seconds later that connection usually works again. I don’t know why these connections fail. I keep track of how many fails occur for each device and if any of them repeat enough times I de-register that device and send an email saying the device is lost. When I get the email I go check the WiFi device, reset it, and then prompt the base station to go look for it again. It does and it finds it, registers it and begins to poll it every 10 seconds.

Eventually all three connections fail every poll. Then the problem becomes the the Ethernet connection to my SMTP email server also fails and I will never know about the failure. The only option then is a board reset of the Mega 2560.

reboot, function, fail randomly, fail completely, repeat…

Obviously, something isn’t right, but I haven’t got a clue what. I must be missing something(s) annoying and/or fundamental. I find the whole Client/Server thing very confusing; how do you choose which device operates in which class?… Should I just connect once and then leave the connection as is, three simultaneous open socket connections?… how do you do that?…

Here’s the relevant code for the base station, Mega 2560:

  // set Ethernet Retransmission count and timeout
  Ethernet.setRetransmissionTimeout(100);
  Ethernet.setRetransmissionCount(15); // 100 ms x 15 tries = 1500 ms timeout (default: 200ms x 8 1600ms)
  // set connection Client timeout
  Sentinel.setConnectionTimeout(900); 

sometimes the connection fails with the Client.ConnectionTimeout…

sometimes the connection fails with the Ethernet.RetransmissionTimeout…

sometimes the connection fails in less than 3 ms…

void Poll()
{
  
  // poll native sensors, done every loop
  pollTemp();
  //
  pollGrid();
  //
  pollSmoke();
  
  // poll WiFi sensors, done every 10 seconds (polling interval)
  if(millis() - pollingIntervalStart > pollingInterval && emailSent/* && !sendMessage*/) // don't poll lan if sending email or phone mssg, ie emailSent = true and sendMessage = false
  {  
//    unsigned long pollSpan = 0UL; 
    static int numFails[numDevices] = {0,0,0,0,0,0,0,0}; //sets all values to 0? numDevices assigned in lanConfig.h file
    
      // turn both leds on
    digitalWrite(redLed, HIGH);
    digitalWrite(greenLed, HIGH);
    Serial.println(F("heartbeat, 2"));

    //
    int ms = 0;
    int dvcPrint = 0;
    
    bool msPrint = false;
    for(int dvc = dvr_Water; dvc < numDevices; dvc++)
    {
//      pollStart = millis();  // start polling timer for this device
    
      // assemble full IP addr
//      lanIPx = Ethernet.localIP();
      // create current device's full IP address
      lanIPx[3] = lanIPreg[dvc]; 

      //
      if(bitRead(guiReg, gbr_verbose))
        {
          Serial.print(F("Polling "));
          Serial.print(deviceNames[dvc]);
        }
      // check if device is NO poll, derigistered, native or lost
      if(!bitRead(guiReg, dvc + pollOffset) || !bitRead(deviceReg, dvc) || lanIPreg[dvc] == -2 || bitRead(guiReg, dvc + lostOffset))
      {
        
        if(bitRead(guiReg, gbr_verbose))
        {
          if(lanIPreg[dvc] == -2) // native sensor
          {
            Serial.print(F(" (native sensor)... "));
            Serial.println(mssgDone);  
          }
          else if(!bitRead(deviceReg, dvc)) // not registered
          {
            Serial.println(F(": no Device registered."));
          }
          else if(!bitRead(guiReg, dvc + pollOffset)) 
          {
            Serial.println(F(": not included in Poll."));
          }
          else if(bitRead(guiReg, dvc + lostOffset)) // device lost
          {
            Serial.println(F(": Device has been lost!"));
          }
          
//          int ms = (int)(millis() - pollSpan);
//          Serial.print(ms);
//          Serial.println(F(" ms"));
//          if(ms > peak_ms)
//          {
//            peak_ms = ms;
//            //
//            Serial.print(F("peak_ms = "));
//            Serial.print(peak_ms);
//            Serial.print(F(" ms: "));
//            Serial.println(deviceNames[dvc]);
//            //
//            DailyLogHeader(__LINE__);
//            DailyLog.print(F("peak_ms = "));
//            DailyLog.print(peak_ms);
//            DailyLog.println(F(" ms."));
//            DailyLog.flush();
//          }
        }
        
        //
        continue; // skip polling the affected device, move on to next device
      }
      if(bitRead(guiReg, gbr_verbose))
      {
        Serial.print(F(" at "));
        Serial.print(lanIPx);
        Serial.print(F("... "));
      }

      // poll the device
      int result = pollWiFi(dvc);
      
      // measure the time it took to poll the device
      ms = (int)(millis() - pollStart);
      if(ms > peak_ms) 
      {
        peak_ms = ms;
        msPrint = true;
        dvcPrint = dvc;
      }
      
      //
      if(result > 0) // poll failed, update gui and log fail
      {
        numFails[dvc]++;
        //
        if(!bitRead(guiReg, gbr_verbose)) // print fail anyway
        {
          Serial.print(F("Polling "));
          Serial.print(deviceNames[dvc]);
//          Serial.print(F("... "));
          Serial.print(F(" at "));
          Serial.print(lanIPx);
          Serial.print(F("... "));
        }
        Serial.print(mssgFailed);
        Serial.print(F(" ("));
        Serial.print(numFails[dvc]);
        Serial.print(F(" at "));
        Serial.print(result);
        Serial.print(F(") / "));
        Serial.print(ms);
        Serial.println(F(" ms"));
        //
        DailyLogHeader(__LINE__);
        DailyLog.print(F("Polling "));
        DailyLog.print(deviceNames[dvc]);
        DailyLog.print(F(" "));
        DailyLog.print(mssgFailed);
        DailyLog.print(F(" ("));
        DailyLog.print(numFails[dvc]);
        DailyLog.print(F(" at "));
        DailyLog.print(result);
        DailyLog.print(F(") / "));
        DailyLog.print(ms);
        DailyLog.println(F(" ms"));
        DailyLog.flush();

        if(numFails[dvc] > 15)
        {
//          gridAlarm = false;
          if(!gridAlarm)  // this means power is still ON, so we've LOST the device
          {
            // device lost
            lostDevice = dvc;
            //
            bitClear(deviceReg, dvc);    // not registered
            bitClear(guiReg, dvc + pollOffset);   // skip
            bitSet(guiReg, dvc + lostOffset);    // lost
            //
            bitSet(guiReg, gbr_verbose);
            //
            deviceCount = countDevices();
    
            //
            lanIPreg[dvc] = -3;
            saveLanIPreg();
            // trigger email send
            adminEmailSent = false;
    
            //
            Serial.print(F("Device Lost! -> "));
            Serial.println(deviceNames[dvc]);
            Serial.println(F("Deregistering this device."));
            //
            DailyLogHeader(__LINE__);
            DailyLog.print(F("Device LOST! -> "));
            DailyLog.println(deviceNames[dvc]);
            DailyLog.println(F("Deregistering this device."));

            // break out of for() loop to immediately send email
            break;
          }
          else            // power if OFF to the sensor, just reset numFails until power comes back on
          {
            Serial.println(F("Power grid offline. numFails reset to zero."));
            DailyLogHeader(__LINE__);
            DailyLog.println(F("Power grid offline. numFails reset to zero."));
          }
          //
          numFails[dvc] = 0;
        }
      }
      else  // successful poll
      {
//        Serial.println(mssgDone);
        numFails[dvc] = 0;
        if(bitRead(guiReg, gbr_verbose)) 
        {
          Serial.print(mssgDone);
          Serial.print(F(" "));
          Serial.print(ms);
          Serial.println(F(" ms"));
        }
      }
    } // end for() loop

    // print the new peak ms
    if(msPrint)
    {
      Serial.print(F("peak_ms = "));
      Serial.print(peak_ms);
      Serial.print(F(" ms: "));
      Serial.println(deviceNames[dvcPrint]);
      //
      DailyLogHeader(__LINE__);
      DailyLog.print(F("peak_ms = "));
      DailyLog.print(peak_ms);
      DailyLog.println(F(" ms."));
      DailyLog.println(deviceNames[dvcPrint]);
      DailyLog.flush();
    }
    if(bitRead(guiReg, gbr_verbose)) Serial.println(F("<-->"));
      
    //
    pollingIntervalStart = millis();
    
    // turn both leds off
    digitalWrite(redLed, LOW);
    digitalWrite(greenLed, LOW);
    Serial.println(F("heartbeat, 0"));
  }
}

int pollWiFi(int device)
{
  
  int result = 0;
  // poll device
  hungComs = true;  //watchdog boolean
  
  pollStart = millis();
  // Connect to device
  if(!Sentinel.connect(lanIPx, 502))// lanIPx preset before call to here
  {
    Sentinel.stop();  // necessary to allow polling of other devices!
    //
    return __LINE__;
  }
 
  // get data from device
  result = getRemoteData(device); 
  // close the connection
  Sentinel.stop();
  hungComs = false;
  // 
  return result;
}

int getRemoteData(int device) // returns fail line number on fail or 0 (zero) on success
{
  byte connectedDevice= 0;
//  static byte lastState = 0;  // bit Register for remembering previous alarm states
  int serNum = 0;    // 

  // connection still good, ask remote device for update
  Sentinel.write(funcIDs[FUNC_INDEX]);  // send Sentinel's serial num 0x2A
  //
  int idx = 0;
  // listen for reply
  while(idx < 4)
  {
    if(millis() - pollStart > 3999) break;  // timeout safety
    //
    if (Sentinel.available()) 
    {
      while(Sentinel.available()) // this loop reads all the data in inBuffer... 
      {
        if(millis() - pollStart > 3999) break;
        //
        byte b;
        //
        b = Sentinel.read();
        if(idx > 3)continue;  // but only saves the first four bytes.
        inBuffer[idx] = b;
        idx++;
      }
    }
  }
  
  //
  Sentinel.stop();

  // clear previous data from buffers
  connectedDevice = 0;  
  serNum = 0;      
  stateData = 0;      

  // load new data into buffers
  connectedDevice = inBuffer[0];  // device IP addrerss (Name)
  serNum = inBuffer[1] << 8;      // highbyte
  serNum += inBuffer[2];          // lowbyte
  stateData = inBuffer[3];        // state
    
  // verify the data
  bool fail = false;
  if(connectedDevice < 2 && connectedDevice > 7) 
  {
    Serial.println(F("Invalid Func_Index!")); 
    DailyLog.println(F("Invalid Func_Index!")); 
    DailyLog.flush();
    //
    fail = true;
  }
  else  // connected device is valid
  {
    if(device != NULL)
    {
      if(device != connectedDevice) 
      {
        Serial.println(F("Mismatched devices!")); 
        Serial.print(deviceNames[device]);
        Serial.print(F(" / ")); 
        Serial.println(deviceNames[connectedDevice]);
        //
        DailyLog.println(F("Mismatched devices!")); 
        DailyLog.print(deviceNames[device]);
        DailyLog.print(F(" / ")); 
        DailyLog.println(deviceNames[connectedDevice]);
        DailyLog.flush();
        fail = true;
      }
    }
    else 
    {
      device = connectedDevice;  // device == NULL so copy valid connected device to it
      // register the device
      bitSet(deviceReg, device);
      // loop() will detect change in devReg and save the new value
    }
  }
  //
  if(serNum != funcIDs[connectedDevice]) 
  {
    Serial.println(F("Invalid serial number for given device!")); 
    DailyLog.println(F("Invalid serial number for given device!")); 
    DailyLog.flush();
    //
    fail = true;
  }
  // valid states?
  if(stateData == 0xFF)
  {
    Serial.println(F("Communication error!"));
    fail = true;
  }
  else  // verify state data
  {
    switch(connectedDevice)
    {
      case dvr_Water:
      {
        if(stateData > 1) fail = true;  // only 1 valid bit
        else waterAlarm = (bool)stateData;
        //
        if(bitRead(waterAlarm, 0) != bitRead(mainAlarmReg, dvr_Water))
        {
//          printBinary(stateData);
          if(bitRead(waterAlarm, 0)) 
          {
            bitSet(mainAlarmReg, dvr_Water);
            acknowledged = false;
            bitClear(guiReg, gbr_Ack);
          }
          else bitClear(mainAlarmReg, dvr_Water);
          //
          emailSent = false;
        }
      }
      break;
      //
      case dvr_Security:
      {
        if(stateData > 7) fail = true;  // only three valid bits
        else
        {
          securityAlarm = stateData; // 8 bit register, 3 are valid
          //
          if(bitRead(securityAlarm, 0) != bitRead(mainAlarmReg, dvr_Security))
          {
//            printBinary(stateData);
            if(bitRead(securityAlarm, 0)) 
            {
              bitSet(mainAlarmReg, dvr_Security);
              //
              acknowledged = false;
              bitClear(guiReg, gbr_Ack);
            }
            else bitClear(mainAlarmReg, dvr_Security);
            //
            emailSent = false;
          }
        }
      }
      break;
      //
      case dvr_Grid:
      case dvr_Temp:
      case dvr_Smoke:
      {
        // no code here
      }
      break;
      //
      case dvr_Boiler:
      {
        if(stateData > 31) fail = true; // 5 valid bits (4 boiler reads + alarm state), so 2^5 - 1 = 31
        else boilerAlarm = stateData;
        //
        if(bitRead(boilerAlarm, 0) != bitRead(mainAlarmReg, dvr_Boiler))
        {
          // alarm state has changed, send alert
          if(bitRead(boilerAlarm, 0))               // alarm is ON
          {
            bitSet(mainAlarmReg, dvr_Boiler);
            acknowledged = false;
            bitClear(guiReg, gbr_Ack);
          }
          else bitClear(mainAlarmReg, dvr_Boiler);  // alarm is OFF
          //
          emailSent = false;
        }
      }
      break;
    }
  }

  // clear out inBuffer
  for(byte i = 0; i < 4;i++) inBuffer[i] = 0;
  
  //
  if(fail)
  {
    //
    int ms = millis() - pollStart;
    if(ms > peak_ms) 
    {
      peak_ms = ms;
      Serial.print(F("peak_ms: "));
      Serial.print(peak_ms);
      Serial.println(F(" ms"));
      //
      DailyLog.print(F("peak_ms: "));
      DailyLog.print(peak_ms);
      DailyLog.println(F(" ms"));
      DailyLog.flush();
    }
    if(connectedDevice >= 2 && connectedDevice <= 7) Serial.println(deviceNames[connectedDevice]); // name
    else Serial.println(connectedDevice);
    Serial.print(F("0x"));
    Serial.println(serNum, HEX);
    Serial.println(stateData);
    Serial.print(F("ms: "));
    Serial.println(ms);     // elapsed time
    //
    if(connectedDevice >= 2 && connectedDevice <= 7) DailyLog.println(deviceNames[connectedDevice]); // name
    else DailyLog.println(connectedDevice);
    DailyLog.print(F("0x"));
    DailyLog.println(serNum, HEX);
    DailyLog.println(stateData);
    DailyLog.print(F("ms: "));
    DailyLog.println(ms);
    DailyLog.flush();
  }

  // clear data from buffers
  connectedDevice = 0;  
  serNum = 0;      
  stateData = 0; 
  
  //
  if(fail) return __LINE__;
  return 0;
}

and the relevant remote device code:

WiFiServer thisDevice(502);
WiFiClient ClientList[8];
  // listen for incoming call from Sentinel controller
  WiFiClient Incoming = thisDevice.accept();
  unsigned long whileTimeout = 0UL;
  if(Incoming)
  {
    // indicated connection to sentinel is made
    WiFiDrv::analogWrite(blueLED, 30);
    digitalWrite(commLED, HIGH);
    whileTimeout = millis();
    //
    for(byte i = 0; i < 8; i++)
    {
      if(!ClientList[i])
      {
//        Incoming.print("Hello, client number: ");
//        Incoming.println(i);
        // send state data (4 bytes)
        Incoming.write(FUNC_INDEX);                     // byte 0
        Incoming.write(highByte(funcIDs[FUNC_INDEX]));  // byte 1
        Incoming.write(lowByte(funcIDs[FUNC_INDEX]));   // byte 2
        Incoming.write(boilerAlarmReg);                 // byte 3
        // Once we "accept", the client is no longer tracked by EthernetServer
        // so we must store it into our list of clients
        ClientList[i] = Incoming;
        //
        break;
      }
    }
  }
  else updateState();
  // check for incoming data from all clients
  for (byte i = 0; i < 8; i++) 
  {
    while (ClientList[i] && ClientList[i].available() > 0) 
    {
      // read incoming data from the client
//      
      byte c = 0;
      byte b = Incoming.read();
      //
      if(b != funcIDs[1] && b != funcIDs[FUNC_INDEX])
      {
        // send state data (4 bytes)
        thisDevice.write(FUNC_INDEX);                     // byte 0
        thisDevice.write(highByte(funcIDs[FUNC_INDEX]));  // byte 1
        thisDevice.write(lowByte(funcIDs[FUNC_INDEX]));   // byte 2
        thisDevice.write(0xFF);                           // byte 3
  
        Serial.print(F("error: byte = "));
        Serial.println(b);
        //
        break;
      }
           
      // parse byte
      if(b == funcIDs[1]) // 0x2A
      {
        // send state data (4 bytes)
        thisDevice.write(FUNC_INDEX);                     // byte 0
        thisDevice.write(highByte(funcIDs[FUNC_INDEX]));  // byte 1
        thisDevice.write(lowByte(funcIDs[FUNC_INDEX]));   // byte 2
        thisDevice.write(boilerAlarmReg);                 // byte 3
        //
        hungComms = false;
      }
      else if(b == funcIDs[FUNC_INDEX])
      {
        // secondary action (eg. pause monitoring poll call timeout while Sentinel is busy with DTMF phone calling)
        //
        hungComms = false;
      }
      else if(b == 0)
      {
        c++;
        if(c > 100)
        {
          // send state data (4 bytes)
          thisDevice.write(FUNC_INDEX);                     // byte 0
          thisDevice.write(highByte(funcIDs[FUNC_INDEX]));  // byte 1
          thisDevice.write(lowByte(funcIDs[FUNC_INDEX]));   // byte 2
          thisDevice.write(0xFF);                           // byte 3
  
          Serial.print(F("error: counter = "));
          Serial.println(c);
          
          break;
        }
      }
      else
      {
        Serial.print(F("error: b = 0x"));
        if(b < 16) Serial.print(F("0"));
        Serial.println(b, HEX);
        break;
      }
      
      //
      if(whileTimeout != 0UL && millis() - whileTimeout > 2999)
      {
        Serial.print(F("Data read timeout on line "));
        Serial.println(__LINE__);
        //
        break;
      }
    }
  }

  // stop any clients which disconnect
  for (byte i = 0; i < 8; i++) 
  {
    if (ClientList[i] && !ClientList[i].connected()) 
    {
      ClientList[i].stop();
      
      // connection closed
      WiFiDrv::analogWrite(blueLED, LOW);
      digitalWrite(commLED, LOW);
  
      // reset loop() timer
      pollCallStart = millis();
    }
  }

  //
  if(bitRead(boilerAlarmReg, 0)) non_blockingBlinkLED(alarmLED, 500, 500);
} // end of loop()

I hope someone can spot the bugs that destabilize the project.

Thanks.

Stephen

I have not read everything in detail but glancing over the code, a couple of things feel suspicious

shouldn't you be reading from ClientList[I] instead of Incoming.read??


similarité you have

This seems to indicate you collect a list of WiFiClient but when you do respond, it seems to be with thisDevice.write(...) instead of ClientList[i].write(...). I would assume it makes mores sense to use the WiFiClient` instance to handle transmission to a specific connection ?

thought?

Show ALL the code including cpp and h files.
Have you considered a simple version that you slowly build on, maybe starting with a sample from either the hardware samples or the library samples. I have been a programmer over 50 years and I almost always start with an existing sketch.

Hey JML

Thanks…

      byte b = Incoming.read();

was left from when I was using the Server.available() method. I switched to .accept() hoping for an improvement. I’ll try your suggestion and see how it works.

same story with the

WiFiClient ClientList[8];

I needed that for the .accept() method.

I’ll try your suggestions…

Thanks sonofcy,

I did indeed start with existing sketches.

Originally I was using the ArduinoModbus examples, which is why the port is still 502. I initially thought my problems were due to declared library incompatibility with Mega 2560. So I dumped it and used my own protocol which is the four byte data packet format.

I was also at one time using watchdog timers because I was afraid of getting stuck in some while loop somewhere, which has happened a lot in other projects. Anyway, I dumped it too. I still use a “watchdog” boolean but am considering dumping that too, see what happens.

Re posting all the code, I’ll do it here, it’s a lot of code though… is there a way to upload the whole .ino file, or just copy paste the whole works here?

Modbus? I would have started with Client/Server and used a well established library.
No idea why you think you need 'your own protocol'.
Between looking at your 'code' and @J-M-L 's observations I am bowing out. Good luck.

Thanks for your help.

Stephen

Hey JML

tried your change but it made no difference…

so I switched back to .available() and got rid of hungComms bool…

this code is for a different device than the earlier code, but identical

  // listen for incoming call from Sentinel controller
 WiFiClient Incoming = thisDevice.available();
  if(Incoming)
  {
    // indicated connection to sentinel is made
    WiFiDrv::analogWrite(blueLED, 30);
    digitalWrite(commLED, HIGH);
    //
    byte c = 0;
    while(Incoming.connected())
    {
      byte b = 0;
      // read the incoming byte
      if(Incoming.available())
      {
        b = Incoming.read();
        if(b != funcIDs[1] && b != funcIDs[FUNC_INDEX])
        {
          // send state data (4 bytes)
          thisDevice.write(FUNC_INDEX);                     // byte 0
          thisDevice.write(highByte(funcIDs[FUNC_INDEX]));  // byte 1
          thisDevice.write(lowByte(funcIDs[FUNC_INDEX]));   // byte 2
          thisDevice.write(0xFF);                           // byte 3

          Serial.print(F("error: byte = "));
          Serial.println(b);
          //
          break;
        }
      }
      
      // parse byte
      if(b == funcIDs[1]) // 0x2A
      {
        // send state data (4 bytes)
        thisDevice.write(FUNC_INDEX);                     // byte 0
        thisDevice.write(highByte(funcIDs[FUNC_INDEX]));  // byte 1
        thisDevice.write(lowByte(funcIDs[FUNC_INDEX]));   // byte 2
        thisDevice.write(waterAlarm);                     // byte 3
      }
      else if(b == funcIDs[FUNC_INDEX])
      {
        // secondary action (eg. pause monitoring poll call timeout while Sentinel is busy with DTMF phone calling)
      }
      else if(b == 0)
      {
        c++;
        if(c > 100)
        {
          // send state data (4 bytes)
          thisDevice.write(FUNC_INDEX);                     // byte 0
          thisDevice.write(highByte(funcIDs[FUNC_INDEX]));  // byte 1
          thisDevice.write(lowByte(funcIDs[FUNC_INDEX]));   // byte 2
          thisDevice.write(0xFF);                           // byte 3
  
          Serial.print(F("error: counter = "));
          Serial.println(c);
          
          break;
        }
      }
    }
    // connection closed
    WiFiDrv::analogWrite(blueLED, LOW);
    digitalWrite(commLED, LOW);

    // reset loop() timer
    pollCallStart = millis();
  }
  else updateState();

Also, if it helps:

#include <SPI.h>
#include <WiFiNINA.h> // for MKR WiFi 1010
#include <utility/wifi_drv.h> // for the native RGB led
//#include <Adafruit_SleepyDog.h> // watchdog timer library
#include "arduino_secrets.h"
#include "lanConfig.h"

#define FUNC_INDEX 2
#define FUNC_ID funcIDs[FUNC_INDEX] // 0x3039
#define FUNC_NAME deviceNames[FUNC_INDEX] // water sensor 

and lanConfig.h:


/*
  These constants must be included in all device source code in order for the whole system to functon.
*/
const int numDevices = 8; 
//
const char *deviceNames[] = {"Gateway", "Sentinel", "Water Sensor", "Security Sensor", "Grid Sensor", "Temp Sensor", "Smoke Sensor", "Boiler Sensor"};
const int funcIDs[] = {0x2775, 0x002A, 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539};

re:

I found this example forWiFi Server class at https://docs.arduino.cc/libraries/wifinina/#Server%20Class under .write() method

void loop() {

// listen for incoming clients

WiFiClient client = server.available();

if (client == true) {

// read bytes from the incoming client and write them back

// to any clients connected to the server:

server.write(client.read());

}

server.write(client.read());

from that I thought my equivalent would be

thisDevice.write(Incoming.read())…

that is why I used the WiFiServer instance ‘thisDevice.write()’ instead of using the WiFiClient ‘Incoming’ instance…

Is this an error?

In a previous form of my code I did use the WiFiClient instance ‘Incoming.write()’ but there was still the same problems. Then I found the above reference and changed it to what you see…

Part of the reason I find the whole Client/Server thing so confusing…

@Stephen_Kurth ,

I'm sorry can't help you directly but your question reminded me of this:

I wrote my contribution to that discussion as I was learning how to use TCP, so the code is as I learnt it. I used the client/ server to collect weather data to a central point. I kept the connections open all the time.

Your description of your problems sounds similar to my own experience of lost connections and general annoying problems. Before I used TCP I used UDP, no connections to worry about, but instead there were errors and lost packets to deal with. I gave up on TCP and went back to UDP. I've now given up on UDP and decided to use wires. No more lost connections, lost packets and errors.

I hope you find the topic I linked to useful, I shall follow this one to see how you progress.

Thanks @PerryBebbington !!

I’ll dive into that link right away.

I’m soooo ready to just get back to using wired connections. So much easier to run some cable thru the house compared to the frustrations I’m creating for myself trying to be elegant with WiFi… I '‘d just hate to cede defeat to this problem…

Here’s a copy of the most recent export file of the GUI display textBox with some failures.

2026-03-11-1.txt (12.9 KB)

Hmm… seems like all the comments I put into it are not there.

Let’s remember that WiFi and TCP work reliably for most everyday uses at home or in enterprise environments. When was the last time you had to reboot your router or your computer because the wireless connection was lost or a web page simply refused to load?

In many Arduino projects I have seen, the real problem is often elsewhere. It is frequently the result of poorly chosen and outdated hardware, limited RAM, crippled firmware or libraries, and sometimes insufficient programming practices when dealing with sockets and TCP ports. The situation is often made worse by abstraction libraries that hide the underlying mechanisms and that have not been thoroughly tested.

These libraries also tend to implement very little in terms of recovery mechanisms after failures, reconnection handling, or boundary and stress testing. As a result, error conditions, connection drops, and edge cases are not handled robustly, which gives the impression that WiFi or TCP themselves are unreliable, when in reality the issue usually lies in the implementation.

Another frequent issue is that developers often do not implement explicit reconnection mechanisms and do not check the return values of network calls to detect and handle errors. This is sometimes compounded by the heavy use of the String class on constrained microcontrollers, which can lead to memory fragmentation over time and further reduce stability.

In the present case, using an Arduino Mega 2560 with an Ethernet Shield 2 introduces several structural constraints. The Mega 2560 provides only 8 KB of SRAM for the entire application, including network buffers, user data and stack, which leaves very little margin when several TCP sockets are active. The Ethernet Shield 2 uses the W5500 Ethernet controller, which provides 32 KB of internal buffer memory shared across up to eight sockets. In practice this means each socket receives only a small portion of that memory, and the microcontroller itself has almost no RAM available for application buffers. The Arduino Ethernet library is essentially a thin wrapper around the chip registers and does not implement higher level connection supervision, automatic recovery after failures, or systematic timeout management. If the program does not (or just can’t) explicitly monitor socket states, verify return codes, and actively close or reset sockets when needed, connections can easily stall or remain in inconsistent states.

A similar observation often applies when using boards such as the Arduino MKR WiFi 1010 as wireless peripheral devices. These boards rely on a separate radio module running its own firmware, accessed through a high level WiFi library. The abstraction hides most of the networking behavior from the application code, but the libraries provide limited diagnostics and only basic recovery mechanisms. When used as small TCP endpoints or distributed wireless nodes, failures such as lost associations, stalled sockets or firmware edge cases are not always handled automatically, and the application layer frequently needs to implement its own reconnection logic and error handling to maintain long term stability.

Developing reliable networking applications on small microcontrollers is not straightforward. The constraints in RAM, the simplicity of the networking stacks and the level of abstraction provided by many libraries mean that the developer must understand what is happening underneath the API. You need careful socket management, systematic verification of return values, explicit recovery logic, and often some detailed inspection of the underlying libraries and firmware to understand how they actually handle connections and failure conditions.

As far as I’m concerned I would not use any mega with Ethernet shield - it’s too old and isnot a MCU designed for networking. I moved toward more uniform architectures based on ESP32 devices. Each node runs the same platform with a full TCP/IP stack integrated in the SoC, more RAM available for buffers and the application, and a WiFi subsystem designed to operate directly with the stack. This allows much more consistent behavior, easier error handling, and the possibility to implement more reliable reconnection and recovery mechanisms without being constrained by tiny memory but it still requires a solid understanding of how sockets and TCP operate under the hood.

Thanks @J-M-L

Great post! Thanks for taking the time to write it.

Exactly! Which is why I get so frustrated. I know it can work, but my project just doesn’t , at least not reliably…

Not sure if this matters, the result from compiling says 3087 bytes (37%) of 8192 bytes (max) of dynamic memory used for Globals

Thanks, I think that nicely summarizes the problem I’m having. If I can remain undaunted I’ll try to do those things…

I’ve got significant time and effort invested in my current hardware and software so, for now I’ll keep on flogging it to see if I can make it work better. I’ve already found a few bits of stinky looking code I wrote…

Eventually, maybe I ‘ll switch over to an ESP32 device; but that’s a whole, brand new learning curve to jump onto…

I’ll post any updates I may have just in case anybody is following…

Are you familiar with the sunk cost fallacy ?

I don't know which country you are in, does your local dialect have anything to say about "flogging a dead horse"?

Seriously, given that advice you already have I think you should consider moving to a platform better suited to WiFi / TCP / UDP than then Mega with an add on board.

My experience is based on the ESP8266. From what I have read the ESP32 is better all round, but I have not used it so I have no direct experience. If @J-M-L says it's good then that is advice I'd take as I know J-M-L knows what he's talking about.

If you do stick with the Mega arrangement then my experience was that UDP was more manageable because it does not establish a connection, so there cannot be a loss of connection, and the errors and packet losses are certain to happen, so you have to be aware of them and find ways to deal with them.

Hey @PerryBebbington

Not sure, but I am familiar with the concept of throwing good money after bad…

Yes… that’s why I chose the term :face_with_bags_under_eyes: “Blood from a stone” analogy works too….

Thanks for your advice!

I don't use my ESP8266 anymore so may be they did improve those also.

I do have a few MKR boards but I must say they collect dust :scream::anxious_face_with_sweat: as I preferred the ESP32 architecture and underlying RTOS rather than the bare-metal architecture when in needs to do more complex stuff (essentially, ESP32 has the TCP/IP stack integrated in the same MCU with multitasking, whereas MKR Wi-Fi relies on a coprocessor and a SPI based library interface. That’s why ESP32 is more flexible for complex networking).