Web-/Socket- server on Teensy 4.1 using QNEthernet

MrExplore

Member
Hi all,

I've been working on a web- and websocket-server and I have it "working". However, I'm still having issues and I could use some hints and tips.
Please keep in mind, I'm a, let's say "intermediate beginner". I did a fair amount of projects but this seems like "next level stuff".

My code is below and here are some details:

- Using a Teensy 4.1
- Using QNEthernet
- Using WebSockets2 Generic (https://github.com/khoih-prog/WebSockets2_Generic)
- Serving web interface from SD card, thanks to Fertje in this post

The way it works:
- A client connects to the webserver and gets HTML5/Javascript
- The Javascript sets up a websocket connection for bi-directional communication
- The Javascript sends an "alive" ping to the server to keep the socket connection open
- If no ping is received for 5 seconds the websocket connection is closed


I'll try to explain what I've been doing so far and what the issues are:

- I started out using Native Ethernet but constantly got resets on the Teensy when doing page refreshes.
- I then used QNEthernet only changing:
Code:
// change 1=========================
#define USE_NATIVE_ETHERNET         true
//#define USE_QN_ETHERNET             true

// Changed to:

//#define USE_NATIVE_ETHERNET         true
#define USE_QN_ETHERNET             true

// Change 2 ========================
client.write(...

// Changed to:

client.writeFully(...

Ok, the refresh-reset issues were gone in the browsers on my desktop computer.
They do still occur when accessing the page from my phone (Brave refresh causes reset and Chrome can't get a page at all)

So it seemed like I made some progress, until I came home after being away for an hour or so.
The Teensy server was unreachable and the LED's on the ethernet connector were off.


Although this code below "works" I'm facing the above mentioned issues. Also the code could, I think, certainly be improved.

Thanks for reading and looking forward to your feedback.



Code:
// SERVERS ==================================================
#define WEBSOCKETS_USE_ETHERNET     true
//#define USE_NATIVE_ETHERNET         true
#define USE_QN_ETHERNET             true

#define REQ_BUF_SZ                  50

#include <SPI.h>
#include <SD.h>
#include <WebSockets2_Generic.h>
#include <QNEthernet.h>

using namespace websockets2_generic;

WebsocketsClient socketClient;                                        // Only one socketclient at a time allowed 
bool socketClientConnected = false;

WebsocketsServer socketServer;
const uint16_t websocketsPort = 8080;

EthernetServer httpServer(80);

byte eth_MC[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};                 // MaC address

IPAddress eth_IP(192, 168, 8, 120);                                   // IP
IPAddress eth_SN(255, 255, 255, 0);                                   // SubNet
IPAddress eth_GW(192, 168, 8, 1);                                     // GateWay
IPAddress eth_DN(0, 0, 0, 0);                                         // DNs    << dummy DNS for initializing ethernet

File webFile;
char HTTP_req[REQ_BUF_SZ] = {0};                                      // buffered HTTP request stored as null terminated string
char req_index = 0;                                                   // index into HTTP_req buffer
const int chipSelect = BUILTIN_SDCARD;

unsigned long lastAlive = 0;                                          // timestamp of last alive message from socketclient

// JSON =====================================================
#include <ArduinoJson.h>
StaticJsonDocument<128> jDocRx;                                       // https://arduinojson.org/v6/assistant/#/step1 to determine size
StaticJsonDocument<128> jDocTx;                                       // jDocRx for receiving, jDocTx for sending

// DEBUGGING ================================================
#include <Streaming.h>
                            

/* ====================================
   _____  ______ ______ __  __ ____ 
  / ___/ / ____//_  __// / / // __ \
  \__ \ / __/    / /  / / / // /_/ /
 ___/ // /___   / /  / /_/ // ____/ 
/____//_____/  /_/   \____//_/     
*/
 
void setup(){

  Serial.begin(115200);

  // SDcard =====================================================
  SD.begin(chipSelect);

  // Ethernet ========================================================
  Ethernet.begin(eth_MC, eth_IP, eth_DN, eth_GW, eth_SN);
  //delay(2000);                                                      // Ethernet.begin(mac, ip, dns, gateway, subnet);
  socketServer.listen(websocketsPort);
  httpServer.begin();

} // end setup


/* =============================
    __    ____   ____   ____ 
   / /   / __ \ / __ \ / __ \
  / /   / / / // / / // /_/ /
 / /___/ /_/ // /_/ // ____/ 
/_____/\____/ \____//_/      
*/

void loop(){

  // SOCKETCLIENT TIMEOUT ===========================================
  if( (millis() - lastAlive) > 5000 && socketClientConnected)              // If no "alive" is received for 5 seconds &AND& ws_client_connected is true                                             
  {
    Serial << "Socket connection closed, client inactive" << endl;
    closeSocketConnection();                                            
  }

  // CLIENTS ========================================================
  listenForSocketClients();
  socketClient.poll();
  listenForHttpClients(); 
}


/* ============================================================================================================
    __     ____ _____ ______ ______ _   __ ______ ____   ____   ______ __     ____ ______ _   __ ______ _____
   / /    /  _// ___//_  __// ____// | / // ____// __ \ / __ \ / ____// /    /  _// ____// | / //_  __// ___/
  / /     / /  \__ \  / /  / __/  /  |/ // /_   / / / // /_/ // /    / /     / / / __/  /  |/ /  / /   \__ \ 
 / /___ _/ /  ___/ / / /  / /___ / /|  // __/  / /_/ // _, _// /___ / /___ _/ / / /___ / /|  /  / /   ___/ / 
/_____//___/ /____/ /_/  /_____//_/ |_//_/     \____//_/ |_| \____//_____//___//_____//_/ |_/  /_/   /____/  
                                                                                                             
*/

void listenForSocketClients(){
  
  if (socketServer.poll()){                                           // v05: removed "&& !socketClientConnected" to allow overriding existing connection
    socketClient = socketServer.accept();
    socketClientConnected = true;
    lastAlive = millis();                                             // Timestamp the moment of connection as first alive
    Serial << "socketclient connected" << endl;
    socketClient.onMessage(handleMessage);
    socketClient.onEvent(handleEvent);
  }
}

void closeSocketConnection(){

  socketClient.close();                                               // close the socket                               
  socketClientConnected = false;                                      // and set websocket socketClientConnected to false
}


void listenForHttpClients(){
  
  EthernetClient client = httpServer.available();
  
  if (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
          if (req_index < (REQ_BUF_SZ - 1)) {
            HTTP_req[req_index] = c;                                  // save HTTP request character
            req_index++;
          }
          if (c == '\n' && currentLineIsBlank) {
            String thereq = String(HTTP_req);
            Serial << "thereq:  " << thereq << endl;
            int theFileLeftPos = thereq.indexOf(" ");
            int theFileRightPos = thereq.indexOf(" ",theFileLeftPos+2);
            String theFile = thereq.substring(theFileLeftPos+2,(theFileRightPos));
            String contentType; 
            if (StrContains(HTTP_req, ".html")) { contentType = "Content-Type: text/html";}
            if (StrContains(HTTP_req, ".js")  ) { contentType = "Content-Type: text/javascript";}
            if (StrContains(HTTP_req, ".css") ) { contentType = "Content-Type: text/css";}
            if (StrContains(HTTP_req, ".txt") ) { contentType = "Content-Type: text/plain";}
            if (StrContains(HTTP_req, ".svg") ) { contentType = "Content-Type: image/svg+xml";}
            if (StrContains(HTTP_req, ".png") ) { contentType = "Content-Type: image/png";}
            if (StrContains(HTTP_req, ".gif") ) { contentType = "Content-Type: image/gif";}
            if (StrContains(HTTP_req, ".jpg") ) { contentType = "Content-Type: image/jpeg";}
            if (StrContains(HTTP_req, ".ico") ) { contentType = "Content-Type: image/x-icon";}
            
            client.println("HTTP/1.1 200 OK");
            client.println(contentType);
            client.println("Cache-Control: public, max-age=31536000");
            client.println("Connnection: close");
            client.println();
            
            webFile = SD.open(theFile.c_str(),O_READ);
            
            if (webFile) {
              const int bufSize = 1024; 
              byte clientBuf[bufSize];
              int clientCount = 0;
              char tBuf[513];
              while (webFile.available()){
                clientCount = webFile.read(tBuf,512);
                client.writeFully((byte*)tBuf,clientCount);             // Use "writeFully" instead of "write" when using QNEthernet
              }
              webFile.close();
            }

            client.flush();
            client.close();
            
            req_index = 0;
            StrClear(HTTP_req, REQ_BUF_SZ);
            break;
          }
          
      if (c == '\n') { currentLineIsBlank = true;}
      else if (c != '\r') { currentLineIsBlank = false;}
      
      } // end if (client.available())
      
    } // end while (client.connected())
    
    client.stop();
    
  } // end if client
  
} // end void listenForHttpClients


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

// listenForHttpClients helper StrContains
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 handleMessage(WebsocketsClient &client, WebsocketsMessage message){
  
  auto data = message.data();

  Serial << "message received: " << data << "   " << millis() << endl;

  deserializeJson(jDocRx, data);                                                    // Deserialize messagedata into jDocRx object
   
  String id = jDocRx["id"];                                                         // The messages contain an id to determine the kind of message and its destination
  
  if(id == "ping"){
    lastAlive = millis();                                                           // Timestamp the last alive message from client
  }
}

void handleEvent(WebsocketsClient &client, WebsocketsEvent event, String data){
  //Serial << "websocket event:   " << event << endl;
  if (event == WebsocketsEvent::ConnectionClosed){
    Serial << "Socket connection closed by client   " << millis()<< endl;           // << PRINTED OUT 1000+ TIMES BEFORE RESET
    closeSocketConnection();
  }
}
 
Update:

So it seemed like I made some progress, until I came home after being away for an hour or so.
The Teensy server was unreachable and the LED's on the ethernet connector were off.

This seemed to be a one time issue due to... unknown.
It seems to be stable now.

Hints and tips on improvement are still welcome though.
 
Ok, I reverted to Native Ethernet.

A "workaround" seems to be closing the existing websocket connection before serving the HTML.
Not ideal but I only need one connection so closing it before it's re-opened by the client is ok.

Any thoughts on why the ethernet is dropping with QNEthernet are welcome.

For now it seems to work... for now :confused:

Code:
// LISTEN FOR HTTP CLIENT ==========================================
void listenForHttpClients(){
  
  EthernetClient client = httpServer.available();
  
  if (client){
    
    socketClient.close();                                                                               // Socket needs to be closed, if not Teensy resets
    
    boolean currentLineIsBlank = true;
    ......
    .....
 
Back
Top