diff --git a/doc/ota_updates/ota_updates.md b/doc/ota_updates/ota_updates.md index b7963a114..8af5be15a 100644 --- a/doc/ota_updates/ota_updates.md +++ b/doc/ota_updates/ota_updates.md @@ -340,6 +340,7 @@ function sendFile($path) { header('Content-Type: application/octet-stream', true); header('Content-Disposition: attachment; filename='.basename($path)); header('Content-Length: '.filesize($path), true); + header('x-MD5: '.md5_file($path), true); readfile($path); } diff --git a/libraries/ESP8266httpClient/src/ESP8266httpClient.cpp b/libraries/ESP8266httpClient/src/ESP8266httpClient.cpp index 4b29695b7..af2e235a5 100644 --- a/libraries/ESP8266httpClient/src/ESP8266httpClient.cpp +++ b/libraries/ESP8266httpClient/src/ESP8266httpClient.cpp @@ -41,6 +41,8 @@ httpClient::httpClient() { _reuse = false; _https = false; + _userAgent = "ESP8266httpClient"; + _headerKeysCount = 0; _currentHeaders = NULL; @@ -77,7 +79,7 @@ httpClient::~httpClient() { * @param httpsFingerprint const char * */ void httpClient::begin(const char *url, const char * httpsFingerprint) { - begin(String(url), String(httpsFingerprint)); + begin(String(url), String(httpsFingerprint)); } /** @@ -111,7 +113,7 @@ void httpClient::begin(String url, String httpsFingerprint) { _host = url.substring(0, index); // hostname url.remove(0, (index + 1)); // remove hostname + : - index = url.indexOf('/'); + index = url.indexOf('/'); _port = url.substring(0, index).toInt(); // get port url.remove(0, index); // remove port hasPort = true; @@ -200,7 +202,6 @@ bool httpClient::connected() { return false; } - /** * try to reuse the connection to the server * keep-alive @@ -210,6 +211,14 @@ void httpClient::setReuse(bool reuse) { _reuse = reuse; } +/** + * set User Agent + * @param userAgent const char * + */ +void httpClient::setUserAgent(const char * userAgent) { + _userAgent = userAgent; +} + /** * send a GET request * @return http code @@ -240,7 +249,7 @@ int httpClient::POST(String payload) { * @return -1 if no info or > 0 when Content-Length is set by server */ int httpClient::sendRequest(const char * type, uint8_t * payload, size_t size) { - // connect ro server + // connect to server if(!connect()) { return HTTPC_ERROR_CONNECTION_REFUSED; } @@ -265,6 +274,77 @@ int httpClient::sendRequest(const char * type, uint8_t * payload, size_t size) { return handleHeaderResponse(); } +/** + * sendRequest + * @param type const char * "GET", "POST", .... + * @param stream Stream * data stream for the message body + * @param size size_t size for the message body if 0 not Content-Length is send + * @return -1 if no info or > 0 when Content-Length is set by server + */ +int httpClient::sendRequest(const char * type, Stream * stream, size_t size) { + + if(!stream) { + return HTTPC_ERROR_NO_STREAM; + } + + // connect to server + if(!connect()) { + return HTTPC_ERROR_CONNECTION_REFUSED; + } + + if(size > 0) { + addHeader("Content-Length", String(size)); + } + + // send Header + if(!sendHeader(type)) { + return HTTPC_ERROR_SEND_HEADER_FAILED; + } + + // create buffer for read + uint8_t buff[1460] = { 0 }; + + int len = size; + int bytesWritten = 0; + + if(len == 0) { + len = -1; + } + + // read all data from stream and send it to server + while(connected() && stream->available() && (len > 0 || len == -1)) { + + // get available data size + size_t s = stream->available(); + + if(s) { + int c = stream->readBytes(buff, ((s > sizeof(buff)) ? sizeof(buff) : s)); + + // write it to Stream + bytesWritten += _tcp->write((const uint8_t *)buff, c); + + if(len > 0) { + len -= c; + } + + delay(0); + } else { + delay(1); + } + } + + if(size && (int)size != bytesWritten) { + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] Stream payload bytesWritten %d and size %d mismatch!.\n", bytesWritten, _size); + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] ERROR SEND PAYLOAD FAILED!"); + return HTTPC_ERROR_SEND_PAYLOAD_FAILED; + } else { + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] Stream payload written: %d\n", bytesWritten); + } + + // handle Server Response (Header) + return handleHeaderResponse(); +} + /** * size of message body / payload * @return -1 if no info or > 0 when Content-Length is set by server @@ -374,7 +454,6 @@ String httpClient::getString(void) { return sstring; } - /** * adds Header to the request * @param name @@ -383,16 +462,20 @@ String httpClient::getString(void) { */ void httpClient::addHeader(const String& name, const String& value, bool first) { - String headerLine = name; - headerLine += ": "; - headerLine += value; - headerLine += "\r\n"; + // not allow set of Header handled by code + if(!name.equalsIgnoreCase("Connection") && !name.equalsIgnoreCase("User-Agent") && !name.equalsIgnoreCase("Host")) { + String headerLine = name; + headerLine += ": "; + headerLine += value; + headerLine += "\r\n"; - if(first) { - _Headers = headerLine + _Headers; - } else { - _Headers += headerLine; + if(first) { + _Headers = headerLine + _Headers; + } else { + _Headers += headerLine; + } } + } void httpClient::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { @@ -448,7 +531,6 @@ bool httpClient::connect(void) { return true; } - if(_https) { DEBUG_HTTPCLIENT("[HTTP-Client] connect https...\n"); if(_tcps) { @@ -502,9 +584,10 @@ bool httpClient::sendHeader(const char * type) { if(!connected()) { return false; } + String header = String(type) + " " + _url + " HTTP/1.1\r\n" "Host: " + _host + "\r\n" - "User-Agent: ESP8266httpClient\r\n" + "User-Agent: " + _userAgent + "\r\n" "Connection: "; if(_reuse) { diff --git a/libraries/ESP8266httpClient/src/ESP8266httpClient.h b/libraries/ESP8266httpClient/src/ESP8266httpClient.h index 3b21c18ea..7fad644e4 100644 --- a/libraries/ESP8266httpClient/src/ESP8266httpClient.h +++ b/libraries/ESP8266httpClient/src/ESP8266httpClient.h @@ -59,12 +59,14 @@ class httpClient { bool connected(void); void setReuse(bool reuse); /// keep-alive + void setUserAgent(const char * userAgent); /// request handling int GET(); int POST(uint8_t * payload, size_t size); int POST(String payload); int sendRequest(const char * type, uint8_t * payload = NULL, size_t size = 0); + int sendRequest(const char * type, Stream * stream, size_t size = 0); void addHeader(const String& name, const String& value, bool first = false); @@ -105,7 +107,8 @@ class httpClient { bool _https; String _httpsFingerprint; - String _Headers; + String _Headers; + String _userAgent; /// Response handling RequestArgument* _currentHeaders; diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdate/httpUpdate.ino b/libraries/ESP8266httpUpdate/examples/httpUpdate/httpUpdate.ino new file mode 100644 index 000000000..99e99c86b --- /dev/null +++ b/libraries/ESP8266httpUpdate/examples/httpUpdate/httpUpdate.ino @@ -0,0 +1,61 @@ +/** + * httpUpdate.ino + * + * Created on: 27.11.2015 + * + */ + +#include + +#include +#include + +#include +#include + +#define USE_SERIAL Serial + +ESP8266WiFiMulti WiFiMulti; + +void setup() { + + USE_SERIAL.begin(115200); + // USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "PASSWORD"); + +} + +void loop() { + // wait for WiFi connection + if((WiFiMulti.run() == WL_CONNECTED)) { + + t_httpUpdate_return ret = ESPhttpUpdate.update("http://server/file.bin"); + //t_httpUpdate_return ret = ESPhttpUpdate.update("https://server/file.bin"); + + switch(ret) { + case HTTP_UPDATE_FAILED: + USE_SERIAL.println("HTTP_UPDATE_FAILD"); + break; + + case HTTP_UPDATE_NO_UPDATES: + USE_SERIAL.println("HTTP_UPDATE_NO_UPDATES"); + break; + + case HTTP_UPDATE_OK: + USE_SERIAL.println("HTTP_UPDATE_OK"); + break; + } + } +} + diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp index 33907465c..fb0572417 100644 --- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp +++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp @@ -23,103 +23,96 @@ * */ + #include "ESP8266httpUpdate.h" -ESP8266HTTPUpdate::ESP8266HTTPUpdate(void) { +ESP8266HTTPUpdate::ESP8266HTTPUpdate(void) { } ESP8266HTTPUpdate::~ESP8266HTTPUpdate(void) { - } -t_httpUpdate_return ESP8266HTTPUpdate::update(const char * host, uint16_t port, const char * url, const char * current_version) { - WiFiClient tcp; - DEBUG_HTTP_UPDATE("[httpUpdate] connected to %s:%u %s .... ", host, port, url); - - if(!tcp.connect(host, port)) { - DEBUG_HTTP_UPDATE("failed.\n"); - return HTTP_UPDATE_FAILED; - } - DEBUG_HTTP_UPDATE("ok.\n"); - return update(tcp, host, url, current_version); +/** + * + * @param url const char * + * @param current_version const char * + * @param httpsFingerprint const char * + * @return t_httpUpdate_return + */ +t_httpUpdate_return ESP8266HTTPUpdate::update(const char * url, const char * current_version, const char * httpsFingerprint) { + httpClient http; + http.begin(url, httpsFingerprint); + return handleUpdate(&http, current_version); } -t_httpUpdate_return ESP8266HTTPUpdate::update(WiFiClient& tcp, const char* host, const char* url, const char * current_version) { +/** + * + * @param host const char * + * @param port uint16_t + * @param url const char * + * @param current_version const char * + * @param httpsFingerprint const char * + * @return + */ +t_httpUpdate_return ESP8266HTTPUpdate::update(const char * host, uint16_t port, const char * url, const char * current_version, bool https, const char * httpsFingerprint) { + httpClient http; + http.begin(host, port, url, https, httpsFingerprint); + return handleUpdate(&http, current_version); +} + +t_httpUpdate_return ESP8266HTTPUpdate::update(String host, uint16_t port, String url, String current_version, bool https, String httpsFingerprint) { + httpClient http; + http.begin(host, port, url, https, httpsFingerprint); + return handleUpdate(&http, current_version.c_str()); +} + +/** + * + * @param http httpClient * + * @param current_version const char * + * @return t_httpUpdate_return + */ +t_httpUpdate_return ESP8266HTTPUpdate::handleUpdate(httpClient * http, const char * current_version) { + t_httpUpdate_return ret = HTTP_UPDATE_FAILED; - // set Timeout for readBytesUntil and readStringUntil - tcp.setTimeout(2000); - tcp.setNoDelay(true); - String req = "GET "; + http->setUserAgent("ESP8266-http-Update"); + http->addHeader("x-ESP8266-STA-MAC", WiFi.macAddress()); + http->addHeader("x-ESP8266-AP-MAC", WiFi.softAPmacAddress()); + http->addHeader("x-ESP8266-free-space", String(ESP.getFreeSketchSpace())); + http->addHeader("x-ESP8266-sketch-size", String(ESP.getSketchSize())); + http->addHeader("x-ESP8266-chip-size", String(ESP.getFlashChipRealSize())); + http->addHeader("x-ESP8266-sdk-version", ESP.getSdkVersion()); - req += url; - req += " HTTP/1.1\r\n" - "Host: "; - req += host; - req += "\r\n" - "Connection: close\r\n" - "User-Agent: ESP8266-http-Update\r\n" - "x-ESP8266-STA-MAC: "; - req += WiFi.macAddress(); - req += "\r\n" - "x-ESP8266-AP-MAC: "; - req += WiFi.softAPmacAddress(); - req += "\r\n" - "x-ESP8266-free-space: "; - req += ESP.getFreeSketchSpace(); - req += "\r\n" - "x-ESP8266-sketch-size: "; - req += ESP.getSketchSize(); - req += "\r\n" - "x-ESP8266-chip-size: "; - req += ESP.getFlashChipRealSize(); - req += "\r\n" - "x-ESP8266-sdk-version: "; - req += ESP.getSdkVersion(); - - if(current_version[0] != 0x00) { - req += "\r\n" - "x-ESP8266-version: "; - req += current_version; + if(current_version && current_version[0] != 0x00) { + http->addHeader("x-ESP8266-version", current_version); } - req += "\r\n" - "\r\n"; + const char * headerkeys[] = { "x-MD5" }; + size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); - tcp.write(req.c_str(), req.length()); + // track these headers + http->collectHeaders(headerkeys, headerkeyssize); - uint32_t code = 0; - size_t len = 0; - while(true) { - String headerLine = tcp.readStringUntil('\n'); - headerLine.trim(); // remove \r - - if(headerLine.length() > 0) { - DEBUG_HTTP_UPDATE("[httpUpdate][Header] RX: %s\n", headerLine.c_str()); - if(headerLine.startsWith("HTTP/1.")) { - // 9 = lenght of "HTTP/1.x " - code = headerLine.substring(9, headerLine.indexOf(' ', 9)).toInt(); - } else if(headerLine.startsWith("Content-Length: ")) { - // 16 = lenght of "Content-Length: " - len = headerLine.substring(16).toInt(); - } - } else { - break; - } - } + int code = http->GET(); + int len = http->getSize(); DEBUG_HTTP_UPDATE("[httpUpdate] Header read fin.\n"); DEBUG_HTTP_UPDATE("[httpUpdate] Server header:\n"); DEBUG_HTTP_UPDATE("[httpUpdate] - code: %d\n", code); DEBUG_HTTP_UPDATE("[httpUpdate] - len: %d\n", len); + if(http->hasHeader("x-MD5")) { + DEBUG_HTTP_UPDATE("[httpUpdate] - MD5: %s\n", http->header("x-MD5").c_str()); + } + DEBUG_HTTP_UPDATE("[httpUpdate] ESP8266 info:\n"); DEBUG_HTTP_UPDATE("[httpUpdate] - free Space: %d\n", ESP.getFreeSketchSpace()); DEBUG_HTTP_UPDATE("[httpUpdate] - current Sketch Size: %d\n", ESP.getSketchSize()); - if(current_version[0] != 0x00) { + if(current_version && current_version[0] != 0x00) { DEBUG_HTTP_UPDATE("[httpUpdate] - current version: %s\n", current_version); } @@ -131,15 +124,17 @@ t_httpUpdate_return ESP8266HTTPUpdate::update(WiFiClient& tcp, const char* host, DEBUG_HTTP_UPDATE("[httpUpdate] FreeSketchSpace to low (%d) needed: %d\n", ESP.getFreeSketchSpace(), len); } else { + WiFiClient * tcp = http->getStreamPtr(); + WiFiUDP::stopAll(); - WiFiClient::stopAllExcept(&tcp); + WiFiClient::stopAllExcept(tcp); delay(100); - if(ESP.updateSketch(tcp, len, false, false)) { + if(runUpdate(*tcp, len, http->header("x-MD5"))) { ret = HTTP_UPDATE_OK; DEBUG_HTTP_UPDATE("[httpUpdate] Update ok\n"); - tcp.stop(); + http->end(); ESP.restart(); } else { ret = HTTP_UPDATE_FAILED; @@ -148,7 +143,7 @@ t_httpUpdate_return ESP8266HTTPUpdate::update(WiFiClient& tcp, const char* host, } } else { ret = HTTP_UPDATE_FAILED; - DEBUG_HTTP_UPDATE("[httpUpdate] Content-Length is 0?!\n"); + DEBUG_HTTP_UPDATE("[httpUpdate] Content-Length is 0 or not set by Server?!\n"); } break; case 304: @@ -164,11 +159,42 @@ t_httpUpdate_return ESP8266HTTPUpdate::update(WiFiClient& tcp, const char* host, break; } + http->end(); + return ret; } -t_httpUpdate_return ESP8266HTTPUpdate::update(String host, uint16_t port, String url, String current_version) { - return update(host.c_str(), port, url.c_str(), current_version.c_str()); +/** + * write Update to flash + * @param in Stream& + * @param size uint32_t + * @param md5 String + * @return true if Update ok + */ +bool ESP8266HTTPUpdate::runUpdate(Stream& in, uint32_t size, String md5) { + + if(!Update.begin(size)) { + DEBUG_HTTP_UPDATE("[httpUpdate] Update.begin failed!\n"); + return false; + } + + if(md5.length()) { + Update.setMD5(md5.c_str()); + } + + if(Update.writeStream(in) != size) { + DEBUG_HTTP_UPDATE("[httpUpdate] Update.writeStream failed!\n"); + return false; + } + + if(!Update.end()) { + DEBUG_HTTP_UPDATE("[httpUpdate] Update.end failed!\n"); + return false; + } + + return true; } + + ESP8266HTTPUpdate ESPhttpUpdate; diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h index 3f72bc98e..406d8de3f 100644 --- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h +++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h @@ -28,8 +28,9 @@ #include #include -#include #include +#include +#include //#define DEBUG_HTTP_UPDATE(...) Serial1.printf( __VA_ARGS__ ) @@ -48,9 +49,13 @@ class ESP8266HTTPUpdate { ESP8266HTTPUpdate(void); ~ESP8266HTTPUpdate(void); - t_httpUpdate_return update(const char * host, uint16_t port, const char * url = "/", const char * current_version = ""); - t_httpUpdate_return update(String host, uint16_t port, String url = "/", String current_version = ""); - t_httpUpdate_return update(WiFiClient& client, const char* host, const char* url = "/", const char * current_version = ""); + t_httpUpdate_return update(const char * url, const char * current_version = "", const char * httpsFingerprint = ""); + t_httpUpdate_return update(const char * host, uint16_t port, const char * url = "/", const char * current_version = "", bool https = false, const char * httpsFingerprint = ""); + t_httpUpdate_return update(String host, uint16_t port, String url = "/", String current_version = "", bool https = false, String httpsFingerprint = ""); + + protected: + t_httpUpdate_return handleUpdate(httpClient * http, const char * current_version); + bool runUpdate(Stream& in, uint32_t size, String md5); }; extern ESP8266HTTPUpdate ESPhttpUpdate;