iobroker.zigbee 3.1.2 → 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
@@ -60,27 +60,93 @@ const networkOptions = {
60
60
 
61
61
 
62
62
  const savedSettings = [
63
- 'port', 'panID', 'channel', 'disableLed', 'countDown', 'groups', 'extPanID', 'precfgkey', 'transmitPower',
64
- 'adapterType', 'debugHerdsman', 'disableBackup', 'disablePing', 'external', 'startWithInconsistent',
65
- 'warnOnDeviceAnnouncement', 'baudRate', 'flowCTRL', 'autostart', 'readAtAnnounce', 'startReadDelay', 'readAllAtStart',
63
+ 'port', 'panID', 'channel', 'disableLed', 'countDown', 'groups', 'extPanID', 'precfgkey', 'transmitPower','useNewCompositeStates',
64
+ 'adapterType', 'debugHerdsman', 'disableBackup', 'external', 'startWithInconsistent','pingTimeout','listDevicesAtStart',
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
- return devInfo.info.device._ieeeAddr == ieeeAddr;
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
  }
@@ -89,9 +155,9 @@ function getDevice(ieeeAddr) {
89
155
  function getDeviceByNetwork(nwk) {
90
156
  return devices.find((devInfo) => {
91
157
  try {
92
- return devInfo.info.device._networkAddress == nwk;
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',
@@ -120,7 +378,8 @@ function getCoordinatorCard(dev) {
120
378
  rid = id.split('.').join('_'),
121
379
  image = `<img src="${img_src}" width="80px">`,
122
380
  paired = '',
123
- status = dev ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : `<div class="col tool"><i class="material-icons icon-red">remove_circle</i></div>`,
381
+ status = coordinatorinfo.autostart ? (dev ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : `<div class="col tool"><i class="material-icons icon-red">remove_circle</i></div>`) : `<div class="col tool"><i class="material-icons icon-orange">pause_circle_filled</i></div>`,
382
+ //status = dev ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : `<div class="col tool"><i class="material-icons icon-red">remove_circle</i></div>`,
124
383
  lqi_cls = dev ? getLQICls(dev.link_quality) : -1,
125
384
  lq = (dev && dev.link_quality) ? `<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>` : '',
126
385
  info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
@@ -136,13 +395,15 @@ function getCoordinatorCard(dev) {
136
395
  <li><span class="label">ZHC / ZH:</span><span>${coordinatorinfo.converters} / ${coordinatorinfo.herdsman}</span></li>
137
396
  </ul>
138
397
  </div>`,
139
- permitJoinBtn = (dev && dev.info && dev.info.device._type == 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
398
+ permitJoinBtn = '<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>',
399
+ //permitJoinBtn = `<div class="col tool"><button name="join" class="btn-floating-sml waves-effect waves-light right hoverable green><i class="material-icons">leak_add</i></button></div>`,
140
400
  card = `<div id="${id}" class="device">
141
401
  <div class="card hoverable">
142
402
  <div class="card-content zcard">
143
403
  <span class="top right small" style="border-radius: 50%">
144
404
  ${lq}
145
405
  ${status}
406
+ ${permitJoinBtn}
146
407
  </span>
147
408
  <!--/a--!>
148
409
  <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
@@ -150,11 +411,6 @@ function getCoordinatorCard(dev) {
150
411
  ${info}
151
412
  <div class="footer right-align"></div>
152
413
  </div>
153
- <div class="card-action">
154
- <div class="card-reveal-buttons">
155
- ${permitJoinBtn}
156
- </div>
157
- </div>
158
414
  </div>
159
415
  </div>`;
160
416
  return card;
@@ -175,9 +431,10 @@ function getGroupCard(dev) {
175
431
  }
176
432
  }
177
433
  devGroups[numid] = dev;
434
+ const roomInfo = rooms.length ? `<li><span class="labelinfo">rooms:</span><span>${rooms.join(',') || ''}</span></li>` : '';
178
435
  const room = rooms.join(',') || '&nbsp';
179
436
  let memberCount = 0;
180
- let info = `<div style="min-height:88px; font-size: 0.8em; height: 98px; overflow-y: auto" class="truncate">
437
+ let info = `<div style="min-height:88px; font-size: 0.8em; overflow-y: auto" class="truncate">
181
438
  <ul>`;
182
439
  info = info.concat(`<li><span class="labelinfo">Group ${numid}</span></li>`);
183
440
  if (dev.memberinfo === undefined) {
@@ -189,7 +446,7 @@ function getGroupCard(dev) {
189
446
  memberCount = (dev.memberinfo.length < 8 ? dev.memberinfo.length : 7);
190
447
  }
191
448
  ;
192
- info = info.concat(` </ul>
449
+ info = info.concat(` ${roomInfo}</ul>
193
450
  </div>`);
194
451
  const image = `<img src="${dev.common.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
195
452
  const dashCard = getDashCard(dev, dev.common.icon, memberCount > 0);
@@ -207,11 +464,11 @@ function getGroupCard(dev) {
207
464
  </div>
208
465
  <i class="left">${image}</i>
209
466
  ${info}
467
+
210
468
  <div class="footer right-align"></div>
211
469
  </div>
212
470
  <div class="card-action">
213
471
  <div class="card-reveal-buttons">
214
- <span class="left" style="padding-top:8px">${room}</span>
215
472
  <button name="deletegrp" class="right btn-flat btn-small">
216
473
  <i class="material-icons icon-black">delete</i>
217
474
  </button>
@@ -229,100 +486,174 @@ function getGroupCard(dev) {
229
486
  return card;
230
487
  }
231
488
 
232
- function sanitizeModelParameter(parameter) {
233
- const replaceByUnderscore = /[\s/]/g;
234
- return parameter.replace(replaceByUnderscore, '_');
489
+ function getDeviceCards() {
490
+ return $('#devices .device').not('.group');
235
491
  }
236
492
 
237
- function getCard(dev) {
238
- //console.warn(JSON.stringify(dev));
239
- 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) {
240
501
  const title = dev.common.name,
241
- id = (dev._id ? dev._id : ''),
242
- type = (dev.common.type ? dev.common.type : 'unknown'),
243
- type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'),
244
- 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,
245
506
  rooms = [],
246
- isActive = (dev.common.deactivated ? false : true),
247
- lang = systemLang || 'en',
248
- ieee = id.replace(namespace + '.', ''),
249
- isDebug = checkDebugDevice(ieee);
250
- for (const r in dev.rooms) {
251
- if (dev.rooms[r].hasOwnProperty(lang)) {
252
- rooms.push(dev.rooms[r][lang]);
253
- } else {
254
- rooms.push(dev.rooms[r]);
255
- }
256
- }
257
- const room = rooms.join(',') || '&nbsp';
507
+ lang = systemLang || 'en';
258
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>`;
259
511
  const rid = id.split('.').join('_');
260
- const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
261
- const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
262
- nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
263
- battery_cls = (isActive ? getBatteryCls(dev.battery) : ''),
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';">`,
514
+ nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
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
- <li><span class="labelinfo">groups:</span><span>${dev.groupNames || ''}</span></li>
276
- </ul>
277
- </div>`,
278
- 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>`,
279
- 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>`,
280
- infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
281
- const dashCard = getDashCard(dev);
282
- const card = `<div id="${id}" class="device">
283
- <div class="card hoverable flipable ${isActive ? '' : 'bg_red'}">
284
- <div class="front face">${dashCard}</div>
285
- <div class="back face">
286
- <div class="card-content zcard">
287
- <div class="flip" style="cursor: pointer">
288
- <span class="top right small" style="border-radius: 50%">
289
- ${battery}
290
- <!--${lq}-->
291
- ${status}
292
- </span>
293
- <!--/a--!>
294
- <span id="dName" class="card-title truncate">${title}</span><!--${paired}--!>
295
- </div>
296
- <i class="left">${image}</i>
297
- ${info}
298
- <div class="footer right-align"></div>
299
- </div>
300
- <div class="card-action">
301
- <div class="card-reveal-buttons">
302
- ${infoBtn}
303
- <span class="left" style="padding-top:8px">${room}</span>
304
- <span class="left fw_info"></span>
305
- <button name="delete" class="right btn-flat btn-small tooltipped" title="Delete">
306
- <i class="material-icons icon-red">delete</i>
307
- </button>
308
- <button name="edit" class="right btn-flat btn-small tooltipped" title="Edit">
309
- <i class="material-icons icon-black">edit</i>
310
- </button>
311
- <button name="swapimage" class="right btn-flat btn-small tooltipped" title="Select Image">
312
- <i class="material-icons icon-black">image</i>
313
- </button>
314
- <button name="reconfigure" class="right btn-flat btn-small tooltipped" title="Reconfigure">
315
- <i class="material-icons icon-red">sync</i>
316
- </button>
317
- ${deactBtn}
318
- ${debugBtn}
319
- </div>
320
- </div>
321
- </div>
322
- </div>
323
- </div>`;
324
- return card;
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
+ }
619
+
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
+ }
640
+
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
+ }
325
655
  }
656
+
326
657
  /*
327
658
  function openReval(e, id, name){
328
659
  const $card = $(e.target).closest('.card');
@@ -377,6 +708,17 @@ function closeReval(e, id) {
377
708
  });
378
709
  }
379
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
+ ////
380
722
  function deleteConfirmation(id, name) {
381
723
  const text = translateWord('Do you really want to delete device') + ' "' + name + '" (' + id + ')?';
382
724
  $('#modaldelete').find('p').text(text);
@@ -385,7 +727,7 @@ function deleteConfirmation(id, name) {
385
727
  $('#modaldelete a.btn[name=\'yes\']').unbind('click');
386
728
  $('#modaldelete a.btn[name=\'yes\']').click(() => {
387
729
  const force = $('#force').prop('checked');
388
- deleteDevice(id, force);
730
+ deleteZigbeeDevice(id, force);
389
731
  });
390
732
  $('#modaldelete').modal('open');
391
733
  Materialize.updateTextFields();
@@ -400,7 +742,7 @@ function deleteNvBackupConfirmation() {
400
742
  $('#modaldelete a.btn[name=\'yes\']').click(() => {
401
743
  //const force = $('#force').prop('checked');
402
744
  showWaitingDialog('Attempting to delete nvBackup.json', 60000);
403
- sendTo(namespace, 'deleteNVBackup', {}, function (msg) {
745
+ sendToWrapper(namespace, 'deleteNVBackup', {}, function (msg) {
404
746
  closeWaitingDialog();
405
747
  if (msg) {
406
748
  if (msg.error) {
@@ -442,189 +784,62 @@ function EndPointIDfromEndPoint(ep) {
442
784
 
443
785
  function editName(id, name) {
444
786
 
445
- const device_options = {};
446
- const received_options = {};
447
-
448
- function removeOption(k) {
449
- if (k && device_options.hasOwnProperty(k)) delete device_options[k];
450
- }
451
-
452
- function addOption() {
453
- let idx=1;
454
- let key = '';
455
- do {
456
- key = `o${idx++}`;
457
- }
458
- while (device_options.hasOwnProperty(key));
459
- device_options[key] = {key:`option_${idx++}`, value:''};
460
- }
461
-
462
- function updateOptions() {
463
- const html_options=[];
464
-
465
- console.warn(`device_options is ${JSON.stringify(device_options)}`)
466
-
467
- for (const k in device_options) {
468
- html_options.push(`<div class="row">`);
469
- html_options.push(`<div class="input-field suffix col s5 m5 l5"><input id="option_key_${k}" type="text" class="value" /><label for="option_key_${k}">Option</label></div>`)
470
- 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>`)
471
- html_options.push(`<div class="col"><a id="option_rem_${k}" class='btn' ><i class="material-icons">remove_circle</i></a></div>`);
472
- html_options.push(`</div>`)
473
- }
474
- console.warn(`html is ${$('#modaledit').find('.options_grid').html()}`)
475
- $('#modaledit').find('.options_grid').html(html_options.join(''));
476
- console.warn(`html is now ${$('#modaledit').find('.options_grid').html()}`)
477
- if (html_options.length > 0) {
478
- $('#modaledit').find('.options_available').removeClass('hide');
479
- for (const k of Object.keys(device_options)) {
480
- $(`#option_key_${k}`).val(device_options[k].key);
481
- $(`#option_value_${k}`).val(device_options[k].value);
482
- $(`#option_rem_${k}`).unbind('click');
483
- $(`#option_rem_${k}`).click(() => { removeOption(k); updateOptions() });
484
- }
485
- }
486
- else {
487
- $('#modaledit').find('.options_available').addClass('hide');
488
- }
489
- }
490
-
491
- function getOptionsFromUI(_do, _so) {
492
- const _no = {};
493
- let changed = false;
494
- for (const k in _do) {
495
- const key = $(`#option_key_${k}`).val();
496
- _do[k].key = key;
497
- const val = $(`#option_value_${k}`).val();
498
- try {
499
- _do[k].value = JSON.parse(val);
500
- }
501
- catch {
502
- _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>`);
503
795
  }
504
- if (device_options[k].key.length > 0) {
505
- _no[key] = device_options[k].value;
506
- 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 || []);
507
800
  }
508
801
  }
509
- changed |= (Object.keys(_no).length != Object.keys(_so).length);
510
- if (changed) return _no;
511
- return undefined;
802
+ return html;
512
803
  }
513
804
 
514
805
 
515
-
516
- console.warn('editName called with ' + id + ' and ' + name);
517
806
  const dev = devices.find((d) => d._id == id);
518
807
  $('#modaledit').find('input[id=\'d_name\']').val(name);
519
808
  const groupables = [];
520
809
  if (dev && dev.info && dev.info.endpoints) {
521
810
  for (const ep of dev.info.endpoints) {
522
- if (ep.inputClusters.includes(4)) {
523
- groupables.push({epid: EndPointIDfromEndPoint(ep), ep: ep, memberOf: []});
811
+ if (ep.input_clusters.includes(4)) {
812
+ groupables.push({epid: EndPointIDfromEndPoint(ep), ep: ep, memberOf: dev.groups_by_ep ? dev.groups_by_ep[ep.ID] || [] : []});
524
813
  }
525
814
  }
526
815
  }
527
816
  const numEP = groupables.length;
528
817
 
529
- if (numEP > 0) {
530
- $('#modaledit').find('.groups_available').removeClass('hide');
531
- $('#modaledit').find('.row.epid0').addClass('hide');
532
- $('#modaledit').find('.row.epid1').addClass('hide');
533
- $('#modaledit').find('.row.epid2').addClass('hide');
534
- $('#modaledit').find('.row.epid3').addClass('hide');
535
- $('#modaledit').find('.row.epid4').addClass('hide');
536
- $('#modaledit').find('.row.epid5').addClass('hide');
537
- $('#modaledit').find('.row.epid6').addClass('hide');
538
- // go through all the groups. Find the ones to list for each groupable
539
- if (numEP == 1) {
540
- $('#modaledit').find('.endpointid').addClass('hide');
541
- } else {
542
- $('#modaledit').find('.endpointid').removeClass('hide');
543
- }
544
- for (const d of devices) {
545
- if (d && d.common && d.common.type == 'group') {
546
- if (d.hasOwnProperty('memberinfo')) {
547
- for (const member of d.memberinfo) {
548
- const epid = EndPointIDfromEndPoint(member.ep);
549
- for (let i = 0; i < groupables.length; i++) {
550
- if (groupables[i].epid == epid) {
551
- groupables[i].memberOf.push(d.native.id.replace('group_', ''));
552
- }
553
- }
554
- }
555
- }
556
- }
557
- }
558
- console.log('groupables: ' + JSON.stringify(groupables));
559
- for (let i = 0; i < groupables.length; i++) {
560
- if (i > 1) {
561
- $('#modaledit').find('translate.device_with_endpoint').innerHtml = name + ' ' + groupables[i].epid;
562
- }
563
- $('#modaledit').find('.row.epid' + i).removeClass('hide');
564
- list2select('#d_groups_ep' + i, groups, groupables[i].memberOf || []);
565
- }
566
- }
567
- else
568
- {
569
- $('#modaledit').find('.groups_available').addClass('hide');
570
- }
571
- sendTo(namespace, 'getLocalConfigItems', { target:id, global:false, key:'options' }, function (msg) {
572
- if (msg) {
573
- if (msg.error) showMessage(msg.error, '_Error');
574
- console.warn(`return is ${msg}`)
575
- Object.keys(device_options).forEach(key => delete device_options[key]);
576
- Object.keys(received_options).forEach(key => delete received_options[key]);
577
- if (typeof msg.options === 'object') {
578
-
579
- let cnt = 1;
580
- for (const key in msg.options)
581
- {
582
- received_options[key]=msg.options[key];
583
- device_options[`o${cnt}`] = { key:key, value:msg.options[key]}
584
- cnt++;
585
- }
586
- }
587
- updateOptions();
588
-
589
- } else showMessage('callback without message');
590
- });
818
+ updateGroupables(groupables);
591
819
  $('#modaledit a.btn[name=\'save\']').unbind('click');
592
- $('#modaledit a.btn[name=\'add_options\']').unbind('click');
593
- $('#modaledit a.btn[name=\'add_options\']').click(() => {
594
- getOptionsFromUI(device_options, received_options);
595
- addOption();
596
- updateOptions()
597
- });
598
820
  $('#modaledit a.btn[name=\'save\']').click(() => {
599
821
  const newName = $('#modaledit').find('input[id=\'d_name\']').val();
600
- const groupsbyid = {};
822
+ const groupsById = {};
601
823
  if (groupables.length > 0) {
602
- for (let i = 0; i < groupables.length; i++) {
603
- const ng = $('#d_groups_ep' + i).val();
604
- if (ng.toString() != groupables[i].memberOf.toString())
605
- groupsbyid[groupables[i].ep.ID] = GenerateGroupChange(groupables[i].memberOf, ng);
606
- }
607
- }
608
- // read device_options from UI
609
- const co = getOptionsFromUI(device_options, received_options)
610
- console.warn(`options have ${co ? 'changed' : 'not changed'} : ${JSON.stringify(co)} vs ${JSON.stringify(received_options)} , saving them`);
611
- if (co) {
612
- sendTo(namespace, 'updateLocalConfigItems', {
613
- target: id,
614
- global:false,
615
- data: { options:co }
616
- },
617
- function (msg) {
618
- if (msg && msg.error) showMessage(msg.error, '_Error');
619
- });
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
+ }
620
830
  }
621
- updateDev(id, newName, groupsbyid);
622
-
831
+ updateDev(id, newName, groupsById);
623
832
  });
624
833
  $('#modaledit').modal('open');
625
834
  Materialize.updateTextFields();
626
835
  }
627
836
 
837
+
838
+ ////
839
+ //
840
+ //. section GroupFunctions
841
+ //
842
+ ////
628
843
  function GenerateGroupChange(oldmembers, newmembers) {
629
844
  const grpchng = [];
630
845
  for (const oldg of oldmembers)
@@ -634,8 +849,8 @@ function GenerateGroupChange(oldmembers, newmembers) {
634
849
  return grpchng;
635
850
  }
636
851
 
637
- function deleteDevice(id, force) {
638
- sendTo(namespace, 'deleteDevice', {id: id, force: force}, function (msg) {
852
+ function deleteZigbeeDevice(id, force) {
853
+ sendToWrapper(namespace, 'deleteZigbeeDevice', {id: id, force: force}, function (msg) {
639
854
  closeWaitingDialog();
640
855
  if (msg) {
641
856
  if (msg.error) {
@@ -650,7 +865,7 @@ function deleteDevice(id, force) {
650
865
 
651
866
 
652
867
  function cleanDeviceStates(force) {
653
- sendTo(namespace, 'cleanDeviceStates', {force: force}, function (msg) {
868
+ sendToWrapper(namespace, 'cleanDeviceStates', {force: force}, function (msg) {
654
869
  closeWaitingDialog();
655
870
  if (msg) {
656
871
  if (msg.error) {
@@ -668,7 +883,7 @@ function cleanDeviceStates(force) {
668
883
 
669
884
  function renameDevice(id, name) {
670
885
  showMessage('rename device with ' + id + ' and ' + name, _('Error'));
671
- sendTo(namespace, 'renameDevice', {id: id, name: name}, function (msg) {
886
+ sendToWrapper(namespace, 'renameDevice', {id: id, name: name}, function (msg) {
672
887
  if (msg) {
673
888
  if (msg.error) {
674
889
  showMessage(msg.error, _('Error'));
@@ -680,7 +895,6 @@ function renameDevice(id, name) {
680
895
  }
681
896
 
682
897
  function showDevices() {
683
- console.warn('show Devices called')
684
898
  let html = '';
685
899
  let hasCoordinator = false;
686
900
  const lang = systemLang || 'en';
@@ -719,12 +933,12 @@ function showDevices() {
719
933
  html += card;
720
934
  continue;
721
935
  };
722
- if (d.info && d.info.device && d.info.device._type == 'Coordinator') {
936
+ if (d.info && d.info.device && d.info.device.type == 'Coordinator') {
723
937
  hasCoordinator=true;
724
938
  const card = getCoordinatorCard(d);
725
939
  html += card;
726
940
  } else {
727
- //if (d.groups && d.info && d.info.device._type == "Router") {
941
+ //if (d.groups && d.info && d.info.device.type == "Router") {
728
942
  if (d.groups) {
729
943
  //devGroups[d._id] = d.groups;
730
944
  if (typeof d.groups.map == 'function') {
@@ -772,17 +986,20 @@ function showDevices() {
772
986
  $('.card.flipable').toggleClass('flipped');
773
987
  });
774
988
 
775
- shuffleInstance = new Shuffle($('#devices'), {
776
- itemSelector: '.device',
777
- sizer: '.js-shuffle-sizer',
778
- });
779
- 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
+ }
780
998
 
781
999
  const getDevName = function (dev_block) {
782
1000
  return dev_block.find('#dName').text();
783
1001
  };
784
1002
  const getDevId = function (dev_block) {
785
- console.warn(`getDevId called with ${JSON.stringify(dev_block)}`)
786
1003
  return dev_block.attr('id');
787
1004
  };
788
1005
  $('.card-reveal-buttons button[name=\'delete\']').click(function () {
@@ -819,15 +1036,20 @@ function showDevices() {
819
1036
  const name = getDevName(dev_block);
820
1037
  editGroup(id, name, false);
821
1038
  });
822
- $('button.btn-floating[name=\'join\']').click(function () {
1039
+ $('button[name=\'joinCard\']').click(function () {
823
1040
  const dev_block = $(this).parents('div.device');
824
1041
  if (!$('#pairing').hasClass('pulse')) {
825
1042
  joinProcess(getDevId(dev_block));
826
1043
  }
827
1044
  showPairingProcess();
828
1045
  });
1046
+ $('button[name=\'deviceQuery\']').click(function () {
1047
+ const dev_block = $(this).parents('div.device');
1048
+ sendTo(namespace, 'setState', {id: `${getDevId(dev_block)}.device_query`, val: true}, function (data) {
1049
+ //console.log(data);
1050
+ }); });
829
1051
  $('#modalpairing a.btn[name=\'extendpairing\']').click(function () {
830
- letsPairing();
1052
+ openNetwork();
831
1053
  });
832
1054
  $('#modalpairing a.btn[name=\'endpairing\']').click(function () {
833
1055
  stopPairing();
@@ -845,7 +1067,7 @@ function showDevices() {
845
1067
  });
846
1068
  $('.card-reveal-buttons button[name=\'reconfigure\']').click(function () {
847
1069
  const dev_block = $(this).parents('div.device');
848
- reconfigureDlg(getDevId(dev_block));
1070
+ reconfigureConfirmation(getDevId(dev_block));
849
1071
  });
850
1072
  $('.card-reveal-buttons button[name=\'swapactive\']').click(function () {
851
1073
  const dev_block = $(this).parents('div.device');
@@ -856,6 +1078,14 @@ function showDevices() {
856
1078
  translateAll();
857
1079
  }
858
1080
 
1081
+ function downloadIcons() {
1082
+ sendToWrapper(namespace, 'downloadIcons', {}, function (msg) {
1083
+ if (msg && msg.msg) {
1084
+ showMessage(msg.msg, _('Result'));
1085
+ }
1086
+ });
1087
+ }
1088
+
859
1089
  function checkFwUpdate() {
860
1090
  const deviceCards = getDeviceCards();
861
1091
  const getFwInfoNode = function (deviceCard) {
@@ -878,9 +1108,8 @@ function checkFwUpdate() {
878
1108
  fwInfoNode.html(createBtn('system_update', 'Click to start firmware update', false));
879
1109
  $(fwInfoNode).find('button[name=\'fw_update\']').click(() => {
880
1110
  fwInfoNode.html(createBtn('check_circle', 'Firmware update started, check progress in logs.', true, 'icon-blue'));
881
- sendTo(namespace, 'startOta', {devId: devId}, (msg) => {
1111
+ sendToWrapper(namespace, 'startOta', {devId: devId}, (msg) => {
882
1112
  fwInfoNode.html(createBtn('check_circle', 'Finished, see logs.', true));
883
- console.log(msg);
884
1113
  });
885
1114
  });
886
1115
  } else if (msg.status == 'not_available') {
@@ -900,13 +1129,13 @@ function checkFwUpdate() {
900
1129
  }
901
1130
  const devId = getDevId(devIdAttr);
902
1131
  getFwInfoNode(deviceCard).html('<span class="left" style="padding-top:8px">checking...</span>');
903
- sendTo(namespace, 'checkOtaAvail', {devId: devId}, callback);
1132
+ sendToWrapper(namespace, 'checkOtaAvail', {devId: devId}, callback);
904
1133
  }
905
1134
  }
906
1135
 
907
1136
  function letsPairingWithCode(code) {
908
1137
  messages = [];
909
- sendTo(namespace, 'letsPairing', {code: code, stop:false}, function (msg) {
1138
+ sendToWrapper(namespace, 'letsPairing', {code: code, stop:false}, function (msg) {
910
1139
  if (msg && msg.error) {
911
1140
  showMessage(msg.error, _('Error'));
912
1141
  }
@@ -916,18 +1145,19 @@ function letsPairingWithCode(code) {
916
1145
  });
917
1146
  }
918
1147
 
919
- function letsPairing() {
1148
+ function openNetwork() {
920
1149
  messages = [];
921
- sendTo(namespace, 'letsPairing', {stop:false}, function (msg) {
1150
+ sendToWrapper(namespace, 'letsPairing', {stop:false}, function (msg) {
922
1151
  if (msg && msg.error) {
923
1152
  showMessage(msg.error, _('Error'));
924
1153
  }
1154
+ else showPairingProcess();
925
1155
  });
926
1156
  }
927
1157
 
928
1158
  function stopPairing() {
929
1159
  messages = [];
930
- sendTo(namespace, 'letsPairing', {stop:true}, function (msg) {
1160
+ sendToWrapper(namespace, 'letsPairing', {stop:true}, function (msg) {
931
1161
  if (msg && msg.error) {
932
1162
  showMessage(msg.error, _('Error'));
933
1163
  }
@@ -936,7 +1166,7 @@ function stopPairing() {
936
1166
 
937
1167
  function touchlinkReset() {
938
1168
  messages = [];
939
- sendTo(namespace, 'touchlinkReset', {}, function (msg) {
1169
+ sendToWrapper(namespace, 'touchlinkReset', {}, function (msg) {
940
1170
  if (msg && msg.error) {
941
1171
  showMessage(msg.error, _('Error'));
942
1172
  }
@@ -945,7 +1175,7 @@ function touchlinkReset() {
945
1175
 
946
1176
  function joinProcess(devId) {
947
1177
  messages = [];
948
- sendTo(namespace, 'letsPairing', {id: devId, stop:false}, function (msg) {
1178
+ sendToWrapper(namespace, 'letsPairing', {id: devId, stop:false}, function (msg) {
949
1179
  if (msg && msg.error) {
950
1180
  showMessage(msg.error, _('Error'));
951
1181
  }
@@ -953,10 +1183,8 @@ function joinProcess(devId) {
953
1183
  }
954
1184
 
955
1185
  function getCoordinatorInfo() {
956
- console.warn('calling getCoordinatorInfo');
957
- sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
1186
+ sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
958
1187
  if (msg) {
959
- console.warn(JSON.stringify(msg))
960
1188
  if (msg.error) {
961
1189
  errorData.push(msg.error);
962
1190
  delete msg.error;
@@ -983,9 +1211,10 @@ function checkDebugDevice(id) {
983
1211
  }
984
1212
  return -1;
985
1213
  }
1214
+
986
1215
  async function toggleDebugDevice(id) {
987
- sendTo(namespace, 'setDeviceDebug', {id:id}, function (msg) {
988
- sendTo(namespace, 'getDebugDevices', {}, function(msg) {
1216
+ sendToWrapper(namespace, 'setDeviceDebug', {id:id}, function (msg) {
1217
+ sendToWrapper(namespace, 'getDebugDevices', {}, function(msg) {
989
1218
  if (msg && typeof (msg.debugDevices == 'array')) {
990
1219
  debugDevices = msg.debugDevices;
991
1220
  }
@@ -997,7 +1226,7 @@ async function toggleDebugDevice(id) {
997
1226
  }
998
1227
 
999
1228
  function updateLocalConfigItems(device, data, global) {
1000
- sendTo(namespace, 'updateLocalConfigItems', {target: device, data:data, global:global}, function(msg) {
1229
+ sendToWrapper(namespace, 'updateLocalConfigItems', {target: device, data:data, global:global}, function(msg) {
1001
1230
  if (msg && msg.hasOwnProperty.error) {
1002
1231
  showMessage(msg.error, _('Error'));
1003
1232
  }
@@ -1005,38 +1234,128 @@ function updateLocalConfigItems(device, data, global) {
1005
1234
  });
1006
1235
  }
1007
1236
 
1008
-
1009
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
+ }
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 = {};
1339
+
1010
1340
  const dev = devices.find((d) => d._id == id);
1341
+ const availableOptions = (dev.info.mapped ? dev.info.mapped.options.slice() || []:[]);
1011
1342
  const imghtml = `<img src="${dev.common.icon || dev.icon}" width="80px">`
1012
1343
  //console.error(imghtml)
1013
1344
  const selectItems= [''];
1014
1345
  $('#chooseimage').find('input[id=\'d_name\']').val(dev.common.name);
1015
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
+ });
1353
+
1016
1354
 
1017
- sendTo(namespace, 'getLocalImages', {}, function(msg) {
1355
+
1356
+ sendToWrapper(namespace, 'getLocalImages', {}, function(msg) {
1018
1357
  if (msg && msg.imageData) {
1019
- const imagedata = msg.imageData;
1020
- const default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
1021
- if (dev.legacyIcon) imagedata.unshift( { file:dev.legacyIcon, name:'legacy', data:dev.legacyIcon});
1022
- imagedata.unshift( { file:'none', name:'default', data:default_icon});
1023
- imagedata.unshift( { file:'current', name:'current', data:dev.common.icon || dev.icon});
1024
-
1025
- list2select('#images', imagedata, selectItems,
1026
- function (key, image) {
1027
- return image.name
1028
- },
1029
- function (key, image) {
1030
- return image.file;
1031
- },
1032
- function (key, image) {
1033
- if (image.isBase64) {
1034
- return `data-icon="data:image/png; base64, ${image.data}"`;
1035
- } else {
1036
- return `data-icon="${image.data}"`;
1037
- }
1038
- },
1039
- );
1358
+ updateImageSelection(dev, msg.imageData);
1040
1359
 
1041
1360
  $('#chooseimage a.btn[name=\'save\']').unbind('click');
1042
1361
  $('#chooseimage a.btn[name=\'save\']').click(() => {
@@ -1046,14 +1365,36 @@ async function selectImageOverride(id) {
1046
1365
  const data = {};
1047
1366
  if (image != 'current') data.icon= image;
1048
1367
  if (name != dev.common.name) data.name = name;
1368
+ data.options = getOptionsFromUI(device_options, received_options)
1369
+
1049
1370
  updateLocalConfigItems(id, data, global);
1050
1371
  });
1051
- $('#chooseimage').modal('open');
1052
- 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
+ });
1053
1394
  }
1054
1395
  });
1055
- }
1056
1396
 
1397
+ }
1057
1398
 
1058
1399
  function safestring(val) {
1059
1400
  const t = typeof val;
@@ -1063,6 +1404,11 @@ function safestring(val) {
1063
1404
  return val;
1064
1405
  }
1065
1406
 
1407
+ ////
1408
+ //
1409
+ //. section DebugUI
1410
+ //
1411
+ ////
1066
1412
  function fne(item) {
1067
1413
  const rv = [];
1068
1414
  if (item.flags) {
@@ -1080,6 +1426,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1080
1426
  const Html = [];
1081
1427
  const filterSet = new Set();
1082
1428
  let isodd = true;
1429
+ const buttonList = [];
1083
1430
  if (dbgMsghide.has('i_'+devID)) {
1084
1431
  console.warn('in all filtered out')
1085
1432
  Html.push('&nbsp;')
@@ -1094,8 +1441,11 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1094
1441
  const redText = (item.errors && item.errors.length > 0 ? ' id="dbgred"' : '');
1095
1442
  idx--;
1096
1443
  const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
1097
- if (idx==0)
1098
- LHtml.push(`<td${rowspan}>${item.dataID.toString(16).slice(-4)}</td><td${rowspan}>${safestring(item.payload)}</td>`);
1444
+ if (idx==0) {
1445
+ const msgbutton = `<a id="lx_${item.dataID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Messages from ${new Date(item.dataID).toLocaleTimeString()}"><i class="material-icons large">speaker_notes</i></a>`
1446
+ buttonList.push(item.dataID)
1447
+ LHtml.push(`<td${rowspan}>${msgbutton}</td><td${rowspan}>${safestring(item.payload)}</td>`);
1448
+ }
1099
1449
  LHtml.push(`<td></td><td${redText}>${safestring(state.payload)}</td><td${redText}>${state.id}</td><td${redText}>${state.value}</td><td${redText}>${fne(item)}</td></tr>`);
1100
1450
  IHtml.unshift(...LHtml)
1101
1451
  }
@@ -1105,17 +1455,17 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1105
1455
  isodd=!isodd;
1106
1456
  }
1107
1457
  }
1108
- 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>`
1109
- 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>`
1110
1460
  const dataHide = dbgMsgfilter.has('hi_'+devID) ? 'Data hidden' : '&nbsp;';
1111
- return `<thead id="dbgtable"><tr><td>&nbsp</td><td>Incoming messages</td><td>&nbsp;</td><td>&nbsp;</td><td>${dataHide}</td><td>${ifbutton}</td><td>${ofbutton}</td></tr><tr><td>ID</td><td>Zigbee Payload</td><td>&nbsp;</td><td>State Payload</td><td>ID</td><td>value</td><td>Flags</td></tr></thead><tbody>${Html.join('')}</tbody>`;
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 };
1112
1462
  }
1113
1463
 
1114
-
1115
1464
  function HtmlFromOutDebugMessages(messages, devID, filter) {
1116
1465
  const Html = [];
1117
1466
  const filterSet = new Set();
1118
1467
  let isodd=true;
1468
+ const buttonList = [];
1119
1469
  if (dbgMsghide.has('o_'+devID)) {
1120
1470
  console.warn('out all filtered out')
1121
1471
  Html.push('&nbsp;')
@@ -1131,8 +1481,11 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1131
1481
  const redText = (item.errors && item.errors.length > 0 ? ' id="dbgred"' : '');
1132
1482
  const LHtml = [(`<tr id="${isodd ? 'dbgrowodd' : 'dbgroweven'}">`)];
1133
1483
  idx--;
1134
- if (idx==0)
1135
- LHtml.push(`<td${rowspan}>${item.dataID.toString(16).slice(-4)}</td><td${rowspan}>${safestring(item.payload)}</td>`);
1484
+ if (idx==0) {
1485
+ const msgbutton = `<a id="lx_${item.dataID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Messages from ${new Date(item.dataID).toLocaleTimeString()}"><i class="material-icons large">speaker_notes</i></a>`
1486
+ LHtml.push(`<td${rowspan}>${msgbutton}</td><td${rowspan}>${safestring(item.payload)}</td>`);
1487
+ buttonList.push(item.dataID)
1488
+ }
1136
1489
  LHtml.push(`<td${redText}>${state.ep ? state.ep : ''}</td><td${redText}>${state.id}</td><td${redText}>${safestring(state.value)}</td><td${redText}>${safestring(state.payload)}</td><td${redText}>${fne(item)}</td></tr>`);
1137
1490
  IHtml.unshift(...LHtml);
1138
1491
 
@@ -1143,25 +1496,26 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1143
1496
  isodd=!isodd;
1144
1497
  }
1145
1498
  }
1146
- 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>`
1147
- 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>`
1148
1501
  const dataHide = dbgMsgfilter.has('ho_'+devID) ? 'Data hidden' : '&nbsp;';
1149
- return `<thead id="dbgtable"><tr><td>&nbsp</td><td>Outgoing messages</td><td>&nbsp;</td><td>&nbsp;</td><td>${dataHide}</td><td>${ifbutton}</td><td>${ofbutton}</td></tr><tr><td>ID</td><td>Zigbee Payload</td><td>EP</td><td>ID</td><td>value</td><td>State Payload</td><td>Flags</td></tr></thead><tbody>${Html.join('')}</tbody>`;
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};
1150
1503
  }
1151
1504
 
1152
-
1153
1505
  function displayDebugMessages(msg) {
1154
1506
  const buttonNames = [];
1507
+ const idButtons = [];
1155
1508
  if (msg.byId) {
1156
1509
  const dbgData = msg.byId;
1157
1510
  const keys = Object.keys(dbgData);
1158
1511
  const keylength = keys.length;
1159
1512
  const Html = [];
1160
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>`;
1161
- const fbutton = `<a id="f_all" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">${dbgMsgfilter.size != 0 ? 'filter_list' : 'format_align_justify' }</i></a>`;
1162
- 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>`;
1163
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>`;
1164
- Html.push(`<li><table><thead id="dbgtable"><tr><td>${logbutton}</td><td colspan="3">Debug information by device</td><td>${fbutton}</td><td>${hbutton}</td><td>${button}</td></tr></thead><tbody>`);
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>`);
1165
1519
  if (!keylength) {
1166
1520
  Html.push('<tr><td></td><td>No debug data loaded - press reload to refresh</td><td></td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr></tbody></table></li>')
1167
1521
  $('#dbg_data_list').html(Html.join(''));
@@ -1176,20 +1530,28 @@ function displayDebugMessages(msg) {
1176
1530
  const modelUrl = (type_url === 'unknown') ? 'unknown' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${image}</a>`;
1177
1531
  const devName = (dev && dev.common && dev.common.name) ? dev.common.name : 'unnamed';
1178
1532
  const button = `<a id="e_${devID}" class="btn-floating waves-effect waves-light green tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons large">sync_problem</i></a>`
1533
+ const dbutton = `<a id="d_${devID}" class="btn-floating waves-effect waves-light red tooltipped center-align hoverable translateT" title="Update debug messages"><i class="material-icons icon-yellow large">delete_forever</i></a>`;
1179
1534
  buttonNames.push(devID);
1180
- Html.push(`<li><table><thead id="dbgtable"><tr><td colspan="4">${devName} (ID: ${devID} Model: ${dev && dev.common ? dev.common.name : 'unknown'})</td><td>${modelUrl}</td><td>&nbsp;</td><td>${button}</td></tr></thead><tbody>`);
1535
+ Html.push(`<li><table><thead id="dbgtable"><tr><td colspan="4">${devName} (ID: ${devID} Model: ${dev && dev.common ? dev.common.name : 'unknown'})</td><td>${modelUrl}</td><td>${button}</td><td>${dbutton}</td></tr></thead><tbody>`);
1181
1536
  if (dbgData[devID].IN.length > 0) {
1182
- Html.push(`${HtmlFromInDebugMessages(dbgData[devID].IN, devID, dbgMsgfilter.has('i_'+devID))}`);
1537
+ const indata = HtmlFromInDebugMessages(dbgData[devID].IN, devID, dbgMsgfilter.has('i_'+devID));
1538
+ Html.push(`${indata.html}`);
1539
+ idButtons.push(...indata.buttonList)
1183
1540
  }
1184
1541
  if (dbgData[devID].OUT.length > 0) {
1185
- Html.push(`${HtmlFromOutDebugMessages(dbgData[devID].OUT, devID, dbgMsgfilter.has('o_'+devID))}`);
1542
+ const outdata = HtmlFromOutDebugMessages(dbgData[devID].OUT, devID, dbgMsgfilter.has('o_'+devID));
1543
+ Html.push(`${outdata.html}`);
1544
+ idButtons.push(...outdata.buttonList)
1186
1545
  }
1187
1546
  Html.push('</tbody></table></li>');
1188
1547
  }
1189
1548
  $('#dbg_data_list').html(Html.join(''));
1190
1549
  }
1191
1550
  $(`#e_all`).click(function () {
1192
- getDebugMessages();
1551
+ getDebugMessages(false);
1552
+ });
1553
+ $(`#d_all`).click(function () {
1554
+ getDebugMessages(true, 'all');
1193
1555
  });
1194
1556
  $(`#l_all`).click(function () {
1195
1557
  debugInLog = !debugInLog;
@@ -1221,7 +1583,10 @@ function displayDebugMessages(msg) {
1221
1583
  });
1222
1584
  for (const b of buttonNames) {
1223
1585
  $(`#e_${b}`).click(function () {
1224
- getDebugMessages();
1586
+ getDebugMessages(false);
1587
+ });
1588
+ $(`#d_${b}`).click(function () {
1589
+ getDebugMessages(true, b);
1225
1590
  });
1226
1591
  $(`#o_${b}`).click(function () {
1227
1592
  if (dbgMsgfilter.has(`o_${b}`)) dbgMsgfilter.delete(`o_${b}`); else dbgMsgfilter.add(`o_${b}`);
@@ -1240,101 +1605,167 @@ function displayDebugMessages(msg) {
1240
1605
  displayDebugMessages(debugMessages);
1241
1606
  });
1242
1607
  }
1608
+ for (const b of idButtons) {
1609
+ $(`#lx_${b}`).click(function() { showMessageList(b)});
1610
+ }
1243
1611
  }
1244
1612
  }
1245
1613
 
1246
- function getDebugMessages() {
1247
- sendTo(namespace, 'getDebugMessages', { inlog: debugInLog }, function(msg) {
1248
- debugMessages = msg;
1249
- if (msg) displayDebugMessages(debugMessages)
1250
- })
1251
- }
1614
+ function showNamedMessages(messages, title, icon, timestamp) {
1615
+ // noinspection JSJQueryEfficiency
1616
+ let $dialogMessage = $('#dialog-message');
1617
+ if (!$dialogMessage.length) {
1618
+ $('body').append(
1619
+ '<div class="m"><div id="dialog-message" class="modal modal-fixed-footer">' +
1620
+ ' <div class="modal-content">' +
1621
+ ' <h6 class="dialog-title title"></h6>' +
1622
+ ' <p><i class="large material-icons dialog-icon"></i><span class="dialog-text"></span></p>' +
1623
+ ' </div>' +
1624
+ ' <div class="modal-footer">' +
1625
+ ' <a class="modal-action modal-close waves-effect waves-green btn-flat translate">Ok</a>' +
1626
+ ' </div>' +
1627
+ '</div></div>');
1628
+ $dialogMessage = $('#dialog-message');
1629
+ }
1630
+ if (icon) {
1631
+ $dialogMessage.find('.dialog-icon')
1632
+ .show()
1633
+ .html(icon);
1634
+ } else {
1635
+ $dialogMessage.find('.dialog-icon').hide();
1636
+ }
1637
+ if (title) {
1638
+ $dialogMessage.find('.dialog-title').html(title).show();
1639
+ } else {
1640
+ $dialogMessage.find('.dialog-title').hide();
1641
+ }
1642
+ const lihtml = ['```<br><ul>'];
1643
+ for (const key of Object.keys(messages)) {
1644
+ lihtml.push(`<li>${key}: ${messages[key]}</li>`)
1645
+ }
1646
+ lihtml.push('</ul><br>```')
1647
+ $dialogMessage.find('.dialog-text').html(lihtml);
1648
+ $dialogMessage.modal().modal('open');
1252
1649
 
1650
+ }
1253
1651
 
1254
- function getDevices() {
1255
- console.warn('getDevices called')
1256
- sendTo(namespace, 'getCoordinatorInfo', {}, function (msg) {
1257
- console.warn(`getCoordinatorInfo returned ${JSON.stringify(msg)}`)
1258
- if (msg) {
1259
- console.warn(JSON.stringify(msg))
1260
- if (msg.error) {
1261
- errorData.push(msg.error);
1262
- delete msg.error;
1263
- isHerdsmanRunning = false;
1264
- } else {
1265
- isHerdsmanRunning = true;
1652
+ function showMessageList(msgId) {
1653
+ for (const devId of Object.keys(debugMessages.byId)) {
1654
+ for (const id of debugMessages.byId[devId].IN) {
1655
+ if (id.dataID == msgId) {
1656
+ showNamedMessages(id.messages, `Messages from ${new Date(msgId).toLocaleTimeString()} for device ${devId}`);
1657
+ return;
1658
+ }
1659
+ }
1660
+ for (const id of debugMessages.byId[devId].OUT) {
1661
+ if (id.dataID == msgId) {
1662
+ showNamedMessages(id.messages, `Messages from ${new Date(msgId).toLocaleTimeString()} for device ${devId}`);
1663
+ return;
1266
1664
  }
1267
- coordinatorinfo = msg;
1268
- updateStartButton()
1269
1665
  }
1270
- sendTo(namespace, 'getDevices', {}, function (msg) {
1271
- if (msg) {
1272
- devices = msg.devices ? msg.devices : [];
1273
- // check if stashed error messages are sent alongside
1274
- if (msg.clean)
1275
- $('#state_cleanup_btn').removeClass('hide');
1276
- else
1277
- $('#state_cleanup_btn').addClass('hide');
1278
- if (msg.errors && msg.errors.length > 0) {
1279
- $('#show_errors_btn').removeClass('hide');
1280
- errorData = msg.errors;
1281
- }
1282
- else {
1283
- $('#show_errors_btn').addClass('hide');
1284
- }
1666
+ }
1667
+ }
1285
1668
 
1286
- //check if debug messages are sent alongside
1287
- if (msg && typeof (msg.debugDevices == 'array')) {
1288
- debugDevices = msg.debugDevices;
1289
- console.warn('debug devices is sent')
1290
- }
1291
- else
1292
- debugDevices = [];
1293
- if (debugMessages.byId) {
1294
- debugMessages.byId = msg;
1295
- if (msg) displayDebugMessages(debugMessages)
1296
- }
1669
+ function getDebugMessages(deleteBeforeRead, deleteSelected) {
1670
+ sendToWrapper(namespace, 'getDebugMessages', { inlog: debugInLog, del:deleteBeforeRead ? deleteSelected : '' }, function(msg) {
1671
+ debugMessages = msg;
1672
+ if (msg) displayDebugMessages(debugMessages)
1673
+ })
1674
+ }
1675
+
1676
+ ////
1677
+ //
1678
+ //. section getDataFromAdapter
1679
+ //
1680
+ ////
1681
+ function getDevices() {
1682
+ function sendForData() {
1683
+ sendToWrapper(namespace, 'getCoordinatorInfo', {}, function (msg) {
1684
+ if (msg) {
1297
1685
  if (msg.error) {
1298
1686
  errorData.push(msg.error);
1687
+ delete msg.error;
1299
1688
  isHerdsmanRunning = false;
1300
- updateStartButton();
1301
- showDevices();
1302
1689
  } else {
1303
1690
  isHerdsmanRunning = true;
1691
+ }
1692
+ coordinatorinfo = msg;
1693
+ updateStartButton()
1694
+ }
1695
+ sendToWrapper(namespace, 'getDevices', {}, function (msg) {
1696
+ if (msg) {
1697
+ devices = msg.devices ? msg.devices : [];
1698
+ // check if stashed error messages are sent alongside
1699
+ if (msg.clean)
1700
+ $('#state_cleanup_btn').removeClass('hide');
1701
+ else
1702
+ $('#state_cleanup_btn').addClass('hide');
1703
+ if (msg.errors && msg.errors.length > 0) {
1704
+ $('#show_errors_btn').removeClass('hide');
1705
+ errorData = msg.errors;
1706
+ }
1707
+ else {
1708
+ $('#show_errors_btn').addClass('hide');
1709
+ }
1710
+ let newDebugMessages = false;
1711
+
1712
+ //check if debug messages are sent alongside
1713
+ if (msg && typeof (msg.debugDevices == 'array')) {
1714
+ debugDevices = msg.debugDevices;
1715
+ }
1716
+ else
1717
+ debugDevices = [];
1718
+ if (debugMessages.byId) {
1719
+ newDebugMessages = true;
1720
+ debugMessages.byId = msg;
1721
+ if (msg) displayDebugMessages(debugMessages)
1722
+ }
1723
+ lockout.isActive = false;
1724
+ if (msg.error) {
1725
+ errorData.push(msg.error);
1726
+ isHerdsmanRunning = false;
1727
+ } else {
1728
+ isHerdsmanRunning = true;
1729
+ if (!newDebugMessages) {
1730
+ getDebugMessages();
1731
+ }
1732
+ //getExclude();
1733
+ getBinding();
1734
+ }
1304
1735
  updateStartButton();
1305
1736
  showDevices();
1306
- getDebugMessages();
1307
- getExclude();
1308
- getBinding();
1737
+ showLocalData();
1738
+ UpdateAdapterAlive(true)
1309
1739
  }
1310
- }
1740
+ });
1311
1741
  });
1312
- });
1742
+ }
1743
+
1744
+ if (lockout.timeoutid) {
1745
+ clearTimeout(lockout.timeoutid);
1746
+ }
1747
+
1748
+ setTimeout(() => {
1749
+ lockout.isActive = true;
1750
+ lockout.timeoutid = undefined;
1751
+ sendForData();
1752
+ }, 100);
1753
+
1313
1754
  }
1314
1755
 
1315
1756
  function getNamedColors() {
1316
- sendTo(namespace, 'getNamedColors', {}, function(msg) {
1757
+ sendToWrapper(namespace, 'getNamedColors', {}, function(msg) {
1317
1758
  if (msg && typeof msg.colors) {
1318
1759
  namedColors = msg.colors;
1319
1760
  }
1320
1761
  });
1321
1762
  }
1322
1763
 
1323
- function getDeviceCards() {
1324
- return $('#devices .device').not('.group');
1325
- }
1326
-
1327
- function getDeviceCard(devId) {
1328
- if (devId.startsWith('0x')) {
1329
- devId = devId.substr(2, devId.length);
1330
- }
1331
- return $('#devices').find(`div[id='${namespace}.${devId}']`);
1332
- }
1333
1764
 
1334
- function getMap() {
1765
+ function getMap(rebuild) {
1335
1766
  $('#refresh').addClass('disabled');
1336
1767
  if (isHerdsmanRunning) {
1337
- sendTo(namespace, 'getMap', {}, function (msg) {
1768
+ sendToWrapper(namespace, 'getMap', { forcebuild:rebuild}, function (msg) {
1338
1769
  $('#refresh').removeClass('disabled');
1339
1770
  if (msg) {
1340
1771
  if (msg.error) {
@@ -1356,27 +1787,27 @@ function getMap() {
1356
1787
  else showMessage('Unable to generate map, the zigbee subsystem is inactive', 'Map generation error');
1357
1788
  }
1358
1789
 
1359
- function getRandomExtPanID()
1360
- {
1361
- const bytes = [];
1362
- for (let i = 0;i<16;i++) {
1363
- bytes.push(Math.floor(Math.random() * 16).toString(16));
1364
- }
1365
- return bytes.join('');
1366
- }
1367
-
1368
- function getRandomChannel()
1369
- {
1370
- const channels = [11,15,20,25]
1371
- return channels[Math.floor(Math.random() * 4)];
1372
- }
1373
1790
 
1374
1791
 
1375
1792
 
1376
1793
  // the function loadSettings has to exist ...
1377
1794
 
1378
1795
  function load(settings, onChange) {
1379
- 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
+
1380
1811
  if (settings.extPanID === undefined || settings.extPanID == '') {
1381
1812
  settings.channel = getRandomChannel();
1382
1813
  }
@@ -1404,6 +1835,7 @@ function load(settings, onChange) {
1404
1835
  settings.baudRate = 115200;
1405
1836
  }
1406
1837
  if (settings.autostart === undefined) settings.autostart = false;
1838
+ if (typeof settings.pingCluster != 'string') settings.pingCluster = settings.disablePing ? 'off' : 'default';
1407
1839
 
1408
1840
  // example: select elements with id=key and class=value and insert value
1409
1841
  for (const key in settings) {
@@ -1427,13 +1859,20 @@ function load(settings, onChange) {
1427
1859
  }
1428
1860
  }
1429
1861
 
1430
-
1431
1862
  getComPorts(onChange);
1432
1863
 
1433
1864
  //dialog = new MatDialog({EndingTop: '50%'});
1434
- getDevices();
1435
- getNamedColors();
1436
- 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
+
1437
1876
  //getDebugMessages();
1438
1877
  //getMap();
1439
1878
  //addCard();
@@ -1442,13 +1881,10 @@ function load(settings, onChange) {
1442
1881
  onChange(false);
1443
1882
 
1444
1883
  $('#test-btn').click(function () {
1445
- console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1446
1884
  if (!isHerdsmanRunning) {
1447
1885
  const port = $('#port.value').val();
1448
- console.warn(`port is ${port}`)
1449
1886
  showWaitingDialog(`Trying to connect to ${port}`, 300);
1450
- sendTo(namespace, 'testConnection', { address:port }, function(msg) {
1451
- console.warn(`send to returned with ${JSON.stringify(msg)}`);
1887
+ sendToWrapper(namespace, 'testConnection', { address:port }, function(msg) {
1452
1888
  closeWaitingDialog();
1453
1889
  if (msg) {
1454
1890
  if (msg.error) {
@@ -1467,7 +1903,6 @@ function load(settings, onChange) {
1467
1903
  })
1468
1904
  // test start commands
1469
1905
  $('#show_test_run').click(function () {
1470
- console.warn(`isHerdsmanRunning: ${isHerdsmanRunning}`)
1471
1906
  doTestStart(!isHerdsmanRunning);
1472
1907
  });
1473
1908
 
@@ -1477,6 +1912,9 @@ function load(settings, onChange) {
1477
1912
  $('#show_errors_btn').click(function () {
1478
1913
  showMessage(errorData.join('<br>'), 'Stashed error messages');
1479
1914
  });
1915
+ $('#download_icons_btn').click(function () {
1916
+ showMessage(downloadIcons());
1917
+ });
1480
1918
  $('#fw_check_btn').click(function () {
1481
1919
  checkFwUpdate();
1482
1920
  });
@@ -1486,14 +1924,16 @@ function load(settings, onChange) {
1486
1924
  });
1487
1925
  $('#pairing').click(function () {
1488
1926
  if (!$('#pairing').hasClass('pulse')) {
1489
- letsPairing();
1490
- }
1491
- console.warn('lets pairing');
1492
- showPairingProcess();
1927
+ openNetwork();
1928
+ } else showPairingProcess();
1493
1929
  });
1494
1930
 
1495
1931
  $('#refresh').click(function () {
1496
- getMap();
1932
+ getMap(false);
1933
+ });
1934
+ $('#regenerate').click(function () {
1935
+ getMap(true);
1936
+ $('#modalviewconfig').modal('close');
1497
1937
  });
1498
1938
 
1499
1939
  $('#reset-btn').click(function () {
@@ -1505,7 +1945,9 @@ function load(settings, onChange) {
1505
1945
  });
1506
1946
 
1507
1947
  $('#ErrorNotificationBtn').click(function () {
1508
- if (!isHerdsmanRunning) showMessage('The zigbee subsystem is not running. Please ensure that the configuration is correct and either start the subsystem manually from the hardware tab or set it to automatically start in the settings.', _('Zigbee subsystem error'));
1948
+ if (!isHerdsmanRunning) {
1949
+ doTestStart(!isHerdsmanRunning, true);
1950
+ }
1509
1951
  })
1510
1952
 
1511
1953
  $('#viewconfig').click(function () {
@@ -1519,18 +1961,14 @@ function load(settings, onChange) {
1519
1961
  showChannels();
1520
1962
  });
1521
1963
 
1522
- sendTo(namespace, 'getGroups', {}, function (data) {
1523
- groups = data.groups;
1524
- //showGroups();
1525
- });
1526
1964
 
1527
1965
  $('#add_group').click(function () {
1528
- 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));
1529
1967
  addGroup(maxind + 1, 'Group ' + maxind + 1);
1530
1968
  });
1531
1969
 
1532
1970
  $('#add_grp_btn').click(function () {
1533
- 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));
1534
1972
  addGroup(maxind + 1, 'Group ' + maxind + 1);
1535
1973
  });
1536
1974
 
@@ -1556,6 +1994,7 @@ function load(settings, onChange) {
1556
1994
  $('.dropdown-trigger').dropdown({constrainWidth: false});
1557
1995
  Materialize.updateTextFields();
1558
1996
  $('.collapsible').collapsible();
1997
+
1559
1998
  Materialize.Tabs.init($('.tabs'));
1560
1999
  $('#device-search').keyup(function (event) {
1561
2000
  doFilter(event.target.value.toLowerCase());
@@ -1589,11 +2028,15 @@ function load(settings, onChange) {
1589
2028
  addExcludeDialog();
1590
2029
  });
1591
2030
 
2031
+ $('#updateData').click(function () {
2032
+ getDevices();
2033
+ });
2034
+
1592
2035
  $('#add_binding').click(function () {
1593
2036
  addBindingDialog();
1594
2037
  });
1595
2038
 
1596
- sendTo(namespace, 'getLibData', {key: 'cidList'}, function (data) {
2039
+ sendToWrapper(namespace, 'getLibData', {key: 'cidList'}, function (data) {
1597
2040
  cidList = data.list;
1598
2041
  });
1599
2042
  }
@@ -1608,21 +2051,30 @@ function showMessages() {
1608
2051
  $('#stdout_t').text(messages.join('\n'));
1609
2052
  }
1610
2053
 
1611
- function showPairingProcess() {
2054
+ function showPairingProcess(noextrabuttons) {
1612
2055
  if (isHerdsmanRunning) $('#modalpairing').modal({
1613
2056
  startingTop: '4%',
1614
2057
  endingTop: '10%',
1615
2058
  dismissible: false
1616
2059
  });
1617
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
+
1618
2070
  $('#modalpairing').modal('open');
1619
2071
  Materialize.updateTextFields();
1620
2072
  }
1621
2073
 
1622
- function doTestStart(start) {
2074
+ function doTestStart(start, interactive) {
1623
2075
  updateStartButton(true);
1624
2076
  if (start) {
1625
- const ovr = { extPanID:$('#extPanID.value').val(),
2077
+ const ovr = interactive ? {} : { extPanID:$('#extPanID.value').val(),
1626
2078
  panID: $('#PanID.value').val(),
1627
2079
  channel: $('#channel.value').val(),
1628
2080
  port: $('#port.value').val(),
@@ -1633,18 +2085,25 @@ function doTestStart(start) {
1633
2085
  };
1634
2086
  // $('#testStartStart').addClass('disabled');
1635
2087
  messages = [];
1636
- 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) {
1637
2092
  if (msg) {
2093
+ closeWaitingDialog();
2094
+ updateStartButton(false);
1638
2095
  if (msg.status)
1639
2096
  $('#testStartStop').removeClass('disabled');
1640
- else
2097
+ else {
2098
+ //showMessage(`The zigbee subsystem is not running. Please ensure that the configuration is correct. ${msg.error ? 'Error on start-Attempt ' + msg.error.message : ''}`);
1641
2099
  $('#testStartStart').removeClass('disabled');
2100
+ }
1642
2101
  }
1643
2102
  })
1644
2103
  }
1645
2104
  else {
1646
2105
  //$('#testStartStop').addClass('disabled');
1647
- sendTo(namespace, 'testConnect', { start:false }, function(msg) {
2106
+ sendToWrapper(namespace, 'testConnect', { start:false }, function(msg) {
1648
2107
  if (msg) {
1649
2108
  if (msg.status) $('#testStartStart').removeClass('disabled');
1650
2109
  else $('#testStartStop').removeClass('disabled');
@@ -1682,15 +2141,14 @@ function getDevId(adapterDevId) {
1682
2141
 
1683
2142
 
1684
2143
  function updateStartButton(block) {
1685
- console.warn(`update start button with${isHerdsmanRunning ? ' Herdsman' : 'out Herdsman'}`);
1686
2144
  if (block) {
1687
2145
  $('#show_test_run').addClass('disabled');
1688
2146
  $('#reset-btn').addClass('disabled');
1689
2147
  $('#deleteNVRam-btn').addClass('disabled');
1690
2148
  $('#ErrorNotificationBtn').removeClass('hide')
1691
2149
  $('#ErrorNotificationBtn').removeClass('blinking')
1692
- $('#ErrorNotificationIcon').removeClass('icon-red')
1693
- $('#ErrorNotificationIcon').addClass('icon-orange')
2150
+ $('#ErrorNotificationBtn').removeClass('red')
2151
+ $('#ErrorNotificationBtn').addClass('orange')
1694
2152
  return;
1695
2153
  }
1696
2154
  if (isHerdsmanRunning)
@@ -1707,8 +2165,8 @@ function updateStartButton(block) {
1707
2165
  //$('#pairing').removeClass('hide');
1708
2166
  }
1709
2167
  else {
1710
- $('#ErrorNotificationIcon').addClass('icon-red')
1711
- $('#ErrorNotificationIcon').removeClass('icon-orange')
2168
+ $('#ErrorNotificationBtn').addClass('red')
2169
+ $('#ErrorNotificationBtn').removeClass('orange')
1712
2170
  $('#ErrorNotificationBtn').removeClass('hide')
1713
2171
  $('#ErrorNotificationBtn').addClass('blinking');
1714
2172
  $('#show_test_run').removeClass('disabled');
@@ -1729,7 +2187,6 @@ socket.emit('subscribeObjects', namespace + '.*');
1729
2187
  socket.on('stateChange', function (id, state) {
1730
2188
  // only watch our own states
1731
2189
  if (id.substring(0, namespaceLen) !== namespace) return;
1732
- //console.log('stateChange', id, state);
1733
2190
  if (state) {
1734
2191
  if (id.match(/\.info\.pairingMode$/)) {
1735
2192
  if (state.val) {
@@ -1791,14 +2248,12 @@ socket.on('stateChange', function (id, state) {
1791
2248
 
1792
2249
  socket.on('objectChange', function (id, obj) {
1793
2250
  if (id.substring(0, namespaceLen) !== namespace) return;
1794
- //console.log('objectChange', id, obj);
1795
2251
  if (obj && obj.type == 'device') { // && obj.common.type !== 'group') {
1796
2252
  updateDevice(id);
1797
2253
  }
1798
2254
  if (!obj) {
1799
2255
  // delete state or device
1800
2256
  const elems = id.split('.');
1801
- //console.log('elems', elems);
1802
2257
  if (elems.length === 3) {
1803
2258
  removeDevice(id);
1804
2259
  showDevices();
@@ -1850,10 +2305,11 @@ function showNetworkMap(devices, map) {
1850
2305
  const createNode = function (dev, mapEntry) {
1851
2306
  if (dev.common && (dev.common.type == 'group' || dev.common.deactivated)) return undefined;
1852
2307
  const extInfo = (mapEntry && mapEntry.networkAddress) ? `\n (nwkAddr: 0x${mapEntry.networkAddress.toString(16)} | ${mapEntry.networkAddress})` : '';
2308
+ const t = dev._id.replace(namespace + '.', '');
1853
2309
  const node = {
1854
2310
  id: dev._id,
1855
2311
  label: (dev.link_quality > 0 ? dev.common.name : `${dev.common.name}\n(disconnected)`),
1856
- title: dev._id.replace(namespace + '.', '') + extInfo,
2312
+ title: `${t} ${extInfo}`,
1857
2313
  shape: 'circularImage',
1858
2314
  image: dev.common.icon || dev.icon,
1859
2315
  imagePadding: {top: 5, bottom: 5, left: 5, right: 5},
@@ -1862,20 +2318,20 @@ function showNetworkMap(devices, map) {
1862
2318
  borderWidth: 1,
1863
2319
  borderWidthSelected: 4,
1864
2320
  };
1865
- if (dev.info && dev.info.device._type == 'Coordinator') {
2321
+ if (dev.common && dev.common.type === 'Coordinator') {
1866
2322
  // node.shape = 'star';
1867
2323
  node.image = 'zigbee.png';
1868
2324
  node.label = 'Coordinator';
1869
2325
  // delete node.color;
1870
2326
  }
2327
+ console.warn(`node for device ${JSON.stringify(node)}`)
1871
2328
  return node;
1872
2329
  };
1873
2330
 
1874
2331
  if (map.lqis) {
1875
2332
  map.lqis.forEach((mapEntry) => {
1876
- const dev = getDevice(mapEntry.ieeeAddr);
2333
+ const dev = getDeviceByIEEE(mapEntry.ieeeAddr);
1877
2334
  if (!dev) {
1878
- //console.log("No dev with ieee "+mapEntry.ieeeAddr);
1879
2335
  return;
1880
2336
  }
1881
2337
 
@@ -1889,7 +2345,7 @@ function showNetworkMap(devices, map) {
1889
2345
  node = nodes[mapEntry.ieeeAddr];
1890
2346
  }
1891
2347
  if (node) {
1892
- const parentDev = getDevice(mapEntry.parent);
2348
+ const parentDev = getDeviceByIEEE(mapEntry.parent);
1893
2349
  const to = parentDev ? parentDev._id : undefined;
1894
2350
  const from = dev._id;
1895
2351
  let label = mapEntry.lqi.toString();
@@ -2022,7 +2478,7 @@ function showNetworkMap(devices, map) {
2022
2478
 
2023
2479
  if (node) {
2024
2480
  node.font = {color: '#ff0000'};
2025
- if (dev.info && dev.info.device._type == 'Coordinator') {
2481
+ if (dev.info && dev.info.device && dev.info.device.type == 'Coordinator') {
2026
2482
  node.font = {color: '#00ff00'};
2027
2483
  }
2028
2484
  nodesArray.push(node);
@@ -2156,7 +2612,7 @@ function getComPorts(onChange) {
2156
2612
  // timeout = setTimeout(function () {
2157
2613
  // getComPorts(onChange);
2158
2614
  // }, 2000);
2159
- sendTo(namespace, 'listUart', null, function (list) {
2615
+ sendToWrapper(namespace, 'listUart', null, function (list) {
2160
2616
  // if (timeout) {
2161
2617
  // clearTimeout(timeout);
2162
2618
  // timeout = null;
@@ -2187,35 +2643,37 @@ function loadDeveloperTab() {
2187
2643
  updateSelect('#dev', devices,
2188
2644
  function (key, device) {
2189
2645
  if (device.hasOwnProperty('info')) {
2190
- if (device.info.device._type === 'Coordinator') {
2646
+ if (device.info.device && device.info.device.type === 'Coordinator') {
2191
2647
  return null;
2192
2648
  }
2193
- return `${device.common.name} (${device.info.name})`;
2649
+ if (device.common.type === 'group') return null;
2650
+ return `${device.common.name} (${device.info.device.ieee})`;
2194
2651
  } else { // fallback if device in list but not paired
2195
2652
  return device.common.name + ' ' + device.native.id;
2196
2653
  }
2197
2654
  },
2198
2655
  function (key, device) {
2199
- return device._id;
2656
+ return device.native.id;
2200
2657
  });
2201
- // add groups to device selector
2202
- const groupList = [];
2203
- for (const key in groups) {
2204
- groupList.push({
2205
- _id: namespace + '.' + key.toString(16).padStart(16, '0'),
2206
- groupId: key,
2207
- groupName: groups[key]
2208
- });
2209
- }
2210
- updateSelect('#dev', groupList,
2211
- function (key, device) {
2212
- return 'Group ' + device.groupId + ': ' + device.groupName;
2213
- },
2214
- function (key, device) {
2215
- return device._id;
2216
- }, true);
2658
+ /*
2659
+ const groupList = [];
2660
+ for (const key in groups) {
2661
+ groupList.push({
2662
+ id: namespace + '.' + key.toString(16).padStart(16, '0'),
2663
+ groupId: key,
2664
+ groupName: groups[key]
2665
+ });
2666
+ }
2667
+ updateSelect('#dev', groupList,
2668
+ function (key, device) {
2669
+ return 'Group ' + device.groupId + ': ' + device.groupName;
2670
+ },
2671
+ function (key, device) {
2672
+ return device.id;
2673
+ }, true);
2217
2674
 
2218
- // fill cid, cmd, type selector
2675
+ // fill cid, cmd, type selector
2676
+ */
2219
2677
  populateSelector('#cid', 'cidList');
2220
2678
  populateSelector('#cmd', 'cmdListFoundation', this.value);
2221
2679
  populateSelector('#type', 'typeList', this.value);
@@ -2296,10 +2754,10 @@ function loadDeveloperTab() {
2296
2754
  }
2297
2755
 
2298
2756
  const device = devices.find(obj => {
2299
- return obj._id === this.value;
2757
+ return this.value ===obj.native.id;
2300
2758
  });
2301
2759
 
2302
- const epList = device ? device.info.device._endpoints : null;
2760
+ const epList = device ? device.info.endpoints : null;
2303
2761
  updateSelect('#ep', epList,
2304
2762
  function (key, ep) {
2305
2763
  return ep.ID;
@@ -2380,7 +2838,7 @@ function loadDeveloperTab() {
2380
2838
  data = prepareData();
2381
2839
  }
2382
2840
  sendToZigbee(data.devId, data.ep, data.cid, data.cmd, data.cmdType, data.zclData, data.cfg, function (reply) {
2383
- console.log('Reply from zigbee: ' + JSON.stringify(reply));
2841
+ console.log('Send to Zigbee replied with ' + JSON.stringify(reply));
2384
2842
  if (reply.hasOwnProperty('localErr')) {
2385
2843
  showDevRunInfo(reply.localErr, reply.errMsg, 'yellow');
2386
2844
  } else if (reply.hasOwnProperty('localStatus')) {
@@ -2395,7 +2853,7 @@ function loadDeveloperTab() {
2395
2853
 
2396
2854
  responseCodes = null;
2397
2855
  // load list of response codes
2398
- sendTo(namespace, 'getLibData', {key: 'respCodes'}, function (data) {
2856
+ sendToWrapper(namespace, 'getLibData', {key: 'respCodes'}, function (data) {
2399
2857
  responseCodes = data.list;
2400
2858
  });
2401
2859
  }
@@ -2448,7 +2906,7 @@ function sendToZigbee(id, ep, cid, cmd, cmdType, zclData, cfg, callback) {
2448
2906
 
2449
2907
  console.log('Send to zigbee, id ' + id + ',ep ' + ep + ', cid ' + cid + ', cmd ' + cmd + ', cmdType ' + cmdType + ', zclData ' + JSON.stringify(zclData));
2450
2908
 
2451
- sendTo(namespace, 'sendToZigbee', data, function (reply) {
2909
+ sendToWrapper(namespace, 'sendToZigbee', data, function (reply) {
2452
2910
  clearTimeout(sendTimeout);
2453
2911
  if (callback) {
2454
2912
  callback(reply);
@@ -2496,7 +2954,7 @@ function populateSelector(selectId, key, cid) {
2496
2954
  updateSelect(selectId, null);
2497
2955
  return;
2498
2956
  }
2499
- sendTo(namespace, 'getLibData', {key: key, cid: cid}, function (data) {
2957
+ sendToWrapper(namespace, 'getLibData', {key: key, cid: cid}, function (data) {
2500
2958
  const list = data.list;
2501
2959
  if (key === 'attrIdList') {
2502
2960
  updateSelect(selectId, list,
@@ -2639,7 +3097,7 @@ function deleteGroupConfirmation(id, name) {
2639
3097
 
2640
3098
  function updateGroup(newId, newName, remove) {
2641
3099
  groups[newId] = newName;
2642
- sendTo(namespace, 'renameGroup', {id: newId, name: newName, remove: remove}, function (msg) {
3100
+ sendToWrapper(namespace, 'renameGroup', {id: newId, name: newName, remove: remove}, function (msg) {
2643
3101
  if (msg && msg.error) {
2644
3102
  showMessage(msg.error, _('Error'));
2645
3103
  }
@@ -2649,7 +3107,7 @@ function updateGroup(newId, newName, remove) {
2649
3107
 
2650
3108
  function deleteGroup(id) {
2651
3109
  delete groups[id];
2652
- sendTo(namespace, 'deleteGroup', id, function (msg) {
3110
+ sendToWrapper(namespace, 'deleteGroup', id, function (msg) {
2653
3111
  if (msg && msg.error) {
2654
3112
  showMessage(msg.error, _('Error'));
2655
3113
  }
@@ -2672,7 +3130,7 @@ function updateDev(id, newName, newGroups) {
2672
3130
  const keys = Object.keys(newGroups);
2673
3131
  if (keys && keys.length) {
2674
3132
  command.groups = newGroups
2675
- sendTo(namespace, 'updateGroupMembership', command, function (msg) {
3133
+ sendToWrapper(namespace, 'updateGroupMembership', command, function (msg) {
2676
3134
  closeWaitingDialog();
2677
3135
  if (msg && msg.error) {
2678
3136
  showMessage(msg.error, _('Error'));
@@ -2686,7 +3144,7 @@ function updateDev(id, newName, newGroups) {
2686
3144
  }
2687
3145
  else if (needName)
2688
3146
  {
2689
- sendTo(namespace, 'renameDevice', command, function(msg) {
3147
+ sendToWrapper(namespace, 'renameDevice', command, function(msg) {
2690
3148
  //closeWaitingDialog();
2691
3149
  if (msg && msg.error) {
2692
3150
  showMessage(msg.error, _('Error'));
@@ -2705,9 +3163,9 @@ function resetConfirmation() {
2705
3163
  const btn = $('#modalreset .modal-content a.btn');
2706
3164
  btn.unbind('click');
2707
3165
  btn.click(function (e) {
2708
- sendTo(namespace, 'reset', {mode: e.target.id}, function (err) {
3166
+ sendToWrapper(namespace, 'reset', {mode: e.target.id}, function (err) {
2709
3167
  if (err) {
2710
- console.log(err);
3168
+ console.log(`reset attempt failed with ${err}`);
2711
3169
  } else {
2712
3170
  console.log('Reset done');
2713
3171
  }
@@ -2735,14 +3193,14 @@ function prepareBindingDialog(bindObj) {
2735
3193
  return 'Select source device';
2736
3194
  }
2737
3195
  if (device.hasOwnProperty('info')) {
2738
- if (device.info.device._type === 'Coordinator') {
3196
+ if (device.info.device && device.info.device.type === 'Coordinator') {
2739
3197
  return null;
2740
3198
  }
2741
3199
  // check for output clusters
2742
3200
  let allow = false;
2743
3201
  for (const cluster of allowClusters) {
2744
3202
  if (device.info.endpoints) for (const ep of device.info.endpoints) {
2745
- if (ep.outputClusters.includes(cluster)) {
3203
+ if (ep.output_clusters.includes(cluster)) {
2746
3204
  allow = true;
2747
3205
  break;
2748
3206
  }
@@ -2786,14 +3244,14 @@ function prepareBindingDialog(bindObj) {
2786
3244
  return 'Select target device';
2787
3245
  }
2788
3246
  if (device.hasOwnProperty('info')) {
2789
- if (device.info.device._type === 'Coordinator') {
3247
+ if (device.info.device && device.info.device.type === 'Coordinator') {
2790
3248
  return null;
2791
3249
  }
2792
3250
  // check for input clusters
2793
3251
  let allow = false;
2794
3252
  for (const cluster of allowClusters) {
2795
3253
  if (device.info.endpoints) for (const ep of device.info.endpoints) {
2796
- if (ep.inputClusters.includes(cluster)) {
3254
+ if (ep.input_clusters.includes(cluster)) {
2797
3255
  allow = true;
2798
3256
  break;
2799
3257
  }
@@ -2837,7 +3295,7 @@ function prepareBindingDialog(bindObj) {
2837
3295
 
2838
3296
  const epList = device ? device.info.endpoints : [];
2839
3297
  const sClusterList = epList.map((ep) => {
2840
- const clusters = ep.outputClusters.map((cl) => {
3298
+ const clusters = ep.output_clusters.map((cl) => {
2841
3299
  return allowClusters.includes(cl) ? {ID: ep.ID + '_' + cl, name: allowClustersName[cl]} : null;
2842
3300
  }).filter((i) => {
2843
3301
  return i != null;
@@ -2863,7 +3321,7 @@ function prepareBindingDialog(bindObj) {
2863
3321
 
2864
3322
  const epList = device ? device.info.endpoints : [];
2865
3323
  const tClusterList = epList.map((ep) => {
2866
- const clusters = ep.inputClusters.map((cl) => {
3324
+ const clusters = ep.input_clusters.map((cl) => {
2867
3325
  return (allowClusters.includes(cl) && (!sourceCl || sourceCl == cl)) ? {
2868
3326
  ID: ep.ID + '_' + cl,
2869
3327
  name: allowClustersName[cl]
@@ -2936,7 +3394,7 @@ function addBindingDialog() {
2936
3394
  }
2937
3395
 
2938
3396
  function addBinding(bind_source, bind_source_ep, bind_target, bind_target_ep, unbind_from_coordinator) {
2939
- sendTo(namespace, 'addBinding', {
3397
+ sendToWrapper(namespace, 'addBinding', {
2940
3398
  bind_source: bind_source,
2941
3399
  bind_source_ep: bind_source_ep,
2942
3400
  bind_target: bind_target,
@@ -2953,7 +3411,7 @@ function addBinding(bind_source, bind_source_ep, bind_target, bind_target_ep, un
2953
3411
  }
2954
3412
 
2955
3413
  function editBinding(bind_id, bind_source, bind_source_ep, bind_target, bind_target_ep, unbind_from_coordinator) {
2956
- sendTo(namespace, 'editBinding', {
3414
+ sendToWrapper(namespace, 'editBinding', {
2957
3415
  id: bind_id,
2958
3416
  bind_source: bind_source,
2959
3417
  bind_source_ep: bind_source_ep,
@@ -3046,7 +3504,7 @@ function showBinding() {
3046
3504
  }
3047
3505
 
3048
3506
  function getBinding() {
3049
- sendTo(namespace, 'getBinding', {}, function (msg) {
3507
+ sendToWrapper(namespace, 'getBinding', {}, function (msg) {
3050
3508
  if (msg) {
3051
3509
  if (msg.error) {
3052
3510
  showMessage(msg.error, _('Error'));
@@ -3071,7 +3529,7 @@ function deleteBindingConfirmation(id) {
3071
3529
  }
3072
3530
 
3073
3531
  function deleteBinding(id) {
3074
- sendTo(namespace, 'delBinding', id, (msg) => {
3532
+ sendToWrapper(namespace, 'delBinding', id, (msg) => {
3075
3533
  closeWaitingDialog();
3076
3534
  if (msg) {
3077
3535
  if (msg.error) {
@@ -3093,15 +3551,15 @@ function findClName(id) {
3093
3551
  }
3094
3552
 
3095
3553
  function genDevInfo(device) {
3096
- //console.log(device);
3097
3554
  const dev = (device && device.info) ? device.info.device : undefined;
3098
3555
  const mapped = (device && device.info) ? device.info.mapped : undefined;
3556
+ const endpoints = (device && device.info) ? device.info.endpoints : [];
3099
3557
  if (!dev) return `<div class="truncate">No info</div>`;
3100
3558
  const genRow = function (name, value, refresh) {
3101
3559
  if (value === undefined) {
3102
3560
  return '';
3103
3561
  } else {
3104
- return `<li><span class="label">${name}:</span><span>${value}</span></li>`;
3562
+ return `<li><span class="label">${name.replace('_',' ')}:</span><span>${value}</span></li>`;
3105
3563
  }
3106
3564
  };
3107
3565
  const genRowValues = function (name, value) {
@@ -3109,72 +3567,67 @@ function genDevInfo(device) {
3109
3567
  return '';
3110
3568
  } else {
3111
3569
  let label = `${name}:`;
3112
- return value.map((val) => {
3113
- const row = `<li><span class="label">${label}</span><span>${val}</span></li>`;
3114
- label = '';
3115
- return row;
3116
- }).join('');
3570
+ try {
3571
+ return value.map((val) => {
3572
+ const row = `<li><span class="label">${label}</span><span>${val}</span></li>`;
3573
+ label = '';
3574
+ return row;
3575
+ }).join('');
3576
+ }
3577
+ catch {
3578
+ return `<li><span class="label">${label}</span><span>${JSON.stringify(value)}</span></li>`
3579
+ }
3117
3580
  }
3118
3581
  };
3119
3582
  const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(mapped.model)}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
3120
- const mappedInfo = (!mapped) ? '' :
3121
- `<div style="font-size: 0.9em">
3122
- <ul>
3123
- ${genRow('model', modelUrl)}
3124
- ${genRow('description', mapped.description)}
3125
- ${genRow('supports', mapped.supports)}
3126
- </ul>
3127
- </div>`;
3583
+ const mappedInfo = [];
3584
+ if (mapped) {
3585
+ mappedInfo.push(
3586
+ `<div style="font-size: 0.9em">
3587
+ <ul>`);
3588
+ for (const item in mapped) {
3589
+ if (item == 'model')
3590
+ mappedInfo.push(genRow(item,modelUrl));
3591
+ else
3592
+ mappedInfo.push(genRow(item,mapped[item]));
3593
+ }
3594
+ mappedInfo.push(
3595
+ ` </ul>
3596
+ </div>`);
3597
+ }
3128
3598
  let epInfo = '';
3129
- for (const epind in dev._endpoints) {
3130
- const ep = dev._endpoints[epind];
3599
+ for (const epind in endpoints) {
3600
+ const ep = endpoints[epind];
3131
3601
  epInfo +=
3132
3602
  `<div style="font-size: 0.9em" class="truncate">
3133
3603
  <ul>
3134
3604
  ${genRow('endpoint', ep.ID)}
3135
- ${genRow('profile', ep.profileID)}
3136
- ${genRowValues('input clusters', ep.inputClusters.map(findClName))}
3137
- ${genRowValues('output clusters', ep.outputClusters.map(findClName))}
3605
+ ${genRow('profile', ep.profile)}
3606
+ ${genRowValues('input clusters', ep.input_clusters ? ep.input_clusters.map(findClName) : 'none')}
3607
+ ${genRowValues('output clusters', ep.output_clusters ? ep.output_clusters.map(findClName): 'none')}
3138
3608
  </ul>
3139
3609
  </div>`;
3140
3610
  }
3141
3611
  const imgSrc = device.icon || device.common.icon;
3142
3612
  const imgInfo = (imgSrc) ? `<img src=${imgSrc} width='150px' onerror="this.onerror=null;this.src='img/unavailable.png';"><div class="divider"></div>` : '';
3143
- const info =
3613
+ const info =[
3144
3614
  `<div class="col s12 m6 l6 xl6">
3145
3615
  ${imgInfo}
3146
- ${mappedInfo}
3616
+ ${mappedInfo.join('')}
3147
3617
  <div class="divider"></div>
3148
3618
  <div style="font-size: 0.9em" class="truncate">
3149
- <ul>
3150
- ${genRow('modelZigbee', dev._modelID)}
3151
- ${genRow('type', dev._type)}
3152
- ${genRow('ieee', dev.ieeeAddr)}
3153
- ${genRow('nwk', dev._networkAddress)}
3154
- ${genRow('manuf id', dev._manufacturerID)}
3155
- ${genRow('manufacturer', dev._manufacturerName)}
3156
- ${genRow('power', dev._powerSource)}
3157
- ${genRow('app version', dev._applicationVersion)}
3158
- ${genRow('hard version', dev._hardwareVersion)}
3159
- ${genRow('zcl version', dev._zclVersion)}
3160
- ${genRow('stack version', dev._stackVersion)}
3161
- ${genRow('date code', dev._dateCode)}
3162
- ${genRow('build', dev._softwareBuildID)}
3163
- ${genRow('interviewed', dev._interviewCompleted)}
3164
- ${genRow('configured', (device.isConfigured), true)}
3619
+ <ul>`];
3620
+ for (const item in dev) {
3621
+ info.push(genRow(item, dev[item]));
3622
+ }
3623
+ info.push(` ${genRow('configured', (device.isConfigured), true)}
3165
3624
  </ul>
3166
3625
  </div>
3167
3626
  </div>
3168
3627
  <div class="col s12 m6 l6 xl6">
3169
3628
  ${epInfo}
3170
- </div>`;
3171
- return info;
3172
- }
3173
-
3174
- function showDevInfo(id) {
3175
- const info = genDevInfo(getDeviceByID(id));
3176
- $('#devinfo').html(info);
3177
- $('#modaldevinfo').modal('open');
3629
+ </div>`);
3630
+ return info.join('');
3178
3631
  }
3179
3632
 
3180
3633
  let waitingTimeout, waitingInt;
@@ -3204,7 +3657,7 @@ function closeWaitingDialog() {
3204
3657
 
3205
3658
 
3206
3659
  function showChannels() {
3207
- sendTo(namespace, 'getChannels', {}, function (msg) {
3660
+ sendToWrapper(namespace, 'getChannels', {}, function (msg) {
3208
3661
  closeWaitingDialog();
3209
3662
  if (msg) {
3210
3663
  if (msg.error) {
@@ -3276,7 +3729,7 @@ function prepareExcludeDialog(excludeObj) {
3276
3729
  return 'Select model';
3277
3730
  }
3278
3731
  if (device.hasOwnProperty('info')) {
3279
- if (device.info.device._type == 'Coordinator') {
3732
+ if (device.info.device && device.info.device.type == 'Coordinator') {
3280
3733
  return null;
3281
3734
  }
3282
3735
  return device.common.type;
@@ -3323,21 +3776,20 @@ function addExcludeDialog() {
3323
3776
 
3324
3777
  function addExclude(exclude_model) {
3325
3778
  if (typeof exclude_model == 'object' && exclude_model.hasOwnProperty('common'))
3326
- sendTo(namespace, 'addExclude', { exclude_model: exclude_model }, function (msg) {
3779
+ sendToWrapper(namespace, 'addExclude', { exclude_model: exclude_model }, function (msg) {
3327
3780
  closeWaitingDialog();
3328
3781
  if (msg) {
3329
3782
  if (msg.error) {
3330
3783
  showMessage(msg.error, _('Error'));
3331
3784
  }
3332
3785
  }
3333
- console.log('getting excludes ?');
3334
3786
  getExclude();
3335
3787
  });
3336
3788
  else closeWaitingDialog();
3337
3789
  }
3338
3790
 
3339
3791
  function getExclude() {
3340
- sendTo(namespace, 'getExclude', {}, function (msg) {
3792
+ sendToWrapper(namespace, 'getExclude', {}, function (msg) {
3341
3793
  if (msg) {
3342
3794
  if (msg.error) {
3343
3795
  showMessage(msg.error, _('Error'));
@@ -3408,14 +3860,13 @@ function deleteExcludeConfirmation(id) {
3408
3860
  }
3409
3861
 
3410
3862
  function deleteExclude(id) {
3411
- sendTo(namespace, 'delExclude', id, (msg) => {
3863
+ sendToWrapper(namespace, 'delExclude', id, (msg) => {
3412
3864
  closeWaitingDialog();
3413
3865
  if (msg) {
3414
3866
  if (msg.error) {
3415
3867
  showMessage(msg.error, _('Error'));
3416
3868
  }
3417
3869
  }
3418
- console.log('getting excludes ?');
3419
3870
  getExclude();
3420
3871
  });
3421
3872
  }
@@ -3498,161 +3949,9 @@ function sortByTitle(element) {
3498
3949
  return element.querySelector('.card-title').textContent.toLowerCase().trim();
3499
3950
  }
3500
3951
 
3501
- function getDashCard(dev, groupImage, groupstatus) {
3502
- const title = dev.common.name,
3503
- id = dev._id,
3504
- type = dev.common.type,
3505
- img_src = (groupImage ? groupImage : dev.common.icon || dev.icon),
3506
- isActive = !dev.common.deactivated,
3507
- rooms = [],
3508
- lang = systemLang || 'en';
3509
- const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
3510
- const rid = id.split('.').join('_');
3511
- const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
3512
- const image = `<img src="${img_src}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
3513
- nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
3514
- battery_cls = getBatteryCls(dev.battery),
3515
- lqi_cls = getLQICls(dev.link_quality),
3516
- 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>'),
3517
- 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>` : '',
3518
- 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 : ''),
3519
- //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>`),
3520
- //permitJoinBtn = (isActive && dev.info && dev.info.device._type === 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
3521
- //infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
3522
- 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>` : '';
3523
- const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => {
3524
- const id = stateDef.id;
3525
- const sid = id.split('.').join('_');
3526
- let val = stateDef.val || '';
3527
- if (stateDef.role === 'switch' && stateDef.write) {
3528
- val = `<span class="switch"><label><input type="checkbox" ${(val) ? 'checked' : ''}><span class="lever"></span></label></span>`;
3529
- } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
3530
- val = `<span class="range-field dash"><input type="range" min="0" max="100" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
3531
- } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
3532
- val = `<span class="range-field dash"><input type="range" min="150" max="500" ${(val != undefined) ? `value="${val}"` : ''} /></span>`;
3533
- } else if (stateDef.type === 'boolean') {
3534
- const disabled = (stateDef.write) ? '' : 'disabled="disabled"';
3535
- val = `<label class="dash"><input type="checkbox" ${(val == true) ? 'checked=\'checked\'' : ''} ${disabled}/><span></span></label>`;
3536
- } else if (stateDef.role === 'level.color.rgb') {
3537
- const options = []
3538
- for (const key of namedColors) {
3539
- options.push(`<option value="${key}" ${val===key ? 'selected' : ''}>${key}</option>`);
3540
- }
3541
- val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
3542
- } else if (stateDef.states && stateDef.write) {
3543
- let options;
3544
- if (typeof stateDef.states == 'string') {
3545
- const sts = stateDef.states.split(';');
3546
- if (sts.length < 2) return '';
3547
- options = sts.map((item) => {
3548
- const v = item.split(':');
3549
- return `<option value="${v[0]}" ${(val == v[0]) ? 'selected' : ''}>${v[1]}</option>`;
3550
- });
3551
- } else {
3552
- options = [];
3553
- for (const [key, value] of Object.entries(stateDef.states)) {
3554
- options.push(`<option value="${key}" ${(val == key) ? 'selected' : ''}>${key}</option>`);
3555
- }
3556
- }
3557
- if (options.length < 2) return '';
3558
- val = `<select class="browser-default enum" style="color : white; background-color: grey; height: 16px; padding: 0; width: auto; display: inline-block">${options.join('')}</select>`;
3559
- } else if (stateDef.write) {
3560
- return;
3561
- // val = `<span class="input-field dash value"><input class="dash value" id="${stateDef.name}" value="${val}"></input></span>`;
3562
- }
3563
- else {
3564
- val = `<span class="dash value">${val ? val : '(null)'} ${(stateDef.unit) ? stateDef.unit : ''}</span>`;
3565
- }
3566
- return `<li><span class="label dash truncate">${stateDef.name}</span><span id=${sid} oid=${id} class="state">${val}</span></li>`;
3567
- }).join('') : '';
3568
- const dashCard = `
3569
- <div class="card-content zcard ${isActive ? '' : 'bg_red'}">
3570
- <div class="flip" style="cursor: pointer">
3571
- <span class="top right small" style="border-radius: 50%">
3572
- ${idleTime}
3573
- ${battery}
3574
- ${lq}
3575
- </span>
3576
- <span class="card-title truncate">${title}</span>
3577
- </div>
3578
- <i class="left">${image}</i>
3579
- <div style="min-height:88px; font-size: 0.8em; height: 130px; overflow-y: auto" class="truncate">
3580
- <ul>
3581
- ${(isActive ? info : 'Device deactivated')}
3582
- </ul>
3583
- </div>
3584
- <div class="footer right-align"></div>
3585
- </div>`;
3586
-
3587
- return dashCard;
3588
- }
3589
-
3590
- function setDashStates(id, state) {
3591
- const devId = getDevId(id);
3592
- const dev = getDeviceByID(devId);
3593
- if (dev) {
3594
- const stateDef = dev.statesDef.find((stateDef) => stateDef.id == id);
3595
- if (stateDef) {
3596
- const sid = id.split('.').join('_');
3597
- if (stateDef.role === 'switch' && stateDef.write) {
3598
- $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
3599
- } else if (stateDef.role === 'level.dimmer' && stateDef.write) {
3600
- $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
3601
- } else if (stateDef.role === 'level.color.temperature' && stateDef.write) {
3602
- $(`#${sid}`).find('input[type=\'range\']').prop('value', state.val);
3603
- } else if (stateDef.states && stateDef.write) {
3604
- $(`#${sid}`).find(`select option[value=${state.val}]`).prop('selected', true);
3605
- } else if (stateDef.type === 'boolean') {
3606
- $(`#${sid}`).find('input[type=\'checkbox\']').prop('checked', state.val);
3607
- } else {
3608
- $(`#${sid}`).find('.value').text(`${state.val} ${(stateDef.unit) ? stateDef.unit : ''}`);
3609
- }
3610
- }
3611
- }
3612
- }
3613
-
3614
- function hookControls() {
3615
- $('input[type=\'checkbox\']').change(function (event) {
3616
- const val = $(this).is(':checked');
3617
- const id = $(this).parents('.state').attr('oid');
3618
- sendTo(namespace, 'setState', {id: id, val: val}, function (data) {
3619
- //console.log(data);
3620
- });
3621
- });
3622
- $('input[type=\'range\']').change(function (event) {
3623
- const val = $(this).val();
3624
- const id = $(this).parents('.state').attr('oid');
3625
- sendTo(namespace, 'setState', {id: id, val: val}, function (data) {
3626
- //console.log(data);
3627
- });
3628
- });
3629
- $('.state select').on('change', function () {
3630
- const val = $(this).val();
3631
- const id = $(this).parents('.state').attr('oid');
3632
- sendTo(namespace, 'setState', {id: id, val: val}, function (data) {
3633
- //console.log(data);
3634
- });
3635
- });
3636
- }
3637
-
3638
- function getIdleTime(value) {
3639
- return (value) ? moment(new Date(value)).fromNow(true) : '';
3640
- }
3641
-
3642
- function updateCardTimer() {
3643
- if (devices) {
3644
- devices.forEach((dev) => {
3645
- const id = dev._id;
3646
- if (id) {
3647
- const rid = id.split('.').join('_');
3648
- $(`#${rid}_link_quality_lc`).text(getIdleTime(dev.link_quality_lc));
3649
- }
3650
- });
3651
- }
3652
- }
3653
3952
 
3654
3953
  function updateDevice(id) {
3655
- sendTo(namespace, 'getDevice', {id: id}, function (msg) {
3954
+ sendToWrapper(namespace, 'getDevice', {id: id}, function (msg) {
3656
3955
  if (msg) {
3657
3956
  const devs = msg.devices;
3658
3957
  if (devs) {
@@ -3682,13 +3981,13 @@ function swapActive(id) {
3682
3981
  const dev = getDeviceByID(id);
3683
3982
  if (dev && dev.common) {
3684
3983
  dev.common.deactivated = !(dev.common.deactivated);
3685
- sendTo(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
3984
+ sendToWrapper(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
3686
3985
  showDevices();
3687
3986
  });
3688
3987
  }
3689
3988
  }
3690
3989
 
3691
- function reconfigureDlg(id) {
3990
+ function reconfigureConfirmation(id) {
3692
3991
  const text = translateWord(`Do you really want to reconfigure device?`);
3693
3992
  $('#modalreconfigure').find('p').text(text);
3694
3993
  $('#modalreconfigure a.btn[name=\'yes\']').unbind('click');
@@ -3700,7 +3999,7 @@ function reconfigureDlg(id) {
3700
3999
  }
3701
4000
 
3702
4001
  function reconfigureDevice(id) {
3703
- sendTo(namespace, 'reconfigure', {id: id}, function (msg) {
4002
+ sendToWrapper(namespace, 'reconfigure', {id: id}, function (msg) {
3704
4003
  closeWaitingDialog();
3705
4004
  if (msg) {
3706
4005
  if (msg.error) {
@@ -3708,7 +4007,7 @@ function reconfigureDevice(id) {
3708
4007
  }
3709
4008
  }
3710
4009
  });
3711
- showWaitingDialog('Device is being reconfigure', 30);
4010
+ showWaitingDialog('Device is being reconfigured', 30);
3712
4011
  }
3713
4012
 
3714
4013
  const warnLevel = {
@@ -3721,34 +4020,28 @@ function validateConfigData(key, val) {
3721
4020
  if (validatableKeys.indexOf(key) < 0 || !val) return;
3722
4021
  if (warnLevel[key]) {
3723
4022
  if (warnLevel[key](val)) {
3724
- //console.warn(`warning set for ${key} (${val})`)
3725
4023
  $(`#${key}_ALERT`).removeClass('hide')
3726
4024
  } else $(`#${key}_ALERT`).addClass('hide')
3727
4025
  }
3728
4026
  if (nvRamBackup[key]) {
3729
- //console.warn(`value of ${key} is ${val} (${nvRamBackup[key]})`);
3730
4027
  if ((typeof val == 'string' && typeof nvRamBackup[key] == 'string' && val.toLowerCase == nvRamBackup[key].toLowerCase) || val == nvRamBackup[key])
3731
4028
  {
3732
- //console.warn(`ok set for ${key} (${val})`)
3733
4029
  $(`#${key}_OK`).removeClass('hide')
3734
4030
  $(`#${key}_NOK`).addClass('hide')
3735
4031
  }
3736
4032
  else
3737
4033
  {
3738
- //console.warn(`nok set for ${key} (${val})`)
3739
4034
  $(`#${key}_OK`).addClass('hide')
3740
4035
  $(`#${key}_NOK`).removeClass('hide')
3741
4036
  }
3742
4037
  }
3743
4038
  else {
3744
- //console.warn(`noval set for ${key} (${val})`)
3745
4039
  $(`#${key}_OK`).addClass('hide')
3746
4040
  $(`#${key}_NOK`).addClass('hide')
3747
4041
  }
3748
4042
  }
3749
4043
 
3750
4044
  function validateNVRamBackup(update, src) {
3751
- //console.warn('validateNVRam');
3752
4045
  const validatedKeys = src ? [src] : validatableKeys;
3753
4046
  const validator = {};
3754
4047
  for (const key of validatedKeys) {
@@ -3766,9 +4059,7 @@ function validateNVRamBackup(update, src) {
3766
4059
 
3767
4060
 
3768
4061
  function readNVRamBackup(update) {
3769
- console.warn('read nvRam')
3770
- sendTo(namespace, 'readNVRam', {}, function(msg) {
3771
- console.warn(JSON.stringify(msg));
4062
+ sendToWrapper(namespace, 'readNVRam', {}, function(msg) {
3772
4063
  if (msg) {
3773
4064
  if (msg.error && update) {
3774
4065
  if (msg.error.includes('ENOENT')) showMessage('Unable to read nvRam backup - no backup available.',_('Error'))