iobroker.zigbee 3.1.4 → 3.1.5

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
@@ -64,23 +64,89 @@ const savedSettings = [
64
64
  'adapterType', 'debugHerdsman', 'disableBackup', 'external', 'startWithInconsistent','pingTimeout','listDevicesAtStart',
65
65
  'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart', 'readAtAnnounce', 'startReadDelay', 'readAllAtStart','pingCluster'
66
66
  ];
67
+ const lockout = {
68
+ timeoutid:undefined,
69
+ isActive:false,
70
+ };
71
+
72
+ const connectionStatus = {
73
+ connected: false,
74
+ lastcheck: Date.now(),
75
+ }
76
+
77
+
78
+ ////
79
+ //
80
+ //. section Alive
81
+ //
82
+ ////
83
+
84
+ function keepAlive(callback) {
85
+ const responseTimeout = setTimeout(function() {
86
+ UpdateAdapterAlive(false); }, 500);
87
+ sendTo(namespace, 'aliveCheck', {}, function(msg) {
88
+ clearTimeout(responseTimeout);
89
+ UpdateAdapterAlive(true);
90
+ if (callback) callback();
91
+ });
92
+ }
93
+
94
+ function startKeepalive() {
95
+ return setInterval(keepAlive, 10000);
96
+ }
97
+
98
+ function UpdateAdapterAlive(state) {
99
+ if (connectionStatus.connected === state) return;
100
+ connectionStatus.time = Date.now();
101
+ if (state) {
102
+ $('#adapterStopped_btn').addClass('hide');
103
+ $('#code_pairing').removeClass('disabled');
104
+ $('#touchlink_btn').removeClass('disabled');
105
+ $('#add_grp_btn').removeClass('disabled');
106
+ $('#fw_check_btn').removeClass('disabled');
107
+ $('#ErrorNotificationBtn').removeClass('disabled');
108
+ $('#show_errors_btn').removeClass('disabled');
109
+ $('#download_icons_btn').removeClass('disabled');
110
+ $('#pairing').removeClass('disabled');
111
+ }
112
+ else {
113
+ $('#adapterStopped_btn').removeClass('hide');
114
+ $('#code_pairing').addClass('disabled');
115
+ $('#touchlink_btn').addClass('disabled');
116
+ $('#add_grp_btn').addClass('disabled');
117
+ $('#fw_check_btn').addClass('disabled');
118
+ $('#ErrorNotificationBtn').addClass('disabled');
119
+ $('#show_errors_btn').addClass('disabled');
120
+ $('#pairing').addClass('disabled');
121
+ $('#download_icons_btn').addClass('disabled');
122
+ }
123
+ connectionStatus.connected = state;
124
+ }
125
+
126
+
127
+ ////
128
+ //
129
+ // Utility functions
130
+ //
131
+ ////
132
+ function sendToWrapper(target,command,msg,callback) {
133
+ if (connectionStatus.connected)
134
+ sendTo(target,command,msg,callback);
135
+ else if (callback) callback({error:'Cannot execute command - adapter is not running'});
136
+ }
67
137
 
68
138
  function getDeviceByID(ID) {
69
139
  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
- }
140
+ return (devInfo ? devInfo._id : '') == ID;
75
141
  });
76
142
  }
77
143
 
78
- function getDevice(ieeeAddr) {
144
+ function getDeviceByIEEE(ieeeAddr) {
79
145
  return devices.find((devInfo) => {
80
146
  try {
81
147
  return devInfo.info.device.ieee == ieeeAddr;
82
148
  } catch (e) {
83
- //console.log("No dev with ieee " + ieeeAddr);
149
+ return false;
84
150
  }
85
151
  });
86
152
  }
@@ -91,7 +157,7 @@ function getDeviceByNetwork(nwk) {
91
157
  try {
92
158
  return devInfo.info.device.nwk == nwk;
93
159
  } catch (e) {
94
- //console.log("No dev with nwkAddr " + nwk);
160
+ return false;
95
161
  }
96
162
  });
97
163
  }
@@ -112,6 +178,198 @@ function getLQICls(value) {
112
178
  return 'icon-green';
113
179
  }
114
180
 
181
+ function sanitizeModelParameter(parameter) {
182
+ const replaceByUnderscore = /[\s/]/g;
183
+ return parameter.replace(replaceByUnderscore, '_');
184
+ }
185
+
186
+ /////
187
+ //
188
+ // Section Local Data
189
+ //
190
+ ////
191
+
192
+
193
+ function getModelData(data) {
194
+ console.warn(JSON.stringify(data));
195
+ const devicesByModel = {};
196
+ for (const dev of data) {
197
+ const modelID = dev.info?.mapped?.model || dev.info.device.modelZigbee || dev.info.device.name || 'unknown';
198
+ if (devicesByModel[modelID])
199
+ devicesByModel[modelID].devices.push(dev);
200
+ else devicesByModel[modelID] = {devices:[dev], icon:dev.common.icon};
201
+ }
202
+ console.warn(JSON.stringify(devicesByModel));
203
+ const Html = [];
204
+ // Html.push(`<ul class="collapsible">`);
205
+ Html.push(`<ul class="collection">`)
206
+ for (const key of Object.keys(devicesByModel)) {
207
+ const model = devicesByModel[key];
208
+ Html.push(`<li class="collection-item avatar>`);
209
+ //Html.push(`<li>`)
210
+ //Html.push(`<div class="collapsible-header"><img src=${model.iccon} alt="" class="circle" width="40" height="auto">&nbsp;Paired Models</div>`);
211
+ //Html.push(`<div class="collapsile-body"<span>${getDeviceData(model.devices)}</span></div>`);
212
+ Html.push(`<img src = ${model.iccon} alt="" class="circle" width="40" height="auto">`);
213
+ Html.push(`<span class=title></p>`);
214
+ Html.push(getDeviceData(model.devices).join('<br>'))
215
+ Html.push(`</p><a href="#!" class="secondary-content"><i class="material-icons">grade</i></a></li>`)
216
+ }
217
+ Html.push('</ul>');
218
+ return Html;
219
+ }
220
+ function getDeviceData(deviceList, withIcon) {
221
+ const Html = [`<div class="container">`];
222
+ for (const dev of deviceList) {
223
+ const iconLink = `<img src=${dev.common.icon} class="circle" width="40" height="auto">`;
224
+ Html.push(`<div="row"><div class="col s4">${withIcon ? iconLink : ''}<br>${dev.info.device.ieee}<br>connectedInfo</div>`)
225
+ Html.push(`<div class=col s4>Device Name:${dev.common.name}</div><div class=col s4>Connected: true</div></div>`);
226
+ if (dev.options) {
227
+ Html.push(`<div="row"><div class="col s3">Options</div>`)
228
+ for (const o of dev.options) {
229
+ Html.push(`<div class=col s4>${o.key}</div><div class=col s4>${o.value}</div><div>`);
230
+ }
231
+ Html.push(`</div>`);
232
+ }
233
+ Html.push(`</div>`)
234
+ }
235
+ Html.push(`</div>`)
236
+ return Html;
237
+ }
238
+ function getGlobalOptionData() {
239
+ return ['No Data Yet'];
240
+ }
241
+
242
+ function showLocalData() {
243
+ return;
244
+ /*
245
+ const Html = [];
246
+
247
+ Html.push(`<ul class="collapsible">`);
248
+ Html.push('<li>')
249
+ Html.push (`<li class="active"><div class="collapsible-header">
250
+ Paired Models
251
+ </div>`);
252
+ Html.push (`<div class="collapsible-body">
253
+ <span>${getModelData(devices).join('')}</span>
254
+ </div>`);
255
+ Html.push ('</li><li>')
256
+ Html.push (`<div class="collapsible-header">
257
+ Paired Devices
258
+ </div>`);
259
+ Html.push (`<div class="collapsible-body">
260
+ <span>${getDeviceData(devices, true).join('')}</span>
261
+ </div>`);
262
+ Html.push ('</li><li>')
263
+ Html.push (`<div class="collapsible-header">
264
+ Global Options
265
+ </div>`);
266
+ Html.push (`<div class="collapsible-body">
267
+ <span>${getGlobalOptionData(devices).join('')}</span>
268
+ </div>`);
269
+ Html.push ('</li>')
270
+ Html.push (`</ul>`);
271
+ $('#tab-overrides').html(Html.join(''));
272
+ $('.collapsible').collapsible();
273
+ */
274
+ }
275
+
276
+ /////
277
+ //
278
+ // Section Cards
279
+ //
280
+ ////
281
+
282
+ function getCard(dev) {
283
+ if (!dev._id) return '';
284
+ const title = dev.common.name,
285
+ id = (dev._id ? dev._id : ''),
286
+ type = (dev.common.type ? dev.common.type : 'unknown'),
287
+ type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'),
288
+ img_src = dev.common.icon || dev.icon,
289
+ rooms = [],
290
+ isActive = (dev.common.deactivated ? false : true),
291
+ lang = systemLang || 'en',
292
+ ieee = id.replace(namespace + '.', ''),
293
+ isDebug = checkDebugDevice(ieee);
294
+ for (const r in dev.rooms) {
295
+ if (dev.rooms[r].hasOwnProperty(lang)) {
296
+ rooms.push(dev.rooms[r][lang]);
297
+ } else {
298
+ rooms.push(dev.rooms[r]);
299
+ }
300
+ }
301
+ const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
302
+ const rid = id.split('.').join('_');
303
+ const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
304
+ const groupInfo = dev.groupNames ? `<li><span class="labelinfo">groups:</span><span>${dev.groupNames || ''}</span></li>` : '';
305
+ const roomInfo = rooms.length ? `<li><span class="labelinfo">rooms:</span><span>${rooms.join(',') || ''}</span></li>` : '';
306
+ const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
307
+ nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
308
+ battery_cls = (isActive ? getBatteryCls(dev.battery) : ''),
309
+ lqi_cls = getLQICls(dev.link_quality),
310
+ 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>` : '',
311
+ lq = (dev.link_quality > 0)
312
+ ? `<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>`
313
+ : `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`,
314
+ status = (isActive ? lq : `<div class="col tool"><i class="material-icons icon-red">cancel</i></div>`),
315
+ info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
316
+ <ul>
317
+ <li><span class="labelinfo">ieee:</span><span>0x${ieee}</span></li>
318
+ <li><span class="labelinfo">nwk:</span><span>${(nwk) ? nwk.toString() + ' (0x' + nwk.toString(16) + ')' : ''}</span></li>
319
+ <li><span class="labelinfo">model:</span><span>${modelUrl}</span></li>
320
+ ${groupInfo}
321
+ ${roomInfo}
322
+ </ul>
323
+ </div>`,
324
+ 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>`,
325
+ 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>`,
326
+ infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
327
+
328
+ const dashCard = getDashCard(dev);
329
+ const card = `<div id="${id}" class="device">
330
+ <div class="card hoverable flipable ${isActive ? '' : 'bg_red'}">
331
+ <div class="front face">${dashCard}</div>
332
+ <div class="back face">
333
+ <div class="card-content zcard">
334
+ <div class="flip" style="cursor: pointer">
335
+ <span class="top right small" style="border-radius: 50%">
336
+ ${battery}
337
+ <!--${lq}-->
338
+ ${status}
339
+ </span>
340
+ <!--/a--!>
341
+ <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
342
+ </div>
343
+ <i class="left">${image}</i>
344
+ ${info}
345
+ <div class="footer right-align"></div>
346
+ </div>
347
+ <div class="card-action">
348
+ <div class="card-reveal-buttons">
349
+ ${infoBtn}
350
+
351
+ <span class="left fw_info"></span>
352
+ <button name="delete" class="right btn-flat btn-small tooltipped" title="Delete">
353
+ <i class="material-icons icon-red">delete</i>
354
+ </button>
355
+ <button name="edit" class="right btn-flat btn-small tooltipped" title="Edit">
356
+ <i class="material-icons icon-black">edit</i>
357
+ </button>
358
+ <button name="swapimage" class="right btn-flat btn-small tooltipped" title="Select Image">
359
+ <i class="material-icons icon-black">image</i>
360
+ </button>
361
+ <button name="reconfigure" class="right btn-flat btn-small tooltipped" title="Reconfigure">
362
+ <i class="material-icons icon-red">sync</i>
363
+ </button>
364
+ ${deactBtn}
365
+ ${debugBtn}
366
+ </div>
367
+ </div>
368
+ </div>
369
+ </div>
370
+ </div>`;
371
+ return card;
372
+ }
115
373
 
116
374
  function getCoordinatorCard(dev) {
117
375
  const title = 'Zigbee Coordinator',
@@ -228,103 +486,174 @@ function getGroupCard(dev) {
228
486
  return card;
229
487
  }
230
488
 
231
- function sanitizeModelParameter(parameter) {
232
- const replaceByUnderscore = /[\s/]/g;
233
- return parameter.replace(replaceByUnderscore, '_');
489
+ function getDeviceCards() {
490
+ return $('#devices .device').not('.group');
234
491
  }
235
492
 
236
- function getCard(dev) {
237
- //console.warn(JSON.stringify(dev));
238
- if (!dev._id) return '';
493
+ function getDeviceCard(devId) {
494
+ if (devId.startsWith('0x')) {
495
+ devId = devId.substr(2, devId.length);
496
+ }
497
+ return $('#devices').find(`div[id='${namespace}.${devId}']`);
498
+ }
499
+
500
+ function getDashCard(dev, groupImage, groupstatus) {
239
501
  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,
502
+ id = dev._id,
503
+ type = dev.common.type,
504
+ img_src = (groupImage ? groupImage : dev.common.icon || dev.icon),
505
+ isActive = !dev.common.deactivated,
244
506
  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
- }
507
+ lang = systemLang || 'en';
256
508
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
509
+ 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>`;
510
+ 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
511
  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';">`,
512
+ const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
513
+ const image = `<img src="${img_src}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
262
514
  nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
263
- battery_cls = (isActive ? getBatteryCls(dev.battery) : ''),
515
+ battery_cls = getBatteryCls(dev.battery),
264
516
  lqi_cls = getLQICls(dev.link_quality),
517
+ 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
518
  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>` : '';
519
+ 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 : ''),
520
+ //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>`),
521
+ //infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
522
+ 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>` : '';
523
+ const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => {
524
+ const id = stateDef.id;
525
+ const sid = id.split('.').join('_');
526
+ let val = stateDef.val || '';
527
+ if (stateDef.role === 'switch' && stateDef.write) {
528
+ val = `<span class="switch"><label><input type="checkbox" ${(val) ? 'checked' : ''}><span class="lever"></span></label></span>`;
529
+ } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
530
+ val = `<span class="range-field dash"><input type="range" min="0" max="100" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
531
+ } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
532
+ val = `<span class="range-field dash"><input type="range" min="150" max="500" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
533
+ } else if (stateDef.type === 'boolean') {
534
+ const disabled = (stateDef.write) ? '' : 'disabled="disabled"';
535
+ val = `<label class="dash"><input type="checkbox" ${(val == true) ? 'checked=\'checked\'' : ''} ${disabled}/><span></span></label>`;
536
+ } else if (stateDef.role === 'level.color.rgb') {
537
+ const options = []
538
+ for (const key of namedColors) {
539
+ options.push(`<option value="${key}" ${val===key ? 'selected' : ''}>${key}</option>`);
540
+ }
541
+ val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
542
+ } else if (stateDef.states && stateDef.write) {
543
+ let options;
544
+ if (typeof stateDef.states == 'string') {
545
+ const sts = stateDef.states.split(';');
546
+ if (sts.length < 2) return '';
547
+ options = sts.map((item) => {
548
+ const v = item.split(':');
549
+ return `<option value="${v[0]}" ${(val == v[0]) ? 'selected' : ''}>${v[1]}</option>`;
550
+ });
551
+ } else {
552
+ options = [];
553
+ for (const [key, value] of Object.entries(stateDef.states)) {
554
+ options.push(`<option value="${key}" ${(val == key) ? 'selected' : ''}>${key}</option>`);
555
+ }
556
+ }
557
+ if (options.length < 2) return '';
558
+ val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
559
+ } else if (stateDef.write) {
560
+ return;
561
+ // val = `<span class="input-field dash value"><input class="dash value" id="${stateDef.name}" value="${val}"></input></span>`;
562
+ }
563
+ else {
564
+ val = `<span class="dash value">${val ? val : '(null)'} ${(stateDef.unit) ? stateDef.unit : ''}</span>`;
565
+ }
566
+ return `<li><span class="label dash truncate">${stateDef.name}</span><span id=${sid} oid=${id} class="state">${val}</span></li>`;
567
+ }).join('') : '';
568
+ const dashCard = `
569
+ <div class="card-content zcard ${isActive ? '' : 'bg_red'}">
570
+ <div style="cursor: pointer">
571
+ <span class="top right small" style="border-radius: 50%">
572
+ ${device_queryBtn}
573
+ ${permitJoinBtn}
574
+ </span>
575
+ <div class="flip">
576
+ <span class="top right small" style="border-radius: 50%">
577
+ ${idleTime}
578
+ ${battery}
579
+ ${lq}
580
+ </span>
581
+ <span class="card-title truncate">${title}</span>
582
+ </div>
583
+ </div>
584
+ <i class="left">${image}</i>
585
+ <div style="min-height:88px; font-size: 0.8em; height: 130px; width: 220px; overflow-y: auto" class="truncate">
586
+ <ul>
587
+ ${(isActive ? info : 'Device deactivated')}
588
+ </ul>
589
+ </div>
590
+ <div class="footer right-align"></div>
591
+ </div>`;
592
+
593
+ return dashCard;
594
+ }
595
+
596
+ function setDashStates(id, state) {
597
+ const devId = getDevId(id);
598
+ const dev = getDeviceByID(devId);
599
+ if (dev) {
600
+ const stateDef = dev.statesDef.find((stateDef) => stateDef.id == id);
601
+ if (stateDef) {
602
+ const sid = id.split('.').join('_');
603
+ if (stateDef.role === 'switch' && stateDef.write) {
604
+ $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
605
+ } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
606
+ $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
607
+ } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
608
+ $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
609
+ } else if (stateDef.states && stateDef.write) {
610
+ $(`#${sid}`).find(`select option[value=${state.val}]`).prop('selected', true);
611
+ } else if (stateDef.type === 'boolean') {
612
+ $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
613
+ } else {
614
+ $(`#${sid}`).find('.value').text(`${state.val} ${(stateDef.unit) ? stateDef.unit : ''}`);
615
+ }
616
+ }
617
+ }
618
+ }
282
619
 
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}
620
+ function hookControls() {
621
+ $('input[type=\'checkbox\']').change(function (event) {
622
+ const val = $(this).is(':checked');
623
+ const id = $(this).parents('.state').attr('oid');
624
+ sendToWrapper(namespace, 'setState', {id: id, val: val}, function (data) {
625
+ });
626
+ });
627
+ $('input[type=\'range\']').change(function (event) {
628
+ const val = $(this).val();
629
+ const id = $(this).parents('.state').attr('oid');
630
+ sendToWrapper(namespace, 'setState', {id: id, val: val}, function (data) {
631
+ });
632
+ });
633
+ $('.state select').on('change', function () {
634
+ const val = $(this).val();
635
+ const id = $(this).parents('.state').attr('oid');
636
+ sendToWrapper(namespace, 'setState', {id: id, val: val}, function (data) {
637
+ });
638
+ });
639
+ }
305
640
 
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;
641
+ function getIdleTime(value) {
642
+ return (value) ? moment(new Date(value)).fromNow(true) : '';
643
+ }
644
+
645
+ function updateCardTimer() {
646
+ if (devices) {
647
+ devices.forEach((dev) => {
648
+ const id = dev._id;
649
+ if (id) {
650
+ const rid = id.split('.').join('_');
651
+ $(`#${rid}_link_quality_lc`).text(getIdleTime(dev.link_quality_lc));
652
+ }
653
+ });
654
+ }
327
655
  }
656
+
328
657
  /*
329
658
  function openReval(e, id, name){
330
659
  const $card = $(e.target).closest('.card');
@@ -379,6 +708,17 @@ function closeReval(e, id) {
379
708
  });
380
709
  }
381
710
 
711
+ function showDevInfo(id) {
712
+ const info = genDevInfo(getDeviceByID(id));
713
+ $('#devinfo').html(info);
714
+ $('#modaldevinfo').modal('open');
715
+ }
716
+
717
+ ////
718
+ //
719
+ // section Confirmations
720
+ //
721
+ ////
382
722
  function deleteConfirmation(id, name) {
383
723
  const text = translateWord('Do you really want to delete device') + ' "' + name + '" (' + id + ')?';
384
724
  $('#modaldelete').find('p').text(text);
@@ -402,7 +742,7 @@ function deleteNvBackupConfirmation() {
402
742
  $('#modaldelete a.btn[name=\'yes\']').click(() => {
403
743
  //const force = $('#force').prop('checked');
404
744
  showWaitingDialog('Attempting to delete nvBackup.json', 60000);
405
- sendTo(namespace, 'deleteNVBackup', {}, function (msg) {
745
+ sendToWrapper(namespace, 'deleteNVBackup', {}, function (msg) {
406
746
  closeWaitingDialog();
407
747
  if (msg) {
408
748
  if (msg.error) {
@@ -444,211 +784,62 @@ function EndPointIDfromEndPoint(ep) {
444
784
 
445
785
  function editName(id, name) {
446
786
 
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;
787
+ function updateGroupables(groupables) {
788
+ const html = [];
789
+ if (groupables && groupables.length > 0)
790
+ {
791
+ for (const groupable of groupables) {
792
+ const k = groupable.ep.ID || -1;
793
+ const n = groupable.epid != `unidentified` ? groupable.epid : `Endpoint ${k}`;
794
+ 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
795
  }
527
- if (device_options[k].key.length > 0) {
528
- _no[key] = device_options[k].value;
529
- changed |= _no[key] != _so[key];
796
+ $('#modaledit').find('.endpoints_for_groups').html(html.join(''));
797
+ for (const groupable of groupables) {
798
+ console.warn(`list 2 select called with ${groupable.ep.ID}, groups ${JSON.stringify(groups)}, groupable ${JSON.stringify(groupable)}`);
799
+ list2select(`#gk_${groupable.ep.ID || -1}`, groups, groupable.memberOf || []);
530
800
  }
531
801
  }
532
- changed |= (Object.keys(_no).length != Object.keys(_so).length);
533
- if (changed) return _no;
534
- return undefined;
802
+ return html;
535
803
  }
536
804
 
537
805
 
538
-
806
+ const dev = devices.find((d) => d._id == id);
807
+ $('#modaledit').find('input[id=\'d_name\']').val(name);
808
+ const groupables = [];
539
809
  if (dev && dev.info && dev.info.endpoints) {
540
810
  for (const ep of dev.info.endpoints) {
541
811
  if (ep.input_clusters.includes(4)) {
542
- groupables.push({epid: EndPointIDfromEndPoint(ep), ep: ep, memberOf: []});
812
+ groupables.push({epid: EndPointIDfromEndPoint(ep), ep: ep, memberOf: dev.groups_by_ep ? dev.groups_by_ep[ep.ID] || [] : []});
543
813
  }
544
814
  }
545
815
  }
546
816
  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
817
 
613
- } else showMessage('callback without message');
614
- });
818
+ updateGroupables(groupables);
615
819
  $('#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
820
  $('#modaledit a.btn[name=\'save\']').click(() => {
623
821
  const newName = $('#modaledit').find('input[id=\'d_name\']').val();
624
- const groupsbyid = {};
822
+ const groupsById = {};
625
823
  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
- });
824
+ for (const groupable of groupables) {
825
+ const k = groupable.ep.ID || -1;
826
+ const ng = $('#gk_' + k).val();
827
+ if (ng.toString() != groupable.memberOf.toString())
828
+ groupsById[k] = GenerateGroupChange(groupable.memberOf, ng);
829
+ }
644
830
  }
645
- updateDev(id, newName, groupsbyid);
646
-
831
+ updateDev(id, newName, groupsById);
647
832
  });
648
833
  $('#modaledit').modal('open');
649
834
  Materialize.updateTextFields();
650
835
  }
651
836
 
837
+
838
+ ////
839
+ //
840
+ //. section GroupFunctions
841
+ //
842
+ ////
652
843
  function GenerateGroupChange(oldmembers, newmembers) {
653
844
  const grpchng = [];
654
845
  for (const oldg of oldmembers)
@@ -659,7 +850,7 @@ function GenerateGroupChange(oldmembers, newmembers) {
659
850
  }
660
851
 
661
852
  function deleteZigbeeDevice(id, force) {
662
- sendTo(namespace, 'deleteZigbeeDevice', {id: id, force: force}, function (msg) {
853
+ sendToWrapper(namespace, 'deleteZigbeeDevice', {id: id, force: force}, function (msg) {
663
854
  closeWaitingDialog();
664
855
  if (msg) {
665
856
  if (msg.error) {
@@ -674,7 +865,7 @@ function deleteZigbeeDevice(id, force) {
674
865
 
675
866
 
676
867
  function cleanDeviceStates(force) {
677
- sendTo(namespace, 'cleanDeviceStates', {force: force}, function (msg) {
868
+ sendToWrapper(namespace, 'cleanDeviceStates', {force: force}, function (msg) {
678
869
  closeWaitingDialog();
679
870
  if (msg) {
680
871
  if (msg.error) {
@@ -692,7 +883,7 @@ function cleanDeviceStates(force) {
692
883
 
693
884
  function renameDevice(id, name) {
694
885
  showMessage('rename device with ' + id + ' and ' + name, _('Error'));
695
- sendTo(namespace, 'renameDevice', {id: id, name: name}, function (msg) {
886
+ sendToWrapper(namespace, 'renameDevice', {id: id, name: name}, function (msg) {
696
887
  if (msg) {
697
888
  if (msg.error) {
698
889
  showMessage(msg.error, _('Error'));
@@ -704,7 +895,6 @@ function renameDevice(id, name) {
704
895
  }
705
896
 
706
897
  function showDevices() {
707
- console.warn('show Devices called')
708
898
  let html = '';
709
899
  let hasCoordinator = false;
710
900
  const lang = systemLang || 'en';
@@ -796,17 +986,20 @@ function showDevices() {
796
986
  $('.card.flipable').toggleClass('flipped');
797
987
  });
798
988
 
799
- shuffleInstance = new Shuffle($('#devices'), {
800
- itemSelector: '.device',
801
- sizer: '.js-shuffle-sizer',
802
- });
803
- doFilter();
989
+ const element = $('#devices');
990
+
991
+ if (element) {
992
+ shuffleInstance = devices && devices.length ? new Shuffle(element, {
993
+ itemSelector: '.device',
994
+ sizer: '.js-shuffle-sizer',
995
+ }) : undefined;
996
+ doFilter();
997
+ }
804
998
 
805
999
  const getDevName = function (dev_block) {
806
1000
  return dev_block.find('#dName').text();
807
1001
  };
808
1002
  const getDevId = function (dev_block) {
809
- console.warn(`getDevId called with ${JSON.stringify(dev_block)}`)
810
1003
  return dev_block.attr('id');
811
1004
  };
812
1005
  $('.card-reveal-buttons button[name=\'delete\']').click(function () {
@@ -856,7 +1049,7 @@ function showDevices() {
856
1049
  //console.log(data);
857
1050
  }); });
858
1051
  $('#modalpairing a.btn[name=\'extendpairing\']').click(function () {
859
- letsPairing();
1052
+ openNetwork();
860
1053
  });
861
1054
  $('#modalpairing a.btn[name=\'endpairing\']').click(function () {
862
1055
  stopPairing();
@@ -874,7 +1067,7 @@ function showDevices() {
874
1067
  });
875
1068
  $('.card-reveal-buttons button[name=\'reconfigure\']').click(function () {
876
1069
  const dev_block = $(this).parents('div.device');
877
- reconfigureDlg(getDevId(dev_block));
1070
+ reconfigureConfirmation(getDevId(dev_block));
878
1071
  });
879
1072
  $('.card-reveal-buttons button[name=\'swapactive\']').click(function () {
880
1073
  const dev_block = $(this).parents('div.device');
@@ -886,7 +1079,7 @@ function showDevices() {
886
1079
  }
887
1080
 
888
1081
  function downloadIcons() {
889
- sendTo(namespace, 'downloadIcons', {}, function (msg) {
1082
+ sendToWrapper(namespace, 'downloadIcons', {}, function (msg) {
890
1083
  if (msg && msg.msg) {
891
1084
  showMessage(msg.msg, _('Result'));
892
1085
  }
@@ -915,9 +1108,8 @@ function checkFwUpdate() {
915
1108
  fwInfoNode.html(createBtn('system_update', 'Click to start firmware update', false));
916
1109
  $(fwInfoNode).find('button[name=\'fw_update\']').click(() => {
917
1110
  fwInfoNode.html(createBtn('check_circle', 'Firmware update started, check progress in logs.', true, 'icon-blue'));
918
- sendTo(namespace, 'startOta', {devId: devId}, (msg) => {
1111
+ sendToWrapper(namespace, 'startOta', {devId: devId}, (msg) => {
919
1112
  fwInfoNode.html(createBtn('check_circle', 'Finished, see logs.', true));
920
- console.log(msg);
921
1113
  });
922
1114
  });
923
1115
  } else if (msg.status == 'not_available') {
@@ -937,13 +1129,13 @@ function checkFwUpdate() {
937
1129
  }
938
1130
  const devId = getDevId(devIdAttr);
939
1131
  getFwInfoNode(deviceCard).html('<span class="left" style="padding-top:8px">checking...</span>');
940
- sendTo(namespace, 'checkOtaAvail', {devId: devId}, callback);
1132
+ sendToWrapper(namespace, 'checkOtaAvail', {devId: devId}, callback);
941
1133
  }
942
1134
  }
943
1135
 
944
1136
  function letsPairingWithCode(code) {
945
1137
  messages = [];
946
- sendTo(namespace, 'letsPairing', {code: code, stop:false}, function (msg) {
1138
+ sendToWrapper(namespace, 'letsPairing', {code: code, stop:false}, function (msg) {
947
1139
  if (msg && msg.error) {
948
1140
  showMessage(msg.error, _('Error'));
949
1141
  }
@@ -953,18 +1145,19 @@ function letsPairingWithCode(code) {
953
1145
  });
954
1146
  }
955
1147
 
956
- function letsPairing() {
1148
+ function openNetwork() {
957
1149
  messages = [];
958
- sendTo(namespace, 'letsPairing', {stop:false}, function (msg) {
1150
+ sendToWrapper(namespace, 'letsPairing', {stop:false}, function (msg) {
959
1151
  if (msg && msg.error) {
960
1152
  showMessage(msg.error, _('Error'));
961
1153
  }
1154
+ else showPairingProcess();
962
1155
  });
963
1156
  }
964
1157
 
965
1158
  function stopPairing() {
966
1159
  messages = [];
967
- sendTo(namespace, 'letsPairing', {stop:true}, function (msg) {
1160
+ sendToWrapper(namespace, 'letsPairing', {stop:true}, function (msg) {
968
1161
  if (msg && msg.error) {
969
1162
  showMessage(msg.error, _('Error'));
970
1163
  }
@@ -973,7 +1166,7 @@ function stopPairing() {
973
1166
 
974
1167
  function touchlinkReset() {
975
1168
  messages = [];
976
- sendTo(namespace, 'touchlinkReset', {}, function (msg) {
1169
+ sendToWrapper(namespace, 'touchlinkReset', {}, function (msg) {
977
1170
  if (msg && msg.error) {
978
1171
  showMessage(msg.error, _('Error'));
979
1172
  }
@@ -982,7 +1175,7 @@ function touchlinkReset() {
982
1175
 
983
1176
  function joinProcess(devId) {
984
1177
  messages = [];
985
- sendTo(namespace, 'letsPairing', {id: devId, stop:false}, function (msg) {
1178
+ sendToWrapper(namespace, 'letsPairing', {id: devId, stop:false}, function (msg) {
986
1179
  if (msg && msg.error) {
987
1180
  showMessage(msg.error, _('Error'));
988
1181
  }
@@ -990,10 +1183,8 @@ function joinProcess(devId) {
990
1183
  }
991
1184
 
992
1185
  function getCoordinatorInfo() {
993
- console.warn('calling getCoordinatorInfo');
994
- sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
1186
+ sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
995
1187
  if (msg) {
996
- console.warn(JSON.stringify(msg))
997
1188
  if (msg.error) {
998
1189
  errorData.push(msg.error);
999
1190
  delete msg.error;
@@ -1020,9 +1211,10 @@ function checkDebugDevice(id) {
1020
1211
  }
1021
1212
  return -1;
1022
1213
  }
1214
+
1023
1215
  async function toggleDebugDevice(id) {
1024
- sendTo(namespace, 'setDeviceDebug', {id:id}, function (msg) {
1025
- sendTo(namespace, 'getDebugDevices', {}, function(msg) {
1216
+ sendToWrapper(namespace, 'setDeviceDebug', {id:id}, function (msg) {
1217
+ sendToWrapper(namespace, 'getDebugDevices', {}, function(msg) {
1026
1218
  if (msg && typeof (msg.debugDevices == 'array')) {
1027
1219
  debugDevices = msg.debugDevices;
1028
1220
  }
@@ -1034,46 +1226,136 @@ async function toggleDebugDevice(id) {
1034
1226
  }
1035
1227
 
1036
1228
  function updateLocalConfigItems(device, data, global) {
1037
- sendTo(namespace, 'updateLocalConfigItems', {target: device, data:data, global:global}, function(msg) {
1229
+ sendToWrapper(namespace, 'updateLocalConfigItems', {target: device, data:data, global:global}, function(msg) {
1038
1230
  if (msg && msg.hasOwnProperty.error) {
1039
1231
  showMessage(msg.error, _('Error'));
1040
1232
  }
1041
- getDevices();
1042
- });
1043
- }
1233
+ getDevices();
1234
+ });
1235
+ }
1236
+
1237
+ async function selectImageOverride(id) {
1238
+
1239
+ // start local functions
1240
+ function removeOption(k) {
1241
+ if (k && device_options.hasOwnProperty(k)) {
1242
+ if (dev.info.mapped && dev.info.mapped.options && dev.info.mapped.options.includes(device_options[k].key))
1243
+ availableOptions.push(device_options[k].key)
1244
+ delete device_options[k];
1245
+ }
1246
+ }
1247
+
1248
+ function addOption() {
1249
+ let idx=1;
1250
+ let key = '';
1251
+ const optionName = $('#option_Selector').val();
1252
+ do {
1253
+ key = `o${idx++}`;
1254
+ }
1255
+ while (device_options.hasOwnProperty(key));
1256
+ device_options[key] = { key:optionName, value:''};
1257
+ idx = availableOptions.indexOf(optionName);
1258
+ if (idx > -1) availableOptions.splice(idx, 1);
1259
+ }
1260
+
1261
+ function updateOptions(candidates) {
1262
+ if (candidates.length > 0) {
1263
+ $('#chooseimage').find('.new_options_available').removeClass('hide');
1264
+ list2select('#option_Selector', candidates, [], (key, val) => { return val; }, (key, val) => { return val; })
1265
+ }
1266
+ else {
1267
+ $('#chooseimage').find('.new_options_available').addClass('hide');
1268
+ }
1269
+ const html_options=[];
1270
+
1271
+ for (const k of Object.keys(device_options)) {
1272
+ html_options.push(`<div class="row">`);
1273
+ 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>`)
1274
+ 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>`)
1275
+ 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>`);
1276
+ html_options.push(`</div>`)
1277
+ }
1278
+ $('#chooseimage').find('.options_grid').html(html_options.join(''));
1279
+ if (html_options.length > 0) {
1280
+ for (const k of Object.keys(device_options)) {
1281
+ $(`#option_key_${k}`).val(device_options[k].key);
1282
+ $(`#option_value_${k}`).val(device_options[k].value);
1283
+ $(`#option_rem_${k}`).unbind('click');
1284
+ $(`#option_rem_${k}`).click(() => { removeOption(k); updateOptions(availableOptions) });
1285
+ }
1286
+ }
1287
+ }
1288
+
1289
+ function getOptionsFromUI(_do, _so) {
1290
+ const _no = {};
1291
+ let changed = false;
1292
+ for (const k of Object.keys(_do)) {
1293
+ const key = $(`#option_key_${k}`).val();
1294
+ _do[k].key = key;
1295
+ const val = $(`#option_value_${k}`).val();
1296
+ try {
1297
+ _do[k].value = JSON.parse(val);
1298
+ }
1299
+ catch {
1300
+ _do[k].value = val;
1301
+ }
1302
+ if (device_options[k].key.length > 0) {
1303
+ _no[key] = device_options[k].value;
1304
+ changed |= _no[key] != _so[key];
1305
+ }
1306
+ }
1307
+ changed |= (Object.keys(_no).length != Object.keys(_so).length);
1308
+ console.warn(`${changed} : ${JSON.stringify(_do)} - ${JSON.stringify(_no)}`)
1309
+ if (changed) return _no;
1310
+ return undefined;
1311
+ }
1044
1312
 
1313
+ function updateImageSelection(dev, imagedata) {
1314
+ const default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
1315
+ if (dev.legacyIcon) imagedata.unshift( { file:dev.legacyIcon, name:'legacy', data:dev.legacyIcon});
1316
+ imagedata.unshift( { file:'none', name:'default', data:default_icon});
1317
+ imagedata.unshift( { file:'current', name:'current', data:dev.common.icon || dev.icon});
1318
+
1319
+ list2select('#images', imagedata, selectItems,
1320
+ function (key, image) {
1321
+ return image.name
1322
+ },
1323
+ function (key, image) {
1324
+ return image.file;
1325
+ },
1326
+ function (key, image) {
1327
+ if (image.isBase64) {
1328
+ return `data-icon="data:image/png; base64, ${image.data}"`;
1329
+ } else {
1330
+ return `data-icon="${image.data}"`;
1331
+ }
1332
+ },
1333
+ );
1334
+
1335
+ }
1336
+ // end local functions
1337
+ const device_options = {};
1338
+ const received_options = {};
1045
1339
 
1046
- async function selectImageOverride(id) {
1047
1340
  const dev = devices.find((d) => d._id == id);
1341
+ const availableOptions = (dev.info.mapped ? dev.info.mapped.options.slice() || []:[]);
1048
1342
  const imghtml = `<img src="${dev.common.icon || dev.icon}" width="80px">`
1049
1343
  //console.error(imghtml)
1050
1344
  const selectItems= [''];
1051
1345
  $('#chooseimage').find('input[id=\'d_name\']').val(dev.common.name);
1052
1346
  $('#chooseimage').find('.currentIcon').html(imghtml);
1347
+ $('#option_add_1084').unbind('click');
1348
+ $('#option_add_1084').click(() => {
1349
+ getOptionsFromUI(device_options, received_options);
1350
+ addOption();
1351
+ updateOptions(availableOptions)
1352
+ });
1053
1353
 
1054
- sendTo(namespace, 'getLocalImages', {}, function(msg) {
1354
+
1355
+
1356
+ sendToWrapper(namespace, 'getLocalImages', {}, function(msg) {
1055
1357
  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
- );
1358
+ updateImageSelection(dev, msg.imageData);
1077
1359
 
1078
1360
  $('#chooseimage a.btn[name=\'save\']').unbind('click');
1079
1361
  $('#chooseimage a.btn[name=\'save\']').click(() => {
@@ -1083,14 +1365,36 @@ async function selectImageOverride(id) {
1083
1365
  const data = {};
1084
1366
  if (image != 'current') data.icon= image;
1085
1367
  if (name != dev.common.name) data.name = name;
1368
+ data.options = getOptionsFromUI(device_options, received_options)
1369
+
1086
1370
  updateLocalConfigItems(id, data, global);
1087
1371
  });
1088
- $('#chooseimage').modal('open');
1089
- Materialize.updateTextFields();
1372
+ sendToWrapper(namespace, 'getLocalConfigItems', { target:id, global:false, key:'options' }, function (msg) {
1373
+ if (msg) {
1374
+ if (msg.error) showMessage(msg.error, '_Error');
1375
+ Object.keys(device_options).forEach(key => delete device_options[key]);
1376
+ Object.keys(received_options).forEach(key => delete received_options[key]);
1377
+ if (typeof msg.options === 'object') {
1378
+ let cnt = 1;
1379
+ for (const key in msg.options)
1380
+ {
1381
+ const idx = availableOptions.indexOf(key);
1382
+ console.warn(`key ${key} : index : ${idx}`);
1383
+ if (idx > -1) availableOptions.splice(idx,1);
1384
+ received_options[key]=msg.options[key];
1385
+ device_options[`o${cnt}`] = { key:key, value:msg.options[key]}
1386
+ cnt++;
1387
+ }
1388
+ }
1389
+ updateOptions(availableOptions);
1390
+ } else showMessage('callback without message');
1391
+ $('#chooseimage').modal('open');
1392
+ Materialize.updateTextFields();
1393
+ });
1090
1394
  }
1091
1395
  });
1092
- }
1093
1396
 
1397
+ }
1094
1398
 
1095
1399
  function safestring(val) {
1096
1400
  const t = typeof val;
@@ -1100,6 +1404,11 @@ function safestring(val) {
1100
1404
  return val;
1101
1405
  }
1102
1406
 
1407
+ ////
1408
+ //
1409
+ //. section DebugUI
1410
+ //
1411
+ ////
1103
1412
  function fne(item) {
1104
1413
  const rv = [];
1105
1414
  if (item.flags) {
@@ -1146,13 +1455,12 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1146
1455
  isodd=!isodd;
1147
1456
  }
1148
1457
  }
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>`
1458
+ 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>`
1459
+ 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
1460
  const dataHide = dbgMsgfilter.has('hi_'+devID) ? 'Data hidden' : '&nbsp;';
1152
1461
  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
1462
  }
1154
1463
 
1155
-
1156
1464
  function HtmlFromOutDebugMessages(messages, devID, filter) {
1157
1465
  const Html = [];
1158
1466
  const filterSet = new Set();
@@ -1188,13 +1496,12 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1188
1496
  isodd=!isodd;
1189
1497
  }
1190
1498
  }
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>`
1499
+ 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>`
1500
+ 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
1501
  const dataHide = dbgMsgfilter.has('ho_'+devID) ? 'Data hidden' : '&nbsp;';
1194
1502
  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
1503
  }
1196
1504
 
1197
-
1198
1505
  function displayDebugMessages(msg) {
1199
1506
  const buttonNames = [];
1200
1507
  const idButtons = [];
@@ -1204,9 +1511,9 @@ function displayDebugMessages(msg) {
1204
1511
  const keylength = keys.length;
1205
1512
  const Html = [];
1206
1513
  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>`;
1514
+ 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>`;
1515
+ 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>`;
1516
+ 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
1517
  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
1518
  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
1519
  if (!keylength) {
@@ -1299,7 +1606,6 @@ function displayDebugMessages(msg) {
1299
1606
  });
1300
1607
  }
1301
1608
  for (const b of idButtons) {
1302
- console.warn(`trying to add link to button ${b}`);
1303
1609
  $(`#lx_${b}`).click(function() { showMessageList(b)});
1304
1610
  }
1305
1611
  }
@@ -1344,48 +1650,38 @@ function showNamedMessages(messages, title, icon, timestamp) {
1344
1650
  }
1345
1651
 
1346
1652
  function showMessageList(msgId) {
1347
- console.warn(`trying to show messages for ${msgId}`);
1348
- console.warn(JSON.stringify(debugMessages));
1349
1653
  for (const devId of Object.keys(debugMessages.byId)) {
1350
1654
  for (const id of debugMessages.byId[devId].IN) {
1351
1655
  if (id.dataID == msgId) {
1352
- console.warn(`showing messages for ${id.type} ${devId}`);
1353
1656
  showNamedMessages(id.messages, `Messages from ${new Date(msgId).toLocaleTimeString()} for device ${devId}`);
1354
1657
  return;
1355
1658
  }
1356
1659
  }
1357
1660
  for (const id of debugMessages.byId[devId].OUT) {
1358
1661
  if (id.dataID == msgId) {
1359
- console.warn(`showing messages for ${msgId}`);
1360
1662
  showNamedMessages(id.messages, `Messages from ${new Date(msgId).toLocaleTimeString()} for device ${devId}`);
1361
1663
  return;
1362
1664
  }
1363
1665
  }
1364
1666
  }
1365
- console.warn(`nothing to show`);
1366
-
1367
-
1368
1667
  }
1369
1668
 
1370
1669
  function getDebugMessages(deleteBeforeRead, deleteSelected) {
1371
- sendTo(namespace, 'getDebugMessages', { inlog: debugInLog, del:deleteBeforeRead ? deleteSelected : '' }, function(msg) {
1670
+ sendToWrapper(namespace, 'getDebugMessages', { inlog: debugInLog, del:deleteBeforeRead ? deleteSelected : '' }, function(msg) {
1372
1671
  debugMessages = msg;
1373
1672
  if (msg) displayDebugMessages(debugMessages)
1374
1673
  })
1375
1674
  }
1376
1675
 
1377
- const lockout = {
1378
- timeoutid:undefined,
1379
- isActive:false,
1380
- };
1676
+ ////
1677
+ //
1678
+ //. section getDataFromAdapter
1679
+ //
1680
+ ////
1381
1681
  function getDevices() {
1382
- console.warn('getDevices called')
1383
-
1384
1682
  function sendForData() {
1385
- sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
1386
- console.warn(`getCoordinatorInfo returned ${JSON.stringify(msg)}`)
1683
+ sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
1387
1684
  if (msg) {
1388
- console.warn(JSON.stringify(msg))
1389
1685
  if (msg.error) {
1390
1686
  errorData.push(msg.error);
1391
1687
  delete msg.error;
@@ -1396,7 +1692,7 @@ function getDevices() {
1396
1692
  coordinatorinfo = msg;
1397
1693
  updateStartButton()
1398
1694
  }
1399
- sendTo(namespace, 'getDevices', {}, function (msg) {
1695
+ sendToWrapper(namespace, 'getDevices', {}, function (msg) {
1400
1696
  if (msg) {
1401
1697
  devices = msg.devices ? msg.devices : [];
1402
1698
  // check if stashed error messages are sent alongside
@@ -1416,13 +1712,11 @@ function getDevices() {
1416
1712
  //check if debug messages are sent alongside
1417
1713
  if (msg && typeof (msg.debugDevices == 'array')) {
1418
1714
  debugDevices = msg.debugDevices;
1419
- console.warn('debug devices is sent')
1420
1715
  }
1421
1716
  else
1422
1717
  debugDevices = [];
1423
1718
  if (debugMessages.byId) {
1424
1719
  newDebugMessages = true;
1425
- console.warn('having debug messages');
1426
1720
  debugMessages.byId = msg;
1427
1721
  if (msg) displayDebugMessages(debugMessages)
1428
1722
  }
@@ -1430,19 +1724,18 @@ function getDevices() {
1430
1724
  if (msg.error) {
1431
1725
  errorData.push(msg.error);
1432
1726
  isHerdsmanRunning = false;
1433
- updateStartButton();
1434
- showDevices();
1435
1727
  } else {
1436
1728
  isHerdsmanRunning = true;
1437
- updateStartButton();
1438
- showDevices();
1439
1729
  if (!newDebugMessages) {
1440
- console.warn('getting debug messages');
1441
1730
  getDebugMessages();
1442
1731
  }
1443
1732
  //getExclude();
1444
1733
  getBinding();
1445
1734
  }
1735
+ updateStartButton();
1736
+ showDevices();
1737
+ showLocalData();
1738
+ UpdateAdapterAlive(true)
1446
1739
  }
1447
1740
  });
1448
1741
  });
@@ -1450,7 +1743,6 @@ function getDevices() {
1450
1743
 
1451
1744
  if (lockout.timeoutid) {
1452
1745
  clearTimeout(lockout.timeoutid);
1453
- console.warn('clearing getDevices timeout')
1454
1746
  }
1455
1747
 
1456
1748
  setTimeout(() => {
@@ -1462,28 +1754,18 @@ function getDevices() {
1462
1754
  }
1463
1755
 
1464
1756
  function getNamedColors() {
1465
- sendTo(namespace, 'getNamedColors', {}, function(msg) {
1757
+ sendToWrapper(namespace, 'getNamedColors', {}, function(msg) {
1466
1758
  if (msg && typeof msg.colors) {
1467
1759
  namedColors = msg.colors;
1468
1760
  }
1469
1761
  });
1470
1762
  }
1471
1763
 
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
1764
 
1483
1765
  function getMap(rebuild) {
1484
1766
  $('#refresh').addClass('disabled');
1485
1767
  if (isHerdsmanRunning) {
1486
- sendTo(namespace, 'getMap', { forcebuild:rebuild}, function (msg) {
1768
+ sendToWrapper(namespace, 'getMap', { forcebuild:rebuild}, function (msg) {
1487
1769
  $('#refresh').removeClass('disabled');
1488
1770
  if (msg) {
1489
1771
  if (msg.error) {
@@ -1505,27 +1787,27 @@ function getMap(rebuild) {
1505
1787
  else showMessage('Unable to generate map, the zigbee subsystem is inactive', 'Map generation error');
1506
1788
  }
1507
1789
 
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
1790
 
1523
1791
 
1524
1792
 
1525
1793
  // the function loadSettings has to exist ...
1526
1794
 
1527
1795
  function load(settings, onChange) {
1528
- console.warn(JSON.stringify(settings));
1796
+ function getRandomExtPanID()
1797
+ {
1798
+ const bytes = [];
1799
+ for (let i = 0;i<16;i++) {
1800
+ bytes.push(Math.floor(Math.random() * 16).toString(16));
1801
+ }
1802
+ return bytes.join('');
1803
+ }
1804
+
1805
+ function getRandomChannel()
1806
+ {
1807
+ const channels = [11,15,20,25]
1808
+ return channels[Math.floor(Math.random() * 4)];
1809
+ }
1810
+
1529
1811
  if (settings.extPanID === undefined || settings.extPanID == '') {
1530
1812
  settings.channel = getRandomChannel();
1531
1813
  }
@@ -1577,13 +1859,20 @@ function load(settings, onChange) {
1577
1859
  }
1578
1860
  }
1579
1861
 
1580
-
1581
1862
  getComPorts(onChange);
1582
1863
 
1583
1864
  //dialog = new MatDialog({EndingTop: '50%'});
1584
- getDevices();
1585
- getNamedColors();
1586
- readNVRamBackup(false);
1865
+ const keepAliveHandle = startKeepalive();
1866
+ keepAlive(() => {
1867
+ getDevices();
1868
+ getNamedColors();
1869
+ readNVRamBackup(false);
1870
+ sendToWrapper(namespace, 'getGroups', {}, function (data) {
1871
+ groups = data.groups || {};
1872
+ //showGroups();
1873
+ });
1874
+ })
1875
+
1587
1876
  //getDebugMessages();
1588
1877
  //getMap();
1589
1878
  //addCard();
@@ -1592,13 +1881,10 @@ function load(settings, onChange) {
1592
1881
  onChange(false);
1593
1882
 
1594
1883
  $('#test-btn').click(function () {
1595
- console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1596
1884
  if (!isHerdsmanRunning) {
1597
1885
  const port = $('#port.value').val();
1598
- console.warn(`port is ${port}`)
1599
1886
  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)}`);
1887
+ sendToWrapper(namespace, 'testConnection', { address:port }, function(msg) {
1602
1888
  closeWaitingDialog();
1603
1889
  if (msg) {
1604
1890
  if (msg.error) {
@@ -1617,7 +1903,6 @@ function load(settings, onChange) {
1617
1903
  })
1618
1904
  // test start commands
1619
1905
  $('#show_test_run').click(function () {
1620
- console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1621
1906
  doTestStart(!isHerdsmanRunning);
1622
1907
  });
1623
1908
 
@@ -1639,10 +1924,8 @@ function load(settings, onChange) {
1639
1924
  });
1640
1925
  $('#pairing').click(function () {
1641
1926
  if (!$('#pairing').hasClass('pulse')) {
1642
- letsPairing();
1643
- }
1644
- console.warn('lets pairing');
1645
- showPairingProcess();
1927
+ openNetwork();
1928
+ } else showPairingProcess();
1646
1929
  });
1647
1930
 
1648
1931
  $('#refresh').click(function () {
@@ -1678,18 +1961,14 @@ function load(settings, onChange) {
1678
1961
  showChannels();
1679
1962
  });
1680
1963
 
1681
- sendTo(namespace, 'getGroups', {}, function (data) {
1682
- groups = data.groups;
1683
- //showGroups();
1684
- });
1685
1964
 
1686
1965
  $('#add_group').click(function () {
1687
- const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
1966
+ const maxind = parseInt(Object.getOwnPropertyNames(groups || {}).reduce((a, b) => a > b ? a : b, 0));
1688
1967
  addGroup(maxind + 1, 'Group ' + maxind + 1);
1689
1968
  });
1690
1969
 
1691
1970
  $('#add_grp_btn').click(function () {
1692
- const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a, b) => a > b ? a : b, 0));
1971
+ const maxind = parseInt(Object.getOwnPropertyNames(groups || {}).reduce((a, b) => a > b ? a : b, 0));
1693
1972
  addGroup(maxind + 1, 'Group ' + maxind + 1);
1694
1973
  });
1695
1974
 
@@ -1715,6 +1994,7 @@ function load(settings, onChange) {
1715
1994
  $('.dropdown-trigger').dropdown({constrainWidth: false});
1716
1995
  Materialize.updateTextFields();
1717
1996
  $('.collapsible').collapsible();
1997
+
1718
1998
  Materialize.Tabs.init($('.tabs'));
1719
1999
  $('#device-search').keyup(function (event) {
1720
2000
  doFilter(event.target.value.toLowerCase());
@@ -1748,11 +2028,15 @@ function load(settings, onChange) {
1748
2028
  addExcludeDialog();
1749
2029
  });
1750
2030
 
2031
+ $('#updateData').click(function () {
2032
+ getDevices();
2033
+ });
2034
+
1751
2035
  $('#add_binding').click(function () {
1752
2036
  addBindingDialog();
1753
2037
  });
1754
2038
 
1755
- sendTo(namespace, 'getLibData', {key: 'cidList'}, function (data) {
2039
+ sendToWrapper(namespace, 'getLibData', {key: 'cidList'}, function (data) {
1756
2040
  cidList = data.list;
1757
2041
  });
1758
2042
  }
@@ -1767,13 +2051,22 @@ function showMessages() {
1767
2051
  $('#stdout_t').text(messages.join('\n'));
1768
2052
  }
1769
2053
 
1770
- function showPairingProcess() {
2054
+ function showPairingProcess(noextrabuttons) {
1771
2055
  if (isHerdsmanRunning) $('#modalpairing').modal({
1772
2056
  startingTop: '4%',
1773
2057
  endingTop: '10%',
1774
2058
  dismissible: false
1775
2059
  });
1776
2060
 
2061
+ if (noextrabuttons) {
2062
+ $('#modalpairing').find('.endpairing').addClass('hide');
2063
+ $('#modalpairing').find('.extendpairing').addClass('hide');
2064
+ }
2065
+ else {
2066
+ $('#modalpairing').find('.endpairing').removeClass('hide');
2067
+ $('#modalpairing').find('.extendpairing').removeClass('hide');
2068
+ }
2069
+
1777
2070
  $('#modalpairing').modal('open');
1778
2071
  Materialize.updateTextFields();
1779
2072
  }
@@ -1792,24 +2085,25 @@ function doTestStart(start, interactive) {
1792
2085
  };
1793
2086
  // $('#testStartStart').addClass('disabled');
1794
2087
  messages = [];
1795
- if (interactive) showWaitingDialog('Trying to start the zigbee subsystem manually', 120);
1796
- sendTo(namespace, 'testConnect', { start:true, zigbeeOptions:ovr }, function(msg) {
2088
+ if (interactive) showPairingProcess(true)
2089
+
2090
+ // showWaitingDialog('Trying to start the zigbee subsystem manually', 120);
2091
+ sendToWrapper(namespace, 'testConnect', { start:true, zigbeeOptions:ovr }, function(msg) {
1797
2092
  if (msg) {
1798
2093
  closeWaitingDialog();
1799
- isHerdsmanRunning = msg.status;
1800
2094
  updateStartButton(false);
1801
2095
  if (msg.status)
1802
2096
  $('#testStartStop').removeClass('disabled');
1803
2097
  else {
1804
2098
  //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');
2099
+ $('#testStartStart').removeClass('disabled');
1806
2100
  }
1807
2101
  }
1808
2102
  })
1809
2103
  }
1810
2104
  else {
1811
2105
  //$('#testStartStop').addClass('disabled');
1812
- sendTo(namespace, 'testConnect', { start:false }, function(msg) {
2106
+ sendToWrapper(namespace, 'testConnect', { start:false }, function(msg) {
1813
2107
  if (msg) {
1814
2108
  if (msg.status) $('#testStartStart').removeClass('disabled');
1815
2109
  else $('#testStartStop').removeClass('disabled');
@@ -1847,7 +2141,6 @@ function getDevId(adapterDevId) {
1847
2141
 
1848
2142
 
1849
2143
  function updateStartButton(block) {
1850
- console.warn(`update start button with${isHerdsmanRunning ? ' Herdsman' : 'out Herdsman'}`);
1851
2144
  if (block) {
1852
2145
  $('#show_test_run').addClass('disabled');
1853
2146
  $('#reset-btn').addClass('disabled');
@@ -1894,7 +2187,6 @@ socket.emit('subscribeObjects', namespace + '.*');
1894
2187
  socket.on('stateChange', function (id, state) {
1895
2188
  // only watch our own states
1896
2189
  if (id.substring(0, namespaceLen) !== namespace) return;
1897
- //console.log('stateChange', id, state);
1898
2190
  if (state) {
1899
2191
  if (id.match(/\.info\.pairingMode$/)) {
1900
2192
  if (state.val) {
@@ -1956,14 +2248,12 @@ socket.on('stateChange', function (id, state) {
1956
2248
 
1957
2249
  socket.on('objectChange', function (id, obj) {
1958
2250
  if (id.substring(0, namespaceLen) !== namespace) return;
1959
- //console.log('objectChange', id, obj);
1960
2251
  if (obj && obj.type == 'device') { // && obj.common.type !== 'group') {
1961
2252
  updateDevice(id);
1962
2253
  }
1963
2254
  if (!obj) {
1964
2255
  // delete state or device
1965
2256
  const elems = id.split('.');
1966
- //console.log('elems', elems);
1967
2257
  if (elems.length === 3) {
1968
2258
  removeDevice(id);
1969
2259
  showDevices();
@@ -2015,10 +2305,11 @@ function showNetworkMap(devices, map) {
2015
2305
  const createNode = function (dev, mapEntry) {
2016
2306
  if (dev.common && (dev.common.type == 'group' || dev.common.deactivated)) return undefined;
2017
2307
  const extInfo = (mapEntry && mapEntry.networkAddress) ? `\n (nwkAddr: 0x${mapEntry.networkAddress.toString(16)} | ${mapEntry.networkAddress})` : '';
2308
+ const t = dev._id.replace(namespace + '.', '');
2018
2309
  const node = {
2019
2310
  id: dev._id,
2020
2311
  label: (dev.link_quality > 0 ? dev.common.name : `${dev.common.name}\n(disconnected)`),
2021
- title: dev._id.replace(namespace + '.', '') + extInfo,
2312
+ title: `${t} ${extInfo}`,
2022
2313
  shape: 'circularImage',
2023
2314
  image: dev.common.icon || dev.icon,
2024
2315
  imagePadding: {top: 5, bottom: 5, left: 5, right: 5},
@@ -2027,20 +2318,20 @@ function showNetworkMap(devices, map) {
2027
2318
  borderWidth: 1,
2028
2319
  borderWidthSelected: 4,
2029
2320
  };
2030
- if (dev.info && dev.info.device.type == 'Coordinator') {
2321
+ if (dev.common && dev.common.type === 'Coordinator') {
2031
2322
  // node.shape = 'star';
2032
2323
  node.image = 'zigbee.png';
2033
2324
  node.label = 'Coordinator';
2034
2325
  // delete node.color;
2035
2326
  }
2327
+ console.warn(`node for device ${JSON.stringify(node)}`)
2036
2328
  return node;
2037
2329
  };
2038
2330
 
2039
2331
  if (map.lqis) {
2040
2332
  map.lqis.forEach((mapEntry) => {
2041
- const dev = getDevice(mapEntry.ieeeAddr);
2333
+ const dev = getDeviceByIEEE(mapEntry.ieeeAddr);
2042
2334
  if (!dev) {
2043
- //console.log("No dev with ieee "+mapEntry.ieeeAddr);
2044
2335
  return;
2045
2336
  }
2046
2337
 
@@ -2054,7 +2345,7 @@ function showNetworkMap(devices, map) {
2054
2345
  node = nodes[mapEntry.ieeeAddr];
2055
2346
  }
2056
2347
  if (node) {
2057
- const parentDev = getDevice(mapEntry.parent);
2348
+ const parentDev = getDeviceByIEEE(mapEntry.parent);
2058
2349
  const to = parentDev ? parentDev._id : undefined;
2059
2350
  const from = dev._id;
2060
2351
  let label = mapEntry.lqi.toString();
@@ -2321,7 +2612,7 @@ function getComPorts(onChange) {
2321
2612
  // timeout = setTimeout(function () {
2322
2613
  // getComPorts(onChange);
2323
2614
  // }, 2000);
2324
- sendTo(namespace, 'listUart', null, function (list) {
2615
+ sendToWrapper(namespace, 'listUart', null, function (list) {
2325
2616
  // if (timeout) {
2326
2617
  // clearTimeout(timeout);
2327
2618
  // timeout = null;
@@ -2465,7 +2756,6 @@ function loadDeveloperTab() {
2465
2756
  const device = devices.find(obj => {
2466
2757
  return this.value ===obj.native.id;
2467
2758
  });
2468
- console.warn(`dev selector: ${this.selectedIndex} ${this.value} ->${JSON.stringify(device)}`)
2469
2759
 
2470
2760
  const epList = device ? device.info.endpoints : null;
2471
2761
  updateSelect('#ep', epList,
@@ -2548,7 +2838,7 @@ function loadDeveloperTab() {
2548
2838
  data = prepareData();
2549
2839
  }
2550
2840
  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));
2841
+ console.log('Send to Zigbee replied with ' + JSON.stringify(reply));
2552
2842
  if (reply.hasOwnProperty('localErr')) {
2553
2843
  showDevRunInfo(reply.localErr, reply.errMsg, 'yellow');
2554
2844
  } else if (reply.hasOwnProperty('localStatus')) {
@@ -2563,7 +2853,7 @@ function loadDeveloperTab() {
2563
2853
 
2564
2854
  responseCodes = null;
2565
2855
  // load list of response codes
2566
- sendTo(namespace, 'getLibData', {key: 'respCodes'}, function (data) {
2856
+ sendToWrapper(namespace, 'getLibData', {key: 'respCodes'}, function (data) {
2567
2857
  responseCodes = data.list;
2568
2858
  });
2569
2859
  }
@@ -2616,7 +2906,7 @@ function sendToZigbee(id, ep, cid, cmd, cmdType, zclData, cfg, callback) {
2616
2906
 
2617
2907
  console.log('Send to zigbee, id ' + id + ',ep ' + ep + ', cid ' + cid + ', cmd ' + cmd + ', cmdType ' + cmdType + ', zclData ' + JSON.stringify(zclData));
2618
2908
 
2619
- sendTo(namespace, 'sendToZigbee', data, function (reply) {
2909
+ sendToWrapper(namespace, 'sendToZigbee', data, function (reply) {
2620
2910
  clearTimeout(sendTimeout);
2621
2911
  if (callback) {
2622
2912
  callback(reply);
@@ -2664,7 +2954,7 @@ function populateSelector(selectId, key, cid) {
2664
2954
  updateSelect(selectId, null);
2665
2955
  return;
2666
2956
  }
2667
- sendTo(namespace, 'getLibData', {key: key, cid: cid}, function (data) {
2957
+ sendToWrapper(namespace, 'getLibData', {key: key, cid: cid}, function (data) {
2668
2958
  const list = data.list;
2669
2959
  if (key === 'attrIdList') {
2670
2960
  updateSelect(selectId, list,
@@ -2807,7 +3097,7 @@ function deleteGroupConfirmation(id, name) {
2807
3097
 
2808
3098
  function updateGroup(newId, newName, remove) {
2809
3099
  groups[newId] = newName;
2810
- sendTo(namespace, 'renameGroup', {id: newId, name: newName, remove: remove}, function (msg) {
3100
+ sendToWrapper(namespace, 'renameGroup', {id: newId, name: newName, remove: remove}, function (msg) {
2811
3101
  if (msg && msg.error) {
2812
3102
  showMessage(msg.error, _('Error'));
2813
3103
  }
@@ -2817,7 +3107,7 @@ function updateGroup(newId, newName, remove) {
2817
3107
 
2818
3108
  function deleteGroup(id) {
2819
3109
  delete groups[id];
2820
- sendTo(namespace, 'deleteGroup', id, function (msg) {
3110
+ sendToWrapper(namespace, 'deleteGroup', id, function (msg) {
2821
3111
  if (msg && msg.error) {
2822
3112
  showMessage(msg.error, _('Error'));
2823
3113
  }
@@ -2840,7 +3130,7 @@ function updateDev(id, newName, newGroups) {
2840
3130
  const keys = Object.keys(newGroups);
2841
3131
  if (keys && keys.length) {
2842
3132
  command.groups = newGroups
2843
- sendTo(namespace, 'updateGroupMembership', command, function (msg) {
3133
+ sendToWrapper(namespace, 'updateGroupMembership', command, function (msg) {
2844
3134
  closeWaitingDialog();
2845
3135
  if (msg && msg.error) {
2846
3136
  showMessage(msg.error, _('Error'));
@@ -2854,7 +3144,7 @@ function updateDev(id, newName, newGroups) {
2854
3144
  }
2855
3145
  else if (needName)
2856
3146
  {
2857
- sendTo(namespace, 'renameDevice', command, function(msg) {
3147
+ sendToWrapper(namespace, 'renameDevice', command, function(msg) {
2858
3148
  //closeWaitingDialog();
2859
3149
  if (msg && msg.error) {
2860
3150
  showMessage(msg.error, _('Error'));
@@ -2873,9 +3163,9 @@ function resetConfirmation() {
2873
3163
  const btn = $('#modalreset .modal-content a.btn');
2874
3164
  btn.unbind('click');
2875
3165
  btn.click(function (e) {
2876
- sendTo(namespace, 'reset', {mode: e.target.id}, function (err) {
3166
+ sendToWrapper(namespace, 'reset', {mode: e.target.id}, function (err) {
2877
3167
  if (err) {
2878
- console.log(err);
3168
+ console.log(`reset attempt failed with ${err}`);
2879
3169
  } else {
2880
3170
  console.log('Reset done');
2881
3171
  }
@@ -3104,7 +3394,7 @@ function addBindingDialog() {
3104
3394
  }
3105
3395
 
3106
3396
  function addBinding(bind_source, bind_source_ep, bind_target, bind_target_ep, unbind_from_coordinator) {
3107
- sendTo(namespace, 'addBinding', {
3397
+ sendToWrapper(namespace, 'addBinding', {
3108
3398
  bind_source: bind_source,
3109
3399
  bind_source_ep: bind_source_ep,
3110
3400
  bind_target: bind_target,
@@ -3121,7 +3411,7 @@ function addBinding(bind_source, bind_source_ep, bind_target, bind_target_ep, un
3121
3411
  }
3122
3412
 
3123
3413
  function editBinding(bind_id, bind_source, bind_source_ep, bind_target, bind_target_ep, unbind_from_coordinator) {
3124
- sendTo(namespace, 'editBinding', {
3414
+ sendToWrapper(namespace, 'editBinding', {
3125
3415
  id: bind_id,
3126
3416
  bind_source: bind_source,
3127
3417
  bind_source_ep: bind_source_ep,
@@ -3214,7 +3504,7 @@ function showBinding() {
3214
3504
  }
3215
3505
 
3216
3506
  function getBinding() {
3217
- sendTo(namespace, 'getBinding', {}, function (msg) {
3507
+ sendToWrapper(namespace, 'getBinding', {}, function (msg) {
3218
3508
  if (msg) {
3219
3509
  if (msg.error) {
3220
3510
  showMessage(msg.error, _('Error'));
@@ -3239,7 +3529,7 @@ function deleteBindingConfirmation(id) {
3239
3529
  }
3240
3530
 
3241
3531
  function deleteBinding(id) {
3242
- sendTo(namespace, 'delBinding', id, (msg) => {
3532
+ sendToWrapper(namespace, 'delBinding', id, (msg) => {
3243
3533
  closeWaitingDialog();
3244
3534
  if (msg) {
3245
3535
  if (msg.error) {
@@ -3261,7 +3551,6 @@ function findClName(id) {
3261
3551
  }
3262
3552
 
3263
3553
  function genDevInfo(device) {
3264
- //console.log(device);
3265
3554
  const dev = (device && device.info) ? device.info.device : undefined;
3266
3555
  const mapped = (device && device.info) ? device.info.mapped : undefined;
3267
3556
  const endpoints = (device && device.info) ? device.info.endpoints : [];
@@ -3309,7 +3598,6 @@ function genDevInfo(device) {
3309
3598
  let epInfo = '';
3310
3599
  for (const epind in endpoints) {
3311
3600
  const ep = endpoints[epind];
3312
- console.warn(JSON.stringify(ep));
3313
3601
  epInfo +=
3314
3602
  `<div style="font-size: 0.9em" class="truncate">
3315
3603
  <ul>
@@ -3342,12 +3630,6 @@ function genDevInfo(device) {
3342
3630
  return info.join('');
3343
3631
  }
3344
3632
 
3345
- function showDevInfo(id) {
3346
- const info = genDevInfo(getDeviceByID(id));
3347
- $('#devinfo').html(info);
3348
- $('#modaldevinfo').modal('open');
3349
- }
3350
-
3351
3633
  let waitingTimeout, waitingInt;
3352
3634
 
3353
3635
  function showWaitingDialog(text, timeout) {
@@ -3375,7 +3657,7 @@ function closeWaitingDialog() {
3375
3657
 
3376
3658
 
3377
3659
  function showChannels() {
3378
- sendTo(namespace, 'getChannels', {}, function (msg) {
3660
+ sendToWrapper(namespace, 'getChannels', {}, function (msg) {
3379
3661
  closeWaitingDialog();
3380
3662
  if (msg) {
3381
3663
  if (msg.error) {
@@ -3494,21 +3776,20 @@ function addExcludeDialog() {
3494
3776
 
3495
3777
  function addExclude(exclude_model) {
3496
3778
  if (typeof exclude_model == 'object' && exclude_model.hasOwnProperty('common'))
3497
- sendTo(namespace, 'addExclude', { exclude_model: exclude_model }, function (msg) {
3779
+ sendToWrapper(namespace, 'addExclude', { exclude_model: exclude_model }, function (msg) {
3498
3780
  closeWaitingDialog();
3499
3781
  if (msg) {
3500
3782
  if (msg.error) {
3501
3783
  showMessage(msg.error, _('Error'));
3502
3784
  }
3503
3785
  }
3504
- console.log('getting excludes ?');
3505
3786
  getExclude();
3506
3787
  });
3507
3788
  else closeWaitingDialog();
3508
3789
  }
3509
3790
 
3510
3791
  function getExclude() {
3511
- sendTo(namespace, 'getExclude', {}, function (msg) {
3792
+ sendToWrapper(namespace, 'getExclude', {}, function (msg) {
3512
3793
  if (msg) {
3513
3794
  if (msg.error) {
3514
3795
  showMessage(msg.error, _('Error'));
@@ -3579,14 +3860,13 @@ function deleteExcludeConfirmation(id) {
3579
3860
  }
3580
3861
 
3581
3862
  function deleteExclude(id) {
3582
- sendTo(namespace, 'delExclude', id, (msg) => {
3863
+ sendToWrapper(namespace, 'delExclude', id, (msg) => {
3583
3864
  closeWaitingDialog();
3584
3865
  if (msg) {
3585
3866
  if (msg.error) {
3586
3867
  showMessage(msg.error, _('Error'));
3587
3868
  }
3588
3869
  }
3589
- console.log('getting excludes ?');
3590
3870
  getExclude();
3591
3871
  });
3592
3872
  }
@@ -3669,168 +3949,9 @@ function sortByTitle(element) {
3669
3949
  return element.querySelector('.card-title').textContent.toLowerCase().trim();
3670
3950
  }
3671
3951
 
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
3952
 
3832
3953
  function updateDevice(id) {
3833
- sendTo(namespace, 'getDevice', {id: id}, function (msg) {
3954
+ sendToWrapper(namespace, 'getDevice', {id: id}, function (msg) {
3834
3955
  if (msg) {
3835
3956
  const devs = msg.devices;
3836
3957
  if (devs) {
@@ -3860,13 +3981,13 @@ function swapActive(id) {
3860
3981
  const dev = getDeviceByID(id);
3861
3982
  if (dev && dev.common) {
3862
3983
  dev.common.deactivated = !(dev.common.deactivated);
3863
- sendTo(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
3984
+ sendToWrapper(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
3864
3985
  showDevices();
3865
3986
  });
3866
3987
  }
3867
3988
  }
3868
3989
 
3869
- function reconfigureDlg(id) {
3990
+ function reconfigureConfirmation(id) {
3870
3991
  const text = translateWord(`Do you really want to reconfigure device?`);
3871
3992
  $('#modalreconfigure').find('p').text(text);
3872
3993
  $('#modalreconfigure a.btn[name=\'yes\']').unbind('click');
@@ -3878,7 +3999,7 @@ function reconfigureDlg(id) {
3878
3999
  }
3879
4000
 
3880
4001
  function reconfigureDevice(id) {
3881
- sendTo(namespace, 'reconfigure', {id: id}, function (msg) {
4002
+ sendToWrapper(namespace, 'reconfigure', {id: id}, function (msg) {
3882
4003
  closeWaitingDialog();
3883
4004
  if (msg) {
3884
4005
  if (msg.error) {
@@ -3899,34 +4020,28 @@ function validateConfigData(key, val) {
3899
4020
  if (validatableKeys.indexOf(key) < 0 || !val) return;
3900
4021
  if (warnLevel[key]) {
3901
4022
  if (warnLevel[key](val)) {
3902
- //console.warn(`warning set for ${key} (${val})`)
3903
4023
  $(`#${key}_ALERT`).removeClass('hide')
3904
4024
  } else $(`#${key}_ALERT`).addClass('hide')
3905
4025
  }
3906
4026
  if (nvRamBackup[key]) {
3907
- //console.warn(`value of ${key} is ${val} (${nvRamBackup[key]})`);
3908
4027
  if ((typeof val == 'string' && typeof nvRamBackup[key] == 'string' && val.toLowerCase == nvRamBackup[key].toLowerCase) || val == nvRamBackup[key])
3909
4028
  {
3910
- //console.warn(`ok set for ${key} (${val})`)
3911
4029
  $(`#${key}_OK`).removeClass('hide')
3912
4030
  $(`#${key}_NOK`).addClass('hide')
3913
4031
  }
3914
4032
  else
3915
4033
  {
3916
- //console.warn(`nok set for ${key} (${val})`)
3917
4034
  $(`#${key}_OK`).addClass('hide')
3918
4035
  $(`#${key}_NOK`).removeClass('hide')
3919
4036
  }
3920
4037
  }
3921
4038
  else {
3922
- //console.warn(`noval set for ${key} (${val})`)
3923
4039
  $(`#${key}_OK`).addClass('hide')
3924
4040
  $(`#${key}_NOK`).addClass('hide')
3925
4041
  }
3926
4042
  }
3927
4043
 
3928
4044
  function validateNVRamBackup(update, src) {
3929
- //console.warn('validateNVRam');
3930
4045
  const validatedKeys = src ? [src] : validatableKeys;
3931
4046
  const validator = {};
3932
4047
  for (const key of validatedKeys) {
@@ -3944,9 +4059,7 @@ function validateNVRamBackup(update, src) {
3944
4059
 
3945
4060
 
3946
4061
  function readNVRamBackup(update) {
3947
- console.warn('read nvRam')
3948
- sendTo(namespace, 'readNVRam', {}, function(msg) {
3949
- console.warn(JSON.stringify(msg));
4062
+ sendToWrapper(namespace, 'readNVRam', {}, function(msg) {
3950
4063
  if (msg) {
3951
4064
  if (msg.error && update) {
3952
4065
  if (msg.error.includes('ENOENT')) showMessage('Unable to read nvRam backup - no backup available.',_('Error'))