New lwIP-based Ethernet library for Teensy 4.1

This code I posted a few years ago #141 puts up a web page that allows you to turn the onboard LED on and off. It is almost immediate in v0.28 and about a second in v0.29. Don't know why it's not 4-5 seconds
like my other program.

Code:
/* blinkServer 2023/8/22
* cleaned up version of https://forum.pjrc.com/threads/68066-New-lwIP-based-Ethernet-library-for-Teensy-4-1?p=299978&viewfull=1#post299978
* open serial monitor to see what the arduino receives and the server address
* requires DHCP
* for hardware: ..  or bare teensy 4.1
*/

#include <QNEthernet.h>

#define LEDPIN13 13

using namespace qindesign::network;
constexpr uint32_t kDHCPTimeout = 10000;  // 10 seconds
constexpr uint16_t kServerPort = 80; //443 for TLS

EthernetServer server(kServerPort); //server port

IPAddress ip;
String readString;
boolean gledon=false;
int theline=0;
uint8_t mac[6];

void setup(){

  pinMode(LEDPIN13, OUTPUT); //pin selected to control
  digitalWrite(LEDPIN13, gledon);  //turn off when starting up
 
  //enable serial data print
  Serial.begin(9600);
  while (!Serial && millis()<5000) {
    ; // wait for serial port to connect or 5 seconds
  }
  //Serial.print(CrashReport);
  //stdPrint = &Serial;  // Make printf work (a QNEthernet feature)
 
  Serial.println("blinkServer"); // so I can keep track of what is loaded
  Ethernet.macAddress(mac);
  Serial.printf("\nMAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  if(mac[5]==0xf3){   //host name stuff
    Ethernet.setHostname("GPSD_Box");   
  }else{
    Ethernet.setHostname("bare-Teensy");
  }
 
  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");
    gledon=false;
  } else {
    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]);

    // Start the server
    ip = Ethernet.localIP();
    printf("Listening for clients on '%s'.  Copy this to your browser:  http://%u.%u.%u.%u   \n\n", Ethernet.hostname().c_str(), ip[0], ip[1], ip[2], ip[3]);
    server.begin();
    gledon=true;
  }
  digitalWrite(LEDPIN13, gledon);  //LED will be on when starting up and has ip address
}

void loop(){
  // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        //read char by char HTTP request
        if (readString.length() < 100) {
          readString += c;  //store characters to string
          //Serial.print(c); //uncomment to see in serial monitor
        }

        if (c == '\n') {  //HTTP request ends with '\n'
          theline++;
          Serial.println(theline); //separate responses with a line #
          Serial.print(readString); // \n already included
         
          //set the status of items before sending the HTML in case this is a response to previous change
          //this will prevent having to update again as in formServer.ino
          //note that request not containing "LED+ON" or "LED+OFF" will leave gledon status unchanged
          if(readString.indexOf("LED+ON") >0){
            gledon=true;
            digitalWrite(LEDPIN13, gledon);  
          }else if(readString.indexOf("LED+OFF") >0){
            gledon=false;
            digitalWrite(LEDPIN13, gledon); 
          }

          //now output HTML data header
          //use \ slash to escape the " in the html
          /////////////////////
          client.writeFully("HTTP/1.1 200 OK\n");
          client.writeFully("Content-Type: text/html\n");
          client.writeFully("\n");

          client.writeFully("<HTML>");
          client.writeFully("<HEAD>");
          client.writeFully("<TITLE>blink</TITLE>");
          client.writeFully("</HEAD>");
          client.writeFully("<BODY>");
          client.writeFully("<font size=\"10\">");

          client.writeFully("<H1>blinkServer</H1>");

          char buff[60];
          sprintf(buff, "<FORM ACTION=\"http://%u.%u.%u.%u:80\" method=get >",  ip[0], ip[1], ip[2], ip[3]);
          client.writeFully(buff);
         
          if(gledon){
            client.writeFully("The LED is ON ");
            client.writeFully("<INPUT TYPE=SUBMIT NAME=\"submit\" VALUE=\"TURN LED OFF\" style=\"font-size: 70px\">");
          }else{
            client.writeFully("The LED is OFF ");
            client.writeFully("<INPUT TYPE=SUBMIT NAME=\"submit\" VALUE=\"TURN LED ON\" style=\"font-size: 70px\">");
          }
          client.writeFully("<BR>");
          client.writeFully("</font>");
          client.writeFully("</BODY>");
          client.writeFully("</HTML>");
          client.flush();

          delay(1);
          client.stop();  //stopping client.  Page is done till next request so stop.

          //clearing string for next read
          readString="";
        }
      }
    }
  }
}
[/ICODE]
 
Thought maybe the sprintf might be the problem since the change notice said printf had error correction added but removing it from the main loop didn't help. I do occasionally get 3-4 second delays, but usually ~2.

BTW I never heard from anyone at all when I posted this code before. I thought it was extremely useful back then as nearly the simplest way to put up a web page with the teensy4.1 that anyone could try needing only a web browser. Any comments?
 
I did some rudimentary testing:
1. Copied the code into the Arduino IDE (2.3.2) and made no modifications
2. Installed QNEthernet 0.28.0
3. Ran this a bunch of times: time curl http://192.168.1.10 (replace with your Teensy address)
4. Saw times between 30 and 50 milliseconds, with the occasional 100-something milliseconds
5. Installed QNEthernet 0.29.0
6. Ran the same command a bunch of times
7. I see very similar time ranges: average mid-30's milliseconds with the occational 100-something milliseconds

I also tested it in my browser, both loading the page and pressing the button. The response time was very fast.

I've also been running lighting controls using sACN and Art-Net via a Teensy 4.1 with this library (those are both UDP), and the LEDs are very smooth.

My network setup:
* MacBook Pro over Wi-Fi
* Teensy 4.1 connected to an eero on the same network

What does your network setup look like?
 
I also just ran the test suite (pio test -v -e teensy41-test -f test_ethernet). The results:
1. 0.29.0: 40.905s, 27.365s, 26.836s
2. 0.28.0: 41.173s, 27.210s, 27.721s

I'm seeing that 0.29.0 is just a smidgen faster from this and from timing the curl test above.
 
Sorry, I messed up posting as I don't see it here. Had a table with some numbers. I am running the teensy at 24Mhz. At 600Mhz it seemed similar.
 
I'm running on a wireless local network on a M2 mac Air. Tried the curl at 24Mhz, Faster:
v0.29:
real 0m4.075s <---
user 0m0.022s
sys 0m0.015s

v.028:
real 0m0.060s
user 0m0.020s
sys 0m0.014s

The reason for using 24Mhz is in my app it draws 95mA vs 322mA @ 600Mhz.

Don't know why but this test code draws about 250mA on v.028 or 0.29.

EDIT: The 250mA is something with Arduino-teensy when first compiled. When I unplug the teensy and plug back in I get the same ~95mA as in the past. The current draw didn't seem to effect the timing.
 
Last edited:
Edited the current draw issue above but the speed issue at 24Mhz remains. Using the time curl test repeditively with about 1 second after a response the 'real' time increases about 1 sec each try until it levels at about 6 seconds.

The same procedure with v0.28 stays below .06 seconds.
 
I changed to 24MHz for the same curl tests I did above, and v0.29.0 is a hair faster than v0.28.0. I'm seeing approx. 40-something milliseconds on v0.28.0 and 30-something milliseconds on v0.29.0.
 
Just to clarify, I’m not saying you’re wrong, just that I haven’t yet figured out a way to duplicate the issue on my network.
 
Found another clue. Running blinkServer #226 at 24Mhz and then using the served page button or the time curl I get an extra line on the Arduino terminal in v0.29 that is not in my code:
Code:
GET / HTTP/1.1
25
Host: 192.168.1.97
26
GET / HTTP/1.1
27
Host: 192.168.1.97
28
GET / HTTP/1.1
29
Host: 192.168.1.97
30
GET / HTTP/1.1
31
Host: 192.168.1.97
32
GET / HTTP/1.1
33
Host: 192.168.1.97

With v0.28:
Code:
1
GET / HTTP/1.1
2
GET / HTTP/1.1
3
GET / HTTP/1.1
4
GET / HTTP/1.1
5
GET / HTTP/1.1
 
The extra line (Host: 192.168.1.97) occurs when the 'real' time is > ~3.5 seconds. Sometimes it takes 3 or 4 tries to build up to >3.5 but becomes about 5 seconds. Even if I let it alone for 5 minutes the 4-5 second delay will continue.

Unplugging the teensy and replugging and using just the time curl and repeating as soon as I can get this progression:
Code:
MikesAir:~ michael$ time curl http://192.168.1.97
<HTML><HEAD><TITLE>blink</TITLE></HEAD><BODY><font size="10"><H1>blinkServer</H1>F_CPU: 24 MHz<FORM ACTION="http://192.168.1.97:80" method=get >The LED is ON <INPUT TYPE=SUBMIT NAME="submit" VALUE="TURN LED OFF" style="font-size: 70px"><BR></font></BODY></HTML>
real    0m0.088s
user    0m0.020s
sys    0m0.019s
MikesAir:~ michael$ time curl http://192.168.1.97
<HTML><HEAD><TITLE>blink</TITLE></HEAD><BODY><font size="10"><H1>blinkServer</H1>F_CPU: 24 MHz<FORM ACTION="http://192.168.1.97:80" method=get >The LED is ON <INPUT TYPE=SUBMIT NAME="submit" VALUE="TURN LED OFF" style="font-size: 70px"><BR></font></BODY></HTML>
real    0m1.097s
user    0m0.023s
sys    0m0.015s
MikesAir:~ michael$ time curl http://192.168.1.97
<HTML><HEAD><TITLE>blink</TITLE></HEAD><BODY><font size="10"><H1>blinkServer</H1>F_CPU: 24 MHz<FORM ACTION="http://192.168.1.97:80" method=get >The LED is ON <INPUT TYPE=SUBMIT NAME="submit" VALUE="TURN LED OFF" style="font-size: 70px"><BR></font></BODY></HTML>
real    0m2.072s
user    0m0.021s
sys    0m0.016s
MikesAir:~ michael$ time curl http://192.168.1.97
<HTML><HEAD><TITLE>blink</TITLE></HEAD><BODY><font size="10"><H1>blinkServer</H1>F_CPU: 24 MHz<FORM ACTION="http://192.168.1.97:80" method=get >The LED is ON <INPUT TYPE=SUBMIT NAME="submit" VALUE="TURN LED OFF" style="font-size: 70px"><BR></font></BODY></HTML>
real    0m3.072s
user    0m0.024s
sys    0m0.016s
MikesAir:~ michael$ time curl http://192.168.1.97
<HTML><HEAD><TITLE>blink</TITLE></HEAD><BODY><font size="10"><H1>blinkServer</H1>F_CPU: 24 MHz<FORM ACTION="http://192.168.1.97:80" method=get >The LED is ON <INPUT TYPE=SUBMIT NAME="submit" VALUE="TURN LED OFF" style="font-size: 70px"><BR></font></BODY></HTML>
real    0m5.552s
user    0m0.021s
sys    0m0.020s
 
Thanks for that clue. At first glance, the “Host:” line should be there because HTTP 1.1 clients are supposed to send that. I’m just surprised that “Host:” line isn’t there for the 0.28.0 version. I wasn’t looking closely at the serial output before, just the timing.

Your program prints the current line number and then a received line. I’m also surprised it’s not printing more headers. (Assuming there are any.)

Having said all that, it looks like the program shuts down the client after the first line is received, so clearly it’s receiving more data than it should.

I’ll look more closely when I’m next near my code.
 
client.close() instead of stop() caused v0.28 to also print the "Host: 192.168.1.97" but didn't effect it's speed. v0.29 seemed unaffected and the same slowness. Interesting that the Get and Host line print at nearly the same time after a long 4 to 5 second pause when each line requires a new trip through loop(). Looks like when it finally starts reading it reads both lines and loops around. I'll try to add code to verify.

I tried v0.29 on another program that serves web pages and images more like a typical server and a typical image at 24Mhz took ~.09 seconds v0.28 and ~60 seconds on v0.29. Upping the speed to 600Mhz didn't change v.028 much but the v.29 couldn't do much better than ~5 seconds to load the image. This program used java-script and jquirery to time the image loading on the client. It's loads pictures off the SD and would require a big effort to simplify. Also it's j-quirery stuff I copied out of a book so I don't know about posting. Maybe private?
 
@bicycleguy I'm still having trouble duplicating the issue. I have a request: Can you provide a small program that demonstrates the problem with UDP? (#224)

On the subject of receiving the "Host:" header, the close request might not be complete, and the Arduino-style client API can still return received data in some cases, even if the client isn't "connected". This might explain why you're receiving data after trying to close. Note that I'm not seeing the "Host:" header on my end, with either version of the library.

I also just went through the code differences between v0.28.0 and v0.29.0, and I'm not seeing anything that would explain what you're seeing.

One thing to try: Completely uninstall the QNEthernet library, restart the IDE, reinstall v0.29.0, and then test. Next, completely uninstall the library again, restart the IDE, reinstall v0.28.0, and then test. Is it possible that the files are being modified externally or something? Maybe also double-check that there's no traces of the library after each uninstall.
 
@shawn Dang, can't understand how it's not showing up for you. I had been deleting the cores and sketches cache at /private/var/folders/q9/p*/T/arduino, YRMV, but haven't tried deleting and reinstalling the libraries.
 
In fact, just to be sure the library isn’t there after removing it, try to compile your program and observe that it doesn’t compile, for each uninstall of the library.
 
Observed no compile. Quit, deleted the cache as above, then restarted reinstalled v29 then same.
This any help?
 

Attachments

  • blinkServerLog.txt
    115.7 KB · Views: 26
What happens if you connect the Teensy to your computer directly (and assign a static IP or something) instead of going through a router?
 
I think this discussion might be better moved to a QNEthernet library issue on the GitHub repo. (Or a different thread here. Reason being findability, one-thing-per-discussion, etc.)
 
Last edited:
Back
Top