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.

2405 lines
70 KiB

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