iobroker.zigbee 3.0.5 → 3.1.4

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.
package/admin/admin.js CHANGED
@@ -22,11 +22,15 @@ let devices = [],
22
22
  binding = [],
23
23
  excludes = [],
24
24
  coordinatorinfo = {
25
- type: 'd2',
26
- version: 'd2',
27
- revision: 'd2',
28
- port: 'd2',
29
- channel: 'd2'
25
+ installSource: 'IADefault_1',
26
+ channel: '-1',
27
+ port: 'Default_1',
28
+ installedVersion: 'Default_1',
29
+ type: 'Default_1',
30
+ revision: 'Default_1',
31
+ version: '9-9.9.9.9',
32
+ herdsman: '4.0.0',
33
+ converters: '24.0.0',
30
34
  },
31
35
  cidList,
32
36
  shuffleInstance,
@@ -56,9 +60,9 @@ const networkOptions = {
56
60
 
57
61
 
58
62
  const savedSettings = [
59
- 'port', 'panID', 'channel', 'disableLed', 'countDown', 'groups', 'extPanID', 'precfgkey', 'transmitPower',
60
- 'adapterType', 'debugHerdsman', 'disableBackup', 'disablePing', 'external', 'startWithInconsistent',
61
- 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart'
63
+ 'port', 'panID', 'channel', 'disableLed', 'countDown', 'groups', 'extPanID', 'precfgkey', 'transmitPower','useNewCompositeStates',
64
+ 'adapterType', 'debugHerdsman', 'disableBackup', 'external', 'startWithInconsistent','pingTimeout','listDevicesAtStart',
65
+ 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart', 'readAtAnnounce', 'startReadDelay', 'readAllAtStart','pingCluster'
62
66
  ];
63
67
 
64
68
  function getDeviceByID(ID) {
@@ -74,7 +78,7 @@ function getDeviceByID(ID) {
74
78
  function getDevice(ieeeAddr) {
75
79
  return devices.find((devInfo) => {
76
80
  try {
77
- return devInfo.info.device._ieeeAddr == ieeeAddr;
81
+ return devInfo.info.device.ieee == ieeeAddr;
78
82
  } catch (e) {
79
83
  //console.log("No dev with ieee " + ieeeAddr);
80
84
  }
@@ -85,7 +89,7 @@ function getDevice(ieeeAddr) {
85
89
  function getDeviceByNetwork(nwk) {
86
90
  return devices.find((devInfo) => {
87
91
  try {
88
- return devInfo.info.device._networkAddress == nwk;
92
+ return devInfo.info.device.nwk == nwk;
89
93
  } catch (e) {
90
94
  //console.log("No dev with nwkAddr " + nwk);
91
95
  }
@@ -111,14 +115,15 @@ function getLQICls(value) {
111
115
 
112
116
  function getCoordinatorCard(dev) {
113
117
  const title = 'Zigbee Coordinator',
114
- id = dev._id,
118
+ id = dev && dev._id ? dev._id : '0x00000000',
115
119
  img_src = 'zigbee.png',
116
120
  rid = id.split('.').join('_'),
117
121
  image = `<img src="${img_src}" width="80px">`,
118
122
  paired = '',
119
- status = `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>`,
120
- lqi_cls = getLQICls(dev.link_quality),
121
- lq = (dev.link_quality) ? `<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>` : '',
123
+ status = coordinatorinfo.autostart ? (dev ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : `<div class="col tool"><i class="material-icons icon-red">remove_circle</i></div>`) : `<div class="col tool"><i class="material-icons icon-orange">pause_circle_filled</i></div>`,
124
+ //status = dev ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : `<div class="col tool"><i class="material-icons icon-red">remove_circle</i></div>`,
125
+ lqi_cls = dev ? getLQICls(dev.link_quality) : -1,
126
+ lq = (dev && dev.link_quality) ? `<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>` : '',
122
127
  info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
123
128
  <ul>
124
129
  <li><span class="label">type:</span><span>${coordinatorinfo.type}</span></li>
@@ -126,15 +131,21 @@ function getCoordinatorCard(dev) {
126
131
  <li><span class="label">revision:</span><span>${coordinatorinfo.revision}</span></li>
127
132
  <li><span class="label">port:</span><span>${coordinatorinfo.port}</span></li>
128
133
  <li><span class="label">channel:</span><span>${coordinatorinfo.channel}</span></li>
134
+ <li><span class="label">------------</span><span>Software versions </span></li>
135
+ <li><span class="label">adapter:</span><span>${coordinatorinfo.installedVersion}</span></li>
136
+ <li><span class="label">installed from:</span><span>${coordinatorinfo.installSource}</span></li>
137
+ <li><span class="label">ZHC / ZH:</span><span>${coordinatorinfo.converters} / ${coordinatorinfo.herdsman}</span></li>
129
138
  </ul>
130
139
  </div>`,
131
- 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>' : '',
140
+ permitJoinBtn = '<div class="col tool"><button name="joinCard" class="waves-effect btn-small btn-flat right hoverable green"><i class="material-icons icon-green">leak_add</i></button></div>',
141
+ //permitJoinBtn = `<div class="col tool"><button name="join" class="btn-floating-sml waves-effect waves-light right hoverable green><i class="material-icons">leak_add</i></button></div>`,
132
142
  card = `<div id="${id}" class="device">
133
143
  <div class="card hoverable">
134
144
  <div class="card-content zcard">
135
145
  <span class="top right small" style="border-radius: 50%">
136
146
  ${lq}
137
147
  ${status}
148
+ ${permitJoinBtn}
138
149
  </span>
139
150
  <!--/a--!>
140
151
  <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
@@ -142,11 +153,6 @@ function getCoordinatorCard(dev) {
142
153
  ${info}
143
154
  <div class="footer right-align"></div>
144
155
  </div>
145
- <div class="card-action">
146
- <div class="card-reveal-buttons">
147
- ${permitJoinBtn}
148
- </div>
149
- </div>
150
156
  </div>
151
157
  </div>`;
152
158
  return card;
@@ -167,9 +173,10 @@ function getGroupCard(dev) {
167
173
  }
168
174
  }
169
175
  devGroups[numid] = dev;
176
+ const roomInfo = rooms.length ? `<li><span class="labelinfo">rooms:</span><span>${rooms.join(',') || ''}</span></li>` : '';
170
177
  const room = rooms.join(',') || '&nbsp';
171
178
  let memberCount = 0;
172
- let info = `<div style="min-height:88px; font-size: 0.8em; height: 98px; overflow-y: auto" class="truncate">
179
+ let info = `<div style="min-height:88px; font-size: 0.8em; overflow-y: auto" class="truncate">
173
180
  <ul>`;
174
181
  info = info.concat(`<li><span class="labelinfo">Group ${numid}</span></li>`);
175
182
  if (dev.memberinfo === undefined) {
@@ -181,7 +188,7 @@ function getGroupCard(dev) {
181
188
  memberCount = (dev.memberinfo.length < 8 ? dev.memberinfo.length : 7);
182
189
  }
183
190
  ;
184
- info = info.concat(` </ul>
191
+ info = info.concat(` ${roomInfo}</ul>
185
192
  </div>`);
186
193
  const image = `<img src="${dev.common.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
187
194
  const dashCard = getDashCard(dev, dev.common.icon, memberCount > 0);
@@ -199,11 +206,11 @@ function getGroupCard(dev) {
199
206
  </div>
200
207
  <i class="left">${image}</i>
201
208
  ${info}
209
+
202
210
  <div class="footer right-align"></div>
203
211
  </div>
204
212
  <div class="card-action">
205
213
  <div class="card-reveal-buttons">
206
- <span class="left" style="padding-top:8px">${room}</span>
207
214
  <button name="deletegrp" class="right btn-flat btn-small">
208
215
  <i class="material-icons icon-black">delete</i>
209
216
  </button>
@@ -246,12 +253,13 @@ function getCard(dev) {
246
253
  rooms.push(dev.rooms[r]);
247
254
  }
248
255
  }
249
- const room = rooms.join(',') || '&nbsp';
250
256
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
251
257
  const rid = id.split('.').join('_');
252
258
  const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
259
+ const groupInfo = dev.groupNames ? `<li><span class="labelinfo">groups:</span><span>${dev.groupNames || ''}</span></li>` : '';
260
+ const roomInfo = rooms.length ? `<li><span class="labelinfo">rooms:</span><span>${rooms.join(',') || ''}</span></li>` : '';
253
261
  const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
254
- nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
262
+ nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
255
263
  battery_cls = (isActive ? getBatteryCls(dev.battery) : ''),
256
264
  lqi_cls = getLQICls(dev.link_quality),
257
265
  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>` : '',
@@ -264,12 +272,14 @@ function getCard(dev) {
264
272
  <li><span class="labelinfo">ieee:</span><span>0x${ieee}</span></li>
265
273
  <li><span class="labelinfo">nwk:</span><span>${(nwk) ? nwk.toString() + ' (0x' + nwk.toString(16) + ')' : ''}</span></li>
266
274
  <li><span class="labelinfo">model:</span><span>${modelUrl}</span></li>
267
- <li><span class="labelinfo">groups:</span><span>${dev.groupNames || ''}</span></li>
275
+ ${groupInfo}
276
+ ${roomInfo}
268
277
  </ul>
269
278
  </div>`,
270
279
  deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive ? 'Deactivate' : 'Activate')}"><i class="material-icons ${(isActive ? 'icon-green' : 'icon-red')}">power_settings_new</i></button>`,
271
280
  debugBtn = `<button name="swapdebug" class="right btn-flat btn-small tooltipped" title="${(isDebug > -1 ? (isDebug > 0) ?'Automatic by '+debugDevices[isDebug-1]: 'Disable Debug' : 'Enable Debug')}"><i class="material-icons icon-${(isDebug > -1 ? (isDebug > 0 ? 'orange' : 'green') : 'gray')}">bug_report</i></button>`,
272
281
  infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
282
+
273
283
  const dashCard = getDashCard(dev);
274
284
  const card = `<div id="${id}" class="device">
275
285
  <div class="card hoverable flipable ${isActive ? '' : 'bg_red'}">
@@ -292,7 +302,7 @@ function getCard(dev) {
292
302
  <div class="card-action">
293
303
  <div class="card-reveal-buttons">
294
304
  ${infoBtn}
295
- <span class="left" style="padding-top:8px">${room}</span>
305
+
296
306
  <span class="left fw_info"></span>
297
307
  <button name="delete" class="right btn-flat btn-small tooltipped" title="Delete">
298
308
  <i class="material-icons icon-red">delete</i>
@@ -377,7 +387,7 @@ function deleteConfirmation(id, name) {
377
387
  $('#modaldelete a.btn[name=\'yes\']').unbind('click');
378
388
  $('#modaldelete a.btn[name=\'yes\']').click(() => {
379
389
  const force = $('#force').prop('checked');
380
- deleteDevice(id, force);
390
+ deleteZigbeeDevice(id, force);
381
391
  });
382
392
  $('#modaldelete').modal('open');
383
393
  Materialize.updateTextFields();
@@ -396,7 +406,8 @@ function deleteNvBackupConfirmation() {
396
406
  closeWaitingDialog();
397
407
  if (msg) {
398
408
  if (msg.error) {
399
- showMessage(msg.error, _('Error'));
409
+ if (msg.error.includes('ENOENT')) showMessage('No nvRam backup available for deletion.', _('Error'))
410
+ else showMessage(msg.error, _('Error'));
400
411
  } else {
401
412
  getDevices();
402
413
  }
@@ -435,29 +446,50 @@ function editName(id, name) {
435
446
 
436
447
  const device_options = {};
437
448
  const received_options = {};
449
+ console.warn('editName called with ' + id + ' and ' + name);
450
+ const dev = devices.find((d) => d._id == id);
451
+ $('#modaledit').find('input[id=\'d_name\']').val(name);
452
+ const groupables = [];
438
453
 
439
454
  function removeOption(k) {
440
- if (k && device_options.hasOwnProperty(k)) delete device_options[k];
455
+ if (k && device_options.hasOwnProperty(k)) {
456
+ if (dev.info.mapped && dev.info.mapped.options && dev.info.mapped.options.includes(device_options[k].key))
457
+ availableOptions.push(device_options[k].key)
458
+ delete device_options[k];
459
+ }
441
460
  }
442
461
 
443
462
  function addOption() {
444
463
  let idx=1;
445
464
  let key = '';
465
+ const optionName = $('#option_Selector').val();
466
+ console.warn(`option name is ${optionName}`);
446
467
  do {
447
468
  key = `o${idx++}`;
448
469
  }
449
470
  while (device_options.hasOwnProperty(key));
450
- device_options[key] = {key:`option_${idx++}`, value:''};
471
+ device_options[key] = { key:optionName, value:''};
472
+ console.warn(`device_options: ${JSON.stringify(device_options)}`);
473
+ idx = availableOptions.indexOf(optionName);
474
+ console.warn(`idx: ${idx}, ao:${JSON.stringify(availableOptions)}, on: ${optionName}`);
475
+ if (idx > -1) availableOptions.splice(idx, 1);
451
476
  }
452
477
 
453
- function updateOptions() {
478
+ function updateOptions(candidates) {
479
+ if (candidates.length > 0) {
480
+ $('#modaledit').find('.new_options_available').removeClass('hide');
481
+ list2select('#option_Selector', candidates, [], (key, val) => { return val; }, (key, val) => { return val; })
482
+ }
483
+ else {
484
+ $('#modaledit').find('.new_options_available').addClass('hide');
485
+ }
454
486
  const html_options=[];
455
487
 
456
- console.warn(`device_options is ${JSON.stringify(device_options)}`)
488
+ console.warn(`option_Selector is ${JSON.stringify(device_options)}`)
457
489
 
458
490
  for (const k in device_options) {
459
491
  html_options.push(`<div class="row">`);
460
- html_options.push(`<div class="input-field suffix col s5 m5 l5"><input id="option_key_${k}" type="text" class="value" /><label for="option_key_${k}">Option</label></div>`)
492
+ html_options.push(`<div class="input-field suffix col s5 m5 l5"><input disabled id="option_key_${k}" type="text" class="value" /><label for="option_key_${k}">Option</label></div>`)
461
493
  html_options.push(`<div class="input-field suffix col s5 m5 l5"><input id="option_value_${k}" type="text" class="value" /><label for="option_value_${k}">Value</label></div>`)
462
494
  html_options.push(`<div class="col"><a id="option_rem_${k}" class='btn' ><i class="material-icons">remove_circle</i></a></div>`);
463
495
  html_options.push(`</div>`)
@@ -471,11 +503,11 @@ function editName(id, name) {
471
503
  $(`#option_key_${k}`).val(device_options[k].key);
472
504
  $(`#option_value_${k}`).val(device_options[k].value);
473
505
  $(`#option_rem_${k}`).unbind('click');
474
- $(`#option_rem_${k}`).click(() => { removeOption(k); updateOptions() });
506
+ $(`#option_rem_${k}`).click(() => { removeOption(k); updateOptions(availableOptions) });
475
507
  }
476
508
  }
477
509
  else {
478
- $('#modaledit').find('.options_available').addClass('hide');
510
+ if (candidates.length == 0) $('#modaledit').find('.options_available').addClass('hide');
479
511
  }
480
512
  }
481
513
 
@@ -504,18 +536,15 @@ function editName(id, name) {
504
536
 
505
537
 
506
538
 
507
- console.warn('editName called with ' + id + ' and ' + name);
508
- const dev = devices.find((d) => d._id == id);
509
- $('#modaledit').find('input[id=\'d_name\']').val(name);
510
- const groupables = [];
511
539
  if (dev && dev.info && dev.info.endpoints) {
512
540
  for (const ep of dev.info.endpoints) {
513
- if (ep.inputClusters.includes(4)) {
541
+ if (ep.input_clusters.includes(4)) {
514
542
  groupables.push({epid: EndPointIDfromEndPoint(ep), ep: ep, memberOf: []});
515
543
  }
516
544
  }
517
545
  }
518
546
  const numEP = groupables.length;
547
+ const availableOptions = (dev.info.mapped ? dev.info.mapped.options.slice() || []:[]);
519
548
 
520
549
  if (numEP > 0) {
521
550
  $('#modaledit').find('.groups_available').removeClass('hide');
@@ -562,7 +591,7 @@ function editName(id, name) {
562
591
  sendTo(namespace, 'getLocalConfigItems', { target:id, global:false, key:'options' }, function (msg) {
563
592
  if (msg) {
564
593
  if (msg.error) showMessage(msg.error, '_Error');
565
- console.warn(`return is ${msg}`)
594
+ console.warn(`return is ${JSON.stringify(msg)}`)
566
595
  Object.keys(device_options).forEach(key => delete device_options[key]);
567
596
  Object.keys(received_options).forEach(key => delete received_options[key]);
568
597
  if (typeof msg.options === 'object') {
@@ -570,12 +599,16 @@ function editName(id, name) {
570
599
  let cnt = 1;
571
600
  for (const key in msg.options)
572
601
  {
602
+ const idx = availableOptions.indexOf(key);
603
+ console.warn(`key ${key} : index : ${idx}`);
604
+ if (idx > -1) availableOptions.splice(idx,1);
573
605
  received_options[key]=msg.options[key];
574
606
  device_options[`o${cnt}`] = { key:key, value:msg.options[key]}
575
607
  cnt++;
576
608
  }
577
609
  }
578
- updateOptions();
610
+ console.warn(`avo ${JSON.stringify(availableOptions)}, mapped: ${JSON.stringify(dev.info.mapped.options)}`);
611
+ updateOptions(availableOptions);
579
612
 
580
613
  } else showMessage('callback without message');
581
614
  });
@@ -584,7 +617,7 @@ function editName(id, name) {
584
617
  $('#modaledit a.btn[name=\'add_options\']').click(() => {
585
618
  getOptionsFromUI(device_options, received_options);
586
619
  addOption();
587
- updateOptions()
620
+ updateOptions(availableOptions)
588
621
  });
589
622
  $('#modaledit a.btn[name=\'save\']').click(() => {
590
623
  const newName = $('#modaledit').find('input[id=\'d_name\']').val();
@@ -625,8 +658,8 @@ function GenerateGroupChange(oldmembers, newmembers) {
625
658
  return grpchng;
626
659
  }
627
660
 
628
- function deleteDevice(id, force) {
629
- sendTo(namespace, 'deleteDevice', {id: id, force: force}, function (msg) {
661
+ function deleteZigbeeDevice(id, force) {
662
+ sendTo(namespace, 'deleteZigbeeDevice', {id: id, force: force}, function (msg) {
630
663
  closeWaitingDialog();
631
664
  if (msg) {
632
665
  if (msg.error) {
@@ -671,7 +704,9 @@ function renameDevice(id, name) {
671
704
  }
672
705
 
673
706
  function showDevices() {
707
+ console.warn('show Devices called')
674
708
  let html = '';
709
+ let hasCoordinator = false;
675
710
  const lang = systemLang || 'en';
676
711
  // sort by rooms
677
712
  devices.sort((a, b) => {
@@ -703,19 +738,17 @@ function showDevices() {
703
738
  });
704
739
  for (let i = 0; i < devices.length; i++) {
705
740
  const d = devices[i];
706
- if (!d.info) {
707
- if (d.common && d.common.type == 'group') {
708
- const card = getGroupCard(d);
709
- html += card;
710
- continue;
711
- }
712
- }
713
- ;
714
- if (d.info && d.info.device._type == 'Coordinator') {
741
+ if (d.common && d.common.type == 'group') {
742
+ const card = getGroupCard(d);
743
+ html += card;
744
+ continue;
745
+ };
746
+ if (d.info && d.info.device && d.info.device.type == 'Coordinator') {
747
+ hasCoordinator=true;
715
748
  const card = getCoordinatorCard(d);
716
749
  html += card;
717
750
  } else {
718
- //if (d.groups && d.info && d.info.device._type == "Router") {
751
+ //if (d.groups && d.info && d.info.device.type == "Router") {
719
752
  if (d.groups) {
720
753
  //devGroups[d._id] = d.groups;
721
754
  if (typeof d.groups.map == 'function') {
@@ -730,6 +763,8 @@ function showDevices() {
730
763
  html += card;
731
764
  }
732
765
  }
766
+ if (!hasCoordinator) html += getCoordinatorCard();
767
+
733
768
  $('#devices').html(html);
734
769
  hookControls();
735
770
 
@@ -771,6 +806,7 @@ function showDevices() {
771
806
  return dev_block.find('#dName').text();
772
807
  };
773
808
  const getDevId = function (dev_block) {
809
+ console.warn(`getDevId called with ${JSON.stringify(dev_block)}`)
774
810
  return dev_block.attr('id');
775
811
  };
776
812
  $('.card-reveal-buttons button[name=\'delete\']').click(function () {
@@ -807,13 +843,24 @@ function showDevices() {
807
843
  const name = getDevName(dev_block);
808
844
  editGroup(id, name, false);
809
845
  });
810
- $('button.btn-floating[name=\'join\']').click(function () {
846
+ $('button[name=\'joinCard\']').click(function () {
811
847
  const dev_block = $(this).parents('div.device');
812
848
  if (!$('#pairing').hasClass('pulse')) {
813
849
  joinProcess(getDevId(dev_block));
814
850
  }
815
851
  showPairingProcess();
816
852
  });
853
+ $('button[name=\'deviceQuery\']').click(function () {
854
+ const dev_block = $(this).parents('div.device');
855
+ sendTo(namespace, 'setState', {id: `${getDevId(dev_block)}.device_query`, val: true}, function (data) {
856
+ //console.log(data);
857
+ }); });
858
+ $('#modalpairing a.btn[name=\'extendpairing\']').click(function () {
859
+ letsPairing();
860
+ });
861
+ $('#modalpairing a.btn[name=\'endpairing\']').click(function () {
862
+ stopPairing();
863
+ });
817
864
  $('.card-reveal-buttons button[name=\'info\']').click(function () {
818
865
  const dev_block = $(this).parents('div.device');
819
866
  showDevInfo(getDevId(dev_block));
@@ -838,6 +885,14 @@ function showDevices() {
838
885
  translateAll();
839
886
  }
840
887
 
888
+ function downloadIcons() {
889
+ sendTo(namespace, 'downloadIcons', {}, function (msg) {
890
+ if (msg && msg.msg) {
891
+ showMessage(msg.msg, _('Result'));
892
+ }
893
+ });
894
+ }
895
+
841
896
  function checkFwUpdate() {
842
897
  const deviceCards = getDeviceCards();
843
898
  const getFwInfoNode = function (deviceCard) {
@@ -888,7 +943,7 @@ function checkFwUpdate() {
888
943
 
889
944
  function letsPairingWithCode(code) {
890
945
  messages = [];
891
- sendTo(namespace, 'letsPairing', {code: code}, function (msg) {
946
+ sendTo(namespace, 'letsPairing', {code: code, stop:false}, function (msg) {
892
947
  if (msg && msg.error) {
893
948
  showMessage(msg.error, _('Error'));
894
949
  }
@@ -900,7 +955,16 @@ function letsPairingWithCode(code) {
900
955
 
901
956
  function letsPairing() {
902
957
  messages = [];
903
- sendTo(namespace, 'letsPairing', {}, function (msg) {
958
+ sendTo(namespace, 'letsPairing', {stop:false}, function (msg) {
959
+ if (msg && msg.error) {
960
+ showMessage(msg.error, _('Error'));
961
+ }
962
+ });
963
+ }
964
+
965
+ function stopPairing() {
966
+ messages = [];
967
+ sendTo(namespace, 'letsPairing', {stop:true}, function (msg) {
904
968
  if (msg && msg.error) {
905
969
  showMessage(msg.error, _('Error'));
906
970
  }
@@ -918,7 +982,7 @@ function touchlinkReset() {
918
982
 
919
983
  function joinProcess(devId) {
920
984
  messages = [];
921
- sendTo(namespace, 'letsPairing', {id: devId}, function (msg) {
985
+ sendTo(namespace, 'letsPairing', {id: devId, stop:false}, function (msg) {
922
986
  if (msg && msg.error) {
923
987
  showMessage(msg.error, _('Error'));
924
988
  }
@@ -926,17 +990,19 @@ function joinProcess(devId) {
926
990
  }
927
991
 
928
992
  function getCoordinatorInfo() {
993
+ console.warn('calling getCoordinatorInfo');
929
994
  sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
930
995
  if (msg) {
996
+ console.warn(JSON.stringify(msg))
931
997
  if (msg.error) {
932
998
  errorData.push(msg.error);
999
+ delete msg.error;
933
1000
  isHerdsmanRunning = false;
934
- updateStartButton();
935
1001
  } else {
936
- coordinatorinfo = msg;
937
1002
  isHerdsmanRunning = true;
938
- updateStartButton()
939
1003
  }
1004
+ coordinatorinfo = msg;
1005
+ updateStartButton()
940
1006
  }
941
1007
  });
942
1008
  }
@@ -1051,6 +1117,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1051
1117
  const Html = [];
1052
1118
  const filterSet = new Set();
1053
1119
  let isodd = true;
1120
+ const buttonList = [];
1054
1121
  if (dbgMsghide.has('i_'+devID)) {
1055
1122
  console.warn('in all filtered out')
1056
1123
  Html.push('&nbsp;')
@@ -1065,8 +1132,11 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1065
1132
  const redText = (item.errors && item.errors.length > 0 ? ' id="dbgred"' : '');
1066
1133
  idx--;
1067
1134
  const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
1068
- if (idx==0)
1069
- LHtml.push(`<td${rowspan}>${item.dataID.toString(16).slice(-4)}</td><td${rowspan}>${safestring(item.payload)}</td>`);
1135
+ if (idx==0) {
1136
+ const msgbutton = `<a id="lx_${item.dataID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Messages from ${new Date(item.dataID).toLocaleTimeString()}"><i class="material-icons large">speaker_notes</i></a>`
1137
+ buttonList.push(item.dataID)
1138
+ LHtml.push(`<td${rowspan}>${msgbutton}</td><td${rowspan}>${safestring(item.payload)}</td>`);
1139
+ }
1070
1140
  LHtml.push(`<td></td><td${redText}>${safestring(state.payload)}</td><td${redText}>${state.id}</td><td${redText}>${state.value}</td><td${redText}>${fne(item)}</td></tr>`);
1071
1141
  IHtml.unshift(...LHtml)
1072
1142
  }
@@ -1079,7 +1149,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1079
1149
  const ifbutton = `<a id="i_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsgfilter.has('i_'+devID) ? 'filter_list' : 'format_align_justify' }</i></a>`
1080
1150
  const ofbutton = `<a id="hi_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsghide.has('i_'+devID) ? 'unfold_more' : 'unfold_less' }</i></a>`
1081
1151
  const dataHide = dbgMsgfilter.has('hi_'+devID) ? 'Data hidden' : '&nbsp;';
1082
- return `<thead id="dbgtable"><tr><td>&nbsp</td><td>Incoming messages</td><td>&nbsp;</td><td>&nbsp;</td><td>${dataHide}</td><td>${ifbutton}</td><td>${ofbutton}</td></tr><tr><td>ID</td><td>Zigbee Payload</td><td>&nbsp;</td><td>State Payload</td><td>ID</td><td>value</td><td>Flags</td></tr></thead><tbody>${Html.join('')}</tbody>`;
1152
+ return {html:`<thead id="dbgtable"><tr><td>&nbsp</td><td>Incoming messages</td><td>&nbsp;</td><td>&nbsp;</td><td>${dataHide}</td><td>${ifbutton}</td><td>${ofbutton}</td></tr><tr><td>ID</td><td>Zigbee Payload</td><td>&nbsp;</td><td>State Payload</td><td>ID</td><td>value</td><td>Flags</td></tr></thead><tbody>${Html.join('')}</tbody>`, buttonList };
1083
1153
  }
1084
1154
 
1085
1155
 
@@ -1087,6 +1157,7 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1087
1157
  const Html = [];
1088
1158
  const filterSet = new Set();
1089
1159
  let isodd=true;
1160
+ const buttonList = [];
1090
1161
  if (dbgMsghide.has('o_'+devID)) {
1091
1162
  console.warn('out all filtered out')
1092
1163
  Html.push('&nbsp;')
@@ -1102,8 +1173,11 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1102
1173
  const redText = (item.errors && item.errors.length > 0 ? ' id="dbgred"' : '');
1103
1174
  const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
1104
1175
  idx--;
1105
- if (idx==0)
1106
- LHtml.push(`<td${rowspan}>${item.dataID.toString(16).slice(-4)}</td><td${rowspan}>${safestring(item.payload)}</td>`);
1176
+ if (idx==0) {
1177
+ const msgbutton = `<a id="lx_${item.dataID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Messages from ${new Date(item.dataID).toLocaleTimeString()}"><i class="material-icons large">speaker_notes</i></a>`
1178
+ LHtml.push(`<td${rowspan}>${msgbutton}</td><td${rowspan}>${safestring(item.payload)}</td>`);
1179
+ buttonList.push(item.dataID)
1180
+ }
1107
1181
  LHtml.push(`<td${redText}>${state.ep ? state.ep : ''}</td><td${redText}>${state.id}</td><td${redText}>${safestring(state.value)}</td><td${redText}>${safestring(state.payload)}</td><td${redText}>${fne(item)}</td></tr>`);
1108
1182
  IHtml.unshift(...LHtml);
1109
1183
 
@@ -1117,22 +1191,24 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1117
1191
  const ifbutton = `<a id="o_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsgfilter.has('o_'+devID) ? 'filter_list' : 'format_align_justify' }</i></a>`
1118
1192
  const ofbutton = `<a id="ho_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsghide.has('o_'+devID) ? 'unfold_more' : 'unfold_less'}</i></a>`
1119
1193
  const dataHide = dbgMsgfilter.has('ho_'+devID) ? 'Data hidden' : '&nbsp;';
1120
- return `<thead id="dbgtable"><tr><td>&nbsp</td><td>Outgoing messages</td><td>&nbsp;</td><td>&nbsp;</td><td>${dataHide}</td><td>${ifbutton}</td><td>${ofbutton}</td></tr><tr><td>ID</td><td>Zigbee Payload</td><td>EP</td><td>ID</td><td>value</td><td>State Payload</td><td>Flags</td></tr></thead><tbody>${Html.join('')}</tbody>`;
1194
+ return { html:`<thead id="dbgtable"><tr><td>&nbsp</td><td>Outgoing messages</td><td>&nbsp;</td><td>&nbsp;</td><td>${dataHide}</td><td>${ifbutton}</td><td>${ofbutton}</td></tr><tr><td>ID</td><td>Zigbee Payload</td><td>EP</td><td>ID</td><td>value</td><td>State Payload</td><td>Flags</td></tr></thead><tbody>${Html.join('')}</tbody>`, buttonList};
1121
1195
  }
1122
1196
 
1123
1197
 
1124
1198
  function displayDebugMessages(msg) {
1125
1199
  const buttonNames = [];
1200
+ const idButtons = [];
1126
1201
  if (msg.byId) {
1127
1202
  const dbgData = msg.byId;
1128
1203
  const keys = Object.keys(dbgData);
1129
1204
  const keylength = keys.length;
1130
1205
  const Html = [];
1131
1206
  const button = `<a id="e_all" class="btn-floating waves-effect waves-light green tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">sync_problem</i></a>`;
1132
- const fbutton = `<a id="f_all" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsgfilter.size != 0 ? 'filter_list' : 'format_align_justify' }</i></a>`;
1207
+ const dbutton = `<a id="d_all" class="btn-floating waves-effect waves-light red tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons icon-yellowlarge">delete_forever</i></a>`;
1208
+ const fbutton = `<a id="f_all" class="btn-floating waves-effect waves-light green tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsgfilter.size != 0 ? 'filter_list' : 'format_align_justify' }</i></a>`;
1133
1209
  const hbutton = `<a id="h_all" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsghide.size != 0 ? 'unfold_more' : 'unfold_less'}</i></a>`;
1134
1210
  const logbutton = `<a id="l_all" class="btn-floating waves-effect waves-light ${debugInLog ? 'green' : 'red'} tooltipped center-align hoverable translateT" title="Log messages"><i class="material-icons large">${debugInLog ? 'speaker_notes' : 'speaker_notes_off'}</i></a>`;
1135
- Html.push(`<li><table><thead id="dbgtable"><tr><td>${logbutton}</td><td colspan="3">Debug information by device</td><td>${fbutton}</td><td>${hbutton}</td><td>${button}</td></tr></thead><tbody>`);
1211
+ Html.push(`<li><table><thead id="dbgtable"><tr><td>${logbutton}</td><td colspan="3">Debug information by device</td><td>${fbutton}</td><td>${hbutton}</td><td>${button}</td><td>${dbutton}</td></tr></thead><tbody>`);
1136
1212
  if (!keylength) {
1137
1213
  Html.push('<tr><td></td><td>No debug data loaded - press reload to refresh</td><td></td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr></tbody></table></li>')
1138
1214
  $('#dbg_data_list').html(Html.join(''));
@@ -1147,20 +1223,28 @@ function displayDebugMessages(msg) {
1147
1223
  const modelUrl = (type_url === 'unknown') ? 'unknown' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${image}</a>`;
1148
1224
  const devName = (dev && dev.common && dev.common.name) ? dev.common.name : 'unnamed';
1149
1225
  const button = `<a id="e_${devID}" class="btn-floating waves-effect waves-light green tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">sync_problem</i></a>`
1226
+ const dbutton = `<a id="d_${devID}" class="btn-floating waves-effect waves-light red tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons icon-yellow large">delete_forever</i></a>`;
1150
1227
  buttonNames.push(devID);
1151
- Html.push(`<li><table><thead id="dbgtable"><tr><td colspan="4">${devName} (ID: ${devID} Model: ${dev && dev.common ? dev.common.name : 'unknown'})</td><td>${modelUrl}</td><td>&nbsp;</td><td>${button}</td></tr></thead><tbody>`);
1228
+ Html.push(`<li><table><thead id="dbgtable"><tr><td colspan="4">${devName} (ID: ${devID} Model: ${dev && dev.common ? dev.common.name : 'unknown'})</td><td>${modelUrl}</td><td>${button}</td><td>${dbutton}</td></tr></thead><tbody>`);
1152
1229
  if (dbgData[devID].IN.length > 0) {
1153
- Html.push(`${HtmlFromInDebugMessages(dbgData[devID].IN, devID, dbgMsgfilter.has('i_'+devID))}`);
1230
+ const indata = HtmlFromInDebugMessages(dbgData[devID].IN, devID, dbgMsgfilter.has('i_'+devID));
1231
+ Html.push(`${indata.html}`);
1232
+ idButtons.push(...indata.buttonList)
1154
1233
  }
1155
1234
  if (dbgData[devID].OUT.length > 0) {
1156
- Html.push(`${HtmlFromOutDebugMessages(dbgData[devID].OUT, devID, dbgMsgfilter.has('o_'+devID))}`);
1235
+ const outdata = HtmlFromOutDebugMessages(dbgData[devID].OUT, devID, dbgMsgfilter.has('o_'+devID));
1236
+ Html.push(`${outdata.html}`);
1237
+ idButtons.push(...outdata.buttonList)
1157
1238
  }
1158
1239
  Html.push('</tbody></table></li>');
1159
1240
  }
1160
1241
  $('#dbg_data_list').html(Html.join(''));
1161
1242
  }
1162
1243
  $(`#e_all`).click(function () {
1163
- getDebugMessages();
1244
+ getDebugMessages(false);
1245
+ });
1246
+ $(`#d_all`).click(function () {
1247
+ getDebugMessages(true, 'all');
1164
1248
  });
1165
1249
  $(`#l_all`).click(function () {
1166
1250
  debugInLog = !debugInLog;
@@ -1192,7 +1276,10 @@ function displayDebugMessages(msg) {
1192
1276
  });
1193
1277
  for (const b of buttonNames) {
1194
1278
  $(`#e_${b}`).click(function () {
1195
- getDebugMessages();
1279
+ getDebugMessages(false);
1280
+ });
1281
+ $(`#d_${b}`).click(function () {
1282
+ getDebugMessages(true, b);
1196
1283
  });
1197
1284
  $(`#o_${b}`).click(function () {
1198
1285
  if (dbgMsgfilter.has(`o_${b}`)) dbgMsgfilter.delete(`o_${b}`); else dbgMsgfilter.add(`o_${b}`);
@@ -1211,60 +1298,167 @@ function displayDebugMessages(msg) {
1211
1298
  displayDebugMessages(debugMessages);
1212
1299
  });
1213
1300
  }
1301
+ for (const b of idButtons) {
1302
+ console.warn(`trying to add link to button ${b}`);
1303
+ $(`#lx_${b}`).click(function() { showMessageList(b)});
1304
+ }
1305
+ }
1306
+ }
1307
+
1308
+ function showNamedMessages(messages, title, icon, timestamp) {
1309
+ // noinspection JSJQueryEfficiency
1310
+ let $dialogMessage = $('#dialog-message');
1311
+ if (!$dialogMessage.length) {
1312
+ $('body').append(
1313
+ '<div class="m"><div id="dialog-message" class="modal modal-fixed-footer">' +
1314
+ ' <div class="modal-content">' +
1315
+ ' <h6 class="dialog-title title"></h6>' +
1316
+ ' <p><i class="large material-icons dialog-icon"></i><span class="dialog-text"></span></p>' +
1317
+ ' </div>' +
1318
+ ' <div class="modal-footer">' +
1319
+ ' <a class="modal-action modal-close waves-effect waves-green btn-flat translate">Ok</a>' +
1320
+ ' </div>' +
1321
+ '</div></div>');
1322
+ $dialogMessage = $('#dialog-message');
1323
+ }
1324
+ if (icon) {
1325
+ $dialogMessage.find('.dialog-icon')
1326
+ .show()
1327
+ .html(icon);
1328
+ } else {
1329
+ $dialogMessage.find('.dialog-icon').hide();
1330
+ }
1331
+ if (title) {
1332
+ $dialogMessage.find('.dialog-title').html(title).show();
1333
+ } else {
1334
+ $dialogMessage.find('.dialog-title').hide();
1335
+ }
1336
+ const lihtml = ['```<br><ul>'];
1337
+ for (const key of Object.keys(messages)) {
1338
+ lihtml.push(`<li>${key}: ${messages[key]}</li>`)
1339
+ }
1340
+ lihtml.push('</ul><br>```')
1341
+ $dialogMessage.find('.dialog-text').html(lihtml);
1342
+ $dialogMessage.modal().modal('open');
1343
+
1344
+ }
1345
+
1346
+ function showMessageList(msgId) {
1347
+ console.warn(`trying to show messages for ${msgId}`);
1348
+ console.warn(JSON.stringify(debugMessages));
1349
+ for (const devId of Object.keys(debugMessages.byId)) {
1350
+ for (const id of debugMessages.byId[devId].IN) {
1351
+ if (id.dataID == msgId) {
1352
+ console.warn(`showing messages for ${id.type} ${devId}`);
1353
+ showNamedMessages(id.messages, `Messages from ${new Date(msgId).toLocaleTimeString()} for device ${devId}`);
1354
+ return;
1355
+ }
1356
+ }
1357
+ for (const id of debugMessages.byId[devId].OUT) {
1358
+ if (id.dataID == msgId) {
1359
+ console.warn(`showing messages for ${msgId}`);
1360
+ showNamedMessages(id.messages, `Messages from ${new Date(msgId).toLocaleTimeString()} for device ${devId}`);
1361
+ return;
1362
+ }
1363
+ }
1214
1364
  }
1365
+ console.warn(`nothing to show`);
1366
+
1367
+
1215
1368
  }
1216
1369
 
1217
- function getDebugMessages() {
1218
- sendTo(namespace, 'getDebugMessages', { inlog: debugInLog }, function(msg) {
1370
+ function getDebugMessages(deleteBeforeRead, deleteSelected) {
1371
+ sendTo(namespace, 'getDebugMessages', { inlog: debugInLog, del:deleteBeforeRead ? deleteSelected : '' }, function(msg) {
1219
1372
  debugMessages = msg;
1220
1373
  if (msg) displayDebugMessages(debugMessages)
1221
1374
  })
1222
1375
  }
1223
1376
 
1224
-
1377
+ const lockout = {
1378
+ timeoutid:undefined,
1379
+ isActive:false,
1380
+ };
1225
1381
  function getDevices() {
1226
- getCoordinatorInfo();
1227
- sendTo(namespace, 'getDevices', {}, function (msg) {
1228
- if (msg) {
1229
- devices = msg.devices ? msg.devices : [];
1230
- // check if stashed error messages are sent alongside
1231
- if (msg.clean)
1232
- $('#state_cleanup_btn').removeClass('hide');
1233
- else
1234
- $('#state_cleanup_btn').addClass('hide');
1235
- if (msg.errors && msg.errors.length > 0) {
1236
- $('#show_errors_btn').removeClass('hide');
1237
- errorData = msg.errors;
1238
- }
1239
- else {
1240
- $('#show_errors_btn').addClass('hide');
1241
- }
1382
+ console.warn('getDevices called')
1242
1383
 
1243
- //check if debug messages are sent alongside
1244
- if (msg && typeof (msg.debugDevices == 'array')) {
1245
- debugDevices = msg.debugDevices;
1246
- console.warn('debug devices is sent')
1247
- }
1248
- else
1249
- debugDevices = [];
1250
- if (debugMessages.byId) {
1251
- debugMessages.byId = msg;
1252
- if (msg) displayDebugMessages(debugMessages)
1253
- }
1254
- if (msg.error) {
1255
- errorData.push(msg.error);
1256
- isHerdsmanRunning = false;
1257
- updateStartButton();
1258
- } else {
1259
- isHerdsmanRunning = true;
1260
- updateStartButton();
1261
- showDevices();
1262
- getDebugMessages();
1263
- getExclude();
1264
- getBinding();
1384
+ function sendForData() {
1385
+ sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
1386
+ console.warn(`getCoordinatorInfo returned ${JSON.stringify(msg)}`)
1387
+ if (msg) {
1388
+ console.warn(JSON.stringify(msg))
1389
+ if (msg.error) {
1390
+ errorData.push(msg.error);
1391
+ delete msg.error;
1392
+ isHerdsmanRunning = false;
1393
+ } else {
1394
+ isHerdsmanRunning = true;
1395
+ }
1396
+ coordinatorinfo = msg;
1397
+ updateStartButton()
1265
1398
  }
1266
- }
1267
- });
1399
+ sendTo(namespace, 'getDevices', {}, function (msg) {
1400
+ if (msg) {
1401
+ devices = msg.devices ? msg.devices : [];
1402
+ // check if stashed error messages are sent alongside
1403
+ if (msg.clean)
1404
+ $('#state_cleanup_btn').removeClass('hide');
1405
+ else
1406
+ $('#state_cleanup_btn').addClass('hide');
1407
+ if (msg.errors && msg.errors.length > 0) {
1408
+ $('#show_errors_btn').removeClass('hide');
1409
+ errorData = msg.errors;
1410
+ }
1411
+ else {
1412
+ $('#show_errors_btn').addClass('hide');
1413
+ }
1414
+ let newDebugMessages = false;
1415
+
1416
+ //check if debug messages are sent alongside
1417
+ if (msg && typeof (msg.debugDevices == 'array')) {
1418
+ debugDevices = msg.debugDevices;
1419
+ console.warn('debug devices is sent')
1420
+ }
1421
+ else
1422
+ debugDevices = [];
1423
+ if (debugMessages.byId) {
1424
+ newDebugMessages = true;
1425
+ console.warn('having debug messages');
1426
+ debugMessages.byId = msg;
1427
+ if (msg) displayDebugMessages(debugMessages)
1428
+ }
1429
+ lockout.isActive = false;
1430
+ if (msg.error) {
1431
+ errorData.push(msg.error);
1432
+ isHerdsmanRunning = false;
1433
+ updateStartButton();
1434
+ showDevices();
1435
+ } else {
1436
+ isHerdsmanRunning = true;
1437
+ updateStartButton();
1438
+ showDevices();
1439
+ if (!newDebugMessages) {
1440
+ console.warn('getting debug messages');
1441
+ getDebugMessages();
1442
+ }
1443
+ //getExclude();
1444
+ getBinding();
1445
+ }
1446
+ }
1447
+ });
1448
+ });
1449
+ }
1450
+
1451
+ if (lockout.timeoutid) {
1452
+ clearTimeout(lockout.timeoutid);
1453
+ console.warn('clearing getDevices timeout')
1454
+ }
1455
+
1456
+ setTimeout(() => {
1457
+ lockout.isActive = true;
1458
+ lockout.timeoutid = undefined;
1459
+ sendForData();
1460
+ }, 100);
1461
+
1268
1462
  }
1269
1463
 
1270
1464
  function getNamedColors() {
@@ -1286,26 +1480,29 @@ function getDeviceCard(devId) {
1286
1480
  return $('#devices').find(`div[id='${namespace}.${devId}']`);
1287
1481
  }
1288
1482
 
1289
- function getMap() {
1483
+ function getMap(rebuild) {
1290
1484
  $('#refresh').addClass('disabled');
1291
- sendTo(namespace, 'getMap', {}, function (msg) {
1292
- $('#refresh').removeClass('disabled');
1293
- if (msg) {
1294
- if (msg.error) {
1295
- errorData.push(msg.error);
1296
- isHerdsmanRunning = false;
1297
- updateStartButton();
1298
- } else {
1299
- isHerdsmanRunning = true;
1300
- updateStartButton();
1301
- if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
1302
- showMessage(msg.errors.join('<p>'), 'Map generation messages');
1485
+ if (isHerdsmanRunning) {
1486
+ sendTo(namespace, 'getMap', { forcebuild:rebuild}, function (msg) {
1487
+ $('#refresh').removeClass('disabled');
1488
+ if (msg) {
1489
+ if (msg.error) {
1490
+ errorData.push(msg.error);
1491
+ isHerdsmanRunning = false;
1492
+ updateStartButton();
1493
+ } else {
1494
+ isHerdsmanRunning = true;
1495
+ updateStartButton();
1496
+ if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
1497
+ showMessage(msg.errors.join('<p>'), 'Map generation messages');
1498
+ }
1499
+ map = msg;
1500
+ showNetworkMap(devices, map);
1303
1501
  }
1304
- map = msg;
1305
- showNetworkMap(devices, map);
1306
1502
  }
1307
- }
1308
- });
1503
+ });
1504
+ }
1505
+ else showMessage('Unable to generate map, the zigbee subsystem is inactive', 'Map generation error');
1309
1506
  }
1310
1507
 
1311
1508
  function getRandomExtPanID()
@@ -1328,6 +1525,7 @@ function getRandomChannel()
1328
1525
  // the function loadSettings has to exist ...
1329
1526
 
1330
1527
  function load(settings, onChange) {
1528
+ console.warn(JSON.stringify(settings));
1331
1529
  if (settings.extPanID === undefined || settings.extPanID == '') {
1332
1530
  settings.channel = getRandomChannel();
1333
1531
  }
@@ -1355,6 +1553,7 @@ function load(settings, onChange) {
1355
1553
  settings.baudRate = 115200;
1356
1554
  }
1357
1555
  if (settings.autostart === undefined) settings.autostart = false;
1556
+ if (typeof settings.pingCluster != 'string') settings.pingCluster = settings.disablePing ? 'off' : 'default';
1358
1557
 
1359
1558
  // example: select elements with id=key and class=value and insert value
1360
1559
  for (const key in settings) {
@@ -1428,6 +1627,9 @@ function load(settings, onChange) {
1428
1627
  $('#show_errors_btn').click(function () {
1429
1628
  showMessage(errorData.join('<br>'), 'Stashed error messages');
1430
1629
  });
1630
+ $('#download_icons_btn').click(function () {
1631
+ showMessage(downloadIcons());
1632
+ });
1431
1633
  $('#fw_check_btn').click(function () {
1432
1634
  checkFwUpdate();
1433
1635
  });
@@ -1439,11 +1641,16 @@ function load(settings, onChange) {
1439
1641
  if (!$('#pairing').hasClass('pulse')) {
1440
1642
  letsPairing();
1441
1643
  }
1644
+ console.warn('lets pairing');
1442
1645
  showPairingProcess();
1443
1646
  });
1444
1647
 
1445
1648
  $('#refresh').click(function () {
1446
- getMap();
1649
+ getMap(false);
1650
+ });
1651
+ $('#regenerate').click(function () {
1652
+ getMap(true);
1653
+ $('#modalviewconfig').modal('close');
1447
1654
  });
1448
1655
 
1449
1656
  $('#reset-btn').click(function () {
@@ -1454,6 +1661,12 @@ function load(settings, onChange) {
1454
1661
  deleteNvBackupConfirmation();
1455
1662
  });
1456
1663
 
1664
+ $('#ErrorNotificationBtn').click(function () {
1665
+ if (!isHerdsmanRunning) {
1666
+ doTestStart(!isHerdsmanRunning, true);
1667
+ }
1668
+ })
1669
+
1457
1670
  $('#viewconfig').click(function () {
1458
1671
  showViewConfig();
1459
1672
  });
@@ -1555,7 +1768,7 @@ function showMessages() {
1555
1768
  }
1556
1769
 
1557
1770
  function showPairingProcess() {
1558
- $('#modalpairing').modal({
1771
+ if (isHerdsmanRunning) $('#modalpairing').modal({
1559
1772
  startingTop: '4%',
1560
1773
  endingTop: '10%',
1561
1774
  dismissible: false
@@ -1565,10 +1778,10 @@ function showPairingProcess() {
1565
1778
  Materialize.updateTextFields();
1566
1779
  }
1567
1780
 
1568
- function doTestStart(start) {
1781
+ function doTestStart(start, interactive) {
1569
1782
  updateStartButton(true);
1570
1783
  if (start) {
1571
- const ovr = { extPanID:$('#extPanID.value').val(),
1784
+ const ovr = interactive ? {} : { extPanID:$('#extPanID.value').val(),
1572
1785
  panID: $('#PanID.value').val(),
1573
1786
  channel: $('#channel.value').val(),
1574
1787
  port: $('#port.value').val(),
@@ -1579,12 +1792,18 @@ function doTestStart(start) {
1579
1792
  };
1580
1793
  // $('#testStartStart').addClass('disabled');
1581
1794
  messages = [];
1795
+ if (interactive) showWaitingDialog('Trying to start the zigbee subsystem manually', 120);
1582
1796
  sendTo(namespace, 'testConnect', { start:true, zigbeeOptions:ovr }, function(msg) {
1583
1797
  if (msg) {
1798
+ closeWaitingDialog();
1799
+ isHerdsmanRunning = msg.status;
1800
+ updateStartButton(false);
1584
1801
  if (msg.status)
1585
1802
  $('#testStartStop').removeClass('disabled');
1586
- else
1587
- $('#testStartStart').removeClass('disabled');
1803
+ else {
1804
+ //showMessage(`The zigbee subsystem is not running. Please ensure that the configuration is correct. ${msg.error ? 'Error on start-Attempt ' + msg.error.message : ''}`);
1805
+ $('#testStartStop').removeClass('disabled');
1806
+ }
1588
1807
  }
1589
1808
  })
1590
1809
  }
@@ -1628,14 +1847,15 @@ function getDevId(adapterDevId) {
1628
1847
 
1629
1848
 
1630
1849
  function updateStartButton(block) {
1850
+ console.warn(`update start button with${isHerdsmanRunning ? ' Herdsman' : 'out Herdsman'}`);
1631
1851
  if (block) {
1632
1852
  $('#show_test_run').addClass('disabled');
1633
1853
  $('#reset-btn').addClass('disabled');
1634
1854
  $('#deleteNVRam-btn').addClass('disabled');
1635
1855
  $('#ErrorNotificationBtn').removeClass('hide')
1636
1856
  $('#ErrorNotificationBtn').removeClass('blinking')
1637
- $('#ErrorNotificationIcon').removeClass('icon-red')
1638
- $('#ErrorNotificationIcon').addClass('icon-orange')
1857
+ $('#ErrorNotificationBtn').removeClass('red')
1858
+ $('#ErrorNotificationBtn').addClass('orange')
1639
1859
  return;
1640
1860
  }
1641
1861
  if (isHerdsmanRunning)
@@ -1645,15 +1865,25 @@ function updateStartButton(block) {
1645
1865
  $('#show_test_run').removeClass('disabled');
1646
1866
  $('#deleteNVRam-btn').removeClass('disabled');
1647
1867
  $('#reset-btn').removeClass('disabled');
1868
+ $('#fw_check_btn').removeClass('hide');
1869
+ $('#add_grp_btn').removeClass('hide');
1870
+ $('#touchlink_btn').removeClass('hide');
1871
+ $('#code_pairing').removeClass('hide');
1872
+ //$('#pairing').removeClass('hide');
1648
1873
  }
1649
1874
  else {
1650
- $('#ErrorNotificationIcon').addClass('icon-red')
1651
- $('#ErrorNotificationIcon').removeClass('icon-orange')
1875
+ $('#ErrorNotificationBtn').addClass('red')
1876
+ $('#ErrorNotificationBtn').removeClass('orange')
1652
1877
  $('#ErrorNotificationBtn').removeClass('hide')
1653
1878
  $('#ErrorNotificationBtn').addClass('blinking');
1654
1879
  $('#show_test_run').removeClass('disabled');
1655
1880
  $('#deleteNVRam-btn').removeClass('disabled');
1656
1881
  $('#reset-btn').addClass('disabled');
1882
+ $('#fw_check_btn').addClass('hide');
1883
+ $('#add_grp_btn').addClass('hide');
1884
+ $('#touchlink_btn').addClass('hide');
1885
+ $('#code_pairing').addClass('hide');
1886
+ //$('#pairing').addClass('hide');
1657
1887
  }
1658
1888
  }
1659
1889
  // subscribe to changes
@@ -1797,7 +2027,7 @@ function showNetworkMap(devices, map) {
1797
2027
  borderWidth: 1,
1798
2028
  borderWidthSelected: 4,
1799
2029
  };
1800
- if (dev.info && dev.info.device._type == 'Coordinator') {
2030
+ if (dev.info && dev.info.device.type == 'Coordinator') {
1801
2031
  // node.shape = 'star';
1802
2032
  node.image = 'zigbee.png';
1803
2033
  node.label = 'Coordinator';
@@ -1957,7 +2187,7 @@ function showNetworkMap(devices, map) {
1957
2187
 
1958
2188
  if (node) {
1959
2189
  node.font = {color: '#ff0000'};
1960
- if (dev.info && dev.info.device._type == 'Coordinator') {
2190
+ if (dev.info && dev.info.device && dev.info.device.type == 'Coordinator') {
1961
2191
  node.font = {color: '#00ff00'};
1962
2192
  }
1963
2193
  nodesArray.push(node);
@@ -2122,35 +2352,37 @@ function loadDeveloperTab() {
2122
2352
  updateSelect('#dev', devices,
2123
2353
  function (key, device) {
2124
2354
  if (device.hasOwnProperty('info')) {
2125
- if (device.info.device._type === 'Coordinator') {
2355
+ if (device.info.device && device.info.device.type === 'Coordinator') {
2126
2356
  return null;
2127
2357
  }
2128
- return `${device.common.name} (${device.info.name})`;
2358
+ if (device.common.type === 'group') return null;
2359
+ return `${device.common.name} (${device.info.device.ieee})`;
2129
2360
  } else { // fallback if device in list but not paired
2130
2361
  return device.common.name + ' ' + device.native.id;
2131
2362
  }
2132
2363
  },
2133
2364
  function (key, device) {
2134
- return device._id;
2135
- });
2136
- // add groups to device selector
2137
- const groupList = [];
2138
- for (const key in groups) {
2139
- groupList.push({
2140
- _id: namespace + '.' + key.toString(16).padStart(16, '0'),
2141
- groupId: key,
2142
- groupName: groups[key]
2365
+ return device.native.id;
2143
2366
  });
2144
- }
2145
- updateSelect('#dev', groupList,
2146
- function (key, device) {
2147
- return 'Group ' + device.groupId + ': ' + device.groupName;
2148
- },
2149
- function (key, device) {
2150
- return device._id;
2151
- }, true);
2367
+ /*
2368
+ const groupList = [];
2369
+ for (const key in groups) {
2370
+ groupList.push({
2371
+ id: namespace + '.' + key.toString(16).padStart(16, '0'),
2372
+ groupId: key,
2373
+ groupName: groups[key]
2374
+ });
2375
+ }
2376
+ updateSelect('#dev', groupList,
2377
+ function (key, device) {
2378
+ return 'Group ' + device.groupId + ': ' + device.groupName;
2379
+ },
2380
+ function (key, device) {
2381
+ return device.id;
2382
+ }, true);
2152
2383
 
2153
- // fill cid, cmd, type selector
2384
+ // fill cid, cmd, type selector
2385
+ */
2154
2386
  populateSelector('#cid', 'cidList');
2155
2387
  populateSelector('#cmd', 'cmdListFoundation', this.value);
2156
2388
  populateSelector('#type', 'typeList', this.value);
@@ -2231,10 +2463,11 @@ function loadDeveloperTab() {
2231
2463
  }
2232
2464
 
2233
2465
  const device = devices.find(obj => {
2234
- return obj._id === this.value;
2466
+ return this.value ===obj.native.id;
2235
2467
  });
2468
+ console.warn(`dev selector: ${this.selectedIndex} ${this.value} ->${JSON.stringify(device)}`)
2236
2469
 
2237
- const epList = device ? device.info.device._endpoints : null;
2470
+ const epList = device ? device.info.endpoints : null;
2238
2471
  updateSelect('#ep', epList,
2239
2472
  function (key, ep) {
2240
2473
  return ep.ID;
@@ -2670,14 +2903,14 @@ function prepareBindingDialog(bindObj) {
2670
2903
  return 'Select source device';
2671
2904
  }
2672
2905
  if (device.hasOwnProperty('info')) {
2673
- if (device.info.device._type === 'Coordinator') {
2906
+ if (device.info.device && device.info.device.type === 'Coordinator') {
2674
2907
  return null;
2675
2908
  }
2676
2909
  // check for output clusters
2677
2910
  let allow = false;
2678
2911
  for (const cluster of allowClusters) {
2679
- for (const ep of device.info.endpoints) {
2680
- if (ep.outputClusters.includes(cluster)) {
2912
+ if (device.info.endpoints) for (const ep of device.info.endpoints) {
2913
+ if (ep.output_clusters.includes(cluster)) {
2681
2914
  allow = true;
2682
2915
  break;
2683
2916
  }
@@ -2721,14 +2954,14 @@ function prepareBindingDialog(bindObj) {
2721
2954
  return 'Select target device';
2722
2955
  }
2723
2956
  if (device.hasOwnProperty('info')) {
2724
- if (device.info.device._type === 'Coordinator') {
2957
+ if (device.info.device && device.info.device.type === 'Coordinator') {
2725
2958
  return null;
2726
2959
  }
2727
2960
  // check for input clusters
2728
2961
  let allow = false;
2729
2962
  for (const cluster of allowClusters) {
2730
- for (const ep of device.info.endpoints) {
2731
- if (ep.inputClusters.includes(cluster)) {
2963
+ if (device.info.endpoints) for (const ep of device.info.endpoints) {
2964
+ if (ep.input_clusters.includes(cluster)) {
2732
2965
  allow = true;
2733
2966
  break;
2734
2967
  }
@@ -2772,7 +3005,7 @@ function prepareBindingDialog(bindObj) {
2772
3005
 
2773
3006
  const epList = device ? device.info.endpoints : [];
2774
3007
  const sClusterList = epList.map((ep) => {
2775
- const clusters = ep.outputClusters.map((cl) => {
3008
+ const clusters = ep.output_clusters.map((cl) => {
2776
3009
  return allowClusters.includes(cl) ? {ID: ep.ID + '_' + cl, name: allowClustersName[cl]} : null;
2777
3010
  }).filter((i) => {
2778
3011
  return i != null;
@@ -2798,7 +3031,7 @@ function prepareBindingDialog(bindObj) {
2798
3031
 
2799
3032
  const epList = device ? device.info.endpoints : [];
2800
3033
  const tClusterList = epList.map((ep) => {
2801
- const clusters = ep.inputClusters.map((cl) => {
3034
+ const clusters = ep.input_clusters.map((cl) => {
2802
3035
  return (allowClusters.includes(cl) && (!sourceCl || sourceCl == cl)) ? {
2803
3036
  ID: ep.ID + '_' + cl,
2804
3037
  name: allowClustersName[cl]
@@ -3031,12 +3264,13 @@ function genDevInfo(device) {
3031
3264
  //console.log(device);
3032
3265
  const dev = (device && device.info) ? device.info.device : undefined;
3033
3266
  const mapped = (device && device.info) ? device.info.mapped : undefined;
3267
+ const endpoints = (device && device.info) ? device.info.endpoints : [];
3034
3268
  if (!dev) return `<div class="truncate">No info</div>`;
3035
3269
  const genRow = function (name, value, refresh) {
3036
3270
  if (value === undefined) {
3037
3271
  return '';
3038
3272
  } else {
3039
- return `<li><span class="label">${name}:</span><span>${value}</span></li>`;
3273
+ return `<li><span class="label">${name.replace('_',' ')}:</span><span>${value}</span></li>`;
3040
3274
  }
3041
3275
  };
3042
3276
  const genRowValues = function (name, value) {
@@ -3044,66 +3278,68 @@ function genDevInfo(device) {
3044
3278
  return '';
3045
3279
  } else {
3046
3280
  let label = `${name}:`;
3047
- return value.map((val) => {
3048
- const row = `<li><span class="label">${label}</span><span>${val}</span></li>`;
3049
- label = '';
3050
- return row;
3051
- }).join('');
3281
+ try {
3282
+ return value.map((val) => {
3283
+ const row = `<li><span class="label">${label}</span><span>${val}</span></li>`;
3284
+ label = '';
3285
+ return row;
3286
+ }).join('');
3287
+ }
3288
+ catch {
3289
+ return `<li><span class="label">${label}</span><span>${JSON.stringify(value)}</span></li>`
3290
+ }
3052
3291
  }
3053
3292
  };
3054
3293
  const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(mapped.model)}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
3055
- const mappedInfo = (!mapped) ? '' :
3056
- `<div style="font-size: 0.9em">
3057
- <ul>
3058
- ${genRow('model', modelUrl)}
3059
- ${genRow('description', mapped.description)}
3060
- ${genRow('supports', mapped.supports)}
3061
- </ul>
3062
- </div>`;
3294
+ const mappedInfo = [];
3295
+ if (mapped) {
3296
+ mappedInfo.push(
3297
+ `<div style="font-size: 0.9em">
3298
+ <ul>`);
3299
+ for (const item in mapped) {
3300
+ if (item == 'model')
3301
+ mappedInfo.push(genRow(item,modelUrl));
3302
+ else
3303
+ mappedInfo.push(genRow(item,mapped[item]));
3304
+ }
3305
+ mappedInfo.push(
3306
+ ` </ul>
3307
+ </div>`);
3308
+ }
3063
3309
  let epInfo = '';
3064
- for (const epind in dev._endpoints) {
3065
- const ep = dev._endpoints[epind];
3310
+ for (const epind in endpoints) {
3311
+ const ep = endpoints[epind];
3312
+ console.warn(JSON.stringify(ep));
3066
3313
  epInfo +=
3067
3314
  `<div style="font-size: 0.9em" class="truncate">
3068
3315
  <ul>
3069
3316
  ${genRow('endpoint', ep.ID)}
3070
- ${genRow('profile', ep.profileID)}
3071
- ${genRowValues('input clusters', ep.inputClusters.map(findClName))}
3072
- ${genRowValues('output clusters', ep.outputClusters.map(findClName))}
3317
+ ${genRow('profile', ep.profile)}
3318
+ ${genRowValues('input clusters', ep.input_clusters ? ep.input_clusters.map(findClName) : 'none')}
3319
+ ${genRowValues('output clusters', ep.output_clusters ? ep.output_clusters.map(findClName): 'none')}
3073
3320
  </ul>
3074
3321
  </div>`;
3075
3322
  }
3076
3323
  const imgSrc = device.icon || device.common.icon;
3077
3324
  const imgInfo = (imgSrc) ? `<img src=${imgSrc} width='150px' onerror="this.onerror=null;this.src='img/unavailable.png';"><div class="divider"></div>` : '';
3078
- const info =
3325
+ const info =[
3079
3326
  `<div class="col s12 m6 l6 xl6">
3080
3327
  ${imgInfo}
3081
- ${mappedInfo}
3328
+ ${mappedInfo.join('')}
3082
3329
  <div class="divider"></div>
3083
3330
  <div style="font-size: 0.9em" class="truncate">
3084
- <ul>
3085
- ${genRow('modelZigbee', dev._modelID)}
3086
- ${genRow('type', dev._type)}
3087
- ${genRow('ieee', dev.ieeeAddr)}
3088
- ${genRow('nwk', dev._networkAddress)}
3089
- ${genRow('manuf id', dev._manufacturerID)}
3090
- ${genRow('manufacturer', dev._manufacturerName)}
3091
- ${genRow('power', dev._powerSource)}
3092
- ${genRow('app version', dev._applicationVersion)}
3093
- ${genRow('hard version', dev._hardwareVersion)}
3094
- ${genRow('zcl version', dev._zclVersion)}
3095
- ${genRow('stack version', dev._stackVersion)}
3096
- ${genRow('date code', dev._dateCode)}
3097
- ${genRow('build', dev._softwareBuildID)}
3098
- ${genRow('interviewed', dev._interviewCompleted)}
3099
- ${genRow('configured', (device.isConfigured), true)}
3331
+ <ul>`];
3332
+ for (const item in dev) {
3333
+ info.push(genRow(item, dev[item]));
3334
+ }
3335
+ info.push(` ${genRow('configured', (device.isConfigured), true)}
3100
3336
  </ul>
3101
3337
  </div>
3102
3338
  </div>
3103
3339
  <div class="col s12 m6 l6 xl6">
3104
3340
  ${epInfo}
3105
- </div>`;
3106
- return info;
3341
+ </div>`);
3342
+ return info.join('');
3107
3343
  }
3108
3344
 
3109
3345
  function showDevInfo(id) {
@@ -3211,7 +3447,7 @@ function prepareExcludeDialog(excludeObj) {
3211
3447
  return 'Select model';
3212
3448
  }
3213
3449
  if (device.hasOwnProperty('info')) {
3214
- if (device.info.device._type == 'Coordinator') {
3450
+ if (device.info.device && device.info.device.type == 'Coordinator') {
3215
3451
  return null;
3216
3452
  }
3217
3453
  return device.common.type;
@@ -3442,17 +3678,18 @@ function getDashCard(dev, groupImage, groupstatus) {
3442
3678
  rooms = [],
3443
3679
  lang = systemLang || 'en';
3444
3680
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
3681
+ const permitJoinBtn = dev.battery || dev.common.type == 'group' ? '' : `<div class="col tool"><button name="joinCard" class="waves-effect btn-small btn-flat right hoverable green"><i class="material-icons icon-green">leak_add</i></button></div>`;
3682
+ const device_queryBtn = dev.battery || dev.common.type == 'group' ? '' : `<div class="col tool"><button name="deviceQuery" class="waves-effect btn-small btn-flat right hoverable green"><i class="material-icons icon-green">play_for_work</i></button></div>`;
3445
3683
  const rid = id.split('.').join('_');
3446
3684
  const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
3447
3685
  const image = `<img src="${img_src}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
3448
- nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
3686
+ nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
3449
3687
  battery_cls = getBatteryCls(dev.battery),
3450
3688
  lqi_cls = getLQICls(dev.link_quality),
3451
3689
  unconnected_icon = (groupImage ? (groupstatus ? '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>' : '<div class="col tool"><i class="material-icons icon-red">cancel</i></div>') :'<div class="col tool"><i class="material-icons icon-red">leak_remove</i></div>'),
3452
3690
  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>` : '',
3453
3691
  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 ? unconnected_icon : ''),
3454
3692
  //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>`),
3455
- //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>' : '',
3456
3693
  //infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
3457
3694
  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>` : '';
3458
3695
  const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => {
@@ -3502,13 +3739,19 @@ function getDashCard(dev, groupImage, groupstatus) {
3502
3739
  }).join('') : '';
3503
3740
  const dashCard = `
3504
3741
  <div class="card-content zcard ${isActive ? '' : 'bg_red'}">
3505
- <div class="flip" style="cursor: pointer">
3742
+ <div style="cursor: pointer">
3743
+ <span class="top right small" style="border-radius: 50%">
3744
+ ${device_queryBtn}
3745
+ ${permitJoinBtn}
3746
+ </span>
3747
+ <div class="flip">
3506
3748
  <span class="top right small" style="border-radius: 50%">
3507
3749
  ${idleTime}
3508
3750
  ${battery}
3509
3751
  ${lq}
3510
3752
  </span>
3511
- <span class="card-title truncate">${title}</span>
3753
+ <span class="card-title truncate">${title}</span>
3754
+ </div>
3512
3755
  </div>
3513
3756
  <i class="left">${image}</i>
3514
3757
  <div style="min-height:88px; font-size: 0.8em; height: 130px; overflow-y: auto" class="truncate">
@@ -3643,7 +3886,7 @@ function reconfigureDevice(id) {
3643
3886
  }
3644
3887
  }
3645
3888
  });
3646
- showWaitingDialog('Device is being reconfigure', 30);
3889
+ showWaitingDialog('Device is being reconfigured', 30);
3647
3890
  }
3648
3891
 
3649
3892
  const warnLevel = {
@@ -3656,34 +3899,34 @@ function validateConfigData(key, val) {
3656
3899
  if (validatableKeys.indexOf(key) < 0 || !val) return;
3657
3900
  if (warnLevel[key]) {
3658
3901
  if (warnLevel[key](val)) {
3659
- console.warn(`warning set for ${key} (${val})`)
3902
+ //console.warn(`warning set for ${key} (${val})`)
3660
3903
  $(`#${key}_ALERT`).removeClass('hide')
3661
3904
  } else $(`#${key}_ALERT`).addClass('hide')
3662
3905
  }
3663
3906
  if (nvRamBackup[key]) {
3664
- console.warn(`value of ${key} is ${val} (${nvRamBackup[key]})`);
3907
+ //console.warn(`value of ${key} is ${val} (${nvRamBackup[key]})`);
3665
3908
  if ((typeof val == 'string' && typeof nvRamBackup[key] == 'string' && val.toLowerCase == nvRamBackup[key].toLowerCase) || val == nvRamBackup[key])
3666
3909
  {
3667
- console.warn(`ok set for ${key} (${val})`)
3910
+ //console.warn(`ok set for ${key} (${val})`)
3668
3911
  $(`#${key}_OK`).removeClass('hide')
3669
3912
  $(`#${key}_NOK`).addClass('hide')
3670
3913
  }
3671
3914
  else
3672
3915
  {
3673
- console.warn(`nok set for ${key} (${val})`)
3916
+ //console.warn(`nok set for ${key} (${val})`)
3674
3917
  $(`#${key}_OK`).addClass('hide')
3675
3918
  $(`#${key}_NOK`).removeClass('hide')
3676
3919
  }
3677
3920
  }
3678
3921
  else {
3679
- console.warn(`noval set for ${key} (${val})`)
3922
+ //console.warn(`noval set for ${key} (${val})`)
3680
3923
  $(`#${key}_OK`).addClass('hide')
3681
3924
  $(`#${key}_NOK`).addClass('hide')
3682
3925
  }
3683
3926
  }
3684
3927
 
3685
3928
  function validateNVRamBackup(update, src) {
3686
- console.warn('validateNVRam');
3929
+ //console.warn('validateNVRam');
3687
3930
  const validatedKeys = src ? [src] : validatableKeys;
3688
3931
  const validator = {};
3689
3932
  for (const key of validatedKeys) {
@@ -3703,9 +3946,11 @@ function validateNVRamBackup(update, src) {
3703
3946
  function readNVRamBackup(update) {
3704
3947
  console.warn('read nvRam')
3705
3948
  sendTo(namespace, 'readNVRam', {}, function(msg) {
3949
+ console.warn(JSON.stringify(msg));
3706
3950
  if (msg) {
3707
3951
  if (msg.error && update) {
3708
- showMessages(msg.error, _('Error'));
3952
+ if (msg.error.includes('ENOENT')) showMessage('Unable to read nvRam backup - no backup available.',_('Error'))
3953
+ else showMessage(msg.error, _('Error'));
3709
3954
  delete msg.error;
3710
3955
  }
3711
3956
  nvRamBackup = msg;