iobroker.zigbee 1.6.0 → 1.6.12

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
@@ -97,7 +97,7 @@ Works with devices from this list https://github.com/ioBroker/ioBroker.zigbee/wi
97
97
 
98
98
  You can thank the authors by these links:
99
99
  * to Kirov Ilya https://www.paypal.me/goofyk
100
- * to Arthur Rupp https://paypal.me/pools/c/8gWlKqAfIF
100
+ * to Arthur Rupp https://paypal.me/ArthurRupp
101
101
 
102
102
  <!--
103
103
  Placeholder for the next version (at the beginning of the line):
@@ -112,8 +112,22 @@ You can thank the authors by these links:
112
112
 
113
113
  ## Changelog
114
114
 
115
+ ### 1.6.12 (2022-01)
116
+ * (asgothian) Groups were newly revised (read here https://github.com/ioBroker/ioBroker.zigbee/pull/1327)
117
+
118
+
119
+ ### 1.6.9 (2021-12)
120
+ * (simatec) fix admin Dark-Mode
121
+ * (asgothian) Expose Access Handling
122
+ * (arteck) translations
123
+ * (asgothian) fix groups
124
+ * (agross) use different normalization rules
125
+
126
+ ### 1.6.1 (2021-08)
127
+ * (kirovilya) herdsman compatibility
128
+
115
129
  ### 1.6.0 (2021-08-09)
116
- Attention!
130
+ ## Attention! Attention! Attention! Attention! Attention! Attention! Attention!
117
131
 
118
132
  After introducing a new z-stack startup procedure into zigbee-herdsman, we got some problems with our adapter in version 1.5.6.
119
133
  This was discussed [here](https://github.com/ioBroker/ioBroker.zigbee/issues/1110) and [here](https://github.com/Koenkk/zigbee-herdsman/issues/376)
@@ -3,8 +3,33 @@ const path = location.pathname;
3
3
  const parts = path.split('/');
4
4
  parts.splice(-3);
5
5
 
6
- const socket = io.connect('/', {path: parts.join('/') + '/socket.io'});
7
- const instance = window.location.search.slice(-1) || 0;
6
+ const socket = io.connect('/', { path: parts.join('/') + '/socket.io' });
7
+ var query = (window.location.search || '').replace(/^\?/, '').replace(/#.*$/, '');
8
+ var args = {};
9
+ let theme = null;
10
+
11
+ // parse parameters
12
+ query.trim().split('&').filter(function (t) { return t.trim(); }).forEach(function (b, i) {
13
+ const parts = b.split('=');
14
+ if (!i && parts.length === 1 && !isNaN(parseInt(b, 10))) {
15
+ args.instance = parseInt(b, 10);
16
+ }
17
+ var name = parts[0];
18
+ args[name] = parts.length === 2 ? parts[1] : true;
19
+
20
+ if (name === 'instance') {
21
+ args.instance = parseInt(args.instance, 10) || 0;
22
+ }
23
+
24
+ if (args[name] === 'true') {
25
+ args[name] = true;
26
+ } else if (args[name] === 'false') {
27
+ args[name] = false;
28
+ }
29
+ });
30
+
31
+ var instance = args.instance;
32
+
8
33
  let common = null; // common information of adapter
9
34
  const host = null; // host object on which the adapter runs
10
35
  const changed = false;
@@ -71,6 +96,17 @@ function loadSettings(callback) {
71
96
  if (typeof load === 'undefined') {
72
97
  alert('Please implement save function in your admin/index.html');
73
98
  } else {
99
+ // detect, that we are now in react container (themeNames = ['dark', 'blue', 'colored', 'light'])
100
+
101
+ const _query = query.split('&');
102
+
103
+ for (var q = 0; q < _query.length; q++) {
104
+ if (_query[q].indexOf('react=') !== -1) {
105
+ $('.adapter-container').addClass('react-' + _query[q].substring(6));
106
+ theme = 'react-' + _query[q].substring(6);
107
+ }
108
+ }
109
+
74
110
  load(res.native, onChange);
75
111
  }
76
112
  if (typeof callback === 'function') {
@@ -205,4 +241,4 @@ function showMessage(message, title, icon) {
205
241
  }
206
242
  $dialogMessage.find('.dialog-text').html(message);
207
243
  $dialogMessage.modal().modal('open');
208
- }
244
+ }
package/admin/admin.js CHANGED
@@ -146,7 +146,7 @@ function getGroupCard(dev) {
146
146
  info = info.concat(`<li><span class="labelinfo">No devices in group</span></li>`);
147
147
  } else {
148
148
  for (let m=0;m < dev.memberinfo.length; m++) {
149
- info = info.concat(`<li><span class="labelinfo">${dev.memberinfo[m].name}</span><span> ...${dev.memberinfo[m].ieee.slice(-4)}</span></li>`);
149
+ info = info.concat(`<li><span align:"left">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid}</span><span align:"right"> ...${dev.memberinfo[m].ieee.slice(-4)}</span></li>`);
150
150
  }
151
151
  memberCount = (dev.memberinfo.length<8?dev.memberinfo.length:7);
152
152
  };
@@ -154,7 +154,7 @@ function getGroupCard(dev) {
154
154
  </div>`);
155
155
  const image = `<img src="img/group_${memberCount}.png" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
156
156
  const dashCard = getDashCard(dev,`img/group_${memberCount}.png` );
157
- const card = `<div id="${id}" class="device">
157
+ const card = `<div id="${id}" class="device group">
158
158
  <div class="card hoverable flipable">
159
159
  <div class="front face">${dashCard}</div>
160
160
  <div class="back face">
@@ -187,12 +187,16 @@ function getGroupCard(dev) {
187
187
  return card;
188
188
  }
189
189
 
190
-
190
+ function sanitizeModelParameter(parameter) {
191
+ const replaceByUnderscore = /[\s/]/g;
192
+ return parameter.replace(replaceByUnderscore, '_');
193
+ }
191
194
 
192
195
  function getCard(dev) {
193
196
  const title = dev.common.name,
194
197
  id = dev._id,
195
- type = (dev.common.type ? dev.common.type.replace('/','_'):'unknown'),
198
+ type = (dev.common.type ? dev.common.type : 'unknown'),
199
+ type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'),
196
200
  img_src = dev.icon || dev.common.icon,
197
201
  rooms = [],
198
202
  lang = systemLang || 'en';
@@ -206,7 +210,7 @@ function getCard(dev) {
206
210
  const room = rooms.join(',') || '&nbsp';
207
211
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
208
212
  const rid = id.split('.').join('_');
209
- const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
213
+ const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
210
214
  const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
211
215
  nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
212
216
  battery_cls = getBatteryCls(dev.battery),
@@ -254,6 +258,9 @@ function getCard(dev) {
254
258
  <button name="edit" class="right btn-flat btn-small">
255
259
  <i class="material-icons icon-green">edit</i>
256
260
  </button>
261
+ <button name="reconfigure" class="right btn-flat btn-small tooltipped" title="Reconfigure">
262
+ <i class="material-icons icon-red">sync</i>
263
+ </button>
257
264
  ${permitJoinBtn}
258
265
  </div>
259
266
  </div>
@@ -344,25 +351,96 @@ function cleanConfirmation() {
344
351
  Materialize.updateTextFields();
345
352
  }
346
353
 
354
+ function EndPointIDfromEndPoint(ep)
355
+ {
356
+ if (ep && ep.deviceIeeeAddress && ep.ID)
357
+ return `${ep.deviceIeeeAddress}:${ep.ID}`;
358
+ return 'unidentified';
359
+ }
360
+
347
361
  function editName(id, name) {
362
+ console.log('editName called with '+name);
348
363
  const dev = devices.find((d) => d._id == id);
349
364
  $('#modaledit').find("input[id='d_name']").val(name);
350
- if (dev.info && dev.info.device._type == 'Router') {
351
- list2select('#d_groups', groups, devGroups[id] || []);
352
- $('#modaledit').find('.input-field.groups').removeClass('hide');
353
- } else {
354
- $('#modaledit').find('.input-field.groups').addClass('hide');
355
- }
365
+ // if (dev.info && dev.info.device._type == 'Router') {
366
+ const groupables = [];
367
+ if (dev && dev.info && dev.info.endpoints) {
368
+ for (const ep of dev.info.endpoints) {
369
+ if (ep.inputClusters.includes(4)) {
370
+ groupables.push({ epid:EndPointIDfromEndPoint(ep), ep:ep, memberOf:[]});
371
+ }
372
+ }
373
+ }
374
+ const numEP = groupables.length;
375
+ // console.log('groupables: '+JSON.stringify(groupables));
376
+ $('#modaledit').find('.row.epid0').addClass('hide');
377
+ $('#modaledit').find('.row.epid1').addClass('hide');
378
+ $('#modaledit').find('.row.epid2').addClass('hide');
379
+ $('#modaledit').find('.row.epid3').addClass('hide');
380
+ if (numEP > 0) {
381
+ // go through all the groups. Find the ones to list for each groupable
382
+ if (numEP == 1) {
383
+ $('#modaledit').find('.endpointid').addClass('hide');
384
+ }
385
+ else {
386
+ $('#modaledit').find('.endpointid').removeClass('hide');
387
+ }
388
+ for (const d of devices) {
389
+ if (d && d.common && d.common.type == 'group') {
390
+ if (d.hasOwnProperty("memberinfo")) {
391
+ for (const member of d.memberinfo) {
392
+ const epid = EndPointIDfromEndPoint(member.ep)
393
+ for (var i=0;i<groupables.length;i++) {
394
+ if (groupables[i].epid == epid) {
395
+ groupables[i].memberOf.push(d.native.id.replace('group_', ''));
396
+ }
397
+ }
398
+ }
399
+ }
400
+ }
401
+ }
402
+ console.log("groupables: " + JSON.stringify(groupables));
403
+ for (var i = 0;i<groupables.length;i++)
404
+ {
405
+ if (i > 1) {
406
+ $('#modaledit').find("translate.device_with_endpoint").innerHtml = name + ' ' + groupables[i].epid;
407
+ }
408
+ $('#modaledit').find('.row.epid'+i).removeClass('hide');
409
+ list2select('#d_groups_ep'+i, groups, groupables[i].memberOf || []);
410
+ }
411
+ }
412
+ // } else {
413
+ // $('#modaledit').find('.input-field.endpoints').addClass('hide');
414
+ // $('#modaledit').find('.input-field.groups').addClass('hide');
415
+ // }
356
416
  $("#modaledit a.btn[name='save']").unbind('click');
357
417
  $("#modaledit a.btn[name='save']").click(() => {
358
- const newName = $('#modaledit').find("input[id='d_name']").val(),
359
- newGroups = $('#d_groups').val();
360
- updateDev(id, newName, newGroups);
418
+ const newName = $('#modaledit').find("input[id='d_name']").val();
419
+ const groupsbyid = {};
420
+ if (groupables.length > 0) {
421
+ for (var i = 0;i<groupables.length;i++) {
422
+ const ng = $('#d_groups_ep'+i).val();
423
+ if (ng.toString() != groupables[i].memberOf.toString())
424
+ groupsbyid[groupables[i].ep.ID] = GenerateGroupChange(groupables[i].memberOf, ng);
425
+ }
426
+ }
427
+ console.log('grpid ' + JSON.stringify(groupsbyid));
428
+ updateDev(id, newName, groupsbyid);
361
429
  });
362
430
  $('#modaledit').modal('open');
363
431
  Materialize.updateTextFields();
364
432
  }
365
433
 
434
+ function GenerateGroupChange(oldmembers, newmembers)
435
+ {
436
+ let grpchng = [];
437
+ for (const oldg of oldmembers)
438
+ if (!newmembers.includes(oldg)) grpchng.push('-'+oldg);
439
+ for (const newg of newmembers)
440
+ if (!oldmembers.includes(newg)) grpchng.push(newg)
441
+ return grpchng;
442
+ }
443
+
366
444
  function deleteDevice(id, force) {
367
445
  sendTo(namespace, 'deleteDevice', {id: id, force: force}, function (msg) {
368
446
  closeWaitingDialog();
@@ -525,7 +603,7 @@ function showDevices() {
525
603
  const dev_block = $(this).parents('div.device'),
526
604
  id = dev_block.attr('id').replace(namespace+'.group_', ''),
527
605
  name = getDevName(dev_block);
528
- editGroupName(id, name);
606
+ editGroupName(id, name, false);
529
607
  });
530
608
  $("button.btn-floating[name='join']").click(function() {
531
609
  const dev_block = $(this).parents('div.device');
@@ -544,6 +622,10 @@ function showDevices() {
544
622
  $("a.btn-flat[name='close']").click((e) => {
545
623
  closeReval(e);
546
624
  });
625
+ $(".card-reveal-buttons button[name='reconfigure']").click(function() {
626
+ const dev_block = $(this).parents('div.device');
627
+ reconfigureDlg(getDevId(dev_block));
628
+ });
547
629
 
548
630
  showNetworkMap(devices, map);
549
631
  translateAll();
@@ -656,7 +738,7 @@ function getDevices() {
656
738
  }
657
739
 
658
740
  function getDeviceCards() {
659
- return $('#devices .device');
741
+ return $('#devices .device').not(".group");
660
742
  }
661
743
 
662
744
  function getDeviceCard(devId) {
@@ -759,11 +841,11 @@ function load(settings, onChange) {
759
841
 
760
842
  $('#add_group').click(function() {
761
843
  const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
762
- editGroupName(maxind+1, '');
844
+ editGroupName(maxind+1, 'Group ' + maxind+1, true);
763
845
  });
764
846
  $('#add_grp_btn').click(function() {
765
847
  const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
766
- editGroupName(maxind+1, '');
848
+ editGroupName(maxind+1, 'Group ' + maxind+1, true);
767
849
  getDevices();
768
850
  });
769
851
 
@@ -1702,7 +1784,7 @@ function showGroups() {
1702
1784
  $("a.btn-floating[name='groupedit']").click(function() {
1703
1785
  const index = $(this).attr('id'),
1704
1786
  name = groups[index];
1705
- editGroupName(index, name);
1787
+ editGroupName(index, name, false);
1706
1788
  });
1707
1789
  $("a.btn-floating[name='groupdelete']").click(function() {
1708
1790
  const index = $(this).attr('id'),
@@ -1711,8 +1793,35 @@ function showGroups() {
1711
1793
  });
1712
1794
  }
1713
1795
 
1714
- function editGroupName(id, name) {
1796
+ function editGroupName(id, name, isnew) {
1797
+ //const dev = devices.find((d) => d._id == id);
1798
+ //console.log('devices: '+ JSON.stringify(devices));
1799
+ const groupables = [];
1800
+ for (const d of devices) {
1801
+ if (d && d.info && d.info.endpoints) {
1802
+ for (const ep of d.info.endpoints) {
1803
+ if (ep.inputClusters.includes(4))
1804
+ {
1805
+ groupables.push(ep);
1806
+ }
1807
+ }
1808
+ }
1809
+ //console.log('device ' + JSON.stringify(d));
1810
+ }
1811
+
1715
1812
  //var text = 'Enter new name for "'+name+'" ('+id+')?';
1813
+ if (isnew) {
1814
+ $('#groupedit').find('.editgroup').addClass('hide');
1815
+ $('#groupedit').find('.addgroup').removeClass('hide');
1816
+ $('#groupedit').find('.input-field.members').addClass('hide');
1817
+ $('#groupedit').find('.input-field.groupid').removeClass('hide');
1818
+ }
1819
+ else {
1820
+ $('#groupedit').find('.editgroup').removeClass('hide');
1821
+ $('#groupedit').find('.addgroup').addClass('hide');
1822
+ $('#groupedit').find('.input-field.members').removeClass('hide');
1823
+ $('#groupedit').find('.input-field.groupid').addClass('hide');
1824
+ }
1716
1825
  $('#groupedit').find("input[id='g_index']").val(id);
1717
1826
  $('#groupedit').find("input[id='g_name']").val(name);
1718
1827
  $("#groupedit a.btn[name='save']").unbind('click');
@@ -1756,6 +1865,21 @@ function updateDev(id, newName, newGroups) {
1756
1865
  if (dev && dev.common.name != newName) {
1757
1866
  renameDevice(id, newName);
1758
1867
  }
1868
+ const keys = Object.keys(newGroups)
1869
+ if (keys && keys.length)
1870
+ {
1871
+ sendTo(namespace, 'updateGroupMembership', { id: id, groups: newGroups }, function (msg) {
1872
+ if (msg && msg.error) {
1873
+ showMessage(msg.error, _('Error'));
1874
+ }
1875
+ else {
1876
+ // save dev-groups on success
1877
+ dev.groups = newGroups;
1878
+ }
1879
+ showDevices();
1880
+ });
1881
+ }
1882
+ /*
1759
1883
  if (dev.info.device._type == 'Router') {
1760
1884
  const oldGroups = devGroups[id] || [];
1761
1885
  if (oldGroups.toString() != newGroups.toString()) {
@@ -1772,6 +1896,7 @@ function updateDev(id, newName, newGroups) {
1772
1896
  });
1773
1897
  }
1774
1898
  }
1899
+ */
1775
1900
  }
1776
1901
 
1777
1902
  function resetConfirmation() {
@@ -2166,7 +2291,7 @@ function genDevInfo(device) {
2166
2291
  const dev = (device && device.info) ? device.info.device : undefined;
2167
2292
  const mapped = (device && device.info) ? device.info.mapped : undefined;
2168
2293
  if (!dev) return `<div class="truncate">No info</div>`;
2169
- const genRow = function(name, value) {
2294
+ const genRow = function(name, value, refresh) {
2170
2295
  if (value === undefined) {
2171
2296
  return '';
2172
2297
  } else {
@@ -2185,7 +2310,7 @@ function genDevInfo(device) {
2185
2310
  }).join('');
2186
2311
  }
2187
2312
  };
2188
- const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${mapped.model}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
2313
+ const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(mapped.model)}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
2189
2314
  const mappedInfo = (!mapped) ? '' :
2190
2315
  `<div style="font-size: 0.9em">
2191
2316
  <ul>
@@ -2230,7 +2355,7 @@ function genDevInfo(device) {
2230
2355
  ${genRow('date code', dev._dateCode)}
2231
2356
  ${genRow('build', dev._softwareBuildID)}
2232
2357
  ${genRow('interviewed', dev._interviewCompleted)}
2233
- ${genRow('configured', (dev.meta.configured === 1))}
2358
+ ${genRow('configured', (dev.meta.configured === 1), true)}
2234
2359
  </ul>
2235
2360
  </div>
2236
2361
  </div>
@@ -2246,17 +2371,19 @@ function showDevInfo(id){
2246
2371
  $('#modaldevinfo').modal('open');
2247
2372
  }
2248
2373
 
2374
+ let waitingTimeout, waitingInt;
2249
2375
 
2250
2376
  function showWaitingDialog(text, timeout){
2251
2377
  let countDown = timeout;
2252
- const waitingInt = setInterval(function() {
2378
+ waitingInt = setInterval(function() {
2253
2379
  countDown -= 1;
2254
2380
  const percent = 100-100*countDown/timeout;
2255
2381
  $('#waiting_progress_line').css('width', `${percent}%`);
2256
2382
  }, 1000);
2257
- setTimeout(function() {
2383
+ waitingTimeout = setTimeout(function() {
2258
2384
  $('#waiting_progress_line').css('width', `0%`);
2259
2385
  clearTimeout(waitingInt);
2386
+ clearTimeout(waitingTimeout);
2260
2387
  $('#modalWaiting').modal('close');
2261
2388
  }, timeout*1000);
2262
2389
  $('#waiting_message').text(text);
@@ -2264,6 +2391,8 @@ function showWaitingDialog(text, timeout){
2264
2391
  }
2265
2392
 
2266
2393
  function closeWaitingDialog(){
2394
+ if (waitingInt) clearTimeout(waitingInt);
2395
+ if (waitingTimeout) clearTimeout(waitingTimeout);
2267
2396
  $('#modalWaiting').modal('close');
2268
2397
  }
2269
2398
 
@@ -2427,10 +2556,10 @@ function showExclude() {
2427
2556
  const exclude_dev = devices.find((d) => d.common.type == exclude_id) || {common: {name: exclude_id}};
2428
2557
  // exclude_icon = (exclude_dev.icon) ? `<img src="${exclude_dev.icon}" width="64px">` : '';
2429
2558
 
2430
- const modelUrl = (!exclude_id) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${exclude_id}.html" target="_blank" rel="noopener noreferrer">${exclude_id}</a>`;
2559
+ const modelUrl = (!exclude_id) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(exclude_id)}.html" target="_blank" rel="noopener noreferrer">${exclude_id}</a>`;
2431
2560
 
2432
2561
  const card = `
2433
- <div id="${exclude_id}" class="exclude col s12 m6 l4 xl3">
2562
+ <div id="${exclude_id}" class="exclude col s12 m6 l4 xl3" style="height: 135px;padding-bottom: 10px;">
2434
2563
  <div class="card hoverable">
2435
2564
  <div class="card-content zcard">
2436
2565
  <i class="left"><img src="${exclude_dev.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';"></i>
@@ -2698,3 +2827,26 @@ function removeDevice(id) {
2698
2827
  }
2699
2828
  }
2700
2829
  }
2830
+
2831
+ function reconfigureDlg(id) {
2832
+ const text = translateWord(`Do you really want to reconfigure device?`);
2833
+ $('#modalreconfigure').find('p').text(text);
2834
+ $("#modalreconfigure a.btn[name='yes']").unbind('click');
2835
+ $("#modalreconfigure a.btn[name='yes']").click(() => {
2836
+ reconfigureDevice(id, force);
2837
+ });
2838
+ $('#modalreconfigure').modal('open');
2839
+ Materialize.updateTextFields();
2840
+ }
2841
+
2842
+ function reconfigureDevice(id) {
2843
+ sendTo(namespace, 'reconfigure', {id: id}, function (msg) {
2844
+ closeWaitingDialog();
2845
+ if (msg) {
2846
+ if (msg.error) {
2847
+ showMessage(msg.error, _('Error'));
2848
+ }
2849
+ }
2850
+ });
2851
+ showWaitingDialog('Device is being reconfigure', 30);
2852
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file