iobroker.zigbee 2.0.5 → 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.
package/README.md CHANGED
@@ -152,6 +152,13 @@ You can thank the authors by these links:
152
152
 
153
153
  -----------------------------------------------------------------------------------------------------
154
154
  ## Changelog
155
+ ### 3.0.0 (2025-04-08)
156
+ * (asgothian) Breaking change: Start of zigbee subsystem requires configuration entry !!!
157
+ * (asgothian) Hardware configuration panel
158
+ * (asgothian) Update for external converter - detect /dist/ subfolder
159
+ * (asgothian) Update device image: use of icons defined in external converter (beta)
160
+ *
161
+
155
162
  ### 2.0.5 (2025-03-25)
156
163
  * (asgothian) ZHC 23.6.0
157
164
  * (asgothian) ZH 3.3.x
@@ -177,7 +184,6 @@ You can thank the authors by these links:
177
184
  * (asgothian) fix Adapter-Checker notes
178
185
  * (asgothian) improve base64 image detection
179
186
  * (asgothian) removed unused adaptert objects (info.groups, excludes) from adapter config
180
- *
181
187
 
182
188
  ### 2.0.2 (2025-03-02)
183
189
  * (asgothian) expose generation with expose function requiring a device. (Issue #1842)
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);
@@ -782,18 +810,27 @@ function getCoordinatorInfo() {
782
810
  sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
783
811
  if (msg) {
784
812
  if (msg.error) {
785
- showMessage(msg.error, _('Error'));
813
+ errorData.push(msg.error);
814
+ isHerdsmanRunning = false;
815
+ updateStartButton();
786
816
  } else {
787
817
  coordinatorinfo = msg;
818
+ isHerdsmanRunning = true;
819
+ updateStartButton()
788
820
  }
789
821
  }
790
822
  });
791
823
  }
824
+
792
825
  function checkDebugDevice(id) {
793
- 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
794
831
  for (const addressPart of debugDevices) {
795
832
  if (typeof id === 'string' && id.includes(addressPart)) {
796
- return debugDevices.indexOf(addressPart)+1;
833
+ return debugDevices.indexOf(addressPart)+1; // debug set by pattern (>0)
797
834
  }
798
835
  }
799
836
  return -1;
@@ -1067,8 +1104,10 @@ function getDebugMessages() {
1067
1104
 
1068
1105
  function getDevices() {
1069
1106
  getCoordinatorInfo();
1070
- sendTo(namespace, 'getDeviceCleanupRequired', {}, function(msg) {
1107
+ sendTo(namespace, 'getDevices', {}, function (msg) {
1071
1108
  if (msg) {
1109
+ devices = msg.devices ? msg.devices : [];
1110
+ // check if stashed error messages are sent alongside
1072
1111
  if (msg.clean)
1073
1112
  $('#state_cleanup_btn').removeClass('hide');
1074
1113
  else
@@ -1077,23 +1116,28 @@ function getDevices() {
1077
1116
  $('#show_errors_btn').removeClass('hide');
1078
1117
  errorData = msg.errors;
1079
1118
  }
1080
- else
1119
+ else {
1081
1120
  $('#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) {
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
+ }
1093
1134
  if (msg.error) {
1094
- showMessage(msg.error, _('Error'));
1135
+ errorData.push(msg.error);
1136
+ isHerdsmanRunning = false;
1137
+ updateStartButton();
1095
1138
  } else {
1096
- devices = msg;
1139
+ isHerdsmanRunning = true;
1140
+ updateStartButton();
1097
1141
  showDevices();
1098
1142
  getDebugMessages();
1099
1143
  getExclude();
@@ -1128,8 +1172,12 @@ function getMap() {
1128
1172
  $('#refresh').removeClass('disabled');
1129
1173
  if (msg) {
1130
1174
  if (msg.error) {
1131
- showMessage(msg.error, _('Error'));
1175
+ errorData.push(msg.error);
1176
+ isHerdsmanRunning = false;
1177
+ updateStartButton();
1132
1178
  } else {
1179
+ isHerdsmanRunning = true;
1180
+ updateStartButton();
1133
1181
  if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
1134
1182
  showMessage(msg.errors.join('<p>'), 'Map generation messages');
1135
1183
  }
@@ -1149,14 +1197,24 @@ function getRandomExtPanID()
1149
1197
  return bytes.join('');
1150
1198
  }
1151
1199
 
1200
+ function getRandomChannel()
1201
+ {
1202
+ const channels = [11,15,20,25]
1203
+ return channels[Math.floor(Math.random() * 4)];
1204
+ }
1205
+
1206
+
1152
1207
 
1153
1208
  // the function loadSettings has to exist ...
1154
1209
 
1155
1210
  function load(settings, onChange) {
1156
- if (settings.panID === undefined) {
1211
+ if (settings.extPanID === undefined || settings.extPanID == '') {
1212
+ settings.channel = getRandomChannel();
1213
+ }
1214
+ if (settings.panID === undefined || settings.panID == 0) {
1157
1215
  settings.panID = Math.floor(Math.random() * 10000);
1158
1216
  }
1159
- if (settings.extPanID === undefined) {
1217
+ if (settings.extPanID === undefined || settings.extPanID == '') {
1160
1218
  settings.extPanID = getRandomExtPanID();
1161
1219
  }
1162
1220
  // fix for previous wrong value
@@ -1167,9 +1225,6 @@ function load(settings, onChange) {
1167
1225
  if (settings.precfgkey === undefined) {
1168
1226
  settings.precfgkey = '01030507090B0D0F00020406080A0C0D';
1169
1227
  }
1170
- if (settings.channel === undefined) {
1171
- settings.channel = 11;
1172
- }
1173
1228
  if (settings.disablePing === undefined) {
1174
1229
  settings.disablePing = false;
1175
1230
  }
@@ -1179,6 +1234,7 @@ function load(settings, onChange) {
1179
1234
  if (settings.baudRate === undefined) {
1180
1235
  settings.baudRate = 115200;
1181
1236
  }
1237
+ if (settings.autostart === undefined) settings.autostart = false;
1182
1238
 
1183
1239
  // example: select elements with id=key and class=value and insert value
1184
1240
  for (const key in settings) {
@@ -1190,10 +1246,12 @@ function load(settings, onChange) {
1190
1246
  if (value.attr('type') === 'checkbox') {
1191
1247
  value.prop('checked', settings[key]).change(function () {
1192
1248
  onChange();
1249
+ validateNVRamBackup(false, key)
1193
1250
  });
1194
1251
  } else {
1195
1252
  value.val(settings[key]).change(function () {
1196
1253
  onChange();
1254
+ validateNVRamBackup(false, key)
1197
1255
  }).keyup(function () {
1198
1256
  $(this).trigger('change');
1199
1257
  });
@@ -1206,6 +1264,7 @@ function load(settings, onChange) {
1206
1264
  //dialog = new MatDialog({EndingTop: '50%'});
1207
1265
  getDevices();
1208
1266
  getNamedColors();
1267
+ readNVRamBackup(false);
1209
1268
  //getDebugMessages();
1210
1269
  //getMap();
1211
1270
  //addCard();
@@ -1213,6 +1272,36 @@ function load(settings, onChange) {
1213
1272
  // Signal to admin, that no changes yet
1214
1273
  onChange(false);
1215
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
+
1216
1305
  $('#state_cleanup_btn').click(function () {
1217
1306
  cleanConfirmation();
1218
1307
  });
@@ -1241,6 +1330,10 @@ function load(settings, onChange) {
1241
1330
  resetConfirmation();
1242
1331
  });
1243
1332
 
1333
+ $('#deleteNVRam-btn').click(function () {
1334
+ deleteNvBackupConfirmation();
1335
+ });
1336
+
1244
1337
  $('#viewconfig').click(function () {
1245
1338
  showViewConfig();
1246
1339
  });
@@ -1248,6 +1341,9 @@ function load(settings, onChange) {
1248
1341
  $('#scan').click(function () {
1249
1342
  showChannels();
1250
1343
  });
1344
+ $('#scan_t').click(function () {
1345
+ showChannels();
1346
+ });
1251
1347
 
1252
1348
  sendTo(namespace, 'getGroups', {}, function (data) {
1253
1349
  groups = data.groups;
@@ -1274,6 +1370,10 @@ function load(settings, onChange) {
1274
1370
  }
1275
1371
  });
1276
1372
 
1373
+ $('#hardware').click(function() {
1374
+ validateNVRamBackup(false);
1375
+ });
1376
+
1277
1377
  $(document).ready(function () {
1278
1378
  $('.modal').modal({
1279
1379
  startingTop: '30%',
@@ -1331,6 +1431,7 @@ function showMessages() {
1331
1431
  data = mess + '\n' + data;
1332
1432
  }
1333
1433
  $('#stdout').text(data);
1434
+ $('#stdout_t').text(messages.join('\n'));
1334
1435
  }
1335
1436
 
1336
1437
  function showPairingProcess() {
@@ -1344,6 +1445,41 @@ function showPairingProcess() {
1344
1445
  Materialize.updateTextFields();
1345
1446
  }
1346
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
+
1347
1483
  // ... and the function save has to exist.
1348
1484
  // you have to make sure the callback is called with the settings object as first param!
1349
1485
 
@@ -1370,6 +1506,36 @@ function getDevId(adapterDevId) {
1370
1506
  return adapterDevId.split('.').slice(0, 3).join('.');
1371
1507
  }
1372
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
+ }
1373
1539
  // subscribe to changes
1374
1540
  socket.emit('subscribe', namespace + '.*');
1375
1541
  socket.emit('subscribeObjects', namespace + '.*');
@@ -1404,6 +1570,14 @@ socket.on('stateChange', function (id, state) {
1404
1570
  else {
1405
1571
  messages.push(state.val);
1406
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
+ }
1407
1581
  }
1408
1582
  } else {
1409
1583
  const devId = getDevId(id);
@@ -2215,31 +2389,6 @@ function list2select(selector, list, selected, getText, getKey, getData) {
2215
2389
  element.select();
2216
2390
  }
2217
2391
 
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
2392
  function editGroup(id, name) {
2244
2393
  const grp = devGroups[id];
2245
2394
  let info = '';
@@ -2843,48 +2992,6 @@ function showDevInfo(id) {
2843
2992
  $('#modaldevinfo').modal('open');
2844
2993
  }
2845
2994
 
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
2995
  let waitingTimeout, waitingInt;
2889
2996
 
2890
2997
  function showWaitingDialog(text, timeout) {
@@ -3354,14 +3461,17 @@ function updateCardTimer() {
3354
3461
  }
3355
3462
 
3356
3463
  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();
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
+ }
3365
3475
  }
3366
3476
  }
3367
3477
  });
@@ -3409,3 +3519,72 @@ function reconfigureDevice(id) {
3409
3519
  });
3410
3520
  showWaitingDialog('Device is being reconfigure', 30);
3411
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
+ }