iobroker.zigbee 1.7.5 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +22 -29
  3. package/admin/admin.js +494 -469
  4. package/admin/i18n/de/translations.json +108 -0
  5. package/admin/i18n/en/translations.json +108 -0
  6. package/admin/i18n/es/translations.json +102 -0
  7. package/admin/i18n/fr/translations.json +108 -0
  8. package/admin/i18n/it/translations.json +102 -0
  9. package/admin/i18n/nl/translations.json +108 -0
  10. package/admin/i18n/pl/translations.json +108 -0
  11. package/admin/i18n/pt/translations.json +102 -0
  12. package/admin/i18n/ru/translations.json +108 -0
  13. package/admin/i18n/uk/translations.json +108 -0
  14. package/admin/i18n/zh-cn/translations.json +102 -0
  15. package/admin/index_m.html +1 -1
  16. package/admin/tab_m.html +44 -3
  17. package/admin/words.js +107 -108
  18. package/io-package.json +326 -357
  19. package/lib/backup.js +2 -2
  20. package/lib/binding.js +23 -24
  21. package/lib/colors.js +16 -14
  22. package/lib/commands.js +89 -82
  23. package/lib/developer.js +6 -7
  24. package/lib/devices.js +145 -154
  25. package/lib/exclude.js +30 -36
  26. package/lib/exposes.js +106 -111
  27. package/lib/groups.js +53 -54
  28. package/lib/json.js +3 -4
  29. package/lib/networkmap.js +2 -2
  30. package/lib/ota.js +23 -15
  31. package/lib/rgb.js +47 -44
  32. package/lib/seriallist.js +21 -10
  33. package/lib/states.js +488 -498
  34. package/lib/statescontroller.js +170 -164
  35. package/lib/utils.js +22 -21
  36. package/lib/zbBaseExtension.js +4 -4
  37. package/lib/zbDelayedAction.js +5 -13
  38. package/lib/zbDeviceAvailability.js +47 -44
  39. package/lib/zbDeviceConfigure.js +18 -23
  40. package/lib/zbDeviceEvent.js +3 -4
  41. package/lib/zigbeecontroller.js +97 -100
  42. package/main.js +149 -133
  43. package/package.json +33 -19
  44. package/.eslintignore +0 -2
  45. package/.eslintrc.json +0 -37
  46. package/.github/FUNDING.yml +0 -3
  47. package/.github/stale.yml +0 -13
  48. package/.github/workflows/test-and-release.yml +0 -151
  49. package/.travis/wiki.sh +0 -28
  50. package/admin/adapter-settings.js +0 -244
package/admin/admin.js CHANGED
@@ -38,7 +38,7 @@ function getDeviceByID(ID) {
38
38
  return devices.find((devInfo) => {
39
39
  try {
40
40
  return devInfo._id == ID;
41
- } catch (e) {
41
+ } catch (e) {
42
42
  //console.log("No dev with ieee " + ieeeAddr);
43
43
  }
44
44
  });
@@ -48,17 +48,18 @@ function getDevice(ieeeAddr) {
48
48
  return devices.find((devInfo) => {
49
49
  try {
50
50
  return devInfo.info.device._ieeeAddr == ieeeAddr;
51
- } catch (e) {
51
+ } catch (e) {
52
52
  //console.log("No dev with ieee " + ieeeAddr);
53
53
  }
54
54
  });
55
55
  }
56
+
56
57
  // eslint-disable-next-line no-unused-vars
57
58
  function getDeviceByNetwork(nwk) {
58
59
  return devices.find((devInfo) => {
59
60
  try {
60
61
  return devInfo.info.device._networkAddress == nwk;
61
- } catch (e) {
62
+ } catch (e) {
62
63
  //console.log("No dev with nwkAddr " + nwk);
63
64
  }
64
65
  });
@@ -125,12 +126,12 @@ function getCoordinatorCard(dev) {
125
126
  }
126
127
 
127
128
  function getGroupCard(dev) {
128
- const id = (dev._id ? dev._id: ''),
129
+ const id = (dev._id ? dev._id : ''),
129
130
  title = dev.common.name,
130
131
  lq = '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>',
131
132
  rooms = [],
132
- numid = parseInt(id.replace(namespace+'.group_', '')),
133
- lang = systemLang || 'en';
133
+ numid = parseInt(id.replace(namespace + '.group_', '')),
134
+ lang = systemLang || 'en';
134
135
  for (const r in dev.rooms) {
135
136
  if (dev.rooms[r].hasOwnProperty(lang)) {
136
137
  rooms.push(dev.rooms[r][lang]);
@@ -143,19 +144,20 @@ function getGroupCard(dev) {
143
144
  let memberCount = 0;
144
145
  let info = `<div style="min-height:88px; font-size: 0.8em; height: 98px; overflow-y: auto" class="truncate">
145
146
  <ul>`;
146
- info = info.concat(`<li><span class="labelinfo">Group ${numid}</span></li>`);
147
+ info = info.concat(`<li><span class="labelinfo">Group ${numid}</span></li>`);
147
148
  if (dev.memberinfo === undefined) {
148
149
  info = info.concat(`<li><span class="labelinfo">No devices in group</span></li>`);
149
150
  } else {
150
- for (let m=0;m < dev.memberinfo.length; m++) {
151
+ for (let m = 0; m < dev.memberinfo.length; m++) {
151
152
  info = info.concat(`<li><span align:"left">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid}</span><span align:"right"> ...${dev.memberinfo[m].ieee.slice(-4)}</span></li>`);
152
153
  }
153
- memberCount = (dev.memberinfo.length<8?dev.memberinfo.length:7);
154
- };
154
+ memberCount = (dev.memberinfo.length < 8 ? dev.memberinfo.length : 7);
155
+ }
156
+ ;
155
157
  info = info.concat(` </ul>
156
158
  </div>`);
157
159
  const image = `<img src="img/group_${memberCount}.png" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
158
- const dashCard = getDashCard(dev,`img/group_${memberCount}.png` );
160
+ const dashCard = getDashCard(dev, `img/group_${memberCount}.png`);
159
161
  const card = `<div id="${id}" class="device group">
160
162
  <div class="card hoverable flipable">
161
163
  <div class="front face">${dashCard}</div>
@@ -202,7 +204,7 @@ function getCard(dev) {
202
204
  img_src = dev.icon || dev.common.icon,
203
205
  rooms = [],
204
206
  isActive = (dev.common.deactivated ? false : true),
205
- lang = systemLang || 'en';
207
+ lang = systemLang || 'en';
206
208
  for (const r in dev.rooms) {
207
209
  if (dev.rooms[r].hasOwnProperty(lang)) {
208
210
  rooms.push(dev.rooms[r][lang]);
@@ -216,25 +218,25 @@ function getCard(dev) {
216
218
  const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
217
219
  const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
218
220
  nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
219
- battery_cls = (isActive ? getBatteryCls(dev.battery):''),
221
+ battery_cls = (isActive ? getBatteryCls(dev.battery) : ''),
220
222
  lqi_cls = getLQICls(dev.link_quality),
221
223
  battery = (dev.battery && isActive) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
222
224
  lq = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : '',
223
- status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (isActive ? `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`:''),
225
+ status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (isActive ? `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>` : ''),
224
226
  info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
225
227
  <ul>
226
- <li><span class="labelinfo">ieee:</span><span>0x${id.replace(namespace+'.', '')}</span></li>
227
- <li><span class="labelinfo">nwk:</span><span>${(nwk) ? nwk.toString()+' (0x'+nwk.toString(16)+')' : ''}</span></li>
228
+ <li><span class="labelinfo">ieee:</span><span>0x${id.replace(namespace + '.', '')}</span></li>
229
+ <li><span class="labelinfo">nwk:</span><span>${(nwk) ? nwk.toString() + ' (0x' + nwk.toString(16) + ')' : ''}</span></li>
228
230
  <li><span class="labelinfo">model:</span><span>${modelUrl}</span></li>
229
231
  <li><span class="labelinfo">groups:</span><span>${dev.groupNames || ''}</span></li>
230
232
  </ul>
231
233
  </div>`,
232
234
  permitJoinBtn = (dev.info && dev.info.device._type == 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
233
- deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive?'Deactivate':'Activate')}"><i class="material-icons icon-${(isActive?'red':'green')}">power_settings_new</i></button>`,
235
+ deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive ? 'Deactivate' : 'Activate')}"><i class="material-icons icon-${(isActive ? 'red' : 'green')}">power_settings_new</i></button>`,
234
236
  infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
235
237
  const dashCard = getDashCard(dev);
236
238
  const card = `<div id="${id}" class="device">
237
- <div class="card hoverable flipable ${isActive?'':'bg_red'}">
239
+ <div class="card hoverable flipable ${isActive ? '' : 'bg_red'}">
238
240
  <div class="front face">${dashCard}</div>
239
241
  <div class="back face">
240
242
  <div class="card-content zcard">
@@ -274,6 +276,7 @@ function getCard(dev) {
274
276
  </div>`;
275
277
  return card;
276
278
  }
279
+
277
280
  /*
278
281
  function openReval(e, id, name){
279
282
  const $card = $(e.target).closest('.card');
@@ -301,7 +304,7 @@ function openReval(e, id, name){
301
304
  });
302
305
  }
303
306
  */
304
- function closeReval(e, id){
307
+ function closeReval(e, id) {
305
308
  const $cardReveal = $(e.target).closest('.card-reveal');
306
309
  const $revealName = $cardReveal[0].getAttribute('name');
307
310
  if ($revealName == 'edit' && id) {
@@ -320,21 +323,21 @@ function closeReval(e, id){
320
323
  translateY: 0,
321
324
  duration: 225,
322
325
  easing: 'easeInOutQuad',
323
- complete: function(anim) {
326
+ complete: function (anim) {
324
327
  const el = anim.animatables[0].target;
325
- $(el).css({ display: 'none'});
328
+ $(el).css({display: 'none'});
326
329
  $card.css('overflow', $card.data('initialOverflow'));
327
330
  }
328
331
  });
329
332
  }
330
333
 
331
334
  function deleteConfirmation(id, name) {
332
- const text = translateWord('Do you really want to delete device') + ' "'+name+'" ('+id+')?';
335
+ const text = translateWord('Do you really want to delete device') + ' "' + name + '" (' + id + ')?';
333
336
  $('#modaldelete').find('p').text(text);
334
337
  $('#force').prop('checked', false);
335
338
  $('#forcediv').removeClass('hide');
336
- $("#modaldelete a.btn[name='yes']").unbind('click');
337
- $("#modaldelete a.btn[name='yes']").click(() => {
339
+ $('#modaldelete a.btn[name=\'yes\']').unbind('click');
340
+ $('#modaldelete a.btn[name=\'yes\']').click(() => {
338
341
  const force = $('#force').prop('checked');
339
342
  deleteDevice(id, force);
340
343
  });
@@ -347,8 +350,8 @@ function cleanConfirmation() {
347
350
  $('#modalclean').find('p').text(text);
348
351
  $('#cforce').prop('checked', false);
349
352
  $('#cforcediv').removeClass('hide');
350
- $("#modalclean a.btn[name='yes']").unbind('click');
351
- $("#modalclean a.btn[name='yes']").click(() => {
353
+ $('#modalclean a.btn[name=\'yes\']').unbind('click');
354
+ $('#modalclean a.btn[name=\'yes\']').click(() => {
352
355
  const force = $('#cforce').prop('checked');
353
356
  cleanDeviceStates(force);
354
357
  });
@@ -356,78 +359,75 @@ function cleanConfirmation() {
356
359
  Materialize.updateTextFields();
357
360
  }
358
361
 
359
- function EndPointIDfromEndPoint(ep)
360
- {
361
- if (ep && ep.deviceIeeeAddress && ep.ID)
362
- return `${ep.deviceIeeeAddress}:${ep.ID}`;
363
- return 'unidentified';
362
+ function EndPointIDfromEndPoint(ep) {
363
+ if (ep && ep.deviceIeeeAddress && ep.ID)
364
+ return `${ep.deviceIeeeAddress}:${ep.ID}`;
365
+ return 'unidentified';
364
366
  }
365
367
 
366
368
  function editName(id, name) {
367
- console.log('editName called with '+name);
369
+ console.log('editName called with ' + name);
368
370
  const dev = devices.find((d) => d._id == id);
369
- $('#modaledit').find("input[id='d_name']").val(name);
371
+ $('#modaledit').find('input[id=\'d_name\']').val(name);
370
372
  // if (dev.info && dev.info.device._type == 'Router') {
371
- const groupables = [];
372
- if (dev && dev.info && dev.info.endpoints) {
373
+ const groupables = [];
374
+ if (dev && dev.info && dev.info.endpoints) {
373
375
  for (const ep of dev.info.endpoints) {
374
- if (ep.inputClusters.includes(4)) {
375
- groupables.push({ epid:EndPointIDfromEndPoint(ep), ep:ep, memberOf:[]});
376
- }
376
+ if (ep.inputClusters.includes(4)) {
377
+ groupables.push({epid: EndPointIDfromEndPoint(ep), ep: ep, memberOf: []});
378
+ }
377
379
  }
378
- }
379
- const numEP = groupables.length;
380
+ }
381
+ const numEP = groupables.length;
380
382
  // console.log('groupables: '+JSON.stringify(groupables));
381
- $('#modaledit').find('.row.epid0').addClass('hide');
382
- $('#modaledit').find('.row.epid1').addClass('hide');
383
- $('#modaledit').find('.row.epid2').addClass('hide');
384
- $('#modaledit').find('.row.epid3').addClass('hide');
385
- if (numEP > 0) {
383
+ $('#modaledit').find('.row.epid0').addClass('hide');
384
+ $('#modaledit').find('.row.epid1').addClass('hide');
385
+ $('#modaledit').find('.row.epid2').addClass('hide');
386
+ $('#modaledit').find('.row.epid3').addClass('hide');
387
+ if (numEP > 0) {
386
388
  // go through all the groups. Find the ones to list for each groupable
387
389
  if (numEP == 1) {
388
- $('#modaledit').find('.endpointid').addClass('hide');
389
- }
390
- else {
391
- $('#modaledit').find('.endpointid').removeClass('hide');
390
+ $('#modaledit').find('.endpointid').addClass('hide');
391
+ } else {
392
+ $('#modaledit').find('.endpointid').removeClass('hide');
392
393
  }
393
394
  for (const d of devices) {
394
- if (d && d.common && d.common.type == 'group') {
395
- if (d.hasOwnProperty("memberinfo")) {
396
- for (const member of d.memberinfo) {
397
- const epid = EndPointIDfromEndPoint(member.ep)
398
- for (var i=0;i<groupables.length;i++) {
399
- if (groupables[i].epid == epid) {
400
- groupables[i].memberOf.push(d.native.id.replace('group_', ''));
401
- }
395
+ if (d && d.common && d.common.type == 'group') {
396
+ if (d.hasOwnProperty('memberinfo')) {
397
+ for (const member of d.memberinfo) {
398
+ const epid = EndPointIDfromEndPoint(member.ep);
399
+ for (var i = 0; i < groupables.length; i++) {
400
+ if (groupables[i].epid == epid) {
401
+ groupables[i].memberOf.push(d.native.id.replace('group_', ''));
402
+ }
403
+ }
404
+ }
402
405
  }
403
- }
404
406
  }
405
- }
406
407
  }
407
- console.log("groupables: " + JSON.stringify(groupables));
408
- for (var i = 0;i<groupables.length;i++)
409
- {
410
- if (i > 1) {
411
- $('#modaledit').find("translate.device_with_endpoint").innerHtml = name + ' ' + groupables[i].epid;
412
- }
413
- $('#modaledit').find('.row.epid'+i).removeClass('hide');
414
- list2select('#d_groups_ep'+i, groups, groupables[i].memberOf || []);
408
+ console.log('groupables: ' + JSON.stringify(groupables));
409
+ for (var i = 0; i < groupables.length; i++) {
410
+ if (i > 1) {
411
+ $('#modaledit').find('translate.device_with_endpoint').innerHtml = name + ' ' + groupables[i].epid;
412
+ }
413
+ $('#modaledit').find('.row.epid' + i).removeClass('hide');
414
+ list2select('#d_groups_ep' + i, groups, groupables[i].memberOf || []);
415
415
  }
416
- }
416
+ }
417
417
  // } else {
418
418
  // $('#modaledit').find('.input-field.endpoints').addClass('hide');
419
419
  // $('#modaledit').find('.input-field.groups').addClass('hide');
420
420
  // }
421
- $("#modaledit a.btn[name='save']").unbind('click');
422
- $("#modaledit a.btn[name='save']").click(() => {
423
- const newName = $('#modaledit').find("input[id='d_name']").val();
421
+ $('#modaledit a.btn[name=\'save\']').unbind('click');
422
+ $('#modaledit a.btn[name=\'save\']').click(() => {
423
+ const newName = $('#modaledit').find('input[id=\'d_name\']').val();
424
424
  const groupsbyid = {};
425
- if (groupables.length > 0) {
426
- for (var i = 0;i<groupables.length;i++) {
427
- const ng = $('#d_groups_ep'+i).val();
428
- if (ng.toString() != groupables[i].memberOf.toString())
429
- groupsbyid[groupables[i].ep.ID] = GenerateGroupChange(groupables[i].memberOf, ng);
430
- }
425
+ if (groupables.length > 0) {
426
+ for (var i = 0; i < groupables.length; i++) {
427
+ const ng = $('#d_groups_ep' + i).val();
428
+ if (ng.toString() != groupables[i].memberOf.toString())
429
+ groupsbyid[groupables[i].ep.ID] = GenerateGroupChange(groupables[i].memberOf, ng);
430
+ }
431
431
  }
432
432
  console.log('grpid ' + JSON.stringify(groupsbyid));
433
433
  updateDev(id, newName, groupsbyid);
@@ -436,14 +436,13 @@ function editName(id, name) {
436
436
  Materialize.updateTextFields();
437
437
  }
438
438
 
439
- function GenerateGroupChange(oldmembers, newmembers)
440
- {
441
- let grpchng = [];
442
- for (const oldg of oldmembers)
443
- if (!newmembers.includes(oldg)) grpchng.push('-'+oldg);
444
- for (const newg of newmembers)
445
- if (!oldmembers.includes(newg)) grpchng.push(newg)
446
- return grpchng;
439
+ function GenerateGroupChange(oldmembers, newmembers) {
440
+ let grpchng = [];
441
+ for (const oldg of oldmembers)
442
+ if (!newmembers.includes(oldg)) grpchng.push('-' + oldg);
443
+ for (const newg of newmembers)
444
+ if (!oldmembers.includes(newg)) grpchng.push(newg);
445
+ return grpchng;
447
446
  }
448
447
 
449
448
  function deleteDevice(id, force) {
@@ -474,6 +473,7 @@ function cleanDeviceStates(force) {
474
473
  });
475
474
  showWaitingDialog('Device is being removed', 10);
476
475
  }
476
+
477
477
  function renameDevice(id, name) {
478
478
  sendTo(namespace, 'renameDevice', {id: id, name: name}, function (msg) {
479
479
  if (msg) {
@@ -490,7 +490,7 @@ function showDevices() {
490
490
  let html = '';
491
491
  const lang = systemLang || 'en';
492
492
  // sort by rooms
493
- devices.sort((a, b)=>{
493
+ devices.sort((a, b) => {
494
494
  const roomsA = [], roomsB = [];
495
495
  for (const r in a.rooms) {
496
496
  if (a.rooms[r].hasOwnProperty(lang)) {
@@ -517,29 +517,28 @@ function showDevices() {
517
517
  }
518
518
  return 0;
519
519
  });
520
- for (let i=0;i < devices.length; i++) {
520
+ for (let i = 0; i < devices.length; i++) {
521
521
  const d = devices[i];
522
522
  if (!d.info) {
523
- if (d.common && d.common.type == 'group')
524
- {
523
+ if (d.common && d.common.type == 'group') {
525
524
  const card = getGroupCard(d);
526
525
  html += card;
527
526
  continue;
528
527
  }
529
- };
528
+ }
529
+ ;
530
530
  if (d.info && d.info.device._type == 'Coordinator') {
531
531
  const card = getCoordinatorCard(d);
532
532
  html += card;
533
533
  } else {
534
- //if (d.groups && d.info && d.info.device._type == "Router") {
534
+ //if (d.groups && d.info && d.info.device._type == "Router") {
535
535
  if (d.groups) {
536
536
  // devGroups[d._id] = d.groups;
537
537
  if (typeof d.groups.map == 'function') {
538
- d.groupNames = d.groups.map(item=>{
538
+ d.groupNames = d.groups.map(item => {
539
539
  return groups[item] || '';
540
540
  }).join(', ');
541
- }
542
- else {
541
+ } else {
543
542
  d.groupNames = '..';
544
543
  }
545
544
  }
@@ -551,13 +550,13 @@ function showDevices() {
551
550
  hookControls();
552
551
 
553
552
  // update rooms filter
554
- const allRooms = new Set(devices.map((item)=>item.rooms).flat().map((room)=>{
553
+ const allRooms = new Set(devices.map((item) => item.rooms).flat().map((room) => {
555
554
  if (room && room.hasOwnProperty(lang)) {
556
555
  return room[lang];
557
556
  } else {
558
557
  return room;
559
558
  }
560
- }).filter((item)=>item != undefined));
559
+ }).filter((item) => item != undefined));
561
560
  const roomSelector = $('#room-filter');
562
561
  roomSelector.empty();
563
562
  roomSelector.append(`<li class="device-order-item" data-type="All" tabindex="0"><a class="translate" data-lang="All">All</a></li>`);
@@ -568,69 +567,69 @@ function showDevices() {
568
567
  $('#room-filter-btn').text($(this).text());
569
568
  doFilter();
570
569
  });
571
- $(".flip").click(function(){
572
- const card = $(this).parents(".card");
573
- card.toggleClass("flipped");
570
+ $('.flip').click(function () {
571
+ const card = $(this).parents('.card');
572
+ card.toggleClass('flipped');
574
573
  });
575
574
  $('#rotate_btn').click(function () {
576
- $('.card.flipable').toggleClass("flipped");
575
+ $('.card.flipable').toggleClass('flipped');
577
576
  });
578
577
 
579
- shuffleInstance = new Shuffle($("#devices"), {
578
+ shuffleInstance = new Shuffle($('#devices'), {
580
579
  itemSelector: '.device',
581
580
  sizer: '.js-shuffle-sizer',
582
581
  });
583
582
  doFilter();
584
583
 
585
- const getDevName = function(dev_block) {
584
+ const getDevName = function (dev_block) {
586
585
  return dev_block.find('#dName').text();
587
586
  };
588
- const getDevId = function(dev_block) {
587
+ const getDevId = function (dev_block) {
589
588
  return dev_block.attr('id');
590
589
  };
591
- $(".card-reveal-buttons button[name='delete']").click(function() {
590
+ $('.card-reveal-buttons button[name=\'delete\']').click(function () {
592
591
  const dev_block = $(this).parents('div.device');
593
592
  deleteConfirmation(getDevId(dev_block), getDevName(dev_block));
594
593
  });
595
- $(".card-reveal-buttons button[name='deletegrp']").click(function() {
594
+ $('.card-reveal-buttons button[name=\'deletegrp\']').click(function () {
596
595
  const dev_block = $(this).parents('div.device');
597
- const id = dev_block.attr('id').replace(namespace+'.group_', '');
596
+ const id = dev_block.attr('id').replace(namespace + '.group_', '');
598
597
  deleteGroupConfirmation(id, getDevName(dev_block));
599
598
  });
600
- $(".card-reveal-buttons button[name='edit']").click(function() {
599
+ $('.card-reveal-buttons button[name=\'edit\']').click(function () {
601
600
  const dev_block = $(this).parents('div.device'),
602
601
  id = getDevId(dev_block),
603
602
  name = getDevName(dev_block);
604
603
  editName(id, name);
605
604
  });
606
- $(".card-reveal-buttons button[name='editgrp']").click(function() {
605
+ $('.card-reveal-buttons button[name=\'editgrp\']').click(function () {
607
606
  const dev_block = $(this).parents('div.device'),
608
- id = dev_block.attr('id').replace(namespace+'.group_', ''),
607
+ id = dev_block.attr('id').replace(namespace + '.group_', ''),
609
608
  name = getDevName(dev_block);
610
609
  editGroupName(id, name, false);
611
610
  });
612
- $("button.btn-floating[name='join']").click(function() {
611
+ $('button.btn-floating[name=\'join\']').click(function () {
613
612
  const dev_block = $(this).parents('div.device');
614
613
  if (!$('#pairing').hasClass('pulse'))
615
614
  joinProcess(getDevId(dev_block));
616
615
  showPairingProcess();
617
616
  });
618
- $(".card-reveal-buttons button[name='info']").click(function() {
617
+ $('.card-reveal-buttons button[name=\'info\']').click(function () {
619
618
  const dev_block = $(this).parents('div.device');
620
619
  showDevInfo(getDevId(dev_block));
621
620
  });
622
- $("a.btn[name='done']").click((e) => {
621
+ $('a.btn[name=\'done\']').click((e) => {
623
622
  const dev_block = $(this).parents('div.device');
624
623
  closeReval(e, getDevId(dev_block), getDevName(dev_block));
625
624
  });
626
- $("a.btn-flat[name='close']").click((e) => {
625
+ $('a.btn-flat[name=\'close\']').click((e) => {
627
626
  closeReval(e);
628
627
  });
629
- $(".card-reveal-buttons button[name='reconfigure']").click(function() {
628
+ $('.card-reveal-buttons button[name=\'reconfigure\']').click(function () {
630
629
  const dev_block = $(this).parents('div.device');
631
630
  reconfigureDlg(getDevId(dev_block));
632
631
  });
633
- $(".card-reveal-buttons button[name='swapactive']").click(function() {
632
+ $('.card-reveal-buttons button[name=\'swapactive\']').click(function () {
634
633
  const dev_block = $(this).parents('div.device');
635
634
  swapActive(getDevId(dev_block));
636
635
  });
@@ -641,10 +640,10 @@ function showDevices() {
641
640
 
642
641
  function checkFwUpdate() {
643
642
  const deviceCards = getDeviceCards();
644
- const getFwInfoNode = function(deviceCard) {
643
+ const getFwInfoNode = function (deviceCard) {
645
644
  return deviceCard.find('.fw_info');
646
645
  };
647
- const createBtn = function(icon, hint, disabled, color) {
646
+ const createBtn = function (icon, hint, disabled, color) {
648
647
  const disabledAttr = disabled ? '[disabled]="true"' : '';
649
648
  if (!color) {
650
649
  color = !disabled ? 'icon-green' : '';
@@ -652,14 +651,14 @@ function checkFwUpdate() {
652
651
  return `<button name="fw_update" class="left btn-flat btn-small" title="${hint}" ${disabledAttr}>
653
652
  <i class="material-icons ${color}">${icon}</i></button>`;
654
653
  };
655
- const callback = function(msg) {
654
+ const callback = function (msg) {
656
655
  if (msg) {
657
656
  const deviceCard = getDeviceCard(msg.device);
658
657
  const devId = getDevId(deviceCard.attr('id'));
659
658
  const fwInfoNode = getFwInfoNode(deviceCard);
660
659
  if (msg.status == 'available') {
661
660
  fwInfoNode.html(createBtn('system_update', 'Click to start firmware update', false));
662
- $(fwInfoNode).find("button[name='fw_update']").click(() => {
661
+ $(fwInfoNode).find('button[name=\'fw_update\']').click(() => {
663
662
  fwInfoNode.html(createBtn('check_circle', 'Firmware update started, check progress in logs.', true, 'icon-blue'));
664
663
  sendTo(namespace, 'startOta', {devId: devId}, (msg) => {
665
664
  fwInfoNode.html(createBtn('check_circle', 'Finished, see logs.', true));
@@ -669,13 +668,13 @@ function checkFwUpdate() {
669
668
  } else if (msg.status == 'not_available') {
670
669
  fwInfoNode.html(createBtn('check_circle', 'Up-to-date', true));
671
670
  } else if (msg.status == 'fail') {
672
- fwInfoNode.html(createBtn('check_circle', 'Firmware check failed, '+msg.msg, true, 'icon-red'));
671
+ fwInfoNode.html(createBtn('check_circle', 'Firmware check failed, ' + msg.msg, true, 'icon-red'));
673
672
  } else {
674
673
  fwInfoNode.html(createBtn('not_interested', 'No firmware update available', true));
675
674
  }
676
675
  }
677
676
  };
678
- for (let i=0;i < deviceCards.length; i++) {
677
+ for (let i = 0; i < deviceCards.length; i++) {
679
678
  const deviceCard = $(deviceCards[i]);
680
679
  const devId = getDevId(deviceCard.attr('id'));
681
680
  getFwInfoNode(deviceCard).html('<span class="left" style="padding-top:8px">checking...</span>');
@@ -746,7 +745,7 @@ function getDevices() {
746
745
  }
747
746
 
748
747
  function getDeviceCards() {
749
- return $('#devices .device').not(".group");
748
+ return $('#devices .device').not('.group');
750
749
  }
751
750
 
752
751
  function getDeviceCard(devId) {
@@ -810,35 +809,35 @@ function load(settings, onChange) {
810
809
  // Signal to admin, that no changes yet
811
810
  onChange(false);
812
811
 
813
- $('#state_cleanup_btn').click(function() {
812
+ $('#state_cleanup_btn').click(function () {
814
813
  cleanConfirmation();
815
814
  });
816
- $('#fw_check_btn').click(function() {
815
+ $('#fw_check_btn').click(function () {
817
816
  checkFwUpdate();
818
817
  });
819
- $('#touchlink_btn').click(function() {
818
+ $('#touchlink_btn').click(function () {
820
819
  touchlinkReset();
821
820
  showPairingProcess();
822
821
  });
823
- $('#pairing').click(function() {
822
+ $('#pairing').click(function () {
824
823
  if (!$('#pairing').hasClass('pulse'))
825
824
  letsPairing();
826
825
  showPairingProcess();
827
826
  });
828
827
 
829
- $('#refresh').click(function() {
828
+ $('#refresh').click(function () {
830
829
  getMap();
831
830
  });
832
831
 
833
- $('#reset-btn').click(function() {
832
+ $('#reset-btn').click(function () {
834
833
  resetConfirmation();
835
834
  });
836
835
 
837
- $('#viewconfig').click(function() {
836
+ $('#viewconfig').click(function () {
838
837
  showViewConfig();
839
838
  });
840
839
 
841
- $('#scan').click(function() {
840
+ $('#scan').click(function () {
842
841
  showChannels();
843
842
  });
844
843
 
@@ -847,18 +846,18 @@ function load(settings, onChange) {
847
846
  showGroups();
848
847
  });
849
848
 
850
- $('#add_group').click(function() {
849
+ $('#add_group').click(function () {
851
850
  // showGroupList(true);
852
- const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
853
- editGroupName(maxind+1, 'Group ' + maxind+1, true);
851
+ const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
852
+ editGroupName(maxind + 1, 'Group ' + maxind + 1, true);
854
853
  });
855
- $('#add_grp_btn').click(function() {
854
+ $('#add_grp_btn').click(function () {
856
855
  // showGroupList(true);
857
- const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
858
- editGroupName(maxind+1, 'Group ' + maxind+1, true);
856
+ const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
857
+ editGroupName(maxind + 1, 'Group ' + maxind + 1, true);
859
858
  });
860
859
 
861
- $(document).ready(function() {
860
+ $(document).ready(function () {
862
861
  $('.modal').modal({
863
862
  startingTop: '30%',
864
863
  endingTop: '10%',
@@ -882,7 +881,7 @@ function load(settings, onChange) {
882
881
  $('#pairing').attr('data-tooltip', transText);
883
882
  }
884
883
 
885
- $('ul.tabs').on('click', 'a', function(e) {
884
+ $('ul.tabs').on('click', 'a', function (e) {
886
885
  if ($(e.target).attr('id') == 'tabmap') {
887
886
  redrawMap();
888
887
  }
@@ -891,11 +890,11 @@ function load(settings, onChange) {
891
890
  }
892
891
  });
893
892
 
894
- $('#add_exclude').click(function() {
893
+ $('#add_exclude').click(function () {
895
894
  addExcludeDialog();
896
895
  });
897
896
 
898
- $('#add_binding').click(function() {
897
+ $('#add_binding').click(function () {
899
898
  addBindingDialog();
900
899
  });
901
900
 
@@ -934,7 +933,7 @@ function save(callback) {
934
933
  const $this = $(this);
935
934
  if (savedSettings.indexOf($this.attr('id')) === -1) return;
936
935
  if ($this.hasClass('validate') && $this.hasClass('invalid')) {
937
- showMessage('Invalid input for ' +$this.attr('id'), _('Error'));
936
+ showMessage('Invalid input for ' + $this.attr('id'), _('Error'));
938
937
  return;
939
938
  }
940
939
  if ($this.attr('type') === 'checkbox') {
@@ -948,7 +947,7 @@ function save(callback) {
948
947
 
949
948
 
950
949
  function getDevId(adapterDevId) {
951
- return adapterDevId.split('.').slice(0,3).join('.');
950
+ return adapterDevId.split('.').slice(0, 3).join('.');
952
951
  }
953
952
 
954
953
  // subscribe to changes
@@ -975,7 +974,7 @@ socket.on('stateChange', function (id, state) {
975
974
  } else {
976
975
  $('#pairing').addClass('pulse');
977
976
  $('#pairing').html(state.val);
978
- const percent = 100-100*state.val/($('#countDown').val() || 60);
977
+ const percent = 100 - 100 * state.val / ($('#countDown').val() || 60);
979
978
  $('#progress_line').css('width', `${percent}%`);
980
979
  }
981
980
  } else if (id.match(/\.info\.pairingMessage$/)) {
@@ -1021,6 +1020,7 @@ socket.on('objectChange', function (id, obj) {
1021
1020
  }
1022
1021
  }
1023
1022
  });
1023
+
1024
1024
  /*
1025
1025
  socket.emit('getObject', 'system.config', function (err, res) {
1026
1026
  if (!err && res && res.common) {
@@ -1033,45 +1033,47 @@ socket.emit('getObject', 'system.config', function (err, res) {
1033
1033
  function putEventToNode(devId) {
1034
1034
  if (network) {
1035
1035
  const nodesArray = Object.values(network.body.data.nodes._data);
1036
- const node = nodesArray.find((node) => { return node.id == devId; });
1036
+ const node = nodesArray.find((node) => {
1037
+ return node.id == devId;
1038
+ });
1037
1039
  if (node) {
1038
1040
  const exists = networkEvents.find((event) => {
1039
1041
  return event.node == node.id;
1040
1042
  });
1041
1043
  if (!exists) {
1042
1044
  networkEvents.push({node: node.id, radius: 0, forward: true});
1043
- // } else {
1044
- // exists.radius = 0;
1045
- // exists.forward = true;
1045
+ // } else {
1046
+ // exists.radius = 0;
1047
+ // exists.forward = true;
1046
1048
  }
1047
1049
  }
1048
1050
  }
1049
1051
  }
1050
1052
 
1051
- function showNetworkMap(devices, map){
1053
+ function showNetworkMap(devices, map) {
1052
1054
  // create an object with nodes
1053
1055
  const nodes = {};
1054
1056
  // create an array with edges
1055
1057
  const edges = [];
1056
1058
 
1057
1059
  if (map.lqis == undefined || map.lqis.length === 0) { // first init
1058
- $('#filterParent, #filterSibl, #filterPrvChild, #filterMesh').change(function() {
1060
+ $('#filterParent, #filterSibl, #filterPrvChild, #filterMesh').change(function () {
1059
1061
  updateMapFilter();
1060
1062
  });
1061
1063
  }
1062
1064
 
1063
- const createNode = function(dev, mapEntry) {
1065
+ const createNode = function (dev, mapEntry) {
1064
1066
  if (dev.common && dev.common.type == 'group') return undefined;
1065
1067
  const extInfo = (mapEntry && mapEntry.networkAddress) ? `\n (nwkAddr: 0x${mapEntry.networkAddress.toString(16)} | ${mapEntry.networkAddress})` : '';
1066
1068
  const node = {
1067
1069
  id: dev._id,
1068
- label: (dev.link_quality >0 ? dev.common.name:`${dev.common.name}\n(disconnected)`) ,
1069
- title: dev._id.replace(namespace+'.', '') + extInfo,
1070
+ label: (dev.link_quality > 0 ? dev.common.name : `${dev.common.name}\n(disconnected)`),
1071
+ title: dev._id.replace(namespace + '.', '') + extInfo,
1070
1072
  shape: 'circularImage',
1071
1073
  image: dev.icon,
1072
1074
  imagePadding: {top: 5, bottom: 5, left: 5, right: 5},
1073
1075
  color: {background: 'white', highlight: {background: 'white'}},
1074
- font: {color:'#007700'},
1076
+ font: {color: '#007700'},
1075
1077
  borderWidth: 1,
1076
1078
  borderWidthSelected: 4,
1077
1079
  };
@@ -1085,7 +1087,7 @@ function showNetworkMap(devices, map){
1085
1087
  };
1086
1088
 
1087
1089
  if (map.lqis) {
1088
- map.lqis.forEach((mapEntry)=>{
1090
+ map.lqis.forEach((mapEntry) => {
1089
1091
  const dev = getDevice(mapEntry.ieeeAddr);
1090
1092
  if (!dev) {
1091
1093
  //console.log("No dev with ieee "+mapEntry.ieeeAddr);
@@ -1098,8 +1100,7 @@ function showNetworkMap(devices, map){
1098
1100
  if (node) {
1099
1101
  nodes[mapEntry.ieeeAddr] = node;
1100
1102
  }
1101
- }
1102
- else {
1103
+ } else {
1103
1104
  node = nodes[mapEntry.ieeeAddr];
1104
1105
  }
1105
1106
  if (node) {
@@ -1117,7 +1118,7 @@ function showNetworkMap(devices, map){
1117
1118
 
1118
1119
  if (mapEntry.relationship === 0 || mapEntry.relationship === 1) { // 0 - parent, 1 - child
1119
1120
  // // parent/child
1120
- if (mapEntry.status !== 'online' ) {
1121
+ if (mapEntry.status !== 'online') {
1121
1122
  label = label + ' (off)';
1122
1123
  linkColor = '#ff0000';
1123
1124
  }
@@ -1134,8 +1135,8 @@ function showNetworkMap(devices, map){
1134
1135
  if (reverse) {
1135
1136
  // update reverse edge
1136
1137
  edge = reverse;
1137
- edge.label += '\n'+label;
1138
- edge.arrows.from = { enabled: false, scaleFactor: 0.5 }; // start hidden if node is not selected
1138
+ edge.label += '\n' + label;
1139
+ edge.arrows.from = {enabled: false, scaleFactor: 0.5}; // start hidden if node is not selected
1139
1140
  if (mapEntry.relationship == 1) { //
1140
1141
  edge.color.color = linkColor;
1141
1142
  edge.color.highlight = linkColor;
@@ -1150,7 +1151,7 @@ function showNetworkMap(devices, map){
1150
1151
  size: 0, // start hidden
1151
1152
  color: linkColor
1152
1153
  },
1153
- arrows: { to: { enabled: false, scaleFactor: 0.5 }},
1154
+ arrows: {to: {enabled: false, scaleFactor: 0.5}},
1154
1155
  //arrowStrikethrough: false,
1155
1156
  color: {
1156
1157
  color: linkColor,
@@ -1164,7 +1165,7 @@ function showNetworkMap(devices, map){
1164
1165
  values.fromArrow = values.fromArrowScale != 1 ? true : false; // simplified, arrow existing if scale is not default value
1165
1166
  },
1166
1167
  label: () => {
1167
- // see onMapSelect workaround
1168
+ // see onMapSelect workaround
1168
1169
  // values.size = 10;
1169
1170
  }
1170
1171
  },
@@ -1230,15 +1231,16 @@ function showNetworkMap(devices, map){
1230
1231
  const nodesArray = Object.values(nodes);
1231
1232
  // add devices without network links to map
1232
1233
  devices.forEach((dev) => {
1233
- const node = nodesArray.find((node) => { return node.id == dev._id; });
1234
+ const node = nodesArray.find((node) => {
1235
+ return node.id == dev._id;
1236
+ });
1234
1237
  if (!node) {
1235
1238
  const node = createNode(dev);
1236
1239
 
1237
- if (node)
1238
- {
1239
- node.font = {color:'#ff0000'};
1240
+ if (node) {
1241
+ node.font = {color: '#ff0000'};
1240
1242
  if (dev.info && dev.info.device._type == 'Coordinator') {
1241
- node.font = {color:'#000000'};
1243
+ node.font = {color: '#000000'};
1242
1244
  }
1243
1245
  nodesArray.push(node);
1244
1246
  }
@@ -1260,7 +1262,7 @@ function showNetworkMap(devices, map){
1260
1262
  shape: 'box'
1261
1263
  },
1262
1264
  layout: {
1263
- improvedLayout:true,
1265
+ improvedLayout: true,
1264
1266
  }
1265
1267
  };
1266
1268
 
@@ -1271,13 +1273,14 @@ function showNetworkMap(devices, map){
1271
1273
  // may be moved to edge.chosen.label if fixed
1272
1274
  function doSelection(select, edges, data) {
1273
1275
  edges.forEach((edgeId => {
1274
- const options = data.edges._data[edgeId];
1276
+ const id = (typeof edgeId === 'string') ? edgeId : edgeId.id;
1277
+ const options = data.edges._data.get(id);
1275
1278
  if (select) {
1276
1279
  options.font.size = 15;
1277
1280
  } else {
1278
1281
  options.font.size = 0;
1279
1282
  }
1280
- network.clustering.updateEdge(edgeId, options);
1283
+ network.clustering.updateEdge(id, options);
1281
1284
  }));
1282
1285
  }
1283
1286
 
@@ -1309,25 +1312,26 @@ function showNetworkMap(devices, map){
1309
1312
  if (networkEvents.length > 0) {
1310
1313
  network.redraw();
1311
1314
  const toDelete = [];
1312
- networkEvents.forEach((event, index)=>{
1315
+ networkEvents.forEach((event, index) => {
1313
1316
  if (event.radius >= 1) {
1314
1317
  toDelete.push(index);
1315
1318
  } else {
1316
1319
  event.radius += 0.08;
1317
1320
  }
1318
1321
  });
1319
- toDelete.forEach((index)=>{
1322
+ toDelete.forEach((index) => {
1320
1323
  networkEvents.splice(index, 1);
1321
1324
  });
1322
1325
  }
1323
1326
  }
1324
- network.on('beforeDrawing', function(ctx) {
1327
+
1328
+ network.on('beforeDrawing', function (ctx) {
1325
1329
  if (networkEvents.length > 0) {
1326
- networkEvents.forEach((event)=>{
1330
+ networkEvents.forEach((event) => {
1327
1331
  const inode = event.node;
1328
1332
  const nodePosition = network.getPositions();
1329
1333
  event.radius = (event.radius > 1) ? 1 : event.radius;
1330
- const cap = Math.cos(event.radius*Math.PI/2);
1334
+ const cap = Math.cos(event.radius * Math.PI / 2);
1331
1335
  const colorCircle = `rgba(0, 255, 255, ${cap.toFixed(2)})`;
1332
1336
  const colorBorder = `rgba(0, 255, 255, ${cap.toFixed(2)})`;
1333
1337
  ctx.strokeStyle = colorCircle;
@@ -1344,11 +1348,11 @@ function showNetworkMap(devices, map){
1344
1348
  function redrawMap() {
1345
1349
  if (network != undefined && devices.length > 0) {
1346
1350
  const width = ($('.adapter-body').width() || $('#main').width()) - 20,
1347
- height = ($('.adapter-body').height() || ($('#main').height())) -120;
1351
+ height = ($('.adapter-body').height() || ($('#main').height())) - 120;
1348
1352
  network.setSize(width, height);
1349
1353
  network.redraw();
1350
1354
  network.fit();
1351
- network.moveTo({offset:{x:0.5 * width, y:0.5 * height}});
1355
+ network.moveTo({offset: {x: 0.5 * width, y: 0.5 * height}});
1352
1356
  }
1353
1357
  }
1354
1358
 
@@ -1362,9 +1366,9 @@ function updateMapFilter() {
1362
1366
  const invisColor = $('#filterMesh').is(':checked') ? 0.2 : 0;
1363
1367
  mapEdges.forEach((edge) => {
1364
1368
  if (((edge.relationship === 0 || edge.relationship === 1) && showParent)
1365
- || (edge.relationship === 2 && showSibl)
1366
- || (edge.relationship === 3 && showParent) // ignore relationship "unknown"
1367
- || (edge.relationship === 4 && showPrvChild)) {
1369
+ || (edge.relationship === 2 && showSibl)
1370
+ || (edge.relationship === 3 && showParent) // ignore relationship "unknown"
1371
+ || (edge.relationship === 4 && showPrvChild)) {
1368
1372
  edge.color.opacity = 1.0;
1369
1373
  } else {
1370
1374
  edge.color.opacity = invisColor;
@@ -1388,13 +1392,15 @@ function getComPorts(onChange) {
1388
1392
  // }, 1000);
1389
1393
  // return;
1390
1394
  // }
1391
- if (!list) return;
1395
+ if (!list) {
1396
+ return;
1397
+ }
1392
1398
  const element = $('#ports');
1393
1399
  for (let j = 0; j < list.length; j++) {
1394
- element.append('<li><a href="#!">' + list[j].comName +'</a></li>');
1400
+ element.append('<li><a href="#!" data-value="' + list[j].comName + '">' + list[j].comName + (list[j].label ? (' [' + list[j].label + ']') : '') + '</a></li>');
1395
1401
  }
1396
- $('#ports a').click(function() {
1397
- $('#port').val($(this).text());
1402
+ $('#ports a').click(function () {
1403
+ $('#port').val($(this).data('value'));
1398
1404
  Materialize.updateTextFields();
1399
1405
  onChange();
1400
1406
  });
@@ -1404,29 +1410,33 @@ function getComPorts(onChange) {
1404
1410
  function loadDeveloperTab() {
1405
1411
  // fill device selector
1406
1412
  updateSelect('#dev', devices,
1407
- function(key, device) {
1413
+ function (key, device) {
1408
1414
  if (device.hasOwnProperty('info')) {
1409
- if (device.info.device._type == 'Coordinator') {
1415
+ if (device.info.device._type === 'Coordinator') {
1410
1416
  return null;
1411
1417
  }
1412
1418
  return `${device.common.name} (${device.info.name})`;
1413
1419
  } else { // fallback if device in list but not paired
1414
- device.common.name + ' ' +device.native.id;
1420
+ return device.common.name + ' ' + device.native.id;
1415
1421
  }
1416
1422
  },
1417
- function(key, device) {
1423
+ function (key, device) {
1418
1424
  return device._id;
1419
1425
  });
1420
1426
  // add groups to device selector
1421
1427
  const groupList = [];
1422
1428
  for (const key in groups) {
1423
- groupList.push({'_id': namespace+'.'+key.toString(16).padStart(16, '0'), 'groupId': key, 'groupName': groups[key]});
1429
+ groupList.push({
1430
+ _id: namespace + '.' + key.toString(16).padStart(16, '0'),
1431
+ groupId: key,
1432
+ groupName: groups[key]
1433
+ });
1424
1434
  }
1425
1435
  updateSelect('#dev', groupList,
1426
- function(key, device) {
1427
- return 'Group '+device.groupId+': '+device.groupName;
1436
+ function (key, device) {
1437
+ return 'Group ' + device.groupId + ': ' + device.groupName;
1428
1438
  },
1429
- function(key, device) {
1439
+ function (key, device) {
1430
1440
  return device._id;
1431
1441
  }, true);
1432
1442
 
@@ -1436,10 +1446,10 @@ function loadDeveloperTab() {
1436
1446
  populateSelector('#type', 'typeList', this.value);
1437
1447
 
1438
1448
  if (responseCodes == false) {
1439
- const getValue = function() { // convert to number if needed
1449
+ const getValue = function () { // convert to number if needed
1440
1450
  let attrData = $('#value-input').val();
1441
1451
  if (attrData.startsWith('"') && attrData.endsWith('"')) {
1442
- attrData = attrData.substr(1, attrData.length -2);
1452
+ attrData = attrData.substr(1, attrData.length - 2);
1443
1453
  } else {
1444
1454
  const numValue = Number(attrData);
1445
1455
  attrData = !isNaN(numValue) ? numValue : attrData;
@@ -1464,35 +1474,38 @@ function loadDeveloperTab() {
1464
1474
  return data;
1465
1475
  };
1466
1476
 
1467
- const prepareExpertData = function() {
1477
+ const prepareExpertData = function () {
1468
1478
  try {
1469
1479
  return JSON.parse($('#expert-json').val());
1470
1480
  } catch (exception) {
1471
1481
  showDevRunInfo('JSON error', exception, 'yellow');
1472
1482
  }
1473
1483
  };
1474
- const setExpertData = function(prop, value, removeIfEmpty = true) {
1484
+ const setExpertData = function (prop, value, removeIfEmpty = true) {
1475
1485
  if (!$('#expert-mode').is(':checked')) {
1476
1486
  return;
1477
1487
  }
1478
- if (!removeIfEmpty && value == null) { value = ''; }
1488
+ if (!removeIfEmpty && value == null) {
1489
+ value = '';
1490
+ }
1479
1491
  let data;
1480
1492
  if (prop) {
1481
1493
  data = prepareExpertData();
1482
1494
  // https://stackoverflow.com/a/6394168/6937282
1483
- const assignVal = function index(obj,is, value) {
1495
+ const assignVal = function index(obj, is, value) {
1484
1496
  if (typeof is == 'string') {
1485
- return index(obj,is.split('.'), value);
1486
- } else if (is.length==1 && value!==undefined) {
1497
+ return index(obj, is.split('.'), value);
1498
+ } else if (is.length === 1 && value !== undefined) {
1487
1499
  if (value == null) {
1488
1500
  return delete obj[is[0]];
1489
1501
  } else {
1490
1502
  return obj[is[0]] = value;
1491
1503
  }
1492
- } else if (is.length==0) {
1504
+ } else if (!is.length) {
1493
1505
  return obj;
1494
- } else
1495
- return index(obj[is[0]],is.slice(1), value);
1506
+ } else {
1507
+ return index(obj[is[0]], is.slice(1), value);
1508
+ }
1496
1509
  };
1497
1510
  assignVal(data, prop, value);
1498
1511
  } else {
@@ -1502,7 +1515,7 @@ function loadDeveloperTab() {
1502
1515
  };
1503
1516
 
1504
1517
  // init event listener only at first load
1505
- $('#dev-selector').change(function() {
1518
+ $('#dev-selector').change(function () {
1506
1519
  if (this.selectedIndex <= 0) {
1507
1520
  return;
1508
1521
  }
@@ -1513,48 +1526,48 @@ function loadDeveloperTab() {
1513
1526
 
1514
1527
  const epList = device ? device.info.device._endpoints : null;
1515
1528
  updateSelect('#ep', epList,
1516
- function(key, ep) {
1529
+ function (key, ep) {
1517
1530
  return ep.ID;
1518
1531
  },
1519
- function(key, ep) {
1532
+ function (key, ep) {
1520
1533
  return ep.ID;
1521
1534
  });
1522
1535
  setExpertData('devId', this.value);
1523
1536
  setExpertData('ep', $('#ep-selector').val(), false);
1524
1537
  });
1525
1538
 
1526
- $('#ep-selector').change(function() {
1539
+ $('#ep-selector').change(function () {
1527
1540
  setExpertData('ep', this.value);
1528
1541
  });
1529
1542
 
1530
- $('#cid-selector').change(function() {
1543
+ $('#cid-selector').change(function () {
1531
1544
  populateSelector('#attrid', 'attrIdList', this.value);
1532
- if ($('#cmd-type-selector').val() == 'functional') {
1545
+ if ($('#cmd-type-selector').val() === 'functional') {
1533
1546
  const cid = $('#cid-selector option:selected').val();
1534
1547
  populateSelector('#cmd', 'cmdListFunctional', cid);
1535
1548
  }
1536
1549
  setExpertData('cid', this.value);
1537
1550
  });
1538
1551
 
1539
- $('#cmd-type-selector').change(function() {
1540
- if (this.value == 'foundation') {
1552
+ $('#cmd-type-selector').change(function () {
1553
+ if (this.value === 'foundation') {
1541
1554
  populateSelector('#cmd', 'cmdListFoundation');
1542
- } else if (this.value == 'functional') {
1555
+ } else if (this.value === 'functional') {
1543
1556
  const cid = $('#cid-selector option:selected').val();
1544
1557
  populateSelector('#cmd', 'cmdListFunctional', cid);
1545
1558
  }
1546
1559
  setExpertData('cmdType', this.value);
1547
1560
  });
1548
1561
 
1549
- $('#cmd-selector').change(function() {
1562
+ $('#cmd-selector').change(function () {
1550
1563
  setExpertData('cmd', this.value);
1551
1564
  });
1552
- $('#attrid-selector').change(function() {
1553
- setExpertData('zclData', {[this.value]:{}});
1565
+ $('#attrid-selector').change(function () {
1566
+ setExpertData('zclData', {[this.value]: {}});
1554
1567
  });
1555
1568
 
1556
1569
  // value selector checkbox
1557
- $('#value-needed').change(function() {
1570
+ $('#value-needed').change(function () {
1558
1571
  const attr = $('#attrid-selector').val();
1559
1572
  let attrData = null;
1560
1573
  if (this.checked === true) {
@@ -1563,17 +1576,17 @@ function loadDeveloperTab() {
1563
1576
  } else {
1564
1577
  $('#value-input').attr('disabled', 'disabled');
1565
1578
  }
1566
- setExpertData('zclData.'+attr, attrData);
1579
+ setExpertData('zclData.' + attr, attrData);
1567
1580
  $('#type-selector').select();
1568
1581
  Materialize.updateTextFields();
1569
1582
  });
1570
1583
 
1571
- $('#value-input').keyup(function() {
1584
+ $('#value-input').keyup(function () {
1572
1585
  const attr = $('#attrid-selector').val();
1573
- setExpertData('zclData.'+attr, getValue());
1586
+ setExpertData('zclData.' + attr, getValue());
1574
1587
  });
1575
1588
 
1576
- $('#expert-mode').change(function() {
1589
+ $('#expert-mode').change(function () {
1577
1590
  if (this.checked === true) {
1578
1591
  setExpertData();
1579
1592
  $('#expert-json-box').css('display', 'inline-block');
@@ -1584,7 +1597,7 @@ function loadDeveloperTab() {
1584
1597
  Materialize.updateTextFields();
1585
1598
  });
1586
1599
 
1587
- $('#dev-send-btn').click(function() {
1600
+ $('#dev-send-btn').click(function () {
1588
1601
  let data;
1589
1602
  if ($('#expert-mode').is(':checked')) {
1590
1603
  data = prepareExpertData();
@@ -1592,7 +1605,7 @@ function loadDeveloperTab() {
1592
1605
  data = prepareData();
1593
1606
  }
1594
1607
  sendToZigbee(data.devId, data.ep, data.cid, data.cmd, data.cmdType, data.zclData, data.cfg, function (reply) {
1595
- console.log('Reply from zigbee: '+ JSON.stringify(reply));
1608
+ console.log('Reply from zigbee: ' + JSON.stringify(reply));
1596
1609
  if (reply.hasOwnProperty('localErr')) {
1597
1610
  showDevRunInfo(reply.localErr, reply.errMsg, 'yellow');
1598
1611
  } else if (reply.hasOwnProperty('localStatus')) {
@@ -1633,25 +1646,34 @@ function loadDeveloperTab() {
1633
1646
  */
1634
1647
  function sendToZigbee(id, ep, cid, cmd, cmdType, zclData, cfg, callback) {
1635
1648
  if (!id) {
1636
- if (callback) {callback({localErr: 'Incomplete', errMsg: 'Please select Device and Endpoint!'});}
1649
+ if (callback) {
1650
+ callback({localErr: 'Incomplete', errMsg: 'Please select Device and Endpoint!'});
1651
+ }
1637
1652
  return;
1638
1653
  }
1639
1654
  if (!cid || !cmd || !cmdType) {
1640
- if (callback) {callback({localErr: 'Incomplete', errMsg: 'Please choose ClusterId, Command, CommandType and AttributeId!'});}
1655
+ if (callback) {
1656
+ callback({
1657
+ localErr: 'Incomplete',
1658
+ errMsg: 'Please choose ClusterId, Command, CommandType and AttributeId!'
1659
+ });
1660
+ }
1641
1661
  return;
1642
1662
  }
1643
1663
  const data = {id: id, ep: ep, cid: cid, cmd: cmd, cmdType: cmdType, zclData: zclData, cfg: cfg};
1644
- if (callback) {callback({localStatus: 'Send', errMsg: 'Waiting for reply...'});}
1664
+ if (callback) {
1665
+ callback({localStatus: 'Send', errMsg: 'Waiting for reply...'});
1666
+ }
1645
1667
 
1646
- const sendTimeout = setTimeout(function() {
1668
+ const sendTimeout = setTimeout(function () {
1647
1669
  if (callback) {
1648
1670
  callback({localErr: 'Timeout', errMsg: 'We did not receive any response.'});
1649
1671
  }
1650
1672
  }, 15000);
1651
1673
 
1652
- console.log('Send to zigbee, id '+id+ ',ep '+ep+', cid '+cid+', cmd '+cmd+', cmdType '+cmdType+', zclData '+JSON.stringify(zclData));
1674
+ console.log('Send to zigbee, id ' + id + ',ep ' + ep + ', cid ' + cid + ', cmd ' + cmd + ', cmdType ' + cmdType + ', zclData ' + JSON.stringify(zclData));
1653
1675
 
1654
- sendTo(namespace, 'sendToZigbee', data, function(reply) {
1676
+ sendTo(namespace, 'sendToZigbee', data, function (reply) {
1655
1677
  clearTimeout(sendTimeout);
1656
1678
  if (callback) {
1657
1679
  callback(reply);
@@ -1664,11 +1686,10 @@ function sendToZigbee(id, ep, cid, cmd, cmdType, zclData, cfg, callback) {
1664
1686
  */
1665
1687
  function showDevRunInfo(result, text, level) {
1666
1688
  const card = $('#devActResult');
1667
- if (level == 'yellow') {
1668
- card.removeClass( 'white-text' ).addClass( 'yellow-text' );
1669
- }
1670
- else {
1671
- card.removeClass( 'yellow-text' ).addClass( 'white-text' );
1689
+ if (level === 'yellow') {
1690
+ card.removeClass('white-text').addClass('yellow-text');
1691
+ } else {
1692
+ card.removeClass('yellow-text').addClass('white-text');
1672
1693
  }
1673
1694
  $('#devActResult').text(result);
1674
1695
  $('#devInfoMsg').text(text);
@@ -1676,13 +1697,13 @@ function showDevRunInfo(result, text, level) {
1676
1697
 
1677
1698
  function addDevLog(reply) {
1678
1699
  const statusCode = reply.statusCode;
1679
- let logHtml = '<span>'+JSON.stringify(reply.msg)+'</span><br>';
1700
+ let logHtml = '<span>' + JSON.stringify(reply.msg) + '</span><br>';
1680
1701
  if (responseCodes != undefined) {
1681
1702
  const status = Object.keys(responseCodes).find(key => responseCodes[key] === statusCode);
1682
1703
  if (statusCode == 0) {
1683
- logHtml = '<span class="green-text">'+status+'</span> '+logHtml;
1704
+ logHtml = '<span class="green-text">' + status + '</span> ' + logHtml;
1684
1705
  } else {
1685
- logHtml = '<span class="yellow-text">'+status+'</span> '+logHtml;
1706
+ logHtml = '<span class="yellow-text">' + status + '</span> ' + logHtml;
1686
1707
  }
1687
1708
  }
1688
1709
  const logView = $('#dev_result_log');
@@ -1694,7 +1715,7 @@ function addDevLog(reply) {
1694
1715
  * Query adapter and update select with result
1695
1716
  */
1696
1717
  function populateSelector(selectId, key, cid) {
1697
- $(selectId+'>option:enabled').remove(); // remove existing elements
1718
+ $(selectId + '>option:enabled').remove(); // remove existing elements
1698
1719
  $(selectId).select();
1699
1720
  if (cid == '-2') {
1700
1721
  updateSelect(selectId, null);
@@ -1705,7 +1726,7 @@ function populateSelector(selectId, key, cid) {
1705
1726
  if (key === 'attrIdList') {
1706
1727
  updateSelect(selectId, list,
1707
1728
  (attrName, attr) => {
1708
- return attrName + ' ('+attr.ID +', type '+attr.type+')';
1729
+ return attrName + ' (' + attr.ID + ', type ' + attr.type + ')';
1709
1730
  },
1710
1731
  (attrName) => {
1711
1732
  return attrName;
@@ -1713,7 +1734,7 @@ function populateSelector(selectId, key, cid) {
1713
1734
  } else if (key === 'typeList') {
1714
1735
  updateSelect(selectId, list,
1715
1736
  (name, val) => {
1716
- return name +' ('+val+')';
1737
+ return name + ' (' + val + ')';
1717
1738
  },
1718
1739
  (name, val) => {
1719
1740
  return val;
@@ -1721,7 +1742,7 @@ function populateSelector(selectId, key, cid) {
1721
1742
  } else {
1722
1743
  updateSelect(selectId, list,
1723
1744
  (propName, propInfo) => {
1724
- return propName +' ('+propInfo.ID+')';
1745
+ return propName + ' (' + propInfo.ID + ')';
1725
1746
  },
1726
1747
  (propName) => {
1727
1748
  return propName;
@@ -1731,32 +1752,31 @@ function populateSelector(selectId, key, cid) {
1731
1752
  }
1732
1753
 
1733
1754
  function updateSelect(id, list, getText, getId, append = false) {
1734
- const selectId = id+'-selector';
1755
+ const selectId = id + '-selector';
1735
1756
  const mySelect = $(selectId);
1736
1757
  if (!append) {
1737
- $(selectId+'>:not(:first[disabled])').remove(); // remove existing elements, except first if disabled, (is 'Select...' info)
1758
+ $(selectId + '>:not(:first[disabled])').remove(); // remove existing elements, except first if disabled, (is 'Select...' info)
1738
1759
  mySelect.select();
1739
1760
  }
1740
1761
  if (list == null && !append) {
1741
1762
  const infoOption = new Option('Nothing available');
1742
1763
  infoOption.disabled = true;
1743
- mySelect.append( infoOption);
1744
- }
1745
- else {
1764
+ mySelect.append(infoOption);
1765
+ } else {
1746
1766
  const keys = Object.keys(list); // is index in case of array
1747
- for (let i=0; i<keys.length; i++) {
1767
+ for (let i = 0; i < keys.length; i++) {
1748
1768
  const key = keys[i];
1749
1769
  const item = list[key];
1750
1770
  const optionText = getText(key, item);
1751
1771
  if (optionText == null) {
1752
1772
  continue;
1753
1773
  }
1754
- mySelect.append( new Option(optionText, getId(key, item)));
1774
+ mySelect.append(new Option(optionText, getId(key, item)));
1755
1775
  }
1756
1776
  }
1757
1777
 
1758
- if ($(id+'-c-input').length > 0) {
1759
- mySelect.append( new Option('CUSTOM', -2));
1778
+ if ($(id + '-c-input').length > 0) {
1779
+ mySelect.append(new Option('CUSTOM', -2));
1760
1780
  }
1761
1781
  // update select element (Materialize)
1762
1782
  mySelect.select();
@@ -1785,17 +1805,17 @@ function showGroups() {
1785
1805
  const element = $('#groups_table');
1786
1806
  for (const j in groups) {
1787
1807
  if (groups.hasOwnProperty(j)) {
1788
- element.append(`<tr id="group_${j}" class="group"><td>${j}</td><td><div>${groups[j]}<span class="right">`+
1789
- `<a id="${j}" name="groupedit" class="waves-effect green btn-floating"><i class="material-icons">edit</i></a>`+
1790
- `<a id="${j}" name="groupdelete" class="waves-effect red btn-floating"><i class="material-icons">delete</i></a></span></div></td></tr>`);
1808
+ element.append(`<tr id="group_${j}" class="group"><td>${j}</td><td><div>${groups[j]}<span class="right">` +
1809
+ `<a id="${j}" name="groupedit" class="waves-effect green btn-floating"><i class="material-icons">edit</i></a>` +
1810
+ `<a id="${j}" name="groupdelete" class="waves-effect red btn-floating"><i class="material-icons">delete</i></a></span></div></td></tr>`);
1791
1811
  }
1792
1812
  }
1793
- $("a.btn-floating[name='groupedit']").click(function() {
1813
+ $('a.btn-floating[name=\'groupedit\']').click(function () {
1794
1814
  const index = $(this).attr('id'),
1795
1815
  name = groups[index];
1796
1816
  editGroupName(index, name, false);
1797
1817
  });
1798
- $("a.btn-floating[name='groupdelete']").click(function() {
1818
+ $('a.btn-floating[name=\'groupdelete\']').click(function () {
1799
1819
  const index = $(this).attr('id'),
1800
1820
  name = groups[index];
1801
1821
  deleteGroupConfirmation(index, name);
@@ -1807,53 +1827,51 @@ function editGroupName(id, name, isnew) {
1807
1827
  //console.log('devices: '+ JSON.stringify(devices));
1808
1828
  const groupables = [];
1809
1829
  for (const d of devices) {
1810
- if (d && d.info && d.info.endpoints) {
1811
- for (const ep of d.info.endpoints) {
1812
- if (ep.inputClusters.includes(4))
1813
- {
1814
- groupables.push(ep);
1815
- }
1816
- }
1817
- }
1818
- //console.log('device ' + JSON.stringify(d));
1830
+ if (d && d.info && d.info.endpoints) {
1831
+ for (const ep of d.info.endpoints) {
1832
+ if (ep.inputClusters.includes(4)) {
1833
+ groupables.push(ep);
1834
+ }
1835
+ }
1836
+ }
1837
+ //console.log('device ' + JSON.stringify(d));
1819
1838
  }
1820
1839
 
1821
1840
  //var text = 'Enter new name for "'+name+'" ('+id+')?';
1822
1841
  if (isnew) {
1823
- $('#groupedit').find('.editgroup').addClass('hide');
1824
- $('#groupedit').find('.addgroup').removeClass('hide');
1825
- $('#groupedit').find('.input-field.members').addClass('hide');
1826
- $('#groupedit').find('.input-field.groupid').removeClass('hide');
1827
- }
1828
- else {
1829
- $('#groupedit').find('.editgroup').removeClass('hide');
1830
- $('#groupedit').find('.addgroup').addClass('hide');
1831
- $('#groupedit').find('.input-field.members').removeClass('hide');
1832
- $('#groupedit').find('.input-field.groupid').addClass('hide');
1842
+ $('#groupedit').find('.editgroup').addClass('hide');
1843
+ $('#groupedit').find('.addgroup').removeClass('hide');
1844
+ $('#groupedit').find('.input-field.members').addClass('hide');
1845
+ $('#groupedit').find('.input-field.groupid').removeClass('hide');
1846
+ } else {
1847
+ $('#groupedit').find('.editgroup').removeClass('hide');
1848
+ $('#groupedit').find('.addgroup').addClass('hide');
1849
+ $('#groupedit').find('.input-field.members').removeClass('hide');
1850
+ $('#groupedit').find('.input-field.groupid').addClass('hide');
1833
1851
  }
1834
- $('#groupedit').find("input[id='g_index']").val(id);
1835
- $('#groupedit').find("input[id='g_name']").val(name);
1836
- $("#groupedit a.btn[name='save']").unbind('click');
1837
- $("#groupedit a.btn[name='save']").click(() => {
1838
- const newId = $('#groupedit').find("input[id='g_index']").val(),
1839
- newName = $('#groupedit').find("input[id='g_name']").val();
1852
+ $('#groupedit').find('input[id=\'g_index\']').val(id);
1853
+ $('#groupedit').find('input[id=\'g_name\']').val(name);
1854
+ $('#groupedit a.btn[name=\'save\']').unbind('click');
1855
+ $('#groupedit a.btn[name=\'save\']').click(() => {
1856
+ const newId = $('#groupedit').find('input[id=\'g_index\']').val(),
1857
+ newName = $('#groupedit').find('input[id=\'g_name\']').val();
1840
1858
  updateGroup(id, newId, newName);
1841
- // showGroups();
1842
- // getDevices();
1859
+ // showGroups();
1860
+ // getDevices();
1843
1861
  });
1844
1862
  $('#groupedit').modal('open');
1845
1863
  Materialize.updateTextFields();
1846
1864
  }
1847
1865
 
1848
1866
  function deleteGroupConfirmation(id, name) {
1849
- const text = translateWord('Do you really whant to delete group') + ' "'+name+'" ('+id+')?';
1867
+ const text = translateWord('Do you really whant to delete group') + ' "' + name + '" (' + id + ')?';
1850
1868
  $('#modaldelete').find('p').text(text);
1851
1869
  $('#forcediv').addClass('hide');
1852
- $("#modaldelete a.btn[name='yes']").unbind('click');
1853
- $("#modaldelete a.btn[name='yes']").click(() => {
1870
+ $('#modaldelete a.btn[name=\'yes\']').unbind('click');
1871
+ $('#modaldelete a.btn[name=\'yes\']').click(() => {
1854
1872
  deleteGroup(id);
1855
- // showGroups();
1856
- // getDevices();
1873
+ // showGroups();
1874
+ // getDevices();
1857
1875
  });
1858
1876
  $('#modaldelete').modal('open');
1859
1877
  }
@@ -1861,8 +1879,8 @@ function deleteGroupConfirmation(id, name) {
1861
1879
  function updateGroup(id, newId, newName) {
1862
1880
  delete groups[id];
1863
1881
  groups[newId] = newName;
1864
- sendTo(namespace, 'renameGroup', { id: newId, name: newName}, function(msg) {
1865
- if (msg && ms.error) {
1882
+ sendTo(namespace, 'renameGroup', {id: newId, name: newName}, function (msg) {
1883
+ if (msg && msg.error) {
1866
1884
  showMessage(msg.error, _('Error'));
1867
1885
  }
1868
1886
  getDevices();
@@ -1871,8 +1889,8 @@ function updateGroup(id, newId, newName) {
1871
1889
 
1872
1890
  function deleteGroup(id) {
1873
1891
  delete groups[id];
1874
- sendTo(namespace, 'deleteGroup', id , function(msg) {
1875
- if (msg && ms.error) {
1892
+ sendTo(namespace, 'deleteGroup', id, function (msg) {
1893
+ if (msg && msg.error) {
1876
1894
  showMessage(msg.error, _('Error'));
1877
1895
  }
1878
1896
  getDevices();
@@ -1880,58 +1898,55 @@ function deleteGroup(id) {
1880
1898
  }
1881
1899
 
1882
1900
  function updateDev(id, newName, newGroups) {
1883
- const dev = devices.find((d) => d._id == id);
1884
- if (dev && dev.common.name != newName) {
1901
+ const dev = devices.find((d) => d._id === id);
1902
+ if (dev && dev.common.name !== newName) {
1885
1903
  renameDevice(id, newName);
1886
1904
  }
1887
- const keys = Object.keys(newGroups)
1888
- if (keys && keys.length)
1889
- {
1890
- sendTo(namespace, 'updateGroupMembership', { id: id, groups: newGroups }, function (msg) {
1891
- closeWaitingDialog();
1892
- if (msg && msg.error) {
1893
- showMessage(msg.error, _('Error'));
1894
- }
1895
- else {
1896
- // save dev-groups on success
1897
- dev.groups = newGroups;
1898
- }
1899
- showDevices();
1900
- });
1901
- showWaitingDialog('Updating group memberships', 10);
1905
+ const keys = Object.keys(newGroups);
1906
+ if (keys && keys.length) {
1907
+ sendTo(namespace, 'updateGroupMembership', {id: id, groups: newGroups}, function (msg) {
1908
+ closeWaitingDialog();
1909
+ if (msg && msg.error) {
1910
+ showMessage(msg.error, _('Error'));
1911
+ } else {
1912
+ // save dev-groups on success
1913
+ dev.groups = newGroups;
1914
+ }
1915
+ showDevices();
1916
+ });
1917
+ showWaitingDialog('Updating group memberships', 10);
1902
1918
 
1903
1919
  }
1904
- /*
1905
- if (dev.info.device._type == 'Router') {
1906
- const oldGroups = devGroups[id] || [];
1907
- if (oldGroups.toString() != newGroups.toString()) {
1908
- devGroups[id] = newGroups;
1909
- sendTo(namespace, 'updateGroupMembership', { id: id, groups: newGroups }, function (msg) {
1910
- if (msg && msg.error) {
1911
- showMessage(msg.error, _('Error'));
1912
- }
1913
- else {
1914
- // save dev-groups on success
1915
- dev.groups = newGroups;
1916
- }
1917
- showDevices();
1918
- });
1920
+ /*
1921
+ if (dev.info.device._type == 'Router') {
1922
+ const oldGroups = devGroups[id] || [];
1923
+ if (oldGroups.toString() != newGroups.toString()) {
1924
+ devGroups[id] = newGroups;
1925
+ sendTo(namespace, 'updateGroupMembership', { id: id, groups: newGroups }, function (msg) {
1926
+ if (msg && msg.error) {
1927
+ showMessage(msg.error, _('Error'));
1928
+ }
1929
+ else {
1930
+ // save dev-groups on success
1931
+ dev.groups = newGroups;
1932
+ }
1933
+ showDevices();
1934
+ });
1935
+ }
1919
1936
  }
1920
- }
1921
- */
1937
+ */
1922
1938
  }
1923
1939
 
1924
1940
  function resetConfirmation() {
1925
1941
  $('#modalreset').modal('open');
1926
1942
  const btn = $('#modalreset .modal-content a.btn');
1927
1943
  btn.unbind('click');
1928
- btn.click(function(e) {
1944
+ btn.click(function (e) {
1929
1945
  sendTo(namespace, 'reset', {mode: e.target.id}, function (err) {
1930
1946
  if (err) {
1931
1947
  console.log(err);
1932
- }
1933
- else {
1934
- console.log('Reseted');
1948
+ } else {
1949
+ console.log('Reset done');
1935
1950
  }
1936
1951
  });
1937
1952
  });
@@ -1941,7 +1956,7 @@ function showViewConfig() {
1941
1956
  $('#modalviewconfig').modal('open');
1942
1957
  }
1943
1958
 
1944
- function prepareBindingDialog(bindObj){
1959
+ function prepareBindingDialog(bindObj) {
1945
1960
  const binddevices = devices.slice();
1946
1961
  binddevices.unshift('');
1947
1962
  const bind_source = (bindObj) ? [bindObj.bind_source] : [''];
@@ -1952,12 +1967,12 @@ function prepareBindingDialog(bindObj){
1952
1967
  const allowClustersName = {5: 'genScenes', 6: 'genOnOff', 8: 'genLevelCtrl', 768: 'lightingColorCtrl'};
1953
1968
  // fill device selector
1954
1969
  list2select('#bind_source', binddevices, bind_source,
1955
- function(key, device) {
1970
+ function (key, device) {
1956
1971
  if (device == '') {
1957
1972
  return 'Select source device';
1958
1973
  }
1959
1974
  if (device.hasOwnProperty('info')) {
1960
- if (device.info.device._type == 'Coordinator') {
1975
+ if (device.info.device._type === 'Coordinator') {
1961
1976
  return null;
1962
1977
  }
1963
1978
  // check for output clusters
@@ -1977,19 +1992,18 @@ function prepareBindingDialog(bindObj){
1977
1992
  return null;
1978
1993
  }
1979
1994
  return device.common.name;
1980
- }
1981
- else { // fallback if device in list but not paired
1982
- device.common.name + ' ' +device.native.id;
1995
+ } else { // fallback if device in list but not paired
1996
+ return device.common.name + ' ' + device.native.id;
1983
1997
  }
1984
1998
  },
1985
- function(key, device) {
1999
+ function (key, device) {
1986
2000
  if (device == '') {
1987
2001
  return '';
1988
2002
  } else {
1989
2003
  return device._id;
1990
2004
  }
1991
2005
  },
1992
- function(key, device) {
2006
+ function (key, device) {
1993
2007
  if (device == '') {
1994
2008
  return 'disabled';
1995
2009
  } else if (device.icon) {
@@ -2004,12 +2018,12 @@ function prepareBindingDialog(bindObj){
2004
2018
  bindtargets.push({'_id': key, 'groupId': key, 'groupName': groups[key]});
2005
2019
  }
2006
2020
  list2select('#bind_target', bindtargets, bind_target,
2007
- function(key, device) {
2021
+ function (key, device) {
2008
2022
  if (device == '') {
2009
2023
  return 'Select target device';
2010
2024
  }
2011
2025
  if (device.hasOwnProperty('info')) {
2012
- if (device.info.device._type == 'Coordinator') {
2026
+ if (device.info.device._type === 'Coordinator') {
2013
2027
  return null;
2014
2028
  }
2015
2029
  // check for input clusters
@@ -2035,14 +2049,14 @@ function prepareBindingDialog(bindObj){
2035
2049
  }
2036
2050
  }
2037
2051
  },
2038
- function(key, device) {
2052
+ function (key, device) {
2039
2053
  if (device == '') {
2040
2054
  return '';
2041
2055
  } else {
2042
2056
  return device._id;
2043
2057
  }
2044
2058
  },
2045
- function(key, device) {
2059
+ function (key, device) {
2046
2060
  if (device == '') {
2047
2061
  return 'disabled';
2048
2062
  } else if (device.icon) {
@@ -2061,13 +2075,17 @@ function prepareBindingDialog(bindObj){
2061
2075
  const epList = device ? device.info.endpoints : [];
2062
2076
  const sClusterList = epList.map((ep) => {
2063
2077
  const clusters = ep.outputClusters.map((cl) => {
2064
- return allowClusters.includes(cl) ? {ID: ep.ID+'_'+cl, name: allowClustersName[cl]} : null;
2065
- }).filter((i) => {return i != null;});
2066
- return clusters.length == 0 ? null: [{ID: ep.ID, name: 'all'}, clusters];
2067
- }).flat(2).filter((i) => {return i != null;});
2078
+ return allowClusters.includes(cl) ? {ID: ep.ID + '_' + cl, name: allowClustersName[cl]} : null;
2079
+ }).filter((i) => {
2080
+ return i != null;
2081
+ });
2082
+ return clusters.length == 0 ? null : [{ID: ep.ID, name: 'all'}, clusters];
2083
+ }).flat(2).filter((i) => {
2084
+ return i != null;
2085
+ });
2068
2086
  list2select('#bind_source_ep', sClusterList, (selected) ? [selected] : [],
2069
2087
  (key, ep) => {
2070
- return ep.ID+' '+ep.name;
2088
+ return ep.ID + ' ' + ep.name;
2071
2089
  },
2072
2090
  (key, ep) => {
2073
2091
  return ep.ID;
@@ -2083,13 +2101,20 @@ function prepareBindingDialog(bindObj){
2083
2101
  const epList = device ? device.info.endpoints : [];
2084
2102
  const tClusterList = epList.map((ep) => {
2085
2103
  const clusters = ep.inputClusters.map((cl) => {
2086
- return (allowClusters.includes(cl) && (!sourceCl || sourceCl == cl)) ? {ID: ep.ID+'_'+cl, name: allowClustersName[cl]} : null;
2087
- }).filter((i) => {return i != null;});
2088
- return clusters.length == 0 ? null: [{ID: ep.ID, name: 'all'}, clusters];
2089
- }).flat(2).filter((i) => {return i != null;});
2104
+ return (allowClusters.includes(cl) && (!sourceCl || sourceCl == cl)) ? {
2105
+ ID: ep.ID + '_' + cl,
2106
+ name: allowClustersName[cl]
2107
+ } : null;
2108
+ }).filter((i) => {
2109
+ return i != null;
2110
+ });
2111
+ return !clusters.length ? null : [{ID: ep.ID, name: 'all'}, clusters];
2112
+ }).flat(2).filter(i => {
2113
+ return i != null;
2114
+ });
2090
2115
  list2select('#bind_target_ep', tClusterList, (selected) ? [selected] : [],
2091
2116
  (key, ep) => {
2092
- return ep.ID+' '+ep.name;
2117
+ return ep.ID + ' ' + ep.name;
2093
2118
  },
2094
2119
  (key, ep) => {
2095
2120
  return ep.ID;
@@ -2097,7 +2122,7 @@ function prepareBindingDialog(bindObj){
2097
2122
  );
2098
2123
  };
2099
2124
 
2100
- $('#bind_source').change(function() {
2125
+ $('#bind_source').change(function () {
2101
2126
  if (this.selectedIndex <= 0) {
2102
2127
  return;
2103
2128
  }
@@ -2109,7 +2134,7 @@ function prepareBindingDialog(bindObj){
2109
2134
  configureSourceEp();
2110
2135
  }
2111
2136
 
2112
- $('#bind_target').change(function() {
2137
+ $('#bind_target').change(function () {
2113
2138
  if (this.selectedIndex <= 0) {
2114
2139
  return;
2115
2140
  }
@@ -2122,7 +2147,7 @@ function prepareBindingDialog(bindObj){
2122
2147
  configureTargetEp();
2123
2148
  }
2124
2149
 
2125
- $('#bind_source_ep').change(function() {
2150
+ $('#bind_source_ep').change(function () {
2126
2151
  $('#bind_target').trigger('change');
2127
2152
  });
2128
2153
 
@@ -2131,8 +2156,8 @@ function prepareBindingDialog(bindObj){
2131
2156
  }
2132
2157
 
2133
2158
  function addBindingDialog() {
2134
- $("#bindingmodaledit a.btn[name='save']").unbind('click');
2135
- $("#bindingmodaledit a.btn[name='save']").click(() => {
2159
+ $('#bindingmodaledit a.btn[name=\'save\']').unbind('click');
2160
+ $('#bindingmodaledit a.btn[name=\'save\']').click(() => {
2136
2161
  const //bind_id = $('#bindingmodaledit').find("input[id='bind_id']").val(),
2137
2162
  bind_source = $('#bindingmodaledit').find('#bind_source option:selected').val(),
2138
2163
  bind_source_ep = $('#bindingmodaledit').find('#bind_source_ep option:selected').val(),
@@ -2156,10 +2181,8 @@ function addBinding(bind_source, bind_source_ep, bind_target, bind_target_ep, un
2156
2181
  unbind_from_coordinator
2157
2182
  }, function (msg) {
2158
2183
  closeWaitingDialog();
2159
- if (msg) {
2160
- if (msg.error) {
2161
- showMessage(msg.error, _('Error'));
2162
- }
2184
+ if (msg && msg.error) {
2185
+ showMessage(msg.error, _('Error'));
2163
2186
  }
2164
2187
  getBinding();
2165
2188
  });
@@ -2176,10 +2199,8 @@ function editBinding(bind_id, bind_source, bind_source_ep, bind_target, bind_tar
2176
2199
  unbind_from_coordinator
2177
2200
  }, function (msg) {
2178
2201
  closeWaitingDialog();
2179
- if (msg) {
2180
- if (msg.error) {
2181
- showMessage(msg.error, _('Error'));
2182
- }
2202
+ if (msg && msg.error) {
2203
+ showMessage(msg.error, _('Error'));
2183
2204
  }
2184
2205
  getBinding();
2185
2206
  });
@@ -2187,8 +2208,8 @@ function editBinding(bind_id, bind_source, bind_source_ep, bind_target, bind_tar
2187
2208
  }
2188
2209
 
2189
2210
  function editBindingDialog(bindObj) {
2190
- $("#bindingmodaledit a.btn[name='save']").unbind('click');
2191
- $("#bindingmodaledit a.btn[name='save']").click(() => {
2211
+ $('#bindingmodaledit a.btn[name=\'save\']').unbind('click');
2212
+ $('#bindingmodaledit a.btn[name=\'save\']').click(() => {
2192
2213
  const //bind_id = $('#bindingmodaledit').find("input[id='bind_id']").val(),
2193
2214
  bind_source = $('#bindingmodaledit').find('#bind_source option:selected').val(),
2194
2215
  bind_source_ep = $('#bindingmodaledit').find('#bind_source_ep option:selected').val(),
@@ -2224,9 +2245,9 @@ function showBinding() {
2224
2245
  <i class="right">${target_icon}</i>
2225
2246
  <div style="min-height:72px; font-size: 0.8em" class="truncate">
2226
2247
  <ul>
2227
- <li><span class="label">source:</span><span>0x${bind_source.replace(namespace+'.', '')}</span></li>
2248
+ <li><span class="label">source:</span><span>0x${bind_source.replace(namespace + '.', '')}</span></li>
2228
2249
  <li><span class="label">endpoint:</span><span>${bind_source_ep}</span></li>
2229
- <li><span class="label">target:</span><span>0x${bind_target.replace(namespace+'.', '')}</span></li>
2250
+ <li><span class="label">target:</span><span>0x${bind_target.replace(namespace + '.', '')}</span></li>
2230
2251
  <li><span class="label">endpoint:</span><span>${bind_target_ep}</span></li>
2231
2252
  </ul>
2232
2253
  </div>
@@ -2248,11 +2269,11 @@ function showBinding() {
2248
2269
  element.append(card);
2249
2270
  });
2250
2271
 
2251
- $("#binding button[name='delete']").click(function() {
2272
+ $('#binding button[name=\'delete\']').click(function () {
2252
2273
  const bind_id = $(this).parents('.binding')[0].id;
2253
2274
  deleteBindingConfirmation(bind_id);
2254
2275
  });
2255
- $("#binding button[name='edit']").click(function() {
2276
+ $('#binding button[name=\'edit\']').click(function () {
2256
2277
  const bind_id = $(this).parents('.binding')[0].id;
2257
2278
  const bindObj = binding.find((b) => b.id == bind_id);
2258
2279
  if (bindObj) {
@@ -2279,8 +2300,8 @@ function deleteBindingConfirmation(id) {
2279
2300
  $('#modaldelete').find('p').text(text);
2280
2301
  //$('#forcediv').removeClass('hide');
2281
2302
  $('#forcediv').addClass('hide');
2282
- $("#modaldelete a.btn[name='yes']").unbind('click');
2283
- $("#modaldelete a.btn[name='yes']").click(() => {
2303
+ $('#modaldelete a.btn[name=\'yes\']').unbind('click');
2304
+ $('#modaldelete a.btn[name=\'yes\']').click(() => {
2284
2305
  deleteBinding(id);
2285
2306
  });
2286
2307
  $('#modaldelete').modal('open');
@@ -2313,14 +2334,14 @@ function genDevInfo(device) {
2313
2334
  const dev = (device && device.info) ? device.info.device : undefined;
2314
2335
  const mapped = (device && device.info) ? device.info.mapped : undefined;
2315
2336
  if (!dev) return `<div class="truncate">No info</div>`;
2316
- const genRow = function(name, value, refresh) {
2337
+ const genRow = function (name, value, refresh) {
2317
2338
  if (value === undefined) {
2318
2339
  return '';
2319
2340
  } else {
2320
2341
  return `<li><span class="label">${name}:</span><span>${value}</span></li>`;
2321
2342
  }
2322
2343
  };
2323
- const genRowValues = function(name, value) {
2344
+ const genRowValues = function (name, value) {
2324
2345
  if (value === undefined) {
2325
2346
  return '';
2326
2347
  } else {
@@ -2355,7 +2376,7 @@ function genDevInfo(device) {
2355
2376
  </div>`;
2356
2377
  }
2357
2378
  const imgSrc = device.icon || device.common.icon;
2358
- const imgInfo = (imgSrc) ? `<img src=${imgSrc} width='150px' onerror="this.onerror=null;this.src='img/unavailable.png';"><div class="divider"></div>`: '';
2379
+ const imgInfo = (imgSrc) ? `<img src=${imgSrc} width='150px' onerror="this.onerror=null;this.src='img/unavailable.png';"><div class="divider"></div>` : '';
2359
2380
  const info =
2360
2381
  `<div class="col s12 m6 l6 xl6">
2361
2382
  ${imgInfo}
@@ -2387,29 +2408,28 @@ function genDevInfo(device) {
2387
2408
  return info;
2388
2409
  }
2389
2410
 
2390
- function showDevInfo(id){
2411
+ function showDevInfo(id) {
2391
2412
  const info = genDevInfo(getDeviceByID(id));
2392
2413
  $('#devinfo').html(info);
2393
2414
  $('#modaldevinfo').modal('open');
2394
2415
  }
2395
2416
 
2396
- function showGroupList(show){
2417
+ function showGroupList(show) {
2397
2418
  const htmlsections = [];
2398
2419
  for (const groupid in devGroups) {
2399
- const dev = devGroups[groupid];
2400
- const grpname = (dev.common && dev.common.name?dev.common.name:'Group '+groupid);
2401
- const selectables = [];
2402
- const members = [];
2403
- if (dev && dev.memberinfo)
2404
- {
2405
- selectables.push(`<select id="members_${groupid}" multiple>`)
2406
- for (let m=0;m<dev.memberinfo.length; m++) {
2407
- members.push(`${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (${dev.memberinfo[m].ieee})`)
2408
- selectables.push(`<option value="${m}">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (...${dev.memberinfo[m].ieee.slice(-4)})</option>`);
2409
- }
2410
- selectables.push('</select>');
2411
- }
2412
- htmlsections.push(`
2420
+ const dev = devGroups[groupid];
2421
+ const grpname = (dev.common && dev.common.name ? dev.common.name : 'Group ' + groupid);
2422
+ const selectables = [];
2423
+ const members = [];
2424
+ if (dev && dev.memberinfo) {
2425
+ selectables.push(`<select id="members_${groupid}" multiple>`);
2426
+ for (let m = 0; m < dev.memberinfo.length; m++) {
2427
+ members.push(`${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (${dev.memberinfo[m].ieee})`);
2428
+ selectables.push(`<option value="${m}">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (...${dev.memberinfo[m].ieee.slice(-4)})</option>`);
2429
+ }
2430
+ selectables.push('</select>');
2431
+ }
2432
+ htmlsections.push(`
2413
2433
  <div class="row">
2414
2434
  <div class="col s4 m4 l4">
2415
2435
  <h5>${grpname}<h5>
@@ -2422,38 +2442,38 @@ function showGroupList(show){
2422
2442
  }
2423
2443
 
2424
2444
  $('#grouplist').html(htmlsections.join(''));
2425
- $('#add').click(function() {
2426
- const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
2427
- editGroupName(maxind+1, 'Group ' + maxind+1, true);
2445
+ $('#add').click(function () {
2446
+ const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
2447
+ editGroupName(maxind + 1, 'Group ' + maxind + 1, true);
2428
2448
  showGroupList(false);
2429
2449
  });
2430
2450
 
2431
- $("#modalgrouplist a.btn[name='save']").unbind('click');
2432
- $("#modalgrouplist a.btn[name='save']").click(() => {
2451
+ $('#modalgrouplist a.btn[name=\'save\']').unbind('click');
2452
+ $('#modalgrouplist a.btn[name=\'save\']').click(() => {
2433
2453
  });
2434
2454
  if (show) $('#modalgrouplist').modal('open');
2435
2455
  }
2436
2456
 
2437
2457
  let waitingTimeout, waitingInt;
2438
2458
 
2439
- function showWaitingDialog(text, timeout){
2459
+ function showWaitingDialog(text, timeout) {
2440
2460
  let countDown = timeout;
2441
- waitingInt = setInterval(function() {
2461
+ waitingInt = setInterval(function () {
2442
2462
  countDown -= 1;
2443
- const percent = 100-100*countDown/timeout;
2463
+ const percent = 100 - 100 * countDown / timeout;
2444
2464
  $('#waiting_progress_line').css('width', `${percent}%`);
2445
2465
  }, 1000);
2446
- waitingTimeout = setTimeout(function() {
2466
+ waitingTimeout = setTimeout(function () {
2447
2467
  $('#waiting_progress_line').css('width', `0%`);
2448
2468
  clearTimeout(waitingInt);
2449
2469
  clearTimeout(waitingTimeout);
2450
2470
  $('#modalWaiting').modal('close');
2451
- }, timeout*1000);
2471
+ }, timeout * 1000);
2452
2472
  $('#waiting_message').text(text);
2453
2473
  $('#modalWaiting').modal('open');
2454
2474
  }
2455
2475
 
2456
- function closeWaitingDialog(){
2476
+ function closeWaitingDialog() {
2457
2477
  if (waitingInt) clearTimeout(waitingInt);
2458
2478
  if (waitingTimeout) clearTimeout(waitingTimeout);
2459
2479
  $('#modalWaiting').modal('close');
@@ -2470,7 +2490,7 @@ function showChannels() {
2470
2490
  $('#modalchannels').modal('open');
2471
2491
  let info = '';
2472
2492
  for (let ch = 11; ch < 27; ch++) {
2473
- const value = msg.energyvalues[ch-11];
2493
+ const value = msg.energyvalues[ch - 11];
2474
2494
  info +=
2475
2495
  `<div style="padding-top: 10px">
2476
2496
  <span class="">№ ${ch}: ${value}%</span>
@@ -2528,7 +2548,7 @@ function prepareExcludeDialog(excludeObj) {
2528
2548
 
2529
2549
  list2select('#exclude_target', onlyOneTargets, exclude_target,
2530
2550
 
2531
- function(key, device) {
2551
+ function (key, device) {
2532
2552
  if (device == '') {
2533
2553
  return 'Select model';
2534
2554
  }
@@ -2541,14 +2561,14 @@ function prepareExcludeDialog(excludeObj) {
2541
2561
  return device.common.type;
2542
2562
  }
2543
2563
  },
2544
- function(key, device) {
2564
+ function (key, device) {
2545
2565
  if (device == '') {
2546
2566
  return '';
2547
2567
  } else {
2548
2568
  return device._id;
2549
2569
  }
2550
2570
  },
2551
- function(key, device) {
2571
+ function (key, device) {
2552
2572
  if (device == '') {
2553
2573
  return 'disabled';
2554
2574
  } else if (device.icon) {
@@ -2562,8 +2582,8 @@ function prepareExcludeDialog(excludeObj) {
2562
2582
  }
2563
2583
 
2564
2584
  function addExcludeDialog() {
2565
- $("#excludemodaledit a.btn[name='save']").unbind('click');
2566
- $("#excludemodaledit a.btn[name='save']").click(() => {
2585
+ $('#excludemodaledit a.btn[name=\'save\']').unbind('click');
2586
+ $('#excludemodaledit a.btn[name=\'save\']').click(() => {
2567
2587
  const exclude_id = $('#excludemodaledit').find('#exclude_target option:selected').val();
2568
2588
 
2569
2589
  const ids = devices.map(el => el._id);
@@ -2646,7 +2666,7 @@ function showExclude() {
2646
2666
  element.append(card);
2647
2667
  });
2648
2668
 
2649
- $("#exclude button[name='delete']").click(function() {
2669
+ $('#exclude button[name=\'delete\']').click(function () {
2650
2670
  const exclude_id = $(this).parents('.exclude')[0].id;
2651
2671
  deleteExcludeConfirmation(exclude_id);
2652
2672
  deleteExclude(exclude_id);
@@ -2654,14 +2674,13 @@ function showExclude() {
2654
2674
  }
2655
2675
 
2656
2676
 
2657
-
2658
2677
  function deleteExcludeConfirmation(id) {
2659
2678
  const text = translateWord('Do you really want to delete exclude?');
2660
2679
  $('#modaldelete').find('p').text(text);
2661
2680
  //$('#forcediv').removeClass('hide');
2662
2681
  $('#forcediv').addClass('hide');
2663
- $("#modaldelete a.btn[name='yes']").unbind('click');
2664
- $("#modaldelete a.btn[name='yes']").click(() => {
2682
+ $('#modaldelete a.btn[name=\'yes\']').unbind('click');
2683
+ $('#modaldelete a.btn[name=\'yes\']').click(() => {
2665
2684
  deleteExclude(id);
2666
2685
  });
2667
2686
  $('#modaldelete').modal('open');
@@ -2702,7 +2721,7 @@ function doFilter(inputText) {
2702
2721
  } else {
2703
2722
  return room;
2704
2723
  }
2705
- }).filter((item)=>item != undefined).map((item)=>item.toLowerCase().trim());
2724
+ }).filter((item) => item != undefined).map((item) => item.toLowerCase().trim());
2706
2725
  valid = rooms.includes(roomFilter);
2707
2726
  } else {
2708
2727
  valid = false;
@@ -2719,9 +2738,9 @@ function doFilter(inputText) {
2719
2738
  function doSort() {
2720
2739
  if (shuffleInstance) {
2721
2740
  const sortOrder = $('#device-order-btn').text().toLowerCase();
2722
- if (sortOrder == 'default') {
2741
+ if (sortOrder === 'default') {
2723
2742
  shuffleInstance.sort({});
2724
- } else if (sortOrder == 'a-z') {
2743
+ } else if (sortOrder === 'a-z') {
2725
2744
  shuffleInstance.sort({
2726
2745
  by: sortByTitle
2727
2746
  });
@@ -2735,12 +2754,12 @@ function sortByTitle(element) {
2735
2754
 
2736
2755
  function getDashCard(dev, groupImage) {
2737
2756
  const title = dev.common.name,
2738
- id = dev._id,
2739
- type = dev.common.type,
2740
- img_src = (groupImage ? groupImage: dev.icon || dev.common.icon),
2741
- isActive = (dev.common.deactivated ? false : true),
2742
- rooms = [],
2743
- lang = systemLang || 'en';
2757
+ id = dev._id,
2758
+ type = dev.common.type,
2759
+ img_src = (groupImage ? groupImage : dev.icon || dev.common.icon),
2760
+ isActive = !dev.common.deactivated,
2761
+ rooms = [],
2762
+ lang = systemLang || 'en';
2744
2763
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
2745
2764
  const rid = id.split('.').join('_');
2746
2765
  const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
@@ -2749,38 +2768,46 @@ function getDashCard(dev, groupImage) {
2749
2768
  battery_cls = getBatteryCls(dev.battery),
2750
2769
  lqi_cls = getLQICls(dev.link_quality),
2751
2770
  battery = (dev.battery && isActive) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
2752
- lq = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : (isActive ? '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>':''),
2753
- status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (groupImage || !isActive ? '': `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`),
2754
- permitJoinBtn = (isActive && dev.info && dev.info.device._type == 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
2771
+ lq = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : (isActive ? '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>' : ''),
2772
+ status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (groupImage || !isActive ? '' : `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`),
2773
+ permitJoinBtn = (isActive && dev.info && dev.info.device._type === 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
2755
2774
  infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
2756
2775
  idleTime = (dev.link_quality_lc > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_lc_icon" class="material-icons idletime">access_time</i><div id="${rid}_link_quality_lc" class="center" style="font-size:0.7em">${getIdleTime(dev.link_quality_lc)}</div></div>` : '';
2757
- const info = (dev.statesDef) ? dev.statesDef.map((stateDef)=>{
2776
+ const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => {
2758
2777
  const id = stateDef.id;
2759
2778
  const sid = id.split('.').join('_');
2760
2779
  let val = stateDef.val || '';
2761
- if (stateDef.role == 'switch' && stateDef.write) {
2762
- val = `<span class="switch"><label><input type="checkbox" ${(val) ? "checked" : ""}><span class="lever"></span></label></span>`;
2763
- } else if (stateDef.role == 'level.dimmer' && stateDef.write) {
2764
- val = `<span class="range-field dash"><input type="range" min="0" max="100" ${(val != undefined) ? `value="${val}"` : ""} /></span>`;
2765
- } else if (stateDef.role == 'level.color.temperature' && stateDef.write) {
2766
- val = `<span class="range-field dash"><input type="range" min="150" max="500" ${(val != undefined) ? `value="${val}"` : ""} /></span>`;
2767
- } else if (stateDef.type == 'boolean') {
2780
+ if (stateDef.role === 'switch' && stateDef.write) {
2781
+ val = `<span class="switch"><label><input type="checkbox" ${(val) ? 'checked' : ''}><span class="lever"></span></label></span>`;
2782
+ } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
2783
+ val = `<span class="range-field dash"><input type="range" min="0" max="100" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
2784
+ } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
2785
+ val = `<span class="range-field dash"><input type="range" min="150" max="500" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
2786
+ } else if (stateDef.type === 'boolean') {
2768
2787
  const disabled = (stateDef.write) ? '' : 'disabled="disabled"';
2769
- val = `<label class="dash"><input type="checkbox" ${(val == true) ? "checked='checked'" : ""} ${disabled}/><span></span></label>`;
2788
+ val = `<label class="dash"><input type="checkbox" ${(val == true) ? 'checked=\'checked\'' : ''} ${disabled}/><span></span></label>`;
2770
2789
  } else if (stateDef.states && stateDef.write) {
2771
- const sts = stateDef.states.split(';');
2772
- const options = sts.map((item) => {
2773
- const v = item.split(':');
2774
- return `<option value="${v[0]}" ${(val == v[0]) ? "selected" : ""}>${v[1]}</option>`;
2775
- });
2776
- val = `<select class="browser-default enum" style="height: 16px; padding: 0px; width: auto; display: inline-block">${options.join('')}</select>`;
2790
+ let options;
2791
+ if (typeof stateDef.states == 'string') {
2792
+ const sts = stateDef.states.split(';');
2793
+ options = sts.map((item) => {
2794
+ const v = item.split(':');
2795
+ return `<option value="${v[0]}" ${(val == v[0]) ? 'selected' : ''}>${v[1]}</option>`;
2796
+ });
2797
+ } else {
2798
+ options = [];
2799
+ for (const [key, value] of Object.entries(stateDef.states)) {
2800
+ options.push(`<option value="${key}" ${(val == key) ? 'selected' : ''}>${value}</option>`);
2801
+ }
2802
+ }
2803
+ val = `<select class="browser-default enum" style="height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
2777
2804
  } else {
2778
2805
  val = `<span class="dash value">${val} ${(stateDef.unit) ? stateDef.unit : ''}</span>`;
2779
2806
  }
2780
2807
  return `<li><span class="label dash truncate">${stateDef.name}</span><span id=${sid} oid=${id} class="state">${val}</span></li>`;
2781
2808
  }).join('') : '';
2782
2809
  const dashCard = `
2783
- <div class="card-content zcard ${isActive?'':'bg_red'}">
2810
+ <div class="card-content zcard ${isActive ? '' : 'bg_red'}">
2784
2811
  <div class="flip" style="cursor: pointer">
2785
2812
  <span class="top right small" style="border-radius: 50%">
2786
2813
  ${idleTime}
@@ -2793,7 +2820,7 @@ function getDashCard(dev, groupImage) {
2793
2820
  <i class="left">${image}</i>
2794
2821
  <div style="min-height:88px; font-size: 0.8em; height: 130px; overflow-y: auto" class="truncate">
2795
2822
  <ul>
2796
- ${(isActive?info:'Device deactivated')}
2823
+ ${(isActive ? info : 'Device deactivated')}
2797
2824
  </ul>
2798
2825
  </div>
2799
2826
  <div class="footer right-align"></div>
@@ -2806,19 +2833,19 @@ function setDashStates(id, state) {
2806
2833
  const devId = getDevId(id);
2807
2834
  const dev = getDeviceByID(devId);
2808
2835
  if (dev) {
2809
- const stateDef = dev.statesDef.find((stateDef)=> stateDef.id == id);
2836
+ const stateDef = dev.statesDef.find((stateDef) => stateDef.id == id);
2810
2837
  if (stateDef) {
2811
2838
  const sid = id.split('.').join('_');
2812
- if (stateDef.role == 'switch' && stateDef.write) {
2813
- $(`#${sid}`).find("input[type='checkbox']").prop('checked', state.val);
2814
- } else if (stateDef.role == 'level.dimmer' && stateDef.write) {
2815
- $(`#${sid}`).find("input[type='range']").prop('value', state.val);
2816
- } else if (stateDef.role == 'level.color.temperature' && stateDef.write) {
2817
- $(`#${sid}`).find("input[type='range']").prop('value', state.val);
2839
+ if (stateDef.role === 'switch' && stateDef.write) {
2840
+ $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
2841
+ } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
2842
+ $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
2843
+ } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
2844
+ $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
2818
2845
  } else if (stateDef.states && stateDef.write) {
2819
2846
  $(`#${sid}`).find(`select option[value=${state.val}]`).prop('selected', true);
2820
- } else if (stateDef.type == 'boolean') {
2821
- $(`#${sid}`).find("input[type='checkbox']").prop('checked', state.val);
2847
+ } else if (stateDef.type === 'boolean') {
2848
+ $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
2822
2849
  } else {
2823
2850
  $(`#${sid}`).find('.value').text(`${state.val} ${(stateDef.unit) ? stateDef.unit : ''}`);
2824
2851
  }
@@ -2827,23 +2854,23 @@ function setDashStates(id, state) {
2827
2854
  }
2828
2855
 
2829
2856
  function hookControls() {
2830
- $("input[type='checkbox']").change(function (event) {
2857
+ $('input[type=\'checkbox\']').change(function (event) {
2831
2858
  const val = $(this).is(':checked');
2832
- const id = $(this).parents(".state").attr('oid');
2859
+ const id = $(this).parents('.state').attr('oid');
2833
2860
  sendTo(namespace, 'setState', {id: id, val: val}, function (data) {
2834
2861
  //console.log(data);
2835
2862
  });
2836
2863
  });
2837
- $("input[type='range']").change(function (event) {
2864
+ $('input[type=\'range\']').change(function (event) {
2838
2865
  const val = $(this).val();
2839
- const id = $(this).parents(".state").attr('oid');
2866
+ const id = $(this).parents('.state').attr('oid');
2840
2867
  sendTo(namespace, 'setState', {id: id, val: val}, function (data) {
2841
2868
  //console.log(data);
2842
2869
  });
2843
2870
  });
2844
- $(".state select").on( "change", function () {
2871
+ $('.state select').on('change', function () {
2845
2872
  const val = $(this).val();
2846
- const id = $(this).parents(".state").attr('oid');
2873
+ const id = $(this).parents('.state').attr('oid');
2847
2874
  sendTo(namespace, 'setState', {id: id, val: val}, function (data) {
2848
2875
  //console.log(data);
2849
2876
  });
@@ -2851,12 +2878,12 @@ function hookControls() {
2851
2878
  }
2852
2879
 
2853
2880
  function getIdleTime(value) {
2854
- return (value) ? moment(new Date(value)).fromNow(true) : "";
2881
+ return (value) ? moment(new Date(value)).fromNow(true) : '';
2855
2882
  }
2856
2883
 
2857
2884
  function updateCardTimer() {
2858
2885
  if (devices) {
2859
- devices.forEach((dev)=>{
2886
+ devices.forEach((dev) => {
2860
2887
  const id = dev._id;
2861
2888
  if (id) {
2862
2889
  const rid = id.split('.').join('_');
@@ -2873,9 +2900,7 @@ function updateDevice(id) {
2873
2900
  showMessage(devs.error, _('Error'));
2874
2901
  } else {
2875
2902
  removeDevice(id);
2876
- devs.forEach((dev)=>{
2877
- devices.push(dev);
2878
- })
2903
+ devs.forEach(dev => devices.push(dev));
2879
2904
  showDevices();
2880
2905
  }
2881
2906
  }
@@ -2893,21 +2918,21 @@ function removeDevice(id) {
2893
2918
  }
2894
2919
 
2895
2920
  function swapActive(id) {
2896
- const dev = getDeviceByID(id);
2897
- if (dev && dev.common) {
2898
- dev.common.deactivated = !(dev.common.deactivated)
2899
- sendTo(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
2900
- showDevices();
2901
- });
2902
- }
2921
+ const dev = getDeviceByID(id);
2922
+ if (dev && dev.common) {
2923
+ dev.common.deactivated = !(dev.common.deactivated);
2924
+ sendTo(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
2925
+ showDevices();
2926
+ });
2927
+ }
2903
2928
  }
2904
2929
 
2905
2930
  function reconfigureDlg(id) {
2906
2931
  const text = translateWord(`Do you really want to reconfigure device?`);
2907
2932
  $('#modalreconfigure').find('p').text(text);
2908
- $("#modalreconfigure a.btn[name='yes']").unbind('click');
2909
- $("#modalreconfigure a.btn[name='yes']").click(() => {
2910
- reconfigureDevice(id, force);
2933
+ $('#modalreconfigure a.btn[name=\'yes\']').unbind('click');
2934
+ $('#modalreconfigure a.btn[name=\'yes\']').click(() => {
2935
+ reconfigureDevice(id/*, force*/);
2911
2936
  });
2912
2937
  $('#modalreconfigure').modal('open');
2913
2938
  Materialize.updateTextFields();