Added upload function for html files.

This commit is contained in:
Magnus 2021-03-27 18:04:07 +01:00
parent 5f04d6e65e
commit 384ec017a8
6 changed files with 324 additions and 42 deletions

146
data/upload.htm Normal file
View File

@ -0,0 +1,146 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Beer Gravity Monitor</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
<!-- START MENU -->
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
<a class="navbar-brand" href="/upload.htm">Beer Gravity Monitor - Missing html files</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<div class="spinner-border text-light" id="spinner" role="status"></div>
</div>
</nav>
<!-- START MAIN INDEX -->
<div class="container">
<hr class="my-4">
<div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert">
<div id="alert-msg">...</div>
<button type="button" id="alert-btn" class="close" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<script type="text/javascript">
function showError( msg ) {
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
function showSuccess( msg ) {
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
$("#alert-btn").click(function(e){
$('.alert').addClass('d-none').removeClass('show')
});
</script>
<div class="row mb-3">
<div class="col-md-8 themed-grid-col bg-light">The listed files below needs to be uploaded to the FileSystem in order for the GUI to work.
You can also flash the LittleFS filesystem but in that case you will loose your device settings. An OTA upgrade will automatically download
the files if they are found in the same location as the firmware.bin. This page is a fallback option.
</div>
<div class="col-md-8 themed-grid-col bg-light"><br><strong>Once all the files are confirmed, please reboot the device for normal operation.</strong></div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">index.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="index">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">device.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="device">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">config.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">about.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div>
</div>
<div class="row mb-3">
<form action="/api/upload" method="post" enctype="multipart/form-data">
<div class="col-md-8 custom-file">
<input type="file" class="custom-file-input" name="name" id="name">
<label class="custom-file-label" for="name">Choose file</label>
</div>
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Upload</button>
</form>
</div>
<hr class="my-4">
</div>
<script type="text/javascript">
window.onload = getUpload;
// Add the following code if you want the name of the file appear on select
$(".custom-file-input").on("change", function() {
var fileName = $(this).val().split("\\").pop();
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
});
function getUpload() {
var url = "/api/upload";
//var url = "/test/upload.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
if( cfg["index"] )
$("#index").text("Completed.");
else
$("#index").text("File is missing.");
if( cfg["device"] )
$("#device").text("Completed.");
else
$("#device").text("File is missing.");
if( cfg["config"] )
$("#config").text("Completed.");
else
$("#config").text("File is missing.");
if( cfg["about"] )
$("#about").text("Completed.");
else
$("#about").text("File is missing.");
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
});
}
</script>
<!-- START FOOTER -->
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div>
</body>
</html>

1
data/upload.min.htm Normal file
View File

@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/upload.htm">Beer Gravity Monitor - Missing html files</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">The listed files below needs to be uploaded to the FileSystem in order for the GUI to work. You can also flash the LittleFS filesystem but in that case you will loose your device settings. An OTA upgrade will automatically download the files if they are found in the same location as the firmware.bin. This page is a fallback option.</div><div class="col-md-8 themed-grid-col bg-light"><br><strong>Once all the files are confirmed, please reboot the device for normal operation.</strong></div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">index.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="index">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">device.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="device">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">config.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">about.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div></div><div class="row mb-3"><form action="/api/upload" method="post" enctype="multipart/form-data"><div class="col-md-8 custom-file"><input type="file" class="custom-file-input" name="name" id="name"> <label class="custom-file-label" for="name">Choose file</label></div><button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Upload</button></form></div><hr class="my-4"></div><script type="text/javascript">function getUpload(){var e="/api/upload";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),e.index?$("#index").text("Completed."):$("#index").text("File is missing."),e.device?$("#device").text("Completed."):$("#device").text("File is missing."),e.config?$("#config").text("Completed."):$("#config").text("File is missing."),e.about?$("#about").text("Completed."):$("#about").text("File is missing.")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getUpload,$(".custom-file-input").on("change",function(){var e=$(this).val().split("\\").pop();$(this).siblings(".custom-file-label").addClass("selected").html(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>

View File

@ -25,17 +25,17 @@ SOFTWARE.
#if defined( EMBED_HTML )
/*INCBIN(IndexHtm, "data/index.htm" );
INCBIN(DeviceHtm, "data/device.htm" );
INCBIN(ConfigHtm, "data/config.htm" );
INCBIN(AboutHtm, "data/about.htm" );*/
// Using minify to reduce memory usage. Reducing RAM memory usage with about 7%
INCBIN(IndexHtm, "data/index.min.htm" );
INCBIN(DeviceHtm, "data/device.min.htm" );
INCBIN(ConfigHtm, "data/config.min.htm" );
INCBIN(AboutHtm, "data/about.min.htm" );
#else
// Minium web interface for uploading htm files
INCBIN(UploadHtm, "data/upload.min.htm" );
#endif
// EOF

View File

@ -31,6 +31,7 @@ SOFTWARE.
#include <ArduinoJson.h>
#include <incbin.h>
#include <ESP8266WiFi.h>
//#define DEBUG_ESP_HTTP_SERVER
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <LittleFS.h>
@ -41,9 +42,11 @@ INCBIN_EXTERN(IndexHtm);
INCBIN_EXTERN(DeviceHtm);
INCBIN_EXTERN(ConfigHtm);
INCBIN_EXTERN(AboutHtm);
#else
INCBIN_EXTERN(UploadHtm);
#endif
WebServer myWebServer;
WebServer myWebServer; // My wrapper class fr webserver functions
ESP8266WebServer server(80);
extern bool sleepModeActive;
@ -84,9 +87,9 @@ void webHandleConfig() {
double temp = myTempSensor.getValueCelcius();
double gravity = calculateGravity( angle, temp );
doc[ CFG_PARAM_ANGLE ] = reduceFloatPrecision( angle);
doc[ CFG_PARAM_GRAVITY ] = reduceFloatPrecision( gravityTemperatureCorrection( gravity, temp ), 4);
doc[ CFG_PARAM_BATTERY ] = reduceFloatPrecision( myBatteryVoltage.getVoltage());
doc[ CFG_PARAM_ANGLE ] = reduceFloatPrecision( angle);
doc[ CFG_PARAM_GRAVITY ] = reduceFloatPrecision( gravityTemperatureCorrection( gravity, temp ), 4);
doc[ CFG_PARAM_BATTERY ] = reduceFloatPrecision( myBatteryVoltage.getVoltage());
#if LOG_LEVEL==6
serializeJson(doc, Serial);
@ -97,6 +100,72 @@ void webHandleConfig() {
server.send(200, "application/json", out.c_str() );
}
//
// Callback from webServer when / has been accessed.
//
void webHandleUpload() {
#if LOG_LEVEL==6
Log.verbose(F("WEB : webServer callback for /api/upload." CR));
#endif
Log.notice(F("WEB : webServer callback for /api/upload." CR));
DynamicJsonDocument doc(100);
doc[ "index" ] = myWebServer.checkHtmlFile( WebServer::HTML_INDEX );
doc[ "device" ] = myWebServer.checkHtmlFile( WebServer::HTML_DEVICE );
doc[ "config" ] = myWebServer.checkHtmlFile( WebServer::HTML_CONFIG );
doc[ "about" ] = myWebServer.checkHtmlFile( WebServer::HTML_ABOUT );
#if LOG_LEVEL==6
serializeJson(doc, Serial);
Serial.print( CR );
#endif
String out;
serializeJson(doc, out);
server.send(200, "application/json", out.c_str() );
}
//
// Callback from webServer when / has been accessed.
//
File uploadFile;
void webHandleUploadFile() {
Log.notice(F("WEB : webServer callback for /api/upload/file." CR));
#if LOG_LEVEL==6
Log.verbose(F("WEB : webServer callback for /api/upload/file." CR));
#endif
HTTPUpload& upload = server.upload();
String f = upload.filename;
bool validFilename = false;
if( f.equalsIgnoreCase("index.min.htm") || f.equalsIgnoreCase("device.min.htm") ||
f.equalsIgnoreCase("config.min.htm") || f.equalsIgnoreCase("about.min.htm") ) {
validFilename = true;
}
Log.notice(F("WEB : webServer callback for /api/upload, receiving file %s, valid=%s." CR), f.c_str(), validFilename?"yes":"no");
if(upload.status == UPLOAD_FILE_START) {
Log.notice(F("WEB : Start upload." CR) );
if( validFilename )
uploadFile = LittleFS.open( f, "w");
} else if(upload.status == UPLOAD_FILE_WRITE) {
Log.notice(F("WEB : Writing upload." CR) );
if(uploadFile)
uploadFile.write(upload.buf, upload.currentSize); // Write the received bytes to the file
} else if(upload.status == UPLOAD_FILE_END){
Log.notice(F("WEB : Finish upload." CR) );
if(uploadFile) {
uploadFile.close();
Log.notice(F("WEB : File uploaded %d bytes." CR), upload.totalSize);
}
server.sendHeader("Location","/");
server.send(303);
} else {
server.send(500, "text/plain", "Couldn't create file.");
}
}
//
// Callback from webServer when / has been accessed.
//
@ -146,15 +215,15 @@ void webHandleStatus() {
double temp = myTempSensor.getValueCelcius();
double gravity = calculateGravity( angle, temp );
doc[ CFG_PARAM_ID ] = myConfig.getID();
doc[ CFG_PARAM_ANGLE ] = reduceFloatPrecision( angle);
doc[ CFG_PARAM_GRAVITY ] = reduceFloatPrecision( gravityTemperatureCorrection( gravity, temp ), 4);
doc[ CFG_PARAM_TEMP_C ] = reduceFloatPrecision( temp, 1);
doc[ CFG_PARAM_TEMP_F ] = reduceFloatPrecision( myTempSensor.getValueFarenheight(), 1);
doc[ CFG_PARAM_BATTERY ] = reduceFloatPrecision( myBatteryVoltage.getVoltage());
doc[ CFG_PARAM_TEMPFORMAT ] = String( myConfig.getTempFormat() );
doc[ CFG_PARAM_SLEEP_MODE ] = sleepModeAlwaysSkip;
doc[ CFG_PARAM_RSSI ] = WiFi.RSSI();
doc[ CFG_PARAM_ID ] = myConfig.getID();
doc[ CFG_PARAM_ANGLE ] = reduceFloatPrecision( angle);
doc[ CFG_PARAM_GRAVITY ] = reduceFloatPrecision( gravityTemperatureCorrection( gravity, temp ), 4);
doc[ CFG_PARAM_TEMP_C ] = reduceFloatPrecision( temp, 1);
doc[ CFG_PARAM_TEMP_F ] = reduceFloatPrecision( myTempSensor.getValueFarenheight(), 1);
doc[ CFG_PARAM_BATTERY ] = reduceFloatPrecision( myBatteryVoltage.getVoltage());
doc[ CFG_PARAM_TEMPFORMAT ] = String( myConfig.getTempFormat() );
doc[ CFG_PARAM_SLEEP_MODE ] = sleepModeAlwaysSkip;
doc[ CFG_PARAM_RSSI ] = WiFi.RSSI();
#if LOG_LEVEL==6
serializeJson(doc, Serial);
Serial.print( CR );
@ -299,14 +368,46 @@ void webHandleConfigHardware() {
server.send(302, "text/plain", "Hardware config updated" );
}
//
// Helper function to check if files exist on file system.
//
const char* WebServer::getHtmlFileName( HtmlFile item ) {
#if LOG_LEVEL==6
Log.verbose(F("WEB : Looking up filename for %d." CR), item);
#endif
switch( item ) {
case HTML_INDEX:
return "index.min.htm";
case HTML_DEVICE:
return "device.min.htm";
case HTML_CONFIG:
return "config.min.htm";
case HTML_ABOUT:
return "about.min.htm";
}
return "";
}
//
// Helper function to check if files exist on file system.
//
bool WebServer::checkHtmlFile( HtmlFile item ) {
const char *fn = getHtmlFileName( item );
#if LOG_LEVEL==6
Log.verbose(F("WEB : Checking for file %s." CR), fn );
#endif
// TODO: We might need to add more checks here like zero file size etc. But for now we only check if the file exist.
return LittleFS.exists( fn );
}
//
// Setup the Web Server callbacks and start it
//
bool WebServer::setupWebServer() {
#if LOG_LEVEL==6
Log.verbose(F("WEB : Setting up web server." CR));
#endif
Log.notice(F("WEB : Web server setup started." CR));
MDNS.begin( myConfig.getMDNS() );
@ -331,34 +432,53 @@ bool WebServer::setupWebServer() {
} );
#else
// Show files in the filessytem at startup
FSInfo fs;
LittleFS.info(fs);
Log.notice( F("File system: Total=%d, Used=%d." CR), fs.totalBytes, fs.usedBytes );
Log.notice( F("WEB : File system Total=%d, Used=%d." CR), fs.totalBytes, fs.usedBytes );
Dir dir = LittleFS.openDir("/");
while( dir.next() ) {
Log.notice( F("File: %s, %d bytes" CR), dir.fileName().c_str(), dir.fileSize() );
Log.notice( F("WEB : File=%s, %d bytes" CR), dir.fileName().c_str(), dir.fileSize() );
}
server.serveStatic("/", LittleFS, "/index.htm" );
server.serveStatic("/index.htm", LittleFS, "/index.htm" );
server.serveStatic("/device.htm", LittleFS, "/device.htm" );
server.serveStatic("/config.htm", LittleFS, "/config.htm" );
server.serveStatic("/about.htm", LittleFS, "/about.htm" );
// Check if the html files exist, if so serve them, else show the static upload page.
if( checkHtmlFile( HTML_INDEX ) && checkHtmlFile( HTML_DEVICE ) && checkHtmlFile( HTML_CONFIG ) && checkHtmlFile( HTML_ABOUT ) ) {
Log.notice(F("WEB : All html files exist, starting in normal mode." CR));
server.serveStatic("/", LittleFS, "/index.min.htm" );
server.serveStatic("/index.htm", LittleFS, "/index.min.htm" );
server.serveStatic("/device.htm", LittleFS, "/device.min.htm" );
server.serveStatic("/config.htm", LittleFS, "/config.min.htm" );
server.serveStatic("/about.htm", LittleFS, "/about.min.htm" );
// Also add the static upload view in case we we have issues that needs to be fixed.
server.on("/upload.htm",[]() {
server.send_P(200, "text/html", (const char*) gUploadHtmData, gUploadHtmSize );
} );
} else {
Log.error(F("WEB : Missing html files, starting with upload UI." CR));
server.on("/",[]() {
server.send_P(200, "text/html", (const char*) gUploadHtmData, gUploadHtmSize );
} );
}
#endif
// Dynamic content
server.on("/api/config", webHandleConfig); // Get config.json
server.on("/api/device", webHandleDevice); // Get device.json
server.on("/api/calibrate", webHandleCalibrate); // Run calibration routine (param id)
server.on("/api/factory", webHandleFactoryReset); // Reset the device
server.on("/api/status", webHandleStatus); // Get the status.json
server.on("/api/clearwifi", webHandleClearWIFI); // Clear wifi settings
server.on("/api/config", HTTP_GET, webHandleConfig); // Get config.json
server.on("/api/device", HTTP_GET, webHandleDevice); // Get device.json
server.on("/api/calibrate", HTTP_GET, webHandleCalibrate); // Run calibration routine (param id)
server.on("/api/factory", HTTP_GET, webHandleFactoryReset); // Reset the device
server.on("/api/status", HTTP_GET, webHandleStatus); // Get the status.json
server.on("/api/clearwifi", HTTP_GET, webHandleClearWIFI); // Clear wifi settings
server.on("/api/upload", HTTP_GET, webHandleUpload); // Get upload.json
server.on("/api/status/sleepmode", webHandleStatusSleepmode);
server.on("/api/config/device", webHandleConfigDevice);
server.on("/api/config/push", webHandleConfigPush);
server.on("/api/config/gravity", webHandleConfigGravity);
server.on("/api/config/hardware", webHandleConfigHardware);
server.on("/api/upload", HTTP_POST, [](){ server.send(200); }, webHandleUploadFile); // File upload data
server.on("/api/status/sleepmode", HTTP_POST, webHandleStatusSleepmode); // Change sleep mode
server.on("/api/config/device", HTTP_POST, webHandleConfigDevice); // Change device settings
server.on("/api/config/push", HTTP_POST, webHandleConfigPush); // Change push settings
server.on("/api/config/gravity", HTTP_POST, webHandleConfigGravity); // Change gravity settings
server.on("/api/config/hardware", HTTP_POST, webHandleConfigHardware); // Change hardware settings
server.onNotFound( []() {
Log.error(F("WEB : URL not found %s received." CR), server.uri().c_str());

View File

@ -29,8 +29,17 @@ SOFTWARE.
// classes
class WebServer {
public:
bool setupWebServer();
void loop();
enum HtmlFile {
HTML_INDEX = 0,
HTML_DEVICE = 1,
HTML_CONFIG = 2,
HTML_ABOUT = 3
};
bool setupWebServer();
void loop();
bool checkHtmlFile( HtmlFile item );
const char* getHtmlFileName( HtmlFile item );
};
// Global instance created

6
test/upload.json Normal file
View File

@ -0,0 +1,6 @@
{
"index": false,
"device": false,
"config": false,
"about": true
}