Files
AI-on-the-edge-device/sd-card/html/ota_page.html
CaCO3 d4a0ad20ff Restructure menus (#2921)
* renamed config pages
* restructured menu
2024-02-17 00:40:46 +01:00

294 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en" xml:lang="en">
<head>
<title>OTA Update</title>
<meta charset="UTF-8" />
<style>
h1 {font-size: 2em;}
h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
h3 {font-size: 1.2em;}
p {font-size: 1em;}
input[type=file] {
width: 660px;
padding: 5px 0px;
display: inline-block;
font-size: 16px;
}
.button {
padding: 5px 10px;
width: 205px;
font-size: 16px;
}
</style>
<link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
<script type="text/javascript" src="jquery-3.6.0.min.js?v=$COMMIT_HASH"></script>
<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
<script type="text/javascript" src="firework.js?v=$COMMIT_HASH"></script>
</head>
<body style="font-family: arial; padding: 0px 10px;">
<h2>OTA Update</h2>
<p>Check the <a href="https://github.com/jomjol/AI-on-the-edge-device/releases" target=_blank>Release Page</a> to see if there is an update available. </p>
<p>You can also automatically get notified about a release, see <a href="https://jomjol.github.io/AI-on-the-edge-device-docs/New-Releases-Notification" target=_blank>Release Notifications</a>.</p>
<h3>Update</h3>
On the <a href="https://github.com/jomjol/AI-on-the-edge-device/releases" target=_blank>Release Page</a>, pick the <i><span style="font-family:monospace">AI-on-the-edge-device__update__*.zip</span></i> file!</p>
<p>Alternatively the following file types are supported:</p>
<ul>
<li><span style="font-family:monospace">*.zip</span> &rarr; Gets unpacked on the SD-Card (most top folder)</li>
<li><span style="font-family:monospace">*.bin</span> &rarr; gets installed onto the ESP32s program flash</li>
<li><span style="font-family:monospace">*.tfl/tflite</span> &rarr; Gets copied to your <i>SD-Card/config</i></li>
</ul>
<p><b>Do not reload this page or switch to another page while the update is in progress!</b><br></p>
<form id="upload_form" enctype="multipart/form-data" method="post">
<input type="file" accept=".bin,.zip,.tfl,.tflite" name="file_selector" id="file_selector" onchange="validate_file()"><br><br>
<button class="button" id="start_OTA_button" type="button" onclick="start_OTA()" disabled>Upload And Install</button>
<br><br>
<progress id="progressBar" value="0" max="100" style="width:600px;"></progress>
<h3><span id="status">Status: Idle</span></h3>
<p id="loaded_n_total"></p>
</form>
<script language="JavaScript">
var domainname = getDomainname();
var action_runtime = 0;
/* Max size of an individual file. Make sure this
* value is same as that set in server_file.c */
var MAX_FILE_SIZE = 8000*1024;
var MAX_FILE_SIZE_STR = "8MB";
function validate_file() {
document.getElementById("start_OTA_button").disabled = true;
var fileInput = document.getElementById("file_selector").files;
var filepath = document.getElementById("file_selector").value;
console.log("filepath: " + filepath);
filename = filepath.split(/[\\\/]/).pop();
console.log("filename: " + filename);
/* Various checks on filename length and file size */
if (fileInput.length == 0) {
firework.launch('No file selected!', 'danger', 30000);
return;
} else if (filename.length == 0) {
firework.launch('File path on server is not set!', 'danger', 30000);
return;
} else if (filename.length > 100) {
firework.launch('Filename is too long! Max 100 characters.', 'danger', 30000);
return;
} else if (filename.indexOf(' ') >= 0) {
firework.launch('Filename can not have spaces!', 'danger', 30000);
return;
} else if (filename[filename.length-1] == '/') {
firework.launch('Filename not specified after path!', 'danger', 30000);
return;
} else if (fileInput[0].size > MAX_FILE_SIZE) {
firework.launch("File size must be less than " + MAX_FILE_SIZE_STR + "!", 'danger', 30000);
return;
}
/* Check if the fillename matches our expected pattern
* - AI-on-the-edge-device__update__*.zip
* - firmware__*.bin
* - *.ftl
* - *.tflite */
if ( /(^AI-on-the-edge-device__update__)[a-zRC0-9()_\-.]*(\.zip$)/i.test(filename) || // OK
( /(^AI-on-the-edge-device__firmware)[a-zRC0-9()_\-.]*(\.bin$)/i.test(filename)) ||
( /[a-zRC0-9()_\-.]*(\.tfl$)/i.test(filename)) ||
( /[a-zRC0-9()_\-.]*(\.tflite$)/i.test(filename))) {
firework.launch('Great, the filename matches our expectations. You can now press "Upload and install".', 'success', 5000);
}
/* Following filenames are acceptiod but not prefered:
* - *.bin
* - *.zip */
else if (filename.endsWith(".zip") || filename.endsWith(".bin")) { // Warning but still accepted
firework.launch('The filename does not match the suggested filename pattern, but is nevertheless accepted. You can now press "Upload and install".', 'warning', 10000);
}
/* Any other file name format is not accepted */
else { // invalid
firework.launch('The filename does not match our expectations!', 'danger', 30000);
return;
}
document.getElementById("start_OTA_button").disabled = false;
}
function start_OTA() {
document.getElementById("start_OTA_button").disabled = true;
var file_name = document.getElementById("file_selector").value;
document.getElementById("file_selector").disabled = true;
file_name = file_name.split(/[\\\/]/).pop();
document.getElementById("status").innerText = "Status: File selected";
prepareOnServer();
}
function doRebootAfterUpdate() {
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/reboot", true);
xhttp.send();
}
function prepareOnServer() {
document.getElementById("status").innerText = "Status: Preparing device...";
var xhttp = new XMLHttpRequest();
var file_name = document.getElementById("file_selector").value;
filePath = file_name.split(/[\\\/]/).pop();
/* first delete the old firmware AND empty the /firmware directory*/
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
/* keine Reaktion, damit sich das Dokument nicht ändert */
upload();
} else if (xhttp.status == 0) {
firework.launch('Server closed the connection abruptly!', 'danger', 30000);
} else {
firework.launch('An error occured: ' + xhttp.responseText, 'danger', 30000);
}
}
};
var _toDo = domainname + "/ota?task=emptyfirmwaredir";
xhttp.open("GET", _toDo, true);
xhttp.send();
}
function extract() {
document.getElementById("status").innerText = "Status: Processing on device...";
var xhttp = new XMLHttpRequest();
/* first delete the old firmware */
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
document.cookie = "page=overview.html?v=$COMMIT_HASH" + "; path=/"; // Make sure after the reboot we go to the overview page
if (xhttp.responseText.startsWith("reboot")) { // Reboot required
console.log("Upload completed, the device will now restart and install the update!");
document.getElementById("status").innerText = "Status: Installing...";
firework.launch('Upload completed, the device will now restart and install the update', 'success', 5000);
/* Tell it to reboot */
doRebootAfterUpdate();
action_runtime = 0;
updateTimer = setInterval(function() {
action_runtime += 1;
console.log("Waiting: " + action_runtime + "s");
_("progressBar").value = Math.round(action_runtime);
if (action_runtime > 10) { // After 10 seconds, start to check if we are up again
/* Check if the device is up again and forward to index page if so */
fetch('reboot_page.html?v=$COMMIT_HASH&' + Math.random(), {mode: 'no-cors'}).then(
r=>{parent.location.href=('index.html?v=' + Math.random());}
)
}
if (action_runtime > 100) { // We reached 300 seconds but device is not ready yet
firework.launch("The device seems not do be up again, or maybe we missed it. Try to reload this page or reset the device!", 'danger', 30000);
clearInterval(updateTimer);
}
}, 3000);
}
else // No reboot required
{
document.getElementById("status").innerText = "Status: Update completed";
firework.launch('Update completed!', 'success', 5000);
document.getElementById("file_selector").disabled = false;
}
} else if (xhttp.status == 0) {
firework.launch('Server closed the connection abruptly!', 'danger', 30000);
} else {
firework.launch('An error occured: ' + xhttp.responseText, 'danger', 30000);
}
}
};
var file_name = document.getElementById("file_selector").value;
filePath = file_name.split(/[\\\/]/).pop();
var _toDo = domainname + "/ota?task=update&file=" + filePath;
xhttp.open("GET", _toDo, true);
xhttp.send();
}
function _(el) {
return document.getElementById(el);
}
function upload() {
document.getElementById("status").innerText = "Status: Uploading...";
var upload_path = "/upload/firmware/" + filePath;
var file = _("file_selector").files[0];
var formdata = new FormData();
formdata.append("file_selector", file);
var ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", progressHandler, false);
ajax.addEventListener("load", completeHandler, false);
ajax.addEventListener("error", errorHandler, false);
ajax.addEventListener("abort", abortHandler, false);
ajax.open("POST", upload_path);
ajax.send(file);
}
function progressHandler(event) {
_("loaded_n_total").innerHTML = "Uploaded " + (event.loaded / 1024 / 1024).toFixed(2) +
" MB of " + (event.total / 1024/ 1024).toFixed(2) + " MB";
var percent = (event.loaded / event.total) * 100;
_("progressBar").value = Math.round(percent);
_("status").innerHTML = "Status: " + Math.round(percent) + "% uploaded. Please wait...";
}
function completeHandler(event) {
_("status").innerHTML = "Status: " + event.target.responseText;
_("progressBar").value = 0; //will clear progress bar after successful upload
_("loaded_n_total").innerHTML = "";
extract();
}
function errorHandler(event) {
_("status").innerHTML = "Status: Upload Failed";
firework.launch('Upload failed!', 'danger', 30000);
document.getElementById("file_selector").disabled = false;
}
function abortHandler(event) {
_("status").innerHTML = "Status: Upload Aborted";
firework.launch('Upload aborted!', 'danger', 30000);
document.getElementById("file_selector").disabled = false;
}
</script>
</body>
</html>