diff --git a/data/js/updates.js b/data/js/updates.js deleted file mode 100644 index f791ca5..0000000 --- a/data/js/updates.js +++ /dev/null @@ -1,64 +0,0 @@ -function eByID(id) -{ - return document.getElementById(id) -} - -function semverCompare(a, b) -{ - a = a.replace("v","") - b = b.replace("v","") - if (a.startsWith(b + "-")) return -1 - if (b.startsWith(a + "-")) return 1 - return a.localeCompare(b, undefined, { numeric: true, sensitivity: "case", caseFirst: "upper" }) -} - -function getfName(asset) -{ - return asset.browser_download_url.split('/').pop() -} - -async function getLatestGithubRelease(currentVersion) -{ - const jsonData = await (await fetch('https://api.github.com/repos/speeduino/AirBear/releases/latest')).json(); - const latestVersion = jsonData.tag_name; - eByID("latest_release_txt").innerHTML = latestVersion - if(semverCompare(latestVersion, currentVersion) == 1) //Value of 1 means a > b - { - eByID("update_btn").disabled = false - - for(const asset of jsonData.assets) - { - if(asset.name.includes("littlefs")) - { - const newData_url = "http://speeduino.com/fw/AirBear/" + latestVersion + "/" + getfName(asset) - eByID("newData_url").value = newData_url - console.log("Data file: " + newData_url) - } - else - { - const newFW_url = "http://speeduino.com/fw/AirBear/" + latestVersion + "/" + getfName(asset) - eByID("newFW_url").value = newFW_url - console.log("FW file: " + newFW_url) - } - } - } -} - -async function scanWifi() -{ - const s = eByID("ssid") - const jsonData = await (await fetch('/wifi')).json() - for(const network of jsonData.networks) - { - const opt = document.createElement("option"); - opt.value = network.ssid; - opt.text = network.ssid; - s.add(opt) - } -} - -function toggleData() -{ - const dataField = eByID("newData_url") - dataField.disabled = !dataField.disabled -} \ No newline at end of file diff --git a/src/globals.h b/src/globals.h index fbbad87..c3dcbf7 100644 --- a/src/globals.h +++ b/src/globals.h @@ -7,7 +7,7 @@ #include #define PRODUCT_NAME "AirBear" -#define FIRMWARE_VERSION "0.0.6" +#define FIRMWARE_VERSION "0.0.3" #define FAKE_RPM diff --git a/src/main.cpp b/src/main.cpp index 9636e61..e95bd5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,6 @@ void setup() initTimers(); initBLE(); initWiFi(); - updateFromRemote(); //Check for pending remote firmware updates delay(1000); Serial.println("Connection Type: " + String(config.getUChar("connection_type"))); @@ -37,74 +36,88 @@ void setup() { initTCP(); } - if( (config.getUChar("connection_type") == CONNECTION_TYPE_WIFI) ) + + if(updatesPending()) { - //Init file system - if (!SPIFFS.begin(true)) { - Serial.println("An error has occurred while mounting SPIFFS"); - } - initSSE(); - initSerialData(); - - //Init the web server - // Web Server Root URL + //When updates are pending, only show the minimum pages + server.on("/updateStatus", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/json", update_progress_json(request)); + }); server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(SPIFFS, "/index.html", "text/html"); - }); - - server.on("/data", HTTP_GET, [](AsyncWebServerRequest *request) { - String jsonOutput; - serializeJson(readings_JSON, jsonOutput); - //request->send(200, "text/json", JSON.stringify(readings_JSON)); - request->send(200, "text/json", jsonOutput.c_str()); - }); - - server.serveStatic("/", SPIFFS, "/"); - + request->send(200, "text/html", updateInProgressPage()); + }); } else { - //If not using the web dash then the root URL will produce the config page - server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", webConfigRequest(request)); - }); - } + if( (config.getUChar("connection_type") == CONNECTION_TYPE_WIFI) && (updatesPending() == false) ) + { + //Init file system + if (!SPIFFS.begin(true)) { + Serial.println("An error has occurred while mounting SPIFFS"); + } + initSSE(); + initSerialData(); + + //Init the web server + // Web Server Root URL + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(SPIFFS, "/index.html", "text/html"); + }); + + server.on("/data", HTTP_GET, [](AsyncWebServerRequest *request) { + String jsonOutput; + serializeJson(readings_JSON, jsonOutput); + //request->send(200, "text/json", JSON.stringify(readings_JSON)); + request->send(200, "text/json", jsonOutput.c_str()); + }); + + server.serveStatic("/", SPIFFS, "/"); - server.on(WEB_CONFIG_URL, HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", webConfigRequest(request)); - }); - - server.on(WEB_CONFIG_URL, HTTP_POST, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", webConfigPOSTRequest(request)); - }); - - //Updates the firmware AND data from remote URLs - server.on(UPDATE_REMOTE_URL, HTTP_POST, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", saveRemoteFW_URLs(request)); - ESP.restart(); - }); - //Scan the wifi networks and return them as JSON - server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/json", scanWifi(request)); - }); - - server.on(UPDATE_DATA_UPLOAD_URL, HTTP_POST, [](AsyncWebServerRequest *request) { + } + else + { + //If not using the web dash then the root URL will produce the config page + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/html", webConfigRequest(request)); + }); + } + + server.on(WEB_CONFIG_URL, HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/html", webConfigRequest(request)); + }); + + server.on(WEB_CONFIG_URL, HTTP_POST, [](AsyncWebServerRequest *request) { + request->send(200, "text/html", webConfigPOSTRequest(request)); + }); + + //Updates the firmware AND data from remote URLs + server.on(UPDATE_REMOTE_URL, HTTP_POST, [](AsyncWebServerRequest *request) { + request->send(200, "text/html", saveRemoteFW_URLs(request)); + delay(1000); //Wait 1 second to allow the page to be sent before restarting + ESP.restart(); + }); + //Scan the wifi networks and return them as JSON + server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/json", scanWifi(request)); + }); + + server.on(UPDATE_DATA_UPLOAD_URL, HTTP_POST, [](AsyncWebServerRequest *request) { + //This runs when the uplaod is completed + partitionUploadComplete(request); + },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + //This runs each time a new chunk is received + partitionUploadChunk(request, filename, index, data, len, final, U_SPIFFS); + } + ); + server.on(UPDATE_FW_UPLOAD_URL, HTTP_POST, [](AsyncWebServerRequest *request) { //This runs when the uplaod is completed partitionUploadComplete(request); - },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { //This runs each time a new chunk is received - partitionUploadChunk(request, filename, index, data, len, final, U_SPIFFS); - } - ); - server.on(UPDATE_FW_UPLOAD_URL, HTTP_POST, [](AsyncWebServerRequest *request) { - //This runs when the uplaod is completed - partitionUploadComplete(request); - },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - //This runs each time a new chunk is received - partitionUploadChunk(request, filename, index, data, len, final, U_FLASH); - } - ); - + partitionUploadChunk(request, filename, index, data, len, final, U_FLASH); + } + ); + } // Start server @@ -116,6 +129,7 @@ void setup() server.begin(); + updateFromRemote(); //Check for pending remote firmware updates //By default the ESP32-C3 will output a bunch of diag messages on bootup over UART. //This messes up the secondary serial on the Speeduino so these bootup messages are disabled. diff --git a/src/static/static_html.cpp b/src/static/static_html.cpp new file mode 100644 index 0000000..a346ab3 --- /dev/null +++ b/src/static/static_html.cpp @@ -0,0 +1,7 @@ +#include "static_html.h" + + +String staticHTML_head() +{ + return String(""); +} \ No newline at end of file diff --git a/src/static/static_html.h b/src/static/static_html.h new file mode 100644 index 0000000..9c94d38 --- /dev/null +++ b/src/static/static_html.h @@ -0,0 +1,8 @@ +#ifndef STATIC_HTML_H +#define STATIC_HTML_H + +#include "../globals.h" + +String staticHTML_head(); + +#endif \ No newline at end of file diff --git a/src/static/static_js.cpp b/src/static/static_js.cpp new file mode 100644 index 0000000..b0d59c5 --- /dev/null +++ b/src/static/static_js.cpp @@ -0,0 +1,11 @@ +#include "static_js.h" + +String staticJS_updates() +{ + String js = ""; + js += ""; + + return js; +} \ No newline at end of file diff --git a/src/static/static_js.h b/src/static/static_js.h new file mode 100644 index 0000000..39fb514 --- /dev/null +++ b/src/static/static_js.h @@ -0,0 +1,8 @@ +#ifndef STATIC_JS_H +#define STATIC_JS_H + +#include "../globals.h" + +String staticJS_updates(); + +#endif \ No newline at end of file diff --git a/src/static/static_js.js b/src/static/static_js.js new file mode 100644 index 0000000..ade574b --- /dev/null +++ b/src/static/static_js.js @@ -0,0 +1,102 @@ +//This file is uglified using UglifyJS 3 and the config file within this directory. +//The output of this is placed into static_js.cpp +//This can be generaetd with the cmd: uglifyjs --config-file uglifyjs.config.json static_js.js + +function getElementByID(id) +{ + return document.getElementById(id) +} + + +function semverCompare(a, b) +{ + a = a.replace('v','') + b = b.replace('v','') + if (a.startsWith(b + '-')) return -1 + if (b.startsWith(a + '-')) return 1 + return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'case', caseFirst: 'upper' }) +} + +function getfileName(asset) +{ + return asset.browser_download_url.split('/').pop() +} + +async function getLatestGithubRelease(currentVersion) +{ + const jsonData = await (await fetch('https://api.github.com/repos/speeduino/AirBear/releases/latest')).json(); + const latestVersion = jsonData.tag_name; + getElementByID('latest_release_txt').innerHTML = latestVersion + if(semverCompare(latestVersion, currentVersion) == 1) //Value of 1 means a > b + { + getElementByID('update_btn').disabled = false + + for(const asset of jsonData.assets) + { + if(asset.name.includes('littlefs')) + { + const newData_url = 'http://speeduino.com/fw/AirBear/' + latestVersion + '/' + getfileName(asset) + getElementByID('newData_url').value = newData_url + console.log("Data file: " + newData_url) + } + else + { + const newFW_url = 'http://speeduino.com/fw/AirBear/' + latestVersion + '/' + getfileName(asset) + getElementByID('newFW_url').value = newFW_url + console.log("FW file: " + newFW_url) + } + } + } +} + +async function scanWifi() +{ + const s = getElementByID('ssid') + const jsonData = await (await fetch('/wifi')).json() + for(const network of jsonData.networks) + { + const opt = document.createElement('option'); + opt.value = network.ssid; + opt.text = network.ssid; + s.add(opt) + } +} + +function toggleData() +{ + const dataField = getElementByID('newData_url') + dataField.disabled = !dataField.disabled +} + +function setInnerHTML(id, val) +{ + id.innerHTML = val +} + +function updateProgress() +{ + setTimeout(async () => { + let jsonData + try { + const response = await fetch('/updateStatus') + jsonData = await response.json(); + } + catch (error) + { + console.log(error) + updateProgress() + } + + if(jsonData) + { + setInnerHTML(getElementByID('updateStatus'), jsonData.updateStatus) + setInnerHTML(getElementByID('updateComplete'), jsonData.updateProgress) + setInnerHTML(getElementByID('updateSize'), jsonData.updateSize) + const percentComplete = Math.floor((jsonData.updateProgress / jsonData.updateSize) * 100) + setInnerHTML(getElementByID('updatePercent'), percentComplete) + console.log(percentComplete) + if(percentComplete >= 98) { window.location.href = '/' } + else { updateProgress() } + } + }, 1500); +} diff --git a/src/static/uglifyjs.config.json b/src/static/uglifyjs.config.json new file mode 100644 index 0000000..141b8e5 --- /dev/null +++ b/src/static/uglifyjs.config.json @@ -0,0 +1,116 @@ +{ + parse: { + bare_returns : false, + expression : false, + filename : null, + html5_comments : true, + module : false, + shebang : true, + strict : false, + toplevel : null + }, + compress: { + annotations : true, + arguments : true, + arrows : true, + assignments : true, + awaits : true, + booleans : true, + collapse_vars : true, + comparisons : true, + conditionals : true, + dead_code : true, + default_values : true, + directives : true, + drop_console : true, + drop_debugger : true, + evaluate : true, + expression : false, + functions : true, + global_defs : true, + hoist_exports : true, + hoist_funs : true, + hoist_props : true, + hoist_vars : false, + ie : false, + if_return : true, + imports : true, + inline : true, + join_vars : true, + keep_fargs : false, + keep_fnames : false, + keep_infinity : false, + loops : true, + merge_vars : true, + module : false, + negate_iife : true, + objects : true, + optional_chains : true, + passes : 2, + properties : true, + pure_funcs : null, + pure_getters : "strict", + reduce_funcs : true, + reduce_vars : true, + rests : true, + sequences : true, + side_effects : true, + spreads : true, + strings : true, + switches : true, + templates : true, + top_retain : null, + toplevel : false, + typeofs : true, + unsafe : false, + unsafe_comps : false, + unsafe_Function : false, + unsafe_math : false, + unsafe_proto : false, + unsafe_regexp : false, + unsafe_undefined : false, + unused : true, + varify : true, + webkit : false, + yields : true + }, + mangle: { + eval : false, + ie : false, + keep_fargs : false, + keep_fnames : false, + properties : false, + reserved : ['updateProgress','getLatestGithubRelease','scanWifi','toggleData'], + toplevel : true, + v8 : false, + webkit : false + }, + output: { + annotations : false, + ascii_only : false, + beautify : false, + braces : false, + comments : /@license|@preserve|^!/, + extendscript : false, + galio : false, + ie : false, + indent_level : 4, + indent_start : 0, + inline_script : true, + keep_quoted_props: false, + max_line_len : false, + preamble : null, + preserve_line : false, + quote_keys : false, + quote_style : 3, + semicolons : true, + shebang : true, + source_map : null, + v8 : false, + webkit : false, + width : 80, + wrap_iife : false + }, + wrap: false + } + \ No newline at end of file diff --git a/src/updater.cpp b/src/updater.cpp index df5ad6d..747580b 100644 --- a/src/updater.cpp +++ b/src/updater.cpp @@ -6,17 +6,17 @@ This file contains routines for remotely updating the LittleFS partition that co #include "timer.h" #include "config.h" #include "timer.h" - -#include +#include "static/static_js.h" +#include "static/static_html.h" #include -#include - -#include #include - #include +uint32_t updateSize = 0; +uint32_t updateProgress = 0; +String updateComment = ""; + //Load a data partition from an uploaded file void partitionUploadChunk(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final, uint8_t partitionType) { @@ -82,21 +82,55 @@ void partitionUploadComplete(AsyncWebServerRequest *request) String saveRemoteFW_URLs(AsyncWebServerRequest *request) { - String resultPage = "Update page"; + if (request->hasParam("newFW_url", true)) { config.putString("newFW_url", request->getParam("newFW_url", true)->value()); } if (request->hasParam("newData_url", true)) { - HTTPClient client; config.putString("newData_url", request->getParam("newData_url", true)->value()); } - return resultPage; + + return updateInProgressPage(); +} + +String updateInProgressPage() +{ + //Create the updates page + String updatePage = staticHTML_head(); + updatePage += staticJS_updates(); + updatePage += ""; + updatePage += "Current Status:
"; + updatePage += "Current Progress:
"; + updatePage += "Update Size:
"; + updatePage += "Update Completion: %
"; + updatePage += ""; + + return updatePage; } void update_progress(int cur, int total) { Serial.printf("CALLBACK: HTTP update process at %d of %d bytes...\n", cur, total); + updateSize = total; + updateProgress = cur; +} + +bool updatesPending() +{ + return ( (config.getString("newFW_url", "") != "") || (config.getString("newData_url", "") != "") ); +} + +String update_progress_json(AsyncWebServerRequest *request) +{ + JsonDocument json; + json["updateSize"] = updateSize; + json["updateProgress"] = updateProgress; + json["updateStatus"] = updateComment; + + String output; + serializeJson(json, output); + return output; } void updateFromRemote() @@ -113,6 +147,7 @@ void updateFromRemote() httpUpdate.onProgress(update_progress); config.putString("newFW_url", ""); + updateComment = "Updating Firmware"; t_httpUpdate_return ret = httpUpdate.update(client, newFW_url); switch (ret) @@ -135,6 +170,7 @@ void updateFromRemote() Serial.println(newData_url); httpUpdate.onProgress(update_progress); + updateComment = "Updating Filesystem"; t_httpUpdate_return ret = httpUpdate.updateSpiffs(client, newData_url); config.putString("newData_url", ""); diff --git a/src/updater.h b/src/updater.h index f480042..944f942 100644 --- a/src/updater.h +++ b/src/updater.h @@ -12,5 +12,8 @@ void partitionUploadChunk(AsyncWebServerRequest *request, String filename, size_ void partitionUploadComplete(AsyncWebServerRequest *request); String saveRemoteFW_URLs(AsyncWebServerRequest *request); void updateFromRemote(); +String update_progress_json(AsyncWebServerRequest *request); +bool updatesPending(); +String updateInProgressPage(); #endif \ No newline at end of file diff --git a/src/web_config.cpp b/src/web_config.cpp index e5d2a4b..4fc7bb2 100644 --- a/src/web_config.cpp +++ b/src/web_config.cpp @@ -2,12 +2,16 @@ #include "updater.h" #include "config.h" +#include "static/static_html.h" +#include "static/static_js.h" + String webConfigRequest(AsyncWebServerRequest *request) { String response = ""; - response += "Speeduino Web Config"; - response += ""; - //response += ""; + response += staticHTML_head(); + response += "Speeduino Web Config"; + //response += ""; + response += staticJS_updates(); response += "