Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2434 lines
71 KiB

8 years ago
8 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
8 years ago
6 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
6 years ago
8 years ago
6 years ago
6 years ago
6 years ago
7 years ago
6 years ago
5 years ago
5 years ago
8 years ago
8 years ago
6 years ago
6 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
8 years ago
6 years ago
8 years ago
6 years ago
8 years ago
8 years ago
8 years ago
6 years ago
8 years ago
6 years ago
8 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
6 years ago
6 years ago
6 years ago
8 years ago
  1. var debug = false;
  2. var websock;
  3. var password = false;
  4. var maxNetworks;
  5. var messages = [];
  6. var free_size = 0;
  7. var urls = {};
  8. var numChanged = 0;
  9. var numReboot = 0;
  10. var numReconnect = 0;
  11. var numReload = 0;
  12. var configurationSaved = false;
  13. var ws_pingpong;
  14. var useWhite = false;
  15. var useCCT = false;
  16. var now = 0;
  17. var ago = 0;
  18. <!-- removeIf(!rfm69)-->
  19. var packets;
  20. var filters = [];
  21. <!-- endRemoveIf(!rfm69)-->
  22. <!-- removeIf(!sensor)-->
  23. var Magnitudes = [];
  24. var MagnitudeErrors = {};
  25. var MagnitudeNames = {};
  26. var MagnitudeTypePrefixes = {};
  27. var MagnitudePrefixTypes = {};
  28. <!-- endRemoveIf(!sensor)-->
  29. // -----------------------------------------------------------------------------
  30. // Messages
  31. // -----------------------------------------------------------------------------
  32. function initMessages() {
  33. messages[1] = "Remote update started";
  34. messages[2] = "OTA update started";
  35. messages[3] = "Error parsing data!";
  36. messages[4] = "The file does not look like a valid configuration backup or is corrupted";
  37. messages[5] = "Changes saved. You should reboot your board now";
  38. messages[7] = "Passwords do not match!";
  39. messages[8] = "Changes saved";
  40. messages[9] = "No changes detected";
  41. messages[10] = "Session expired, please reload page...";
  42. }
  43. // -----------------------------------------------------------------------------
  44. // Utils
  45. // -----------------------------------------------------------------------------
  46. $.fn.enterKey = function (fnc) {
  47. return this.each(function () {
  48. $(this).keypress(function (ev) {
  49. var keycode = parseInt(ev.keyCode ? ev.keyCode : ev.which, 10);
  50. if (13 === keycode) {
  51. return fnc.call(this, ev);
  52. }
  53. });
  54. });
  55. };
  56. function keepTime() {
  57. $("span[name='ago']").html(ago);
  58. ago++;
  59. if (0 === now) { return; }
  60. var date = new Date(now * 1000);
  61. var text = date.toISOString().substring(0, 19).replace("T", " ");
  62. $("input[name='now']").val(text);
  63. $("span[name='now']").html(text);
  64. now++;
  65. }
  66. function zeroPad(number, positions) {
  67. return number.toString().padStart(positions, "0");
  68. }
  69. function loadTimeZones() {
  70. var time_zones = [
  71. -720, -660, -600, -570, -540,
  72. -480, -420, -360, -300, -240,
  73. -210, -180, -120, -60, 0,
  74. 60, 120, 180, 210, 240,
  75. 270, 300, 330, 345, 360,
  76. 390, 420, 480, 510, 525,
  77. 540, 570, 600, 630, 660,
  78. 720, 765, 780, 840
  79. ];
  80. for (var i in time_zones) {
  81. var tz = time_zones[i];
  82. var offset = tz >= 0 ? tz : -tz;
  83. var text = "GMT" + (tz >= 0 ? "+" : "-") +
  84. zeroPad(parseInt(offset / 60, 10), 2) + ":" +
  85. zeroPad(offset % 60, 2);
  86. $("select[name='ntpOffset']").append(
  87. $("<option></option>")
  88. .attr("value", tz)
  89. .text(text)
  90. );
  91. }
  92. }
  93. function validatePassword(password) {
  94. // http://www.the-art-of-web.com/javascript/validate-password/
  95. // at least one lowercase and one uppercase letter or number
  96. // at least eight characters (letters, numbers or special characters)
  97. // MUST be 8..63 printable ASCII characters. See:
  98. // https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Target_users_(authentication_key_distribution)
  99. // https://github.com/xoseperez/espurna/issues/1151
  100. var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{8,63}$/;
  101. return (
  102. (password !== undefined)
  103. && (typeof password === "string")
  104. && (password.length > 0)
  105. && re_password.test(password)
  106. );
  107. }
  108. function validateFormPasswords(form) {
  109. var passwords = $("input[name='adminPass1'],input[name='adminPass2']", form);
  110. var adminPass1 = passwords.first().val(),
  111. adminPass2 = passwords.last().val();
  112. var formValidity = passwords.first()[0].checkValidity();
  113. if (formValidity && (adminPass1.length === 0) && (adminPass2.length === 0)) {
  114. return true;
  115. }
  116. var validPass1 = validatePassword(adminPass1),
  117. validPass2 = validatePassword(adminPass2);
  118. if (formValidity && validPass1 && validPass2) {
  119. return true;
  120. }
  121. if (!formValidity || (adminPass1.length > 0 && !validPass1)) {
  122. alert("The password you have entered is not valid, it must be 8..63 characters and have at least 1 lowercase and 1 uppercase / number!");
  123. }
  124. if (adminPass1 !== adminPass2) {
  125. alert("Passwords are different!");
  126. }
  127. return false;
  128. }
  129. function validateFormHostname(form) {
  130. // RFCs mandate that a hostname's labels may contain only
  131. // the ASCII letters 'a' through 'z' (case-insensitive),
  132. // the digits '0' through '9', and the hyphen.
  133. // Hostname labels cannot begin or end with a hyphen.
  134. // No other symbols, punctuation characters, or blank spaces are permitted.
  135. // Negative lookbehind does not work in Javascript
  136. // var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{1,31}(?<!-)$');
  137. var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{0,30}[A-Za-z0-9]$');
  138. var hostname = $("input[name='hostname']", form);
  139. if ("true" !== hostname.attr("hasChanged")) {
  140. return true;
  141. }
  142. if (re_hostname.test(hostname.val())) {
  143. return true;
  144. }
  145. alert("Hostname cannot be empty and may only contain the ASCII letters ('A' through 'Z' and 'a' through 'z'), the digits '0' through '9', and the hyphen ('-')! They can neither start or end with an hyphen.");
  146. return false;
  147. }
  148. function validateForm(form) {
  149. return validateFormPasswords(form) && validateFormHostname(form);
  150. }
  151. // Observe all group settings to selectively update originals based on the current data
  152. var groupSettingsObserver = new MutationObserver(function(mutations) {
  153. mutations.forEach(function(mutation) {
  154. // If any new elements are added, set "settings-target" element as changed to forcibly send the data
  155. var targets = $(mutation.target).attr("data-settings-target");
  156. if (targets !== undefined) {
  157. mutation.addedNodes.forEach(function(node) {
  158. var overrides = [];
  159. targets.split(" ").forEach(function(target) {
  160. var elem = $("[name='" + target + "']", node);
  161. if (!elem.length) return;
  162. var value = getValue(elem);
  163. if ((value === null) || (value === elem[0].defaultValue)) {
  164. overrides.push(elem);
  165. }
  166. });
  167. setOriginalsFromValues($("input,select", node));
  168. overrides.forEach(function(elem) {
  169. elem.attr("hasChanged", "true");
  170. if (elem.prop("tagName") === "SELECT") {
  171. elem.prop("value", 0);
  172. }
  173. });
  174. });
  175. }
  176. // If anything was removed, forcibly send **all** of the group to avoid having any outdated keys
  177. // TODO: hide instead of remove?
  178. var changed = $(mutation.target).attr("hasChanged") === "true";
  179. if (changed || mutation.removedNodes.length) {
  180. $(mutation.target).attr("hasChanged", "true");
  181. $("input,select", mutation.target.childNodes).attr("hasChanged", "true");
  182. }
  183. });
  184. });
  185. // These fields will always be a list of values
  186. function isGroupValue(value) {
  187. var names = [
  188. "ssid", "pass", "gw", "mask", "ip", "dns",
  189. "schEnabled", "schSwitch","schAction","schType","schHour","schMinute","schWDs","schUTC",
  190. "relayBoot", "relayPulse", "relayTime", "relayLastSch",
  191. "mqttGroup", "mqttGroupSync", "relayOnDisc",
  192. "dczRelayIdx", "dczMagnitude",
  193. "tspkRelay", "tspkMagnitude",
  194. "ledGPIO", "ledMode", "ledRelay",
  195. "adminPass",
  196. "node", "key", "topic",
  197. "rpnRule", "rpnTopic", "rpnName"
  198. ];
  199. return names.indexOf(value) >= 0;
  200. }
  201. function getValue(element) {
  202. if ($(element).attr("type") === "checkbox") {
  203. return $(element).prop("checked") ? 1 : 0;
  204. } else if ($(element).attr("type") === "radio") {
  205. if (!$(element).prop("checked")) {
  206. return null;
  207. }
  208. }
  209. return $(element).val();
  210. }
  211. function addValue(data, name, value) {
  212. if (name in data) {
  213. if (!Array.isArray(data[name])) {
  214. data[name] = [data[name]];
  215. }
  216. data[name].push(value);
  217. } else if (isGroupValue(name)) {
  218. data[name] = [value];
  219. } else {
  220. data[name] = value;
  221. }
  222. }
  223. function getData(form, changed, cleanup) {
  224. // Populate two sets of data, ones that had been changed and ones that stayed the same
  225. var data = {};
  226. var changed_data = [];
  227. if (cleanup === undefined) {
  228. cleanup = true;
  229. }
  230. if (changed === undefined) {
  231. changed = true;
  232. }
  233. $("input,select", form).each(function() {
  234. if ($(this).attr("data-settings-ignore") === "true") {
  235. return;
  236. }
  237. var name = $(this).attr("name");
  238. var real_name = $(this).attr("data-settings-real-name");
  239. if (real_name !== undefined) {
  240. name = real_name;
  241. }
  242. var value = getValue(this);
  243. if (null !== value) {
  244. var haschanged = ("true" === $(this).attr("hasChanged"));
  245. var indexed = changed_data.indexOf(name) >= 0;
  246. if ((haschanged || !changed) && !indexed) {
  247. changed_data.push(name);
  248. }
  249. addValue(data, name, value);
  250. }
  251. });
  252. // Finally, filter out only fields that had changed.
  253. // Note: We need to preserve dynamic lists like schedules, wifi etc.
  254. // so we don't accidentally break when user deletes entry in the middle
  255. var resulting_data = {};
  256. for (var value in data) {
  257. if (changed_data.indexOf(value) >= 0) {
  258. resulting_data[value] = data[value];
  259. }
  260. }
  261. // Hack: clean-up leftover arrays.
  262. // When empty, the receiving side will prune all keys greater than the current one.
  263. if (cleanup) {
  264. $(".group-settings").each(function() {
  265. var haschanged = ("true" === $(this).attr("hasChanged"));
  266. if (haschanged && !this.children.length) {
  267. var targets = this.dataset.settingsTarget;
  268. if (targets === undefined) return;
  269. targets.split(" ").forEach(function(target) {
  270. resulting_data[target] = [];
  271. });
  272. }
  273. });
  274. }
  275. return resulting_data;
  276. }
  277. function randomString(length, args) {
  278. if (typeof args === "undefined") {
  279. args = {
  280. lowercase: true,
  281. uppercase: true,
  282. numbers: true,
  283. special: true
  284. }
  285. }
  286. var mask = "";
  287. if (args.lowercase) { mask += "abcdefghijklmnopqrstuvwxyz"; }
  288. if (args.uppercase) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; }
  289. if (args.numbers || args.hex) { mask += "0123456789"; }
  290. if (args.hex) { mask += "ABCDEF"; }
  291. if (args.special) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; }
  292. var source = new Uint32Array(length);
  293. var result = new Array(length);
  294. window.crypto.getRandomValues(source).forEach(function(value, i) {
  295. result[i] = mask[value % mask.length];
  296. });
  297. return result.join("");
  298. }
  299. function generateAPIKey() {
  300. var apikey = randomString(16, {hex: true});
  301. $("input[name='apiKey']")
  302. .val(apikey)
  303. .attr("original", "-".repeat(16))
  304. .attr("haschanged", "true");
  305. return false;
  306. }
  307. function generatePassword() {
  308. var password = "";
  309. do {
  310. password = randomString(10);
  311. } while (!validatePassword(password));
  312. return password;
  313. }
  314. function toggleVisiblePassword() {
  315. var elem = this.previousElementSibling;
  316. if (elem.type === "password") {
  317. elem.type = "text";
  318. } else {
  319. elem.type = "password";
  320. }
  321. return false;
  322. }
  323. function doGeneratePassword() {
  324. var elems = $("input", $("#formPassword"));
  325. elems
  326. .val(generatePassword())
  327. .attr("haschanged", "true")
  328. .each(function() {
  329. this.type = "text";
  330. });
  331. return false;
  332. }
  333. function getJson(str) {
  334. try {
  335. return JSON.parse(str);
  336. } catch (e) {
  337. return false;
  338. }
  339. }
  340. function moduleVisible(module) {
  341. if (module == "sch") {
  342. $("li.module-" + module).css("display", "inherit");
  343. $("div.module-" + module).css("display", "flex");
  344. return;
  345. }
  346. $(".module-" + module).css("display", "inherit");
  347. }
  348. <!-- removeIf(!thermostat)-->
  349. function checkTempRangeMin() {
  350. var min = parseInt($("#tempRangeMinInput").val(), 10);
  351. var max = parseInt($("#tempRangeMaxInput").val(), 10);
  352. if (min > max - 1) {
  353. $("#tempRangeMinInput").val(max - 1);
  354. }
  355. }
  356. function checkTempRangeMax() {
  357. var min = parseInt($("#tempRangeMinInput").val(), 10);
  358. var max = parseInt($("#tempRangeMaxInput").val(), 10);
  359. if (max < min + 1) {
  360. $("#tempRangeMaxInput").val(min + 1);
  361. }
  362. }
  363. function doResetThermostatCounters(ask) {
  364. var question = (typeof ask === "undefined" || false === ask) ?
  365. null :
  366. "Are you sure you want to reset burning counters?";
  367. return doAction(question, "thermostat_reset_counters");
  368. }
  369. <!-- endRemoveIf(!thermostat)-->
  370. function initSelectGPIO(select) {
  371. // TODO: properly lock used GPIOs via locking and apply the mask here
  372. var mapping = [
  373. [153, "NONE"],
  374. [0, "0 (FLASH)"],
  375. [1, "1 (U0TXD)"],
  376. [2, "2 (U1TXD)"],
  377. [3, "3 (U0RXD)"],
  378. [4, "4 (SDA)"],
  379. [5, "5 (SCL)"],
  380. [9, "9 (SDD2)"],
  381. [10, "10 (SDD3)"],
  382. [12, "12 (MTDI)"],
  383. [13, "13 (MTCK)"],
  384. [14, "14 (MTMS)"],
  385. [15, "15 (MTDO)"],
  386. [16, "16 (WAKE)"],
  387. ];
  388. for (n in mapping) {
  389. var elem = $('<option value="' + mapping[n][0] + '">');
  390. elem.html(mapping[n][1]);
  391. elem.appendTo(select);
  392. }
  393. }
  394. // -----------------------------------------------------------------------------
  395. // Actions
  396. // -----------------------------------------------------------------------------
  397. function send(json) {
  398. if (debug) console.log(json);
  399. websock.send(json);
  400. }
  401. function sendAction(action, data) {
  402. send(JSON.stringify({action: action, data: data}));
  403. }
  404. function sendConfig(data) {
  405. send(JSON.stringify({config: data}));
  406. }
  407. function setOriginalsFromValues(elems) {
  408. if (typeof elems == "undefined") {
  409. elems = $("input,select");
  410. }
  411. elems.each(function() {
  412. var value;
  413. if ($(this).attr("type") === "checkbox") {
  414. value = $(this).prop("checked");
  415. } else {
  416. value = $(this).val();
  417. }
  418. $(this).attr("original", value);
  419. hasChanged.call(this);
  420. });
  421. }
  422. function resetOriginals() {
  423. setOriginalsFromValues();
  424. $(".group-settings").attr("haschanged", "false")
  425. numReboot = numReconnect = numReload = 0;
  426. configurationSaved = false;
  427. }
  428. function doReload(milliseconds) {
  429. setTimeout(function() {
  430. window.location.reload();
  431. }, parseInt(milliseconds, 10));
  432. }
  433. /**
  434. * Check a file object to see if it is a valid firmware image
  435. * The file first byte should be 0xE9
  436. * @param {file} file File object
  437. * @param {Function} callback Function to call back with the result
  438. */
  439. function checkFirmware(file, callback) {
  440. var reader = new FileReader();
  441. reader.onloadend = function(evt) {
  442. if (FileReader.DONE === evt.target.readyState) {
  443. var magic = evt.target.result.charCodeAt(0);
  444. if ((0x1F === magic) && (0x8B === evt.target.result.charCodeAt(1))) {
  445. callback(true);
  446. return;
  447. }
  448. if (0xE9 !== magic) {
  449. alert("Binary image does not start with a magic byte");
  450. callback(false);
  451. return;
  452. }
  453. var modes = ['QIO', 'QOUT', 'DIO', 'DOUT'];
  454. var flash_mode = evt.target.result.charCodeAt(2);
  455. if (0x03 !== flash_mode) {
  456. var response = window.confirm("Binary image is using " + modes[flash_mode] + " flash mode! Make sure that the device supports it before proceeding.");
  457. callback(response);
  458. } else {
  459. callback(true);
  460. }
  461. }
  462. };
  463. var blob = file.slice(0, 3);
  464. reader.readAsBinaryString(blob);
  465. }
  466. function doUpgrade() {
  467. var file = $("input[name='upgrade']")[0].files[0];
  468. if (typeof file === "undefined") {
  469. alert("First you have to select a file from your computer.");
  470. return false;
  471. }
  472. if (file.size > free_size) {
  473. alert("Image it too large to fit in the available space for OTA. Consider doing a two-step update.");
  474. return false;
  475. }
  476. checkFirmware(file, function(ok) {
  477. if (!ok) {
  478. return;
  479. }
  480. var data = new FormData();
  481. data.append("upgrade", file, file.name);
  482. var xhr = new XMLHttpRequest();
  483. var msg_ok = "Firmware image uploaded, board rebooting. This page will be refreshed in 5 seconds.";
  484. var msg_err = "There was an error trying to upload the new image, please try again: ";
  485. var network_error = function(e) {
  486. alert(msg_err + " xhr request " + e.type);
  487. };
  488. xhr.addEventListener("error", network_error, false);
  489. xhr.addEventListener("abort", network_error, false);
  490. xhr.addEventListener("load", function(e) {
  491. $("#upgrade-progress").hide();
  492. if ("OK" === xhr.responseText) {
  493. alert(msg_ok);
  494. doReload(5000);
  495. } else {
  496. alert(msg_err + xhr.status.toString() + " " + xhr.statusText + ", " + xhr.responseText);
  497. }
  498. }, false);
  499. xhr.upload.addEventListener("progress", function(e) {
  500. $("#upgrade-progress").show();
  501. if (e.lengthComputable) {
  502. $("progress").attr({ value: e.loaded, max: e.total });
  503. }
  504. }, false);
  505. xhr.open("POST", urls.upgrade.href);
  506. xhr.send(data);
  507. });
  508. return false;
  509. }
  510. function doUpdatePassword() {
  511. var form = $("#formPassword");
  512. if (validateFormPasswords(form)) {
  513. sendConfig(getData(form, true, false));
  514. }
  515. return false;
  516. }
  517. function checkChanges() {
  518. if (numChanged > 0) {
  519. var response = window.confirm("Some changes have not been saved yet, do you want to save them first?");
  520. if (response) {
  521. doUpdate();
  522. }
  523. }
  524. }
  525. function doAction(question, action) {
  526. checkChanges();
  527. if (question) {
  528. var response = window.confirm(question);
  529. if (false === response) {
  530. return false;
  531. }
  532. }
  533. sendAction(action, {});
  534. doReload(5000);
  535. return false;
  536. }
  537. function doReboot(ask) {
  538. var question = (typeof ask === "undefined" || false === ask) ?
  539. null :
  540. "Are you sure you want to reboot the device?";
  541. return doAction(question, "reboot");
  542. }
  543. function doReconnect(ask) {
  544. var question = (typeof ask === "undefined" || false === ask) ?
  545. null :
  546. "Are you sure you want to disconnect from the current WIFI network?";
  547. return doAction(question, "reconnect");
  548. }
  549. function doCheckOriginals() {
  550. var response;
  551. if (numReboot > 0) {
  552. response = window.confirm("You have to reboot the board for the changes to take effect, do you want to do it now?");
  553. if (response) { doReboot(false); }
  554. } else if (numReconnect > 0) {
  555. response = window.confirm("You have to reconnect to the WiFi for the changes to take effect, do you want to do it now?");
  556. if (response) { doReconnect(false); }
  557. } else if (numReload > 0) {
  558. response = window.confirm("You have to reload the page to see the latest changes, do you want to do it now?");
  559. if (response) { doReload(0); }
  560. }
  561. resetOriginals();
  562. }
  563. function waitForSave(){
  564. if (!configurationSaved) {
  565. setTimeout(waitForSave, 1000);
  566. } else {
  567. doCheckOriginals();
  568. }
  569. }
  570. function doUpdate() {
  571. var forms = $(".form-settings");
  572. if (validateForm(forms)) {
  573. // Get data
  574. sendConfig(getData(forms));
  575. // Empty special fields
  576. $(".pwrExpected").val(0);
  577. $("input[name='snsResetCalibration']").prop("checked", false);
  578. $("input[name='pwrResetCalibration']").prop("checked", false);
  579. $("input[name='pwrResetE']").prop("checked", false);
  580. // Change handling
  581. numChanged = 0;
  582. waitForSave();
  583. }
  584. return false;
  585. }
  586. function doBackup() {
  587. document.getElementById("downloader").src = urls.config.href;
  588. return false;
  589. }
  590. function onFileUpload(event) {
  591. var inputFiles = this.files;
  592. if (typeof inputFiles === "undefined" || inputFiles.length === 0) {
  593. return false;
  594. }
  595. var inputFile = inputFiles[0];
  596. this.value = "";
  597. var response = window.confirm("Previous settings will be overwritten. Are you sure you want to restore this settings?");
  598. if (!response) {
  599. return false;
  600. }
  601. var reader = new FileReader();
  602. reader.onload = function(e) {
  603. var data = getJson(e.target.result);
  604. if (data) {
  605. sendAction("restore", data);
  606. } else {
  607. window.alert(messages[4]);
  608. }
  609. };
  610. reader.readAsText(inputFile);
  611. return false;
  612. }
  613. function doRestore() {
  614. if (typeof window.FileReader !== "function") {
  615. alert("The file API isn't supported on this browser yet.");
  616. } else {
  617. $("#uploader").click();
  618. }
  619. return false;
  620. }
  621. function doFactoryReset() {
  622. var response = window.confirm("Are you sure you want to restore to factory settings?");
  623. if (!response) {
  624. return false;
  625. }
  626. sendAction("factory_reset", {});
  627. doReload(5000);
  628. return false;
  629. }
  630. function doToggle(id, value) {
  631. sendAction("relay", {id: id, status: value ? 1 : 0 });
  632. return false;
  633. }
  634. function doScan() {
  635. $("#scanResult").html("");
  636. $("div.scan.loading").show();
  637. sendAction("scan", {});
  638. return false;
  639. }
  640. function doHAConfig() {
  641. $("#haConfig")
  642. .text("")
  643. .height(0)
  644. .show();
  645. sendAction("haconfig", {});
  646. return false;
  647. }
  648. function doDebugCommand() {
  649. var el = $("input[name='dbgcmd']");
  650. var command = el.val();
  651. el.val("");
  652. sendAction("dbgcmd", {command: command});
  653. return false;
  654. }
  655. function doDebugClear() {
  656. $("#weblog").text("");
  657. return false;
  658. }
  659. <!-- removeIf(!rfm69)-->
  660. function doClearCounts() {
  661. sendAction("clear-counts", {});
  662. return false;
  663. }
  664. function doClearMessages() {
  665. packets.clear().draw(false);
  666. return false;
  667. }
  668. function doFilter(e) {
  669. var index = packets.cell(this).index();
  670. if (index == 'undefined') return;
  671. var c = index.column;
  672. var column = packets.column(c);
  673. if (filters[c]) {
  674. filters[c] = false;
  675. column.search("");
  676. $(column.header()).removeClass("filtered");
  677. } else {
  678. filters[c] = true;
  679. var data = packets.row(this).data();
  680. if (e.which == 1) {
  681. column.search('^' + data[c] + '$', true, false );
  682. } else {
  683. column.search('^((?!(' + data[c] + ')).)*$', true, false );
  684. }
  685. $(column.header()).addClass("filtered");
  686. }
  687. column.draw();
  688. return false;
  689. }
  690. function doClearFilters() {
  691. for (var i = 0; i < packets.columns()[0].length; i++) {
  692. if (filters[i]) {
  693. filters[i] = false;
  694. var column = packets.column(i);
  695. column.search("");
  696. $(column.header()).removeClass("filtered");
  697. column.draw();
  698. }
  699. }
  700. return false;
  701. }
  702. <!-- endRemoveIf(!rfm69)-->
  703. function delParent() {
  704. var parent = $(this).parent().parent();
  705. $(parent).remove();
  706. }
  707. // -----------------------------------------------------------------------------
  708. // Visualization
  709. // -----------------------------------------------------------------------------
  710. function toggleMenu() {
  711. $("#layout").toggleClass("active");
  712. $("#menu").toggleClass("active");
  713. $("#menuLink").toggleClass("active");
  714. }
  715. function showPanel() {
  716. $(".panel").hide();
  717. if ($("#layout").hasClass("active")) { toggleMenu(); }
  718. $("#" + $(this).attr("data")).show();
  719. }
  720. // -----------------------------------------------------------------------------
  721. // Relays & magnitudes mapping
  722. // -----------------------------------------------------------------------------
  723. function createRelayList(data, container, template_name) {
  724. var current = $("#" + container + " > div").length;
  725. if (current > 0) { return; }
  726. var template = $("#" + template_name + " .pure-g")[0];
  727. for (var i in data) {
  728. var line = $(template).clone();
  729. $("label", line).html("Switch #" + i);
  730. $("input", line).attr("tabindex", 40 + i).val(data[i]);
  731. setOriginalsFromValues($("input", line));
  732. line.appendTo("#" + container);
  733. }
  734. }
  735. <!-- removeIf(!sensor)-->
  736. function createMagnitudeList(data, container, template_name) {
  737. var current = $("#" + container + " > div").length;
  738. if (current > 0) { return; }
  739. var template = $("#" + template_name + " .pure-g")[0];
  740. var size = data.size;
  741. for (var i=0; i<size; ++i) {
  742. var line = $(template).clone();
  743. $("label", line).html(MagnitudeNames[data.type[i]] + " #" + parseInt(data.index[i], 10));
  744. $("div.hint", line).html(Magnitudes[i].description);
  745. $("input", line).attr("tabindex", 40 + i).val(data.idx[i]);
  746. setOriginalsFromValues($("input", line));
  747. line.appendTo("#" + container);
  748. }
  749. }
  750. <!-- endRemoveIf(!sensor)-->
  751. // -----------------------------------------------------------------------------
  752. // RPN Rules
  753. // -----------------------------------------------------------------------------
  754. function addRPNRule() {
  755. var template = $("#rpnRuleTemplate .pure-g")[0];
  756. var line = $(template).clone();
  757. var tabindex = $("#rpnRules > div").length + 100;
  758. $(line).find("input").each(function() {
  759. $(this).attr("tabindex", tabindex++);
  760. });
  761. $(line).find("button").on('click', delParent);
  762. setOriginalsFromValues($("input", line));
  763. line.appendTo("#rpnRules");
  764. }
  765. function addRPNTopic() {
  766. var template = $("#rpnTopicTemplate .pure-g")[0];
  767. var line = $(template).clone();
  768. var tabindex = $("#rpnTopics > div").length + 120;
  769. $(line).find("input").each(function() {
  770. $(this).attr("tabindex", tabindex++);
  771. });
  772. $(line).find("button").on('click', delParent);
  773. setOriginalsFromValues($("input", line));
  774. line.appendTo("#rpnTopics");
  775. }
  776. // -----------------------------------------------------------------------------
  777. // RFM69
  778. // -----------------------------------------------------------------------------
  779. <!-- removeIf(!rfm69)-->
  780. function addMapping() {
  781. var template = $("#nodeTemplate .pure-g")[0];
  782. var line = $(template).clone();
  783. var tabindex = $("#mapping > div").length * 3 + 50;
  784. $(line).find("input").each(function() {
  785. $(this).attr("tabindex", tabindex++);
  786. });
  787. $(line).find("button").on('click', delParent);
  788. setOriginalsFromValues($("input", line));
  789. line.appendTo("#mapping");
  790. }
  791. <!-- endRemoveIf(!rfm69)-->
  792. // -----------------------------------------------------------------------------
  793. // Wifi
  794. // -----------------------------------------------------------------------------
  795. function numNetworks() {
  796. return $("#networks > div").length;
  797. }
  798. function delNetwork() {
  799. var parent = $(this).parents(".pure-g");
  800. $(parent).remove();
  801. }
  802. function moreNetwork() {
  803. var parent = $(this).parents(".pure-g");
  804. $(".more", parent).toggle();
  805. }
  806. function addNetwork(network) {
  807. var number = numNetworks();
  808. if (number >= maxNetworks) {
  809. alert("Max number of networks reached");
  810. return null;
  811. }
  812. if (network === undefined) {
  813. network = {};
  814. }
  815. var tabindex = 200 + number * 10;
  816. var template = $("#networkTemplate").children();
  817. var line = $(template).clone();
  818. $(line).find("input").each(function() {
  819. $(this).attr("tabindex", tabindex);
  820. tabindex++;
  821. });
  822. $(".password-reveal", line).on("click", toggleVisiblePassword);
  823. $(line).find(".button-del-network").on("click", delNetwork);
  824. $(line).find(".button-more-network").on("click", moreNetwork);
  825. Object.entries(network).forEach(function(pair) {
  826. // XXX: UI deleting this network will only re-use stored values.
  827. var key = pair[0],
  828. val = pair[1];
  829. if (key === "stored") {
  830. $(line).find(".button-del-network").prop("disabled", val);
  831. return;
  832. }
  833. $("input[name='" + key + "']", line).val(val);
  834. });
  835. line.appendTo("#networks");
  836. return line;
  837. }
  838. // -----------------------------------------------------------------------------
  839. // Relays scheduler
  840. // -----------------------------------------------------------------------------
  841. function numSchedules() {
  842. return $("#schedules > div").length;
  843. }
  844. function maxSchedules() {
  845. var value = $("#schedules").attr("data-settings-max");
  846. return parseInt(value === undefined ? 0 : value, 10);
  847. }
  848. function delSchedule() {
  849. var parent = $(this).parents(".pure-g");
  850. $(parent).remove();
  851. }
  852. function moreSchedule() {
  853. var parent = $(this).parents(".pure-g");
  854. $("div.more", parent).toggle();
  855. }
  856. function addSchedule(values) {
  857. var schedules = numSchedules();
  858. if (schedules >= maxSchedules()) {
  859. alert("Max number of schedules reached");
  860. return null;
  861. }
  862. if (values === undefined) {
  863. values = {};
  864. }
  865. var tabindex = 200 + schedules * 10;
  866. var template = $("#scheduleTemplate").children();
  867. var line = $(template).clone();
  868. var type = "none";
  869. switch(values.schType) {
  870. case 1:
  871. type = "switch";
  872. break;
  873. case 2:
  874. type = "light";
  875. break;
  876. case 3:
  877. type = "curtain";
  878. break;
  879. }
  880. template = $("#" + type + "ActionTemplate").children();
  881. $(line).find("#schActionDiv").append(template.clone());
  882. $(line).find("input").each(function() {
  883. $(this).attr("tabindex", tabindex);
  884. tabindex++;
  885. });
  886. $(line).find(".button-del-schedule").on("click", delSchedule);
  887. $(line).find(".button-more-schedule").on("click", moreSchedule);
  888. var schUTC_id = "schUTC" + schedules;
  889. $(line).find("input[name='schUTC']").prop("id", schUTC_id).next().prop("for", schUTC_id);
  890. var schEnabled_id = "schEnabled" + schedules;
  891. $(line).find("input[name='schEnabled']").prop("id", schEnabled_id).next().prop("for", schEnabled_id);
  892. $(line).find("input[type='checkbox']").prop("checked", false);
  893. Object.entries(values).forEach(function(kv) {
  894. var key = kv[0], value = kv[1];
  895. $("input[name='" + key + "']", line).val(value);
  896. $("select[name='" + key + "']", line).prop("value", value);
  897. $("input[type='checkbox'][name='" + key + "']", line).prop("checked", value);
  898. });
  899. line.appendTo("#schedules");
  900. return line;
  901. }
  902. // -----------------------------------------------------------------------------
  903. // Relays
  904. // -----------------------------------------------------------------------------
  905. function initRelays(data) {
  906. var current = $("#relays > div").length;
  907. if (current > 0) { return; }
  908. var template = $("#relayTemplate .pure-g")[0];
  909. for (var i=0; i<data.length; i++) {
  910. // Add relay fields
  911. var line = $(template).clone();
  912. $(".id", line).html(i);
  913. $(":checkbox", line).prop('checked', data[i]).attr("data", i)
  914. .prop("id", "relay" + i)
  915. .on("change", function (event) {
  916. var id = parseInt($(event.target).attr("data"), 10);
  917. var status = $(event.target).prop("checked");
  918. doToggle(id, status);
  919. });
  920. $("label.toggle", line).prop("for", "relay" + i)
  921. line.appendTo("#relays");
  922. }
  923. }
  924. function updateRelays(data) {
  925. var size = data.size;
  926. for (var i=0; i<size; ++i) {
  927. var elem = $("input[name='relay'][data='" + i + "']");
  928. elem.prop("checked", data.status[i]);
  929. var lock = {
  930. 0: false,
  931. 1: !data.status[i],
  932. 2: data.status[i]
  933. };
  934. elem.prop("disabled", lock[data.lock[i]]); // RELAY_LOCK_DISABLED=0
  935. }
  936. }
  937. function createCheckboxes() {
  938. $("input[type='checkbox']").each(function() {
  939. if($(this).prop("name"))$(this).prop("id", $(this).prop("name"));
  940. $(this).parent().addClass("toggleWrapper");
  941. $(this).after('<label for="' + $(this).prop("name") + '" class="toggle"><span class="toggle__handler"></span></label>')
  942. });
  943. }
  944. function initRelayConfig(data) {
  945. var current = $("#relayConfig > legend").length; // there is a legend per relay
  946. if (current > 0) { return; }
  947. var size = data.size;
  948. var start = data.start;
  949. var template = $("#relayConfigTemplate").children();
  950. for (var i=start; i<size; ++i) {
  951. var line = $(template).clone();
  952. $("span.id", line).html(i);
  953. $("span.gpio", line).html(data.gpio[i]);
  954. $("select[name='relayBoot']", line).val(data.boot[i]);
  955. $("select[name='relayPulse']", line).val(data.pulse[i]);
  956. $("input[name='relayTime']", line).val(data.pulse_time[i]);
  957. if ("sch_last" in data) {
  958. $("input[name='relayLastSch']", line)
  959. .prop('checked', data.sch_last[i])
  960. .attr("id", "relayLastSch" + i)
  961. .attr("name", "relayLastSch" + i)
  962. .next().attr("for","relayLastSch" + (i));
  963. }
  964. if ("group" in data) {
  965. $("input[name='mqttGroup']", line).val(data.group[i]);
  966. }
  967. if ("group_sync" in data) {
  968. $("select[name='mqttGroupSync']", line).val(data.group_sync[i]);
  969. }
  970. if ("on_disc" in data) {
  971. $("select[name='relayOnDisc']", line).val(data.on_disc[i]);
  972. }
  973. setOriginalsFromValues($("input,select", line));
  974. line.appendTo("#relayConfig");
  975. // Populate the relay SELECTs
  976. $("select.isrelay").append(
  977. $("<option></option>")
  978. .attr("value", i)
  979. .text("Switch #" + i)
  980. );
  981. }
  982. }
  983. // -----------------------------------------------------------------------------
  984. // Sensors & Magnitudes
  985. // -----------------------------------------------------------------------------
  986. <!-- removeIf(!sensor)-->
  987. function initMagnitudes(data) {
  988. // check if already initialized (each magnitude is inside div.pure-g)
  989. var done = $("#magnitudes > div").length;
  990. if (done > 0) { return; }
  991. var size = data.size;
  992. // add templates
  993. var template = $("#magnitudeTemplate").children();
  994. for (var i=0; i<size; ++i) {
  995. var magnitude = {
  996. "name": MagnitudeNames[data.type[i]] + " #" + parseInt(data.index[i], 10),
  997. "units": data.units[i],
  998. "description": data.description[i]
  999. };
  1000. Magnitudes.push(magnitude);
  1001. var line = $(template).clone();
  1002. $("label", line).html(magnitude.name);
  1003. $("input", line).attr("data", i);
  1004. $("div.sns-desc", line).html(magnitude.description);
  1005. $("div.sns-info", line).hide();
  1006. line.appendTo("#magnitudes");
  1007. }
  1008. }
  1009. <!-- endRemoveIf(!sensor)-->
  1010. // -----------------------------------------------------------------------------
  1011. // Curtains
  1012. // -----------------------------------------------------------------------------
  1013. <!-- removeIf(!curtain)-->
  1014. //Create the controls for one curtain. It is called when curtain is updated (so created the first time)
  1015. //Let this there as we plan to have more than one curtain per switch
  1016. function initCurtain(data) {
  1017. var current = $("#curtains > div").length;
  1018. if (current > 0) { return; }
  1019. // add curtain template (prepare multi switches)
  1020. var template = $("#curtainTemplate").children();
  1021. var line = $(template).clone();
  1022. // init curtain button
  1023. $(line).find(".button-curtain-open").on("click", function() {
  1024. sendAction("curtainAction", {button: 1});
  1025. $(this).css('background', 'red');
  1026. });
  1027. $(line).find(".button-curtain-pause").on("click", function() {
  1028. sendAction("curtainAction", {button: 0});
  1029. $(this).css('background', 'red');
  1030. });
  1031. $(line).find(".button-curtain-close").on("click", function() {
  1032. sendAction("curtainAction", {button: 2});
  1033. $(this).css('background', 'red');
  1034. });
  1035. line.appendTo("#curtains");
  1036. // init curtain slider
  1037. $("#curtainSet").on("change", function() {
  1038. var value = $(this).val();
  1039. var parent = $(this).parents(".pure-g");
  1040. $("span", parent).html(value);
  1041. sendAction("curtainAction", {position: value});
  1042. });
  1043. }
  1044. function initCurtainConfig(data) {
  1045. var current = $("#curtainConfig > legend").length; // there is a legend per relay
  1046. if (current > 0) { return; }
  1047. // Populate the curtain select
  1048. $("select.iscurtain").append(
  1049. $("<option></option>")
  1050. .attr("value", "0")
  1051. .text("Curtain #" + "0")
  1052. );
  1053. }
  1054. <!-- endRemoveIf(!curtain)-->
  1055. // -----------------------------------------------------------------------------
  1056. // Lights
  1057. // -----------------------------------------------------------------------------
  1058. <!-- removeIf(!light)-->
  1059. // wheelColorPicker accepts:
  1060. // hsv(0...360,0...1,0...1)
  1061. // hsv(0...100%,0...100%,0...100%)
  1062. // While we use:
  1063. // hsv(0...360,0...100%,0...100%)
  1064. function _hsv_round(value) {
  1065. return Math.round(value * 100) / 100;
  1066. }
  1067. function getPickerRGB(picker) {
  1068. return $(picker).wheelColorPicker("getValue", "css");
  1069. }
  1070. function setPickerRGB(picker, value) {
  1071. $(picker).wheelColorPicker("setValue", value, true);
  1072. }
  1073. // TODO: use pct values instead of doing conversion?
  1074. function getPickerHSV(picker) {
  1075. var color = $(picker).wheelColorPicker("getColor");
  1076. return String(Math.ceil(_hsv_round(color.h) * 360))
  1077. + "," + String(Math.ceil(_hsv_round(color.s) * 100))
  1078. + "," + String(Math.ceil(_hsv_round(color.v) * 100));
  1079. }
  1080. function setPickerHSV(picker, value) {
  1081. if (value === getPickerHSV(picker)) return;
  1082. var chunks = value.split(",");
  1083. $(picker).wheelColorPicker("setColor", {
  1084. h: _hsv_round(chunks[0] / 360),
  1085. s: _hsv_round(chunks[1] / 100),
  1086. v: _hsv_round(chunks[2] / 100)
  1087. });
  1088. }
  1089. function initColor(cfg) {
  1090. var rgb = false;
  1091. if (typeof cfg === "object") {
  1092. rgb = cfg.rgb;
  1093. }
  1094. // check if already initialized
  1095. var done = $("#colors > div").length;
  1096. if (done > 0) { return; }
  1097. // add template
  1098. var template = $("#colorTemplate").children();
  1099. var line = $(template).clone();
  1100. line.appendTo("#colors");
  1101. // init color wheel
  1102. $("input[name='color']").wheelColorPicker({
  1103. sliders: (rgb ? "wrgbp" : "whsp")
  1104. }).on("sliderup", function() {
  1105. if (rgb) {
  1106. sendAction("color", {rgb: getPickerRGB(this)});
  1107. } else {
  1108. sendAction("color", {hsv: getPickerHSV(this)});
  1109. }
  1110. });
  1111. }
  1112. function initCCT() {
  1113. // check if already initialized
  1114. var done = $("#cct > div").length;
  1115. if (done > 0) { return; }
  1116. $("#miredsTemplate").children().clone().appendTo("#cct");
  1117. $("#mireds").on("change", function() {
  1118. var value = $(this).val();
  1119. var parent = $(this).parents(".pure-g");
  1120. $("span", parent).html(value);
  1121. sendAction("mireds", {mireds: value});
  1122. });
  1123. }
  1124. function initChannels(num) {
  1125. // check if already initialized
  1126. var done = $("#channels > div").length > 0;
  1127. if (done) { return; }
  1128. // does it have color channels?
  1129. var colors = $("#colors > div").length > 0;
  1130. // calculate channels to create
  1131. var max = num;
  1132. if (colors) {
  1133. max = num % 3;
  1134. if ((max > 0) & useWhite) {
  1135. max--;
  1136. if (useCCT) {
  1137. max--;
  1138. }
  1139. }
  1140. }
  1141. var start = num - max;
  1142. var onChannelSliderChange = function() {
  1143. var id = $(this).attr("data");
  1144. var value = $(this).val();
  1145. var parent = $(this).parents(".pure-g");
  1146. $("span", parent).html(value);
  1147. sendAction("channel", {id: id, value: value});
  1148. };
  1149. // add channel templates
  1150. var i = 0;
  1151. var template = $("#channelTemplate").children();
  1152. for (i=0; i<max; i++) {
  1153. var channel_id = start + i;
  1154. var line = $(template).clone();
  1155. $("span.slider", line).attr("data", channel_id);
  1156. $("input.slider", line).attr("data", channel_id).on("change", onChannelSliderChange);
  1157. $("label", line).html("Channel #" + channel_id);
  1158. line.appendTo("#channels");
  1159. }
  1160. // Init channel dropdowns
  1161. for (i=0; i<num; i++) {
  1162. $("select.islight").append(
  1163. $("<option></option>").attr("value",i).text("Channel #" + i));
  1164. }
  1165. // add brightness template
  1166. var template = $("#brightnessTemplate").children();
  1167. var line = $(template).clone();
  1168. line.appendTo("#channels");
  1169. // init bright slider
  1170. $("#brightness").on("change", function() {
  1171. var value = $(this).val();
  1172. var parent = $(this).parents(".pure-g");
  1173. $("span", parent).html(value);
  1174. sendAction("brightness", {value: value});
  1175. });
  1176. }
  1177. <!-- endRemoveIf(!light)-->
  1178. // -----------------------------------------------------------------------------
  1179. // RFBridge
  1180. // -----------------------------------------------------------------------------
  1181. <!-- removeIf(!rfbridge)-->
  1182. function rfbLearn() {
  1183. var parent = $(this).parents(".pure-g");
  1184. var input = $("input", parent);
  1185. sendAction("rfblearn", {id: input.attr("data-id"), status: input.attr("data-status")});
  1186. }
  1187. function rfbForget() {
  1188. var parent = $(this).parents(".pure-g");
  1189. var input = $("input", parent);
  1190. sendAction("rfbforget", {id: input.attr("data-id"), status: input.attr("data-status")});
  1191. }
  1192. function rfbSend() {
  1193. var parent = $(this).parents(".pure-g");
  1194. var input = $("input", parent);
  1195. sendAction("rfbsend", {id: input.attr("data-id"), status: input.attr("data-status"), data: input.val()});
  1196. }
  1197. function addRfbNode() {
  1198. var numNodes = $("#rfbNodes > legend").length;
  1199. var template = $("#rfbNodeTemplate").children();
  1200. var line = $(template).clone();
  1201. $("span", line).html(numNodes);
  1202. $(line).find("input").each(function() {
  1203. this.dataset["id"] = numNodes;
  1204. });
  1205. $(line).find(".button-rfb-learn").on("click", rfbLearn);
  1206. $(line).find(".button-rfb-forget").on("click", rfbForget);
  1207. $(line).find(".button-rfb-send").on("click", rfbSend);
  1208. line.appendTo("#rfbNodes");
  1209. return line;
  1210. }
  1211. <!-- endRemoveIf(!rfbridge)-->
  1212. // -----------------------------------------------------------------------------
  1213. // LightFox
  1214. // -----------------------------------------------------------------------------
  1215. <!-- removeIf(!lightfox)-->
  1216. function lightfoxLearn() {
  1217. sendAction("lightfoxLearn", {});
  1218. }
  1219. function lightfoxClear() {
  1220. sendAction("lightfoxClear", {});
  1221. }
  1222. function initLightfox(data, relayCount) {
  1223. var numNodes = data.length;
  1224. var template = $("#lightfoxNodeTemplate").children();
  1225. var i, j;
  1226. for (i=0; i<numNodes; i++) {
  1227. var $line = $(template).clone();
  1228. $line.find("label > span").text(data[i]["id"]);
  1229. $line.find("select").each(function() {
  1230. $(this).attr("name", "btnRelay" + data[i]["id"]);
  1231. for (j=0; j < relayCount; j++) {
  1232. $(this).append($("<option >").attr("value", j).text("Switch #" + j));
  1233. }
  1234. $(this).val(data[i]["relay"]);
  1235. status = !status;
  1236. });
  1237. setOriginalsFromValues($("input,select", $line));
  1238. $line.appendTo("#lightfoxNodes");
  1239. }
  1240. var $panel = $("#panel-lightfox")
  1241. $(".button-lightfox-learn").off("click").click(lightfoxLearn);
  1242. $(".button-lightfox-clear").off("click").click(lightfoxClear);
  1243. }
  1244. <!-- endRemoveIf(!lightfox)-->
  1245. // -----------------------------------------------------------------------------
  1246. // Processing
  1247. // -----------------------------------------------------------------------------
  1248. function processData(data) {
  1249. if (debug) console.log(data);
  1250. // title
  1251. if ("app_name" in data) {
  1252. var title = data.app_name;
  1253. if ("app_version" in data) {
  1254. title = title + " " + data.app_version;
  1255. }
  1256. $("span[name=title]").html(title);
  1257. if ("hostname" in data) {
  1258. title = data.hostname + " - " + title;
  1259. }
  1260. document.title = title;
  1261. }
  1262. Object.keys(data).forEach(function(key) {
  1263. var i;
  1264. var value = data[key];
  1265. // ---------------------------------------------------------------------
  1266. // Web mode
  1267. // ---------------------------------------------------------------------
  1268. if ("webMode" === key) {
  1269. password = (1 === value);
  1270. $("#layout").toggle(!password);
  1271. $("#password").toggle(password);
  1272. }
  1273. // ---------------------------------------------------------------------
  1274. // Actions
  1275. // ---------------------------------------------------------------------
  1276. if ("action" === key) {
  1277. if ("reload" === data.action) { doReload(1000); }
  1278. return;
  1279. }
  1280. // ---------------------------------------------------------------------
  1281. // RFBridge
  1282. // ---------------------------------------------------------------------
  1283. <!-- removeIf(!rfbridge)-->
  1284. if ("rfbCount" === key) {
  1285. for (i=0; i<data.rfbCount; i++) { addRfbNode(); }
  1286. return;
  1287. }
  1288. if ("rfb" === key) {
  1289. var rfb = data.rfb;
  1290. var size = rfb.size;
  1291. var start = rfb.start;
  1292. var processOn = ((rfb.on !== undefined) && (rfb.on.length > 0));
  1293. var processOff = ((rfb.off !== undefined) && (rfb.off.length > 0));
  1294. for (var i=0; i<size; ++i) {
  1295. if (processOn) $("input[name='rfbcode'][data-id='" + (i + start) + "'][data-status='1']").val(rfb.on[i]);
  1296. if (processOff) $("input[name='rfbcode'][data-id='" + (i + start) + "'][data-status='0']").val(rfb.off[i]);
  1297. }
  1298. return;
  1299. }
  1300. <!-- endRemoveIf(!rfbridge)-->
  1301. // ---------------------------------------------------------------------
  1302. // LightFox
  1303. // ---------------------------------------------------------------------
  1304. <!-- removeIf(!lightfox)-->
  1305. if ("lightfoxButtons" === key) {
  1306. initLightfox(data["lightfoxButtons"], data["lightfoxRelayCount"]);
  1307. return;
  1308. }
  1309. <!-- endRemoveIf(!lightfox)-->
  1310. // ---------------------------------------------------------------------
  1311. // RFM69
  1312. // ---------------------------------------------------------------------
  1313. <!-- removeIf(!rfm69)-->
  1314. if (key == "packet") {
  1315. var packet = data.packet;
  1316. var d = new Date();
  1317. packets.row.add([
  1318. d.toLocaleTimeString('en-US', { hour12: false }),
  1319. packet.senderID,
  1320. packet.packetID,
  1321. packet.targetID,
  1322. packet.key,
  1323. packet.value,
  1324. packet.rssi,
  1325. packet.duplicates,
  1326. packet.missing,
  1327. ]).draw(false);
  1328. return;
  1329. }
  1330. if (key == "mapping") {
  1331. for (var i in data.mapping) {
  1332. // add a new row
  1333. addMapping();
  1334. // get group
  1335. var line = $("#mapping .pure-g")[i];
  1336. // fill in the blanks
  1337. var mapping = data.mapping[i];
  1338. Object.keys(mapping).forEach(function(key) {
  1339. var id = "input[name=" + key + "]";
  1340. if ($(id, line).length) $(id, line).val(mapping[key]);
  1341. });
  1342. setOriginalsFromValues($("input", line));
  1343. }
  1344. return;
  1345. }
  1346. <!-- endRemoveIf(!rfm69)-->
  1347. // ---------------------------------------------------------------------
  1348. // RPN Rules
  1349. // ---------------------------------------------------------------------
  1350. if (key == "rpnRules") {
  1351. for (var i in data.rpnRules) {
  1352. // add a new row
  1353. addRPNRule();
  1354. // get group
  1355. var line = $("#rpnRules .pure-g")[i];
  1356. // fill in the blanks
  1357. var rule = data.rpnRules[i];
  1358. $("input", line).val(rule);
  1359. setOriginalsFromValues($("input", line));
  1360. }
  1361. return;
  1362. }
  1363. if (key == "rpnTopics") {
  1364. for (var i in data.rpnTopics) {
  1365. // add a new row
  1366. addRPNTopic();
  1367. // get group
  1368. var line = $("#rpnTopics .pure-g")[i];
  1369. // fill in the blanks
  1370. var topic = data.rpnTopics[i];
  1371. var name = data.rpnNames[i];
  1372. $("input[name='rpnTopic']", line).val(topic);
  1373. $("input[name='rpnName']", line).val(name);
  1374. setOriginalsFromValues($("input", line));
  1375. }
  1376. return;
  1377. }
  1378. if (key == "rpnNames") return;
  1379. // ---------------------------------------------------------------------
  1380. // Curtains
  1381. // ---------------------------------------------------------------------
  1382. <!-- removeIf(!curtain)-->
  1383. function applyCurtain(a, b) {
  1384. $("#curtainGetPicture").css('background', 'linear-gradient(' + a + ', black ' + b + '%, #a0d6ff ' + b + '%)');
  1385. }
  1386. if ("curtainState" === key) {
  1387. initCurtain();
  1388. switch(value.type) {
  1389. case '0': //Roller
  1390. default:
  1391. applyCurtain('180deg', value.get);
  1392. break;
  1393. case '1': //One side left to right
  1394. applyCurtain('90deg', value.get);
  1395. break;
  1396. case '2': //One side right to left
  1397. applyCurtain('270deg', value.get);
  1398. break;
  1399. case '3': //Two sides
  1400. $("#curtainGetPicture").css('background', 'linear-gradient(90deg, black ' + value.get/2 + '%, #a0d6ff ' + value.get/2 + '% ' + (100 - value.get/2) + '%, black ' + (100 - value.get/2) + '%)');
  1401. break;
  1402. }
  1403. $("#curtainSet").val(value.set);
  1404. if(!value.moving) {
  1405. $("button.curtain-button").css('background', 'rgb(66, 184, 221)');
  1406. } else {
  1407. if(!value.button)
  1408. $("button.button-curtain-pause").css('background', 'rgb(192, 0, 0)');
  1409. else if(value.button == 1) {
  1410. $("button.button-curtain-close").css('background', 'rgb(66, 184, 221)');
  1411. $("button.button-curtain-open").css('background', 'rgb(192, 0, 0)');
  1412. }
  1413. else if(value.button == 2) {
  1414. $("button.button-curtain-open").css('background', 'rgb(66, 184, 221)');
  1415. $("button.button-curtain-close").css('background', 'rgb(192, 0, 0)');
  1416. }
  1417. }
  1418. return;
  1419. }
  1420. <!-- endRemoveIf(!curtain)-->
  1421. // ---------------------------------------------------------------------
  1422. // Lights
  1423. // ---------------------------------------------------------------------
  1424. <!-- removeIf(!light)-->
  1425. if ("rgb" === key) {
  1426. initColor({rgb: true});
  1427. setPickerRGB($("input[name='color']"), value);
  1428. return;
  1429. }
  1430. if ("hsv" === key) {
  1431. initColor({hsv: true});
  1432. setPickerHSV($("input[name='color']"), value);
  1433. return;
  1434. }
  1435. if ("brightness" === key) {
  1436. $("#brightness").val(value);
  1437. $("span.brightness").html(value);
  1438. return;
  1439. }
  1440. if ("channels" === key) {
  1441. var len = value.length;
  1442. initChannels(len);
  1443. for (i in value) {
  1444. var ch = value[i];
  1445. $("input.slider[data=" + i + "]").val(ch);
  1446. $("span.slider[data=" + i + "]").html(ch);
  1447. }
  1448. return;
  1449. }
  1450. if ("mireds" === key) {
  1451. $("#mireds").attr("min", value["cold"]);
  1452. $("#mireds").attr("max", value["warm"]);
  1453. $("#mireds").val(value["value"]);
  1454. $("span.mireds").html(value["value"]);
  1455. return;
  1456. }
  1457. if ("useWhite" === key) {
  1458. useWhite = value;
  1459. }
  1460. if ("useCCT" === key) {
  1461. initCCT();
  1462. useCCT = value;
  1463. }
  1464. <!-- endRemoveIf(!light)-->
  1465. // ---------------------------------------------------------------------
  1466. // Sensors & Magnitudes
  1467. // ---------------------------------------------------------------------
  1468. <!-- removeIf(!sensor)-->
  1469. {
  1470. var position = key.indexOf("Correction");
  1471. if (position > 0 && position === key.length - 10) {
  1472. var template = $("#magnitudeCorrectionTemplate > div")[0];
  1473. var elem = $(template).clone();
  1474. var prefix = key.slice(0, position);
  1475. $("label", elem).html(MagnitudeNames[MagnitudePrefixTypes[prefix]]);
  1476. $("input", elem).attr("name", key).val(value);
  1477. setOriginalsFromValues($("input", elem));
  1478. elem.appendTo("#magnitude-corrections");
  1479. moduleVisible("magnitude-corrections");
  1480. return;
  1481. }
  1482. }
  1483. if ("snsErrors" === key) {
  1484. for (var index in value) {
  1485. var type = value[index][0];
  1486. var name = value[index][1];
  1487. MagnitudeErrors[type] = name;
  1488. }
  1489. return;
  1490. }
  1491. if ("snsMagnitudes" === key) {
  1492. for (var index in value) {
  1493. var type = value[index][0];
  1494. var prefix = value[index][1];
  1495. var name = value[index][2];
  1496. MagnitudeNames[type] = name;
  1497. MagnitudeTypePrefixes[type] = prefix;
  1498. MagnitudePrefixTypes[prefix] = type;
  1499. moduleVisible(prefix);
  1500. }
  1501. return;
  1502. }
  1503. if ("magnitudesConfig" === key) {
  1504. initMagnitudes(value);
  1505. return;
  1506. }
  1507. if ("magnitudes" === key) {
  1508. for (var i=0; i<value.size; ++i) {
  1509. var inputElem = $("input[name='magnitude'][data='" + i + "']");
  1510. var infoElem = inputElem.parent().parent().find("div.sns-info");
  1511. var error = value.error[i] || 0;
  1512. var text = (0 === error)
  1513. ? value.value[i] + Magnitudes[i].units
  1514. : MagnitudeErrors[error];
  1515. inputElem.val(text);
  1516. if (value.info !== undefined) {
  1517. var info = value.info[i] || 0;
  1518. infoElem.toggle(info != 0);
  1519. infoElem.text(info);
  1520. }
  1521. }
  1522. return;
  1523. }
  1524. if ("pzemVisible" === key) {
  1525. $("input[name='snsSave']").prop("disabled", true);
  1526. $("input[name='snsSave']")
  1527. .parent().parent().find(".hint")
  1528. .text("PZEM004 module saves the energy data on it's own")
  1529. return;
  1530. }
  1531. <!-- endRemoveIf(!sensor)-->
  1532. // ---------------------------------------------------------------------
  1533. // WiFi
  1534. // ---------------------------------------------------------------------
  1535. if ("wifi" === key) {
  1536. maxNetworks = parseInt(value["max"], 10);
  1537. value["networks"].forEach(function(network) {
  1538. var schema = value["schema"];
  1539. if (schema.length !== network.length) {
  1540. throw "WiFi schema mismatch!";
  1541. }
  1542. var _network = {};
  1543. schema.forEach(function(key, index) {
  1544. _network[key] = network[index];
  1545. });
  1546. addNetwork(_network);
  1547. });
  1548. return;
  1549. }
  1550. if ("scanResult" === key) {
  1551. $("div.scan.loading").hide();
  1552. $("#scanResult").show();
  1553. }
  1554. // -----------------------------------------------------------------------------
  1555. // Home Assistant
  1556. // -----------------------------------------------------------------------------
  1557. if ("haConfig" === key) {
  1558. send("{}");
  1559. $("#haConfig")
  1560. .append(new Text(value))
  1561. .height($("#haConfig")[0].scrollHeight);
  1562. return;
  1563. }
  1564. // -----------------------------------------------------------------------------
  1565. // Relays scheduler
  1566. // -----------------------------------------------------------------------------
  1567. if ("schedules" === key) {
  1568. $("#schedules").attr("data-settings-max", value.max);
  1569. for (var i=0; i<value.size; ++i) {
  1570. // XXX: no
  1571. var sch_map = {};
  1572. Object.keys(value).forEach(function(key) {
  1573. if ("size" == key) return;
  1574. if ("max" == key) return;
  1575. sch_map[key] = value[key][i];
  1576. });
  1577. addSchedule(sch_map);
  1578. }
  1579. return;
  1580. }
  1581. // ---------------------------------------------------------------------
  1582. // Relays
  1583. // ---------------------------------------------------------------------
  1584. if ("relayState" === key) {
  1585. initRelays(value.status);
  1586. updateRelays(value);
  1587. return;
  1588. }
  1589. // Relay configuration
  1590. if ("relayConfig" === key) {
  1591. initRelayConfig(value);
  1592. return;
  1593. }
  1594. // ---------------------------------------------------------------------
  1595. // Curtain(s)
  1596. // ---------------------------------------------------------------------
  1597. <!-- removeIf(!curtain)-->
  1598. // Relay configuration
  1599. if ("curtainConfig" === key) {
  1600. initCurtainConfig(value);
  1601. return;
  1602. }
  1603. <!-- endRemoveIf(!curtain)-->
  1604. // ---------------------------------------------------------------------
  1605. // LEDs
  1606. // ---------------------------------------------------------------------
  1607. if ("led" === key) {
  1608. if($("#ledConfig > div").length > 0) return;
  1609. var schema = value["schema"];
  1610. value["list"].forEach(function(led_data, index) {
  1611. if (schema.length !== led_data.length) {
  1612. throw "LED schema mismatch!";
  1613. }
  1614. var led = {};
  1615. schema.forEach(function(key, index) {
  1616. led[key] = led_data[index];
  1617. });
  1618. var line = $($("#ledConfigTemplate").children()).clone();
  1619. $("span.id", line).html(index);
  1620. $("select", line).attr("data", index);
  1621. $("input", line).attr("data", index);
  1622. $("select[name='ledGPIO']", line).val(led.GPIO);
  1623. // XXX: checkbox implementation depends on unique id
  1624. // $("input[name='ledInv']", line).val(led.Inv);
  1625. $("select[name='ledMode']", line).val(led.Mode);
  1626. $("input[name='ledRelay']", line).val(led.Relay);
  1627. setOriginalsFromValues($("input,select", line));
  1628. line.appendTo("#ledConfig");
  1629. });
  1630. return;
  1631. }
  1632. // ---------------------------------------------------------------------
  1633. // Domoticz
  1634. // ---------------------------------------------------------------------
  1635. // Domoticz - Relays
  1636. if ("dczRelays" === key) {
  1637. createRelayList(value, "dczRelays", "dczRelayTemplate");
  1638. return;
  1639. }
  1640. // Domoticz - Magnitudes
  1641. <!-- removeIf(!sensor)-->
  1642. if ("dczMagnitudes" === key) {
  1643. createMagnitudeList(value, "dczMagnitudes", "dczMagnitudeTemplate");
  1644. return;
  1645. }
  1646. <!-- endRemoveIf(!sensor)-->
  1647. // ---------------------------------------------------------------------
  1648. // Thingspeak
  1649. // ---------------------------------------------------------------------
  1650. // Thingspeak - Relays
  1651. if ("tspkRelays" === key) {
  1652. createRelayList(value, "tspkRelays", "tspkRelayTemplate");
  1653. return;
  1654. }
  1655. // Thingspeak - Magnitudes
  1656. <!-- removeIf(!sensor)-->
  1657. if ("tspkMagnitudes" === key) {
  1658. createMagnitudeList(value, "tspkMagnitudes", "tspkMagnitudeTemplate");
  1659. return;
  1660. }
  1661. <!-- endRemoveIf(!sensor)-->
  1662. // ---------------------------------------------------------------------
  1663. // HTTP API
  1664. // ---------------------------------------------------------------------
  1665. // Auto generate an APIKey if none defined yet
  1666. if ("apiVisible" === key) {
  1667. if (data.apiKey === undefined || data.apiKey === "") {
  1668. generateAPIKey();
  1669. }
  1670. }
  1671. // ---------------------------------------------------------------------
  1672. // General
  1673. // ---------------------------------------------------------------------
  1674. // Messages
  1675. if ("message" === key) {
  1676. if (value == 8) {
  1677. configurationSaved = true;
  1678. }
  1679. window.alert(messages[value]);
  1680. return;
  1681. }
  1682. // Web log
  1683. if ("weblog" === key) {
  1684. send("{}");
  1685. var msg = value["msg"];
  1686. var pre = value["pre"];
  1687. for (var i=0; i < msg.length; ++i) {
  1688. if (pre[i]) {
  1689. $("#weblog").append(new Text(pre[i]));
  1690. }
  1691. $("#weblog").append(new Text(msg[i]));
  1692. }
  1693. $("#weblog").scrollTop($("#weblog")[0].scrollHeight - $("#weblog").height());
  1694. return;
  1695. }
  1696. // Enable options
  1697. var position = key.indexOf("Visible");
  1698. if (position > 0 && position === key.length - 7) {
  1699. var module = key.slice(0,-7);
  1700. moduleVisible(module);
  1701. return;
  1702. }
  1703. if ("deviceip" === key) {
  1704. var a_href = $("span[name='" + key + "']").parent();
  1705. a_href.attr("href", "//" + value);
  1706. a_href.next().attr("href", "telnet://" + value);
  1707. }
  1708. if ("now" === key) {
  1709. now = value;
  1710. return;
  1711. }
  1712. if ("free_size" === key) {
  1713. free_size = parseInt(value, 10);
  1714. }
  1715. // Pre-process
  1716. if ("mqttStatus" === key) {
  1717. value = value ? "CONNECTED" : "NOT CONNECTED";
  1718. }
  1719. if ("ntpStatus" === key) {
  1720. value = value ? "SYNC'D" : "NOT SYNC'D";
  1721. }
  1722. if ("uptime" === key) {
  1723. ago = 0;
  1724. var uptime = parseInt(value, 10);
  1725. var seconds = uptime % 60; uptime = parseInt(uptime / 60, 10);
  1726. var minutes = uptime % 60; uptime = parseInt(uptime / 60, 10);
  1727. var hours = uptime % 24; uptime = parseInt(uptime / 24, 10);
  1728. var days = uptime;
  1729. value = days + "d " + zeroPad(hours, 2) + "h " + zeroPad(minutes, 2) + "m " + zeroPad(seconds, 2) + "s";
  1730. }
  1731. <!-- removeIf(!thermostat)-->
  1732. if ("tmpUnits" == key) {
  1733. $("span.tmpUnit").html(data[key] == 3 ? "ºF" : "ºC");
  1734. }
  1735. <!-- endRemoveIf(!thermostat)-->
  1736. // ---------------------------------------------------------------------
  1737. // Matching
  1738. // ---------------------------------------------------------------------
  1739. var elems = [];
  1740. var pre;
  1741. var post;
  1742. // Look for INPUTs
  1743. var input = $("input[name='" + key + "']");
  1744. if (input.length > 0) {
  1745. if (input.attr("type") === "checkbox") {
  1746. input.prop("checked", value);
  1747. } else if (input.attr("type") === "radio") {
  1748. input.val([value]);
  1749. } else {
  1750. pre = input.attr("pre") || "";
  1751. post = input.attr("post") || "";
  1752. input.val(pre + value + post);
  1753. }
  1754. elems.push(input);
  1755. }
  1756. // Look for SPANs
  1757. var span = $("span[name='" + key + "']");
  1758. if (span.length > 0) {
  1759. if (Array.isArray(value)) {
  1760. value.forEach(function(elem) {
  1761. span.append(elem);
  1762. span.append('</br>');
  1763. elems.push(span);
  1764. });
  1765. } else {
  1766. pre = span.attr("pre") || "";
  1767. post = span.attr("post") || "";
  1768. span.html(pre + value + post);
  1769. elems.push(span);
  1770. }
  1771. }
  1772. // Look for SELECTs
  1773. var select = $("select[name='" + key + "']");
  1774. if (select.length > 0) {
  1775. select.val(value);
  1776. elems.push(select);
  1777. }
  1778. setOriginalsFromValues($(elems));
  1779. });
  1780. }
  1781. function hasChanged() {
  1782. var newValue, originalValue;
  1783. if ($(this).attr("type") === "checkbox") {
  1784. newValue = $(this).prop("checked");
  1785. originalValue = ($(this).attr("original") === "true");
  1786. } else {
  1787. newValue = $(this).val();
  1788. originalValue = $(this).attr("original");
  1789. }
  1790. var hasChanged = ("true" === $(this).attr("hasChanged"));
  1791. var action = $(this).attr("action");
  1792. if (typeof originalValue === "undefined") { return; }
  1793. if ("none" === action) { return; }
  1794. if (newValue !== originalValue) {
  1795. if (!hasChanged) {
  1796. ++numChanged;
  1797. if ("reconnect" === action) { ++numReconnect; }
  1798. if ("reboot" === action) { ++numReboot; }
  1799. if ("reload" === action) { ++numReload; }
  1800. }
  1801. $(this).attr("hasChanged", true);
  1802. } else {
  1803. if (hasChanged) {
  1804. --numChanged;
  1805. if ("reconnect" === action) { --numReconnect; }
  1806. if ("reboot" === action) { --numReboot; }
  1807. if ("reload" === action) { --numReload; }
  1808. }
  1809. $(this).attr("hasChanged", false);
  1810. }
  1811. }
  1812. // -----------------------------------------------------------------------------
  1813. // Init & connect
  1814. // -----------------------------------------------------------------------------
  1815. function initUrls(root) {
  1816. var paths = ["ws", "upgrade", "config", "auth"];
  1817. urls["root"] = root;
  1818. paths.forEach(function(path) {
  1819. urls[path] = new URL(path, root);
  1820. urls[path].protocol = root.protocol;
  1821. });
  1822. if (root.protocol == "https:") {
  1823. urls.ws.protocol = "wss:";
  1824. } else {
  1825. urls.ws.protocol = "ws:";
  1826. }
  1827. }
  1828. function connectToURL(url) {
  1829. initUrls(url);
  1830. fetch(urls.auth.href, {
  1831. 'method': 'GET',
  1832. 'cors': true,
  1833. 'credentials': 'same-origin'
  1834. }).then(function(response) {
  1835. // Nothing to do, reload page and retry
  1836. if (response.status != 200) {
  1837. doReload(5000);
  1838. return;
  1839. }
  1840. // update websock object
  1841. if (websock) { websock.close(); }
  1842. websock = new WebSocket(urls.ws.href);
  1843. websock.onmessage = function(evt) {
  1844. var data = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
  1845. if (data) {
  1846. processData(data);
  1847. }
  1848. };
  1849. websock.onclose = function(evt) {
  1850. clearInterval(ws_pingpong);
  1851. if (window.confirm("Connection lost with the device, click OK to refresh the page")) {
  1852. $("#layout").toggle(false);
  1853. window.location.reload();
  1854. }
  1855. }
  1856. websock.onopen = function(evt) {
  1857. ws_pingpong = setInterval(function() { sendAction("ping", {}); }, 5000);
  1858. }
  1859. }).catch(function(error) {
  1860. console.log(error);
  1861. doReload(5000);
  1862. });
  1863. }
  1864. function connect(host) {
  1865. if (!host.startsWith("http:") && !host.startsWith("https:")) {
  1866. host = "http://" + host;
  1867. }
  1868. connectToURL(new URL(host));
  1869. }
  1870. function connectToCurrentURL() {
  1871. connectToURL(new URL(window.location));
  1872. }
  1873. $(function() {
  1874. initMessages();
  1875. loadTimeZones();
  1876. createCheckboxes();
  1877. setInterval(function() { keepTime(); }, 1000);
  1878. $(".password-reveal").on("click", toggleVisiblePassword);
  1879. $("#menuLink").on("click", toggleMenu);
  1880. $(".pure-menu-link").on("click", showPanel);
  1881. $("progress").attr({ value: 0, max: 100 });
  1882. $(".button-update").on("click", doUpdate);
  1883. $(".button-update-password").on("click", doUpdatePassword);
  1884. $(".button-generate-password").on("click", doGeneratePassword);
  1885. $(".button-reboot").on("click", doReboot);
  1886. $(".button-reconnect").on("click", doReconnect);
  1887. $(".button-wifi-scan").on("click", doScan);
  1888. $(".button-ha-config").on("click", doHAConfig);
  1889. $(".button-dbgcmd").on("click", doDebugCommand);
  1890. $("input[name='dbgcmd']").enterKey(doDebugCommand);
  1891. $(".button-dbg-clear").on("click", doDebugClear);
  1892. $(".button-settings-backup").on("click", doBackup);
  1893. $(".button-settings-restore").on("click", doRestore);
  1894. $(".button-settings-factory").on("click", doFactoryReset);
  1895. $("#uploader").on("change", onFileUpload);
  1896. $(".button-upgrade").on("click", doUpgrade);
  1897. <!-- removeIf(!thermostat)-->
  1898. $(".button-thermostat-reset-counters").on('click', doResetThermostatCounters);
  1899. <!-- endRemoveIf(!thermostat)-->
  1900. $(".button-apikey").on("click", generateAPIKey);
  1901. $(".button-upgrade-browse").on("click", function() {
  1902. $("input[name='upgrade']")[0].click();
  1903. return false;
  1904. });
  1905. $("input[name='upgrade']").change(function (){
  1906. var file = this.files[0];
  1907. $("input[name='filename']").val(file.name);
  1908. });
  1909. $(".button-add-network").on("click", function() {
  1910. $(".more", addNetwork()).toggle();
  1911. });
  1912. $(".button-add-switch-schedule").on("click", function() {
  1913. addSchedule({schType: 1, schSwitch: -1});
  1914. });
  1915. <!-- removeIf(!light)-->
  1916. $(".button-add-light-schedule").on("click", function() {
  1917. addSchedule({schType: 2, schSwitch: -1});
  1918. });
  1919. <!-- endRemoveIf(!light)-->
  1920. <!-- removeIf(!curtain)-->
  1921. $(".button-add-curtain-schedule").on("click", function() {
  1922. addSchedule({schType: 3, schSwitch: -1});
  1923. });
  1924. <!-- endRemoveIf(!curtain)-->
  1925. $(".button-add-rpnrule").on('click', addRPNRule);
  1926. $(".button-add-rpntopic").on('click', addRPNTopic);
  1927. $(".button-del-parent").on('click', delParent);
  1928. <!-- removeIf(!rfm69)-->
  1929. $(".button-add-mapping").on('click', addMapping);
  1930. $(".button-clear-counts").on('click', doClearCounts);
  1931. $(".button-clear-messages").on('click', doClearMessages);
  1932. $(".button-clear-filters").on('click', doClearFilters);
  1933. $('#packets tbody').on('mousedown', 'td', doFilter);
  1934. packets = $('#packets').DataTable({
  1935. "paging": false
  1936. });
  1937. for (var i = 0; i < packets.columns()[0].length; i++) {
  1938. filters[i] = false;
  1939. }
  1940. <!-- endRemoveIf(!rfm69)-->
  1941. $(".gpio-select").each(function(_, elem) {
  1942. initSelectGPIO(elem)
  1943. });
  1944. $(document).on("change", "input", hasChanged);
  1945. $(document).on("change", "select", hasChanged);
  1946. $("textarea").on("dblclick", function() { this.select(); });
  1947. resetOriginals();
  1948. $(".group-settings").each(function() {
  1949. groupSettingsObserver.observe(this, {childList: true});
  1950. });
  1951. // don't autoconnect when opening from filesystem
  1952. if (window.location.protocol === "file:") {
  1953. processData({"webMode": 0});
  1954. return;
  1955. }
  1956. // Check host param in query string
  1957. var search = new URLSearchParams(window.location.search),
  1958. host = search.get("host");
  1959. if (host !== null) {
  1960. connect(host);
  1961. } else {
  1962. connectToCurrentURL();
  1963. }
  1964. });