ota_page.html 13 KB

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