iobroker.zigbee 3.2.2 → 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 +10 -0
- package/admin/admin.js +120 -48
- package/admin/index_m.html +4 -0
- package/admin/tab_m.html +6 -1
- package/io-package.json +16 -15
- package/lib/DeviceDebug.js +1 -1
- package/lib/commands.js +3 -0
- package/lib/statescontroller.js +81 -31
- package/lib/zbDeviceAvailability.js +45 -16
- package/lib/zbDeviceEvent.js +5 -0
- package/lib/zigbeecontroller.js +61 -39
- package/main.js +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -154,6 +154,16 @@ 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
|
+
|
|
157
167
|
### 3.2.2 (2025-10-27)
|
|
158
168
|
* (asgothian) Bugfix on delete object.
|
|
159
169
|
* (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="
|
|
607
|
-
<
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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(',') || ' ';
|
|
640
667
|
let memberCount = 0;
|
|
641
|
-
|
|
642
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(' ')
|
|
@@ -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 ?
|
|
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
|
|
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('<
|
|
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
|
-
|
|
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'}">
|
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,21 @@
|
|
|
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
|
+
},
|
|
6
19
|
"3.2.2": {
|
|
7
20
|
"en": "Bugfix on delete object.\nimproved device query.\nfixed delete device with local overrides.\n",
|
|
8
21
|
"de": "Bugfix auf Löschobjekt.\nverbesserte geräteabfrage.\nfeste löschvorrichtung mit lokalen overrides.\n",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
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",
|
|
81
94
|
"uk": "Видалення додаткового входу\nДодати додаткові конфігурації\nНе читати стани від деактивованих пристроїв\nІгноровані пристрої для оновлення групового стану\nЗміна відображення для деактивованих пристроїв в дереві об'єкта (сірий, не підключений значок)\nдокладніше пристрій debug\nпристрій Debug UI удосконалення\nПірсинг і пристрій Query гудзики на маршрутизаторних картках\nZHC 25.31.0, ZH 6.1.2 або новачок\nВаріанти, засновані на визначених параметрах ZHC",
|
|
82
95
|
"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
|
{
|
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);
|
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
|
@@ -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
|
-
|
|
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) {
|
|
@@ -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${
|
|
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:
|
|
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${
|
|
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
|
|
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": {
|