diff --git a/examples/PsychicHttp/main.cpp b/examples/PsychicHttp/main.cpp new file mode 100644 index 0000000..4385498 --- /dev/null +++ b/examples/PsychicHttp/main.cpp @@ -0,0 +1,114 @@ +/* + ----------------------- + ElegantOTA - Async Demo Example + ----------------------- + + NOTE: Make sure you have enabled Async Mode in ElegantOTA before compiling this example! + Guide: https://docs.elegantota.pro/async-mode/ + + Skill Level: Beginner + + This example provides with a bare minimal app with ElegantOTA functionality which works + with AsyncWebServer. + + Github: https://github.com/ayushsharma82/ElegantOTA + WiKi: https://docs.elegantota.pro + + Works with both ESP8266 & ESP32 + + ------------------------------- + + Upgrade to ElegantOTA Pro: https://elegantota.pro + +*/ + +#if defined(ESP8266) + #include + #include +#elif defined(ESP32) + #include +#endif + +#include +#include + +PsychicWebSocketHandler websocketHandler; +PsychicEventSource eventSource; +PsychicHttpServer server; + + +const char* ssid = "........"; +const char* password = "........"; + +unsigned long ota_progress_millis = 0; + +void onOTAStart() { + // Log when OTA has started + Serial.println("OTA update started!"); + // +} + +void onOTAProgress(size_t current, size_t final) { + // Log every 1 second + if (millis() - ota_progress_millis > 1000) { + ota_progress_millis = millis(); + Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final); + } +} + +void onOTAEnd(bool success) { + // Log when OTA has finished + if (success) { + Serial.println("OTA update finished successfully!"); + } else { + Serial.println("There was an error during OTA update!"); + } + // +} + +void setup(void) { + Serial.begin(115200); + WiFi.mode(WIFI_STA); + 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.listen(80); // NOTE: for PsychicHttp you MUST call listen() before registering any urls using .on() + + // Set Authentication Credentials + ElegantOTA.setAuth("test", "test"); + + //setup server config stuff here + server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + + server.onOpen([](PsychicClient *client) { + Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + }); + + //example callback everytime a connection is closed + server.onClose([](PsychicClient *client) { + Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + }); + + ElegantOTA.begin(&server); // Start ElegantOTA + // ElegantOTA callbacks + ElegantOTA.onStart(onOTAStart); + ElegantOTA.onProgress(onOTAProgress); + ElegantOTA.onEnd(onOTAEnd); + + Serial.println("HTTP server started"); +} + +void loop(void) { + ElegantOTA.loop(); +} diff --git a/examples/PsychicHttp/platform.ini b/examples/PsychicHttp/platform.ini new file mode 100644 index 0000000..c37b35e --- /dev/null +++ b/examples/PsychicHttp/platform.ini @@ -0,0 +1,25 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32cam] +platform = espressif32 +board = esp32cam +framework = arduino +monitor_speed = 115200 +lib_ldf_mode = deep +; NOTE the below build flag activates the code for using PsychicHttp +build_flags = -DELEGANTOTA_USE_PSYCHIC=1 +lib_deps = + ; I found that the packaged up version was not sufficient / recent enough + https://github.com/hoeken/PsychicHttp.git + ; if you were pulling ElegantOTA from this temporay fork + ;https://github.com/HowardsPlayPen/ElegantOTA.git + ; To pull mainline from the main repository + https://github.com/ayushsharma82/ElegantOTA.git \ No newline at end of file diff --git a/src/ElegantOTA.cpp b/src/ElegantOTA.cpp index 4bf7bfa..13ad931 100644 --- a/src/ElegantOTA.cpp +++ b/src/ElegantOTA.cpp @@ -23,6 +23,26 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username, response->addHeader("Content-Encoding", "gzip"); request->send(response); }); + #elif defined(ELEGANTOTA_USE_PSYCHIC) + PsychicEndpoint* endpoint = _server->on("/update", HTTP_GET, [this](PsychicRequest *request){ + + PsychicResponse response(request); + response.setCode(200); + response.setContentType("text/html"); + + response.addHeader("Content-Encoding", "gzip"); + + //add our actual content + response.setContent(ELEGANT_HTML, sizeof(ELEGANT_HTML)); + + return response.send(); + + }); + if(endpoint && _authenticate ) + { + endpoint->setAuthentication(_username, _password); + } + #else _server->on("/update", HTTP_GET, [&](){ if (_authenticate && !_server->authenticate(_username, _password)) { @@ -101,6 +121,98 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username, return request->send((Update.hasError()) ? 400 : 200, "text/plain", (Update.hasError()) ? _update_error_str.c_str() : "OK"); }); + #elif defined(ELEGANTOTA_USE_PSYCHIC) + endpoint = _server->on("/ota/start", HTTP_GET, [this](PsychicRequest *request) { + + // Get header x-ota-mode value, if present + OTA_Mode mode = OTA_MODE_FIRMWARE; + // Get mode from arg + PsychicWebParameter * p = request->getParam("mode"); + if (request->hasParam("mode") && p) { + + String argValue = p->value(); + if (argValue == "fs") { + ELEGANTOTA_DEBUG_MSG("OTA Mode: Filesystem\n"); + mode = OTA_MODE_FILESYSTEM; + } else { + ELEGANTOTA_DEBUG_MSG("OTA Mode: Firmware\n"); + mode = OTA_MODE_FIRMWARE; + } + } + + // Get file MD5 hash from arg + p = request->getParam("hash"); + if (request->hasParam("") && p) { + String hash = p->value(); + ELEGANTOTA_DEBUG_MSG(String("MD5: "+hash+"\n").c_str()); + if (!Update.setMD5(hash.c_str())) { + ELEGANTOTA_DEBUG_MSG("ERROR: MD5 hash not valid\n"); + return request->reply(400, "text/plain", "MD5 parameter invalid"); + } + } + + #if UPDATE_DEBUG == 1 + // Serial output must be active to see the callback serial prints + Serial.setDebugOutput(true); + #endif + + // Pre-OTA update callback + if (preUpdateCallback != NULL) preUpdateCallback(); + + // Start update process + #if defined(ESP8266) + uint32_t update_size = mode == OTA_MODE_FILESYSTEM ? ((size_t)FS_end - (size_t)FS_start) : ((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); + if (mode == OTA_MODE_FILESYSTEM) { + close_all_fs(); + } + Update.runAsync(true); + if (!Update.begin(update_size, mode == OTA_MODE_FILESYSTEM ? U_FS : U_FLASH)) { + ELEGANTOTA_DEBUG_MSG("Failed to start update process\n"); + // Save error to string + StreamString str; + Update.printError(str); + _update_error_str = str.c_str(); + _update_error_str += "\n"; + ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str()); + } + #elif defined(ESP32) + if (!Update.begin(UPDATE_SIZE_UNKNOWN, mode == OTA_MODE_FILESYSTEM ? U_SPIFFS : U_FLASH)) { + ELEGANTOTA_DEBUG_MSG("Failed to start update process\n"); + // Save error to string + StreamString str; + Update.printError(str); + _update_error_str = str.c_str(); + _update_error_str += "\n"; + ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str()); + } + #elif defined(TARGET_RP2040) + uint32_t update_size = 0; + // Gather FS Size + if (mode == OTA_MODE_FILESYSTEM) { + update_size = ((size_t)&_FS_end - (size_t)&_FS_start); + LittleFS.end(); + } else { + FSInfo64 i; + LittleFS.begin(); + LittleFS.info64(i); + update_size = i.totalBytes - i.usedBytes; + } + // Start update process + if (!Update.begin(update_size, mode == OTA_MODE_FILESYSTEM ? U_FS : U_FLASH)) { + ELEGANTOTA_DEBUG_MSG("Failed to start update process because there is not enough space\n"); + _update_error_str = "Not enough space"; + return _server->send(400, "text/plain", _update_error_str.c_str()); + } + #endif + + return request->reply((Update.hasError()) ? 400 : 200, "text/plain", (Update.hasError()) ? _update_error_str.c_str() : "OK"); + }); + + if(endpoint && _authenticate ) + { + endpoint->setAuthentication(_username, _password); + } + #else _server->on("/ota/start", HTTP_GET, [&]() { if (_authenticate && !_server->authenticate(_username, _password)) { @@ -243,6 +355,76 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username, return; } }); + #elif defined(ELEGANTOTA_USE_PSYCHIC) + + PsychicUploadHandler *uploadHandler = new PsychicUploadHandler(); + uploadHandler->onUpload([this](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + + //Upload handler chunks in data + if (!index) { + // Reset progress size on first frame + _current_progress_size = 0; + } + + // Write chunked data to the free sketch space + if(len){ + if (Update.write(data, len) != len) { + return request->reply(400, "text/plain", "Failed to write chunked data to free space"); + } + _current_progress_size += len; + // Progress update callback + if (progressUpdateCallback != NULL) progressUpdateCallback(_current_progress_size, request->contentLength()); + } + + if (last) { // if the final flag is set then this is the last frame of data + if (!Update.end(true)) { //true to set the size to the current progress + // Save error to string + StreamString str; + Update.printError(str); + _update_error_str = str.c_str(); + _update_error_str += "\n"; + ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str()); + } + }else{ + return ESP_OK; // TODO is this the right return code here? + } + + + return ESP_OK; + }); + + //gets called after upload has been handled + uploadHandler->onRequest([this](PsychicRequest *request) + { + // Post-OTA update callback + if (postUpdateCallback != NULL) postUpdateCallback(!Update.hasError()); + + PsychicResponse response(request); + + response.setContentType("text/plain"); + response.addHeader("Connection", "close"); + response.addHeader("Access-Control-Allow-Origin", "*"); + + bool hasError = (Update.hasError()); + response.setCode(hasError ? 400 : 200); + response.setContent(hasError ? _update_error_str.c_str() : "OK"); + + esp_err_t err = response.send(); + + // Set reboot flag + if (!Update.hasError()) { + if (_auto_reboot) { + _reboot_request_millis = millis(); + _reboot = true; + } + } + return err; + }); + + uploadHandler->setAuthentication(_username, _password); + + _server->on("/ota/upload", HTTP_POST, uploadHandler); + #else _server->on("/ota/upload", HTTP_POST, [&](){ if (_authenticate && !_server->authenticate(_username, _password)) { diff --git a/src/ElegantOTA.h b/src/ElegantOTA.h index eb961cb..1e6981d 100644 --- a/src/ElegantOTA.h +++ b/src/ElegantOTA.h @@ -67,6 +67,9 @@ _____ _ _ ___ _____ _ #include "AsyncTCP.h" #include "ESPAsyncWebServer.h" #define ELEGANTOTA_WEBSERVER AsyncWebServer + #elif ELEGANTOTA_USE_PSYCHIC == 1 + #include + #define ELEGANTOTA_WEBSERVER PsychicHttpServer #else #include "WiFi.h" #include "WiFiClient.h"