index.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>ArduPilot Custom Firmware Builder</title>
  6. <meta name="description"
  7. content="ArduPilot Custom Firmware Builder. It allows to build custom ArduPilot firmware by selecting the wanted features.">
  8. <meta name="author" content="ArduPilot Team">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. <!-- OG Meta Tags to improve the way the post looks when you share the page on LinkedIn, Facebook, Google+ -->
  11. <meta property="og:site_name" content="ArduPilot"/>
  12. <meta property="og:site" content=""/>
  13. <meta property="og:title" content="ArduPilot Custom Firmware Builder"/>
  14. <meta property="og:description"
  15. content="ArduPilot Custom Firmware Builder. It allows to build custom ArduPilot firmware by selecting the wanted features."/>
  16. <!-- description shown in the actual shared post -->
  17. <meta property="og:type" content="website">
  18. <meta property="og:url" content="https://custom.ardupilot.org/">
  19. <meta property="og:image" content="https://ardupilot.org/application/files/6315/7552/1962/ArduPilot-Motto.png">
  20. <link rel="stylesheet" type="text/css" href="{{ url_for('static',filename='styles/main.css') }}">
  21. <script type="text/javascript" src="{{ url_for('static', filename='js/CollapsibleLists.js')}}"></script>
  22. </head>
  23. <body onload="javascript: init()">
  24. <div id="main">
  25. <a href="https://custom.ardupilot.org/">
  26. <div id="logo">
  27. </div>
  28. </a>
  29. <div id="menu">
  30. <h2>ArduPilot Custom Firmware Builder</h2>
  31. <p style="color: red;">To try out the newest features of the application, visit our beta server at <a href="https://custom-beta.ardupilot.org">custom-beta.ardupilot.org</a></p>
  32. <form action="/generate" method="post">
  33. <div id="vehicle_list">
  34. <label for="vehicle">Choose a vehicle:
  35. <select name="vehicle" id="vehicle" onchange="onVehicleChange(this.value);">
  36. {% for vehicle in get_vehicle_names() %}
  37. <option value="{{vehicle}}" {% if vehicle == get_default_vehicle_name() %} selected {% endif %}>{{vehicle}}</option>
  38. {% endfor %}
  39. </select>
  40. </label>
  41. </div>
  42. <p></p>
  43. <div id="branch_list"></div>
  44. <p></p>
  45. <div id="board_list"></div>
  46. <p></p>
  47. <div id="build_options"></div>
  48. <br>
  49. <div id="message" style="color:red"></div>
  50. <br>
  51. <input type="submit" value="Generate" id="submit">
  52. <input type="button" value="Reset option defaults" id="reset_def" onclick="Features.applyDefaults();">
  53. </form>
  54. </div>
  55. <hr>
  56. <p>Exisiting builds (click on the status of a build to view it):</p>
  57. <div id="build_status"></div>
  58. <br/>
  59. <script>
  60. const Features = (() => {
  61. let features = {};
  62. let features_dictionary = {};
  63. function resetDictionary() {
  64. features_dictionary = {};
  65. features.forEach((category, cat_idx) => {
  66. category['options'].forEach((option, opt_idx) => {
  67. features_dictionary[option.define] = {
  68. 'category_index' : cat_idx,
  69. 'option_index' : opt_idx,
  70. };
  71. });
  72. });
  73. }
  74. function reset(new_features) {
  75. features = new_features;
  76. resetDictionary();
  77. }
  78. function getByDefine(define) {
  79. let dict_value = features_dictionary[define];
  80. if (dict_value == undefined) {
  81. return null;
  82. }
  83. return features[dict_value['category_index']]['options'][dict_value['option_index']];
  84. }
  85. function updateDefaults(defines_array) {
  86. // updates default on the basis of define array passed
  87. // the define array consists define in format, EXAMPLE_DEFINE or !EXAMPLE_DEFINE
  88. // we update the defaults in features object by processing those defines
  89. for (let i=0; i<defines_array.length; i++) {
  90. let select_opt = (defines_array[i][0] != '!');
  91. let sanitised_define = (select_opt ? defines_array[i] : defines_array[i].substring(1)); // this removes the leading '!' from define if it contatins
  92. if (getByDefine(sanitised_define)) {
  93. let cat_idx = features_dictionary[sanitised_define]['category_index'];
  94. let opt_idx = features_dictionary[sanitised_define]['option_index'];
  95. getByDefine(sanitised_define).default = select_opt ? 1 : 0;
  96. }
  97. }
  98. }
  99. function applyDefaults() {
  100. features.forEach(category => {
  101. category['options'].forEach(option => {
  102. element = document.getElementById(option['label']);
  103. if (element != undefined) {
  104. element.checked = (option['default'] == 1);
  105. }
  106. });
  107. });
  108. }
  109. return {reset, getByDefine, updateDefaults, applyDefaults};
  110. })();
  111. var pending_update_calls = 0; // to keep track of unresolved Promises
  112. function init() {
  113. refresh_builds();
  114. onVehicleChange(document.getElementById("vehicle").value);
  115. }
  116. function setMessage (messageText) {
  117. document.getElementById("message").innerHTML = messageText;
  118. }
  119. // enables or disables the elements with ids passed as an array
  120. // if enable is true, the elements are enabled and vice-versa
  121. function enableDisableElementsById(ids, enable) {
  122. for (let i=0; i<ids.length; i++) {
  123. let element = document.getElementById(ids[i]);
  124. if (element) {
  125. element.disabled = (!enable);
  126. }
  127. }
  128. }
  129. function onVehicleChange(new_vehicle) {
  130. // following elemets will be blocked (disabled) when we make the request
  131. let elements_to_block = ['vehicle', 'submit', 'reset_def'];
  132. enableDisableElementsById(elements_to_block, false);
  133. let request_url = '/get_allowed_branches/'+new_vehicle;
  134. setMessage("Fetching the list of available branches for "+new_vehicle);
  135. pending_update_calls += 1;
  136. sendAjaxRequestForJsonResponse(request_url)
  137. .then((json_response) => {
  138. let new_branch = json_response.default_branch;
  139. let all_branches = json_response.branches;
  140. updateBranches(all_branches, new_branch);
  141. })
  142. .catch((message) => {
  143. console.log("Branch update failed. "+message);
  144. })
  145. .finally(() => {
  146. setMessage("");
  147. enableDisableElementsById(elements_to_block, true);
  148. pending_update_calls -= 1;
  149. fetchAndUpdateDefaults();
  150. });
  151. }
  152. function updateBranches(all_branches, new_branch) {
  153. let branch_element = document.getElementById('branch');
  154. let old_branch = branch_element ? branch_element.value : '';
  155. fillBranches(all_branches, new_branch);
  156. if (old_branch != new_branch) {
  157. onBranchChange(new_branch);
  158. }
  159. }
  160. function onBranchChange(new_branch) {
  161. // following elemets will be blocked (disabled) when we make the request
  162. let elements_to_block = ['branch', 'submit', 'reset_def'];
  163. enableDisableElementsById(elements_to_block, false);
  164. let request_url = '/boards_and_features/'+new_branch;
  165. setMessage("Fetching the list of boards and features for "+new_branch);
  166. pending_update_calls += 1;
  167. sendAjaxRequestForJsonResponse(request_url)
  168. .then((json_response) => {
  169. let boards = json_response.boards;
  170. let new_board = json_response.default_board;
  171. let new_features = json_response.features;
  172. updateBoards(boards, new_board);
  173. Features.reset(new_features);
  174. fillBuildOptions(new_features);
  175. })
  176. .catch((message) => {
  177. console.log("Boards and features update failed. "+message);
  178. })
  179. .finally(() => {
  180. setMessage("");
  181. enableDisableElementsById(elements_to_block, true);
  182. pending_update_calls -= 1;
  183. fetchAndUpdateDefaults();
  184. });
  185. }
  186. function updateBoards(all_boards, new_board) {
  187. let board_element = document.getElementById('board');
  188. let old_board = board_element ? board.value : '';
  189. fillBoards(all_boards, new_board);
  190. if (old_board != new_board) {
  191. onBoardChange(new_board);
  192. }
  193. }
  194. function onBoardChange(new_board) {
  195. fetchAndUpdateDefaults();
  196. }
  197. function fetchAndUpdateDefaults() {
  198. // return early if there is an unresolved promise (i.e., there is an ongoing ajax call)
  199. if (pending_update_calls > 0) {
  200. return;
  201. }
  202. elements_to_block = ['reset_def']
  203. enableDisableElementsById(elements_to_block, false);
  204. let branch = document.getElementById('branch').value;
  205. let vehicle = document.getElementById('vehicle').value;
  206. let board = document.getElementById('board').value;
  207. let request_url = '/get_defaults/'+vehicle+'/'+branch+'/'+board;
  208. setMessage("Fetching defaults for "+vehicle+" and "+board+" on "+branch);
  209. sendAjaxRequestForJsonResponse(request_url)
  210. .then((json_response) => {
  211. Features.updateDefaults(json_response);
  212. Features.applyDefaults();
  213. })
  214. .catch((message) => {
  215. console.log("Default reset failed. "+message);
  216. })
  217. .finally(() => {
  218. setMessage("");
  219. enableDisableElementsById(elements_to_block, true);
  220. });
  221. }
  222. function refresh_builds() {
  223. var output = document.getElementById('build_status');
  224. var xhr = new XMLHttpRequest();
  225. xhr.open('GET', "/builds/status.html");
  226. // disable cache, thanks to: https://stackoverflow.com/questions/22356025/force-cache-control-no-cache-in-chrome-via-xmlhttprequest-on-f5-reload
  227. xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0");
  228. xhr.setRequestHeader("Expires", "Tue, 01 Jan 1980 1:00:00 GMT");
  229. xhr.setRequestHeader("Pragma", "no-cache");
  230. xhr.onload = function () {
  231. if (xhr.status === 200) {
  232. output.innerHTML = xhr.responseText;
  233. }
  234. setTimeout(refresh_builds, 5000)
  235. }
  236. xhr.send();
  237. }
  238. function fillBoards(boards, default_board) {
  239. let output = document.getElementById('board_list');
  240. output.innerHTML = "<p>Please select the required options for the custom firmware build, then hit 'Generate'.</p>"+
  241. "<label for='board'>Choose a board: "+
  242. "<select name='board' id='board' onchange='onBoardChange(this.value)'>"+
  243. "</select>"+
  244. "</label>";
  245. let boardList = document.getElementById("board")
  246. boards.forEach(board => {
  247. let opt = document.createElement('option');
  248. opt.value = board;
  249. opt.innerHTML = board;
  250. opt.selected = (board === default_board);
  251. boardList.appendChild(opt);
  252. });
  253. }
  254. function fillBuildOptions(buildOptions) {
  255. let output = document.getElementById('build_options');
  256. output.innerHTML = "<label for='features'>Select Features: "+
  257. "<ul class='collapsibleList' id='outer_list'></ul>"+
  258. "</label>";
  259. let outerList = document.getElementById("outer_list");
  260. buildOptions.forEach(category => {
  261. let outerListItem = document.createElement('li');
  262. outerListItem.innerHTML = category['name'];
  263. let innerList = document.createElement('ul');
  264. category['options'].forEach(option => {
  265. let innerListItem = document.createElement('li');
  266. let checkBox = document.createElement('input');
  267. checkBox.type = "checkbox";
  268. checkBox.name = option['label'];
  269. checkBox.id = option['label'];
  270. checkBox.value = "1";
  271. checkBox.checked = (option['default'] == 1);
  272. checkBox.addEventListener('click', function handleClick(event) {
  273. dependencies(option['label'], option['dependency']);
  274. });
  275. innerListItem.appendChild(checkBox);
  276. innerListItem.appendChild(document.createTextNode(option['description']));
  277. innerList.appendChild(innerListItem);
  278. });
  279. outerListItem.appendChild(innerList);
  280. outerList.appendChild(outerListItem);
  281. });
  282. CollapsibleLists.apply();
  283. }
  284. function dependencies(f_label, f_dependency1) {
  285. cb = document.getElementById(f_label);
  286. switch (cb.name) {
  287. case f_label:
  288. const f_dependency = f_dependency1.split(",")
  289. var arrayLength = f_dependency.length;
  290. for (let i = 0; i < arrayLength; i++) {
  291. if (document.getElementById(f_dependency[i]).checked == false) {
  292. document.getElementById(f_dependency[i]).checked = cb.checked;
  293. }
  294. }
  295. break;
  296. }
  297. }
  298. // returns a Promise
  299. // the promise is resolved when we recieve status code 200 from the AJAX request
  300. // the JSON response for the request is returned in such case
  301. // the promise is rejected when the status code is not 200
  302. // the status code is returned in such case
  303. function sendAjaxRequestForJsonResponse(url) {
  304. return new Promise((resolve, reject) => {
  305. var xhr = new XMLHttpRequest();
  306. xhr.open('GET', url);
  307. xhr.onload = function () {
  308. if (xhr.status == 200) {
  309. resolve(JSON.parse(xhr.response));
  310. } else {
  311. reject("Got response:"+xhr.response+" (Status Code: "+xhr.status+")");
  312. }
  313. }
  314. xhr.send();
  315. });
  316. }
  317. function fillBranches(branches, branch_to_select) {
  318. var output = document.getElementById('branch_list');
  319. output.innerHTML = "<label for='branch'>Choose a branch: "+
  320. "<select name='branch' id='branch' onchange='onBranchChange(this.value);'>"+
  321. "</select>"+
  322. "</label>";
  323. branchList = document.getElementById("branch");
  324. branches.forEach(branch => {
  325. opt = document.createElement('option');
  326. opt.value = branch['full_name'];
  327. opt.innerHTML = branch['label'];
  328. opt.selected = (branch['full_name'] === branch_to_select);
  329. branchList.appendChild(opt);
  330. });
  331. }
  332. </script>
  333. </div>
  334. </body>
  335. <hr>
  336. <footer>Created by Will Piper, <a href=https://github.com/ArduPilot/CustomBuild>Source Code</a>.</footer>
  337. </html>