From 2f284d931961e41c498427ee9dd5a4c0797b99c0 Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Thu, 6 Oct 2022 01:29:27 -0400 Subject: [PATCH] v1.6.0 to save heap when sending large data ### Releases v1.6.0 1. Support using `CString` to save heap to send `very large data`. Check [request->send(200, textPlainStr, jsonChartDataCharStr); - Without using String Class - to save heap #8](https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/8) and [All memmove() removed - string no longer destroyed #11](https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/11) 2. Add multiple examples to demo the new feature --- CONTRIBUTING.md | 21 +- changelog.md | 27 +- .../AsyncMultiWebServer.ino | 128 ++++--- .../Async_AdvancedWebServer.ino | 149 ++++---- ...bServer_MemoryIssues_SendArduinoString.ino | 182 ++++++---- ...cedWebServer_MemoryIssues_Send_CString.ino | 190 +++++----- .../Async_HelloServer/Async_HelloServer.ino | 145 ++++---- .../Async_HelloServer2/Async_HelloServer2.ino | 143 ++++---- .../Async_HttpBasicAuth.ino | 129 ++++--- .../Async_PostServer/Async_PostServer.ino | 162 +++++---- .../Async_RegexPatterns.ino | 124 ++++--- .../Async_SimpleWebServer.ino | 126 ++++--- .../WebClientRepeating/WebClientRepeating.ino | 104 +++--- examples/WebClientRepeating/defines.h | 83 ++--- library.json | 38 +- library.properties | 22 +- ...cedWebServer_MemoryIssues_Send_CString.png | Bin 0 -> 70959 bytes platformio/platformio.ini | 322 +---------------- src/AsyncEventSource_Teensy41.cpp | 99 ++++-- src/AsyncEventSource_Teensy41.h | 59 +++- src/AsyncJson_Teensy41.h | 84 ++++- src/AsyncWebAuthentication_Teensy41.cpp | 27 +- src/AsyncWebAuthentication_Teensy41.h | 15 +- src/AsyncWebHandlerImpl_Teensy41.h | 39 ++- src/AsyncWebHandlers_Teensy41.cpp | 30 +- src/AsyncWebRequest_Teensy41.cpp | 196 +++++++++-- src/AsyncWebResponseImpl_Teensy41.h | 85 ++++- src/AsyncWebResponses_Teensy41.cpp | 322 +++++++++++++++-- src/AsyncWebServer_Teensy41.cpp | 59 +++- src/AsyncWebServer_Teensy41.h | 10 +- src/AsyncWebServer_Teensy41.hpp | 283 +++++++++++---- src/AsyncWebServer_Teensy41_Debug.h | 6 +- src/AsyncWebSocket_Teensy41.cpp | 327 +++++++++++++++--- src/AsyncWebSocket_Teensy41.h | 171 +++++++-- src/AsyncWebSynchronization_Teensy41.h | 69 ++-- src/Crypto/Hash.h | 2 +- src/Crypto/bearssl_hash.h | 6 +- src/Crypto/md5.h | 2 +- src/Crypto/sha1.h | 2 +- src/StringArray_Teensy41.h | 204 ++++++----- src/libb64/cdecode.c | 8 +- src/libb64/cdecode.h | 8 +- src/libb64/cencode.c | 8 +- src/libb64/cencode.h | 8 +- 44 files changed, 2746 insertions(+), 1478 deletions(-) create mode 100644 pics/Async_AdvancedWebServer_MemoryIssues_Send_CString.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8640589..c3e9ed2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,21 +1,23 @@ -## Contributing to AsyncWebServer_Ethernet +## Contributing to AsyncWebServer_Teensy41 ### Reporting Bugs -Please report bugs in AsyncWebServer_Ethernet if you find them. +Please report bugs in AsyncWebServer_Teensy41 if you find them. However, before reporting a bug please check through the following: -* [Existing Open Issues](https://github.com/khoih-prog/AsyncWebServer_Ethernet/issues) - someone might have already encountered this. +* [Existing Open Issues](https://github.com/khoih-prog/AsyncWebServer_Teensy41/issues) - someone might have already encountered this. -If you don't find anything, please [open a new issue](https://github.com/khoih-prog/AsyncWebServer_Ethernet/issues/new). +If you don't find anything, please [open a new issue](https://github.com/khoih-prog/AsyncWebServer_Teensy41/issues/new). ### How to submit a bug report Please ensure to specify the following: * Arduino IDE version (e.g. 1.8.19) or Platform.io version -* Board Core Version (e.g. ESP8266 core v3.0.2) +* `Teensyduino` Core Version (e.g. `Teensyduino core v1.57`) +* `QNEthernet` library version (e.g. `QNEthernet v0.15.0`) +* Board type and relevant info * Contextual information (e.g. what you were trying to achieve) * Simplest possible steps to reproduce * Anything that might be relevant in your opinion, such as: @@ -27,13 +29,13 @@ Please ensure to specify the following: ``` Arduino IDE version: 1.8.19 -ESP8266_NODEMCU_ESP12E using ESP8266_W5500 Ethernet -ESP8266 core v3.0.2 +Teensyduino core v1.57 +Teensy 4.1 using QNEthernet v0.15.0 OS: Ubuntu 20.04 LTS Linux xy-Inspiron-3593 5.15.0-48-generic #54~20.04.1-Ubuntu SMP Thu Sep 1 16:17:26 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux Context: -I encountered a crash while using this library +I encountered an endless loop while using this library Steps to reproduce: 1. ... @@ -41,12 +43,11 @@ Steps to reproduce: 3. ... 4. ... ``` - ### Sending Feature Requests Feel free to post feature requests. It's helpful if you can explain exactly why the feature would be useful. -There are usually some outstanding feature requests in the [existing issues list](https://github.com/khoih-prog/AsyncWebServer_Ethernet/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. +There are usually some outstanding feature requests in the [existing issues list](https://github.com/khoih-prog/AsyncWebServer_Teensy41/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. ### Sending Pull Requests diff --git a/changelog.md b/changelog.md index e5f7cc7..90eb5d3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,16 +1,19 @@ -# AsyncWebServer_Ethernet +# AsyncWebServer_Teensy41 -[![arduino-library-badge](https://www.ardu-badge.com/badge/AsyncWebServer_Ethernet.svg?)](https://www.ardu-badge.com/AsyncWebServer_Ethernet) -[![GitHub release](https://img.shields.io/github/release/khoih-prog/AsyncWebServer_Ethernet.svg)](https://github.com/khoih-prog/AsyncWebServer_Ethernet/releases) +[![arduino-library-badge](https://www.ardu-badge.com/badge/AsyncWebServer_Teensy41.svg?)](https://www.ardu-badge.com/AsyncWebServer_Teensy41) +[![GitHub release](https://img.shields.io/github/release/khoih-prog/AsyncWebServer_Teensy41.svg)](https://github.com/khoih-prog/AsyncWebServer_Teensy41/releases) +[![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/khoih-prog/AsyncWebServer_Teensy41/blob/master/LICENSE) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#Contributing) -[![GitHub issues](https://img.shields.io/github/issues/khoih-prog/AsyncWebServer_Ethernet.svg)](http://github.com/khoih-prog/AsyncWebServer_Ethernet/issues) +[![GitHub issues](https://img.shields.io/github/issues/khoih-prog/AsyncWebServer_Teensy41.svg)](http://github.com/khoih-prog/AsyncWebServer_Teensy41/issues) --- --- ## Table of contents +* [Table of contents](#table-of-contents) * [Changelog](#changelog) + * [Releases v1.6.0](#releases-v160) * [Releases v1.5.0](#releases-v150) * [Releases v1.4.1](#releases-v141) @@ -19,16 +22,20 @@ ## Changelog -#### Releases v1.5.0 +### Releases v1.6.0 1. Support using `CString` to save heap to send `very large data`. Check [request->send(200, textPlainStr, jsonChartDataCharStr); - Without using String Class - to save heap #8](https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/8) and [All memmove() removed - string no longer destroyed #11](https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/11) 2. Add multiple examples to demo the new feature -#### Releases v1.4.1 +### Releases v1.5.0 + +1. Fix issue with slow browsers or network. Check [Target stops responding after variable time when using Firefox on Windows 10 #3](https://github.com/khoih-prog/AsyncWebServer_RP2040W/issues/3) +2. Add functions and example `Async_AdvancedWebServer_favicon` to support `favicon.ico` + +### Releases v1.4.1 + +1. Initial porting and coding for **Teensy 4.1 using built-in QNEthernet** +2. Bump up version to v1.4.1 to sync with [AsyncWebServer_STM32](https://github.com/khoih-prog/AsyncWebServer_STM32) v1.4.1 -1. Initial coding to port [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) to ESP8266 boards using W5x00 / ENC28J60 Ethernet. -2. Add more examples. -3. Add debugging features. -4. Bump up to v1.4.1 to sync with [AsyncWebServer_WT32_ETH01 v1.4.1](https://github.com/khoih-prog/AsyncWebServer_WT32_ETH01). diff --git a/examples/AsyncMultiWebServer/AsyncMultiWebServer.ino b/examples/AsyncMultiWebServer/AsyncMultiWebServer.ino index e7003b0..92e07d8 100644 --- a/examples/AsyncMultiWebServer/AsyncMultiWebServer.ino +++ b/examples/AsyncMultiWebServer/AsyncMultiWebServer.ino @@ -1,22 +1,49 @@ /**************************************************************************************************************************** - AsyncMultiWebServer.h + AsyncMultiWebServer.h - Dead simple AsyncWebServer for Teensy41 QNEthernet - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license *****************************************************************************************************************************/ -#include "defines.h" +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 + +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif + +#define USING_DHCP true +//#define USING_DHCP false + +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; -#include +#include unsigned int analogReadPin [] = { 12, 13, 14 }; -#define BUFFER_SIZE 512 +#define BUFFER_SIZE 500 #define HTTP_PORT1 8080 #define HTTP_PORT2 8081 @@ -55,7 +82,7 @@ body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Col \ \

Hello from %s

\ -

running AsyncWebServer_Ethernet

\ +

running AsyncWebServer_Teensy41

\

on %s

\

Uptime: %d d %02d:%02d:%02d

\ \ @@ -101,63 +128,56 @@ void handleNotFound(AsyncWebServerRequest * request) request->send(404, F("text/plain"), message); } -void initEthernet() +void setup() { - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); + Serial.begin(115200); + while (!Serial && millis() < 5000); -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); + delay(200); + + Serial.print("\nStart AsyncMultiWebServer on "); Serial.print(BOARD_NAME); + Serial.print(" with "); Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); + + delay(500); + +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); #endif - - eth.setDefault(); - - if (!eth.begin()) + + if (!Ethernet.waitForLocalIP(5000)) { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) { - delay(1000); + Serial.println(F("Ethernet cable is not connected.")); } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) + + // Stay here forever + while (true) { - Serial.print("."); - delay(1000); + delay(1); } } - - Serial.println(); + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); +#if USING_DHCP + delay(1000); +#else + delay(2000); #endif - - Serial.println(eth.localIP()); -} -void setup() -{ - Serial.begin(115200); - while (!Serial && millis() < 5000); - - delay(200); - - Serial.print("\nStart AsyncMultiWebServer on "); Serial.print(BOARD_NAME); - Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); - - initEthernet(); - for (serverIndex = 0; serverIndex < NUM_SERVERS; serverIndex++) { multiServer[serverIndex] = new AsyncWebServer(http_port[serverIndex]); diff --git a/examples/Async_AdvancedWebServer/Async_AdvancedWebServer.ino b/examples/Async_AdvancedWebServer/Async_AdvancedWebServer.ino index ff0dd36..9b211a7 100644 --- a/examples/Async_AdvancedWebServer/Async_AdvancedWebServer.ino +++ b/examples/Async_AdvancedWebServer/Async_AdvancedWebServer.ino @@ -1,12 +1,12 @@ /**************************************************************************************************************************** - Async_AdvancedWebServer.ino + Async_AdvancedWebServer.ino - Dead simple AsyncWebServer for Teensy41 QNEthernet - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license Copyright (c) 2015, Majenko Technologies @@ -38,17 +38,46 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************************************************************/ -#include "defines.h" +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 + +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif + +#define USING_DHCP true +//#define USING_DHCP false + +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; -#include +#include AsyncWebServer server(80); int reqCount = 0; // number of requests received +const int led = 13; + void handleRoot(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); #define BUFFER_SIZE 400 @@ -68,21 +97,21 @@ body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Col \ \ \ -

AsyncWebServer_Ethernet!

\ +

AsyncWebServer_Teensy41!

\

running on %s

\

Uptime: %d d %02d:%02d:%02d

\ \ \ -", BOARD_NAME, SHIELD_TYPE, day, hr % 24, min % 60, sec % 60); +", BOARD_NAME, BOARD_NAME, day, hr % 24, min % 60, sec % 60); request->send(200, "text/html", temp); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } void handleNotFound(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); String message = "File Not Found\n\n"; message += "URI: "; @@ -99,14 +128,14 @@ void handleNotFound(AsyncWebServerRequest *request) } request->send(404, "text/plain", message); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } void drawGraph(AsyncWebServerRequest *request) { String out; - out.reserve(3000); + out.reserve(4000); char temp[70]; out += "\n"; @@ -126,54 +155,11 @@ void drawGraph(AsyncWebServerRequest *request) request->send(200, "image/svg+xml", out); } -void initEthernet() -{ - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); -#endif - - eth.setDefault(); - - if (!eth.begin()) - { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) - { - delay(1000); - } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) - { - Serial.print("."); - delay(1000); - } - } - - Serial.println(); - -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); -#endif - - Serial.println(eth.localIP()); -} void setup() { - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LED_OFF); + pinMode(led, OUTPUT); + digitalWrite(led, 0); Serial.begin(115200); while (!Serial && millis() < 5000); @@ -182,10 +168,47 @@ void setup() Serial.print("\nStart Async_AdvancedWebServer on "); Serial.print(BOARD_NAME); Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); + + delay(500); + +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); +#endif + + if (!Ethernet.waitForLocalIP(5000)) + { + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) + { + Serial.println(F("Ethernet cable is not connected.")); + } + + // Stay here forever + while (true) + { + delay(1); + } + } + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } + +#if USING_DHCP + delay(1000); +#else + delay(2000); +#endif - initEthernet(); - server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { handleRoot(request); @@ -206,7 +229,7 @@ void setup() server.begin(); Serial.print(F("HTTP EthernetWebServer is @ IP : ")); - Serial.println(eth.localIP()); + Serial.println(Ethernet.localIP()); } void loop() diff --git a/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino b/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino index 6580df5..9e5db34 100644 --- a/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino +++ b/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino @@ -1,12 +1,12 @@ /**************************************************************************************************************************** - Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino - - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino - Dead simple AsyncWebServer for Teensy41 QNEthernet + + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license Copyright (c) 2015, Majenko Technologies @@ -38,24 +38,52 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************************************************************/ -#include "defines.h" +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 + +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif -#include +#define USING_DHCP true +//#define USING_DHCP false + +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; + +#include // In bytes -//#define STRING_SIZE 40000 -#define STRING_SIZE 12000 +#define STRING_SIZE 40000 AsyncWebServer server(80); int reqCount = 0; // number of requests received -#define BUFFER_SIZE 512 +const int led = 13; + +#define BUFFER_SIZE 768 char temp[BUFFER_SIZE]; void handleRoot(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); int sec = millis() / 1000; int min = sec / 60; @@ -72,21 +100,21 @@ body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Col \ \ \ -

AsyncWebServer_Ethernet!

\ +

AsyncWebServer_Teensy41!

\

running on %s

\

Uptime: %d d %02d:%02d:%02d

\ \ \ -", BOARD_NAME, SHIELD_TYPE, day, hr % 24, min % 60, sec % 60); +", BOARD_NAME, BOARD_NAME, day, hr % 24, min % 60, sec % 60); request->send(200, "text/html", temp); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } void handleNotFound(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); String message = "File Not Found\n\n"; message += "URI: "; @@ -103,18 +131,26 @@ void handleNotFound(AsyncWebServerRequest *request) } request->send(404, "text/plain", message); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); +} + +extern unsigned long _heap_start; +extern unsigned long _heap_end; +extern char *__brkval; + +int freeHeapSize() +{ + return ( (char *)&_heap_end - __brkval); } void PrintHeapData(String hIn) { - // cores/esp8266/Esp.cpp static uint32_t maxFreeHeap = 0xFFFFFFFF; // Get once at the beginning for comparison only - static uint32_t totalHeap = ESP.getFreeHeap(); + static uint32_t totalHeap = freeHeapSize(); - uint32_t freeHeap = ESP.getFreeHeap(); + uint32_t freeHeap = freeHeapSize(); // Print and update only when larger heap if (maxFreeHeap > freeHeap) @@ -135,8 +171,8 @@ void PrintStringSize(String & out) { static uint32_t count = 0; - // Print only when cStr length too large and corrupting memory or every (20 * 5) s - if ( (out.length() >= STRING_SIZE) || (++count > 20) ) + // Print only when cStr length too large and corrupting memory or every (12 * 5) s + if ( (out.length() >= STRING_SIZE) || (++count > 12) ) { Serial.print("\nOut String Length="); Serial.println(out.length()); @@ -150,14 +186,16 @@ void drawGraph(AsyncWebServerRequest *request) String out; out.reserve(STRING_SIZE); - char temp[70]; + char temp[240]; out += "\n"; - out += "\n"; + out += "\n"; out += "\n"; int y = rand() % 130; - for (int x = 10; x < 1500; x += 10) + // Won't work if using 5000 because of heap memory + //for (int x = 10; x < 5000; x += 10) + for (int x = 10; x < 1800; x += 10) { int y2 = rand() % 130; sprintf(temp, "\n", x, 140 - y, x + 10, 140 - y2); @@ -176,54 +214,10 @@ void drawGraph(AsyncWebServerRequest *request) PrintHeapData("Post Send"); } -void initEthernet() -{ - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); -#endif - - eth.setDefault(); - - if (!eth.begin()) - { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) - { - delay(1000); - } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) - { - Serial.print("."); - delay(1000); - } - } - - Serial.println(); - -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); -#endif - - Serial.println(eth.localIP()); -} - void setup() -{ - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LED_OFF); +{ + pinMode(led, OUTPUT); + digitalWrite(led, 0); Serial.begin(115200); while (!Serial && millis() < 5000); @@ -232,11 +226,48 @@ void setup() Serial.print("\nStart Async_AdvancedWebServer_MemoryIssues_SendArduinoString on "); Serial.print(BOARD_NAME); Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); PrintHeapData("Start =>"); - initEthernet(); + /////////////////////////////////// + +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); +#endif + + if (!Ethernet.waitForLocalIP(5000)) + { + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) + { + Serial.println(F("Ethernet cable is not connected.")); + } + + // Stay here forever + while (true) + { + delay(1); + } + } + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } + +#if USING_DHCP + delay(1000); +#else + delay(2000); +#endif /////////////////////////////////// @@ -260,10 +291,9 @@ void setup() server.begin(); Serial.print(F("HTTP EthernetWebServer is @ IP : ")); - Serial.println(eth.localIP()); + Serial.println(Ethernet.localIP()); PrintHeapData("Pre Create Arduino String"); - } void heartBeatPrint() diff --git a/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/Async_AdvancedWebServer_MemoryIssues_Send_CString.ino b/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/Async_AdvancedWebServer_MemoryIssues_Send_CString.ino index af57851..5ad8489 100644 --- a/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/Async_AdvancedWebServer_MemoryIssues_Send_CString.ino +++ b/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/Async_AdvancedWebServer_MemoryIssues_Send_CString.ino @@ -1,12 +1,12 @@ /**************************************************************************************************************************** - Async_AdvancedWebServer_MemoryIssues_Send_CString.ino - - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + Async_AdvancedWebServer_MemoryIssues_Send_CString.ino - Dead simple AsyncWebServer for Teensy41 QNEthernet + + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license Copyright (c) 2015, Majenko Technologies @@ -38,25 +38,54 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************************************************************/ -#include "defines.h" +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 -#include +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif + +#define USING_DHCP true +//#define USING_DHCP false + +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; + +#include char *cStr; // In bytes -#define CSTRING_SIZE 12000 +#define CSTRING_SIZE 40000 AsyncWebServer server(80); int reqCount = 0; // number of requests received +const int led = 13; + #define BUFFER_SIZE 768 // a little larger in case required for header shift (destructive send) char temp[BUFFER_SIZE]; void handleRoot(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); int sec = millis() / 1000; int min = sec / 60; @@ -73,21 +102,21 @@ body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Col \ \ \ -

AsyncWebServer_Ethernet!

\ +

AsyncWebServer_Teensy41!

\

running on %s

\

Uptime: %d d %02d:%02d:%02d

\ \ \ -", BOARD_NAME, SHIELD_TYPE, day, hr % 24, min % 60, sec % 60); +", BOARD_NAME, BOARD_NAME, day, hr % 24, min % 60, sec % 60); request->send(200, "text/html", temp); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } void handleNotFound(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); String message = "File Not Found\n\n"; message += "URI: "; @@ -104,18 +133,26 @@ void handleNotFound(AsyncWebServerRequest *request) } request->send(404, "text/plain", message); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); +} + +extern unsigned long _heap_start; +extern unsigned long _heap_end; +extern char *__brkval; + +int freeHeapSize() +{ + return ( (char *)&_heap_end - __brkval); } void PrintHeapData(String hIn) { - // cores/esp8266/Esp.cpp static uint32_t maxFreeHeap = 0xFFFFFFFF; // Get once at the beginning for comparison only - static uint32_t totalHeap = ESP.getFreeHeap(); + static uint32_t totalHeap = freeHeapSize(); - uint32_t freeHeap = ESP.getFreeHeap(); + uint32_t freeHeap = freeHeapSize(); // Print and update only when larger heap if (maxFreeHeap > freeHeap) @@ -134,8 +171,16 @@ void PrintHeapData(String hIn) void PrintStringSize(const char* cStr) { - Serial.print("\nOut String Length="); - Serial.println(strlen(cStr)); + static uint32_t count = 0; + + // Print only when cStr length too large and corrupting memory or every (12 * 5) s + if ( (strlen(cStr) >= CSTRING_SIZE) || (++count > 12) ) + { + Serial.print("\nOut String Length="); + Serial.println(strlen(cStr)); + + count = 0; + } } void drawGraph(AsyncWebServerRequest *request) @@ -145,11 +190,12 @@ void drawGraph(AsyncWebServerRequest *request) cStr[0] = '\0'; strcat(cStr, "\n"); - strcat(cStr, "\n"); + strcat(cStr, "\n"); strcat(cStr, "\n"); int y = rand() % 130; - for (int x = 10; x < 1500; x += 10) + //for (int x = 10; x < 1800; x += 10) + for (int x = 10; x < 5000; x += 10) { int y2 = rand() % 130; sprintf(temp, "\n", x, 140 - y, x + 10, 140 - y2); @@ -161,65 +207,20 @@ void drawGraph(AsyncWebServerRequest *request) PrintHeapData("Pre Send"); - // Print only when cStr length too large and corrupting memory - if ( (strlen(cStr) >= CSTRING_SIZE)) - { - PrintStringSize(cStr); - } + PrintStringSize(cStr); request->send(200, "image/svg+xml", cStr, false); - PrintHeapData("Post Send"); -} - -void initEthernet() -{ - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); + // Won't work if using this because of heap memory + //request->send(200, "image/svg+xml", cStr); -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); -#endif - - eth.setDefault(); - - if (!eth.begin()) - { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) - { - delay(1000); - } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) - { - Serial.print("."); - delay(1000); - } - } - - Serial.println(); - -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); -#endif - - Serial.println(eth.localIP()); + PrintHeapData("Post Send"); } void setup() { - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LED_OFF); + pinMode(led, OUTPUT); + digitalWrite(led, 0); Serial.begin(115200); while (!Serial && millis() < 5000); @@ -228,10 +229,10 @@ void setup() Serial.print("\nStart Async_AdvancedWebServer_MemoryIssues_Send_CString on "); Serial.print(BOARD_NAME); Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); PrintHeapData("Start =>"); - + cStr = (char *) malloc(CSTRING_SIZE); // make a little larger than required if (cStr == NULL) @@ -243,7 +244,42 @@ void setup() /////////////////////////////////// - initEthernet(); +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); +#endif + + if (!Ethernet.waitForLocalIP(5000)) + { + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) + { + Serial.println(F("Ethernet cable is not connected.")); + } + + // Stay here forever + while (true) + { + delay(1); + } + } + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } + +#if USING_DHCP + delay(1000); +#else + delay(2000); +#endif /////////////////////////////////// @@ -267,7 +303,7 @@ void setup() server.begin(); Serial.print(F("HTTP EthernetWebServer is @ IP : ")); - Serial.println(eth.localIP()); + Serial.println(Ethernet.localIP()); PrintHeapData("Pre Create Arduino String"); diff --git a/examples/Async_HelloServer/Async_HelloServer.ino b/examples/Async_HelloServer/Async_HelloServer.ino index 230854f..51955fd 100644 --- a/examples/Async_HelloServer/Async_HelloServer.ino +++ b/examples/Async_HelloServer/Async_HelloServer.ino @@ -1,31 +1,60 @@ /**************************************************************************************************************************** - Async_HelloServer.h + Async_HelloServer.h - Dead simple AsyncWebServer for Teensy41 QNEthernet - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license *****************************************************************************************************************************/ -#include "defines.h" +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 + +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif + +#define USING_DHCP true +//#define USING_DHCP false + +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; -#include +#include AsyncWebServer server(80); +const int led = 13; + void handleRoot(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); - request->send(200, "text/plain", String("Hello from AsyncWebServer_Ethernet on ") + BOARD_NAME ); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 1); + request->send(200, "text/plain", String("Hello from AsyncWebServer_Teensy41 on ") + BOARD_NAME ); + digitalWrite(led, 0); } void handleNotFound(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); String message = "File Not Found\n\n"; message += "URI: "; @@ -43,57 +72,13 @@ void handleNotFound(AsyncWebServerRequest *request) } request->send(404, "text/plain", message); - digitalWrite(LED_BUILTIN, LED_OFF); -} - -void initEthernet() -{ - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); -#endif - - eth.setDefault(); - - if (!eth.begin()) - { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) - { - delay(1000); - } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) - { - Serial.print("."); - delay(1000); - } - } - - Serial.println(); - -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); -#endif - - Serial.println(eth.localIP()); + digitalWrite(led, 0); } void setup() { - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LED_OFF); + pinMode(led, OUTPUT); + digitalWrite(led, 0); Serial.begin(115200); while (!Serial && millis() < 5000); @@ -102,10 +87,46 @@ void setup() Serial.print("\nStart Async_HelloServer on "); Serial.print(BOARD_NAME); Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); - initEthernet(); - + delay(500); + +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); +#endif + + if (!Ethernet.waitForLocalIP(5000)) + { + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) + { + Serial.println(F("Ethernet cable is not connected.")); + } + + // Stay here forever + while (true) + { + delay(1); + } + } + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } + +#if USING_DHCP + delay(1000); +#else + delay(2000); +#endif server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { handleRoot(request); @@ -121,7 +142,7 @@ void setup() server.begin(); Serial.print(F("HTTP EthernetWebServer is @ IP : ")); - Serial.println(eth.localIP()); + Serial.println(Ethernet.localIP()); } void loop() diff --git a/examples/Async_HelloServer2/Async_HelloServer2.ino b/examples/Async_HelloServer2/Async_HelloServer2.ino index e1273cd..921cced 100644 --- a/examples/Async_HelloServer2/Async_HelloServer2.ino +++ b/examples/Async_HelloServer2/Async_HelloServer2.ino @@ -1,31 +1,59 @@ /**************************************************************************************************************************** - Async_HelloServer.ino + Async_HelloServer.ino - Dead simple AsyncWebServer for Teensy41 QNEthernet - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license *****************************************************************************************************************************/ +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 + +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif -#include "defines.h" +#define USING_DHCP true +//#define USING_DHCP false -#include +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; + +#include AsyncWebServer server(80); +const int led = 13; + void handleRoot(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); - request->send(200, "text/plain", String("Hello from AsyncWebServer_Ethernet on ") + BOARD_NAME ); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 1); + request->send(200, "text/plain", String("Hello from AsyncWebServer_Teensy41 on ") + BOARD_NAME ); + digitalWrite(led, 0); } void handleNotFound(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); String message = "File Not Found\n\n"; message += "URI: "; @@ -43,69 +71,62 @@ void handleNotFound(AsyncWebServerRequest *request) } request->send(404, "text/plain", message); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } -void initEthernet() +void setup() { - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); + pinMode(led, OUTPUT); + digitalWrite(led, 0); -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); + Serial.begin(115200); + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print("\nStart Async_HelloServer2 on "); Serial.print(BOARD_NAME); + Serial.print(" with "); Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); + + delay(500); + +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); #endif - - eth.setDefault(); - - if (!eth.begin()) + + if (!Ethernet.waitForLocalIP(5000)) { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) { - delay(1000); + Serial.println(F("Ethernet cable is not connected.")); } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) + + // Stay here forever + while (true) { - Serial.print("."); - delay(1000); + delay(1); } } - - Serial.println(); + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); +#if USING_DHCP + delay(1000); +#else + delay(2000); #endif - - Serial.println(eth.localIP()); -} -void setup() -{ - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LED_OFF); - - Serial.begin(115200); - while (!Serial && millis() < 5000); - - delay(200); - - Serial.print("\nStart Async_HelloServer2 on "); Serial.print(BOARD_NAME); - Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); - - initEthernet(); - server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { handleRoot(request); @@ -144,7 +165,7 @@ void setup() server.begin(); Serial.print("HTTP EthernetWebServer started @ IP : "); - Serial.println(eth.localIP()); + Serial.println(Ethernet.localIP()); } void loop() diff --git a/examples/Async_HttpBasicAuth/Async_HttpBasicAuth.ino b/examples/Async_HttpBasicAuth/Async_HttpBasicAuth.ino index 793195e..0f3d484 100644 --- a/examples/Async_HttpBasicAuth/Async_HttpBasicAuth.ino +++ b/examples/Async_HttpBasicAuth/Async_HttpBasicAuth.ino @@ -1,81 +1,101 @@ /**************************************************************************************************************************** - Async_HttpBasicAuth.ino + Async_HttpBasicAuth.ino - Dead simple AsyncWebServer for Teensy41 QNEthernet - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license *****************************************************************************************************************************/ -#include "defines.h" +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 + +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif -#include +#define USING_DHCP true +//#define USING_DHCP false + +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; + +#include AsyncWebServer server(80); const char* www_username = "admin"; const char* www_password = "ethernet"; -void initEthernet() +void setup() { - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); + Serial.begin(115200); + while (!Serial && millis() < 5000); -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); + delay(200); + + Serial.print("\nStart Async_HTTPBasicAuth on "); Serial.print(BOARD_NAME); + Serial.print(" with "); Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); + + delay(500); + +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); #endif - - eth.setDefault(); - - if (!eth.begin()) + + if (!Ethernet.waitForLocalIP(5000)) { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) { - delay(1000); + Serial.println(F("Ethernet cable is not connected.")); } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) + + // Stay here forever + while (true) { - Serial.print("."); - delay(1000); + delay(1); } } - - Serial.println(); + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); +#if USING_DHCP + delay(1000); +#else + delay(2000); #endif - - Serial.println(eth.localIP()); -} -void setup() -{ - Serial.begin(115200); - while (!Serial && millis() < 5000); - - delay(200); - - Serial.print("\nStart Async_HttpBasicAuth on "); Serial.print(BOARD_NAME); - Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); - - initEthernet(); - server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { if (!request->authenticate(www_username, www_password)) @@ -86,13 +106,14 @@ void setup() request->send(200, "text/plain", "Login OK"); }); + server.begin(); Serial.print(F("Async_HttpBasicAuth started @ IP : ")); - Serial.println(eth.localIP()); + Serial.println(Ethernet.localIP()); Serial.print(F("Open http://")); - Serial.print(eth.localIP()); + Serial.print(Ethernet.localIP()); Serial.println(F("/ in your browser to see it working")); Serial.print(F("Login using username = ")); diff --git a/examples/Async_PostServer/Async_PostServer.ino b/examples/Async_PostServer/Async_PostServer.ino index db71515..46857e3 100644 --- a/examples/Async_PostServer/Async_PostServer.ino +++ b/examples/Async_PostServer/Async_PostServer.ino @@ -1,25 +1,54 @@ /**************************************************************************************************************************** - Async_PostServer.ino + Async_PostServer.ino - Dead simple AsyncWebServer for Teensy41 QNEthernet - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license *****************************************************************************************************************************/ -#include "defines.h" +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 -#include +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif + +#define USING_DHCP true +//#define USING_DHCP false + +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; + +#include AsyncWebServer server(80); +const int led = 13; + const String postForms = "\ \ -AsyncWebServer_Ethernet POST handling\ +AsyncWebServer POST handling\ \ @@ -40,24 +69,24 @@ body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Col void handleRoot(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); request->send(200, "text/html", postForms); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } void handlePlain(AsyncWebServerRequest *request) { if (request->method() != HTTP_POST) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); request->send(405, "text/plain", "Method Not Allowed"); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } else { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); request->send(200, "text/plain", "POST body was:\n" + request->arg("plain")); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } } @@ -65,26 +94,26 @@ void handleForm(AsyncWebServerRequest *request) { if (request->method() != HTTP_POST) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); request->send(405, "text/plain", "Method Not Allowed"); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } else { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); String message = "POST form was:\n"; for (uint8_t i = 0; i < request->args(); i++) { message += " " + request->argName(i) + ": " + request->arg(i) + "\n"; } request->send(200, "text/plain", message); - digitalWrite(LED_BUILTIN, LED_OFF); + digitalWrite(led, 0); } } void handleNotFound(AsyncWebServerRequest *request) { - digitalWrite(LED_BUILTIN, LED_ON); + digitalWrite(led, 1); String message = "File Not Found\n\n"; message += "URI: "; message += request->url(); @@ -98,57 +127,13 @@ void handleNotFound(AsyncWebServerRequest *request) message += " " + request->argName(i) + ": " + request->arg(i) + "\n"; } request->send(404, "text/plain", message); - digitalWrite(LED_BUILTIN, LED_OFF); -} - -void initEthernet() -{ - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); -#endif - - eth.setDefault(); - - if (!eth.begin()) - { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) - { - delay(1000); - } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) - { - Serial.print("."); - delay(1000); - } - } - - Serial.println(); - -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); -#endif - - Serial.println(eth.localIP()); + digitalWrite(led, 0); } void setup() { - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LED_OFF); + pinMode(led, OUTPUT); + digitalWrite(led, 0); Serial.begin(115200); while (!Serial && millis() < 5000); @@ -157,20 +142,59 @@ void setup() Serial.print("\nStart Async_PostServer on "); Serial.print(BOARD_NAME); Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); + + delay(500); + +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); +#endif + + if (!Ethernet.waitForLocalIP(5000)) + { + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) + { + Serial.println(F("Ethernet cable is not connected.")); + } + + // Stay here forever + while (true) + { + delay(1); + } + } + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } - initEthernet(); +#if USING_DHCP + delay(1000); +#else + delay(2000); +#endif server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { handleRoot(request); }); + //server.on("/postplain/", handlePlain); server.on("/postplain/", HTTP_POST, [](AsyncWebServerRequest * request) { handlePlain(request); }); + //server.on("/postform/", handleForm); server.on("/postform/", HTTP_POST, [](AsyncWebServerRequest * request) { handleForm(request); @@ -181,7 +205,7 @@ void setup() server.begin(); Serial.print(F("HTTP EthernetWebServer started @ IP : ")); - Serial.println(eth.localIP()); + Serial.println(Ethernet.localIP()); } void loop() diff --git a/examples/Async_RegexPatterns/Async_RegexPatterns.ino b/examples/Async_RegexPatterns/Async_RegexPatterns.ino index e2cce5c..fef2c3a 100644 --- a/examples/Async_RegexPatterns/Async_RegexPatterns.ino +++ b/examples/Async_RegexPatterns/Async_RegexPatterns.ino @@ -1,12 +1,12 @@ /**************************************************************************************************************************** - Async_RegexPatterns.ino + Async_RegexPatterns.ino - Dead simple AsyncWebServer for Teensy41 QNEthernet - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license *****************************************************************************************************************************/ @@ -30,9 +30,36 @@ #define ASYNCWEBSERVER_REGEX true -#include "defines.h" +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 + +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif + +#define USING_DHCP true +//#define USING_DHCP false + +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; -#include +#include AsyncWebServer server(80); @@ -43,62 +70,53 @@ void notFound(AsyncWebServerRequest *request) request->send(404, "text/plain", "Not found"); } -void initEthernet() +void setup() { - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); + Serial.begin(115200); + while (!Serial && millis() < 5000); -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); + Serial.print("\nStart Async_RegexPatterns on "); Serial.print(BOARD_NAME); + Serial.print(" with "); Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); + + delay(500); + +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); #endif - - eth.setDefault(); - - if (!eth.begin()) + + if (!Ethernet.waitForLocalIP(5000)) { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) { - delay(1000); + Serial.println(F("Ethernet cable is not connected.")); } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) + + // Stay here forever + while (true) { - Serial.print("."); - delay(1000); + delay(1); } } - - Serial.println(); + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); +#if USING_DHCP + delay(1000); +#else + delay(2000); #endif - - Serial.println(eth.localIP()); -} - -void setup() -{ - Serial.begin(115200); - while (!Serial && millis() < 5000); - - delay(200); - - Serial.print("\nStart Async_RegexPatterns on "); Serial.print(BOARD_NAME); - Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); - - initEthernet(); server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { @@ -125,7 +143,7 @@ void setup() server.begin(); Serial.print("Server started @ "); - Serial.println(eth.localIP()); + Serial.println(Ethernet.localIP()); } void loop() diff --git a/examples/Async_SimpleWebServer/Async_SimpleWebServer.ino b/examples/Async_SimpleWebServer/Async_SimpleWebServer.ino index 81dd798..52a1538 100644 --- a/examples/Async_SimpleWebServer/Async_SimpleWebServer.ino +++ b/examples/Async_SimpleWebServer/Async_SimpleWebServer.ino @@ -1,12 +1,12 @@ /**************************************************************************************************************************** - Async_SimpleWebServer.ino + Async_SimpleWebServer.ino - Dead simple AsyncWebServer for Teensy41 QNEthernet - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license *****************************************************************************************************************************/ @@ -17,9 +17,36 @@ // * handle missing pages / 404s // -#include "defines.h" +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported +#endif + +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 + +#define SHIELD_TYPE "Teensy4.1 QNEthernet" + +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif + +#define USING_DHCP true +//#define USING_DHCP false + +#if !USING_DHCP + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); +#endif + +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; -#include +#include AsyncWebServer server(80); @@ -30,66 +57,57 @@ void notFound(AsyncWebServerRequest *request) request->send(404, "text/plain", "Not found"); } -void initEthernet() +void setup() { - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); + Serial.begin(115200); + while (!Serial && millis() < 5000); -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); + Serial.print("\nStart Async_SimpleWebServer on "); Serial.print(BOARD_NAME); + Serial.print(" with "); Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); + + delay(500); + +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); #endif - - eth.setDefault(); - - if (!eth.begin()) + + if (!Ethernet.waitForLocalIP(5000)) { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) { - delay(1000); + Serial.println(F("Ethernet cable is not connected.")); } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) + + // Stay here forever + while (true) { - Serial.print("."); - delay(1000); + delay(1); } } - - Serial.println(); + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); +#if USING_DHCP + delay(1000); +#else + delay(2000); #endif - - Serial.println(eth.localIP()); -} - -void setup() -{ - Serial.begin(115200); - while (!Serial && millis() < 5000); - - delay(200); - Serial.print("\nStart Async_AdvancedWebServer on "); Serial.print(BOARD_NAME); - Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); - - initEthernet(); - server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { - request->send(200, "text/plain", "Hello, world from " SHIELD_TYPE); + request->send(200, "text/plain", "Hello, world from Teensy 4.1 and QNEthernet"); }); // Send a GET request to /get?message= @@ -131,7 +149,7 @@ void setup() server.begin(); Serial.print("Server started @ "); - Serial.println(eth.localIP()); + Serial.println(Ethernet.localIP()); } void loop() diff --git a/examples/WebClientRepeating/WebClientRepeating.ino b/examples/WebClientRepeating/WebClientRepeating.ino index 374cb65..e85f462 100644 --- a/examples/WebClientRepeating/WebClientRepeating.ino +++ b/examples/WebClientRepeating/WebClientRepeating.ino @@ -1,26 +1,24 @@ /**************************************************************************************************************************** - WebClientRepeating.ino + WebClientRepeating.ino - Dead simple AsyncWebServer for Teensy41 QNEthernet - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license *****************************************************************************************************************************/ #include "defines.h" -#include - -char server[] = "arduino.tips"; +char server[] = "arduino.cc"; unsigned long lastConnectionTime = 0; // last time you connected to the server, in milliseconds -const unsigned long postingInterval = 60000L; // delay between updates, in milliseconds +const unsigned long postingInterval = 10000L; // delay between updates, in milliseconds // Initialize the Web client object -TCPClient client; +EthernetClient client; // this method makes a HTTP connection to the server void httpRequest() @@ -38,7 +36,7 @@ void httpRequest() // send the HTTP PUT request client.println(F("GET /asciilogo.txt HTTP/1.1")); - client.println(F("Host: arduino.tips")); + client.println(F("Host: arduino.cc")); client.println(F("Connection: close")); client.println(); @@ -52,64 +50,54 @@ void httpRequest() } } -void initEthernet() +void setup() { - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); + // Open serial communications and wait for port to open: + Serial.begin(115200); + while (!Serial && millis() < 5000); + + Serial.print("\nStart WebClientRepeating on "); Serial.print(BOARD_NAME); + Serial.print(" with "); Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WEBSERVER_TEENSY41_VERSION); + + delay(500); -#if !USING_DHCP - eth.config(localIP, gateway, netMask, gateway); +#if USING_DHCP + // Start the Ethernet connection, using DHCP + Serial.print("Initialize Ethernet using DHCP => "); + Ethernet.begin(); +#else + // Start the Ethernet connection, using static IP + Serial.print("Initialize Ethernet using static IP => "); + Ethernet.begin(myIP, myNetmask, myGW); + Ethernet.setDNSServerIP(mydnsServer); #endif - - eth.setDefault(); - - if (!eth.begin()) + + if (!Ethernet.waitForLocalIP(5000)) { - Serial.println("No Ethernet hardware ... Stop here"); - - while (true) + Serial.println(F("Failed to configure Ethernet")); + + if (!Ethernet.linkStatus()) { - delay(1000); + Serial.println(F("Ethernet cable is not connected.")); } - } - else - { - Serial.print("Connecting to network : "); - - while (!eth.connected()) + + // Stay here forever + while (true) { - Serial.print("."); - delay(1000); + delay(1); } } - - Serial.println(); - -#if USING_DHCP - Serial.print("Ethernet DHCP IP address: "); -#else - Serial.print("Ethernet Static IP address: "); -#endif - - Serial.println(eth.localIP()); -} - -void setup() -{ - Serial.begin(115200); - while (!Serial && millis() < 5000); - - delay(200); - - Serial.print("\nStart WebClientRepeating on "); Serial.print(BOARD_NAME); - Serial.print(" with "); Serial.println(SHIELD_TYPE); - Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); - - initEthernet(); + else + { + Serial.print(F("Connected! IP address:")); Serial.println(Ethernet.localIP()); + } +#if USING_DHCP + delay(1000); +#else delay(2000); +#endif } void loop() diff --git a/examples/WebClientRepeating/defines.h b/examples/WebClientRepeating/defines.h index eeebd1d..a6d5d0e 100644 --- a/examples/WebClientRepeating/defines.h +++ b/examples/WebClientRepeating/defines.h @@ -1,73 +1,50 @@ /**************************************************************************************************************************** defines.h - For ESP8266 using W5x00/ENC8266 Ethernet - - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + Dead simple AsyncWebServer for Teensy41 QNEthernet + + For Teensy41 with QNEthernet + + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Licensed under GPLv3 license - ***************************************************************************************************************************************/ + *****************************************************************************************************************************/ + #ifndef defines_h #define defines_h -#if defined(ESP8266) - #define LED_ON LOW - #define LED_OFF HIGH -#else - #error Only ESP8266 -#endif - -#define _AWS_ETHERNET_LOGLEVEL_ 2 - -////////////////////////////////////////////////////////// - -#define USING_W5500 true -#define USING_W5100 false -#define USING_ENC28J60 false - -#include - -#define CSPIN 16 // 5 - -#if USING_W5500 - #include "W5500lwIP.h" - #define SHIELD_TYPE "ESP8266_W5500 Ethernet" - - Wiznet5500lwIP eth(CSPIN); - -#elif USING_W5100 - #include - #define SHIELD_TYPE "ESP8266_W5100 Ethernet" - - Wiznet5100lwIP eth(CSPIN); - -#elif USING_ENC28J60 - #include - #define SHIELD_TYPE "ESP8266_ENC28J60 Ethernet" - - ENC28J60lwIP eth(CSPIN); -#else - // default if none selected - #include "W5500lwIP.h" - - Wiznet5500lwIP eth(CSPIN); +#if !( defined(CORE_TEENSY) && defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) ) + #error Only Teensy 4.1 supported #endif -#include // WiFiClient (-> TCPClient) +// Debug Level from 0 to 4 +#define _TEENSY41_ASYNC_TCP_LOGLEVEL_ 1 +#define _AWS_TEENSY41_LOGLEVEL_ 1 -using TCPClient = WiFiClient; +#define SHIELD_TYPE "Teensy4.1 QNEthernet" -////////////////////////////////////////////////////////// +#if (_AWS_TEENSY41_LOGLEVEL_ > 3) + #warning Using QNEthernet lib for Teensy 4.1. Must also use Teensy Packages Patch or error +#endif -#define USING_DHCP true +#define USING_DHCP true +//#define USING_DHCP false #if !USING_DHCP - IPAddress localIP(192, 168, 2, 222); - IPAddress gateway(192, 168, 2, 1); - IPAddress netMask(255, 255, 255, 0); + // Set the static IP address to use if the DHCP fails to assign + IPAddress myIP(192, 168, 2, 222); + IPAddress myNetmask(255, 255, 255, 0); + IPAddress myGW(192, 168, 2, 1); + //IPAddress mydnsServer(192, 168, 2, 1); + IPAddress mydnsServer(8, 8, 8, 8); #endif +#include "QNEthernet.h" // https://github.com/ssilverman/QNEthernet +using namespace qindesign::network; + +#include + #endif //defines_h diff --git a/library.json b/library.json index 4aabda2..9015cc2 100644 --- a/library.json +++ b/library.json @@ -1,8 +1,8 @@ { - "name":"AsyncWebServer_Ethernet", - "version": "1.5.0", - "description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet. This is Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library. Now supporting using CString to save heap to send very large data", - "keywords":"http, async, async-webserver, websocket, webserver, esp8266, w5x00, enc28j60", + "name":"AsyncWebServer_Teensy41", + "version": "1.6.0", + "description":"Asynchronous HTTP and WebSocket Server Library for Teensy 4.1 using QNEthernet. This library is one of the current or future Async libraries to support Teensy 4.1 using QNEthernet, such as AsyncHTTPRequest_Generic, AsyncHTTPSRequest_Generic, AsyncMQTT_Generic, Teensy41_AsyncWebServer, Teensy41_AsyncUDP, Teensy41_AsyncDNSServer, AsyncWebServer_Teensy41_SSL, etc. Now supporting using CString to save heap to send very large data", + "keywords":"async, tcp, http, websocket, webserver, async-tcp, async-http, async-webserver, async-websocket, teensy, teensy41, teensy-41, qnethernet, lwip", "authors": [ { @@ -12,27 +12,35 @@ { "name": "Khoi Hoang", "url": "https://github.com/khoih-prog", - "email": "khoih.prog@gmail.com", + "email": "khoih-dot-prog@gmail.com", "maintainer": true - } + } ], "repository": { "type": "git", - "url": "https://github.com/khoih-prog/AsyncWebServer_Ethernet" + "url": "https://github.com/khoih-prog/AsyncWebServer_Teensy41.git" }, "dependencies": [ { - "owner": "me-no-dev", - "name": "ESPAsyncTCP", - "version": ">=1.2.2", - "platforms": ["espressif8266"] + "owner": "khoih-prog", + "name": "Teensy41_AsyncTCP", + "version": ">=1.1.0", + "platforms": ["teensy", "avr"] } ], - "license": "LGPL-3.0", - "frameworks": "arduino", - "platforms": ["espressif8266"], + "homepage": "https://github.com/khoih-prog/AsyncWebServer_Teensy41", + "export": { + "exclude": [ + "linux", + "extras", + "tests" + ] + }, + "license": "GPL-3.0", + "frameworks": "*", + "platforms": ["teensy", "avr"], "examples": "examples/*/*/*.ino", - "headers": "AsyncWebServer_Ethernet.h" + "headers": ["AsyncWebServer_Teensy41.h", "AsyncWebServer_Teensy41.hpp"] } diff --git a/library.properties b/library.properties index 0a8e377..e9d3285 100644 --- a/library.properties +++ b/library.properties @@ -1,12 +1,12 @@ -name=AsyncWebServer_Ethernet -version=1.5.0 -author=Hristo Gochkov,Khoi Hoang -maintainer=Khoi Hoang -sentence=Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet -paragraph=This is Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library. Now supporting using CString to save heap to send very large data +name=AsyncWebServer_Teensy41 +version=1.6.0 +author=Hristo Gochkov, Khoi Hoang +maintainer=Khoi Hoang +sentence=Asynchronous HTTP and WebSocket Server Library for Teensy 4.1 using QNEthernet +paragraph=This library is one of the current or future Async libraries to support Teensy 4.1 using QNEthernet, such as AsyncHTTPRequest_Generic, AsyncHTTPSRequest_Generic, AsyncMQTT_Generic, Teensy41_AsyncWebServer, Teensy41_AsyncUDP, Teensy41_AsyncDNSServer, AsyncWebServer_Teensy41_SSL, etc. Now supporting using CString to save heap to send very large data category=Communication -url=https://github.com/khoih-prog/AsyncWebServer_Ethernet -license=GPLv3 -architectures=esp8266 -depends=ESPAsyncTCP -includes=AsyncWebServer_Ethernet.h +url=https://github.com/khoih-prog/AsyncWebServer_Teensy41 +license=LGPLv3 +architectures=teensy, avr +depends=Teensy41_AsyncTCP +includes=AsyncWebServer_Teensy41.h, AsyncWebServer_Teensy41.hpp diff --git a/pics/Async_AdvancedWebServer_MemoryIssues_Send_CString.png b/pics/Async_AdvancedWebServer_MemoryIssues_Send_CString.png new file mode 100644 index 0000000000000000000000000000000000000000..4e51b453c0b7d23e66edaf179c6cedc0b4428680 GIT binary patch literal 70959 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%0DEez4r3(tVcE0_53T&ec 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 literal 0 HcmV?d00001 diff --git a/platformio/platformio.ini b/platformio/platformio.ini index e8768d9..62c8002 100644 --- a/platformio/platformio.ini +++ b/platformio/platformio.ini @@ -11,17 +11,10 @@ [platformio] ; ============================================================ ; chose environment: -; ESP8266 -; ESP32 -; SAMD -; NRF52 -; STM32 +; teensy + ; ============================================================ -default_envs = ESP8266 -;default_envs = ESP32 -;default_envs = SAMD -;default_envs = NRF52 -;default_envs = STM32 +default_envs = teensy [env] ; ============================================================ @@ -39,316 +32,27 @@ lib_compat_mode = strict lib_ldf_mode = chain+ ;lib_ldf_mode = deep+ -lib_deps = - -build_flags = -; set your debug output (default=Serial) -; -D DEBUG_ESP_PORT=Serial -; comment the folowing line to enable WiFi debugging -; -D NDEBUG - -[env:ESP8266] -platform = espressif8266 -framework = arduino - -; PlatformIO 4.x -; ESPAsyncTCP@>=1.2.2 - +lib_deps = +; PlatformIO 4.x +; Teensy41_AsyncTCP@>=1.1.0 +; ; PlatformIO 5.x - me-no-dev/ESPAsyncTCP@>=1.2.2 - -; ============================================================ -; Board configuration -; choose your board by uncommenting one of the following lines -; ============================================================ -;board = gen4iod -;board = huzzah -;board = oak -;board = esp_wroom_02 -;board = espduino -;board = espectro -;board = espino -;board = espresso_lite_v1 -;board = espresso_lite_v2 -;board = esp12e -;board = esp01_1m -;board = esp01 -;board = esp07 -;board = esp8285 -;board = heltec_wifi_kit_8 -;board = inventone -;board = nodemcu -board = nodemcuv2 -;board = modwifi -;board = phoenix_v1 -;board = phoenix_v2 -;board = sparkfunBlynk -;board = thing -;board = thingdev -;board = esp210 -;board = espinotee -;board = d1 -;board = d1_mini -;board = d1_mini_lite -;board = d1_mini_pro -;board = wifi_slot -;board = wifiduino -;board = wifinfo -;board = wio_link -;board = wio_node -;board = xinabox_cw01 -;board = esp32doit-devkit-v1 - -[env:ESP32] -platform = espressif32 -framework = arduino + khoih-prog/Teensy41_AsyncTCP@>=1.1.0 -; PlatformIO 4.x -; AsyncTCP@>=1.1.1 - -; PlatformIO 5.x -; me-no-dev/AsyncTCP@>=1.1.1 -; ============================================================ -; Board configuration -; choose your board by uncommenting one of the following lines -; ============================================================ -;board = esp32cam -;board = alksesp32 -;board = featheresp32 -;board = espea32 -;board = bpi-bit -;board = d-duino-32 -board = esp32doit-devkit-v1 -;board = pocket_32 -;board = fm-devkit -;board = pico32 -;board = esp32-evb -;board = esp32-gateway -;board = esp32-pro -;board = esp32-poe -;board = oroca_edubot -;board = onehorse32dev -;board = lopy -;board = lopy4 -;board = wesp32 -;board = esp32thing -;board = sparkfun_lora_gateway_1-channel -;board = ttgo-lora32-v1 -;board = ttgo-t-beam -;board = turta_iot_node -;board = lolin_d32 -;board = lolin_d32_pro -;board = lolin32 -;board = wemosbat -;board = widora-air -;board = xinabox_cw02 -;board = iotbusio -;board = iotbusproteus -;board = nina_w10 - -[env:SAMD] -platform = atmelsam -framework = arduino -; ============================================================ -; Choose your board by uncommenting one of the following lines -; ============================================================ -; ============================================================ -; Board configuration Adafruit SAMD -; ============================================================ - -;board = adafruit_feather_m0 -;board = adafruit_feather_m0_express -;board = adafruit_metro_m0 -;board = adafruit_circuitplayground_m0 -;board = adafruit_gemma_m0 -;board = adafruit_trinket_m0 -;board = adafruit_itsybitsy_m0 -;board = adafruit_pirkey -;board = adafruit_hallowing -;board = adafruit_crickit_m0 -;board = adafruit_metro_m4 -;board = adafruit_grandcentral_m4 -board = adafruit_itsybitsy_m4 -;board = adafruit_feather_m4 -;board = adafruit_trellis_m4 -;board = adafruit_pyportal_m4 -;board = adafruit_pyportal_m4_titano -;board = adafruit_pybadge_m4 -;board = adafruit_metro_m4_airliftlite -;board = adafruit_pygamer_m4 -;board = adafruit_pygamer_advance_m4 -;board = adafruit_pybadge_airlift_m4 -;board = adafruit_monster_m4sk -;board = adafruit_hallowing_m4 - -; ============================================================ -; Board configuration Arduino SAMD and SAM -; ============================================================ - -;board = arduino_zero_edbg -;board = arduino_zero_native -;board = mkr1000 -;board = mkrzero -;board = mkrwifi1010 -;board = nano_33_iot -;board = mkrfox1200 -;board = mkrwan1300 -;board = mkrwan1310 -;board = mkrgsm1400 -;board = mkrnb1500 -;board = mkrvidor4000 -;board = adafruit_circuitplayground_m0 -;board = mzero_pro_bl_dbg -;board = mzero_pro_bl -;board = mzero_bl -;board = tian -;board = tian_cons -;board = arduino_due_x_dbg -;board = arduino_due_x - -; ============================================================ -; Board configuration Seeeduino SAMD -; ============================================================ - -;board = seeed_wio_terminal -;board = Seeed_femto_m0 -;board = seeed_XIAO_m0 -;board = Wio_Lite_MG126 -;board = WioGPS -;board = zero -;board = rolawan -;board = seeed_grove_ui_wireless - - -[env:NRF52] -platform = nordicnrf52 -framework = arduino -; ============================================================ -; Board configuration Adafruit nRF52 -; choose your board by uncommenting one of the following lines -; ============================================================ -;board = feather52832 -board = feather52840 -;board = feather52840sense -;board = itsybitsy52840 -;board = cplaynrf52840 -;board = cluenrf52840 -;board = metro52840 -;board = pca10056 -;board = particle_xenon -;board = mdbt50qrx -;board = ninab302 -;board = ninab112 - +build_flags = +; set your build_flags + [env:STM32] -platform = ststm32 +platform = teensy framework = arduino ; ============================================================ ; Choose your board by uncommenting one of the following lines ; ============================================================ -; ============================================================ -; Board configuration Nucleo-144 -; ============================================================ - -;board = nucleo_f207zg -;board = nucleo_f429zi -;board = nucleo_f746zg -;board = nucleo_f756zg -;board = nucleo_f767zi -;board = nucleo_h743zi -;board = nucleo_l496zg -;board = nucleo_l496zg-p -;board = nucleo_l4r5zi -;board = nucleo_l4r5zi-p - -; ============================================================ -; Board configuration Nucleo-64 -; ============================================================ - -;board = nucleo_f030r8 -;board = nucleo_f072rb - -;board = nucleo_f091rc -;board = nucleo_f103rb -;board = nucleo_f302r8 -;board = nucleo_f303re -;board = nucleo_f401re -;board = nucleo_f411re -;board = nucleo_f446re -;board = nucleo_g071rb -;board = nucleo_g431rb -;board = nucleo_g474re -;board = nucleo_l053r8 -;board = nucleo_l073rz -;board = nucleo_l152re -;board = nucleo_l433rc_p -;board = nucleo_l452re -;board = nucleo_l452re-p -;board = nucleo_l476rg -;board = pnucleo_wb55rg - -; ============================================================ -; Board configuration Nucleo-32 -; ============================================================ - -;board = nucleo_f031k6 -;board = nucleo_l031k6 -;board = nucleo_l412kb -;board = nucleo_l432lc -;board = nucleo_f303k8 -;board = nucleo_g431kb - -; ============================================================ -; Board configuration Discovery Boards -; ============================================================ - -;board = disco_f030r8 -;board = disco_f072rb -;board = disco_f030r8 -;board = disco_f100rb -;board = disco_f407vg -;board = disco_f413zh -;board = disco_f746ng -;board = disco_g0316 -;board = disco_l475vg_iot -;board = disco_f072cz-lrwan1 - -; ============================================================ -; Board configuration STM32MP1 Boards -; ============================================================ - -;board = stm32mp157a-dk1 -;board = stm32mp157c-dk2 - -; ============================================================ -; Board configuration Generic Boards -; ============================================================ - -;board = bluepill_f103c6 -;board = bluepill_f103c8 -;board = blackpill_f103c8 -;board = stm32f103cx -;board = stm32f103rx -;board = stm32f103tx -;board = stm32f103vx -;board = stm32f103zx -;board = stm32f103zet6 -;board = maplemini_f103cb -;board = blackpill_f303cc -;board = black_f407ve -;board = black_f407vg -;board = black_f407ze -;board = black_f407zg -;board = blue_f407ve_mini -;board = blackpill_f401cc -;board = blackpill_f411ce -;board = coreboard_f401rc -;board = feather_f405 - ; ============================================================ ; Board configuration Many more Boards to be filled ; ============================================================ + diff --git a/src/AsyncEventSource_Teensy41.cpp b/src/AsyncEventSource_Teensy41.cpp index 4785812..fb91b3e 100644 --- a/src/AsyncEventSource_Teensy41.cpp +++ b/src/AsyncEventSource_Teensy41.cpp @@ -14,24 +14,30 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_TEENSY41_LOGLEVEL_ 1 +#if !defined(_AWS_TEENSY41_LOGLEVEL_) + #define _AWS_TEENSY41_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Teensy41_Debug.h" #include "Arduino.h" #include "AsyncEventSource_Teensy41.h" +///////////////////////////////////////////////////////// + static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) { String ev = ""; @@ -146,6 +152,9 @@ static String generateEventMessage(const char *message, const char *event, uint3 return ev; } +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + // Message AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) @@ -164,15 +173,19 @@ AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) } } +///////////////////////////////////////////////////////// + AsyncEventSourceMessage::~AsyncEventSourceMessage() { if (_data != NULL) free(_data); } +///////////////////////////////////////////////////////// + size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { - (void)time; + AWS_TEENSY41_UNUSED(time); // If the whole message is now acked... if (_acked + len > _len) @@ -190,6 +203,8 @@ size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) return 0; } +///////////////////////////////////////////////////////// + size_t AsyncEventSourceMessage::send(AsyncClient *client) { const size_t len = _len - _sent; @@ -209,6 +224,9 @@ size_t AsyncEventSourceMessage::send(AsyncClient *client) return sent; } +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + // Client AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) @@ -228,13 +246,13 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A _client->onError(NULL, NULL); _client->onAck([](void *r, AsyncClient * c, size_t len, uint32_t time) { - (void)c; + AWS_TEENSY41_UNUSED(c); ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); _client->onPoll([](void *r, AsyncClient * c) { - (void)c; + AWS_TEENSY41_UNUSED(c); ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); @@ -256,12 +274,16 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A delete request; } +///////////////////////////////////////////////////////// + AsyncEventSourceClient::~AsyncEventSourceClient() { _messageQueue.free(); close(); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage) { if (dataMessage == NULL) @@ -287,6 +309,8 @@ void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage) _runQueue(); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::_onAck(size_t len, uint32_t time) { while (len && !_messageQueue.isEmpty()) @@ -300,6 +324,8 @@ void AsyncEventSourceClient::_onAck(size_t len, uint32_t time) _runQueue(); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::_onPoll() { if (!_messageQueue.isEmpty()) @@ -308,36 +334,48 @@ void AsyncEventSourceClient::_onPoll() } } +///////////////////////////////////////////////////////// void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) { _client->close(true); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::_onDisconnect() { _client = NULL; _server->_handleDisconnect(this); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::close() { if (_client != NULL) _client->close(); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::write(const char * message, size_t len) { _queueMessage(new AsyncEventSourceMessage(message, len)); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) { String ev = generateEventMessage(message, event, id, reconnect); _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); } -void AsyncEventSourceClient::_runQueue() { +///////////////////////////////////////////////////////// + +void AsyncEventSourceClient::_runQueue() +{ while (!_messageQueue.isEmpty() && _messageQueue.front()->finished()) { _messageQueue.remove(_messageQueue.front()); @@ -350,6 +388,8 @@ void AsyncEventSourceClient::_runQueue() { } } +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// // Handler @@ -362,42 +402,39 @@ AsyncEventSource::AsyncEventSource(const String& url) , _connectcb(NULL) {} +///////////////////////////////////////////////////////// + AsyncEventSource::~AsyncEventSource() { close(); } +///////////////////////////////////////////////////////// + void AsyncEventSource::onConnect(ArEventHandlerFunction cb) { _connectcb = cb; } +///////////////////////////////////////////////////////// + void AsyncEventSource::_addClient(AsyncEventSourceClient * client) { - /*char * temp = (char *)malloc(2054); - if(temp != NULL){ - memset(temp+1,' ',2048); - temp[0] = ':'; - temp[2049] = '\r'; - temp[2050] = '\n'; - temp[2051] = '\r'; - temp[2052] = '\n'; - temp[2053] = 0; - client->write((const char *)temp, 2053); - free(temp); - }*/ - _clients.add(client); if (_connectcb) _connectcb(client); } +///////////////////////////////////////////////////////// + void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client) { _clients.remove(client); } +///////////////////////////////////////////////////////// + void AsyncEventSource::close() { for (const auto &c : _clients) @@ -407,6 +444,8 @@ void AsyncEventSource::close() } } +///////////////////////////////////////////////////////// + // pmb fix size_t AsyncEventSource::avgPacketsWaiting() const { @@ -418,7 +457,8 @@ size_t AsyncEventSource::avgPacketsWaiting() const for (const auto &c : _clients) { - if (c->connected()) { + if (c->connected()) + { aql += c->packetsWaiting(); ++nConnectedClients; } @@ -428,6 +468,8 @@ size_t AsyncEventSource::avgPacketsWaiting() const return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up } +///////////////////////////////////////////////////////// + void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) { String ev = generateEventMessage(message, event, id, reconnect); @@ -441,6 +483,8 @@ void AsyncEventSource::send(const char *message, const char *event, uint32_t id, } } +///////////////////////////////////////////////////////// + size_t AsyncEventSource::count() const { return _clients.count_if([](AsyncEventSourceClient * c) @@ -449,6 +493,8 @@ size_t AsyncEventSource::count() const }); } +///////////////////////////////////////////////////////// + bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) { if (request->method() != HTTP_GET || !request->url().equals(_url)) @@ -461,6 +507,8 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) return true; } +///////////////////////////////////////////////////////// + void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) { if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) @@ -469,6 +517,9 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) request->send(new AsyncEventSourceResponse(this)); } +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + // Response AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) @@ -481,6 +532,8 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) addHeader("Connection", "keep-alive"); } +///////////////////////////////////////////////////////// + void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) { String out = _assembleHead(request->version()); @@ -488,6 +541,8 @@ void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) _state = RESPONSE_WAIT_ACK; } +///////////////////////////////////////////////////////// + size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))) { if (len) @@ -498,4 +553,6 @@ size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len return 0; } +///////////////////////////////////////////////////////// + diff --git a/src/AsyncEventSource_Teensy41.h b/src/AsyncEventSource_Teensy41.h index c2f44c1..090b66e 100644 --- a/src/AsyncEventSource_Teensy41.h +++ b/src/AsyncEventSource_Teensy41.h @@ -14,15 +14,17 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -40,17 +42,23 @@ // Teensy41 #include +///////////////////////////////////////////////////////// + #define SSE_MAX_QUEUED_MESSAGES 32 //#define SSE_MAX_QUEUED_MESSAGES 8 #define DEFAULT_MAX_SSE_CLIENTS 8 //#define DEFAULT_MAX_SSE_CLIENTS 4 +///////////////////////////////////////////////////////// + class AsyncEventSource; class AsyncEventSourceResponse; class AsyncEventSourceClient; typedef std::function ArEventHandlerFunction; +///////////////////////////////////////////////////////// + class AsyncEventSourceMessage { private: @@ -66,17 +74,24 @@ class AsyncEventSourceMessage size_t ack(size_t len, uint32_t time __attribute__((unused))); size_t send(AsyncClient *client); - bool finished() + ///////////////////////////////////////////////// + + inline bool finished() { return _acked == _len; } - bool sent() + ///////////////////////////////////////////////// + + inline bool sent() { return _sent == _len; } }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class AsyncEventSourceClient { private: @@ -92,30 +107,42 @@ class AsyncEventSourceClient AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); ~AsyncEventSourceClient(); - AsyncClient* client() + ///////////////////////////////////////////////// + + inline AsyncClient* client() { return _client; } + ///////////////////////////////////////////////// + void close(); void write(const char * message, size_t len); void send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0); - bool connected() const + ///////////////////////////////////////////////// + + inline bool connected() const { return (_client != NULL) && _client->connected(); } - uint32_t lastId() const + ///////////////////////////////////////////////// + + inline uint32_t lastId() const { return _lastId; } - size_t packetsWaiting() const + ///////////////////////////////////////////////// + + inline size_t packetsWaiting() const { return _messageQueue.length(); } + ///////////////////////////////////////////////// + //system callbacks (do not call) void _onAck(size_t len, uint32_t time); void _onPoll(); @@ -123,6 +150,9 @@ class AsyncEventSourceClient void _onDisconnect(); }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class AsyncEventSource: public AsyncWebHandler { private: @@ -134,11 +164,15 @@ class AsyncEventSource: public AsyncWebHandler AsyncEventSource(const String& url); ~AsyncEventSource(); - const char * url() const + ///////////////////////////////////////////////// + + inline const char * url() const { return _url.c_str(); } + ///////////////////////////////////////////////// + void close(); void onConnect(ArEventHandlerFunction cb); void send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0); @@ -152,6 +186,9 @@ class AsyncEventSource: public AsyncWebHandler virtual void handleRequest(AsyncWebServerRequest *request) override final; }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class AsyncEventSourceResponse: public AsyncWebServerResponse { private: @@ -163,7 +200,9 @@ class AsyncEventSourceResponse: public AsyncWebServerResponse void _respond(AsyncWebServerRequest *request); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return true; } diff --git a/src/AsyncJson_Teensy41.h b/src/AsyncJson_Teensy41.h index 413fb69..096575c 100644 --- a/src/AsyncJson_Teensy41.h +++ b/src/AsyncJson_Teensy41.h @@ -14,15 +14,17 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ /* Async Response to use with ArduinoJson and AsyncWebServer @@ -63,19 +65,26 @@ #ifndef ASYNC_JSON_TEENSY41_H_ #define ASYNC_JSON_TEENSY41_H_ +///////////////////////////////////////////////////////// + #include #include #include +///////////////////////////////////////////////////////// + #if ARDUINOJSON_VERSION_MAJOR == 5 -#define ARDUINOJSON_5_COMPATIBILITY + #define ARDUINOJSON_5_COMPATIBILITY #else -#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 + #define DYNAMIC_JSON_DOCUMENT_SIZE 1024 #endif constexpr const char* JSON_MIMETYPE = "application/json"; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* Json Response * */ @@ -94,6 +103,8 @@ class ChunkPrint : public Print virtual ~ChunkPrint() {} + ///////////////////////////////////////////////// + size_t write(uint8_t c) { if (_to_skip > 0) @@ -113,12 +124,16 @@ class ChunkPrint : public Print return 0; } - size_t write(const uint8_t *buffer, size_t size) + ///////////////////////////////////////////////// + + inline size_t write(const uint8_t *buffer, size_t size) { return this->Print::write(buffer, size); } }; +///////////////////////////////////////////////// + class AsyncJsonResponse: public AsyncAbstractResponse { protected: @@ -134,6 +149,8 @@ class AsyncJsonResponse: public AsyncAbstractResponse public: + ///////////////////////////////////////////////// + #ifdef ARDUINOJSON_5_COMPATIBILITY AsyncJsonResponse(bool isArray = false): _isValid {false} { @@ -158,17 +175,25 @@ class AsyncJsonResponse: public AsyncAbstractResponse } #endif + ///////////////////////////////////////////////// + ~AsyncJsonResponse() {} + + ///////////////////////////////////////////////// - JsonVariant & getRoot() + inline JsonVariant & getRoot() { return _root; } + + ///////////////////////////////////////////////// - bool _sourceValid() const + inline bool _sourceValid() const { return _isValid; } + + ///////////////////////////////////////////////// size_t setLength() { @@ -187,11 +212,15 @@ class AsyncJsonResponse: public AsyncAbstractResponse return _contentLength; } - size_t getSize() + ///////////////////////////////////////////////// + + inline size_t getSize() { return _jsonBuffer.size(); } + ///////////////////////////////////////////////// + size_t _fillBuffer(uint8_t *data, size_t len) { ChunkPrint dest(data, _sentLength, len); @@ -203,8 +232,14 @@ class AsyncJsonResponse: public AsyncAbstractResponse #endif return len; } + + ///////////////////////////////////////////////// + }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class PrettyAsyncJsonResponse: public AsyncJsonResponse { public: @@ -214,6 +249,8 @@ class PrettyAsyncJsonResponse: public AsyncJsonResponse PrettyAsyncJsonResponse (bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse {isArray, maxJsonBufferSize} {} #endif + ///////////////////////////////////////////////// + size_t setLength () { #ifdef ARDUINOJSON_5_COMPATIBILITY @@ -229,6 +266,8 @@ class PrettyAsyncJsonResponse: public AsyncJsonResponse return _contentLength; } + + ///////////////////////////////////////////////// size_t _fillBuffer (uint8_t *data, size_t len) { @@ -244,8 +283,13 @@ class PrettyAsyncJsonResponse: public AsyncJsonResponse } }; +///////////////////////////////////////////////// + typedef std::function ArJsonRequestHandlerFunction; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class AsyncCallbackJsonWebHandler: public AsyncWebHandler { private: @@ -262,6 +306,8 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler size_t _maxContentLength; public: + + ///////////////////////////////////////////////// #ifdef ARDUINOJSON_5_COMPATIBILITY AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) @@ -271,21 +317,29 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler : _uri(uri), _method(HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} #endif - void setMethod(WebRequestMethodComposite method) + ///////////////////////////////////////////////// + + inline void setMethod(WebRequestMethodComposite method) { _method = method; } + + ///////////////////////////////////////////////// - void setMaxContentLength(int maxContentLength) + inline void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } + + ///////////////////////////////////////////////// - void onRequest(ArJsonRequestHandlerFunction fn) + inline void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; } + ///////////////////////////////////////////////// + virtual bool canHandle(AsyncWebServerRequest *request) override final { if (!_onRequest) @@ -305,6 +359,8 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler return true; } + ///////////////////////////////////////////////// + virtual void handleRequest(AsyncWebServerRequest *request) override final { if (_onRequest) @@ -340,10 +396,14 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler request->send(500); } } + + ///////////////////////////////////////////////// virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { } + + ///////////////////////////////////////////////// virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { @@ -362,6 +422,8 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler } } } + + ///////////////////////////////////////////////// virtual bool isRequestHandlerTrivial() override final { diff --git a/src/AsyncWebAuthentication_Teensy41.cpp b/src/AsyncWebAuthentication_Teensy41.cpp index 733fdde..a2ce7e4 100644 --- a/src/AsyncWebAuthentication_Teensy41.cpp +++ b/src/AsyncWebAuthentication_Teensy41.cpp @@ -14,18 +14,22 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_TEENSY41_LOGLEVEL_ 1 +#if !defined(_AWS_TEENSY41_LOGLEVEL_) + #define _AWS_TEENSY41_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Teensy41_Debug.h" @@ -37,6 +41,8 @@ #include "Crypto/bearssl_hash.h" #include "Crypto/Hash.h" +///////////////////////////////////////////////// + // Basic Auth hash = base64("username:password") bool checkBasicAuthentication(const char * hash, const char * username, const char * password) @@ -73,6 +79,7 @@ bool checkBasicAuthentication(const char * hash, const char * username, const ch LOGDEBUG("checkBasicAuthentication: NULL encoded"); delete[] toencode; + return false; } @@ -84,6 +91,7 @@ bool checkBasicAuthentication(const char * hash, const char * username, const ch delete[] toencode; delete[] encoded; + return true; } @@ -91,9 +99,12 @@ bool checkBasicAuthentication(const char * hash, const char * username, const ch delete[] toencode; delete[] encoded; + return false; } +///////////////////////////////////////////////// + static bool getMD5(uint8_t * data, uint16_t len, char * output) { //33 bytes or more @@ -130,6 +141,8 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output) return true; } +///////////////////////////////////////////////// + static String genRandomMD5() { // For Teensy41 @@ -148,6 +161,8 @@ static String genRandomMD5() return res; } +///////////////////////////////////////////////// + static String stringMD5(const String& in) { char * out = (char*) malloc(33); @@ -163,6 +178,8 @@ static String stringMD5(const String& in) return res; } +///////////////////////////////////////////////// + String generateDigestHash(const char * username, const char * password, const char * realm) { if (username == NULL || password == NULL || realm == NULL) @@ -192,6 +209,8 @@ String generateDigestHash(const char * username, const char * password, const ch return res; } +///////////////////////////////////////////////// + String requestDigestAuthentication(const char * realm) { String header = "realm=\""; @@ -212,6 +231,8 @@ String requestDigestAuthentication(const char * realm) return header; } +///////////////////////////////////////////////// + bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri) { diff --git a/src/AsyncWebAuthentication_Teensy41.h b/src/AsyncWebAuthentication_Teensy41.h index c18fbf6..3fd4996 100644 --- a/src/AsyncWebAuthentication_Teensy41.h +++ b/src/AsyncWebAuthentication_Teensy41.h @@ -14,15 +14,17 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -33,11 +35,18 @@ #include "Arduino.h" #include "AsyncWebServer_Teensy41_Debug.h" +///////////////////////////////////////////////// + bool checkBasicAuthentication(const char * header, const char * username, const char * password); + String requestDigestAuthentication(const char * realm); -bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); + +bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, + const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); //for storing hashed versions on the device that can be authenticated against String generateDigestHash(const char * username, const char * password, const char * realm); +///////////////////////////////////////////////// + #endif // ASYNCWEB_AUTHENTICATION_TEENSY41_H_ diff --git a/src/AsyncWebHandlerImpl_Teensy41.h b/src/AsyncWebHandlerImpl_Teensy41.h index be0eeb2..345ba4a 100644 --- a/src/AsyncWebHandlerImpl_Teensy41.h +++ b/src/AsyncWebHandlerImpl_Teensy41.h @@ -14,15 +14,17 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -39,6 +41,8 @@ #include "stddef.h" #include +///////////////////////////////////////////////// + class AsyncStaticWebHandler: public AsyncWebHandler { private: @@ -66,6 +70,8 @@ class AsyncStaticWebHandler: public AsyncWebHandler AsyncStaticWebHandler& setLastModified(time_t last_modified); AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated + ///////////////////////////////////////////////// + AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) { _callback = newCallback; @@ -87,31 +93,44 @@ class AsyncCallbackWebHandler: public AsyncWebHandler public: AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} - void setUri(const String& uri) + ///////////////////////////////////////////////// + + inline void setUri(const String& uri) { _uri = uri; _isRegex = uri.startsWith("^") && uri.endsWith("$"); } - void setMethod(WebRequestMethodComposite method) + ///////////////////////////////////////////////// + + inline void setMethod(WebRequestMethodComposite method) { _method = method; } - void onRequest(ArRequestHandlerFunction fn) + + ///////////////////////////////////////////////// + + inline void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; } - void onUpload(ArUploadHandlerFunction fn) + ///////////////////////////////////////////////// + + inline void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; } - void onBody(ArBodyHandlerFunction fn) + ///////////////////////////////////////////////// + + inline void onBody(ArBodyHandlerFunction fn) { _onBody = fn; } + ///////////////////////////////////////////////// + virtual bool canHandle(AsyncWebServerRequest *request) override final { if (!_onRequest) @@ -158,6 +177,8 @@ class AsyncCallbackWebHandler: public AsyncWebHandler return true; } + ///////////////////////////////////////////////// + virtual void handleRequest(AsyncWebServerRequest *request) override final { if (_onRequest) @@ -166,12 +187,16 @@ class AsyncCallbackWebHandler: public AsyncWebHandler request->send(500); } + ///////////////////////////////////////////////// + virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { if (_onBody) _onBody(request, data, len, index, total); } + ///////////////////////////////////////////////// + virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; diff --git a/src/AsyncWebHandlers_Teensy41.cpp b/src/AsyncWebHandlers_Teensy41.cpp index bfc77dd..a011ae2 100644 --- a/src/AsyncWebHandlers_Teensy41.cpp +++ b/src/AsyncWebHandlers_Teensy41.cpp @@ -14,24 +14,30 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_TEENSY41_LOGLEVEL_ 1 +#if !defined(_AWS_TEENSY41_LOGLEVEL_) + #define _AWS_TEENSY41_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Teensy41_Debug.h" #include "AsyncWebServer_Teensy41.hpp" #include "AsyncWebHandlerImpl_Teensy41.h" +///////////////////////////////////////////////// + AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, /*FS& fs,*/ const char* path, const char* cache_control) : _uri(uri), _path(path), _cache_control(cache_control), _last_modified(""), _callback(nullptr) { @@ -59,12 +65,16 @@ AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, /*FS& fs,*/ const _gzipStats = 0xF8; } +///////////////////////////////////////////////// + AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) { _isDir = isDir; return *this; } +///////////////////////////////////////////////// + AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) { _cache_control = String(cache_control); @@ -72,6 +82,8 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_ return *this; } +///////////////////////////////////////////////// + AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) { _last_modified = String(last_modified); @@ -79,6 +91,8 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_m return *this; } +///////////////////////////////////////////////// + AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) { char result[30]; @@ -88,12 +102,16 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_mo return setLastModified((const char *)result); } +///////////////////////////////////////////////// + // For Teensy41 AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) { return setLastModified((struct tm *)gmtime(&last_modified)); } +///////////////////////////////////////////////// + AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() { time_t last_modified; @@ -104,6 +122,8 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() return setLastModified(last_modified); } +///////////////////////////////////////////////// + bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) { if (request->method() != HTTP_GET @@ -117,9 +137,13 @@ bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) return false; } +///////////////////////////////////////////////// + // For Teensy41 #define FILE_IS_REAL(f) (f == true) +///////////////////////////////////////////////// + uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const { uint8_t w = value; diff --git a/src/AsyncWebRequest_Teensy41.cpp b/src/AsyncWebRequest_Teensy41.cpp index bd89dba..ddca5a7 100644 --- a/src/AsyncWebRequest_Teensy41.cpp +++ b/src/AsyncWebRequest_Teensy41.cpp @@ -14,18 +14,22 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_TEENSY41_LOGLEVEL_ 1 +#if !defined(_AWS_TEENSY41_LOGLEVEL_) + #define _AWS_TEENSY41_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Teensy41_Debug.h" @@ -33,12 +37,16 @@ #include "AsyncWebResponseImpl_Teensy41.h" #include "AsyncWebAuthentication_Teensy41.h" +///////////////////////////////////////////////// + //static const String SharedEmptyString = String(); #define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; +///////////////////////////////////////////////// + AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) : _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(0) , _version(0), _method(HTTP_ANY), _url(), _host(), _contentType(), _boundary() @@ -61,14 +69,14 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) { c->onError([](void *r, AsyncClient * c, int8_t error) { - (void)c; + AWS_TEENSY41_UNUSED(c); AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); c->onAck([](void *r, AsyncClient * c, size_t len, uint32_t time) { - (void)c; + AWS_TEENSY41_UNUSED(c); AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); @@ -82,26 +90,28 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) c->onTimeout([](void *r, AsyncClient * c, uint32_t time) { - (void)c; + AWS_TEENSY41_UNUSED(c); AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); c->onData([](void *r, AsyncClient * c, void *buf, size_t len) { - (void)c; + AWS_TEENSY41_UNUSED(c); AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); c->onPoll([](void *r, AsyncClient * c) { - (void)c; + AWS_TEENSY41_UNUSED(c); AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); } +///////////////////////////////////////////////// + AsyncWebServerRequest::~AsyncWebServerRequest() { _headers.free(); @@ -122,6 +132,8 @@ AsyncWebServerRequest::~AsyncWebServerRequest() } } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_onData(void *buf, size_t len) { size_t i = 0; @@ -249,6 +261,8 @@ void AsyncWebServerRequest::_onData(void *buf, size_t len) } } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_removeNotInterestingHeaders() { if (_interestingHeaders.containsIgnoreCase("ANY")) @@ -263,6 +277,8 @@ void AsyncWebServerRequest::_removeNotInterestingHeaders() } } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_onPoll() { LOGDEBUG("onPoll"); @@ -273,6 +289,8 @@ void AsyncWebServerRequest::_onPoll() } } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_onAck(size_t len, uint32_t time) { LOGDEBUG3("onAck: len =", len, ", time =", time); @@ -292,25 +310,33 @@ void AsyncWebServerRequest::_onAck(size_t len, uint32_t time) } } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_onError(int8_t error) { - (void)error; + AWS_TEENSY41_UNUSED(error); } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_onTimeout(uint32_t time) { - (void)time; + AWS_TEENSY41_UNUSED(time); LOGDEBUG3("TIMEOUT: time =", time, ", state =", _client->stateToString()); _client->close(); } +///////////////////////////////////////////////// + void AsyncWebServerRequest::onDisconnect (ArDisconnectHandler fn) { _onDisconnectfn = fn; } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_onDisconnect() { LOGDEBUG("_onDisconnect"); @@ -323,16 +349,22 @@ void AsyncWebServerRequest::_onDisconnect() _server->_handleDisconnect(this); } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_addParam(AsyncWebParameter *p) { _params.add(p); } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_addPathParam(const char *p) { _pathParams.add(new String(p)); } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_addGetParams(const String& params) { size_t start = 0; @@ -356,6 +388,8 @@ void AsyncWebServerRequest::_addGetParams(const String& params) } } +///////////////////////////////////////////////// + bool AsyncWebServerRequest::_parseReqHead() { // Split the head into method, url and version @@ -414,6 +448,8 @@ bool AsyncWebServerRequest::_parseReqHead() return true; } +///////////////////////////////////////////////// + bool strContains(String src, String find, bool mindcase = true) { int pos = 0, i = 0; @@ -445,6 +481,8 @@ bool strContains(String src, String find, bool mindcase = true) return false; } +///////////////////////////////////////////////// + bool AsyncWebServerRequest::_parseReqHeader() { int index = _temp.indexOf(':'); @@ -514,6 +552,8 @@ bool AsyncWebServerRequest::_parseReqHeader() return true; } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) { if (data && (char)data != '&') @@ -535,6 +575,8 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) } } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) { _itemBuffer[_itemBufferIndex++] = data; @@ -549,6 +591,8 @@ void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) } } +///////////////////////////////////////////////// + enum { EXPECT_BOUNDARY, @@ -564,6 +608,7 @@ enum PARSE_ERROR }; +///////////////////////////////////////////////// void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) { @@ -594,16 +639,19 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) if (_parsedLength < 2 && data != '-') { _multiParseState = PARSE_ERROR; + return; } else if (_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data) { _multiParseState = PARSE_ERROR; + return; } else if (_parsedLength - 2 == _boundary.length() && data != '\r') { _multiParseState = PARSE_ERROR; + return; } else if (_parsedLength - 3 == _boundary.length()) @@ -611,6 +659,7 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) if (data != '\n') { _multiParseState = PARSE_ERROR; + return; } @@ -781,7 +830,6 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) free(_itemBuffer); _itemBuffer = NULL; } - } else { @@ -848,6 +896,8 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) } } +///////////////////////////////////////////////// + void AsyncWebServerRequest::_parseLine() { if (_parseState == PARSE_REQ_START) @@ -902,12 +952,15 @@ void AsyncWebServerRequest::_parseLine() } } +///////////////////////////////////////////////// size_t AsyncWebServerRequest::headers() const { return _headers.length(); } +///////////////////////////////////////////////// + bool AsyncWebServerRequest::hasHeader(const String& name) const { for (const auto& h : _headers) @@ -921,6 +974,8 @@ bool AsyncWebServerRequest::hasHeader(const String& name) const return false; } +///////////////////////////////////////////////// + AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const { for (const auto& h : _headers) @@ -934,6 +989,8 @@ AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const return nullptr; } +///////////////////////////////////////////////// + AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const { auto header = _headers.nth(num); @@ -941,11 +998,14 @@ AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const return (header ? *header : nullptr); } +///////////////////////////////////////////////// + size_t AsyncWebServerRequest::params() const { return _params.length(); } +///////////////////////////////////////////////// bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const { @@ -960,6 +1020,8 @@ bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) c return false; } +///////////////////////////////////////////////// + AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const { for (const auto& p : _params) @@ -973,6 +1035,8 @@ AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post return nullptr; } +///////////////////////////////////////////////// + AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const { auto param = _params.nth(num); @@ -980,12 +1044,16 @@ AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const return (param ? *param : nullptr); } +///////////////////////////////////////////////// + void AsyncWebServerRequest::addInterestingHeader(const String& name) { if (!_interestingHeaders.containsIgnoreCase(name)) _interestingHeaders.add(name); } +///////////////////////////////////////////////// + void AsyncWebServerRequest::send(AsyncWebServerResponse *response) { _response = response; @@ -1011,16 +1079,32 @@ void AsyncWebServerRequest::send(AsyncWebServerResponse *response) } } -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback) +///////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, + AwsTemplateProcessor callback) { return new AsyncProgmemResponse(code, contentType, content, len, callback); } -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback) +///////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, + AwsTemplateProcessor callback) { return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback); } +///////////////////////////////////////////////////////// + +//RSMOD/////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const char * content) +{ + return new AsyncBasicResponse(code, contentType, content); +} +///////////////////////////////////////////////// + AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content) { return new AsyncBasicResponse(code, contentType, content); @@ -1037,17 +1121,24 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const St ///////////////////////////////////////////////// -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback) +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, + AwsTemplateProcessor callback) { return new AsyncStreamResponse(stream, contentType, len, callback); } -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) +///////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, + AwsTemplateProcessor templateCallback) { return new AsyncCallbackResponse(contentType, len, callback, templateCallback); } -AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) +///////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, + AwsTemplateProcessor templateCallback) { if (_version) return new AsyncChunkedResponse(contentType, callback, templateCallback); @@ -1055,31 +1146,60 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const Strin return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); } +///////////////////////////////////////////////// + AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize) { return new AsyncResponseStream(contentType, bufferSize); } +//RSMOD/////////////////////////////////////////////// + +void AsyncWebServerRequest::send(int code, const String& contentType, const char *content, bool nonCopyingSend) +{ + if (nonCopyingSend) + { + send(beginResponse(code, contentType, String(content))); // for backwards compatibility + } + else + { + send(beginResponse(code, contentType, content)); + } +} + +///////////////////////////////////////////////// + void AsyncWebServerRequest::send(int code, const String& contentType, const String& content) { send(beginResponse(code, contentType, content)); } -void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback) +///////////////////////////////////////////////// + +void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, + AwsTemplateProcessor callback) { send(beginResponse(stream, contentType, len, callback)); } -void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) +///////////////////////////////////////////////// + +void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, + AwsTemplateProcessor templateCallback) { send(beginResponse(contentType, len, callback, templateCallback)); } -void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) +///////////////////////////////////////////////// + +void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, + AwsTemplateProcessor templateCallback) { send(beginChunkedResponse(contentType, callback, templateCallback)); } +///////////////////////////////////////////////// + void AsyncWebServerRequest::redirect(const String& url) { AsyncWebServerResponse * response = beginResponse(302); @@ -1087,6 +1207,8 @@ void AsyncWebServerRequest::redirect(const String& url) send(response); } +///////////////////////////////////////////////// + bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash) { LOGDEBUG1("AsyncWebServerRequest::authenticate: auth-len =", _authorization.length()); @@ -1118,6 +1240,8 @@ bool AsyncWebServerRequest::authenticate(const char * username, const char * pas return false; } +///////////////////////////////////////////////// + bool AsyncWebServerRequest::authenticate(const char * hash) { if (!_authorization.length() || hash == NULL) @@ -1141,12 +1265,15 @@ bool AsyncWebServerRequest::authenticate(const char * hash) String realm = hStr.substring(0, separator); hStr = hStr.substring(separator + 1); - return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); + return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), + realm.c_str(), true, NULL, NULL, NULL); } return (_authorization.equals(hash)); } +///////////////////////////////////////////////// + void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest) { AsyncWebServerResponse * r = beginResponse(401); @@ -1172,6 +1299,8 @@ void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDig send(r); } +///////////////////////////////////////////////// + bool AsyncWebServerRequest::hasArg(const char* name) const { for (const auto& arg : _params) @@ -1185,6 +1314,8 @@ bool AsyncWebServerRequest::hasArg(const char* name) const return false; } +///////////////////////////////////////////////// + const String& AsyncWebServerRequest::arg(const String& name) const { for (const auto& arg : _params) @@ -1198,16 +1329,22 @@ const String& AsyncWebServerRequest::arg(const String& name) const return SharedEmptyString; } +///////////////////////////////////////////////// + const String& AsyncWebServerRequest::arg(size_t i) const { return getParam(i)->value(); } +///////////////////////////////////////////////// + const String& AsyncWebServerRequest::argName(size_t i) const { return getParam(i)->name(); } +///////////////////////////////////////////////// + const String& AsyncWebServerRequest::pathArg(size_t i) const { auto param = _pathParams.nth(i); @@ -1215,6 +1352,8 @@ const String& AsyncWebServerRequest::pathArg(size_t i) const return (param ? **param : SharedEmptyString); } +///////////////////////////////////////////////// + const String& AsyncWebServerRequest::header(const char* name) const { AsyncWebHeader* h = getHeader(String(name)); @@ -1222,6 +1361,8 @@ const String& AsyncWebServerRequest::header(const char* name) const return (h ? h->value() : SharedEmptyString); } +///////////////////////////////////////////////// + const String& AsyncWebServerRequest::header(size_t i) const { AsyncWebHeader* h = getHeader(i); @@ -1229,6 +1370,8 @@ const String& AsyncWebServerRequest::header(size_t i) const return (h ? h->value() : SharedEmptyString); } +///////////////////////////////////////////////// + const String& AsyncWebServerRequest::headerName(size_t i) const { AsyncWebHeader* h = getHeader(i); @@ -1236,6 +1379,8 @@ const String& AsyncWebServerRequest::headerName(size_t i) const return (h ? h->name() : SharedEmptyString); } +///////////////////////////////////////////////// + String AsyncWebServerRequest::urlDecode(const String& text) const { char temp[] = "0x00"; @@ -1270,6 +1415,7 @@ String AsyncWebServerRequest::urlDecode(const String& text) const return decoded; } +///////////////////////////////////////////////// const char * AsyncWebServerRequest::methodToString() const { @@ -1293,6 +1439,8 @@ const char * AsyncWebServerRequest::methodToString() const return "UNKNOWN"; } +///////////////////////////////////////////////// + const char *AsyncWebServerRequest::requestedConnTypeToString() const { switch (_reqconntype) @@ -1312,7 +1460,10 @@ const char *AsyncWebServerRequest::requestedConnTypeToString() const } } -bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) +///////////////////////////////////////////////// + +bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, + RequestedConnectionType erct3) { bool res = false; @@ -1327,3 +1478,6 @@ bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType return res; } + +///////////////////////////////////////////////// + diff --git a/src/AsyncWebResponseImpl_Teensy41.h b/src/AsyncWebResponseImpl_Teensy41.h index a303261..e6d89f4 100644 --- a/src/AsyncWebResponseImpl_Teensy41.h +++ b/src/AsyncWebResponseImpl_Teensy41.h @@ -14,15 +14,17 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -37,26 +39,40 @@ #endif #include + // It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. +///////////////////////////////////////////////// class AsyncBasicResponse: public AsyncWebServerResponse { private: String _content; + + char *_contentCstr; // RSMOD + String _partialHeader; public: AsyncBasicResponse(int code, const String& contentType = String(), const String& content = String()); + + AsyncBasicResponse(int code, const String& contentType, const char *content = nullptr); // RSMOD + void _respond(AsyncWebServerRequest *request); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return true; } + + ///////////////////////////////////////////////// }; +///////////////////////////////////////////////// + class AsyncAbstractResponse: public AsyncWebServerResponse { private: @@ -77,23 +93,35 @@ class AsyncAbstractResponse: public AsyncWebServerResponse void _respond(AsyncWebServerRequest *request); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return false; } - virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) + ///////////////////////////////////////////////// + + virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } + + ///////////////////////////////////////////////// }; +///////////////////////////////////////////////// + #ifndef TEMPLATE_PLACEHOLDER #define TEMPLATE_PLACEHOLDER '%' #endif +///////////////////////////////////////////////// + #define TEMPLATE_PARAM_NAME_LENGTH 32 +///////////////////////////////////////////////// + class AsyncStreamResponse: public AsyncAbstractResponse { private: @@ -102,14 +130,20 @@ class AsyncStreamResponse: public AsyncAbstractResponse public: AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return !!(_content); } + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; }; +///////////////////////////////////////////////// + class AsyncCallbackResponse: public AsyncAbstractResponse { private: @@ -119,14 +153,20 @@ class AsyncCallbackResponse: public AsyncAbstractResponse public: AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return !!(_content); } + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; }; +///////////////////////////////////////////////// + class AsyncChunkedResponse: public AsyncAbstractResponse { private: @@ -136,16 +176,24 @@ class AsyncChunkedResponse: public AsyncAbstractResponse public: AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return !!(_content); } + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; }; +///////////////////////////////////////////////// + class cbuf; +///////////////////////////////////////////////// + class AsyncProgmemResponse: public AsyncAbstractResponse { private: @@ -154,10 +202,21 @@ class AsyncProgmemResponse: public AsyncAbstractResponse public: AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); - bool _sourceValid() const { return true; } + + ///////////////////////////////////////////////// + + inline bool _sourceValid() const + { + return (_state < RESPONSE_END); + } + + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; }; +///////////////////////////////////////////////// + class AsyncResponseStream: public AsyncAbstractResponse, public Print { private: @@ -167,15 +226,21 @@ class AsyncResponseStream: public AsyncAbstractResponse, public Print AsyncResponseStream(const String& contentType, size_t bufferSize); ~AsyncResponseStream(); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return (_state < RESPONSE_END); } + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; size_t write(const uint8_t *data, size_t len); size_t write(uint8_t data); using Print::write; }; +///////////////////////////////////////////////// + #endif /* ASYNCWEBSERVERRESPONSEIMPL_TEENSY41_H_ */ diff --git a/src/AsyncWebResponses_Teensy41.cpp b/src/AsyncWebResponses_Teensy41.cpp index 3c03e06..7af0715 100644 --- a/src/AsyncWebResponses_Teensy41.cpp +++ b/src/AsyncWebResponses_Teensy41.cpp @@ -14,18 +14,22 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_TEENSY41_LOGLEVEL_ 1 +#if !defined(_AWS_TEENSY41_LOGLEVEL_) + #define _AWS_TEENSY41_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Teensy41_Debug.h" @@ -33,6 +37,8 @@ #include "AsyncWebResponseImpl_Teensy41.h" #include "cbuf.hpp" +///////////////////////////////////////////////// + // Since ESP8266 does not link memchr by default, here's its implementation. void* memchr(void* ptr, int ch, size_t count) { @@ -47,12 +53,15 @@ void* memchr(void* ptr, int ch, size_t count) return nullptr; } +///////////////////////////////////////////////// + /* Abstract Response * */ const char* AsyncWebServerResponse::_responseCodeToString(int code) { - switch (code) { + switch (code) + { case 100: return "Continue"; case 101: return "Switching Protocols"; case 200: return "OK"; @@ -97,6 +106,9 @@ const char* AsyncWebServerResponse::_responseCodeToString(int code) } } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + AsyncWebServerResponse::AsyncWebServerResponse() : _code(0) , _headers(LinkedList([](AsyncWebHeader * h) @@ -112,34 +124,46 @@ AsyncWebServerResponse::AsyncWebServerResponse() } } +///////////////////////////////////////////////// + AsyncWebServerResponse::~AsyncWebServerResponse() { _headers.free(); } +///////////////////////////////////////////////// + void AsyncWebServerResponse::setCode(int code) { if (_state == RESPONSE_SETUP) _code = code; } +///////////////////////////////////////////////// + void AsyncWebServerResponse::setContentLength(size_t len) { if (_state == RESPONSE_SETUP) _contentLength = len; } +///////////////////////////////////////////////// + void AsyncWebServerResponse::setContentType(const String& type) { if (_state == RESPONSE_SETUP) _contentType = type; } +///////////////////////////////////////////////// + void AsyncWebServerResponse::addHeader(const String& name, const String& value) { _headers.add(new AsyncWebHeader(name, value)); } +///////////////////////////////////////////////// + String AsyncWebServerResponse::_assembleHead(uint8_t version) { if (version) @@ -183,40 +207,84 @@ String AsyncWebServerResponse::_assembleHead(uint8_t version) return out; } +///////////////////////////////////////////////// + bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } +///////////////////////////////////////////////// + bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } +///////////////////////////////////////////////// + bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } +///////////////////////////////////////////////// + bool AsyncWebServerResponse::_sourceValid() const { return false; } +///////////////////////////////////////////////// + void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request) { _state = RESPONSE_END; request->client()->close(); } +///////////////////////////////////////////////// + size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) { - (void)request; - (void)len; - (void)time; + AWS_TEENSY41_UNUSED(request); + AWS_TEENSY41_UNUSED(len); + AWS_TEENSY41_UNUSED(time); + return 0; } +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +//RSMOD/////////////////////////////////////////////// + +/* + String/Code Response + * */ +AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const char *content) +{ + _code = code; + _content = String(""); + _contentCstr = (char *)content; // RSMOD + _contentType = contentType; + _partialHeader = String(); + + int iLen; + + if ((iLen = strlen(_contentCstr))) + { + _contentLength = iLen; + + if (!_contentType.length()) + _contentType = "text/plain"; + } + + addHeader("Connection", "close"); +} + +///////////////////////////////////////////////// + /* String/Code Response * */ @@ -224,7 +292,11 @@ AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, cons { _code = code; _content = content; + + _contentCstr = nullptr; // RSMOD + _contentType = contentType; + _partialHeader = String(); if (_content.length()) { @@ -236,6 +308,8 @@ AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, cons addHeader("Connection", "close"); } +///////////////////////////////////////////////// + void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) { _state = RESPONSE_HEADERS; @@ -243,68 +317,215 @@ void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) size_t outLen = out.length(); size_t space = request->client()->space(); + LOGDEBUG3("AsyncBasicResponse::_respond : Pre_respond, _contentLength =", _contentLength, ", out =", out ); + LOGDEBUG3("outLen =", outLen, ", _contentCstr =", _contentCstr); + if (!_contentLength && space >= outLen) { + LOGDEBUG("Step 1"); + _writtenLength += request->client()->write(out.c_str(), outLen); _state = RESPONSE_WAIT_ACK; } else if (_contentLength && space >= outLen + _contentLength) { + LOGDEBUG("Step 2"); + + if (_contentCstr) + { + _content = String(_contentCstr); // short _contentCstr - so just send as Arduino String - not much of a penalty - fall into below + } + out += _content; outLen += _contentLength; _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; } else if (space && space < outLen) { String partial = out.substring(0, space); - _content = out.substring(space) + _content; - _contentLength += outLen - space; + + LOGDEBUG("Step 3"); + + if (_contentCstr) + { + _partialHeader = out.substring(space); + } + else + { + _content = out.substring(space) + _content; + _contentLength += outLen - space; + } + + LOGDEBUG1("partial =", partial); + _writtenLength += request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; } else if (space > outLen && space < (outLen + _contentLength)) { size_t shift = space - outLen; + + LOGDEBUG("Step 4"); + outLen += shift; _sentLength += shift; - out += _content.substring(0, shift); - _content = _content.substring(shift); + + if (_contentCstr) + { + char *s = (char *)malloc(shift + 1); + + if (s != nullptr) + { + strncpy(s, _contentCstr, shift); + s[shift] = '\0'; + out += String(s); + _contentCstr += shift; + + free(s); + } + else + { + LOGERROR("AsyncBasicResponse::_respond: Out of heap"); + + return; + } + } + else + { + out += _content.substring(0, shift); + _content = _content.substring(shift); + } + + LOGDEBUG1("out =", out); + _writtenLength += request->client()->write(out.c_str(), outLen); _state = RESPONSE_CONTENT; } else { - _content = out + _content; - _contentLength += outLen; + LOGDEBUG("Step 5"); + + if (_contentCstr) + { + _partialHeader = out; + } + else + { + _content = out + _content; + _contentLength += outLen; + } + _state = RESPONSE_CONTENT; } + + LOGDEBUG3("AsyncBasicResponse::_respond : Post_respond, _contentLength =", _contentLength, ", out =", out ); + LOGDEBUG3("outLen =", outLen, ", _contentCstr =", _contentCstr); } +///////////////////////////////////////////////// + size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) { - (void)time; + AWS_TEENSY41_UNUSED(time); + + LOGDEBUG1("AsyncBasicResponse::_ack : Pre_ack, _contentLength =", _contentLength); + _ackedLength += len; if (_state == RESPONSE_CONTENT) { + String out; size_t available = _contentLength - _sentLength; size_t space = request->client()->space(); + if (_partialHeader.length() > 0) + { + if (_partialHeader.length() > space) + { + // Header longer than space - send a piece of it, and make the _partialHeader = to what remains + String _subHeader; + String tmpString; + + _subHeader = _partialHeader.substring(0, space); + tmpString = _partialHeader.substring(space); + _partialHeader = tmpString; + + _writtenLength += request->client()->write(_subHeader.c_str(), space); + + return (_partialHeader.length()); + } + else + { + // _partialHeader is <= space length - therefore send the whole thing, and make the remaining length = to the _contrentLength + _writtenLength += request->client()->write(_partialHeader.c_str(), _partialHeader.length()); + + _partialHeader = String(); + + return (_contentLength); + } + } + + // if we are here - there is no _partialHJeader to send + + LOGDEBUG3("AsyncBasicResponse::_ack : available =", available, ", space =", space ); + //we can fit in this packet if (space > available) { - _writtenLength += request->client()->write(_content.c_str(), available); - _content = String(); + LOGDEBUG1("AsyncBasicResponse::_ack : Pre_ack, _contentLength =", _contentLength); + + if (_contentCstr) + { + LOGDEBUG1("In space>available : output =", _contentCstr); + + _writtenLength += request->client()->write(_contentCstr, available); + //_contentCstr[0] = '\0'; + } + else + { + _writtenLength += request->client()->write(_content.c_str(), available); + _content = String(); + } + _state = RESPONSE_WAIT_ACK; return available; } //send some data, the rest on ack - String out = _content.substring(0, space); - _content = _content.substring(space); + if (_contentCstr) + { + char *s = (char *)malloc(space + 1); + + if (s != nullptr) + { + strncpy(s, _contentCstr, space); + s[space] = '\0'; + out = String(s); + _contentCstr += space; + + free(s); + } + else + { + LOGERROR("AsyncBasicResponse::_ack: Out of heap"); + + return 0; + } + } + else + { + out = _content.substring(0, space); + _content = _content.substring(space); + } + _sentLength += space; + + LOGDEBUG1("In space>available : output =", out); + _writtenLength += request->client()->write(out.c_str(), space); return space; @@ -314,13 +535,16 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint if (_ackedLength >= _writtenLength) { _state = RESPONSE_END; - request->client()->close(true); /* Might it be required? */ } } + LOGDEBUG3("AsyncBasicResponse::_ack : Post_ack, _contentLength =", _contentLength, ", _contentCstr =", _contentCstr); + return 0; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// /* Abstract Response @@ -337,6 +561,8 @@ AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _ca } } +///////////////////////////////////////////////// + void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request) { addHeader("Connection", "close"); @@ -345,9 +571,11 @@ void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request) _ack(request, 0, 0); } +///////////////////////////////////////////////// + size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) { - (void)time; + AWS_TEENSY41_UNUSED(time); if (!_sourceValid()) { @@ -447,6 +675,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u if (readLen == RESPONSE_TRY_AGAIN) { free(buf); + return 0; } @@ -463,7 +692,8 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u _writtenLength += request->client()->write((const char*)buf, outLen); } - if (_chunked) { + if (_chunked) + { _sentLength += readLen; } else @@ -495,6 +725,8 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u return 0; } +///////////////////////////////////////////////// + size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) { // If we have something in cache, copy it to buffer @@ -513,6 +745,8 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s return readFromCache + readFromContent; } +///////////////////////////////////////////////// + size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) { if (!_callback) @@ -525,10 +759,12 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size // Search for template placeholders uint8_t* pTemplateStart = data; - while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*) memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) + while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*) memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, + &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] - uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*) memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; + uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*) memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, + &data[len - 1] - pTemplateStart) : nullptr; // temporary buffer to hold parameter name uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; @@ -559,7 +795,8 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size { // closing placeholder not found, check if it's in the remaining file data memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); - const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); + const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), + TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); if (readFromCacheOrContent) { @@ -577,7 +814,8 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position { // but first, store read file data in cache - _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), + buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); ++pTemplateStart; } } @@ -644,6 +882,9 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size return len; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Stream Response * */ @@ -656,6 +897,8 @@ AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentTy _contentType = contentType; } +///////////////////////////////////////////////// + size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len) { size_t available = _content->available(); @@ -668,6 +911,9 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len) return outLen; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Callback Response * */ @@ -686,6 +932,8 @@ AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t l _filledLength = 0; } +///////////////////////////////////////////////// + size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len) { size_t ret = _content(data, len, _filledLength); @@ -698,6 +946,9 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len) return ret; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Chunked Response * */ @@ -713,6 +964,8 @@ AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsRespons _filledLength = 0; } +///////////////////////////////////////////////// + size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len) { size_t ret = _content(data, len, _filledLength); @@ -725,6 +978,9 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len) return ret; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* * Progmem Response * */ @@ -738,6 +994,8 @@ AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, _readLength = 0; } +///////////////////////////////////////////////// + size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len) { size_t left = _contentLength - _readLength; @@ -756,6 +1014,9 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len) return left; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Response Stream (You can print/write/printf to it, up to the contentLen bytes) * */ @@ -768,16 +1029,22 @@ AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t buffe _content = new cbuf(bufferSize); } +///////////////////////////////////////////////// + AsyncResponseStream::~AsyncResponseStream() { delete _content; } +///////////////////////////////////////////////// + size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) { return _content->read((char*)buf, maxLen); } +///////////////////////////////////////////////// + size_t AsyncResponseStream::write(const uint8_t *data, size_t len) { if (_started()) @@ -795,7 +1062,12 @@ size_t AsyncResponseStream::write(const uint8_t *data, size_t len) return written; } +///////////////////////////////////////////////// + size_t AsyncResponseStream::write(uint8_t data) { return write(&data, 1); } + +///////////////////////////////////////////////// + diff --git a/src/AsyncWebServer_Teensy41.cpp b/src/AsyncWebServer_Teensy41.cpp index a4e780a..e2bc899 100644 --- a/src/AsyncWebServer_Teensy41.cpp +++ b/src/AsyncWebServer_Teensy41.cpp @@ -14,24 +14,32 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_TEENSY41_LOGLEVEL_ 1 +#if !defined(_AWS_TEENSY41_LOGLEVEL_) + #define _AWS_TEENSY41_LOGLEVEL_ 1 +#endif + +///////////////////////////////////////////////// #include "AsyncWebServer_Teensy41_Debug.h" #include "AsyncWebServer_Teensy41.hpp" #include "AsyncWebHandlerImpl_Teensy41.h" +///////////////////////////////////////////////// + AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port), _rewrites(LinkedList([](AsyncWebRewrite * r) { @@ -68,6 +76,8 @@ AsyncWebServer::AsyncWebServer(uint16_t port) }, this); } +///////////////////////////////////////////////// + AsyncWebServer::~AsyncWebServer() { reset(); @@ -77,6 +87,8 @@ AsyncWebServer::~AsyncWebServer() delete _catchAllHandler; } +///////////////////////////////////////////////// + AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) { _rewrites.add(rewrite); @@ -84,55 +96,75 @@ AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) return *rewrite; } +///////////////////////////////////////////////// + bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite) { return _rewrites.remove(rewrite); } +///////////////////////////////////////////////// + AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) { return addRewrite(new AsyncWebRewrite(from, to)); } +///////////////////////////////////////////////// + AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) { _handlers.add(handler); return *handler; } +///////////////////////////////////////////////// + bool AsyncWebServer::removeHandler(AsyncWebHandler *handler) { return _handlers.remove(handler); } +///////////////////////////////////////////////// + void AsyncWebServer::begin() { _server.setNoDelay(true); _server.begin(); } +///////////////////////////////////////////////// + void AsyncWebServer::end() { _server.end(); } +///////////////////////////////////////////////// + #if ASYNC_TCP_SSL_ENABLED void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) { _server.onSslFileRequest(cb, arg); } +///////////////////////////////////////////////// + void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password) { _server.beginSecure(cert, key, password); } #endif +///////////////////////////////////////////////// + void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request) { delete request; } +///////////////////////////////////////////////// + void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) { for (const auto& r : _rewrites) @@ -145,6 +177,8 @@ void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) } } +///////////////////////////////////////////////// + void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) { for (const auto& h : _handlers) @@ -160,6 +194,7 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) request->setHandler(_catchAllHandler); } +///////////////////////////////////////////////// AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) @@ -176,7 +211,10 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom return *handler; } -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload) +///////////////////////////////////////////////// + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, + ArUploadHandlerFunction onUpload) { AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); handler->setUri(uri); @@ -188,6 +226,8 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom return *handler; } +///////////////////////////////////////////////// + AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) { AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); @@ -199,6 +239,8 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom return *handler; } +///////////////////////////////////////////////// + AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest) { AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); @@ -209,16 +251,22 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFun return *handler; } +///////////////////////////////////////////////// + void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn) { _catchAllHandler->onRequest(fn); } +///////////////////////////////////////////////// + void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) { _catchAllHandler->onBody(fn); } +///////////////////////////////////////////////// + void AsyncWebServer::reset() { _rewrites.free(); @@ -231,3 +279,6 @@ void AsyncWebServer::reset() _catchAllHandler->onBody(NULL); } } + +///////////////////////////////////////////////// + diff --git a/src/AsyncWebServer_Teensy41.h b/src/AsyncWebServer_Teensy41.h index 630d130..577e107 100644 --- a/src/AsyncWebServer_Teensy41.h +++ b/src/AsyncWebServer_Teensy41.h @@ -14,20 +14,24 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #ifndef _ASYNC_WEBSERVER_TEENSY41_H_ #define _ASYNC_WEBSERVER_TEENSY41_H_ +///////////////////////////////////////////////// + #include #include "AsyncWebServer_Teensy41.hpp" @@ -37,4 +41,6 @@ #include "AsyncWebSocket_Teensy41.h" #include "AsyncEventSource_Teensy41.h" +///////////////////////////////////////////////// + #endif /* _ASYNC_WEBSERVER_TEENSY41_H_ */ diff --git a/src/AsyncWebServer_Teensy41.hpp b/src/AsyncWebServer_Teensy41.hpp index 6c45fcd..b62fc32 100644 --- a/src/AsyncWebServer_Teensy41.hpp +++ b/src/AsyncWebServer_Teensy41.hpp @@ -14,36 +14,46 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 - 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #ifndef _ASYNC_WEBSERVER_TEENSY41_HPP_ #define _ASYNC_WEBSERVER_TEENSY41_HPP_ -#define ASYNC_WEBSERVER_TEENSY41_VERSION "AsyncWebServer_Teensy41 v1.5.0" +///////////////////////////////////////////////// + +#define ASYNC_WEBSERVER_TEENSY41_VERSION "AsyncWebServer_Teensy41 v1.6.0" #define ASYNC_WEBSERVER_TEENSY41_VERSION_MAJOR 1 -#define ASYNC_WEBSERVER_TEENSY41_VERSION_MINOR 5 +#define ASYNC_WEBSERVER_TEENSY41_VERSION_MINOR 6 #define ASYNC_WEBSERVER_TEENSY41_VERSION_PATCH 0 -#define ASYNC_WEBSERVER_TEENSY41_VERSION_INT 1005000 +#define ASYNC_WEBSERVER_TEENSY41_VERSION_INT 1006000 + +///////////////////////////////////////////////// #ifndef BOARD_NAME #define BOARD_NAME "Teensy 4.1" #endif -#ifndef AWS_Teensy41_UNUSED - #define AWS_Teensy41_UNUSED(x) (void)(x) +///////////////////////////////////////////////// + +#ifndef AWS_TEENSY41_UNUSED + #define AWS_TEENSY41_UNUSED(x) (void)(x) #endif +///////////////////////////////////////////////// + #include "Arduino.h" #include @@ -52,8 +62,13 @@ #include "AsyncWebServer_Teensy41_Debug.h" #include "StringArray_Teensy41.h" +///////////////////////////////////////////////// + #ifdef ASYNCWEBSERVER_REGEX - #warning Using ASYNCWEBSERVER_REGEX + #if(_AWS_TEENSY41_LOGLEVEL_>3) + #warning Using ASYNCWEBSERVER_REGEX + #endif + #define ASYNCWEBSERVER_REGEX_ATTRIBUTE #else #define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) @@ -63,6 +78,8 @@ static const String SharedEmptyString = String(); +///////////////////////////////////////////////// + class AsyncWebServer; class AsyncWebServerRequest; class AsyncWebServerResponse; @@ -74,6 +91,8 @@ class AsyncStaticWebHandler; class AsyncCallbackWebHandler; class AsyncResponseStream; +///////////////////////////////////////////////// + #ifndef WEBSERVER_H typedef enum { @@ -92,7 +111,10 @@ class AsyncResponseStream; #define RESPONSE_TRY_AGAIN 0xFFFFFFFF typedef uint8_t WebRequestMethodComposite; -typedef std::function ArDisconnectHandler; +typedef std::function ArDisconnectHandler; + +///////////////////////////////////////////////// +///////////////////////////////////////////////// /* PARAMETER :: Chainable object to hold GET/POST and FILE parameters @@ -111,32 +133,48 @@ class AsyncWebParameter AsyncWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {} - const String& name() const + ///////////////////////////////////////////////// + + inline const String& name() const { return _name; } - const String& value() const + ///////////////////////////////////////////////// + + inline const String& value() const { return _value; } - size_t size() const + ///////////////////////////////////////////////// + + inline size_t size() const { return _size; } - bool isPost() const + ///////////////////////////////////////////////// + + inline bool isPost() const { return _isForm; } - bool isFile() const + ///////////////////////////////////////////////// + + inline bool isFile() const { return _isFile; } + + ///////////////////////////////////////////////// }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + + /* HEADER :: Chainable object to hold the headers * */ @@ -150,38 +188,53 @@ class AsyncWebHeader public: AsyncWebHeader(const String& name, const String& value): _name(name), _value(value) {} - AsyncWebHeader(const String& data): _name(), _value() + ///////////////////////////////////////////////// + + AsyncWebHeader(const String& data): _name(), _value() { - if (!data) + if (!data) return; - + int index = data.indexOf(':'); - - if (index < 0) + + if (index < 0) return; - + _name = data.substring(0, index); _value = data.substring(index + 2); } + ///////////////////////////////////////////////// + ~AsyncWebHeader() {} - const String& name() const + ///////////////////////////////////////////////// + + inline const String& name() const { return _name; } - const String& value() const + ///////////////////////////////////////////////// + + inline const String& value() const { return _value; } - String toString() const + ///////////////////////////////////////////////// + + inline String toString() const { return String(_name + ": " + _value + "\r\n"); } + + ///////////////////////////////////////////////// }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect * */ @@ -191,6 +244,9 @@ typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, typedef std::function AwsResponseFiller; typedef std::function AwsTemplateProcessor; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + class AsyncWebServerRequest { friend class AsyncWebServer; @@ -270,54 +326,76 @@ class AsyncWebServerRequest AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); ~AsyncWebServerRequest(); - AsyncClient* client() + ///////////////////////////////////////////////// + + inline AsyncClient* client() { return _client; } - uint8_t version() const + ///////////////////////////////////////////////// + + inline uint8_t version() const { return _version; } - WebRequestMethodComposite method() const + ///////////////////////////////////////////////// + + inline WebRequestMethodComposite method() const { return _method; } - const String& url() const + ///////////////////////////////////////////////// + + inline const String& url() const { return _url; } - const String& host() const + ///////////////////////////////////////////////// + + inline const String& host() const { return _host; } - const String& contentType() const + ///////////////////////////////////////////////// + + inline const String& contentType() const { return _contentType; } - size_t contentLength() const + ///////////////////////////////////////////////// + + inline size_t contentLength() const { return _contentLength; } - bool multipart() const + ///////////////////////////////////////////////// + + inline bool multipart() const { return _isMultipart; } + ///////////////////////////////////////////////// + const char * methodToString() const; const char * requestedConnTypeToString() const; - RequestedConnectionType requestedConnType() const + ///////////////////////////////////////////////// + + inline RequestedConnectionType requestedConnType() const { return _reqconntype; } + ///////////////////////////////////////////////// + bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); void onDisconnect (ArDisconnectHandler fn); @@ -328,34 +406,42 @@ class AsyncWebServerRequest bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); void requestAuthentication(const char * realm = NULL, bool isDigest = true); - void setHandler(AsyncWebHandler *handler) + ///////////////////////////////////////////////// + + inline void setHandler(AsyncWebHandler *handler) { _handler = handler; } + ///////////////////////////////////////////////// + void addInterestingHeader(const String& name); void redirect(const String& url); void send(AsyncWebServerResponse *response); void send(int code, const String& contentType = String(), const String& content = String()); + void send(int code, const String& contentType, const char *content, bool nonCopyingSend = true); // RSMOD void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr); void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); AsyncWebServerResponse *beginResponse(int code, const String& contentType = String(), const String& content = String()); + AsyncWebServerResponse *beginResponse(int code, const String& contentType, const char * content = nullptr); // RSMOD // KH add - AsyncWebServerResponse *beginResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr); + AsyncWebServerResponse *beginResponse(int code, const String& contentType, const uint8_t * content, size_t len, + AwsTemplateProcessor callback = nullptr); ////// AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, - size_t len, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, + AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, + AwsTemplateProcessor callback=nullptr); AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); @@ -373,11 +459,15 @@ class AsyncWebServerRequest AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const; AsyncWebParameter* getParam(size_t num) const; - size_t args() const + ///////////////////////////////////////////////// + + inline size_t args() const { return params(); // get arguments count } + ///////////////////////////////////////////////// + const String& arg(const String& name) const; // get request argument value by name const String& arg(size_t i) const; // get request argument value by number const String& argName(size_t i) const; // get request argument name by number @@ -391,6 +481,9 @@ class AsyncWebServerRequest String urlDecode(const String& text) const; }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) * */ @@ -401,6 +494,9 @@ bool ON_STA_FILTER(AsyncWebServerRequest *request); bool ON_AP_FILTER(AsyncWebServerRequest *request); +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* REWRITE :: One instance can be handle any Request (done by the Server) * */ @@ -414,51 +510,71 @@ class AsyncWebRewrite ArRequestFilterFunction _filter; public: - AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL) + + ///////////////////////////////////////////////// + + AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL) { int index = _toUrl.indexOf('?'); - - if (index > 0) + + if (index > 0) { _params = _toUrl.substring(index + 1); _toUrl = _toUrl.substring(0, index); } } + ///////////////////////////////////////////////// + virtual ~AsyncWebRewrite() {} - AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) + ///////////////////////////////////////////////// + + inline AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } - bool filter(AsyncWebServerRequest *request) const + ///////////////////////////////////////////////// + + inline bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } - const String& from(void) const + ///////////////////////////////////////////////// + + inline const String& from() const { return _from; } - const String& toUrl(void) const + ///////////////////////////////////////////////// + + inline const String& toUrl() const { return _toUrl; } - const String& params(void) const + ///////////////////////////////////////////////// + + inline const String& params() const { return _params; } - virtual bool match(AsyncWebServerRequest *request) + ///////////////////////////////////////////////// + + virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* HANDLER :: One instance can be attached to any Request (done by the Server) * */ @@ -473,41 +589,61 @@ class AsyncWebHandler public: AsyncWebHandler(): _username(""), _password("") {} - AsyncWebHandler& setFilter(ArRequestFilterFunction fn) + ///////////////////////////////////////////////// + + inline AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } - AsyncWebHandler& setAuthentication(const char *username, const char *password) + ///////////////////////////////////////////////// + + inline AsyncWebHandler& setAuthentication(const char *username, const char *password) { _username = String(username); _password = String(password); return *this; }; - bool filter(AsyncWebServerRequest *request) + ///////////////////////////////////////////////// + + inline bool filter(AsyncWebServerRequest *request) { return _filter == NULL || _filter(request); } + ///////////////////////////////////////////////// + virtual ~AsyncWebHandler() {} - virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))) + ///////////////////////////////////////////////// + + virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))) { return false; } + ///////////////////////////////////////////////// + virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))) {} - virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))) {} - virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))) {} + virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), + size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), + bool final __attribute__((unused))) {} + virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), + size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))) {} - virtual bool isRequestHandlerTrivial() + ///////////////////////////////////////////////// + + virtual bool isRequestHandlerTrivial() { return true; } }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* RESPONSE :: One instance is created for each Request (attached by the Handler) * */ @@ -517,6 +653,8 @@ typedef enum RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED } WebResponseState; +///////////////////////////////////////////////////////// + class AsyncWebServerResponse { protected: @@ -549,6 +687,9 @@ class AsyncWebServerResponse virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* SERVER :: One instance * */ @@ -557,6 +698,8 @@ typedef std::function ArRequestHandlerFunc typedef std::function ArUploadHandlerFunction; typedef std::function ArBodyHandlerFunction; +///////////////////////////////////////////////////////// + class AsyncWebServer { protected: @@ -599,46 +742,64 @@ class AsyncWebServer void _rewriteRequest(AsyncWebServerRequest *request); }; -class DefaultHeaders +///////////////////////////////////////////////////////// + +class DefaultHeaders { using headers_t = LinkedList; headers_t _headers; + ///////////////////////////////////////////////// + DefaultHeaders() - : _headers(headers_t([](AsyncWebHeader * h) + : _headers(headers_t([](AsyncWebHeader * h) { delete h; })) {} + ///////////////////////////////////////////////// + public: using ConstIterator = headers_t::ConstIterator; - void addHeader(const String& name, const String& value) + ///////////////////////////////////////////////// + + void addHeader(const String& name, const String& value) { _headers.add(new AsyncWebHeader(name, value)); } - ConstIterator begin() const + ///////////////////////////////////////////////// + + inline ConstIterator begin() const { return _headers.begin(); } - ConstIterator end() const + ///////////////////////////////////////////////// + + inline ConstIterator end() const { return _headers.end(); } + ///////////////////////////////////////////////// + DefaultHeaders(DefaultHeaders const &) = delete; DefaultHeaders &operator=(DefaultHeaders const &) = delete; - static DefaultHeaders &Instance() + ///////////////////////////////////////////////// + + static DefaultHeaders &Instance() { static DefaultHeaders instance; return instance; } }; +///////////////////////////////////////////////////////// + #include "AsyncWebResponseImpl_Teensy41.h" #include "AsyncWebHandlerImpl_Teensy41.h" #include "AsyncWebSocket_Teensy41.h" diff --git a/src/AsyncWebServer_Teensy41_Debug.h b/src/AsyncWebServer_Teensy41_Debug.h index 9b4f8c9..2ec8127 100644 --- a/src/AsyncWebServer_Teensy41_Debug.h +++ b/src/AsyncWebServer_Teensy41_Debug.h @@ -14,15 +14,17 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once diff --git a/src/AsyncWebSocket_Teensy41.cpp b/src/AsyncWebSocket_Teensy41.cpp index c912887..d2ba8eb 100644 --- a/src/AsyncWebSocket_Teensy41.cpp +++ b/src/AsyncWebSocket_Teensy41.cpp @@ -14,20 +14,24 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #include "Arduino.h" -#define _AWS_TEENSY41_LOGLEVEL_ 1 +#if !defined(_AWS_TEENSY41_LOGLEVEL_) + #define _AWS_TEENSY41_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Teensy41_Debug.h" @@ -39,8 +43,12 @@ #include "Crypto/sha1.h" #include "Crypto/Hash.h" +///////////////////////////////////////////////// + #define MAX_PRINTF_LEN 64 +///////////////////////////////////////////////// + char *ltrim(char *s) { while (isspace(*s)) @@ -49,6 +57,8 @@ char *ltrim(char *s) return s; } +///////////////////////////////////////////////// + char *rtrim(char *s) { char* back = s + strlen(s); @@ -60,11 +70,15 @@ char *rtrim(char *s) return s; } +///////////////////////////////////////////////// + char *trim(char *s) { return rtrim(ltrim(s)); } +///////////////////////////////////////////////// + size_t b64_encoded_size(size_t inlen) { size_t ret; @@ -79,6 +93,8 @@ size_t b64_encoded_size(size_t inlen) return ret; } +///////////////////////////////////////////////// + char * b64_encode(const unsigned char *in, size_t len, char * out) { const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -124,6 +140,8 @@ char * b64_encode(const unsigned char *in, size_t len, char * out) return out; } +///////////////////////////////////////////////// + size_t webSocketSendFrameWindow(AsyncClient *client) { if (!client->canSend()) @@ -137,6 +155,8 @@ size_t webSocketSendFrameWindow(AsyncClient *client) return (space - 8); } +///////////////////////////////////////////////// + size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len) { if (!client->canSend()) @@ -236,19 +256,22 @@ size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool return len; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// /* AsyncWebSocketMessageBuffer */ - +///////////////////////////////////////////////// AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer() : _data(nullptr), _len(0), _lock(false), _count(0) { - } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t * data, size_t size) : _data(nullptr), _len(size), _lock(false), _count(0) { @@ -266,6 +289,7 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t * data, size_t } } +///////////////////////////////////////////////// AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) : _data(nullptr), _len(size), _lock(false), _count(0) @@ -276,9 +300,10 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) { _data[_len] = 0; } - } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer & copy) : _data(nullptr), _len(0), _lock(false), _count(0) { @@ -297,9 +322,10 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const AsyncWebSocketMes memcpy(_data, copy._data, _len); _data[_len] = 0; } - } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer && copy) : _data(nullptr), _len(0), _lock(false), _count(0) { @@ -312,9 +338,10 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBu _data = copy._data; copy._data = nullptr; } - } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer() { if (_data) @@ -323,6 +350,8 @@ AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer() } } +///////////////////////////////////////////////// + bool AsyncWebSocketMessageBuffer::reserve(size_t size) { _len = size; @@ -345,9 +374,11 @@ bool AsyncWebSocketMessageBuffer::reserve(size_t size) { return false; } - } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Control Frame */ @@ -362,6 +393,9 @@ class AsyncWebSocketControl bool _finished; public: + + ///////////////////////////////////////////////// + AsyncWebSocketControl(uint8_t opcode, uint8_t *data = NULL, size_t len = 0, bool mask = false) : _opcode(opcode), _len(len), _mask(len && mask), _finished(false) { @@ -384,27 +418,37 @@ class AsyncWebSocketControl _data = NULL; } + ///////////////////////////////////////////////// + virtual ~AsyncWebSocketControl() { if (_data != NULL) free(_data); } + ///////////////////////////////////////////////// + virtual bool finished() const { return _finished; } - uint8_t opcode() + ///////////////////////////////////////////////// + + inline uint8_t opcode() { return _opcode; } - uint8_t len() + ///////////////////////////////////////////////// + + inline uint8_t len() { return _len + 2; } + ///////////////////////////////////////////////// + size_t send(AsyncClient *client) { _finished = true; @@ -412,6 +456,9 @@ class AsyncWebSocketControl } }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Basic Buffered Message */ @@ -435,6 +482,8 @@ AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(const char * data, size_t } } +///////////////////////////////////////////////// + AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(uint8_t opcode, bool mask) : _len(0), _sent(0), _ack(0), _acked(0), _data(NULL) { @@ -442,6 +491,7 @@ AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(uint8_t opcode, bool mask _mask = mask; } +///////////////////////////////////////////////// AsyncWebSocketBasicMessage::~AsyncWebSocketBasicMessage() { @@ -449,9 +499,12 @@ AsyncWebSocketBasicMessage::~AsyncWebSocketBasicMessage() free(_data); } +///////////////////////////////////////////////// + void AsyncWebSocketBasicMessage::ack(size_t len, uint32_t time) { - (void)time; + AWS_TEENSY41_UNUSED(time); + _acked += len; if (_sent == _len && _acked == _ack) @@ -460,6 +513,8 @@ void AsyncWebSocketBasicMessage::ack(size_t len, uint32_t time) } } +///////////////////////////////////////////////// + size_t AsyncWebSocketBasicMessage::send(AsyncClient *client) { if (_status != WS_MSG_SENDING) @@ -512,6 +567,8 @@ size_t AsyncWebSocketBasicMessage::send(AsyncClient *client) return sent; } +///////////////////////////////////////////////// + bool AsyncWebSocketBasicMessage::reserve(size_t size) { if (size) @@ -523,6 +580,7 @@ bool AsyncWebSocketBasicMessage::reserve(size_t size) memset(_data, 0, size); _len = size; _status = WS_MSG_SENDING; + return true; } } @@ -530,6 +588,9 @@ bool AsyncWebSocketBasicMessage::reserve(size_t size) return false; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* AsyncWebSocketMultiMessage Message */ @@ -554,9 +615,9 @@ AsyncWebSocketMultiMessage::AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuff { _status = WS_MSG_ERROR; } - } +///////////////////////////////////////////////// AsyncWebSocketMultiMessage::~AsyncWebSocketMultiMessage() { @@ -566,9 +627,12 @@ AsyncWebSocketMultiMessage::~AsyncWebSocketMultiMessage() } } +///////////////////////////////////////////////// + void AsyncWebSocketMultiMessage::ack(size_t len, uint32_t time) { - (void)time; + AWS_TEENSY41_UNUSED(time); + _acked += len; if (_sent >= _len && _acked >= _ack) @@ -579,6 +643,8 @@ void AsyncWebSocketMultiMessage::ack(size_t len, uint32_t time) LOGDEBUG1("ACK:", _len); } +///////////////////////////////////////////////// + size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) { if (_status != WS_MSG_SENDING) @@ -592,6 +658,7 @@ size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) if (_sent == _len) { _status = WS_MSG_SENT; + return 0; } @@ -634,6 +701,8 @@ size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) return sent; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// /* Async WebSocket Client @@ -641,6 +710,8 @@ size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) const char * AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING"; const size_t AWSC_PING_PAYLOAD_LEN = 22; +///////////////////////////////////////////////// + AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) : _controlQueue(LinkedList([](AsyncWebSocketControl * c) { @@ -662,13 +733,15 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async _client->onError([](void *r, AsyncClient * c, int8_t error) { - (void)c; + AWS_TEENSY41_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onError(error); }, this); _client->onAck([](void *r, AsyncClient * c, size_t len, uint32_t time) { - (void)c; + AWS_TEENSY41_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this); @@ -680,19 +753,22 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async _client->onTimeout([](void *r, AsyncClient * c, uint32_t time) { - (void)c; + AWS_TEENSY41_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this); _client->onData([](void *r, AsyncClient * c, void *buf, size_t len) { - (void)c; + AWS_TEENSY41_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this); _client->onPoll([](void *r, AsyncClient * c) { - (void)c; + AWS_TEENSY41_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onPoll(); }, this); @@ -702,6 +778,8 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async delete request; } +///////////////////////////////////////////////// + AsyncWebSocketClient::~AsyncWebSocketClient() { _messageQueue.free(); @@ -709,6 +787,8 @@ AsyncWebSocketClient::~AsyncWebSocketClient() _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) { _lastMessageTime = millis(); @@ -742,6 +822,8 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) _runQueue(); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onPoll() { if (_client->canSend() && (!_controlQueue.isEmpty() || !_messageQueue.isEmpty())) { @@ -753,6 +835,8 @@ void AsyncWebSocketClient::_onPoll() } } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_runQueue() { while (!_messageQueue.isEmpty() && _messageQueue.front()->finished()) @@ -771,6 +855,8 @@ void AsyncWebSocketClient::_runQueue() } } +///////////////////////////////////////////////// + bool AsyncWebSocketClient::queueIsFull() { if ((_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED) ) @@ -779,6 +865,8 @@ bool AsyncWebSocketClient::queueIsFull() return false; } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage) { if (dataMessage == NULL) @@ -804,6 +892,8 @@ void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage) _runQueue(); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage) { if (controlMessage == NULL) @@ -815,6 +905,8 @@ void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage) _runQueue(); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::close(uint16_t code, const char * message) { if (_status != WS_CONNECTED) @@ -856,26 +948,37 @@ void AsyncWebSocketClient::close(uint16_t code, const char * message) _queueControl(new AsyncWebSocketControl(WS_DISCONNECT)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::ping(uint8_t *data, size_t len) { if (_status == WS_CONNECTED) _queueControl(new AsyncWebSocketControl(WS_PING, data, len)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onError(int8_t) {} +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onTimeout(uint32_t time) { - (void)time; + AWS_TEENSY41_UNUSED(time); + _client->close(true); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onDisconnect() { _client = NULL; _server->_handleDisconnect(this); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { _lastMessageTime = millis(); @@ -900,9 +1003,11 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) data += 2; plen -= 2; } - else if (_pinfo.len == 127) { + else if (_pinfo.len == 127) + { _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 - | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56; + | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 + | (uint64_t)(fdata[2]) << 56; data += 8; plen -= 8; } @@ -930,7 +1035,8 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) if (_pinfo.index == 0) { - if (_pinfo.opcode) { + if (_pinfo.opcode) + { _pinfo.message_opcode = _pinfo.opcode; _pinfo.num = 0; } @@ -1002,6 +1108,8 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) } } +///////////////////////////////////////////////// + size_t AsyncWebSocketClient::printf(const char *format, ...) { va_list arg; @@ -1011,6 +1119,7 @@ size_t AsyncWebSocketClient::printf(const char *format, ...) if (!temp) { va_end(arg); + return 0; } @@ -1025,6 +1134,7 @@ size_t AsyncWebSocketClient::printf(const char *format, ...) if (!buffer) { delete[] temp; + return 0; } @@ -1041,69 +1151,96 @@ size_t AsyncWebSocketClient::printf(const char *format, ...) } delete[] temp; + return len; } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(const char * message, size_t len) { _queueMessage(new AsyncWebSocketBasicMessage(message, len)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(const char * message) { text(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(uint8_t * message, size_t len) { text((const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(char * message) { text(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(const String & message) { text(message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer * buffer) { _queueMessage(new AsyncWebSocketMultiMessage(buffer)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(const char * message, size_t len) { _queueMessage(new AsyncWebSocketBasicMessage(message, len, WS_BINARY)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(const char * message) { binary(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(uint8_t * message, size_t len) { binary((const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(char * message) { binary(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(const String & message) { binary(message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer) { _queueMessage(new AsyncWebSocketMultiMessage(buffer, WS_BINARY)); } +///////////////////////////////////////////////// + IPAddress AsyncWebSocketClient::remoteIP() { if (!_client) @@ -1115,6 +1252,8 @@ IPAddress AsyncWebSocketClient::remoteIP() return _client->remoteIP(); } +///////////////////////////////////////////////// + uint16_t AsyncWebSocketClient::remotePort() { if (!_client) @@ -1125,6 +1264,9 @@ uint16_t AsyncWebSocketClient::remotePort() return _client->remotePort(); } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Async Web Socket - Each separate socket location */ @@ -1143,8 +1285,12 @@ AsyncWebSocket::AsyncWebSocket(const String & url) _eventHandler = NULL; } +///////////////////////////////////////////////// + AsyncWebSocket::~AsyncWebSocket() {} +///////////////////////////////////////////////// + void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) { if (_eventHandler != NULL) @@ -1153,20 +1299,25 @@ void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType ty } } +///////////////////////////////////////////////// + void AsyncWebSocket::_addClient(AsyncWebSocketClient * client) { _clients.add(client); } +///////////////////////////////////////////////// + void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient * client) { - _clients.remove_first([ = ](AsyncWebSocketClient * c) { return c->id() == client->id(); }); } +///////////////////////////////////////////////// + bool AsyncWebSocket::availableForWriteAll() { for (const auto& c : _clients) @@ -1178,6 +1329,8 @@ bool AsyncWebSocket::availableForWriteAll() return true; } +///////////////////////////////////////////////// + bool AsyncWebSocket::availableForWrite(uint32_t id) { for (const auto& c : _clients) @@ -1189,6 +1342,8 @@ bool AsyncWebSocket::availableForWrite(uint32_t id) return true; } +///////////////////////////////////////////////// + size_t AsyncWebSocket::count() const { return _clients.count_if([](AsyncWebSocketClient * c) @@ -1197,6 +1352,8 @@ size_t AsyncWebSocket::count() const }); } +///////////////////////////////////////////////// + AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id) { for (const auto &c : _clients) @@ -1210,6 +1367,7 @@ AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id) return nullptr; } +///////////////////////////////////////////////// void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message) { @@ -1219,6 +1377,8 @@ void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message) c->close(code, message); } +///////////////////////////////////////////////// + void AsyncWebSocket::closeAll(uint16_t code, const char * message) { for (const auto& c : _clients) @@ -1228,6 +1388,8 @@ void AsyncWebSocket::closeAll(uint16_t code, const char * message) } } +///////////////////////////////////////////////// + void AsyncWebSocket::cleanupClients(uint16_t maxClients) { if (count() > maxClients) @@ -1236,6 +1398,8 @@ void AsyncWebSocket::cleanupClients(uint16_t maxClients) } } +///////////////////////////////////////////////// + void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len) { AsyncWebSocketClient * c = client(id); @@ -1244,6 +1408,8 @@ void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len) c->ping(data, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::pingAll(uint8_t *data, size_t len) { for (const auto& c : _clients) @@ -1253,6 +1419,8 @@ void AsyncWebSocket::pingAll(uint8_t *data, size_t len) } } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, const char * message, size_t len) { AsyncWebSocketClient * c = client(id); @@ -1261,6 +1429,8 @@ void AsyncWebSocket::text(uint32_t id, const char * message, size_t len) c->text(message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer) { if (!buffer) @@ -1280,6 +1450,7 @@ void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer) _cleanBuffers(); } +///////////////////////////////////////////////// void AsyncWebSocket::textAll(const char * message, size_t len) { @@ -1287,6 +1458,8 @@ void AsyncWebSocket::textAll(const char * message, size_t len) textAll(WSBuffer); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len) { AsyncWebSocketClient * c = client(id); @@ -1295,12 +1468,16 @@ void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len) c->binary(message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(const char * message, size_t len) { AsyncWebSocketMessageBuffer * buffer = makeBuffer((uint8_t *)message, len); binaryAll(buffer); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer) { if (!buffer) @@ -1318,6 +1495,8 @@ void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer) _cleanBuffers(); } +///////////////////////////////////////////////// + void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage * message) { AsyncWebSocketClient * c = client(id); @@ -1326,6 +1505,8 @@ void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage * message) c->message(message); } +///////////////////////////////////////////////// + void AsyncWebSocket::messageAll(AsyncWebSocketMultiMessage * message) { for (const auto& c : _clients) @@ -1337,6 +1518,8 @@ void AsyncWebSocket::messageAll(AsyncWebSocketMultiMessage * message) _cleanBuffers(); } +///////////////////////////////////////////////// + size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...) { AsyncWebSocketClient * c = client(id); @@ -1347,12 +1530,15 @@ size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...) va_start(arg, format); size_t len = c->printf(format, arg); va_end(arg); + return len; } return 0; } +///////////////////////////////////////////////// + size_t AsyncWebSocket::printfAll(const char *format, ...) { va_list arg; @@ -1380,97 +1566,134 @@ size_t AsyncWebSocket::printfAll(const char *format, ...) va_end(arg); textAll(buffer); + return len; } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, const char * message) { text(id, message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, uint8_t * message, size_t len) { text(id, (const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, char * message) { text(id, message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, const String & message) { text(id, message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(const char * message) { textAll(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(uint8_t * message, size_t len) { textAll((const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(char * message) { textAll(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(const String & message) { textAll(message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, const char * message) { binary(id, message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, uint8_t * message, size_t len) { binary(id, (const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, char * message) { binary(id, message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, const String & message) { binary(id, message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(const char * message) { binaryAll(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(uint8_t * message, size_t len) { binaryAll((const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(char * message) { binaryAll(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(const String & message) { binaryAll(message.c_str(), message.length()); } +///////////////////////////////////////////////// + const char * WS_STR_CONNECTION = "Connection"; -const char * WS_STR_UPGRADE = "Upgrade"; -const char * WS_STR_ORIGIN = "Origin"; -const char * WS_STR_VERSION = "Sec-WebSocket-Version"; -const char * WS_STR_KEY = "Sec-WebSocket-Key"; -const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol"; -const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept"; -const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +const char * WS_STR_UPGRADE = "Upgrade"; +const char * WS_STR_ORIGIN = "Origin"; +const char * WS_STR_VERSION = "Sec-WebSocket-Version"; +const char * WS_STR_KEY = "Sec-WebSocket-Key"; +const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol"; +const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept"; +const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +///////////////////////////////////////////////// bool AsyncWebSocket::canHandle(AsyncWebServerRequest * request) { @@ -1490,6 +1713,8 @@ bool AsyncWebSocket::canHandle(AsyncWebServerRequest * request) return true; } +///////////////////////////////////////////////// + void AsyncWebSocket::handleRequest(AsyncWebServerRequest * request) { if (!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)) @@ -1499,7 +1724,8 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest * request) return; } - if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) { + if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + { return request->requestAuthentication(); } @@ -1527,6 +1753,8 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest * request) request->send(response); } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size) { AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(size); @@ -1540,6 +1768,8 @@ AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size) return buffer; } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t size) { AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(data, size); @@ -1553,6 +1783,8 @@ AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t return buffer; } +///////////////////////////////////////////////// + void AsyncWebSocket::_cleanBuffers() { AsyncWebLockGuard l(_lock); @@ -1566,28 +1798,21 @@ void AsyncWebSocket::_cleanBuffers() } } +///////////////////////////////////////////////// + AsyncWebSocket::AsyncWebSocketClientLinkedList AsyncWebSocket::getClients() const { return _clients; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480 */ -/*static */ -/*void acceptKey(char * skey, char * ckey) { - char sha1HashBin[22] = { 0 }; - //String key = base64_encode(sha1HashBin, 20); - __disable_irq(); - char buf[256]; - sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", skey); - SHA1(sha1HashBin, (const char *)buf, strlen(buf)); - b64_encode((const unsigned char *)sha1HashBin, 20, buf); - sprintf(ckey, "%s", trim(buf)); - __enable_irq(); -} -*/ + AsyncWebSocketResponse::AsyncWebSocketResponse(const String & key, AsyncWebSocket * server) { _server = server; @@ -1599,6 +1824,7 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String & key, AsyncWebSocke if (hash == NULL) { _state = RESPONSE_FAILED; + return; } @@ -1608,11 +1834,12 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String & key, AsyncWebSocke { free(hash); _state = RESPONSE_FAILED; + return; } LOGDEBUG1("key =", key.c_str()); - // KH, for Teensy41 + // KH, for Ethernet sha1_context _ctx; (String&) key += WS_STR_UUID; @@ -1633,22 +1860,28 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String & key, AsyncWebSocke free(hash); } +///////////////////////////////////////////////// + void AsyncWebSocketResponse::_respond(AsyncWebServerRequest * request) { if (_state == RESPONSE_FAILED) { request->client()->close(true); + return; } String out = _assembleHead(request->version()); request->client()->write(out.c_str(), _headLength); + _state = RESPONSE_WAIT_ACK; } +///////////////////////////////////////////////// + size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { - (void)time; + AWS_TEENSY41_UNUSED(time); if (len) { diff --git a/src/AsyncWebSocket_Teensy41.h b/src/AsyncWebSocket_Teensy41.h index 2f02574..349ffd3 100644 --- a/src/AsyncWebSocket_Teensy41.h +++ b/src/AsyncWebSocket_Teensy41.h @@ -14,15 +14,17 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -35,20 +37,26 @@ // Teensy41 #include +///////////////////////////////////////////////// + #define WS_MAX_QUEUED_MESSAGES 32 //#define WS_MAX_QUEUED_MESSAGES 8 #define DEFAULT_MAX_WS_CLIENTS 8 //#define DEFAULT_MAX_WS_CLIENTS 4 +///////////////////////////////////////////////// + #include "AsyncWebSynchronization_Teensy41.h" +///////////////////////////////////////////////// + class AsyncWebSocket; class AsyncWebSocketResponse; class AsyncWebSocketClient; class AsyncWebSocketControl; - +///////////////////////////////////////////////// typedef struct { @@ -75,6 +83,8 @@ typedef struct uint64_t index; } AwsFrameInfo; +///////////////////////////////////////////////// + typedef enum { WS_DISCONNECTED, @@ -108,6 +118,8 @@ typedef enum WS_EVT_DATA } AwsEventType; +///////////////////////////////////////////////// + class AsyncWebSocketMessageBuffer { private: @@ -124,15 +136,20 @@ class AsyncWebSocketMessageBuffer AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&); ~AsyncWebSocketMessageBuffer(); + ///////////////////////////////////////////////// + void operator ++(int i) { - (void)i; + AWS_TEENSY41_UNUSED(i); + _count++; } + ///////////////////////////////////////////////// + void operator --(int i) { - (void)i; + AWS_TEENSY41_UNUSED(i); if (_count > 0) { @@ -140,42 +157,60 @@ class AsyncWebSocketMessageBuffer } ; } + ///////////////////////////////////////////////// + bool reserve(size_t size); - void lock() + ///////////////////////////////////////////////// + + inline void lock() { _lock = true; } - void unlock() + ///////////////////////////////////////////////// + + inline void unlock() { _lock = false; } - uint8_t * get() + ///////////////////////////////////////////////// + + inline uint8_t * get() { return _data; } - size_t length() + ///////////////////////////////////////////////// + + inline size_t length() { return _len; } - uint32_t count() + ///////////////////////////////////////////////// + + inline uint32_t count() { return _count; } - bool canDelete() + ///////////////////////////////////////////////// + + inline bool canDelete() { return (!_count && !_lock); } + ///////////////////////////////////////////////// + friend AsyncWebSocket; }; +///////////////////////////////////////////////// + class AsyncWebSocketMessage { protected: @@ -188,22 +223,30 @@ class AsyncWebSocketMessage virtual ~AsyncWebSocketMessage() {} virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {} - virtual size_t send(AsyncClient *client __attribute__((unused))) + ///////////////////////////////////////////////// + + virtual size_t send(AsyncClient *client __attribute__((unused))) { return 0; } - virtual bool finished() + ///////////////////////////////////////////////// + + virtual bool finished() { return _status != WS_MSG_SENDING; } - virtual bool betweenFrames() const + ///////////////////////////////////////////////// + + virtual bool betweenFrames() const { return false; } }; +///////////////////////////////////////////////// + class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage { private: @@ -218,17 +261,23 @@ class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage AsyncWebSocketBasicMessage(uint8_t opcode = WS_TEXT, bool mask = false); virtual ~AsyncWebSocketBasicMessage() override; - virtual bool betweenFrames() const override + ///////////////////////////////////////////////// + + virtual bool betweenFrames() const override { return _acked == _ack; } + ///////////////////////////////////////////////// + virtual void ack(size_t len, uint32_t time) override; virtual size_t send(AsyncClient *client) override; virtual bool reserve(size_t size); }; +///////////////////////////////////////////////// + class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage { private: @@ -243,15 +292,21 @@ class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode = WS_TEXT, bool mask = false); virtual ~AsyncWebSocketMultiMessage() override; - virtual bool betweenFrames() const override + ///////////////////////////////////////////////// + + virtual bool betweenFrames() const override { return _acked == _ack; } + ///////////////////////////////////////////////// + virtual void ack(size_t len, uint32_t time) override ; virtual size_t send(AsyncClient *client) override ; }; +///////////////////////////////////////////////// + class AsyncWebSocketClient { private: @@ -279,32 +334,44 @@ class AsyncWebSocketClient AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); ~AsyncWebSocketClient(); + ////////////////////////////////////////////////// + //client id increments for the given server - uint32_t id() + inline uint32_t id() { return _clientId; } - AwsClientStatus status() + ///////////////////////////////////////////////// + + inline AwsClientStatus status() { return _status; } - AsyncClient* client() + ///////////////////////////////////////////////// + + inline AsyncClient* client() { return _client; } - AsyncWebSocket *server() + ///////////////////////////////////////////////// + + inline AsyncWebSocket *server() { return _server; } - AwsFrameInfo const &pinfo() const + ///////////////////////////////////////////////// + + inline AwsFrameInfo const &pinfo() const { return _pinfo; } + ///////////////////////////////////////////////// + IPAddress remoteIP(); uint16_t remotePort(); @@ -312,23 +379,31 @@ class AsyncWebSocketClient void close(uint16_t code = 0, const char * message = NULL); void ping(uint8_t *data = NULL, size_t len = 0); + ///////////////////////////////////////////////// + //set auto-ping period in seconds. disabled if zero (default) - void keepAlivePeriod(uint16_t seconds) + inline void keepAlivePeriod(uint16_t seconds) { _keepAlivePeriod = seconds * 1000; } - uint16_t keepAlivePeriod() + ///////////////////////////////////////////////// + + inline uint16_t keepAlivePeriod() { return (uint16_t)(_keepAlivePeriod / 1000); } + ///////////////////////////////////////////////// + //data packets - void message(AsyncWebSocketMessage *message) + inline void message(AsyncWebSocketMessage *message) { _queueMessage(message); } + ///////////////////////////////////////////////// + bool queueIsFull(); size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); @@ -347,11 +422,15 @@ class AsyncWebSocketClient void binary(const String &message); void binary(AsyncWebSocketMessageBuffer *buffer); - bool canSend() + ///////////////////////////////////////////////// + + inline bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; } + ///////////////////////////////////////////////// + //system callbacks (do not call) void _onAck(size_t len, uint32_t time); void _onError(int8_t); @@ -361,8 +440,12 @@ class AsyncWebSocketClient void _onData(void *pbuf, size_t plen); }; +///////////////////////////////////////////////// + typedef std::function AwsEventHandler; +///////////////////////////////////////////////// + //WebServer Handler implementation that plays the role of a socket server class AsyncWebSocket: public AsyncWebHandler { @@ -381,32 +464,44 @@ class AsyncWebSocket: public AsyncWebHandler AsyncWebSocket(const String& url); ~AsyncWebSocket(); - const char * url() const + ///////////////////////////////////////////////// + + inline const char * url() const { return _url.c_str(); } - void enable(bool e) + ///////////////////////////////////////////////// + + inline void enable(bool e) { _enabled = e; } - bool enabled() const + ///////////////////////////////////////////////// + + inline bool enabled() const { return _enabled; } + ///////////////////////////////////////////////// + bool availableForWriteAll(); bool availableForWrite(uint32_t id); size_t count() const; AsyncWebSocketClient * client(uint32_t id); - bool hasClient(uint32_t id) + ///////////////////////////////////////////////// + + inline bool hasClient(uint32_t id) { return client(id) != NULL; } + ///////////////////////////////////////////////// + void close(uint32_t id, uint16_t code = 0, const char * message = NULL); void closeAll(uint16_t code = 0, const char * message = NULL); void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); @@ -446,25 +541,30 @@ class AsyncWebSocket: public AsyncWebHandler size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); + ///////////////////////////////////////////////// + //event listener - void onEvent(AwsEventHandler handler) + inline void onEvent(AwsEventHandler handler) { _eventHandler = handler; } + ///////////////////////////////////////////////// + //system callbacks (do not call) - uint32_t _getNextId() + inline uint32_t _getNextId() { return _cNextId++; } + ///////////////////////////////////////////////// + void _addClient(AsyncWebSocketClient * client); void _handleDisconnect(AsyncWebSocketClient * client); void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); virtual bool canHandle(AsyncWebServerRequest *request) override final; virtual void handleRequest(AsyncWebServerRequest *request) override final; - // messagebuffer functions/objects. AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); @@ -474,6 +574,8 @@ class AsyncWebSocket: public AsyncWebHandler AsyncWebSocketClientLinkedList getClients() const; }; +///////////////////////////////////////////////// + //WebServer response to authenticate the socket and detach the tcp client from the web server request class AsyncWebSocketResponse: public AsyncWebServerResponse { @@ -486,11 +588,14 @@ class AsyncWebSocketResponse: public AsyncWebServerResponse void _respond(AsyncWebServerRequest *request); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return true; } }; +///////////////////////////////////////////////// #endif /* ASYNCWEBSOCKET_TEENSY41_H_ */ diff --git a/src/AsyncWebSynchronization_Teensy41.h b/src/AsyncWebSynchronization_Teensy41.h index e1e8c3f..38f0340 100644 --- a/src/AsyncWebSynchronization_Teensy41.h +++ b/src/AsyncWebSynchronization_Teensy41.h @@ -14,15 +14,17 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -34,44 +36,59 @@ #include +///////////////////////////////////////////////// + // This is the Teensy41 version of the Sync Lock which is currently unimplemented class AsyncWebLock { -public: - AsyncWebLock() { - } + public: + AsyncWebLock() {} + + ~AsyncWebLock() {} - ~AsyncWebLock() { - } + ///////////////////////////////////////////////// + + inline bool lock() const + { + return false; + } - bool lock() const { - return false; - } + ///////////////////////////////////////////////// - void unlock() const { - } + inline void unlock() const {} }; class AsyncWebLockGuard { -private: - const AsyncWebLock *_lock; - -public: - AsyncWebLockGuard(const AsyncWebLock &l) { - if (l.lock()) { - _lock = &l; - } else { - _lock = NULL; + private: + const AsyncWebLock *_lock; + + public: + + ///////////////////////////////////////////////// + + AsyncWebLockGuard(const AsyncWebLock &l) + { + if (l.lock()) + { + _lock = &l; + } + else + { + _lock = NULL; + } } - } - ~AsyncWebLockGuard() { - if (_lock) { - _lock->unlock(); + ///////////////////////////////////////////////// + + ~AsyncWebLockGuard() + { + if (_lock) + { + _lock->unlock(); + } } - } }; #endif // ASYNCWEBSYNCHRONIZATION_TEENSY41_H_ diff --git a/src/Crypto/Hash.h b/src/Crypto/Hash.h index 192f38a..f8a457b 100644 --- a/src/Crypto/Hash.h +++ b/src/Crypto/Hash.h @@ -27,7 +27,7 @@ #ifndef HASH_H_ #define HASH_H_ -#include "AsyncWebServer_Ethernet_Debug.h" +#include "AsyncWebServer_Teensy41_Debug.h" #ifdef HASH_BUFFER_SIZE #undef HASH_BUFFER_SIZE diff --git a/src/Crypto/bearssl_hash.h b/src/Crypto/bearssl_hash.h index 9455814..12263d4 100644 --- a/src/Crypto/bearssl_hash.h +++ b/src/Crypto/bearssl_hash.h @@ -27,7 +27,7 @@ #ifndef BR_BEARSSL_HASH_H__ #define BR_BEARSSL_HASH_H__ -#include "AsyncWebServer_Ethernet_Debug.h" +#include "AsyncWebServer_Teensy41_Debug.h" #include #include @@ -1315,7 +1315,7 @@ void br_ghash_pclmul(void *y, const void *h, const void *data, size_t len); * * \return the `pclmul` GHASH implementation, or `0`. */ -br_ghash br_ghash_pclmul_get(void); +br_ghash br_ghash_pclmul_get(); /** * \brief GHASH implementation using the POWER8 opcodes. @@ -1341,7 +1341,7 @@ void br_ghash_pwr8(void *y, const void *h, const void *data, size_t len); * * \return the `pwr8` GHASH implementation, or `0`. */ -br_ghash br_ghash_pwr8_get(void); +br_ghash br_ghash_pwr8_get(); #ifdef __cplusplus } diff --git a/src/Crypto/md5.h b/src/Crypto/md5.h index eda48c7..034e331 100644 --- a/src/Crypto/md5.h +++ b/src/Crypto/md5.h @@ -38,7 +38,7 @@ #ifndef LWIP_INCLUDED_POLARSSL_MD5_H #define LWIP_INCLUDED_POLARSSL_MD5_H -#include "AsyncWebServer_Ethernet_Debug.h" +#include "AsyncWebServer_Teensy41_Debug.h" /** * \brief MD5 context structure diff --git a/src/Crypto/sha1.h b/src/Crypto/sha1.h index b18e1f0..2d141d1 100644 --- a/src/Crypto/sha1.h +++ b/src/Crypto/sha1.h @@ -38,7 +38,7 @@ #ifndef LWIP_INCLUDED_POLARSSL_SHA1_H #define LWIP_INCLUDED_POLARSSL_SHA1_H -#include "AsyncWebServer_Ethernet_Debug.h" +#include "AsyncWebServer_Teensy41_Debug.h" #ifdef SHA1_BUFFER_SIZE #undef SHA1_BUFFER_SIZE diff --git a/src/StringArray_Teensy41.h b/src/StringArray_Teensy41.h index ef55409..771668f 100644 --- a/src/StringArray_Teensy41.h +++ b/src/StringArray_Teensy41.h @@ -14,15 +14,17 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.5.0 + Version: 1.6.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for Teensy 4.1 using built-in QNEthernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 1.5.0 K Hoang 01/10/2022 Fix issue with slow browsers or network. Add function and example to support favicon.ico + 1.6.0 K Hoang 06/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -33,6 +35,8 @@ #include "stddef.h" #include "WString.h" +///////////////////////////////////////////////// + template class LinkedListNode { @@ -43,17 +47,24 @@ class LinkedListNode LinkedListNode(const T val): _value(val), next(nullptr) {} ~LinkedListNode() {} - const T& value() const + ///////////////////////////////////////////////// + + inline const T& value() const { return _value; }; - - T& value() + + ///////////////////////////////////////////////// + + inline T& value() { return _value; } }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + template class Item = LinkedListNode> class LinkedList { @@ -74,23 +85,31 @@ class LinkedList Iterator(ItemType* current = nullptr) : _node(current) {} Iterator(const Iterator& i) : _node(i._node) {} - Iterator& operator ++() + ///////////////////////////////////////////////// + + inline Iterator& operator ++() { _node = _node->next; return *this; } - - bool operator != (const Iterator& i) const + + ///////////////////////////////////////////////// + + inline bool operator != (const Iterator& i) const { return _node != i._node; } - - const T& operator * () const + + ///////////////////////////////////////////////// + + inline const T& operator * () const { return _node->value(); } - - const T* operator -> () const + + ///////////////////////////////////////////////// + + inline const T* operator -> () const { return &_node->value(); } @@ -99,118 +118,139 @@ class LinkedList public: typedef const Iterator ConstIterator; - ConstIterator begin() const + ///////////////////////////////////////////////// + + inline ConstIterator begin() const { return ConstIterator(_root); } - - ConstIterator end() const + + ///////////////////////////////////////////////// + + inline ConstIterator end() const { return ConstIterator(nullptr); } + ///////////////////////////////////////////////// + LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} ~LinkedList() {} - - void add(const T& t) + + ///////////////////////////////////////////////// + + void add(const T& t) { auto it = new ItemType(t); - - if (!_root) + + if (!_root) { _root = it; - } - else + } + else { auto i = _root; - - while (i->next) + + while (i->next) i = i->next; - + i->next = it; } } - - T& front() const + + ///////////////////////////////////////////////// + + inline T& front() const { return _root->value(); } - bool isEmpty() const + ///////////////////////////////////////////////// + + inline bool isEmpty() const { return _root == nullptr; } - - size_t length() const + + ///////////////////////////////////////////////// + + size_t length() const { size_t i = 0; auto it = _root; - - while (it) + + while (it) { i++; it = it->next; } - + return i; } - - size_t count_if(Predicate predicate) const + + ///////////////////////////////////////////////// + + size_t count_if(Predicate predicate) const { size_t i = 0; auto it = _root; - - while (it) + + while (it) { - if (!predicate) + if (!predicate) { i++; } - else if (predicate(it->value())) + else if (predicate(it->value())) { i++; } - + it = it->next; } + return i; } - - const T* nth(size_t N) const + + ///////////////////////////////////////////////// + + const T* nth(size_t N) const { size_t i = 0; auto it = _root; - while (it) + while (it) { if (i++ == N) return &(it->value()); - + it = it->next; } - + return nullptr; } - - bool remove(const T& t) + + ///////////////////////////////////////////////// + + bool remove(const T& t) { auto it = _root; auto pit = _root; - - while (it) + + while (it) { - if (it->value() == t) + if (it->value() == t) { - if (it == _root) + if (it == _root) { _root = _root->next; - } - else + } + else { pit->next = it->next; } - if (_onRemove) + if (_onRemove) { _onRemove(it->value()); } @@ -218,67 +258,73 @@ class LinkedList delete it; return true; } - + pit = it; it = it->next; } - + return false; } - - bool remove_first(Predicate predicate) + + ///////////////////////////////////////////////// + + bool remove_first(Predicate predicate) { auto it = _root; auto pit = _root; - - while (it) + + while (it) { - if (predicate(it->value())) + if (predicate(it->value())) { - if (it == _root) + if (it == _root) { _root = _root->next; - } - else + } + else { pit->next = it->next; } - - if (_onRemove) + + if (_onRemove) { _onRemove(it->value()); } - + delete it; return true; } - + pit = it; it = it->next; } - + return false; } - void free() + ///////////////////////////////////////////////// + + void free() { - while (_root != nullptr) + while (_root != nullptr) { auto it = _root; _root = _root->next; - - if (_onRemove) + + if (_onRemove) { _onRemove(it->value()); } - + delete it; } - + _root = nullptr; } }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// class StringArray : public LinkedList { @@ -286,16 +332,18 @@ class StringArray : public LinkedList StringArray() : LinkedList(nullptr) {} - bool containsIgnoreCase(const String& str) + ///////////////////////////////////////////////// + + bool containsIgnoreCase(const String& str) { - for (const auto& s : *this) + for (const auto& s : *this) { - if (str.equalsIgnoreCase(s)) + if (str.equalsIgnoreCase(s)) { return true; } } - + return false; } }; diff --git a/src/libb64/cdecode.c b/src/libb64/cdecode.c index 9f2a1c6..9c06675 100644 --- a/src/libb64/cdecode.c +++ b/src/libb64/cdecode.c @@ -4,12 +4,12 @@ This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 - For ESP8266 using W5x00/ENC8266 Ethernet + For Teensy41 with QNEthernet - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Copyright (c) 2016 Hristo Gochkov. All rights reserved. This file is part of the esp8266 core for Arduino environment. @@ -18,7 +18,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. - If not, see + If not, see . *****************************************************************************************************************************/ #include "cdecode.h" diff --git a/src/libb64/cdecode.h b/src/libb64/cdecode.h index 91fdceb..1872c6c 100644 --- a/src/libb64/cdecode.h +++ b/src/libb64/cdecode.h @@ -4,12 +4,12 @@ This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 - For ESP8266 using W5x00/ENC8266 Ethernet + For Teensy41 with QNEthernet - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Copyright (c) 2016 Hristo Gochkov. All rights reserved. This file is part of the esp8266 core for Arduino environment. @@ -18,7 +18,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. - If not, see + If not, see . *****************************************************************************************************************************/ #pragma once diff --git a/src/libb64/cencode.c b/src/libb64/cencode.c index 7405704..671244e 100644 --- a/src/libb64/cencode.c +++ b/src/libb64/cencode.c @@ -4,12 +4,12 @@ This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 - For ESP8266 using W5x00/ENC8266 Ethernet + For Teensy41 with QNEthernet - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Copyright (c) 2016 Hristo Gochkov. All rights reserved. This file is part of the esp8266 core for Arduino environment. @@ -18,7 +18,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. - If not, see + If not, see . *****************************************************************************************************************************/ #include "cencode.h" diff --git a/src/libb64/cencode.h b/src/libb64/cencode.h index 8972390..9d86714 100644 --- a/src/libb64/cencode.h +++ b/src/libb64/cencode.h @@ -4,12 +4,12 @@ This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 - For ESP8266 using W5x00/ENC8266 Ethernet + For Teensy41 with QNEthernet - AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + AsyncWebServer_Teensy41 is a library for the Teensy41 with QNEthernet Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) - Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Teensy41 Copyright (c) 2016 Hristo Gochkov. All rights reserved. This file is part of the esp8266 core for Arduino environment. @@ -18,7 +18,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. - If not, see + If not, see . *****************************************************************************************************************************/ #pragma once