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.

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