iobroker.zigbee 3.1.2 → 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
@@ -60,9 +60,9 @@ const networkOptions = {
60
60
 
61
61
 
62
62
  const savedSettings = [
63
- 'port', 'panID', 'channel', 'disableLed', 'countDown', 'groups', 'extPanID', 'precfgkey', 'transmitPower',
64
- 'adapterType', 'debugHerdsman', 'disableBackup', 'disablePing', 'external', 'startWithInconsistent',
65
- 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart', 'readAtAnnounce', 'startReadDelay', 'readAllAtStart',
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'
66
66
  ];
67
67
 
68
68
  function getDeviceByID(ID) {
@@ -78,7 +78,7 @@ function getDeviceByID(ID) {
78
78
  function getDevice(ieeeAddr) {
79
79
  return devices.find((devInfo) => {
80
80
  try {
81
- return devInfo.info.device._ieeeAddr == ieeeAddr;
81
+ return devInfo.info.device.ieee == ieeeAddr;
82
82
  } catch (e) {
83
83
  //console.log("No dev with ieee " + ieeeAddr);
84
84
  }
@@ -89,7 +89,7 @@ function getDevice(ieeeAddr) {
89
89
  function getDeviceByNetwork(nwk) {
90
90
  return devices.find((devInfo) => {
91
91
  try {
92
- return devInfo.info.device._networkAddress == nwk;
92
+ return devInfo.info.device.nwk == nwk;
93
93
  } catch (e) {
94
94
  //console.log("No dev with nwkAddr " + nwk);
95
95
  }
@@ -120,7 +120,8 @@ function getCoordinatorCard(dev) {
120
120
  rid = id.split('.').join('_'),
121
121
  image = `<img src="${img_src}" width="80px">`,
122
122
  paired = '',
123
- 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>`,
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>`,
124
125
  lqi_cls = dev ? getLQICls(dev.link_quality) : -1,
125
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>` : '',
126
127
  info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
@@ -136,13 +137,15 @@ function getCoordinatorCard(dev) {
136
137
  <li><span class="label">ZHC / ZH:</span><span>${coordinatorinfo.converters} / ${coordinatorinfo.herdsman}</span></li>
137
138
  </ul>
138
139
  </div>`,
139
- permitJoinBtn = (dev && 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>`,
140
142
  card = `<div id="${id}" class="device">
141
143
  <div class="card hoverable">
142
144
  <div class="card-content zcard">
143
145
  <span class="top right small" style="border-radius: 50%">
144
146
  ${lq}
145
147
  ${status}
148
+ ${permitJoinBtn}
146
149
  </span>
147
150
  <!--/a--!>
148
151
  <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
@@ -150,11 +153,6 @@ function getCoordinatorCard(dev) {
150
153
  ${info}
151
154
  <div class="footer right-align"></div>
152
155
  </div>
153
- <div class="card-action">
154
- <div class="card-reveal-buttons">
155
- ${permitJoinBtn}
156
- </div>
157
- </div>
158
156
  </div>
159
157
  </div>`;
160
158
  return card;
@@ -175,9 +173,10 @@ function getGroupCard(dev) {
175
173
  }
176
174
  }
177
175
  devGroups[numid] = dev;
176
+ const roomInfo = rooms.length ? `<li><span class="labelinfo">rooms:</span><span>${rooms.join(',') || ''}</span></li>` : '';
178
177
  const room = rooms.join(',') || '&nbsp';
179
178
  let memberCount = 0;
180
- 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">
181
180
  <ul>`;
182
181
  info = info.concat(`<li><span class="labelinfo">Group ${numid}</span></li>`);
183
182
  if (dev.memberinfo === undefined) {
@@ -189,7 +188,7 @@ function getGroupCard(dev) {
189
188
  memberCount = (dev.memberinfo.length < 8 ? dev.memberinfo.length : 7);
190
189
  }
191
190
  ;
192
- info = info.concat(` </ul>
191
+ info = info.concat(` ${roomInfo}</ul>
193
192
  </div>`);
194
193
  const image = `<img src="${dev.common.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
195
194
  const dashCard = getDashCard(dev, dev.common.icon, memberCount > 0);
@@ -207,11 +206,11 @@ function getGroupCard(dev) {
207
206
  </div>
208
207
  <i class="left">${image}</i>
209
208
  ${info}
209
+
210
210
  <div class="footer right-align"></div>
211
211
  </div>
212
212
  <div class="card-action">
213
213
  <div class="card-reveal-buttons">
214
- <span class="left" style="padding-top:8px">${room}</span>
215
214
  <button name="deletegrp" class="right btn-flat btn-small">
216
215
  <i class="material-icons icon-black">delete</i>
217
216
  </button>
@@ -254,12 +253,13 @@ function getCard(dev) {
254
253
  rooms.push(dev.rooms[r]);
255
254
  }
256
255
  }
257
- const room = rooms.join(',') || '&nbsp';
258
256
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
259
257
  const rid = id.split('.').join('_');
260
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>` : '';
261
261
  const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
262
- nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
262
+ nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
263
263
  battery_cls = (isActive ? getBatteryCls(dev.battery) : ''),
264
264
  lqi_cls = getLQICls(dev.link_quality),
265
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>` : '',
@@ -272,12 +272,14 @@ function getCard(dev) {
272
272
  <li><span class="labelinfo">ieee:</span><span>0x${ieee}</span></li>
273
273
  <li><span class="labelinfo">nwk:</span><span>${(nwk) ? nwk.toString() + ' (0x' + nwk.toString(16) + ')' : ''}</span></li>
274
274
  <li><span class="labelinfo">model:</span><span>${modelUrl}</span></li>
275
- <li><span class="labelinfo">groups:</span><span>${dev.groupNames || ''}</span></li>
275
+ ${groupInfo}
276
+ ${roomInfo}
276
277
  </ul>
277
278
  </div>`,
278
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>`,
279
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>`,
280
281
  infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
282
+
281
283
  const dashCard = getDashCard(dev);
282
284
  const card = `<div id="${id}" class="device">
283
285
  <div class="card hoverable flipable ${isActive ? '' : 'bg_red'}">
@@ -300,7 +302,7 @@ function getCard(dev) {
300
302
  <div class="card-action">
301
303
  <div class="card-reveal-buttons">
302
304
  ${infoBtn}
303
- <span class="left" style="padding-top:8px">${room}</span>
305
+
304
306
  <span class="left fw_info"></span>
305
307
  <button name="delete" class="right btn-flat btn-small tooltipped" title="Delete">
306
308
  <i class="material-icons icon-red">delete</i>
@@ -385,7 +387,7 @@ function deleteConfirmation(id, name) {
385
387
  $('#modaldelete a.btn[name=\'yes\']').unbind('click');
386
388
  $('#modaldelete a.btn[name=\'yes\']').click(() => {
387
389
  const force = $('#force').prop('checked');
388
- deleteDevice(id, force);
390
+ deleteZigbeeDevice(id, force);
389
391
  });
390
392
  $('#modaldelete').modal('open');
391
393
  Materialize.updateTextFields();
@@ -444,29 +446,50 @@ function editName(id, name) {
444
446
 
445
447
  const device_options = {};
446
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 = [];
447
453
 
448
454
  function removeOption(k) {
449
- 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
+ }
450
460
  }
451
461
 
452
462
  function addOption() {
453
463
  let idx=1;
454
464
  let key = '';
465
+ const optionName = $('#option_Selector').val();
466
+ console.warn(`option name is ${optionName}`);
455
467
  do {
456
468
  key = `o${idx++}`;
457
469
  }
458
470
  while (device_options.hasOwnProperty(key));
459
- 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);
460
476
  }
461
477
 
462
- 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
+ }
463
486
  const html_options=[];
464
487
 
465
- console.warn(`device_options is ${JSON.stringify(device_options)}`)
488
+ console.warn(`option_Selector is ${JSON.stringify(device_options)}`)
466
489
 
467
490
  for (const k in device_options) {
468
491
  html_options.push(`<div class="row">`);
469
- 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>`)
470
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>`)
471
494
  html_options.push(`<div class="col"><a id="option_rem_${k}" class='btn' ><i class="material-icons">remove_circle</i></a></div>`);
472
495
  html_options.push(`</div>`)
@@ -480,11 +503,11 @@ function editName(id, name) {
480
503
  $(`#option_key_${k}`).val(device_options[k].key);
481
504
  $(`#option_value_${k}`).val(device_options[k].value);
482
505
  $(`#option_rem_${k}`).unbind('click');
483
- $(`#option_rem_${k}`).click(() => { removeOption(k); updateOptions() });
506
+ $(`#option_rem_${k}`).click(() => { removeOption(k); updateOptions(availableOptions) });
484
507
  }
485
508
  }
486
509
  else {
487
- $('#modaledit').find('.options_available').addClass('hide');
510
+ if (candidates.length == 0) $('#modaledit').find('.options_available').addClass('hide');
488
511
  }
489
512
  }
490
513
 
@@ -513,18 +536,15 @@ function editName(id, name) {
513
536
 
514
537
 
515
538
 
516
- console.warn('editName called with ' + id + ' and ' + name);
517
- const dev = devices.find((d) => d._id == id);
518
- $('#modaledit').find('input[id=\'d_name\']').val(name);
519
- const groupables = [];
520
539
  if (dev && dev.info && dev.info.endpoints) {
521
540
  for (const ep of dev.info.endpoints) {
522
- if (ep.inputClusters.includes(4)) {
541
+ if (ep.input_clusters.includes(4)) {
523
542
  groupables.push({epid: EndPointIDfromEndPoint(ep), ep: ep, memberOf: []});
524
543
  }
525
544
  }
526
545
  }
527
546
  const numEP = groupables.length;
547
+ const availableOptions = (dev.info.mapped ? dev.info.mapped.options.slice() || []:[]);
528
548
 
529
549
  if (numEP > 0) {
530
550
  $('#modaledit').find('.groups_available').removeClass('hide');
@@ -571,7 +591,7 @@ function editName(id, name) {
571
591
  sendTo(namespace, 'getLocalConfigItems', { target:id, global:false, key:'options' }, function (msg) {
572
592
  if (msg) {
573
593
  if (msg.error) showMessage(msg.error, '_Error');
574
- console.warn(`return is ${msg}`)
594
+ console.warn(`return is ${JSON.stringify(msg)}`)
575
595
  Object.keys(device_options).forEach(key => delete device_options[key]);
576
596
  Object.keys(received_options).forEach(key => delete received_options[key]);
577
597
  if (typeof msg.options === 'object') {
@@ -579,12 +599,16 @@ function editName(id, name) {
579
599
  let cnt = 1;
580
600
  for (const key in msg.options)
581
601
  {
602
+ const idx = availableOptions.indexOf(key);
603
+ console.warn(`key ${key} : index : ${idx}`);
604
+ if (idx > -1) availableOptions.splice(idx,1);
582
605
  received_options[key]=msg.options[key];
583
606
  device_options[`o${cnt}`] = { key:key, value:msg.options[key]}
584
607
  cnt++;
585
608
  }
586
609
  }
587
- updateOptions();
610
+ console.warn(`avo ${JSON.stringify(availableOptions)}, mapped: ${JSON.stringify(dev.info.mapped.options)}`);
611
+ updateOptions(availableOptions);
588
612
 
589
613
  } else showMessage('callback without message');
590
614
  });
@@ -593,7 +617,7 @@ function editName(id, name) {
593
617
  $('#modaledit a.btn[name=\'add_options\']').click(() => {
594
618
  getOptionsFromUI(device_options, received_options);
595
619
  addOption();
596
- updateOptions()
620
+ updateOptions(availableOptions)
597
621
  });
598
622
  $('#modaledit a.btn[name=\'save\']').click(() => {
599
623
  const newName = $('#modaledit').find('input[id=\'d_name\']').val();
@@ -634,8 +658,8 @@ function GenerateGroupChange(oldmembers, newmembers) {
634
658
  return grpchng;
635
659
  }
636
660
 
637
- function deleteDevice(id, force) {
638
- sendTo(namespace, 'deleteDevice', {id: id, force: force}, function (msg) {
661
+ function deleteZigbeeDevice(id, force) {
662
+ sendTo(namespace, 'deleteZigbeeDevice', {id: id, force: force}, function (msg) {
639
663
  closeWaitingDialog();
640
664
  if (msg) {
641
665
  if (msg.error) {
@@ -719,12 +743,12 @@ function showDevices() {
719
743
  html += card;
720
744
  continue;
721
745
  };
722
- if (d.info && d.info.device && d.info.device._type == 'Coordinator') {
746
+ if (d.info && d.info.device && d.info.device.type == 'Coordinator') {
723
747
  hasCoordinator=true;
724
748
  const card = getCoordinatorCard(d);
725
749
  html += card;
726
750
  } else {
727
- //if (d.groups && d.info && d.info.device._type == "Router") {
751
+ //if (d.groups && d.info && d.info.device.type == "Router") {
728
752
  if (d.groups) {
729
753
  //devGroups[d._id] = d.groups;
730
754
  if (typeof d.groups.map == 'function') {
@@ -819,13 +843,18 @@ function showDevices() {
819
843
  const name = getDevName(dev_block);
820
844
  editGroup(id, name, false);
821
845
  });
822
- $('button.btn-floating[name=\'join\']').click(function () {
846
+ $('button[name=\'joinCard\']').click(function () {
823
847
  const dev_block = $(this).parents('div.device');
824
848
  if (!$('#pairing').hasClass('pulse')) {
825
849
  joinProcess(getDevId(dev_block));
826
850
  }
827
851
  showPairingProcess();
828
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
+ }); });
829
858
  $('#modalpairing a.btn[name=\'extendpairing\']').click(function () {
830
859
  letsPairing();
831
860
  });
@@ -856,6 +885,14 @@ function showDevices() {
856
885
  translateAll();
857
886
  }
858
887
 
888
+ function downloadIcons() {
889
+ sendTo(namespace, 'downloadIcons', {}, function (msg) {
890
+ if (msg && msg.msg) {
891
+ showMessage(msg.msg, _('Result'));
892
+ }
893
+ });
894
+ }
895
+
859
896
  function checkFwUpdate() {
860
897
  const deviceCards = getDeviceCards();
861
898
  const getFwInfoNode = function (deviceCard) {
@@ -1080,6 +1117,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1080
1117
  const Html = [];
1081
1118
  const filterSet = new Set();
1082
1119
  let isodd = true;
1120
+ const buttonList = [];
1083
1121
  if (dbgMsghide.has('i_'+devID)) {
1084
1122
  console.warn('in all filtered out')
1085
1123
  Html.push('&nbsp;')
@@ -1094,8 +1132,11 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1094
1132
  const redText = (item.errors && item.errors.length > 0 ? ' id="dbgred"' : '');
1095
1133
  idx--;
1096
1134
  const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
1097
- if (idx==0)
1098
- 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
+ }
1099
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>`);
1100
1141
  IHtml.unshift(...LHtml)
1101
1142
  }
@@ -1108,7 +1149,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1108
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>`
1109
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>`
1110
1151
  const dataHide = dbgMsgfilter.has('hi_'+devID) ? 'Data hidden' : '&nbsp;';
1111
- 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 };
1112
1153
  }
1113
1154
 
1114
1155
 
@@ -1116,6 +1157,7 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1116
1157
  const Html = [];
1117
1158
  const filterSet = new Set();
1118
1159
  let isodd=true;
1160
+ const buttonList = [];
1119
1161
  if (dbgMsghide.has('o_'+devID)) {
1120
1162
  console.warn('out all filtered out')
1121
1163
  Html.push('&nbsp;')
@@ -1131,8 +1173,11 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1131
1173
  const redText = (item.errors && item.errors.length > 0 ? ' id="dbgred"' : '');
1132
1174
  const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
1133
1175
  idx--;
1134
- if (idx==0)
1135
- 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
+ }
1136
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>`);
1137
1182
  IHtml.unshift(...LHtml);
1138
1183
 
@@ -1146,22 +1191,24 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1146
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>`
1147
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>`
1148
1193
  const dataHide = dbgMsgfilter.has('ho_'+devID) ? 'Data hidden' : '&nbsp;';
1149
- 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};
1150
1195
  }
1151
1196
 
1152
1197
 
1153
1198
  function displayDebugMessages(msg) {
1154
1199
  const buttonNames = [];
1200
+ const idButtons = [];
1155
1201
  if (msg.byId) {
1156
1202
  const dbgData = msg.byId;
1157
1203
  const keys = Object.keys(dbgData);
1158
1204
  const keylength = keys.length;
1159
1205
  const Html = [];
1160
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>`;
1161
- 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>`;
1162
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>`;
1163
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>`;
1164
- 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>`);
1165
1212
  if (!keylength) {
1166
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>')
1167
1214
  $('#dbg_data_list').html(Html.join(''));
@@ -1176,20 +1223,28 @@ function displayDebugMessages(msg) {
1176
1223
  const modelUrl = (type_url === 'unknown') ? 'unknown' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${image}</a>`;
1177
1224
  const devName = (dev && dev.common && dev.common.name) ? dev.common.name : 'unnamed';
1178
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>`;
1179
1227
  buttonNames.push(devID);
1180
- 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>`);
1181
1229
  if (dbgData[devID].IN.length > 0) {
1182
- 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)
1183
1233
  }
1184
1234
  if (dbgData[devID].OUT.length > 0) {
1185
- 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)
1186
1238
  }
1187
1239
  Html.push('</tbody></table></li>');
1188
1240
  }
1189
1241
  $('#dbg_data_list').html(Html.join(''));
1190
1242
  }
1191
1243
  $(`#e_all`).click(function () {
1192
- getDebugMessages();
1244
+ getDebugMessages(false);
1245
+ });
1246
+ $(`#d_all`).click(function () {
1247
+ getDebugMessages(true, 'all');
1193
1248
  });
1194
1249
  $(`#l_all`).click(function () {
1195
1250
  debugInLog = !debugInLog;
@@ -1221,7 +1276,10 @@ function displayDebugMessages(msg) {
1221
1276
  });
1222
1277
  for (const b of buttonNames) {
1223
1278
  $(`#e_${b}`).click(function () {
1224
- getDebugMessages();
1279
+ getDebugMessages(false);
1280
+ });
1281
+ $(`#d_${b}`).click(function () {
1282
+ getDebugMessages(true, b);
1225
1283
  });
1226
1284
  $(`#o_${b}`).click(function () {
1227
1285
  if (dbgMsgfilter.has(`o_${b}`)) dbgMsgfilter.delete(`o_${b}`); else dbgMsgfilter.add(`o_${b}`);
@@ -1240,76 +1298,167 @@ function displayDebugMessages(msg) {
1240
1298
  displayDebugMessages(debugMessages);
1241
1299
  });
1242
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
+ }
1243
1364
  }
1365
+ console.warn(`nothing to show`);
1366
+
1367
+
1244
1368
  }
1245
1369
 
1246
- function getDebugMessages() {
1247
- sendTo(namespace, 'getDebugMessages', { inlog: debugInLog }, function(msg) {
1370
+ function getDebugMessages(deleteBeforeRead, deleteSelected) {
1371
+ sendTo(namespace, 'getDebugMessages', { inlog: debugInLog, del:deleteBeforeRead ? deleteSelected : '' }, function(msg) {
1248
1372
  debugMessages = msg;
1249
1373
  if (msg) displayDebugMessages(debugMessages)
1250
1374
  })
1251
1375
  }
1252
1376
 
1253
-
1377
+ const lockout = {
1378
+ timeoutid:undefined,
1379
+ isActive:false,
1380
+ };
1254
1381
  function getDevices() {
1255
1382
  console.warn('getDevices called')
1256
- sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
1257
- console.warn(`getCoordinatorInfo returned ${JSON.stringify(msg)}`)
1258
- if (msg) {
1259
- console.warn(JSON.stringify(msg))
1260
- if (msg.error) {
1261
- errorData.push(msg.error);
1262
- delete msg.error;
1263
- isHerdsmanRunning = false;
1264
- } else {
1265
- isHerdsmanRunning = true;
1266
- }
1267
- coordinatorinfo = msg;
1268
- updateStartButton()
1269
- }
1270
- sendTo(namespace, 'getDevices', {}, function (msg) {
1271
- if (msg) {
1272
- devices = msg.devices ? msg.devices : [];
1273
- // check if stashed error messages are sent alongside
1274
- if (msg.clean)
1275
- $('#state_cleanup_btn').removeClass('hide');
1276
- else
1277
- $('#state_cleanup_btn').addClass('hide');
1278
- if (msg.errors && msg.errors.length > 0) {
1279
- $('#show_errors_btn').removeClass('hide');
1280
- errorData = msg.errors;
1281
- }
1282
- else {
1283
- $('#show_errors_btn').addClass('hide');
1284
- }
1285
1383
 
1286
- //check if debug messages are sent alongside
1287
- if (msg && typeof (msg.debugDevices == 'array')) {
1288
- debugDevices = msg.debugDevices;
1289
- console.warn('debug devices is sent')
1290
- }
1291
- else
1292
- debugDevices = [];
1293
- if (debugMessages.byId) {
1294
- debugMessages.byId = msg;
1295
- if (msg) displayDebugMessages(debugMessages)
1296
- }
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))
1297
1389
  if (msg.error) {
1298
1390
  errorData.push(msg.error);
1391
+ delete msg.error;
1299
1392
  isHerdsmanRunning = false;
1300
- updateStartButton();
1301
- showDevices();
1302
1393
  } else {
1303
1394
  isHerdsmanRunning = true;
1304
- updateStartButton();
1305
- showDevices();
1306
- getDebugMessages();
1307
- getExclude();
1308
- getBinding();
1309
1395
  }
1396
+ coordinatorinfo = msg;
1397
+ updateStartButton()
1310
1398
  }
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
+ });
1311
1448
  });
1312
- });
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
+
1313
1462
  }
1314
1463
 
1315
1464
  function getNamedColors() {
@@ -1331,10 +1480,10 @@ function getDeviceCard(devId) {
1331
1480
  return $('#devices').find(`div[id='${namespace}.${devId}']`);
1332
1481
  }
1333
1482
 
1334
- function getMap() {
1483
+ function getMap(rebuild) {
1335
1484
  $('#refresh').addClass('disabled');
1336
1485
  if (isHerdsmanRunning) {
1337
- sendTo(namespace, 'getMap', {}, function (msg) {
1486
+ sendTo(namespace, 'getMap', { forcebuild:rebuild}, function (msg) {
1338
1487
  $('#refresh').removeClass('disabled');
1339
1488
  if (msg) {
1340
1489
  if (msg.error) {
@@ -1404,6 +1553,7 @@ function load(settings, onChange) {
1404
1553
  settings.baudRate = 115200;
1405
1554
  }
1406
1555
  if (settings.autostart === undefined) settings.autostart = false;
1556
+ if (typeof settings.pingCluster != 'string') settings.pingCluster = settings.disablePing ? 'off' : 'default';
1407
1557
 
1408
1558
  // example: select elements with id=key and class=value and insert value
1409
1559
  for (const key in settings) {
@@ -1477,6 +1627,9 @@ function load(settings, onChange) {
1477
1627
  $('#show_errors_btn').click(function () {
1478
1628
  showMessage(errorData.join('<br>'), 'Stashed error messages');
1479
1629
  });
1630
+ $('#download_icons_btn').click(function () {
1631
+ showMessage(downloadIcons());
1632
+ });
1480
1633
  $('#fw_check_btn').click(function () {
1481
1634
  checkFwUpdate();
1482
1635
  });
@@ -1493,7 +1646,11 @@ function load(settings, onChange) {
1493
1646
  });
1494
1647
 
1495
1648
  $('#refresh').click(function () {
1496
- getMap();
1649
+ getMap(false);
1650
+ });
1651
+ $('#regenerate').click(function () {
1652
+ getMap(true);
1653
+ $('#modalviewconfig').modal('close');
1497
1654
  });
1498
1655
 
1499
1656
  $('#reset-btn').click(function () {
@@ -1505,7 +1662,9 @@ function load(settings, onChange) {
1505
1662
  });
1506
1663
 
1507
1664
  $('#ErrorNotificationBtn').click(function () {
1508
- if (!isHerdsmanRunning) showMessage('The zigbee subsystem is not running. Please ensure that the configuration is correct and either start the subsystem manually from the hardware tab or set it to automatically start in the settings.', _('Zigbee subsystem error'));
1665
+ if (!isHerdsmanRunning) {
1666
+ doTestStart(!isHerdsmanRunning, true);
1667
+ }
1509
1668
  })
1510
1669
 
1511
1670
  $('#viewconfig').click(function () {
@@ -1619,10 +1778,10 @@ function showPairingProcess() {
1619
1778
  Materialize.updateTextFields();
1620
1779
  }
1621
1780
 
1622
- function doTestStart(start) {
1781
+ function doTestStart(start, interactive) {
1623
1782
  updateStartButton(true);
1624
1783
  if (start) {
1625
- const ovr = { extPanID:$('#extPanID.value').val(),
1784
+ const ovr = interactive ? {} : { extPanID:$('#extPanID.value').val(),
1626
1785
  panID: $('#PanID.value').val(),
1627
1786
  channel: $('#channel.value').val(),
1628
1787
  port: $('#port.value').val(),
@@ -1633,12 +1792,18 @@ function doTestStart(start) {
1633
1792
  };
1634
1793
  // $('#testStartStart').addClass('disabled');
1635
1794
  messages = [];
1795
+ if (interactive) showWaitingDialog('Trying to start the zigbee subsystem manually', 120);
1636
1796
  sendTo(namespace, 'testConnect', { start:true, zigbeeOptions:ovr }, function(msg) {
1637
1797
  if (msg) {
1798
+ closeWaitingDialog();
1799
+ isHerdsmanRunning = msg.status;
1800
+ updateStartButton(false);
1638
1801
  if (msg.status)
1639
1802
  $('#testStartStop').removeClass('disabled');
1640
- else
1641
- $('#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
+ }
1642
1807
  }
1643
1808
  })
1644
1809
  }
@@ -1689,8 +1854,8 @@ function updateStartButton(block) {
1689
1854
  $('#deleteNVRam-btn').addClass('disabled');
1690
1855
  $('#ErrorNotificationBtn').removeClass('hide')
1691
1856
  $('#ErrorNotificationBtn').removeClass('blinking')
1692
- $('#ErrorNotificationIcon').removeClass('icon-red')
1693
- $('#ErrorNotificationIcon').addClass('icon-orange')
1857
+ $('#ErrorNotificationBtn').removeClass('red')
1858
+ $('#ErrorNotificationBtn').addClass('orange')
1694
1859
  return;
1695
1860
  }
1696
1861
  if (isHerdsmanRunning)
@@ -1707,8 +1872,8 @@ function updateStartButton(block) {
1707
1872
  //$('#pairing').removeClass('hide');
1708
1873
  }
1709
1874
  else {
1710
- $('#ErrorNotificationIcon').addClass('icon-red')
1711
- $('#ErrorNotificationIcon').removeClass('icon-orange')
1875
+ $('#ErrorNotificationBtn').addClass('red')
1876
+ $('#ErrorNotificationBtn').removeClass('orange')
1712
1877
  $('#ErrorNotificationBtn').removeClass('hide')
1713
1878
  $('#ErrorNotificationBtn').addClass('blinking');
1714
1879
  $('#show_test_run').removeClass('disabled');
@@ -1862,7 +2027,7 @@ function showNetworkMap(devices, map) {
1862
2027
  borderWidth: 1,
1863
2028
  borderWidthSelected: 4,
1864
2029
  };
1865
- if (dev.info && dev.info.device._type == 'Coordinator') {
2030
+ if (dev.info && dev.info.device.type == 'Coordinator') {
1866
2031
  // node.shape = 'star';
1867
2032
  node.image = 'zigbee.png';
1868
2033
  node.label = 'Coordinator';
@@ -2022,7 +2187,7 @@ function showNetworkMap(devices, map) {
2022
2187
 
2023
2188
  if (node) {
2024
2189
  node.font = {color: '#ff0000'};
2025
- if (dev.info && dev.info.device._type == 'Coordinator') {
2190
+ if (dev.info && dev.info.device && dev.info.device.type == 'Coordinator') {
2026
2191
  node.font = {color: '#00ff00'};
2027
2192
  }
2028
2193
  nodesArray.push(node);
@@ -2187,35 +2352,37 @@ function loadDeveloperTab() {
2187
2352
  updateSelect('#dev', devices,
2188
2353
  function (key, device) {
2189
2354
  if (device.hasOwnProperty('info')) {
2190
- if (device.info.device._type === 'Coordinator') {
2355
+ if (device.info.device && device.info.device.type === 'Coordinator') {
2191
2356
  return null;
2192
2357
  }
2193
- return `${device.common.name} (${device.info.name})`;
2358
+ if (device.common.type === 'group') return null;
2359
+ return `${device.common.name} (${device.info.device.ieee})`;
2194
2360
  } else { // fallback if device in list but not paired
2195
2361
  return device.common.name + ' ' + device.native.id;
2196
2362
  }
2197
2363
  },
2198
2364
  function (key, device) {
2199
- return device._id;
2200
- });
2201
- // add groups to device selector
2202
- const groupList = [];
2203
- for (const key in groups) {
2204
- groupList.push({
2205
- _id: namespace + '.' + key.toString(16).padStart(16, '0'),
2206
- groupId: key,
2207
- groupName: groups[key]
2365
+ return device.native.id;
2208
2366
  });
2209
- }
2210
- updateSelect('#dev', groupList,
2211
- function (key, device) {
2212
- return 'Group ' + device.groupId + ': ' + device.groupName;
2213
- },
2214
- function (key, device) {
2215
- return device._id;
2216
- }, 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);
2217
2383
 
2218
- // fill cid, cmd, type selector
2384
+ // fill cid, cmd, type selector
2385
+ */
2219
2386
  populateSelector('#cid', 'cidList');
2220
2387
  populateSelector('#cmd', 'cmdListFoundation', this.value);
2221
2388
  populateSelector('#type', 'typeList', this.value);
@@ -2296,10 +2463,11 @@ function loadDeveloperTab() {
2296
2463
  }
2297
2464
 
2298
2465
  const device = devices.find(obj => {
2299
- return obj._id === this.value;
2466
+ return this.value ===obj.native.id;
2300
2467
  });
2468
+ console.warn(`dev selector: ${this.selectedIndex} ${this.value} ->${JSON.stringify(device)}`)
2301
2469
 
2302
- const epList = device ? device.info.device._endpoints : null;
2470
+ const epList = device ? device.info.endpoints : null;
2303
2471
  updateSelect('#ep', epList,
2304
2472
  function (key, ep) {
2305
2473
  return ep.ID;
@@ -2735,14 +2903,14 @@ function prepareBindingDialog(bindObj) {
2735
2903
  return 'Select source device';
2736
2904
  }
2737
2905
  if (device.hasOwnProperty('info')) {
2738
- if (device.info.device._type === 'Coordinator') {
2906
+ if (device.info.device && device.info.device.type === 'Coordinator') {
2739
2907
  return null;
2740
2908
  }
2741
2909
  // check for output clusters
2742
2910
  let allow = false;
2743
2911
  for (const cluster of allowClusters) {
2744
2912
  if (device.info.endpoints) for (const ep of device.info.endpoints) {
2745
- if (ep.outputClusters.includes(cluster)) {
2913
+ if (ep.output_clusters.includes(cluster)) {
2746
2914
  allow = true;
2747
2915
  break;
2748
2916
  }
@@ -2786,14 +2954,14 @@ function prepareBindingDialog(bindObj) {
2786
2954
  return 'Select target device';
2787
2955
  }
2788
2956
  if (device.hasOwnProperty('info')) {
2789
- if (device.info.device._type === 'Coordinator') {
2957
+ if (device.info.device && device.info.device.type === 'Coordinator') {
2790
2958
  return null;
2791
2959
  }
2792
2960
  // check for input clusters
2793
2961
  let allow = false;
2794
2962
  for (const cluster of allowClusters) {
2795
2963
  if (device.info.endpoints) for (const ep of device.info.endpoints) {
2796
- if (ep.inputClusters.includes(cluster)) {
2964
+ if (ep.input_clusters.includes(cluster)) {
2797
2965
  allow = true;
2798
2966
  break;
2799
2967
  }
@@ -2837,7 +3005,7 @@ function prepareBindingDialog(bindObj) {
2837
3005
 
2838
3006
  const epList = device ? device.info.endpoints : [];
2839
3007
  const sClusterList = epList.map((ep) => {
2840
- const clusters = ep.outputClusters.map((cl) => {
3008
+ const clusters = ep.output_clusters.map((cl) => {
2841
3009
  return allowClusters.includes(cl) ? {ID: ep.ID + '_' + cl, name: allowClustersName[cl]} : null;
2842
3010
  }).filter((i) => {
2843
3011
  return i != null;
@@ -2863,7 +3031,7 @@ function prepareBindingDialog(bindObj) {
2863
3031
 
2864
3032
  const epList = device ? device.info.endpoints : [];
2865
3033
  const tClusterList = epList.map((ep) => {
2866
- const clusters = ep.inputClusters.map((cl) => {
3034
+ const clusters = ep.input_clusters.map((cl) => {
2867
3035
  return (allowClusters.includes(cl) && (!sourceCl || sourceCl == cl)) ? {
2868
3036
  ID: ep.ID + '_' + cl,
2869
3037
  name: allowClustersName[cl]
@@ -3096,12 +3264,13 @@ function genDevInfo(device) {
3096
3264
  //console.log(device);
3097
3265
  const dev = (device && device.info) ? device.info.device : undefined;
3098
3266
  const mapped = (device && device.info) ? device.info.mapped : undefined;
3267
+ const endpoints = (device && device.info) ? device.info.endpoints : [];
3099
3268
  if (!dev) return `<div class="truncate">No info</div>`;
3100
3269
  const genRow = function (name, value, refresh) {
3101
3270
  if (value === undefined) {
3102
3271
  return '';
3103
3272
  } else {
3104
- return `<li><span class="label">${name}:</span><span>${value}</span></li>`;
3273
+ return `<li><span class="label">${name.replace('_',' ')}:</span><span>${value}</span></li>`;
3105
3274
  }
3106
3275
  };
3107
3276
  const genRowValues = function (name, value) {
@@ -3109,66 +3278,68 @@ function genDevInfo(device) {
3109
3278
  return '';
3110
3279
  } else {
3111
3280
  let label = `${name}:`;
3112
- return value.map((val) => {
3113
- const row = `<li><span class="label">${label}</span><span>${val}</span></li>`;
3114
- label = '';
3115
- return row;
3116
- }).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
+ }
3117
3291
  }
3118
3292
  };
3119
3293
  const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(mapped.model)}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
3120
- const mappedInfo = (!mapped) ? '' :
3121
- `<div style="font-size: 0.9em">
3122
- <ul>
3123
- ${genRow('model', modelUrl)}
3124
- ${genRow('description', mapped.description)}
3125
- ${genRow('supports', mapped.supports)}
3126
- </ul>
3127
- </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
+ }
3128
3309
  let epInfo = '';
3129
- for (const epind in dev._endpoints) {
3130
- const ep = dev._endpoints[epind];
3310
+ for (const epind in endpoints) {
3311
+ const ep = endpoints[epind];
3312
+ console.warn(JSON.stringify(ep));
3131
3313
  epInfo +=
3132
3314
  `<div style="font-size: 0.9em" class="truncate">
3133
3315
  <ul>
3134
3316
  ${genRow('endpoint', ep.ID)}
3135
- ${genRow('profile', ep.profileID)}
3136
- ${genRowValues('input clusters', ep.inputClusters.map(findClName))}
3137
- ${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')}
3138
3320
  </ul>
3139
3321
  </div>`;
3140
3322
  }
3141
3323
  const imgSrc = device.icon || device.common.icon;
3142
3324
  const imgInfo = (imgSrc) ? `<img src=${imgSrc} width='150px' onerror="this.onerror=null;this.src='img/unavailable.png';"><div class="divider"></div>` : '';
3143
- const info =
3325
+ const info =[
3144
3326
  `<div class="col s12 m6 l6 xl6">
3145
3327
  ${imgInfo}
3146
- ${mappedInfo}
3328
+ ${mappedInfo.join('')}
3147
3329
  <div class="divider"></div>
3148
3330
  <div style="font-size: 0.9em" class="truncate">
3149
- <ul>
3150
- ${genRow('modelZigbee', dev._modelID)}
3151
- ${genRow('type', dev._type)}
3152
- ${genRow('ieee', dev.ieeeAddr)}
3153
- ${genRow('nwk', dev._networkAddress)}
3154
- ${genRow('manuf id', dev._manufacturerID)}
3155
- ${genRow('manufacturer', dev._manufacturerName)}
3156
- ${genRow('power', dev._powerSource)}
3157
- ${genRow('app version', dev._applicationVersion)}
3158
- ${genRow('hard version', dev._hardwareVersion)}
3159
- ${genRow('zcl version', dev._zclVersion)}
3160
- ${genRow('stack version', dev._stackVersion)}
3161
- ${genRow('date code', dev._dateCode)}
3162
- ${genRow('build', dev._softwareBuildID)}
3163
- ${genRow('interviewed', dev._interviewCompleted)}
3164
- ${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)}
3165
3336
  </ul>
3166
3337
  </div>
3167
3338
  </div>
3168
3339
  <div class="col s12 m6 l6 xl6">
3169
3340
  ${epInfo}
3170
- </div>`;
3171
- return info;
3341
+ </div>`);
3342
+ return info.join('');
3172
3343
  }
3173
3344
 
3174
3345
  function showDevInfo(id) {
@@ -3276,7 +3447,7 @@ function prepareExcludeDialog(excludeObj) {
3276
3447
  return 'Select model';
3277
3448
  }
3278
3449
  if (device.hasOwnProperty('info')) {
3279
- if (device.info.device._type == 'Coordinator') {
3450
+ if (device.info.device && device.info.device.type == 'Coordinator') {
3280
3451
  return null;
3281
3452
  }
3282
3453
  return device.common.type;
@@ -3507,17 +3678,18 @@ function getDashCard(dev, groupImage, groupstatus) {
3507
3678
  rooms = [],
3508
3679
  lang = systemLang || 'en';
3509
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>`;
3510
3683
  const rid = id.split('.').join('_');
3511
3684
  const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
3512
3685
  const image = `<img src="${img_src}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
3513
- nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
3686
+ nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
3514
3687
  battery_cls = getBatteryCls(dev.battery),
3515
3688
  lqi_cls = getLQICls(dev.link_quality),
3516
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>'),
3517
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>` : '',
3518
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 : ''),
3519
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>`),
3520
- //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>' : '',
3521
3693
  //infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
3522
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>` : '';
3523
3695
  const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => {
@@ -3567,13 +3739,19 @@ function getDashCard(dev, groupImage, groupstatus) {
3567
3739
  }).join('') : '';
3568
3740
  const dashCard = `
3569
3741
  <div class="card-content zcard ${isActive ? '' : 'bg_red'}">
3570
- <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">
3571
3748
  <span class="top right small" style="border-radius: 50%">
3572
3749
  ${idleTime}
3573
3750
  ${battery}
3574
3751
  ${lq}
3575
3752
  </span>
3576
- <span class="card-title truncate">${title}</span>
3753
+ <span class="card-title truncate">${title}</span>
3754
+ </div>
3577
3755
  </div>
3578
3756
  <i class="left">${image}</i>
3579
3757
  <div style="min-height:88px; font-size: 0.8em; height: 130px; overflow-y: auto" class="truncate">
@@ -3708,7 +3886,7 @@ function reconfigureDevice(id) {
3708
3886
  }
3709
3887
  }
3710
3888
  });
3711
- showWaitingDialog('Device is being reconfigure', 30);
3889
+ showWaitingDialog('Device is being reconfigured', 30);
3712
3890
  }
3713
3891
 
3714
3892
  const warnLevel = {