iobroker.zigbee 1.6.12 → 1.6.14
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 +58 -14
- package/admin/admin.js +94 -20
- package/admin/index_m.html +29 -0
- package/admin/tab_m.html +11 -0
- package/docs/tutorial/zigbee.png +0 -0
- package/io-package.json +11 -1
- package/lib/commands.js +40 -5
- package/lib/groups.js +27 -11
- package/lib/ota.js +26 -0
- package/lib/states.js +2 -1
- package/lib/statescontroller.js +125 -72
- package/lib/zbDeviceAvailability.js +12 -1
- package/lib/zigbeecontroller.js +25 -2
- package/main.js +4 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -11,13 +11,19 @@
|
|
|
11
11
|
|
|
12
12
|
## ioBroker adapter for Zigbee devices via TI cc2531/cc2530/cc26x2r/cc2538 and deCONZ ConBee/RaspBee.
|
|
13
13
|
|
|
14
|
-
With the Zigbee-coordinator based on Texas Instruments SoC
|
|
14
|
+
With the Zigbee-coordinator based on Texas Instruments SoC, deCONZ ConBee/RaspBee modules, Silicon Labs EZSP v8 or ZIGate USB-TTL it creates its own zigbee-network, into which zigbee-devices are connected. By work directly with the coordinator, the driver allows you to manage devices without additional application / gateways / bridge from device manufacturers (Xiaomi / TRADFRI / Hue / Tuya). About the device Zigbee-network can be read [here (in English)](https://www.zigbee2mqtt.io/information/zigbee_network.html).
|
|
15
15
|
|
|
16
16
|
## Hardware
|
|
17
17
|
|
|
18
|
+
|
|
19
|
+
One coordinator device is required for each zigbee Adapter instance. The device must be flashed with the respective coordinator firmware. A list of supported coordinators, the necessary equipment for the firmware and the device preparation process for different coordinator devices are described [here (in English)](https://www.zigbee2mqtt.io/guide/adapters/) or [here (in Russian)](https://myzigbee.ru/books/%D0%BF%D1%80%D0%BE%D1%88%D0%B8%D0%B2%D0%BA%D0%B8/page/%D0%BF%D1%80%D0%BE%D1%88%D0%B8%D0%B2%D0%BA%D0%B0-cc2531cc2530)
|
|
20
|
+
|
|
21
|
+
|
|
18
22
|
### Texas Instruments SoC
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
|
|
24
|
+
Recommended devices are based on either the CC2652 or CC1352 chip. Devices based on cc253x chips are still supported but are no longer recommended.
|
|
25
|
+
Only CC26xx/cc1352/cc2538 Devices support extraction of the NVRam backup which should allow to swap coordinator hardware without having to reconnect all zigbee devices to the network.
|
|
26
|
+
Current firmware files for these devices can be found [on GitHub](https://github.com/Koenkk/Z-Stack-firmware)
|
|
21
27
|
|
|
22
28
|
<span><img src="https://ae01.alicdn.com/kf/HTB1Httue3vD8KJjSsplq6yIEFXaJ/Wireless-Zigbee-CC2531-Sniffer-Bare-Board-Packet-Protocol-Analyzer-Module-USB-Interface-Dongle-Capture-Packet.jpg_640x640.jpg" width="100"></span>
|
|
23
29
|
<span><img src="http://img.dxcdn.com/productimages/sku_429478_2.jpg" width="100"></span>
|
|
@@ -26,17 +32,30 @@ For work, you need one of the following Adapters [all are listed here](https://w
|
|
|
26
32
|
<span><img src="docs/de/img/CC2538_CC2592_PA.PNG" width="100"></span>
|
|
27
33
|
<span><img src="docs/de/img/cc26x2r.PNG" width="100"></span>
|
|
28
34
|
|
|
29
|
-
The necessary equipment for the firmware and the device preparation process are described [here (in English)](https://www.zigbee2mqtt.io/getting_started/what_do_i_need.html) or [here (in Russian)](https://myzigbee.ru/books/%D0%BF%D1%80%D0%BE%D1%88%D0%B8%D0%B2%D0%BA%D0%B8/page/%D0%BF%D1%80%D0%BE%D1%88%D0%B8%D0%B2%D0%BA%D0%B0-cc2531cc2530)
|
|
30
35
|
|
|
31
36
|
### Dresden Elektronik SoC
|
|
32
37
|
|
|
33
|
-
(experimental support)
|
|
34
|
-
|
|
35
38
|
<span><img src="docs/en/img/deconz.png"></span>
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
ConBee II
|
|
39
|
-
RaspBee
|
|
40
|
+
recommended:
|
|
41
|
+
- ConBee II
|
|
42
|
+
- RaspBee II
|
|
43
|
+
|
|
44
|
+
no longer recommended:
|
|
45
|
+
- ConBee I
|
|
46
|
+
- RaspBee
|
|
47
|
+
|
|
48
|
+
While Conbee/RaspBee Support is no longer considered experimental in the zigbee-herdsman and zigbee-herdsman-converters libraries used by the zigbee Adapter, use of these devices with the adapter may limit functionality. Known issues are:
|
|
49
|
+
- link quality display may be incorrect
|
|
50
|
+
- device map metrics may be incorrect
|
|
51
|
+
- NVRam Backup is not supported.
|
|
52
|
+
|
|
53
|
+
### Silicon Labs EZPS v8 / Zigate USB-TTL
|
|
54
|
+
|
|
55
|
+
Support for these chipsets is experimental. Please refer to the respective documentation on [this page](https://www.zigbee2mqtt.io/guide/adapters/) with regards to the state of the integration into the zigbee-herdsman and zigbee-herdsman-converters libraries.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
40
59
|
|
|
41
60
|
|
|
42
61
|
## Work with adapter
|
|
@@ -101,7 +120,7 @@ You can thank the authors by these links:
|
|
|
101
120
|
|
|
102
121
|
<!--
|
|
103
122
|
Placeholder for the next version (at the beginning of the line):
|
|
104
|
-
|
|
123
|
+
|
|
105
124
|
https://github.com/AlCalzone/release-script#usage
|
|
106
125
|
npm run release minor -- --all 0.9.8 -> 0.10.0
|
|
107
126
|
npm run release patch -- --all 0.9.8 -> 0.9.9
|
|
@@ -111,9 +130,34 @@ You can thank the authors by these links:
|
|
|
111
130
|
-->
|
|
112
131
|
|
|
113
132
|
## Changelog
|
|
133
|
+
### 1.6.14 (2022-01)
|
|
134
|
+
* (asgothian) OTA limitation
|
|
135
|
+
- devices with the available state set to false are excluded from OTA updates (and the update check)
|
|
136
|
+
- devices with link_quality 0 are excluded from OTA updates (and the update check)
|
|
137
|
+
* (asgothian) Device deactivation:
|
|
138
|
+
- Devices can be marked inactive from the device card.
|
|
139
|
+
- inactive devices are not pinged
|
|
140
|
+
- state changes by the user are not sent to inactive devices.
|
|
141
|
+
- when a pingable device is marked active (from being inactive) it will be pinged again.
|
|
142
|
+
- inactive devices are excluded from OTA updates.
|
|
143
|
+
* (asgothian) Group rework part 2:
|
|
144
|
+
- state device.groups will now be deleted with state Cleanup
|
|
145
|
+
- state info.groups is now obsolete and will be deleted at adapter start (after transferring data to
|
|
146
|
+
the new storage)
|
|
147
|
+
* (asgothian) Device name persistance.
|
|
148
|
+
- Changes to device names made within the zigbee adapter are stored in the file dev_names.json. This file
|
|
149
|
+
is not deleted when the adapter is removed, and will be referenced when a device is added to the zigbee adapter. Deleting and reinstalling the adapter will no longer remove custom device names, nor will deleting and adding the device anew.
|
|
150
|
+
* (asgothian) Readme edit to reflect the current information on zigbee coordinator hardware.
|
|
151
|
+
* (arteck) Zigbee-Herdsman 0.14.4, Zigbee-Herdsman-Converters 14.0.394
|
|
152
|
+
|
|
153
|
+
### 1.6.13 (2022-01)
|
|
154
|
+
|
|
155
|
+
* (kirovilya) update to Zigbee-Herdsman 0.14
|
|
156
|
+
|
|
114
157
|
|
|
115
158
|
### 1.6.12 (2022-01)
|
|
116
|
-
* (asgothian) Groups were newly revised (read here
|
|
159
|
+
* (asgothian) Groups were newly revised (read [here](https://github.com/ioBroker/ioBroker.zigbee/pull/1327) )
|
|
160
|
+
- object device.groups is obsolet..the old one is no longer up to date
|
|
117
161
|
|
|
118
162
|
|
|
119
163
|
### 1.6.9 (2021-12)
|
|
@@ -204,7 +248,7 @@ in this case, the *states* attribute will be formed based on the *exposes* descr
|
|
|
204
248
|
|
|
205
249
|
### 1.3.1 (2020-10-30)
|
|
206
250
|
* [Experimental Zigate support](https://github.com/Koenkk/zigbee-herdsman/issues/242) (zigbee-herdsman)
|
|
207
|
-
* New devices by:
|
|
251
|
+
* New devices by:
|
|
208
252
|
asgothian, arteck, kirovilya, PaulchenPlump
|
|
209
253
|
|
|
210
254
|
### 1.3.0 (2020-10-07)
|
|
@@ -213,8 +257,8 @@ in this case, the *states* attribute will be formed based on the *exposes* descr
|
|
|
213
257
|
* Allow to select bind cluster
|
|
214
258
|
* Admin Tab support (experimental)
|
|
215
259
|
* (UncleSamSwiss, DutchmanNL) Translation
|
|
216
|
-
* New devices by:
|
|
217
|
-
arteck, kirovilya, Shade, krumbholz, fre, Alex18081, ae, asgothian,
|
|
260
|
+
* New devices by:
|
|
261
|
+
arteck, kirovilya, Shade, krumbholz, fre, Alex18081, ae, asgothian,
|
|
218
262
|
Strunzdesign, kairauer, VLGorskij, Hesse-Bub, PaulchenPlump, blackrozes
|
|
219
263
|
|
|
220
264
|
### 1.2.1 (2020-08-16)
|
package/admin/admin.js
CHANGED
|
@@ -129,6 +129,7 @@ function getGroupCard(dev) {
|
|
|
129
129
|
title = dev.common.name,
|
|
130
130
|
lq = '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>',
|
|
131
131
|
rooms = [],
|
|
132
|
+
numid = parseInt(id.replace(namespace+'.group_', '')),
|
|
132
133
|
lang = systemLang || 'en';
|
|
133
134
|
for (const r in dev.rooms) {
|
|
134
135
|
if (dev.rooms[r].hasOwnProperty(lang)) {
|
|
@@ -137,11 +138,12 @@ function getGroupCard(dev) {
|
|
|
137
138
|
rooms.push(dev.rooms[r]);
|
|
138
139
|
}
|
|
139
140
|
}
|
|
141
|
+
devGroups[numid] = dev;
|
|
140
142
|
const room = rooms.join(',') || ' ';
|
|
141
143
|
let memberCount = 0;
|
|
142
144
|
let info = `<div style="min-height:88px; font-size: 0.8em; height: 98px; overflow-y: auto" class="truncate">
|
|
143
145
|
<ul>`;
|
|
144
|
-
info = info.concat(`<li><span class="labelinfo">Group ${
|
|
146
|
+
info = info.concat(`<li><span class="labelinfo">Group ${numid}</span></li>`);
|
|
145
147
|
if (dev.memberinfo === undefined) {
|
|
146
148
|
info = info.concat(`<li><span class="labelinfo">No devices in group</span></li>`);
|
|
147
149
|
} else {
|
|
@@ -199,6 +201,7 @@ function getCard(dev) {
|
|
|
199
201
|
type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'),
|
|
200
202
|
img_src = dev.icon || dev.common.icon,
|
|
201
203
|
rooms = [],
|
|
204
|
+
isActive = (dev.common.deactivated ? false : true),
|
|
202
205
|
lang = systemLang || 'en';
|
|
203
206
|
for (const r in dev.rooms) {
|
|
204
207
|
if (dev.rooms[r].hasOwnProperty(lang)) {
|
|
@@ -213,11 +216,11 @@ function getCard(dev) {
|
|
|
213
216
|
const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
|
|
214
217
|
const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
|
|
215
218
|
nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
|
|
216
|
-
battery_cls = getBatteryCls(dev.battery),
|
|
219
|
+
battery_cls = (isActive ? getBatteryCls(dev.battery):''),
|
|
217
220
|
lqi_cls = getLQICls(dev.link_quality),
|
|
218
|
-
battery = (dev.battery) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
|
|
219
|
-
lq = (dev.link_quality
|
|
220
|
-
status = (dev.link_quality
|
|
221
|
+
battery = (dev.battery && isActive) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
|
|
222
|
+
lq = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : '',
|
|
223
|
+
status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (isActive ? `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`:''),
|
|
221
224
|
info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
|
|
222
225
|
<ul>
|
|
223
226
|
<li><span class="labelinfo">ieee:</span><span>0x${id.replace(namespace+'.', '')}</span></li>
|
|
@@ -227,10 +230,11 @@ function getCard(dev) {
|
|
|
227
230
|
</ul>
|
|
228
231
|
</div>`,
|
|
229
232
|
permitJoinBtn = (dev.info && dev.info.device._type == 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
|
|
233
|
+
deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive?'Deactivate':'Activate')}"><i class="material-icons icon-${(isActive?'red':'green')}">power_settings_new</i></button>`,
|
|
230
234
|
infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
|
|
231
235
|
const dashCard = getDashCard(dev);
|
|
232
236
|
const card = `<div id="${id}" class="device">
|
|
233
|
-
<div class="card hoverable flipable">
|
|
237
|
+
<div class="card hoverable flipable ${isActive?'':'bg_red'}">
|
|
234
238
|
<div class="front face">${dashCard}</div>
|
|
235
239
|
<div class="back face">
|
|
236
240
|
<div class="card-content zcard">
|
|
@@ -261,6 +265,7 @@ function getCard(dev) {
|
|
|
261
265
|
<button name="reconfigure" class="right btn-flat btn-small tooltipped" title="Reconfigure">
|
|
262
266
|
<i class="material-icons icon-red">sync</i>
|
|
263
267
|
</button>
|
|
268
|
+
${deactBtn}
|
|
264
269
|
${permitJoinBtn}
|
|
265
270
|
</div>
|
|
266
271
|
</div>
|
|
@@ -512,7 +517,6 @@ function showDevices() {
|
|
|
512
517
|
}
|
|
513
518
|
return 0;
|
|
514
519
|
});
|
|
515
|
-
devGroups = {};
|
|
516
520
|
for (let i=0;i < devices.length; i++) {
|
|
517
521
|
const d = devices[i];
|
|
518
522
|
if (!d.info) {
|
|
@@ -529,7 +533,7 @@ function showDevices() {
|
|
|
529
533
|
} else {
|
|
530
534
|
//if (d.groups && d.info && d.info.device._type == "Router") {
|
|
531
535
|
if (d.groups) {
|
|
532
|
-
devGroups[d._id] = d.groups;
|
|
536
|
+
// devGroups[d._id] = d.groups;
|
|
533
537
|
if (typeof d.groups.map == 'function') {
|
|
534
538
|
d.groupNames = d.groups.map(item=>{
|
|
535
539
|
return groups[item] || '';
|
|
@@ -626,6 +630,10 @@ function showDevices() {
|
|
|
626
630
|
const dev_block = $(this).parents('div.device');
|
|
627
631
|
reconfigureDlg(getDevId(dev_block));
|
|
628
632
|
});
|
|
633
|
+
$(".card-reveal-buttons button[name='swapactive']").click(function() {
|
|
634
|
+
const dev_block = $(this).parents('div.device');
|
|
635
|
+
swapActive(getDevId(dev_block));
|
|
636
|
+
});
|
|
629
637
|
|
|
630
638
|
showNetworkMap(devices, map);
|
|
631
639
|
translateAll();
|
|
@@ -840,13 +848,14 @@ function load(settings, onChange) {
|
|
|
840
848
|
});
|
|
841
849
|
|
|
842
850
|
$('#add_group').click(function() {
|
|
851
|
+
// showGroupList(true);
|
|
843
852
|
const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
|
|
844
853
|
editGroupName(maxind+1, 'Group ' + maxind+1, true);
|
|
845
854
|
});
|
|
846
855
|
$('#add_grp_btn').click(function() {
|
|
856
|
+
// showGroupList(true);
|
|
847
857
|
const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
|
|
848
858
|
editGroupName(maxind+1, 'Group ' + maxind+1, true);
|
|
849
|
-
getDevices();
|
|
850
859
|
});
|
|
851
860
|
|
|
852
861
|
$(document).ready(function() {
|
|
@@ -1830,7 +1839,7 @@ function editGroupName(id, name, isnew) {
|
|
|
1830
1839
|
newName = $('#groupedit').find("input[id='g_name']").val();
|
|
1831
1840
|
updateGroup(id, newId, newName);
|
|
1832
1841
|
// showGroups();
|
|
1833
|
-
getDevices();
|
|
1842
|
+
// getDevices();
|
|
1834
1843
|
});
|
|
1835
1844
|
$('#groupedit').modal('open');
|
|
1836
1845
|
Materialize.updateTextFields();
|
|
@@ -1844,7 +1853,7 @@ function deleteGroupConfirmation(id, name) {
|
|
|
1844
1853
|
$("#modaldelete a.btn[name='yes']").click(() => {
|
|
1845
1854
|
deleteGroup(id);
|
|
1846
1855
|
// showGroups();
|
|
1847
|
-
getDevices();
|
|
1856
|
+
// getDevices();
|
|
1848
1857
|
});
|
|
1849
1858
|
$('#modaldelete').modal('open');
|
|
1850
1859
|
}
|
|
@@ -1852,12 +1861,22 @@ function deleteGroupConfirmation(id, name) {
|
|
|
1852
1861
|
function updateGroup(id, newId, newName) {
|
|
1853
1862
|
delete groups[id];
|
|
1854
1863
|
groups[newId] = newName;
|
|
1855
|
-
sendTo(namespace, 'renameGroup', { id: newId, name: newName} )
|
|
1864
|
+
sendTo(namespace, 'renameGroup', { id: newId, name: newName}, function(msg) {
|
|
1865
|
+
if (msg && ms.error) {
|
|
1866
|
+
showMessage(msg.error, _('Error'));
|
|
1867
|
+
}
|
|
1868
|
+
getDevices();
|
|
1869
|
+
});
|
|
1856
1870
|
}
|
|
1857
1871
|
|
|
1858
1872
|
function deleteGroup(id) {
|
|
1859
1873
|
delete groups[id];
|
|
1860
|
-
sendTo(namespace, 'deleteGroup', id )
|
|
1874
|
+
sendTo(namespace, 'deleteGroup', id , function(msg) {
|
|
1875
|
+
if (msg && ms.error) {
|
|
1876
|
+
showMessage(msg.error, _('Error'));
|
|
1877
|
+
}
|
|
1878
|
+
getDevices();
|
|
1879
|
+
});
|
|
1861
1880
|
}
|
|
1862
1881
|
|
|
1863
1882
|
function updateDev(id, newName, newGroups) {
|
|
@@ -1869,6 +1888,7 @@ function updateDev(id, newName, newGroups) {
|
|
|
1869
1888
|
if (keys && keys.length)
|
|
1870
1889
|
{
|
|
1871
1890
|
sendTo(namespace, 'updateGroupMembership', { id: id, groups: newGroups }, function (msg) {
|
|
1891
|
+
closeWaitingDialog();
|
|
1872
1892
|
if (msg && msg.error) {
|
|
1873
1893
|
showMessage(msg.error, _('Error'));
|
|
1874
1894
|
}
|
|
@@ -1878,6 +1898,8 @@ function updateDev(id, newName, newGroups) {
|
|
|
1878
1898
|
}
|
|
1879
1899
|
showDevices();
|
|
1880
1900
|
});
|
|
1901
|
+
showWaitingDialog('Updating group memberships', 10);
|
|
1902
|
+
|
|
1881
1903
|
}
|
|
1882
1904
|
/*
|
|
1883
1905
|
if (dev.info.device._type == 'Router') {
|
|
@@ -2371,6 +2393,47 @@ function showDevInfo(id){
|
|
|
2371
2393
|
$('#modaldevinfo').modal('open');
|
|
2372
2394
|
}
|
|
2373
2395
|
|
|
2396
|
+
function showGroupList(show){
|
|
2397
|
+
const htmlsections = [];
|
|
2398
|
+
for (const groupid in devGroups) {
|
|
2399
|
+
const dev = devGroups[groupid];
|
|
2400
|
+
const grpname = (dev.common && dev.common.name?dev.common.name:'Group '+groupid);
|
|
2401
|
+
const selectables = [];
|
|
2402
|
+
const members = [];
|
|
2403
|
+
if (dev && dev.memberinfo)
|
|
2404
|
+
{
|
|
2405
|
+
selectables.push(`<select id="members_${groupid}" multiple>`)
|
|
2406
|
+
for (let m=0;m<dev.memberinfo.length; m++) {
|
|
2407
|
+
members.push(`${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (${dev.memberinfo[m].ieee})`)
|
|
2408
|
+
selectables.push(`<option value="${m}">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (...${dev.memberinfo[m].ieee.slice(-4)})</option>`);
|
|
2409
|
+
}
|
|
2410
|
+
selectables.push('</select>');
|
|
2411
|
+
}
|
|
2412
|
+
htmlsections.push(`
|
|
2413
|
+
<div class="row">
|
|
2414
|
+
<div class="col s4 m4 l4">
|
|
2415
|
+
<h5>${grpname}<h5>
|
|
2416
|
+
</div>
|
|
2417
|
+
<div class=col s7 m7 l7">
|
|
2418
|
+
${members.join('<br>')}
|
|
2419
|
+
</div>
|
|
2420
|
+
</div>
|
|
2421
|
+
`);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
$('#grouplist').html(htmlsections.join(''));
|
|
2425
|
+
$('#add').click(function() {
|
|
2426
|
+
const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
|
|
2427
|
+
editGroupName(maxind+1, 'Group ' + maxind+1, true);
|
|
2428
|
+
showGroupList(false);
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
$("#modalgrouplist a.btn[name='save']").unbind('click');
|
|
2432
|
+
$("#modalgrouplist a.btn[name='save']").click(() => {
|
|
2433
|
+
});
|
|
2434
|
+
if (show) $('#modalgrouplist').modal('open');
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2374
2437
|
let waitingTimeout, waitingInt;
|
|
2375
2438
|
|
|
2376
2439
|
function showWaitingDialog(text, timeout){
|
|
@@ -2675,6 +2738,7 @@ function getDashCard(dev, groupImage) {
|
|
|
2675
2738
|
id = dev._id,
|
|
2676
2739
|
type = dev.common.type,
|
|
2677
2740
|
img_src = (groupImage ? groupImage: dev.icon || dev.common.icon),
|
|
2741
|
+
isActive = (dev.common.deactivated ? false : true),
|
|
2678
2742
|
rooms = [],
|
|
2679
2743
|
lang = systemLang || 'en';
|
|
2680
2744
|
const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
|
|
@@ -2684,12 +2748,12 @@ function getDashCard(dev, groupImage) {
|
|
|
2684
2748
|
nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
|
|
2685
2749
|
battery_cls = getBatteryCls(dev.battery),
|
|
2686
2750
|
lqi_cls = getLQICls(dev.link_quality),
|
|
2687
|
-
battery = (dev.battery) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
|
|
2688
|
-
lq = (dev.link_quality
|
|
2689
|
-
status = (dev.link_quality
|
|
2690
|
-
permitJoinBtn = (dev.info && dev.info.device._type == 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
|
|
2751
|
+
battery = (dev.battery && isActive) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
|
|
2752
|
+
lq = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : (isActive ? '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>':''),
|
|
2753
|
+
status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (groupImage || !isActive ? '': `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`),
|
|
2754
|
+
permitJoinBtn = (isActive && dev.info && dev.info.device._type == 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
|
|
2691
2755
|
infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
|
|
2692
|
-
idleTime = (dev.link_quality_lc > 0) ? `<div class="col tool"><i id="${rid}_link_quality_lc_icon" class="material-icons idletime">access_time</i><div id="${rid}_link_quality_lc" class="center" style="font-size:0.7em">${getIdleTime(dev.link_quality_lc)}</div></div>` : '';
|
|
2756
|
+
idleTime = (dev.link_quality_lc > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_lc_icon" class="material-icons idletime">access_time</i><div id="${rid}_link_quality_lc" class="center" style="font-size:0.7em">${getIdleTime(dev.link_quality_lc)}</div></div>` : '';
|
|
2693
2757
|
const info = (dev.statesDef) ? dev.statesDef.map((stateDef)=>{
|
|
2694
2758
|
const id = stateDef.id;
|
|
2695
2759
|
const sid = id.split('.').join('_');
|
|
@@ -2716,7 +2780,7 @@ function getDashCard(dev, groupImage) {
|
|
|
2716
2780
|
return `<li><span class="label dash truncate">${stateDef.name}</span><span id=${sid} oid=${id} class="state">${val}</span></li>`;
|
|
2717
2781
|
}).join('') : '';
|
|
2718
2782
|
const dashCard = `
|
|
2719
|
-
<div class="card-content zcard">
|
|
2783
|
+
<div class="card-content zcard ${isActive?'':'bg_red'}">
|
|
2720
2784
|
<div class="flip" style="cursor: pointer">
|
|
2721
2785
|
<span class="top right small" style="border-radius: 50%">
|
|
2722
2786
|
${idleTime}
|
|
@@ -2729,7 +2793,7 @@ function getDashCard(dev, groupImage) {
|
|
|
2729
2793
|
<i class="left">${image}</i>
|
|
2730
2794
|
<div style="min-height:88px; font-size: 0.8em; height: 130px; overflow-y: auto" class="truncate">
|
|
2731
2795
|
<ul>
|
|
2732
|
-
${info}
|
|
2796
|
+
${(isActive?info:'Device deactivated')}
|
|
2733
2797
|
</ul>
|
|
2734
2798
|
</div>
|
|
2735
2799
|
<div class="footer right-align"></div>
|
|
@@ -2828,6 +2892,16 @@ function removeDevice(id) {
|
|
|
2828
2892
|
}
|
|
2829
2893
|
}
|
|
2830
2894
|
|
|
2895
|
+
function swapActive(id) {
|
|
2896
|
+
const dev = getDeviceByID(id);
|
|
2897
|
+
if (dev && dev.common) {
|
|
2898
|
+
dev.common.deactivated = !(dev.common.deactivated)
|
|
2899
|
+
sendTo(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
|
|
2900
|
+
showDevices();
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2831
2905
|
function reconfigureDlg(id) {
|
|
2832
2906
|
const text = translateWord(`Do you really want to reconfigure device?`);
|
|
2833
2907
|
$('#modalreconfigure').find('p').text(text);
|
package/admin/index_m.html
CHANGED
|
@@ -204,6 +204,18 @@
|
|
|
204
204
|
i.icon-orange {
|
|
205
205
|
color: orange;
|
|
206
206
|
}
|
|
207
|
+
.m.react-blue div.bg_red {
|
|
208
|
+
background-color: #400000;
|
|
209
|
+
color: DimGray;
|
|
210
|
+
}
|
|
211
|
+
.m div.bg_red {
|
|
212
|
+
background-color: LightGray;
|
|
213
|
+
color: DimGray;
|
|
214
|
+
}
|
|
215
|
+
.m.react-dark div.bg_red {
|
|
216
|
+
background-color: #400000;
|
|
217
|
+
color: DimGray;
|
|
218
|
+
}
|
|
207
219
|
.m.react-dark i.icon-orange {
|
|
208
220
|
color: orange!important;
|
|
209
221
|
}
|
|
@@ -246,6 +258,7 @@
|
|
|
246
258
|
height: calc(100% - 140px)!important;
|
|
247
259
|
padding-top: 5px;
|
|
248
260
|
padding-bottom: 5px;
|
|
261
|
+
overflow: auto;
|
|
249
262
|
}
|
|
250
263
|
.m .page {
|
|
251
264
|
height: calc(100% - 120px);
|
|
@@ -1095,6 +1108,22 @@
|
|
|
1095
1108
|
<a name="hide" href="#!" class="modal-action modal-close waves-effect waves-green btn green translate">Hide</a>
|
|
1096
1109
|
</div>
|
|
1097
1110
|
</div>
|
|
1111
|
+
<div id="modalgrouplist" class="modal modal-fixed-footer modal-fixed-header">
|
|
1112
|
+
<div class="modal-header">
|
|
1113
|
+
<h5 class="translate">Groups
|
|
1114
|
+
<a name="add" class="modal-action modal-close waves-effect waves-green btn green translate">Add Group</a>
|
|
1115
|
+
</h5>
|
|
1116
|
+
</div>
|
|
1117
|
+
<div class="modal-content">
|
|
1118
|
+
<div id="grouplist" class="row">
|
|
1119
|
+
|
|
1120
|
+
</div>
|
|
1121
|
+
</div>
|
|
1122
|
+
<div class="modal-footer">
|
|
1123
|
+
<a name="save" href="#!" class="modal-action modal-close waves-effect waves-green btn green translate">Save</a>
|
|
1124
|
+
<a href="#!" class="modal-action modal-close waves-effect waves-red btn-flat translate">Cancel</a>
|
|
1125
|
+
</div>
|
|
1126
|
+
</div>
|
|
1098
1127
|
|
|
1099
1128
|
<div id="modalWaiting" class="modal modal">
|
|
1100
1129
|
<div class="modal-content">
|
package/admin/tab_m.html
CHANGED
|
@@ -186,6 +186,17 @@
|
|
|
186
186
|
i.icon-blue {
|
|
187
187
|
color: blue;
|
|
188
188
|
}
|
|
189
|
+
.m.react-blue div.bg_red {
|
|
190
|
+
background-color: #400000;
|
|
191
|
+
color: DimGray;
|
|
192
|
+
}
|
|
193
|
+
.m div.bg_red {
|
|
194
|
+
background-color: LightGray;
|
|
195
|
+
color: DimGray;
|
|
196
|
+
}
|
|
197
|
+
.m.react-dark div.bg_red {
|
|
198
|
+
background-color: #400000;
|
|
199
|
+
}
|
|
189
200
|
.m.react-dark i.icon-blue {
|
|
190
201
|
color: rgb(100, 181, 246)!important;
|
|
191
202
|
}
|
package/docs/tutorial/zigbee.png
CHANGED
|
Binary file
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "zigbee",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.14",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.6.14": {
|
|
7
|
+
"de": "Gruppen wurden überarbeitet 2",
|
|
8
|
+
"en": "Groups were newly revised 2",
|
|
9
|
+
"ru": "Группы были пересмотрены 2"
|
|
10
|
+
},
|
|
11
|
+
"1.6.13": {
|
|
12
|
+
"de": "zh 0.14.xx",
|
|
13
|
+
"en": "zh 0.14.xx",
|
|
14
|
+
"ru": "zh 0.14.xx"
|
|
15
|
+
},
|
|
6
16
|
"1.6.12": {
|
|
7
17
|
"de": "Gruppen wurden überarbeitet",
|
|
8
18
|
"en": "Groups were newly revised",
|
package/lib/commands.js
CHANGED
|
@@ -102,10 +102,17 @@ class Commands {
|
|
|
102
102
|
this.reconfigure(obj.from, obj.command, obj.message, obj.callback);
|
|
103
103
|
}
|
|
104
104
|
break;
|
|
105
|
+
case 'setDeviceActivated':
|
|
106
|
+
if (obj && obj.message && typeof obj.message === 'object') {
|
|
107
|
+
this.setDeviceActivated(obj.from, obj.command, obj.message, obj.callback);
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
|
|
115
|
+
|
|
109
116
|
letsPairing(from, command, message, callback) {
|
|
110
117
|
if (this.zbController) {
|
|
111
118
|
let devId = '';
|
|
@@ -179,10 +186,10 @@ class Commands {
|
|
|
179
186
|
result.forEach(async (devInfo) => {
|
|
180
187
|
if (devInfo._id) {
|
|
181
188
|
// groups
|
|
182
|
-
const grState = alls[`${devInfo._id}.groups`];
|
|
183
|
-
if (grState && grState.val) {
|
|
184
|
-
groups[devInfo._id] = JSON.parse(grState.val);
|
|
185
|
-
}
|
|
189
|
+
// const grState = alls[`${devInfo._id}.groups`];
|
|
190
|
+
// if (grState && grState.val) {
|
|
191
|
+
// groups[devInfo._id] = JSON.parse(grState.val);
|
|
192
|
+
// }
|
|
186
193
|
// battery and link_quality
|
|
187
194
|
const lqState = alls[`${devInfo._id}.link_quality`];
|
|
188
195
|
devInfo.link_quality = (lqState) ? lqState.val: undefined;
|
|
@@ -234,6 +241,16 @@ class Commands {
|
|
|
234
241
|
if (groupmembers && groupmembers.length > 0) {
|
|
235
242
|
const memberinfo = [];
|
|
236
243
|
for (const member of groupmembers) {
|
|
244
|
+
if (groups) {
|
|
245
|
+
const grouparray = groups[member.ieee];
|
|
246
|
+
if (grouparray)
|
|
247
|
+
{
|
|
248
|
+
if (!grouparray.includes(match[1])) {
|
|
249
|
+
groups[member.ieee].push(match[1])
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else groups[member.ieee] = [match[1]];
|
|
253
|
+
}
|
|
237
254
|
const device = await this.adapter.getObjectAsync(this.adapter.namespace + '.' + member.ieee.substr(2));
|
|
238
255
|
if (device) {
|
|
239
256
|
member.device = device.common.name;
|
|
@@ -270,12 +287,21 @@ class Commands {
|
|
|
270
287
|
}
|
|
271
288
|
}
|
|
272
289
|
devInfo.paired = !!devInfo.info;
|
|
273
|
-
devInfo.groups = groups[devInfo._id];
|
|
290
|
+
// devInfo.groups = groups[devInfo._id];
|
|
274
291
|
devices.push(devInfo);
|
|
275
292
|
}
|
|
276
293
|
return devices;
|
|
277
294
|
})
|
|
278
295
|
.then(async (devices) => {
|
|
296
|
+
// fill group info
|
|
297
|
+
for (const groupdev in groups) {
|
|
298
|
+
this.debug('GetDevices scanning group ' + groupdev + ' ' + JSON.stringify(groups[groupdev]))
|
|
299
|
+
const device = devices.find((dev) =>( groupdev === getZbId(dev._id) ))
|
|
300
|
+
if (device) {
|
|
301
|
+
device.groups = groups[groupdev]
|
|
302
|
+
this.debug('adding group info to device ' + groupdev);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
279
305
|
// append devices that paired but not created
|
|
280
306
|
if (!id) {
|
|
281
307
|
for (const d of pairedDevices) {
|
|
@@ -438,6 +464,15 @@ class Commands {
|
|
|
438
464
|
}
|
|
439
465
|
}
|
|
440
466
|
|
|
467
|
+
async setDeviceActivated(from, command, msg, callback) {
|
|
468
|
+
if (this.stController) {
|
|
469
|
+
const id = msg.id;
|
|
470
|
+
const targetstate = msg.deactivated;
|
|
471
|
+
this.stController.setDeviceActivated(id, targetstate);
|
|
472
|
+
this.adapter.sendTo(from, command, {}, callback);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
441
476
|
async reconfigure(from, command, msg, callback) {
|
|
442
477
|
if (this.zbController) {
|
|
443
478
|
const devid = getZbId(msg.id);
|
package/lib/groups.js
CHANGED
|
@@ -15,7 +15,11 @@ class Groups {
|
|
|
15
15
|
this.adapter.getStateAsync('info.groups')
|
|
16
16
|
.then((groupsState) => {
|
|
17
17
|
const groups = (groupsState && groupsState.val) ? JSON.parse(groupsState.val) : {};
|
|
18
|
-
|
|
18
|
+
for (const gid in groups) {
|
|
19
|
+
stController.storeDeviceName(`group_${gid}`, groups[gid]);
|
|
20
|
+
}
|
|
21
|
+
// this.Adapter.deleteState('info.groups');
|
|
22
|
+
this.syncGroups();
|
|
19
23
|
});
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -90,16 +94,17 @@ class Groups {
|
|
|
90
94
|
async getGroups(obj) {
|
|
91
95
|
const response = { groups: {} };
|
|
92
96
|
try {
|
|
93
|
-
const groupsState = await this.adapter.getStateAsync('info.groups');
|
|
97
|
+
// const groupsState = await this.adapter.getStateAsync('info.groups');
|
|
94
98
|
const herdsmanGroups = await this.zbController.getGroups();
|
|
95
99
|
|
|
96
|
-
const groups = (groupsState && groupsState.val) ? JSON.parse(groupsState.val) : {};
|
|
100
|
+
// const groups = (groupsState && groupsState.val) ? JSON.parse(groupsState.val) : {};
|
|
97
101
|
|
|
102
|
+
const groups = {};
|
|
98
103
|
if (typeof herdsmanGroups === 'object') {
|
|
99
104
|
for (const group of herdsmanGroups) {
|
|
100
105
|
const gid = group.groupID;
|
|
101
|
-
if (gid
|
|
102
|
-
groups[gid]
|
|
106
|
+
if (gid) {
|
|
107
|
+
groups[gid]=this.stController.verifyDeviceName(`group_${gid}`,`Group ${gid}`);
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
}
|
|
@@ -199,6 +204,7 @@ class Groups {
|
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
async deleteGroup(from, command, message) {
|
|
207
|
+
/*
|
|
202
208
|
const members = await this.getGroupMembersFromController(parseInt(message));
|
|
203
209
|
if (members && members.length) {
|
|
204
210
|
for (const member of members) {
|
|
@@ -221,17 +227,27 @@ class Groups {
|
|
|
221
227
|
const objGroups = (groupsEntry && groupsEntry.val ? JSON.parse(groupsEntry.val) : {});
|
|
222
228
|
delete objGroups[message.toString()];
|
|
223
229
|
await this.adapter.setStateAsync('info.groups', JSON.stringify(objGroups), true);
|
|
230
|
+
*/
|
|
224
231
|
await this.zbController.removeGroupById(message);
|
|
225
|
-
this.stController.
|
|
232
|
+
await this.stController.deleteDeviceStatesAsync(`group_${parseInt(message)}`);
|
|
226
233
|
}
|
|
227
234
|
|
|
228
235
|
async renameGroup(from, command, message) {
|
|
229
|
-
|
|
230
|
-
const
|
|
236
|
+
|
|
237
|
+
// const groupsEntry = await this.adapter.getStateAsync('info.groups');
|
|
238
|
+
// const objGroups = (groupsEntry && groupsEntry.val ? JSON.parse(groupsEntry.val) : {});
|
|
231
239
|
const name = message.name;
|
|
232
240
|
const id = `group_${message.id}`;
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
this.stController.storeDeviceName(id, name);
|
|
242
|
+
try {
|
|
243
|
+
await this.zbController.verifyGroupExists(message.id);
|
|
244
|
+
}
|
|
245
|
+
catch (e)
|
|
246
|
+
{
|
|
247
|
+
if (e && e.hasOwnProperty('code')) this.warn('renameGroup caught error ' + JSON.stringify(e.code));
|
|
248
|
+
}
|
|
249
|
+
// objGroups[message.id.toString()] = message.name;
|
|
250
|
+
// await this.adapter.setStateAsync('info.groups', JSON.stringify(objGroups), true);
|
|
235
251
|
|
|
236
252
|
const group = await this.adapter.getStateAsync(id);
|
|
237
253
|
if (!group) {
|
|
@@ -264,7 +280,7 @@ class Groups {
|
|
|
264
280
|
}
|
|
265
281
|
|
|
266
282
|
|
|
267
|
-
async syncGroups(
|
|
283
|
+
async syncGroups() {
|
|
268
284
|
const groups = await this.getGroups();
|
|
269
285
|
const chain = [];
|
|
270
286
|
const usedGroupsIds = [];
|
package/lib/ota.js
CHANGED
|
@@ -62,6 +62,20 @@ class Ota {
|
|
|
62
62
|
this.warn(`Update or check already in progress for '${device.name}', skipping...`);
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
+
// do not attempt update for a device which has been deactivated or is unavailable
|
|
66
|
+
const stateObj = await this.adapter.getObjectAsync(obj.message.devId);
|
|
67
|
+
if (stateObj && stateObj.common && stateObj.common.deactivated) {
|
|
68
|
+
this.warn(`Device ${obj.message.devId} is deactivated, skipping...`);
|
|
69
|
+
this.adapter.sendTo(obj.from, obj.command, {status: 'fail', device: getZbId(obj.message.devId), msg: 'Device is deactivated'}, obj.callback);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const availablestate = await this.adapter.getStateAsync(obj.message.devId.replace(this.namespace + '.', '') + '.available');
|
|
73
|
+
const lqi = await this.adapter.getStateAsync(obj.message.devId.replace(this.namespace + '.', '') + '.link_quality');
|
|
74
|
+
if ((availablestate && (!availablestate.val)) || (lqi && lqi.val < 1)) {
|
|
75
|
+
this.warn(`Device ${obj.message.devId} is marked unavailable, skipping...`);
|
|
76
|
+
this.adapter.sendTo(obj.from, obj.command, {status: 'fail', device: getZbId(obj.message.devId), msg: 'Device is marked unavailable'}, obj.callback);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
65
79
|
this.inProgress.add(device.device.ieeeAddr);
|
|
66
80
|
const result = {status: 'unknown', device: device ? device.name : null};
|
|
67
81
|
try {
|
|
@@ -96,6 +110,18 @@ class Ota {
|
|
|
96
110
|
this.error(`Update or check already in progress for '${device.name}', skipping...`);
|
|
97
111
|
return;
|
|
98
112
|
}
|
|
113
|
+
// do not attempt update for a device which has been deactivated or is unavailable
|
|
114
|
+
const stateObj = await this.adapter.getObjectAsync(obj.message.devId);
|
|
115
|
+
if (stateObj && stateObj.common && stateObj.common.deactivated) {
|
|
116
|
+
this.warn(`Device ${obj.message.devId} is deactivated, skipping...`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const availablestate = await this.adapter.getStateAsync(obj.message.devId.replace(this.namespace + '.', '') + '.available');
|
|
120
|
+
const lqi = await this.adapter.getStateAsync(obj.message.devId.replace(this.namespace + '.', '') + '.link_quality');
|
|
121
|
+
if ((availablestate && (!availablestate.val)) || (lqi && lqi.val < 1)) {
|
|
122
|
+
this.warn(`Device ${obj.message.devId} is marked unavailable, skipping...`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
99
125
|
this.inProgress.add(device.device.ieeeAddr);
|
|
100
126
|
const result = {status: 'unknown', device: device ? device.name : null};
|
|
101
127
|
try {
|
package/lib/states.js
CHANGED
|
@@ -60,7 +60,7 @@ const unitLookup = {
|
|
|
60
60
|
const timers = {};
|
|
61
61
|
|
|
62
62
|
const states = {
|
|
63
|
-
groups: {
|
|
63
|
+
/* groups: {
|
|
64
64
|
id: 'groups',
|
|
65
65
|
name: 'Groups',
|
|
66
66
|
icon: undefined,
|
|
@@ -70,6 +70,7 @@ const states = {
|
|
|
70
70
|
type: 'string',
|
|
71
71
|
isOption: true,
|
|
72
72
|
},
|
|
73
|
+
*/
|
|
73
74
|
link_quality: {
|
|
74
75
|
id: 'link_quality',
|
|
75
76
|
prop: 'linkquality',
|
package/lib/statescontroller.js
CHANGED
|
@@ -4,6 +4,8 @@ const EventEmitter = require('events').EventEmitter;
|
|
|
4
4
|
const statesMapping = require('./devices');
|
|
5
5
|
const getAdId = require('./utils').getAdId;
|
|
6
6
|
const getZbId = require('./utils').getZbId;
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
var savedDeviceNames = {};
|
|
7
9
|
var knownUndefinedDevices = {};
|
|
8
10
|
|
|
9
11
|
|
|
@@ -14,6 +16,13 @@ class StatesController extends EventEmitter {
|
|
|
14
16
|
this.adapter.on('stateChange', this.onStateChange.bind(this));
|
|
15
17
|
this.query_device_block = [];
|
|
16
18
|
this.debugDevices = undefined;
|
|
19
|
+
let fn = adapter.expandFileName('dev_names.json');
|
|
20
|
+
this.dev_names_fn = fn.replace('.', '_');
|
|
21
|
+
fs.readFile(this.dev_names_fn, (err, data) => {
|
|
22
|
+
if (!err) {
|
|
23
|
+
savedDeviceNames = JSON.parse(data);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
info(message, data) {
|
|
@@ -36,6 +45,16 @@ class StatesController extends EventEmitter {
|
|
|
36
45
|
this.adapter.sendError(error, message);
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
retainDeviceNames()
|
|
49
|
+
{
|
|
50
|
+
fs.writeFile(this.dev_names_fn, JSON.stringify(savedDeviceNames, null, 2), (err) => {
|
|
51
|
+
if (err)
|
|
52
|
+
this.error('error saving device names: ' + JSON.Stringify(err));
|
|
53
|
+
else
|
|
54
|
+
this.debug('saved device names');
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
getDebugDevices() {
|
|
40
59
|
this.debugDevices = [];
|
|
41
60
|
this.adapter.getState(this.adapter.namespace + '.info.debugmessages', (err, state) => {
|
|
@@ -104,6 +123,10 @@ class StatesController extends EventEmitter {
|
|
|
104
123
|
if (obj) {
|
|
105
124
|
const model = obj.common.type;
|
|
106
125
|
if (!model) return;
|
|
126
|
+
if (obj.common.deactivated) {
|
|
127
|
+
this.debug('State Change detected on deactivated Device - ignored');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
107
130
|
if (model === 'group') {
|
|
108
131
|
deviceId = parseInt(deviceId.replace('0xgroup_', ''));
|
|
109
132
|
}
|
|
@@ -247,9 +270,28 @@ class StatesController extends EventEmitter {
|
|
|
247
270
|
}
|
|
248
271
|
|
|
249
272
|
renameDevice(id, newName) {
|
|
273
|
+
this.storeDeviceName(id, newName);
|
|
250
274
|
this.adapter.extendObject(id, {common: {name: newName}});
|
|
251
275
|
}
|
|
252
276
|
|
|
277
|
+
setDeviceActivated(id, active) {
|
|
278
|
+
this.adapter.extendObject(id, {common: {deactivated: active }})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
storeDeviceName(id, name) {
|
|
282
|
+
savedDeviceNames[id.replace(`${this.adapter.namespace}.`, '')] = name;
|
|
283
|
+
this.retainDeviceNames();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
verifyDeviceName(id, name) {
|
|
287
|
+
const savedId = id.replace(`${this.adapter.namespace}.`, '');
|
|
288
|
+
if (!savedDeviceNames.hasOwnProperty(savedId)) {
|
|
289
|
+
savedDeviceNames[savedId] = name;
|
|
290
|
+
this.retainDeviceNames();
|
|
291
|
+
}
|
|
292
|
+
return savedDeviceNames[savedId];
|
|
293
|
+
}
|
|
294
|
+
|
|
253
295
|
deleteDeviceStates(devId, callback) {
|
|
254
296
|
this.adapter.getStatesOf(devId, (err, states) => {
|
|
255
297
|
if (!err && states) {
|
|
@@ -262,6 +304,15 @@ class StatesController extends EventEmitter {
|
|
|
262
304
|
});
|
|
263
305
|
});
|
|
264
306
|
}
|
|
307
|
+
|
|
308
|
+
async deleteDeviceStatesAsync(devId) {
|
|
309
|
+
const states = await this.adapter.getStatesOf(devId);
|
|
310
|
+
if (states) {
|
|
311
|
+
await this.adapter.deleteState(devId, null, state._id);
|
|
312
|
+
}
|
|
313
|
+
await this.adapter.deleteDevice(devId);
|
|
314
|
+
}
|
|
315
|
+
|
|
265
316
|
// eslint-disable-next-line no-unused-vars
|
|
266
317
|
async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback) {
|
|
267
318
|
const devStates = await this.getDevStates(ieeeAddr, model);
|
|
@@ -280,7 +331,7 @@ class StatesController extends EventEmitter {
|
|
|
280
331
|
statename = arr[1];
|
|
281
332
|
}
|
|
282
333
|
if (commonStates.find((statedesc) => statename === statedesc.id) === undefined &&
|
|
283
|
-
devStates.states.find((statedesc) => statename === statedesc.id) === undefined
|
|
334
|
+
devStates.states.find((statedesc) => statename === statedesc.id) === undefined) {
|
|
284
335
|
if (state.common.hasOwnProperty('custom') && !force) {
|
|
285
336
|
this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
|
|
286
337
|
} else {
|
|
@@ -304,80 +355,83 @@ class StatesController extends EventEmitter {
|
|
|
304
355
|
updateState(devId, name, value, common) {
|
|
305
356
|
this.adapter.getObject(devId, (err, obj) => {
|
|
306
357
|
if (obj) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (common
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
358
|
+
if (!obj.common.deactivated) {
|
|
359
|
+
const new_common = {name: name};
|
|
360
|
+
const id = devId + '.' + name;
|
|
361
|
+
const new_name = obj.common.name;
|
|
362
|
+
if (common) {
|
|
363
|
+
if (common.name !== undefined) {
|
|
364
|
+
new_common.name = common.name;
|
|
365
|
+
}
|
|
366
|
+
if (common.type !== undefined) {
|
|
367
|
+
new_common.type = common.type;
|
|
368
|
+
}
|
|
369
|
+
if (common.unit !== undefined) {
|
|
370
|
+
new_common.unit = common.unit;
|
|
371
|
+
}
|
|
372
|
+
if (common.states !== undefined) {
|
|
373
|
+
new_common.states = common.states;
|
|
374
|
+
}
|
|
375
|
+
if (common.read !== undefined) {
|
|
376
|
+
new_common.read = common.read;
|
|
377
|
+
}
|
|
378
|
+
if (common.write !== undefined) {
|
|
379
|
+
new_common.write = common.write;
|
|
380
|
+
}
|
|
381
|
+
if (common.role !== undefined) {
|
|
382
|
+
new_common.role = common.role;
|
|
383
|
+
}
|
|
384
|
+
if (common.min !== undefined) {
|
|
385
|
+
new_common.min = common.min;
|
|
386
|
+
}
|
|
387
|
+
if (common.max !== undefined) {
|
|
388
|
+
new_common.max = common.max;
|
|
389
|
+
}
|
|
390
|
+
if (common.icon !== undefined) {
|
|
391
|
+
new_common.icon = common.icon;
|
|
392
|
+
}
|
|
340
393
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
394
|
+
// check if state exist
|
|
395
|
+
this.adapter.getObject(id, (err, stobj) => {
|
|
396
|
+
let hasChanges = false;
|
|
397
|
+
if (stobj) {
|
|
398
|
+
// update state - not change name and role (user can it changed)
|
|
399
|
+
if (stobj.common.name)
|
|
400
|
+
delete new_common.name;
|
|
401
|
+
else
|
|
402
|
+
new_common.name = new_name + ' ' + new_common.name;
|
|
403
|
+
delete new_common.role;
|
|
404
|
+
|
|
405
|
+
// check whether any common property is different
|
|
406
|
+
if (stobj.common) {
|
|
407
|
+
for (const property in new_common) {
|
|
408
|
+
if (stobj.common.hasOwnProperty(property)) {
|
|
409
|
+
if (stobj.common[property] === new_common[property]) {
|
|
410
|
+
delete new_common[property];
|
|
411
|
+
} else {
|
|
412
|
+
hasChanges = true;
|
|
413
|
+
}
|
|
361
414
|
}
|
|
362
415
|
}
|
|
363
416
|
}
|
|
417
|
+
} else {
|
|
418
|
+
hasChanges = true;
|
|
364
419
|
}
|
|
365
|
-
} else {
|
|
366
|
-
hasChanges = true;
|
|
367
|
-
}
|
|
368
420
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
421
|
+
// only change object when any common property has changed
|
|
422
|
+
if (hasChanges) {
|
|
423
|
+
this.adapter.extendObject(id, {type: 'state', common: new_common, native: {} }, () => {
|
|
424
|
+
value !== undefined && this.setState_typed(id, value, true, (stobj) ? stobj.common.type : new_common.type);
|
|
425
|
+
});
|
|
426
|
+
} else if (value !== undefined) {
|
|
427
|
+
this.setState_typed(id, value, true, stobj.common.type);
|
|
428
|
+
}
|
|
377
429
|
|
|
378
|
-
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
else this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
379
433
|
} else {
|
|
380
|
-
this.debug(`
|
|
434
|
+
this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
|
|
381
435
|
}
|
|
382
436
|
});
|
|
383
437
|
}
|
|
@@ -421,6 +475,7 @@ class StatesController extends EventEmitter {
|
|
|
421
475
|
}
|
|
422
476
|
|
|
423
477
|
updateDev(dev_id, dev_name, model, callback) {
|
|
478
|
+
const __dev_name = this.verifyDeviceName(dev_id, dev_name);
|
|
424
479
|
const id = '' + dev_id;
|
|
425
480
|
const modelDesc = statesMapping.findModel(model);
|
|
426
481
|
let icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
|
|
@@ -429,7 +484,7 @@ class StatesController extends EventEmitter {
|
|
|
429
484
|
this.adapter.setObjectNotExists(id, {
|
|
430
485
|
type: 'device',
|
|
431
486
|
// actually this is an error, so device.common has no attribute type. It must be in native part
|
|
432
|
-
common: {name:
|
|
487
|
+
common: {name: __dev_name, type: model, icon: icon},
|
|
433
488
|
native: {id: dev_id}
|
|
434
489
|
}, () => {
|
|
435
490
|
// update type and icon
|
|
@@ -438,15 +493,13 @@ class StatesController extends EventEmitter {
|
|
|
438
493
|
}
|
|
439
494
|
|
|
440
495
|
async syncDevStates(dev, model) {
|
|
441
|
-
const devId = dev.ieeeAddr.substr(2)
|
|
442
|
-
hasGroups = dev.type === 'Router';
|
|
496
|
+
const devId = dev.ieeeAddr.substr(2);
|
|
443
497
|
// devId - iobroker device id
|
|
444
498
|
const devStates = await this.getDevStates(dev.ieeeAddr, model);
|
|
445
499
|
if (!devStates) {
|
|
446
500
|
return;
|
|
447
501
|
}
|
|
448
|
-
const states = statesMapping.commonStates.concat(devStates.states)
|
|
449
|
-
.concat((hasGroups) ? [statesMapping.groupsState] : []);
|
|
502
|
+
const states = statesMapping.commonStates.concat(devStates.states);
|
|
450
503
|
|
|
451
504
|
for (const stateInd in states) {
|
|
452
505
|
if (!states.hasOwnProperty(stateInd)) continue;
|
|
@@ -39,6 +39,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
39
39
|
this.state = {};
|
|
40
40
|
this.active_ping = true;
|
|
41
41
|
this.forced_ping = true;
|
|
42
|
+
this.forcedNonPingable = {};
|
|
42
43
|
this.number_of_registered_devices = 0;
|
|
43
44
|
// force publish availability for new devices
|
|
44
45
|
this.zigbee.on('new', (entity) => {
|
|
@@ -81,6 +82,8 @@ class DeviceAvailability extends BaseExtension {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
async registerDevicePing(device, entity) {
|
|
85
|
+
this.debug('register device Ping for ' + JSON.stringify(device.ieeeAddr));
|
|
86
|
+
this.forcedNonPingable[device.ieeeAddr] = false;
|
|
84
87
|
// this.warn(`Called registerDevicePing for '${device}' of '${entity}'`);
|
|
85
88
|
if (!this.isPingable(device)) return;
|
|
86
89
|
// ensure we do not already have this device in the queue
|
|
@@ -97,6 +100,14 @@ class DeviceAvailability extends BaseExtension {
|
|
|
97
100
|
}, this.startDevicePingDelay);
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
async deregisterDevicePing(device) {
|
|
104
|
+
this.info('deregister device Ping for deactivated device ' + JSON.stringify(device.ieeeAddr));
|
|
105
|
+
this.forcedNonPingable[device.ieeeAddr] = true;
|
|
106
|
+
if (this.timers[device.ieeeAddr]) {
|
|
107
|
+
clearTimeout(this.timers[device.ieeeAddr]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
async startDevicePing() {
|
|
101
112
|
// this.warn(JSON.stringify(this));
|
|
102
113
|
this.startDevicePingTimeout = null;
|
|
@@ -269,7 +280,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
269
280
|
|
|
270
281
|
onZigbeeEvent(data) {
|
|
271
282
|
const device = data.device;
|
|
272
|
-
if (!device) {
|
|
283
|
+
if (!device || this.forcedNonPingable[device.ieeeAddr]) {
|
|
273
284
|
return;
|
|
274
285
|
}
|
|
275
286
|
|
package/lib/zigbeecontroller.js
CHANGED
|
@@ -150,7 +150,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
150
150
|
break;
|
|
151
151
|
case '20':
|
|
152
152
|
powerText = 'high+';
|
|
153
|
-
break;
|
|
153
|
+
break;
|
|
154
154
|
default:
|
|
155
155
|
powerText = 'normal';
|
|
156
156
|
}
|
|
@@ -178,8 +178,16 @@ class ZigbeeController extends EventEmitter {
|
|
|
178
178
|
}
|
|
179
179
|
for (const device of devices) {
|
|
180
180
|
const entity = await this.resolveEntity(device);
|
|
181
|
+
this.adapter.getObject(device.ieeeAddr.substr(2),(err, obj) => {
|
|
182
|
+
if (obj && obj.common && obj.common.deactivated)
|
|
183
|
+
{
|
|
184
|
+
this.callExtensionMethod('deregisterDevicePing', [device, entity]);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.callExtensionMethod('registerDevicePing', [device, entity]);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
181
190
|
// ensure that objects for all found clients are present
|
|
182
|
-
this.callExtensionMethod('registerDevicePing', [device, entity]);
|
|
183
191
|
|
|
184
192
|
if (entity.mapped) this.emit('new', entity);
|
|
185
193
|
this.info(
|
|
@@ -281,6 +289,21 @@ class ZigbeeController extends EventEmitter {
|
|
|
281
289
|
}
|
|
282
290
|
}
|
|
283
291
|
|
|
292
|
+
async verifyGroupExists(id) {
|
|
293
|
+
const nid = (typeof(id) === 'number' ? id:parseInt(id));
|
|
294
|
+
let group = await this.herdsman.getGroupByID(nid);
|
|
295
|
+
if (!group) {
|
|
296
|
+
group = await this.herdsman.createGroup(nid);
|
|
297
|
+
group.toZigbee = groupConverters;
|
|
298
|
+
group.model = 'group';
|
|
299
|
+
this.debug('verifyGroupExists: created group ' + nid);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
this.debug('verifyGroupExists: group ' + nid + ' exists');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
}
|
|
306
|
+
|
|
284
307
|
async getGroupMembersFromController(id) {
|
|
285
308
|
const members = [];
|
|
286
309
|
try {
|
package/main.js
CHANGED
|
@@ -306,7 +306,6 @@ class Zigbee extends utils.Adapter {
|
|
|
306
306
|
|
|
307
307
|
async onZigbeeAdapterReady() {
|
|
308
308
|
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
309
|
-
this.log.warn("config :" + JSON.stringify(this.config));
|
|
310
309
|
this.log.info(`Zigbee started`);
|
|
311
310
|
// https://github.com/ioBroker/ioBroker.zigbee/issues/668
|
|
312
311
|
const extPanIdFix = this.config.extPanIdFix ? this.config.extPanIdFix : false;
|
|
@@ -838,6 +837,10 @@ class Zigbee extends utils.Adapter {
|
|
|
838
837
|
this.setState('info.pairingMessage', message, true);
|
|
839
838
|
}
|
|
840
839
|
|
|
840
|
+
expandFileName(fn) {
|
|
841
|
+
return path.join(utils.getAbsoluteInstanceDataDir(this), fn);
|
|
842
|
+
}
|
|
843
|
+
|
|
841
844
|
onLog(level, msg, data) {
|
|
842
845
|
if (msg) {
|
|
843
846
|
let logger = this.log.info;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zigbee",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.14",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Kirov Ilya",
|
|
6
6
|
"email": "kirovilya@gmail.com"
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"node": ">=10"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"zigbee-herdsman": "0.
|
|
22
|
-
"zigbee-herdsman-converters": "14.0.
|
|
21
|
+
"zigbee-herdsman": "0.14.6",
|
|
22
|
+
"zigbee-herdsman-converters": "14.0.397",
|
|
23
23
|
"@iobroker/adapter-core": "^2.4.0",
|
|
24
24
|
"tar": "^6.0.5",
|
|
25
25
|
"typescript": "^4.0.5"
|