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.

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