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:
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.
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();
}
}