diff --git a/platformio.ini b/platformio.ini index 033b016..edb2dc8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -40,7 +40,7 @@ lib_deps = bbx10/DNSServer@^1.1.0 [esp32] -platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4.1/platform-espressif32-2.0.4.1.zip +platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4/platform-espressif32-2.0.4.zip platform_packages = framework = ${common.framework} build_unflags = ${common.build_unflags} @@ -54,7 +54,7 @@ build_flags = -g lib_deps = ${common.lib_deps} [esp32c3] -platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4.1/platform-espressif32-2.0.4.1.zip +platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4/platform-espressif32-2.0.4.zip platform_packages = framework = ${common.framework} build_unflags = ${common.build_unflags} diff --git a/src/HttpReleaseUpdate.cpp b/src/HttpReleaseUpdate.cpp new file mode 100644 index 0000000..c58e4a4 --- /dev/null +++ b/src/HttpReleaseUpdate.cpp @@ -0,0 +1,221 @@ +#include "HttpReleaseUpdate.h" + +#include +#include +#include + +bool checkVersion(WiFiClientSecure& client, const String& url, const String& version) { + HTTPClient http; + if (!http.begin(client, url)) + return false; + + if (version.length() > 0) { + // http.addHeader("If-None-Match", version); + + int httpCode = http.sendRequest("HEAD"); + if (httpCode < 300 || httpCode > 400 || http.getLocation().indexOf(version) > 0) { + Serial.printf("Not updating from (sc=%d): %s\n", httpCode, http.getLocation().c_str()); + http.end(); + return false; + } else { + Serial.printf("Updating from (sc=%d): %s\n", httpCode, http.getLocation().c_str()); + http.end(); + } + } + return true; +} + +HttpUpdateResult HttpReleaseUpdate::update(WiFiClientSecure& client, const String& url, const String& version) { + if (!checkVersion(client, url, version)) + return HTTP_UPDATE_NO_UPDATES; + + HTTPClient http; + http.useHTTP10(true); + http.setTimeout(_httpClientTimeout); + http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); + if (!http.begin(client, url)) + return HTTP_UPDATE_FAILED; + return handleUpdate(http); +} + +/** + * return error code as int + * @return int error code + */ +int HttpReleaseUpdate::getLastError(void) { + return _lastError; +} + +/** + * return error code as String + * @return String error + */ +String HttpReleaseUpdate::getLastErrorString(void) { + if (_lastError == 0) { + return String(); // no error + } + + // error from Update class + if (_lastError > 0) { + StreamString error; + Update.printError(error); + error.trim(); // remove line ending + return String("Update error: ") + error; + } + + // error from http client + if (_lastError > -100) { + return String("HTTP error: ") + HTTPClient::errorToString(_lastError); + } + + switch (_lastError) { + case HTTP_UE_TOO_LESS_SPACE: + return "Not Enough space"; + case HTTP_UE_SERVER_NOT_REPORT_SIZE: + return "Server Did Not Report Size"; + case HTTP_UE_SERVER_FILE_NOT_FOUND: + return "File Not Found (404)"; + case HTTP_UE_SERVER_FORBIDDEN: + return "Forbidden (403)"; + case HTTP_UE_SERVER_WRONG_HTTP_CODE: + return "Wrong HTTP Code"; + case HTTP_UE_SERVER_FAULTY_MD5: + return "Wrong MD5"; + case HTTP_UE_BIN_VERIFY_HEADER_FAILED: + return "Verify Bin Header Failed"; + case HTTP_UE_BIN_FOR_WRONG_FLASH: + return "New Binary Does Not Fit Flash Size"; + case HTTP_UE_NO_PARTITION: + return "Partition Could Not be Found"; + } + + return String(); +} + +HttpUpdateResult HttpReleaseUpdate::handleUpdate(HTTPClient& http) { + HttpUpdateResult ret = HTTP_UPDATE_FAILED; +#ifdef VERSION + http.setUserAgent("ESPresense/" VERSION); +#else + http.setUserAgent("ESPresense/0.0"); +#endif + + int code = http.GET(); + if (code <= 0) { + Serial.printf("HTTP error: %s\n", http.errorToString(code).c_str()); + _lastError = code; + goto exit; + } + + switch (code) { + case HTTP_CODE_OK: { + int len = http.getSize(); + if (len > 0) { + int sketchFreeSpace = ESP.getFreeSketchSpace(); + if (!sketchFreeSpace) { + _lastError = HTTP_UE_NO_PARTITION; + goto exit; + } + + if (len > sketchFreeSpace) { + Serial.printf("FreeSketchSpace too low (%d) needed: %d\n", sketchFreeSpace, len); + _lastError = HTTP_UE_TOO_LESS_SPACE; + goto exit; + } + + WiFiClient* tcp = http.getStreamPtr(); + if(tcp->peek() != 0xE9) { + Serial.printf("Magic header does not start with 0xE9\n"); + _lastError = HTTP_UE_BIN_VERIFY_HEADER_FAILED; + goto exit; + } + + if (_cbStart) { + _cbStart(); + } + + if (runUpdate(*tcp, len)) { + ret = HTTP_UPDATE_OK; + if (_rebootOnUpdate) { + ESP.restart(); + } + + if (_cbEnd) { + _cbEnd(); + } + } else { + ret = HTTP_UPDATE_FAILED; + + if (_cbEnd) { + _cbEnd(); + } + } + } else { + _lastError = HTTP_UE_SERVER_NOT_REPORT_SIZE; + Serial.printf("Content-Length was 0 or wasn't set by Server?!\n"); + goto exit; + } + } break; + case HTTP_CODE_NOT_MODIFIED: + ret = HTTP_UPDATE_NO_UPDATES; + break; + case HTTP_CODE_NOT_FOUND: + _lastError = HTTP_UE_SERVER_FILE_NOT_FOUND; + ret = HTTP_UPDATE_FAILED; + break; + case HTTP_CODE_FORBIDDEN: + _lastError = HTTP_UE_SERVER_FORBIDDEN; + ret = HTTP_UPDATE_FAILED; + break; + default: + _lastError = HTTP_UE_SERVER_WRONG_HTTP_CODE; + Serial.printf("HTTP Code is (%d)\n", code); + break; + } + +exit: + http.end(); + return ret; +} + +bool HttpReleaseUpdate::runUpdate(Stream& in, uint32_t size) { + StreamString error; + + if (_cbProgress) { + Update.onProgress(_cbProgress); + } + + if (!Update.begin(size, U_FLASH, _ledPin, _ledOn)) { + _lastError = Update.getError(); + Update.printError(error); + error.trim(); // remove line ending + Serial.printf("Update.begin failed! (%s)\n", error.c_str()); + return false; + } + + if (_cbProgress) { + _cbProgress(0, size); + } + + if (Update.writeStream(in) != size) { + _lastError = Update.getError(); + Update.printError(error); + error.trim(); // remove line ending + Serial.printf("Update.writeStream failed! (%s)\n", error.c_str()); + return false; + } + + if (_cbProgress) { + _cbProgress(size, size); + } + + if (!Update.end()) { + _lastError = Update.getError(); + Update.printError(error); + error.trim(); // remove line ending + Serial.printf("Update.end failed! (%s)\n", error.c_str()); + return false; + } + + return true; +} diff --git a/src/HttpReleaseUpdate.h b/src/HttpReleaseUpdate.h new file mode 100644 index 0000000..93fd7b0 --- /dev/null +++ b/src/HttpReleaseUpdate.h @@ -0,0 +1,83 @@ +#ifndef ___HTTP_RELEASE_UPDATE_H___ +#define ___HTTP_RELEASE_UPDATE_H___ + +#include +#include +#include +#include +#include +#include + +#define HTTP_UE_TOO_LESS_SPACE (-100) +#define HTTP_UE_SERVER_NOT_REPORT_SIZE (-101) +#define HTTP_UE_SERVER_FILE_NOT_FOUND (-102) +#define HTTP_UE_SERVER_FORBIDDEN (-103) +#define HTTP_UE_SERVER_WRONG_HTTP_CODE (-104) +#define HTTP_UE_SERVER_FAULTY_MD5 (-105) +#define HTTP_UE_BIN_VERIFY_HEADER_FAILED (-106) +#define HTTP_UE_BIN_FOR_WRONG_FLASH (-107) +#define HTTP_UE_NO_PARTITION (-108) + +enum HttpUpdateResult { + HTTP_UPDATE_FAILED, + HTTP_UPDATE_NO_UPDATES, + HTTP_UPDATE_OK +}; + +using HttpUpdateStartCB = std::function; +using HttpUpdateEndCB = std::function; +using HttpUpdateErrorCB = std::function; +using HttpUpdateProgressCB = std::function; + +class HttpReleaseUpdate { + public: + void setTimeout(int httpClientTimeout) { + _httpClientTimeout = httpClientTimeout; + } + + void rebootOnUpdate(bool reboot) { + _rebootOnUpdate = reboot; + } + + void setLedPin(int ledPin = -1, uint8_t ledOn = HIGH) { + _ledPin = ledPin; + _ledOn = ledOn; + } + + HttpUpdateResult update(WiFiClientSecure& client, const String& url, const String& version); + + void onStart(HttpUpdateStartCB cbOnStart) { _cbStart = cbOnStart; } + void onEnd(HttpUpdateEndCB cbOnEnd) { _cbEnd = cbOnEnd; } + void onError(HttpUpdateErrorCB cbOnError) { _cbError = cbOnError; } + void onProgress(HttpUpdateProgressCB cbOnProgress) { _cbProgress = cbOnProgress; } + + int getLastError(void); + String getLastErrorString(void); + + protected: + HttpUpdateResult handleUpdate(HTTPClient& http); + bool runUpdate(Stream& in, uint32_t size); + + void _setLastError(int err) { + _lastError = err; + if (_cbError) { + _cbError(err); + } + } + int _lastError; + bool _rebootOnUpdate = true; + + private: + int _httpClientTimeout = 8000; + + // Callbacks + HttpUpdateStartCB _cbStart; + HttpUpdateEndCB _cbEnd; + HttpUpdateErrorCB _cbError; + HttpUpdateProgressCB _cbProgress; + + int _ledPin; + uint8_t _ledOn; +}; + +#endif diff --git a/src/main.h b/src/main.h index 30c014e..9bfecd8 100644 --- a/src/main.h +++ b/src/main.h @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -25,6 +24,7 @@ #include "Network.h" #include "Enrollment.h" #include "HttpServer.h" +#include "HttpReleaseUpdate.h" #include "MotionSensors.h" #include "I2C.h" @@ -57,7 +57,7 @@ bool autoUpdate, arduinoOta, prerelease; bool discovery, activeScan, publishTele, publishRooms, publishDevices; bool updateInProgress() { - return updateStartedMillis > 0 && millis() - updateStartedMillis < 60000; + return updateStartedMillis > 0 && millis() - updateStartedMillis < 90000; } String resetReason(RESET_REASON reason) @@ -165,56 +165,49 @@ void configureOTA() ArduinoOTA.begin(); } -void firmwareUpdate() -{ +void firmwareUpdate() { #ifdef FIRMWARE if (!autoUpdate) return; static unsigned long lastFirmwareCheck = 0; + static unsigned short autoUpdateAttempts = 0; unsigned long uptime = getUptimeSeconds(); if (uptime - lastFirmwareCheck < CHECK_FOR_UPDATES_INTERVAL) return; lastFirmwareCheck = uptime; - HTTPClient http; - WiFiClientSecure client; - client.setInsecure(); - String firmwareUrl = prerelease ? "https://espresense.com/releases/latest-any/download/" FIRMWARE ".bin" : "https://github.com/ESPresense/ESPresense/releases/latest/download/" FIRMWARE ".bin"; - if (!http.begin(client, firmwareUrl)) - return; -#ifdef VERSION - int httpCode = http.sendRequest("HEAD"); - if (httpCode < 300 || httpCode > 400 || http.getLocation().indexOf(String(VERSION)) > 0) - { - Serial.printf("Not updating from (sc=%d): %s\n", httpCode, http.getLocation().c_str()); - http.end(); - return; - } - else - { - Serial.printf("Updating from (sc=%d): %s\n", httpCode, http.getLocation().c_str()); - } -#endif - - updateStartedMillis = millis(); - mqttClient.disconnect(); - NimBLEDevice::getScan()->stop(); - SPIFFS.end(); - HttpServer::UpdateStart(); - GUI::updateStart(); - httpUpdate.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); - httpUpdate.onProgress([](int progress, int total) - { - GUI::updateProgress((progress / (total / 100))); - }); - t_httpUpdate_return ret = httpUpdate.update(client, firmwareUrl); - GUI::updateEnd(); - HttpServer::UpdateEnd(); + WiFiClientSecure client; + client.setTimeout(12); + client.setInsecure(); + HttpReleaseUpdate httpUpdate; + httpUpdate.setTimeout(12000); + httpUpdate.onStart([](void) { + autoUpdateAttempts++; + updateStartedMillis = millis(); + mqttClient.disconnect(); + NimBLEDevice::getScan()->stop(); + HttpServer::UpdateStart(); + GUI::updateStart(); + }); + httpUpdate.onEnd([](void) { + if (autoUpdateAttempts > 3) ESP.restart(); + updateStartedMillis = 0; + GUI::updateEnd(); + HttpServer::UpdateEnd(); + }); + httpUpdate.onProgress([](int progress, int total) { + GUI::updateProgress((progress / (total / 100))); + }); + #ifdef VERSION + auto ret = httpUpdate.update(client, firmwareUrl, String(VERSION)); + #else + auto ret = httpUpdate.update(client, firmwareUrl, ""); + #endif switch (ret) { case HTTP_UPDATE_FAILED: @@ -225,9 +218,6 @@ void firmwareUpdate() Serial.printf("No Update!\n"); break; } - - SPIFFS.begin(true); - updateStartedMillis = 0; #endif }