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.

1045 lines
31 KiB

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