iobroker.zigbee 3.1.4 → 3.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/admin/admin.js CHANGED
@@ -9,6 +9,7 @@ const Materialize = (typeof M !== 'undefined') ? M : Materialize,
9
9
  namespace = 'zigbee.' + instance,
10
10
  namespaceLen = namespace.length;
11
11
  let devices = [],
12
+ models = [],
12
13
  debugDevices = [],
13
14
  messages = [],
14
15
  map = {},
@@ -17,6 +18,7 @@ let devices = [],
17
18
  network,
18
19
  networkEvents,
19
20
  responseCodes = false,
21
+ localConfigData = {},
20
22
  groups = {},
21
23
  devGroups = {}, // eslint-disable-line prefer-const
22
24
  binding = [],
@@ -64,23 +66,89 @@ const savedSettings = [
64
66
  'adapterType', 'debugHerdsman', 'disableBackup', 'external', 'startWithInconsistent','pingTimeout','listDevicesAtStart',
65
67
  'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart', 'readAtAnnounce', 'startReadDelay', 'readAllAtStart','pingCluster'
66
68
  ];
69
+ const lockout = {
70
+ timeoutid:undefined,
71
+ isActive:false,
72
+ };
73
+
74
+ const connectionStatus = {
75
+ connected: false,
76
+ lastcheck: Date.now(),
77
+ }
78
+
79
+
80
+ ////
81
+ //
82
+ //. section Alive
83
+ //
84
+ ////
85
+
86
+ function keepAlive(callback) {
87
+ const responseTimeout = setTimeout(function() {
88
+ UpdateAdapterAlive(false); }, 500);
89
+ sendTo(namespace, 'aliveCheck', {}, function(msg) {
90
+ clearTimeout(responseTimeout);
91
+ UpdateAdapterAlive(true);
92
+ if (callback) callback();
93
+ });
94
+ }
95
+
96
+ function startKeepalive() {
97
+ return setInterval(keepAlive, 10000);
98
+ }
99
+
100
+ function UpdateAdapterAlive(state) {
101
+ if (connectionStatus.connected === state) return;
102
+ connectionStatus.time = Date.now();
103
+ if (state) {
104
+ $('#adapterStopped_btn').addClass('hide');
105
+ $('#code_pairing').removeClass('disabled');
106
+ $('#touchlink_btn').removeClass('disabled');
107
+ $('#add_grp_btn').removeClass('disabled');
108
+ $('#fw_check_btn').removeClass('disabled');
109
+ $('#ErrorNotificationBtn').removeClass('disabled');
110
+ $('#show_errors_btn').removeClass('disabled');
111
+ $('#download_icons_btn').removeClass('disabled');
112
+ $('#pairing').removeClass('disabled');
113
+ }
114
+ else {
115
+ $('#adapterStopped_btn').removeClass('hide');
116
+ $('#code_pairing').addClass('disabled');
117
+ $('#touchlink_btn').addClass('disabled');
118
+ $('#add_grp_btn').addClass('disabled');
119
+ $('#fw_check_btn').addClass('disabled');
120
+ $('#ErrorNotificationBtn').addClass('disabled');
121
+ $('#show_errors_btn').addClass('disabled');
122
+ $('#pairing').addClass('disabled');
123
+ $('#download_icons_btn').addClass('disabled');
124
+ }
125
+ connectionStatus.connected = state;
126
+ }
127
+
128
+
129
+ ////
130
+ //
131
+ // Utility functions
132
+ //
133
+ ////
134
+ function sendToWrapper(target,command,msg,callback) {
135
+ if (connectionStatus.connected)
136
+ sendTo(target,command,msg,callback);
137
+ else if (callback) callback({error:'Cannot execute command - adapter is not running'});
138
+ }
67
139
 
68
140
  function getDeviceByID(ID) {
69
141
  if (devices) return devices.find((devInfo) => {
70
- try {
71
- return devInfo._id == ID;
72
- } catch (e) {
73
- //console.log("No dev with ieee " + ieeeAddr);
74
- }
142
+ return (devInfo ? devInfo._id : '') == ID;
75
143
  });
76
144
  }
77
145
 
78
- function getDevice(ieeeAddr) {
146
+ function getDeviceByIEEE(ieeeAddr) {
79
147
  return devices.find((devInfo) => {
80
148
  try {
81
149
  return devInfo.info.device.ieee == ieeeAddr;
82
150
  } catch (e) {
83
- //console.log("No dev with ieee " + ieeeAddr);
151
+ return false;
84
152
  }
85
153
  });
86
154
  }
@@ -91,7 +159,7 @@ function getDeviceByNetwork(nwk) {
91
159
  try {
92
160
  return devInfo.info.device.nwk == nwk;
93
161
  } catch (e) {
94
- //console.log("No dev with nwkAddr " + nwk);
162
+ return false;
95
163
  }
96
164
  });
97
165
  }
@@ -112,6 +180,270 @@ function getLQICls(value) {
112
180
  return 'icon-green';
113
181
  }
114
182
 
183
+ function sanitizeModelParameter(parameter) {
184
+ const replaceByUnderscore = /[\s/]/g;
185
+ try {
186
+ return parameter.replace(replaceByUnderscore, '_');
187
+ }
188
+ catch {}
189
+ return parameter;
190
+ }
191
+
192
+ /////
193
+ //
194
+ // Section Local Data
195
+ //
196
+ ////
197
+
198
+ const LocalDataDisplayValues = {
199
+ unfoldedModels : {}, // { plug01: {devices: true/false, options: true/false}}
200
+ unfoldedDevices : {}, // { 0xdeadbeefdeadbeef: true/false}
201
+ buttonSet: new Set(),
202
+ showModels: true,
203
+ }
204
+
205
+
206
+ function getModelData(data, models) {
207
+ const Html = [];
208
+ // Html.push(`<ul class="collapsible">`);
209
+ const s = new Set();
210
+ for (const k of Object.keys(models)) {
211
+ const model = models[k];
212
+ const key = model.model.model;
213
+ console.warn(`getmodeldata: model is ${key}, sO: ${JSON.stringify(model.setOptions)}`);
214
+ const numOptions = Object.keys(model.setOptions).length;
215
+ const foldData = LocalDataDisplayValues.unfoldedModels[k] || { devices:false, options:false};
216
+ let numrows = 1;
217
+ if (foldData.devices) numrows += model.devices.length;
218
+ if (numOptions > 0) numrows += 1;
219
+ if (foldData.options) numrows += numOptions;
220
+ //const numrows = (foldData.devices ? model.devices.length : 0) + (foldData.options ? numOptions : 0) + numOptions > 0 ? 2 : 1;
221
+ console.warn(`numrows is ${numrows} with ${model.devices.length} ${foldData.devices ? 'shown' : 'hidden'} devices and ${numOptions} options ${foldData.options ? 'shown' : 'hidden'}`);
222
+ const d_btn_name = `d_toggle_${k}`;
223
+ const e_btn_name = `m_edit_${k}`;
224
+ const d_btn_tip = `fold / unfold devices of ${key}`;
225
+ const e_btn_tip = `edit model ${key}`;
226
+ const d_btn = btnParam(d_btn_name, d_btn_tip, foldData.devices ? 'expand_less' : 'expand_more', false);
227
+ const e_btn = btnParam(e_btn_name, e_btn_tip, 'edit', 'green', false)
228
+ LocalDataDisplayValues.buttonSet.add(d_btn_name);
229
+ LocalDataDisplayValues.buttonSet.add(e_btn_name);
230
+ Html.push(`<tr id="datarowodd"><td rowspan="${numrows}"><img src = ${model.icon} alt="" width="80" height="auto"></td><td colspan="2">Devices of Model ${key}</td><td>${d_btn}&nbsp;${e_btn}</td></tr>`)
231
+ let cnt = 0;
232
+ if (foldData.devices) {
233
+ for (const dev of model.devices) {
234
+ let devieee = dev._id.replace(`${namespace}.`, '');
235
+
236
+ if (devieee == undefined) devieee = 'unknown' + cnt++;
237
+ const bn = `d_edit_${devieee}`
238
+ Html.push(`<tr id="datarowopt"><td>${devieee}</td><td>${dev.common.name}</td><td>${btnParam(bn, 'edit '+ devieee, 'edit', 'lime', true)}</td></tr>`)
239
+ }
240
+ }
241
+ if (numOptions > 0) {
242
+ const o_btn_name = `o_toggle_${k}`;
243
+ const o_btn_tip = `fold / unfold options for Model ${key}`;
244
+ LocalDataDisplayValues.buttonSet.add(o_btn_name);
245
+ Html.push(`<tr id="dataroweven"></td><td colspan="2">Options for ${key}</td><td>${btnParam(o_btn_name, o_btn_tip, foldData.options ? 'expand_less' : 'expand_more')}</td></tr>`)
246
+ if (foldData.options) {
247
+ for (const key of Object.keys(model.setOptions)) {
248
+ Html.push(`<tr id="dataroweven"><td>${key}</td><td ${model.setOptions[key] === undefined ? 'id="datared">"not set on model"' : '>'+model.setOptions[key]}</td><td>&nbsp;</td></tr>`)
249
+ }
250
+ }
251
+ }
252
+
253
+ }
254
+ return Html;
255
+ }
256
+
257
+ function btnParam(id, tooltip, icon, color, disabled) {
258
+ return `<a id="${id}" class="btn-floating waves-effect waves-light right ${color ? color : 'blue'} ${disabled ? 'disabled ' : ''}tooltipped center-align hoverable translateT" title="${tooltip}"><i class="material-icons large">${icon}</i></a>`;
259
+ }
260
+
261
+
262
+ function getDeviceData(deviceList, withIcon) {
263
+ const Html = [];
264
+ for (const dev of deviceList) {
265
+ const rowspan = dev.options ? Object.keys(dev.options).length + 2 : 2;
266
+ const iconLink = `<img src=${dev.common.icon} class="dev_list">`;
267
+ const devieee = dev._id.replace(`${namespace}.`, '');
268
+ const o_btn_name = `do_toggle_${devieee}`;
269
+ const o_btn_tip = `fold / unfold options for ${devieee}`;
270
+ LocalDataDisplayValues.buttonSet.add(o_btn_name);
271
+ const bn = `f_edit_${devieee}`
272
+ LocalDataDisplayValues.buttonSet.add(bn);
273
+
274
+ Html.push(`<tr id="datarowodd"><td rowspan="${rowspan}">${iconLink}</td><td colspan="2">${dev.common.name} (${devieee})</td><td>${btnParam(o_btn_name, o_btn_tip, LocalDataDisplayValues.unfoldedDevices ? 'do_not_disturb' : 'add_circle')}</td></tr>`);
275
+ Html.push(`<tr id="dataroweven"><td colspan="2">Device flags</td><td>${btnParam(bn, 'edit flags','edit')}</td></tr>`);
276
+ //console.warn(`dev is ${JSON.stringify(dev)}`);
277
+ if (dev.options && LocalDataDisplayValues.unfoldedDevices[devieee]) {
278
+ for (const o of dev.options) {
279
+ const bn = `o_edit_${devieee}.${o.key}`
280
+ LocalDataDisplayValues.buttonSet.add(bn);
281
+ Html.push(`<tr id="datarowopt"><td>${o.key}></td><td>${o.value}</td><td>${btnParam(bn, 'edit flags','edit')}</td></tr>`);
282
+ }
283
+ }
284
+ }
285
+ return Html;
286
+ }
287
+
288
+ function showLocalData() {
289
+ LocalDataDisplayValues.buttonSet.clear();
290
+ const ModelHtml = getModelData(devices, models);
291
+ const DeviceHtml = getDeviceData(devices);
292
+ const sm = LocalDataDisplayValues.showModels;
293
+ const dmtoggle = btnParam('t_all_models', sm ? 'fold Models / show Devices' : 'fold Devices / show Models', !sm ? 'developer_board' : 'devices_other')
294
+
295
+ const RowSpan = sm ? ModelHtml.length +2 : DeviceHtml.length + 2;
296
+ const Html = [];
297
+ if (sm) {
298
+ Html.push(`<table style="width:100%"><tr id="datatable"><th rowspan="${RowSpan}">&nbsp;</th><th colspan=3>Model Data</th><th>${dmtoggle}</th><th rowspan="${RowSpan}">&nbsp;</th></tr>`)
299
+ Html.push(ModelHtml.join(''));
300
+ }
301
+ else {
302
+ Html.push(`<table style="width:100%"><tr id="datatable"><th rowspan="${RowSpan}">&nbsp;</th><th colspan=3>Device Data</th><th>${dmtoggle}</th><th rowspan="${RowSpan}">&nbsp;</th></tr>`)
303
+ Html.push(DeviceHtml.join(''));
304
+ }
305
+ Html.push(`<tr id="datatable"><td colspan="4">Statistics</td></tr>`)
306
+ Html.push('</table>');
307
+ $('#tab-overrides').html(Html.join(''));
308
+
309
+ $('#t_all_models').click(function () {
310
+ LocalDataDisplayValues.showModels = !LocalDataDisplayValues.showModels;
311
+ showLocalData();
312
+ });
313
+
314
+ console.warn(`lddv is ${JSON.stringify(LocalDataDisplayValues)}`)
315
+ for (const item of LocalDataDisplayValues.buttonSet) {
316
+ console.warn(`adding click to ${item}`)
317
+ if (item.startsWith('d_toggle_')) $(`#${item}`).click(function () {
318
+ const key = item.substring(9);
319
+ console.warn(`clicked ${item}`);
320
+ if (LocalDataDisplayValues.unfoldedModels.hasOwnProperty(key))
321
+ LocalDataDisplayValues.unfoldedModels[key].devices =! LocalDataDisplayValues.unfoldedModels[key].devices;
322
+ else
323
+ LocalDataDisplayValues.unfoldedModels[key] = { devices:true, options: false };
324
+ showLocalData();
325
+ });
326
+ if (item.startsWith('o_toggle_')) $(`#${item}`).click(function () {
327
+ console.warn(`clicked ${item}`);
328
+ const key = item.substring(9);
329
+ if (LocalDataDisplayValues.unfoldedModels.hasOwnProperty(key))
330
+ LocalDataDisplayValues.unfoldedModels[key].options = !LocalDataDisplayValues.unfoldedModels[key].options;
331
+ else
332
+ LocalDataDisplayValues.unfoldedModels[key] = { devices:false, options: true };
333
+ showLocalData();
334
+ })
335
+ if (item.startsWith('do_toggle_')) $(`#${item}`).click(function () {
336
+ console.warn(`clicked ${item}`);
337
+ const key = item.substring(10);
338
+ if (LocalDataDisplayValues.unfoldedDevices.hasOwnProperty(key))
339
+ LocalDataDisplayValues.unfoldedDevices[key] =! LocalDataDisplayValues.unfoldedDevices[key];
340
+ else
341
+ LocalDataDisplayValues.unfoldedDevices[key] = true;
342
+ showLocalData();
343
+ })
344
+
345
+ }
346
+
347
+
348
+ }
349
+
350
+ /////
351
+ //
352
+ // Section Cards
353
+ //
354
+ ////
355
+
356
+ function getCard(dev) {
357
+ if (!dev._id) return '';
358
+ const title = dev.common.name,
359
+ id = (dev._id ? dev._id : ''),
360
+ type = (dev.common.type ? dev.common.type : 'unknown'),
361
+ type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'),
362
+ img_src = dev.common.icon || dev.icon,
363
+ rooms = [],
364
+ isActive = (dev.common.deactivated ? false : true),
365
+ lang = systemLang || 'en',
366
+ ieee = id.replace(namespace + '.', ''),
367
+ isDebug = checkDebugDevice(ieee);
368
+ for (const r in dev.rooms) {
369
+ if (dev.rooms[r].hasOwnProperty(lang)) {
370
+ rooms.push(dev.rooms[r][lang]);
371
+ } else {
372
+ rooms.push(dev.rooms[r]);
373
+ }
374
+ }
375
+ const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
376
+ const rid = id.split('.').join('_');
377
+ const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
378
+ const groupInfo = dev.groupNames ? `<li><span class="labelinfo">groups:</span><span>${dev.groupNames || ''}</span></li>` : '';
379
+ const roomInfo = rooms.length ? `<li><span class="labelinfo">rooms:</span><span>${rooms.join(',') || ''}</span></li>` : '';
380
+ const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
381
+ nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
382
+ battery_cls = (isActive ? getBatteryCls(dev.battery) : ''),
383
+ lqi_cls = getLQICls(dev.link_quality),
384
+ battery = (dev.battery && isActive) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
385
+ lq = (dev.link_quality > 0)
386
+ ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>`
387
+ : `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`,
388
+ status = (isActive ? lq : `<div class="col tool"><i class="material-icons icon-red">cancel</i></div>`),
389
+ info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
390
+ <ul>
391
+ <li><span class="labelinfo">ieee:</span><span>0x${ieee}</span></li>
392
+ <li><span class="labelinfo">nwk:</span><span>${(nwk) ? nwk.toString() + ' (0x' + nwk.toString(16) + ')' : ''}</span></li>
393
+ <li><span class="labelinfo">model:</span><span>${modelUrl}</span></li>
394
+ ${groupInfo}
395
+ ${roomInfo}
396
+ </ul>
397
+ </div>`,
398
+ deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive ? 'Deactivate' : 'Activate')}"><i class="material-icons ${(isActive ? 'icon-green' : 'icon-red')}">power_settings_new</i></button>`,
399
+ debugBtn = `<button name="swapdebug" class="right btn-flat btn-small tooltipped" title="${(isDebug > -1 ? (isDebug > 0) ?'Automatic by '+debugDevices[isDebug-1]: 'Disable Debug' : 'Enable Debug')}"><i class="material-icons icon-${(isDebug > -1 ? (isDebug > 0 ? 'orange' : 'green') : 'gray')}">bug_report</i></button>`,
400
+ infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
401
+
402
+ const dashCard = getDashCard(dev);
403
+ const card = `<div id="${id}" class="device">
404
+ <div class="card hoverable flipable ${isActive ? '' : 'bg_red'}">
405
+ <div class="front face">${dashCard}</div>
406
+ <div class="back face">
407
+ <div class="card-content zcard">
408
+ <div class="flip" style="cursor: pointer">
409
+ <span class="top right small" style="border-radius: 50%">
410
+ ${battery}
411
+ <!--${lq}-->
412
+ ${status}
413
+ </span>
414
+ <!--/a--!>
415
+ <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
416
+ </div>
417
+ <i class="left">${image}</i>
418
+ ${info}
419
+ <div class="footer right-align"></div>
420
+ </div>
421
+ <div class="card-action">
422
+ <div class="card-reveal-buttons">
423
+ ${infoBtn}
424
+
425
+ <span class="left fw_info"></span>
426
+ <button name="delete" class="right btn-flat btn-small tooltipped" title="Delete">
427
+ <i class="material-icons icon-red">delete</i>
428
+ </button>
429
+ <button name="edit" class="right btn-flat btn-small tooltipped" title="Edit">
430
+ <i class="material-icons icon-black">edit</i>
431
+ </button>
432
+ <button name="swapimage" class="right btn-flat btn-small tooltipped" title="Select Image">
433
+ <i class="material-icons icon-black">image</i>
434
+ </button>
435
+ <button name="reconfigure" class="right btn-flat btn-small tooltipped" title="Reconfigure">
436
+ <i class="material-icons icon-red">sync</i>
437
+ </button>
438
+ ${deactBtn}
439
+ ${debugBtn}
440
+ </div>
441
+ </div>
442
+ </div>
443
+ </div>
444
+ </div>`;
445
+ return card;
446
+ }
115
447
 
116
448
  function getCoordinatorCard(dev) {
117
449
  const title = 'Zigbee Coordinator',
@@ -228,103 +560,174 @@ function getGroupCard(dev) {
228
560
  return card;
229
561
  }
230
562
 
231
- function sanitizeModelParameter(parameter) {
232
- const replaceByUnderscore = /[\s/]/g;
233
- return parameter.replace(replaceByUnderscore, '_');
563
+ function getDeviceCards() {
564
+ return $('#devices .device').not('.group');
234
565
  }
235
566
 
236
- function getCard(dev) {
237
- //console.warn(JSON.stringify(dev));
238
- if (!dev._id) return '';
567
+ function getDeviceCard(devId) {
568
+ if (devId.startsWith('0x')) {
569
+ devId = devId.substr(2, devId.length);
570
+ }
571
+ return $('#devices').find(`div[id='${namespace}.${devId}']`);
572
+ }
573
+
574
+ function getDashCard(dev, groupImage, groupstatus) {
239
575
  const title = dev.common.name,
240
- id = (dev._id ? dev._id : ''),
241
- type = (dev.common.type ? dev.common.type : 'unknown'),
242
- type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'),
243
- img_src = dev.common.icon || dev.icon,
576
+ id = dev._id,
577
+ type = dev.common.type,
578
+ img_src = (groupImage ? groupImage : dev.common.icon || dev.icon),
579
+ isActive = !dev.common.deactivated,
244
580
  rooms = [],
245
- isActive = (dev.common.deactivated ? false : true),
246
- lang = systemLang || 'en',
247
- ieee = id.replace(namespace + '.', ''),
248
- isDebug = checkDebugDevice(ieee);
249
- for (const r in dev.rooms) {
250
- if (dev.rooms[r].hasOwnProperty(lang)) {
251
- rooms.push(dev.rooms[r][lang]);
252
- } else {
253
- rooms.push(dev.rooms[r]);
254
- }
255
- }
581
+ lang = systemLang || 'en';
256
582
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
583
+ const permitJoinBtn = dev.battery || dev.common.type == 'group' ? '' : `<div class="col tool"><button name="joinCard" class="waves-effect btn-small btn-flat right hoverable green"><i class="material-icons icon-green">leak_add</i></button></div>`;
584
+ const device_queryBtn = dev.battery || dev.common.type == 'group' ? '' : `<div class="col tool"><button name="deviceQuery" class="waves-effect btn-small btn-flat right hoverable green"><i class="material-icons icon-green">play_for_work</i></button></div>`;
257
585
  const rid = id.split('.').join('_');
258
- const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
259
- const groupInfo = dev.groupNames ? `<li><span class="labelinfo">groups:</span><span>${dev.groupNames || ''}</span></li>` : '';
260
- const roomInfo = rooms.length ? `<li><span class="labelinfo">rooms:</span><span>${rooms.join(',') || ''}</span></li>` : '';
261
- const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
586
+ const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
587
+ const image = `<img src="${img_src}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
262
588
  nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
263
- battery_cls = (isActive ? getBatteryCls(dev.battery) : ''),
589
+ battery_cls = getBatteryCls(dev.battery),
264
590
  lqi_cls = getLQICls(dev.link_quality),
591
+ unconnected_icon = (groupImage ? (groupstatus ? '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>' : '<div class="col tool"><i class="material-icons icon-red">cancel</i></div>') :'<div class="col tool"><i class="material-icons icon-red">leak_remove</i></div>'),
265
592
  battery = (dev.battery && isActive) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
266
- lq = (dev.link_quality > 0)
267
- ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>`
268
- : `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`,
269
- status = (isActive ? lq : `<div class="col tool"><i class="material-icons icon-red">cancel</i></div>`),
270
- info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
271
- <ul>
272
- <li><span class="labelinfo">ieee:</span><span>0x${ieee}</span></li>
273
- <li><span class="labelinfo">nwk:</span><span>${(nwk) ? nwk.toString() + ' (0x' + nwk.toString(16) + ')' : ''}</span></li>
274
- <li><span class="labelinfo">model:</span><span>${modelUrl}</span></li>
275
- ${groupInfo}
276
- ${roomInfo}
277
- </ul>
278
- </div>`,
279
- deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive ? 'Deactivate' : 'Activate')}"><i class="material-icons ${(isActive ? 'icon-green' : 'icon-red')}">power_settings_new</i></button>`,
280
- debugBtn = `<button name="swapdebug" class="right btn-flat btn-small tooltipped" title="${(isDebug > -1 ? (isDebug > 0) ?'Automatic by '+debugDevices[isDebug-1]: 'Disable Debug' : 'Enable Debug')}"><i class="material-icons icon-${(isDebug > -1 ? (isDebug > 0 ? 'orange' : 'green') : 'gray')}">bug_report</i></button>`,
281
- infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
593
+ lq = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : (isActive ? unconnected_icon : ''),
594
+ //status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (groupImage || !isActive ? '' : `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`),
595
+ //infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
596
+ idleTime = (dev.link_quality_lc > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_lc_icon" class="material-icons idletime">access_time</i><div id="${rid}_link_quality_lc" class="center" style="font-size:0.7em">${getIdleTime(dev.link_quality_lc)}</div></div>` : '';
597
+ const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => {
598
+ const id = stateDef.id;
599
+ const sid = id.split('.').join('_');
600
+ let val = stateDef.val || '';
601
+ if (stateDef.role === 'switch' && stateDef.write) {
602
+ val = `<span class="switch"><label><input type="checkbox" ${(val) ? 'checked' : ''}><span class="lever"></span></label></span>`;
603
+ } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
604
+ val = `<span class="range-field dash"><input type="range" min="0" max="100" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
605
+ } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
606
+ val = `<span class="range-field dash"><input type="range" min="150" max="500" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
607
+ } else if (stateDef.type === 'boolean') {
608
+ const disabled = (stateDef.write) ? '' : 'disabled="disabled"';
609
+ val = `<label class="dash"><input type="checkbox" ${(val == true) ? 'checked=\'checked\'' : ''} ${disabled}/><span></span></label>`;
610
+ } else if (stateDef.role === 'level.color.rgb') {
611
+ const options = []
612
+ for (const key of namedColors) {
613
+ options.push(`<option value="${key}" ${val===key ? 'selected' : ''}>${key}</option>`);
614
+ }
615
+ val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
616
+ } else if (stateDef.states && stateDef.write) {
617
+ let options;
618
+ if (typeof stateDef.states == 'string') {
619
+ const sts = stateDef.states.split(';');
620
+ if (sts.length < 2) return '';
621
+ options = sts.map((item) => {
622
+ const v = item.split(':');
623
+ return `<option value="${v[0]}" ${(val == v[0]) ? 'selected' : ''}>${v[1]}</option>`;
624
+ });
625
+ } else {
626
+ options = [];
627
+ for (const [key, value] of Object.entries(stateDef.states)) {
628
+ options.push(`<option value="${key}" ${(val == key) ? 'selected' : ''}>${key}</option>`);
629
+ }
630
+ }
631
+ if (options.length < 2) return '';
632
+ val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
633
+ } else if (stateDef.write) {
634
+ return;
635
+ // val = `<span class="input-field dash value"><input class="dash value" id="${stateDef.name}" value="${val}"></input></span>`;
636
+ }
637
+ else {
638
+ val = `<span class="dash value">${val ? val : '(null)'} ${(stateDef.unit) ? stateDef.unit : ''}</span>`;
639
+ }
640
+ return `<li><span class="label dash truncate">${stateDef.name}</span><span id=${sid} oid=${id} class="state">${val}</span></li>`;
641
+ }).join('') : '';
642
+ const dashCard = `
643
+ <div class="card-content zcard ${isActive ? '' : 'bg_red'}">
644
+ <div style="cursor: pointer">
645
+ <span class="top right small" style="border-radius: 50%">
646
+ ${device_queryBtn}
647
+ ${permitJoinBtn}
648
+ </span>
649
+ <div class="flip">
650
+ <span class="top right small" style="border-radius: 50%">
651
+ ${idleTime}
652
+ ${battery}
653
+ ${lq}
654
+ </span>
655
+ <span class="card-title truncate">${title}</span>
656
+ </div>
657
+ </div>
658
+ <i class="left">${image}</i>
659
+ <div style="min-height:88px; font-size: 0.8em; height: 130px; width: 220px; overflow-y: auto" class="truncate">
660
+ <ul>
661
+ ${(isActive ? info : 'Device deactivated')}
662
+ </ul>
663
+ </div>
664
+ <div class="footer right-align"></div>
665
+ </div>`;
282
666
 
283
- const dashCard = getDashCard(dev);
284
- const card = `<div id="${id}" class="device">
285
- <div class="card hoverable flipable ${isActive ? '' : 'bg_red'}">
286
- <div class="front face">${dashCard}</div>
287
- <div class="back face">
288
- <div class="card-content zcard">
289
- <div class="flip" style="cursor: pointer">
290
- <span class="top right small" style="border-radius: 50%">
291
- ${battery}
292
- <!--${lq}-->
293
- ${status}
294
- </span>
295
- <!--/a--!>
296
- <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
297
- </div>
298
- <i class="left">${image}</i>
299
- ${info}
300
- <div class="footer right-align"></div>
301
- </div>
302
- <div class="card-action">
303
- <div class="card-reveal-buttons">
304
- ${infoBtn}
667
+ return dashCard;
668
+ }
669
+
670
+ function setDashStates(id, state) {
671
+ const devId = getDevId(id);
672
+ const dev = getDeviceByID(devId);
673
+ if (dev) {
674
+ const stateDef = dev.statesDef.find((stateDef) => stateDef.id == id);
675
+ if (stateDef) {
676
+ const sid = id.split('.').join('_');
677
+ if (stateDef.role === 'switch' && stateDef.write) {
678
+ $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
679
+ } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
680
+ $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
681
+ } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
682
+ $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
683
+ } else if (stateDef.states && stateDef.write) {
684
+ $(`#${sid}`).find(`select option[value=${state.val}]`).prop('selected', true);
685
+ } else if (stateDef.type === 'boolean') {
686
+ $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
687
+ } else {
688
+ $(`#${sid}`).find('.value').text(`${state.val} ${(stateDef.unit) ? stateDef.unit : ''}`);
689
+ }
690
+ }
691
+ }
692
+ }
693
+
694
+ function hookControls() {
695
+ $('input[type=\'checkbox\']').change(function (event) {
696
+ const val = $(this).is(':checked');
697
+ const id = $(this).parents('.state').attr('oid');
698
+ sendToWrapper(namespace, 'setState', {id: id, val: val}, function (data) {
699
+ });
700
+ });
701
+ $('input[type=\'range\']').change(function (event) {
702
+ const val = $(this).val();
703
+ const id = $(this).parents('.state').attr('oid');
704
+ sendToWrapper(namespace, 'setState', {id: id, val: val}, function (data) {
705
+ });
706
+ });
707
+ $('.state select').on('change', function () {
708
+ const val = $(this).val();
709
+ const id = $(this).parents('.state').attr('oid');
710
+ sendToWrapper(namespace, 'setState', {id: id, val: val}, function (data) {
711
+ });
712
+ });
713
+ }
305
714
 
306
- <span class="left fw_info"></span>
307
- <button name="delete" class="right btn-flat btn-small tooltipped" title="Delete">
308
- <i class="material-icons icon-red">delete</i>
309
- </button>
310
- <button name="edit" class="right btn-flat btn-small tooltipped" title="Edit">
311
- <i class="material-icons icon-black">edit</i>
312
- </button>
313
- <button name="swapimage" class="right btn-flat btn-small tooltipped" title="Select Image">
314
- <i class="material-icons icon-black">image</i>
315
- </button>
316
- <button name="reconfigure" class="right btn-flat btn-small tooltipped" title="Reconfigure">
317
- <i class="material-icons icon-red">sync</i>
318
- </button>
319
- ${deactBtn}
320
- ${debugBtn}
321
- </div>
322
- </div>
323
- </div>
324
- </div>
325
- </div>`;
326
- return card;
715
+ function getIdleTime(value) {
716
+ return (value) ? moment(new Date(value)).fromNow(true) : '';
717
+ }
718
+
719
+ function updateCardTimer() {
720
+ if (devices) {
721
+ devices.forEach((dev) => {
722
+ const id = dev._id;
723
+ if (id) {
724
+ const rid = id.split('.').join('_');
725
+ $(`#${rid}_link_quality_lc`).text(getIdleTime(dev.link_quality_lc));
726
+ }
727
+ });
728
+ }
327
729
  }
730
+
328
731
  /*
329
732
  function openReval(e, id, name){
330
733
  const $card = $(e.target).closest('.card');
@@ -379,6 +782,17 @@ function closeReval(e, id) {
379
782
  });
380
783
  }
381
784
 
785
+ function showDevInfo(id) {
786
+ const info = genDevInfo(getDeviceByID(id));
787
+ $('#devinfo').html(info);
788
+ $('#modaldevinfo').modal('open');
789
+ }
790
+
791
+ ////
792
+ //
793
+ // section Confirmations
794
+ //
795
+ ////
382
796
  function deleteConfirmation(id, name) {
383
797
  const text = translateWord('Do you really want to delete device') + ' "' + name + '" (' + id + ')?';
384
798
  $('#modaldelete').find('p').text(text);
@@ -402,7 +816,7 @@ function deleteNvBackupConfirmation() {
402
816
  $('#modaldelete a.btn[name=\'yes\']').click(() => {
403
817
  //const force = $('#force').prop('checked');
404
818
  showWaitingDialog('Attempting to delete nvBackup.json', 60000);
405
- sendTo(namespace, 'deleteNVBackup', {}, function (msg) {
819
+ sendToWrapper(namespace, 'deleteNVBackup', {}, function (msg) {
406
820
  closeWaitingDialog();
407
821
  if (msg) {
408
822
  if (msg.error) {
@@ -444,211 +858,62 @@ function EndPointIDfromEndPoint(ep) {
444
858
 
445
859
  function editName(id, name) {
446
860
 
447
- const device_options = {};
448
- const received_options = {};
449
- console.warn('editName called with ' + id + ' and ' + name);
450
- const dev = devices.find((d) => d._id == id);
451
- $('#modaledit').find('input[id=\'d_name\']').val(name);
452
- const groupables = [];
453
-
454
- function removeOption(k) {
455
- if (k && device_options.hasOwnProperty(k)) {
456
- if (dev.info.mapped && dev.info.mapped.options && dev.info.mapped.options.includes(device_options[k].key))
457
- availableOptions.push(device_options[k].key)
458
- delete device_options[k];
459
- }
460
- }
461
-
462
- function addOption() {
463
- let idx=1;
464
- let key = '';
465
- const optionName = $('#option_Selector').val();
466
- console.warn(`option name is ${optionName}`);
467
- do {
468
- key = `o${idx++}`;
469
- }
470
- while (device_options.hasOwnProperty(key));
471
- device_options[key] = { key:optionName, value:''};
472
- console.warn(`device_options: ${JSON.stringify(device_options)}`);
473
- idx = availableOptions.indexOf(optionName);
474
- console.warn(`idx: ${idx}, ao:${JSON.stringify(availableOptions)}, on: ${optionName}`);
475
- if (idx > -1) availableOptions.splice(idx, 1);
476
- }
477
-
478
- function updateOptions(candidates) {
479
- if (candidates.length > 0) {
480
- $('#modaledit').find('.new_options_available').removeClass('hide');
481
- list2select('#option_Selector', candidates, [], (key, val) => { return val; }, (key, val) => { return val; })
482
- }
483
- else {
484
- $('#modaledit').find('.new_options_available').addClass('hide');
485
- }
486
- const html_options=[];
487
-
488
- console.warn(`option_Selector is ${JSON.stringify(device_options)}`)
489
-
490
- for (const k in device_options) {
491
- html_options.push(`<div class="row">`);
492
- html_options.push(`<div class="input-field suffix col s5 m5 l5"><input disabled id="option_key_${k}" type="text" class="value" /><label for="option_key_${k}">Option</label></div>`)
493
- 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>`)
494
- html_options.push(`<div class="col"><a id="option_rem_${k}" class='btn' ><i class="material-icons">remove_circle</i></a></div>`);
495
- html_options.push(`</div>`)
496
- }
497
- console.warn(`html is ${$('#modaledit').find('.options_grid').html()}`)
498
- $('#modaledit').find('.options_grid').html(html_options.join(''));
499
- console.warn(`html is now ${$('#modaledit').find('.options_grid').html()}`)
500
- if (html_options.length > 0) {
501
- $('#modaledit').find('.options_available').removeClass('hide');
502
- for (const k of Object.keys(device_options)) {
503
- $(`#option_key_${k}`).val(device_options[k].key);
504
- $(`#option_value_${k}`).val(device_options[k].value);
505
- $(`#option_rem_${k}`).unbind('click');
506
- $(`#option_rem_${k}`).click(() => { removeOption(k); updateOptions(availableOptions) });
507
- }
508
- }
509
- else {
510
- if (candidates.length == 0) $('#modaledit').find('.options_available').addClass('hide');
511
- }
512
- }
513
-
514
- function getOptionsFromUI(_do, _so) {
515
- const _no = {};
516
- let changed = false;
517
- for (const k in _do) {
518
- const key = $(`#option_key_${k}`).val();
519
- _do[k].key = key;
520
- const val = $(`#option_value_${k}`).val();
521
- try {
522
- _do[k].value = JSON.parse(val);
523
- }
524
- catch {
525
- _do[k].value = val;
861
+ function updateGroupables(groupables) {
862
+ const html = [];
863
+ if (groupables && groupables.length > 0)
864
+ {
865
+ for (const groupable of groupables) {
866
+ const k = groupable.ep.ID || -1;
867
+ const n = groupable.epid != `unidentified` ? groupable.epid : `Endpoint ${k}`;
868
+ html.push(`<div class="input-field suffix col s12 m12 l12"><select id="gk_${k}" class="materialSelect" multiple><option value="1">select</option><select><label for="gk_${k}">Group membership for ${n}</label></div>`);
526
869
  }
527
- if (device_options[k].key.length > 0) {
528
- _no[key] = device_options[k].value;
529
- changed |= _no[key] != _so[key];
870
+ $('#modaledit').find('.endpoints_for_groups').html(html.join(''));
871
+ for (const groupable of groupables) {
872
+ console.warn(`list 2 select called with ${groupable.ep.ID}, groups ${JSON.stringify(groups)}, groupable ${JSON.stringify(groupable)}`);
873
+ list2select(`#gk_${groupable.ep.ID || -1}`, groups, groupable.memberOf || []);
530
874
  }
531
875
  }
532
- changed |= (Object.keys(_no).length != Object.keys(_so).length);
533
- if (changed) return _no;
534
- return undefined;
876
+ return html;
535
877
  }
536
878
 
537
879
 
538
-
880
+ const dev = devices.find((d) => d._id == id);
881
+ $('#modaledit').find('input[id=\'d_name\']').val(name);
882
+ const groupables = [];
539
883
  if (dev && dev.info && dev.info.endpoints) {
540
884
  for (const ep of dev.info.endpoints) {
541
885
  if (ep.input_clusters.includes(4)) {
542
- groupables.push({epid: EndPointIDfromEndPoint(ep), ep: ep, memberOf: []});
886
+ groupables.push({epid: EndPointIDfromEndPoint(ep), ep: ep, memberOf: dev.groups_by_ep ? dev.groups_by_ep[ep.ID] || [] : []});
543
887
  }
544
888
  }
545
889
  }
546
890
  const numEP = groupables.length;
547
- const availableOptions = (dev.info.mapped ? dev.info.mapped.options.slice() || []:[]);
548
-
549
- if (numEP > 0) {
550
- $('#modaledit').find('.groups_available').removeClass('hide');
551
- $('#modaledit').find('.row.epid0').addClass('hide');
552
- $('#modaledit').find('.row.epid1').addClass('hide');
553
- $('#modaledit').find('.row.epid2').addClass('hide');
554
- $('#modaledit').find('.row.epid3').addClass('hide');
555
- $('#modaledit').find('.row.epid4').addClass('hide');
556
- $('#modaledit').find('.row.epid5').addClass('hide');
557
- $('#modaledit').find('.row.epid6').addClass('hide');
558
- // go through all the groups. Find the ones to list for each groupable
559
- if (numEP == 1) {
560
- $('#modaledit').find('.endpointid').addClass('hide');
561
- } else {
562
- $('#modaledit').find('.endpointid').removeClass('hide');
563
- }
564
- for (const d of devices) {
565
- if (d && d.common && d.common.type == 'group') {
566
- if (d.hasOwnProperty('memberinfo')) {
567
- for (const member of d.memberinfo) {
568
- const epid = EndPointIDfromEndPoint(member.ep);
569
- for (let i = 0; i < groupables.length; i++) {
570
- if (groupables[i].epid == epid) {
571
- groupables[i].memberOf.push(d.native.id.replace('group_', ''));
572
- }
573
- }
574
- }
575
- }
576
- }
577
- }
578
- console.log('groupables: ' + JSON.stringify(groupables));
579
- for (let i = 0; i < groupables.length; i++) {
580
- if (i > 1) {
581
- $('#modaledit').find('translate.device_with_endpoint').innerHtml = name + ' ' + groupables[i].epid;
582
- }
583
- $('#modaledit').find('.row.epid' + i).removeClass('hide');
584
- list2select('#d_groups_ep' + i, groups, groupables[i].memberOf || []);
585
- }
586
- }
587
- else
588
- {
589
- $('#modaledit').find('.groups_available').addClass('hide');
590
- }
591
- sendTo(namespace, 'getLocalConfigItems', { target:id, global:false, key:'options' }, function (msg) {
592
- if (msg) {
593
- if (msg.error) showMessage(msg.error, '_Error');
594
- console.warn(`return is ${JSON.stringify(msg)}`)
595
- Object.keys(device_options).forEach(key => delete device_options[key]);
596
- Object.keys(received_options).forEach(key => delete received_options[key]);
597
- if (typeof msg.options === 'object') {
598
-
599
- let cnt = 1;
600
- for (const key in msg.options)
601
- {
602
- const idx = availableOptions.indexOf(key);
603
- console.warn(`key ${key} : index : ${idx}`);
604
- if (idx > -1) availableOptions.splice(idx,1);
605
- received_options[key]=msg.options[key];
606
- device_options[`o${cnt}`] = { key:key, value:msg.options[key]}
607
- cnt++;
608
- }
609
- }
610
- console.warn(`avo ${JSON.stringify(availableOptions)}, mapped: ${JSON.stringify(dev.info.mapped.options)}`);
611
- updateOptions(availableOptions);
612
891
 
613
- } else showMessage('callback without message');
614
- });
892
+ updateGroupables(groupables);
615
893
  $('#modaledit a.btn[name=\'save\']').unbind('click');
616
- $('#modaledit a.btn[name=\'add_options\']').unbind('click');
617
- $('#modaledit a.btn[name=\'add_options\']').click(() => {
618
- getOptionsFromUI(device_options, received_options);
619
- addOption();
620
- updateOptions(availableOptions)
621
- });
622
894
  $('#modaledit a.btn[name=\'save\']').click(() => {
623
895
  const newName = $('#modaledit').find('input[id=\'d_name\']').val();
624
- const groupsbyid = {};
896
+ const groupsById = {};
625
897
  if (groupables.length > 0) {
626
- for (let i = 0; i < groupables.length; i++) {
627
- const ng = $('#d_groups_ep' + i).val();
628
- if (ng.toString() != groupables[i].memberOf.toString())
629
- groupsbyid[groupables[i].ep.ID] = GenerateGroupChange(groupables[i].memberOf, ng);
630
- }
631
- }
632
- // read device_options from UI
633
- const co = getOptionsFromUI(device_options, received_options)
634
- console.warn(`options have ${co ? 'changed' : 'not changed'} : ${JSON.stringify(co)} vs ${JSON.stringify(received_options)} , saving them`);
635
- if (co) {
636
- sendTo(namespace, 'updateLocalConfigItems', {
637
- target: id,
638
- global:false,
639
- data: { options:co }
640
- },
641
- function (msg) {
642
- if (msg && msg.error) showMessage(msg.error, '_Error');
643
- });
898
+ for (const groupable of groupables) {
899
+ const k = groupable.ep.ID || -1;
900
+ const ng = $('#gk_' + k).val();
901
+ if (ng.toString() != groupable.memberOf.toString())
902
+ groupsById[k] = GenerateGroupChange(groupable.memberOf, ng);
903
+ }
644
904
  }
645
- updateDev(id, newName, groupsbyid);
646
-
905
+ updateDev(id, newName, groupsById);
647
906
  });
648
907
  $('#modaledit').modal('open');
649
908
  Materialize.updateTextFields();
650
909
  }
651
910
 
911
+
912
+ ////
913
+ //
914
+ //. section GroupFunctions
915
+ //
916
+ ////
652
917
  function GenerateGroupChange(oldmembers, newmembers) {
653
918
  const grpchng = [];
654
919
  for (const oldg of oldmembers)
@@ -659,7 +924,7 @@ function GenerateGroupChange(oldmembers, newmembers) {
659
924
  }
660
925
 
661
926
  function deleteZigbeeDevice(id, force) {
662
- sendTo(namespace, 'deleteZigbeeDevice', {id: id, force: force}, function (msg) {
927
+ sendToWrapper(namespace, 'deleteZigbeeDevice', {id: id, force: force}, function (msg) {
663
928
  closeWaitingDialog();
664
929
  if (msg) {
665
930
  if (msg.error) {
@@ -674,7 +939,7 @@ function deleteZigbeeDevice(id, force) {
674
939
 
675
940
 
676
941
  function cleanDeviceStates(force) {
677
- sendTo(namespace, 'cleanDeviceStates', {force: force}, function (msg) {
942
+ sendToWrapper(namespace, 'cleanDeviceStates', {force: force}, function (msg) {
678
943
  closeWaitingDialog();
679
944
  if (msg) {
680
945
  if (msg.error) {
@@ -692,7 +957,7 @@ function cleanDeviceStates(force) {
692
957
 
693
958
  function renameDevice(id, name) {
694
959
  showMessage('rename device with ' + id + ' and ' + name, _('Error'));
695
- sendTo(namespace, 'renameDevice', {id: id, name: name}, function (msg) {
960
+ sendToWrapper(namespace, 'renameDevice', {id: id, name: name}, function (msg) {
696
961
  if (msg) {
697
962
  if (msg.error) {
698
963
  showMessage(msg.error, _('Error'));
@@ -704,7 +969,6 @@ function renameDevice(id, name) {
704
969
  }
705
970
 
706
971
  function showDevices() {
707
- console.warn('show Devices called')
708
972
  let html = '';
709
973
  let hasCoordinator = false;
710
974
  const lang = systemLang || 'en';
@@ -776,6 +1040,7 @@ function showDevices() {
776
1040
  return room;
777
1041
  }
778
1042
  }).filter((item) => item != undefined));
1043
+ console.warn(`rooms is ${JSON.stringify(allRooms)}`);
779
1044
  const roomSelector = $('#room-filter');
780
1045
  roomSelector.empty();
781
1046
  roomSelector.append(`<li class="device-order-item" data-type="All" tabindex="0"><a class="translate" data-lang="All">All</a></li>`);
@@ -796,17 +1061,22 @@ function showDevices() {
796
1061
  $('.card.flipable').toggleClass('flipped');
797
1062
  });
798
1063
 
799
- shuffleInstance = new Shuffle($('#devices'), {
800
- itemSelector: '.device',
801
- sizer: '.js-shuffle-sizer',
802
- });
803
- doFilter();
1064
+ const element = $('#devices');
1065
+
1066
+ if ($('tab-main')) try {
1067
+ shuffleInstance = devices && devices.length ? new Shuffle(element, {
1068
+ itemSelector: '.device',
1069
+ sizer: '.js-shuffle-sizer',
1070
+ }) : undefined;
1071
+ doFilter();
1072
+ } catch {
1073
+ // empty.
1074
+ }
804
1075
 
805
1076
  const getDevName = function (dev_block) {
806
1077
  return dev_block.find('#dName').text();
807
1078
  };
808
1079
  const getDevId = function (dev_block) {
809
- console.warn(`getDevId called with ${JSON.stringify(dev_block)}`)
810
1080
  return dev_block.attr('id');
811
1081
  };
812
1082
  $('.card-reveal-buttons button[name=\'delete\']').click(function () {
@@ -856,7 +1126,7 @@ function showDevices() {
856
1126
  //console.log(data);
857
1127
  }); });
858
1128
  $('#modalpairing a.btn[name=\'extendpairing\']').click(function () {
859
- letsPairing();
1129
+ openNetwork();
860
1130
  });
861
1131
  $('#modalpairing a.btn[name=\'endpairing\']').click(function () {
862
1132
  stopPairing();
@@ -874,7 +1144,7 @@ function showDevices() {
874
1144
  });
875
1145
  $('.card-reveal-buttons button[name=\'reconfigure\']').click(function () {
876
1146
  const dev_block = $(this).parents('div.device');
877
- reconfigureDlg(getDevId(dev_block));
1147
+ reconfigureConfirmation(getDevId(dev_block));
878
1148
  });
879
1149
  $('.card-reveal-buttons button[name=\'swapactive\']').click(function () {
880
1150
  const dev_block = $(this).parents('div.device');
@@ -886,7 +1156,7 @@ function showDevices() {
886
1156
  }
887
1157
 
888
1158
  function downloadIcons() {
889
- sendTo(namespace, 'downloadIcons', {}, function (msg) {
1159
+ sendToWrapper(namespace, 'downloadIcons', {}, function (msg) {
890
1160
  if (msg && msg.msg) {
891
1161
  showMessage(msg.msg, _('Result'));
892
1162
  }
@@ -915,9 +1185,8 @@ function checkFwUpdate() {
915
1185
  fwInfoNode.html(createBtn('system_update', 'Click to start firmware update', false));
916
1186
  $(fwInfoNode).find('button[name=\'fw_update\']').click(() => {
917
1187
  fwInfoNode.html(createBtn('check_circle', 'Firmware update started, check progress in logs.', true, 'icon-blue'));
918
- sendTo(namespace, 'startOta', {devId: devId}, (msg) => {
1188
+ sendToWrapper(namespace, 'startOta', {devId: devId}, (msg) => {
919
1189
  fwInfoNode.html(createBtn('check_circle', 'Finished, see logs.', true));
920
- console.log(msg);
921
1190
  });
922
1191
  });
923
1192
  } else if (msg.status == 'not_available') {
@@ -937,13 +1206,13 @@ function checkFwUpdate() {
937
1206
  }
938
1207
  const devId = getDevId(devIdAttr);
939
1208
  getFwInfoNode(deviceCard).html('<span class="left" style="padding-top:8px">checking...</span>');
940
- sendTo(namespace, 'checkOtaAvail', {devId: devId}, callback);
1209
+ sendToWrapper(namespace, 'checkOtaAvail', {devId: devId}, callback);
941
1210
  }
942
1211
  }
943
1212
 
944
1213
  function letsPairingWithCode(code) {
945
1214
  messages = [];
946
- sendTo(namespace, 'letsPairing', {code: code, stop:false}, function (msg) {
1215
+ sendToWrapper(namespace, 'letsPairing', {code: code, stop:false}, function (msg) {
947
1216
  if (msg && msg.error) {
948
1217
  showMessage(msg.error, _('Error'));
949
1218
  }
@@ -953,18 +1222,19 @@ function letsPairingWithCode(code) {
953
1222
  });
954
1223
  }
955
1224
 
956
- function letsPairing() {
1225
+ function openNetwork() {
957
1226
  messages = [];
958
- sendTo(namespace, 'letsPairing', {stop:false}, function (msg) {
1227
+ sendToWrapper(namespace, 'letsPairing', {stop:false}, function (msg) {
959
1228
  if (msg && msg.error) {
960
1229
  showMessage(msg.error, _('Error'));
961
1230
  }
1231
+ else showPairingProcess();
962
1232
  });
963
1233
  }
964
1234
 
965
1235
  function stopPairing() {
966
1236
  messages = [];
967
- sendTo(namespace, 'letsPairing', {stop:true}, function (msg) {
1237
+ sendToWrapper(namespace, 'letsPairing', {stop:true}, function (msg) {
968
1238
  if (msg && msg.error) {
969
1239
  showMessage(msg.error, _('Error'));
970
1240
  }
@@ -973,7 +1243,7 @@ function stopPairing() {
973
1243
 
974
1244
  function touchlinkReset() {
975
1245
  messages = [];
976
- sendTo(namespace, 'touchlinkReset', {}, function (msg) {
1246
+ sendToWrapper(namespace, 'touchlinkReset', {}, function (msg) {
977
1247
  if (msg && msg.error) {
978
1248
  showMessage(msg.error, _('Error'));
979
1249
  }
@@ -982,7 +1252,7 @@ function touchlinkReset() {
982
1252
 
983
1253
  function joinProcess(devId) {
984
1254
  messages = [];
985
- sendTo(namespace, 'letsPairing', {id: devId, stop:false}, function (msg) {
1255
+ sendToWrapper(namespace, 'letsPairing', {id: devId, stop:false}, function (msg) {
986
1256
  if (msg && msg.error) {
987
1257
  showMessage(msg.error, _('Error'));
988
1258
  }
@@ -990,10 +1260,8 @@ function joinProcess(devId) {
990
1260
  }
991
1261
 
992
1262
  function getCoordinatorInfo() {
993
- console.warn('calling getCoordinatorInfo');
994
- sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
1263
+ sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
995
1264
  if (msg) {
996
- console.warn(JSON.stringify(msg))
997
1265
  if (msg.error) {
998
1266
  errorData.push(msg.error);
999
1267
  delete msg.error;
@@ -1020,9 +1288,10 @@ function checkDebugDevice(id) {
1020
1288
  }
1021
1289
  return -1;
1022
1290
  }
1291
+
1023
1292
  async function toggleDebugDevice(id) {
1024
- sendTo(namespace, 'setDeviceDebug', {id:id}, function (msg) {
1025
- sendTo(namespace, 'getDebugDevices', {}, function(msg) {
1293
+ sendToWrapper(namespace, 'setDeviceDebug', {id:id}, function (msg) {
1294
+ sendToWrapper(namespace, 'getDebugDevices', {}, function(msg) {
1026
1295
  if (msg && typeof (msg.debugDevices == 'array')) {
1027
1296
  debugDevices = msg.debugDevices;
1028
1297
  }
@@ -1034,7 +1303,7 @@ async function toggleDebugDevice(id) {
1034
1303
  }
1035
1304
 
1036
1305
  function updateLocalConfigItems(device, data, global) {
1037
- sendTo(namespace, 'updateLocalConfigItems', {target: device, data:data, global:global}, function(msg) {
1306
+ sendToWrapper(namespace, 'updateLocalConfigItems', {target: device, data:data, global:global}, function(msg) {
1038
1307
  if (msg && msg.hasOwnProperty.error) {
1039
1308
  showMessage(msg.error, _('Error'));
1040
1309
  }
@@ -1042,38 +1311,128 @@ function updateLocalConfigItems(device, data, global) {
1042
1311
  });
1043
1312
  }
1044
1313
 
1045
-
1046
1314
  async function selectImageOverride(id) {
1315
+
1316
+ // start local functions
1317
+ function removeOption(k) {
1318
+ if (k && device_options.hasOwnProperty(k)) {
1319
+ if (dev.info.mapped && dev.info.mapped.options && dev.info.mapped.options.includes(device_options[k].key))
1320
+ availableOptions.push(device_options[k].key)
1321
+ delete device_options[k];
1322
+ }
1323
+ }
1324
+
1325
+ function addOption() {
1326
+ let idx=1;
1327
+ let key = '';
1328
+ const optionName = $('#option_Selector').val();
1329
+ do {
1330
+ key = `o${idx++}`;
1331
+ }
1332
+ while (device_options.hasOwnProperty(key));
1333
+ device_options[key] = { key:optionName, value:''};
1334
+ idx = availableOptions.indexOf(optionName);
1335
+ if (idx > -1) availableOptions.splice(idx, 1);
1336
+ }
1337
+
1338
+ function updateOptions(candidates) {
1339
+ if (candidates.length > 0) {
1340
+ $('#chooseimage').find('.new_options_available').removeClass('hide');
1341
+ list2select('#option_Selector', candidates, [], (key, val) => { return val; }, (key, val) => { return val; })
1342
+ }
1343
+ else {
1344
+ $('#chooseimage').find('.new_options_available').addClass('hide');
1345
+ }
1346
+ const html_options=[];
1347
+
1348
+ for (const k of Object.keys(device_options)) {
1349
+ html_options.push(`<div class="row">`);
1350
+ html_options.push(`<div class="input-field suffix col s5 m5 l5"><input disabled id="option_key_${k}" type="text" class="value" /><label for="option_key_${k}">Option</label></div>`)
1351
+ 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>`)
1352
+ html_options.push(`<div class="col"><a id="option_rem_${k}" class="btn-large round red" ><i class="material-icons icon-red">remove_circle</i></a></div>`);
1353
+ html_options.push(`</div>`)
1354
+ }
1355
+ $('#chooseimage').find('.options_grid').html(html_options.join(''));
1356
+ if (html_options.length > 0) {
1357
+ for (const k of Object.keys(device_options)) {
1358
+ $(`#option_key_${k}`).val(device_options[k].key);
1359
+ $(`#option_value_${k}`).val(device_options[k].value);
1360
+ $(`#option_rem_${k}`).unbind('click');
1361
+ $(`#option_rem_${k}`).click(() => { removeOption(k); updateOptions(availableOptions) });
1362
+ }
1363
+ }
1364
+ }
1365
+
1366
+ function getOptionsFromUI(_do, _so) {
1367
+ const _no = {};
1368
+ let changed = false;
1369
+ for (const k of Object.keys(_do)) {
1370
+ const key = $(`#option_key_${k}`).val();
1371
+ _do[k].key = key;
1372
+ const val = $(`#option_value_${k}`).val();
1373
+ try {
1374
+ _do[k].value = JSON.parse(val);
1375
+ }
1376
+ catch {
1377
+ _do[k].value = val;
1378
+ }
1379
+ if (device_options[k].key.length > 0) {
1380
+ _no[key] = device_options[k].value;
1381
+ changed |= _no[key] != _so[key];
1382
+ }
1383
+ }
1384
+ changed |= (Object.keys(_no).length != Object.keys(_so).length);
1385
+ console.warn(`${changed} : ${JSON.stringify(_do)} - ${JSON.stringify(_no)}`)
1386
+ if (changed) return _no;
1387
+ return undefined;
1388
+ }
1389
+
1390
+ function updateImageSelection(dev, imagedata) {
1391
+ const default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
1392
+ if (dev.legacyIcon) imagedata.unshift( { file:dev.legacyIcon, name:'legacy', data:dev.legacyIcon});
1393
+ imagedata.unshift( { file:'none', name:'default', data:default_icon});
1394
+ imagedata.unshift( { file:'current', name:'current', data:dev.common.icon || dev.icon});
1395
+
1396
+ list2select('#images', imagedata, selectItems,
1397
+ function (key, image) {
1398
+ return image.name
1399
+ },
1400
+ function (key, image) {
1401
+ return image.file;
1402
+ },
1403
+ function (key, image) {
1404
+ if (image.isBase64) {
1405
+ return `data-icon="data:image/png; base64, ${image.data}"`;
1406
+ } else {
1407
+ return `data-icon="${image.data}"`;
1408
+ }
1409
+ },
1410
+ );
1411
+
1412
+ }
1413
+ // end local functions
1414
+ const device_options = {};
1415
+ const received_options = {};
1416
+
1047
1417
  const dev = devices.find((d) => d._id == id);
1418
+ const availableOptions = (dev.info.mapped ? dev.info.mapped.options.slice() || []:[]);
1048
1419
  const imghtml = `<img src="${dev.common.icon || dev.icon}" width="80px">`
1049
1420
  //console.error(imghtml)
1050
1421
  const selectItems= [''];
1051
1422
  $('#chooseimage').find('input[id=\'d_name\']').val(dev.common.name);
1052
1423
  $('#chooseimage').find('.currentIcon').html(imghtml);
1424
+ $('#option_add_1084').unbind('click');
1425
+ $('#option_add_1084').click(() => {
1426
+ getOptionsFromUI(device_options, received_options);
1427
+ addOption();
1428
+ updateOptions(availableOptions)
1429
+ });
1430
+
1053
1431
 
1054
- sendTo(namespace, 'getLocalImages', {}, function(msg) {
1432
+
1433
+ sendToWrapper(namespace, 'getLocalImages', {}, function(msg) {
1055
1434
  if (msg && msg.imageData) {
1056
- const imagedata = msg.imageData;
1057
- const default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
1058
- if (dev.legacyIcon) imagedata.unshift( { file:dev.legacyIcon, name:'legacy', data:dev.legacyIcon});
1059
- imagedata.unshift( { file:'none', name:'default', data:default_icon});
1060
- imagedata.unshift( { file:'current', name:'current', data:dev.common.icon || dev.icon});
1061
-
1062
- list2select('#images', imagedata, selectItems,
1063
- function (key, image) {
1064
- return image.name
1065
- },
1066
- function (key, image) {
1067
- return image.file;
1068
- },
1069
- function (key, image) {
1070
- if (image.isBase64) {
1071
- return `data-icon="data:image/png; base64, ${image.data}"`;
1072
- } else {
1073
- return `data-icon="${image.data}"`;
1074
- }
1075
- },
1076
- );
1435
+ updateImageSelection(dev, msg.imageData);
1077
1436
 
1078
1437
  $('#chooseimage a.btn[name=\'save\']').unbind('click');
1079
1438
  $('#chooseimage a.btn[name=\'save\']').click(() => {
@@ -1083,14 +1442,36 @@ async function selectImageOverride(id) {
1083
1442
  const data = {};
1084
1443
  if (image != 'current') data.icon= image;
1085
1444
  if (name != dev.common.name) data.name = name;
1445
+ data.options = getOptionsFromUI(device_options, received_options)
1446
+
1086
1447
  updateLocalConfigItems(id, data, global);
1087
1448
  });
1088
- $('#chooseimage').modal('open');
1089
- Materialize.updateTextFields();
1449
+ sendToWrapper(namespace, 'getLocalConfigItems', { target:id, global:false, key:'options' }, function (msg) {
1450
+ if (msg) {
1451
+ if (msg.error) showMessage(msg.error, '_Error');
1452
+ Object.keys(device_options).forEach(key => delete device_options[key]);
1453
+ Object.keys(received_options).forEach(key => delete received_options[key]);
1454
+ if (typeof msg.options === 'object') {
1455
+ let cnt = 1;
1456
+ for (const key in msg.options)
1457
+ {
1458
+ const idx = availableOptions.indexOf(key);
1459
+ console.warn(`key ${key} : index : ${idx}`);
1460
+ if (idx > -1) availableOptions.splice(idx,1);
1461
+ received_options[key]=msg.options[key];
1462
+ device_options[`o${cnt}`] = { key:key, value:msg.options[key]}
1463
+ cnt++;
1464
+ }
1465
+ }
1466
+ updateOptions(availableOptions);
1467
+ } else showMessage('callback without message');
1468
+ $('#chooseimage').modal('open');
1469
+ Materialize.updateTextFields();
1470
+ });
1090
1471
  }
1091
1472
  });
1092
- }
1093
1473
 
1474
+ }
1094
1475
 
1095
1476
  function safestring(val) {
1096
1477
  const t = typeof val;
@@ -1100,6 +1481,11 @@ function safestring(val) {
1100
1481
  return val;
1101
1482
  }
1102
1483
 
1484
+ ////
1485
+ //
1486
+ //. section DebugUI
1487
+ //
1488
+ ////
1103
1489
  function fne(item) {
1104
1490
  const rv = [];
1105
1491
  if (item.flags) {
@@ -1146,13 +1532,12 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1146
1532
  isodd=!isodd;
1147
1533
  }
1148
1534
  }
1149
- 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>`
1150
- 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>`
1535
+ const ifbutton = `<a id="i_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Filter debug messages"><i class="material-icons large">${dbgMsgfilter.has('i_'+devID) ? 'filter_list' : 'format_align_justify' }</i></a>`
1536
+ const ofbutton = `<a id="hi_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Hide debug messages"><i class="material-icons large">${dbgMsghide.has('i_'+devID) ? 'unfold_more' : 'unfold_less' }</i></a>`
1151
1537
  const dataHide = dbgMsgfilter.has('hi_'+devID) ? 'Data hidden' : '&nbsp;';
1152
1538
  return {html:`<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>`, buttonList };
1153
1539
  }
1154
1540
 
1155
-
1156
1541
  function HtmlFromOutDebugMessages(messages, devID, filter) {
1157
1542
  const Html = [];
1158
1543
  const filterSet = new Set();
@@ -1188,13 +1573,12 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1188
1573
  isodd=!isodd;
1189
1574
  }
1190
1575
  }
1191
- 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>`
1192
- 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>`
1576
+ const ifbutton = `<a id="o_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Filter debug messages"><i class="material-icons large">${dbgMsgfilter.has('o_'+devID) ? 'filter_list' : 'format_align_justify' }</i></a>`
1577
+ const ofbutton = `<a id="ho_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Hide debug messages"><i class="material-icons large">${dbgMsghide.has('o_'+devID) ? 'unfold_more' : 'unfold_less'}</i></a>`
1193
1578
  const dataHide = dbgMsgfilter.has('ho_'+devID) ? 'Data hidden' : '&nbsp;';
1194
1579
  return { html:`<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>`, buttonList};
1195
1580
  }
1196
1581
 
1197
-
1198
1582
  function displayDebugMessages(msg) {
1199
1583
  const buttonNames = [];
1200
1584
  const idButtons = [];
@@ -1204,9 +1588,9 @@ function displayDebugMessages(msg) {
1204
1588
  const keylength = keys.length;
1205
1589
  const Html = [];
1206
1590
  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>`;
1207
- const dbutton = `<a id="d_all" class="btn-floating waves-effect waves-light red tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons icon-yellowlarge">delete_forever</i></a>`;
1208
- const fbutton = `<a id="f_all" class="btn-floating waves-effect waves-light green tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsgfilter.size != 0 ? 'filter_list' : 'format_align_justify' }</i></a>`;
1209
- 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>`;
1591
+ const dbutton = `<a id="d_all" class="btn-floating waves-effect waves-light red tooltipped center-align hoverable translateT" title="Delete debug messages"><i class="material-icons icon-yellowlarge">delete_forever</i></a>`;
1592
+ const fbutton = `<a id="f_all" class="btn-floating waves-effect waves-light green tooltipped center-align hoverable translateT" title="Filter debug messages"><i class="material-icons large">${dbgMsgfilter.size != 0 ? 'filter_list' : 'format_align_justify' }</i></a>`;
1593
+ const hbutton = `<a id="h_all" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Hide debug messages"><i class="material-icons large">${dbgMsghide.size != 0 ? 'unfold_more' : 'unfold_less'}</i></a>`;
1210
1594
  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>`;
1211
1595
  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><td>${dbutton}</td></tr></thead><tbody>`);
1212
1596
  if (!keylength) {
@@ -1299,7 +1683,6 @@ function displayDebugMessages(msg) {
1299
1683
  });
1300
1684
  }
1301
1685
  for (const b of idButtons) {
1302
- console.warn(`trying to add link to button ${b}`);
1303
1686
  $(`#lx_${b}`).click(function() { showMessageList(b)});
1304
1687
  }
1305
1688
  }
@@ -1344,48 +1727,38 @@ function showNamedMessages(messages, title, icon, timestamp) {
1344
1727
  }
1345
1728
 
1346
1729
  function showMessageList(msgId) {
1347
- console.warn(`trying to show messages for ${msgId}`);
1348
- console.warn(JSON.stringify(debugMessages));
1349
1730
  for (const devId of Object.keys(debugMessages.byId)) {
1350
1731
  for (const id of debugMessages.byId[devId].IN) {
1351
1732
  if (id.dataID == msgId) {
1352
- console.warn(`showing messages for ${id.type} ${devId}`);
1353
1733
  showNamedMessages(id.messages, `Messages from ${new Date(msgId).toLocaleTimeString()} for device ${devId}`);
1354
1734
  return;
1355
1735
  }
1356
1736
  }
1357
1737
  for (const id of debugMessages.byId[devId].OUT) {
1358
1738
  if (id.dataID == msgId) {
1359
- console.warn(`showing messages for ${msgId}`);
1360
1739
  showNamedMessages(id.messages, `Messages from ${new Date(msgId).toLocaleTimeString()} for device ${devId}`);
1361
1740
  return;
1362
1741
  }
1363
1742
  }
1364
1743
  }
1365
- console.warn(`nothing to show`);
1366
-
1367
-
1368
1744
  }
1369
1745
 
1370
1746
  function getDebugMessages(deleteBeforeRead, deleteSelected) {
1371
- sendTo(namespace, 'getDebugMessages', { inlog: debugInLog, del:deleteBeforeRead ? deleteSelected : '' }, function(msg) {
1747
+ sendToWrapper(namespace, 'getDebugMessages', { inlog: debugInLog, del:deleteBeforeRead ? deleteSelected : '' }, function(msg) {
1372
1748
  debugMessages = msg;
1373
1749
  if (msg) displayDebugMessages(debugMessages)
1374
1750
  })
1375
1751
  }
1376
1752
 
1377
- const lockout = {
1378
- timeoutid:undefined,
1379
- isActive:false,
1380
- };
1753
+ ////
1754
+ //
1755
+ //. section getDataFromAdapter
1756
+ //
1757
+ ////
1381
1758
  function getDevices() {
1382
- console.warn('getDevices called')
1383
-
1384
1759
  function sendForData() {
1385
- sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
1386
- console.warn(`getCoordinatorInfo returned ${JSON.stringify(msg)}`)
1760
+ sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
1387
1761
  if (msg) {
1388
- console.warn(JSON.stringify(msg))
1389
1762
  if (msg.error) {
1390
1763
  errorData.push(msg.error);
1391
1764
  delete msg.error;
@@ -1396,61 +1769,33 @@ function getDevices() {
1396
1769
  coordinatorinfo = msg;
1397
1770
  updateStartButton()
1398
1771
  }
1399
- sendTo(namespace, 'getDevices', {}, function (msg) {
1400
- if (msg) {
1401
- devices = msg.devices ? msg.devices : [];
1402
- // check if stashed error messages are sent alongside
1403
- if (msg.clean)
1404
- $('#state_cleanup_btn').removeClass('hide');
1405
- else
1406
- $('#state_cleanup_btn').addClass('hide');
1407
- if (msg.errors && msg.errors.length > 0) {
1408
- $('#show_errors_btn').removeClass('hide');
1409
- errorData = msg.errors;
1410
- }
1411
- else {
1412
- $('#show_errors_btn').addClass('hide');
1413
- }
1414
- let newDebugMessages = false;
1415
-
1416
- //check if debug messages are sent alongside
1417
- if (msg && typeof (msg.debugDevices == 'array')) {
1418
- debugDevices = msg.debugDevices;
1419
- console.warn('debug devices is sent')
1420
- }
1421
- else
1422
- debugDevices = [];
1423
- if (debugMessages.byId) {
1424
- newDebugMessages = true;
1425
- console.warn('having debug messages');
1426
- debugMessages.byId = msg;
1427
- if (msg) displayDebugMessages(debugMessages)
1428
- }
1429
- lockout.isActive = false;
1430
- if (msg.error) {
1431
- errorData.push(msg.error);
1432
- isHerdsmanRunning = false;
1433
- updateStartButton();
1434
- showDevices();
1435
- } else {
1436
- isHerdsmanRunning = true;
1437
- updateStartButton();
1438
- showDevices();
1439
- if (!newDebugMessages) {
1440
- console.warn('getting debug messages');
1441
- getDebugMessages();
1442
- }
1443
- //getExclude();
1444
- getBinding();
1445
- }
1772
+ });
1773
+ sendToWrapper(namespace, 'getLocalConfigItems', {getAllData:true}, function(msg) {
1774
+ if (msg.hasOwnProperty('by_id') && msg.hasOwnProperty('by_model'))
1775
+ localConfigData = msg;
1776
+ })
1777
+ sendToWrapper(namespace, 'getDevices', {}, function (msg) {
1778
+ if (msg) {
1779
+ extractDevicesData(msg);
1780
+ if (msg.error) {
1781
+ errorData.push(msg.error);
1782
+ isHerdsmanRunning = false;
1783
+ } else {
1784
+ isHerdsmanRunning = true;
1785
+ getBinding();
1446
1786
  }
1447
- });
1787
+ updateStartButton();
1788
+ showDevices();
1789
+ showLocalData();
1790
+ UpdateAdapterAlive(true)
1791
+ }
1448
1792
  });
1449
1793
  }
1450
1794
 
1795
+
1796
+
1451
1797
  if (lockout.timeoutid) {
1452
1798
  clearTimeout(lockout.timeoutid);
1453
- console.warn('clearing getDevices timeout')
1454
1799
  }
1455
1800
 
1456
1801
  setTimeout(() => {
@@ -1461,29 +1806,50 @@ function getDevices() {
1461
1806
 
1462
1807
  }
1463
1808
 
1809
+ function extractDevicesData(msg) {
1810
+ devices = msg.devices ? msg.devices : [];
1811
+ // check if stashed error messages are sent alongside
1812
+ if (msg.clean)
1813
+ $('#state_cleanup_btn').removeClass('hide');
1814
+ else
1815
+ $('#state_cleanup_btn').addClass('hide');
1816
+ if (msg.errors && msg.errors.length > 0) {
1817
+ $('#show_errors_btn').removeClass('hide');
1818
+ errorData = msg.errors;
1819
+ }
1820
+ else {
1821
+ $('#show_errors_btn').addClass('hide');
1822
+ }
1823
+ //check if debug messages are sent alongside
1824
+ if (msg && typeof (msg.debugDevices == 'array')) {
1825
+ debugDevices = msg.debugDevices;
1826
+ }
1827
+ else
1828
+ debugDevices = [];
1829
+ if (debugMessages.byId) {
1830
+ debugMessages.byId = msg;
1831
+ if (msg) displayDebugMessages(debugMessages);
1832
+ }
1833
+ if (msg.models) models = msg.models;
1834
+ lockout.isActive = false;
1835
+
1836
+ }
1837
+
1838
+
1839
+
1464
1840
  function getNamedColors() {
1465
- sendTo(namespace, 'getNamedColors', {}, function(msg) {
1841
+ sendToWrapper(namespace, 'getNamedColors', {}, function(msg) {
1466
1842
  if (msg && typeof msg.colors) {
1467
1843
  namedColors = msg.colors;
1468
1844
  }
1469
1845
  });
1470
1846
  }
1471
1847
 
1472
- function getDeviceCards() {
1473
- return $('#devices .device').not('.group');
1474
- }
1475
-
1476
- function getDeviceCard(devId) {
1477
- if (devId.startsWith('0x')) {
1478
- devId = devId.substr(2, devId.length);
1479
- }
1480
- return $('#devices').find(`div[id='${namespace}.${devId}']`);
1481
- }
1482
1848
 
1483
1849
  function getMap(rebuild) {
1484
1850
  $('#refresh').addClass('disabled');
1485
1851
  if (isHerdsmanRunning) {
1486
- sendTo(namespace, 'getMap', { forcebuild:rebuild}, function (msg) {
1852
+ sendToWrapper(namespace, 'getMap', { forcebuild:rebuild}, function (msg) {
1487
1853
  $('#refresh').removeClass('disabled');
1488
1854
  if (msg) {
1489
1855
  if (msg.error) {
@@ -1505,27 +1871,27 @@ function getMap(rebuild) {
1505
1871
  else showMessage('Unable to generate map, the zigbee subsystem is inactive', 'Map generation error');
1506
1872
  }
1507
1873
 
1508
- function getRandomExtPanID()
1509
- {
1510
- const bytes = [];
1511
- for (let i = 0;i<16;i++) {
1512
- bytes.push(Math.floor(Math.random() * 16).toString(16));
1513
- }
1514
- return bytes.join('');
1515
- }
1516
-
1517
- function getRandomChannel()
1518
- {
1519
- const channels = [11,15,20,25]
1520
- return channels[Math.floor(Math.random() * 4)];
1521
- }
1522
1874
 
1523
1875
 
1524
1876
 
1525
1877
  // the function loadSettings has to exist ...
1526
1878
 
1527
1879
  function load(settings, onChange) {
1528
- console.warn(JSON.stringify(settings));
1880
+ function getRandomExtPanID()
1881
+ {
1882
+ const bytes = [];
1883
+ for (let i = 0;i<16;i++) {
1884
+ bytes.push(Math.floor(Math.random() * 16).toString(16));
1885
+ }
1886
+ return bytes.join('');
1887
+ }
1888
+
1889
+ function getRandomChannel()
1890
+ {
1891
+ const channels = [11,15,20,25]
1892
+ return channels[Math.floor(Math.random() * 4)];
1893
+ }
1894
+
1529
1895
  if (settings.extPanID === undefined || settings.extPanID == '') {
1530
1896
  settings.channel = getRandomChannel();
1531
1897
  }
@@ -1577,13 +1943,20 @@ function load(settings, onChange) {
1577
1943
  }
1578
1944
  }
1579
1945
 
1580
-
1581
1946
  getComPorts(onChange);
1582
1947
 
1583
1948
  //dialog = new MatDialog({EndingTop: '50%'});
1584
- getDevices();
1585
- getNamedColors();
1586
- readNVRamBackup(false);
1949
+ const keepAliveHandle = startKeepalive();
1950
+ keepAlive(() => {
1951
+ getDevices();
1952
+ getNamedColors();
1953
+ readNVRamBackup(false);
1954
+ sendToWrapper(namespace, 'getGroups', {}, function (data) {
1955
+ groups = data.groups || {};
1956
+ //showGroups();
1957
+ });
1958
+ })
1959
+
1587
1960
  //getDebugMessages();
1588
1961
  //getMap();
1589
1962
  //addCard();
@@ -1592,13 +1965,10 @@ function load(settings, onChange) {
1592
1965
  onChange(false);
1593
1966
 
1594
1967
  $('#test-btn').click(function () {
1595
- console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1596
1968
  if (!isHerdsmanRunning) {
1597
1969
  const port = $('#port.value').val();
1598
- console.warn(`port is ${port}`)
1599
1970
  showWaitingDialog(`Trying to connect to ${port}`, 300);
1600
- sendTo(namespace, 'testConnection', { address:port }, function(msg) {
1601
- console.warn(`send to returned with ${JSON.stringify(msg)}`);
1971
+ sendToWrapper(namespace, 'testConnection', { address:port }, function(msg) {
1602
1972
  closeWaitingDialog();
1603
1973
  if (msg) {
1604
1974
  if (msg.error) {
@@ -1617,7 +1987,6 @@ function load(settings, onChange) {
1617
1987
  })
1618
1988
  // test start commands
1619
1989
  $('#show_test_run').click(function () {
1620
- console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1621
1990
  doTestStart(!isHerdsmanRunning);
1622
1991
  });
1623
1992
 
@@ -1639,10 +2008,8 @@ function load(settings, onChange) {
1639
2008
  });
1640
2009
  $('#pairing').click(function () {
1641
2010
  if (!$('#pairing').hasClass('pulse')) {
1642
- letsPairing();
1643
- }
1644
- console.warn('lets pairing');
1645
- showPairingProcess();
2011
+ openNetwork();
2012
+ } else showPairingProcess();
1646
2013
  });
1647
2014
 
1648
2015
  $('#refresh').click(function () {
@@ -1678,18 +2045,14 @@ function load(settings, onChange) {
1678
2045
  showChannels();
1679
2046
  });
1680
2047
 
1681
- sendTo(namespace, 'getGroups', {}, function (data) {
1682
- groups = data.groups;
1683
- //showGroups();
1684
- });
1685
2048
 
1686
2049
  $('#add_group').click(function () {
1687
- const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
2050
+ const maxind = parseInt(Object.getOwnPropertyNames(groups || {}).reduce((a, b) => a > b ? a : b, 0));
1688
2051
  addGroup(maxind + 1, 'Group ' + maxind + 1);
1689
2052
  });
1690
2053
 
1691
2054
  $('#add_grp_btn').click(function () {
1692
- const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
2055
+ const maxind = parseInt(Object.getOwnPropertyNames(groups || {}).reduce((a, b) => a > b ? a : b, 0));
1693
2056
  addGroup(maxind + 1, 'Group ' + maxind + 1);
1694
2057
  });
1695
2058
 
@@ -1715,6 +2078,7 @@ function load(settings, onChange) {
1715
2078
  $('.dropdown-trigger').dropdown({constrainWidth: false});
1716
2079
  Materialize.updateTextFields();
1717
2080
  $('.collapsible').collapsible();
2081
+
1718
2082
  Materialize.Tabs.init($('.tabs'));
1719
2083
  $('#device-search').keyup(function (event) {
1720
2084
  doFilter(event.target.value.toLowerCase());
@@ -1748,11 +2112,15 @@ function load(settings, onChange) {
1748
2112
  addExcludeDialog();
1749
2113
  });
1750
2114
 
2115
+ $('#updateData').click(function () {
2116
+ getDevices();
2117
+ });
2118
+
1751
2119
  $('#add_binding').click(function () {
1752
2120
  addBindingDialog();
1753
2121
  });
1754
2122
 
1755
- sendTo(namespace, 'getLibData', {key: 'cidList'}, function (data) {
2123
+ sendToWrapper(namespace, 'getLibData', {key: 'cidList'}, function (data) {
1756
2124
  cidList = data.list;
1757
2125
  });
1758
2126
  }
@@ -1767,13 +2135,22 @@ function showMessages() {
1767
2135
  $('#stdout_t').text(messages.join('\n'));
1768
2136
  }
1769
2137
 
1770
- function showPairingProcess() {
2138
+ function showPairingProcess(noextrabuttons) {
1771
2139
  if (isHerdsmanRunning) $('#modalpairing').modal({
1772
2140
  startingTop: '4%',
1773
2141
  endingTop: '10%',
1774
2142
  dismissible: false
1775
2143
  });
1776
2144
 
2145
+ if (noextrabuttons) {
2146
+ $('#modalpairing').find('.endpairing').addClass('hide');
2147
+ $('#modalpairing').find('.extendpairing').addClass('hide');
2148
+ }
2149
+ else {
2150
+ $('#modalpairing').find('.endpairing').removeClass('hide');
2151
+ $('#modalpairing').find('.extendpairing').removeClass('hide');
2152
+ }
2153
+
1777
2154
  $('#modalpairing').modal('open');
1778
2155
  Materialize.updateTextFields();
1779
2156
  }
@@ -1792,24 +2169,25 @@ function doTestStart(start, interactive) {
1792
2169
  };
1793
2170
  // $('#testStartStart').addClass('disabled');
1794
2171
  messages = [];
1795
- if (interactive) showWaitingDialog('Trying to start the zigbee subsystem manually', 120);
1796
- sendTo(namespace, 'testConnect', { start:true, zigbeeOptions:ovr }, function(msg) {
2172
+ if (interactive) showPairingProcess(true)
2173
+
2174
+ // showWaitingDialog('Trying to start the zigbee subsystem manually', 120);
2175
+ sendToWrapper(namespace, 'testConnect', { start:true, zigbeeOptions:ovr }, function(msg) {
1797
2176
  if (msg) {
1798
2177
  closeWaitingDialog();
1799
- isHerdsmanRunning = msg.status;
1800
2178
  updateStartButton(false);
1801
2179
  if (msg.status)
1802
2180
  $('#testStartStop').removeClass('disabled');
1803
2181
  else {
1804
2182
  //showMessage(`The zigbee subsystem is not running. Please ensure that the configuration is correct. ${msg.error ? 'Error on start-Attempt ' + msg.error.message : ''}`);
1805
- $('#testStartStop').removeClass('disabled');
2183
+ $('#testStartStart').removeClass('disabled');
1806
2184
  }
1807
2185
  }
1808
2186
  })
1809
2187
  }
1810
2188
  else {
1811
2189
  //$('#testStartStop').addClass('disabled');
1812
- sendTo(namespace, 'testConnect', { start:false }, function(msg) {
2190
+ sendToWrapper(namespace, 'testConnect', { start:false }, function(msg) {
1813
2191
  if (msg) {
1814
2192
  if (msg.status) $('#testStartStart').removeClass('disabled');
1815
2193
  else $('#testStartStop').removeClass('disabled');
@@ -1847,7 +2225,6 @@ function getDevId(adapterDevId) {
1847
2225
 
1848
2226
 
1849
2227
  function updateStartButton(block) {
1850
- console.warn(`update start button with${isHerdsmanRunning ? ' Herdsman' : 'out Herdsman'}`);
1851
2228
  if (block) {
1852
2229
  $('#show_test_run').addClass('disabled');
1853
2230
  $('#reset-btn').addClass('disabled');
@@ -1894,7 +2271,6 @@ socket.emit('subscribeObjects', namespace + '.*');
1894
2271
  socket.on('stateChange', function (id, state) {
1895
2272
  // only watch our own states
1896
2273
  if (id.substring(0, namespaceLen) !== namespace) return;
1897
- //console.log('stateChange', id, state);
1898
2274
  if (state) {
1899
2275
  if (id.match(/\.info\.pairingMode$/)) {
1900
2276
  if (state.val) {
@@ -1956,14 +2332,12 @@ socket.on('stateChange', function (id, state) {
1956
2332
 
1957
2333
  socket.on('objectChange', function (id, obj) {
1958
2334
  if (id.substring(0, namespaceLen) !== namespace) return;
1959
- //console.log('objectChange', id, obj);
1960
2335
  if (obj && obj.type == 'device') { // && obj.common.type !== 'group') {
1961
2336
  updateDevice(id);
1962
2337
  }
1963
2338
  if (!obj) {
1964
2339
  // delete state or device
1965
2340
  const elems = id.split('.');
1966
- //console.log('elems', elems);
1967
2341
  if (elems.length === 3) {
1968
2342
  removeDevice(id);
1969
2343
  showDevices();
@@ -2015,10 +2389,11 @@ function showNetworkMap(devices, map) {
2015
2389
  const createNode = function (dev, mapEntry) {
2016
2390
  if (dev.common && (dev.common.type == 'group' || dev.common.deactivated)) return undefined;
2017
2391
  const extInfo = (mapEntry && mapEntry.networkAddress) ? `\n (nwkAddr: 0x${mapEntry.networkAddress.toString(16)} | ${mapEntry.networkAddress})` : '';
2392
+ const t = dev._id.replace(namespace + '.', '');
2018
2393
  const node = {
2019
2394
  id: dev._id,
2020
2395
  label: (dev.link_quality > 0 ? dev.common.name : `${dev.common.name}\n(disconnected)`),
2021
- title: dev._id.replace(namespace + '.', '') + extInfo,
2396
+ title: `${t} ${extInfo}`,
2022
2397
  shape: 'circularImage',
2023
2398
  image: dev.common.icon || dev.icon,
2024
2399
  imagePadding: {top: 5, bottom: 5, left: 5, right: 5},
@@ -2027,20 +2402,20 @@ function showNetworkMap(devices, map) {
2027
2402
  borderWidth: 1,
2028
2403
  borderWidthSelected: 4,
2029
2404
  };
2030
- if (dev.info && dev.info.device.type == 'Coordinator') {
2405
+ if (dev.common && dev.common.type === 'Coordinator') {
2031
2406
  // node.shape = 'star';
2032
2407
  node.image = 'zigbee.png';
2033
2408
  node.label = 'Coordinator';
2034
2409
  // delete node.color;
2035
2410
  }
2411
+ console.warn(`node for device ${JSON.stringify(node)}`)
2036
2412
  return node;
2037
2413
  };
2038
2414
 
2039
2415
  if (map.lqis) {
2040
2416
  map.lqis.forEach((mapEntry) => {
2041
- const dev = getDevice(mapEntry.ieeeAddr);
2417
+ const dev = getDeviceByIEEE(mapEntry.ieeeAddr);
2042
2418
  if (!dev) {
2043
- //console.log("No dev with ieee "+mapEntry.ieeeAddr);
2044
2419
  return;
2045
2420
  }
2046
2421
 
@@ -2054,7 +2429,7 @@ function showNetworkMap(devices, map) {
2054
2429
  node = nodes[mapEntry.ieeeAddr];
2055
2430
  }
2056
2431
  if (node) {
2057
- const parentDev = getDevice(mapEntry.parent);
2432
+ const parentDev = getDeviceByIEEE(mapEntry.parent);
2058
2433
  const to = parentDev ? parentDev._id : undefined;
2059
2434
  const from = dev._id;
2060
2435
  let label = mapEntry.lqi.toString();
@@ -2321,7 +2696,7 @@ function getComPorts(onChange) {
2321
2696
  // timeout = setTimeout(function () {
2322
2697
  // getComPorts(onChange);
2323
2698
  // }, 2000);
2324
- sendTo(namespace, 'listUart', null, function (list) {
2699
+ sendToWrapper(namespace, 'listUart', null, function (list) {
2325
2700
  // if (timeout) {
2326
2701
  // clearTimeout(timeout);
2327
2702
  // timeout = null;
@@ -2465,7 +2840,6 @@ function loadDeveloperTab() {
2465
2840
  const device = devices.find(obj => {
2466
2841
  return this.value ===obj.native.id;
2467
2842
  });
2468
- console.warn(`dev selector: ${this.selectedIndex} ${this.value} ->${JSON.stringify(device)}`)
2469
2843
 
2470
2844
  const epList = device ? device.info.endpoints : null;
2471
2845
  updateSelect('#ep', epList,
@@ -2548,7 +2922,7 @@ function loadDeveloperTab() {
2548
2922
  data = prepareData();
2549
2923
  }
2550
2924
  sendToZigbee(data.devId, data.ep, data.cid, data.cmd, data.cmdType, data.zclData, data.cfg, function (reply) {
2551
- console.log('Reply from zigbee: ' + JSON.stringify(reply));
2925
+ console.log('Send to Zigbee replied with ' + JSON.stringify(reply));
2552
2926
  if (reply.hasOwnProperty('localErr')) {
2553
2927
  showDevRunInfo(reply.localErr, reply.errMsg, 'yellow');
2554
2928
  } else if (reply.hasOwnProperty('localStatus')) {
@@ -2563,7 +2937,7 @@ function loadDeveloperTab() {
2563
2937
 
2564
2938
  responseCodes = null;
2565
2939
  // load list of response codes
2566
- sendTo(namespace, 'getLibData', {key: 'respCodes'}, function (data) {
2940
+ sendToWrapper(namespace, 'getLibData', {key: 'respCodes'}, function (data) {
2567
2941
  responseCodes = data.list;
2568
2942
  });
2569
2943
  }
@@ -2616,7 +2990,7 @@ function sendToZigbee(id, ep, cid, cmd, cmdType, zclData, cfg, callback) {
2616
2990
 
2617
2991
  console.log('Send to zigbee, id ' + id + ',ep ' + ep + ', cid ' + cid + ', cmd ' + cmd + ', cmdType ' + cmdType + ', zclData ' + JSON.stringify(zclData));
2618
2992
 
2619
- sendTo(namespace, 'sendToZigbee', data, function (reply) {
2993
+ sendToWrapper(namespace, 'sendToZigbee', data, function (reply) {
2620
2994
  clearTimeout(sendTimeout);
2621
2995
  if (callback) {
2622
2996
  callback(reply);
@@ -2664,7 +3038,7 @@ function populateSelector(selectId, key, cid) {
2664
3038
  updateSelect(selectId, null);
2665
3039
  return;
2666
3040
  }
2667
- sendTo(namespace, 'getLibData', {key: key, cid: cid}, function (data) {
3041
+ sendToWrapper(namespace, 'getLibData', {key: key, cid: cid}, function (data) {
2668
3042
  const list = data.list;
2669
3043
  if (key === 'attrIdList') {
2670
3044
  updateSelect(selectId, list,
@@ -2807,7 +3181,7 @@ function deleteGroupConfirmation(id, name) {
2807
3181
 
2808
3182
  function updateGroup(newId, newName, remove) {
2809
3183
  groups[newId] = newName;
2810
- sendTo(namespace, 'renameGroup', {id: newId, name: newName, remove: remove}, function (msg) {
3184
+ sendToWrapper(namespace, 'renameGroup', {id: newId, name: newName, remove: remove}, function (msg) {
2811
3185
  if (msg && msg.error) {
2812
3186
  showMessage(msg.error, _('Error'));
2813
3187
  }
@@ -2817,7 +3191,7 @@ function updateGroup(newId, newName, remove) {
2817
3191
 
2818
3192
  function deleteGroup(id) {
2819
3193
  delete groups[id];
2820
- sendTo(namespace, 'deleteGroup', id, function (msg) {
3194
+ sendToWrapper(namespace, 'deleteGroup', id, function (msg) {
2821
3195
  if (msg && msg.error) {
2822
3196
  showMessage(msg.error, _('Error'));
2823
3197
  }
@@ -2840,7 +3214,7 @@ function updateDev(id, newName, newGroups) {
2840
3214
  const keys = Object.keys(newGroups);
2841
3215
  if (keys && keys.length) {
2842
3216
  command.groups = newGroups
2843
- sendTo(namespace, 'updateGroupMembership', command, function (msg) {
3217
+ sendToWrapper(namespace, 'updateGroupMembership', command, function (msg) {
2844
3218
  closeWaitingDialog();
2845
3219
  if (msg && msg.error) {
2846
3220
  showMessage(msg.error, _('Error'));
@@ -2854,7 +3228,7 @@ function updateDev(id, newName, newGroups) {
2854
3228
  }
2855
3229
  else if (needName)
2856
3230
  {
2857
- sendTo(namespace, 'renameDevice', command, function(msg) {
3231
+ sendToWrapper(namespace, 'renameDevice', command, function(msg) {
2858
3232
  //closeWaitingDialog();
2859
3233
  if (msg && msg.error) {
2860
3234
  showMessage(msg.error, _('Error'));
@@ -2873,9 +3247,9 @@ function resetConfirmation() {
2873
3247
  const btn = $('#modalreset .modal-content a.btn');
2874
3248
  btn.unbind('click');
2875
3249
  btn.click(function (e) {
2876
- sendTo(namespace, 'reset', {mode: e.target.id}, function (err) {
3250
+ sendToWrapper(namespace, 'reset', {mode: e.target.id}, function (err) {
2877
3251
  if (err) {
2878
- console.log(err);
3252
+ console.log(`reset attempt failed with ${err}`);
2879
3253
  } else {
2880
3254
  console.log('Reset done');
2881
3255
  }
@@ -3104,7 +3478,7 @@ function addBindingDialog() {
3104
3478
  }
3105
3479
 
3106
3480
  function addBinding(bind_source, bind_source_ep, bind_target, bind_target_ep, unbind_from_coordinator) {
3107
- sendTo(namespace, 'addBinding', {
3481
+ sendToWrapper(namespace, 'addBinding', {
3108
3482
  bind_source: bind_source,
3109
3483
  bind_source_ep: bind_source_ep,
3110
3484
  bind_target: bind_target,
@@ -3121,7 +3495,7 @@ function addBinding(bind_source, bind_source_ep, bind_target, bind_target_ep, un
3121
3495
  }
3122
3496
 
3123
3497
  function editBinding(bind_id, bind_source, bind_source_ep, bind_target, bind_target_ep, unbind_from_coordinator) {
3124
- sendTo(namespace, 'editBinding', {
3498
+ sendToWrapper(namespace, 'editBinding', {
3125
3499
  id: bind_id,
3126
3500
  bind_source: bind_source,
3127
3501
  bind_source_ep: bind_source_ep,
@@ -3214,7 +3588,7 @@ function showBinding() {
3214
3588
  }
3215
3589
 
3216
3590
  function getBinding() {
3217
- sendTo(namespace, 'getBinding', {}, function (msg) {
3591
+ sendToWrapper(namespace, 'getBinding', {}, function (msg) {
3218
3592
  if (msg) {
3219
3593
  if (msg.error) {
3220
3594
  showMessage(msg.error, _('Error'));
@@ -3239,7 +3613,7 @@ function deleteBindingConfirmation(id) {
3239
3613
  }
3240
3614
 
3241
3615
  function deleteBinding(id) {
3242
- sendTo(namespace, 'delBinding', id, (msg) => {
3616
+ sendToWrapper(namespace, 'delBinding', id, (msg) => {
3243
3617
  closeWaitingDialog();
3244
3618
  if (msg) {
3245
3619
  if (msg.error) {
@@ -3261,7 +3635,6 @@ function findClName(id) {
3261
3635
  }
3262
3636
 
3263
3637
  function genDevInfo(device) {
3264
- //console.log(device);
3265
3638
  const dev = (device && device.info) ? device.info.device : undefined;
3266
3639
  const mapped = (device && device.info) ? device.info.mapped : undefined;
3267
3640
  const endpoints = (device && device.info) ? device.info.endpoints : [];
@@ -3309,7 +3682,6 @@ function genDevInfo(device) {
3309
3682
  let epInfo = '';
3310
3683
  for (const epind in endpoints) {
3311
3684
  const ep = endpoints[epind];
3312
- console.warn(JSON.stringify(ep));
3313
3685
  epInfo +=
3314
3686
  `<div style="font-size: 0.9em" class="truncate">
3315
3687
  <ul>
@@ -3342,12 +3714,6 @@ function genDevInfo(device) {
3342
3714
  return info.join('');
3343
3715
  }
3344
3716
 
3345
- function showDevInfo(id) {
3346
- const info = genDevInfo(getDeviceByID(id));
3347
- $('#devinfo').html(info);
3348
- $('#modaldevinfo').modal('open');
3349
- }
3350
-
3351
3717
  let waitingTimeout, waitingInt;
3352
3718
 
3353
3719
  function showWaitingDialog(text, timeout) {
@@ -3375,7 +3741,7 @@ function closeWaitingDialog() {
3375
3741
 
3376
3742
 
3377
3743
  function showChannels() {
3378
- sendTo(namespace, 'getChannels', {}, function (msg) {
3744
+ sendToWrapper(namespace, 'getChannels', {}, function (msg) {
3379
3745
  closeWaitingDialog();
3380
3746
  if (msg) {
3381
3747
  if (msg.error) {
@@ -3494,21 +3860,20 @@ function addExcludeDialog() {
3494
3860
 
3495
3861
  function addExclude(exclude_model) {
3496
3862
  if (typeof exclude_model == 'object' && exclude_model.hasOwnProperty('common'))
3497
- sendTo(namespace, 'addExclude', { exclude_model: exclude_model }, function (msg) {
3863
+ sendToWrapper(namespace, 'addExclude', { exclude_model: exclude_model }, function (msg) {
3498
3864
  closeWaitingDialog();
3499
3865
  if (msg) {
3500
3866
  if (msg.error) {
3501
3867
  showMessage(msg.error, _('Error'));
3502
3868
  }
3503
3869
  }
3504
- console.log('getting excludes ?');
3505
3870
  getExclude();
3506
3871
  });
3507
3872
  else closeWaitingDialog();
3508
3873
  }
3509
3874
 
3510
3875
  function getExclude() {
3511
- sendTo(namespace, 'getExclude', {}, function (msg) {
3876
+ sendToWrapper(namespace, 'getExclude', {}, function (msg) {
3512
3877
  if (msg) {
3513
3878
  if (msg.error) {
3514
3879
  showMessage(msg.error, _('Error'));
@@ -3579,14 +3944,13 @@ function deleteExcludeConfirmation(id) {
3579
3944
  }
3580
3945
 
3581
3946
  function deleteExclude(id) {
3582
- sendTo(namespace, 'delExclude', id, (msg) => {
3947
+ sendToWrapper(namespace, 'delExclude', id, (msg) => {
3583
3948
  closeWaitingDialog();
3584
3949
  if (msg) {
3585
3950
  if (msg.error) {
3586
3951
  showMessage(msg.error, _('Error'));
3587
3952
  }
3588
3953
  }
3589
- console.log('getting excludes ?');
3590
3954
  getExclude();
3591
3955
  });
3592
3956
  }
@@ -3669,181 +4033,24 @@ function sortByTitle(element) {
3669
4033
  return element.querySelector('.card-title').textContent.toLowerCase().trim();
3670
4034
  }
3671
4035
 
3672
- function getDashCard(dev, groupImage, groupstatus) {
3673
- const title = dev.common.name,
3674
- id = dev._id,
3675
- type = dev.common.type,
3676
- img_src = (groupImage ? groupImage : dev.common.icon || dev.icon),
3677
- isActive = !dev.common.deactivated,
3678
- rooms = [],
3679
- lang = systemLang || 'en';
3680
- const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
3681
- const permitJoinBtn = dev.battery || dev.common.type == 'group' ? '' : `<div class="col tool"><button name="joinCard" class="waves-effect btn-small btn-flat right hoverable green"><i class="material-icons icon-green">leak_add</i></button></div>`;
3682
- const device_queryBtn = dev.battery || dev.common.type == 'group' ? '' : `<div class="col tool"><button name="deviceQuery" class="waves-effect btn-small btn-flat right hoverable green"><i class="material-icons icon-green">play_for_work</i></button></div>`;
3683
- const rid = id.split('.').join('_');
3684
- const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
3685
- const image = `<img src="${img_src}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
3686
- nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
3687
- battery_cls = getBatteryCls(dev.battery),
3688
- lqi_cls = getLQICls(dev.link_quality),
3689
- unconnected_icon = (groupImage ? (groupstatus ? '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>' : '<div class="col tool"><i class="material-icons icon-red">cancel</i></div>') :'<div class="col tool"><i class="material-icons icon-red">leak_remove</i></div>'),
3690
- battery = (dev.battery && isActive) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
3691
- lq = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : (isActive ? unconnected_icon : ''),
3692
- //status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (groupImage || !isActive ? '' : `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`),
3693
- //infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
3694
- idleTime = (dev.link_quality_lc > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_lc_icon" class="material-icons idletime">access_time</i><div id="${rid}_link_quality_lc" class="center" style="font-size:0.7em">${getIdleTime(dev.link_quality_lc)}</div></div>` : '';
3695
- const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => {
3696
- const id = stateDef.id;
3697
- const sid = id.split('.').join('_');
3698
- let val = stateDef.val || '';
3699
- if (stateDef.role === 'switch' && stateDef.write) {
3700
- val = `<span class="switch"><label><input type="checkbox" ${(val) ? 'checked' : ''}><span class="lever"></span></label></span>`;
3701
- } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
3702
- val = `<span class="range-field dash"><input type="range" min="0" max="100" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
3703
- } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
3704
- val = `<span class="range-field dash"><input type="range" min="150" max="500" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
3705
- } else if (stateDef.type === 'boolean') {
3706
- const disabled = (stateDef.write) ? '' : 'disabled="disabled"';
3707
- val = `<label class="dash"><input type="checkbox" ${(val == true) ? 'checked=\'checked\'' : ''} ${disabled}/><span></span></label>`;
3708
- } else if (stateDef.role === 'level.color.rgb') {
3709
- const options = []
3710
- for (const key of namedColors) {
3711
- options.push(`<option value="${key}" ${val===key ? 'selected' : ''}>${key}</option>`);
3712
- }
3713
- val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
3714
- } else if (stateDef.states && stateDef.write) {
3715
- let options;
3716
- if (typeof stateDef.states == 'string') {
3717
- const sts = stateDef.states.split(';');
3718
- if (sts.length < 2) return '';
3719
- options = sts.map((item) => {
3720
- const v = item.split(':');
3721
- return `<option value="${v[0]}" ${(val == v[0]) ? 'selected' : ''}>${v[1]}</option>`;
3722
- });
3723
- } else {
3724
- options = [];
3725
- for (const [key, value] of Object.entries(stateDef.states)) {
3726
- options.push(`<option value="${key}" ${(val == key) ? 'selected' : ''}>${key}</option>`);
3727
- }
3728
- }
3729
- if (options.length < 2) return '';
3730
- val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
3731
- } else if (stateDef.write) {
3732
- return;
3733
- // val = `<span class="input-field dash value"><input class="dash value" id="${stateDef.name}" value="${val}"></input></span>`;
3734
- }
3735
- else {
3736
- val = `<span class="dash value">${val ? val : '(null)'} ${(stateDef.unit) ? stateDef.unit : ''}</span>`;
3737
- }
3738
- return `<li><span class="label dash truncate">${stateDef.name}</span><span id=${sid} oid=${id} class="state">${val}</span></li>`;
3739
- }).join('') : '';
3740
- const dashCard = `
3741
- <div class="card-content zcard ${isActive ? '' : 'bg_red'}">
3742
- <div style="cursor: pointer">
3743
- <span class="top right small" style="border-radius: 50%">
3744
- ${device_queryBtn}
3745
- ${permitJoinBtn}
3746
- </span>
3747
- <div class="flip">
3748
- <span class="top right small" style="border-radius: 50%">
3749
- ${idleTime}
3750
- ${battery}
3751
- ${lq}
3752
- </span>
3753
- <span class="card-title truncate">${title}</span>
3754
- </div>
3755
- </div>
3756
- <i class="left">${image}</i>
3757
- <div style="min-height:88px; font-size: 0.8em; height: 130px; overflow-y: auto" class="truncate">
3758
- <ul>
3759
- ${(isActive ? info : 'Device deactivated')}
3760
- </ul>
3761
- </div>
3762
- <div class="footer right-align"></div>
3763
- </div>`;
3764
-
3765
- return dashCard;
3766
- }
3767
-
3768
- function setDashStates(id, state) {
3769
- const devId = getDevId(id);
3770
- const dev = getDeviceByID(devId);
3771
- if (dev) {
3772
- const stateDef = dev.statesDef.find((stateDef) => stateDef.id == id);
3773
- if (stateDef) {
3774
- const sid = id.split('.').join('_');
3775
- if (stateDef.role === 'switch' && stateDef.write) {
3776
- $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
3777
- } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
3778
- $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
3779
- } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
3780
- $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
3781
- } else if (stateDef.states && stateDef.write) {
3782
- $(`#${sid}`).find(`select option[value=${state.val}]`).prop('selected', true);
3783
- } else if (stateDef.type === 'boolean') {
3784
- $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
3785
- } else {
3786
- $(`#${sid}`).find('.value').text(`${state.val} ${(stateDef.unit) ? stateDef.unit : ''}`);
3787
- }
3788
- }
3789
- }
3790
- }
3791
-
3792
- function hookControls() {
3793
- $('input[type=\'checkbox\']').change(function (event) {
3794
- const val = $(this).is(':checked');
3795
- const id = $(this).parents('.state').attr('oid');
3796
- sendTo(namespace, 'setState', {id: id, val: val}, function (data) {
3797
- //console.log(data);
3798
- });
3799
- });
3800
- $('input[type=\'range\']').change(function (event) {
3801
- const val = $(this).val();
3802
- const id = $(this).parents('.state').attr('oid');
3803
- sendTo(namespace, 'setState', {id: id, val: val}, function (data) {
3804
- //console.log(data);
3805
- });
3806
- });
3807
- $('.state select').on('change', function () {
3808
- const val = $(this).val();
3809
- const id = $(this).parents('.state').attr('oid');
3810
- sendTo(namespace, 'setState', {id: id, val: val}, function (data) {
3811
- //console.log(data);
3812
- });
3813
- });
3814
- }
3815
-
3816
- function getIdleTime(value) {
3817
- return (value) ? moment(new Date(value)).fromNow(true) : '';
3818
- }
3819
-
3820
- function updateCardTimer() {
3821
- if (devices) {
3822
- devices.forEach((dev) => {
3823
- const id = dev._id;
3824
- if (id) {
3825
- const rid = id.split('.').join('_');
3826
- $(`#${rid}_link_quality_lc`).text(getIdleTime(dev.link_quality_lc));
3827
- }
3828
- });
3829
- }
3830
- }
3831
4036
 
3832
4037
  function updateDevice(id) {
3833
- sendTo(namespace, 'getDevice', {id: id}, function (msg) {
3834
- if (msg) {
3835
- const devs = msg.devices;
3836
- if (devs) {
3837
- if (devs.error) {
3838
- showMessage(devs.error, _('Error'));
3839
- } else {
3840
- removeDevice(id);
3841
- devs.forEach(dev => devices.push(dev));
3842
- showDevices();
4038
+ if (devices.length > 0)
4039
+ sendToWrapper(namespace, 'getDevice', {id: id}, function (msg) {
4040
+ if (msg) {
4041
+ const devs = msg.devices;
4042
+ if (devs) {
4043
+ if (devs.error) {
4044
+ showMessage(devs.error, _('Error'));
4045
+ } else {
4046
+ removeDevice(id);
4047
+ devs.forEach(dev => devices.push(dev));
4048
+ showDevices();
4049
+ }
3843
4050
  }
3844
4051
  }
3845
- }
3846
- });
4052
+ });
4053
+ else sendToWrapper(namespace, 'getDevices', {}, extractDevicesData)
3847
4054
  }
3848
4055
 
3849
4056
  function removeDevice(id) {
@@ -3860,13 +4067,13 @@ function swapActive(id) {
3860
4067
  const dev = getDeviceByID(id);
3861
4068
  if (dev && dev.common) {
3862
4069
  dev.common.deactivated = !(dev.common.deactivated);
3863
- sendTo(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
4070
+ sendToWrapper(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
3864
4071
  showDevices();
3865
4072
  });
3866
4073
  }
3867
4074
  }
3868
4075
 
3869
- function reconfigureDlg(id) {
4076
+ function reconfigureConfirmation(id) {
3870
4077
  const text = translateWord(`Do you really want to reconfigure device?`);
3871
4078
  $('#modalreconfigure').find('p').text(text);
3872
4079
  $('#modalreconfigure a.btn[name=\'yes\']').unbind('click');
@@ -3878,7 +4085,7 @@ function reconfigureDlg(id) {
3878
4085
  }
3879
4086
 
3880
4087
  function reconfigureDevice(id) {
3881
- sendTo(namespace, 'reconfigure', {id: id}, function (msg) {
4088
+ sendToWrapper(namespace, 'reconfigure', {id: id}, function (msg) {
3882
4089
  closeWaitingDialog();
3883
4090
  if (msg) {
3884
4091
  if (msg.error) {
@@ -3899,34 +4106,28 @@ function validateConfigData(key, val) {
3899
4106
  if (validatableKeys.indexOf(key) < 0 || !val) return;
3900
4107
  if (warnLevel[key]) {
3901
4108
  if (warnLevel[key](val)) {
3902
- //console.warn(`warning set for ${key} (${val})`)
3903
4109
  $(`#${key}_ALERT`).removeClass('hide')
3904
4110
  } else $(`#${key}_ALERT`).addClass('hide')
3905
4111
  }
3906
4112
  if (nvRamBackup[key]) {
3907
- //console.warn(`value of ${key} is ${val} (${nvRamBackup[key]})`);
3908
4113
  if ((typeof val == 'string' && typeof nvRamBackup[key] == 'string' && val.toLowerCase == nvRamBackup[key].toLowerCase) || val == nvRamBackup[key])
3909
4114
  {
3910
- //console.warn(`ok set for ${key} (${val})`)
3911
4115
  $(`#${key}_OK`).removeClass('hide')
3912
4116
  $(`#${key}_NOK`).addClass('hide')
3913
4117
  }
3914
4118
  else
3915
4119
  {
3916
- //console.warn(`nok set for ${key} (${val})`)
3917
4120
  $(`#${key}_OK`).addClass('hide')
3918
4121
  $(`#${key}_NOK`).removeClass('hide')
3919
4122
  }
3920
4123
  }
3921
4124
  else {
3922
- //console.warn(`noval set for ${key} (${val})`)
3923
4125
  $(`#${key}_OK`).addClass('hide')
3924
4126
  $(`#${key}_NOK`).addClass('hide')
3925
4127
  }
3926
4128
  }
3927
4129
 
3928
4130
  function validateNVRamBackup(update, src) {
3929
- //console.warn('validateNVRam');
3930
4131
  const validatedKeys = src ? [src] : validatableKeys;
3931
4132
  const validator = {};
3932
4133
  for (const key of validatedKeys) {
@@ -3944,9 +4145,7 @@ function validateNVRamBackup(update, src) {
3944
4145
 
3945
4146
 
3946
4147
  function readNVRamBackup(update) {
3947
- console.warn('read nvRam')
3948
- sendTo(namespace, 'readNVRam', {}, function(msg) {
3949
- console.warn(JSON.stringify(msg));
4148
+ sendToWrapper(namespace, 'readNVRam', {}, function(msg) {
3950
4149
  if (msg) {
3951
4150
  if (msg.error && update) {
3952
4151
  if (msg.error.includes('ENOENT')) showMessage('Unable to read nvRam backup - no backup available.',_('Error'))