New lwIP-based Ethernet library for Teensy 4.1

@jdredd: There’s code in the NativeEthernet library that blocks (a spinning `while` loop) unless a cable is plugged in. QNEthernet can tolerate having a cable unplugged, even when the cable is plugged into a different network or unplugged for a long time. See the ServerWithAddressListener example.

See also: https://github.com/vjmuzik/NativeEthernet/issues/12
 
It’s been on my mind the last few days. When I have some time, I’ll maybe look into what’s required to implement it from an API perspective. What would a good API look like to you? I know what 1588 is, and even how it’s implemented, I’ve just never used it, so an external perspective from someone who isn’t as close to the internal plumbing would be nice.

P.S. Thank you for the nice words. :)

I am happy with any interface, simple amd that has example code. I'm a brute force programmer, and was hoping others would chime in here.
 
I just released v0.10.0. The changes:

Code:
### Added
* Added a way to send raw Ethernet frames. The new function
  is `EthernetClass::sendRaw(frame, len)`.
* Added new sections to the README:
  1. "Sending raw Ethernet frames", and
  2. "How to implement VLAN tagging".
* Added calls to `loop()` in `EthernetClient::connected()`
  and `operator bool()`.
* Added `EthernetUDP::beginMulticast(ip, localPort, reuse)`, where `reuse`
  controls the SO_REUSEADDR socket option.
* Added `EthernetClass::joinGroup(ip)` and `leaveGroup(ip)` for joining and
  leaving a multicast group.
* Added a "How to use multicast" section to the README.

### Changed
* Changed `kMTU` type to be `size_t` everywhere.
* Added `stdPrint` as an `extern` variable to `QNEthernet.h` and moved it to the
  `qindesign::network` namespace.
* Changed transmit data buffers to be 64-byte aligned, for
  "optimal performance".\
  See: IMXRT1060RM_rev2.pdf, "Table 41-38. Enhanced transmit buffer descriptor
  field definitions", page 2186.
* Updated lwIP to v2.1.3.
* Changed `EthernetUDP::beginMulticast` to release resources if joining the
  group failed.
* Increased MEMP_NUM_IGMP_GROUP to 9 to allow 8 multicast groups.

### Removed
* Removed mention of the need to re-announce mDNS and adjusted the
  docs accordingly.

### Fixed
* Changed receive data buffers to be 64-byte aligned.\
  See: IMXRT1060RM_rev2.pdf, "Table 41-36. Receive buffer descriptor field
  definitions", page 2183.
* Changed TA value in ENET_MMFR register to 2, per the chip docs.
* Multicast reception now works. Had to set the ENET_GAUR and ENET_GALR
  registers appropriately.

Multicast receive now works!

Link: https://github.com/ssilverman/QNEthernet/releases/tag/v0.10.0
 
shawn, thank you for great library.

I tried to migrate my application where server talked with multiple clients by similar way as at AdvancedChatServer example.
When I replaced Ethernet library on QNEthernet the application runs fine for some time (~30seconds) when multiple clients attached. However, the clients give network error later and server could not accept clients anymore.

I found that your FixedWidthServer example uses C++ vector approach instead. I would like to ask some questions.

Is it possible to use multiple EthernetClient instances - sockets into your library?

Could you explain what does next code mean at your example?

// Keeps track of state for a single client.
struct ClientState {
ClientState(EthernetClient client)
: client(std::move(client)) {}

EthernetClient client;

...
}

Thank you.
 
Can you show the code you’re using? It’s easy to have resource leaks. Yes, you can use multiple clients.
 
shawn, please find adjusted AdvancedCharServer example at attachments.

Something happened at the example. I just attached 2 clients. The clients lost connection after approximately 1min even when ones stay passive. The putty provides fatal error.

Thank you.
 

Attachments

  • AdvancedChatServer_QNEthernet.ino
    2.5 KB · Views: 56
  • fatal error.JPG
    fatal error.JPG
    16.5 KB · Views: 40
I tried the program and it seems to work fine. Some notes and questions:

1. Do you have the latest QNEthernet installed in your Arduino libraries folder? (The latest is v0.10.0, and not available yet from the Library Manager.)
2. Are you using the latest Teensyduino? (1.55 as of this writing.)
3. For configuring a static IP without starting the DHCP client, use `Ethernet.begin(ip, subnet, gateway)` instead of `Ethernet.begin()`.
4. For the differences between the boolean operator and `connected()` for `EthernetClient`s, see https://github.com/ssilverman/QNEth...ey-of-how-connections-aka-ethernetclient-work.
5. For connection detection and data availability, use `if (clients.available() > 0)` and `if (!clients.connected())` instead of `if (clients && clients.available() > 0)` and `if (clients && !clients.connected())`. In other words, no need to use the boolean operator form in addition to the `connected()` call. They both check for connection, but `connected()` also checks for data available after a disconnect.
 
Last edited:
Thank you shawn for advices

I am using 0.9.0 version of library with 1.55 version of teensyduino.

The issue with fatal error appeared even with one client after some time.
I tried some changes:
replace USB to Ethernet converter for another model to exclude hardware issue. It does not help.
Use different declaration. The declaration Ethernet.begin(ip, subnet, gateway); without Ethernet.begin(); helps to eliminate the issue. It exactly what you suggested at #3.

My original application tried to receive IP from DHCP first and assign static IP if fail. The code:
Ethernet.begin();
if (!Ethernet.waitForLocalIP(10000)){
Ethernet.begin(ip, subnet, gateway);
// or
// Ethernet.setLocalIP(ip);
// Ethernet.setSubnetMask(subnet);
// Ethernet.setGatewayIP(gateway);
}
The fatal error appeared in such case again. Is it possible to resolve this issue?

shawn, thank you for boolean, connected() and available() explanation.
I would like to ask a question: The library description said the resources cleaned up by library automatically after disconnected clients. Is it right? Could I omit client.stop(); or client.close(); statements?

Thank you
 
Looks that "Ethernet" should be closed before opening again. The statement Ethernet.end(); in front of new opening Ethernet.begin(ip, subnet, gateway) solves the issue for DHCP case.
 
A few more comments :)
Also, you've asked great questions.

1. I was wrong about replacing `if (clients && !clients.connected())` with `if (!clients.connected())`. As your code is structured currently, `clients.connected()` will always return false if never-been or not connected (with no data available), and so you'll see a continuous stream of "disconnect client #" messages without that `clients` boolean check. However, internally, when the client has been connected and then gets disconnected, `clients` might return true if the stack hasn't yet updated, but then inside `operator bool()`, the stack gets updated and then `clients.connected()` will return false, causing that `if` block to execute. This gets confusing and is why I use a ClientState object in the example code. On the simplest level, it tracks whether a client has no more data available, and when that's true, the associated ClientState instance gets removed from the list (the vector). If you don't use a list removal scheme then you'll still need an associated `bool` variable in some ClientState object to know whether to stop() or close() the client socket. Otherwise, there's no obvious way to know, just by looking at an instance of EthernetClient whether to release it or not; it might need to be released or it might not have been connected in the first place (see also point #2 below). Does that all make sense?

1.5. To expand on that last thought in the previous point, it might appear that `(clients && !clients.connected())` can never be true because both indicate whether the socket is connected (and empty in the case of the second function). Walking through it, `clients.connected()`will be false when disconnected and empty. `clients` will be false when disconnected. Therefore, `!clients.connected()`, when true, must indicate that the socket is disconnected, meaning `clients` will be false. However, both `operator bool()` (i.e. what `clients` calls) and `connected()` move the stack along, and so internal state can be changed after each of those calls, invalidating the "connected" state.

2. It's always a good idea to clean up resources when you own and are done with them, and not calling release functions when not needed, as "good practice". Having said that, the resources will be cleaned up as needed inside: connected(), operator bool(), available(), read(), read(buf, size), and peek(). (Also in stop() and close(), of course.) This means that some call somewhere needs to be made to release the resources. For example, in the FixedWidthServer example, you'll note the ClientState's `closed` variable is set to true when a call to `connected()` returns false. In that case (if you look at the source code in QNEthernetClient.cpp:connected()), `conn_`, the shared pointer, gets set to nullptr when that function returns false (or it's already nullptr). You'll also note that the example never calls `close()` or `stop()` on the client. Also note that the calls to `clients.available() > 0` in your code will clean up if necessary, so you don't need to call `stop()` below.

3. I don't love how the `operator bool()` and `connected()` API is defined because it's confusing, but I'm trying to maintain at least rudimentary compatibility with the Arduino API.

4. You don't need to obtain your own MAC address (eg. your `teensyMAC(mac)` function). By default, both `Ethernet.begin` functions use the Teensy's built-in MAC address. To access this, you can call `Ethernet.macAddress(mac)` (or using the Arduino-style `Ethernet.MACAddress(mac)`). If you really want to use your own MAC address then you can use one of the other `begin` functions. (`setMACAddress(mac)` isn't yet implemented).

5. I'm working on letting the `begin` functions be called without having to call `end()` in between, for robustness. In your example, what's happening might be: 1. Set via DHCP, so the DHCP client is started. 2. Your specified timeout happens before DHCP gets a chance to vend an address, but the DHCP client is still running. 3. You set the static IP and then start the server. 4. The DHCP client gets an address and replaces the static address. ----- In my experiments, where I time out after 1 second (DHCP for me takes about 5-6 seconds), the server, even though the address has changed, continues to work and accepts connections on the newly-DHCP-assigned address. I don't see Teensy crashes. To avoid having to call `end()` (because it spins down the clock and doesn't need to), I'm calling `dhcp_release_and_stop(netif_);` at the top of `EthernetClass::begin(ip, mask, gateway)`, just after `netif_ = enet_netif();`.

6. Could you try adding that `dhcp_release_and_stop(netif_);` line and see if that works for you instead of calling `end()`? Also be sure to get the latest version from GitHub.
 
I just released v0.11.0. The changes:

Code:
### Added
* Implemented `EthernetClass::setMACAddress(mac)`.
* Added `EthernetServer::maxListeners()`, `EthernetClient::maxSockets()`, and
  `EthernetUDP::maxSockets()` so user code doesn't need to guess. These are
  `constexpr` functions that return the compile-time constants from the
  lwIP configuration.
* Added `EthernetServer::port()` for returning the server's port.
* Added `EthernetClass::setHostname(hostname)` and `hostname()` for setting and
  getting the DHCP client option 12 hostname.
* Added `EthernetClass::maxMulticastGroups()` `constexpr` function.
* Added a "Write immediacy" subsection to the README that addresses when data is
  sent over a connection. It's under the "How to write data to
  connections" section.

### Changed
* Changed the default DHCP client option 12 hostname to "teensy-lwip".

### Fixed
* Stop the DHCP client when restarting `Ethernet` (in `begin(ip, mask, gateway)`
  and `setMACAddress(mac)`) to ensure that a static IP won't get overwritten by
  any previously running DHCP client. This also obviates the need to call
  `Ethernet.end()` before re-calling `begin`.

This is the first release that I'll add to the Arduino Library Manager.

Link: https://github.com/ssilverman/QNEthernet/releases/tag/v0.11.0
 
I just released v0.12.0. The changes:

Code:
## [0.12.0]

### Added
* Added a way to disable and enable Nagle's algorithm. The new functions are
  `EthernetClient::setNoDelay(flag)` and `isNoDelay()`.
* Implemented `EthernetServer::availableForWrite()` as the minimum availability
  of all the connections, or zero if there's no connections.
* New `AppWithListenersTemplate` example.
* Added `EthernetClass::operator bool()` for testing whether Ethernet
  is initialized.
* Added a new way to send and receive raw Ethernet frames. There's a new
  `EthernetFrame` instance (of `EthernetFrameClass`) that is used similarly
  to `EthernetUDP`.
* New `RawFrameMonitor` example.
* New `EthernetUDP::send(data, len)` function for sending a packet without
  having to use `beginPacket()`/`write()`/`endPacket()`. It causes
  less overhead.

### Changed
* Changed `EthernetUDP::flush()` to be a no-op.
* Reduced lwIP's `MEM_SIZE` to 16KiB from 24000.
* Split `MDNSClass::addService()` into two overloaded functions: one with three
  arguments and one with four. No more defaulted TXT record function parameter;
  the three-argument version calls the four-argument version with NULL for
  that function.
* Updated `keywords.txt`.
* Updated `SNTPClient` example: Removed unneeded includes, made the packet
  buffer a global variable, and added setting the RTC and time.
* Changed `EthernetClass::mtu()` to `static size_t`. It was non-static
  and `int`.
* Updated `enet_output_frame(frame, len)` to check if the system is initialized.

### Removed
* Removed `EthernetClass::sendRaw(frame, len)` because there's a new
  `EthernetFrame` API with a `send(frame, len)` function.

### Fixed
* Fixed the length check when sending raw Ethernet frames to exclude the FCS
  field. It checks that the length is in the range 60-1518 instead of 64-1522.
* Fixed `check_link_status()` to check if Ethernet is initialized before trying
  to access the PHY.

Highlights:
* New Ethernet raw frame API, `EthernetFrame`,
* Nagle's algorithm access,
* Reduction of pre-allocated memory by 8k, and
* Two new examples.

Link: https://github.com/ssilverman/QNEthernet/releases/tag/v0.12.0
 
Last edited:
I just released v0.13.0. The changes:

Code:
### Added
* `EthernetFrame` convenience functions that also write the header:
  * `beginFrame(dstAddr, srcAddr, typeOrLen)`
  * `beginVLANFrame(dstAddr, srcAddr, vlanInfo, typeOrLen)`
* `qindesign::network::util` Print utility functions. The `breakf` function
  parameter is used as the stopping condition in `writeFully()`.
  * `writeFully(Print &, buf, size, breakf = nullptr)`
  * `writeMagic(Print &, mac, breakf = nullptr)`
* `enet_deinit()` now gracefully stops any transmission in progress before
  shutting down Ethernet.
* `EthernetClass` functions:
  * `linkIsFullDuplex()`: Returns whether the link is full duplex (`true`) or
    half duplex (`false`).
  * `broadcastIP()`: Returns the broadcast IP address associated with the
    current local IP and subnet mask.
* Functions that return a pointer to the received data:
  * `EthernetUDP::data()`
  * `EthernetFrame::data()`
* `DNSClient::getServer(index)` function for retrieving a specific DNS
  server address.
* `EthernetUDP::localPort()`: Returns the port to which the socket is bound.
* Three new examples:
  1. `IPerfServer`
  2. `OSCPrinter`
  3. `PixelPusherServer`

### Changed
* The `EthernetClient::writeFully()` functions were changed to return the number
  of bytes actually written. These can break early if the connection was closed
  while attempting to send the bytes.
* Changed `EthernetClient::writeFully()` functions to use the new `writeFully()`
  Print utility function.
* Changed the `Ethernet` object to be a reference to a singleton. This matches
  how the `EthernetFrame` object works.
* Changed the `read(buf, len)` functions to allow a NULL buffer so that the
  caller can skip data without having to read into a buffer.
* Moved internal classes and structs into an "internal" namespace to avoid any
  potential contflicts with user declarations.

### Removed
* Removed IEEE 1588 initialization and timer read.

### Fixed
* Fixed `EthernetClient::availableForWrite()` to re-check the state after the
  call to `EthernetClass::loop()`.

Highlights:
* More convenience functions, including `Ethernet.broadcastIP()`, `Ethernet.linkIsFullDuplex()`, and some `EthernetFrame` functions,
* A way to get the direct pointer to received UDP and Frame data,
* A way to skip received data by passing in a NULL buffer to the read() functions, and
* Three new examples: IPerfServer, OSCPrinter, and PixelPusherServer.

Link: https://github.com/ssilverman/QNEthernet/releases/tag/v0.13.0
 
Who's up for testing PTP support and IPv6 support (they're separate)? I'm gauging interest in actual testing before spending much more time on them. If at least two people per "thing" might actually utilize it and provide me some feedback, then I'll clean some stuff up and push the branches.

To whet your appetite, here's some code that watches address changes:

Code:
#include <QNEthernet.h>

using namespace qindesign::network;

constexpr uint32_t kDHCPTimeout = 10000;  // 10 seconds

// Main program setup.
void setup() {
  Serial.begin(115200);
  if (CrashReport) {
    Serial.println(CrashReport);
    CrashReport.clear();
  }
  stdPrint = &Serial;  // Make printf work (a QNEthernet feature)
  printf("Starting...\n");

  uint8_t mac[6];
  Ethernet.macAddress(mac);
  printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x\n",
         mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

  // Listen for link changes
  Ethernet.onLinkState([](bool state) {
    printf("[Ethernet] Link %s\n", state ? "ON" : "OFF");
    // Optionally tell something about the new state
  });

  // Listen for IPv4 address changes
  Ethernet.onAddressChanged([]() {
    IPAddress ip = Ethernet.localIP();
    bool hasIP = !(ip == INADDR_NONE);  // IPAddress has no operator!=()
    if (hasIP) {
      IPAddress ip = Ethernet.localIP();
      IPAddress subnet = Ethernet.subnetMask();
      IPAddress gw = Ethernet.gatewayIP();
      IPAddress dns = Ethernet.dnsServerIP();

      printf("[Ethernet] Address changed:\n"
             "    Local IP    = %u.%u.%u.%u\n"
             "    Subnet mask = %u.%u.%u.%u\n"
             "    Gateway     = %u.%u.%u.%u\n"
             "    DNS         = %u.%u.%u.%u\n",
             ip[0], ip[1], ip[2], ip[3],
             subnet[0], subnet[1], subnet[2], subnet[3],
             gw[0], gw[1], gw[2], gw[3],
             dns[0], dns[1], dns[2], dns[3]);
    } else {
      printf("[Ethernet] Address changed: No IP address\n");
    }

    // Tell something about the new state
    //addressChanged(hasIP);
  });

  // Listen for IPv6 address changes
  Ethernet.onAddress6Changed([](int which,
                                const IPv6Address &oldIP,
                                uint8_t oldState) {
    printf("[Ethernet] Address6 changed: \n"
           "    IP: ");
    IPv6Address ip6 = Ethernet.localIPv6(which);
    uint8_t state = Ethernet.localIPv6State(which);
    if (oldIP != ip6) {
      stdoutPrint.print(oldIP);
      printf(" -> ");
    }

    if (ip6.isLinkLocal())        { printf("(link-local)"); }
    else if (ip6.isUniqueLocal()) { printf("(unique local)"); }
    else if (ip6.isGlobal())      { printf("(global)"); }
    else if (ip6.isMulticast())   { printf("(multicast)"); }
    else if (ip6.isLoopback())    { printf("(loopback)"); }
    else if (ip6.isAny())         { printf("(any)"); }
    stdoutPrint.println(ip6);

    printf("    State: ");
    if (oldState != state) {
      printState(oldState);
      printf(" -> ");
    }
    printState(state);
    printf("\n");

    // Tell something about the new state
    //IPv6AddressStates s = IPv6Address::state(state);
    //if ((s == IPv6AddressStates::kPreferred) ||
    //    (s == IPv6AddressStates::kDeprecated)) {
    //  address6Changed(ip6, true);
    //} else if ((s == IPv6AddressStates::kDuplicated) ||
    //           (s == IPv6AddressStates::kInvalid)) {
    //  address6Changed(ip6, false);
    //}
  });

  printf("Starting Ethernet with DHCP...\n");
  if (!Ethernet.begin()) {
    printf("Failed to start Ethernet\n");
    return;
  }

  // Waiting is an alternative to a listener-only approach, where the
  // listeners start and stop things, depending on what happens
  if (!Ethernet.waitForLocalIP(kDHCPTimeout)) {
    printf("Failed to get IP address from DHCP\n");
  } else {
    printf("Got IP address from DHCP\n");
  }
  if (!Ethernet.waitForLocalIPv6(kDHCPTimeout)) {
    printf("Failed to get local IPv6 address\n");
  } else {
    printf("Got local IPv6 address\n");
  }
  if (!Ethernet.waitForGlobalIPv6(kDHCPTimeout)) {
    printf("Failed to get global IPv6 address\n");
  } else {
    printf("Got global IPv6 address\n");
  }

  printf("IPv6 addresses:\n");
  for (int i = 0; i < Ethernet.numIPv6Addresses(); i++) {
    printf("    %d: ", i);
    IPv6Address ip6 = Ethernet.localIPv6(i);
    stdoutPrint.println(ip6);
  }

  IPv6Address ip6 = Ethernet.linkLocalIPv6();
  printf("Link local:   ");
  stdoutPrint.println(ip6);
  ip6 = Ethernet.uniqueLocalIPv6();
  printf("Unique local: ");
  stdoutPrint.println(ip6);
  ip6 = Ethernet.globalIPv6();
  printf("Global:       ");
  stdoutPrint.println(ip6);
}

// Prints the given IPv6 address state.
void printState(uint8_t state) {
  IPv6AddressStates s = IPv6Address::state(state);
  switch (s) {
    case IPv6AddressStates::kInvalid:
      printf("Invalid");
      break;
    case IPv6AddressStates::kPreferred:
      printf("Preferred");
      break;
    case IPv6AddressStates::kDeprecated:
      printf("Deprecated");
      break;
    case IPv6AddressStates::kDuplicated:
      printf("Duplicated");
      break;
    case IPv6AddressStates::kTentative:
      printf("Tentative(%d)", IPv6Address::tentativeCount(state));
      break;
    default:
      printf("Unknown");
  }
}

// Main program loop.
void loop() {
}
 
Last edited:
Hi Shawn. I see you did not get any replies. I'm interested in PTP. Not sure how well I can support you with testing, but I'll try.
 
I just released v0.14.0 of the library. The changes:

Code:
## [0.14.0]

### Added
* Added a `util::StdioPrint` class, a `Print` decorator for stdio output files.
  It routes `Print` functions to a specific `FILE*`. This exists mainly to make
  it easy to use `Printable` objects without needing a prior call to `fflush()`.
* Added `MDNSClass::maxServices()`.
* Added `operator==()` and `operator!=()` operators for `const IPAddress`. They
  are in the usual namespace. These allow `==` to be used with `const IPAddress`
  values without having to use `const_cast`, and also introduce the completely
  missing `!=` operator.
* Added a way to declare the `_write()` function as weak via a new
  `QNETHERNET_WEAK_WRITE` macro. Defining this macro will cause the function to
  be declared as weak.
* Implemented `EthernetFrameClass::availableForWrite()`.
* Added size limiting to `EthernetFrameClass` write functions.
* New `EthernetClass::waitForLink(timeout)` function that waits for a link to
  be detected.
* Added a way to allow or disallow receiving frames addressed to specific MAC
  addresses: `EthernetClass::setMACAddressAllowed(mac, flag)`

### Changed
* Updated `SNTPClient` example to use `EthernetUDP::send()` instead of
  `beginPacket()`/`write()`/`endPacket()`.
* Updated `PixelPusherServer` example to use the frame average for the
  update period.
* Implemented `EthernetHardwareStatus` enum for the deprecated
  `Ethernet.hardwareStatus()` function. This replaces the zero return value with
  the new non-zero `EthernetOtherHardware`.
* Cleaned up how internal IP addresses are used.
* Changed `_write()` (stdio) to do nothing if the requested length is zero
  because that's what `fwrite()` is specified to do.
* Updated examples to use new `operator!=()` for `IPAddress`.
* Moved lwIP's heap to RAM2 (DMAMEM).
* Updated `EthernetFrame`-related documentation to explain that the API doesn't
  receive any known Ethernet frame types, including IPv4, ARP, and IPv6
  (if enabled).
* Clarified in the examples that `Ethernet.macAddress()` retrieves, not sets.
* Changed `EthernetClass::setMACAddress(mac)` parameter to `const`.
* Moved CRC-32 lookup table to RAM2 (DMAMEM).
* Made const those functions which could be made const.
* Updated examples and README to consider listeners and their relationship with
  a static IP and link detection.

### Fixed

* Fixed `EthernetUDP::send()` function to take the host and port as arguments,
  per its description. There's now two of them: one that takes an `IPAddress`
  and another that takes a `char *` hostname.
* Fixed `enet_output_frame()` to correctly return `false` if Ethernet is
  not initialized.
* Fixed not being able to set the DNS server IP before starting Ethernet.
* Fixed raw frame API to consider any padding bytes.

Highlights:
* "==" and "!=" operators for `const IPAddress`.
* A way to wait for link up.
* A way to allow specific MAC addresses past the system filter, for use with the raw frame API. (Short of enabling promiscuous mode.)
* Smaller memory usage by moving lwIP's heap and the CRC-32 lookup table to RAM2 (DMAMEM).
* Updated the README and examples to consider listeners and their relationship with static IPs and link detection.
* Fixed the raw frame API to consider any padding bytes.

Link: https://github.com/ssilverman/QNEthernet/releases/tag/v0.14.0
 
@shawn - I have been working with a T4.1, Arduino 1.8.19 and TD1.56. I am redoing an FTP client that was working with WiFiSPI on an esp8266. I now kind of have working with QNEthernet. There are a couple of issues though. For some reason that I can't figure out I cannot connect to an FTP server using a host name but can connect if using a remote IP address. I am having the same problem with NativeEthernet as well. I have three different computers using UBuntu 20.04 setup with vsftpd. Using gFTP or Filezilla I can connect to any of the other computers using a hostname.local. They all three will connect to each other.

Here is a trimmed down sketch that reproduces the problem with the T4.1:
Code:
#include "Arduino.h"
#include "QNEthernet.h"
#include "QNDNSClient.h"

using namespace qindesign::network;

EthernetClient sclient;
IPAddress ip;
constexpr uint32_t kDHCPTimeout = 10000;  // 10 seconds

void setup(void) {
  Serial.begin(115200);
  while (!Serial && millis() < 4000) {
    // Wait for Serial to initialize
  }
  stdPrint = &Serial;  // Make printf work (a QNEthernet feature)

#if defined(ARDUINO_TEENSY41) || defined(ARDUINO_TEENSY40)
  if(CrashReport)
	Serial.print(CrashReport);
#endif

  printf("\nFTP Client\n\n");

  uint8_t mac[6];
  Ethernet.macAddress(mac);
  printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x\n",
         mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  printf("Starting Ethernet with DHCP...\n");
  if (!Ethernet.begin()) {
    printf("Failed to start Ethernet\n");
    return;
  }
  if (!Ethernet.waitForLocalIP(kDHCPTimeout)) {
    printf("Failed to get IP address from DHCP\n");
    return;
  }
  IPAddress ip = Ethernet.localIP();
  printf("    Local IP    = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.subnetMask();
  printf("    Subnet mask = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.gatewayIP();
  printf("    Gateway     = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.dnsServerIP();
  printf("    DNS         = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
}

void loop(void) {

delay(1000);

  //Try with hostname even without .local still fails.
  if(!DNSClient::getHostByName((const char *)"wwatsond1.local", ip, kDHCPTimeout))
    printf("getHostByName((const char *)wwatsond1.local, ip, kDHCPTimeout) FAILED\n");
  else
    printf("getHostByName((const char *)wwatsond1.local, ip, kDHCPTimeout) WORKED\n");

  //Try with IP
  if(!DNSClient::getHostByName((const char *)"192.168.0.104", ip, kDHCPTimeout))
    printf("getHostByName((const char *)192.168.0.104, ip, kDHCPTimeout) FAILED\n");
  else
    printf("getHostByName((const char *)192.168.0.104, ip, kDHCPTimeout) WORKED\n");

  //Try with hostname even without .local still fails.
  if (sclient.connect("wwatsond1.local", 21)) {  // 21 = FTP server
   printf((const char *)"Connect with Host name: connected\n");
  } else {
    printf((const char *)"Connect with Host name: failed\n");
  }

  //Try with IP
  if (sclient.connect("192.168.0.104", 21)) {  // 21 = FTP server
   printf((const char *)"Connect with IP: Connected\n");
  } else {
    printf((const char *)"Connect with IP: failed\n");
  }

}

The resulting output:
Code:
FTP Client

MAC = 04:e9:e5:0b:b8:0f
Starting Ethernet with DHCP...
    Local IP    = 192.168.0.106
    Subnet mask = 255.255.255.0
    Gateway     = 192.168.0.1
    DNS         = 192.168.0.1
getHostByName((const char *)wwatsond1.local, ip, kDHCPTimeout) FAILED
getHostByName((const char *)192.168.0.104, ip, kDHCPTimeout) WORKED
Connect with Host name: failed
Connect with IP: Connected

I am also testing directly with 'getHostByName()' which also fails using a hostname. I am looping the code with a delay a the beginning of loop().
Eventually the T4. stalls with this output:
Code:
getHostByName((const char *)wwatsond1.local, ip, kDHCPTimeout) FAILED
getHostByName((const char *)192.168.0.104, ip, kDHCPTimeout) WORKED
Connect with Host name: failed
Connect with IP: Connected
getHostByName((const char *)wwatsond1.local, ip, kDHCPTimeout) [COLOR="#FF0000"]WORKED[/COLOR]
getHostByName((const char *)192.168.0.104, ip, kDHCPTimeout) WORKED
Assertion "tcp_slowtmr: TIME-WAIT pcb->state == TIME-WAIT" failed at line 1442 in /home/wwatson/Arduino/libraries/QNEthernet/src/lwip/tcp.c
Notice that at that point getHostByName() worked?

when using the full version of the FTP client and connecting with a remote IP I can use it for a while but eventually It will stall with the same Assertion error shown above.

Any advice is appreciated:)
Thanks

EDIT: I have tried changing the T4.1 clock speed and optimizations. Results were the same...
 
Last edited:
Back
Top