iobroker.zigbee 2.0.4 → 3.0.0

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 (48) hide show
  1. package/README.md +90 -57
  2. package/admin/admin.js +497 -120
  3. package/admin/img/philips_hue_lom001.png +0 -0
  4. package/admin/index_m.html +168 -124
  5. package/admin/tab_m.html +20 -11
  6. package/docs/de/img/Bild30.png +0 -0
  7. package/docs/de/img/Bild38.png +0 -0
  8. package/docs/de/img/Info.png +0 -0
  9. package/docs/de/img/Zigbee_config_de.jpg +0 -0
  10. package/docs/de/img/battery.png +0 -0
  11. package/docs/de/img/debug.png +0 -0
  12. package/docs/de/img/delete.png +0 -0
  13. package/docs/de/img/disconnected.png +0 -0
  14. package/docs/de/img/edit_grp.png +0 -0
  15. package/docs/de/img/edit_image.png +0 -0
  16. package/docs/de/img/grp_nok.png +0 -0
  17. package/docs/de/img/grp_ok.png +0 -0
  18. package/docs/de/img/on_off.png +0 -0
  19. package/docs/de/img/reconfigure.png +0 -0
  20. package/docs/de/readme.md +52 -43
  21. package/docs/en/img/Zigbee_config_en.png +0 -0
  22. package/docs/en/img/Zigbee_pairing_en.png +0 -0
  23. package/docs/en/readme.md +71 -66
  24. package/docs/tutorial/groups-1.png +0 -0
  25. package/docs/tutorial/groups-2.png +0 -0
  26. package/docs/tutorial/tab-dev-1.png +0 -0
  27. package/io-package.json +31 -65
  28. package/lib/DeviceDebug.js +5 -2
  29. package/lib/commands.js +182 -31
  30. package/lib/developer.js +0 -0
  31. package/lib/devices.js +2 -2
  32. package/lib/exposes.js +10 -27
  33. package/lib/groups.js +6 -8
  34. package/lib/localConfig.js +4 -5
  35. package/lib/ota.js +6 -6
  36. package/lib/seriallist.js +9 -2
  37. package/lib/statescontroller.js +397 -128
  38. package/lib/utils.js +41 -11
  39. package/lib/zbDeviceAvailability.js +2 -2
  40. package/lib/zbDeviceConfigure.js +99 -58
  41. package/lib/zigbeecontroller.js +152 -128
  42. package/main.js +251 -264
  43. package/package.json +10 -10
  44. package/docs/en/img/Bild23.png +0 -0
  45. package/docs/en/img/Bild25.png +0 -0
  46. package/docs/en/img/Bild26.png +0 -0
  47. package/docs/en/img/Bild4.png +0 -0
  48. package/docs/en/img/Bild9.png +0 -0
package/admin/admin.js CHANGED
@@ -1,5 +1,4 @@
1
1
  /*global $, M, _, sendTo, systemLang, translateWord, translateAll, showMessage, socket, document, instance, vis, Option*/
2
-
3
2
  /*
4
3
  * you must run 'iobroker upload zigbee' if you edited this file to make changes visible
5
4
  */
@@ -31,7 +30,13 @@ let devices = [],
31
30
  },
32
31
  cidList,
33
32
  shuffleInstance,
34
- errorData = [];
33
+ errorData = [],
34
+ debugMessages = {},
35
+ debugInLog = true,
36
+ nvRamBackup = {},
37
+ isHerdsmanRunning = false;
38
+ const dbgMsgfilter = new Set();
39
+ const dbgMsghide = new Set();
35
40
  const updateCardInterval = setInterval(updateCardTimer, 6000);
36
41
 
37
42
  const networkOptions = {
@@ -52,11 +57,12 @@ const networkOptions = {
52
57
 
53
58
  const savedSettings = [
54
59
  'port', 'panID', 'channel', 'disableLed', 'countDown', 'groups', 'extPanID', 'precfgkey', 'transmitPower',
55
- 'adapterType', 'debugHerdsman', 'disableBackup', 'disablePing', 'external', 'startWithInconsistent', 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL'
60
+ 'adapterType', 'debugHerdsman', 'disableBackup', 'disablePing', 'external', 'startWithInconsistent',
61
+ 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart'
56
62
  ];
57
63
 
58
64
  function getDeviceByID(ID) {
59
- return devices.find((devInfo) => {
65
+ if (devices) return devices.find((devInfo) => {
60
66
  try {
61
67
  return devInfo._id == ID;
62
68
  } catch (e) {
@@ -240,7 +246,6 @@ function getCard(dev) {
240
246
  rooms.push(dev.rooms[r]);
241
247
  }
242
248
  }
243
- console.warn('debug for ' + ieee + ' is ' + isDebug);
244
249
  const room = rooms.join(',') || '&nbsp';
245
250
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
246
251
  const rid = id.split('.').join('_');
@@ -378,6 +383,31 @@ function deleteConfirmation(id, name) {
378
383
  Materialize.updateTextFields();
379
384
  }
380
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
+
381
411
  function cleanConfirmation() {
382
412
  const text = translateWord('Do you really want to remove orphaned states?');
383
413
  $('#modalclean').find('p').text(text);
@@ -400,7 +430,7 @@ function EndPointIDfromEndPoint(ep) {
400
430
  }
401
431
 
402
432
  function editName(id, name) {
403
- console.log('editName called with ' + name);
433
+ console.warn('editName called with ' + id + ' and ' + name);
404
434
  const dev = devices.find((d) => d._id == id);
405
435
  $('#modaledit').find('input[id=\'d_name\']').val(name);
406
436
  const groupables = [];
@@ -780,18 +810,27 @@ function getCoordinatorInfo() {
780
810
  sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
781
811
  if (msg) {
782
812
  if (msg.error) {
783
- showMessage(msg.error, _('Error'));
813
+ errorData.push(msg.error);
814
+ isHerdsmanRunning = false;
815
+ updateStartButton();
784
816
  } else {
785
817
  coordinatorinfo = msg;
818
+ isHerdsmanRunning = true;
819
+ updateStartButton()
786
820
  }
787
821
  }
788
822
  });
789
823
  }
824
+
790
825
  function checkDebugDevice(id) {
791
- if (debugDevices.indexOf(id) > -1) return 0
826
+ // returns: -1: debug not set
827
+ // 0: debug set explicitly
828
+ // > 0: debug set by pattern.
829
+ if (!debugDevices) return -1;
830
+ if (debugDevices.indexOf(id) > -1) return 0 // debug set
792
831
  for (const addressPart of debugDevices) {
793
832
  if (typeof id === 'string' && id.includes(addressPart)) {
794
- return debugDevices.indexOf(addressPart)+1;
833
+ return debugDevices.indexOf(addressPart)+1; // debug set by pattern (>0)
795
834
  }
796
835
  }
797
836
  return -1;
@@ -830,7 +869,6 @@ async function selectImageOverride(id) {
830
869
  sendTo(namespace, 'getLocalImages', {}, function(msg) {
831
870
  if (msg && msg.imageData) {
832
871
  const imagedata = msg.imageData;
833
- console.warn(JSON.stringify(dev.common));
834
872
  const default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
835
873
  if (dev.legacyIcon) imagedata.unshift( { file:dev.legacyIcon, name:'legacy', data:dev.legacyIcon});
836
874
  imagedata.unshift( { file:'none', name:'default', data:default_icon});
@@ -844,10 +882,10 @@ async function selectImageOverride(id) {
844
882
  return image.file;
845
883
  },
846
884
  function (key, image) {
847
- if (image.file.length < 50) {
848
- return `data-icon="${image.data}"`;
849
- } else {
885
+ if (image.isBase64) {
850
886
  return `data-icon="data:image/png; base64, ${image.data}"`;
887
+ } else {
888
+ return `data-icon="${image.data}"`;
851
889
  }
852
890
  },
853
891
  );
@@ -857,7 +895,6 @@ async function selectImageOverride(id) {
857
895
  const image = $('#chooseimage').find('#images option:selected').val();
858
896
  const global = $('#chooseimage').find('#globaloverride').prop('checked');
859
897
  const name = $('#chooseimage').find('input[id=\'d_name\']').val();
860
- console.warn(`update device image : ${id} : ${image} : ${global} : ${name} : ${dev.common.name}`);
861
898
  const data = {};
862
899
  if (image != 'current') data.icon= image;
863
900
  if (name != dev.common.name) data.name = name;
@@ -869,10 +906,208 @@ async function selectImageOverride(id) {
869
906
  });
870
907
  }
871
908
 
909
+
910
+ function safestring(val) {
911
+ const t = typeof val;
912
+ if (t==='object') return JSON.stringify(val).replaceAll(',',', ');
913
+ if (t==='string') return val.replaceAll(',',', ');
914
+ if (t==='function') return 'function';
915
+ return val;
916
+ }
917
+
918
+ function fne(item) {
919
+ const rv = [];
920
+ if (item.flags) {
921
+ if (item.flags.includes('SUCCESS')) rv.push('SUCCESS');
922
+ else rv.push(...item.flags);
923
+ }
924
+ if (item.errors && item.errors.length > 0) {
925
+ if (item.errors.length > 1) rv.push('errors: '+item.errors.join(','));
926
+ else rv.push('error: '+item.errors[0]);
927
+ }
928
+ return rv.join(', ');
929
+ }
930
+
931
+ function HtmlFromInDebugMessages(messages, devID, filter) {
932
+ const Html = [];
933
+ const filterSet = new Set();
934
+ let isodd = true;
935
+ if (dbgMsghide.has('i_'+devID)) {
936
+ console.warn('in all filtered out')
937
+ Html.push('&nbsp;')
938
+ } else for (const item of messages) {
939
+ if (item.states.length > 0) {
940
+ const rowspan = item.states.length > 1 ? ` rowspan="${item.states.length}"` : '';
941
+ let idx = item.states.length;
942
+ const IHtml = [];
943
+ let fs = '';
944
+ for (const state of item.states) {
945
+ fs = fs+state.id+'.'+fne(item);
946
+ const redText = (item.errors && item.errors.length > 0 ? ' id="dbgred"' : '');
947
+ idx--;
948
+ const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
949
+ if (idx==0)
950
+ LHtml.push(`<td${rowspan}>${item.dataID.toString(16).slice(-4)}</td><td${rowspan}>${safestring(item.payload)}</td>`);
951
+ LHtml.push(`<td></td><td${redText}>${safestring(state.payload)}</td><td${redText}>${state.id}</td><td${redText}>${state.value}</td><td${redText}>${fne(item)}</td></tr>`);
952
+ IHtml.unshift(...LHtml)
953
+ }
954
+ if (filter)
955
+ if (filterSet.has(fs)) continue; else filterSet.add(fs);
956
+ Html.unshift(...IHtml);
957
+ isodd=!isodd;
958
+ }
959
+ }
960
+ const ifbutton = `<a id="i_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsgfilter.has('i_'+devID) ? 'filter_list' : 'format_align_justify' }</i></a>`
961
+ const ofbutton = `<a id="hi_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsghide.has('i_'+devID) ? 'unfold_more' : 'unfold_less' }</i></a>`
962
+ const dataHide = dbgMsgfilter.has('hi_'+devID) ? 'Data hidden' : '&nbsp;';
963
+ return `<thead id="dbgtable"><tr><td>&nbsp</td><td>Incoming messages</td><td>&nbsp;</td><td>&nbsp;</td><td>${dataHide}</td><td>${ifbutton}</td><td>${ofbutton}</td></tr><tr><td>ID</td><td>Zigbee Payload</td><td>&nbsp;</td><td>State Payload</td><td>ID</td><td>value</td><td>Flags</td></tr></thead><tbody>${Html.join('')}</tbody>`;
964
+ }
965
+
966
+
967
+ function HtmlFromOutDebugMessages(messages, devID, filter) {
968
+ const Html = [];
969
+ const filterSet = new Set();
970
+ let isodd=true;
971
+ if (dbgMsghide.has('o_'+devID)) {
972
+ console.warn('out all filtered out')
973
+ Html.push('&nbsp;')
974
+ }
975
+ else for (const item of messages) {
976
+ if (item.states.length > 0) {
977
+ const rowspan = item.states.length > 1 ? ` rowspan="${item.states.length}"` : '';
978
+ let idx = item.states.length;
979
+ let fs = '';
980
+ const IHtml = [];
981
+ for (const state of item.states) {
982
+ fs = fs+state.id+'.'+fne(item);
983
+ const redText = (item.errors && item.errors.length > 0 ? ' id="dbgred"' : '');
984
+ const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
985
+ idx--;
986
+ if (idx==0)
987
+ LHtml.push(`<td${rowspan}>${item.dataID.toString(16).slice(-4)}</td><td${rowspan}>${safestring(item.payload)}</td>`);
988
+ LHtml.push(`<td${redText}>${state.ep ? state.ep : ''}</td><td${redText}>${state.id}</td><td${redText}>${safestring(state.value)}</td><td${redText}>${safestring(state.payload)}</td><td${redText}>${fne(item)}</td></tr>`);
989
+ IHtml.unshift(...LHtml);
990
+
991
+ }
992
+ if (filter)
993
+ if (filterSet.has(fs)) continue; else filterSet.add(fs);
994
+ Html.unshift(...IHtml);
995
+ isodd=!isodd;
996
+ }
997
+ }
998
+ const ifbutton = `<a id="o_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsgfilter.has('o_'+devID) ? 'filter_list' : 'format_align_justify' }</i></a>`
999
+ const ofbutton = `<a id="ho_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsghide.has('o_'+devID) ? 'unfold_more' : 'unfold_less'}</i></a>`
1000
+ const dataHide = dbgMsgfilter.has('ho_'+devID) ? 'Data hidden' : '&nbsp;';
1001
+ return `<thead id="dbgtable"><tr><td>&nbsp</td><td>Outgoing messages</td><td>&nbsp;</td><td>&nbsp;</td><td>${dataHide}</td><td>${ifbutton}</td><td>${ofbutton}</td></tr><tr><td>ID</td><td>Zigbee Payload</td><td>EP</td><td>ID</td><td>value</td><td>State Payload</td><td>Flags</td></tr></thead><tbody>${Html.join('')}</tbody>`;
1002
+ }
1003
+
1004
+
1005
+ function displayDebugMessages(msg) {
1006
+ const buttonNames = [];
1007
+ if (msg.byId) {
1008
+ const dbgData = msg.byId;
1009
+ const keys = Object.keys(dbgData);
1010
+ const keylength = keys.length;
1011
+ const Html = [];
1012
+ const button = `<a id="e_all" class="btn-floating waves-effect waves-light green tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">sync_problem</i></a>`;
1013
+ const fbutton = `<a id="f_all" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsgfilter.size != 0 ? 'filter_list' : 'format_align_justify' }</i></a>`;
1014
+ const hbutton = `<a id="h_all" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsghide.size != 0 ? 'unfold_more' : 'unfold_less'}</i></a>`;
1015
+ const logbutton = `<a id="l_all" class="btn-floating waves-effect waves-light ${debugInLog ? 'green' : 'red'} tooltipped center-align hoverable translateT" title="Log messages"><i class="material-icons large">${debugInLog ? 'speaker_notes' : 'speaker_notes_off'}</i></a>`;
1016
+ Html.push(`<li><table><thead id="dbgtable"><tr><td>${logbutton}</td><td colspan="3">Debug information by device</td><td>${fbutton}</td><td>${hbutton}</td><td>${button}</td></tr></thead><tbody>`);
1017
+ if (!keylength) {
1018
+ Html.push('<tr><td></td><td>No debug data loaded - press reload to refresh</td><td></td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr></tbody></table></li>')
1019
+ $('#dbg_data_list').html(Html.join(''));
1020
+ }
1021
+ else {
1022
+ Html.push('</tbody></table></li>')
1023
+ for (const devID of Object.keys(dbgData)) {
1024
+ const dev = devices.find((d) => d._id.endsWith(devID.slice(-16)));
1025
+ const type_url = (dev && dev.common && dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown');
1026
+ const image = `<img src="${dev.common.icon || dev.icon}" width="40px" onerror="this.onerror=null;this.src='img/unavailable.png';">`
1027
+ const modelUrl = (type_url === 'unknown') ? 'unknown' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${image}</a>`;
1028
+ const devName = (dev && dev.common && dev.common.name) ? dev.common.name : 'unnamed';
1029
+ const button = `<a id="e_${devID}" class="btn-floating waves-effect waves-light green tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">sync_problem</i></a>`
1030
+ buttonNames.push(devID);
1031
+ Html.push(`<li><table><thead id="dbgtable"><tr><td colspan="4">${devName} (ID: ${devID} Model: ${dev && dev.common ? dev.common.name : 'unknown'})</td><td>${modelUrl}</td><td>&nbsp;</td><td>${button}</td></tr></thead><tbody>`);
1032
+ if (dbgData[devID].IN.length > 0) {
1033
+ Html.push(`${HtmlFromInDebugMessages(dbgData[devID].IN, devID, dbgMsgfilter.has('i_'+devID))}`);
1034
+ }
1035
+ if (dbgData[devID].OUT.length > 0) {
1036
+ Html.push(`${HtmlFromOutDebugMessages(dbgData[devID].OUT, devID, dbgMsgfilter.has('o_'+devID))}`);
1037
+ }
1038
+ Html.push('</tbody></table></li>');
1039
+ }
1040
+ $('#dbg_data_list').html(Html.join(''));
1041
+ }
1042
+ $(`#e_all`).click(function () {
1043
+ getDebugMessages();
1044
+ });
1045
+ $(`#l_all`).click(function () {
1046
+ debugInLog = !debugInLog;
1047
+ getDebugMessages();
1048
+ });
1049
+ $(`#f_all`).click(function () {
1050
+ if (dbgMsgfilter.size > 0) {
1051
+ dbgMsgfilter.clear();
1052
+ }
1053
+ else {
1054
+ for (const item of Object.keys(msg.byId)) {
1055
+ dbgMsgfilter.add(`o_${item}`)
1056
+ dbgMsgfilter.add(`i_${item}`)
1057
+ }
1058
+ }
1059
+ displayDebugMessages(debugMessages);
1060
+ });
1061
+ $(`#h_all`).click(function () {
1062
+ if (dbgMsghide.size > 0) {
1063
+ dbgMsghide.clear();
1064
+ }
1065
+ else {
1066
+ for (const item of Object.keys(msg.byId)) {
1067
+ dbgMsghide.add(`o_${item}`)
1068
+ dbgMsghide.add(`i_${item}`)
1069
+ }
1070
+ }
1071
+ displayDebugMessages(debugMessages);
1072
+ });
1073
+ for (const b of buttonNames) {
1074
+ $(`#e_${b}`).click(function () {
1075
+ getDebugMessages();
1076
+ });
1077
+ $(`#o_${b}`).click(function () {
1078
+ if (dbgMsgfilter.has(`o_${b}`)) dbgMsgfilter.delete(`o_${b}`); else dbgMsgfilter.add(`o_${b}`);
1079
+ displayDebugMessages(debugMessages);
1080
+ });
1081
+ $(`#i_${b}`).click(function () {
1082
+ if (dbgMsgfilter.has(`i_${b}`)) dbgMsgfilter.delete(`i_${b}`); else dbgMsgfilter.add(`i_${b}`);
1083
+ displayDebugMessages(debugMessages);
1084
+ });
1085
+ $(`#ho_${b}`).click(function () {
1086
+ if (dbgMsghide.has(`o_${b}`)) dbgMsghide.delete(`o_${b}`); else dbgMsghide.add(`o_${b}`);
1087
+ displayDebugMessages(debugMessages);
1088
+ });
1089
+ $(`#hi_${b}`).click(function () {
1090
+ if (dbgMsghide.has(`i_${b}`)) dbgMsghide.delete(`i_${b}`); else dbgMsghide.add(`i_${b}`);
1091
+ displayDebugMessages(debugMessages);
1092
+ });
1093
+ }
1094
+ }
1095
+ }
1096
+
1097
+ function getDebugMessages() {
1098
+ sendTo(namespace, 'getDebugMessages', { inlog: debugInLog }, function(msg) {
1099
+ debugMessages = msg;
1100
+ if (msg) displayDebugMessages(debugMessages)
1101
+ })
1102
+ }
1103
+
1104
+
872
1105
  function getDevices() {
873
1106
  getCoordinatorInfo();
874
- sendTo(namespace, 'getDeviceCleanupRequired', {}, function(msg) {
1107
+ sendTo(namespace, 'getDevices', {}, function (msg) {
875
1108
  if (msg) {
1109
+ devices = msg.devices ? msg.devices : [];
1110
+ // check if stashed error messages are sent alongside
876
1111
  if (msg.clean)
877
1112
  $('#state_cleanup_btn').removeClass('hide');
878
1113
  else
@@ -881,24 +1116,30 @@ function getDevices() {
881
1116
  $('#show_errors_btn').removeClass('hide');
882
1117
  errorData = msg.errors;
883
1118
  }
884
- else
1119
+ else {
885
1120
  $('#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
- });
895
- sendTo(namespace, 'getDevices', {}, function (msg) {
896
- if (msg) {
1121
+ }
1122
+
1123
+ //check if debug messages are sent alongside
1124
+ if (msg && typeof (msg.debugDevices == 'array')) {
1125
+ debugDevices = msg.debugDevices;
1126
+ console.warn('debug devices is sent')
1127
+ }
1128
+ else
1129
+ debugDevices = [];
1130
+ if (debugMessages.byId) {
1131
+ debugMessages.byId = msg;
1132
+ if (msg) displayDebugMessages(debugMessages)
1133
+ }
897
1134
  if (msg.error) {
898
- showMessage(msg.error, _('Error'));
1135
+ errorData.push(msg.error);
1136
+ isHerdsmanRunning = false;
1137
+ updateStartButton();
899
1138
  } else {
900
- devices = msg;
1139
+ isHerdsmanRunning = true;
1140
+ updateStartButton();
901
1141
  showDevices();
1142
+ getDebugMessages();
902
1143
  getExclude();
903
1144
  getBinding();
904
1145
  }
@@ -931,8 +1172,12 @@ function getMap() {
931
1172
  $('#refresh').removeClass('disabled');
932
1173
  if (msg) {
933
1174
  if (msg.error) {
934
- showMessage(msg.error, _('Error'));
1175
+ errorData.push(msg.error);
1176
+ isHerdsmanRunning = false;
1177
+ updateStartButton();
935
1178
  } else {
1179
+ isHerdsmanRunning = true;
1180
+ updateStartButton();
936
1181
  if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
937
1182
  showMessage(msg.errors.join('<p>'), 'Map generation messages');
938
1183
  }
@@ -952,14 +1197,24 @@ function getRandomExtPanID()
952
1197
  return bytes.join('');
953
1198
  }
954
1199
 
1200
+ function getRandomChannel()
1201
+ {
1202
+ const channels = [11,15,20,25]
1203
+ return channels[Math.floor(Math.random() * 4)];
1204
+ }
1205
+
1206
+
955
1207
 
956
1208
  // the function loadSettings has to exist ...
957
1209
 
958
1210
  function load(settings, onChange) {
959
- if (settings.panID === undefined) {
1211
+ if (settings.extPanID === undefined || settings.extPanID == '') {
1212
+ settings.channel = getRandomChannel();
1213
+ }
1214
+ if (settings.panID === undefined || settings.panID == 0) {
960
1215
  settings.panID = Math.floor(Math.random() * 10000);
961
1216
  }
962
- if (settings.extPanID === undefined) {
1217
+ if (settings.extPanID === undefined || settings.extPanID == '') {
963
1218
  settings.extPanID = getRandomExtPanID();
964
1219
  }
965
1220
  // fix for previous wrong value
@@ -970,9 +1225,6 @@ function load(settings, onChange) {
970
1225
  if (settings.precfgkey === undefined) {
971
1226
  settings.precfgkey = '01030507090B0D0F00020406080A0C0D';
972
1227
  }
973
- if (settings.channel === undefined) {
974
- settings.channel = 11;
975
- }
976
1228
  if (settings.disablePing === undefined) {
977
1229
  settings.disablePing = false;
978
1230
  }
@@ -982,6 +1234,7 @@ function load(settings, onChange) {
982
1234
  if (settings.baudRate === undefined) {
983
1235
  settings.baudRate = 115200;
984
1236
  }
1237
+ if (settings.autostart === undefined) settings.autostart = false;
985
1238
 
986
1239
  // example: select elements with id=key and class=value and insert value
987
1240
  for (const key in settings) {
@@ -993,10 +1246,12 @@ function load(settings, onChange) {
993
1246
  if (value.attr('type') === 'checkbox') {
994
1247
  value.prop('checked', settings[key]).change(function () {
995
1248
  onChange();
1249
+ validateNVRamBackup(false, key)
996
1250
  });
997
1251
  } else {
998
1252
  value.val(settings[key]).change(function () {
999
1253
  onChange();
1254
+ validateNVRamBackup(false, key)
1000
1255
  }).keyup(function () {
1001
1256
  $(this).trigger('change');
1002
1257
  });
@@ -1009,12 +1264,44 @@ function load(settings, onChange) {
1009
1264
  //dialog = new MatDialog({EndingTop: '50%'});
1010
1265
  getDevices();
1011
1266
  getNamedColors();
1267
+ readNVRamBackup(false);
1268
+ //getDebugMessages();
1012
1269
  //getMap();
1013
1270
  //addCard();
1014
1271
 
1015
1272
  // Signal to admin, that no changes yet
1016
1273
  onChange(false);
1017
1274
 
1275
+ $('#test-btn').click(function () {
1276
+ console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1277
+ if (!isHerdsmanRunning) {
1278
+ const port = $('#port.value').val();
1279
+ console.warn(`port is ${port}`)
1280
+ showWaitingDialog(`Trying to connect to ${port}`, 300);
1281
+ sendTo(namespace, 'testConnection', { address:port }, function(msg) {
1282
+ console.warn(`send to returned with ${JSON.stringify(msg)}`);
1283
+ closeWaitingDialog();
1284
+ if (msg) {
1285
+ if (msg.error) {
1286
+ showMessage(msg.error, _('Error'));
1287
+ }
1288
+ }
1289
+ })
1290
+ }
1291
+ else {
1292
+ showMessage('function unavailable while herdsman is running', _('Error'))
1293
+ }
1294
+ });
1295
+
1296
+ $('#readNVRam-btn').click(function() {
1297
+ readNVRamBackup(true);
1298
+ })
1299
+ // test start commands
1300
+ $('#show_test_run').click(function () {
1301
+ console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1302
+ doTestStart(!isHerdsmanRunning);
1303
+ });
1304
+
1018
1305
  $('#state_cleanup_btn').click(function () {
1019
1306
  cleanConfirmation();
1020
1307
  });
@@ -1043,6 +1330,10 @@ function load(settings, onChange) {
1043
1330
  resetConfirmation();
1044
1331
  });
1045
1332
 
1333
+ $('#deleteNVRam-btn').click(function () {
1334
+ deleteNvBackupConfirmation();
1335
+ });
1336
+
1046
1337
  $('#viewconfig').click(function () {
1047
1338
  showViewConfig();
1048
1339
  });
@@ -1050,6 +1341,9 @@ function load(settings, onChange) {
1050
1341
  $('#scan').click(function () {
1051
1342
  showChannels();
1052
1343
  });
1344
+ $('#scan_t').click(function () {
1345
+ showChannels();
1346
+ });
1053
1347
 
1054
1348
  sendTo(namespace, 'getGroups', {}, function (data) {
1055
1349
  groups = data.groups;
@@ -1076,6 +1370,10 @@ function load(settings, onChange) {
1076
1370
  }
1077
1371
  });
1078
1372
 
1373
+ $('#hardware').click(function() {
1374
+ validateNVRamBackup(false);
1375
+ });
1376
+
1079
1377
  $(document).ready(function () {
1080
1378
  $('.modal').modal({
1081
1379
  startingTop: '30%',
@@ -1133,6 +1431,7 @@ function showMessages() {
1133
1431
  data = mess + '\n' + data;
1134
1432
  }
1135
1433
  $('#stdout').text(data);
1434
+ $('#stdout_t').text(messages.join('\n'));
1136
1435
  }
1137
1436
 
1138
1437
  function showPairingProcess() {
@@ -1146,6 +1445,41 @@ function showPairingProcess() {
1146
1445
  Materialize.updateTextFields();
1147
1446
  }
1148
1447
 
1448
+ function doTestStart(start) {
1449
+ updateStartButton(true);
1450
+ if (start) {
1451
+ const ovr = { extPanID:$('#extPanID.value').val(),
1452
+ panID: $('#PanID.value').val(),
1453
+ channel: $('#channel.value').val(),
1454
+ port: $('#port.value').val(),
1455
+ adapterType: $('#adapterType.value').val(),
1456
+ baudRate: $('#baudRate.value').val(),
1457
+ precfgkey: $('#precfgkey.value').val(),
1458
+ flowCTRL: $('#flowCTRL.value').prop('checked')
1459
+ };
1460
+ // $('#testStartStart').addClass('disabled');
1461
+ messages = [];
1462
+ sendTo(namespace, 'testConnect', { start:true, zigbeeOptions:ovr }, function(msg) {
1463
+ if (msg) {
1464
+ if (msg.status)
1465
+ $('#testStartStop').removeClass('disabled');
1466
+ else
1467
+ $('#testStartStart').removeClass('disabled');
1468
+ }
1469
+ })
1470
+ }
1471
+ else {
1472
+ //$('#testStartStop').addClass('disabled');
1473
+ sendTo(namespace, 'testConnect', { start:false }, function(msg) {
1474
+ if (msg) {
1475
+ if (msg.status) $('#testStartStart').removeClass('disabled');
1476
+ else $('#testStartStop').removeClass('disabled');
1477
+ }
1478
+ })
1479
+
1480
+ }
1481
+ }
1482
+
1149
1483
  // ... and the function save has to exist.
1150
1484
  // you have to make sure the callback is called with the settings object as first param!
1151
1485
 
@@ -1172,6 +1506,36 @@ function getDevId(adapterDevId) {
1172
1506
  return adapterDevId.split('.').slice(0, 3).join('.');
1173
1507
  }
1174
1508
 
1509
+
1510
+ function updateStartButton(block) {
1511
+ if (block) {
1512
+ $('#show_test_run').addClass('disabled');
1513
+ $('#reset-btn').addClass('disabled');
1514
+ $('#deleteNVRam-btn').addClass('disabled');
1515
+ $('#ErrorNotificationBtn').removeClass('hide')
1516
+ $('#ErrorNotificationBtn').removeClass('blinking')
1517
+ $('#ErrorNotificationIcon').removeClass('icon-red')
1518
+ $('#ErrorNotificationIcon').addClass('icon-orange')
1519
+ return;
1520
+ }
1521
+ if (isHerdsmanRunning)
1522
+ {
1523
+ $('#ErrorNotificationBtn').addClass('hide')
1524
+ $('#ErrorNotificationBtn').removeClass('blinking');
1525
+ $('#show_test_run').removeClass('disabled');
1526
+ $('#deleteNVRam-btn').removeClass('disabled');
1527
+ $('#reset-btn').removeClass('disabled');
1528
+ }
1529
+ else {
1530
+ $('#ErrorNotificationIcon').addClass('icon-red')
1531
+ $('#ErrorNotificationIcon').removeClass('icon-orange')
1532
+ $('#ErrorNotificationBtn').removeClass('hide')
1533
+ $('#ErrorNotificationBtn').addClass('blinking');
1534
+ $('#show_test_run').removeClass('disabled');
1535
+ $('#deleteNVRam-btn').removeClass('disabled');
1536
+ $('#reset-btn').addClass('disabled');
1537
+ }
1538
+ }
1175
1539
  // subscribe to changes
1176
1540
  socket.emit('subscribe', namespace + '.*');
1177
1541
  socket.emit('subscribeObjects', namespace + '.*');
@@ -1200,8 +1564,21 @@ socket.on('stateChange', function (id, state) {
1200
1564
  $('#progress_line').css('width', `${percent}%`);
1201
1565
  }
1202
1566
  } else if (id.match(/\.info\.pairingMessage$/)) {
1203
- messages.push(state.val);
1204
- showMessages();
1567
+ if (state.val == 'NewDebugMessage') {
1568
+ getDebugMessages();
1569
+ }
1570
+ else {
1571
+ messages.push(state.val);
1572
+ showMessages();
1573
+ if (state.val.startsWith('Zigbee-Herdsman started successfully')) {
1574
+ isHerdsmanRunning = true;
1575
+ updateStartButton();
1576
+ }
1577
+ if (state.val.startsWith('herdsman stopped') || state.val.startsWith('Error herdsman')) {
1578
+ isHerdsmanRunning = false;
1579
+ updateStartButton();
1580
+ }
1581
+ }
1205
1582
  } else {
1206
1583
  const devId = getDevId(id);
1207
1584
  putEventToNode(devId);
@@ -1226,6 +1603,7 @@ socket.on('stateChange', function (id, state) {
1226
1603
  }
1227
1604
  });
1228
1605
 
1606
+
1229
1607
  socket.on('objectChange', function (id, obj) {
1230
1608
  if (id.substring(0, namespaceLen) !== namespace) return;
1231
1609
  //console.log('objectChange', id, obj);
@@ -1486,7 +1864,6 @@ function showNetworkMap(devices, map) {
1486
1864
  const options = data.edges._data.get(id);
1487
1865
  if (select) {
1488
1866
  options.font.size = 15;
1489
- console.warn(JSON.stringify(options.font));
1490
1867
  } else {
1491
1868
  options.font.size = 0;
1492
1869
  }
@@ -2012,31 +2389,6 @@ function list2select(selector, list, selected, getText, getKey, getData) {
2012
2389
  element.select();
2013
2390
  }
2014
2391
 
2015
- /*
2016
- function showGroups() {
2017
- $('#groups_table').find('.group').remove();
2018
- if (!groups) return;
2019
- const element = $('#groups_table');
2020
- for (const j in groups) {
2021
- if (groups.hasOwnProperty(j)) {
2022
- element.append(`<tr id="group_${j}" class="group"><td>${j}</td><td><div>${groups[j]}<span class="right">` +
2023
- `<a id="${j}" name="groupedit" class="waves-effect green btn-floating"><i class="material-icons">edit</i></a>` +
2024
- `<a id="${j}" name="groupdelete" class="waves-effect red btn-floating"><i class="material-icons">delete</i></a></span></div></td></tr>`);
2025
- }
2026
- }
2027
- $('a.btn-floating[name=\'groupedit\']').click(function () {
2028
- const index = $(this).attr('id'),
2029
- name = groups[index];
2030
- editGroupName(index, name, false);
2031
- });
2032
- $('a.btn-floating[name=\'groupdelete\']').click(function () {
2033
- const index = $(this).attr('id'),
2034
- name = groups[index];
2035
- deleteGroupConfirmation(index, name);
2036
- });
2037
- }
2038
- */
2039
-
2040
2392
  function editGroup(id, name) {
2041
2393
  const grp = devGroups[id];
2042
2394
  let info = '';
@@ -2065,7 +2417,6 @@ function editGroup(id, name) {
2065
2417
  removeMembers.push({id:member.ieee.replace('0x',''), ep:member.epid})
2066
2418
  }
2067
2419
  }
2068
- console.warn(`ID: ${Id} name: ${newName} removeMembers: ${JSON.stringify(removeMembers)}`)
2069
2420
  updateGroup(Id, newName, (removeMembers.length > 0 ? removeMembers: undefined));
2070
2421
  // showGroups();
2071
2422
  getDevices();
@@ -2625,7 +2976,7 @@ function genDevInfo(device) {
2625
2976
  ${genRow('date code', dev._dateCode)}
2626
2977
  ${genRow('build', dev._softwareBuildID)}
2627
2978
  ${genRow('interviewed', dev._interviewCompleted)}
2628
- ${genRow('configured', (dev.meta.configured === 1), true)}
2979
+ ${genRow('configured', (device.isConfigured), true)}
2629
2980
  </ul>
2630
2981
  </div>
2631
2982
  </div>
@@ -2641,48 +2992,6 @@ function showDevInfo(id) {
2641
2992
  $('#modaldevinfo').modal('open');
2642
2993
  }
2643
2994
 
2644
- /*
2645
- function showGroupList(show) {
2646
- const htmlsections = [];
2647
- for (const groupid in devGroups) {
2648
- const dev = devGroups[groupid];
2649
- const grpname = (dev.common && dev.common.name ? dev.common.name : 'Group ' + groupid);
2650
- const selectables = [];
2651
- const members = [];
2652
- if (dev && dev.memberinfo) {
2653
- selectables.push(`<select id="members_${groupid}" multiple>`);
2654
- for (let m = 0; m < dev.memberinfo.length; m++) {
2655
- members.push(`${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (${dev.memberinfo[m].ieee})`);
2656
- selectables.push(`<option value="${m}">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (...${dev.memberinfo[m].ieee.slice(-4)})</option>`);
2657
- }
2658
- selectables.push('</select>');
2659
- }
2660
- htmlsections.push(`
2661
- <div class="row">
2662
- <div class="col s4 m4 l4">
2663
- <h5>${grpname}<h5>
2664
- </div>
2665
- <div class=col s7 m7 l7">
2666
- ${members.join('<br>')}
2667
- </div>
2668
- </div>
2669
- `);
2670
- }
2671
-
2672
- $('#grouplist').html(htmlsections.join(''));
2673
- $('#add').click(function () {
2674
- const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
2675
- addGroup(maxind + 1, 'Group ' + maxind + 1, true);
2676
- showGroupList(false);
2677
- });
2678
-
2679
- $('#modalgrouplist a.btn[name=\'save\']').unbind('click');
2680
- $('#modalgrouplist a.btn[name=\'save\']').click(() => {
2681
- });
2682
- if (show) $('#modalgrouplist').modal('open');
2683
- }
2684
- */
2685
-
2686
2995
  let waitingTimeout, waitingInt;
2687
2996
 
2688
2997
  function showWaitingDialog(text, timeout) {
@@ -2817,16 +3126,12 @@ function addExcludeDialog() {
2817
3126
  $('#excludemodaledit a.btn[name=\'save\']').unbind('click');
2818
3127
  $('#excludemodaledit a.btn[name=\'save\']').click(() => {
2819
3128
  const exclude_id = $('#excludemodaledit').find('#exclude_target option:selected').val();
2820
-
2821
3129
  const ids = devices.map(el => el._id);
2822
3130
  const idx = ids.indexOf(exclude_id);
2823
3131
  const exclude_model = devices[idx];
2824
- console.warn('calling addExclude mit model ' + exclude_model)
2825
-
2826
3132
  addExclude(exclude_model);
2827
3133
  });
2828
3134
  prepareExcludeDialog();
2829
- console.warn('opening dialog');
2830
3135
  $('#excludemodaledit').modal('open');
2831
3136
  Materialize.updateTextFields();
2832
3137
  }
@@ -2855,7 +3160,7 @@ function getExclude() {
2855
3160
  excludes = msg.legacy;
2856
3161
  showExclude();
2857
3162
  }
2858
- } else console.warn('getExclude without msg')
3163
+ }
2859
3164
  });
2860
3165
  }
2861
3166
 
@@ -3156,14 +3461,17 @@ function updateCardTimer() {
3156
3461
  }
3157
3462
 
3158
3463
  function updateDevice(id) {
3159
- sendTo(namespace, 'getDevice', {id: id}, function (devs) {
3160
- if (devs) {
3161
- if (devs.error) {
3162
- showMessage(devs.error, _('Error'));
3163
- } else {
3164
- removeDevice(id);
3165
- devs.forEach(dev => devices.push(dev));
3166
- showDevices();
3464
+ sendTo(namespace, 'getDevice', {id: id}, function (msg) {
3465
+ if (msg) {
3466
+ const devs = msg.devices;
3467
+ if (devs) {
3468
+ if (devs.error) {
3469
+ showMessage(devs.error, _('Error'));
3470
+ } else {
3471
+ removeDevice(id);
3472
+ devs.forEach(dev => devices.push(dev));
3473
+ showDevices();
3474
+ }
3167
3475
  }
3168
3476
  }
3169
3477
  });
@@ -3211,3 +3519,72 @@ function reconfigureDevice(id) {
3211
3519
  });
3212
3520
  showWaitingDialog('Device is being reconfigure', 30);
3213
3521
  }
3522
+
3523
+ const warnLevel = {
3524
+ extPanID : function(v) { return !(v && v.toLowerCase().trim()!='dddddddddddddddd')},
3525
+ channel: function(v) { const num = parseInt(v); return !(num==11 || num==15 || num==20 || num==25)},
3526
+ }
3527
+ const validatableKeys = ['channel', 'precfgkey', 'extPanID', 'panID'];
3528
+
3529
+ function validateConfigData(key, val) {
3530
+ if (validatableKeys.indexOf(key) < 0 || !val) return;
3531
+ if (warnLevel[key]) {
3532
+ if (warnLevel[key](val)) {
3533
+ console.warn(`warning set for ${key} (${val})`)
3534
+ $(`#${key}_ALERT`).removeClass('hide')
3535
+ } else $(`#${key}_ALERT`).addClass('hide')
3536
+ }
3537
+ if (nvRamBackup[key]) {
3538
+ console.warn(`value of ${key} is ${val} (${nvRamBackup[key]})`);
3539
+ if (val == nvRamBackup[key])
3540
+ {
3541
+ console.warn(`ok set for ${key} (${val})`)
3542
+ $(`#${key}_OK`).removeClass('hide')
3543
+ $(`#${key}_NOK`).addClass('hide')
3544
+ }
3545
+ else
3546
+ {
3547
+ console.warn(`nok set for ${key} (${val})`)
3548
+ $(`#${key}_OK`).addClass('hide')
3549
+ $(`#${key}_NOK`).removeClass('hide')
3550
+ }
3551
+ }
3552
+ else {
3553
+ console.warn(`noval set for ${key} (${val})`)
3554
+ $(`#${key}_OK`).addClass('hide')
3555
+ $(`#${key}_NOK`).addClass('hide')
3556
+ }
3557
+ }
3558
+
3559
+ function validateNVRamBackup(update, src) {
3560
+ console.warn('validateNVRam');
3561
+ const validatedKeys = src ? [src] : validatableKeys;
3562
+ const validator = {};
3563
+ for (const key of validatedKeys) {
3564
+ const value = $('#' + key + '.value');
3565
+ if (nvRamBackup[key] && update) {
3566
+ if (value.attr('type') === 'checkbox') {
3567
+ value.prop('checked', nvRamBackup[key]);
3568
+ } else {
3569
+ value.val(nvRamBackup[key])
3570
+ }
3571
+ }
3572
+ validateConfigData(key, value.val());
3573
+ }
3574
+ }
3575
+
3576
+
3577
+ function readNVRamBackup(update) {
3578
+ console.warn('read nvRam')
3579
+ sendTo(namespace, 'readNVRam', {}, function(msg) {
3580
+ if (msg) {
3581
+ if (msg.error && update) {
3582
+ showMessages(msg.error, _('Error'));
3583
+ delete msg.error;
3584
+ }
3585
+ nvRamBackup = msg;
3586
+ validateNVRamBackup(update)
3587
+ }
3588
+ });
3589
+
3590
+ }