iobroker.zigbee 2.0.5 → 3.0.1

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.
Files changed (42) hide show
  1. package/README.md +22 -3
  2. package/admin/admin.js +420 -115
  3. package/admin/index_m.html +285 -229
  4. package/admin/tab_m.html +108 -91
  5. package/docs/de/img/Bild30.png +0 -0
  6. package/docs/de/img/Bild38.png +0 -0
  7. package/docs/de/img/Info.png +0 -0
  8. package/docs/de/img/Zigbee_config_de.jpg +0 -0
  9. package/docs/de/img/battery.png +0 -0
  10. package/docs/de/img/debug.png +0 -0
  11. package/docs/de/img/delete.png +0 -0
  12. package/docs/de/img/disconnected.png +0 -0
  13. package/docs/de/img/edit_grp.png +0 -0
  14. package/docs/de/img/edit_image.png +0 -0
  15. package/docs/de/img/grp_nok.png +0 -0
  16. package/docs/de/img/grp_ok.png +0 -0
  17. package/docs/de/img/on_off.png +0 -0
  18. package/docs/de/img/reconfigure.png +0 -0
  19. package/docs/de/readme.md +52 -43
  20. package/docs/en/img/Zigbee_config_en.png +0 -0
  21. package/docs/en/img/Zigbee_pairing_en.png +0 -0
  22. package/docs/en/readme.md +66 -66
  23. package/io-package.json +32 -31
  24. package/lib/DeviceDebug.js +2 -1
  25. package/lib/commands.js +203 -40
  26. package/lib/devices.js +2 -2
  27. package/lib/exposes.js +8 -30
  28. package/lib/groups.js +1 -1
  29. package/lib/localConfig.js +33 -10
  30. package/lib/networkmap.js +2 -1
  31. package/lib/seriallist.js +9 -2
  32. package/lib/statescontroller.js +185 -91
  33. package/lib/utils.js +41 -11
  34. package/lib/zbDeviceConfigure.js +10 -3
  35. package/lib/zigbeecontroller.js +121 -94
  36. package/main.js +135 -73
  37. package/package.json +8 -8
  38. package/docs/en/img/Bild23.png +0 -0
  39. package/docs/en/img/Bild25.png +0 -0
  40. package/docs/en/img/Bild26.png +0 -0
  41. package/docs/en/img/Bild4.png +0 -0
  42. package/docs/en/img/Bild9.png +0 -0
package/admin/admin.js CHANGED
@@ -32,7 +32,9 @@ let devices = [],
32
32
  shuffleInstance,
33
33
  errorData = [],
34
34
  debugMessages = {},
35
- debugInLog = true;
35
+ debugInLog = true,
36
+ nvRamBackup = {},
37
+ isHerdsmanRunning = false;
36
38
  const dbgMsgfilter = new Set();
37
39
  const dbgMsghide = new Set();
38
40
  const updateCardInterval = setInterval(updateCardTimer, 6000);
@@ -55,11 +57,12 @@ const networkOptions = {
55
57
 
56
58
  const savedSettings = [
57
59
  'port', 'panID', 'channel', 'disableLed', 'countDown', 'groups', 'extPanID', 'precfgkey', 'transmitPower',
58
- 'adapterType', 'debugHerdsman', 'disableBackup', 'disablePing', 'external', 'startWithInconsistent', 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL'
60
+ 'adapterType', 'debugHerdsman', 'disableBackup', 'disablePing', 'external', 'startWithInconsistent',
61
+ 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart'
59
62
  ];
60
63
 
61
64
  function getDeviceByID(ID) {
62
- return devices.find((devInfo) => {
65
+ if (devices) return devices.find((devInfo) => {
63
66
  try {
64
67
  return devInfo._id == ID;
65
68
  } catch (e) {
@@ -380,6 +383,31 @@ function deleteConfirmation(id, name) {
380
383
  Materialize.updateTextFields();
381
384
  }
382
385
 
386
+ function deleteNvBackupConfirmation() {
387
+ const text = translateWord('Do you really want to the NV Backup data ?');
388
+ $('#modaldelete').find('p').text(text);
389
+ $('#force').prop('checked', false);
390
+ $('#forcediv').addClass('hide');
391
+ $('#modaldelete a.btn[name=\'yes\']').unbind('click');
392
+ $('#modaldelete a.btn[name=\'yes\']').click(() => {
393
+ //const force = $('#force').prop('checked');
394
+ showWaitingDialog('Attempting to delete nvBackup.json', 60000);
395
+ sendTo(namespace, 'deleteNVBackup', {}, function (msg) {
396
+ closeWaitingDialog();
397
+ if (msg) {
398
+ if (msg.error) {
399
+ showMessage(msg.error, _('Error'));
400
+ } else {
401
+ getDevices();
402
+ }
403
+ }
404
+ });
405
+ });
406
+ $('#modaldelete').modal('open');
407
+ Materialize.updateTextFields();
408
+ }
409
+
410
+
383
411
  function cleanConfirmation() {
384
412
  const text = translateWord('Do you really want to remove orphaned states?');
385
413
  $('#modalclean').find('p').text(text);
@@ -401,7 +429,81 @@ function EndPointIDfromEndPoint(ep) {
401
429
  return 'unidentified';
402
430
  }
403
431
 
432
+
433
+
404
434
  function editName(id, name) {
435
+
436
+ const device_options = {};
437
+ const received_options = {};
438
+
439
+ function removeOption(k) {
440
+ if (k && device_options.hasOwnProperty(k)) delete device_options[k];
441
+ }
442
+
443
+ function addOption() {
444
+ let idx=1;
445
+ let key = '';
446
+ do {
447
+ key = `o${idx++}`;
448
+ }
449
+ while (device_options.hasOwnProperty(key));
450
+ device_options[key] = {key:`option_${idx++}`, value:''};
451
+ }
452
+
453
+ function updateOptions() {
454
+ const html_options=[];
455
+
456
+ console.warn(`device_options is ${JSON.stringify(device_options)}`)
457
+
458
+ for (const k in device_options) {
459
+ html_options.push(`<div class="row">`);
460
+ html_options.push(`<div class="input-field suffix col s5 m5 l5"><input id="option_key_${k}" type="text" class="value" /><label for="option_key_${k}">Option</label></div>`)
461
+ html_options.push(`<div class="input-field suffix col s5 m5 l5"><input id="option_value_${k}" type="text" class="value" /><label for="option_value_${k}">Value</label></div>`)
462
+ html_options.push(`<div class="col"><a id="option_rem_${k}" class='btn' ><i class="material-icons">remove_circle</i></a></div>`);
463
+ html_options.push(`</div>`)
464
+ }
465
+ console.warn(`html is ${$('#modaledit').find('.options_grid').html()}`)
466
+ $('#modaledit').find('.options_grid').html(html_options.join(''));
467
+ console.warn(`html is now ${$('#modaledit').find('.options_grid').html()}`)
468
+ if (html_options.length > 0) {
469
+ $('#modaledit').find('.options_available').removeClass('hide');
470
+ for (const k of Object.keys(device_options)) {
471
+ $(`#option_key_${k}`).val(device_options[k].key);
472
+ $(`#option_value_${k}`).val(device_options[k].value);
473
+ $(`#option_rem_${k}`).unbind('click');
474
+ $(`#option_rem_${k}`).click(() => { removeOption(k); updateOptions() });
475
+ }
476
+ }
477
+ else {
478
+ $('#modaledit').find('.options_available').addClass('hide');
479
+ }
480
+ }
481
+
482
+ function getOptionsFromUI(_do, _so) {
483
+ const _no = {};
484
+ let changed = false;
485
+ for (const k in _do) {
486
+ const key = $(`#option_key_${k}`).val();
487
+ _do[k].key = key;
488
+ const val = $(`#option_value_${k}`).val();
489
+ try {
490
+ _do[k].value = JSON.parse(val);
491
+ }
492
+ catch {
493
+ _do[k].value = val;
494
+ }
495
+ if (device_options[k].key.length > 0) {
496
+ _no[key] = device_options[k].value;
497
+ changed |= _no[key] != _so[key];
498
+ }
499
+ }
500
+ changed |= (Object.keys(_no).length != Object.keys(_so).length);
501
+ if (changed) return _no;
502
+ return undefined;
503
+ }
504
+
505
+
506
+
405
507
  console.warn('editName called with ' + id + ' and ' + name);
406
508
  const dev = devices.find((d) => d._id == id);
407
509
  $('#modaledit').find('input[id=\'d_name\']').val(name);
@@ -414,14 +516,16 @@ function editName(id, name) {
414
516
  }
415
517
  }
416
518
  const numEP = groupables.length;
417
- $('#modaledit').find('.row.epid0').addClass('hide');
418
- $('#modaledit').find('.row.epid1').addClass('hide');
419
- $('#modaledit').find('.row.epid2').addClass('hide');
420
- $('#modaledit').find('.row.epid3').addClass('hide');
421
- $('#modaledit').find('.row.epid4').addClass('hide');
422
- $('#modaledit').find('.row.epid5').addClass('hide');
423
- $('#modaledit').find('.row.epid6').addClass('hide');
519
+
424
520
  if (numEP > 0) {
521
+ $('#modaledit').find('.groups_available').removeClass('hide');
522
+ $('#modaledit').find('.row.epid0').addClass('hide');
523
+ $('#modaledit').find('.row.epid1').addClass('hide');
524
+ $('#modaledit').find('.row.epid2').addClass('hide');
525
+ $('#modaledit').find('.row.epid3').addClass('hide');
526
+ $('#modaledit').find('.row.epid4').addClass('hide');
527
+ $('#modaledit').find('.row.epid5').addClass('hide');
528
+ $('#modaledit').find('.row.epid6').addClass('hide');
425
529
  // go through all the groups. Find the ones to list for each groupable
426
530
  if (numEP == 1) {
427
531
  $('#modaledit').find('.endpointid').addClass('hide');
@@ -451,7 +555,37 @@ function editName(id, name) {
451
555
  list2select('#d_groups_ep' + i, groups, groupables[i].memberOf || []);
452
556
  }
453
557
  }
558
+ else
559
+ {
560
+ $('#modaledit').find('.groups_available').addClass('hide');
561
+ }
562
+ sendTo(namespace, 'getLocalConfigItems', { target:id, global:false, key:'options' }, function (msg) {
563
+ if (msg) {
564
+ if (msg.error) showMessage(msg.error, '_Error');
565
+ console.warn(`return is ${msg}`)
566
+ Object.keys(device_options).forEach(key => delete device_options[key]);
567
+ Object.keys(received_options).forEach(key => delete received_options[key]);
568
+ if (typeof msg.options === 'object') {
569
+
570
+ let cnt = 1;
571
+ for (const key in msg.options)
572
+ {
573
+ received_options[key]=msg.options[key];
574
+ device_options[`o${cnt}`] = { key:key, value:msg.options[key]}
575
+ cnt++;
576
+ }
577
+ }
578
+ updateOptions();
579
+
580
+ } else showMessage('callback without message');
581
+ });
454
582
  $('#modaledit a.btn[name=\'save\']').unbind('click');
583
+ $('#modaledit a.btn[name=\'add_options\']').unbind('click');
584
+ $('#modaledit a.btn[name=\'add_options\']').click(() => {
585
+ getOptionsFromUI(device_options, received_options);
586
+ addOption();
587
+ updateOptions()
588
+ });
455
589
  $('#modaledit a.btn[name=\'save\']').click(() => {
456
590
  const newName = $('#modaledit').find('input[id=\'d_name\']').val();
457
591
  const groupsbyid = {};
@@ -462,8 +596,21 @@ function editName(id, name) {
462
596
  groupsbyid[groupables[i].ep.ID] = GenerateGroupChange(groupables[i].memberOf, ng);
463
597
  }
464
598
  }
465
- console.log('grpid ' + JSON.stringify(groupsbyid));
599
+ // read device_options from UI
600
+ const co = getOptionsFromUI(device_options, received_options)
601
+ console.warn(`options have ${co ? 'changed' : 'not changed'} : ${JSON.stringify(co)} vs ${JSON.stringify(received_options)} , saving them`);
602
+ if (co) {
603
+ sendTo(namespace, 'updateLocalConfigItems', {
604
+ target: id,
605
+ global:false,
606
+ data: { options:co }
607
+ },
608
+ function (msg) {
609
+ if (msg && msg.error) showMessage(msg.error, '_Error');
610
+ });
611
+ }
466
612
  updateDev(id, newName, groupsbyid);
613
+
467
614
  });
468
615
  $('#modaledit').modal('open');
469
616
  Materialize.updateTextFields();
@@ -782,18 +929,27 @@ function getCoordinatorInfo() {
782
929
  sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
783
930
  if (msg) {
784
931
  if (msg.error) {
785
- showMessage(msg.error, _('Error'));
932
+ errorData.push(msg.error);
933
+ isHerdsmanRunning = false;
934
+ updateStartButton();
786
935
  } else {
787
936
  coordinatorinfo = msg;
937
+ isHerdsmanRunning = true;
938
+ updateStartButton()
788
939
  }
789
940
  }
790
941
  });
791
942
  }
943
+
792
944
  function checkDebugDevice(id) {
793
- if (debugDevices.indexOf(id) > -1) return 0
945
+ // returns: -1: debug not set
946
+ // 0: debug set explicitly
947
+ // > 0: debug set by pattern.
948
+ if (!debugDevices) return -1;
949
+ if (debugDevices.indexOf(id) > -1) return 0 // debug set
794
950
  for (const addressPart of debugDevices) {
795
951
  if (typeof id === 'string' && id.includes(addressPart)) {
796
- return debugDevices.indexOf(addressPart)+1;
952
+ return debugDevices.indexOf(addressPart)+1; // debug set by pattern (>0)
797
953
  }
798
954
  }
799
955
  return -1;
@@ -811,8 +967,8 @@ async function toggleDebugDevice(id) {
811
967
  });
812
968
  }
813
969
 
814
- function updateDeviceData(device, data, global) {
815
- sendTo(namespace, 'updateDeviceData', {target: device, data:data, global:global}, function(msg) {
970
+ function updateLocalConfigItems(device, data, global) {
971
+ sendTo(namespace, 'updateLocalConfigItems', {target: device, data:data, global:global}, function(msg) {
816
972
  if (msg && msg.hasOwnProperty.error) {
817
973
  showMessage(msg.error, _('Error'));
818
974
  }
@@ -861,7 +1017,7 @@ async function selectImageOverride(id) {
861
1017
  const data = {};
862
1018
  if (image != 'current') data.icon= image;
863
1019
  if (name != dev.common.name) data.name = name;
864
- updateDeviceData(id, data, global);
1020
+ updateLocalConfigItems(id, data, global);
865
1021
  });
866
1022
  $('#chooseimage').modal('open');
867
1023
  Materialize.updateTextFields();
@@ -985,6 +1141,7 @@ function displayDebugMessages(msg) {
985
1141
  Html.push('</tbody></table></li>')
986
1142
  for (const devID of Object.keys(dbgData)) {
987
1143
  const dev = devices.find((d) => d._id.endsWith(devID.slice(-16)));
1144
+ if (!dev) continue;
988
1145
  const type_url = (dev && dev.common && dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown');
989
1146
  const image = `<img src="${dev.common.icon || dev.icon}" width="40px" onerror="this.onerror=null;this.src='img/unavailable.png';">`
990
1147
  const modelUrl = (type_url === 'unknown') ? 'unknown' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${image}</a>`;
@@ -1067,8 +1224,10 @@ function getDebugMessages() {
1067
1224
 
1068
1225
  function getDevices() {
1069
1226
  getCoordinatorInfo();
1070
- sendTo(namespace, 'getDeviceCleanupRequired', {}, function(msg) {
1227
+ sendTo(namespace, 'getDevices', {}, function (msg) {
1071
1228
  if (msg) {
1229
+ devices = msg.devices ? msg.devices : [];
1230
+ // check if stashed error messages are sent alongside
1072
1231
  if (msg.clean)
1073
1232
  $('#state_cleanup_btn').removeClass('hide');
1074
1233
  else
@@ -1077,23 +1236,28 @@ function getDevices() {
1077
1236
  $('#show_errors_btn').removeClass('hide');
1078
1237
  errorData = msg.errors;
1079
1238
  }
1080
- else
1239
+ else {
1081
1240
  $('#show_errors_btn').addClass('hide');
1082
- }
1083
- })
1084
- sendTo(namespace, 'getDebugDevices', {}, function(msg) {
1085
- if (msg && typeof (msg.debugDevices == 'array')) {
1086
- debugDevices = msg.debugDevices;
1087
- }
1088
- else
1089
- debugDevices = [];
1090
- });
1091
- sendTo(namespace, 'getDevices', {}, function (msg) {
1092
- if (msg) {
1241
+ }
1242
+
1243
+ //check if debug messages are sent alongside
1244
+ if (msg && typeof (msg.debugDevices == 'array')) {
1245
+ debugDevices = msg.debugDevices;
1246
+ console.warn('debug devices is sent')
1247
+ }
1248
+ else
1249
+ debugDevices = [];
1250
+ if (debugMessages.byId) {
1251
+ debugMessages.byId = msg;
1252
+ if (msg) displayDebugMessages(debugMessages)
1253
+ }
1093
1254
  if (msg.error) {
1094
- showMessage(msg.error, _('Error'));
1255
+ errorData.push(msg.error);
1256
+ isHerdsmanRunning = false;
1257
+ updateStartButton();
1095
1258
  } else {
1096
- devices = msg;
1259
+ isHerdsmanRunning = true;
1260
+ updateStartButton();
1097
1261
  showDevices();
1098
1262
  getDebugMessages();
1099
1263
  getExclude();
@@ -1128,8 +1292,12 @@ function getMap() {
1128
1292
  $('#refresh').removeClass('disabled');
1129
1293
  if (msg) {
1130
1294
  if (msg.error) {
1131
- showMessage(msg.error, _('Error'));
1295
+ errorData.push(msg.error);
1296
+ isHerdsmanRunning = false;
1297
+ updateStartButton();
1132
1298
  } else {
1299
+ isHerdsmanRunning = true;
1300
+ updateStartButton();
1133
1301
  if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
1134
1302
  showMessage(msg.errors.join('<p>'), 'Map generation messages');
1135
1303
  }
@@ -1149,14 +1317,24 @@ function getRandomExtPanID()
1149
1317
  return bytes.join('');
1150
1318
  }
1151
1319
 
1320
+ function getRandomChannel()
1321
+ {
1322
+ const channels = [11,15,20,25]
1323
+ return channels[Math.floor(Math.random() * 4)];
1324
+ }
1325
+
1326
+
1152
1327
 
1153
1328
  // the function loadSettings has to exist ...
1154
1329
 
1155
1330
  function load(settings, onChange) {
1156
- if (settings.panID === undefined) {
1331
+ if (settings.extPanID === undefined || settings.extPanID == '') {
1332
+ settings.channel = getRandomChannel();
1333
+ }
1334
+ if (settings.panID === undefined || settings.panID == 0) {
1157
1335
  settings.panID = Math.floor(Math.random() * 10000);
1158
1336
  }
1159
- if (settings.extPanID === undefined) {
1337
+ if (settings.extPanID === undefined || settings.extPanID == '') {
1160
1338
  settings.extPanID = getRandomExtPanID();
1161
1339
  }
1162
1340
  // fix for previous wrong value
@@ -1167,9 +1345,6 @@ function load(settings, onChange) {
1167
1345
  if (settings.precfgkey === undefined) {
1168
1346
  settings.precfgkey = '01030507090B0D0F00020406080A0C0D';
1169
1347
  }
1170
- if (settings.channel === undefined) {
1171
- settings.channel = 11;
1172
- }
1173
1348
  if (settings.disablePing === undefined) {
1174
1349
  settings.disablePing = false;
1175
1350
  }
@@ -1179,6 +1354,7 @@ function load(settings, onChange) {
1179
1354
  if (settings.baudRate === undefined) {
1180
1355
  settings.baudRate = 115200;
1181
1356
  }
1357
+ if (settings.autostart === undefined) settings.autostart = false;
1182
1358
 
1183
1359
  // example: select elements with id=key and class=value and insert value
1184
1360
  for (const key in settings) {
@@ -1190,10 +1366,12 @@ function load(settings, onChange) {
1190
1366
  if (value.attr('type') === 'checkbox') {
1191
1367
  value.prop('checked', settings[key]).change(function () {
1192
1368
  onChange();
1369
+ validateNVRamBackup(false, key)
1193
1370
  });
1194
1371
  } else {
1195
1372
  value.val(settings[key]).change(function () {
1196
1373
  onChange();
1374
+ validateNVRamBackup(false, key)
1197
1375
  }).keyup(function () {
1198
1376
  $(this).trigger('change');
1199
1377
  });
@@ -1206,6 +1384,7 @@ function load(settings, onChange) {
1206
1384
  //dialog = new MatDialog({EndingTop: '50%'});
1207
1385
  getDevices();
1208
1386
  getNamedColors();
1387
+ readNVRamBackup(false);
1209
1388
  //getDebugMessages();
1210
1389
  //getMap();
1211
1390
  //addCard();
@@ -1213,6 +1392,36 @@ function load(settings, onChange) {
1213
1392
  // Signal to admin, that no changes yet
1214
1393
  onChange(false);
1215
1394
 
1395
+ $('#test-btn').click(function () {
1396
+ console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1397
+ if (!isHerdsmanRunning) {
1398
+ const port = $('#port.value').val();
1399
+ console.warn(`port is ${port}`)
1400
+ showWaitingDialog(`Trying to connect to ${port}`, 300);
1401
+ sendTo(namespace, 'testConnection', { address:port }, function(msg) {
1402
+ console.warn(`send to returned with ${JSON.stringify(msg)}`);
1403
+ closeWaitingDialog();
1404
+ if (msg) {
1405
+ if (msg.error) {
1406
+ showMessage(msg.error, _('Error'));
1407
+ }
1408
+ }
1409
+ })
1410
+ }
1411
+ else {
1412
+ showMessage('function unavailable while herdsman is running', _('Error'))
1413
+ }
1414
+ });
1415
+
1416
+ $('#readNVRam-btn').click(function() {
1417
+ readNVRamBackup(true);
1418
+ })
1419
+ // test start commands
1420
+ $('#show_test_run').click(function () {
1421
+ console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1422
+ doTestStart(!isHerdsmanRunning);
1423
+ });
1424
+
1216
1425
  $('#state_cleanup_btn').click(function () {
1217
1426
  cleanConfirmation();
1218
1427
  });
@@ -1241,6 +1450,10 @@ function load(settings, onChange) {
1241
1450
  resetConfirmation();
1242
1451
  });
1243
1452
 
1453
+ $('#deleteNVRam-btn').click(function () {
1454
+ deleteNvBackupConfirmation();
1455
+ });
1456
+
1244
1457
  $('#viewconfig').click(function () {
1245
1458
  showViewConfig();
1246
1459
  });
@@ -1248,6 +1461,9 @@ function load(settings, onChange) {
1248
1461
  $('#scan').click(function () {
1249
1462
  showChannels();
1250
1463
  });
1464
+ $('#scan_t').click(function () {
1465
+ showChannels();
1466
+ });
1251
1467
 
1252
1468
  sendTo(namespace, 'getGroups', {}, function (data) {
1253
1469
  groups = data.groups;
@@ -1274,6 +1490,10 @@ function load(settings, onChange) {
1274
1490
  }
1275
1491
  });
1276
1492
 
1493
+ $('#hardware').click(function() {
1494
+ validateNVRamBackup(false);
1495
+ });
1496
+
1277
1497
  $(document).ready(function () {
1278
1498
  $('.modal').modal({
1279
1499
  startingTop: '30%',
@@ -1331,6 +1551,7 @@ function showMessages() {
1331
1551
  data = mess + '\n' + data;
1332
1552
  }
1333
1553
  $('#stdout').text(data);
1554
+ $('#stdout_t').text(messages.join('\n'));
1334
1555
  }
1335
1556
 
1336
1557
  function showPairingProcess() {
@@ -1344,6 +1565,41 @@ function showPairingProcess() {
1344
1565
  Materialize.updateTextFields();
1345
1566
  }
1346
1567
 
1568
+ function doTestStart(start) {
1569
+ updateStartButton(true);
1570
+ if (start) {
1571
+ const ovr = { extPanID:$('#extPanID.value').val(),
1572
+ panID: $('#PanID.value').val(),
1573
+ channel: $('#channel.value').val(),
1574
+ port: $('#port.value').val(),
1575
+ adapterType: $('#adapterType.value').val(),
1576
+ baudRate: $('#baudRate.value').val(),
1577
+ precfgkey: $('#precfgkey.value').val(),
1578
+ flowCTRL: $('#flowCTRL.value').prop('checked')
1579
+ };
1580
+ // $('#testStartStart').addClass('disabled');
1581
+ messages = [];
1582
+ sendTo(namespace, 'testConnect', { start:true, zigbeeOptions:ovr }, function(msg) {
1583
+ if (msg) {
1584
+ if (msg.status)
1585
+ $('#testStartStop').removeClass('disabled');
1586
+ else
1587
+ $('#testStartStart').removeClass('disabled');
1588
+ }
1589
+ })
1590
+ }
1591
+ else {
1592
+ //$('#testStartStop').addClass('disabled');
1593
+ sendTo(namespace, 'testConnect', { start:false }, function(msg) {
1594
+ if (msg) {
1595
+ if (msg.status) $('#testStartStart').removeClass('disabled');
1596
+ else $('#testStartStop').removeClass('disabled');
1597
+ }
1598
+ })
1599
+
1600
+ }
1601
+ }
1602
+
1347
1603
  // ... and the function save has to exist.
1348
1604
  // you have to make sure the callback is called with the settings object as first param!
1349
1605
 
@@ -1370,6 +1626,36 @@ function getDevId(adapterDevId) {
1370
1626
  return adapterDevId.split('.').slice(0, 3).join('.');
1371
1627
  }
1372
1628
 
1629
+
1630
+ function updateStartButton(block) {
1631
+ if (block) {
1632
+ $('#show_test_run').addClass('disabled');
1633
+ $('#reset-btn').addClass('disabled');
1634
+ $('#deleteNVRam-btn').addClass('disabled');
1635
+ $('#ErrorNotificationBtn').removeClass('hide')
1636
+ $('#ErrorNotificationBtn').removeClass('blinking')
1637
+ $('#ErrorNotificationIcon').removeClass('icon-red')
1638
+ $('#ErrorNotificationIcon').addClass('icon-orange')
1639
+ return;
1640
+ }
1641
+ if (isHerdsmanRunning)
1642
+ {
1643
+ $('#ErrorNotificationBtn').addClass('hide')
1644
+ $('#ErrorNotificationBtn').removeClass('blinking');
1645
+ $('#show_test_run').removeClass('disabled');
1646
+ $('#deleteNVRam-btn').removeClass('disabled');
1647
+ $('#reset-btn').removeClass('disabled');
1648
+ }
1649
+ else {
1650
+ $('#ErrorNotificationIcon').addClass('icon-red')
1651
+ $('#ErrorNotificationIcon').removeClass('icon-orange')
1652
+ $('#ErrorNotificationBtn').removeClass('hide')
1653
+ $('#ErrorNotificationBtn').addClass('blinking');
1654
+ $('#show_test_run').removeClass('disabled');
1655
+ $('#deleteNVRam-btn').removeClass('disabled');
1656
+ $('#reset-btn').addClass('disabled');
1657
+ }
1658
+ }
1373
1659
  // subscribe to changes
1374
1660
  socket.emit('subscribe', namespace + '.*');
1375
1661
  socket.emit('subscribeObjects', namespace + '.*');
@@ -1404,6 +1690,14 @@ socket.on('stateChange', function (id, state) {
1404
1690
  else {
1405
1691
  messages.push(state.val);
1406
1692
  showMessages();
1693
+ if (state.val.startsWith('Zigbee-Herdsman started successfully')) {
1694
+ isHerdsmanRunning = true;
1695
+ updateStartButton();
1696
+ }
1697
+ if (state.val.startsWith('herdsman stopped') || state.val.startsWith('Error herdsman')) {
1698
+ isHerdsmanRunning = false;
1699
+ updateStartButton();
1700
+ }
1407
1701
  }
1408
1702
  } else {
1409
1703
  const devId = getDevId(id);
@@ -2215,31 +2509,6 @@ function list2select(selector, list, selected, getText, getKey, getData) {
2215
2509
  element.select();
2216
2510
  }
2217
2511
 
2218
- /*
2219
- function showGroups() {
2220
- $('#groups_table').find('.group').remove();
2221
- if (!groups) return;
2222
- const element = $('#groups_table');
2223
- for (const j in groups) {
2224
- if (groups.hasOwnProperty(j)) {
2225
- element.append(`<tr id="group_${j}" class="group"><td>${j}</td><td><div>${groups[j]}<span class="right">` +
2226
- `<a id="${j}" name="groupedit" class="waves-effect green btn-floating"><i class="material-icons">edit</i></a>` +
2227
- `<a id="${j}" name="groupdelete" class="waves-effect red btn-floating"><i class="material-icons">delete</i></a></span></div></td></tr>`);
2228
- }
2229
- }
2230
- $('a.btn-floating[name=\'groupedit\']').click(function () {
2231
- const index = $(this).attr('id'),
2232
- name = groups[index];
2233
- editGroupName(index, name, false);
2234
- });
2235
- $('a.btn-floating[name=\'groupdelete\']').click(function () {
2236
- const index = $(this).attr('id'),
2237
- name = groups[index];
2238
- deleteGroupConfirmation(index, name);
2239
- });
2240
- }
2241
- */
2242
-
2243
2512
  function editGroup(id, name) {
2244
2513
  const grp = devGroups[id];
2245
2514
  let info = '';
@@ -2843,48 +3112,6 @@ function showDevInfo(id) {
2843
3112
  $('#modaldevinfo').modal('open');
2844
3113
  }
2845
3114
 
2846
- /*
2847
- function showGroupList(show) {
2848
- const htmlsections = [];
2849
- for (const groupid in devGroups) {
2850
- const dev = devGroups[groupid];
2851
- const grpname = (dev.common && dev.common.name ? dev.common.name : 'Group ' + groupid);
2852
- const selectables = [];
2853
- const members = [];
2854
- if (dev && dev.memberinfo) {
2855
- selectables.push(`<select id="members_${groupid}" multiple>`);
2856
- for (let m = 0; m < dev.memberinfo.length; m++) {
2857
- members.push(`${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (${dev.memberinfo[m].ieee})`);
2858
- selectables.push(`<option value="${m}">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (...${dev.memberinfo[m].ieee.slice(-4)})</option>`);
2859
- }
2860
- selectables.push('</select>');
2861
- }
2862
- htmlsections.push(`
2863
- <div class="row">
2864
- <div class="col s4 m4 l4">
2865
- <h5>${grpname}<h5>
2866
- </div>
2867
- <div class=col s7 m7 l7">
2868
- ${members.join('<br>')}
2869
- </div>
2870
- </div>
2871
- `);
2872
- }
2873
-
2874
- $('#grouplist').html(htmlsections.join(''));
2875
- $('#add').click(function () {
2876
- const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
2877
- addGroup(maxind + 1, 'Group ' + maxind + 1, true);
2878
- showGroupList(false);
2879
- });
2880
-
2881
- $('#modalgrouplist a.btn[name=\'save\']').unbind('click');
2882
- $('#modalgrouplist a.btn[name=\'save\']').click(() => {
2883
- });
2884
- if (show) $('#modalgrouplist').modal('open');
2885
- }
2886
- */
2887
-
2888
3115
  let waitingTimeout, waitingInt;
2889
3116
 
2890
3117
  function showWaitingDialog(text, timeout) {
@@ -3251,6 +3478,7 @@ function getDashCard(dev, groupImage, groupstatus) {
3251
3478
  let options;
3252
3479
  if (typeof stateDef.states == 'string') {
3253
3480
  const sts = stateDef.states.split(';');
3481
+ if (sts.length < 2) return '';
3254
3482
  options = sts.map((item) => {
3255
3483
  const v = item.split(':');
3256
3484
  return `<option value="${v[0]}" ${(val == v[0]) ? 'selected' : ''}>${v[1]}</option>`;
@@ -3261,9 +3489,14 @@ function getDashCard(dev, groupImage, groupstatus) {
3261
3489
  options.push(`<option value="${key}" ${(val == key) ? 'selected' : ''}>${key}</option>`);
3262
3490
  }
3263
3491
  }
3492
+ if (options.length < 2) return '';
3264
3493
  val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
3265
- } else {
3266
- val = `<span class="dash value">${val} ${(stateDef.unit) ? stateDef.unit : ''}</span>`;
3494
+ } else if (stateDef.write) {
3495
+ return;
3496
+ // val = `<span class="input-field dash value"><input class="dash value" id="${stateDef.name}" value="${val}"></input></span>`;
3497
+ }
3498
+ else {
3499
+ val = `<span class="dash value">${val ? val : '(null)'} ${(stateDef.unit) ? stateDef.unit : ''}</span>`;
3267
3500
  }
3268
3501
  return `<li><span class="label dash truncate">${stateDef.name}</span><span id=${sid} oid=${id} class="state">${val}</span></li>`;
3269
3502
  }).join('') : '';
@@ -3354,14 +3587,17 @@ function updateCardTimer() {
3354
3587
  }
3355
3588
 
3356
3589
  function updateDevice(id) {
3357
- sendTo(namespace, 'getDevice', {id: id}, function (devs) {
3358
- if (devs) {
3359
- if (devs.error) {
3360
- showMessage(devs.error, _('Error'));
3361
- } else {
3362
- removeDevice(id);
3363
- devs.forEach(dev => devices.push(dev));
3364
- showDevices();
3590
+ sendTo(namespace, 'getDevice', {id: id}, function (msg) {
3591
+ if (msg) {
3592
+ const devs = msg.devices;
3593
+ if (devs) {
3594
+ if (devs.error) {
3595
+ showMessage(devs.error, _('Error'));
3596
+ } else {
3597
+ removeDevice(id);
3598
+ devs.forEach(dev => devices.push(dev));
3599
+ showDevices();
3600
+ }
3365
3601
  }
3366
3602
  }
3367
3603
  });
@@ -3409,3 +3645,72 @@ function reconfigureDevice(id) {
3409
3645
  });
3410
3646
  showWaitingDialog('Device is being reconfigure', 30);
3411
3647
  }
3648
+
3649
+ const warnLevel = {
3650
+ extPanID : function(v) { return !(v && v.toLowerCase().trim()!='dddddddddddddddd')},
3651
+ channel: function(v) { const num = parseInt(v); return !(num==11 || num==15 || num==20 || num==25)},
3652
+ }
3653
+ const validatableKeys = ['channel', 'precfgkey', 'extPanID', 'panID'];
3654
+
3655
+ function validateConfigData(key, val) {
3656
+ if (validatableKeys.indexOf(key) < 0 || !val) return;
3657
+ if (warnLevel[key]) {
3658
+ if (warnLevel[key](val)) {
3659
+ console.warn(`warning set for ${key} (${val})`)
3660
+ $(`#${key}_ALERT`).removeClass('hide')
3661
+ } else $(`#${key}_ALERT`).addClass('hide')
3662
+ }
3663
+ if (nvRamBackup[key]) {
3664
+ console.warn(`value of ${key} is ${val} (${nvRamBackup[key]})`);
3665
+ if ((typeof val == 'string' && typeof nvRamBackup[key] == 'string' && val.toLowerCase == nvRamBackup[key].toLowerCase) || val == nvRamBackup[key])
3666
+ {
3667
+ console.warn(`ok set for ${key} (${val})`)
3668
+ $(`#${key}_OK`).removeClass('hide')
3669
+ $(`#${key}_NOK`).addClass('hide')
3670
+ }
3671
+ else
3672
+ {
3673
+ console.warn(`nok set for ${key} (${val})`)
3674
+ $(`#${key}_OK`).addClass('hide')
3675
+ $(`#${key}_NOK`).removeClass('hide')
3676
+ }
3677
+ }
3678
+ else {
3679
+ console.warn(`noval set for ${key} (${val})`)
3680
+ $(`#${key}_OK`).addClass('hide')
3681
+ $(`#${key}_NOK`).addClass('hide')
3682
+ }
3683
+ }
3684
+
3685
+ function validateNVRamBackup(update, src) {
3686
+ console.warn('validateNVRam');
3687
+ const validatedKeys = src ? [src] : validatableKeys;
3688
+ const validator = {};
3689
+ for (const key of validatedKeys) {
3690
+ const value = $('#' + key + '.value');
3691
+ if (nvRamBackup[key] && update) {
3692
+ if (value.attr('type') === 'checkbox') {
3693
+ value.prop('checked', nvRamBackup[key]);
3694
+ } else {
3695
+ value.val(nvRamBackup[key])
3696
+ }
3697
+ }
3698
+ validateConfigData(key, value.val());
3699
+ }
3700
+ }
3701
+
3702
+
3703
+ function readNVRamBackup(update) {
3704
+ console.warn('read nvRam')
3705
+ sendTo(namespace, 'readNVRam', {}, function(msg) {
3706
+ if (msg) {
3707
+ if (msg.error && update) {
3708
+ showMessages(msg.error, _('Error'));
3709
+ delete msg.error;
3710
+ }
3711
+ nvRamBackup = msg;
3712
+ validateNVRamBackup(update)
3713
+ }
3714
+ });
3715
+
3716
+ }