iobroker.zigbee 3.2.2 → 3.2.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/README.md CHANGED
@@ -154,6 +154,19 @@ You can thank the authors by these links:
154
154
 
155
155
  -----------------------------------------------------------------------------------------------------
156
156
  ## Changelog
157
+ ### 3.2.4 (2025-10-31)
158
+ * (asgothian) added missing state
159
+
160
+ ### 3.2.3 (2025-10-31)
161
+ * (asgothian) Improvements on debug UI
162
+ * (asgothian) Option 'resend_states' to publish state values to device on reconnect
163
+ * (asgothian) Improved group card
164
+ * (asgothian) Improved group info
165
+ * (asgothian) Modified coordinator card (2 sides)
166
+ * (asgothian) retry on error 25
167
+ * (asgothian) clear stashed error messages
168
+ * (asgothian) ZHC 25.50.0 or newer
169
+
157
170
  ### 3.2.2 (2025-10-27)
158
171
  * (asgothian) Bugfix on delete object.
159
172
  * (asgothian) improved device query.
package/admin/admin.js CHANGED
@@ -36,7 +36,7 @@ let devices = [],
36
36
  },
37
37
  cidList,
38
38
  shuffleInstance,
39
- errorData = [],
39
+ errorData = { errors:{}, unknownModels: {}},
40
40
  debugMessages = {},
41
41
  debugInLog = true,
42
42
  nvRamBackup = {},
@@ -64,7 +64,7 @@ const networkOptions = {
64
64
  const savedSettings = [
65
65
  'port', 'panID', 'channel', 'disableLed', 'countDown', 'groups', 'extPanID', 'precfgkey', 'transmitPower','useNewCompositeStates',
66
66
  'adapterType', 'debugHerdsman', 'disableBackup', 'external', 'startWithInconsistent','pingTimeout','listDevicesAtStart',
67
- 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart', 'readAtAnnounce', 'startReadDelay', 'readAllAtStart','pingCluster'
67
+ 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart', 'readAtAnnounce', 'startReadDelay', 'readAllAtStart','pingCluster','availableUpdateTime'
68
68
  ];
69
69
  const lockout = {
70
70
  timeoutid:undefined,
@@ -588,32 +588,59 @@ function getCoordinatorCard(dev) {
588
588
  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>` : '',
589
589
  info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
590
590
  <ul>
591
- <li><span class="label">type:</span><span>${coordinatorinfo.type}</span></li>
592
- <li><span class="label">version:</span><span>${coordinatorinfo.version}</span></li>
593
- <li><span class="label">revision:</span><span>${coordinatorinfo.revision}</span></li>
594
- <li><span class="label">port:</span><span>${coordinatorinfo.port}</span></li>
595
- <li><span class="label">channel:</span><span>${coordinatorinfo.channel}</span></li>
596
- <li><span class="label">------------</span><span>Software versions </span></li>
597
- <li><span class="label">adapter:</span><span>${coordinatorinfo.installedVersion}</span></li>
598
- <li><span class="label">installed from:</span><span>${coordinatorinfo.installSource}</span></li>
599
- <li><span class="label">ZHC / ZH:</span><span>${coordinatorinfo.converters} / ${coordinatorinfo.herdsman}</span></li>
591
+ <li><span class="label coordinator">type:</span><span>${coordinatorinfo.type}</span></li>
592
+ <li><span class="label coordinator">version:</span><span>${coordinatorinfo.version}</span></li>
593
+ <li><span class="label coordinator">revision:</span><span>${coordinatorinfo.revision}</span></li>
594
+ <li><span class="label coordinator">port:</span><span>${coordinatorinfo.port}</span></li>
595
+ <li><span class="label coordinator">channel:</span><span>${coordinatorinfo.channel}</span></li>
600
596
  </ul>
601
597
  </div>`,
602
598
  permitJoinBtn = '<div class="col tool"><button name="joinCard" class="waves-effect btn-small btn-flat right hoverable green tooltipped" title="open network"><i class="material-icons icon-green">leak_add</i></button></div>',
603
599
  //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>`,
604
600
  card = `<div id="${id}" class="device">
605
- <div class="card hoverable">
606
- <div class="card-content zcard">
607
- <span class="top right small" style="border-radius: 50%">
608
- ${lq}
609
- ${status}
610
- ${permitJoinBtn}
611
- </span>
612
- <!--/a--!>
613
- <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
614
- <i class="left">${image}</i>
615
- ${info}
616
- <div class="footer right-align"></div>
601
+ <div class="card hoverable flipable">
602
+ <div class="front face">
603
+ <div class="card-content zcard">
604
+ <div class="flip" style="cursor: pointer">
605
+ <span class="top right small" style="border-radius: 50%">
606
+ ${lq}
607
+ ${status}
608
+ ${permitJoinBtn}
609
+ </span>
610
+ <!--/a--!>
611
+ <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
612
+ </div>
613
+ <i class="left">${image}</i>
614
+ ${info}
615
+ <div class="footer right-align">
616
+ <div class="flip" style="cursor: pointer"><i class="material-icons">rotate_left</i></div>
617
+ </div>
618
+ </div>
619
+ </div>
620
+ <div class="back face">
621
+ <div class="card-content zcard">
622
+ <div class="flip" style="cursor: pointer">
623
+ <span class="top right small" style="border-radius: 50%">
624
+ ${lq}
625
+ ${status}
626
+ ${permitJoinBtn}
627
+ </span>
628
+ <!--/a--!>
629
+ <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
630
+ </div>
631
+ <i class="left">${image}</i>
632
+ <div style="min-height:88px; font-size: 0.8em" class="truncate">
633
+ <ul>
634
+ <li><span class="label coordinator">Adapter:</span><span>${coordinatorinfo.installedVersion}</span></li>
635
+ <li><span class="label coordinator">Installed:</span><span>${coordinatorinfo.installSource}</span></li>
636
+ <li><span class="label coordinator">Herdsman:</span><span>${coordinatorinfo.herdsman}</span></li>
637
+ <li><span class="label coordinator">Converters:</span><span>${coordinatorinfo.converters}</span></li>
638
+ </ul>
639
+ </div>
640
+ <div class="footer right-align">
641
+ <div class="flip" style="cursor: pointer"><i class="material-icons">rotate_left</i></div>
642
+ </div>
643
+ </div>
617
644
  </div>
618
645
  </div>
619
646
  </div>`;
@@ -638,19 +665,18 @@ function getGroupCard(dev) {
638
665
  const roomInfo = rooms.length ? `<li><span class="labelinfo">rooms:</span><span>${rooms.join(',') || ''}</span></li>` : '';
639
666
  const room = rooms.join(',') || '&nbsp';
640
667
  let memberCount = 0;
641
- let info = `<div style="min-height:88px; font-size: 0.8em; overflow-y: auto" class="truncate">
642
- <ul>`;
643
- info = info.concat(`<li><span class="labelinfo">Group ${numid}</span></li>`);
668
+ const info = [`<div style="min-height:88px; font-size: 0.8em; height: 90px; width: 220px; overflow-y: auto" class="truncate"><ul>`];
669
+ info.push(`<li><span class="labelinfo">Group ${numid}</span></li>`);
644
670
  if (dev.memberinfo === undefined) {
645
- info = info.concat(`<li><span class="labelinfo">No devices in group</span></li>`);
671
+ info.push(`<li><span class="labelinfo">No devices in group</span></li>`);
646
672
  } else {
647
673
  for (let m = 0; m < dev.memberinfo.length; m++) {
648
- info = info.concat(`<li><span align:"left">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid}</span><span align:"right"> ...${dev.memberinfo[m].ieee.slice(-4)}</span></li>`);
674
+ info.push(`<li><span align:"left">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid}</span><span align:"right"> ...${dev.memberinfo[m].ieee.slice(-4)}</span></li>`);
649
675
  }
650
676
  memberCount = (dev.memberinfo.length < 8 ? dev.memberinfo.length : 7);
651
677
  }
652
678
  ;
653
- info = info.concat(` ${roomInfo}</ul>
679
+ info.push(` ${roomInfo}</ul>
654
680
  </div>`);
655
681
  const infoBtn = `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>`;
656
682
  const image = `<img src="${dev.common.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
@@ -668,7 +694,7 @@ function getGroupCard(dev) {
668
694
  <span id="dName" class="card-title truncate">${title}</span><!----!>
669
695
  </div>
670
696
  <i class="left">${image}</i>
671
- ${info}
697
+ ${info.join('')}
672
698
 
673
699
  <div class="footer right-align"></div>
674
700
  </div>
@@ -1391,7 +1417,7 @@ function getCoordinatorInfo() {
1391
1417
  sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
1392
1418
  if (msg) {
1393
1419
  if (msg.error) {
1394
- errorData.push(msg.error);
1420
+ //errorData.push(msg.error);
1395
1421
  delete msg.error;
1396
1422
  isHerdsmanRunning = false;
1397
1423
  } else {
@@ -1570,13 +1596,7 @@ async function editDeviceOptions(id, isModel) {
1570
1596
  }
1571
1597
  else
1572
1598
  {
1573
- const val = $(`#option_value_${k}`).val();
1574
- try {
1575
- _do[k].value = JSON.parse(val);
1576
- }
1577
- catch {
1578
- _do[k].value = val;
1579
- }
1599
+ _do[k].value = $(`#option_value_${k}`).val();
1580
1600
  }
1581
1601
  if (_do[k].key.length > 0) {
1582
1602
  console.warn(`dok: ${_do[k].key} : ${_do[k].value}`);
@@ -1619,11 +1639,14 @@ async function editDeviceOptions(id, isModel) {
1619
1639
 
1620
1640
  const dialogData = {};
1621
1641
 
1642
+ const adapterDefinedOptions = ['resend_states']
1643
+
1622
1644
  if (isModel) {
1623
1645
  const model = id.model;
1624
1646
  dialogData.model = model;
1625
1647
  dialogData.availableOptions = model.options.slice() || [];
1626
1648
  dialogData.availableOptions.push('custom');
1649
+ dialogData.availableOptions.push(...adapterDefinedOptions)
1627
1650
  if (model.hasLegacyDef) dialogData.availableOptions.push('legacy');
1628
1651
  dialogData.setOptions = {};
1629
1652
  for (const k in Object.keys(id.setOptions))
@@ -1638,6 +1661,7 @@ async function editDeviceOptions(id, isModel) {
1638
1661
  const dev = devices.find((d) => d._id == id);
1639
1662
  dialogData.model = dev.info.mapped;
1640
1663
  dialogData.availableOptions = (dev.info.mapped ? dev.info.mapped.options.slice() || []:[]);
1664
+ dialogData.availableOptions.push(...adapterDefinedOptions)
1641
1665
  dialogData.name = dev.common.name;
1642
1666
  dialogData.icon = dev.common.icon || dev.icon;
1643
1667
  dialogData.default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
@@ -1733,6 +1757,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1733
1757
  const filterSet = new Set();
1734
1758
  let isodd = true;
1735
1759
  const buttonList = [];
1760
+ const idRed = ' id="dbgred"'
1736
1761
  if (dbgMsghide.has('i_'+devID)) {
1737
1762
  console.warn('in all filtered out')
1738
1763
  Html.push('&nbsp;')
@@ -1744,7 +1769,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1744
1769
  let fs = '';
1745
1770
  for (const state of item.states) {
1746
1771
  fs = fs+state.id+'.'+fne(item);
1747
- const redText = (item.errors && item.errors.length > 0 ? ' id="dbgred"' : '');
1772
+ const redText = (item.errors && item.errors.length > 0 ? idRed : '');
1748
1773
  idx--;
1749
1774
  const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
1750
1775
  if (idx==0) {
@@ -1752,7 +1777,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1752
1777
  buttonList.push(item.dataID)
1753
1778
  LHtml.push(`<td${rowspan}>${msgbutton}</td><td${rowspan}>${safestring(item.payload)}</td>`);
1754
1779
  }
1755
- 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>`);
1780
+ LHtml.push(`<td></td><td${redText}>${safestring(state.payload)}</td><td${state.inError ? idRed: redText}>${state.id}</td><td${state.inError ? idRed : redText}>${state.value}</td><td${redText}>${fne(item)}</td></tr>`);
1756
1781
  IHtml.unshift(...LHtml)
1757
1782
  }
1758
1783
  if (filter)
@@ -1990,7 +2015,7 @@ function getDevices() {
1990
2015
  sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
1991
2016
  if (msg) {
1992
2017
  if (msg.error) {
1993
- errorData.push(msg.error);
2018
+ //errorData.push(msg.error);
1994
2019
  delete msg.error;
1995
2020
  isHerdsmanRunning = false;
1996
2021
  } else {
@@ -2008,7 +2033,7 @@ function getDevices() {
2008
2033
  if (msg) {
2009
2034
  extractDevicesData(msg);
2010
2035
  if (msg.error) {
2011
- errorData.push(msg.error);
2036
+ //errorData.push(msg.error);
2012
2037
  isHerdsmanRunning = false;
2013
2038
  } else {
2014
2039
  isHerdsmanRunning = true;
@@ -2045,7 +2070,7 @@ function extractDevicesData(msg) {
2045
2070
  $('#state_cleanup_btn').removeClass('hide');
2046
2071
  else
2047
2072
  $('#state_cleanup_btn').addClass('hide');
2048
- if (msg.errors && msg.errors.length > 0) {
2073
+ if (msg.errors?.hasData) {
2049
2074
  $('#show_errors_btn').removeClass('hide');
2050
2075
  errorData = msg.errors;
2051
2076
  }
@@ -2086,14 +2111,14 @@ function getMap(rebuild) {
2086
2111
  $('#refresh').removeClass('disabled');
2087
2112
  if (msg) {
2088
2113
  if (msg.error) {
2089
- errorData.push(msg.error);
2114
+ //errorData.push(msg.error);
2090
2115
  isHerdsmanRunning = false;
2091
2116
  updateStartButton();
2092
2117
  } else {
2093
2118
  isHerdsmanRunning = true;
2094
2119
  updateStartButton();
2095
2120
  if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
2096
- showMessage(msg.errors.join('<p>'), 'Map generation messages');
2121
+ showMessage(msg.errors.join('<br>'), 'Map generation messages');
2097
2122
  }
2098
2123
  map = msg;
2099
2124
  showNetworkMap(devices, map);
@@ -2231,7 +2256,34 @@ function load(settings, onChange) {
2231
2256
  cleanConfirmation();
2232
2257
  });
2233
2258
  $('#show_errors_btn').click(function () {
2234
- showMessage(errorData.join('<br>'), 'Stashed error messages');
2259
+ const errMsgTable = [];
2260
+ console.warn(JSON.stringify(errorData));
2261
+ if (Object.keys(errorData.errors).length > 0) {
2262
+ errMsgTable.push(`<table><tr><th>Message</th><th>#</th><th>last seen</th></tr>`)
2263
+ for (const err of Object.values(errorData.errors))
2264
+ if (err) errMsgTable.push(`<tr><td>${err.message}</td><td>${err.ts.length}</td><td>${new Date(err.ts[err.ts.length-1]).toLocaleTimeString()}</td></tr>`)
2265
+ errMsgTable.push('</table>');
2266
+ }
2267
+ if (Object.keys(errorData.unknownModels).length > 0) {
2268
+ errMsgTable.push(`<table><tr><th>Unknown Models</th><th>#</th><th>last seen</th></tr>`)
2269
+ for (const err of Object.values(errorData.unknownModels))
2270
+ errMsgTable.push(`<tr><td>${err.message}</td><td>${err.ts.length}</td><td>${new Date(err.ts[err.ts.length-1]).toLocaleTimeString()}</td></tr>`)
2271
+ errMsgTable.push('</table>');
2272
+ }
2273
+ console.warn(JSON.stringify(errMsgTable));
2274
+ showMessage(errMsgTable.join(''), 'Stashed error messages', '<a id="delete_errors_btn" class="btn-floating waves-effect waves-light tooltipped center-align hoverable translateT" title="delete Errors"></i class="material-icons icon-black">delete_sweep</i></a>');
2275
+ $('#delete_errors_btn').unbind('click')
2276
+ $('#delete_errors_btn').click(function () {
2277
+ sendToWrapper(namespace, 'clearErrors', {}, function(msg) {
2278
+ if (msg) {
2279
+ console.warn('msg is ' + JSON.stringify(msg));
2280
+ errorData = msg;
2281
+ $('#show_errors_btn').addClass('hide');
2282
+ }
2283
+ $('#dialog-message').modal('close');
2284
+
2285
+ })
2286
+ })
2235
2287
  });
2236
2288
  $('#download_icons_btn').click(function () {
2237
2289
  showMessage(downloadIcons());
@@ -2619,6 +2671,26 @@ socket.on('stateChange', function (id, state) {
2619
2671
  getDevices();
2620
2672
  }
2621
2673
  }
2674
+ } else if (id.match(/\.info\.lasterror$/)) {
2675
+ try {
2676
+ console.warn(`lasterror is ${JSON.stringify(state)}`)
2677
+ const errobj = JSON.parse(state.val);
2678
+ let changed = false;
2679
+ if (errobj.error) {
2680
+ errorData.errors[errobj.error] = errobj.data;
2681
+ changed = true;
2682
+ }
2683
+ if (errobj.model) {
2684
+ errorData.unknownModels[errobj.model] = errobj.data;
2685
+ changed = true;
2686
+ }
2687
+ errorData.hasData |= changed;
2688
+ if (changed) {
2689
+ $('#show_errors_btn').removeClass('hide');
2690
+ }
2691
+ }
2692
+ catch { console.error('JSON didnt parse') }
2693
+
2622
2694
  } else {
2623
2695
  const devId = getDevId(id);
2624
2696
  putEventToNode(devId);
@@ -4022,7 +4094,7 @@ function genDevInfo(device) {
4022
4094
  `<div style="font-size: 0.9em">
4023
4095
  <ul>`);
4024
4096
  for (const item in mapped) {
4025
- if (item == 'model')
4097
+ if (item == 'model' && mapped.model != 'group')
4026
4098
  mappedInfo.push(genRow(item,modelUrl));
4027
4099
  else
4028
4100
  if (typeof mapped[item] != 'object') mappedInfo.push(genRow(item,mapped[item]));
@@ -4044,7 +4116,7 @@ function genDevInfo(device) {
4044
4116
  </ul>
4045
4117
  </div>`;
4046
4118
  }
4047
- const imgSrc = device.icon || device.common.icon;
4119
+ const imgSrc = mapped?.model == 'group' ? mapped.icon : device.icon || device.common.icon;
4048
4120
  const imgInfo = (imgSrc) ? `<img src=${imgSrc} width='150px' onerror="this.onerror=null;this.src='img/unavailable.png';"><div class="divider"></div>` : '';
4049
4121
  const info =[
4050
4122
  `<div class="col ${device.memberinfo != undefined ? 's12 m12 l12 xl12':'s12 m6 l6 xl6'}">
@@ -858,6 +858,10 @@
858
858
  <input id="useNewCompositeStates" type="checkbox" class="value"/>
859
859
  <label class="translate" for="useNewCompositeStates">use channel for complex exposes</label>
860
860
  </div>
861
+ <div class="input-field col s12 m6 l4">
862
+ <input id="availableUpdateTime" type="number" min="0" max="120" class="value"/>
863
+ <label class="translate" for="availableUpdateTime">Min. Available update timeout.</label>
864
+ </div>
861
865
  <div class="input-field col s12 m12 l12 col-startWithInconsistent">
862
866
  <input id="startWithInconsistent" type="checkbox" class="value"/>
863
867
  <label class="translate" for="startWithInconsistent">SettingsExclude</label>
package/admin/tab_m.html CHANGED
@@ -286,9 +286,14 @@
286
286
  width: 100px;
287
287
  font-weight: bold;
288
288
  }
289
+ .m span.label.coordinator {
290
+ display: inline-block;
291
+ width: 75px;
292
+ font-weight: bold;
293
+ }
289
294
  .m span.label.dash {
290
295
  display: inline-block;
291
- width: 140px;
296
+ width: 135px;
292
297
  font-weight: bold;
293
298
  }
294
299
  .m span.labelinfo {
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "zigbee",
4
- "version": "3.2.2",
4
+ "version": "3.2.4",
5
5
  "news": {
6
+ "3.2.4": {
7
+ "en": "added missing state",
8
+ "de": "fehlender zustand",
9
+ "ru": "отсутствующее государство",
10
+ "pt": "estado em falta adicionado",
11
+ "nl": "ontbrekende status toevoegen",
12
+ "fr": "état manquant ajouté",
13
+ "it": "aggiunto stato mancante",
14
+ "es": "estado desaparecido",
15
+ "pl": "dodany brakujący stan",
16
+ "uk": "додано відсутній стан",
17
+ "zh-cn": "添加缺失状态"
18
+ },
19
+ "3.2.3": {
20
+ "en": "Improvements on debug UI\nOption 'resend_states' to publish state values to device on reconnect\nImproved group card\nImproved group info\nModified coordinator card (2 sides)\nretry on error 25\nclear stashed error messages\nZHC 25.50.0 or newer",
21
+ "de": "Verbesserungen bei debug UI\nOption 'resend_states' zum Veröffentlichen von Zustandswerten auf das Gerät auf dem Reconnect\nVerbesserte Gruppenkarte\nVerbesserte Gruppeninformationen\nModifizierte Koordinatorkarte (2 Seiten)\nretry auf fehler 25\nklare stashed fehlermeldungen\nZHC 25.50.0 oder neuer",
22
+ "ru": "Улучшения в Debug UI\nВариант «resend_states» для публикации значений состояния на устройстве при повторном подключении\nУлучшенная групповая карта\nУлучшенная групповая информация\nИзмененная карта координатора (2 стороны)\nвозвращение к ошибке 25\nскрытые сообщения об ошибках\nZHC 25.50.0 или новее",
23
+ "pt": "Melhorias na interface de depuração\nOpção 'resend_states' para publicar valores de estado para o dispositivo ao religar\nCartão de grupo melhorado\nInformação melhorada do grupo\nCartão coordenador modificado (2 lados)\ntentar novamente no erro 25\nlimpar mensagens de erro escondidas\nZHC 25.50.0 ou mais recente",
24
+ "nl": "Verbeteringen op debug UI\nOptie 'resend_states' om statuswaarden naar apparaat te publiceren bij opnieuw verbinden\nVerbeterde groepskaart\nVerbeterde groepsinfo\nGewijzigde coördinatiekaart (2 zijden)\nopnieuw proberen bij fout 25\nfoutmeldingen wissen\nZHC 25.50.0 of nieuwer",
25
+ "fr": "Amélioration de l'assurance-chômage de débogage\nOption 'resend_states' pour publier les valeurs d'état sur le périphérique sur la connexion\nCarte de groupe améliorée\nInformations de groupe améliorées\nCarte de coordonnateur modifiée (2 côtés)\nréessayer sur l'erreur 25\nmessages d'erreur clairs et cachés\nZHC 25.50.0 ou plus récent",
26
+ "it": "Miglioramenti sul debug UI\nOpzione 'resend_states' per pubblicare i valori di stato sul dispositivo sulla riconnessione\nScheda di gruppo migliorata\nInformazioni di gruppo migliorate\nCarta coordinatrice modificata (2 lati)\nriprovare sull'errore 25\nchiari messaggi di errore stanti\nZHC 25.50.0 o più recente",
27
+ "es": "Mejoras en el depuro UI\nOpción 'resend_states' para publicar valores estatales al dispositivo en la reconexión\nTarjeta de grupo mejorada\nInformación de grupo mejorada\nTarjeta de coordinación modificada (2 lados)\nretry on error 25\nmensajes de error claros\nZHC 25.50.0 o más",
28
+ "pl": "Ulepszenia dotyczące debugowania interfejsu użytkownika\nOpcja \"resend _ states\" do publikowania wartości stanu dla urządzenia przy ponownym połączeniu\nUlepszona karta grupowa\nUlepszone informacje grupowe\nZmodyfikowana karta koordynatora (2 strony)\nponowna próba błędu 25\njasne komunikaty błędów ukryte\nZHC 25.50.0 lub nowsze",
29
+ "uk": "Удосконалення на дебурзі UI\nВаріант 'resend_states' для публікації державних значень для пристрою на відключенні\nПокращена групова карта\nПокращена інформація групи\nЗмінена карта координатора (2 сторони)\nптиця по помилки 25\nчіткі повідомлення про помилку\nZHC 25.50.0 або новачок",
30
+ "zh-cn": "调试 UI 的改进\n选项“ resend_ states ” 以在重新连接时发布状态值以设备\n改进组卡\n改进组信息\n修改的协调员卡(2面)\n重试错误 25\n清除隐藏的错误消息\nZHC 25.50.0或更新"
31
+ },
6
32
  "3.2.2": {
7
33
  "en": "Bugfix on delete object.\nimproved device query.\nfixed delete device with local overrides.\n",
8
34
  "de": "Bugfix auf Löschobjekt.\nverbesserte geräteabfrage.\nfeste löschvorrichtung mit lokalen overrides.\n",
@@ -67,32 +93,6 @@
67
93
  "pl": "Korekty błędów\nZHC25.36.0\nzmniejszony czas zatrzymania do 5 s",
68
94
  "uk": "Помилки\nЗХК25.36.0\nзнижений припуск до 5s",
69
95
  "zh-cn": "错误修正\nZHC25.36.0 (韩语)\n停止时间减少到 5s"
70
- },
71
- "3.1.4": {
72
- "en": "Remove extra logging\nAdd extra configurations\nDo not read states from deactivated devices\nIgnore deactivated devices for group state updates\nChange display for deactivated devices in the object tree (gray, no connected icon)\nmore detailed device debug\ndevice debug UI improvements\nPairing and device Query buttons on router cards\nZHC 25.31.0, ZH 6.1.2 or newer\nOptions based on ZHC defined options",
73
- "de": "Entfernen Sie zusätzliche Protokollierung\nZusätzliche Konfigurationen hinzufügen\nNicht lesen von Zuständen aus deaktivierten Geräten\nDeaktivierte Geräte für Gruppenzustandsupdates ignorieren\nAnzeige für deaktivierte Geräte im Objektbaum ändern (grau, kein angeschlossenes Symbol)\ndetaillierte gerätedetails\ngerät debug UI Verbesserungen\nPaarung und Gerät Query-Tasten auf Routerkarten\nZHC 25.31.0, ZH 6.1.2 oder neuer\nOptionen basierend auf ZHC definierten Optionen",
74
- "ru": "Удалить дополнительную рубку\nДобавьте дополнительные конфигурации\nНе считывать состояния с деактивированных устройств\nИгнорировать деактивированные устройства для обновления состояния группы\nИзменение дисплея для деактивированных устройств в дереве объектов (серый, не подключенный значок)\nболее подробная отладка устройства\nобновление UI Device Debug\nПарирование и кнопки запроса устройства на карточках маршрутизатора\nZHC 25.31.0, ZH 6.1.2 или новее\nОпционы, основанные на опционах ZHC",
75
- "pt": "Remover registro extra\nAdicionar configurações extras\nNão ler os estados dos dispositivos desactivados\nIgnorar os dispositivos desactivados para as actualizações do estado do grupo\nMudar a visualização dos dispositivos desativados na árvore de objetos (cinzento, sem ícone conectado)\ndepuração mais detalhada do dispositivo\nmelhorias na interface de depuração do dispositivo\nEmparelhamento e dispositivo Botões de consulta nas cartas de roteador\nZHC 25.31.0, ZH 6.1.2 ou mais recente\nOpções baseadas nas opções definidas pelo ZHC",
76
- "nl": "Extra loggen verwijderen\nExtra configuraties toevoegen\nNiet lezen van toestanden van gedeactiveerde apparaten\nUitgeschakelde apparaten negeren voor updates van groepstoestand\nWeergave wijzigen voor gedeactiveerde apparaten in de objectboom (grijze, geen verbonden pictogram)\ngedetailleerder apparaat debuggen\napparaat debug UI verbeteringen\nKoppeling en apparaat Query-knoppen op routerkaarten\nZHC 25.31.0, ZH 6.1.2 of nieuwer\nOpties op basis van ZHC gedefinieerde opties",
77
- "fr": "Supprimer l'enregistrement supplémentaire\nAjouter des configurations supplémentaires\nNe pas lire les états des dispositifs désactivés\nIgnorer les dispositifs désactivés pour les mises à jour de l'état du groupe\nModifier l'affichage des périphériques désactivés dans l'arborescence des objets (gris, aucune icône connectée)\ndebug périphérique plus détaillé\namélioration de l'interface utilisateur de débogage\nJumelage et dispositif Boutons de requête sur les cartes routeurs\nZHC 25.31.0, ZH 6.1.2 ou plus récent\nOptions basées sur des options définies par ZHC",
78
- "it": "Rimuovere la registrazione extra\nAggiungi ulteriori configurazioni\nNon leggere gli stati da dispositivi disattivati\nIgnora dispositivi disattivati per gli aggiornamenti di stato di gruppo\nModificare la visualizzazione per dispositivi disattivati nell'albero dell'oggetto (gray, nessuna icona collegata)\ndebug dispositivo più dettagliato\ndispositivo debug UI miglioramenti\nCoppia e dispositivo Pulsanti di coda su schede router\nZHC 25.31.0, ZH 6.1.2 o più recente\nOpzioni basate su opzioni definite ZHC",
79
- "es": "Quitar la tala extra\nAñadir configuraciones adicionales\nNo lea estados de dispositivos desactivados\nIgnorar dispositivos desactivados para actualizaciones de estado de grupo\nCambiar la pantalla para dispositivos desactivados en el árbol del objeto (gray, no icono conectado)\nmás detallado dispositivo depuración\ndepuración del dispositivo UI mejoras\nBotones de búsqueda en tarjetas de router\nZHC 25.31.0, ZH 6.1.2 o newer\nOpciones basadas en opciones definidas ZHC",
80
- "pl": "Usuń dodatkowe logowanie\nDodaj dodatkowe konfiguracje\nNie czytaj stanów z wyłączonych urządzeń\nIgnoruj dezaktywowane urządzenia dla aktualizacji stanu grupy\nZmień wyświetlacz dla dezaktywowanych urządzeń w drzewie obiektu (szary, bez połączonej ikony)\nbardziej szczegółowe debugowanie urządzenia\ndebug urządzenia UI ulepszenia\nParowanie i urządzenie Przyciski zapytania na kartach routera\nZHC 25.31.0, ZH 6.1.2 lub nowsze\nOpcje oparte na opcjach zdefiniowanych przez ZHC",
81
- "uk": "Видалення додаткового входу\nДодати додаткові конфігурації\nНе читати стани від деактивованих пристроїв\nІгноровані пристрої для оновлення групового стану\nЗміна відображення для деактивованих пристроїв в дереві об'єкта (сірий, не підключений значок)\nдокладніше пристрій debug\nпристрій Debug UI удосконалення\nПірсинг і пристрій Query гудзики на маршрутизаторних картках\nZHC 25.31.0, ZH 6.1.2 або новачок\nВаріанти, засновані на визначених параметрах ZHC",
82
- "zh-cn": "删除额外的日志\n添加额外的配置\n不读取已关闭设备的状态\n忽略组状态更新的已关闭设备\n更改对象树上已关闭设备的显示( gray, 无连接图标)\n更详细的设备调试\n设备调试 UI 改进\n路由器卡上的对齐和设备查询按钮\nZHC 25.31.0,ZH 6.1.2或更新\n基于 ZHC 定义选项的选项"
83
- },
84
- "3.1.2": {
85
- "en": "ZHC 25.x\nZH 6.x\nFix pairing bug\nadd ping messages to device debug to verify ping failure reasons\n",
86
- "de": "ZHC 25.x\nZH 6.x\nBehebung des Kopplungsfehlers\nFügen Sie Ping-Nachrichten zum Geräte-Debugging hinzu, um die Gründe für Ping-Ausfälle zu überprüfen.\n",
87
- "ru": "ZHC 25.x\nZH 6.x\nИсправлена ошибка сопряжения.\nдобавьте сообщения ping в отладке устройства для проверки причин отказа ping\n",
88
- "pt": "ZHC 25.x\nZH 6.x\nCorrigir bug de emparelhamento\nAdicionar mensagens de ping ao debug do dispositivo para verificar os motivos da falha no ping\n",
89
- "nl": "ZHC 25.x\nZH 6.x\nRepareer koppelingsfout\nVoeg pingberichten toe aan apparaatdebug om pingfoutredenen te controleren\n",
90
- "fr": "ZHC 25.x\nZH 6.x\nCorriger le bogue de couplage\najouter des messages de ping au débogage des appareils pour vérifier les raisons des échecs de ping\n",
91
- "it": "ZHC 25.x\nZH 6.x\nRisoluzione bug di accoppiamento\nAggiungi messaggi ping al debug del dispositivo per verificare le cause del fallimento del ping\n",
92
- "es": "ZHC 25.x\nZH 6.x\nCorregir error de emparejamiento\nañadir mensajes de ping al depurador del dispositivo para verificar las razones de fallo del ping\n",
93
- "pl": "ZHC 25.x\nZH 6.x\nNapraw błąd parowania\nDodaj wiadomości ping do debugowania urządzenia, aby zweryfikować przyczyny niepowodzenia pingu\n",
94
- "uk": "ЗХК 25.x\nЗХ 6.х\nВиправлено помилку з'єднання\nдодано повідомлення ping до відлагодження пристрою для перевірки причин невдачі ping\n",
95
- "zh-cn": "ZHC 25.x\nZH 6.x (英语)\n修复配对问题\n将ping消息添加到设备调试中,以验证ping失败的原因\n"
96
96
  }
97
97
  },
98
98
  "titleLang": {
@@ -327,7 +327,8 @@
327
327
  "pingTimeout": 300,
328
328
  "pingCluster": "",
329
329
  "listDevicesAtStart": true,
330
- "useNewCompositeStates": false
330
+ "useNewCompositeStates": false,
331
+ "availableUpdateTime": 30
331
332
  },
332
333
  "instanceObjects": [
333
334
  {
@@ -364,6 +365,19 @@
364
365
  },
365
366
  "native": {}
366
367
  },
368
+ {
369
+ "_id": "info.lasterror",
370
+ "type": "state",
371
+ "common": {
372
+ "role": "indicator",
373
+ "name": "Last stashed error",
374
+ "type": "string",
375
+ "read": true,
376
+ "write": false,
377
+ "def": ""
378
+ },
379
+ "native": {}
380
+ },
367
381
  {
368
382
  "_id": "info.pairingCountdown",
369
383
  "type": "state",
@@ -72,7 +72,7 @@ class DeviceDebug extends EventEmitter {
72
72
  this.dataByDevice[item.deviceID] = DevData;
73
73
  }
74
74
  if (message.hasOwnProperty('message') && this.logStatus) {
75
- this.warn(`ELEVATED:${flag} (${dataId.toString(16).slice(-4)}) ${message.message}`)
75
+ this.warn(`ELEVATED:${flag} (${dataId?.toString(16).slice(-4)}) ${message.message}`)
76
76
  }
77
77
  }
78
78
  }
package/lib/commands.js CHANGED
@@ -177,6 +177,9 @@ class Commands {
177
177
  case 'aliveCheck':
178
178
  this.adapter.sendTo(obj.from, obj.command, {msg:'success'}, obj.callback);
179
179
  break;
180
+ case 'clearErrors':
181
+ this.adapter.sendTo(obj.from, obj.command, this.stController.clearStashedErrors(), obj.callback);
182
+ break;
180
183
  default:
181
184
  this.debug(`Commands: Command ${obj.command} is unknown`);
182
185
  //this.adapter.sendTo(obj.from, obj.command, obj.message, obj.callback);
@@ -3,7 +3,7 @@
3
3
  const safeJsonStringify = require('./json');
4
4
  const { EventEmitter } = require('events');
5
5
  const statesMapping = require('./devices');
6
- const { getAdId, getZbId } = require('./utils');
6
+ const { getAdId, getZbId, zbIdorIeeetoAdId } = require('./utils');
7
7
  const fs = require('fs');
8
8
  const localConfig = require('./localConfig');
9
9
  const path = require('path');
@@ -24,11 +24,10 @@ class StatesController extends EventEmitter {
24
24
  this.cleanupRequired = false;
25
25
  this.timeoutHandleUpload = null;
26
26
  this.ImagesToDownload = [];
27
- this.stashedErrors = {};
28
- this.stashedUnknownModels = {};
29
27
  this.debugMessages = { nodevice:{ in:[], out: []} };
30
28
  this.debugActive = true;
31
29
  this.deviceQueryBlock = [];
30
+ this.clearStashedErrors();
32
31
  }
33
32
 
34
33
  info(message, data) {
@@ -52,22 +51,16 @@ class StatesController extends EventEmitter {
52
51
  }
53
52
 
54
53
  getStashedErrors() {
55
- const rv = [];
56
- try {
57
- if (Object.keys(this.stashedErrors).length > 0)
58
- {
59
- rv.push('<p><b>Stashed Messages</b></p>')
60
- rv.push(Object.values(this.stashedErrors).join('<br>'));
61
- }
62
- if (Object.keys(this.stashedUnknownModels).length > 0) {
63
- rv.push('<p><b>Devices whithout Model definition</b></p>')
64
- rv.push(Object.values(this.stashedUnknownModels).join('<br>'));
65
- }
66
- }
67
- catch (error) {
68
- if (this.debugActive) this.debug(`Error collecting stashed errors: ${error && error.message ? error.message : 'no message available'}`);
69
- }
70
- return rv;
54
+ return this.stashedErrors;
55
+ }
56
+
57
+ clearStashedErrors() {
58
+ this.stashedErrors = {
59
+ errors:{},
60
+ unknownModels: {},
61
+ hasData:false
62
+ };
63
+ return this.stashedErrors;
71
64
  }
72
65
 
73
66
  debugMessagesById() {
@@ -382,18 +375,30 @@ class StatesController extends EventEmitter {
382
375
  }
383
376
 
384
377
  stashErrors(key, msg, error) {
385
- if (!this.stashedErrors.hasOwnProperty(key))
386
- {
387
- if (error) this.error(msg); else this.warn(msg);
388
- this.stashedErrors[key] = msg;
378
+ try {
379
+ if (!this.stashedErrors.errors.hasOwnProperty(key))
380
+ {
381
+ if (error) this.error(msg); else this.warn(msg);
382
+ this.stashedErrors.errors[key] = { message:msg, ts:[Date.now()], error:error };
383
+ }
384
+ else this.stashedErrors.errors[key].ts.push(Date.now());
385
+ this.stashedErrors.hasData = true;
386
+ this.adapter.setState('info.lasterror', JSON.stringify({ error: key, data:this.stashedErrors.errors[key]}))
389
387
  }
388
+ catch { /* */ }
390
389
  }
391
390
 
392
391
  stashUnknownModel(model, msg) {
393
- if (!this.stashedUnknownModels.hasOwnProperty(model)) {
394
- this.stashedUnknownModels[model] = msg;
395
- this.error(`Unknown ${model}: ${msg}`)
392
+ try {
393
+ if (!this.stashedErrors.unknownModels.hasOwnProperty(model)) {
394
+ this.stashedErrors.unknownModels[model] = { message:msg, ts:[Date.now()], error:true };
395
+ this.error(`Unknown ${model}: ${msg}`)
396
+ }
397
+ else this.stashedErrors.unknownModels[model].ts.push(Date.now());
398
+ this.stashedErrors.hasData = true;
399
+ this.adapter.setState('info.lasterror', JSON.stringify({ model: model, data:this.stashedErrors.unknownModels[model]}))
396
400
  }
401
+ catch { /* */ }
397
402
  }
398
403
 
399
404
  async triggerComposite(_deviceId, stateDesc, interactive) {
@@ -430,6 +435,50 @@ class StatesController extends EventEmitter {
430
435
  lfArr.push(state);
431
436
  }
432
437
 
438
+ parseOption(val) {
439
+ if (val === undefined || val === null) return {};
440
+ if (!Array.isArray(val)) {
441
+ try {
442
+ const valObj = JSON.parse(val);
443
+ return valObj;
444
+ }
445
+ catch { /* */ }
446
+ if (typeof val =='object') return val;
447
+ }
448
+ if (typeof val === 'string')
449
+ {
450
+ const keys = val.split(/[,;]/);
451
+ const rv = {};
452
+ for (const k of keys) {
453
+ rv[k] = null;
454
+ }
455
+ return rv;
456
+ }
457
+ this.warn(`illegal option ${JSON.stringify(val)}`)
458
+ return {};
459
+ }
460
+
461
+ async handleStateReset(entity, end_with_device_query) {
462
+ const debugID = Date.now();
463
+ const deviceId = entity.device.ieeeAddr;
464
+ const model = entity.mapped?.model || '';
465
+ const states = this.parseOption(entity.options.resend_states);
466
+ if (end_with_device_query) states.end_with_device_query = true;
467
+ await this.publishFromStates(deviceId, model, states, entity.options, debugID);
468
+ }
469
+
470
+ async publishFromStates(deviceId, model, stateIDs, options, debugID) {
471
+ const adId = zbIdorIeeetoAdId(this.adapter, deviceId, true);
472
+ for (const stateID of Object.keys(stateIDs).sort((a,b) => {if (a=='device_query' || a=='state') return (b=='device_query' ? -1 : 1); else return a.localeCompare(b)})) {
473
+ const state = await this.adapter.getStateAsync(`${adId}.${stateID}`);
474
+ if (stateIDs[stateID]!= null) state.val = stateIDs[stateID];
475
+ if (state && state.hasOwnProperty('val')) {
476
+ await this.publishFromState(deviceId, model, stateID, state, options, debugID)
477
+ }
478
+ }
479
+
480
+ }
481
+
433
482
  async publishFromState(deviceId, model, stateKey, state, options, debugID) {
434
483
  if (this.debugActive) this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
435
484
  const has_elevated_debug = this.checkDebugDevice(typeof deviceId == 'number' ? `group_${deviceId}` : deviceId);
@@ -460,7 +509,7 @@ class StatesController extends EventEmitter {
460
509
  if (value === undefined || value === '') {
461
510
  if (has_elevated_debug) {
462
511
  const message = (`no value for device ${deviceId} type '${model}'`);
463
- this.emit('device_debug', { ID:debugID, data: { states:[{id:state.ID, value:'--', payload:'error', ep:stateDesc.epname}],error: 'NOVAL' , IO:false }, message:message});
512
+ this.emit('device_debug', { ID:debugID, data: { states:[{id:state.ID, value:'--', payload:'error', ep:stateDesc.epname}],error: 'NOVAL1' , IO:false }, message:message});
464
513
  }
465
514
  return;
466
515
  }
@@ -1128,17 +1177,18 @@ class StatesController extends EventEmitter {
1128
1177
  } else {
1129
1178
  value = payload[statedesc.prop || statedesc.id];
1130
1179
  }
1180
+
1131
1181
  // checking value
1132
1182
  if (value === undefined || value === null) {
1133
1183
  continue;
1134
1184
  }
1135
1185
 
1136
1186
  let stateID = statedesc.id;
1137
-
1138
1187
  const message = `value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`;
1139
1188
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'04', IO:true }, message});
1140
1189
  else if (this.debugActive) this.debug(message);
1141
1190
 
1191
+
1142
1192
  const common = {
1143
1193
  name: statedesc.name,
1144
1194
  type: statedesc.type,
@@ -1187,7 +1237,7 @@ class StatesController extends EventEmitter {
1187
1237
  }
1188
1238
  const message = `No value published for device ${devId}`;
1189
1239
  if (!has_published) {
1190
- if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOVAL', IO:true }, message:message});
1240
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ states:[{id:'no state', value:'no value', inError:true, payload:payload }], flag:'04', IO:true }, message:message});
1191
1241
  else if (this.debugActive) this.debug(message);
1192
1242
  }
1193
1243
  }
@@ -1226,11 +1276,11 @@ class StatesController extends EventEmitter {
1226
1276
  async processConverters(converters, devId, model, mappedModel, message, meta, debugId, has_elevated_debug) {
1227
1277
  let cnt = 0;
1228
1278
  const publish = (payload, dID) => {
1229
- if (typeof payload === 'object' && Object.keys(payload).length > 0) {
1279
+ if (typeof payload === 'object') {// && Object.keys(payload).length > 0) {
1230
1280
  this.publishToState(devId, model, payload,dID);
1231
1281
  }
1232
1282
  else if (has_elevated_debug)
1233
- this.emit('device_debug', {ID:debugId,data: { error:`NOVAL`, IO:true }, message:` payload ${JSON.stringify(payload)} is empty`})
1283
+ this.emit('device_debug', {ID:debugId,data: { error:`EPAYLD`, IO:true }, message:` payload ${JSON.stringify(payload)} is empty`})
1234
1284
  };
1235
1285
  const options = await new Promise((resolve, reject) => {
1236
1286
  this.collectOptions(devId, model, false, (options) => {
@@ -56,6 +56,7 @@ class DeviceAvailability extends BaseExtension {
56
56
  this.startReadDelay = config.readAllAtStart ? Math.max(500, Math.min(10000, config.startReadDelay * 1000)) : 0;
57
57
  this.debugDevices = [];
58
58
  this.pingCluster = pingClusters[config.pingCluster] ? pingClusters[config.pingCluster] : {};
59
+ this.availableTime = config.availableUpdateTime ? config.availableUpdateTime : Number.MAX_SAFE_INTEGER;
59
60
  }
60
61
 
61
62
  checkDebugDevice(dev) {
@@ -191,7 +192,7 @@ class DeviceAvailability extends BaseExtension {
191
192
  return;
192
193
  }
193
194
  if (this.isPingable(device)) {
194
- const pt = Date.now();
195
+ const debugID = Date.now();
195
196
  let pingCount = this.ping_counters[device.ieeeAddr];
196
197
  if (pingCount === undefined) {
197
198
  this.ping_counters[device.ieeeAddr] = {failed: 0, reported: 0};
@@ -199,43 +200,60 @@ class DeviceAvailability extends BaseExtension {
199
200
  }
200
201
  try {
201
202
  if (!this.pingCluster || !this.pingCluster.hasOwnProperty('id')) {
202
- this.debug(`Pinging '${ieeeAddr}' (${device.modelID}) via ZH Ping`)
203
+ const message = `Pinging '${ieeeAddr}' (${device.modelID}) via ZH Ping`
204
+ if (has_elevated_debug) {
205
+ this.zigbee.emit('device_debug', { ID:debugID, data: { ID: device.ieeeAddr, flag:'PI', IO:false, states:[{ id:'ping', value: 'zh_default', payload: { method:'default'}}]}, message:message});
206
+ }
207
+ else this.debug(message)
203
208
  await device.ping();
204
209
  }
205
210
  else {
206
211
  const zclData = {};
207
212
  zclData[this.pingCluster.attribute] = {};
208
- this.debug(`Pinging '${ieeeAddr}' (${device.modelID}) via ZCL Read with ${this.pingCluster.id}:${this.pingCluster.attribute}`)
213
+ const message = `Pinging '${ieeeAddr}' (${device.modelID}) via ZCL Read with ${this.pingCluster.id}:${this.pingCluster.attribute}`
214
+ if (has_elevated_debug) {
215
+ this.zigbee.emit('device_debug', { ID:debugID, data: { ID: device.ieeeAddr, flag:'PI', states:[{id:'ping', value:'cluster/id', payload: { method: 'custom', cluster:this.pingCluster.id, command:'read', zcl:zclData}}], IO:false}, message:message});
216
+ }
217
+ else this.debug(message)
209
218
  await this.zigbee.publish(device, this.pingCluster.id, 'read', zclData, null, undefined, 'foundation');
210
219
  }
211
- this.publishAvailability(device, true);
212
- if (has_elevated_debug) this.warn(`ELEVATED : Successfully pinged ${ieeeAddr} (${device.modelID}) in ${Date.now()-pt} ms`);
220
+ this.publishAvailability(device, true, false, has_elevated_debug ? debugID : undefined);
221
+ if (has_elevated_debug) {
222
+ const message = `Successfully pinged ${ieeeAddr} (${device.modelID}) in ${Date.now()-debugID} ms`
223
+ this.zigbee.emit('device_debug', { ID:debugID, data: {ID: device.ieeeAddr, flag:'SUCCESS', IO:false}, message:message});
224
+ }
213
225
  this.setTimerPingable(device, 1);
214
226
  this.ping_counters[device.ieeeAddr].failed = 0;
215
227
  } catch (error) {
216
228
  if (error && error.message && error.message.includes('UNSUPPORTED_ATTRIBUTE')) {
217
229
  // this error is acceptable, as it is raised off an answer of the device.
218
230
  this.publishAvailability(device, true);
219
- if (has_elevated_debug) this.warn(`ELEVATED : Successfully pinged ${ieeeAddr} (${device.modelID}) in ${Date.now()-pt} ms`);
231
+ if (has_elevated_debug)
232
+ this.zigbee.emit('device_debug', { ID:debugID, data: {ID: device.ieeeAddr, flag:'SUCCESS', IO:false}, message:`Successfully pinged ${ieeeAddr} (${device.modelID}) in ${Date.now()-debugID} ms`});
220
233
  this.setTimerPingable(device, 1);
221
234
  this.ping_counters[device.ieeeAddr].failed = 0;
222
235
  return;
223
236
  }
224
- if (has_elevated_debug) this.warn(`ELEVATED : Failed to ping ${ieeeAddr} (${device.modelID}) after ${Date.now()-pt} ms${error && error.message ? ' - '+error.message : ''}`);
225
- this.publishAvailability(device, false);
237
+ if (has_elevated_debug)
238
+ this.zigbee.emit('device_debug',{ ID:debugID, data: {ID: device.ieeeAddr, error:'PIFAIL', IO:false}, message:`Failed to ping ${ieeeAddr} (${device.modelID}) after ${Date.now()-debugID} ms${error && error.message ? ' - '+error.message : ''}`});
239
+ this.publishAvailability(device, false, false, has_elevated_debug ? debugID : undefined);
226
240
  if (pingCount.failed++ <= this.max_ping) {
227
- const msg = `Failed to ping ${ieeeAddr} ${device.modelID} for ${JSON.stringify(pingCount)} attempts`
241
+ const message = `Failed to ping ${ieeeAddr} ${device.modelID} for ${JSON.stringify(pingCount)} attempts`
228
242
  if (pingCount.failed < 2 && pingCount.reported < this.max_ping) {
229
- if (has_elevated_debug) this.warn(`ELEVATED: ${msg}`); else this.info(msg);
243
+ if (!has_elevated_debug)
244
+ this.info(message);
230
245
  pingCount.reported++;
231
246
  } else {
232
- this.debug(msg);
247
+ this.debug(message);
233
248
  }
234
249
  this.setTimerPingable(device, pingCount.failed);
235
250
  this.ping_counters[device.ieeeAddr] = pingCount;
236
251
  } else {
237
252
  const msg = `Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`;
238
- if (has_elevated_debug) this.warn(`ELEVATED ${msg}`); else this.info(msg);
253
+ if (has_elevated_debug)
254
+ this.zigbee.emit('device_debug',{ ID:debugID, data: {ID: device.ieeeAddr, error:'PISTOP', IO:false}, message:msg});
255
+ else
256
+ this.info(msg);
239
257
  }
240
258
  }
241
259
  }
@@ -301,6 +319,9 @@ class DeviceAvailability extends BaseExtension {
301
319
  }
302
320
 
303
321
  async publishAvailability(device, available, force) {
322
+ // no device availability until the interview is done.
323
+ if (device?.interviewState == 'IN_PROGRESS')
324
+ return;
304
325
  const entity = await this.zigbee.resolveEntity(device);
305
326
  if (entity && entity.mapped) {
306
327
  const ieeeAddr = device.ieeeAddr;
@@ -308,13 +329,21 @@ class DeviceAvailability extends BaseExtension {
308
329
  this.onReconnect(device);
309
330
  }
310
331
 
311
- if (this.state[ieeeAddr] !== available || force) {
312
- this.state[ieeeAddr] = available;
332
+ const astate = this.state[ieeeAddr] || { available, ts:0 };
333
+ const now = Math.round(Date.now()/1000);
334
+
335
+ if (force || (astate.available !== available) || (now - astate.ts > this.availableTime)) {
336
+ this.state[ieeeAddr] = {
337
+ ts: now,
338
+ available
339
+ }
313
340
  const payload = {available: available};
314
341
  this.debug(`Publish available for ${ieeeAddr} = ${available}`);
315
342
  this.zigbee.emit('publish', utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false), entity.mapped.model, payload);
316
- this.debug(`Publish LQ for ${ieeeAddr} = ${(available ? 10 : 0)}`);
317
- this.zigbee.emit('publish', utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false), entity.mapped.model, {linkquality: (available ? 10 : 0)});
343
+ if (force || (astate.available !== available)) {
344
+ this.debug(`Publish LQ for ${ieeeAddr} = ${(available ? 10 : 0)}`);
345
+ this.zigbee.emit('publish', utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false), entity.mapped.model, {linkquality: (available ? 10 : 0)});
346
+ }
318
347
  }
319
348
  }
320
349
  }
@@ -34,6 +34,10 @@ class DeviceEvent extends BaseExtension {
34
34
  }
35
35
  }
36
36
 
37
+ async deviceExposeChanged(device, mapped) {
38
+ this.warn(`deviceExposeChanged called with ${JSON.stringify(device.ieeeAddr)} / ${JSON.stringify(mapped.model)}`);
39
+ }
40
+
37
41
  async callOnEvent(device, type, data, mappedDevice) {
38
42
  if (!mappedDevice) {
39
43
  mappedDevice = await zigbeeHerdsmanConverters.findByDevice(device);
@@ -52,6 +56,7 @@ class DeviceEvent extends BaseExtension {
52
56
  case `deviceJoined`:
53
57
  {
54
58
  eventData.data = baseData;
59
+ eventData.deviceExposeChanged = function() { this.deviceExposeChanged(device, mappedDevice) } ;
55
60
  break;
56
61
  }
57
62
  case 'stop':
@@ -668,7 +668,7 @@ class ZigbeeController extends EventEmitter {
668
668
  endpoint: device.getEndpoint(1),
669
669
  name: 'Coordinator',
670
670
  };
671
- this.emit('stash_unknown_model', `resoveEntity${device.ieeeAddr}`,`Resolve Entity did not manage to find a mapped device for ${device.ieeeAddr} of type ${device.modelID}`);
671
+ if (device.interviewState != 'IN_PROGRESS') this.emit('stash_unknown_model', `resoveEntity${device.ieeeAddr}`,`Resolve Entity did not manage to find a mapped device for ${device.ieeeAddr} of type ${device.modelID}`);
672
672
  }
673
673
  const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
674
674
  let endpoint;
@@ -906,11 +906,6 @@ class ZigbeeController extends EventEmitter {
906
906
  if (this.adapter.stController.checkDebugDevice(friendlyName)) {
907
907
  this.emit('device_debug', {ID: Date.now(), data: {flag:'da', states:[{id: '--', value:'--', payload:message}] , IO:true} ,message:`Device '${friendlyName}' announced itself`});
908
908
  }
909
- if (this.warnOnDeviceAnnouncement) {
910
- this.warn(`Device '${friendlyName}' announced itself${this.readAtAnnounce ? ', trying to read its status' : ''}`);
911
- } else {
912
- this.info(`Device '${friendlyName}' announced itself${this.readAtAnnounce ? ', trying to read its status' : ''}`);
913
- }
914
909
 
915
910
  if (entity.device && entity.device.modelID && entity.device.interviewState != 'SUCCESSFUL') {
916
911
  this.info(`ignoring device announcement for ${entity.device.modelID} due to interview state ${entity.device.interviewState}`);
@@ -918,6 +913,12 @@ class ZigbeeController extends EventEmitter {
918
913
  return;
919
914
  }
920
915
 
916
+ if (this.warnOnDeviceAnnouncement) {
917
+ this.warn(`Device '${friendlyName}' announced itself${this.readAtAnnounce ? ', trying to read its status' : ''}`);
918
+ } else {
919
+ this.info(`Device '${friendlyName}' announced itself${this.readAtAnnounce ? ', trying to read its status' : ''}`);
920
+ }
921
+
921
922
  const networkOpen = this.herdsman.getPermitJoin();
922
923
  /*
923
924
  if (networkOpen && entity.device && entity.device.modelID && entity.device.interviewState != 'IN_PROGRESS')
@@ -929,11 +930,16 @@ class ZigbeeController extends EventEmitter {
929
930
  */
930
931
  try {
931
932
  if (entity && entity.mapped) {
933
+ if (entity.options?.hasOwnProperty('resend_states')) {
934
+ // trigger setting the states
935
+ this.emit('resend_states',entity, this.readAtAnnounce );
936
+ }
937
+ else
938
+ if (this.readAtAnnounce) await this.doDeviceQuery(message.device || message.ieeeAddr, Date.now(), false);
932
939
  this.callExtensionMethod(
933
940
  'onZigbeeEvent',
934
941
  [{'device': message.device, 'type': 'deviceAnnounce', options: entity.options || {}}, entity ? entity.mapped : null]);
935
942
  this.callExtensionMethod('registerDevicePing', [message.device, entity]);
936
- if (this.readAtAnnounce) await this.doDeviceQuery(message.device || message.ieeeAddr, Date.now(), false);
937
943
  }
938
944
  } catch (error) {
939
945
  this.sendError(error);
@@ -1346,38 +1352,49 @@ class ZigbeeController extends EventEmitter {
1346
1352
  meta.state = preparedOptions.state;
1347
1353
  }
1348
1354
  }
1349
-
1350
- try {
1351
- const result = await converter.convertSet(target, key, preparedValue, meta);
1352
- const message = `convert result ${safeJsonStringify(result)} for device ${deviceId}`;
1353
- if (isGroup)
1354
- this.emit('published', deviceId, model, stateModel, stateList, options, debugID, has_elevated_debug );
1355
- if (has_elevated_debug) {
1356
- this.emit('device_debug', { ID:debugID, data: { flag: 'SUCCESS' , IO:false }, message:message});
1357
- }
1358
- else
1359
- if (this.debugActive) this.debug(message);
1360
- if (result !== undefined) {
1361
- if (stateModel && !isGroup && !stateDesc.noack) {
1362
- this.emit('acknowledge_state', deviceId, model, stateDesc, value );
1355
+ let retry = 2;
1356
+ do
1357
+ {
1358
+ try {
1359
+ const result = await converter.convertSet(target, key, preparedValue, meta);
1360
+ const message = `convert result ${safeJsonStringify(result)} for device ${deviceId}`;
1361
+ if (isGroup)
1362
+ this.emit('published', deviceId, model, stateModel, stateList, options, debugID, has_elevated_debug );
1363
+ if (has_elevated_debug) {
1364
+ this.emit('device_debug', { ID:debugID, data: { flag: 'SUCCESS' , IO:false }, message:message});
1363
1365
  }
1364
- // process sync state list
1365
- this.processSyncStatesList(deviceId, model, syncStateList);
1366
- }
1367
- else {
1366
+ else
1367
+ if (this.debugActive) this.debug(message);
1368
+ if (result !== undefined) {
1369
+ if (stateModel && !isGroup && !stateDesc.noack) {
1370
+ this.emit('acknowledge_state', deviceId, model, stateDesc, value );
1371
+ }
1372
+ // process sync state list
1373
+ this.processSyncStatesList(deviceId, model, syncStateList);
1374
+ }
1375
+ else {
1376
+ if (has_elevated_debug) {
1377
+ const message = `Convert does not return a result result for ${key} with ${safeJsonStringify(preparedValue)} on device ${deviceId}.`;
1378
+ this.emit('device_debug', { ID:debugID, data: { flag: '06' , IO:false }, message:message});
1379
+ }
1380
+ }
1381
+ retry = 0;
1382
+ } catch (error) {
1368
1383
  if (has_elevated_debug) {
1369
- const message = `Convert does not return a result result for ${key} with ${safeJsonStringify(preparedValue)} on device ${deviceId}.`;
1370
- this.emit('device_debug', { ID:debugID, data: { flag: '06' , IO:false }, message:message});
1384
+ const message = `caught error ${error?.message? error.message : 'no reason given'} when setting value for device ${deviceId}.`;
1385
+ this.emit('device_debug', { ID:debugID, data: { error: `EXSET${error.code == 25 ? retry : ''}` , IO:false },message:message});
1386
+ }
1387
+ if (error.code === 25 && retry > 0) {
1388
+ this.warn(`Error ${error.code} on send command to ${deviceId}. (${retry} tries left.), Error: ${error.message}`);
1389
+ retry--;
1390
+ }
1391
+ else {
1392
+ retry = 0;
1393
+ this.adapter.filterError(`Error ${error.code} on send command to ${deviceId}.` +
1394
+ ` Error: ${error.message}`, `Send command to ${deviceId} failed with`, error);
1371
1395
  }
1372
1396
  }
1373
- } catch (error) {
1374
- if (has_elevated_debug) {
1375
- const message = `caught error ${error && error.message ? error.message : 'no reason given'} when setting value for device ${deviceId}.`;
1376
- this.emit('device_debug', { ID:debugID, data: { error: 'EXSET' , IO:false },message:message});
1377
- }
1378
- this.adapter.filterError(`Error ${error.code} on send command to ${deviceId}.` +
1379
- ` Error: ${error.stack}`, `Send command to ${deviceId} failed with`, error);
1380
- }
1397
+ } while (retry > 0)
1381
1398
  });
1382
1399
  } catch (err) {
1383
1400
  const message = `No entity for ${deviceId} : ${err && err.message ? err.message : 'no error message'}`;
@@ -1506,8 +1523,11 @@ class ZigbeeController extends EventEmitter {
1506
1523
  if (this.debugActive) this.debug(`Device query for '${entity.device.ieeeAddr}' started`);
1507
1524
  else this.info(`Device query for '${entity.device.ieeeAddr}' started`);
1508
1525
 
1526
+ const payload = { key:'device_query', read_states:[], unread_states:[] };
1527
+ let cCount = 0;
1509
1528
  for (const converter of mappedModel.toZigbee) {
1510
1529
  if (converter.hasOwnProperty('convertGet')) {
1530
+ cCount++;
1511
1531
  const sources = [];
1512
1532
  if (converter.endpoints && epmap) {
1513
1533
  for (const epname of converter.endpoints) {
@@ -1520,16 +1540,18 @@ class ZigbeeController extends EventEmitter {
1520
1540
  for (const k of converter.key)
1521
1541
  try {
1522
1542
  await converter.convertGet(source, k, {device:entity.device});
1543
+ payload.read_states.push(`${cCount}.${k}`);
1523
1544
  if (elevated) {
1524
- const message = `read for state${converter.key.length ? '' : 's'} '${converter.key.join(',')}' of '${entity.device.ieeeAddr}/${source.ID}' after device query`;
1545
+ const message = `read for state ${k} of '${converter.key.join(',')}' of '${entity.device.ieeeAddr}/${source.ID}' after device query`;
1525
1546
  this.warn(`ELEVATED O02.1 ${message}`);
1526
- this.emit('device_debug', { ID:debugID, data: { flag: k, IO:false }, message:message });
1547
+ this.emit('device_debug', { ID:debugID, data: { flag: '03', IO:false }, message:message });
1527
1548
  }
1528
1549
  else
1529
1550
  this.debug(`read for state${converter.key.length ? '' : 's'} '${converter.key.join(',')}' of '${entity.device.ieeeAddr}/${source.ID}' after device query`);
1530
1551
  } catch (error) {
1552
+ payload.unread_states.push(`${cCount}.${k}`);
1531
1553
  if (elevated) {
1532
- const message = `Failed to read for state${converter.key.length ? '' : 's'} '${converter.key.join(',')}' of '${source.ID}' from query with '${error && error.message ? error.message : 'no error message'}`;
1554
+ const message = `Failed to read for state ${k} of '${converter.key.join(',')}' of '${source.ID}' from query with '${error && error.message ? error.message : 'no error message'}`;
1533
1555
  this.warn(`ELEVATED OE02.1 ${message}`);
1534
1556
  this.emit('device_debug', { ID:debugID, data: { error: 'NOTREAD' , IO:false }, message:message });
1535
1557
  }
@@ -1541,7 +1563,7 @@ class ZigbeeController extends EventEmitter {
1541
1563
  }
1542
1564
  if (elevated) {
1543
1565
  const message = `ELEVATED O07: Device query for '${entity.device.ieeeAddr}}' complete`;
1544
- this.emit('device_debug', { ID:debugID, data: { flag: 'qe' , IO:false }, message:message});
1566
+ this.emit('device_debug', { ID:debugID, data: { flag: 'qe' , IO:false , payload }, message:message});
1545
1567
  }
1546
1568
  else
1547
1569
  this.info(`Device query for '${entity.device.ieeeAddr}' complete`);
package/main.js CHANGED
@@ -216,8 +216,6 @@ class Zigbee extends utils.Adapter {
216
216
  const zigbeeOptions = this.getZigbeeOptions();
217
217
  this.zbController = new ZigbeeController(this);
218
218
 
219
-
220
-
221
219
  this.zbController.on('log', this.onLog.bind(this));
222
220
  this.zbController.on('ready', this.onZigbeeAdapterReady.bind(this));
223
221
  this.zbController.on('disconnect', this.onZigbeeAdapterDisconnected.bind(this));
@@ -229,6 +227,7 @@ class Zigbee extends utils.Adapter {
229
227
  this.zbController.on('publish', this.stController.publishToState.bind(this.stController));
230
228
  this.stController.on('send_payload', this.zbController.publishPayload.bind(this.zbController));
231
229
  this.stController.on('changed', this.zbController.publishFromState.bind(this.zbController));
230
+ this.zbController.on('resend_states', this.stController.handleStateReset.bind(this.stController));
232
231
  this.stController.on('device_query', this.zbController.deviceQuery.bind(this.zbController));
233
232
  this.zbController.on('acknowledge_state', this.acknowledgeState.bind(this));
234
233
  this.zbController.on('stash_error', this.stController.stashErrors.bind(this.stController));
@@ -247,7 +246,7 @@ class Zigbee extends utils.Adapter {
247
246
  this.log.info('Autostart Zigbee subsystem');
248
247
  this.doConnect();
249
248
  }
250
- else this.log.warn('Zigbee autostart option not set - omitting start of zigbee substystem!');
249
+ else this.log.warn('Zigbee autostart option not set - omitting start of zigbee subsystem!');
251
250
  }
252
251
  updateDebugLevel(state) {
253
252
  const dbActive = state === 'debug';
@@ -833,6 +832,7 @@ class Zigbee extends utils.Adapter {
833
832
  disableBackup: this.config.disableBackup,
834
833
  extPanIdFix: extPanIdFix,
835
834
  startWithInconsistent: override.startWithInconsistent ? override.startWithInconsistent: this.config.startWithInconsistent || false,
835
+ availableUpdateTime:this.config.availableUpdateTime,
836
836
  };
837
837
  }
838
838
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.zigbee",
3
- "version": "3.2.2",
3
+ "version": "3.2.4",
4
4
  "author": {
5
5
  "name": "Kirov Ilya",
6
6
  "email": "kirovilya@gmail.com"
@@ -29,7 +29,7 @@
29
29
  "uri-js": "^4.4.1",
30
30
  "typescript": "^5.9.2",
31
31
  "zigbee-herdsman": "^6.0.0",
32
- "zigbee-herdsman-converters": "^25.37.0"
32
+ "zigbee-herdsman-converters": "^25.50.0"
33
33
  },
34
34
  "description": "Zigbee devices",
35
35
  "devDependencies": {