iobroker.zigbee 3.2.1 → 3.2.3
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 +16 -0
- package/admin/admin.js +124 -50
- package/admin/index_m.html +4 -0
- package/admin/tab_m.html +6 -1
- package/io-package.json +29 -28
- package/lib/DeviceDebug.js +1 -1
- package/lib/commands.js +22 -2
- package/lib/localConfig.js +6 -1
- package/lib/statescontroller.js +81 -31
- package/lib/zbDeviceAvailability.js +45 -16
- package/lib/zbDeviceEvent.js +5 -0
- package/lib/zigbeecontroller.js +78 -49
- package/main.js +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -154,6 +154,22 @@ You can thank the authors by these links:
|
|
|
154
154
|
|
|
155
155
|
-----------------------------------------------------------------------------------------------------
|
|
156
156
|
## Changelog
|
|
157
|
+
### 3.2.3 (2025-10-31)
|
|
158
|
+
* (asgothian) Improvements on debug UI
|
|
159
|
+
* (asgothian) Option 'resend_states' to publish state values to device on reconnect
|
|
160
|
+
* (asgothian) Improved group card
|
|
161
|
+
* (asgothian) Improved group info
|
|
162
|
+
* (asgothian) Modified coordinator card (2 sides)
|
|
163
|
+
* (asgothian) retry on error 25
|
|
164
|
+
* (asgothian) clear stashed error messages
|
|
165
|
+
* (asgothian) ZHC 25.50.0 or newer
|
|
166
|
+
|
|
167
|
+
### 3.2.2 (2025-10-27)
|
|
168
|
+
* (asgothian) Bugfix on delete object.
|
|
169
|
+
* (asgothian) improved device query.
|
|
170
|
+
* (asgothain) fixed delete device with local overrides.
|
|
171
|
+
*
|
|
172
|
+
|
|
157
173
|
### 3.2.1 (2025-10-26)
|
|
158
174
|
* (asgothian) fix bug #2640
|
|
159
175
|
*
|
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,
|
|
@@ -466,7 +466,8 @@ function showLocalData() {
|
|
|
466
466
|
const keys = item.replace('d_delall_', '').split('-');
|
|
467
467
|
const model = models[keys[0]];
|
|
468
468
|
const device = model.devices.find( (d) => d.native.id === keys[1]);
|
|
469
|
-
|
|
469
|
+
console.warn(`setting delete confirmation with ${keys[1]} ${models[keys[0]].devices?.length} ${models[keys[0]]?.model.model} `);
|
|
470
|
+
deleteConfirmation(keys[1], device.common.name, keys[1], models[keys[0]]?.devices?.length <=1 ? models[keys[0]]?.model?.model : undefined);
|
|
470
471
|
});
|
|
471
472
|
}
|
|
472
473
|
}
|
|
@@ -497,6 +498,7 @@ function getCard(dev) {
|
|
|
497
498
|
rooms.push(dev.rooms[r]);
|
|
498
499
|
}
|
|
499
500
|
}
|
|
501
|
+
|
|
500
502
|
const NoInterviewIcon = dev.info?.device?.interviewstate != 'SUCCESSFUL' ? `<div class="col tool"><i class="material-icons icon-red">perm_device_information</i></div>` : ``;
|
|
501
503
|
const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
|
|
502
504
|
const rid = id.split('.').join('_');
|
|
@@ -586,32 +588,59 @@ function getCoordinatorCard(dev) {
|
|
|
586
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>` : '',
|
|
587
589
|
info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
|
|
588
590
|
<ul>
|
|
589
|
-
<li><span class="label">type:</span><span>${coordinatorinfo.type}</span></li>
|
|
590
|
-
<li><span class="label">version:</span><span>${coordinatorinfo.version}</span></li>
|
|
591
|
-
<li><span class="label">revision:</span><span>${coordinatorinfo.revision}</span></li>
|
|
592
|
-
<li><span class="label">port:</span><span>${coordinatorinfo.port}</span></li>
|
|
593
|
-
<li><span class="label">channel:</span><span>${coordinatorinfo.channel}</span></li>
|
|
594
|
-
<li><span class="label">------------</span><span>Software versions </span></li>
|
|
595
|
-
<li><span class="label">adapter:</span><span>${coordinatorinfo.installedVersion}</span></li>
|
|
596
|
-
<li><span class="label">installed from:</span><span>${coordinatorinfo.installSource}</span></li>
|
|
597
|
-
<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>
|
|
598
596
|
</ul>
|
|
599
597
|
</div>`,
|
|
600
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>',
|
|
601
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>`,
|
|
602
600
|
card = `<div id="${id}" class="device">
|
|
603
|
-
<div class="card hoverable">
|
|
604
|
-
<div class="
|
|
605
|
-
<
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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>
|
|
615
644
|
</div>
|
|
616
645
|
</div>
|
|
617
646
|
</div>`;
|
|
@@ -636,19 +665,18 @@ function getGroupCard(dev) {
|
|
|
636
665
|
const roomInfo = rooms.length ? `<li><span class="labelinfo">rooms:</span><span>${rooms.join(',') || ''}</span></li>` : '';
|
|
637
666
|
const room = rooms.join(',') || ' ';
|
|
638
667
|
let memberCount = 0;
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
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>`);
|
|
642
670
|
if (dev.memberinfo === undefined) {
|
|
643
|
-
info
|
|
671
|
+
info.push(`<li><span class="labelinfo">No devices in group</span></li>`);
|
|
644
672
|
} else {
|
|
645
673
|
for (let m = 0; m < dev.memberinfo.length; m++) {
|
|
646
|
-
info
|
|
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>`);
|
|
647
675
|
}
|
|
648
676
|
memberCount = (dev.memberinfo.length < 8 ? dev.memberinfo.length : 7);
|
|
649
677
|
}
|
|
650
678
|
;
|
|
651
|
-
info
|
|
679
|
+
info.push(` ${roomInfo}</ul>
|
|
652
680
|
</div>`);
|
|
653
681
|
const infoBtn = `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>`;
|
|
654
682
|
const image = `<img src="${dev.common.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
|
|
@@ -666,7 +694,7 @@ function getGroupCard(dev) {
|
|
|
666
694
|
<span id="dName" class="card-title truncate">${title}</span><!----!>
|
|
667
695
|
</div>
|
|
668
696
|
<i class="left">${image}</i>
|
|
669
|
-
${info}
|
|
697
|
+
${info.join('')}
|
|
670
698
|
|
|
671
699
|
<div class="footer right-align"></div>
|
|
672
700
|
</div>
|
|
@@ -1389,7 +1417,7 @@ function getCoordinatorInfo() {
|
|
|
1389
1417
|
sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
|
|
1390
1418
|
if (msg) {
|
|
1391
1419
|
if (msg.error) {
|
|
1392
|
-
errorData.push(msg.error);
|
|
1420
|
+
//errorData.push(msg.error);
|
|
1393
1421
|
delete msg.error;
|
|
1394
1422
|
isHerdsmanRunning = false;
|
|
1395
1423
|
} else {
|
|
@@ -1568,13 +1596,7 @@ async function editDeviceOptions(id, isModel) {
|
|
|
1568
1596
|
}
|
|
1569
1597
|
else
|
|
1570
1598
|
{
|
|
1571
|
-
|
|
1572
|
-
try {
|
|
1573
|
-
_do[k].value = JSON.parse(val);
|
|
1574
|
-
}
|
|
1575
|
-
catch {
|
|
1576
|
-
_do[k].value = val;
|
|
1577
|
-
}
|
|
1599
|
+
_do[k].value = $(`#option_value_${k}`).val();
|
|
1578
1600
|
}
|
|
1579
1601
|
if (_do[k].key.length > 0) {
|
|
1580
1602
|
console.warn(`dok: ${_do[k].key} : ${_do[k].value}`);
|
|
@@ -1617,11 +1639,14 @@ async function editDeviceOptions(id, isModel) {
|
|
|
1617
1639
|
|
|
1618
1640
|
const dialogData = {};
|
|
1619
1641
|
|
|
1642
|
+
const adapterDefinedOptions = ['resend_states']
|
|
1643
|
+
|
|
1620
1644
|
if (isModel) {
|
|
1621
1645
|
const model = id.model;
|
|
1622
1646
|
dialogData.model = model;
|
|
1623
1647
|
dialogData.availableOptions = model.options.slice() || [];
|
|
1624
1648
|
dialogData.availableOptions.push('custom');
|
|
1649
|
+
dialogData.availableOptions.push(...adapterDefinedOptions)
|
|
1625
1650
|
if (model.hasLegacyDef) dialogData.availableOptions.push('legacy');
|
|
1626
1651
|
dialogData.setOptions = {};
|
|
1627
1652
|
for (const k in Object.keys(id.setOptions))
|
|
@@ -1636,6 +1661,7 @@ async function editDeviceOptions(id, isModel) {
|
|
|
1636
1661
|
const dev = devices.find((d) => d._id == id);
|
|
1637
1662
|
dialogData.model = dev.info.mapped;
|
|
1638
1663
|
dialogData.availableOptions = (dev.info.mapped ? dev.info.mapped.options.slice() || []:[]);
|
|
1664
|
+
dialogData.availableOptions.push(...adapterDefinedOptions)
|
|
1639
1665
|
dialogData.name = dev.common.name;
|
|
1640
1666
|
dialogData.icon = dev.common.icon || dev.icon;
|
|
1641
1667
|
dialogData.default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
|
|
@@ -1731,6 +1757,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
|
|
|
1731
1757
|
const filterSet = new Set();
|
|
1732
1758
|
let isodd = true;
|
|
1733
1759
|
const buttonList = [];
|
|
1760
|
+
const idRed = ' id="dbgred"'
|
|
1734
1761
|
if (dbgMsghide.has('i_'+devID)) {
|
|
1735
1762
|
console.warn('in all filtered out')
|
|
1736
1763
|
Html.push(' ')
|
|
@@ -1742,7 +1769,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
|
|
|
1742
1769
|
let fs = '';
|
|
1743
1770
|
for (const state of item.states) {
|
|
1744
1771
|
fs = fs+state.id+'.'+fne(item);
|
|
1745
|
-
const redText = (item.errors && item.errors.length > 0 ?
|
|
1772
|
+
const redText = (item.errors && item.errors.length > 0 ? idRed : '');
|
|
1746
1773
|
idx--;
|
|
1747
1774
|
const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
|
|
1748
1775
|
if (idx==0) {
|
|
@@ -1750,7 +1777,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
|
|
|
1750
1777
|
buttonList.push(item.dataID)
|
|
1751
1778
|
LHtml.push(`<td${rowspan}>${msgbutton}</td><td${rowspan}>${safestring(item.payload)}</td>`);
|
|
1752
1779
|
}
|
|
1753
|
-
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>`);
|
|
1754
1781
|
IHtml.unshift(...LHtml)
|
|
1755
1782
|
}
|
|
1756
1783
|
if (filter)
|
|
@@ -1835,7 +1862,7 @@ function displayDebugMessages(msg) {
|
|
|
1835
1862
|
const modelUrl = (type_url === 'unknown') ? 'unknown' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${image}</a>`;
|
|
1836
1863
|
const devName = (dev && dev.common && dev.common.name) ? dev.common.name : 'unnamed';
|
|
1837
1864
|
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>`
|
|
1838
|
-
const dbutton = `<a id="d_${devID}" class="btn-floating waves-effect waves-light red tooltipped center-align hoverable translateT" title="
|
|
1865
|
+
const dbutton = `<a id="d_${devID}" class="btn-floating waves-effect waves-light red tooltipped center-align hoverable translateT" title="Delete debug messages"><i class="material-icons icon-yellow large">delete_forever</i></a>`;
|
|
1839
1866
|
buttonNames.push(devID);
|
|
1840
1867
|
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>`);
|
|
1841
1868
|
if (dbgData[devID].IN.length > 0) {
|
|
@@ -1988,7 +2015,7 @@ function getDevices() {
|
|
|
1988
2015
|
sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
|
|
1989
2016
|
if (msg) {
|
|
1990
2017
|
if (msg.error) {
|
|
1991
|
-
errorData.push(msg.error);
|
|
2018
|
+
//errorData.push(msg.error);
|
|
1992
2019
|
delete msg.error;
|
|
1993
2020
|
isHerdsmanRunning = false;
|
|
1994
2021
|
} else {
|
|
@@ -2006,7 +2033,7 @@ function getDevices() {
|
|
|
2006
2033
|
if (msg) {
|
|
2007
2034
|
extractDevicesData(msg);
|
|
2008
2035
|
if (msg.error) {
|
|
2009
|
-
errorData.push(msg.error);
|
|
2036
|
+
//errorData.push(msg.error);
|
|
2010
2037
|
isHerdsmanRunning = false;
|
|
2011
2038
|
} else {
|
|
2012
2039
|
isHerdsmanRunning = true;
|
|
@@ -2043,7 +2070,7 @@ function extractDevicesData(msg) {
|
|
|
2043
2070
|
$('#state_cleanup_btn').removeClass('hide');
|
|
2044
2071
|
else
|
|
2045
2072
|
$('#state_cleanup_btn').addClass('hide');
|
|
2046
|
-
if (msg.errors
|
|
2073
|
+
if (msg.errors?.hasData) {
|
|
2047
2074
|
$('#show_errors_btn').removeClass('hide');
|
|
2048
2075
|
errorData = msg.errors;
|
|
2049
2076
|
}
|
|
@@ -2084,14 +2111,14 @@ function getMap(rebuild) {
|
|
|
2084
2111
|
$('#refresh').removeClass('disabled');
|
|
2085
2112
|
if (msg) {
|
|
2086
2113
|
if (msg.error) {
|
|
2087
|
-
errorData.push(msg.error);
|
|
2114
|
+
//errorData.push(msg.error);
|
|
2088
2115
|
isHerdsmanRunning = false;
|
|
2089
2116
|
updateStartButton();
|
|
2090
2117
|
} else {
|
|
2091
2118
|
isHerdsmanRunning = true;
|
|
2092
2119
|
updateStartButton();
|
|
2093
2120
|
if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
|
|
2094
|
-
showMessage(msg.errors.join('<
|
|
2121
|
+
showMessage(msg.errors.join('<br>'), 'Map generation messages');
|
|
2095
2122
|
}
|
|
2096
2123
|
map = msg;
|
|
2097
2124
|
showNetworkMap(devices, map);
|
|
@@ -2229,7 +2256,34 @@ function load(settings, onChange) {
|
|
|
2229
2256
|
cleanConfirmation();
|
|
2230
2257
|
});
|
|
2231
2258
|
$('#show_errors_btn').click(function () {
|
|
2232
|
-
|
|
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
|
+
})
|
|
2233
2287
|
});
|
|
2234
2288
|
$('#download_icons_btn').click(function () {
|
|
2235
2289
|
showMessage(downloadIcons());
|
|
@@ -2617,6 +2671,26 @@ socket.on('stateChange', function (id, state) {
|
|
|
2617
2671
|
getDevices();
|
|
2618
2672
|
}
|
|
2619
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
|
+
|
|
2620
2694
|
} else {
|
|
2621
2695
|
const devId = getDevId(id);
|
|
2622
2696
|
putEventToNode(devId);
|
|
@@ -4020,7 +4094,7 @@ function genDevInfo(device) {
|
|
|
4020
4094
|
`<div style="font-size: 0.9em">
|
|
4021
4095
|
<ul>`);
|
|
4022
4096
|
for (const item in mapped) {
|
|
4023
|
-
if (item == 'model')
|
|
4097
|
+
if (item == 'model' && mapped.model != 'group')
|
|
4024
4098
|
mappedInfo.push(genRow(item,modelUrl));
|
|
4025
4099
|
else
|
|
4026
4100
|
if (typeof mapped[item] != 'object') mappedInfo.push(genRow(item,mapped[item]));
|
|
@@ -4042,7 +4116,7 @@ function genDevInfo(device) {
|
|
|
4042
4116
|
</ul>
|
|
4043
4117
|
</div>`;
|
|
4044
4118
|
}
|
|
4045
|
-
const imgSrc = device.icon || device.common.icon;
|
|
4119
|
+
const imgSrc = mapped?.model == 'group' ? mapped.icon : device.icon || device.common.icon;
|
|
4046
4120
|
const imgInfo = (imgSrc) ? `<img src=${imgSrc} width='150px' onerror="this.onerror=null;this.src='img/unavailable.png';"><div class="divider"></div>` : '';
|
|
4047
4121
|
const info =[
|
|
4048
4122
|
`<div class="col ${device.memberinfo != undefined ? 's12 m12 l12 xl12':'s12 m6 l6 xl6'}">
|
package/admin/index_m.html
CHANGED
|
@@ -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:
|
|
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.
|
|
4
|
+
"version": "3.2.3",
|
|
5
5
|
"news": {
|
|
6
|
+
"3.2.3": {
|
|
7
|
+
"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",
|
|
8
|
+
"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",
|
|
9
|
+
"ru": "Улучшения в Debug UI\nВариант «resend_states» для публикации значений состояния на устройстве при повторном подключении\nУлучшенная групповая карта\nУлучшенная групповая информация\nИзмененная карта координатора (2 стороны)\nвозвращение к ошибке 25\nскрытые сообщения об ошибках\nZHC 25.50.0 или новее",
|
|
10
|
+
"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",
|
|
11
|
+
"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",
|
|
12
|
+
"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",
|
|
13
|
+
"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",
|
|
14
|
+
"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",
|
|
15
|
+
"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",
|
|
16
|
+
"uk": "Удосконалення на дебурзі UI\nВаріант 'resend_states' для публікації державних значень для пристрою на відключенні\nПокращена групова карта\nПокращена інформація групи\nЗмінена карта координатора (2 сторони)\nптиця по помилки 25\nчіткі повідомлення про помилку\nZHC 25.50.0 або новачок",
|
|
17
|
+
"zh-cn": "调试 UI 的改进\n选项“ resend_ states ” 以在重新连接时发布状态值以设备\n改进组卡\n改进组信息\n修改的协调员卡(2面)\n重试错误 25\n清除隐藏的错误消息\nZHC 25.50.0或更新"
|
|
18
|
+
},
|
|
19
|
+
"3.2.2": {
|
|
20
|
+
"en": "Bugfix on delete object.\nimproved device query.\nfixed delete device with local overrides.\n",
|
|
21
|
+
"de": "Bugfix auf Löschobjekt.\nverbesserte geräteabfrage.\nfeste löschvorrichtung mit lokalen overrides.\n",
|
|
22
|
+
"ru": "Bugfix для удаления объекта.\nулучшенный запрос устройства.\nфиксированное устройство удаления с локальными переопределениями.\n",
|
|
23
|
+
"pt": "Correcção de erros ao apagar o objecto.\npesquisa de dispositivos melhorada.\ndispositivo de exclusão fixo com substituições locais.\n",
|
|
24
|
+
"nl": "Bugfix bij verwijderen van object.\nverbeterde apparaatquery.\nvast delete apparaat met lokale overrides.\n",
|
|
25
|
+
"fr": "Correction sur objet de suppression.\nrequête de périphérique améliorée.\ndispositif de suppression fixe avec redéfinitions locales.\n",
|
|
26
|
+
"it": "Bugfix su delete object.\nmigliore query del dispositivo.\ndispositivo di cancellazione fisso con override locali.\n",
|
|
27
|
+
"es": "Bugfix en el objeto borrado.\nmejorada consulta de dispositivos.\ndispositivo de eliminación fijo con anulas locales.\n",
|
|
28
|
+
"pl": "Bugfix na usunąć obiekt.\nulepszone zapytanie urządzenia.\nnaprawione urządzenie usuwające z lokalnymi przekroczeniami.\n",
|
|
29
|
+
"uk": "Виправлення помилок при видаленні об'єкта.\nпокращений пристрій запиту.\nфіксований пристрій видалення з локальними перенаряддями.\n",
|
|
30
|
+
"zh-cn": "删除对象上的错误修正 .\n改进设备查询 .\n有本地覆盖的固定删除设备 .\n"
|
|
31
|
+
},
|
|
6
32
|
"3.2.1": {
|
|
7
33
|
"en": "fix bug #2640\n",
|
|
8
34
|
"de": "fehler beheben #2640\n",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"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",
|
|
68
94
|
"uk": "Видалення додаткового входу\nДодати додаткові конфігурації\nНе читати стани від деактивованих пристроїв\nІгноровані пристрої для оновлення групового стану\nЗміна відображення для деактивованих пристроїв в дереві об'єкта (сірий, не підключений значок)\nдокладніше пристрій debug\nпристрій Debug UI удосконалення\nПірсинг і пристрій Query гудзики на маршрутизаторних картках\nZHC 25.31.0, ZH 6.1.2 або новачок\nВаріанти, засновані на визначених параметрах ZHC",
|
|
69
95
|
"zh-cn": "删除额外的日志\n添加额外的配置\n不读取已关闭设备的状态\n忽略组状态更新的已关闭设备\n更改对象树上已关闭设备的显示( gray, 无连接图标)\n更详细的设备调试\n设备调试 UI 改进\n路由器卡上的对齐和设备查询按钮\nZHC 25.31.0,ZH 6.1.2或更新\n基于 ZHC 定义选项的选项"
|
|
70
|
-
},
|
|
71
|
-
"3.1.2": {
|
|
72
|
-
"en": "ZHC 25.x\nZH 6.x\nFix pairing bug\nadd ping messages to device debug to verify ping failure reasons\n",
|
|
73
|
-
"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",
|
|
74
|
-
"ru": "ZHC 25.x\nZH 6.x\nИсправлена ошибка сопряжения.\nдобавьте сообщения ping в отладке устройства для проверки причин отказа ping\n",
|
|
75
|
-
"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",
|
|
76
|
-
"nl": "ZHC 25.x\nZH 6.x\nRepareer koppelingsfout\nVoeg pingberichten toe aan apparaatdebug om pingfoutredenen te controleren\n",
|
|
77
|
-
"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",
|
|
78
|
-
"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",
|
|
79
|
-
"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",
|
|
80
|
-
"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",
|
|
81
|
-
"uk": "ЗХК 25.x\nЗХ 6.х\nВиправлено помилку з'єднання\nдодано повідомлення ping до відлагодження пристрою для перевірки причин невдачі ping\n",
|
|
82
|
-
"zh-cn": "ZHC 25.x\nZH 6.x (英语)\n修复配对问题\n将ping消息添加到设备调试中,以验证ping失败的原因\n"
|
|
83
|
-
},
|
|
84
|
-
"3.0.5": {
|
|
85
|
-
"en": "fix random error where devices are not shown due to illegal groups\ndrop support for node 18\nRequired node Versions Node 20.19.0 or 22.11.0 or newer (courtesy of ZH 4.4.1 / ZHC 24.8.0)",
|
|
86
|
-
"de": "fehler beheben, bei denen geräte wegen illegaler gruppen nicht angezeigt werden\ndrop-unterstützung für knoten 183\nErforderliche Knotenversionen Nr. 20.19.0 oder 22.11.0 oder neuer (courtesy of ZH 4.4.1 / ZHC 24.8.0)",
|
|
87
|
-
"ru": "исправить случайную ошибку, когда устройства не отображаются из-за незаконных групп\nподдержка drop для node 18\nТребуемый узел Версии Узел 20.19.0 или 22.11.0 или новее (документ ZH 4.4.1 / ZHC 24.8.0)",
|
|
88
|
-
"pt": "corrigir erro aleatório onde os dispositivos não são mostrados devido a grupos ilegais\nsuporte de gota para o nó 18\nNó necessário Versões Node 20.19.0 ou 22.11.0 ou mais recente (cortesia de ZH 4.4.1 / ZHC 24.8.0)",
|
|
89
|
-
"nl": "fix willekeurige fout wanneer apparaten niet worden getoond als gevolg van illegale groepen\ndrop ondersteuning voor knooppunt 18\nVereiste node Versies Node 20.19.0 of 22.11.0 of nieuwer (met dank aan ZH 4.4.1 / ZHC 24.8.0)",
|
|
90
|
-
"fr": "corriger une erreur aléatoire lorsque les appareils ne sont pas montrés en raison de groupes illégaux\nsupport de chute pour noeud 18\nVersion requise Node 20.19.0 ou 22.11.0 ou plus récent (avec la permission de ZH 4.4.1 / ZHC 248.0)",
|
|
91
|
-
"it": "correggere errore casuale in cui i dispositivi non vengono visualizzati a causa di gruppi illegali\nsupporto a goccia per nodo 18\nNodo richiesto Versioni Nodo 20.19.0 o 22.11.0 o più recente (cortesia di ZH 4.4.1 / ZHC 24.8.0)",
|
|
92
|
-
"es": "corregir errores aleatorios donde no se muestran dispositivos debido a grupos ilegales\napoyo a los nodos 18\nNodo requerido Versiones Nodo 20.19.0 o 22.11.0 o nuevo (cortesía de ZH 4.4.1 / ZHC 24.8.0)",
|
|
93
|
-
"pl": "naprawić błąd losowy w przypadku gdy urządzenia nie są wyświetlane z powodu nielegalnych grup\nobsługa zrzutu dla węzła 18\nWymagany węzeł Wersje Węzeł 20.19.0 lub 22.11.0 lub nowszy (uprzejmość ZH 4.4.1 / ZHC 24.8.0)",
|
|
94
|
-
"uk": "виправити випадкові помилки, де пристрої не відображаються через незаконні групи\nпідтримка крапель для вузла 18 років\nПотрібні версії вузла Node 20.19.0 або 22.11.0 або новачка (кількість ЗЗ 4.4.1 / ЗЖК 24.8.0)",
|
|
95
|
-
"zh-cn": "在设备因非法组而未显示时修复随机错误\n放弃节点支持 第 18 条\n要求的节点版本为20.19.0或22.11.0或更新(礼仪为ZH 4.4.1 / ZHC 24.8.0)"
|
|
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
|
{
|
package/lib/DeviceDebug.js
CHANGED
|
@@ -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
|
|
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);
|
|
@@ -445,6 +448,14 @@ class Commands {
|
|
|
445
448
|
return undefined;
|
|
446
449
|
|
|
447
450
|
}
|
|
451
|
+
|
|
452
|
+
function haveBindableClusters(clusters) {
|
|
453
|
+
const nonBindableClusters = [25,33, 4096]
|
|
454
|
+
if (Array.isArray(clusters)) {
|
|
455
|
+
return (clusters.filter((candidate) => !nonBindableClusters.includes(candidate)).length > 0);
|
|
456
|
+
}
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
448
459
|
const rv = {};
|
|
449
460
|
try {
|
|
450
461
|
rv.device = {
|
|
@@ -463,19 +474,28 @@ class Commands {
|
|
|
463
474
|
date_code:device.device.dateCode,
|
|
464
475
|
build:device.device.softwareBuildID,
|
|
465
476
|
interviewstate:device.device.interviewState || 'UNKNOWN',
|
|
477
|
+
BindSource: false,
|
|
478
|
+
isGroupable: false,
|
|
466
479
|
}
|
|
467
480
|
rv.endpoints = [];
|
|
481
|
+
let dBindSource = false;
|
|
482
|
+
let disGroupable = false;
|
|
468
483
|
for (const ep_idx in device.endpoints) {
|
|
469
484
|
const ep = device.endpoints[ep_idx];
|
|
485
|
+
const bindable = haveBindableClusters(ep.outputClusters);
|
|
486
|
+
dBindSource |= bindable;
|
|
470
487
|
rv.endpoints.push({
|
|
471
488
|
ID:ep.ID,
|
|
472
489
|
epName: device.mapped?.endpoint ? getKey(device.mapped?.endpoint(device), ep.ID) : ep.ID,
|
|
473
490
|
profile:ep.profileID,
|
|
474
491
|
input_clusters:ep.inputClusters,
|
|
475
492
|
output_clusters:ep.outputClusters,
|
|
493
|
+
BindSource: Boolean(bindable),
|
|
476
494
|
})
|
|
477
|
-
|
|
495
|
+
disGroupable |= ep.inputClusters.includes(4);
|
|
478
496
|
}
|
|
497
|
+
rv.device.isGroupable = Boolean(disGroupable);
|
|
498
|
+
rv.device.BindSource = Boolean(dBindSource);
|
|
479
499
|
if (device.mapped) {
|
|
480
500
|
rv.mapped = {
|
|
481
501
|
model:device.mapped.model,
|
|
@@ -745,7 +765,7 @@ class Commands {
|
|
|
745
765
|
else this.adapter.sendTo(from, command, {error:err}, callback);
|
|
746
766
|
}
|
|
747
767
|
this.adapter.sendTo(from, command, {}, callback);
|
|
748
|
-
this.adapter.stController.localConfig.removeLocalData()
|
|
768
|
+
if (msg.dev) this.adapter.stController.localConfig.removeLocalData(devId, msg.model);
|
|
749
769
|
} else {
|
|
750
770
|
this.adapter.sendTo(from, command, {error: err}, callback);
|
|
751
771
|
}
|
package/lib/localConfig.js
CHANGED
|
@@ -142,6 +142,12 @@ class localConfig extends EventEmitter {
|
|
|
142
142
|
return defaultName;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
removeLocalData(dev, model) {
|
|
146
|
+
delete this.localData.by_id[dev];
|
|
147
|
+
if (model) delete this.localData.by_model[model];
|
|
148
|
+
this.retainData();
|
|
149
|
+
}
|
|
150
|
+
|
|
145
151
|
IconForId(id, model, defaultIcon) {
|
|
146
152
|
let modeloverride = {};
|
|
147
153
|
this.debug('Icon for id with ' + id + ', ' + model + ' and ' + defaultIcon);
|
|
@@ -303,7 +309,6 @@ class localConfig extends EventEmitter {
|
|
|
303
309
|
}
|
|
304
310
|
|
|
305
311
|
writeData() {
|
|
306
|
-
this.info('retaining local config: ' + JSON.stringify(this.localData));
|
|
307
312
|
try {
|
|
308
313
|
fs.writeFileSync(this.filename, JSON.stringify(this.localData, null, 2))
|
|
309
314
|
this.info('Saved local configuration data');
|
package/lib/statescontroller.js
CHANGED
|
@@ -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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
394
|
-
this.
|
|
395
|
-
|
|
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: '
|
|
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:{
|
|
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:`
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
225
|
-
|
|
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
|
|
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)
|
|
243
|
+
if (!has_elevated_debug)
|
|
244
|
+
this.info(message);
|
|
230
245
|
pingCount.reported++;
|
|
231
246
|
} else {
|
|
232
|
-
this.debug(
|
|
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)
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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
|
}
|
package/lib/zbDeviceEvent.js
CHANGED
|
@@ -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':
|
package/lib/zigbeecontroller.js
CHANGED
|
@@ -657,7 +657,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
657
657
|
mapped = await zigbeeHerdsmanConverters.findByDevice(device, false);
|
|
658
658
|
}
|
|
659
659
|
catch (error) {
|
|
660
|
-
|
|
660
|
+
// intentionally empty
|
|
661
661
|
}
|
|
662
662
|
if (!mapped) {
|
|
663
663
|
if (device.type === 'Coordinator')
|
|
@@ -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;
|
|
@@ -812,7 +812,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
812
812
|
else if (this._permitJoinInterval) {
|
|
813
813
|
const timestr = this._permitJoinTime > 0 ? ` with ${this._permitJoinTime} second${this._permitJoinTime > 1 ? 's':''} remaining.`: '.';
|
|
814
814
|
this.info(`Closed Zigbee network${timestr}`)
|
|
815
|
-
this.emit('pairing', `Closed network${timestr}
|
|
815
|
+
this.emit('pairing', `Closed network${timestr}`, 0);
|
|
816
816
|
clearInterval(this._permitJoinInterval);
|
|
817
817
|
this._permitJoinInterval = null;
|
|
818
818
|
}
|
|
@@ -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
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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 = `
|
|
1370
|
-
this.emit('device_debug', { ID:debugID, data: {
|
|
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
|
-
}
|
|
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) {
|
|
@@ -1517,24 +1537,33 @@ class ZigbeeController extends EventEmitter {
|
|
|
1517
1537
|
}
|
|
1518
1538
|
if (sources.length == 0) sources.push(entity.device.endpoints[0]);
|
|
1519
1539
|
for (const source of sources) {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1540
|
+
for (const k of converter.key)
|
|
1541
|
+
try {
|
|
1542
|
+
await converter.convertGet(source, k, {device:entity.device});
|
|
1543
|
+
payload.read_states.push(`${cCount}.${k}`);
|
|
1544
|
+
if (elevated) {
|
|
1545
|
+
const message = `read for state ${k} of '${converter.key.join(',')}' of '${entity.device.ieeeAddr}/${source.ID}' after device query`;
|
|
1546
|
+
this.warn(`ELEVATED O02.1 ${message}`);
|
|
1547
|
+
this.emit('device_debug', { ID:debugID, data: { flag: '03', IO:false }, message:message });
|
|
1548
|
+
}
|
|
1549
|
+
else
|
|
1550
|
+
this.debug(`read for state${converter.key.length ? '' : 's'} '${converter.key.join(',')}' of '${entity.device.ieeeAddr}/${source.ID}' after device query`);
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
payload.unread_states.push(`${cCount}.${k}`);
|
|
1553
|
+
if (elevated) {
|
|
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'}`;
|
|
1555
|
+
this.warn(`ELEVATED OE02.1 ${message}`);
|
|
1556
|
+
this.emit('device_debug', { ID:debugID, data: { error: 'NOTREAD' , IO:false }, message:message });
|
|
1557
|
+
}
|
|
1558
|
+
else
|
|
1559
|
+
this.debug(`failed to read for state${converter.key.length ? '' : 's'} '${converter.key.join(',')}' of '${source.ID}'after device query`);
|
|
1528
1560
|
}
|
|
1529
|
-
else
|
|
1530
|
-
this.debug(`failed to read for state${converter.key.length ? '' : 's'} '${converter.key.join(',')}' of '${source.ID}'after device query`);
|
|
1531
|
-
}
|
|
1532
1561
|
}
|
|
1533
1562
|
}
|
|
1534
1563
|
}
|
|
1535
1564
|
if (elevated) {
|
|
1536
1565
|
const message = `ELEVATED O07: Device query for '${entity.device.ieeeAddr}}' complete`;
|
|
1537
|
-
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});
|
|
1538
1567
|
}
|
|
1539
1568
|
else
|
|
1540
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
|
|
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.
|
|
3
|
+
"version": "3.2.3",
|
|
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.
|
|
32
|
+
"zigbee-herdsman-converters": "^25.50.0"
|
|
33
33
|
},
|
|
34
34
|
"description": "Zigbee devices",
|
|
35
35
|
"devDependencies": {
|