@ -30,6 +30,8 @@
# include "esp_crt_bundle.h"
# include "cJSON.h"
# include "mqtt_client.h"
# include "esp_ota_ops.h"
# include "esp_app_format.h"
# include "video_broadcast.h"
# include "3d.h"
@ -1275,6 +1277,11 @@ static const char *html_page =
" <input type='file' id='importFile' accept='.json' style='width:120px'> "
" <button class='btn' onclick='importSettings()'>IMPORT</button></div> "
" <div id='backupStatus' class='msg'></div> "
" <div class='sep'><b>Firmware Update (OTA):</b></div> "
" <div class='flex'><input type='file' id='fwFile' accept='.bin' style='width:150px'> "
" <button class='btn' onclick='uploadFirmware()'>UPDATE</button></div> "
" <div id='fwVersion' class='msg' style='margin-top:4px'></div> "
" <div id='otaStatus' class='msg'></div> "
" </div></div> "
" <div><h2>> IMAGE_UPLOAD</h2><div class='panel'> "
" <div class='flex'><input type='file' id='imgFile' accept='image/*' style='width:150px'> "
@ -1518,7 +1525,18 @@ static const char *html_page =
" loadMqtt();loadHaConfig();loadRotation();loadTransition();marginsLoaded=false;updateStatus(); "
" }).catch(e=>{document.getElementById('backupStatus').innerText='Import failed: '+e;});}; "
" reader.readAsText(f);} "
" updateStatus();loadMqtt();loadHaConfig();loadRotation();loadTransition();setInterval(updateStatus,2000);setInterval(loadHaConfig,10000); "
" function loadOtaStatus(){fetch('/ota/status').then(r=>r.json()).then(d=>{ "
" document.getElementById('fwVersion').innerText='v'+d.version+' ('+d.running+') '+d.date;}).catch(e=>{});} "
" function uploadFirmware(){var f=document.getElementById('fwFile').files[0]; "
" if(!f){alert('Select a .bin firmware file first');return;} "
" if(!f.name.endsWith('.bin')){alert('File must be a .bin firmware file');return;} "
" if(!confirm('Update firmware to '+f.name+'? \\ nDevice will reboot after update.')){return;} "
" document.getElementById('otaStatus').innerText='Uploading firmware...'; "
" fetch('/ota',{method:'POST',body:f,headers:{'Content-Type':'application/octet-stream'}}).then(r=>{ "
" if(r.ok){document.getElementById('otaStatus').innerText='Update complete! Rebooting...';} "
" else{r.text().then(t=>{document.getElementById('otaStatus').innerText='Update failed: '+t;});}} "
" ).catch(e=>{document.getElementById('otaStatus').innerText='Upload failed: '+e;});} "
" updateStatus();loadMqtt();loadHaConfig();loadRotation();loadTransition();loadOtaStatus();setInterval(updateStatus,2000);setInterval(loadHaConfig,10000); "
" </script></body></html> " ;
/**
@ -2814,6 +2832,131 @@ static esp_err_t settings_import_handler(httpd_req_t *req)
return ESP_OK ;
}
/**
* @ brief Handler for POST / ota - Over - The - Air firmware update
*/
static esp_err_t ota_handler ( httpd_req_t * req )
{
ESP_LOGI ( TAG , " OTA update started, firmware size: %d bytes " , req - > content_len ) ;
/ / Get the next OTA partition
const esp_partition_t * update_partition = esp_ota_get_next_update_partition ( NULL ) ;
if ( ! update_partition ) {
ESP_LOGE ( TAG , " OTA: No update partition found " ) ;
httpd_resp_send_err ( req , HTTPD_500_INTERNAL_SERVER_ERROR , " No OTA partition " ) ;
return ESP_FAIL ;
}
ESP_LOGI ( TAG , " OTA: Writing to partition '%s' at offset 0x%lx " ,
update_partition - > label , update_partition - > address ) ;
/ / Start OTA
esp_ota_handle_t ota_handle ;
esp_err_t err = esp_ota_begin ( update_partition , OTA_SIZE_UNKNOWN , & ota_handle ) ;
if ( err ! = ESP_OK ) {
ESP_LOGE ( TAG , " OTA begin failed: %s " , esp_err_to_name ( err ) ) ;
httpd_resp_send_err ( req , HTTPD_500_INTERNAL_SERVER_ERROR , " OTA begin failed " ) ;
return ESP_FAIL ;
}
/ / Receive and write firmware in chunks
char * buf = malloc ( 4096 ) ;
if ( ! buf ) {
esp_ota_abort ( ota_handle ) ;
httpd_resp_send_err ( req , HTTPD_500_INTERNAL_SERVER_ERROR , " Out of memory " ) ;
return ESP_FAIL ;
}
int total_received = 0 ;
int remaining = req - > content_len ;
int last_progress = - 1 ;
while ( remaining > 0 ) {
int to_read = remaining > 4096 ? 4096 : remaining ;
int received = httpd_req_recv ( req , buf , to_read ) ;
if ( received < = 0 ) {
if ( received = = HTTPD_SOCK_ERR_TIMEOUT ) {
continue ;
}
ESP_LOGE ( TAG , " OTA receive error at %d bytes " , total_received ) ;
free ( buf ) ;
esp_ota_abort ( ota_handle ) ;
httpd_resp_send_err ( req , HTTPD_500_INTERNAL_SERVER_ERROR , " Receive failed " ) ;
return ESP_FAIL ;
}
err = esp_ota_write ( ota_handle , buf , received ) ;
if ( err ! = ESP_OK ) {
ESP_LOGE ( TAG , " OTA write failed: %s " , esp_err_to_name ( err ) ) ;
free ( buf ) ;
esp_ota_abort ( ota_handle ) ;
httpd_resp_send_err ( req , HTTPD_500_INTERNAL_SERVER_ERROR , " Flash write failed " ) ;
return ESP_FAIL ;
}
total_received + = received ;
remaining - = received ;
/ / Log progress every 10 %
int progress = ( total_received * 100 ) / req - > content_len ;
if ( progress / 10 ! = last_progress / 10 ) {
ESP_LOGI ( TAG , " OTA progress: %d%% (%d/%d bytes) " , progress , total_received , req - > content_len ) ;
last_progress = progress ;
}
}
free ( buf ) ;
/ / Finish OTA
err = esp_ota_end ( ota_handle ) ;
if ( err ! = ESP_OK ) {
ESP_LOGE ( TAG , " OTA end failed: %s " , esp_err_to_name ( err ) ) ;
httpd_resp_send_err ( req , HTTPD_500_INTERNAL_SERVER_ERROR , " OTA validation failed " ) ;
return ESP_FAIL ;
}
/ / Set boot partition
err = esp_ota_set_boot_partition ( update_partition ) ;
if ( err ! = ESP_OK ) {
ESP_LOGE ( TAG , " Set boot partition failed: %s " , esp_err_to_name ( err ) ) ;
httpd_resp_send_err ( req , HTTPD_500_INTERNAL_SERVER_ERROR , " Set boot failed " ) ;
return ESP_FAIL ;
}
ESP_LOGI ( TAG , " OTA update successful! Rebooting... " ) ;
httpd_resp_send ( req , " OTA update successful! Rebooting... " , - 1 ) ;
/ / Delay to allow response to be sent , then reboot
vTaskDelay ( pdMS_TO_TICKS ( 1000 ) ) ;
esp_restart ( ) ;
return ESP_OK ;
}
/**
* @ brief Handler for GET / ota / status - Return OTA partition info
*/
static esp_err_t ota_status_handler ( httpd_req_t * req )
{
const esp_partition_t * running = esp_ota_get_running_partition ( ) ;
const esp_partition_t * next = esp_ota_get_next_update_partition ( NULL ) ;
const esp_app_desc_t * app_desc = esp_app_get_description ( ) ;
char response [ 512 ] ;
snprintf ( response , sizeof ( response ) ,
" { \" running \" : \" %s \" , \" next \" : \" %s \" , \" version \" : \" %s \" , \" idf \" : \" %s \" , \" date \" : \" %s \" , \" time \" : \" %s \" } " ,
running ? running - > label : " unknown " ,
next ? next - > label : " unknown " ,
app_desc - > version ,
app_desc - > idf_ver ,
app_desc - > date ,
app_desc - > time ) ;
httpd_resp_set_type ( req , " application/json " ) ;
httpd_resp_send ( req , response , strlen ( response ) ) ;
return ESP_OK ;
}
/**
* @ brief Start the HTTP server
*/
@ -3032,6 +3175,21 @@ static void start_webserver(void)
} ;
httpd_register_uri_handler ( http_server , & settings_import_uri ) ;
/ / OTA firmware update endpoints
httpd_uri_t ota_uri = {
. uri = " /ota " ,
. method = HTTP_POST ,
. handler = ota_handler
} ;
httpd_register_uri_handler ( http_server , & ota_uri ) ;
httpd_uri_t ota_status_uri = {
. uri = " /ota/status " ,
. method = HTTP_GET ,
. handler = ota_status_handler
} ;
httpd_register_uri_handler ( http_server , & ota_status_uri ) ;
ESP_LOGI ( TAG , " HTTP server started on port %d " , config . server_port ) ;
} else {
ESP_LOGE ( TAG , " Failed to start HTTP server " ) ;