ssilverman / QNEthernet

An lwIP-based Ethernet library for Teensy 4.1 and possibly some other platforms
GNU Affero General Public License v3.0
85 stars 23 forks source link

Client.Connected() Question #65

Closed cjmccoy2 closed 8 months ago

cjmccoy2 commented 9 months ago

Hello,

I am having troubles understanding an issue I am facing with my code using QNEthernet. I am using the Teensy 4.1 with QNEthernet and ArduinoModbus to send some data over Modbus TCP to a Modbus master.

The program works, but I am having issues resolving the connection after the connection drops (Cable unplugged or maybe the Master drops off the network). I assumed that client.conected() would have caught the disconnection, but it does not. I have tried use the operator bool method with similiar. I have also attempted to manually close the connection, but the client.connection() status does not change. One thought I have come up with may be due to the fact the status of the connection returns 4 (SYN_RCVD). Is the fact that the connection is not 5 (ESTABLISHED) causing this issue? I have not confirmed anything with WireShark, but I will say that the Modbus master side says “TCP Connection Established”.

I appreciate any help with this issue.

// Ethernet
#include <QNEthernet.h>
// Modbus TCP (ArduinoRS485 is a req of ArduinoModbus)
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
// OLED Display
#include <Adafruit_SSD1306.h>
#include <SPI.h>
// Multithreads
#include <TeensyThreads.h>

using namespace qindesign::network;

constexpr uint32_t kDHCPTimeout = 15'000;  // 15 second timeout
constexpr uint16_t kPort = 502;  // Modbus TCP Port
EthernetServer ethServer(kPort);
ModbusTCPServer modbusTCPServer;
IPAddress staticIP{192, 168, 5, 130};
IPAddress subnetMask{255, 255, 252, 0};
IPAddress gateway{192, 168, 4, 1};
IPAddress dnsServer{192, 168, 1, 1};

// Volatile data for use in threads
volatile int ethernetLinkStatus = 0;

// Thread for non-stop ModbusTCP comms
void modbusThread(){

  // Loop forever
  while (1){
    // Setup Incoming connection
    EthernetClient client = ethServer.available();
    client.setConnectionTimeout(500);

    if (client && client.available()){
      printf("New Client!\r\n");
      modbusTCPServer.accept(client);
      while (client.connected()) { 
        // poll for Modbus TCP requests, while client connected
        modbusTCPServer.poll();

      }

    printf("CLient Lost!\r\n");
    }
    //Turn on LE
    if (ethernetLinkStatus){
      digitalWrite(LED_BUILTIN,0);
    }else{
      digitalWrite(LED_BUILTIN,1);
    }
  }

}

void setup() {

  pinMode(LED_BUILTIN, OUTPUT);

  Serial.begin(115200);
  while (!Serial && millis() < 4000) {
    // Wait for Serial
  }
  printf("Starting...\r\n");

  uint8_t mac[6];
  Ethernet.macAddress(mac);  // This is informative; it retrieves, not sets
  printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x\r\n",
         mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

  Ethernet.onLinkState([](bool state) {
    printf("[Ethernet] Link %s\r\n", state ? "Active" : "Inactive");
    Serial.print(ethernetLinkStatus);
    if (state){
      ethernetLinkStatus = 1;
    }else{
      ethernetLinkStatus = 0;
    }
  });

  printf("Starting Ethernet with Static IP ...\r\n");
  Ethernet.setDNSServerIP(dnsServer);
  Ethernet.begin(staticIP, subnetMask, gateway);

  IPAddress ip = Ethernet.localIP();
  printf("    Local IP     = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.subnetMask();
  printf("    Subnet mask  = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.broadcastIP();
  printf("    Broadcast IP = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.gatewayIP();
  printf("    Gateway      = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.dnsServerIP();
  printf("    DNS          = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);

  // start the server
  printf("Starting ethserver\r\n");
  ethServer.begin();
  printf("ethserver started?\r\n");

  if (!modbusTCPServer.begin()) {
    Serial.println("Failed to start Modbus TCP Server!");
    while (1);
  }
  printf("modbus started?\r\n");

  modbusTCPServer.configureHoldingRegisters(0x0000,1);

  // Add Threads
  threads.addThread(modbusThread);

}

// Main program loop.
void loop() {

}
ssilverman commented 9 months ago

Here’s some notes to get you started, but first, a question: is the Teensy connecting to a Windows machine?

A possibly useful link: https://github.com/ssilverman/QNEthernet/blob/ca1679f801999f1da53516beadc8c7bae4dd6ccb/README.md#on-connections-that-hang-around-after-cable-disconnect

For this first note, try aborting all connections (via abort()) when you detect link down, say in the listener, or by checking state set by the listener or by polling the link state, in your main loop.

A second note is that the library doesn’t support being accessed from multiple threads, or any concurrent context, for that matter. (That is, unless you access the library from the same thread, and because of how yield() is set up to call Ethernet.loop(), that’s not quite possible yet.) You’re accessing it from your Modbus thread and from the main loop() via the yield() call being implicitly called at the end of the main loop(). My suggestion is to not use threads at all because you’ll see a performance impact. (See also this comment and its context: https://github.com/ssilverman/QNEthernet/issues/39#issuecomment-1551014434.)

Hope these two notes help.

cjmccoy2 commented 9 months ago

Nope, the other client is a Raspbery Pi running Codesys.

I will say that when I made my initial post I had the PI on Wifi and the Teensy on ethernet plugged into my router. After reading the post you sent me I decided to plug both the PI and Teensy into a dumb switch. When the connection drops to the client, the Teensy still thinks it is connected, but now when restoring the connection to the client it drops and regains after a second or two. This is a new behavior because I never could get the client to connect back to the Teensy without rebooting.

I had thought that I had tried to use the abort() feature initially before my post to drop the connection when link was lost, but I believe there was an error in my code because that works perfectly - with the router and the dumb switch. This solves the issue of the Teensy's cable being disconnected, but not the issue of the client being disconnected.

For this issue, I plan on implementing a "watchdog" and I will just abort the connection if it drops out.

Thanks for your help!

ssilverman commented 9 months ago

I think this is related to TCP trying to very hard to keep a connection open, even when the other side loses state, so you end up with very long timeouts and it’s not possible to close “nicely”, even with close() (or even with Arduino’s blocking stop()), so you have to use abort().

Would you consider this issue closed?