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.

1266 lines
37 KiB

6 years ago
6 years ago
  1. var websock;
  2. var password = false;
  3. var maxNetworks;
  4. var maxSchedules;
  5. var messages = [];
  6. var webhost;
  7. var numChanged = 0;
  8. var numReboot = 0;
  9. var numReconnect = 0;
  10. var numReload = 0;
  11. var useWhite = false;
  12. var manifest;
  13. var now = 0;
  14. var ago = 0;
  15. // -----------------------------------------------------------------------------
  16. // Messages
  17. // -----------------------------------------------------------------------------
  18. function initMessages() {
  19. messages[1] = "Remote update started";
  20. messages[2] = "OTA update started";
  21. messages[3] = "Error parsing data!";
  22. messages[4] = "The file does not look like a valid configuration backup or is corrupted";
  23. messages[5] = "Changes saved. You should reboot your board now";
  24. messages[7] = "Passwords do not match!";
  25. messages[8] = "Changes saved";
  26. messages[9] = "No changes detected";
  27. messages[10] = "Session expired, please reload page...";
  28. }
  29. function sensorName(id) {
  30. var names = [
  31. "DHT", "Dallas", "Emon Analog", "Emon ADC121", "Emon ADS1X15",
  32. "HLW8012", "V9261F", "ECH1560", "Analog", "Digital",
  33. "Events", "PMSX003", "BMX280", "MHZ19", "SI7021",
  34. "SHT3X I2C", "BH1750"
  35. ];
  36. if (1 <= id && id <= names.length) {
  37. return names[id - 1];
  38. }
  39. return null;
  40. }
  41. function magnitudeType(type) {
  42. var types = [
  43. "Temperature", "Humidity", "Pressure",
  44. "Current", "Voltage", "Active Power", "Apparent Power",
  45. "Reactive Power", "Power Factor", "Energy", "Energy (delta)",
  46. "Analog", "Digital", "Events",
  47. "PM1.0", "PM2.5", "PM10", "CO2", "Lux"
  48. ];
  49. if (1 <= type && type <= types.length) {
  50. return types[type - 1];
  51. }
  52. return null;
  53. }
  54. function magnitudeError(error) {
  55. var errors = [
  56. "OK", "Out of Range", "Warming Up", "Timeout", "Wrong ID",
  57. "Data Error", "I2C Error", "GPIO Error"
  58. ];
  59. if (0 <= error && error < errors.length) {
  60. return errors[error];
  61. }
  62. return "Error " + error;
  63. }
  64. // -----------------------------------------------------------------------------
  65. // Utils
  66. // -----------------------------------------------------------------------------
  67. function keepTime() {
  68. if (now === 0) { return; }
  69. var date = new Date(now * 1000);
  70. var text = date.toISOString().substring(0, 19).replace("T", " ");
  71. $("input[name='now']").val(text);
  72. $("span[name='now']").html(text);
  73. $("span[name='ago']").html(ago);
  74. now++;
  75. ago++;
  76. }
  77. // http://www.the-art-of-web.com/javascript/validate-password/
  78. function checkPassword(str) {
  79. // at least one lowercase and one uppercase letter or number
  80. // at least five characters (letters, numbers or special characters)
  81. var re = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$/;
  82. return re.test(str);
  83. }
  84. function zeroPad(number, positions) {
  85. var zeros = "";
  86. for (var i = 0; i < positions; i++) {
  87. zeros += "0";
  88. }
  89. return (zeros + number).slice(-positions);
  90. }
  91. function loadTimeZones() {
  92. var time_zones = [
  93. -720, -660, -600, -570, -540,
  94. -480, -420, -360, -300, -240,
  95. -210, -180, -120, -60, 0,
  96. 60, 120, 180, 210, 240,
  97. 270, 300, 330, 345, 360,
  98. 390, 420, 480, 510, 525,
  99. 540, 570, 600, 630, 660,
  100. 720, 765, 780, 840
  101. ];
  102. for (var i in time_zones) {
  103. var value = parseInt(time_zones[i], 10);
  104. var offset = value >= 0 ? value : -value;
  105. var text = "GMT" + (value >= 0 ? "+" : "-") +
  106. zeroPad(parseInt(offset / 60, 10), 2) + ":" +
  107. zeroPad(offset % 60, 2);
  108. $("select[name='ntpOffset']").append(
  109. $("<option></option>").
  110. attr("value",value).
  111. text(text));
  112. }
  113. }
  114. function validateForm(form) {
  115. // password
  116. var adminPass1 = $("input[name='adminPass']", form).first().val();
  117. if (adminPass1.length > 0 && !checkPassword(adminPass1)) {
  118. alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!");
  119. return false;
  120. }
  121. var adminPass2 = $("input[name='adminPass']", form).last().val();
  122. if (adminPass1 !== adminPass2) {
  123. alert("Passwords are different!");
  124. return false;
  125. }
  126. return true;
  127. }
  128. // These fields will always be a list of values
  129. var is_group = [
  130. "ssid", "pass", "gw", "mask", "ip", "dns",
  131. "schEnabled", "schSwitch","schAction","schHour","schMinute","schWDs",
  132. "relayBoot", "relayPulse", "relayTime",
  133. "mqttGroup", "mqttGroupInv",
  134. "dczRelayIdx", "dczMagnitude",
  135. "tspkRelay", "tspkMagnitude",
  136. "ledMode",
  137. "adminPass"
  138. ];
  139. function getData(form) {
  140. var data = {};
  141. // Populate data
  142. $("input,select", form).each(function() {
  143. var name = $(this).attr("name");
  144. if (name) {
  145. var value = "";
  146. // Do not report these fields
  147. if (name === "filename" || name === "rfbcode" ) {
  148. return;
  149. }
  150. // Grab the value
  151. if ($(this).attr("type") === "checkbox") {
  152. value = $(this).is(":checked") ? 1 : 0;
  153. } else if ($(this).attr("type") === "radio") {
  154. if (!$(this).is(":checked")) {return;}
  155. value = $(this).val();
  156. } else {
  157. value = $(this).val();
  158. }
  159. // Build the object
  160. if (name in data) {
  161. if (!Array.isArray(data[name])) data[name] = [data[name]];
  162. data[name].push(value);
  163. } else if (is_group.indexOf(name) >= 0) {
  164. data[name] = [value];
  165. } else {
  166. data[name] = value;
  167. }
  168. }
  169. });
  170. // Post process
  171. if ("schSwitch" in data) {
  172. data["schSwitch"].push(0xFF);
  173. } else {
  174. data["schSwitch"] = [0xFF];
  175. }
  176. return data;
  177. }
  178. function randomString(length, chars) {
  179. var mask = "";
  180. if (chars.indexOf("a") > -1) { mask += "abcdefghijklmnopqrstuvwxyz"; }
  181. if (chars.indexOf("A") > -1) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; }
  182. if (chars.indexOf("#") > -1) { mask += "0123456789"; }
  183. if (chars.indexOf("@") > -1) { mask += "ABCDEF"; }
  184. if (chars.indexOf("!") > -1) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; }
  185. var result = "";
  186. for (var i = length; i > 0; --i) {
  187. result += mask[Math.round(Math.random() * (mask.length - 1))];
  188. }
  189. return result;
  190. }
  191. function generateAPIKey() {
  192. var apikey = randomString(16, "@#");
  193. $("input[name='apiKey']").val(apikey);
  194. return false;
  195. }
  196. function getJson(str) {
  197. try {
  198. return JSON.parse(str);
  199. } catch (e) {
  200. return false;
  201. }
  202. }
  203. // -----------------------------------------------------------------------------
  204. // Actions
  205. // -----------------------------------------------------------------------------
  206. function resetOriginals() {
  207. $("input,select").each(function() {
  208. $(this).attr("original", $(this).val());
  209. });
  210. numReboot = numReconnect = numReload = 0;
  211. }
  212. function doReload(milliseconds) {
  213. milliseconds = (typeof milliseconds == "undefined") ?
  214. 0 :
  215. parseInt(milliseconds, 10);
  216. setTimeout(function() {
  217. window.location.reload();
  218. }, milliseconds);
  219. }
  220. function doUpgrade() {
  221. var contents = $("input[name='upgrade']")[0].files[0];
  222. if (typeof contents === "undefined") {
  223. alert("First you have to select a file from your computer.");
  224. return false;
  225. }
  226. var filename = $("input[name='upgrade']").val().split("\\").pop();
  227. var data = new FormData();
  228. data.append("upgrade", contents, filename);
  229. $.ajax({
  230. // Your server script to process the upload
  231. url: webhost + "upgrade",
  232. type: "POST",
  233. // Form data
  234. data: data,
  235. // Tell jQuery not to process data or worry about content-type
  236. // You *must* include these options!
  237. cache: false,
  238. contentType: false,
  239. processData: false,
  240. success: function(data, text) {
  241. $("#upgrade-progress").hide();
  242. if (data === "OK") {
  243. alert("Firmware image uploaded, board rebooting. This page will be refreshed in 5 seconds.");
  244. doReload(5000);
  245. } else {
  246. alert("There was an error trying to upload the new image, please try again (" + data + ").");
  247. }
  248. },
  249. // Custom XMLHttpRequest
  250. xhr: function() {
  251. $("#upgrade-progress").show();
  252. var myXhr = $.ajaxSettings.xhr();
  253. if (myXhr.upload) {
  254. // For handling the progress of the upload
  255. myXhr.upload.addEventListener("progress", function(e) {
  256. if (e.lengthComputable) {
  257. $("progress").attr({ value: e.loaded, max: e.total });
  258. }
  259. } , false);
  260. }
  261. return myXhr;
  262. }
  263. });
  264. return false;
  265. }
  266. function doUpdatePassword() {
  267. var form = $("#formPassword");
  268. if (validateForm(form)) {
  269. var data = getData(form);
  270. websock.send(JSON.stringify({"config": data}));
  271. }
  272. return false;
  273. }
  274. function doReboot(ask) {
  275. var response;
  276. ask = (typeof ask == "undefined") ? true : ask;
  277. if (numChanged > 0) {
  278. response = window.confirm("Some changes have not been saved yet, do you want to save them first?");
  279. if (response === true) {
  280. return doUpdate();
  281. }
  282. }
  283. if (ask) {
  284. response = window.confirm("Are you sure you want to reboot the device?");
  285. if (response === false) {
  286. return false;
  287. }
  288. }
  289. websock.send(JSON.stringify({"action": "reboot"}));
  290. doReload(5000);
  291. return false;
  292. }
  293. function doReconnect(ask) {
  294. var response;
  295. ask = (typeof ask == "undefined") ? true : ask;
  296. if (numChanged > 0) {
  297. response = window.confirm("Some changes have not been saved yet, do you want to save them first?");
  298. if (response === true) {
  299. return doUpdate();
  300. }
  301. }
  302. if (ask) {
  303. response = window.confirm("Are you sure you want to disconnect from the current WIFI network?");
  304. if (response === false) {
  305. return false;
  306. }
  307. }
  308. websock.send(JSON.stringify({"action": "reconnect"}));
  309. doReload(5000);
  310. return false;
  311. }
  312. function doUpdate() {
  313. var form = $("#formSave");
  314. if (validateForm(form)) {
  315. // Get data
  316. var data = getData(form);
  317. websock.send(JSON.stringify({"config": data}));
  318. // Empty special fields
  319. $(".pwrExpected").val(0);
  320. $("input[name='pwrResetCalibration']").
  321. prop("checked", false).
  322. iphoneStyle("refresh");
  323. // Change handling
  324. numChanged = 0;
  325. setTimeout(function() {
  326. var response;
  327. if (numReboot > 0) {
  328. response = window.confirm("You have to reboot the board for the changes to take effect, do you want to do it now?");
  329. if (response === true) { doReboot(false); }
  330. } else if (numReconnect > 0) {
  331. response = window.confirm("You have to reconnect to the WiFi for the changes to take effect, do you want to do it now?");
  332. if (response === true) { doReconnect(false); }
  333. } else if (numReload > 0) {
  334. response = window.confirm("You have to reload the page to see the latest changes, do you want to do it now?");
  335. if (response === true) { doReload(); }
  336. }
  337. resetOriginals();
  338. }, 1000);
  339. }
  340. return false;
  341. }
  342. function doBackup() {
  343. document.getElementById("downloader").src = webhost + "config";
  344. return false;
  345. }
  346. function onFileUpload(event) {
  347. var inputFiles = this.files;
  348. if (inputFiles === undefined || inputFiles.length === 0) {
  349. return false;
  350. }
  351. var inputFile = inputFiles[0];
  352. this.value = "";
  353. var response = window.confirm("Previous settings will be overwritten. Are you sure you want to restore this settings?");
  354. if (response === false) {
  355. return false;
  356. }
  357. var reader = new FileReader();
  358. reader.onload = function(e) {
  359. var data = getJson(e.target.result);
  360. if (data) {
  361. websock.send(JSON.stringify({"action": "restore", "data": data}));
  362. } else {
  363. alert(messages[4]);
  364. }
  365. };
  366. reader.readAsText(inputFile);
  367. return false;
  368. }
  369. function doRestore() {
  370. if (typeof window.FileReader !== "function") {
  371. alert("The file API isn't supported on this browser yet.");
  372. } else {
  373. $("#uploader").click();
  374. }
  375. return false;
  376. }
  377. function doFactoryReset() {
  378. var response = window.confirm("Are you sure you want to restore to factory settings?");
  379. if (response === false) {
  380. return false;
  381. }
  382. websock.send(JSON.stringify({"action": "factory_reset"}));
  383. doReload(5000);
  384. return false;
  385. }
  386. function doToggle(element, value) {
  387. var relayID = parseInt(element.attr("data"), 10);
  388. websock.send(JSON.stringify({"action": "relay", "data": { "id": relayID, "status": value ? 1 : 0 }}));
  389. return false;
  390. }
  391. function doScan() {
  392. $("#scanResult").html("");
  393. $("div.scan.loading").show();
  394. websock.send(JSON.stringify({"action": "scan", "data": {}}));
  395. return false;
  396. }
  397. // -----------------------------------------------------------------------------
  398. // Visualization
  399. // -----------------------------------------------------------------------------
  400. function toggleMenu() {
  401. $("#layout").toggleClass("active");
  402. $("#menu").toggleClass("active");
  403. $("#menuLink").toggleClass("active");
  404. }
  405. function showPanel() {
  406. $(".panel").hide();
  407. $("#" + $(this).attr("data")).show();
  408. if ($("#layout").hasClass("active")) { toggleMenu(); }
  409. $("input[type='checkbox']").
  410. iphoneStyle("calculateDimensions").
  411. iphoneStyle("refresh");
  412. }
  413. // -----------------------------------------------------------------------------
  414. // Relays & magnitudes mapping
  415. // -----------------------------------------------------------------------------
  416. function createRelayList(data, container, template_name) {
  417. var current = $("#" + container + " > div").length;
  418. if (current > 0) {
  419. return;
  420. }
  421. var template = $("#" + template_name + " .pure-g")[0];
  422. for (var i=0; i<data.length; i++) {
  423. var line = $(template).clone();
  424. $("label", line).html("Switch #" + i);
  425. $("input", line).attr("tabindex", 40 + i).val(data[i]);
  426. line.appendTo("#" + container);
  427. }
  428. }
  429. function createMagnitudeList(data, container, template_name) {
  430. var current = $("#" + container + " > div").length;
  431. if (current > 0) {
  432. return;
  433. }
  434. var template = $("#" + template_name + " .pure-g")[0];
  435. for (var i=0; i<data.length; i++) {
  436. var line = $(template).clone();
  437. $("label", line).html(magnitudeType(data[i].type) + " #" + parseInt(data[i].index, 10));
  438. $("div.hint", line).html(data[i].name);
  439. $("input", line).attr("tabindex", 40 + i).val(data[i].idx);
  440. line.appendTo("#" + container);
  441. }
  442. }
  443. // -----------------------------------------------------------------------------
  444. // Wifi
  445. // -----------------------------------------------------------------------------
  446. function delNetwork() {
  447. var parent = $(this).parents(".pure-g");
  448. $(parent).remove();
  449. }
  450. function moreNetwork() {
  451. var parent = $(this).parents(".pure-g");
  452. $(".more", parent).toggle();
  453. }
  454. function addNetwork() {
  455. var numNetworks = $("#networks > div").length;
  456. if (numNetworks >= maxNetworks) {
  457. alert("Max number of networks reached");
  458. return null;
  459. }
  460. var tabindex = 200 + numNetworks * 10;
  461. var template = $("#networkTemplate").children();
  462. var line = $(template).clone();
  463. $(line).find("input").each(function() {
  464. $(this).attr("tabindex", tabindex);
  465. tabindex++;
  466. });
  467. $(line).find(".button-del-network").on("click", delNetwork);
  468. $(line).find(".button-more-network").on("click", moreNetwork);
  469. line.appendTo("#networks");
  470. return line;
  471. }
  472. // -----------------------------------------------------------------------------
  473. // Relays scheduler
  474. // -----------------------------------------------------------------------------
  475. function delSchedule() {
  476. var parent = $(this).parents(".pure-g");
  477. $(parent).remove();
  478. }
  479. function moreSchedule() {
  480. var parent = $(this).parents(".pure-g");
  481. $("div.more", parent).toggle();
  482. }
  483. function addSchedule() {
  484. var numSchedules = $("#schedules > div").length;
  485. if (numSchedules >= maxSchedules) {
  486. alert("Max number of schedules reached");
  487. return null;
  488. }
  489. var tabindex = 200 + numSchedules * 10;
  490. var template = $("#scheduleTemplate").children();
  491. var line = $(template).clone();
  492. $(line).find("input").each(function() {
  493. $(this).attr("tabindex", tabindex);
  494. tabindex++;
  495. });
  496. $(line).find(".button-del-schedule").on("click", delSchedule);
  497. $(line).find(".button-more-schedule").on("click", moreSchedule);
  498. line.appendTo("#schedules");
  499. return line;
  500. }
  501. // -----------------------------------------------------------------------------
  502. // Relays
  503. // -----------------------------------------------------------------------------
  504. function initRelays(data) {
  505. var current = $("#relays > div").length;
  506. if (current > 0) {
  507. return;
  508. }
  509. var template = $("#relayTemplate .pure-g")[0];
  510. for (var i=0; i<data.length; i++) {
  511. // Add relay fields
  512. var line = $(template).clone();
  513. $(".id", line).html(i);
  514. $("input", line).attr("data", i);
  515. line.appendTo("#relays");
  516. $(":checkbox", line).iphoneStyle({
  517. onChange: doToggle,
  518. resizeContainer: true,
  519. resizeHandle: true,
  520. checkedLabel: "ON",
  521. uncheckedLabel: "OFF"
  522. });
  523. // Populate the relay SELECTs
  524. $("select.isrelay").append(
  525. $("<option></option>").attr("value",i).text("Switch #" + i));
  526. }
  527. }
  528. function initRelayConfig(data) {
  529. var current = $("#relayConfig > div").length;
  530. if (current > 0) {
  531. return;
  532. }
  533. var template = $("#relayConfigTemplate").children();
  534. for (var i=0; i < data.length; i++) {
  535. var line = $(template).clone();
  536. $("span.gpio", line).html(data[i].gpio);
  537. $("span.id", line).html(i);
  538. $("select[name='relayBoot']", line).val(data[i].boot);
  539. $("select[name='relayPulse']", line).val(data[i].pulse);
  540. $("input[name='relayTime']", line).val(data[i].pulse_ms);
  541. $("input[name='mqttGroup']", line).val(data[i].group);
  542. $("select[name='mqttGroupInv']", line).val(data[i].group_inv);
  543. line.appendTo("#relayConfig");
  544. }
  545. }
  546. // -----------------------------------------------------------------------------
  547. // Sensors & Magnitudes
  548. // -----------------------------------------------------------------------------
  549. function initMagnitudes(data) {
  550. // check if already initialized
  551. var done = $("#magnitudes > div").length;
  552. if (done > 0) {
  553. return;
  554. }
  555. // add templates
  556. var template = $("#magnitudeTemplate").children();
  557. for (var i=0; i<data.length; i++) {
  558. var line = $(template).clone();
  559. $("label", line).html(magnitudeType(data[i].type) + " #" + parseInt(data[i].index, 10));
  560. $("div.hint", line).html(data[i].description);
  561. $("input", line).attr("data", i);
  562. line.appendTo("#magnitudes");
  563. }
  564. }
  565. function getManifest(sensor_id) {
  566. for (var i in manifest) {
  567. if (manifest[i].sensor_id === sensor_id) {
  568. return manifest[i];
  569. }
  570. }
  571. return null;
  572. }
  573. // -----------------------------------------------------------------------------
  574. // Lights
  575. // -----------------------------------------------------------------------------
  576. function initColorRGB() {
  577. // check if already initialized
  578. var done = $("#colors > div").length;
  579. if (done > 0) {
  580. return;
  581. }
  582. // add template
  583. var template = $("#colorRGBTemplate").children();
  584. var line = $(template).clone();
  585. line.appendTo("#colors");
  586. // init color wheel
  587. $("input[name='color']").wheelColorPicker({
  588. sliders: "wrgbp"
  589. }).on("sliderup", function() {
  590. var value = $(this).wheelColorPicker("getValue", "css");
  591. websock.send(JSON.stringify({"action": "color", "data" : {"rgb": value}}));
  592. });
  593. // init bright slider
  594. $("#brightness").on("change", function() {
  595. var value = $(this).val();
  596. var parent = $(this).parents(".pure-g");
  597. $("span", parent).html(value);
  598. websock.send(JSON.stringify({"action": "color", "data" : {"brightness": value}}));
  599. });
  600. }
  601. function initColorHSV() {
  602. // check if already initialized
  603. var done = $("#colors > div").length;
  604. if (done > 0) {
  605. return;
  606. }
  607. // add template
  608. var template = $("#colorHSVTemplate").children();
  609. var line = $(template).clone();
  610. line.appendTo("#colors");
  611. // init color wheel
  612. $("input[name='color']").wheelColorPicker({
  613. sliders: "whsvp"
  614. }).on("sliderup", function() {
  615. var color = $(this).wheelColorPicker("getColor");
  616. var value = parseInt(color.h * 360, 10) + "," + parseInt(color.s * 100, 10) + "," + parseInt(color.v * 100, 10);
  617. websock.send(JSON.stringify({"action": "color", "data" : {"hsv": value}}));
  618. });
  619. }
  620. function initChannels(num) {
  621. // check if already initialized
  622. var done = $("#channels > div").length > 0;
  623. if (done) {
  624. return;
  625. }
  626. // does it have color channels?
  627. var colors = $("#colors > div").length > 0;
  628. // calculate channels to create
  629. var max = num;
  630. if (colors) {
  631. max = num % 3;
  632. if ((max > 0) & useWhite) max--;
  633. }
  634. var start = num - max;
  635. // add templates
  636. var template = $("#channelTemplate").children();
  637. for (var i=0; i<max; i++) {
  638. var channel_id = start + i;
  639. var line = $(template).clone();
  640. $("span.slider", line).attr("data", channel_id);
  641. $("input.slider", line).attr("data", channel_id).on("change", function() {
  642. var id = $(this).attr("data");
  643. var value = $(this).val();
  644. var parent = $(this).parents(".pure-g");
  645. $("span", parent).html(value);
  646. websock.send(JSON.stringify({"action": "channel", "data" : { "id": id, "value": value }}));
  647. });
  648. $("label", line).html("Channel " + (channel_id + 1));
  649. line.appendTo("#channels");
  650. }
  651. }
  652. // -----------------------------------------------------------------------------
  653. // RFBridge
  654. // -----------------------------------------------------------------------------
  655. function addRfbNode() {
  656. var numNodes = $("#rfbNodes > legend").length;
  657. var template = $("#rfbNodeTemplate").children();
  658. var line = $(template).clone();
  659. var status = true;
  660. $("span", line).html(numNodes);
  661. $(line).find("input").each(function() {
  662. $(this).attr("data-id", numNodes);
  663. $(this).attr("data-status", status ? 1 : 0);
  664. status = !status;
  665. });
  666. $(line).find(".button-rfb-learn").on("click", rfbLearn);
  667. $(line).find(".button-rfb-forget").on("click", rfbForget);
  668. $(line).find(".button-rfb-send").on("click", rfbSend);
  669. line.appendTo("#rfbNodes");
  670. return line;
  671. }
  672. function rfbLearn() {
  673. var parent = $(this).parents(".pure-g");
  674. var input = $("input", parent);
  675. websock.send(JSON.stringify({"action": "rfblearn", "data" : {"id" : input.attr("data-id"), "status": input.attr("data-status")}}));
  676. }
  677. function rfbForget() {
  678. var parent = $(this).parents(".pure-g");
  679. var input = $("input", parent);
  680. websock.send(JSON.stringify({"action": "rfbforget", "data" : {"id" : input.attr("data-id"), "status": input.attr("data-status")}}));
  681. }
  682. function rfbSend() {
  683. var parent = $(this).parents(".pure-g");
  684. var input = $("input", parent);
  685. websock.send(JSON.stringify({"action": "rfbsend", "data" : {"id" : input.attr("data-id"), "status": input.attr("data-status"), "data": input.val()}}));
  686. }
  687. // -----------------------------------------------------------------------------
  688. // Processing
  689. // -----------------------------------------------------------------------------
  690. function processData(data) {
  691. // title
  692. if ("app_name" in data) {
  693. var title = data.app_name;
  694. if ("app_version" in data) {
  695. title = title + " " + data.app_version;
  696. }
  697. $("span[name=title]").html(title);
  698. if ("hostname" in data) {
  699. title = data.hostname + " - " + title;
  700. }
  701. document.title = title;
  702. }
  703. Object.keys(data).forEach(function(key) {
  704. var i;
  705. // ---------------------------------------------------------------------
  706. // Web mode
  707. // ---------------------------------------------------------------------
  708. if (key ==="webMode") {
  709. password = data.webMode == 1;
  710. $("#layout").toggle(data.webMode === 0);
  711. $("#password").toggle(data.webMode === 1);
  712. }
  713. // ---------------------------------------------------------------------
  714. // Actions
  715. // ---------------------------------------------------------------------
  716. if (key === "action") {
  717. if (data.action === "reload") doReload(1000);
  718. return;
  719. }
  720. // ---------------------------------------------------------------------
  721. // RFBridge
  722. // ---------------------------------------------------------------------
  723. if (key === "rfbCount") {
  724. for (i=0; i<data.rfbCount; i++) addRfbNode();
  725. return;
  726. }
  727. if (key === "rfbrawVisible") {
  728. $("input[name='rfbcode']").attr("maxlength", 116);
  729. }
  730. if (key === "rfb") {
  731. var nodes = data.rfb;
  732. for (i in nodes) {
  733. var node = nodes[i];
  734. $("input[name='rfbcode'][data-id='" + node["id"] + "'][data-status='" + node["status"] + "']").val(node["data"]);
  735. }
  736. return;
  737. }
  738. // ---------------------------------------------------------------------
  739. // Lights
  740. // ---------------------------------------------------------------------
  741. if (key === "rgb") {
  742. initColorRGB();
  743. $("input[name='color']").wheelColorPicker("setValue", data[key], true);
  744. return;
  745. }
  746. if (key === "hsv") {
  747. initColorHSV();
  748. // wheelColorPicker expects HSV to be between 0 and 1 all of them
  749. var chunks = data[key].split(",");
  750. var obj = {};
  751. obj.h = chunks[0] / 360;
  752. obj.s = chunks[1] / 100;
  753. obj.v = chunks[2] / 100;
  754. $("input[name='color']").wheelColorPicker("setColor", obj);
  755. return;
  756. }
  757. if (key === "brightness") {
  758. $("#brightness").val(data[key]);
  759. $("span.brightness").html(data[key]);
  760. return;
  761. }
  762. if (key === "channels") {
  763. var len = data[key].length;
  764. initChannels(len);
  765. for (i=0; i<len; i++) {
  766. $("input.slider[data=" + i + "]").val(data[key][i]);
  767. $("span.slider[data=" + i + "]").html(data[key][i]);
  768. }
  769. return;
  770. }
  771. if (key === "useWhite") {
  772. useWhite = data[key];
  773. }
  774. // ---------------------------------------------------------------------
  775. // Sensors & Magnitudes
  776. // ---------------------------------------------------------------------
  777. if (key === "magnitudes") {
  778. initMagnitudes(data[key]);
  779. for (i=0; i<data[key].length; i++) {
  780. var error = data[key][i].error || 0;
  781. var text = (error === 0) ?
  782. data[key][i].value + data[key][i].units :
  783. magnitudeError(error);
  784. $("input[name='magnitude'][data='" + i + "']").val(text);
  785. }
  786. return;
  787. }
  788. if (key === "manifest") {
  789. manifest = data[key];
  790. }
  791. // ---------------------------------------------------------------------
  792. // WiFi
  793. // ---------------------------------------------------------------------
  794. if (key === "maxNetworks") {
  795. maxNetworks = parseInt(data.maxNetworks, 10);
  796. return;
  797. }
  798. if (key === "wifi") {
  799. for (i in data.wifi) {
  800. var nwk_line = addNetwork();
  801. var wifi = data.wifi[i];
  802. Object.keys(wifi).forEach(function(key) {
  803. $("input[name='" + key + "']", nwk_line).val(wifi[key]);
  804. });
  805. }
  806. return;
  807. }
  808. if (key == "scanResult") {
  809. $("div.scan.loading").hide();
  810. }
  811. // -----------------------------------------------------------------------------
  812. // Relays scheduler
  813. // -----------------------------------------------------------------------------
  814. if (key === "maxSchedules") {
  815. maxSchedules = parseInt(data.maxSchedules, 10);
  816. return;
  817. }
  818. if (key === "schedule") {
  819. for (i in data.schedule) {
  820. var sch_line = addSchedule();
  821. var schedule = data.schedule[i];
  822. Object.keys(schedule).forEach(function(key) {
  823. $("input[name='" + key + "']", sch_line).val(schedule[key]);
  824. $("select[name='" + key + "']", sch_line).prop("value", schedule[key]);
  825. $(":checkbox", sch_line).prop("checked", schedule[key]);
  826. });
  827. }
  828. return;
  829. }
  830. // ---------------------------------------------------------------------
  831. // Relays
  832. // ---------------------------------------------------------------------
  833. if (key === "relayStatus") {
  834. initRelays(data[key]);
  835. for (i in data[key]) {
  836. // Set the status for each relay
  837. $("input.relayStatus[data='" + i + "']").
  838. prop("checked", data[key][i]).
  839. iphoneStyle("refresh");
  840. }
  841. return;
  842. }
  843. // Relay configuration
  844. if (key === "relayConfig") {
  845. initRelayConfig(data[key]);
  846. return;
  847. }
  848. // ---------------------------------------------------------------------
  849. // Domoticz
  850. // ---------------------------------------------------------------------
  851. // Domoticz - Relays
  852. if (key === "dczRelays") {
  853. createRelayList(data[key], "dczRelays", "dczRelayTemplate");
  854. return;
  855. }
  856. // Domoticz - Magnitudes
  857. if (key === "dczMagnitudes") {
  858. createMagnitudeList(data[key], "dczMagnitudes", "dczMagnitudeTemplate");
  859. return;
  860. }
  861. // ---------------------------------------------------------------------
  862. // Thingspeak
  863. // ---------------------------------------------------------------------
  864. // Thingspeak - Relays
  865. if (key === "tspkRelays") {
  866. createRelayList(data[key], "tspkRelays", "tspkRelayTemplate");
  867. return;
  868. }
  869. // Thingspeak - Magnitudes
  870. if (key === "tspkMagnitudes") {
  871. createMagnitudeList(data[key], "tspkMagnitudes", "tspkMagnitudeTemplate");
  872. return;
  873. }
  874. // ---------------------------------------------------------------------
  875. // General
  876. // ---------------------------------------------------------------------
  877. // Messages
  878. if (key === "message") {
  879. window.alert(messages[data.message]);
  880. return;
  881. }
  882. // Enable options
  883. var position = key.indexOf("Visible");
  884. if (position > 0 && position === key.length - 7) {
  885. var module = key.slice(0,-7);
  886. $(".module-" + module).show();
  887. return;
  888. }
  889. if (key === "now") {
  890. now = data[key];
  891. ago = 0;
  892. return;
  893. }
  894. // Pre-process
  895. if (key === "network") {
  896. data.network = data.network.toUpperCase();
  897. }
  898. if (key === "mqttStatus") {
  899. data.mqttStatus = data.mqttStatus ? "CONNECTED" : "NOT CONNECTED";
  900. }
  901. if (key === "ntpStatus") {
  902. data.ntpStatus = data.ntpStatus ? "SYNC'D" : "NOT SYNC'D";
  903. }
  904. if (key === "uptime") {
  905. var uptime = parseInt(data[key], 10);
  906. var seconds = uptime % 60; uptime = parseInt(uptime / 60, 10);
  907. var minutes = uptime % 60; uptime = parseInt(uptime / 60, 10);
  908. var hours = uptime % 24; uptime = parseInt(uptime / 24, 10);
  909. var days = uptime;
  910. data[key] = days + "d " + zeroPad(hours, 2) + "h " + zeroPad(minutes, 2) + "m " + zeroPad(seconds, 2) + "s";
  911. }
  912. // ---------------------------------------------------------------------
  913. // Matching
  914. // ---------------------------------------------------------------------
  915. var pre;
  916. var post;
  917. // Look for INPUTs
  918. var input = $("input[name='" + key + "']");
  919. if (input.length > 0) {
  920. if (input.attr("type") === "checkbox") {
  921. input.
  922. prop("checked", data[key]).
  923. iphoneStyle("refresh");
  924. } else if (input.attr("type") === "radio") {
  925. input.val([data[key]]);
  926. } else {
  927. pre = input.attr("pre") || "";
  928. post = input.attr("post") || "";
  929. input.val(pre + data[key] + post);
  930. }
  931. }
  932. // Look for SPANs
  933. var span = $("span[name='" + key + "']");
  934. if (span.length > 0) {
  935. pre = span.attr("pre") || "";
  936. post = span.attr("post") || "";
  937. span.html(pre + data[key] + post);
  938. }
  939. // Look for SELECTs
  940. var select = $("select[name='" + key + "']");
  941. if (select.length > 0) {
  942. select.val(data[key]);
  943. }
  944. });
  945. // Auto generate an APIKey if none defined yet
  946. if ($("input[name='apiKey']").val() === "") {
  947. generateAPIKey();
  948. }
  949. resetOriginals();
  950. }
  951. function hasChanged() {
  952. var newValue, originalValue;
  953. if ($(this).attr("type") === "checkbox") {
  954. newValue = $(this).prop("checked");
  955. originalValue = $(this).attr("original") == "true";
  956. } else {
  957. newValue = $(this).val();
  958. originalValue = $(this).attr("original");
  959. }
  960. var hasChanged = $(this).attr("hasChanged") || 0;
  961. var action = $(this).attr("action");
  962. if (typeof originalValue === "undefined") { return; }
  963. if (action === "none") { return; }
  964. if (newValue !== originalValue) {
  965. if (hasChanged === 0) {
  966. ++numChanged;
  967. if (action === "reconnect") ++numReconnect;
  968. if (action === "reboot") ++numReboot;
  969. if (action === "reload") ++numReload;
  970. $(this).attr("hasChanged", 1);
  971. }
  972. } else {
  973. if (hasChanged === 1) {
  974. --numChanged;
  975. if (action === "reconnect") --numReconnect;
  976. if (action === "reboot") --numReboot;
  977. if (action === "reload") --numReload;
  978. $(this).attr("hasChanged", 0);
  979. }
  980. }
  981. }
  982. // -----------------------------------------------------------------------------
  983. // Init & connect
  984. // -----------------------------------------------------------------------------
  985. function connect(host) {
  986. if (typeof host === "undefined") {
  987. host = window.location.href.replace("#", "");
  988. } else {
  989. if (host.indexOf("http") !== 0) {
  990. host = "http://" + host + "/";
  991. }
  992. }
  993. if (host.indexOf("http") !== 0) {return;}
  994. webhost = host;
  995. wshost = host.replace("http", "ws") + "ws";
  996. if (websock) websock.close();
  997. websock = new WebSocket(wshost);
  998. websock.onmessage = function(evt) {
  999. var data = getJson(evt.data);
  1000. if (data) processData(data);
  1001. };
  1002. }
  1003. $(function() {
  1004. initMessages();
  1005. loadTimeZones();
  1006. setInterval(function() { keepTime(); }, 1000);
  1007. $("#menuLink").on("click", toggleMenu);
  1008. $(".pure-menu-link").on("click", showPanel);
  1009. $("progress").attr({ value: 0, max: 100 });
  1010. $(".button-update").on("click", doUpdate);
  1011. $(".button-update-password").on("click", doUpdatePassword);
  1012. $(".button-reboot").on("click", doReboot);
  1013. $(".button-reconnect").on("click", doReconnect);
  1014. $(".button-wifi-scan").on("click", doScan);
  1015. $(".button-settings-backup").on("click", doBackup);
  1016. $(".button-settings-restore").on("click", doRestore);
  1017. $(".button-settings-factory").on("click", doFactoryReset);
  1018. $("#uploader").on("change", onFileUpload);
  1019. $(".button-upgrade").on("click", doUpgrade);
  1020. $(".button-apikey").on("click", generateAPIKey);
  1021. $(".button-upgrade-browse").on("click", function() {
  1022. $("input[name='upgrade']")[0].click();
  1023. return false;
  1024. });
  1025. $("input[name='upgrade']").change(function (){
  1026. var fileName = $(this).val();
  1027. $("input[name='filename']").val(fileName.replace(/^.*[\\\/]/, ""));
  1028. });
  1029. $(".button-add-network").on("click", function() {
  1030. $(".more", addNetwork()).toggle();
  1031. });
  1032. $(".button-add-schedule").on("click", addSchedule);
  1033. $(document).on("change", "input", hasChanged);
  1034. $(document).on("change", "select", hasChanged);
  1035. connect();
  1036. });