Switch HTTP Server

This commit is contained in:
iranl
2024-08-26 21:47:10 +02:00
parent d3c3589233
commit ca9c2feebc
234 changed files with 20090 additions and 8061 deletions

View File

@@ -0,0 +1,138 @@
**OTA update example for PsychicHttp**
Example of OTA (Over The Air) update implementation for PsychicHttp, using Arduino IDE.
**Requirements**
Requirements for project are :
- OTA update for code (firmware)
- OTA update for data (littlefs)
- manual restart of ESP32, triggered by User (no automatic restart after code or data file upload)
**Implementation**
OTA update relies on handler PsychicUploadHandler.
Screenshots and Code are shown below.
**Credits**
https://github.com/hoeken/PsychicHttp/blob/master/src/PsychicUploadHandler.cpp
https://github.com/hoeken/PsychicHttp/issues/30
**Configuration**
Example has been implemented with following configuration :\
Arduino IDE 1.8.19\
arduino-32 v2.0.15\
PsychicHttp 1.1.1\
ESP32S3
**Example Files Structure**
```
arduino_ota
data
| update.html
arduino_ota.ino
code.bin
littlefs.bin
README
```
"code.bin" and "littlefs.bin" are example update files which can be used to update respectily code (firmware) or data (littlefs).
"Real" update files can be generated on Arduino IDE 1.x :
- for code, menu "Sketch -> Export bin"
- for data, using plugin arduino-esp32fs-plugin https://github.com/lorol/arduino-esp32fs-plugin/releases
**SCREENSHOTS**
**Update code (firmware)**
![otaupdate1](images/otaupdate1.png)
![otaupdate2](images/otaupdate2.png)\
```ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x2b (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3808,len:0x4bc
load:0x403c9700,len:0xbd8
load:0x403cc700,len:0x2a0c
entry 0x403c98d0
[332885][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 51
[332895][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 867306
[332908][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 862016
[332919][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[332929][I][arduino_ota.ino:133] operator()(): [OTA] update begin, filename code.bin
[333082][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 856272
[333095][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[snip]
[339557][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 416
[339566][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 1
[339718][I][arduino_ota.ino:165] operator()(): [OTA] Update Success: 867072 written
[339726][I][arduino_ota.ino:184] operator()(): [OTA] Update code or data OK Update.errorString() No Error
[339738][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 52
[339747][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 52
```
**Update data (littlefs)**
![otaupdate3](images/otaupdate3.png)
```
[ 48216][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 51
[ 48226][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1573100
[ 48239][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1567810
[ 48250][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 48261][I][arduino_ota.ino:133] operator()(): [OTA] update begin, filename littlefs.bin
[ 48376][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1562066
[ 48389][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 48408][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1556322
[ 48421][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1550578
[ 48432][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[snip]
[ 54317][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 16930
[ 54327][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 54340][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 11186
[ 54351][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 54363][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 5442
[ 54375][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0
[ 54386][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 1
[ 54396][I][arduino_ota.ino:165] operator()(): [OTA] Update Success: 1572864 written
[ 54404][I][arduino_ota.ino:184] operator()(): [OTA] Update code or data OK Update.errorString() No Error
[ 54415][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 52
[ 54424][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 52
```
**Restart**
![otaupdate4](images/otaupdate4.png)
![otaupdate5](images/otaupdate5.png)
```
[110318][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 51
[110327][I][arduino_ota.ino:205] operator()(): [OTA] <b style='color:green'>Restarting ...</b>
[110338][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 52
[111317][W][WiFiGeneric.cpp:1062] _eventCallback(): Reason: 8 - ASSOC_LEAVE
[111319][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 51
[111332][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 52
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0xc (RTC_SW_CPU_RST),boot:0x8 (SPI_FAST_FLASH_BOOT)
Saved PC:0x420984ae
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3808,len:0x4bc
load:0x403c9700,len:0xbd8
load:0x403cc700,len:0x2a0c
entry 0x403c98d0
[ 283][I][arduino_ota.ino:57] connectToWifi(): [OTA] [WiFi] WiFi is disconnected
[ 791][I][arduino_ota.ino:60] connectToWifi(): [OTA] [WiFi] WiFi is connected, IP address 192.168.1.50
```

View File

@@ -0,0 +1,219 @@
/*
Over The Air (OTA) update example for PsychicHttp web server
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
char *TAG = "OTA"; // ESP_LOG tag
// PsychicHttp
#include <Arduino.h>
#include <WiFi.h>
#include <LittleFS.h>
#include <ESPmDNS.h>
#include <PsychicHttp.h>
PsychicHttpServer server; // main server object
const char *local_hostname = "psychichttp"; // hostname for mdns
// OTA
#include <Update.h>
bool esprestart=false; // true if/when ESP should be restarted, after OTA update
// Wifi
const char *ssid = "SSID"; // your SSID
const char *password = "PASSWORD"; // your PASSWORD
bool connectToWifi() { // Wifi
//client in STA mode
WiFi.mode(WIFI_AP_STA);
WiFi.begin(ssid,password);
WiFi.begin(ssid, password);
// Will try for about 10 seconds (20x 500ms)
int tryDelay = 500;
int numberOfTries = 20;
// Wait for the WiFi event
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL:
ESP_LOGE(TAG,"[WiFi] SSID not found");
break;
case WL_CONNECT_FAILED:
ESP_LOGI(TAG,"[WiFi] Failed - WiFi not connected! Reason: ");
return false;
break;
case WL_CONNECTION_LOST:
ESP_LOGI(TAG,"[WiFi] Connection was lost");
break;
case WL_SCAN_COMPLETED:
ESP_LOGI(TAG,"[WiFi] Scan is completed");
break;
case WL_DISCONNECTED:
ESP_LOGI(TAG,"[WiFi] WiFi is disconnected");
break;
case WL_CONNECTED:
ESP_LOGI(TAG,"[WiFi] WiFi is connected, IP address %s",WiFi.localIP().toString().c_str());
return true;
break;
default:
ESP_LOGI(TAG,"[WiFi] WiFi Status: %d",WiFi.status());
break;
}
delay(tryDelay);
if (numberOfTries <= 0) {
ESP_LOGI(TAG,"[WiFi] Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return false;
}
else numberOfTries--;
}
return false;
}
// =======================================================================
// setup
// =======================================================================
void setup()
{ Serial.begin(115200);
delay(10);
// Wifi
if (connectToWifi()) { //set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
ESP_LOGE(TAG,"Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if(!LittleFS.begin()) {
ESP_LOGI(TAG,"ERROR : LittleFS Mount Failed.");
return;
}
//setup server config stuff here
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
server.listen(80);
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
//server.maxRequestBodySize=2*1024*1024; // 2Mb, change default value if needed
//server.maxUploadSize=64*1024*1024; // 64Mb, change default value if needed
//you can set up a custom 404 handler.
// curl -i http://psychic.local/404
server.onNotFound([](PsychicRequest *request) {
return request->reply(404, "text/html", "Custom 404 Handler");
});
// OTA
PsychicUploadHandler *updateHandler = new PsychicUploadHandler(); // create handler for OTA update
updateHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { // onUpload
/* callback to upload code (firmware) or data (littlefs)
* callback is triggered for each file chunk, from first chunk (index is 0) to last chunk (last is true)
* callback is triggered by handler in handleRequest(), after _multipartUploadHandler() *
* filename : name of file to upload, with naming convention below
* "*code.bin" for code (firmware) update, eg "v1_code.bin"
* "*littlefs.bin" for data (little fs) update, eg "v1_littlefs.bin" *
*/
int command; //command : firmware and filesystem update type, ie code (U_FLASH 0) or data (U_SPIFFS 100)
ESP_LOGI(TAG,"updateHandler->onUpload _error %d Update.hasError() %d last %d", Update.getError(), Update.hasError(), last);
// Update.abort() replaces 1st error (eg "UPDATE_ERROR_ERASE") with abort error ("UPDATE_ERROR_ABORT") so root cause is lost
if (!Update.hasError()) { // no error encountered so far during update, process current chunk
if (!index){ // index is 0, begin update (first chunk)
ESP_LOGI(TAG,"update begin, filename %s", filename.c_str());
Update.clearError(); // first chunk, clear Update error if any
// check if update file is code, data or sd card one
if (!filename.endsWith("code.bin") && !filename.endsWith("littlefs.bin")) { // incorrect file name
ESP_LOGE(TAG,"ERROR : filename %s format is incorrect", filename.c_str());
if (!Update.hasError()) Update.abort();
return(ESP_FAIL);
} // end incorrect file name
else { // file name is correct
// check update type : code or data
if (filename.endsWith("code.bin")) command=U_FLASH; // update code
else command=U_SPIFFS; // update data
if (!Update.begin(UPDATE_SIZE_UNKNOWN, command)) { // start update with max available size
// error, begin is KO
if (!Update.hasError()) Update.abort(); // abort
ESP_LOGE(TAG,"ERROR : update.begin error Update.errorString() %s",Update.errorString());
return(ESP_FAIL);
}
} // end file name is correct
} // end begin update
if ((len) && (!Update.hasError())) { // ongoing update if no error encountered
if (Update.write(data, len) != len) {
// error, write is KO
if (!Update.hasError()) Update.abort();
ESP_LOGE(TAG,"ERROR : update.write len %d Update.errorString() %s",len, Update.errorString()) ;
return(ESP_FAIL);
}
} // end ongoing update
if ((last) && (!Update.hasError())) { // last update if no error encountered
if (Update.end(true)) { // update end is OKTEST
ESP_LOGI(TAG, "Update Success: %u written", index+len);
}
else { // update end is KO
if (!Update.hasError()) Update.abort(); // abort
ESP_LOGE(TAG,"ERROR : update end error Update.errorString() %s", Update.errorString());
return(ESP_FAIL);
}
} // last update if no error encountered
return(ESP_OK);
} // end no error encountered so far during update, process current chunk
else { // error encountered so far during update
return(ESP_FAIL);
}
}); // end onUpload
updateHandler->onRequest([](PsychicRequest *request) { // triggered when update is completed (either OK or KO) and returns request's response (important)
String result; // request result
// code below is executed when update is finished
if (!Update.hasError()) { // update is OK
ESP_LOGI(TAG,"Update code or data OK Update.errorString() %s", Update.errorString());
result = "<b style='color:green'>Update done for file.</b>";
return request->reply(200,"text/html",result.c_str());
// ESP.restart(); // restart ESP if needed
} // end update is OK
else { // update is KO, send request with pretty print error
result = " Update.errorString() " + String(Update.errorString());
ESP_LOGE(TAG,"ERROR : error %s",result.c_str());
return request->reply(500, "text/html", result.c_str());
} // end update is KO
});
server.on("/update", HTTP_GET, [](PsychicRequest*request){
PsychicFileResponse response(request, LittleFS, "/update.html");
return response.send();
});
server.on("/update", HTTP_POST, updateHandler);
server.on("/restart", HTTP_POST, [](PsychicRequest *request) {
String output = "<b style='color:green'>Restarting ...</b>";
ESP_LOGI(TAG,"%s",output.c_str());
esprestart=true;
return request->reply(output.c_str());
});
} // end onRequest
} // end setup
// =======================================================================
// loop
// =======================================================================
void loop() {
delay(2000);
if (esprestart) ESP.restart(); // restart ESP
} // end loop

Binary file not shown.

View File

@@ -0,0 +1,166 @@
<html>
<head>
<title>PSYCHICHTTP</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
input[type=file]::file-selector-button {
margin-right: 20px;
border: none;
background: #3498db; /* blue */
padding: 10px 20px;
border-radius: 30px;
color: #fff;
cursor: pointer;
transition: background .2s ease-in-out;
}
input[type=file]::file-selector-button:hover {
background: green ;
}
.drop-container {
position: relative;
display: flex;
gap: 10px;
flex-direction: column;
justify-content: center;
align-items: center;
height: 150px;
padding: 20px;
border-radius: 10px;
border: 2px dashed #555;
color: #444;
cursor: pointer;
transition: background .2s ease-in-out, border .2s ease-in-out;
}
.drop-container:hover {
background: #eee;
border-color: #111;
}
.drop-container:hover .drop-title {
color: #222;
}
.drop-title {
color: #444;
font-size: 20px;
font-weight: bold;
text-align: center;
transition: color .2s ease-in-out;
}
.drop-container.drag-active {
background: #eee;
border-color: #111;
}
.drop-container.drag-active .drop-title {
color: #222;
}
.restart-button {
margin-right: 20px;
border: none;
background: #3498db; /* blue */
padding: 10px 20px;
border-radius: 30px;
color: #fff;
cursor: pointer;
transition: background .2s ease-in-out;
}
.restart-button:hover {
background: green ;
}
</style>
</head>
<body>
<div class="row">
<h1>PsychicHttp OTA Update</h1>
</div>
<div><p style="text-align: left">This page allows to test OTA update with PsychicHttp, and file naming convention below :
<ul>
<li><b>"*code.bin"</b> for code (firmware) update, eg "v1_code.bin"</li>
<li><b>"*littlefs.bin"</b> for data (littlefs) update, eg "v1_littlefs.bin" </li>
</ul>
</div>
<div>
<form id="upload_form" enctype="multipart/form-data" method="post">
<label for="images" class="drop-container" id="dropcontainer">
<input type="file" class="inputfile" name="updatefile" id="updatefile" value="updatefile" accept=".bin" onchange="uploadFile()" required>
<span class="drop-title">Or drop file here</span>
<progress id="progressBar" value="0" max="100" style="width:300px;"></progress>
<font color='black' id="status"></font>
<p id="loaded_n_total"></p>
</label>
</form>
</div>
<div>
<p style="text-align: left">Update must be done for each of the files provided (code, littlefs). Once updates are made, the ESP32 can be restarted.
<button class="restart-button" onclick="esprestartButton()">Restart</button>
</p>
</div>
</body>
<script>
function uploadFile() {
var urltocall = "/update";
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", progressHandler, false);
xhr.addEventListener("error", errorHandler, false);
var fileupdate = document.getElementById('updatefile'); // get file structure in DOM
var file = fileupdate.files[0]; // get file element
//alert(file.name+" | "+file.size+" | "+file.type);
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("status").innerHTML = xhr.responseText; // success, show response on page
}
else {
document.getElementById("status").innerHTML = xhr.responseText; // error, show response on page
}
};
var formdata = new FormData();
formdata.append("file", file);
xhr.open("POST", urltocall); // trigger upload request with file parameter
xhr.send(formdata);
}
function progressHandler(event) {
// document.getElementById("loaded_n_total").innerHTML = "Chargé " + event.loaded + " octets"; // do not display bytes loaded
var percent = (event.loaded / event.total) * 100;
document.getElementById("progressBar").value = Math.round(percent);
document.getElementById("status").innerHTML = Math.round(percent) + "% uploaded...";
if (percent >= 100) {
document.getElementById("status").innerHTML = "Writing file ...";
}
}
function errorHandler(event) {
document.getElementById("status").innerHTML = "<b style='color:red'>Error event catched by errorHandler !</b>";
}
function esprestartButton() {
/* restart ESP */
var urltocall = "/restart";
xhr=new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4) document.getElementById("status").innerHTML = xhr.responseText;
};
xhr.open("POST", urltocall, false); // trigger restart request with file parameter
xhr.send();
}
</script>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB