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 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 cc253x (and others models) or deCONZ ConBee/RaspBee modules, 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).
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
- For work, you need one of the following Adapters [all are listed here](https://www.zigbee2mqtt.io/information/supported_adapters.html) , flashed with a special ZNP firmware: [cc2531, cc2530, cc26x2r, cc2538](https://github.com/Koenkk/Z-Stack-firmware)
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
- ConBee I
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 https://github.com/ioBroker/ioBroker.zigbee/pull/1327)
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(',') || '&nbsp';
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 ${id.replace(namespace+'.group_', '')}</span></li>`);
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) > 0 ? `<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>` : '',
220
- status = (dev.link_quality) > 0 ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`,
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) > 0 ? `<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>` : '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>',
2689
- status = (dev.link_quality) > 0 ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (groupImage ? '': `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`),
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);
@@ -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
  }
Binary file
package/io-package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "zigbee",
4
- "version": "1.6.12",
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
- this.syncGroups(groups);
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 && groups[gid]=== undefined) {
102
- groups[gid] = `Auto Group ${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.deleteDeviceStates(`group_${parseInt(message)}`);
232
+ await this.stController.deleteDeviceStatesAsync(`group_${parseInt(message)}`);
226
233
  }
227
234
 
228
235
  async renameGroup(from, command, message) {
229
- const groupsEntry = await this.adapter.getStateAsync('info.groups');
230
- const objGroups = (groupsEntry && groupsEntry.val ? JSON.parse(groupsEntry.val) : {});
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
- objGroups[message.id.toString()] = message.name;
234
- await this.adapter.setStateAsync('info.groups', JSON.stringify(objGroups), true);
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(adapterGroups) {
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',
@@ -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 && statename != 'groups') {
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
- const new_common = {name: name};
308
- const id = devId + '.' + name;
309
- const new_name = obj.common.name;
310
- if (common) {
311
- if (common.name !== undefined) {
312
- new_common.name = common.name;
313
- }
314
- if (common.type !== undefined) {
315
- new_common.type = common.type;
316
- }
317
- if (common.unit !== undefined) {
318
- new_common.unit = common.unit;
319
- }
320
- if (common.states !== undefined) {
321
- new_common.states = common.states;
322
- }
323
- if (common.read !== undefined) {
324
- new_common.read = common.read;
325
- }
326
- if (common.write !== undefined) {
327
- new_common.write = common.write;
328
- }
329
- if (common.role !== undefined) {
330
- new_common.role = common.role;
331
- }
332
- if (common.min !== undefined) {
333
- new_common.min = common.min;
334
- }
335
- if (common.max !== undefined) {
336
- new_common.max = common.max;
337
- }
338
- if (common.icon !== undefined) {
339
- new_common.icon = common.icon;
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
- // check if state exist
343
- this.adapter.getObject(id, (err, stobj) => {
344
- let hasChanges = false;
345
- if (stobj) {
346
- // update state - not change name and role (user can it changed)
347
- if (stobj.common.name)
348
- delete new_common.name;
349
- else
350
- new_common.name = new_name + ' ' + new_common.name;
351
- delete new_common.role;
352
-
353
- // check whether any common property is different
354
- if (stobj.common) {
355
- for (const property in new_common) {
356
- if (stobj.common.hasOwnProperty(property)) {
357
- if (stobj.common[property] === new_common[property]) {
358
- delete new_common[property];
359
- } else {
360
- hasChanges = true;
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
- // only change object when any common property has changed
370
- if (hasChanges) {
371
- this.adapter.extendObject(id, {type: 'state', common: new_common, native: {} }, () => {
372
- value !== undefined && this.setState_typed(id, value, true, (stobj) ? stobj.common.type : new_common.type);
373
- });
374
- } else if (value !== undefined) {
375
- this.setState_typed(id, value, true, stobj.common.type);
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(`Wrong device ${devId} ${JSON.stringify(obj)}`);
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: dev_name, type: model, icon: icon},
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
 
@@ -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.12",
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.13.192",
22
- "zigbee-herdsman-converters": "14.0.378",
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"