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.

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