| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- <!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 src="md5.min.js?v=$COMMIT_HASH"></script>
- <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> → Gets unpacked on the SD-Card (most top folder)</li>
- <li><span style="font-family:monospace">*.bin</span> → gets installed onto the ESP32s program flash</li>
- <li><span style="font-family:monospace">*.tfl/tflite</span> → 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", domainname + "/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 url = domainname + "/ota?task=emptyfirmwaredir";
- xhttp.open("GET", url, true);
- xhttp.send();
- }
- function validateMd5(md5_on_device, callback) {
- const reader = new FileReader();
- reader.onload = (event) => {
- const fileContent = event.target.result;
- md5_on_webbrowser = md5(fileContent);
- console.log("MD5 on device: " + md5_on_device + ", MD5 on web browser: " + md5_on_webbrowser);
- if (md5_on_device == md5_on_webbrowser) {
- console.log("MD5 values are equal");
- callback(true);
- }
- else {
- console.log("MD5 values are NOT equal!");
- callback(false);
- }
- }
- var fileInput = document.getElementById("file_selector").files;
- reader.readAsArrayBuffer(fileInput[0]);
- }
- function extract() {
- 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("The device will now reboot and install the update!");
- document.getElementById("status").innerText = "Status: Installing...";
- firework.launch('Upload completed and validated. The device will now reboot 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 url = domainname + "/ota?task=update&file=" + filePath;
- xhttp.open("GET", url, true);
- xhttp.send();
- }
- function _(el) {
- return document.getElementById(el);
- }
- function upload() {
- document.getElementById("status").innerText = "Status: Uploading...";
- var url = domainname + "/upload/firmware/" + filePath + "?md5";
- 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", url);
- 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);
- if (Math.round(percent) == 100) {
- _("progressBar").value = 0; //will clear progress bar after successful upload
- _("loaded_n_total").innerHTML = "";
- _("status").innerHTML = "Status: Upload completed. Validating file...";
- }
- else {
- _("status").innerHTML = "Status: " + Math.round(percent) + "% uploaded...";
- }
- }
- function completeHandler(event) {
- console.log("Upload completed");
- console.log("Response: " + event.target.responseText);
- try {
- md5_on_device = JSON.parse(event.target.responseText).md5;
- validateMd5(md5_on_device, (result) => {
- if (result == true) {
- _("status").innerHTML = "Status: The uploaded file is valid, installing it...";
- extract();
- }
- else {
- _("status").innerHTML = "Status: The file got corrupted! Please upload it again!";
- firework.launch('Upload failed, the file got corrupted! Please upload it again!', 'danger', 30000);
- document.getElementById("start_OTA_button").disabled = false;
- }
- });
- }
- catch (e) {
- // If the firmware is to old, it will return the file sever page instead of the JSON object with the MD5 sum.
- // In juch case just proceed to keep legacy support.
- console.log("It seems to be a legacy firmware, installing the update without validation!");
- _("status").innerHTML = "Status: It seems to be a legacy firmware, installing the update without validation...";
- 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>
|