Explorar o código

Merge pull request #59 from sidoh/homeassistant_json

Add support for HomeAssistant JSON schema
Chris Mullins %!s(int64=8) %!d(string=hai) anos
pai
achega
62e832b77b
Modificáronse 25 ficheiros con 3347 adicións e 7 borrados
  1. 16 1
      README.md
  2. 145 0
      lib/ESP8266WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino
  3. 238 0
      lib/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino
  4. BIN=BIN
      lib/ESP8266WebServer/examples/FSBrowser/data/edit.htm.gz
  5. BIN=BIN
      lib/ESP8266WebServer/examples/FSBrowser/data/favicon.ico
  6. BIN=BIN
      lib/ESP8266WebServer/examples/FSBrowser/data/graphs.js.gz
  7. 97 0
      lib/ESP8266WebServer/examples/FSBrowser/data/index.htm
  8. 72 0
      lib/ESP8266WebServer/examples/HelloServer/HelloServer.ino
  9. 40 0
      lib/ESP8266WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino
  10. 269 0
      lib/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino
  11. 674 0
      lib/ESP8266WebServer/examples/SDWebServer/SdRoot/edit/index.htm
  12. 22 0
      lib/ESP8266WebServer/examples/SDWebServer/SdRoot/index.htm
  13. BIN=BIN
      lib/ESP8266WebServer/examples/SDWebServer/SdRoot/pins.png
  14. 126 0
      lib/ESP8266WebServer/examples/SimpleAuthentification/SimpleAuthentification.ino
  15. 71 0
      lib/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino
  16. 36 0
      lib/ESP8266WebServer/keywords.txt
  17. 9 0
      lib/ESP8266WebServer/library.properties
  18. 534 0
      lib/ESP8266WebServer/src/ESP8266WebServer.cpp
  19. 181 0
      lib/ESP8266WebServer/src/ESP8266WebServer.h
  20. 589 0
      lib/ESP8266WebServer/src/Parsing.cpp
  21. 19 0
      lib/ESP8266WebServer/src/detail/RequestHandler.h
  22. 150 0
      lib/ESP8266WebServer/src/detail/RequestHandlersImpl.h
  23. 57 6
      lib/MiLight/MiLightClient.cpp
  24. 1 0
      platformio.ini
  25. 1 0
      src/main.cpp

+ 16 - 1
README.md

@@ -132,14 +132,29 @@ true%
 To configure your ESP to integrate with MQTT, fill out the following settings:
 
 1. `mqtt_server`- IP or hostname should work. Specify a port with standard syntax (e.g., "mymqttbroker.com:1884").
-1. `mqtt_topic_pattern` - you can control arbitrary configurations of device ID, device type, and group ID with this. More detail is provided below
+1. `mqtt_topic_pattern` - you can control arbitrary configurations of device ID, device type, and group ID with this. A good default choice is something like `milight/:device_id/:device_type/:group_id`. More detail is provided below.
 1. (optionally) `mqtt_username`
 1. (optionally) `mqtt_password`
 
+#### More detail on `mqtt_topic_pattern`
+
 `mqtt_topic_pattern` leverages single-level wildcards (documented [here](https://mosquitto.org/man/mqtt-7.html)). For example, specifying `milight/:device_id/:device_type/:group_id` will cause the ESP to subscribe to the topic `milight/+/+/+`. It will then interpret the second, third, and fourth tokens in topics it receives messages on as `:device_id`, `:device_type`, and `:group_id`, respectively.
 
 Messages should be JSON objects using exactly the same schema that the REST gateway uses for the `/gateways/:device_id/:device_type/:group_id` endpoint. Documented above in the _Bulb commands_ section.
 
+##### Example:
+
+If `mqtt_topic_pattern` is set to `milight/:device_id/:device_type/:group_id`, you could send the following message to it (the below example uses a ruby MQTT client):
+
+```ruby
+irb(main):001:0> require 'mqtt'
+irb(main):002:0> client = MQTT::Client.new('10.133.8.11',1883)
+irb(main):003:0> client.connect
+irb(main):004:0> client.publish('milight/0x118D/rgb_cct/1', '{"status":"ON","color":{"r":255,"g":200,"b":255},"brightness":100}')
+```
+
+This will instruct the ESP to send messages to RGB+CCT bulbs with device ID `0x118D` in group 1 to turn on, set color to RGB(255,200,255), and brightness to 100.
+
 ## UDP Gateways
 
 You can add an arbitrary number of UDP gateways through the REST API or through the web UI. Each gateway server listens on a port and responds to the standard set of commands supported by the Milight protocol. This should allow you to use one of these with standard Milight integrations (SmartThings, Home Assistant, OpenHAB, etc.).

+ 145 - 0
lib/ESP8266WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino

@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2015, Majenko Technologies
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice, this
+ *   list of conditions and the following disclaimer in the documentation and/or
+ *   other materials provided with the distribution.
+ *
+ * * Neither the name of Majenko Technologies nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+
+const char *ssid = "YourSSIDHere";
+const char *password = "YourPSKHere";
+
+ESP8266WebServer server ( 80 );
+
+const int led = 13;
+
+void handleRoot() {
+	digitalWrite ( led, 1 );
+	char temp[400];
+	int sec = millis() / 1000;
+	int min = sec / 60;
+	int hr = min / 60;
+
+	snprintf ( temp, 400,
+
+"<html>\
+  <head>\
+    <meta http-equiv='refresh' content='5'/>\
+    <title>ESP8266 Demo</title>\
+    <style>\
+      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
+    </style>\
+  </head>\
+  <body>\
+    <h1>Hello from ESP8266!</h1>\
+    <p>Uptime: %02d:%02d:%02d</p>\
+    <img src=\"/test.svg\" />\
+  </body>\
+</html>",
+
+		hr, min % 60, sec % 60
+	);
+	server.send ( 200, "text/html", temp );
+	digitalWrite ( led, 0 );
+}
+
+void handleNotFound() {
+	digitalWrite ( led, 1 );
+	String message = "File Not Found\n\n";
+	message += "URI: ";
+	message += server.uri();
+	message += "\nMethod: ";
+	message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
+	message += "\nArguments: ";
+	message += server.args();
+	message += "\n";
+
+	for ( uint8_t i = 0; i < server.args(); i++ ) {
+		message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
+	}
+
+	server.send ( 404, "text/plain", message );
+	digitalWrite ( led, 0 );
+}
+
+void setup ( void ) {
+	pinMode ( led, OUTPUT );
+	digitalWrite ( led, 0 );
+	Serial.begin ( 115200 );
+	WiFi.begin ( ssid, password );
+	Serial.println ( "" );
+
+	// Wait for connection
+	while ( WiFi.status() != WL_CONNECTED ) {
+		delay ( 500 );
+		Serial.print ( "." );
+	}
+
+	Serial.println ( "" );
+	Serial.print ( "Connected to " );
+	Serial.println ( ssid );
+	Serial.print ( "IP address: " );
+	Serial.println ( WiFi.localIP() );
+
+	if ( MDNS.begin ( "esp8266" ) ) {
+		Serial.println ( "MDNS responder started" );
+	}
+
+	server.on ( "/", handleRoot );
+	server.on ( "/test.svg", drawGraph );
+	server.on ( "/inline", []() {
+		server.send ( 200, "text/plain", "this works as well" );
+	} );
+	server.onNotFound ( handleNotFound );
+	server.begin();
+	Serial.println ( "HTTP server started" );
+}
+
+void loop ( void ) {
+	server.handleClient();
+}
+
+void drawGraph() {
+	String out = "";
+	char temp[100];
+	out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n";
+ 	out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n";
+ 	out += "<g stroke=\"black\">\n";
+ 	int y = rand() % 130;
+ 	for (int x = 10; x < 390; x+= 10) {
+ 		int y2 = rand() % 130;
+ 		sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2);
+ 		out += temp;
+ 		y = y2;
+ 	}
+	out += "</g>\n</svg>\n";
+
+	server.send ( 200, "image/svg+xml", out);
+}

+ 238 - 0
lib/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino

@@ -0,0 +1,238 @@
+/* 
+  FSWebServer - Example WebServer with SPIFFS backend for esp8266
+  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
+  This file is part of the ESP8266WebServer library for Arduino environment.
+ 
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+  This library 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
+  Lesser General Public License for more details.
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+  
+  upload the contents of the data folder with MkSPIFFS Tool ("ESP8266 Sketch Data Upload" in Tools menu in Arduino IDE)
+  or you can upload the contents of a folder if you CD in that folder and run the following command:
+  for file in `ls -A1`; do curl -F "file=@$PWD/$file" esp8266fs.local/edit; done
+  
+  access the sample web page at http://esp8266fs.local
+  edit the page by going to http://esp8266fs.local/edit
+*/
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <FS.h>
+
+#define DBG_OUTPUT_PORT Serial
+
+const char* ssid = "wifi-ssid";
+const char* password = "wifi-password";
+const char* host = "esp8266fs";
+
+ESP8266WebServer server(80);
+//holds the current upload
+File fsUploadFile;
+
+//format bytes
+String formatBytes(size_t bytes){
+  if (bytes < 1024){
+    return String(bytes)+"B";
+  } else if(bytes < (1024 * 1024)){
+    return String(bytes/1024.0)+"KB";
+  } else if(bytes < (1024 * 1024 * 1024)){
+    return String(bytes/1024.0/1024.0)+"MB";
+  } else {
+    return String(bytes/1024.0/1024.0/1024.0)+"GB";
+  }
+}
+
+String getContentType(String filename){
+  if(server.hasArg("download")) return "application/octet-stream";
+  else if(filename.endsWith(".htm")) return "text/html";
+  else if(filename.endsWith(".html")) return "text/html";
+  else if(filename.endsWith(".css")) return "text/css";
+  else if(filename.endsWith(".js")) return "application/javascript";
+  else if(filename.endsWith(".png")) return "image/png";
+  else if(filename.endsWith(".gif")) return "image/gif";
+  else if(filename.endsWith(".jpg")) return "image/jpeg";
+  else if(filename.endsWith(".ico")) return "image/x-icon";
+  else if(filename.endsWith(".xml")) return "text/xml";
+  else if(filename.endsWith(".pdf")) return "application/x-pdf";
+  else if(filename.endsWith(".zip")) return "application/x-zip";
+  else if(filename.endsWith(".gz")) return "application/x-gzip";
+  return "text/plain";
+}
+
+bool handleFileRead(String path){
+  DBG_OUTPUT_PORT.println("handleFileRead: " + path);
+  if(path.endsWith("/")) path += "index.htm";
+  String contentType = getContentType(path);
+  String pathWithGz = path + ".gz";
+  if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){
+    if(SPIFFS.exists(pathWithGz))
+      path += ".gz";
+    File file = SPIFFS.open(path, "r");
+    size_t sent = server.streamFile(file, contentType);
+    file.close();
+    return true;
+  }
+  return false;
+}
+
+void handleFileUpload(){
+  if(server.uri() != "/edit") return;
+  HTTPUpload& upload = server.upload();
+  if(upload.status == UPLOAD_FILE_START){
+    String filename = upload.filename;
+    if(!filename.startsWith("/")) filename = "/"+filename;
+    DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename);
+    fsUploadFile = SPIFFS.open(filename, "w");
+    filename = String();
+  } else if(upload.status == UPLOAD_FILE_WRITE){
+    //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize);
+    if(fsUploadFile)
+      fsUploadFile.write(upload.buf, upload.currentSize);
+  } else if(upload.status == UPLOAD_FILE_END){
+    if(fsUploadFile)
+      fsUploadFile.close();
+    DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
+  }
+}
+
+void handleFileDelete(){
+  if(server.args() == 0) return server.send(500, "text/plain", "BAD ARGS");
+  String path = server.arg(0);
+  DBG_OUTPUT_PORT.println("handleFileDelete: " + path);
+  if(path == "/")
+    return server.send(500, "text/plain", "BAD PATH");
+  if(!SPIFFS.exists(path))
+    return server.send(404, "text/plain", "FileNotFound");
+  SPIFFS.remove(path);
+  server.send(200, "text/plain", "");
+  path = String();
+}
+
+void handleFileCreate(){
+  if(server.args() == 0)
+    return server.send(500, "text/plain", "BAD ARGS");
+  String path = server.arg(0);
+  DBG_OUTPUT_PORT.println("handleFileCreate: " + path);
+  if(path == "/")
+    return server.send(500, "text/plain", "BAD PATH");
+  if(SPIFFS.exists(path))
+    return server.send(500, "text/plain", "FILE EXISTS");
+  File file = SPIFFS.open(path, "w");
+  if(file)
+    file.close();
+  else
+    return server.send(500, "text/plain", "CREATE FAILED");
+  server.send(200, "text/plain", "");
+  path = String();
+}
+
+void handleFileList() {
+  if(!server.hasArg("dir")) {server.send(500, "text/plain", "BAD ARGS"); return;}
+  
+  String path = server.arg("dir");
+  DBG_OUTPUT_PORT.println("handleFileList: " + path);
+  Dir dir = SPIFFS.openDir(path);
+  path = String();
+
+  String output = "[";
+  while(dir.next()){
+    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 += "]";
+  server.send(200, "text/json", output);
+}
+
+void setup(void){
+  DBG_OUTPUT_PORT.begin(115200);
+  DBG_OUTPUT_PORT.print("\n");
+  DBG_OUTPUT_PORT.setDebugOutput(true);
+  SPIFFS.begin();
+  {
+    Dir dir = SPIFFS.openDir("/");
+    while (dir.next()) {    
+      String fileName = dir.fileName();
+      size_t fileSize = dir.fileSize();
+      DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
+    }
+    DBG_OUTPUT_PORT.printf("\n");
+  }
+  
+
+  //WIFI INIT
+  DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid);
+  if (String(WiFi.SSID()) != String(ssid)) {
+    WiFi.begin(ssid, password);
+  }
+  
+  while (WiFi.status() != WL_CONNECTED) {
+    delay(500);
+    DBG_OUTPUT_PORT.print(".");
+  }
+  DBG_OUTPUT_PORT.println("");
+  DBG_OUTPUT_PORT.print("Connected! IP address: ");
+  DBG_OUTPUT_PORT.println(WiFi.localIP());
+
+  MDNS.begin(host);
+  DBG_OUTPUT_PORT.print("Open http://");
+  DBG_OUTPUT_PORT.print(host);
+  DBG_OUTPUT_PORT.println(".local/edit to see the file browser");
+  
+  
+  //SERVER INIT
+  //list directory
+  server.on("/list", HTTP_GET, handleFileList);
+  //load editor
+  server.on("/edit", HTTP_GET, [](){
+    if(!handleFileRead("/edit.htm")) server.send(404, "text/plain", "FileNotFound");
+  });
+  //create file
+  server.on("/edit", HTTP_PUT, handleFileCreate);
+  //delete file
+  server.on("/edit", HTTP_DELETE, handleFileDelete);
+  //first callback is called after the request has ended with all parsed arguments
+  //second callback handles file uploads at that location
+  server.on("/edit", HTTP_POST, [](){ server.send(200, "text/plain", ""); }, handleFileUpload);
+
+  //called when the url is not defined here
+  //use it to load content from SPIFFS
+  server.onNotFound([](){
+    if(!handleFileRead(server.uri()))
+      server.send(404, "text/plain", "FileNotFound");
+  });
+
+  //get heap status, analog input value and all GPIO statuses in one json call
+  server.on("/all", HTTP_GET, [](){
+    String json = "{";
+    json += "\"heap\":"+String(ESP.getFreeHeap());
+    json += ", \"analog\":"+String(analogRead(A0));
+    json += ", \"gpio\":"+String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16)));
+    json += "}";
+    server.send(200, "text/json", json);
+    json = String();
+  });
+  server.begin();
+  DBG_OUTPUT_PORT.println("HTTP server started");
+
+}
+ 
+void loop(void){
+  server.handleClient();
+}

BIN=BIN
lib/ESP8266WebServer/examples/FSBrowser/data/edit.htm.gz


BIN=BIN
lib/ESP8266WebServer/examples/FSBrowser/data/favicon.ico


BIN=BIN
lib/ESP8266WebServer/examples/FSBrowser/data/graphs.js.gz


+ 97 - 0
lib/ESP8266WebServer/examples/FSBrowser/data/index.htm

@@ -0,0 +1,97 @@
+<!-- 
+  FSWebServer - Example Index Page
+  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
+  This file is part of the ESP8266WebServer library for Arduino environment.
+ 
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+  This library 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
+  Lesser General Public License for more details.
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+-->
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  <title>ESP Monitor</title>
+  <script type="text/javascript" src="graphs.js"></script>
+  <script type="text/javascript">
+    var heap,temp,digi;
+    var reloadPeriod = 1000;
+    var running = false;
+    
+    function loadValues(){
+      if(!running) return;
+      var xh = new XMLHttpRequest();
+      xh.onreadystatechange = function(){
+        if (xh.readyState == 4){
+          if(xh.status == 200) {
+            var res = JSON.parse(xh.responseText);
+            heap.add(res.heap);
+            temp.add(res.analog);
+            digi.add(res.gpio);
+            if(running) setTimeout(loadValues, reloadPeriod);
+          } else running = false;
+        }
+      };
+      xh.open("GET", "/all", true);
+      xh.send(null);
+    };
+    
+    function run(){
+      if(!running){
+        running = true;
+        loadValues();
+      }
+    }
+    
+    function onBodyLoad(){
+      var refreshInput = document.getElementById("refresh-rate");
+      refreshInput.value = reloadPeriod;
+      refreshInput.onchange = function(e){
+        var value = parseInt(e.target.value);
+        reloadPeriod = (value > 0)?value:0;
+        e.target.value = reloadPeriod;
+      }
+      var stopButton = document.getElementById("stop-button");
+      stopButton.onclick = function(e){
+        running = false;
+      }
+      var startButton = document.getElementById("start-button");
+      startButton.onclick = function(e){
+        run();
+      }
+      
+      // Example with 10K thermistor
+      //function calcThermistor(v) {
+      //  var t = Math.log(((10230000 / v) - 10000));
+      //  t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;
+      //  return (t>120)?0:Math.round(t*10)/10;
+      //}
+      //temp = createGraph(document.getElementById("analog"), "Temperature", 100, 128, 10, 40, false, "cyan", calcThermistor);
+      
+      temp = createGraph(document.getElementById("analog"), "Analog Input", 100, 128, 0, 1023, false, "cyan");
+      heap = createGraph(document.getElementById("heap"), "Current Heap", 100, 125, 0, 30000, true, "orange");
+      digi = createDigiGraph(document.getElementById("digital"), "GPIO", 100, 146, [0, 4, 5, 16], "gold");
+      run();
+    }
+  </script>
+</head>
+<body id="index" style="margin:0; padding:0;" onload="onBodyLoad()">
+  <div id="controls" style="display: block; border: 1px solid rgb(68, 68, 68); padding: 5px; margin: 5px; width: 362px; background-color: rgb(238, 238, 238);">
+    <label>Period (ms):</label>
+    <input type="number" id="refresh-rate"/>
+    <input type="button" id="start-button" value="Start"/>
+    <input type="button" id="stop-button" value="Stop"/>
+  </div>
+  <div id="heap"></div>
+  <div id="analog"></div>
+  <div id="digital"></div>
+</body>
+</html>

+ 72 - 0
lib/ESP8266WebServer/examples/HelloServer/HelloServer.ino

@@ -0,0 +1,72 @@
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+
+const char* ssid = "........";
+const char* password = "........";
+
+ESP8266WebServer server(80);
+
+const int led = 13;
+
+void handleRoot() {
+  digitalWrite(led, 1);
+  server.send(200, "text/plain", "hello from esp8266!");
+  digitalWrite(led, 0);
+}
+
+void handleNotFound(){
+  digitalWrite(led, 1);
+  String message = "File Not Found\n\n";
+  message += "URI: ";
+  message += server.uri();
+  message += "\nMethod: ";
+  message += (server.method() == HTTP_GET)?"GET":"POST";
+  message += "\nArguments: ";
+  message += server.args();
+  message += "\n";
+  for (uint8_t i=0; i<server.args(); i++){
+    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
+  }
+  server.send(404, "text/plain", message);
+  digitalWrite(led, 0);
+}
+
+void setup(void){
+  pinMode(led, OUTPUT);
+  digitalWrite(led, 0);
+  Serial.begin(115200);
+  WiFi.begin(ssid, password);
+  Serial.println("");
+
+  // Wait for connection
+  while (WiFi.status() != WL_CONNECTED) {
+    delay(500);
+    Serial.print(".");
+  }
+  Serial.println("");
+  Serial.print("Connected to ");
+  Serial.println(ssid);
+  Serial.print("IP address: ");
+  Serial.println(WiFi.localIP());
+
+  if (MDNS.begin("esp8266")) {
+    Serial.println("MDNS responder started");
+  }
+
+  server.on("/", handleRoot);
+
+  server.on("/inline", [](){
+    server.send(200, "text/plain", "this works as well");
+  });
+
+  server.onNotFound(handleNotFound);
+
+  server.begin();
+  Serial.println("HTTP server started");
+}
+
+void loop(void){
+  server.handleClient();
+}

+ 40 - 0
lib/ESP8266WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino

@@ -0,0 +1,40 @@
+#include <ESP8266WiFi.h>
+#include <ESP8266mDNS.h>
+#include <ArduinoOTA.h>
+#include <ESP8266WebServer.h>
+
+const char* ssid = "........";
+const char* password = "........";
+
+ESP8266WebServer server(80);
+
+const char* www_username = "admin";
+const char* www_password = "esp8266";
+
+void setup() {
+  Serial.begin(115200);
+  WiFi.mode(WIFI_STA);
+  WiFi.begin(ssid, password);
+  if(WiFi.waitForConnectResult() != WL_CONNECTED) {
+    Serial.println("WiFi Connect Failed! Rebooting...");
+    delay(1000);
+    ESP.restart();
+  }
+  ArduinoOTA.begin();
+
+  server.on("/", [](){
+    if(!server.authenticate(www_username, www_password))
+      return server.requestAuthentication();
+    server.send(200, "text/plain", "Login OK");
+  });
+  server.begin();
+
+  Serial.print("Open http://");
+  Serial.print(WiFi.localIP());
+  Serial.println("/ in your browser to see it working");
+}
+
+void loop() {
+  ArduinoOTA.handle();
+  server.handleClient();
+}

+ 269 - 0
lib/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino

@@ -0,0 +1,269 @@
+/*
+  SDWebServer - Example WebServer with SD Card backend for esp8266
+
+  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
+  This file is part of the ESP8266WebServer library for Arduino environment.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+  Have a FAT Formatted SD Card connected to the SPI port of the ESP8266
+  The web root is the SD Card root folder
+  File extensions with more than 3 charecters are not supported by the SD Library
+  File Names longer than 8 charecters will be truncated by the SD library, so keep filenames shorter
+  index.htm is the default index (works on subfolders as well)
+
+  upload the contents of SdRoot to the root of the SDcard and access the editor by going to http://esp8266sd.local/edit
+
+*/
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <SPI.h>
+#include <SD.h>
+
+#define DBG_OUTPUT_PORT Serial
+
+const char* ssid = "**********";
+const char* password = "**********";
+const char* host = "esp8266sd";
+
+ESP8266WebServer server(80);
+
+static bool hasSD = false;
+File uploadFile;
+
+
+void returnOK() {
+  server.send(200, "text/plain", "");
+}
+
+void returnFail(String msg) {
+  server.send(500, "text/plain", msg + "\r\n");
+}
+
+bool loadFromSdCard(String path){
+  String dataType = "text/plain";
+  if(path.endsWith("/")) path += "index.htm";
+
+  if(path.endsWith(".src")) path = path.substring(0, path.lastIndexOf("."));
+  else if(path.endsWith(".htm")) dataType = "text/html";
+  else if(path.endsWith(".css")) dataType = "text/css";
+  else if(path.endsWith(".js")) dataType = "application/javascript";
+  else if(path.endsWith(".png")) dataType = "image/png";
+  else if(path.endsWith(".gif")) dataType = "image/gif";
+  else if(path.endsWith(".jpg")) dataType = "image/jpeg";
+  else if(path.endsWith(".ico")) dataType = "image/x-icon";
+  else if(path.endsWith(".xml")) dataType = "text/xml";
+  else if(path.endsWith(".pdf")) dataType = "application/pdf";
+  else if(path.endsWith(".zip")) dataType = "application/zip";
+
+  File dataFile = SD.open(path.c_str());
+  if(dataFile.isDirectory()){
+    path += "/index.htm";
+    dataType = "text/html";
+    dataFile = SD.open(path.c_str());
+  }
+
+  if (!dataFile)
+    return false;
+
+  if (server.hasArg("download")) dataType = "application/octet-stream";
+
+  if (server.streamFile(dataFile, dataType) != dataFile.size()) {
+    DBG_OUTPUT_PORT.println("Sent less data than expected!");
+  }
+
+  dataFile.close();
+  return true;
+}
+
+void handleFileUpload(){
+  if(server.uri() != "/edit") return;
+  HTTPUpload& upload = server.upload();
+  if(upload.status == UPLOAD_FILE_START){
+    if(SD.exists((char *)upload.filename.c_str())) SD.remove((char *)upload.filename.c_str());
+    uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE);
+    DBG_OUTPUT_PORT.print("Upload: START, filename: "); DBG_OUTPUT_PORT.println(upload.filename);
+  } else if(upload.status == UPLOAD_FILE_WRITE){
+    if(uploadFile) uploadFile.write(upload.buf, upload.currentSize);
+    DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: "); DBG_OUTPUT_PORT.println(upload.currentSize);
+  } else if(upload.status == UPLOAD_FILE_END){
+    if(uploadFile) uploadFile.close();
+    DBG_OUTPUT_PORT.print("Upload: END, Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
+  }
+}
+
+void deleteRecursive(String path){
+  File file = SD.open((char *)path.c_str());
+  if(!file.isDirectory()){
+    file.close();
+    SD.remove((char *)path.c_str());
+    return;
+  }
+
+  file.rewindDirectory();
+  while(true) {
+    File entry = file.openNextFile();
+    if (!entry) break;
+    String entryPath = path + "/" +entry.name();
+    if(entry.isDirectory()){
+      entry.close();
+      deleteRecursive(entryPath);
+    } else {
+      entry.close();
+      SD.remove((char *)entryPath.c_str());
+    }
+    yield();
+  }
+
+  SD.rmdir((char *)path.c_str());
+  file.close();
+}
+
+void handleDelete(){
+  if(server.args() == 0) return returnFail("BAD ARGS");
+  String path = server.arg(0);
+  if(path == "/" || !SD.exists((char *)path.c_str())) {
+    returnFail("BAD PATH");
+    return;
+  }
+  deleteRecursive(path);
+  returnOK();
+}
+
+void handleCreate(){
+  if(server.args() == 0) return returnFail("BAD ARGS");
+  String path = server.arg(0);
+  if(path == "/" || SD.exists((char *)path.c_str())) {
+    returnFail("BAD PATH");
+    return;
+  }
+
+  if(path.indexOf('.') > 0){
+    File file = SD.open((char *)path.c_str(), FILE_WRITE);
+    if(file){
+      file.write((const char *)0);
+      file.close();
+    }
+  } else {
+    SD.mkdir((char *)path.c_str());
+  }
+  returnOK();
+}
+
+void printDirectory() {
+  if(!server.hasArg("dir")) return returnFail("BAD ARGS");
+  String path = server.arg("dir");
+  if(path != "/" && !SD.exists((char *)path.c_str())) return returnFail("BAD PATH");
+  File dir = SD.open((char *)path.c_str());
+  path = String();
+  if(!dir.isDirectory()){
+    dir.close();
+    return returnFail("NOT DIR");
+  }
+  dir.rewindDirectory();
+  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
+  server.send(200, "text/json", "");
+  WiFiClient client = server.client();
+
+  server.sendContent("[");
+  for (int cnt = 0; true; ++cnt) {
+    File entry = dir.openNextFile();
+    if (!entry)
+    break;
+
+    String output;
+    if (cnt > 0)
+      output = ',';
+
+    output += "{\"type\":\"";
+    output += (entry.isDirectory()) ? "dir" : "file";
+    output += "\",\"name\":\"";
+    output += entry.name();
+    output += "\"";
+    output += "}";
+    server.sendContent(output);
+    entry.close();
+ }
+ server.sendContent("]");
+ dir.close();
+}
+
+void handleNotFound(){
+  if(hasSD && loadFromSdCard(server.uri())) return;
+  String message = "SDCARD Not Detected\n\n";
+  message += "URI: ";
+  message += server.uri();
+  message += "\nMethod: ";
+  message += (server.method() == HTTP_GET)?"GET":"POST";
+  message += "\nArguments: ";
+  message += server.args();
+  message += "\n";
+  for (uint8_t i=0; i<server.args(); i++){
+    message += " NAME:"+server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
+  }
+  server.send(404, "text/plain", message);
+  DBG_OUTPUT_PORT.print(message);
+}
+
+void setup(void){
+  DBG_OUTPUT_PORT.begin(115200);
+  DBG_OUTPUT_PORT.setDebugOutput(true);
+  DBG_OUTPUT_PORT.print("\n");
+  WiFi.begin(ssid, password);
+  DBG_OUTPUT_PORT.print("Connecting to ");
+  DBG_OUTPUT_PORT.println(ssid);
+
+  // Wait for connection
+  uint8_t i = 0;
+  while (WiFi.status() != WL_CONNECTED && i++ < 20) {//wait 10 seconds
+    delay(500);
+  }
+  if(i == 21){
+    DBG_OUTPUT_PORT.print("Could not connect to");
+    DBG_OUTPUT_PORT.println(ssid);
+    while(1) delay(500);
+  }
+  DBG_OUTPUT_PORT.print("Connected! IP address: ");
+  DBG_OUTPUT_PORT.println(WiFi.localIP());
+
+  if (MDNS.begin(host)) {
+    MDNS.addService("http", "tcp", 80);
+    DBG_OUTPUT_PORT.println("MDNS responder started");
+    DBG_OUTPUT_PORT.print("You can now connect to http://");
+    DBG_OUTPUT_PORT.print(host);
+    DBG_OUTPUT_PORT.println(".local");
+  }
+
+
+  server.on("/list", HTTP_GET, printDirectory);
+  server.on("/edit", HTTP_DELETE, handleDelete);
+  server.on("/edit", HTTP_PUT, handleCreate);
+  server.on("/edit", HTTP_POST, [](){ returnOK(); }, handleFileUpload);
+  server.onNotFound(handleNotFound);
+
+  server.begin();
+  DBG_OUTPUT_PORT.println("HTTP server started");
+
+  if (SD.begin(SS)){
+     DBG_OUTPUT_PORT.println("SD Card initialized.");
+     hasSD = true;
+  }
+}
+
+void loop(void){
+  server.handleClient();
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 674 - 0
lib/ESP8266WebServer/examples/SDWebServer/SdRoot/edit/index.htm


+ 22 - 0
lib/ESP8266WebServer/examples/SDWebServer/SdRoot/index.htm

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  <title>ESP Index</title>
+  <style>
+    body {
+      background-color:black;
+      color:white;
+    }
+  </style>
+  <script type="text/javascript">
+    function onBodyLoad(){
+      console.log("we are loaded!!");
+    }
+  </script>
+</head>
+<body id="index" onload="onBodyLoad()">
+  <h1>ESP8266 Pin Functions</h1>
+<img src="pins.png" />
+</body>
+</html>

BIN=BIN
lib/ESP8266WebServer/examples/SDWebServer/SdRoot/pins.png


+ 126 - 0
lib/ESP8266WebServer/examples/SimpleAuthentification/SimpleAuthentification.ino

@@ -0,0 +1,126 @@
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+
+const char* ssid = "........";
+const char* password = "........";
+
+ESP8266WebServer server(80);
+
+//Check if header is present and correct
+bool is_authentified(){
+  Serial.println("Enter is_authentified");
+  if (server.hasHeader("Cookie")){   
+    Serial.print("Found cookie: ");
+    String cookie = server.header("Cookie");
+    Serial.println(cookie);
+    if (cookie.indexOf("ESPSESSIONID=1") != -1) {
+      Serial.println("Authentification Successful");
+      return true;
+    }
+  }
+  Serial.println("Authentification Failed");
+  return false;	
+}
+
+//login page, also called for disconnect
+void handleLogin(){
+  String msg;
+  if (server.hasHeader("Cookie")){   
+    Serial.print("Found cookie: ");
+    String cookie = server.header("Cookie");
+    Serial.println(cookie);
+  }
+  if (server.hasArg("DISCONNECT")){
+    Serial.println("Disconnection");
+    String header = "HTTP/1.1 301 OK\r\nSet-Cookie: ESPSESSIONID=0\r\nLocation: /login\r\nCache-Control: no-cache\r\n\r\n";
+    server.sendContent(header);
+    return;
+  }
+  if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")){
+    if (server.arg("USERNAME") == "admin" &&  server.arg("PASSWORD") == "admin" ){
+      String header = "HTTP/1.1 301 OK\r\nSet-Cookie: ESPSESSIONID=1\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n";
+      server.sendContent(header);
+      Serial.println("Log in Successful");
+      return;
+    }
+  msg = "Wrong username/password! try again.";
+  Serial.println("Log in Failed");
+  }
+  String content = "<html><body><form action='/login' method='POST'>To log in, please use : admin/admin<br>";
+  content += "User:<input type='text' name='USERNAME' placeholder='user name'><br>";
+  content += "Password:<input type='password' name='PASSWORD' placeholder='password'><br>";
+  content += "<input type='submit' name='SUBMIT' value='Submit'></form>" + msg + "<br>";
+  content += "You also can go <a href='/inline'>here</a></body></html>";
+  server.send(200, "text/html", content);
+}
+
+//root page can be accessed only if authentification is ok
+void handleRoot(){
+  Serial.println("Enter handleRoot");
+  String header;
+  if (!is_authentified()){
+    String header = "HTTP/1.1 301 OK\r\nLocation: /login\r\nCache-Control: no-cache\r\n\r\n";
+    server.sendContent(header);
+    return;
+  }
+  String content = "<html><body><H2>hello, you successfully connected to esp8266!</H2><br>";
+  if (server.hasHeader("User-Agent")){
+    content += "the user agent used is : " + server.header("User-Agent") + "<br><br>";
+  }
+  content += "You can access this page until you <a href=\"/login?DISCONNECT=YES\">disconnect</a></body></html>";
+  server.send(200, "text/html", content);
+}
+
+//no need authentification
+void handleNotFound(){
+  String message = "File Not Found\n\n";
+  message += "URI: ";
+  message += server.uri();
+  message += "\nMethod: ";
+  message += (server.method() == HTTP_GET)?"GET":"POST";
+  message += "\nArguments: ";
+  message += server.args();
+  message += "\n";
+  for (uint8_t i=0; i<server.args(); i++){
+    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
+  }
+  server.send(404, "text/plain", message);
+}
+
+void setup(void){
+  Serial.begin(115200);
+  WiFi.begin(ssid, password);
+  Serial.println("");
+
+  // Wait for connection
+  while (WiFi.status() != WL_CONNECTED) {
+    delay(500);
+    Serial.print(".");
+  }
+  Serial.println("");
+  Serial.print("Connected to ");
+  Serial.println(ssid);
+  Serial.print("IP address: ");
+  Serial.println(WiFi.localIP());
+
+
+  server.on("/", handleRoot);
+  server.on("/login", handleLogin);
+  server.on("/inline", [](){
+    server.send(200, "text/plain", "this works without need of authentification");
+  });
+
+  server.onNotFound(handleNotFound);
+  //here the list of headers to be recorded
+  const char * headerkeys[] = {"User-Agent","Cookie"} ;
+  size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*);
+  //ask server to track these headers
+  server.collectHeaders(headerkeys, headerkeyssize );
+  server.begin();
+  Serial.println("HTTP server started");
+}
+
+void loop(void){
+  server.handleClient();
+}

+ 71 - 0
lib/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino

@@ -0,0 +1,71 @@
+/*
+  To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update
+*/
+
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+
+const char* host = "esp8266-webupdate";
+const char* ssid = "........";
+const char* password = "........";
+
+ESP8266WebServer server(80);
+const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
+
+void setup(void){
+  Serial.begin(115200);
+  Serial.println();
+  Serial.println("Booting Sketch...");
+  WiFi.mode(WIFI_AP_STA);
+  WiFi.begin(ssid, password);
+  if(WiFi.waitForConnectResult() == WL_CONNECTED){
+    MDNS.begin(host);
+    server.on("/", HTTP_GET, [](){
+      server.sendHeader("Connection", "close");
+      server.sendHeader("Access-Control-Allow-Origin", "*");
+      server.send(200, "text/html", serverIndex);
+    });
+    server.on("/update", HTTP_POST, [](){
+      server.sendHeader("Connection", "close");
+      server.sendHeader("Access-Control-Allow-Origin", "*");
+      server.send(200, "text/plain", (Update.hasError())?"FAIL":"OK");
+      ESP.restart();
+    },[](){
+      HTTPUpload& upload = server.upload();
+      if(upload.status == UPLOAD_FILE_START){
+        Serial.setDebugOutput(true);
+        WiFiUDP::stopAll();
+        Serial.printf("Update: %s\n", upload.filename.c_str());
+        uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+        if(!Update.begin(maxSketchSpace)){//start with max available size
+          Update.printError(Serial);
+        }
+      } else if(upload.status == UPLOAD_FILE_WRITE){
+        if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
+          Update.printError(Serial);
+        }
+      } else if(upload.status == UPLOAD_FILE_END){
+        if(Update.end(true)){ //true to set the size to the current progress
+          Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
+        } else {
+          Update.printError(Serial);
+        }
+        Serial.setDebugOutput(false);
+      }
+      yield();
+    });
+    server.begin();
+    MDNS.addService("http", "tcp", 80);
+  
+    Serial.printf("Ready! Open http://%s.local in your browser\n", host);
+  } else {
+    Serial.println("WiFi Failed");
+  }
+}
+ 
+void loop(void){
+  server.handleClient();
+  delay(1);
+} 

+ 36 - 0
lib/ESP8266WebServer/keywords.txt

@@ -0,0 +1,36 @@
+#######################################
+# Syntax Coloring Map For Ultrasound
+#######################################
+
+#######################################
+# Datatypes (KEYWORD1)
+#######################################
+
+ESP8266WebServer	KEYWORD1
+HTTPMethod	KEYWORD1
+
+#######################################
+# Methods and Functions (KEYWORD2)
+#######################################
+
+begin	KEYWORD2
+handleClient	KEYWORD2
+on	KEYWORD2
+addHandler	KEYWORD2
+uri	KEYWORD2
+method	KEYWORD2
+client	KEYWORD2
+send	KEYWORD2
+arg	KEYWORD2
+argName	KEYWORD2
+args	KEYWORD2
+hasArg	KEYWORD2
+onNotFound	KEYWORD2
+
+#######################################
+# Constants (LITERAL1)
+#######################################
+
+HTTP_GET	LITERAL1
+HTTP_POST	LITERAL1
+HTTP_ANY	LITERAL1

+ 9 - 0
lib/ESP8266WebServer/library.properties

@@ -0,0 +1,9 @@
+name=ESP8266WebServer
+version=1.0
+author=Ivan Grokhotkov
+maintainer=Ivan Grokhtkov <ivan@esp8266.com>
+sentence=Simple web server library
+paragraph=The library supports HTTP GET and POST requests, provides argument parsing, handles one client at a time.
+category=Communication
+url=
+architectures=esp8266

+ 534 - 0
lib/ESP8266WebServer/src/ESP8266WebServer.cpp

@@ -0,0 +1,534 @@
+/*
+  ESP8266WebServer.cpp - Dead simple web-server.
+  Supports only one simultaneous client, knows how to handle GET and POST.
+
+  Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
+*/
+
+
+#include <Arduino.h>
+#include <libb64/cencode.h>
+#include "WiFiServer.h"
+#include "WiFiClient.h"
+#include "ESP8266WebServer.h"
+#include "FS.h"
+#include "detail/RequestHandlersImpl.h"
+
+//#define DEBUG_ESP_HTTP_SERVER
+#ifdef DEBUG_ESP_PORT
+#define DEBUG_OUTPUT DEBUG_ESP_PORT
+#else
+#define DEBUG_OUTPUT Serial
+#endif
+
+const char * AUTHORIZATION_HEADER = "Authorization";
+
+ESP8266WebServer::ESP8266WebServer(IPAddress addr, int port)
+: _server(addr, port)
+, _currentMethod(HTTP_ANY)
+, _currentHandler(0)
+, _firstHandler(0)
+, _lastHandler(0)
+, _currentArgCount(0)
+, _currentArgs(0)
+, _headerKeysCount(0)
+, _currentHeaders(0)
+, _contentLength(0)
+{
+}
+
+ESP8266WebServer::ESP8266WebServer(int port)
+: _server(port)
+, _currentMethod(HTTP_ANY)
+, _currentHandler(0)
+, _firstHandler(0)
+, _lastHandler(0)
+, _currentArgCount(0)
+, _currentArgs(0)
+, _headerKeysCount(0)
+, _currentHeaders(0)
+, _contentLength(0)
+{
+}
+
+ESP8266WebServer::~ESP8266WebServer() {
+  if (_currentHeaders)
+    delete[]_currentHeaders;
+  _headerKeysCount = 0;
+  RequestHandler* handler = _firstHandler;
+  while (handler) {
+    RequestHandler* next = handler->next();
+    delete handler;
+    handler = next;
+  }
+  close();
+}
+
+void ESP8266WebServer::begin() {
+  _currentStatus = HC_NONE;
+  _server.begin();
+  if(!_headerKeysCount)
+    collectHeaders(0, 0);
+}
+
+bool ESP8266WebServer::authenticate(const char * username, const char * password){
+  if(hasHeader(AUTHORIZATION_HEADER)){
+    String authReq = header(AUTHORIZATION_HEADER);
+    if(authReq.startsWith("Basic")){
+      authReq = authReq.substring(6);
+      authReq.trim();
+      char toencodeLen = strlen(username)+strlen(password)+1;
+      char *toencode = new char[toencodeLen + 1];
+      if(toencode == NULL){
+        authReq = String();
+        return false;
+      }
+      char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
+      if(encoded == NULL){
+        authReq = String();
+        delete[] toencode;
+        return false;
+      }
+      sprintf(toencode, "%s:%s", username, password);
+      if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){
+        authReq = String();
+        delete[] toencode;
+        delete[] encoded;
+        return true;
+      }
+      delete[] toencode;
+      delete[] encoded;
+    }
+    authReq = String();
+  }
+  return false;
+}
+
+void ESP8266WebServer::requestAuthentication(){
+  sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
+  send(401);
+}
+
+void ESP8266WebServer::on(const char* uri, ESP8266WebServer::THandlerFunction handler) {
+  on(uri, HTTP_ANY, handler);
+}
+
+void ESP8266WebServer::on(const char* uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn) {
+  on(uri, method, fn, _fileUploadHandler);
+}
+
+void ESP8266WebServer::on(const char* uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn) {
+  _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
+}
+
+void ESP8266WebServer::addHandler(RequestHandler* handler) {
+    _addRequestHandler(handler);
+}
+
+void ESP8266WebServer::_addRequestHandler(RequestHandler* handler) {
+    if (!_lastHandler) {
+      _firstHandler = handler;
+      _lastHandler = handler;
+    }
+    else {
+      _lastHandler->next(handler);
+      _lastHandler = handler;
+    }
+}
+
+void ESP8266WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
+    _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
+}
+
+void ESP8266WebServer::handleClient() {
+  if (_currentStatus == HC_NONE) {
+    WiFiClient client = _server.available();
+    if (!client) {
+      return;
+    }
+
+#ifdef DEBUG_ESP_HTTP_SERVER
+    DEBUG_OUTPUT.println("New client");
+#endif
+
+    _currentClient = client;
+    _currentStatus = HC_WAIT_READ;
+    _statusChange = millis();
+  }
+
+  if (!_currentClient.connected()) {
+    _currentClient = WiFiClient();
+    _currentStatus = HC_NONE;
+    return;
+  }
+
+  // Wait for data from client to become available
+  if (_currentStatus == HC_WAIT_READ) {
+    if (!_currentClient.available()) {
+      if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) {
+        _currentClient = WiFiClient();
+        _currentStatus = HC_NONE;
+      }
+      yield();
+      return;
+    }
+
+    if (!_parseRequest(_currentClient)) {
+      _currentClient = WiFiClient();
+      _currentStatus = HC_NONE;
+      return;
+    }
+
+    _contentLength = CONTENT_LENGTH_NOT_SET;
+    _handleRequest();
+
+    if (!_currentClient.connected()) {
+      _currentClient = WiFiClient();
+      _currentStatus = HC_NONE;
+      return;
+    } else {
+      _currentStatus = HC_WAIT_CLOSE;
+      _statusChange = millis();
+      return;
+    }
+  }
+
+  if (_currentStatus == HC_WAIT_CLOSE) {
+    if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) {
+      _currentClient = WiFiClient();
+      _currentStatus = HC_NONE;
+    } else {
+      yield();
+      return;
+    }
+  }
+}
+
+void ESP8266WebServer::close() {
+  _server.close();
+}
+
+void ESP8266WebServer::stop() {
+  close();
+}
+
+void ESP8266WebServer::sendHeader(const String& name, const String& value, bool first) {
+  String headerLine = name;
+  headerLine += ": ";
+  headerLine += value;
+  headerLine += "\r\n";
+
+  if (first) {
+    _responseHeaders = headerLine + _responseHeaders;
+  }
+  else {
+    _responseHeaders += headerLine;
+  }
+}
+
+
+void ESP8266WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
+    response = "HTTP/1.1 ";
+    response += String(code);
+    response += " ";
+    response += _responseCodeToString(code);
+    response += "\r\n";
+
+    if (!content_type)
+        content_type = "text/html";
+
+    sendHeader("Content-Type", content_type, true);
+    if (_contentLength == CONTENT_LENGTH_NOT_SET) {
+        sendHeader("Content-Length", String(contentLength));
+    } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
+        sendHeader("Content-Length", String(_contentLength));
+    }
+    sendHeader("Connection", "close");
+    sendHeader("Access-Control-Allow-Origin", "*");
+
+    response += _responseHeaders;
+    response += "\r\n";
+    _responseHeaders = String();
+}
+
+void ESP8266WebServer::send(int code, const char* content_type, const String& content) {
+    String header;
+    _prepareHeader(header, code, content_type, content.length());
+    sendContent(header);
+
+    sendContent(content);
+}
+
+void ESP8266WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
+    size_t contentLength = 0;
+
+    if (content != NULL) {
+        contentLength = strlen_P(content);
+    }
+
+    String header;
+    char type[64];
+    memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
+    _prepareHeader(header, code, (const char* )type, contentLength);
+    sendContent(header);
+    sendContent_P(content);
+}
+
+void ESP8266WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
+    String header;
+    char type[64];
+    memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
+    _prepareHeader(header, code, (const char* )type, contentLength);
+    sendContent(header);
+    sendContent_P(content, contentLength);
+}
+
+void ESP8266WebServer::send(int code, char* content_type, const String& content) {
+  send(code, (const char*)content_type, content);
+}
+
+void ESP8266WebServer::send(int code, const String& content_type, const String& content) {
+  send(code, (const char*)content_type.c_str(), content);
+}
+
+void ESP8266WebServer::sendContent(const String& content) {
+  const size_t unit_size = HTTP_DOWNLOAD_UNIT_SIZE;
+  size_t size_to_send = content.length();
+  const char* send_start = content.c_str();
+
+  while (size_to_send) {
+    size_t will_send = (size_to_send < unit_size) ? size_to_send : unit_size;
+    size_t sent = _currentClient.write(send_start, will_send);
+    if (sent == 0) {
+      break;
+    }
+    size_to_send -= sent;
+    send_start += sent;
+  }
+}
+
+void ESP8266WebServer::sendContent_P(PGM_P content) {
+    char contentUnit[HTTP_DOWNLOAD_UNIT_SIZE + 1];
+
+    contentUnit[HTTP_DOWNLOAD_UNIT_SIZE] = '\0';
+
+    while (content != NULL) {
+        size_t contentUnitLen;
+        PGM_P contentNext;
+
+        // due to the memccpy signature, lots of casts are needed
+        contentNext = (PGM_P)memccpy_P((void*)contentUnit, (PGM_VOID_P)content, 0, HTTP_DOWNLOAD_UNIT_SIZE);
+
+        if (contentNext == NULL) {
+            // no terminator, more data available
+            content += HTTP_DOWNLOAD_UNIT_SIZE;
+            contentUnitLen = HTTP_DOWNLOAD_UNIT_SIZE;
+        }
+        else {
+            // reached terminator. Do not send the terminator
+            contentUnitLen = contentNext - contentUnit - 1;
+            content = NULL;
+        }
+
+        // write is so overloaded, had to use the cast to get it pick the right one
+        _currentClient.write((const char*)contentUnit, contentUnitLen);
+    }
+}
+
+void ESP8266WebServer::sendContent_P(PGM_P content, size_t size) {
+    char contentUnit[HTTP_DOWNLOAD_UNIT_SIZE + 1];
+    contentUnit[HTTP_DOWNLOAD_UNIT_SIZE] = '\0';
+    size_t remaining_size = size;
+
+    while (content != NULL && remaining_size > 0) {
+        size_t contentUnitLen = HTTP_DOWNLOAD_UNIT_SIZE;
+
+        if (remaining_size < HTTP_DOWNLOAD_UNIT_SIZE) contentUnitLen = remaining_size;
+        // due to the memcpy signature, lots of casts are needed
+        memcpy_P((void*)contentUnit, (PGM_VOID_P)content, contentUnitLen);
+
+        content += contentUnitLen;
+        remaining_size -= contentUnitLen;
+
+        // write is so overloaded, had to use the cast to get it pick the right one
+        _currentClient.write((const char*)contentUnit, contentUnitLen);
+    }
+}
+
+
+String ESP8266WebServer::arg(String name) {
+  for (int i = 0; i < _currentArgCount; ++i) {
+    if ( _currentArgs[i].key == name )
+      return _currentArgs[i].value;
+  }
+  return String();
+}
+
+String ESP8266WebServer::arg(int i) {
+  if (i < _currentArgCount)
+    return _currentArgs[i].value;
+  return String();
+}
+
+String ESP8266WebServer::argName(int i) {
+  if (i < _currentArgCount)
+    return _currentArgs[i].key;
+  return String();
+}
+
+int ESP8266WebServer::args() {
+  return _currentArgCount;
+}
+
+bool ESP8266WebServer::hasArg(String  name) {
+  for (int i = 0; i < _currentArgCount; ++i) {
+    if (_currentArgs[i].key == name)
+      return true;
+  }
+  return false;
+}
+
+
+String ESP8266WebServer::header(String name) {
+  for (int i = 0; i < _headerKeysCount; ++i) {
+    if (_currentHeaders[i].key == name)
+      return _currentHeaders[i].value;
+  }
+  return String();
+}
+
+void ESP8266WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
+  _headerKeysCount = headerKeysCount + 1;
+  if (_currentHeaders)
+     delete[]_currentHeaders;
+  _currentHeaders = new RequestArgument[_headerKeysCount];
+  _currentHeaders[0].key = AUTHORIZATION_HEADER;
+  for (int i = 1; i < _headerKeysCount; i++){
+    _currentHeaders[i].key = headerKeys[i-1];
+  }
+}
+
+String ESP8266WebServer::header(int i) {
+  if (i < _headerKeysCount)
+    return _currentHeaders[i].value;
+  return String();
+}
+
+String ESP8266WebServer::headerName(int i) {
+  if (i < _headerKeysCount)
+    return _currentHeaders[i].key;
+  return String();
+}
+
+int ESP8266WebServer::headers() {
+  return _headerKeysCount;
+}
+
+bool ESP8266WebServer::hasHeader(String name) {
+  for (int i = 0; i < _headerKeysCount; ++i) {
+    if ((_currentHeaders[i].key == name) &&  (_currentHeaders[i].value.length() > 0))
+      return true;
+  }
+  return false;
+}
+
+String ESP8266WebServer::hostHeader() {
+  return _hostHeader;
+}
+
+void ESP8266WebServer::onFileUpload(THandlerFunction fn) {
+  _fileUploadHandler = fn;
+}
+
+void ESP8266WebServer::onNotFound(THandlerFunction fn) {
+  _notFoundHandler = fn;
+}
+
+void ESP8266WebServer::_handleRequest() {
+  bool handled = false;
+  if (!_currentHandler){
+#ifdef DEBUG_ESP_HTTP_SERVER
+    DEBUG_OUTPUT.println("request handler not found");
+#endif
+  }
+  else {
+    handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
+#ifdef DEBUG_ESP_HTTP_SERVER
+    if (!handled) {
+      DEBUG_OUTPUT.println("request handler failed to handle request");
+    }
+#endif
+  }
+
+  if (!handled) {
+    if(_notFoundHandler) {
+      _notFoundHandler();
+    }
+    else {
+      send(404, "text/plain", String("Not found: ") + _currentUri);
+    }
+  }
+
+  _currentUri = String();
+}
+
+String ESP8266WebServer::_responseCodeToString(int code) {
+  switch (code) {
+    case 100: return F("Continue");
+    case 101: return F("Switching Protocols");
+    case 200: return F("OK");
+    case 201: return F("Created");
+    case 202: return F("Accepted");
+    case 203: return F("Non-Authoritative Information");
+    case 204: return F("No Content");
+    case 205: return F("Reset Content");
+    case 206: return F("Partial Content");
+    case 300: return F("Multiple Choices");
+    case 301: return F("Moved Permanently");
+    case 302: return F("Found");
+    case 303: return F("See Other");
+    case 304: return F("Not Modified");
+    case 305: return F("Use Proxy");
+    case 307: return F("Temporary Redirect");
+    case 400: return F("Bad Request");
+    case 401: return F("Unauthorized");
+    case 402: return F("Payment Required");
+    case 403: return F("Forbidden");
+    case 404: return F("Not Found");
+    case 405: return F("Method Not Allowed");
+    case 406: return F("Not Acceptable");
+    case 407: return F("Proxy Authentication Required");
+    case 408: return F("Request Time-out");
+    case 409: return F("Conflict");
+    case 410: return F("Gone");
+    case 411: return F("Length Required");
+    case 412: return F("Precondition Failed");
+    case 413: return F("Request Entity Too Large");
+    case 414: return F("Request-URI Too Large");
+    case 415: return F("Unsupported Media Type");
+    case 416: return F("Requested range not satisfiable");
+    case 417: return F("Expectation Failed");
+    case 500: return F("Internal Server Error");
+    case 501: return F("Not Implemented");
+    case 502: return F("Bad Gateway");
+    case 503: return F("Service Unavailable");
+    case 504: return F("Gateway Time-out");
+    case 505: return F("HTTP Version not supported");
+    default:  return "";
+  }
+}

+ 181 - 0
lib/ESP8266WebServer/src/ESP8266WebServer.h

@@ -0,0 +1,181 @@
+/*
+  ESP8266WebServer.h - Dead simple web-server.
+  Supports only one simultaneous client, knows how to handle GET and POST.
+
+  Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
+*/
+
+
+#ifndef ESP8266WEBSERVER_H
+#define ESP8266WEBSERVER_H
+
+#include <functional>
+#include <ESP8266WiFi.h>
+
+enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
+enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
+                        UPLOAD_FILE_ABORTED };
+enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
+
+#define HTTP_DOWNLOAD_UNIT_SIZE 1460
+#define HTTP_UPLOAD_BUFLEN 20
+#define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request
+#define HTTP_MAX_POST_WAIT 1000 //ms to wait for POST data to arrive
+#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection
+
+#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
+#define CONTENT_LENGTH_NOT_SET ((size_t) -2)
+
+class ESP8266WebServer;
+
+typedef struct {
+  HTTPUploadStatus status;
+  String  filename;
+  String  name;
+  String  type;
+  size_t  totalSize;    // file size
+  size_t  currentSize;  // size of data currently in buf
+  uint8_t buf[HTTP_UPLOAD_BUFLEN];
+} HTTPUpload;
+
+#include "detail/RequestHandler.h"
+
+namespace fs {
+class FS;
+}
+
+class ESP8266WebServer
+{
+public:
+  ESP8266WebServer(IPAddress addr, int port = 80);
+  ESP8266WebServer(int port = 80);
+  ~ESP8266WebServer();
+
+  void begin();
+  void handleClient();
+
+  void close();
+  void stop();
+
+  bool authenticate(const char * username, const char * password);
+  void requestAuthentication();
+
+  typedef std::function<void(void)> THandlerFunction;
+  void on(const char* uri, THandlerFunction handler);
+  void on(const char* uri, HTTPMethod method, THandlerFunction fn);
+  void on(const char* uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
+  void addHandler(RequestHandler* handler);
+  void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL );
+  void onNotFound(THandlerFunction fn);  //called when handler is not assigned
+  void onFileUpload(THandlerFunction fn); //handle file uploads
+
+  String uri() { return _currentUri; }
+  HTTPMethod method() { return _currentMethod; }
+  WiFiClient client() { return _currentClient; }
+  HTTPUpload& upload() { return _currentUpload; }
+
+  String arg(String name);        // get request argument value by name
+  String arg(int i);              // get request argument value by number
+  String argName(int i);          // get request argument name by number
+  int args();                     // get arguments count
+  bool hasArg(String name);       // check if argument exists
+  void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
+  String header(String name);      // get request header value by name
+  String header(int i);              // get request header value by number
+  String headerName(int i);          // get request header name by number
+  int headers();                     // get header count
+  bool hasHeader(String name);       // check if header exists
+
+  String hostHeader();            // get request host header if available or empty String if not
+
+  // send response to the client
+  // code - HTTP response code, can be 200 or 404
+  // content_type - HTTP content type, like "text/plain" or "image/png"
+  // content - actual content body
+  void send(int code, const char* content_type = NULL, const String& content = String(""));
+  void send(int code, char* content_type, const String& content);
+  void send(int code, const String& content_type, const String& content);
+  void send_P(int code, PGM_P content_type, PGM_P content);
+  void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
+
+  void setContentLength(size_t contentLength) { _contentLength = contentLength; }
+  void sendHeader(const String& name, const String& value, bool first = false);
+  void sendContent(const String& content);
+  void sendContent_P(PGM_P content);
+  void sendContent_P(PGM_P content, size_t size);
+
+  static String urlDecode(const String& text);
+
+template<typename T> size_t streamFile(T &file, const String& contentType){
+  setContentLength(file.size());
+  if (String(file.name()).endsWith(".gz") &&
+      contentType != "application/x-gzip" &&
+      contentType != "application/octet-stream"){
+    sendHeader("Content-Encoding", "gzip");
+  }
+  send(200, contentType, "");
+  return _currentClient.write(file, HTTP_DOWNLOAD_UNIT_SIZE);
+}
+
+protected:
+  void _addRequestHandler(RequestHandler* handler);
+  void _handleRequest();
+  bool _parseRequest(WiFiClient& client);
+  void _parseArguments(String data);
+  static String _responseCodeToString(int code);
+  bool _parseForm(WiFiClient& client, String boundary, uint32_t len);
+  bool _parseFormUploadAborted();
+  void _uploadWriteByte(uint8_t b);
+  uint8_t _uploadReadByte(WiFiClient& client);
+  void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
+  bool _collectHeader(const char* headerName, const char* headerValue);
+
+  struct RequestArgument {
+    String key;
+    String value;
+  };
+
+  WiFiServer  _server;
+
+  WiFiClient  _currentClient;
+  HTTPMethod  _currentMethod;
+  String      _currentUri;
+  HTTPClientStatus _currentStatus;
+  unsigned long _statusChange;
+
+  RequestHandler*  _currentHandler;
+  RequestHandler*  _firstHandler;
+  RequestHandler*  _lastHandler;
+  THandlerFunction _notFoundHandler;
+  THandlerFunction _fileUploadHandler;
+
+  int              _currentArgCount;
+  RequestArgument* _currentArgs;
+  HTTPUpload       _currentUpload;
+
+  int              _headerKeysCount;
+  RequestArgument* _currentHeaders;
+  size_t           _contentLength;
+  String           _responseHeaders;
+
+  String           _hostHeader;
+
+};
+
+
+#endif //ESP8266WEBSERVER_H

+ 589 - 0
lib/ESP8266WebServer/src/Parsing.cpp

@@ -0,0 +1,589 @@
+/*
+  Parsing.cpp - HTTP request parsing.
+
+  Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
+*/
+
+#include <Arduino.h>
+#include "WiFiServer.h"
+#include "WiFiClient.h"
+#include "ESP8266WebServer.h"
+
+//#define DEBUG_ESP_HTTP_SERVER
+#ifdef DEBUG_ESP_PORT
+#define DEBUG_OUTPUT DEBUG_ESP_PORT
+#else
+#define DEBUG_OUTPUT Serial
+#endif
+
+static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms)
+{
+  char *buf = nullptr;
+  dataLength = 0;
+  while (dataLength < maxLength) {
+    int tries = timeout_ms;
+    size_t newLength;
+    while (!(newLength = client.available()) && tries--) delay(1);
+    if (!newLength) {
+      break;
+    }
+    if (!buf) {
+      buf = (char *) malloc(newLength + 1);
+      if (!buf) {
+        return nullptr;
+      }
+    }
+    else {
+      char* newBuf = (char *) realloc(buf, dataLength + newLength + 1);
+      if (!newBuf) {
+        free(buf);
+        return nullptr;
+      }
+      buf = newBuf;
+    }
+    client.readBytes(buf + dataLength, newLength);
+    dataLength += newLength;
+    buf[dataLength] = '\0';
+  }
+  return buf;
+}
+
+bool ESP8266WebServer::_parseRequest(WiFiClient& client) {
+  // Read the first line of HTTP request
+  String req = client.readStringUntil('\r');
+  client.readStringUntil('\n');
+  //reset header value
+  for (int i = 0; i < _headerKeysCount; ++i) {
+    _currentHeaders[i].value =String();
+   }
+
+  // First line of HTTP request looks like "GET /path HTTP/1.1"
+  // Retrieve the "/path" part by finding the spaces
+  int addr_start = req.indexOf(' ');
+  int addr_end = req.indexOf(' ', addr_start + 1);
+  if (addr_start == -1 || addr_end == -1) {
+#ifdef DEBUG_ESP_HTTP_SERVER
+    DEBUG_OUTPUT.print("Invalid request: ");
+    DEBUG_OUTPUT.println(req);
+#endif
+    return false;
+  }
+
+  String methodStr = req.substring(0, addr_start);
+  String url = req.substring(addr_start + 1, addr_end);
+  String searchStr = "";
+  int hasSearch = url.indexOf('?');
+  if (hasSearch != -1){
+    searchStr = url.substring(hasSearch + 1);
+    url = url.substring(0, hasSearch);
+  }
+  _currentUri = url;
+
+  HTTPMethod method = HTTP_GET;
+  if (methodStr == "POST") {
+    method = HTTP_POST;
+  } else if (methodStr == "DELETE") {
+    method = HTTP_DELETE;
+  } else if (methodStr == "OPTIONS") {
+    method = HTTP_OPTIONS;
+  } else if (methodStr == "PUT") {
+    method = HTTP_PUT;
+  } else if (methodStr == "PATCH") {
+    method = HTTP_PATCH;
+  }
+  _currentMethod = method;
+
+#ifdef DEBUG_ESP_HTTP_SERVER
+  DEBUG_OUTPUT.print("method: ");
+  DEBUG_OUTPUT.print(methodStr);
+  DEBUG_OUTPUT.print(" url: ");
+  DEBUG_OUTPUT.print(url);
+  DEBUG_OUTPUT.print(" search: ");
+  DEBUG_OUTPUT.println(searchStr);
+#endif
+
+  //attach handler
+  RequestHandler* handler;
+  for (handler = _firstHandler; handler; handler = handler->next()) {
+    if (handler->canHandle(_currentMethod, _currentUri))
+      break;
+  }
+  _currentHandler = handler;
+
+  String formData;
+  // below is needed only when POST type request
+  if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){
+    String boundaryStr;
+    String headerName;
+    String headerValue;
+    bool isForm = false;
+    uint32_t contentLength = 0;
+    //parse headers
+    while(1){
+      req = client.readStringUntil('\r');
+      client.readStringUntil('\n');
+      if (req == "") break;//no moar headers
+      int headerDiv = req.indexOf(':');
+      if (headerDiv == -1){
+        break;
+      }
+      headerName = req.substring(0, headerDiv);
+      headerValue = req.substring(headerDiv + 1);
+      headerValue.trim();
+       _collectHeader(headerName.c_str(),headerValue.c_str());
+
+	  #ifdef DEBUG_ESP_HTTP_SERVER
+	  DEBUG_OUTPUT.print("headerName: ");
+	  DEBUG_OUTPUT.println(headerName);
+	  DEBUG_OUTPUT.print("headerValue: ");
+	  DEBUG_OUTPUT.println(headerValue);
+	  #endif
+
+      if (headerName == "Content-Type"){
+        if (headerValue.startsWith("text/plain")){
+          isForm = false;
+        } else if (headerValue.startsWith("multipart/form-data")){
+          boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
+          isForm = true;
+        }
+      } else if (headerName == "Content-Length"){
+        contentLength = headerValue.toInt();
+      } else if (headerName == "Host"){
+        _hostHeader = headerValue;
+      }
+    }
+
+    if (!isForm){
+      size_t plainLength;
+      char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
+      if (plainLength < contentLength) {
+      	free(plainBuf);
+      	return false;
+      }
+#ifdef DEBUG_ESP_HTTP_SERVER
+      DEBUG_OUTPUT.print("Plain: ");
+      DEBUG_OUTPUT.println(plainBuf);
+#endif
+      if (contentLength > 0) {
+        if (searchStr != "") searchStr += '&';
+        if(plainBuf[0] == '{' || plainBuf[0] == '[' || strstr(plainBuf, "=") == NULL){
+          //plain post json or other data
+          searchStr += "plain=";
+          searchStr += plainBuf;
+        } else {
+          searchStr += plainBuf;
+        }
+        free(plainBuf);
+      }
+    }
+    _parseArguments(searchStr);
+    if (isForm){
+      if (!_parseForm(client, boundaryStr, contentLength)) {
+        return false;
+      }
+    }
+  } else {
+    String headerName;
+    String headerValue;
+    //parse headers
+    while(1){
+      req = client.readStringUntil('\r');
+      client.readStringUntil('\n');
+      if (req == "") break;//no moar headers
+      int headerDiv = req.indexOf(':');
+      if (headerDiv == -1){
+        break;
+      }
+      headerName = req.substring(0, headerDiv);
+      headerValue = req.substring(headerDiv + 2);
+      _collectHeader(headerName.c_str(),headerValue.c_str());
+
+	  #ifdef DEBUG_ESP_HTTP_SERVER
+	  DEBUG_OUTPUT.print("headerName: ");
+	  DEBUG_OUTPUT.println(headerName);
+	  DEBUG_OUTPUT.print("headerValue: ");
+	  DEBUG_OUTPUT.println(headerValue);
+	  #endif
+
+	  if (headerName == "Host"){
+        _hostHeader = headerValue;
+      }
+    }
+    _parseArguments(searchStr);
+  }
+  client.flush();
+
+#ifdef DEBUG_ESP_HTTP_SERVER
+  DEBUG_OUTPUT.print("Request: ");
+  DEBUG_OUTPUT.println(url);
+  DEBUG_OUTPUT.print(" Arguments: ");
+  DEBUG_OUTPUT.println(searchStr);
+#endif
+
+  return true;
+}
+
+bool ESP8266WebServer::_collectHeader(const char* headerName, const char* headerValue) {
+  for (int i = 0; i < _headerKeysCount; i++) {
+    if (_currentHeaders[i].key==headerName) {
+            _currentHeaders[i].value=headerValue;
+            return true;
+        }
+  }
+  return false;
+}
+
+void ESP8266WebServer::_parseArguments(String data) {
+#ifdef DEBUG_ESP_HTTP_SERVER
+  DEBUG_OUTPUT.print("args: ");
+  DEBUG_OUTPUT.println(data);
+#endif
+  if (_currentArgs)
+    delete[] _currentArgs;
+  _currentArgs = 0;
+  if (data.length() == 0) {
+    _currentArgCount = 0;
+    return;
+  }
+  _currentArgCount = 1;
+
+  for (int i = 0; i < (int)data.length(); ) {
+    i = data.indexOf('&', i);
+    if (i == -1)
+      break;
+    ++i;
+    ++_currentArgCount;
+  }
+#ifdef DEBUG_ESP_HTTP_SERVER
+  DEBUG_OUTPUT.print("args count: ");
+  DEBUG_OUTPUT.println(_currentArgCount);
+#endif
+
+  _currentArgs = new RequestArgument[_currentArgCount];
+  int pos = 0;
+  int iarg;
+  for (iarg = 0; iarg < _currentArgCount;) {
+    int equal_sign_index = data.indexOf('=', pos);
+    int next_arg_index = data.indexOf('&', pos);
+#ifdef DEBUG_ESP_HTTP_SERVER
+    DEBUG_OUTPUT.print("pos ");
+    DEBUG_OUTPUT.print(pos);
+    DEBUG_OUTPUT.print("=@ ");
+    DEBUG_OUTPUT.print(equal_sign_index);
+    DEBUG_OUTPUT.print(" &@ ");
+    DEBUG_OUTPUT.println(next_arg_index);
+#endif
+    if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
+#ifdef DEBUG_ESP_HTTP_SERVER
+      DEBUG_OUTPUT.print("arg missing value: ");
+      DEBUG_OUTPUT.println(iarg);
+#endif
+      if (next_arg_index == -1)
+        break;
+      pos = next_arg_index + 1;
+      continue;
+    }
+    RequestArgument& arg = _currentArgs[iarg];
+    arg.key = data.substring(pos, equal_sign_index);
+	arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
+#ifdef DEBUG_ESP_HTTP_SERVER
+    DEBUG_OUTPUT.print("arg ");
+    DEBUG_OUTPUT.print(iarg);
+    DEBUG_OUTPUT.print(" key: ");
+    DEBUG_OUTPUT.print(arg.key);
+    DEBUG_OUTPUT.print(" value: ");
+    DEBUG_OUTPUT.println(arg.value);
+#endif
+    ++iarg;
+    if (next_arg_index == -1)
+      break;
+    pos = next_arg_index + 1;
+  }
+  _currentArgCount = iarg;
+#ifdef DEBUG_ESP_HTTP_SERVER
+  DEBUG_OUTPUT.print("args count: ");
+  DEBUG_OUTPUT.println(_currentArgCount);
+#endif
+
+}
+
+void ESP8266WebServer::_uploadWriteByte(uint8_t b){
+  if (_currentUpload.currentSize == HTTP_UPLOAD_BUFLEN){
+    if(_currentHandler && _currentHandler->canUpload(_currentUri))
+      _currentHandler->upload(*this, _currentUri, _currentUpload);
+    _currentUpload.totalSize += _currentUpload.currentSize;
+    _currentUpload.currentSize = 0;
+  }
+  _currentUpload.buf[_currentUpload.currentSize++] = b;
+}
+
+uint8_t ESP8266WebServer::_uploadReadByte(WiFiClient& client){
+  int res = client.read();
+  if(res == -1){
+    while(!client.available() && client.connected())
+      yield();
+    res = client.read();
+  }
+  return (uint8_t)res;
+}
+
+bool ESP8266WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){
+
+#ifdef DEBUG_ESP_HTTP_SERVER
+  DEBUG_OUTPUT.print("Parse Form: Boundary: ");
+  DEBUG_OUTPUT.print(boundary);
+  DEBUG_OUTPUT.print(" Length: ");
+  DEBUG_OUTPUT.println(len);
+#endif
+  String line;
+  int retry = 0;
+  do {
+    line = client.readStringUntil('\r');
+    ++retry;
+  } while (line.length() == 0 && retry < 3);
+
+  client.readStringUntil('\n');
+  //start reading the form
+  if (line == ("--"+boundary)){
+    RequestArgument* postArgs = new RequestArgument[32];
+    int postArgsLen = 0;
+    while(1){
+      String argName;
+      String argValue;
+      String argType;
+      String argFilename;
+      bool argIsFile = false;
+
+      line = client.readStringUntil('\r');
+      client.readStringUntil('\n');
+      if (line.startsWith("Content-Disposition")){
+        int nameStart = line.indexOf('=');
+        if (nameStart != -1){
+          argName = line.substring(nameStart+2);
+          nameStart = argName.indexOf('=');
+          if (nameStart == -1){
+            argName = argName.substring(0, argName.length() - 1);
+          } else {
+            argFilename = argName.substring(nameStart+2, argName.length() - 1);
+            argName = argName.substring(0, argName.indexOf('"'));
+            argIsFile = true;
+#ifdef DEBUG_ESP_HTTP_SERVER
+            DEBUG_OUTPUT.print("PostArg FileName: ");
+            DEBUG_OUTPUT.println(argFilename);
+#endif
+            //use GET to set the filename if uploading using blob
+            if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename");
+          }
+#ifdef DEBUG_ESP_HTTP_SERVER
+          DEBUG_OUTPUT.print("PostArg Name: ");
+          DEBUG_OUTPUT.println(argName);
+#endif
+          argType = "text/plain";
+          line = client.readStringUntil('\r');
+          client.readStringUntil('\n');
+          if (line.startsWith("Content-Type")){
+            argType = line.substring(line.indexOf(':')+2);
+            //skip next line
+            client.readStringUntil('\r');
+            client.readStringUntil('\n');
+          }
+#ifdef DEBUG_ESP_HTTP_SERVER
+          DEBUG_OUTPUT.print("PostArg Type: ");
+          DEBUG_OUTPUT.println(argType);
+#endif
+          if (!argIsFile){
+            while(1){
+              line = client.readStringUntil('\r');
+              client.readStringUntil('\n');
+              if (line.startsWith("--"+boundary)) break;
+              if (argValue.length() > 0) argValue += "\n";
+              argValue += line;
+            }
+#ifdef DEBUG_ESP_HTTP_SERVER
+            DEBUG_OUTPUT.print("PostArg Value: ");
+            DEBUG_OUTPUT.println(argValue);
+            DEBUG_OUTPUT.println();
+#endif
+
+            RequestArgument& arg = postArgs[postArgsLen++];
+            arg.key = argName;
+            arg.value = argValue;
+
+            if (line == ("--"+boundary+"--")){
+#ifdef DEBUG_ESP_HTTP_SERVER
+              DEBUG_OUTPUT.println("Done Parsing POST");
+#endif
+              break;
+            }
+          } else {
+            _currentUpload.status = UPLOAD_FILE_START;
+            _currentUpload.name = argName;
+            _currentUpload.filename = argFilename;
+            _currentUpload.type = argType;
+            _currentUpload.totalSize = 0;
+            _currentUpload.currentSize = 0;
+#ifdef DEBUG_ESP_HTTP_SERVER
+            DEBUG_OUTPUT.print("Start File: ");
+            DEBUG_OUTPUT.print(_currentUpload.filename);
+            DEBUG_OUTPUT.print(" Type: ");
+            DEBUG_OUTPUT.println(_currentUpload.type);
+#endif
+            if(_currentHandler && _currentHandler->canUpload(_currentUri))
+              _currentHandler->upload(*this, _currentUri, _currentUpload);
+            _currentUpload.status = UPLOAD_FILE_WRITE;
+            uint8_t argByte = _uploadReadByte(client);
+readfile:
+            while(argByte != 0x0D){
+              if (!client.connected()) return _parseFormUploadAborted();
+              _uploadWriteByte(argByte);
+              argByte = _uploadReadByte(client);
+            }
+
+            argByte = _uploadReadByte(client);
+            if (!client.connected()) return _parseFormUploadAborted();
+            if (argByte == 0x0A){
+              argByte = _uploadReadByte(client);
+              if (!client.connected()) return _parseFormUploadAborted();
+              if ((char)argByte != '-'){
+                //continue reading the file
+                _uploadWriteByte(0x0D);
+                _uploadWriteByte(0x0A);
+                goto readfile;
+              } else {
+                argByte = _uploadReadByte(client);
+                if (!client.connected()) return _parseFormUploadAborted();
+                if ((char)argByte != '-'){
+                  //continue reading the file
+                  _uploadWriteByte(0x0D);
+                  _uploadWriteByte(0x0A);
+                  _uploadWriteByte((uint8_t)('-'));
+                  goto readfile;
+                }
+              }
+
+              uint8_t endBuf[boundary.length()];
+              client.readBytes(endBuf, boundary.length());
+
+              if (strstr((const char*)endBuf, boundary.c_str()) != NULL){
+                if(_currentHandler && _currentHandler->canUpload(_currentUri))
+                  _currentHandler->upload(*this, _currentUri, _currentUpload);
+                _currentUpload.totalSize += _currentUpload.currentSize;
+                _currentUpload.status = UPLOAD_FILE_END;
+                if(_currentHandler && _currentHandler->canUpload(_currentUri))
+                  _currentHandler->upload(*this, _currentUri, _currentUpload);
+#ifdef DEBUG_ESP_HTTP_SERVER
+                DEBUG_OUTPUT.print("End File: ");
+                DEBUG_OUTPUT.print(_currentUpload.filename);
+                DEBUG_OUTPUT.print(" Type: ");
+                DEBUG_OUTPUT.print(_currentUpload.type);
+                DEBUG_OUTPUT.print(" Size: ");
+                DEBUG_OUTPUT.println(_currentUpload.totalSize);
+#endif
+                line = client.readStringUntil(0x0D);
+                client.readStringUntil(0x0A);
+                if (line == "--"){
+#ifdef DEBUG_ESP_HTTP_SERVER
+                  DEBUG_OUTPUT.println("Done Parsing POST");
+#endif
+                  break;
+                }
+                continue;
+              } else {
+                _uploadWriteByte(0x0D);
+                _uploadWriteByte(0x0A);
+                _uploadWriteByte((uint8_t)('-'));
+                _uploadWriteByte((uint8_t)('-'));
+                uint32_t i = 0;
+                while(i < boundary.length()){
+                  _uploadWriteByte(endBuf[i++]);
+                }
+                argByte = _uploadReadByte(client);
+                goto readfile;
+              }
+            } else {
+              _uploadWriteByte(0x0D);
+              goto readfile;
+            }
+            break;
+          }
+        }
+      }
+    }
+
+    int iarg;
+    int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount;
+    for (iarg = 0; iarg < totalArgs; iarg++){
+      RequestArgument& arg = postArgs[postArgsLen++];
+      arg.key = _currentArgs[iarg].key;
+      arg.value = _currentArgs[iarg].value;
+    }
+    if (_currentArgs) delete[] _currentArgs;
+    _currentArgs = new RequestArgument[postArgsLen];
+    for (iarg = 0; iarg < postArgsLen; iarg++){
+      RequestArgument& arg = _currentArgs[iarg];
+      arg.key = postArgs[iarg].key;
+      arg.value = postArgs[iarg].value;
+    }
+    _currentArgCount = iarg;
+    if (postArgs) delete[] postArgs;
+    return true;
+  }
+#ifdef DEBUG_ESP_HTTP_SERVER
+  DEBUG_OUTPUT.print("Error: line: ");
+  DEBUG_OUTPUT.println(line);
+#endif
+  return false;
+}
+
+String ESP8266WebServer::urlDecode(const String& text)
+{
+	String decoded = "";
+	char temp[] = "0x00";
+	unsigned int len = text.length();
+	unsigned int i = 0;
+	while (i < len)
+	{
+		char decodedChar;
+		char encodedChar = text.charAt(i++);
+		if ((encodedChar == '%') && (i + 1 < len))
+		{
+			temp[2] = text.charAt(i++);
+			temp[3] = text.charAt(i++);
+
+			decodedChar = strtol(temp, NULL, 16);
+		}
+		else {
+			if (encodedChar == '+')
+			{
+				decodedChar = ' ';
+			}
+			else {
+				decodedChar = encodedChar;  // normal ascii char
+			}
+		}
+		decoded += decodedChar;
+	}
+	return decoded;
+}
+
+bool ESP8266WebServer::_parseFormUploadAborted(){
+  _currentUpload.status = UPLOAD_FILE_ABORTED;
+  if(_currentHandler && _currentHandler->canUpload(_currentUri))
+    _currentHandler->upload(*this, _currentUri, _currentUpload);
+  return false;
+}

+ 19 - 0
lib/ESP8266WebServer/src/detail/RequestHandler.h

@@ -0,0 +1,19 @@
+#ifndef REQUESTHANDLER_H
+#define REQUESTHANDLER_H
+
+class RequestHandler {
+public:
+    virtual ~RequestHandler() { }
+    virtual bool canHandle(HTTPMethod method, String uri) { return false; }
+    virtual bool canUpload(String uri) { return false; }
+    virtual bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) { return false; }
+    virtual void upload(ESP8266WebServer& server, String requestUri, HTTPUpload& upload) {}
+
+    RequestHandler* next() { return _next; }
+    void next(RequestHandler* r) { _next = r; }
+
+private:
+    RequestHandler* _next = nullptr;
+};
+
+#endif //REQUESTHANDLER_H

+ 150 - 0
lib/ESP8266WebServer/src/detail/RequestHandlersImpl.h

@@ -0,0 +1,150 @@
+#ifndef REQUESTHANDLERSIMPL_H
+#define REQUESTHANDLERSIMPL_H
+
+#include "RequestHandler.h"
+
+class FunctionRequestHandler : public RequestHandler {
+public:
+    FunctionRequestHandler(ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn, const char* uri, HTTPMethod method)
+    : _fn(fn)
+    , _ufn(ufn)
+    , _uri(uri)
+    , _method(method)
+    {
+    }
+
+    bool canHandle(HTTPMethod requestMethod, String requestUri) override  {
+        if (_method != HTTP_ANY && _method != requestMethod)
+            return false;
+
+        if (requestUri != _uri)
+            return false;
+
+        return true;
+    }
+
+    bool canUpload(String requestUri) override  {
+        if (!_ufn || !canHandle(HTTP_POST, requestUri))
+            return false;
+
+        return true;
+    }
+
+    bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) override {
+        if (!canHandle(requestMethod, requestUri))
+            return false;
+
+        _fn();
+        return true;
+    }
+
+    void upload(ESP8266WebServer& server, String requestUri, HTTPUpload& upload) override {
+        if (canUpload(requestUri))
+            _ufn();
+    }
+
+protected:
+    ESP8266WebServer::THandlerFunction _fn;
+    ESP8266WebServer::THandlerFunction _ufn;
+    String _uri;
+    HTTPMethod _method;
+};
+
+class StaticRequestHandler : public RequestHandler {
+public:
+    StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
+    : _fs(fs)
+    , _uri(uri)
+    , _path(path)
+    , _cache_header(cache_header)
+    {
+        _isFile = fs.exists(path);
+        DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
+        _baseUriLength = _uri.length();
+    }
+
+    bool canHandle(HTTPMethod requestMethod, String requestUri) override  {
+        if (requestMethod != HTTP_GET)
+            return false;
+
+        if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
+            return false;
+
+        return true;
+    }
+
+    bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) override {
+        if (!canHandle(requestMethod, requestUri))
+            return false;
+
+        DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
+
+        String path(_path);
+
+        if (!_isFile) {
+            // Base URI doesn't point to a file.
+            // If a directory is requested, look for index file.
+            if (requestUri.endsWith("/")) requestUri += "index.htm";
+
+            // Append whatever follows this URI in request to get the file path.
+            path += requestUri.substring(_baseUriLength);
+        }
+        DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
+
+        String contentType = getContentType(path);
+
+        // look for gz file, only if the original specified path is not a gz.  So part only works to send gzip via content encoding when a non compressed is asked for
+        // if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
+        if (!path.endsWith(".gz") && !_fs.exists(path))  {
+            String pathWithGz = path + ".gz";
+            if(_fs.exists(pathWithGz))
+                path += ".gz";
+        }
+
+        File f = _fs.open(path, "r");
+        if (!f)
+            return false;
+
+        if (_cache_header.length() != 0)
+            server.sendHeader("Cache-Control", _cache_header);
+
+        server.streamFile(f, contentType);
+        return true;
+    }
+
+    static String getContentType(const String& path) {
+        if (path.endsWith(".html")) return "text/html";
+        else if (path.endsWith(".htm")) return "text/html";
+        else if (path.endsWith(".css")) return "text/css";
+        else if (path.endsWith(".txt")) return "text/plain";
+        else if (path.endsWith(".js")) return "application/javascript";
+        else if (path.endsWith(".png")) return "image/png";
+        else if (path.endsWith(".gif")) return "image/gif";
+        else if (path.endsWith(".jpg")) return "image/jpeg";
+        else if (path.endsWith(".ico")) return "image/x-icon";
+        else if (path.endsWith(".svg")) return "image/svg+xml";
+        else if (path.endsWith(".ttf")) return "application/x-font-ttf";
+        else if (path.endsWith(".otf")) return "application/x-font-opentype";
+        else if (path.endsWith(".woff")) return "application/font-woff";
+        else if (path.endsWith(".woff2")) return "application/font-woff2";
+        else if (path.endsWith(".eot")) return "application/vnd.ms-fontobject";
+        else if (path.endsWith(".sfnt")) return "application/font-sfnt";
+        else if (path.endsWith(".xml")) return "text/xml";
+        else if (path.endsWith(".pdf")) return "application/pdf";
+        else if (path.endsWith(".zip")) return "application/zip";
+        else if(path.endsWith(".gz")) return "application/x-gzip";
+        else if (path.endsWith(".appcache")) return "text/cache-manifest";
+        return "application/octet-stream";
+    }
+
+protected:
+    FS _fs;
+    String _uri;
+    String _path;
+    String _cache_header;
+    bool _isFile;
+    size_t _baseUriLength;
+};
+
+
+#endif //REQUESTHANDLERSIMPL_H

+ 57 - 6
lib/MiLight/MiLightClient.cpp

@@ -1,6 +1,10 @@
 #include <MiLightClient.h>
 #include <MiLightRadioConfig.h>
 #include <Arduino.h>
+#include <RGBConverter.h>
+
+#define COLOR_TEMP_MAX_MIREDS 370
+#define COLOR_TEMP_MIN_MIREDS 153
 
 MiLightClient::MiLightClient(MiLightRadioFactory* radioFactory)
   : resendCount(MILIGHT_DEFAULT_RESEND_COUNT),
@@ -218,9 +222,16 @@ void MiLightClient::command(uint8_t command, uint8_t arg) {
 }
 
 void MiLightClient::update(const JsonObject& request) {
-  if (request.containsKey("status")) {
-    const String& statusStr = request.get<String>("status");
-    MiLightStatus status = (statusStr == "on" || statusStr == "true") ? ON : OFF;
+  if (request.containsKey("status") || request.containsKey("state")) {
+    String strStatus;
+
+    if (request.containsKey("status")) {
+      strStatus = request.get<char*>("status");
+    } else {
+      strStatus = request.get<char*>("state");
+    }
+
+    MiLightStatus status = (strStatus.equalsIgnoreCase("on") || strStatus.equalsIgnoreCase("true")) ? ON : OFF;
     this->updateStatus(status);
   }
 
@@ -277,17 +288,57 @@ void MiLightClient::update(const JsonObject& request) {
   if (request.containsKey("hue")) {
     this->updateHue(request["hue"]);
   }
+  if (request.containsKey("saturation")) {
+    this->updateSaturation(request["saturation"]);
+  }
+
+  // Convert RGB to HSV
+  if (request.containsKey("color")) {
+    JsonObject& color = request["color"];
+
+    uint8_t r = color["r"];
+    uint8_t g = color["g"];
+    uint8_t b = color["b"];
+
+    double hsv[3];
+    RGBConverter converter;
+    converter.rgbToHsv(r, g, b, hsv);
+
+    uint16_t hue = round(hsv[0]*360);
+    uint8_t saturation = round(hsv[1]*100);
+
+    this->updateHue(hue);
+    this->updateSaturation(saturation);
+  }
 
   if (request.containsKey("level")) {
     this->updateBrightness(request["level"]);
   }
+  // HomeAssistant
+  if (request.containsKey("brightness")) {
+    uint8_t scaledBrightness = round(request.get<uint8_t>("brightness") * (100/255.0));
+    this->updateBrightness(scaledBrightness);
+  }
 
   if (request.containsKey("temperature")) {
     this->updateTemperature(request["temperature"]);
   }
-
-  if (request.containsKey("saturation")) {
-    this->updateSaturation(request["saturation"]);
+  // HomeAssistant
+  if (request.containsKey("color_temp")) {
+    // MiLight CCT bulbs range from 2700K-6500K, or ~370.3-153.8 mireds. Note
+    // that mireds are inversely correlated with color temperature.
+    uint32_t tempMireds = request["color_temp"];
+    tempMireds = tempMireds > COLOR_TEMP_MAX_MIREDS ? COLOR_TEMP_MAX_MIREDS : tempMireds;
+    tempMireds = tempMireds < COLOR_TEMP_MIN_MIREDS ? COLOR_TEMP_MIN_MIREDS : tempMireds;
+
+    uint8_t scaledTemp = round(
+      100*
+      (tempMireds - COLOR_TEMP_MIN_MIREDS)
+        /
+      static_cast<double>(COLOR_TEMP_MAX_MIREDS - COLOR_TEMP_MIN_MIREDS)
+    );
+
+    this->updateTemperature(100 - scaledTemp);
   }
 
   if (request.containsKey("mode")) {

+ 1 - 0
platformio.ini

@@ -17,6 +17,7 @@ lib_deps_external =
   WiFiManager
   ArduinoJson
   PubSubClient
+  https://github.com/ratkins/RGBConverter
 build_flags = !python .get_version.py
 # -D MQTT_DEBUG
 # -D MILIGHT_UDP_DEBUG

+ 1 - 0
src/main.cpp

@@ -14,6 +14,7 @@
 #include <ESP8266mDNS.h>
 #include <ESP8266SSDP.h>
 #include <MqttClient.h>
+#include <RGBConverter.h>
 
 WiFiManager wifiManager;