ota_page.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. <!DOCTYPE html>
  2. <html lang="en" xml:lang="en">
  3. <head>
  4. <title>OTA Update</title>
  5. <meta charset="UTF-8" />
  6. <style>
  7. h1 {font-size: 2em;}
  8. h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
  9. h3 {font-size: 1.2em;}
  10. p {font-size: 1em;}
  11. input[type=file] {
  12. width: 660px;
  13. padding: 5px 0px;
  14. display: inline-block;
  15. font-size: 16px;
  16. }
  17. .button {
  18. padding: 5px 10px;
  19. width: 205px;
  20. font-size: 16px;
  21. }
  22. </style>
  23. <link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
  24. <script type="text/javascript" src="jquery-3.6.0.min.js?v=$COMMIT_HASH"></script>
  25. <script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
  26. <script type="text/javascript" src="firework.js?v=$COMMIT_HASH"></script>
  27. </head>
  28. <body style="font-family: arial; padding: 0px 10px;">
  29. <h2>OTA Update</h2>
  30. <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>
  31. <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>
  32. <h3>Update</h3>
  33. 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>
  34. <p>Alternatively the following file types are supported:</p>
  35. <ul>
  36. <li><span style="font-family:monospace">*.zip</span> &rarr; Gets unpacked on the SD-Card (most top folder)</li>
  37. <li><span style="font-family:monospace">*.bin</span> &rarr; gets installed onto the ESP32s program flash</li>
  38. <li><span style="font-family:monospace">*.tfl/tflite</span> &rarr; Gets copied to your <i>SD-Card/config</i></li>
  39. </ul>
  40. <p><b>Do not reload this page or switch to another page while the update is in progress!</b><br></p>
  41. <form id="upload_form" enctype="multipart/form-data" method="post">
  42. <input type="file" accept=".bin,.zip,.tfl,.tflite" name="file_selector" id="file_selector" onchange="validate_file()"><br><br>
  43. <button class="button" id="start_OTA_button" type="button" onclick="start_OTA()" disabled>Upload And Install</button>
  44. <br><br>
  45. <progress id="progressBar" value="0" max="100" style="width:600px;"></progress>
  46. <h3><span id="status">Status: Idle</span></h3>
  47. <p id="loaded_n_total"></p>
  48. </form>
  49. <script language="JavaScript">
  50. var domainname = getDomainname();
  51. var action_runtime = 0;
  52. /* Max size of an individual file. Make sure this
  53. * value is same as that set in server_file.c */
  54. var MAX_FILE_SIZE = 8000*1024;
  55. var MAX_FILE_SIZE_STR = "8MB";
  56. function validate_file() {
  57. document.getElementById("start_OTA_button").disabled = true;
  58. var fileInput = document.getElementById("file_selector").files;
  59. var filepath = document.getElementById("file_selector").value;
  60. console.log("filepath: " + filepath);
  61. filename = filepath.split(/[\\\/]/).pop();
  62. console.log("filename: " + filename);
  63. /* Various checks on filename length and file size */
  64. if (fileInput.length == 0) {
  65. firework.launch('No file selected!', 'danger', 30000);
  66. return;
  67. } else if (filename.length == 0) {
  68. firework.launch('File path on server is not set!', 'danger', 30000);
  69. return;
  70. } else if (filename.length > 100) {
  71. firework.launch('Filename is too long! Max 100 characters.', 'danger', 30000);
  72. return;
  73. } else if (filename.indexOf(' ') >= 0) {
  74. firework.launch('Filename can not have spaces!', 'danger', 30000);
  75. return;
  76. } else if (filename[filename.length-1] == '/') {
  77. firework.launch('Filename not specified after path!', 'danger', 30000);
  78. return;
  79. } else if (fileInput[0].size > MAX_FILE_SIZE) {
  80. firework.launch("File size must be less than " + MAX_FILE_SIZE_STR + "!", 'danger', 30000);
  81. return;
  82. }
  83. /* Check if the fillename matches our expected pattern
  84. * - AI-on-the-edge-device__update__*.zip
  85. * - firmware__*.bin
  86. * - *.ftl
  87. * - *.tflite */
  88. if ( /(^AI-on-the-edge-device__update__)[a-zRC0-9()_\-.]*(\.zip$)/i.test(filename) || // OK
  89. ( /(^AI-on-the-edge-device__firmware)[a-zRC0-9()_\-.]*(\.bin$)/i.test(filename)) ||
  90. ( /[a-zRC0-9()_\-.]*(\.tfl$)/i.test(filename)) ||
  91. ( /[a-zRC0-9()_\-.]*(\.tflite$)/i.test(filename))) {
  92. firework.launch('Great, the filename matches our expectations. You can now press "Upload and install".', 'success', 5000);
  93. }
  94. /* Following filenames are acceptiod but not prefered:
  95. * - *.bin
  96. * - *.zip */
  97. else if (filename.endsWith(".zip") || filename.endsWith(".bin")) { // Warning but still accepted
  98. firework.launch('The filename does not match the suggested filename pattern, but is nevertheless accepted. You can now press "Upload and install".', 'warning', 10000);
  99. }
  100. /* Any other file name format is not accepted */
  101. else { // invalid
  102. firework.launch('The filename does not match our expectations!', 'danger', 30000);
  103. return;
  104. }
  105. document.getElementById("start_OTA_button").disabled = false;
  106. }
  107. function start_OTA() {
  108. document.getElementById("start_OTA_button").disabled = true;
  109. var file_name = document.getElementById("file_selector").value;
  110. document.getElementById("file_selector").disabled = true;
  111. file_name = file_name.split(/[\\\/]/).pop();
  112. document.getElementById("status").innerText = "Status: File selected";
  113. prepareOnServer();
  114. }
  115. function doRebootAfterUpdate() {
  116. var xhttp = new XMLHttpRequest();
  117. xhttp.open("GET", "/reboot", true);
  118. xhttp.send();
  119. }
  120. function prepareOnServer() {
  121. document.getElementById("status").innerText = "Status: Preparing device...";
  122. var xhttp = new XMLHttpRequest();
  123. var file_name = document.getElementById("file_selector").value;
  124. filePath = file_name.split(/[\\\/]/).pop();
  125. /* first delete the old firmware AND empty the /firmware directory*/
  126. xhttp.onreadystatechange = function() {
  127. if (xhttp.readyState == 4) {
  128. if (xhttp.status == 200) {
  129. /* keine Reaktion, damit sich das Dokument nicht ändert */
  130. upload();
  131. } else if (xhttp.status == 0) {
  132. firework.launch('Server closed the connection abruptly!', 'danger', 30000);
  133. } else {
  134. firework.launch('An error occured: ' + xhttp.responseText, 'danger', 30000);
  135. }
  136. }
  137. };
  138. var _toDo = domainname + "/ota?task=emptyfirmwaredir";
  139. xhttp.open("GET", _toDo, true);
  140. xhttp.send();
  141. }
  142. function extract() {
  143. document.getElementById("status").innerText = "Status: Processing on device...";
  144. var xhttp = new XMLHttpRequest();
  145. /* first delete the old firmware */
  146. xhttp.onreadystatechange = function() {
  147. if (xhttp.readyState == 4) {
  148. if (xhttp.status == 200) {
  149. document.cookie = "page=overview.html?v=$COMMIT_HASH" + "; path=/"; // Make sure after the reboot we go to the overview page
  150. if (xhttp.responseText.startsWith("reboot")) { // Reboot required
  151. console.log("Upload completed, the device will now restart and install the update!");
  152. document.getElementById("status").innerText = "Status: Installing...";
  153. firework.launch('Upload completed, the device will now restart and install the update', 'success', 5000);
  154. /* Tell it to reboot */
  155. doRebootAfterUpdate();
  156. action_runtime = 0;
  157. updateTimer = setInterval(function() {
  158. action_runtime += 1;
  159. console.log("Waiting: " + action_runtime + "s");
  160. _("progressBar").value = Math.round(action_runtime);
  161. if (action_runtime > 10) { // After 10 seconds, start to check if we are up again
  162. /* Check if the device is up again and forward to index page if so */
  163. fetch('reboot_page.html?v=$COMMIT_HASH&' + Math.random(), {mode: 'no-cors'}).then(
  164. r=>{parent.location.href=('index.html?v=$COMMIT_HASH');}
  165. )
  166. }
  167. if (action_runtime > 100) { // We reached 300 seconds but device is not ready yet
  168. 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);
  169. clearInterval(updateTimer);
  170. }
  171. }, 3000);
  172. }
  173. else // No reboot required
  174. {
  175. document.getElementById("status").innerText = "Status: Update completed";
  176. firework.launch('Update completed!', 'success', 5000);
  177. document.getElementById("file_selector").disabled = false;
  178. }
  179. } else if (xhttp.status == 0) {
  180. firework.launch('Server closed the connection abruptly!', 'danger', 30000);
  181. } else {
  182. firework.launch('An error occured: ' + xhttp.responseText, 'danger', 30000);
  183. }
  184. }
  185. };
  186. var file_name = document.getElementById("file_selector").value;
  187. filePath = file_name.split(/[\\\/]/).pop();
  188. var _toDo = domainname + "/ota?task=update&file=" + filePath;
  189. xhttp.open("GET", _toDo, true);
  190. xhttp.send();
  191. }
  192. function _(el) {
  193. return document.getElementById(el);
  194. }
  195. function upload() {
  196. document.getElementById("status").innerText = "Status: Uploading...";
  197. var upload_path = "/upload/firmware/" + filePath;
  198. var file = _("file_selector").files[0];
  199. var formdata = new FormData();
  200. formdata.append("file_selector", file);
  201. var ajax = new XMLHttpRequest();
  202. ajax.upload.addEventListener("progress", progressHandler, false);
  203. ajax.addEventListener("load", completeHandler, false);
  204. ajax.addEventListener("error", errorHandler, false);
  205. ajax.addEventListener("abort", abortHandler, false);
  206. ajax.open("POST", upload_path);
  207. ajax.send(file);
  208. }
  209. function progressHandler(event) {
  210. _("loaded_n_total").innerHTML = "Uploaded " + (event.loaded / 1024 / 1024).toFixed(2) +
  211. " MB of " + (event.total / 1024/ 1024).toFixed(2) + " MB";
  212. var percent = (event.loaded / event.total) * 100;
  213. _("progressBar").value = Math.round(percent);
  214. _("status").innerHTML = "Status: " + Math.round(percent) + "% uploaded. Please wait...";
  215. }
  216. function completeHandler(event) {
  217. _("status").innerHTML = "Status: " + event.target.responseText;
  218. _("progressBar").value = 0; //will clear progress bar after successful upload
  219. _("loaded_n_total").innerHTML = "";
  220. extract();
  221. }
  222. function errorHandler(event) {
  223. _("status").innerHTML = "Status: Upload Failed";
  224. firework.launch('Upload failed!', 'danger', 30000);
  225. document.getElementById("file_selector").disabled = false;
  226. }
  227. function abortHandler(event) {
  228. _("status").innerHTML = "Status: Upload Aborted";
  229. firework.launch('Upload aborted!', 'danger', 30000);
  230. document.getElementById("file_selector").disabled = false;
  231. }
  232. </script>
  233. </body>
  234. </html>