Your new topic does not fit any of the above??? Check first. Then post here. Thanks.

Moderator: igrr

User avatar
By Vicne
#53661
Me-no-dev wrote:change in between 2.3.0 and 2.4.0-pre is in the regular TCP client, which has nothing to do with Async TCP lib, therefore there should be no change at all.

I see. I thought the change was at a lower level used by both.
This change is kinda mix between async and sync and is causing issues with some libraries and is mixing the loop context with the system context (where async runs).
Since the regular TCP Client is almost blocking to send the data as fast as possible, I'm not surprised by the results. Running async does not force any delay on any other service / loop code running elsewhere.
Results also depend on what is your loop doing,

In these tests, there is nothing else running, so Async is not used at its best for sure.
how busy is your network with others :)

You mind sharing your test sketches?

I should have done it already, sorry. Here we go:

First, here is my custom version of the "sync" sketch. The main difference compared to the samples is that it's slightly optimized so that very small payloads are included in the same packet as the headers instead of requiring two packets.
It also uses the WifiManager lib so you don't have to hardcode credentials in the sketch.
A sample call is http://<ip>/variable?size=100000

Code: Select all#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

#include <WiFiManager.h>          // from lib https://github.com/tzapu/WiFiManager

#define LED_PIN             2

// Device name
#define DEVICE_NAME         "SpeedTestDemo"

// AP & web servers configuration
#define CLIENT_CONFIG_SSID  DEVICE_NAME"_CFG"
#define HTTP_PORT           80

String dummy1460 = "012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";


ESP8266WebServer server(HTTP_PORT);


void handleVariable() {
    if(server.args() == 0) return server.send(500, "text/plain", "Argument size=xxx is required");
   
    int length = atoi(server.arg(0).c_str());
    if(length == 0) return server.send(500, "text/plain", "Wrong size value in parameter");
   
    WiFiClient client = server.client();

    String header = "HTTP/1.0 200 OK\r\n";
    //header += "Content-Type: text/plain\r\n";
    header += "Content-Type: application/octet-stream\r\n";
    header += "Content-Length: " + String(length) + "\r\n";
    header += "Content-Disposition: attachment; filename=data_" + String(length) + ".dat\r\n";
    header += "Connection: close\r\n";
    header += "\r\n";

    // Prepare 1st packet
    long start=millis();
    //Serial.println("Preparing first packet...");
    String packet = header;

    int remain = length;
    while(remain > 0) {
      int alreadyUsed = packet.length();
      if (alreadyUsed + remain <= 1460) {
        // remaining payload fits.
        packet += dummy1460.substring(0, remain);
        remain = 0;
      }
      else {
        // remaining payload doesn't fit
        packet += dummy1460.substring(0, 1460-alreadyUsed);
        remain -= (1460-alreadyUsed);
      }
      //Serial.println(String("P= ") + packet.length() + ". R=" + remain);
      client.write(packet.c_str(), packet.length());
      packet = "";
    }
    long total=millis()-start;
    Serial.println(String(length) + " bytes payload sent in " + total + "ms or " + (8000 * length)/total + "bps");
}


// *******************************************
// Main
// *******************************************
void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.print("\n");
  delay(1);
 
  //WiFiManager
  Serial.println();
  Serial.println("Initializing Wi-Fi");

  digitalWrite(LED_PIN, LOW); // Turn led ON by making voltage LOW to indicate start of wifi setup

 
  //Local intialization. Once its business is done, there is no need to keep it around
  WiFiManager wifiManager;

  //fetches ssid and pass from eeprom and tries to connect
  //if it does not connect it starts an access point with the specified name
  //and goes into a blocking loop awaiting configuration
  wifiManager.autoConnect(CLIENT_CONFIG_SSID);
 
  //if you get here you have connected to the WiFi
  Serial.println("Connected.");

  digitalWrite(LED_PIN, HIGH);  // Turn the LED off by making the voltage HIGH to indicate end of wifi setup


  //////////////////////////

  //SERVER INIT

  server.on("/", [](){
    server.send(200, "text/html", "<html><body>Follow <a href='/variable?size=1000'>/variable?size=xxx</a> to generate data</body></html>");
  });

  server.on("/variable", handleVariable);
 
  //called when the url is not defined here
  server.onNotFound([](){     
      server.send(404, "text/plain", "FileNotFound");
  });

  server.begin();
  Serial.println("HTTP server started");

  Serial.print("Ready. Please direct your browser to: http://");
  Serial.println(WiFi.localIP());     
}

void loop() {
  // Respond to HTTP requests
  server.handleClient();
}


And here is my sketch to test the AsyncWebServer. It's based on the Async FSBrowser sample where I added a handler for the "/variable" URL.
Please note that I couldn't use the WifiManager lib here due to a conflict (redefinition of HTTP_ANY etc due to the inclusion of ESPWebServer in WifiManager I guess). So credentials must be hardcoded in the sketch below.
It responds to the same URL

Code: Select all#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>
#include <FS.h>
#include <Hash.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>



const char* ssid = "********* ";
const char* password = "*********";



// WEB HANDLER IMPLEMENTATION


//File: edit.htm.gz, Size: 4143
#define edit_htm_gz_len 4143
const uint8_t edit_htm_gz[] PROGMEM = {
 0x1F, 0x8B, 0x08, 0x08, 0xAE, 0x92, 0x90, 0x57, 0x00, 0x03, 0x65, 0x64, 0x69, 0x74, 0x2E, 0x68,
 0x74, 0x6D, 0x00, 0xCD, 0x1B, 0x7B, 0x5F, 0xDB, 0xC8, 0xF1, 0x7F, 0x3E, 0x85, 0x4E, 0x69, 0x6B,
 0xB9, 0x7E, 0x13, 0xC2, 0x25, 0x06, 0x93, 0xDA, 0xC6, 0x04, 0xC2, 0x1B, 0xDB, 0xC9, 0x41, 0x4A,
 0xFB, 0x5B, 0x4B, 0x6B, 0x5B, 0xA0, 0xD7, 0x49, 0x32, 0x36, 0x49, 0xF9, 0xEE, 0x9D, 0xD9, 0x5D,
 0x49, 0x2B, 0x59, 0x76, 0x20, 0x77, 0xD7, 0x5E, 0x1E, 0x48, 0xDA, 0x9D, 0x99, 0x9D, 0xD7, 0xCE,
 0xCC, 0x3E, 0xD8, 0xFD, 0x69, 0xFF, 0xBC, 0x3B, 0xB8, 0xBE, 0xE8, 0x29, 0xD3, 0xD0, 0xB6, 0xF6,
 0x36, 0x76, 0xF1, 0xA1, 0x58, 0xC4, 0x99, 0xB4, 0x54, 0xEA, 0xA8, 0xD8, 0x40, 0x89, 0x01, 0x8F,
 0xD0, 0x0C, 0x2D, 0xBA, 0xD7, 0xEB, 0x5F, 0x28, 0x3D, 0xC3, 0x0C, 0x5D, 0x7F, 0xB7, 0xC6, 0x5B,
 0x36, 0x76, 0x83, 0xF0, 0xD1, 0xA2, 0x4A, 0xF8, 0xE8, 0xD1, 0x96, 0x1A, 0xD2, 0x45, 0x58, 0xD3,
 0x83, 0x40, 0x55, 0x6C, 0x6A, 0x98, 0xA4, 0xA5, 0x06, 0xBA, 0x4F, 0x19, 0x9D, 0xAA, 0x6E, 0x2B,
 0xDF, 0x36, 0x14, 0xE5, 0x6B, 0xC5, 0x74, 0x0C, 0xBA, 0x68, 0x2A, 0xAF, 0xEB, 0xF5, 0x1D, 0xF8,
 0xF6, 0xDC, 0xC0, 0x0C, 0x4D, 0xD7, 0x69, 0x2A, 0x64, 0x14, 0xB8, 0xD6, 0x2C, 0xA4, 0xD8, 0x6A,
 0xD1, 0x71, 0xD8, 0x54, 0xDE, 0x78, 0x0B, 0xFC, 0x18, 0xB9, 0xBE, 0x41, 0xFD, 0xA6, 0xD2, 0xF0,
 0x16, 0x0A, 0x80, 0x98, 0x86, 0xF2, 0x6A, 0x6B, 0x6B, 0x8B, 0xF5, 0x10, 0xFD, 0x7E, 0xE2, 0xBB,
 0x33, 0xC7, 0xA8, 0xE8, 0xAE, 0xE5, 0x02, 0xCC, 0xAB, 0x83, 0x37, 0xF8, 0x17, 0x3B, 0x0D, 0x33,
 0xF0, 0x2C, 0xF2, 0xD8, 0x54, 0x1C, 0xD7, 0xA1, 0x9C, 0xCE, 0xA2, 0x12, 0x4C, 0x89, 0xE1, 0xCE,
 0x9B, 0x4A, 0x1D, 0xFE, 0x36, 0xEA, 0x40, 0xD0, 0x9F, 0x8C, 0x88, 0xA6, 0xD4, 0xCB, 0xE2, 0x5F,
 0x75, 0x4B, 0x29, 0x22, 0xEC, 0xD8, 0x75, 0xC2, 0x4A, 0x60, 0x7E, 0xA5, 0x30, 0xEC, 0x26, 0x67,
 0x83, 0x35, 0x8D, 0x89, 0x6D, 0x5A, 0x40, 0x33, 0x20, 0x4E, 0x50, 0x09, 0xA8, 0x6F, 0x8E, 0xE3,
 0xAE, 0x39, 0x35, 0x27, 0xD3, 0xB0, 0x39, 0x72, 0x2D, 0x63, 0x67, 0xE3, 0x89, 0x09, 0x3C, 0xB3,
 0x98, 0xCC, 0x96, 0x19, 0x00, 0x31, 0x54, 0x53, 0xC2, 0x4C, 0xE8, 0x7A, 0xC0, 0x45, 0x22, 0x2B,
 0x7B, 0xB5, 0x89, 0x3F, 0x31, 0x1D, 0xF1, 0xE1, 0x11, 0xC3, 0x30, 0x9D, 0x09, 0xFB, 0xE2, 0xE4,
 0x2C, 0x93, 0x91, 0x4B, 0x54, 0xE6, 0x53, 0x8B, 0x84, 0xE6, 0x03, 0x23, 0x68, 0x9B, 0x4E, 0x65,
 0x6E, 0x1A, 0xE1, 0xB4, 0xA9, 0x6C, 0xD7, 0x39, 0xC7, 0xFA, 0xCC, 0x0F, 0x50, 0x29, 0x9E, 0x6B,
 0x3A, 0x21, 0xF5, 0x23, 0x32, 0x81, 0x47, 0x1C, 0x46, 0x28, 0xD2, 0x99, 0xD0, 0x66, 0xAC, 0x30,
 0xD3, 0xB1, 0x4C, 0x87, 0x56, 0x46, 0x96, 0xAB, 0xDF, 0xA7, 0x38, 0xD9, 0x46, 0xBA, 0x11, 0x2F,
 0xCD, 0xA9, 0xFB, 0x40, 0x7D, 0xE5, 0x9B, 0x64, 0x05, 0x41, 0x4B, 0xC9, 0x80, 0xF0, 0x01, 0xE3,
 0xE1, 0x7A, 0xBD, 0x1E, 0x03, 0x09, 0x1F, 0x66, 0xA0, 0x21, 0xD0, 0x39, 0xBE, 0x44, 0xB2, 0x49,
 0x42, 0x67, 0xF4, 0xB1, 0xAC, 0x45, 0x41, 0xC3, 0x74, 0xBC, 0x59, 0x98, 0xD1, 0x8C, 0xEC, 0x4C,
 0xAE, 0x47, 0x74, 0x33, 0x7C, 0x8C, 0x14, 0x89, 0x28, 0xDF, 0x84, 0xD1, 0x90, 0x92, 0x6F, 0x13,
 0x8B, 0x19, 0x59, 0xF9, 0x44, 0x7D, 0x83, 0x38, 0xA4, 0xAC, 0xB4, 0x7D, 0x93, 0x00, 0x63, 0xFD,
 0x94, 0x91, 0x2B, 0xB6, 0xFB, 0xB5, 0x32, 0x83, 0x4F, 0x68, 0xB2, 0xA8, 0x1E, 0x26, 0xB6, 0x04,
 0xD3, 0x8F, 0xEE, 0xCD, 0x30, 0xBF, 0x33, 0xB7, 0x31, 0xA3, 0x79, 0xA6, 0xEE, 0x29, 0xF7, 0x1F,
 0xA5, 0x11, 0x69, 0x19, 0x19, 0x8D, 0x6D, 0xC5, 0x55, 0x51, 0x19, 0xB9, 0x61, 0xE8, 0xDA, 0x4D,
 0x31, 0x33, 0x12, 0x6D, 0x71, 0x87, 0x7E, 0xBB, 0xC2, 0xEE, 0x6B, 0x6C, 0x9B, 0x1E, 0x55, 0x51,
 0xC0, 0x5E, 0xA1, 0xA9, 0x13, 0xAB, 0x42, 0x2C, 0x73, 0x02, 0x8A, 0xB4, 0x4D, 0xC3, 0xB0, 0x68,
 0x7A, 0xB2, 0x35, 0x95, 0x99, 0x6F, 0x69, 0x05, 0x83, 0x84, 0xA4, 0x69, 0xDA, 0x64, 0x42, 0x6B,
 0x9E, 0x33, 0xD9, 0x19, 0x91, 0x80, 0x6E, 0x6F, 0x95, 0xCD, 0x4F, 0x9D, 0xF3, 0xAB, 0x79, 0xFD,
 0xF8, 0xC3, 0xC4, 0x6D, 0xC3, 0x9F, 0xB3, 0xFE, 0x70, 0xDA, 0x1B, 0x4E, 0xE0, 0xAD, 0x83, 0x9F,
 0xED, 0xCB, 0x6E, 0xFB, 0x12, 0x1E, 0xDD, 0x46, 0xE9, 0x6E, 0xFC, 0x2B, 0x36, 0x74, 0x3E, 0x18,
 0x9D, 0xC1, 0xB0, 0xD7, 0x6E, 0x1F, 0xD7, 0x8E, 0xCE, 0xE6, 0x9F, 0x8F, 0xB7, 0xB1, 0xBB, 0x63,
 0xD5, 0xAF, 0x3E, 0x4D, 0xEB, 0xC3, 0xCD, 0x77, 0xB6, 0x71, 0x68, 0x4C, 0x75, 0x7B, 0xD8, 0xBE,
 0xFC, 0x7C, 0xF5, 0x70, 0x6D, 0x0F, 0x27, 0xFD, 0xCF, 0x8D, 0xE9, 0xCD, 0xE6, 0xA7, 0xFE, 0xCD,
 0xE7, 0x83, 0x7B, 0xFA, 0xCB, 0xE1, 0xC7, 0x9B, 0xC1, 0x1C, 0x10, 0xF6, 0xDD, 0xFE, 0xF0, 0xAA,
 0xF3, 0xA9, 0x33, 0xB9, 0xE9, 0xE8, 0xBD, 0xC5, 0xC8, 0x3A, 0xEB, 0x7C, 0x68, 0x8F, 0xDA, 0x9B,
 0x3A, 0xA5, 0x93, 0xC1, 0x55, 0x67, 0x76, 0x74, 0x7C, 0x3E, 0x31, 0xCD, 0x69, 0xFF, 0xE6, 0x6C,
 0xA0, 0x77, 0xDF, 0x9C, 0x0C, 0x0F, 0xDB, 0xE6, 0xF4, 0xEC, 0xE3, 0x55, 0xFD, 0xFE, 0xC3, 0x71,
 0x77, 0x5F, 0xBF, 0xFE, 0x78, 0xBD, 0xBD, 0xFF, 0xBA, 0xF6, 0xF3, 0xCF, 0xA7, 0xC6, 0xB9, 0xE9,
 0x0C, 0x1E, 0xBE, 0xB6, 0x27, 0xDD, 0xF9, 0xDB, 0xC7, 0x60, 0x30, 0x3D, 0x7A, 0x70, 0x6A, 0x9F,
 0xDC, 0xBB, 0xA3, 0xC7, 0x53, 0xF8, 0x7F, 0x71, 0x51, 0x1A, 0xF5, 0x1B, 0xC1, 0xF0, 0xF2, 0xE8,
 0xD3, 0x66, 0xF0, 0xEE, 0x8D, 0xD7, 0xD9, 0xDF, 0x7F, 0xB0, 0x47, 0x17, 0x35, 0xDB, 0xB8, 0x1F,
 0x87, 0x6F, 0x5F, 0x87, 0xDE, 0xF5, 0x64, 0x76, 0xF3, 0xEB, 0x9B, 0x8F, 0xD3, 0xDA, 0x39, 0x25,
 0xD7, 0xD3, 0xD2, 0xE3, 0xD7, 0xC7, 0xB7, 0xD3, 0xC1, 0xE1, 0xC3, 0x99, 0x45, 0x16, 0x67, 0x67,
 0xFA, 0x57, 0xBB, 0x64, 0x91, 0x77, 0xE7, 0x03, 0x8B, 0xF8, 0x8D, 0xA1, 0xD1, 0xAE, 0x95, 0xBA,
 0x9B, 0xED, 0xAD, 0xD0, 0xBF, 0xEA, 0x3A, 0xFB, 0xAF, 0xEF, 0xFA, 0x6F, 0x3B, 0x9D, 0x86, 0x3B,
 0xFA, 0x75, 0xF3, 0xC3, 0xFD, 0xF6, 0x87, 0xE1, 0xF6, 0xE5, 0xE8, 0xB2, 0xDD, 0xDF, 0xEA, 0x0C,
 0xC9, 0x75, 0xFF, 0xB2, 0x3D, 0xDE, 0x1A, 0x4D, 0xA7, 0xC7, 0xC7, 0x83, 0x03, 0xA3, 0xFD, 0xD5,
 0x6F, 0x9F, 0xCF, 0xDB, 0x8B, 0xDE, 0xB0, 0x7D, 0x58, 0x3A, 0xEE, 0x9D, 0xD6, 0x1B, 0xFD, 0xEB,
 0xD7, 0x93, 0xD3, 0xED, 0x79, 0x27, 0xE8, 0xB5, 0x2F, 0x3B, 0xF5, 0xC9, 0xC7, 0x92, 0x4D, 0x6E,
 0xDC, 0xEE, 0xEB, 0xC9, 0xD1, 0xB6, 0x79, 0x71, 0x4D, 0xDA, 0x47, 0x9D, 0x8F, 0x81, 0x79, 0x65,
 0x1F, 0x0E, 0xEB, 0xED, 0xF6, 0xC9, 0x39, 0x3D, 0xE8, 0xBE, 0x26, 0xC7, 0x9B, 0xFA, 0x67, 0xD0,
 0xFF, 0xF0, 0x17, 0xFA, 0x73, 0xA9, 0x3D, 0x3F, 0xAF, 0x5B, 0xFA, 0x3B, 0x3A, 0x38, 0xBC, 0x1E,
 0x30, 0xEB, 0xF4, 0xAC, 0x83, 0xC1, 0x7D, 0x7F, 0x76, 0x69, 0x77, 0xBB, 0x85, 0x22, 0x78, 0x6B,
 0xC5, 0xA7, 0x1E, 0x25, 0x61, 0x26, 0xD6, 0x26, 0xD3, 0x0C, 0x23, 0x68, 0x3D, 0xE3, 0xAE, 0x51,
 0x5C, 0xC0, 0x20, 0x07, 0xE9, 0xA0, 0x62, 0x50, 0xDD, 0xF5, 0x09, 0x87, 0x07, 0x6C, 0xEA, 0xA3,
 0x03, 0x22, 0xC6, 0x3F, 0x58, 0x8E, 0x50, 0x78, 0x8A, 0x50, 0x88, 0x63, 0x28, 0x5A, 0x34, 0x93,
 0x30, 0x98, 0x19, 0xF4, 0xC1, 0xD4, 0x69, 0xC5, 0x33, 0x17, 0xD4, 0xAA, 0x30, 0x02, 0xCD, 0x7A,
 0x11, 0xA9, 0xE2, 0x48, 0xF8, 0x4C, 0x26, 0x1E, 0x71, 0xC0, 0x0D, 0xF9, 0x08, 0x51, 0x8B, 0x71,
 0x47, 0x74, 0x0A, 0x51, 0x19, 0x26, 0x9E, 0x8D, 0x4F, 0x3E, 0x03, 0x5D, 0xBF, 0x32, 0x9A, 0x4D,
 0xC6, 0xE6, 0x02, 0xE6, 0xC1, 0xD8, 0x74, 0xCC, 0x90, 0x2A, 0x8D, 0x00, 0xA5, 0x7B, 0xDA, 0x80,
 0x1F, 0xFF, 0x88, 0xE8, 0xDD, 0xD3, 0xC7, 0xB1, 0x4F, 0x6C, 0x1A, 0x3C, 0x9B, 0x1C, 0xE7, 0x67,
 0xEC, 0xBB, 0x90, 0xE5, 0x14, 0xF6, 0x9E, 0x8D, 0x65, 0x30, 0x06, 0xEF, 0x08, 0xDD, 0x35, 0x20,
 0x8C, 0x95, 0xA7, 0x8D, 0x57, 0x33, 0xCF, 0x72, 0x89, 0xC1, 0xC2, 0xEB, 0xCA, 0xB8, 0x96, 0xA4,
 0x10, 0x9F, 0xCF, 0xE1, 0x6C, 0x3A, 0x11, 0x73, 0x7B, 0x53, 0x84, 0x85, 0x54, 0x94, 0xD9, 0xDC,
 0x4A, 0x85, 0x90, 0x0A, 0x47, 0x6B, 0x88, 0xCC, 0x91, 0x93, 0x58, 0x45, 0xA8, 0xE2, 0x9F, 0x2C,
 0x86, 0x23, 0x9F, 0x21, 0xD8, 0xEE, 0xBB, 0x3C, 0x46, 0x0C, 0x88, 0xE0, 0xB5, 0xC4, 0x27, 0x4F,
 0x5B, 0x8D, 0x28, 0x6D, 0xC5, 0x4A, 0x79, 0xCB, 0x3D, 0xEB, 0x15, 0x65, 0x15, 0x47, 0x59, 0x79,
 0xE5, 0xF9, 0xE0, 0x13, 0x74, 0xFE, 0xEC, 0x01, 0x65, 0xBD, 0xE4, 0x0C, 0x1E, 0x8F, 0xC8, 0x2B,
 0x0C, 0xAE, 0x03, 0xA9, 0xCA, 0x88, 0x84, 0x8C, 0x87, 0xCD, 0xD7, 0x0C, 0x03, 0x4B, 0xD8, 0x7E,
 0xC3, 0xB9, 0xDE, 0xAD, 0xB1, 0x5C, 0x85, 0x05, 0x92, 0xEE, 0x9B, 0x5E, 0xB8, 0xB7, 0x31, 0x9E,
 0x39, 0x3A, 0x32, 0xAC, 0x80, 0xC3, 0x93, 0x90, 0x1E, 0x98, 0x16, 0x1D, 0x0A, 0x33, 0x6B, 0xC2,
 0xAF, 0xCA, 0x0A, 0x2A, 0xB4, 0xAC, 0x70, 0x89, 0x99, 0xB7, 0x3F, 0x10, 0x5F, 0x59, 0xD8, 0xD6,
 0x61, 0x18, 0x7A, 0x3B, 0xE2, 0x93, 0x27, 0xBC, 0x96, 0x62, 0xB8, 0xFA, 0x0C, 0xB1, 0xAA, 0x9C,
 0x60, 0x8F, 0xD3, 0xD0, 0x54, 0xD6, 0xAF, 0xB2, 0x3A, 0x86, 0xBD, 0x56, 0xB1, 0x38, 0x03, 0x78,
 0x75, 0x0C, 0x43, 0xAA, 0x49, 0xB3, 0x3D, 0xB3, 0x42, 0xD3, 0xB3, 0xB0, 0x6B, 0x4C, 0xAC, 0x80,
 0x26, 0x3D, 0x0E, 0x38, 0x3F, 0x22, 0x60, 0x88, 0x67, 0x08, 0xF1, 0x50, 0x13, 0x1A, 0x8A, 0x71,
 0x3A, 0x8F, 0x47, 0x46, 0xC4, 0x77, 0xB1, 0x4A, 0x3C, 0x8F, 0x3A, 0x46, 0x77, 0x6A, 0x5A, 0x86,
 0xC6, 0x68, 0x14, 0x23, 0x6E, 0x3D, 0x12, 0x4E, 0x9F, 0xC7, 0x2C, 0x42, 0x56, 0x41, 0xF1, 0x30,
 0x30, 0xF7, 0xFF, 0x0A, 0xB6, 0xA8, 0x71, 0x57, 0x24, 0x06, 0x86, 0x95, 0xA4, 0x35, 0xE2, 0x35,
 0x0D, 0x6B, 0xD0, 0x31, 0x01, 0xF1, 0x3E, 0x11, 0x6B, 0xC6, 0x7A, 0x6B, 0x2F, 0x17, 0x03, 0xE9,
 0xC4, 0x52, 0x8C, 0x66, 0xE0, 0x3E, 0xCE, 0x1A, 0x39, 0x38, 0x00, 0x17, 0x84, 0xBF, 0x57, 0x4D,
 0xC7, 0xA1, 0xFE, 0xE1, 0xE0, 0xF4, 0x04, 0xD0, 0x0A, 0xDC, 0xD4, 0x85, 0x17, 0x73, 0xC1, 0x69,
 0xC5, 0x7C, 0xD8, 0xF7, 0x68, 0xC4, 0x67, 0xF2, 0xC1, 0x81, 0xD3, 0x7C, 0x74, 0x19, 0xF8, 0xCB,
 0xF9, 0xE0, 0xB4, 0x80, 0x2C, 0x96, 0x4E, 0x91, 0x27, 0x4F, 0xC1, 0x2B, 0x2F, 0xDC, 0x20, 0xBC,
 0xF0, 0x5D, 0x9D, 0x06, 0xC1, 0x15, 0xFD, 0x75, 0x46, 0x83, 0x50, 0x2B, 0xF2, 0x70, 0x68, 0x8E,
 0x15, 0x4D, 0xB8, 0x6E, 0x15, 0x46, 0x35, 0x1E, 0xFB, 0x21, 0x0C, 0xAD, 0xB4, 0x5A, 0xCA, 0x96,
 0x80, 0x40, 0x98, 0x18, 0x24, 0x80, 0xDE, 0x59, 0xA0, 0xFC, 0xD4, 0x52, 0x36, 0xEB, 0xF5, 0xA2,
 0x42, 0x2C, 0x28, 0x45, 0x34, 0xB5, 0x77, 0x75, 0x75, 0x7E, 0xF5, 0x45, 0x2D, 0xA5, 0xA1, 0x4A,
 0xEA, 0x6D, 0x53, 0x49, 0x1A, 0x7D, 0x1A, 0x78, 0xAE, 0x13, 0xD0, 0x01, 0x78, 0x46, 0x71, 0x47,
 0x90, 0xA6, 0xE0, 0xD2, 0x4A, 0x34, 0x8E, 0xC2, 0x66, 0x16, 0x00, 0x8E, 0x01, 0x76, 0x7A, 0x01,
 0xA6, 0x65, 0xF6, 0xAD, 0x3E, 0xA0, 0x83, 0xC4, 0x28, 0x4F, 0x52, 0x14, 0x96, 0xC4, 0xE4, 0x2A,
 0xE6, 0x48, 0x82, 0x73, 0x31, 0x32, 0x68, 0xD4, 0x81, 0xC8, 0xF0, 0xCB, 0xE9, 0x09, 0x7E, 0xC5,
 0xF2, 0xEF, 0xC8, 0x30, 0x55, 0xD7, 0x61, 0xD2, 0x23, 0xE7, 0x54, 0x9F, 0xC2, 0xDA, 0x0A, 0xED,
 0x97, 0xAF, 0x3A, 0x8E, 0x88, 0x86, 0x1E, 0x43, 0x61, 0xBA, 0x0F, 0xD3, 0x4F, 0x8C, 0x70, 0x20,
 0x3E, 0x23, 0xDA, 0x51, 0xB7, 0xB0, 0x91, 0xC6, 0xDD, 0xBF, 0xAC, 0x78, 0xD9, 0xB1, 0xA1, 0x57,
 0x53, 0x2F, 0x86, 0x03, 0xE8, 0x53, 0x6B, 0x18, 0x57, 0xD4, 0x0C, 0x44, 0x80, 0xE8, 0x11, 0xB9,
 0xE2, 0x8E, 0x90, 0x3D, 0xF1, 0x1E, 0xD7, 0xD1, 0x2D, 0x53, 0xBF, 0xC7, 0x20, 0x21, 0xF4, 0xA1,
 0xD1, 0xD8, 0xC0, 0x92, 0x12, 0xAB, 0x6C, 0x61, 0x77, 0x3E, 0xD6, 0xD4, 0xAA, 0x5A, 0x04, 0x23,
 0xB7, 0x94, 0x4A, 0xA3, 0x08, 0x4B, 0x94, 0x70, 0xE6, 0x3B, 0x7C, 0x44, 0x59, 0x8D, 0x59, 0xDD,
 0xF3, 0x88, 0x57, 0xC5, 0x39, 0x32, 0x84, 0xE2, 0x32, 0xD3, 0xFF, 0x24, 0xCD, 0xAA, 0xF5, 0xFC,
 0xF0, 0xF8, 0x85, 0x8C, 0x07, 0x55, 0x8B, 0x3A, 0x13, 0x8C, 0x3D, 0xC0, 0x4A, 0x3D, 0x76, 0x38,
 0x99, 0xA1, 0xA7, 0x3F, 0xA7, 0x29, 0x59, 0xD4, 0x2D, 0x2B, 0x92, 0x28, 0x5F, 0xEA, 0xB7, 0x65,
 0x65, 0x49, 0x67, 0x19, 0x1B, 0x9F, 0xF7, 0x5F, 0x64, 0xE4, 0x24, 0xDA, 0x83, 0x46, 0x23, 0x51,
 0x5E, 0xA4, 0xD2, 0x94, 0x2E, 0x99, 0xA0, 0x00, 0x23, 0xC2, 0x71, 0x9A, 0x79, 0x16, 0xA4, 0x13,
 0x38, 0x98, 0xA5, 0x00, 0x52, 0xD3, 0xDE, 0x37, 0xFF, 0x59, 0xD5, 0xBE, 0xFC, 0xAB, 0x7A, 0x5B,
 0x2A, 0x16, 0xDF, 0xFF, 0xA5, 0x56, 0xA5, 0x0B, 0xAA, 0x6B, 0x11, 0x91, 0xE2, 0x97, 0xC6, 0x6D,
 0x82, 0x22, 0xC8, 0xD6, 0xB4, 0xEA, 0xDF, 0x8B, 0xFF, 0xAC, 0x32, 0x9C, 0xD5, 0x08, 0xC0, 0x34,
 0xE6, 0x0A, 0x77, 0xCC, 0xD1, 0x7E, 0x02, 0x6E, 0xB1, 0xF2, 0x84, 0x8A, 0x8F, 0x1A, 0xB1, 0x23,
 0x48, 0xCC, 0x26, 0xDC, 0x3D, 0x65, 0xF0, 0x91, 0xD3, 0x7C, 0x74, 0x80, 0x61, 0x62, 0x40, 0xA7,
 0x8A, 0x9B, 0x26, 0xE0, 0xF3, 0x5C, 0x2C, 0xFC, 0x52, 0x53, 0x31, 0x48, 0x06, 0xBD, 0xF3, 0xE8,
 0x24, 0x01, 0xBD, 0xF3, 0x26, 0x31, 0xA8, 0xC4, 0x4F, 0xFC, 0x5A, 0x52, 0x60, 0x32, 0xC1, 0x4F,
 0x00, 0xCF, 0xF0, 0x97, 0x38, 0x03, 0xA7, 0x5B, 0x53, 0x95, 0xFF, 0xFC, 0x47, 0x72, 0x91, 0xAA,
 0x45, 0x82, 0xF0, 0x28, 0x9A, 0x91, 0x35, 0x31, 0x23, 0x93, 0x69, 0x20, 0xE3, 0x23, 0x76, 0x29,
 0x1A, 0x33, 0xAA, 0x55, 0x53, 0xD1, 0x33, 0x05, 0x2D, 0x0D, 0x12, 0xCC, 0x46, 0x41, 0xE8, 0x43,
 0xD5, 0xA3, 0xD5, 0xCB, 0xEB, 0x06, 0x2F, 0x35, 0x8A, 0xD9, 0x01, 0xB8, 0x0B, 0x42, 0xDD, 0x9D,
 0x89, 0xB2, 0x03, 0x08, 0xD3, 0x49, 0x39, 0x94, 0x29, 0x84, 0xA2, 0x4A, 0xAC, 0xB5, 0x32, 0x77,
 0xA9, 0x02, 0x44, 0x8D, 0x33, 0x26, 0xC6, 0xFD, 0x2B, 0xD7, 0x5D, 0x57, 0x30, 0x19, 0xE6, 0x03,
 0x87, 0x8F, 0x60, 0xAB, 0x3A, 0x48, 0x10, 0x9C, 0x89, 0xCA, 0x02, 0xD6, 0x1B, 0x2F, 0xAF, 0x1E,
 0x22, 0x52, 0x99, 0x8C, 0x89, 0x01, 0x6E, 0xDF, 0x9D, 0x3B, 0xF8, 0xE4, 0x15, 0x06, 0xD7, 0xF1,
 0x2A, 0xDA, 0x05, 0x43, 0x00, 0x57, 0xD8, 0x8A, 0xA4, 0x50, 0xAC, 0x06, 0xBE, 0x2E, 0x8C, 0x50,
 0x52, 0xDF, 0x47, 0xBD, 0xAD, 0xD0, 0x9F, 0xF1, 0xD2, 0xEE, 0x69, 0x69, 0xB8, 0x0B, 0xAE, 0x91,
 0xE7, 0x8C, 0xA6, 0x72, 0x7D, 0xAB, 0x30, 0x0A, 0x16, 0xAF, 0x55, 0xB1, 0x6D, 0x80, 0x4A, 0xC0,
 0xAD, 0x0B, 0xE1, 0xAB, 0x42, 0xC5, 0xCB, 0x30, 0x6C, 0x57, 0x21, 0x03, 0x94, 0x2A, 0x3E, 0x76,
 0x4D, 0x7B, 0xA2, 0x80, 0x00, 0x2D, 0xB5, 0x50, 0x62, 0x12, 0x14, 0xDE, 0xFF, 0x5B, 0x1F, 0xB5,
 0x0A, 0x25, 0x08, 0x4B, 0xB4, 0xEA, 0xB8, 0x73, 0xAD, 0x58, 0x2A, 0xA8, 0x0A, 0x23, 0xDC, 0x52,
 0x6D, 0xB2, 0x10, 0x5B, 0x5A, 0x8D, 0x7A, 0xFD, 0xAF, 0x3B, 0x0A, 0x7E, 0x8B, 0xF5, 0x4B, 0xD4,
 0xC0, 0xF6, 0x87, 0xC8, 0x2C, 0x74, 0x77, 0xE2, 0x2D, 0x0E, 0xBE, 0xB5, 0xA1, 0x2A, 0xB5, 0xBD,
 0xC2, 0xB2, 0x3E, 0xC0, 0x09, 0x2D, 0x2C, 0xBC, 0x4F, 0xA9, 0x33, 0x03, 0xBB, 0x71, 0xA7, 0x15,
 0x4A, 0x41, 0x67, 0xC1, 0x4D, 0xA6, 0x35, 0x8E, 0x32, 0xB3, 0xA2, 0xC0, 0x4A, 0xAD, 0x94, 0xB5,
 0x11, 0xAF, 0x98, 0x44, 0x2B, 0xC2, 0x47, 0x5B, 0x4D, 0xC8, 0x32, 0x23, 0x42, 0x88, 0x99, 0x22,
 0xC5, 0x71, 0x8B, 0x71, 0x24, 0x33, 0x03, 0xAC, 0x6A, 0x90, 0x69, 0x6E, 0xC1, 0x78, 0x0A, 0x73,
 0xC0, 0x94, 0x82, 0xD5, 0x5D, 0x5C, 0x81, 0xEF, 0xE1, 0x16, 0x2D, 0xAC, 0x3F, 0xF0, 0x35, 0x0E,
 0x2F, 0x02, 0x7A, 0x4D, 0xF6, 0x5C, 0x91, 0x86, 0xE3, 0xE2, 0x88, 0x71, 0x13, 0x0B, 0x34, 0x72,
 0x8D, 0x47, 0xC9, 0x7D, 0x82, 0xCE, 0x63, 0x37, 0x9A, 0x33, 0x5A, 0x41, 0xB7, 0xC1, 0x51, 0x45,
 0xAE, 0xD8, 0xC3, 0x4C, 0x91, 0x46, 0xF3, 0xA9, 0xED, 0x3E, 0x50, 0x2E, 0x2E, 0xB5, 0x92, 0xEA,
 0x2B, 0x15, 0x7B, 0x98, 0xE8, 0x47, 0xB8, 0xBD, 0xF4, 0x22, 0xD9, 0x85, 0xB7, 0xFF, 0x98, 0xF8,
 0x4B, 0xD3, 0xE5, 0x7F, 0x2E, 0x7B, 0xEC, 0x42, 0xD1, 0xCC, 0xFE, 0x31, 0x27, 0x8A, 0xB0, 0x45,
 0x7F, 0xF4, 0x99, 0xA7, 0xB0, 0x28, 0x1A, 0xA5, 0x35, 0x16, 0x63, 0xAC, 0xD5, 0xD8, 0x72, 0x34,
 0x8B, 0xE4, 0xF9, 0x43, 0xD5, 0xF5, 0x94, 0xCC, 0x34, 0x83, 0xB2, 0xF9, 0xFC, 0x83, 0x5A, 0xE2,
 0xC8, 0x91, 0x92, 0xF8, 0x57, 0xAE, 0x8E, 0x20, 0xC4, 0x87, 0x34, 0xA3, 0x21, 0x01, 0xBE, 0x56,
 0x41, 0x58, 0x1A, 0x72, 0xE4, 0xFF, 0xB5, 0x7A, 0xD2, 0x71, 0x2F, 0x98, 0xBA, 0xF3, 0xAE, 0xEB,
 0xE0, 0xCA, 0x99, 0x87, 0xBE, 0x07, 0x96, 0x5E, 0x91, 0x27, 0x28, 0x34, 0x03, 0xB6, 0xB4, 0x4B,
 0xA2, 0x20, 0xE4, 0x43, 0x01, 0xFC, 0x9C, 0xA4, 0xC9, 0x71, 0x02, 0xDD, 0x77, 0x2D, 0x6B, 0xE0,
 0x7A, 0x32, 0x0A, 0xE3, 0x31, 0xE9, 0x79, 0xBF, 0xB2, 0xA7, 0x99, 0xF4, 0x44, 0x2F, 0x62, 0xA0,
 0x04, 0x28, 0x3B, 0xD6, 0x09, 0x1D, 0x87, 0x2B, 0x06, 0x63, 0x5D, 0xF9, 0xA3, 0xB1, 0xAE, 0xEF,
 0x0E, 0x87, 0x50, 0xC9, 0x78, 0x16, 0x1F, 0x89, 0x29, 0x0D, 0x4A, 0x03, 0x13, 0x1E, 0xBF, 0x40,
 0x55, 0x96, 0x07, 0x1A, 0x32, 0x05, 0xC8, 0x90, 0xD7, 0x31, 0x64, 0x2C, 0x43, 0xA2, 0xDF, 0x54,
 0xA1, 0x81, 0xC6, 0x5E, 0x02, 0xC8, 0x26, 0xD9, 0x02, 0xCB, 0x6F, 0xAB, 0xE0, 0x04, 0xA7, 0xEC,
 0x51, 0x52, 0x0A, 0xDE, 0x62, 0x15, 0x20, 0xE7, 0x13, 0x7F, 0xCA, 0x60, 0xA9, 0xF4, 0x98, 0xE0,
 0x88, 0x34, 0x19, 0x45, 0x06, 0x59, 0xA9, 0xA9, 0xE9, 0x14, 0x23, 0x48, 0x7E, 0xC1, 0x12, 0x38,
 0x9A, 0x29, 0xE1, 0xC0, 0x1D, 0x8F, 0x03, 0x1A, 0x7E, 0xC6, 0x8E, 0x04, 0x8E, 0x27, 0xF6, 0x3C,
 0xC0, 0x43, 0xD6, 0xB3, 0x24, 0x87, 0xEB, 0xD8, 0xEE, 0x2C, 0xA0, 0x2E, 0xDB, 0x0A, 0xCB, 0x99,
 0x7A, 0x58, 0x75, 0xC7, 0xC6, 0xDA, 0xE5, 0x2A, 0x81, 0x12, 0x39, 0x69, 0xDB, 0x53, 0x34, 0xA1,
 0x27, 0xC6, 0x63, 0x51, 0xEE, 0xBD, 0x06, 0x0C, 0x54, 0x4E, 0xAA, 0x09, 0x10, 0xB8, 0xC2, 0x38,
 0xAF, 0x45, 0x29, 0x71, 0xFC, 0x11, 0xF3, 0x79, 0x49, 0x9D, 0xF1, 0xFE, 0xC4, 0xF2, 0xFC, 0x4E,
 0x6A, 0xE7, 0x13, 0x4A, 0xF8, 0xEA, 0xA0, 0xCC, 0x96, 0x35, 0x65, 0x05, 0x4F, 0x31, 0xE5, 0x0A,
 0x07, 0xFA, 0x9F, 0x17, 0x2D, 0x01, 0x90, 0xEF, 0xC6, 0x69, 0x9A, 0xC6, 0x37, 0xF1, 0xD8, 0x4A,
 0xA1, 0xF8, 0x5E, 0x55, 0x9B, 0xCC, 0x1F, 0x4A, 0xB8, 0x6E, 0x60, 0x2B, 0x2F, 0x69, 0xAA, 0x90,
 0x11, 0xB5, 0xD6, 0xD0, 0xC7, 0x08, 0x1A, 0x8F, 0x80, 0xB0, 0xA9, 0x70, 0x9B, 0xAC, 0x0F, 0xD8,
 0xE8, 0xA9, 0x0A, 0x0B, 0x81, 0x65, 0xD6, 0xD6, 0xC6, 0xDD, 0x74, 0xDD, 0x24, 0x44, 0x01, 0xA7,
 0x3F, 0x71, 0xE7, 0xD4, 0xEF, 0x92, 0x80, 0x6A, 0xC5, 0xE2, 0xEA, 0xB2, 0x47, 0xC0, 0x27, 0x6A,
 0xCF, 0xAD, 0x48, 0xBE, 0x4B, 0x55, 0xAE, 0x26, 0x96, 0x48, 0xCA, 0x89, 0x2C, 0x92, 0x87, 0x5B,
 0x1B, 0x14, 0x35, 0xCB, 0x97, 0x8A, 0x56, 0xB1, 0xAA, 0x06, 0x3D, 0xEE, 0xF3, 0xDD, 0x4D, 0x2D,
 0x26, 0x07, 0x4B, 0x31, 0x70, 0xCD, 0x0B, 0xDF, 0xF5, 0xC8, 0x84, 0x9D, 0x84, 0x24, 0x5D, 0x4B,
 0xA1, 0xBF, 0x1C, 0x99, 0x16, 0x77, 0x9B, 0xE3, 0xCD, 0x04, 0xC1, 0x0A, 0x5F, 0xD2, 0x33, 0x88,
 0x65, 0x2F, 0x23, 0x86, 0x71, 0x02, 0x39, 0x14, 0xBC, 0xC1, 0x97, 0xD3, 0x47, 0x48, 0xED, 0xE0,
 0x47, 0x6A, 0x68, 0x4E, 0x66, 0x5D, 0x1D, 0x6D, 0xA1, 0x2B, 0x31, 0xFA, 0x62, 0xCE, 0xC4, 0x3B,
 0x26, 0x1A, 0xDB, 0x08, 0x87, 0xDE, 0xFA, 0x0E, 0x3C, 0x60, 0x7A, 0x5B, 0xF0, 0x2C, 0x95, 0x52,
 0x0E, 0x80, 0x78, 0x5F, 0xCC, 0x5B, 0xB1, 0x6F, 0xDC, 0x8A, 0x36, 0xC0, 0x8B, 0x89, 0x81, 0xB2,
 0x05, 0x41, 0xEE, 0x34, 0x8A, 0xE9, 0xF0, 0xF9, 0x14, 0x7F, 0xB2, 0x89, 0x15, 0x69, 0x6F, 0x63,
 0x49, 0x59, 0xD9, 0xBA, 0x3D, 0x51, 0xD0, 0xDA, 0xED, 0x0F, 0x06, 0x9B, 0xB3, 0x93, 0xB1, 0x7A,
 0x27, 0x22, 0x98, 0x9B, 0xA1, 0x3E, 0xC5, 0x2D, 0x06, 0xC9, 0xF7, 0x74, 0xF0, 0x46, 0x58, 0xB9,
 0x2E, 0x42, 0xB5, 0x99, 0x69, 0xC3, 0xED, 0x89, 0x9C, 0x36, 0x6B, 0xA9, 0xF1, 0x2E, 0x58, 0x6A,
 0xD2, 0x97, 0x5B, 0x3C, 0x6F, 0xB9, 0x2D, 0x58, 0xC6, 0x5C, 0xA4, 0x06, 0x88, 0xFD, 0x0C, 0x1D,
 0x70, 0x79, 0xF3, 0x35, 0xEE, 0x8E, 0x8F, 0x25, 0xB2, 0xAA, 0xCD, 0xAC, 0x0B, 0xFE, 0x1F, 0xBA,
 0xF5, 0x9C, 0xC9, 0xB2, 0xCA, 0xBC, 0xE5, 0xB6, 0x89, 0x39, 0xFE, 0xCD, 0x92, 0x87, 0x53, 0x33,
 0x90, 0xF7, 0xAE, 0xE5, 0xF0, 0x20, 0x29, 0x20, 0xDE, 0xBE, 0x90, 0xD3, 0x48, 0xB2, 0xA7, 0x81,
 0x9F, 0x67, 0xAE, 0xC1, 0x76, 0xE4, 0x84, 0xDF, 0x62, 0x7D, 0xFA, 0x81, 0x86, 0x31, 0x50, 0x99,
 0x45, 0x78, 0xB1, 0x3F, 0x28, 0xAB, 0x1C, 0xAA, 0xDD, 0xEE, 0x48, 0x1E, 0x2B, 0xE2, 0x33, 0x62,
 0x43, 0x9A, 0x79, 0xDF, 0xDF, 0xF5, 0x5F, 0xB3, 0xEF, 0xFF, 0x4D, 0x52, 0xD5, 0x6F, 0x3E, 0x02,
 0x58, 0xDA, 0xC8, 0xFA, 0x31, 0x2D, 0xF1, 0x3F, 0xAB, 0x75, 0x25, 0x5B, 0x31, 0x7D, 0x88, 0xB0,
 0x91, 0x39, 0x2C, 0x11, 0x6B, 0x81, 0x78, 0xCB, 0x92, 0xF3, 0xF5, 0x9B, 0x77, 0xA0, 0xB9, 0x75,
 0x62, 0xAA, 0xD2, 0x8E, 0xEC, 0x8F, 0x9D, 0x22, 0x64, 0x28, 0xA5, 0x37, 0x9A, 0xF7, 0x7B, 0x27,
 0xBD, 0x41, 0xEF, 0x45, 0xE7, 0x09, 0xB2, 0x1A, 0xA0, 0x32, 0x62, 0x9E, 0x94, 0xA4, 0x90, 0x3F,
 0xCA, 0xA3, 0x5A, 0xD1, 0x49, 0x52, 0x6E, 0xDA, 0xFA, 0xD8, 0x3F, 0x3F, 0xAB, 0x42, 0x13, 0x64,
 0xED, 0x5C, 0x1F, 0xCA, 0x3D, 0x14, 0xCA, 0xDA, 0x13, 0xFD, 0x21, 0x47, 0x94, 0xB5, 0x06, 0x4D,
 0xC1, 0x3F, 0xC3, 0xB8, 0x39, 0x0A, 0xCB, 0x33, 0xCB, 0x87, 0x1E, 0xDF, 0xFE, 0xC7, 0xA4, 0xF6,
 0xDE, 0x30, 0xFD, 0x96, 0x5A, 0xE2, 0x82, 0x4A, 0x49, 0x3E, 0x65, 0x23, 0x67, 0x66, 0x59, 0x89,
 0x7D, 0x56, 0x7B, 0x77, 0x14, 0xB0, 0x20, 0x06, 0xE5, 0x6D, 0xD8, 0xF2, 0xFB, 0x80, 0xC9, 0x96,
 0x2D, 0xFA, 0x4E, 0x99, 0x5D, 0x21, 0x84, 0xA1, 0xA7, 0x14, 0x53, 0x26, 0x86, 0x59, 0xA6, 0x19,
 0xD9, 0x09, 0x4E, 0x00, 0xE2, 0xC0, 0x77, 0xED, 0x03, 0xE1, 0x6B, 0xD9, 0x49, 0xC1, 0xAB, 0x49,
 0x67, 0xC2, 0xCE, 0x76, 0x2D, 0x62, 0x3A, 0x6A, 0xE2, 0xD6, 0x2F, 0x3C, 0x40, 0xF8, 0x3D, 0xB2,
 0x68, 0x96, 0x17, 0x65, 0x04, 0xD2, 0xDF, 0xEF, 0x64, 0x40, 0x59, 0x72, 0x8D, 0x41, 0x59, 0x5A,
 0x5D, 0x01, 0x89, 0xD9, 0x35, 0x06, 0xBC, 0x23, 0x0F, 0x84, 0x5F, 0x13, 0x58, 0x05, 0xAE, 0x4B,
 0xD0, 0xFA, 0xBF, 0x31, 0xEB, 0xAE, 0x02, 0xC4, 0x84, 0xFC, 0x4C, 0xD0, 0x9C, 0x3C, 0x1D, 0xE4,
 0x35, 0x7A, 0xD3, 0xE5, 0x24, 0xBF, 0xA2, 0x64, 0x70, 0x9D, 0xEF, 0xA6, 0x7E, 0xC1, 0x5B, 0x7C,
 0xA6, 0x91, 0x9B, 0xFF, 0x10, 0x28, 0xF6, 0xCD, 0xC4, 0x7E, 0xFC, 0xF0, 0x1B, 0x4B, 0xB9, 0xD8,
 0x82, 0x6A, 0x51, 0xB4, 0x82, 0xC3, 0xB2, 0x23, 0xC8, 0x2A, 0x3F, 0x80, 0x49, 0xE1, 0xF1, 0x31,
 0x33, 0x78, 0xDF, 0x36, 0x24, 0x76, 0x56, 0x79, 0x64, 0x31, 0x87, 0x0B, 0xE6, 0xD6, 0xCB, 0x6C,
 0x88, 0x66, 0x7E, 0x41, 0xC1, 0x86, 0xA9, 0x91, 0xE5, 0x22, 0x29, 0x44, 0x97, 0xB8, 0x90, 0xEF,
 0x36, 0xC0, 0xAA, 0x2A, 0x12, 0x9F, 0x61, 0x27, 0xCC, 0x73, 0x73, 0x16, 0xD3, 0xD0, 0xC9, 0xE4,
 0x60, 0x6C, 0x4A, 0x17, 0x46, 0x30, 0xFE, 0xC0, 0x24, 0xDF, 0x11, 0xAD, 0x7C, 0x95, 0x03, 0x8D,
 0x44, 0xA7, 0x55, 0xFC, 0x88, 0x4F, 0x2E, 0x76, 0xB2, 0x71, 0xED, 0x4F, 0x7A, 0xA8, 0xBF, 0x74,
 0x32, 0x1F, 0xF1, 0x1A, 0x4F, 0xFA, 0xB2, 0x82, 0xA7, 0xA7, 0x52, 0xC8, 0xF9, 0xF3, 0x9E, 0xEF,
 0x22, 0x6C, 0xC7, 0x72, 0x47, 0xDA, 0x17, 0x6C, 0xB8, 0x2D, 0x2B, 0xDF, 0x18, 0xD7, 0x4D, 0x6E,
 0xDD, 0xA7, 0xE2, 0x77, 0xD2, 0xF0, 0x4B, 0xCF, 0x7B, 0x73, 0x52, 0xD7, 0x8F, 0x5B, 0xF8, 0xFB,
 0xC7, 0x6F, 0x6B, 0x4F, 0x90, 0x7E, 0xE8, 0x04, 0x4A, 0x3E, 0x5D, 0x5A, 0x97, 0xEE, 0xC5, 0x62,
 0x3E, 0xA0, 0xFC, 0xF6, 0x4F, 0x7E, 0x76, 0x8F, 0x17, 0xCE, 0x58, 0x1D, 0x66, 0x31, 0xD4, 0xA4,
 0xAC, 0x13, 0x5D, 0x3A, 0x2C, 0x88, 0xFD, 0x3E, 0xBB, 0xF4, 0x27, 0x2D, 0xAE, 0x73, 0xDD, 0x91,
 0x25, 0xD1, 0x29, 0x1D, 0xFA, 0x56, 0xAC, 0xAD, 0xE7, 0x78, 0xE0, 0x33, 0x7C, 0x70, 0xC9, 0x62,
 0x4B, 0x98, 0x52, 0x21, 0xC0, 0x59, 0x48, 0x65, 0xFF, 0xB5, 0xF9, 0x3F, 0x8A, 0x33, 0x98, 0x1D,
 0x45, 0x8E, 0x8B, 0x55, 0x09, 0x36, 0xEA, 0xC3, 0xA8, 0x4C, 0x72, 0xD4, 0xD2, 0x29, 0x94, 0xC7,
 0x9A, 0x0A, 0x51, 0xA4, 0x66, 0xC3, 0x9B, 0x08, 0x57, 0x8C, 0x54, 0xA2, 0xC9, 0x01, 0x06, 0x43,
 0x0E, 0xC4, 0xE2, 0x22, 0x40, 0xB1, 0xA7, 0x0C, 0xF6, 0x17, 0x66, 0xD2, 0x3E, 0xDB, 0x1C, 0x35,
 0x59, 0x14, 0x3E, 0xE2, 0xD7, 0x32, 0x1F, 0x25, 0xA0, 0xEC, 0xE0, 0xC3, 0x80, 0xF6, 0xDD, 0x71,
 0x38, 0x20, 0xA3, 0x40, 0x8B, 0x85, 0x5B, 0x01, 0x0B, 0x40, 0x7D, 0x58, 0xA5, 0x6B, 0x9B, 0x19,
 0xE6, 0x0E, 0xCD, 0xC9, 0xD4, 0xC2, 0x3D, 0xBC, 0xB6, 0x8E, 0xF7, 0xE7, 0x4F, 0x20, 0x12, 0x2F,
 0xD1, 0x02, 0xB0, 0xFE, 0xD4, 0x9D, 0x5F, 0xF8, 0xA6, 0x13, 0x9E, 0xB2, 0x83, 0x46, 0x8D, 0x2D,
 0xCA, 0x64, 0x18, 0xDD, 0xB5, 0x6D, 0xE2, 0x18, 0x41, 0x15, 0x4A, 0xCC, 0x2E, 0x7F, 0xD7, 0x22,
 0xA3, 0xE3, 0xC4, 0x6D, 0x2A, 0x85, 0x80, 0xC0, 0x12, 0x83, 0x77, 0x15, 0xCA, 0xA2, 0x6B, 0x04,
 0x79, 0xEA, 0x98, 0x3E, 0x36, 0x95, 0x6F, 0x73, 0xBC, 0xDE, 0x5E, 0xE8, 0x86, 0xBE, 0x55, 0xE9,
 0x17, 0xCA, 0x78, 0xCD, 0x5B, 0xC7, 0x6F, 0x0E, 0x0F, 0x4D, 0x4F, 0x11, 0x0A, 0x56, 0x36, 0x4D,
 0x69, 0x4F, 0x88, 0x9F, 0x8B, 0x4B, 0xEB, 0x9A, 0x54, 0x24, 0x2C, 0x4B, 0x0A, 0xE1, 0x2E, 0x5D,
 0x2C, 0xA9, 0xAA, 0x88, 0x8A, 0x71, 0xD6, 0x8D, 0x68, 0xA3, 0xBB, 0x9D, 0x3B, 0xF8, 0x6B, 0x0D,
 0x4C, 0x40, 0xF4, 0x88, 0x17, 0x0A, 0x09, 0xE9, 0xCC, 0x7D, 0x96, 0x90, 0x37, 0xCB, 0x42, 0xDE,
 0xBC, 0x40, 0xC8, 0x3C, 0x33, 0xC3, 0xC7, 0x10, 0x86, 0x3F, 0x25, 0x0E, 0x99, 0x50, 0x1F, 0x1A,
 0x90, 0x19, 0xC9, 0x54, 0xBF, 0xB3, 0xA8, 0x3E, 0x7D, 0xA6, 0xA8, 0xFD, 0xA9, 0x39, 0x0E, 0xF3,
 0x04, 0x8E, 0x3A, 0x7E, 0x67, 0xB1, 0x91, 0xB1, 0x97, 0x8A, 0x1D, 0xC5, 0xAB, 0xB8, 0xD0, 0x49,
 0x6F, 0x84, 0xCA, 0xDB, 0x0C, 0x99, 0xEA, 0x5C, 0x94, 0x5C, 0xE9, 0x4B, 0x1D, 0xCF, 0x29, 0xA5,
 0x9E, 0x51, 0xEA, 0xFC, 0xB6, 0x10, 0x94, 0x23, 0xD5, 0x93, 0xB4, 0x8C, 0xE1, 0xF4, 0x70, 0x21,
 0x13, 0x47, 0x6D, 0xD7, 0xE9, 0xB8, 0xC6, 0xE3, 0x09, 0x9E, 0x61, 0xC6, 0x37, 0x4D, 0xE0, 0x3F,
 0x64, 0x14, 0xE5, 0xDB, 0x53, 0x54, 0x3C, 0xC1, 0xFA, 0x2B, 0xC4, 0x16, 0xB0, 0xB0, 0xE1, 0xCE,
 0x41, 0x43, 0x3A, 0xDB, 0x67, 0xAD, 0x4E, 0x7D, 0x3A, 0x06, 0xDD, 0x03, 0xBB, 0x3A, 0xD5, 0x6A,
 0x5F, 0xDE, 0xFF, 0xED, 0xB6, 0x04, 0x2B, 0x92, 0x16, 0x3C, 0x8A, 0x2D, 0x78, 0xF9, 0xDB, 0xED,
 0xDF, 0x8B, 0xB5, 0x89, 0x59, 0x4E, 0x14, 0x69, 0x97, 0xEF, 0xE9, 0x63, 0x99, 0xDF, 0xE3, 0x82,
 0x94, 0x8F, 0x03, 0x7D, 0x81, 0x96, 0x5B, 0xA0, 0xCD, 0x1A, 0x77, 0x84, 0x6D, 0x52, 0x15, 0x5B,
 0x6A, 0xA9, 0x15, 0xE5, 0xC8, 0x32, 0x43, 0xAE, 0xF2, 0x09, 0xCF, 0x5E, 0xF9, 0xAA, 0x8B, 0xBD,
 0x26, 0xC1, 0x36, 0xBA, 0x04, 0x13, 0x93, 0x61, 0x57, 0x6C, 0x54, 0x6C, 0x52, 0xE3, 0x1B, 0x36,
 0x08, 0x98, 0x73, 0x2F, 0x59, 0x8D, 0x2E, 0xA2, 0xAB, 0x99, 0xAB, 0xC9, 0xA0, 0xC0, 0x1D, 0xBC,
 0xE3, 0x2C, 0xEE, 0x36, 0x8B, 0x4B, 0xCE, 0xFC, 0x6E, 0x07, 0xEA, 0x3F, 0x68, 0xD6, 0x6A, 0xBA,
 0xE1, 0xDC, 0x05, 0x90, 0x3A, 0xDD, 0x99, 0x31, 0xB6, 0x60, 0xF9, 0x8A, 0xD3, 0xAB, 0x46, 0xEE,
 0xC8, 0x02, 0xD6, 0xA8, 0xA3, 0xA0, 0x86, 0xC6, 0x6B, 0x54, 0x1B, 0xD5, 0x77, 0xF8, 0x56, 0x85,
 0xF5, 0x90, 0xFC, 0x4B, 0x64, 0xD2, 0x8A, 0x48, 0x81, 0x24, 0x08, 0xAB, 0xF4, 0xB0, 0xA5, 0xCE,
 0xC2, 0x71, 0xE5, 0xAD, 0xBA, 0x27, 0x0D, 0x5B, 0x13, 0xBF, 0x9E, 0x86, 0x87, 0x27, 0x60, 0x47,
 0x76, 0x15, 0x46, 0x95, 0xED, 0xB9, 0xA3, 0xEE, 0x81, 0x64, 0xBB, 0x86, 0xF9, 0xA0, 0x98, 0xD0,
 0x15, 0x8B, 0x03, 0x44, 0xA0, 0x2D, 0xD5, 0xC7, 0x14, 0x92, 0xD3, 0x2E, 0xB4, 0x9D, 0xD3, 0x13,
 0x15, 0x3B, 0xD1, 0x85, 0x95, 0xE8, 0x0A, 0x0A, 0xFB, 0x35, 0x1F, 0x19, 0xC1, 0x64, 0x97, 0x77,
 0x10, 0x27, 0x7D, 0x9D, 0x47, 0x20, 0x16, 0x52, 0x88, 0x05, 0x40, 0xE4, 0x08, 0x28, 0x20, 0x4A,
 0xC6, 0x04, 0x65, 0xBF, 0x9F, 0xF7, 0x5F, 0xFE, 0x90, 0xAA, 0x9A, 0xB0, 0x37, 0x00, 0x00
};

String char1000 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
 
class SPIFFSEditor: public AsyncWebHandler {
  private:
    String _username;
    String _password;
    bool _authenticated;
    uint32_t _startTime;
  public:
    SPIFFSEditor(String username=String(), String password=String()):_username(username),_password(password),_authenticated(false),_startTime(0){}
    bool canHandle(AsyncWebServerRequest *request){
      if(request->method() == HTTP_GET && request->url() == "/edit")
        return true;
      else if (request->method() == HTTP_GET && request->url() == "/list")
        return true;
      else if(request->method() == HTTP_GET && !(request->url().endsWith("/")) && request->hasParam("download"))
        return true;
      else if(request->method() == HTTP_POST && request->url() == "/edit")
        return true;
      else if(request->method() == HTTP_DELETE && request->url() == "/edit")
        return true;
      else if(request->method() == HTTP_PUT && request->url() == "/edit")
        return true;
      return false;
    }

    void handleRequest(AsyncWebServerRequest *request){
      if(_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str()))
        return request->requestAuthentication();

      if(request->method() == HTTP_GET && request->url() == "/edit"){
        AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", edit_htm_gz, edit_htm_gz_len);
        response->addHeader("Content-Encoding", "gzip");
        request->send(response);
      } else if(request->method() == HTTP_GET && request->url() == "/list"){
        if(request->hasParam("dir")){
          String path = request->getParam("dir")->value();
          Dir dir = SPIFFS.openDir(path);
          path = String();
          String output = "[";
          while(dir.next()){
            fs::File entry = dir.openFile("r");
            if (output != "[") output += ',';
            bool isDir = false;
            output += "{\"type\":\"";
            output += (isDir)?"dir":"file";
            output += "\",\"name\":\"";
            output += String(entry.name()).substring(1);
            output += "\"}";
            entry.close();
          }
          output += "]";
          request->send(200, "text/json", output);
          output = String();
        }
        else
          request->send(400);
      } else if(request->method() == HTTP_GET){
        request->send(SPIFFS, request->url(), String(), true);
      } else if(request->method() == HTTP_DELETE){
        if(request->hasParam("path", true)){
          SPIFFS.remove(request->getParam("path", true)->value());
          request->send(200, "", "DELETE: "+request->getParam("path", true)->value());
        } else
          request->send(404);
      } else if(request->method() == HTTP_POST){
        if(request->hasParam("data", true, true) && SPIFFS.exists(request->getParam("data", true, true)->value()))
          request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value());
        else
          request->send(500);
      } else if(request->method() == HTTP_PUT){
        if(request->hasParam("path", true)){
          String filename = request->getParam("path", true)->value();
          if(SPIFFS.exists(filename)){
            request->send(200);
          } else {
            fs::File f = SPIFFS.open(filename, "w");
            if(f){
              f.write((uint8_t)0x00);
              f.close();
              request->send(200, "", "CREATE: "+filename);
            } else {
              request->send(500);
            }
          }
        } else
          request->send(400);
      }
    }

    void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
      if(!index){
        if(!_username.length() || request->authenticate(_username.c_str(),_password.c_str())){
          _authenticated = true;
          request->_tempFile = SPIFFS.open(filename, "w");
          _startTime = millis();
        }
      }
      if(_authenticated && request->_tempFile){
        if(len){
          request->_tempFile.write(data,len);
        }
        if(final){
          request->_tempFile.close();
          uint32_t uploadTime = millis() - _startTime;
          os_printf("upload: %s, %u B, %u ms\n", filename.c_str(), index+len, uploadTime);
        }
      }
    }
};

// SKETCH BEGIN
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
AsyncEventSource events("/events");

void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
  if(type == WS_EVT_CONNECT){
    os_printf("ws[%s][%u] connect\n", server->url(), client->id());
    client->printf("Hello Client %u :)", client->id());
    client->ping();
  } else if(type == WS_EVT_DISCONNECT){
    os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
  } else if(type == WS_EVT_ERROR){
    os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
  } else if(type == WS_EVT_PONG){
    os_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
  } else if(type == WS_EVT_DATA){
    AwsFrameInfo * info = (AwsFrameInfo*)arg;
    String msg = "";
    if(info->final && info->index == 0 && info->len == len){
      //the whole message is in a single frame and we got all of it's data
      os_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);

      if(info->opcode == WS_TEXT){
        for(size_t i=0; i < info->len; i++) {
          msg += (char) data[i];
        }
      } else {
        char buff[3];
        for(size_t i=0; i < info->len; i++) {
          sprintf(buff, "%02x ", (uint8_t) data[i]);
          msg += buff ;
        }
      }
      os_printf("%s\n",msg.c_str());

      if(info->opcode == WS_TEXT)
        client->text("I got your text message");
      else
        client->binary("I got your binary message");
    } else {
      //message is comprised of multiple frames or the frame is split into multiple packets
      if(info->index == 0){
        if(info->num == 0)
          os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
        os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
      }

      os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);

      if(info->opcode == WS_TEXT){
        for(size_t i=0; i < info->len; i++) {
          msg += (char) data[i];
        }
      } else {
        char buff[3];
        for(size_t i=0; i < info->len; i++) {
          sprintf(buff, "%02x ", (uint8_t) data[i]);
          msg += buff ;
        }
      }
      os_printf("%s\n",msg.c_str());

      if((info->index + len) == info->len){
        os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
        if(info->final){
          os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
          if(info->message_opcode == WS_TEXT)
            client->text("I got your text message");
          else
            client->binary("I got your binary message");
        }
      }
    }
  }
}

void handleVariable(AsyncWebServerRequest *request) {
  os_printf("variable\n");
 
  if(!request->hasParam("size")){
    request->send(500, "text/plain", "Argument 'size' is missing");
  }
  else {
    int length = atoi(request->getParam("size")->value().c_str());
    if(length == 0){
      request->send(500, "text/plain", "Argument 'size' must be >= 0 ");
    }
    else {
      //send generated reply of requested size
      // (from AsyncWebServer documentation)
      AsyncWebServerResponse *response = request->beginResponse("application/octet-stream", length, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
        //Write up to "maxLen" bytes into "buffer" and return the amount written.
        //index equals the amount of bytes that have been already sent
        //You will not be asked for more bytes once the content length has been reached.
        //Keep in mind that you can not delay or yield waiting for more data!
        //Send what you currently have and you will be asked for more again
        if (maxLen >= 1000) {
          strcpy((char*)buffer, char1000.c_str());
          return 1000;
        }
        else {
          strncpy((char*)buffer, char1000.c_str(), maxLen);
          return maxLen;
        }
      });
      response->addHeader("Content-Disposition", "attachment; filename=data_" + String(length) + ".dat");
      request->send(response);
    }
  }
}


const char* http_username = "admin";
const char* http_password = "admin";

void setup(){
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.printf("STA: Failed!\n");
    WiFi.disconnect(false);
    delay(1000);
    WiFi.begin(ssid, password);
  }

  //Send OTA events to the browser
  ArduinoOTA.onStart([]() { events.send("Update Start", "ota"); });
  ArduinoOTA.onEnd([]() { events.send("Update End", "ota"); });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    char p[32];
    sprintf(p, "Progress: %u%%\n", (progress/(total/100)));
    events.send(p, "ota");
  });
  ArduinoOTA.onError([](ota_error_t error) {
    if(error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota");
    else if(error == OTA_BEGIN_ERROR) events.send("Begin Failed", "ota");
    else if(error == OTA_CONNECT_ERROR) events.send("Connect Failed", "ota");
    else if(error == OTA_RECEIVE_ERROR) events.send("Recieve Failed", "ota");
    else if(error == OTA_END_ERROR) events.send("End Failed", "ota");
  });
  ArduinoOTA.begin();

  SPIFFS.begin();

  ws.onEvent(onWsEvent);
  server.addHandler(&ws);

  events.onConnect([](AsyncEventSourceClient *client){
    client->send("hello!",NULL,millis(),1000);
  });
  server.addHandler(&events);

  server.addHandler(new SPIFFSEditor(http_username,http_password));
  server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");

  server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/plain", String(ESP.getFreeHeap()));
  });

  server.on("/variable", HTTP_GET, handleVariable);
 
  server.onNotFound([](AsyncWebServerRequest *request){
    os_printf("NOT_FOUND: ");
    if(request->method() == HTTP_GET)
      os_printf("GET");
    else if(request->method() == HTTP_POST)
      os_printf("POST");
    else if(request->method() == HTTP_DELETE)
      os_printf("DELETE");
    else if(request->method() == HTTP_PUT)
      os_printf("PUT");
    else if(request->method() == HTTP_PATCH)
      os_printf("PATCH");
    else if(request->method() == HTTP_HEAD)
      os_printf("HEAD");
    else if(request->method() == HTTP_OPTIONS)
      os_printf("OPTIONS");
    else
      os_printf("UNKNOWN");
    os_printf(" http://%s%s\n", request->host().c_str(), request->url().c_str());

    if(request->contentLength()){
      os_printf("_CONTENT_TYPE: %s\n", request->contentType().c_str());
      os_printf("_CONTENT_LENGTH: %u\n", request->contentLength());
    }

    int headers = request->headers();
    int i;
    for(i=0;i<headers;i++){
      AsyncWebHeader* h = request->getHeader(i);
      os_printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
    }

    int params = request->params();
    for(i=0;i<params;i++){
      AsyncWebParameter* p = request->getParam(i);
      if(p->isFile()){
        os_printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
      } else if(p->isPost()){
        os_printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
      } else {
        os_printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
      }
    }

    request->send(404, "text/plain", "FileNotFound");
  });
  server.onFileUpload([](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
    if(!index)
      os_printf("UploadStart: %s\n", filename.c_str());
    os_printf("%s", (const char*)data);
    if(final)
      os_printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len);
  });
  server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
    if(!index)
      os_printf("BodyStart: %u\n", total);
    os_printf("%s", (const char*)data);
    if(index + len == total)
      os_printf("BodyEnd: %u\n", total);
  });
  server.begin();
}

void loop(){
  ArduinoOTA.handle();
}


For the sake of ompleteness, here are the batch files I used to perform the wget's:
in sequence (the time computation routine was found on the net. Sorry I forgot where so I cannot give due credit :-/ ):
Code: Select all@echo off
set start=%time%

wget -O test1 http://192.168.0.100/variable?size=100000
wget -O test2 http://192.168.0.100/variable?size=100000
wget -O test3 http://192.168.0.100/variable?size=100000
wget -O test4 http://192.168.0.100/variable?size=100000

set end=%time%
set options="tokens=1-4 delims=:.,"
for /f %options% %%a in ("%start%") do set start_h=%%a&set /a start_m=100%%b %% 100&set /a start_s=100%%c %% 100&set /a start_ms=100%%d %% 100
for /f %options% %%a in ("%end%") do set end_h=%%a&set /a end_m=100%%b %% 100&set /a end_s=100%%c %% 100&set /a end_ms=100%%d %% 100

set /a hours=%end_h%-%start_h%
set /a mins=%end_m%-%start_m%
set /a secs=%end_s%-%start_s%
set /a ms=%end_ms%-%start_ms%
if %hours% lss 0 set /a hours = 24%hours%
if %mins% lss 0 set /a hours = %hours% - 1 & set /a mins = 60%mins%
if %secs% lss 0 set /a mins = %mins% - 1 & set /a secs = 60%secs%
if %ms% lss 0 set /a secs = %secs% - 1 & set /a ms = 100%ms%
if 1%ms% lss 100 set ms=0%ms%

:: mission accomplished
set /a totalsecs = %hours%*3600 + %mins%*60 + %secs%
echo Execution time: %hours%:%mins%:%secs%.%ms% (%totalsecs%.%ms%s total)
pause


and in parallel (similarly, I found the "parallel in batch" code here. Kudos go to user benham for the technique):
Code: Select all@echo off
set start=%time%
setlocal enableDelayedExpansion

:: Display the output of each process if the /O option is used
:: else ignore the output of each process
if /i "%~1" equ "/O" (
  set "lockHandle=1"
  set "showOutput=1"
) else (
  set "lockHandle=1^>nul 9"
  set "showOutput="
)

:: List of commands goes here. Each command is prefixed with :::
::: wget -O test1 http://192.168.0.100/variable?size=100000
::: wget -O test2 http://192.168.0.100/variable?size=100000
::: wget -O test3 http://192.168.0.100/variable?size=100000
::: wget -O test4 http://192.168.0.100/variable?size=100000


:: Define the maximum number of parallel processes to run.
:: Each process number can optionally be assigned to a particular server
:: and/or cpu via psexec specs (untested).
set "maxProc=4"

:: Optional - Define CPU targets in terms of PSEXEC specs
::           (everything but the command)
::
:: If a cpu is not defined for a proc, then it will be run on the local machine.
:: I haven't tested this feature, but it seems like it should work.
::
:: set cpu1=psexec \\server1 ...
:: set cpu2=psexec \\server1 ...
:: set cpu3=psexec \\server2 ...
:: etc.

:: For this demo force all cpu specs to undefined (local machine)
for /l %%N in (1 1 %maxProc%) do set "cpu%%N="

:: Get a unique base lock name for this particular instantiation.
:: Incorporate a timestamp from WMIC if possible, but don't fail if
:: WMIC not available. Also incorporate a random number.
  set "lock="
  for /f "skip=1 delims=-+ " %%T in ('2^>nul wmic os get localdatetime') do (
    set "lock=%%T"
    goto :break
  )
  :break
  set "lock=%temp%\lock%lock%_%random%_"

:: Initialize the counters
  set /a "startCount=0, endCount=0"

:: Clear any existing end flags
  for /l %%N in (1 1 %maxProc%) do set "endProc%%N="

:: Launch the commands in a loop
  set launch=1
  for /f "tokens=* delims=:" %%A in ('findstr /b ":::" "%~f0"') do (
    if !startCount! lss %maxProc% (
      set /a "startCount+=1, nextProc=startCount"
    ) else (
      call :wait
    )
    set cmd!nextProc!=%%A
    if defined showOutput echo -------------------------------------------------------------------------------
    echo !time! - proc!nextProc!: starting %%A
    2>nul del %lock%!nextProc!
    %= Redirect the lock handle to the lock file. The CMD process will     =%
    %= maintain an exclusive lock on the lock file until the process ends. =%
    start /b "" cmd /c %lockHandle%^>"%lock%!nextProc!" 2^>^&1 !cpu%%N! %%A
  )
  set "launch="

:wait
:: Wait for procs to finish in a loop
:: If still launching then return as soon as a proc ends
:: else wait for all procs to finish
  :: redirect stderr to null to suppress any error message if redirection
  :: within the loop fails.
  for /l %%N in (1 1 %startCount%) do 2>nul (
    %= Redirect an unused file handle to the lock file. If the process is    =%
    %= still running then redirection will fail and the IF body will not run =%
    if not defined endProc%%N if exist "%lock%%%N" 9>>"%lock%%%N" (
      %= Made it inside the IF body so the process must have finished =%
      if defined showOutput echo ===============================================================================
      echo !time! - proc%%N: finished !cmd%%N!
      if defined showOutput type "%lock%%%N"
      if defined launch (
        set nextProc=%%N
        exit /b
      )
      set /a "endCount+=1, endProc%%N=1"
    )
  )
  if %endCount% lss %startCount% (
    1>nul 2>nul ping /n 2 ::1
    goto :wait
  )

2>nul del %lock%*
if defined showOutput echo ===============================================================================

set end=%time%
set options="tokens=1-4 delims=:.,"
for /f %options% %%a in ("%start%") do set start_h=%%a&set /a start_m=100%%b %% 100&set /a start_s=100%%c %% 100&set /a start_ms=100%%d %% 100
for /f %options% %%a in ("%end%") do set end_h=%%a&set /a end_m=100%%b %% 100&set /a end_s=100%%c %% 100&set /a end_ms=100%%d %% 100

set /a hours=%end_h%-%start_h%
set /a mins=%end_m%-%start_m%
set /a secs=%end_s%-%start_s%
set /a ms=%end_ms%-%start_ms%
if %hours% lss 0 set /a hours = 24%hours%
if %mins% lss 0 set /a hours = %hours% - 1 & set /a mins = 60%mins%
if %secs% lss 0 set /a mins = %mins% - 1 & set /a secs = 60%secs%
if %ms% lss 0 set /a secs = %secs% - 1 & set /a ms = 100%ms%
if 1%ms% lss 100 set ms=0%ms%

:: mission accomplished
set /a totalsecs = %hours%*3600 + %mins%*60 + %secs%
echo Execution time: %hours%:%mins%:%secs%.%ms% (%totalsecs%.%ms%s total)
pause


The tests were performed in my holiday hotel room with my old netbook and a small dlink router at the other end of the room with no internet access (and only the netbook and the ESP were connected to it). In other words far from a workhorse setup, but sufficiently isolated to give meaningfulresults I think.

I admit I spent one hour diffing 2.3.0 and git's 2.4.0-pre and could not pinpoint the exact place where the boost comes from. I saw LWIP settings have changed, I saw much of the "packeting" logic which was in the upper layers has been moved down, but no real "that's the bug they fixed" or something along the lines.
Same for bbx10nde's original observation. I really don't get why that change boosted the transfers by a factor 10...

Do you think that even with an empty loop, the Async version could benefit from similar enhancements as the ESP8266WebServer ?

Kind regards,

Vicne
User avatar
By Vicne
#53689
Me-no-dev wrote:why are you sending at most 1000 bytes in the Async Response? If you send as much as it tells you, results will differ :)

Right. I did so because my source string was 1000 bytes long, but I will change that and report here.
Thanks for the tip :-)

Vicne
User avatar
By Vicne
#53722 Hi,

Indeed, Async works much better now that I generate the contents as requested and not in artificial chunks. Thanks !

Here is an updated chart:
image (1).png

In terms of pure download speed, we clearly have two leagues:
- the "standard" ESP8266WebServer using the current (2.3.0) version of the ESP cores, which provides around 180kbps
- the "optimized" ways (using the AsyncWebServer or the standard server with recent git cores), which provide between 1000 and 1500kbps.
With the new cores, it seems that when the main loop does nothing but serve HTTP pages, the standard ESP8266 performs a bit better than the Async one, but the difference is minor and variance is large from one measurement to another.
However, if you want a http server that can serve several files at once and that runs independantly from the main loop, I think the ASyncWebServer is the one to choose (at least, that will be my choice from now on).

BTW, is there an easy way to make the AsyncWebServer compatible with the WifiManager lib, or is there an better-suited alternative ?

Feel free to comment or add your own experience of course.


Kind regards,


Vicne

PS: here is the source code for the modified "handleVariable" function:
Code: Select allvoid handleVariable(AsyncWebServerRequest *request) {
  os_printf("variable\n");
 
  if(!request->hasParam("size")){
    request->send(500, "text/plain", "Argument 'size' is missing");
  }
  else {
    int length = atoi(request->getParam("size")->value().c_str());
    if(length == 0){
      request->send(500, "text/plain", "Argument 'size' must be >= 0 ");
    }
    else {
      //send generated reply of requested size
      // (from AsyncWebServer documentation)
      AsyncWebServerResponse *response = request->beginResponse("application/octet-stream", length, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
        //Write up to "maxLen" bytes into "buffer" and return the amount written.
        //index equals the amount of bytes that have been already sent
        //You will not be asked for more bytes once the content length has been reached.
        //Keep in mind that you can not delay or yield waiting for more data!
        //Send what you currently have and you will be asked for more again
        int copied = 0;
        while (copied + char1000.length() < maxLen) {
          memcpy((char*)buffer + copied, char1000.c_str(), char1000.length());
          copied += char1000.length();
        }
        memcpy((char*)buffer + copied, char1000.c_str(), maxLen-copied);
        return maxLen;
      });
      response->addHeader("Content-Disposition", "attachment; filename=data_" + String(length) + ".dat");
      request->send(response);
    }
  }
}
You do not have the required permissions to view the files attached to this post.