Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to allow optional integration of PsychicHttp #159

Merged
merged 1 commit into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions examples/PsychicHttp/main.cpp
Original file line number Diff line number Diff line change
@@ -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 <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif

#include <ElegantOTA.h>
#include <PsychicHttp.h>

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!");
// <Add your own code here>
}

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!");
}
// <Add your own code here>
}

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();
}
25 changes: 25 additions & 0 deletions examples/PsychicHttp/platform.ini
Original file line number Diff line number Diff line change
@@ -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
182 changes: 182 additions & 0 deletions src/ElegantOTA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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)) {
Expand Down
3 changes: 3 additions & 0 deletions src/ElegantOTA.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ _____ _ _ ___ _____ _
#include "AsyncTCP.h"
#include "ESPAsyncWebServer.h"
#define ELEGANTOTA_WEBSERVER AsyncWebServer
#elif ELEGANTOTA_USE_PSYCHIC == 1
#include <PsychicHttp.h>
#define ELEGANTOTA_WEBSERVER PsychicHttpServer
#else
#include "WiFi.h"
#include "WiFiClient.h"
Expand Down
Loading