Teensy 4.1 ethernet webserver problem

Status
Not open for further replies.

Ed70

Member
I ported my webserver from a Arduino Due with w5500 ethernet shield to Teensy 4.1.
I use the NativeEthernet library and i got things running. But it is not stable yet. Now and then this error occurs: SendErr: -20
And after a while the connection got lost and i have to reset the Teensy.

My code:

Code:
#include <SPI.h>
#include <NativeEthernet.h>
#include <fnet.h> 
#include <SD.h>

int chipSelect = BUILTIN_SDCARD; //Teensy interne SD card

IPAddress ip( 192, 168, 0, 177 ); 
IPAddress gateway( 192, 168, 0, 1); 
IPAddress subnet( 255, 255, 255, 0 );
EthernetServer server(80); 

#define REQ_BUF_SZ   60 // size of buffer used to capture HTTP requests
char HTTP_req[REQ_BUF_SZ] = {0}; // buffered HTTP request stored as null terminated string
char req_index = 0;              // index into HTTP_req buffer
File webFile;               // the web page file on the SD card

uint8_t mac[6];

void setup() {
SD.begin(BUILTIN_SDCARD);

 Ethernet.setStackHeap(1024 * 128);
 Ethernet.setSocketSize(1460 * 8); //Set buffer size
 Ethernet.setSocketNum(8); //Change number of allowed sockets 

 teensyMAC(mac);
 Ethernet.begin(mac, ip, gateway, gateway, subnet);

  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield niet gevonden of defect...");
   
    while (true) {delay(1); }// do nothing, no point running without Ethernet hardware
}
  if (Ethernet.linkStatus() == LinkOFF) {Serial.println("Ethernetkabel niet verbonden..."); 
  }

  server.begin();
  Serial.print("Server op adres: ");
  Serial.println(Ethernet.localIP()); Serial.println(); 
}

void loop() {

 EthernetClient client = server.available();  // try to get client

   if (client) {  // got client? 
        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {   // client data available to read
                char c = client.read(); // read 1 byte (character) from client
                // limit the size of the stored received HTTP request
                // buffer first part of HTTP request in HTTP_req array (string)
                // leave last element in array as 0 to null terminate string (REQ_BUF_SZ - 1)
                if (req_index < (REQ_BUF_SZ - 1)) {
                    HTTP_req[req_index] = c;          // save HTTP request character
                    req_index++;
                }
                // last line of client request is blank and ends with \n
                // respond to client only after last line received
                if (c == '\n' && currentLineIsBlank) {
                    // send a standard http response header
                    client.println("HTTP/1.1 200 OK");
                    // remainder of header follows below, depending on if
                    // web page or XML page is requested
                    // Ajax request - send XML file
                     
                     if ((strstr(HTTP_req, "Home=A")) || (strstr(HTTP_req, "Home=D"))) {     
                       // send rest of HTTP header
                    client.println("Content-Type: text/xml"); client.println("Connection: keep-alive"); client.println(); 
        
                    }
                    
          
                    else { // web page request
                        // send rest of HTTP header
                    // send a standard http response header
                    client.println("HTTP/1.1 200 OK");
                    client.println("Content-Type: text/html");
                    client.println("Connnection: close");
                    client.println("Refresh: 300");
                    client.println();
                    
                    // open requested web page file
                    if (strstr(HTTP_req, "GET / ") || strstr(HTTP_req, "GET /Home.htm")) {webFile = SD.open("Home.htm");   }     // open web page file HOME
              if (strstr(HTTP_req, "GET / ") || strstr(HTTP_req, "GET /HomeTA.htm")) {webFile = SD.open("HomeTA.htm");   }     
                    
                    if (strstr(HTTP_req, "GET /Home1.htm")) {webFile = SD.open("Home1.htm"); }       
                    if (strstr(HTTP_req, "GET /Home2.htm")) {
                    webFile = SD.open("Home2.htm"); } 
                    if (strstr(HTTP_req, "GET /Home2a.htm")) {webFile = SD.open("Home2a.htm"); }     
                    if (strstr(HTTP_req, "GET /Home3.htm")) {webFile = SD.open("Home3.htm"); }      
                    if (strstr(HTTP_req, "GET /Home4.htm")) {webFile = SD.open("Home4.htm"); }      
                    if (strstr(HTTP_req, "GET /Home4T1.htm")) {webFile = SD.open("Home4T1.htm"); }   
                    if (strstr(HTTP_req, "GET /Home4T2.htm")) {webFile = SD.open("Home4T2.htm"); }       
                    if (strstr(HTTP_req, "GET /Home4T3.htm")) {webFile = SD.open("Home4T3.htm"); }       
                    if (strstr(HTTP_req, "GET /Home4T4.htm")) {webFile = SD.open("Home4T4.htm"); }        
                    if (strstr(HTTP_req, "GET /Home4T5.htm")) {webFile = SD.open("Home4T5.htm"); }       
                    if (strstr(HTTP_req, "GET /Home45.htm")) {webFile = SD.open("Home45.htm"); }    
                    if (strstr(HTTP_req, "GET /Home50.htm")) {webFile = SD.open("Home50.htm"); }    
                                                                 
                              // send web page to client          
      
      if (webFile) {
        const int bufSize = 2048; 
        byte clientBuf[bufSize];
        int clientCount = 0;

while (webFile.available())
        { clientBuf[clientCount] = webFile.read();
          clientCount++;

           if (clientCount > bufSize-1)
          { client.write((const uint8_t *)clientBuf, bufSize);
            clientCount = 0;
          }}
        // final < bufSize byte cleanup packet
        if (clientCount > 0) client.write((const uint8_t *)clientBuf, clientCount);
         webFile.close(); // close the file
      }}
                                          
                    // display received HTTP request on serial port
                    //Serial.print(HTTP_req);      
                    // reset buffer index and all buffer elements to 0
                    req_index = 0;
                    StrClear(HTTP_req, REQ_BUF_SZ);
                    break;
                }
                // every line of text received from the client ends with \r\n
                if (c == '\n') {
                    // last character on line of received text
                    // starting new line with next character read
                    currentLineIsBlank = true;
                } 
                else if (c != '\r') {
                    // a text character was received from client
                    currentLineIsBlank = false;
                }
            } // end if (client.available())
        } // end while (client.connected())
        delay(1);      // give the web browser time to receive the data
        client.stop(); // close the connection
} //end webserver

}


// sets every element of str to 0 (clears array) //webserver
void StrClear(char *str, char length)
{
    for (int i = 0; i < length; i++) {
        str[i] = 0;
    }
}

// searches for the string sfind in the string str
// returns 1 if string found
// returns 0 if string not found
char StrContains(char *str, char *sfind)
{
    char found = 0;
    char index = 0;
    char len;

    len = strlen(str);
    
    if (strlen(sfind) > len) {return 0;}
    while (index < len) {
        if (str[index] == sfind[found]) {
            found++;
            if (strlen(sfind) == found) {return 1;}
        }
        else {found = 0;}
        index++;}
return 0;
}


void teensyMAC(uint8_t *mac) {

  static char teensyMac[23];
  
  #if defined(HW_OCOTP_MAC1) && defined(HW_OCOTP_MAC0)
    Serial.println("using HW_OCOTP_MAC* - see https://forum.pjrc.com/threads/57595-Serial-amp-MAC-Address-Teensy-4-0");
    for(uint8_t by=0; by<2; by++) mac[by]=(HW_OCOTP_MAC1 >> ((1-by)*8)) & 0xFF;
    for(uint8_t by=0; by<4; by++) mac[by+2]=(HW_OCOTP_MAC0 >> ((3-by)*8)) & 0xFF;

    #define MAC_OK

  #else
    
    mac[0] = 0x04;
    mac[1] = 0xE9;
    mac[2] = 0xE5;

    uint32_t SN=0;
    __disable_irq();
    
    #if defined(HAS_KINETIS_FLASH_FTFA) || defined(HAS_KINETIS_FLASH_FTFL)
      Serial.println("using FTFL_FSTAT_FTFA - vis teensyID.h - see https://github.com/sstaub/TeensyID/blob/master/TeensyID.h");
      
      FTFL_FSTAT = FTFL_FSTAT_RDCOLERR | FTFL_FSTAT_ACCERR | FTFL_FSTAT_FPVIOL;
      FTFL_FCCOB0 = 0x41;
      FTFL_FCCOB1 = 15;
      FTFL_FSTAT = FTFL_FSTAT_CCIF;
      while (!(FTFL_FSTAT & FTFL_FSTAT_CCIF)) ; // wait
      SN = *(uint32_t *)&FTFL_FCCOB7;

      #define MAC_OK
      
    #elif defined(HAS_KINETIS_FLASH_FTFE)
      Serial.println("using FTFL_FSTAT_FTFE - vis teensyID.h - see https://github.com/sstaub/TeensyID/blob/master/TeensyID.h");
      
      kinetis_hsrun_disable();
      FTFL_FSTAT = FTFL_FSTAT_RDCOLERR | FTFL_FSTAT_ACCERR | FTFL_FSTAT_FPVIOL;
      *(uint32_t *)&FTFL_FCCOB3 = 0x41070000;
      FTFL_FSTAT = FTFL_FSTAT_CCIF;
      while (!(FTFL_FSTAT & FTFL_FSTAT_CCIF)) ; // wait
      SN = *(uint32_t *)&FTFL_FCCOBB;
      kinetis_hsrun_enable();

      #define MAC_OK
      
    #endif
    
    __enable_irq();

    for(uint8_t by=0; by<3; by++) mac[by+3]=(SN >> ((2-by)*8)) & 0xFF;

  #endif

  #ifdef MAC_OK
    sprintf(teensyMac, "MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    Serial.println(teensyMac);
  #else
    Serial.println("ERROR: could not get MAC");
  #endif
}
 
Thanks for your help defragster.
With these two pages you can do some repro. https://drive.google.com/drive/folders/1gun_xKaPbiJxN2ShETDGysCtaU-g2oPW?usp=sharing
If you start with: http://192.168.0.177/Home3.htm you will see the page called 'verlichting'.
On this page if you hit 'index' you will see the paged called index. On the index page please hit 'Verlichting' again.
If you do this a few times in a row the error message appears (SendErr: -20) or the server crashes. (could take some time)
When it's crashed sometimes after a couple of minutes it is accessible again but other times the only option left is reset the Teensy.
 
You can check this file for the error numbers: https://github.com/vjmuzik/FNET/blob/master/src/stack/fnet_error.h
If I remember correctly this is related to not closing the webpage properly on the Teensy side of things, specifically with this line here:
Code:
 client.println("Connnection: close");

What this line means is that after you have sent your webpage you have to close the client immediately after, but you don’t want to stop the client because the computer may not be done yet. The original library didn’t have this option to do so so I had to add a function for this aptly named client.close(), if the Teensy isn’t closed before the computer says it’s closed then that results in a connection reset error which isn’t something you want to have happen under normal conditions. The order of which device closes first is important and for a web server the Teensy has to be the first to do so, when the computer says it’s closed it’s already expecting the Teensy to be closed because it has to follow the order and if it’s not then when you call client.stop() you end up causing an error and after enough errors the computer will often reject the connection since it thinks something is wrong. You can see this excerpt from the WebServerMDNS example to see how it’s done:
Code:
 void loop() {
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // the connection will be closed after completion of the response
          client.println("Refresh: 5");  // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          // output the value of each analog input pin
          for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
            int sensorReading = analogRead(analogChannel);
            client.print("analog input ");
            client.print(analogChannel);
            client.print(" is ");
            client.print(sensorReading);
            client.println("<br />");
          }
          client.println("</html>");
          [COLOR="#FF0000"]client.close(); //If "Connection: close" make sure to close after printing and before stop to avoid harsh reset[/COLOR]
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        } else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}
I’m pretty sure I ran into this exact issue when I was making that example that’s why I had to add that function.

So once you send your webpage you need to call client.close() such as this:
Code:
 if (webFile) {
        const int bufSize = 2048; 
        byte clientBuf[bufSize];
        int clientCount = 0;

while (webFile.available())
        { clientBuf[clientCount] = webFile.read();
          clientCount++;

           if (clientCount > bufSize-1)
          { client.write((const uint8_t *)clientBuf, bufSize);
            clientCount = 0;
          }}
        // final < bufSize byte cleanup packet
        if (clientCount > 0) client.write((const uint8_t *)clientBuf, clientCount);
         webFile.close(); // close the file
      }}
[COLOR="#FF0000"]client.close();[/COLOR]
 
Thank you vjmuzik. After adding client.close()
it does not compile:
'class EthernetClient' has no member named 'close'
 
Unfortunately, the problem remains. I can now state that it has nothing to do with my sketch. Also with the webserver example the connection to the server will be disconnected at some point. I even used a new ethernet kit to exclude problems in it.
The special thing is that the green light on the magjack keeps blinking and suggests that the connection is fine.
 
Once the PHY chip is configured by the Teensy the lights will blink irregardless if the Teensy is still working or not so even if it’s frozen the lights will still work. I’ll look into this more sometime next week once I order a new micro SD card since I seem to have lost mine.
 
Ok thank you vjmuzik.
Maybe this information is relevant for troubleshooting... Sometimes I see through the serial monitor that the connection with the mqtt server is still maintained. (Although this is not always the case)
 
The problem doesn't seem to have completely disappeared yet, but there is definitely improvement. I am now trying to integrate the control of my philips hue lamps. I have added a basic sketch. After startup this error message appears again: SendErr: -20

I hope you can give me some advice.

Code:
#include <SPI.h>
#include <NativeEthernet.h>
 
//  Hue constants
IPAddress hueHubIP(192,168,0,125);
const char hueUsername[] = "hueKey";  // Hue username
const int hueHubPort = 80;

String command;
 
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  
IPAddress ip(192,168,0,177);  // Teensy IP
EthernetClient client;

void setup()
{
  Ethernet.begin(mac,ip);
  delay(1000);
  Serial.println("Ready.");
}

void loop() 
{
  delay(3000);
  
  Serial.println("activated");
  command = "{\"on\": false}";
  setHue(3,command);
   
 delay (3000);
       
 Serial.println("deactivated");
 command = "{\"on\": false}";
 setHue(3,command);
 
}


/* setHue() is our main command function, which needs to be passed a light number and a 
 * properly formatted command string in JSON format (basically a Javascript style array of variables
 * and values. It then makes a simple HTTP PUT request to the Bridge at the IP specified at the start.
 */
boolean setHue(int lightNum,String command)
{
  if (client.connect(hueHubIP, hueHubPort))
  {
    while (client.connected())
    {
      client.print("PUT /api/");
      client.print(hueUsername);
      client.print("/lights/");
      client.print(lightNum);  // hueLight zero based, add 1
      client.println("/state HTTP/1.1");
      client.println("keep-alive");
      client.print("Host: ");
      client.println(hueHubIP);
      client.print("Content-Length: ");
      client.println(command.length());
      client.println("Content-Type: text/plain;charset=UTF-8");
      client.println();  // blank line before body
      client.println(command);  // Hue command
    }
    client.stop();
    return true;  // command executed
  }
  else
    return false;  // command failed
}
 
Generally a -20 error happens when either the server or the client closes the sending or receiving side without stopping the connection. Considering it’s a Phillips Hue is it expecting the connection to be encrypted with TLS/SSL?
 
I don't think so. In my example sketch there is no connection to the bridge. Also then I get the error message.
I got this working on my Arduino Due.
 
I don't own any Hue products to test this myself, but this should probably work:
Code:
/* setHue() is our main command function, which needs to be passed a light number and a 
 * properly formatted command string in JSON format (basically a Javascript style array of variables
 * and values. It then makes a simple HTTP PUT request to the Bridge at the IP specified at the start.
 */
boolean setHue(int lightNum,String command)
{
  if (client.connect(hueHubIP, hueHubPort))
  {
    client.print("PUT /api/");
    client.print(hueUsername);
    client.print("/lights/");
    client.print(lightNum);  // hueLight zero based, add 1
    client.println("/state HTTP/1.1");
    client.println("keep-alive");
    client.print("Host: ");
    client.println(hueHubIP);
    client.print("Content-Length: ");
    client.println(command.length());
    client.println("Content-Type: text/plain;charset=UTF-8");
    client.println();  // blank line before body
    client.println(command);  // Hue command
    while (client.connected())
    {
      if(client.available()){
        client.read();
      }
    }
    client.stop();
    return true;  // command executed
  }
  else
    return false;  // command failed
}
The reason why it wouldn't have worked before is for two reasons, one you had your print lines in the while loop so it was constantly trying to send the same data after the client closed the connection. The second reason is you weren't reading anything the client was sending and client.connected will never be false if there is unread data. That part hasn't changed from the original library so I'm not sure why it works on the Due and not here, there's also a long standing bug in my library that if you don't read the client data eventually it'll crash from buffer overflow. That may or not be fixed for a long time since I know I have to do a significant rewrite to fix that and I don't have the time to do it right now.
 
My programming knowledge is relatively limited and sometimes it's a challenge to keep having fun in this hobby if you get stuck. Help from people with extensive knowledge is then indispensable. I would like to thank you very much for your time and effort. Your solution works!
I can now further integrate my Hue lamps into my project.
As said before, the server (where I started this post) now works almost without any problems. However, where the server on the Due works without any problems, I have had a few (random) crashes on the Teensy. Could this be related to the bug you are talking about ?
 
It could be related to that bug, if you have any kind of connection and you don’t make sure you read all the data before closing it eventually it will crash. If you do read all the data then there won’t be any problems, as stated before I already know why it does it and what I would need to do to fix it, I just don’t have the time to right now.
 
Status
Not open for further replies.
Back
Top