iobroker.zigbee 2.0.0 → 2.0.2

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/admin/admin.js CHANGED
@@ -3,13 +3,17 @@
3
3
  /*
4
4
  * you must run 'iobroker upload zigbee' if you edited this file to make changes visible
5
5
  */
6
+
7
+
6
8
  const Materialize = (typeof M !== 'undefined') ? M : Materialize,
7
9
  anime = (typeof M !== 'undefined') ? M.anime : anime,
8
10
  namespace = 'zigbee.' + instance,
9
11
  namespaceLen = namespace.length;
10
12
  let devices = [],
13
+ debugDevices = [],
11
14
  messages = [],
12
15
  map = {},
16
+ namedColors = [],
13
17
  mapEdges = null,
14
18
  network,
15
19
  networkEvents,
@@ -26,7 +30,8 @@ let devices = [],
26
30
  channel: 'd2'
27
31
  },
28
32
  cidList,
29
- shuffleInstance;
33
+ shuffleInstance,
34
+ errorData = [];
30
35
  const updateCardInterval = setInterval(updateCardTimer, 6000);
31
36
 
32
37
  const networkOptions = {
@@ -94,7 +99,7 @@ function getLQICls(value) {
94
99
  if (value < 20) return 'icon-red';
95
100
  if (value < 50) return 'icon-orange';
96
101
  }
97
- return '';
102
+ return 'icon-green';
98
103
  }
99
104
 
100
105
 
@@ -172,8 +177,8 @@ function getGroupCard(dev) {
172
177
  ;
173
178
  info = info.concat(` </ul>
174
179
  </div>`);
175
- const image = `<img src="img/group_${memberCount}.png" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
176
- const dashCard = getDashCard(dev, `img/group_${memberCount}.png`);
180
+ const image = `<img src="${dev.common.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
181
+ const dashCard = getDashCard(dev, dev.common.icon, memberCount > 0);
177
182
  const card = `<div id="${id}" class="device group">
178
183
  <div class="card hoverable flipable">
179
184
  <div class="front face">${dashCard}</div>
@@ -199,6 +204,9 @@ function getGroupCard(dev) {
199
204
  <button name="editgrp" class="right btn-flat btn-small">
200
205
  <i class="material-icons icon-green">edit</i>
201
206
  </button>
207
+ <button name="swapimage" class="right btn-flat btn-small tooltipped" title="Edit">
208
+ <i class="material-icons icon-black">image</i>
209
+ </button>
202
210
  </div>
203
211
  </div>
204
212
  </div>
@@ -213,14 +221,18 @@ function sanitizeModelParameter(parameter) {
213
221
  }
214
222
 
215
223
  function getCard(dev) {
224
+ //console.warn(JSON.stringify(dev));
225
+ if (!dev._id) return '';
216
226
  const title = dev.common.name,
217
- id = dev._id,
227
+ id = (dev._id ? dev._id : ''),
218
228
  type = (dev.common.type ? dev.common.type : 'unknown'),
219
229
  type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'),
220
- img_src = dev.icon || dev.common.icon,
230
+ img_src = dev.common.icon || dev.icon,
221
231
  rooms = [],
222
232
  isActive = (dev.common.deactivated ? false : true),
223
- lang = systemLang || 'en';
233
+ lang = systemLang || 'en',
234
+ ieee = id.replace(namespace + '.', ''),
235
+ isDebug = checkDebugDevice(ieee);
224
236
  for (const r in dev.rooms) {
225
237
  if (dev.rooms[r].hasOwnProperty(lang)) {
226
238
  rooms.push(dev.rooms[r][lang]);
@@ -228,6 +240,7 @@ function getCard(dev) {
228
240
  rooms.push(dev.rooms[r]);
229
241
  }
230
242
  }
243
+ console.warn('debug for ' + ieee + ' is ' + isDebug);
231
244
  const room = rooms.join(',') || '&nbsp';
232
245
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
233
246
  const rid = id.split('.').join('_');
@@ -237,18 +250,20 @@ function getCard(dev) {
237
250
  battery_cls = (isActive ? getBatteryCls(dev.battery) : ''),
238
251
  lqi_cls = getLQICls(dev.link_quality),
239
252
  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>` : '',
240
- 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>` : '',
241
- 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>` : ''),
253
+ lq = (dev.link_quality > 0)
254
+ ? `<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>`
255
+ : `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`,
256
+ status = (isActive ? lq : `<div class="col tool"><i class="material-icons icon-red">cancel</i></div>`),
242
257
  info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
243
258
  <ul>
244
- <li><span class="labelinfo">ieee:</span><span>0x${id.replace(namespace + '.', '')}</span></li>
259
+ <li><span class="labelinfo">ieee:</span><span>0x${ieee}</span></li>
245
260
  <li><span class="labelinfo">nwk:</span><span>${(nwk) ? nwk.toString() + ' (0x' + nwk.toString(16) + ')' : ''}</span></li>
246
261
  <li><span class="labelinfo">model:</span><span>${modelUrl}</span></li>
247
262
  <li><span class="labelinfo">groups:</span><span>${dev.groupNames || ''}</span></li>
248
263
  </ul>
249
264
  </div>`,
250
- 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>' : '',
251
- 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>`,
265
+ deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive ? 'Deactivate' : 'Activate')}"><i class="material-icons ${(isActive ? 'icon-green' : 'icon-red')}">power_settings_new</i></button>`,
266
+ debugBtn = `<button name="swapdebug" class="right btn-flat btn-small tooltipped" title="${(isDebug > -1 ? (isDebug > 0) ?'Automatic by '+debugDevices[isDebug-1]: 'Disable Debug' : 'Enable Debug')}"><i class="material-icons icon-${(isDebug > -1 ? (isDebug > 0 ? 'orange' : 'green') : 'gray')}">bug_report</i></button>`,
252
267
  infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
253
268
  const dashCard = getDashCard(dev);
254
269
  const card = `<div id="${id}" class="device">
@@ -259,7 +274,7 @@ function getCard(dev) {
259
274
  <div class="flip" style="cursor: pointer">
260
275
  <span class="top right small" style="border-radius: 50%">
261
276
  ${battery}
262
- ${lq}
277
+ <!--${lq}-->
263
278
  ${status}
264
279
  </span>
265
280
  <!--/a--!>
@@ -274,17 +289,20 @@ function getCard(dev) {
274
289
  ${infoBtn}
275
290
  <span class="left" style="padding-top:8px">${room}</span>
276
291
  <span class="left fw_info"></span>
277
- <button name="delete" class="right btn-flat btn-small">
278
- <i class="material-icons icon-black">delete</i>
292
+ <button name="delete" class="right btn-flat btn-small tooltipped" title="Delete">
293
+ <i class="material-icons icon-red">delete</i>
279
294
  </button>
280
- <button name="edit" class="right btn-flat btn-small">
281
- <i class="material-icons icon-green">edit</i>
295
+ <button name="edit" class="right btn-flat btn-small tooltipped" title="Edit">
296
+ <i class="material-icons icon-black">edit</i>
297
+ </button>
298
+ <button name="swapimage" class="right btn-flat btn-small tooltipped" title="Select Image">
299
+ <i class="material-icons icon-black">image</i>
282
300
  </button>
283
301
  <button name="reconfigure" class="right btn-flat btn-small tooltipped" title="Reconfigure">
284
302
  <i class="material-icons icon-red">sync</i>
285
303
  </button>
286
304
  ${deactBtn}
287
- ${permitJoinBtn}
305
+ ${debugBtn}
288
306
  </div>
289
307
  </div>
290
308
  </div>
@@ -364,6 +382,7 @@ function cleanConfirmation() {
364
382
  const text = translateWord('Do you really want to remove orphaned states?');
365
383
  $('#modalclean').find('p').text(text);
366
384
  $('#cforce').prop('checked', false);
385
+ $('#cforce').removeClass('hide');
367
386
  $('#cforcediv').removeClass('hide');
368
387
  $('#modalclean a.btn[name=\'yes\']').unbind('click');
369
388
  $('#modalclean a.btn[name=\'yes\']').click(() => {
@@ -479,14 +498,18 @@ function cleanDeviceStates(force) {
479
498
  if (msg.error) {
480
499
  showMessage(msg.error, _('Error'));
481
500
  } else {
501
+ if (msg.stateList) {
502
+ //showMessage(msg.stateList.join('<p>'), 'State cleanup results');
503
+ }
482
504
  getDevices();
483
505
  }
484
506
  }
485
507
  });
486
- showWaitingDialog('Device is being removed', 10);
508
+ showWaitingDialog('Orphaned states are being removed', 10);
487
509
  }
488
510
 
489
511
  function renameDevice(id, name) {
512
+ showMessage('rename device with ' + id + ' and ' + name, _('Error'));
490
513
  sendTo(namespace, 'renameDevice', {id: id, name: name}, function (msg) {
491
514
  if (msg) {
492
515
  if (msg.error) {
@@ -616,11 +639,24 @@ function showDevices() {
616
639
  const name = getDevName(dev_block);
617
640
  editName(id, name);
618
641
  });
642
+ $('.card-reveal-buttons button[name=\'swapdebug\']').click(function () {
643
+ const dev_block = $(this).parents('div.device');
644
+ const id = getDevId(dev_block);
645
+ const name = getDevName(dev_block);
646
+ toggleDebugDevice(id, name);
647
+ });
648
+
649
+ $('.card-reveal-buttons button[name=\'swapimage\']').click(function () {
650
+ const dev_block = $(this).parents('div.device');
651
+ const id = getDevId(dev_block);
652
+ selectImageOverride(id);
653
+ });
654
+
619
655
  $('.card-reveal-buttons button[name=\'editgrp\']').click(function () {
620
656
  const dev_block = $(this).parents('div.device');
621
657
  const id = dev_block.attr('id').replace(namespace + '.group_', '');
622
658
  const name = getDevName(dev_block);
623
- editGroupName(id, name, false);
659
+ editGroup(id, name, false);
624
660
  });
625
661
  $('button.btn-floating[name=\'join\']').click(function () {
626
662
  const dev_block = $(this).parents('div.device');
@@ -751,9 +787,111 @@ function getCoordinatorInfo() {
751
787
  }
752
788
  });
753
789
  }
790
+ function checkDebugDevice(id) {
791
+ if (debugDevices.indexOf(id) > -1) return 0
792
+ for (const addressPart of debugDevices) {
793
+ if (typeof id === 'string' && id.includes(addressPart)) {
794
+ return debugDevices.indexOf(addressPart)+1;
795
+ }
796
+ }
797
+ return -1;
798
+ }
799
+ async function toggleDebugDevice(id) {
800
+ sendTo(namespace, 'setDeviceDebug', {id:id}, function (msg) {
801
+ sendTo(namespace, 'getDebugDevices', {}, function(msg) {
802
+ if (msg && typeof (msg.debugDevices == 'array')) {
803
+ debugDevices = msg.debugDevices;
804
+ }
805
+ else
806
+ debugDevices = [];
807
+ showDevices();
808
+ });
809
+ });
810
+ }
811
+
812
+ function updateDeviceData(device, data, global) {
813
+ sendTo(namespace, 'updateDeviceData', {target: device, data:data, global:global}, function(msg) {
814
+ if (msg && msg.hasOwnProperty.error) {
815
+ showMessage(msg.error, _('Error'));
816
+ }
817
+ getDevices();
818
+ });
819
+ }
820
+
821
+
822
+ async function selectImageOverride(id) {
823
+ const dev = devices.find((d) => d._id == id);
824
+ const imghtml = `<img src="${dev.common.icon || dev.icon}" width="80px">`
825
+ //console.error(imghtml)
826
+ const selectItems= [''];
827
+ $('#chooseimage').find('input[id=\'d_name\']').val(dev.common.name);
828
+ $('#chooseimage').find('.currentIcon').html(imghtml);
829
+
830
+ sendTo(namespace, 'getLocalImages', {}, function(msg) {
831
+ if (msg && msg.imageData) {
832
+ const imagedata = msg.imageData;
833
+ console.warn(JSON.stringify(dev.common));
834
+ const default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
835
+ if (dev.legacyIcon) imagedata.unshift( { file:dev.legacyIcon, name:'legacy', data:dev.legacyIcon});
836
+ imagedata.unshift( { file:'none', name:'default', data:default_icon});
837
+ imagedata.unshift( { file:'current', name:'current', data:dev.common.icon || dev.icon});
838
+
839
+ list2select('#images', imagedata, selectItems,
840
+ function (key, image) {
841
+ return image.name
842
+ },
843
+ function (key, image) {
844
+ return image.file;
845
+ },
846
+ function (key, image) {
847
+ if (image.file.length < 50) {
848
+ return `data-icon="${image.data}"`;
849
+ } else {
850
+ return `data-icon="data:image/png; base64, ${image.data}"`;
851
+ }
852
+ },
853
+ );
854
+
855
+ $('#chooseimage a.btn[name=\'save\']').unbind('click');
856
+ $('#chooseimage a.btn[name=\'save\']').click(() => {
857
+ const image = $('#chooseimage').find('#images option:selected').val();
858
+ const global = $('#chooseimage').find('#globaloverride').prop('checked');
859
+ const name = $('#chooseimage').find('input[id=\'d_name\']').val();
860
+ console.warn(`update device image : ${id} : ${image} : ${global} : ${name} : ${dev.common.name}`);
861
+ const data = {};
862
+ if (image != 'current') data.icon= image;
863
+ if (name != dev.common.name) data.name = name;
864
+ updateDeviceData(id, data, global);
865
+ });
866
+ $('#chooseimage').modal('open');
867
+ Materialize.updateTextFields();
868
+ }
869
+ });
870
+ }
754
871
 
755
872
  function getDevices() {
756
873
  getCoordinatorInfo();
874
+ sendTo(namespace, 'getDeviceCleanupRequired', {}, function(msg) {
875
+ if (msg) {
876
+ if (msg.clean)
877
+ $('#state_cleanup_btn').removeClass('hide');
878
+ else
879
+ $('#state_cleanup_btn').addClass('hide');
880
+ if (msg.errors && msg.errors.length > 0) {
881
+ $('#show_errors_btn').removeClass('hide');
882
+ errorData = msg.errors;
883
+ }
884
+ else
885
+ $('#show_errors_btn').addClass('hide');
886
+ }
887
+ })
888
+ sendTo(namespace, 'getDebugDevices', {}, function(msg) {
889
+ if (msg && typeof (msg.debugDevices == 'array')) {
890
+ debugDevices = msg.debugDevices;
891
+ }
892
+ else
893
+ debugDevices = [];
894
+ });
757
895
  sendTo(namespace, 'getDevices', {}, function (msg) {
758
896
  if (msg) {
759
897
  if (msg.error) {
@@ -768,6 +906,14 @@ function getDevices() {
768
906
  });
769
907
  }
770
908
 
909
+ function getNamedColors() {
910
+ sendTo(namespace, 'getNamedColors', {}, function(msg) {
911
+ if (msg && typeof msg.colors) {
912
+ namedColors = msg.colors;
913
+ }
914
+ });
915
+ }
916
+
771
917
  function getDeviceCards() {
772
918
  return $('#devices .device').not('.group');
773
919
  }
@@ -787,6 +933,9 @@ function getMap() {
787
933
  if (msg.error) {
788
934
  showMessage(msg.error, _('Error'));
789
935
  } else {
936
+ if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
937
+ showMessage(msg.errors.join('<p>'), 'Map generation messages');
938
+ }
790
939
  map = msg;
791
940
  showNetworkMap(devices, map);
792
941
  }
@@ -794,14 +943,24 @@ function getMap() {
794
943
  });
795
944
  }
796
945
 
946
+ function getRandomExtPanID()
947
+ {
948
+ const bytes = [];
949
+ for (let i = 0;i<16;i++) {
950
+ bytes.push(Math.floor(Math.random() * 16).toString(16));
951
+ }
952
+ return bytes.join('');
953
+ }
954
+
955
+
797
956
  // the function loadSettings has to exist ...
798
957
 
799
958
  function load(settings, onChange) {
800
959
  if (settings.panID === undefined) {
801
- settings.panID = 6754;
960
+ settings.panID = Math.floor(Math.random() * 10000);
802
961
  }
803
962
  if (settings.extPanID === undefined) {
804
- settings.extPanID = 'DDDDDDDDDDDDDDDD';
963
+ settings.extPanID = getRandomExtPanID();
805
964
  }
806
965
  // fix for previous wrong value
807
966
  if (settings.extPanID === 'DDDDDDDDDDDDDDD') {
@@ -844,11 +1003,13 @@ function load(settings, onChange) {
844
1003
  }
845
1004
  }
846
1005
 
1006
+
847
1007
  getComPorts(onChange);
848
1008
 
849
1009
  //dialog = new MatDialog({EndingTop: '50%'});
850
1010
  getDevices();
851
- getMap();
1011
+ getNamedColors();
1012
+ //getMap();
852
1013
  //addCard();
853
1014
 
854
1015
  // Signal to admin, that no changes yet
@@ -857,6 +1018,9 @@ function load(settings, onChange) {
857
1018
  $('#state_cleanup_btn').click(function () {
858
1019
  cleanConfirmation();
859
1020
  });
1021
+ $('#show_errors_btn').click(function () {
1022
+ showMessage(errorData.join('<br>'), 'Stashed error messages');
1023
+ });
860
1024
  $('#fw_check_btn').click(function () {
861
1025
  checkFwUpdate();
862
1026
  });
@@ -889,17 +1053,17 @@ function load(settings, onChange) {
889
1053
 
890
1054
  sendTo(namespace, 'getGroups', {}, function (data) {
891
1055
  groups = data.groups;
892
- showGroups();
1056
+ //showGroups();
893
1057
  });
894
1058
 
895
1059
  $('#add_group').click(function () {
896
1060
  const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
897
- editGroupName(maxind + 1, 'Group ' + maxind + 1, true);
1061
+ addGroup(maxind + 1, 'Group ' + maxind + 1);
898
1062
  });
899
1063
 
900
1064
  $('#add_grp_btn').click(function () {
901
1065
  const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
902
- editGroupName(maxind + 1, 'Group ' + maxind + 1, true);
1066
+ addGroup(maxind + 1, 'Group ' + maxind + 1);
903
1067
  });
904
1068
 
905
1069
  $('#code_pairing').click(function () {
@@ -1065,7 +1229,7 @@ socket.on('stateChange', function (id, state) {
1065
1229
  socket.on('objectChange', function (id, obj) {
1066
1230
  if (id.substring(0, namespaceLen) !== namespace) return;
1067
1231
  //console.log('objectChange', id, obj);
1068
- if (obj && obj.type == 'device' && obj.common.type !== 'group') {
1232
+ if (obj && obj.type == 'device') { // && obj.common.type !== 'group') {
1069
1233
  updateDevice(id);
1070
1234
  }
1071
1235
  if (!obj) {
@@ -1128,10 +1292,10 @@ function showNetworkMap(devices, map) {
1128
1292
  label: (dev.link_quality > 0 ? dev.common.name : `${dev.common.name}\n(disconnected)`),
1129
1293
  title: dev._id.replace(namespace + '.', '') + extInfo,
1130
1294
  shape: 'circularImage',
1131
- image: dev.icon,
1295
+ image: dev.common.icon || dev.icon,
1132
1296
  imagePadding: {top: 5, bottom: 5, left: 5, right: 5},
1133
- color: {background: 'white', highlight: {background: 'white'}},
1134
- font: {color: '#007700'},
1297
+ color: {background: '#cccccc', highlight: {background: 'white'}},
1298
+ font: {color: '#00bb00'},
1135
1299
  borderWidth: 1,
1136
1300
  borderWidthSelected: 4,
1137
1301
  };
@@ -1139,7 +1303,7 @@ function showNetworkMap(devices, map) {
1139
1303
  // node.shape = 'star';
1140
1304
  node.image = 'zigbee.png';
1141
1305
  node.label = 'Coordinator';
1142
- delete node.color;
1306
+ // delete node.color;
1143
1307
  }
1144
1308
  return node;
1145
1309
  };
@@ -1178,7 +1342,7 @@ function showNetworkMap(devices, map) {
1178
1342
  // // parent/child
1179
1343
  if (mapEntry.status !== 'online') {
1180
1344
  label = label + ' (off)';
1181
- linkColor = '#ff0000';
1345
+ linkColor = '#660000';
1182
1346
  }
1183
1347
  if (mapEntry.lqi < 10) {
1184
1348
  linkColor = '#ff0000';
@@ -1236,8 +1400,6 @@ function showNetworkMap(devices, map) {
1236
1400
  }
1237
1401
  });
1238
1402
  }
1239
-
1240
- // routing
1241
1403
  /*
1242
1404
  if (map.routing) {
1243
1405
  map.routing.forEach((route)=>{
@@ -1248,7 +1410,7 @@ function showNetworkMap(devices, map) {
1248
1410
  const to = routeDest._id;
1249
1411
  const from = routeSource._id;
1250
1412
  const label = route.status;
1251
- const linkColor = '#ff00ff';
1413
+ const linkColor = '#ff55ff';
1252
1414
  const edge = {
1253
1415
  from: from,
1254
1416
  to: to,
@@ -1298,7 +1460,7 @@ function showNetworkMap(devices, map) {
1298
1460
  if (node) {
1299
1461
  node.font = {color: '#ff0000'};
1300
1462
  if (dev.info && dev.info.device._type == 'Coordinator') {
1301
- node.font = {color: '#000000'};
1463
+ node.font = {color: '#00ff00'};
1302
1464
  }
1303
1465
  nodesArray.push(node);
1304
1466
  }
@@ -1324,6 +1486,7 @@ function showNetworkMap(devices, map) {
1324
1486
  const options = data.edges._data.get(id);
1325
1487
  if (select) {
1326
1488
  options.font.size = 15;
1489
+ console.warn(JSON.stringify(options.font));
1327
1490
  } else {
1328
1491
  options.font.size = 0;
1329
1492
  }
@@ -1335,15 +1498,16 @@ function showNetworkMap(devices, map) {
1335
1498
  doSelection(false, event.previousSelection.edges, this.body.data);
1336
1499
  }
1337
1500
  doSelection(true, event.edges, this.body.data);
1338
-
1339
- // if (event.nodes) {
1340
- // event.nodes.forEach((node)=>{
1341
- // //const options = network.clustering.findNode[node];
1342
- // network.clustering.updateClusteredNode(
1343
- // node, {size: 50}
1344
- // );
1345
- // });
1346
- // }
1501
+ /*
1502
+ if (event.nodes) {
1503
+ event.nodes.forEach((node)=>{
1504
+ //const options = network.clustering.findNode[node];
1505
+ network.clustering.updateClusteredNode(
1506
+ node, {size: 50}
1507
+ );
1508
+ });
1509
+ }
1510
+ */
1347
1511
  };
1348
1512
  network.on('selectNode', onMapSelect);
1349
1513
  network.on('deselectNode', onMapSelect);
@@ -1848,6 +2012,7 @@ function list2select(selector, list, selected, getText, getKey, getData) {
1848
2012
  element.select();
1849
2013
  }
1850
2014
 
2015
+ /*
1851
2016
  function showGroups() {
1852
2017
  $('#groups_table').find('.group').remove();
1853
2018
  if (!groups) return;
@@ -1870,48 +2035,59 @@ function showGroups() {
1870
2035
  deleteGroupConfirmation(index, name);
1871
2036
  });
1872
2037
  }
2038
+ */
1873
2039
 
1874
- function editGroupName(id, name, isnew) {
1875
- //const dev = devices.find((d) => d._id == id);
1876
- //console.log('devices: '+ JSON.stringify(devices));
1877
- const groupables = [];
1878
- for (const d of devices) {
1879
- if (d && d.info && d.info.endpoints) {
1880
- for (const ep of d.info.endpoints) {
1881
- if (ep.inputClusters.includes(4)) {
1882
- groupables.push(ep);
1883
- }
1884
- }
2040
+ function editGroup(id, name) {
2041
+ const grp = devGroups[id];
2042
+ let info = '';
2043
+ if (grp && grp.memberinfo) {
2044
+ for (let m=0; m< grp.memberinfo.length; m++) {
2045
+ const mi = grp.memberinfo[m];
2046
+ if (mi)
2047
+ info = info.concat(`<li class="collection-item"><label><input id="member_${m}" type="checkbox" checked="checked"/><span for="member_${m}">${mi.device} Endpoint ${mi.epid} (${mi.ieee})</span></label></li>`);
1885
2048
  }
1886
- //console.log('device ' + JSON.stringify(d));
1887
2049
  }
1888
-
2050
+ $('#groupedit').find('.collection').html(info);
1889
2051
  //var text = 'Enter new name for "'+name+'" ('+id+')?';
1890
- if (isnew) {
1891
- $('#groupedit').find('.editgroup').addClass('hide');
1892
- $('#groupedit').find('.addgroup').removeClass('hide');
1893
- $('#groupedit').find('.input-field.members').addClass('hide');
1894
- $('#groupedit').find('.input-field.groupid').removeClass('hide');
1895
- } else {
1896
- $('#groupedit').find('.editgroup').removeClass('hide');
1897
- $('#groupedit').find('.addgroup').addClass('hide');
1898
- $('#groupedit').find('.input-field.members').removeClass('hide');
1899
- $('#groupedit').find('.input-field.groupid').addClass('hide');
1900
- }
2052
+ $('#groupedit').find('.editgroup').removeClass('hide');
1901
2053
  $('#groupedit').find('input[id=\'g_index\']').val(id);
1902
2054
  $('#groupedit').find('input[id=\'g_name\']').val(name);
1903
2055
  $('#groupedit a.btn[name=\'save\']').unbind('click');
1904
2056
  $('#groupedit a.btn[name=\'save\']').click(() => {
1905
- const newId = $('#groupedit').find('input[id=\'g_index\']').val(),
1906
- newName = $('#groupedit').find('input[id=\'g_name\']').val();
1907
- updateGroup(id, newId, newName);
2057
+ const newName = $('#groupedit').find('input[id=\'g_name\']').val();
2058
+ const Id = $('#groupedit').find('input[id=\'g_index\']').val();
2059
+ const grp = devGroups[Id];
2060
+ const removeMembers = [];
2061
+ if (grp && grp.memberinfo) {
2062
+ for (let m=0; m<grp.memberinfo.length;m++) {
2063
+ const member = grp.memberinfo[m];
2064
+ if (!$(`#member_${m}`).prop('checked'))
2065
+ removeMembers.push({id:member.ieee.replace('0x',''), ep:member.epid})
2066
+ }
2067
+ }
2068
+ console.warn(`ID: ${Id} name: ${newName} removeMembers: ${JSON.stringify(removeMembers)}`)
2069
+ updateGroup(Id, newName, (removeMembers.length > 0 ? removeMembers: undefined));
1908
2070
  // showGroups();
1909
- // getDevices();
2071
+ getDevices();
1910
2072
  });
1911
2073
  $('#groupedit').modal('open');
1912
2074
  Materialize.updateTextFields();
1913
2075
  }
1914
2076
 
2077
+ function addGroup(id, name) {
2078
+ //var text = 'Enter new name for "'+name+'" ('+id+')?';
2079
+ $('#groupadd').find('input[id=\'g_index\']').val(id);
2080
+ $('#groupadd').find('input[id=\'g_name\']').val(name);
2081
+ $('#groupadd a.btn[name=\'save\']').unbind('click');
2082
+ $('#groupadd a.btn[name=\'save\']').click(() => {
2083
+ const newId = $('#groupadd').find('input[id=\'g_index\']').val(),
2084
+ newName = $('#groupadd').find('input[id=\'g_name\']').val();
2085
+ updateGroup(newId, newName);
2086
+ });
2087
+ $('#groupadd').modal('open');
2088
+ Materialize.updateTextFields();
2089
+ }
2090
+
1915
2091
  function deleteGroupConfirmation(id, name) {
1916
2092
  const text = translateWord('Do you really whant to delete group') + ' "' + name + '" (' + id + ')?';
1917
2093
  $('#modaldelete').find('p').text(text);
@@ -1925,10 +2101,9 @@ function deleteGroupConfirmation(id, name) {
1925
2101
  $('#modaldelete').modal('open');
1926
2102
  }
1927
2103
 
1928
- function updateGroup(id, newId, newName) {
1929
- delete groups[id];
2104
+ function updateGroup(newId, newName, remove) {
1930
2105
  groups[newId] = newName;
1931
- sendTo(namespace, 'renameGroup', {id: newId, name: newName}, function (msg) {
2106
+ sendTo(namespace, 'renameGroup', {id: newId, name: newName, remove: remove}, function (msg) {
1932
2107
  if (msg && msg.error) {
1933
2108
  showMessage(msg.error, _('Error'));
1934
2109
  }
@@ -1948,12 +2123,20 @@ function deleteGroup(id) {
1948
2123
 
1949
2124
  function updateDev(id, newName, newGroups) {
1950
2125
  const dev = devices.find((d) => d._id === id);
1951
- if (dev && dev.common.name !== newName) {
1952
- renameDevice(id, newName);
2126
+ const command = {id: id, name: id};
2127
+ let needName = false;
2128
+ if (dev) {
2129
+ if (dev.common.name !== newName) {
2130
+ command.name = newName;
2131
+ needName = true;
2132
+ }
2133
+ else command.name = dev.common.name;
1953
2134
  }
2135
+
1954
2136
  const keys = Object.keys(newGroups);
1955
2137
  if (keys && keys.length) {
1956
- sendTo(namespace, 'updateGroupMembership', {id: id, groups: newGroups}, function (msg) {
2138
+ command.groups = newGroups
2139
+ sendTo(namespace, 'updateGroupMembership', command, function (msg) {
1957
2140
  closeWaitingDialog();
1958
2141
  if (msg && msg.error) {
1959
2142
  showMessage(msg.error, _('Error'));
@@ -1965,24 +2148,20 @@ function updateDev(id, newName, newGroups) {
1965
2148
  showWaitingDialog('Updating group memberships', 10);
1966
2149
 
1967
2150
  }
1968
- /*
1969
- if (dev.info.device._type == 'Router') {
1970
- const oldGroups = devGroups[id] || [];
1971
- if (oldGroups.toString() != newGroups.toString()) {
1972
- devGroups[id] = newGroups;
1973
- sendTo(namespace, 'updateGroupMembership', { id: id, groups: newGroups }, function (msg) {
1974
- if (msg && msg.error) {
1975
- showMessage(msg.error, _('Error'));
1976
- }
1977
- else {
1978
- // save dev-groups on success
1979
- dev.groups = newGroups;
1980
- }
1981
- showDevices();
1982
- });
2151
+ else if (needName)
2152
+ {
2153
+ sendTo(namespace, 'renameDevice', command, function(msg) {
2154
+ //closeWaitingDialog();
2155
+ if (msg && msg.error) {
2156
+ showMessage(msg.error, _('Error'));
2157
+ } else {
2158
+ // save dev-groups on success
2159
+ getDevices();
1983
2160
  }
1984
- }
1985
- */
2161
+
2162
+ })
2163
+
2164
+ }
1986
2165
  }
1987
2166
 
1988
2167
  function resetConfirmation() {
@@ -2462,6 +2641,7 @@ function showDevInfo(id) {
2462
2641
  $('#modaldevinfo').modal('open');
2463
2642
  }
2464
2643
 
2644
+ /*
2465
2645
  function showGroupList(show) {
2466
2646
  const htmlsections = [];
2467
2647
  for (const groupid in devGroups) {
@@ -2492,7 +2672,7 @@ function showGroupList(show) {
2492
2672
  $('#grouplist').html(htmlsections.join(''));
2493
2673
  $('#add').click(function () {
2494
2674
  const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
2495
- editGroupName(maxind + 1, 'Group ' + maxind + 1, true);
2675
+ addGroup(maxind + 1, 'Group ' + maxind + 1, true);
2496
2676
  showGroupList(false);
2497
2677
  });
2498
2678
 
@@ -2501,6 +2681,7 @@ function showGroupList(show) {
2501
2681
  });
2502
2682
  if (show) $('#modalgrouplist').modal('open');
2503
2683
  }
2684
+ */
2504
2685
 
2505
2686
  let waitingTimeout, waitingInt;
2506
2687
 
@@ -2640,27 +2821,29 @@ function addExcludeDialog() {
2640
2821
  const ids = devices.map(el => el._id);
2641
2822
  const idx = ids.indexOf(exclude_id);
2642
2823
  const exclude_model = devices[idx];
2824
+ console.warn('calling addExclude mit model ' + exclude_model)
2643
2825
 
2644
2826
  addExclude(exclude_model);
2645
2827
  });
2646
2828
  prepareExcludeDialog();
2647
-
2829
+ console.warn('opening dialog');
2648
2830
  $('#excludemodaledit').modal('open');
2649
2831
  Materialize.updateTextFields();
2650
2832
  }
2651
2833
 
2652
2834
  function addExclude(exclude_model) {
2653
- sendTo(namespace, 'addExclude', {
2654
- exclude_model: exclude_model
2655
- }, function (msg) {
2656
- closeWaitingDialog();
2657
- if (msg) {
2658
- if (msg.error) {
2659
- showMessage(msg.error, _('Error'));
2835
+ if (typeof exclude_model == 'object' && exclude_model.hasOwnProperty('common'))
2836
+ sendTo(namespace, 'addExclude', { exclude_model: exclude_model }, function (msg) {
2837
+ closeWaitingDialog();
2838
+ if (msg) {
2839
+ if (msg.error) {
2840
+ showMessage(msg.error, _('Error'));
2841
+ }
2660
2842
  }
2661
- }
2662
- getExclude();
2663
- });
2843
+ console.log('getting excludes ?');
2844
+ getExclude();
2845
+ });
2846
+ else closeWaitingDialog();
2664
2847
  }
2665
2848
 
2666
2849
  function getExclude() {
@@ -2669,10 +2852,10 @@ function getExclude() {
2669
2852
  if (msg.error) {
2670
2853
  showMessage(msg.error, _('Error'));
2671
2854
  } else {
2672
- excludes = msg;
2855
+ excludes = msg.legacy;
2673
2856
  showExclude();
2674
2857
  }
2675
- }
2858
+ } else console.warn('getExclude without msg')
2676
2859
  });
2677
2860
  }
2678
2861
 
@@ -2684,14 +2867,11 @@ function showExclude() {
2684
2867
  return;
2685
2868
  }
2686
2869
 
2687
- excludes.forEach(b => {
2688
- const exclude_id = b.id;
2689
-
2870
+ excludes.forEach(id => {
2871
+ const exclude_id = id.key;
2872
+ const exclude_icon = id.value;
2690
2873
  const exclude_dev = devices.find((d) => d.common.type == exclude_id) || {common: {name: exclude_id}};
2691
- // exclude_icon = (exclude_dev.icon) ? `<img src="${exclude_dev.icon}" width="64px">` : '';
2692
-
2693
2874
  const modelUrl = (!exclude_id) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(exclude_id)}.html" target="_blank" rel="noopener noreferrer">${exclude_id}</a>`;
2694
-
2695
2875
  const card = `
2696
2876
  <div id="${exclude_id}" class="exclude col s12 m6 l4 xl3" style="height: 135px;padding-bottom: 10px;">
2697
2877
  <div class="card hoverable">
@@ -2720,7 +2900,7 @@ function showExclude() {
2720
2900
  $('#exclude button[name=\'delete\']').click(function () {
2721
2901
  const exclude_id = $(this).parents('.exclude')[0].id;
2722
2902
  deleteExcludeConfirmation(exclude_id);
2723
- deleteExclude(exclude_id);
2903
+ //deleteExclude(exclude_id);
2724
2904
  });
2725
2905
  }
2726
2906
 
@@ -2745,6 +2925,7 @@ function deleteExclude(id) {
2745
2925
  showMessage(msg.error, _('Error'));
2746
2926
  }
2747
2927
  }
2928
+ console.log('getting excludes ?');
2748
2929
  getExclude();
2749
2930
  });
2750
2931
  }
@@ -2827,11 +3008,11 @@ function sortByTitle(element) {
2827
3008
  return element.querySelector('.card-title').textContent.toLowerCase().trim();
2828
3009
  }
2829
3010
 
2830
- function getDashCard(dev, groupImage) {
3011
+ function getDashCard(dev, groupImage, groupstatus) {
2831
3012
  const title = dev.common.name,
2832
3013
  id = dev._id,
2833
3014
  type = dev.common.type,
2834
- img_src = (groupImage ? groupImage : dev.icon || dev.common.icon),
3015
+ img_src = (groupImage ? groupImage : dev.common.icon || dev.icon),
2835
3016
  isActive = !dev.common.deactivated,
2836
3017
  rooms = [],
2837
3018
  lang = systemLang || 'en';
@@ -2842,11 +3023,12 @@ function getDashCard(dev, groupImage) {
2842
3023
  nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
2843
3024
  battery_cls = getBatteryCls(dev.battery),
2844
3025
  lqi_cls = getLQICls(dev.link_quality),
3026
+ unconnected_icon = (groupImage ? (groupstatus ? '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>' : '<div class="col tool"><i class="material-icons icon-red">cancel</i></div>') :'<div class="col tool"><i class="material-icons icon-red">leak_remove</i></div>'),
2845
3027
  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>` : '',
2846
- 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>' : ''),
2847
- 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>`),
2848
- 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>' : '',
2849
- infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
3028
+ 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 ? unconnected_icon : ''),
3029
+ //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>`),
3030
+ //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>' : '',
3031
+ //infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
2850
3032
  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>` : '';
2851
3033
  const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => {
2852
3034
  const id = stateDef.id;
@@ -2861,6 +3043,12 @@ function getDashCard(dev, groupImage) {
2861
3043
  } else if (stateDef.type === 'boolean') {
2862
3044
  const disabled = (stateDef.write) ? '' : 'disabled="disabled"';
2863
3045
  val = `<label class="dash"><input type="checkbox" ${(val == true) ? 'checked=\'checked\'' : ''} ${disabled}/><span></span></label>`;
3046
+ } else if (stateDef.role === 'level.color.rgb') {
3047
+ const options = []
3048
+ for (const key of namedColors) {
3049
+ options.push(`<option value="${key}" ${val===key ? 'selected' : ''}>${key}</option>`);
3050
+ }
3051
+ val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
2864
3052
  } else if (stateDef.states && stateDef.write) {
2865
3053
  let options;
2866
3054
  if (typeof stateDef.states == 'string') {
@@ -2872,7 +3060,7 @@ function getDashCard(dev, groupImage) {
2872
3060
  } else {
2873
3061
  options = [];
2874
3062
  for (const [key, value] of Object.entries(stateDef.states)) {
2875
- options.push(`<option value="${key}" ${(val == key) ? 'selected' : ''}>${value}</option>`);
3063
+ options.push(`<option value="${key}" ${(val == key) ? 'selected' : ''}>${key}</option>`);
2876
3064
  }
2877
3065
  }
2878
3066
  val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
@@ -2888,7 +3076,6 @@ function getDashCard(dev, groupImage) {
2888
3076
  ${idleTime}
2889
3077
  ${battery}
2890
3078
  ${lq}
2891
- ${status}
2892
3079
  </span>
2893
3080
  <span class="card-title truncate">${title}</span>
2894
3081
  </div>