2018-11-26 11:58:15 +00:00
/*
* This file is part of the " bluetoothheater " distribution
* ( https : //gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright ( C ) 2018 Ray Jones < ray @ mrjones . id . au >
* Copyright ( C ) 2018 James Clark
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
*
*/
2019-05-30 10:31:34 +00:00
# define USE_EMBEDDED_WEBUPDATECODE
2018-11-07 04:07:11 +00:00
2019-07-07 07:18:38 +00:00
# include <Arduino.h>
# include "BTCWifi.h"
2018-11-07 04:07:11 +00:00
# include "BTCWebServer.h"
2019-07-09 12:19:21 +00:00
# include "BTCota.h"
2018-12-16 07:34:39 +00:00
# include "../Utility/DebugPort.h"
# include "../Protocol/TxManage.h"
2019-06-15 23:09:29 +00:00
# include "../Utility/helpers.h"
2018-12-16 07:34:39 +00:00
# include "../cfg/pins.h"
2019-05-18 08:49:22 +00:00
# include "../cfg/BTCConfig.h"
2018-12-16 07:34:39 +00:00
# include "../Utility/BTC_JSON.h"
# include "../Utility/Moderator.h"
2019-07-01 10:46:08 +00:00
# include "../Libraries/WiFiManager-dev/WiFiManager.h"
2019-01-20 10:14:45 +00:00
# include <SPIFFS.h>
2019-05-16 11:12:29 +00:00
# include "../Utility/NVStorage.h"
2019-07-07 07:18:38 +00:00
# include <WiFiClient.h>
# include <WebServer.h>
# include <ESPmDNS.h>
# include <Update.h>
2019-01-11 08:50:27 +00:00
extern WiFiManager wm ;
2019-07-06 13:46:20 +00:00
extern const char * stdHeader ;
extern const char * formatIndex ;
extern const char * updateIndex ;
extern const char * formatDoneContent ;
2018-11-07 04:07:11 +00:00
2019-05-21 12:01:42 +00:00
File fsUploadFile ; // a File object to temporarily store the received file
int SPIFFSupload = 0 ;
2018-11-07 04:07:11 +00:00
WebServer server ( 80 ) ;
2018-11-26 10:26:38 +00:00
WebSocketsServer webSocket = WebSocketsServer ( 81 ) ;
2018-12-12 09:03:44 +00:00
bool bRxWebData = false ; // flags for OLED animation
2018-12-01 18:25:10 +00:00
bool bTxWebData = false ;
2019-05-14 11:29:35 +00:00
bool bUpdateAccessed = false ; // flag used to ensure web update always starts via /update. direct accesses to /updatenow will FAIL
2019-07-06 13:46:20 +00:00
bool bFormatAccessed = false ;
2019-07-11 08:55:31 +00:00
bool bFormatPerformed = false ;
2019-05-22 20:35:09 +00:00
long _SuppliedFileSize = 0 ;
2018-11-07 04:07:11 +00:00
2019-07-07 07:18:38 +00:00
void webSocketEvent ( uint8_t num , WStype_t type , uint8_t * payload , size_t length ) ;
2019-06-30 00:37:24 +00:00
bool checkFile ( File & file ) ;
2019-07-06 13:46:20 +00:00
void addTableData ( String & HTML , String dta ) ;
void rootRedirect ( ) ;
String getContentType ( String filename ) ;
bool handleFileRead ( String path ) ;
2019-07-07 07:18:38 +00:00
void onNotFound ( ) ;
void onErase ( ) ;
2019-07-06 13:46:20 +00:00
void onFormatSPIFFS ( ) ;
void onFormatNow ( ) ;
void onFormatDone ( ) ;
void onWMConfig ( ) ;
2019-07-07 07:18:38 +00:00
void onResetWifi ( ) ;
2019-07-06 13:46:20 +00:00
void onUploadBegin ( ) ;
void onUploadCompletion ( ) ;
void onUploadProgression ( ) ;
void build404Response ( String & content , String file ) ;
void build500Response ( String & content , String file ) ;
void initWebServer ( void ) {
Update
. onProgress ( [ ] ( unsigned int progress , unsigned int total ) {
int percent = ( progress / ( total / 100 ) ) ;
DebugPort . printf ( " Progress: %u%% \r " , percent ) ;
DebugPort . handle ( ) ; // keep telnet spy alive
ShowOTAScreen ( percent , eOTAWWW ) ; // WWW update in place
DebugPort . print ( " ^ " ) ;
} ) ;
if ( MDNS . begin ( " Afterburner " ) ) {
DebugPort . println ( " MDNS responder started " ) ;
}
server . on ( " /wmconfig " , onWMConfig ) ;
2019-07-07 07:18:38 +00:00
server . on ( " /resetwifi " , onResetWifi ) ;
server . on ( " /erase " , HTTP_POST , onErase ) ; // erase file from SPIFFS
2019-07-06 13:46:20 +00:00
// Magical code originally shamelessly lifted from Arduino WebUpdate example, then greatly modified
// This allows pushing new firmware to the ESP from a WEB BROWSER!
// Added authentication and a sequencing flag to ensure this is not bypassed
// You can also upload files to SPIFFS via this same portal
//
// Initial launch page
server . on ( " /update " , HTTP_GET , onUploadBegin ) ;
// handle attempts to browse the /updatenow path - force redirect to root
server . on ( " /updatenow " , HTTP_GET , [ ] ( ) {
DebugPort . println ( " WEB: GET /updatenow - ILLEGAL - root redirect " ) ;
rootRedirect ( ) ;
} ) ;
// valid upload attempts must use post, AND they must have also passed thru /update (bUpdateAccessed = true)
server . on ( " /updatenow " , HTTP_POST , onUploadCompletion , onUploadProgression ) ;
2018-11-07 04:07:11 +00:00
2019-07-06 13:46:20 +00:00
// SPIFFS formatting
server . on ( " /formatspiffs " , HTTP_GET , onFormatSPIFFS ) ;
server . on ( " /formatnow " , HTTP_GET , [ ] ( ) { // deny browse access
DebugPort . println ( " WEB: GET /formatnow - ILLEGAL - root redirect " ) ;
rootRedirect ( ) ;
} ) ;
server . on ( " /formatnow " , HTTP_POST , onFormatNow ) ; // access via POST is legal, but only if bFormatAccess == true
// NOTE: this serves the default home page, and favicon.ico
server . onNotFound ( [ ] ( )
{ // If the client requests any URI
if ( ! handleFileRead ( server . uri ( ) ) ) { // send it if it exists
2019-07-07 07:18:38 +00:00
onNotFound ( ) ;
2019-07-06 13:46:20 +00:00
}
} ) ;
server . begin ( ) ;
webSocket . begin ( ) ;
webSocket . onEvent ( webSocketEvent ) ;
DebugPort . println ( " HTTP server started " ) ;
}
// called by main sketch loop()
bool doWebServer ( void )
{
webSocket . loop ( ) ;
server . handleClient ( ) ;
return true ;
}
2019-01-20 10:14:45 +00:00
String getContentType ( String filename ) { // convert the file extension to the MIME type
if ( filename . endsWith ( " .html " ) ) return " text/html " ;
else if ( filename . endsWith ( " .css " ) ) return " text/css " ;
else if ( filename . endsWith ( " .js " ) ) return " application/javascript " ;
else if ( filename . endsWith ( " .ico " ) ) return " image/x-icon " ;
2019-05-21 12:01:42 +00:00
else if ( filename . endsWith ( " .bin " ) ) return " application/octet-stream " ;
2019-01-20 10:14:45 +00:00
return " text/plain " ;
}
bool handleFileRead ( String path ) { // send the right file to the client (if it exists)
DebugPort . println ( " handleFileRead: " + path ) ;
if ( path . endsWith ( " / " ) ) path + = " index.html " ; // If a folder is requested, send the index file
String contentType = getContentType ( path ) ; // Get the MIME type
if ( SPIFFS . exists ( path ) ) { // If the file exists
File file = SPIFFS . open ( path , " r " ) ; // Open it
2019-06-30 00:37:24 +00:00
if ( ! checkFile ( file ) ) { // check it is readable
2019-07-06 13:46:20 +00:00
file . close ( ) ; // if not, close the file
2019-06-30 00:37:24 +00:00
}
if ( ! file ) {
2019-07-06 13:46:20 +00:00
DebugPort . println ( " \t File exists, but could not be read? " ) ; // dodgy file - throw error back to client
String content ;
build500Response ( content , path ) ;
server . send ( 500 , " text/html " , content ) ;
return false ; // If the file is broken, return false
2019-06-30 00:37:24 +00:00
}
else {
2019-07-06 13:46:20 +00:00
server . streamFile ( file , contentType ) ; // File good, send it to the client
file . close ( ) ; // Then close the file
return true ;
2019-06-30 00:37:24 +00:00
}
2019-01-20 10:14:45 +00:00
}
DebugPort . println ( " \t File Not Found " ) ;
return false ; // If the file doesn't exist, return false
}
2019-07-06 13:46:20 +00:00
const char * stdHeader = R " =====(
< ! DOCTYPE html >
< html lang = " en " >
< head >
< meta charset = " utf-8 " / >
< meta http - equiv = " Pragma " content = " no-cache " >
< meta http - equiv = " Expires " content = " -1 " >
< meta http - equiv = " CACHE-CONTROL " content = " NO-CACHE " >
< style >
2019-07-11 08:55:31 +00:00
body {
font - family : Arial , Helvetica , sans - serif ;
zoom : 200 % ;
}
button {
background - color : # 016 ABC ;
color : # fff ;
border - radius : 25 px ;
}
. del {
color : white ;
font - weight : bold ;
background - color : red ;
border - radius : 50 % ;
height : 30 px ;
width : 30 px ;
}
. fmt {
color : white ;
font - weight : bold ;
background - color : red ;
}
th {
text - align : left ;
}
. throb {
animation : throbber 1 s linear infinite ;
}
@ keyframes throbber {
50 % {
opacity : 0 ;
}
}
2019-07-06 13:46:20 +00:00
< / style >
) = = = = = " ;
const char * updateIndex = R " =====(
2019-07-11 08:55:31 +00:00
< style >
body {
background - color : yellowgreen ;
}
. inputfile {
width : 0.1 px ;
height : 0.1 px ;
opacity : 0 ;
overflow : hidden ;
position : absolute ;
z - index : - 1 ;
}
. inputfile + label {
color : # fff ;
background - color : # 016 ABC ;
display : inline - block ;
border - style : solid ;
border - radius : 25 px ;
border - width : medium ;
border - top - color : # E3E3E3 ;
border - left - color : # E3E3E3 ;
border - right - color : # 979797 ;
border - bottom - color : # 979797 ;
}
# filename {
font - weight : bold ;
font - style : italic ;
}
# upload {
font - weight : bold ;
font - style : italic ;
}
< / style >
2019-07-06 13:46:20 +00:00
< script >
// globals
var sendSize ;
var ws ;
2019-07-11 08:55:31 +00:00
var timeDown ;
var timeUp ;
2019-07-06 13:46:20 +00:00
function _ ( el ) {
return document . getElementById ( el ) ;
}
function onWebSocket ( event ) {
var response = JSON . parse ( event . data ) ;
var key ;
for ( key in response ) {
switch ( key ) {
case " progress " :
// actual data bytes received as fed back via web socket
var bytes = response [ key ] ;
_ ( " loaded_n_total " ) . innerHTML = " Uploaded " + bytes + " bytes of " + sendSize ;
var percent = Math . round ( 100 * ( bytes / sendSize ) ) ;
_ ( " progressBar " ) . value = percent ;
_ ( " status " ) . innerHTML = percent + " % uploaded.. please wait " ;
break ;
}
}
}
function init ( ) {
ws = new WebSocket ( ' ws : //' + window.location.hostname + ':81/');
ws . onmessage = onWebSocket ;
}
function uploadFile ( ) {
_ ( " cancel " ) . hidden = true ;
2019-07-11 08:55:31 +00:00
_ ( " upload " ) . hidden = true ;
_ ( " progressBar " ) . hidden = false ;
2019-07-06 13:46:20 +00:00
var file = _ ( " file1 " ) . files [ 0 ] ;
sendSize = file . size ;
2019-07-11 08:55:31 +00:00
console . log ( file ) ;
2019-07-06 13:46:20 +00:00
var JSONmsg = { } ;
JSONmsg [ ' UploadSize ' ] = sendSize ;
var str = JSON . stringify ( JSONmsg ) ;
console . log ( " JSON Tx: " , str ) ;
ws . send ( str ) ;
var formdata = new FormData ( ) ;
formdata . append ( " update " , file ) ;
var ajax = new XMLHttpRequest ( ) ;
// progress is handled via websocket JSON sent from controller
// using server side progress only shows the buffer filling, not actual delivery.
ajax . addEventListener ( " load " , completeHandler , false ) ;
ajax . addEventListener ( " error " , errorHandler , false ) ;
ajax . addEventListener ( " abort " , abortHandler , false ) ;
ajax . open ( " POST " , " /updatenow " ) ;
ajax . send ( formdata ) ;
}
function completeHandler ( event ) {
_ ( " status " ) . innerHTML = event . target . responseText ;
2019-07-11 08:55:31 +00:00
_ ( " progressBar " ) . hidden = true ;
2019-07-06 13:46:20 +00:00
_ ( " progressBar " ) . value = 0 ;
_ ( " loaded_n_total " ) . innerHTML = " Uploaded " + sendSize + " bytes of " + sendSize ;
var file = _ ( " file1 " ) . files [ 0 ] ;
if ( file . name . endsWith ( " .bin " ) ) {
setTimeout ( function ( ) { window . location . assign ( ' / ' ) ; } , 5000 ) ;
}
else {
setTimeout ( function ( ) { location . assign ( ' / update ' ) ; } , 500 ) ;
}
}
function errorHandler ( event ) {
_ ( " status " ) . innerHTML = " Upload Failed " ;
}
function abortHandler ( event ) {
_ ( " status " ) . innerHTML = " Upload Aborted " ;
}
function onErase ( fn ) {
if ( confirm ( ' Do you really want to erase ' + fn + ' ? ' ) ) {
var formdata = new FormData ( ) ;
formdata . append ( " filename " , fn ) ;
var ajax = new XMLHttpRequest ( ) ;
ajax . open ( " POST " , " /erase " ) ;
ajax . send ( formdata ) ;
setTimeout ( function ( ) { location . reload ( ) ; } , 500 ) ;
}
}
function onBrowseChange ( ) {
2019-07-11 08:55:31 +00:00
_ ( " uploaddiv " ) . hidden = false ;
2019-07-06 13:46:20 +00:00
_ ( " upload " ) . hidden = false ;
_ ( " status " ) . hidden = false ;
_ ( " loaded_n_total " ) . hidden = false ;
_ ( " spacer " ) . hidden = false ;
2019-07-11 08:55:31 +00:00
var file = _ ( " file1 " ) . files [ 0 ] ;
document . getElementById ( ' filename ' ) . innerHTML = file . name ;
}
function onformatClick ( ) {
window . location . assign ( ' / formatspiffs ' ) ;
2019-07-06 13:46:20 +00:00
}
2019-07-11 08:55:31 +00:00
2019-07-06 13:46:20 +00:00
< / script >
< title > Afterburner update < / title >
2019-07-11 08:55:31 +00:00
< / head >
2019-07-06 13:46:20 +00:00
< body onload = " javascript:init() " >
< h1 > Afterburner update < / h1 >
2019-07-11 08:55:31 +00:00
< form id = ' upload_form ' method = ' POST ' enctype = ' multipart / form - data ' autocomplete = ' off ' >
< input type = ' file ' name = ' file1 ' id = ' file1 ' class = ' inputfile ' onchange = ' onBrowseChange ( ) ' / >
< label for = ' file1 ' > & nbsp ; & nbsp ; Select a file to upload & nbsp ; & nbsp ; < / label >
2019-07-06 13:46:20 +00:00
< / form >
2019-07-11 08:55:31 +00:00
< p >
< div id = ' uploaddiv ' hidden > < span id = ' filename ' > < / span > & nbsp ; < button id = " upload " class = ' throb ' onclick = ' uploadFile ( ) ' hidden > Upload < / button >
< progress id = ' progressBar ' value = ' 0 ' max = ' 100 ' style = ' width : 300 px ; ' hidden > < / progress > < p > < / div >
< p id = ' spacer ' hidden > < / p >
< div > < button onclick = window . location . assign ( ' / ' ) id = ' cancel ' > Cancel < / button > < / div >
< h3 id = ' status ' hidden > < / h3 >
< div id = ' loaded_n_total ' hidden > < / div >
2019-07-06 13:46:20 +00:00
) = = = = = " ;
void onWMConfig ( )
2019-06-30 00:37:24 +00:00
{
2019-05-22 11:08:38 +00:00
DebugPort . println ( " WEB: GET /wmconfig " ) ;
2019-01-16 09:22:17 +00:00
server . send ( 200 , " text/plain " , " Start Config Portal - Retaining credential " ) ;
2019-01-11 08:28:22 +00:00
DebugPort . println ( " Starting web portal for wifi config " ) ;
2019-01-16 09:22:17 +00:00
delay ( 500 ) ;
wifiEnterConfigPortal ( true , false , 3000 ) ;
2019-01-11 08:28:22 +00:00
}
2019-01-13 19:59:32 +00:00
2019-07-07 07:18:38 +00:00
void onResetWifi ( )
2019-06-30 00:37:24 +00:00
{
2019-05-22 11:08:38 +00:00
DebugPort . println ( " WEB: GET /resetwifi " ) ;
2019-01-16 09:22:17 +00:00
server . send ( 200 , " text/plain " , " Start Config Portal - Resetting Wifi credentials! " ) ;
2019-01-12 21:32:13 +00:00
DebugPort . println ( " diconnecting client and wifi, then rebooting " ) ;
2019-01-16 09:22:17 +00:00
delay ( 500 ) ;
wifiEnterConfigPortal ( true , true , 3000 ) ;
2019-01-11 08:28:22 +00:00
}
2019-02-11 08:34:11 +00:00
2019-07-06 13:46:20 +00:00
//<p><a href="/update"> <button type="button">Add</button></a>
//<p><a href="/"><button type="button">Home</button></a>
2019-06-30 00:37:24 +00:00
2019-07-07 07:18:38 +00:00
void onNotFound ( )
2019-06-30 00:37:24 +00:00
{
String path = server . uri ( ) ;
if ( path . endsWith ( " / " ) ) path + = " index.html " ; // If a folder is requested, send the index file
2019-07-06 13:46:20 +00:00
String message ;
build404Response ( message , path ) ;
2019-06-30 00:37:24 +00:00
server . send ( 404 , " text/html " , message ) ;
2018-11-07 04:07:11 +00:00
}
2019-05-16 11:12:29 +00:00
void rootRedirect ( )
{
2019-05-22 11:08:38 +00:00
server . sendHeader ( " Location " , " / " ) ; // reselect the update page
server . send ( 303 ) ;
2019-05-16 11:12:29 +00:00
}
2019-07-07 07:18:38 +00:00
bool sendWebSocketString ( const char * Str )
2018-12-19 12:07:51 +00:00
{
2019-07-09 12:19:21 +00:00
// CProfile profile;
2018-12-19 12:07:51 +00:00
if ( webSocket . connectedClients ( ) ) {
2019-07-09 12:19:21 +00:00
// unsigned long tCon = profile.elapsed(true);
2018-12-19 12:07:51 +00:00
bTxWebData = true ; // OLED tx data animation flag
webSocket . broadcastTXT ( Str ) ;
2019-07-09 12:19:21 +00:00
// unsigned long tWeb = profile.elapsed(true);
// DebugPort.printf("Websend times : %ld,%ld\r\n", tCon, tWeb);
2018-12-01 18:25:10 +00:00
return true ;
}
return false ;
2018-11-07 04:07:11 +00:00
}
2018-12-19 12:07:51 +00:00
2019-06-02 20:34:45 +00:00
void webSocketEvent ( uint8_t num , WStype_t type , uint8_t * payload , size_t length )
{
2018-11-26 10:26:38 +00:00
if ( type = = WStype_TEXT ) {
2018-12-11 10:19:02 +00:00
bRxWebData = true ;
char cmd [ 256 ] ;
memset ( cmd , 0 , 256 ) ;
for ( int i = 0 ; i < length & & i < 256 ; i + + ) {
2018-11-26 10:26:38 +00:00
cmd [ i ] = payload [ i ] ;
}
2018-12-11 10:19:02 +00:00
interpretJsonCommand ( cmd ) ; // send to the main heater controller decode routine
2018-12-01 18:25:10 +00:00
}
}
2019-07-07 07:18:38 +00:00
bool isWebSocketClientChange ( )
{
static int prevNumClients = - 1 ;
int numClients = webSocket . connectedClients ( ) ;
if ( numClients ! = prevNumClients ) {
prevNumClients = numClients ;
DebugPort . println ( " Changed number of web clients, should reset JSON moderator " ) ;
return true ;
}
return false ;
}
2018-12-01 18:25:10 +00:00
bool hasWebClientSpoken ( bool reset )
{
bool retval = bRxWebData ;
if ( reset )
bRxWebData = false ;
return retval ;
}
bool hasWebServerSpoken ( bool reset )
{
bool retval = bTxWebData ;
if ( reset )
bTxWebData = false ;
return retval ;
2018-11-26 10:26:38 +00:00
}
2018-12-11 10:19:02 +00:00
2019-05-22 20:35:09 +00:00
void setUploadSize ( long val )
{
_SuppliedFileSize = val ;
} ;
2019-06-30 00:37:24 +00:00
// Sometimes SPIFFS gets corrupted (WTF?)
// When this happens, you can see the files exist, but you cannot read them
// This routine checks the file is readable.
// Typical failure mechanism is read returns 0, and the WifiClient upload never progresses
// The software watchdog then steps in after 15 seconds of that nonsense
bool checkFile ( File & file )
{
uint8_t buf [ 128 ] ;
bool bOK = true ;
size_t available = file . available ( ) ;
while ( available ) {
int toRead = ( available > 128 ) ? 128 : available ;
int Read = file . read ( buf , toRead ) ;
if ( Read ! = toRead ) {
bOK = false ;
DebugPort . printf ( " SPIFFS precautionary file check failed for %s \r \n " , file . name ( ) ) ;
break ;
}
available = file . available ( ) ;
}
file . seek ( 0 ) ;
return bOK ;
}
2019-07-07 07:18:38 +00:00
void listSPIFFS ( const char * dirname , uint8_t levels , String & HTMLreport , int withHTMLanchors )
2019-06-30 00:37:24 +00:00
{
char msg [ 128 ] ;
2019-07-07 07:18:38 +00:00
File root = SPIFFS . open ( dirname ) ;
2019-06-30 00:37:24 +00:00
if ( ! root ) {
sprintf ( msg , " Failed to open directory \" %s \" " , dirname ) ;
DebugPort . println ( msg ) ;
HTMLreport + = msg ; HTMLreport + = " <br> " ;
return ;
}
if ( ! root . isDirectory ( ) ) {
sprintf ( msg , " \" %s \" is not a directory " , dirname ) ;
DebugPort . println ( msg ) ;
HTMLreport + = msg ; HTMLreport + = " <br> " ;
return ;
}
2019-07-06 13:46:20 +00:00
HTMLreport + = " <hr><h3>Current SPIFFS contents:</h3> " ;
// create HTML table header
HTMLreport + = R " =====(<table>
< tr >
< th > < / th >
< th style = " width:200px " > Name < / th >
< th style = " width:60px " > Size < / th >
< th > < / th >
< / tr >
) = = = = = " ;
2019-06-30 00:37:24 +00:00
File file = root . openNextFile ( ) ;
while ( file ) {
2019-07-06 13:46:20 +00:00
HTMLreport + = " <tr> \n " ;
2019-06-30 00:37:24 +00:00
if ( file . isDirectory ( ) ) {
2019-07-06 13:46:20 +00:00
addTableData ( HTMLreport , " DIR " ) ;
addTableData ( HTMLreport , file . name ( ) ) ;
addTableData ( HTMLreport , " " ) ;
addTableData ( HTMLreport , " " ) ;
2019-06-30 00:37:24 +00:00
sprintf ( msg , " DIR : %s " , file . name ( ) ) ;
DebugPort . println ( msg ) ;
2019-07-06 13:46:20 +00:00
2019-06-30 00:37:24 +00:00
if ( levels ) {
2019-07-07 07:18:38 +00:00
listSPIFFS ( file . name ( ) , levels - 1 , HTMLreport ) ;
2019-06-30 00:37:24 +00:00
}
} else {
String fn = file . name ( ) ;
2019-07-06 13:46:20 +00:00
String ers ;
if ( withHTMLanchors = = 2 )
2019-07-11 08:55:31 +00:00
ers = " <input class='del' type='button' value='X' onClick=onErase(' " + fn + " ')> " ;
2019-06-30 00:37:24 +00:00
if ( withHTMLanchors ) {
if ( fn . endsWith ( " .html " ) | | fn . endsWith ( " .htm " ) ) {
String fn2 ( fn ) ;
fn = " <a href= \" " + fn2 + " \" > " + fn2 + " </a> " ;
}
}
2019-07-06 13:46:20 +00:00
String sz ; sz + = int ( file . size ( ) ) ;
addTableData ( HTMLreport , " " ) ;
addTableData ( HTMLreport , fn ) ;
addTableData ( HTMLreport , sz ) ;
addTableData ( HTMLreport , ers ) ;
sprintf ( msg , " FILE: %s SIZE: %d " , fn . c_str ( ) , file . size ( ) ) ;
2019-06-30 00:37:24 +00:00
DebugPort . println ( msg ) ;
}
2019-07-06 13:46:20 +00:00
HTMLreport + = " </tr> \n " ;
2019-06-30 00:37:24 +00:00
file = root . openNextFile ( ) ;
}
2019-07-06 13:46:20 +00:00
HTMLreport + = " </table> \n " ;
if ( withHTMLanchors ) {
char usage [ 128 ] ;
int used = SPIFFS . usedBytes ( ) ;
int total = SPIFFS . totalBytes ( ) ;
float percent = used * 100. / total ;
2019-07-07 07:18:38 +00:00
sprintf ( usage , " <p><b>Usage</b><br> %d / %d bytes (%.1f%%) \n <p> " , used , total , percent ) ;
2019-07-06 13:46:20 +00:00
HTMLreport + = usage ;
}
2019-06-30 00:37:24 +00:00
}
2019-07-06 13:46:20 +00:00
void addTableData ( String & HTML , String dta )
{
HTML + = " <td> " ;
HTML + = dta ;
HTML + = " </td> \n " ;
}
// erase a file from SPIFFS partition
2019-07-07 07:18:38 +00:00
void onErase ( )
2019-07-06 13:46:20 +00:00
{
String filename = server . arg ( " filename " ) ; // get request argument value by name
if ( filename . length ( ) ! = 0 ) {
2019-07-07 07:18:38 +00:00
DebugPort . printf ( " onErase: %s " , filename . c_str ( ) ) ;
2019-07-06 13:46:20 +00:00
if ( SPIFFS . exists ( filename . c_str ( ) ) ) {
SPIFFS . remove ( filename . c_str ( ) ) ;
DebugPort . println ( " ERASED \r \n " ) ;
}
else
DebugPort . println ( " NOT FOUND \r \n " ) ;
}
}
// function called upon completion of file (form) upload
void onUploadCompletion ( )
{
2019-07-09 12:19:21 +00:00
_SuppliedFileSize = 0 ;
2019-07-06 13:46:20 +00:00
DebugPort . println ( " WEB: POST /updatenow completion " ) ;
// completion functionality
if ( SPIFFSupload ) {
if ( SPIFFSupload = = 1 ) {
DebugPort . println ( " WEB: SPIFFS OK " ) ;
server . send ( 200 , " text/plain " , " OK - File uploaded to SPIFFS " ) ;
// javascript reselects the /update page!
}
else {
DebugPort . println ( " WEB: SPIFFS FAIL " ) ;
server . send ( 500 , " text/plain " , " 500: couldn't create file " ) ;
}
SPIFFSupload = 0 ;
}
else {
if ( Update . hasError ( ) ) {
DebugPort . println ( " WEB: UPDATE FAIL " ) ;
server . send ( 200 , " text/plain " , " FAIL - Afterburner will reboot shortly " ) ;
}
else {
DebugPort . println ( " WEB: UPDATE OK " ) ;
server . send ( 200 , " text/plain " , " OK - Afterburner will reboot shortly " ) ;
}
delay ( 1000 ) ;
// javascript redirects to root page so we go there after reboot!
forceBootInit ( ) ;
ESP . restart ( ) ; // reboot
}
}
void onUploadBegin ( )
{
DebugPort . println ( " WEB: GET /update " ) ;
sCredentials creds = NVstore . getCredentials ( ) ;
if ( ! server . authenticate ( creds . webUpdateUsername , creds . webUpdatePassword ) ) {
return server . requestAuthentication ( ) ;
}
bUpdateAccessed = true ;
bFormatAccessed = false ;
2019-07-11 08:55:31 +00:00
bFormatPerformed = false ;
2019-07-06 13:46:20 +00:00
# ifdef USE_EMBEDDED_WEBUPDATECODE
String SPIFFSinfo ;
2019-07-07 07:18:38 +00:00
listSPIFFS ( " / " , 2 , SPIFFSinfo , 2 ) ;
2019-07-06 13:46:20 +00:00
String content = stdHeader ;
content + = updateIndex + SPIFFSinfo ;
2019-07-11 08:55:31 +00:00
content + = " <p><button class='fmt' onclick='onformatClick()'>Format SPIFFS</button> " ;
2019-07-06 13:46:20 +00:00
content + = " </body></html> " ;
server . send ( 200 , " text/html " , content ) ;
# else
handleFileRead ( " /uploadfirmware.html " ) ;
# endif
}
void onUploadProgression ( )
{
if ( bUpdateAccessed ) { // only allow progression via /update, attempts to directly access /updatenow will fail
HTTPUpload & upload = server . upload ( ) ;
if ( upload . status = = UPLOAD_FILE_START ) {
String filename = upload . filename ;
2019-07-09 12:19:21 +00:00
2019-07-06 13:46:20 +00:00
DebugPort . setDebugOutput ( true ) ;
if ( filename . endsWith ( " .bin " ) ) {
2019-07-09 12:19:21 +00:00
DebugPort . printf ( " Update: %s \r \n " , filename . c_str ( ) ) ;
int sizetouse = - 1 ; //start with max available size
if ( _SuppliedFileSize ) {
sizetouse = _SuppliedFileSize ; // adapt to websocket supplied size
}
if ( ! Update . begin ( sizetouse ) ) {
2019-07-06 13:46:20 +00:00
Update . printError ( DebugPort ) ;
}
}
else {
if ( ! filename . startsWith ( " / " ) ) filename = " / " + filename ;
DebugPort . printf ( " handleFileUpload Name: %s \r \n " , filename . c_str ( ) ) ;
fsUploadFile = SPIFFS . open ( filename , " w " ) ; // Open the file for writing in SPIFFS (create if it doesn't exist)
SPIFFSupload = fsUploadFile ? 1 : 2 ;
}
}
// handle file segments
else if ( upload . status = = UPLOAD_FILE_WRITE ) {
# if USE_SW_WATCHDOG == 1
feedWatchdog ( ) ; // we get stuck here for a while, don't let the watchdog bite!
# endif
if ( upload . totalSize ) {
char JSON [ 64 ] ;
sprintf ( JSON , " { \" progress \" :%d} " , upload . totalSize ) ;
2019-07-07 07:18:38 +00:00
sendWebSocketString ( JSON ) ; // feedback proper byte count of update to browser via websocket
2019-07-06 13:46:20 +00:00
}
int percent = 0 ;
if ( _SuppliedFileSize )
percent = 100 * upload . totalSize / _SuppliedFileSize ;
ShowOTAScreen ( percent , eOTAbrowser ) ; // browser update
DebugPort . print ( " . " ) ;
if ( fsUploadFile ) {
fsUploadFile . write ( upload . buf , upload . currentSize ) ; // Write the received bytes to the file
}
else {
if ( Update . write ( upload . buf , upload . currentSize ) ! = upload . currentSize ) {
Update . printError ( DebugPort ) ;
}
}
}
// handle end of upload
else if ( upload . status = = UPLOAD_FILE_END ) {
2019-07-09 12:19:21 +00:00
delay ( 2000 ) ;
2019-07-06 13:46:20 +00:00
if ( SPIFFSupload ) {
if ( fsUploadFile ) {
fsUploadFile . close ( ) ; // Close the file again
DebugPort . printf ( " handleFileUpload Size: %d \r \n " , upload . totalSize ) ;
}
}
else {
2019-07-09 12:19:21 +00:00
if ( ! CheckFirmwareCRC ( _SuppliedFileSize ) )
Update . abort ( ) ;
if ( Update . end ( ) ) {
2019-07-06 13:46:20 +00:00
DebugPort . printf ( " Update Success: %u \r \n Rebooting... \r \n " , upload . totalSize ) ;
} else {
Update . printError ( DebugPort ) ;
}
}
DebugPort . setDebugOutput ( false ) ;
bUpdateAccessed = false ;
} else {
DebugPort . printf ( " Update Failed Unexpectedly (likely broken connection): status=%d \r \n " , upload . status ) ;
bUpdateAccessed = false ;
}
}
else {
// attempt to POST without using /update - forced redirect to root
DebugPort . println ( " WEB: POST /updatenow forbidden entry " ) ;
rootRedirect ( ) ;
}
}
/***************************************************************************************
* FORMAT SPIFFS HANDLING
*
* User must first access / formatspiffs .
* If not already authenticated , an Username / Password challenge is presented
* If that passes , bFormatAccessed is set , unlocking access to the / formatnow path
* The presneted web page offers Format and Cancel button .
* Cancel will immediatly return to the file upload path ' / update '
* Format will then present a confirmation dialog , user must press Yes to proceed .
*
* Assuming Yes was pressed , a HTTP POST to / format now with the payload ' confirm ' = ' yes ' is performed
* The / formatnow handler will check that confirm does equal yes , and that bFormatAccessed was set
* If all good SPIFFS is re - formatted - no response is set .
* The javascript though from the / formatspiffs page performs a reload shortly after the post ( 200 ms timeout )
*
* As bFormatAccessed is still set , a confimration page is the presented advising files now need to be uploaded
* A button allows direct access to / update
*/
void onFormatSPIFFS ( )
{
DebugPort . println ( " WEB: GET /formatspiffs " ) ;
bUpdateAccessed = false ;
String content = stdHeader ;
2019-07-11 08:55:31 +00:00
if ( ! bFormatPerformed ) {
2019-07-06 13:46:20 +00:00
sCredentials creds = NVstore . getCredentials ( ) ;
if ( ! server . authenticate ( creds . webUpdateUsername , creds . webUpdatePassword ) ) {
return server . requestAuthentication ( ) ;
}
bFormatAccessed = true ; // only set after we pass authentication
content + = formatIndex ;
}
else {
bFormatAccessed = false ;
2019-07-11 08:55:31 +00:00
bFormatPerformed = false ;
2019-07-06 13:46:20 +00:00
content + = formatDoneContent ;
}
server . send ( 200 , " text/html " , content ) ;
}
2019-07-11 08:55:31 +00:00
const char * formatDoneContent = R " =====(
< style >
body {
background - color : yellow ;
}
< / style >
< / head >
< body >
2019-07-06 13:46:20 +00:00
< h1 > SPIFFS partition has been formatted < / h1 >
< h3 > You must now upload the web content . < / h3 >
2019-07-11 08:55:31 +00:00
< p > Latest web content can be downloaded from < a href = ' http : //www.mrjones.id.au/afterburner/firmware.html' target='_blank'>http://www.mrjones.id.au/afterburner/firmware.html</a>
2019-07-06 13:46:20 +00:00
< h4 class = " throb " >Please ensure you unzip the web page content, then upload all the files contained.</h4>
< p > < button onclick = window . location . assign ( ' / update ' ) > Upload web content < / button >
< / body >
< / html >
) = = = = = " ;
const char * formatIndex = R " =====(
2019-07-11 08:55:31 +00:00
< style >
body {
background - color : orangered ;
}
< / style >
2019-07-06 13:46:20 +00:00
< script >
function init ( ) {
}
function onFormat ( ) {
var formdata = new FormData ( ) ;
if ( confirm ( ' Do you really want to reformat the SPIFFS partition ? ' ) ) {
2019-07-11 08:55:31 +00:00
document . getElementById ( ' throb ' ) . innerHTML = ' FORMATTING - Please wait ' ;
formdata . append ( ' confirm ' , ' yes ' ) ;
2019-07-06 13:46:20 +00:00
setTimeout ( function ( ) { location . reload ( ) ; } , 200 ) ;
}
else {
2019-07-11 08:55:31 +00:00
formdata . append ( ' confirm ' , ' no ' ) ;
setTimeout ( function ( ) { location . assign ( ' / update ' ) ; } , 20 ) ;
2019-07-06 13:46:20 +00:00
}
var ajax = new XMLHttpRequest ( ) ;
ajax . open ( " POST " , " /formatnow " ) ;
ajax . send ( formdata ) ;
}
< / script >
< title > Afterburner SPIFFS format < / title >
2019-07-11 08:55:31 +00:00
< / head >
2019-07-06 13:46:20 +00:00
< body onload = " javascript:init() " >
< h1 > Format SPIFFS partition < / h1 >
2019-07-11 08:55:31 +00:00
< h3 class = ' throb ' id = ' throb ' > CAUTION ! This will erase all web content < / h1 >
< p > < button class = ' fmt ' onClick = ' onFormat ( ) ' > Format < / button > < br >
< p > < a href = ' / update ' > < button > Cancel < / button > < / a >
2019-07-06 13:46:20 +00:00
< / body >
< / html >
) = = = = = " ;
void onFormatNow ( )
{
// HTTP POST handler, do not need to return a web page!
DebugPort . println ( " WEB: POST /formatnow " ) ;
String confirm = server . arg ( " confirm " ) ; // get request argument value by name
if ( confirm = = " yes " & & bFormatAccessed ) { // confirm user agrees, and we did pass thru /formatspiffs first
DebugPort . println ( " Formatting SPIFFS partition " ) ;
SPIFFS . format ( ) ; // re-format the SPIFFS partition
2019-07-11 08:55:31 +00:00
bFormatPerformed = true ;
2019-07-06 13:46:20 +00:00
}
else {
bFormatAccessed = false ; // user cancelled upon last confirm popup, or not authenticated access
2019-07-11 08:55:31 +00:00
bFormatPerformed = false ;
2019-07-06 13:46:20 +00:00
rootRedirect ( ) ;
}
}
/***************************************************************************************
* HTTP RESPONSE 404 - FILE NOT FOUND HANDLING
*/
void build404Response ( String & content , String file )
{
content + = stdHeader ;
2019-07-11 08:55:31 +00:00
content + = R " =====(</head>
< body >
2019-07-06 13:46:20 +00:00
< h1 > 404 : File Not Found < / h1 >
< p > URI : < b > < i > ) = = = = = " ;
content + = file ;
content + = R " =====(</i></b><br>
Method : ) = = = = = " ;
content + = ( server . method ( ) = = HTTP_GET ) ? " GET " : " POST " ;
content + = " <br>Arguments: " ;
for ( uint8_t i = 0 ; i < server . args ( ) ; i + + ) {
content + = " " + server . argName ( i ) + " : " + server . arg ( i ) + " <br> " ;
}
content + = R " =====(<hr>
< p > Please check the URL . < br >
If OK please try uploading the file from the web content .
< p > Latest web content can be downloaded from < a href = " http://www.mrjones.id.au/afterburner/firmware.html " target = " _blank " > http : //www.mrjones.id.au/afterburner/firmware.html</a>
< h4 class = " throb " >Please ensure you unzip the web page content, then upload all the files contained.</h4>
< p > < a href = " /update " > < button > Upload web content < / button > < / a > < br >
) = = = = = " ;
String SPIFFSinfo ;
2019-07-07 07:18:38 +00:00
listSPIFFS ( " / " , 2 , SPIFFSinfo , 1 ) ;
2019-07-06 13:46:20 +00:00
content + = SPIFFSinfo ;
content + = " </body> " ;
content + = " </html> " ;
}
/***************************************************************************************
* HTTP RESPONSE 500 - SERVER ERROR HANDLING
*/
void build500Response ( String & content , String file )
{
content = stdHeader ;
2019-07-11 08:55:31 +00:00
content + = R " =====(</head>
< body >
2019-07-06 13:46:20 +00:00
< h1 > 500 : Internal Server Error < / h1 >
< h3 class = " throb " >Sorry, cannot open file</h3>
< p > < b > < i > " )===== " ;
content + = file ;
content + = R " =====( " < / i > < / b > exists , but cannot be streamed ?
< hr >
< p > Recommended remedy is to re - format the SPIFFS partition , then reload the web content files .
< br > Latest web content can be downloaded from < a href = " http://www.mrjones.id.au/afterburner/firmware.html " target = " _blank " > http : //www.mrjones.id.au/afterburner/firmware.html</a> <i>(opens in new page)</i>.
2019-07-11 08:55:31 +00:00
< p > To format the SPIFFS partition , press < button class = ' fmt ' onClick = location . assign ( ' / formatspiffs ' ) > Format SPIFFS < / button >
2019-07-06 13:46:20 +00:00
< p > You will then need to upload each file of the web content by using the subsequent " <b>Upload</b> " button .
< hr >
< h4 class = " throb " >Please ensure you unzip the web page content, then upload all the files contained.</h4>
< / body >
< / html >
) = = = = = " ;
}