Can push SPIFFS files now via the firmware upload browser form.

However only works as a flat file system for now...
This commit is contained in:
Ray Jones 2019-05-21 22:01:42 +10:00
parent 0572acaeca
commit 21cf587798
3 changed files with 154 additions and 22 deletions

View File

@ -110,8 +110,8 @@
#if USE_SPIFFS == 1
#include <SPIFFS.h>
#endif
//#include "esp_system.h"
// SSID & password now stored in NV storage - these are still the default values.
//#define AP_SSID "Afterburner"
//#define AP_PASSWORD "thereisnospoon"
@ -119,7 +119,7 @@
const int FirmwareRevision = 23;
const int FirmwareSubRevision = 0;
const char* FirmwareDate = "12 May 2019";
const char* FirmwareDate = "21 May 2019";
#ifdef ESP32

View File

@ -32,6 +32,7 @@
#include "../Utility/Moderator.h"
#include <WiFiManager.h>
#if USE_SPIFFS == 1
#include <FS.H>
#include <SPIFFS.h>
#endif
#include "../Utility/NVStorage.h"
@ -40,6 +41,9 @@ extern void ShowOTAScreen(int percent=0, bool webpdate=false);
extern WiFiManager wm;
File fsUploadFile; // a File object to temporarily store the received file
int SPIFFSupload = 0;
WebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);
@ -56,6 +60,7 @@ String getContentType(String filename) { // convert the file extension to the MI
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";
else if (filename.endsWith(".bin")) return "application/octet-stream";
return "text/plain";
}
@ -127,6 +132,90 @@ void handleBTCNotFound() {
}
const char* serverIndex = R"=====(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<script>
// global variables
var sendSize;
var Socket;
function _(el) {
return document.getElementById(el);
}
function init() {
Socket = new WebSocket('ws://' + window.location.hostname + ':81/');
Socket.onmessage = function(event){
var response = JSON.parse(event.data);
var key;
for(key in response) {
console.log("JSON decode:", key, response[key]);
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 uploadFile() {
_("cancel").hidden = true;
var file = _("file1").files[0];
sendSize = file.size;
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) {
console.log(event);
_("status").innerHTML = event.target.responseText;
_("progressBar").value = 0;
_("loaded_n_total").innerHTML = "Uploaded " + sendSize + " bytes of " + sendSize;
setTimeout(function () {
window.location.assign("/");
}, 5000);
}
function errorHandler(event) {
console.log(event);
_("status").innerHTML = "Upload Failed";
}
function abortHandler(event) {
console.log(event);
_("status").innerHTML = "Upload Aborted";
}
</script>
<style>
body {font-family: Arial, Helvetica, sans-serif;}
</style>
<title>Afterburner firmware update</title>
</head>
<body onload="javascript:init()">
<h1>Afterburner firmware update</h1>
<form id='upload_form' method='POST' enctype='multipart/form-data'>
<input type='file' name='file1' id='file1'> <BR>
<input type='button' value='Update' onclick="uploadFile()">
<progress id="progressBar" value="0" max="100" style="width:300px;"></progress><BR>
<h3 id="status"></h3>
<p id="loaded_n_total"></p>
<BR>
<input type='button' onclick=window.location.assign("/") value='Cancel' id="cancel">
</form>
</body>
</html>
)=====";
const char* rootIndex = R"=====(
@ -175,7 +264,9 @@ void initWebServer(void) {
server.on("/formatspiffs", handleFormat);
server.on("/tst", HTTP_GET, []() {
rootRedirect();
server.sendHeader("Location","/"); // reselect the update page
server.send(303);
// rootRedirect();
});
// Magical code originally shamelessly lifted from Arduino WebUpdate example, then modified
@ -191,7 +282,8 @@ void initWebServer(void) {
bUpdateAccessed = true;
server.sendHeader("Connection", "close");
server.sendHeader("Cache-Control", "no-cache");
handleFileRead("/uploadfirmware.html");
server.send(200, "text/html", serverIndex);
// handleFileRead("/uploadfirmware.html");
});
server.on("/updatenow", HTTP_GET, []() { // handle attempts to just browse the /updatenow path - force redirect to root
rootRedirect();
@ -199,22 +291,46 @@ void initWebServer(void) {
// actual guts that manages the new firmware upload
server.on("/updatenow", HTTP_POST, []() {
// completion functionality
if(Update.hasError())
server.send(200, "text/plain", "FAIL - Afterburner will reboot shortly");
else
server.send(200, "OK - Afterburner will reboot shortly");
delay(1000);
ESP.restart(); // reboot
if(SPIFFSupload) {
if(SPIFFSupload == 1) {
server.send(200, "OK");
server.sendHeader("Location","/update"); // reselect the update page
server.send(303);
}
else {
server.send(500, "text/plain", "500: couldn't create file");
}
SPIFFSupload = 0;
}
else {
if(Update.hasError())
server.send(200, "text/plain", "FAIL - Afterburner will reboot shortly");
else
server.send(200, "OK - Afterburner will reboot shortly");
delay(1000);
ESP.restart(); // reboot
}
}, []() {
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;
DebugPort.setDebugOutput(true);
DebugPort.printf("Update: %s\r\n", upload.filename.c_str());
if (!Update.begin()) { //start with max available size
Update.printError(DebugPort);
if(filename.endsWith(".bin")) {
DebugPort.printf("Update: %s\r\n", filename.c_str());
if (!Update.begin()) { //start with max available size
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;
//filename = String();
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
// handle upload segments
#if USE_SW_WATCHDOG == 1
feedWatchdog(); // we get stuck here for a while, don't let the watchdog bite!
#endif
@ -222,17 +338,30 @@ void initWebServer(void) {
char JSON[64];
sprintf(JSON, "{\"progress\":%d}", upload.totalSize);
sendWebServerString(JSON); // feedback proper byte count of update
// DebugPort.print(JSON);
}
DebugPort.print(".");
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(DebugPort);
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);
}
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) { //true to set the size to the current progress
DebugPort.printf("Update Success: %u\r\nRebooting...\r\n", upload.totalSize);
} else {
Update.printError(DebugPort);
// handle end of upload
if(SPIFFSupload) {
if(fsUploadFile) {
fsUploadFile.close(); // Close the file again
DebugPort.printf("handleFileUpload Size: %d\r\n", upload.totalSize);
}
}
else {
if (Update.end(true)) { //true to set the size to the current progress
DebugPort.printf("Update Success: %u\r\nRebooting...\r\n", upload.totalSize);
} else {
Update.printError(DebugPort);
}
}
DebugPort.setDebugOutput(false);
bUpdateAccessed = false;

View File

@ -46,6 +46,11 @@ Working so far:
Request a linear change in Hz according to the deviation from the setpoint
* 2 external digital inputs, 2 digital outputs, analogue input
* New Mk2 "Red PCB" that properly fits into an off the shelf case (requires machining)
* Software updates over WiFi:
Native OTA via espota.exe or espota.py (AP or STA mode)
Web browser upload new binary to controller (AP or STA mode)
Direct discovery and download of updates from internet server (STA mode)
* Factory default menu option
To be implemented
@ -55,8 +60,6 @@ To be implemented
* Temperature probe offset or mapping to correct adverse readings.
* MQTT pub/sub
* "fuel gauge" - Integrate pump frequency, assuming a repeatable dose of fuel per pump cycle...
* Expand hardware compatability with different MCU setups. IE Arduino Due/Mega/Pro ESP8266 & ESP32
* Documentation
* Regular Hot Burn cycle (DPF mode!)
* list under construction.....