iobroker.zigbee 1.6.3 → 1.6.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -14
- package/admin/adapter-settings.js +39 -3
- package/admin/admin.js +273 -47
- package/admin/img/14153905L.png +0 -0
- package/admin/img/81855.png +0 -0
- package/admin/img/HG06338.png +0 -0
- package/admin/img/R7060.png +0 -0
- package/admin/img/TI0001-cover.png +0 -0
- package/admin/img/WHD02.png +0 -0
- package/admin/img/ikea_E1812.png +0 -0
- package/admin/img/tuya_rb280.png +0 -0
- package/admin/index_m.html +170 -24
- package/admin/tab_m.html +181 -216
- package/admin/words.js +30 -29
- package/docs/tutorial/zigbee.png +0 -0
- package/io-package.json +28 -1
- package/lib/commands.js +75 -9
- package/lib/developer.js +6 -2
- package/lib/devices.js +46 -1
- package/lib/exposes.js +3 -1
- package/lib/groups.js +54 -28
- package/lib/ota.js +26 -0
- package/lib/rgb.js +30 -0
- package/lib/states.js +40 -14
- package/lib/statescontroller.js +207 -92
- package/lib/utils.js +1 -1
- package/lib/zbBaseExtension.js +4 -3
- package/lib/zbDelayedAction.js +1 -0
- package/lib/zbDeviceAvailability.js +14 -2
- package/lib/zbDeviceConfigure.js +11 -7
- package/lib/zbDeviceEvent.js +6 -0
- package/lib/zigbeecontroller.js +119 -17
- package/main.js +12 -4
- package/package.json +3 -3
package/admin/admin.js
CHANGED
|
@@ -129,6 +129,7 @@ function getGroupCard(dev) {
|
|
|
129
129
|
title = dev.common.name,
|
|
130
130
|
lq = '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>',
|
|
131
131
|
rooms = [],
|
|
132
|
+
numid = parseInt(id.replace(namespace+'.group_', '')),
|
|
132
133
|
lang = systemLang || 'en';
|
|
133
134
|
for (const r in dev.rooms) {
|
|
134
135
|
if (dev.rooms[r].hasOwnProperty(lang)) {
|
|
@@ -137,16 +138,17 @@ function getGroupCard(dev) {
|
|
|
137
138
|
rooms.push(dev.rooms[r]);
|
|
138
139
|
}
|
|
139
140
|
}
|
|
141
|
+
devGroups[numid] = dev;
|
|
140
142
|
const room = rooms.join(',') || ' ';
|
|
141
143
|
let memberCount = 0;
|
|
142
144
|
let info = `<div style="min-height:88px; font-size: 0.8em; height: 98px; overflow-y: auto" class="truncate">
|
|
143
145
|
<ul>`;
|
|
144
|
-
info = info.concat(`<li><span class="labelinfo">Group ${
|
|
146
|
+
info = info.concat(`<li><span class="labelinfo">Group ${numid}</span></li>`);
|
|
145
147
|
if (dev.memberinfo === undefined) {
|
|
146
148
|
info = info.concat(`<li><span class="labelinfo">No devices in group</span></li>`);
|
|
147
149
|
} else {
|
|
148
150
|
for (let m=0;m < dev.memberinfo.length; m++) {
|
|
149
|
-
info = info.concat(`<li><span
|
|
151
|
+
info = info.concat(`<li><span align:"left">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid}</span><span align:"right"> ...${dev.memberinfo[m].ieee.slice(-4)}</span></li>`);
|
|
150
152
|
}
|
|
151
153
|
memberCount = (dev.memberinfo.length<8?dev.memberinfo.length:7);
|
|
152
154
|
};
|
|
@@ -154,7 +156,7 @@ function getGroupCard(dev) {
|
|
|
154
156
|
</div>`);
|
|
155
157
|
const image = `<img src="img/group_${memberCount}.png" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
|
|
156
158
|
const dashCard = getDashCard(dev,`img/group_${memberCount}.png` );
|
|
157
|
-
const card = `<div id="${id}" class="device">
|
|
159
|
+
const card = `<div id="${id}" class="device group">
|
|
158
160
|
<div class="card hoverable flipable">
|
|
159
161
|
<div class="front face">${dashCard}</div>
|
|
160
162
|
<div class="back face">
|
|
@@ -187,14 +189,19 @@ function getGroupCard(dev) {
|
|
|
187
189
|
return card;
|
|
188
190
|
}
|
|
189
191
|
|
|
190
|
-
|
|
192
|
+
function sanitizeModelParameter(parameter) {
|
|
193
|
+
const replaceByUnderscore = /[\s/]/g;
|
|
194
|
+
return parameter.replace(replaceByUnderscore, '_');
|
|
195
|
+
}
|
|
191
196
|
|
|
192
197
|
function getCard(dev) {
|
|
193
198
|
const title = dev.common.name,
|
|
194
199
|
id = dev._id,
|
|
195
|
-
type = (dev.common.type ? dev.common.type
|
|
200
|
+
type = (dev.common.type ? dev.common.type : 'unknown'),
|
|
201
|
+
type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'),
|
|
196
202
|
img_src = dev.icon || dev.common.icon,
|
|
197
203
|
rooms = [],
|
|
204
|
+
isActive = (dev.common.deactivated ? false : true),
|
|
198
205
|
lang = systemLang || 'en';
|
|
199
206
|
for (const r in dev.rooms) {
|
|
200
207
|
if (dev.rooms[r].hasOwnProperty(lang)) {
|
|
@@ -206,14 +213,14 @@ function getCard(dev) {
|
|
|
206
213
|
const room = rooms.join(',') || ' ';
|
|
207
214
|
const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
|
|
208
215
|
const rid = id.split('.').join('_');
|
|
209
|
-
const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${
|
|
216
|
+
const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
|
|
210
217
|
const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
|
|
211
218
|
nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
|
|
212
|
-
battery_cls = getBatteryCls(dev.battery),
|
|
219
|
+
battery_cls = (isActive ? getBatteryCls(dev.battery):''),
|
|
213
220
|
lqi_cls = getLQICls(dev.link_quality),
|
|
214
|
-
battery = (dev.battery) ? `<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>` : '',
|
|
215
|
-
lq = (dev.link_quality
|
|
216
|
-
status = (dev.link_quality
|
|
221
|
+
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>` : '',
|
|
222
|
+
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>` : '',
|
|
223
|
+
status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (isActive ? `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`:''),
|
|
217
224
|
info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
|
|
218
225
|
<ul>
|
|
219
226
|
<li><span class="labelinfo">ieee:</span><span>0x${id.replace(namespace+'.', '')}</span></li>
|
|
@@ -223,10 +230,11 @@ function getCard(dev) {
|
|
|
223
230
|
</ul>
|
|
224
231
|
</div>`,
|
|
225
232
|
permitJoinBtn = (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>' : '',
|
|
233
|
+
deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive?'Deactivate':'Activate')}"><i class="material-icons icon-${(isActive?'red':'green')}">power_settings_new</i></button>`,
|
|
226
234
|
infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
|
|
227
235
|
const dashCard = getDashCard(dev);
|
|
228
236
|
const card = `<div id="${id}" class="device">
|
|
229
|
-
<div class="card hoverable flipable">
|
|
237
|
+
<div class="card hoverable flipable ${isActive?'':'bg_red'}">
|
|
230
238
|
<div class="front face">${dashCard}</div>
|
|
231
239
|
<div class="back face">
|
|
232
240
|
<div class="card-content zcard">
|
|
@@ -254,6 +262,10 @@ function getCard(dev) {
|
|
|
254
262
|
<button name="edit" class="right btn-flat btn-small">
|
|
255
263
|
<i class="material-icons icon-green">edit</i>
|
|
256
264
|
</button>
|
|
265
|
+
<button name="reconfigure" class="right btn-flat btn-small tooltipped" title="Reconfigure">
|
|
266
|
+
<i class="material-icons icon-red">sync</i>
|
|
267
|
+
</button>
|
|
268
|
+
${deactBtn}
|
|
257
269
|
${permitJoinBtn}
|
|
258
270
|
</div>
|
|
259
271
|
</div>
|
|
@@ -344,25 +356,96 @@ function cleanConfirmation() {
|
|
|
344
356
|
Materialize.updateTextFields();
|
|
345
357
|
}
|
|
346
358
|
|
|
359
|
+
function EndPointIDfromEndPoint(ep)
|
|
360
|
+
{
|
|
361
|
+
if (ep && ep.deviceIeeeAddress && ep.ID)
|
|
362
|
+
return `${ep.deviceIeeeAddress}:${ep.ID}`;
|
|
363
|
+
return 'unidentified';
|
|
364
|
+
}
|
|
365
|
+
|
|
347
366
|
function editName(id, name) {
|
|
367
|
+
console.log('editName called with '+name);
|
|
348
368
|
const dev = devices.find((d) => d._id == id);
|
|
349
369
|
$('#modaledit').find("input[id='d_name']").val(name);
|
|
350
|
-
if (dev.info && dev.info.device._type == 'Router') {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
370
|
+
// if (dev.info && dev.info.device._type == 'Router') {
|
|
371
|
+
const groupables = [];
|
|
372
|
+
if (dev && dev.info && dev.info.endpoints) {
|
|
373
|
+
for (const ep of dev.info.endpoints) {
|
|
374
|
+
if (ep.inputClusters.includes(4)) {
|
|
375
|
+
groupables.push({ epid:EndPointIDfromEndPoint(ep), ep:ep, memberOf:[]});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const numEP = groupables.length;
|
|
380
|
+
// console.log('groupables: '+JSON.stringify(groupables));
|
|
381
|
+
$('#modaledit').find('.row.epid0').addClass('hide');
|
|
382
|
+
$('#modaledit').find('.row.epid1').addClass('hide');
|
|
383
|
+
$('#modaledit').find('.row.epid2').addClass('hide');
|
|
384
|
+
$('#modaledit').find('.row.epid3').addClass('hide');
|
|
385
|
+
if (numEP > 0) {
|
|
386
|
+
// go through all the groups. Find the ones to list for each groupable
|
|
387
|
+
if (numEP == 1) {
|
|
388
|
+
$('#modaledit').find('.endpointid').addClass('hide');
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
$('#modaledit').find('.endpointid').removeClass('hide');
|
|
392
|
+
}
|
|
393
|
+
for (const d of devices) {
|
|
394
|
+
if (d && d.common && d.common.type == 'group') {
|
|
395
|
+
if (d.hasOwnProperty("memberinfo")) {
|
|
396
|
+
for (const member of d.memberinfo) {
|
|
397
|
+
const epid = EndPointIDfromEndPoint(member.ep)
|
|
398
|
+
for (var i=0;i<groupables.length;i++) {
|
|
399
|
+
if (groupables[i].epid == epid) {
|
|
400
|
+
groupables[i].memberOf.push(d.native.id.replace('group_', ''));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
console.log("groupables: " + JSON.stringify(groupables));
|
|
408
|
+
for (var i = 0;i<groupables.length;i++)
|
|
409
|
+
{
|
|
410
|
+
if (i > 1) {
|
|
411
|
+
$('#modaledit').find("translate.device_with_endpoint").innerHtml = name + ' ' + groupables[i].epid;
|
|
412
|
+
}
|
|
413
|
+
$('#modaledit').find('.row.epid'+i).removeClass('hide');
|
|
414
|
+
list2select('#d_groups_ep'+i, groups, groupables[i].memberOf || []);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// } else {
|
|
418
|
+
// $('#modaledit').find('.input-field.endpoints').addClass('hide');
|
|
419
|
+
// $('#modaledit').find('.input-field.groups').addClass('hide');
|
|
420
|
+
// }
|
|
356
421
|
$("#modaledit a.btn[name='save']").unbind('click');
|
|
357
422
|
$("#modaledit a.btn[name='save']").click(() => {
|
|
358
|
-
const newName = $('#modaledit').find("input[id='d_name']").val()
|
|
359
|
-
|
|
360
|
-
|
|
423
|
+
const newName = $('#modaledit').find("input[id='d_name']").val();
|
|
424
|
+
const groupsbyid = {};
|
|
425
|
+
if (groupables.length > 0) {
|
|
426
|
+
for (var i = 0;i<groupables.length;i++) {
|
|
427
|
+
const ng = $('#d_groups_ep'+i).val();
|
|
428
|
+
if (ng.toString() != groupables[i].memberOf.toString())
|
|
429
|
+
groupsbyid[groupables[i].ep.ID] = GenerateGroupChange(groupables[i].memberOf, ng);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
console.log('grpid ' + JSON.stringify(groupsbyid));
|
|
433
|
+
updateDev(id, newName, groupsbyid);
|
|
361
434
|
});
|
|
362
435
|
$('#modaledit').modal('open');
|
|
363
436
|
Materialize.updateTextFields();
|
|
364
437
|
}
|
|
365
438
|
|
|
439
|
+
function GenerateGroupChange(oldmembers, newmembers)
|
|
440
|
+
{
|
|
441
|
+
let grpchng = [];
|
|
442
|
+
for (const oldg of oldmembers)
|
|
443
|
+
if (!newmembers.includes(oldg)) grpchng.push('-'+oldg);
|
|
444
|
+
for (const newg of newmembers)
|
|
445
|
+
if (!oldmembers.includes(newg)) grpchng.push(newg)
|
|
446
|
+
return grpchng;
|
|
447
|
+
}
|
|
448
|
+
|
|
366
449
|
function deleteDevice(id, force) {
|
|
367
450
|
sendTo(namespace, 'deleteDevice', {id: id, force: force}, function (msg) {
|
|
368
451
|
closeWaitingDialog();
|
|
@@ -434,7 +517,6 @@ function showDevices() {
|
|
|
434
517
|
}
|
|
435
518
|
return 0;
|
|
436
519
|
});
|
|
437
|
-
devGroups = {};
|
|
438
520
|
for (let i=0;i < devices.length; i++) {
|
|
439
521
|
const d = devices[i];
|
|
440
522
|
if (!d.info) {
|
|
@@ -451,7 +533,7 @@ function showDevices() {
|
|
|
451
533
|
} else {
|
|
452
534
|
//if (d.groups && d.info && d.info.device._type == "Router") {
|
|
453
535
|
if (d.groups) {
|
|
454
|
-
devGroups[d._id] = d.groups;
|
|
536
|
+
// devGroups[d._id] = d.groups;
|
|
455
537
|
if (typeof d.groups.map == 'function') {
|
|
456
538
|
d.groupNames = d.groups.map(item=>{
|
|
457
539
|
return groups[item] || '';
|
|
@@ -525,7 +607,7 @@ function showDevices() {
|
|
|
525
607
|
const dev_block = $(this).parents('div.device'),
|
|
526
608
|
id = dev_block.attr('id').replace(namespace+'.group_', ''),
|
|
527
609
|
name = getDevName(dev_block);
|
|
528
|
-
editGroupName(id, name);
|
|
610
|
+
editGroupName(id, name, false);
|
|
529
611
|
});
|
|
530
612
|
$("button.btn-floating[name='join']").click(function() {
|
|
531
613
|
const dev_block = $(this).parents('div.device');
|
|
@@ -544,6 +626,14 @@ function showDevices() {
|
|
|
544
626
|
$("a.btn-flat[name='close']").click((e) => {
|
|
545
627
|
closeReval(e);
|
|
546
628
|
});
|
|
629
|
+
$(".card-reveal-buttons button[name='reconfigure']").click(function() {
|
|
630
|
+
const dev_block = $(this).parents('div.device');
|
|
631
|
+
reconfigureDlg(getDevId(dev_block));
|
|
632
|
+
});
|
|
633
|
+
$(".card-reveal-buttons button[name='swapactive']").click(function() {
|
|
634
|
+
const dev_block = $(this).parents('div.device');
|
|
635
|
+
swapActive(getDevId(dev_block));
|
|
636
|
+
});
|
|
547
637
|
|
|
548
638
|
showNetworkMap(devices, map);
|
|
549
639
|
translateAll();
|
|
@@ -656,7 +746,7 @@ function getDevices() {
|
|
|
656
746
|
}
|
|
657
747
|
|
|
658
748
|
function getDeviceCards() {
|
|
659
|
-
return $('#devices .device');
|
|
749
|
+
return $('#devices .device').not(".group");
|
|
660
750
|
}
|
|
661
751
|
|
|
662
752
|
function getDeviceCard(devId) {
|
|
@@ -758,13 +848,14 @@ function load(settings, onChange) {
|
|
|
758
848
|
});
|
|
759
849
|
|
|
760
850
|
$('#add_group').click(function() {
|
|
851
|
+
// showGroupList(true);
|
|
761
852
|
const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
|
|
762
|
-
editGroupName(maxind+1, '');
|
|
853
|
+
editGroupName(maxind+1, 'Group ' + maxind+1, true);
|
|
763
854
|
});
|
|
764
855
|
$('#add_grp_btn').click(function() {
|
|
856
|
+
// showGroupList(true);
|
|
765
857
|
const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
|
|
766
|
-
editGroupName(maxind+1, '');
|
|
767
|
-
getDevices();
|
|
858
|
+
editGroupName(maxind+1, 'Group ' + maxind+1, true);
|
|
768
859
|
});
|
|
769
860
|
|
|
770
861
|
$(document).ready(function() {
|
|
@@ -1702,7 +1793,7 @@ function showGroups() {
|
|
|
1702
1793
|
$("a.btn-floating[name='groupedit']").click(function() {
|
|
1703
1794
|
const index = $(this).attr('id'),
|
|
1704
1795
|
name = groups[index];
|
|
1705
|
-
editGroupName(index, name);
|
|
1796
|
+
editGroupName(index, name, false);
|
|
1706
1797
|
});
|
|
1707
1798
|
$("a.btn-floating[name='groupdelete']").click(function() {
|
|
1708
1799
|
const index = $(this).attr('id'),
|
|
@@ -1711,8 +1802,35 @@ function showGroups() {
|
|
|
1711
1802
|
});
|
|
1712
1803
|
}
|
|
1713
1804
|
|
|
1714
|
-
function editGroupName(id, name) {
|
|
1805
|
+
function editGroupName(id, name, isnew) {
|
|
1806
|
+
//const dev = devices.find((d) => d._id == id);
|
|
1807
|
+
//console.log('devices: '+ JSON.stringify(devices));
|
|
1808
|
+
const groupables = [];
|
|
1809
|
+
for (const d of devices) {
|
|
1810
|
+
if (d && d.info && d.info.endpoints) {
|
|
1811
|
+
for (const ep of d.info.endpoints) {
|
|
1812
|
+
if (ep.inputClusters.includes(4))
|
|
1813
|
+
{
|
|
1814
|
+
groupables.push(ep);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
//console.log('device ' + JSON.stringify(d));
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1715
1821
|
//var text = 'Enter new name for "'+name+'" ('+id+')?';
|
|
1822
|
+
if (isnew) {
|
|
1823
|
+
$('#groupedit').find('.editgroup').addClass('hide');
|
|
1824
|
+
$('#groupedit').find('.addgroup').removeClass('hide');
|
|
1825
|
+
$('#groupedit').find('.input-field.members').addClass('hide');
|
|
1826
|
+
$('#groupedit').find('.input-field.groupid').removeClass('hide');
|
|
1827
|
+
}
|
|
1828
|
+
else {
|
|
1829
|
+
$('#groupedit').find('.editgroup').removeClass('hide');
|
|
1830
|
+
$('#groupedit').find('.addgroup').addClass('hide');
|
|
1831
|
+
$('#groupedit').find('.input-field.members').removeClass('hide');
|
|
1832
|
+
$('#groupedit').find('.input-field.groupid').addClass('hide');
|
|
1833
|
+
}
|
|
1716
1834
|
$('#groupedit').find("input[id='g_index']").val(id);
|
|
1717
1835
|
$('#groupedit').find("input[id='g_name']").val(name);
|
|
1718
1836
|
$("#groupedit a.btn[name='save']").unbind('click');
|
|
@@ -1721,7 +1839,7 @@ function editGroupName(id, name) {
|
|
|
1721
1839
|
newName = $('#groupedit').find("input[id='g_name']").val();
|
|
1722
1840
|
updateGroup(id, newId, newName);
|
|
1723
1841
|
// showGroups();
|
|
1724
|
-
getDevices();
|
|
1842
|
+
// getDevices();
|
|
1725
1843
|
});
|
|
1726
1844
|
$('#groupedit').modal('open');
|
|
1727
1845
|
Materialize.updateTextFields();
|
|
@@ -1735,7 +1853,7 @@ function deleteGroupConfirmation(id, name) {
|
|
|
1735
1853
|
$("#modaldelete a.btn[name='yes']").click(() => {
|
|
1736
1854
|
deleteGroup(id);
|
|
1737
1855
|
// showGroups();
|
|
1738
|
-
getDevices();
|
|
1856
|
+
// getDevices();
|
|
1739
1857
|
});
|
|
1740
1858
|
$('#modaldelete').modal('open');
|
|
1741
1859
|
}
|
|
@@ -1743,12 +1861,22 @@ function deleteGroupConfirmation(id, name) {
|
|
|
1743
1861
|
function updateGroup(id, newId, newName) {
|
|
1744
1862
|
delete groups[id];
|
|
1745
1863
|
groups[newId] = newName;
|
|
1746
|
-
sendTo(namespace, 'renameGroup', { id: newId, name: newName} )
|
|
1864
|
+
sendTo(namespace, 'renameGroup', { id: newId, name: newName}, function(msg) {
|
|
1865
|
+
if (msg && ms.error) {
|
|
1866
|
+
showMessage(msg.error, _('Error'));
|
|
1867
|
+
}
|
|
1868
|
+
getDevices();
|
|
1869
|
+
});
|
|
1747
1870
|
}
|
|
1748
1871
|
|
|
1749
1872
|
function deleteGroup(id) {
|
|
1750
1873
|
delete groups[id];
|
|
1751
|
-
sendTo(namespace, 'deleteGroup', id )
|
|
1874
|
+
sendTo(namespace, 'deleteGroup', id , function(msg) {
|
|
1875
|
+
if (msg && ms.error) {
|
|
1876
|
+
showMessage(msg.error, _('Error'));
|
|
1877
|
+
}
|
|
1878
|
+
getDevices();
|
|
1879
|
+
});
|
|
1752
1880
|
}
|
|
1753
1881
|
|
|
1754
1882
|
function updateDev(id, newName, newGroups) {
|
|
@@ -1756,6 +1884,24 @@ function updateDev(id, newName, newGroups) {
|
|
|
1756
1884
|
if (dev && dev.common.name != newName) {
|
|
1757
1885
|
renameDevice(id, newName);
|
|
1758
1886
|
}
|
|
1887
|
+
const keys = Object.keys(newGroups)
|
|
1888
|
+
if (keys && keys.length)
|
|
1889
|
+
{
|
|
1890
|
+
sendTo(namespace, 'updateGroupMembership', { id: id, groups: newGroups }, function (msg) {
|
|
1891
|
+
closeWaitingDialog();
|
|
1892
|
+
if (msg && msg.error) {
|
|
1893
|
+
showMessage(msg.error, _('Error'));
|
|
1894
|
+
}
|
|
1895
|
+
else {
|
|
1896
|
+
// save dev-groups on success
|
|
1897
|
+
dev.groups = newGroups;
|
|
1898
|
+
}
|
|
1899
|
+
showDevices();
|
|
1900
|
+
});
|
|
1901
|
+
showWaitingDialog('Updating group memberships', 10);
|
|
1902
|
+
|
|
1903
|
+
}
|
|
1904
|
+
/*
|
|
1759
1905
|
if (dev.info.device._type == 'Router') {
|
|
1760
1906
|
const oldGroups = devGroups[id] || [];
|
|
1761
1907
|
if (oldGroups.toString() != newGroups.toString()) {
|
|
@@ -1772,6 +1918,7 @@ function updateDev(id, newName, newGroups) {
|
|
|
1772
1918
|
});
|
|
1773
1919
|
}
|
|
1774
1920
|
}
|
|
1921
|
+
*/
|
|
1775
1922
|
}
|
|
1776
1923
|
|
|
1777
1924
|
function resetConfirmation() {
|
|
@@ -2166,7 +2313,7 @@ function genDevInfo(device) {
|
|
|
2166
2313
|
const dev = (device && device.info) ? device.info.device : undefined;
|
|
2167
2314
|
const mapped = (device && device.info) ? device.info.mapped : undefined;
|
|
2168
2315
|
if (!dev) return `<div class="truncate">No info</div>`;
|
|
2169
|
-
const genRow = function(name, value) {
|
|
2316
|
+
const genRow = function(name, value, refresh) {
|
|
2170
2317
|
if (value === undefined) {
|
|
2171
2318
|
return '';
|
|
2172
2319
|
} else {
|
|
@@ -2185,7 +2332,7 @@ function genDevInfo(device) {
|
|
|
2185
2332
|
}).join('');
|
|
2186
2333
|
}
|
|
2187
2334
|
};
|
|
2188
|
-
const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${mapped.model}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
|
|
2335
|
+
const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(mapped.model)}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
|
|
2189
2336
|
const mappedInfo = (!mapped) ? '' :
|
|
2190
2337
|
`<div style="font-size: 0.9em">
|
|
2191
2338
|
<ul>
|
|
@@ -2230,7 +2377,7 @@ function genDevInfo(device) {
|
|
|
2230
2377
|
${genRow('date code', dev._dateCode)}
|
|
2231
2378
|
${genRow('build', dev._softwareBuildID)}
|
|
2232
2379
|
${genRow('interviewed', dev._interviewCompleted)}
|
|
2233
|
-
${genRow('configured', (dev.meta.configured === 1))}
|
|
2380
|
+
${genRow('configured', (dev.meta.configured === 1), true)}
|
|
2234
2381
|
</ul>
|
|
2235
2382
|
</div>
|
|
2236
2383
|
</div>
|
|
@@ -2246,17 +2393,60 @@ function showDevInfo(id){
|
|
|
2246
2393
|
$('#modaldevinfo').modal('open');
|
|
2247
2394
|
}
|
|
2248
2395
|
|
|
2396
|
+
function showGroupList(show){
|
|
2397
|
+
const htmlsections = [];
|
|
2398
|
+
for (const groupid in devGroups) {
|
|
2399
|
+
const dev = devGroups[groupid];
|
|
2400
|
+
const grpname = (dev.common && dev.common.name?dev.common.name:'Group '+groupid);
|
|
2401
|
+
const selectables = [];
|
|
2402
|
+
const members = [];
|
|
2403
|
+
if (dev && dev.memberinfo)
|
|
2404
|
+
{
|
|
2405
|
+
selectables.push(`<select id="members_${groupid}" multiple>`)
|
|
2406
|
+
for (let m=0;m<dev.memberinfo.length; m++) {
|
|
2407
|
+
members.push(`${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (${dev.memberinfo[m].ieee})`)
|
|
2408
|
+
selectables.push(`<option value="${m}">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (...${dev.memberinfo[m].ieee.slice(-4)})</option>`);
|
|
2409
|
+
}
|
|
2410
|
+
selectables.push('</select>');
|
|
2411
|
+
}
|
|
2412
|
+
htmlsections.push(`
|
|
2413
|
+
<div class="row">
|
|
2414
|
+
<div class="col s4 m4 l4">
|
|
2415
|
+
<h5>${grpname}<h5>
|
|
2416
|
+
</div>
|
|
2417
|
+
<div class=col s7 m7 l7">
|
|
2418
|
+
${members.join('<br>')}
|
|
2419
|
+
</div>
|
|
2420
|
+
</div>
|
|
2421
|
+
`);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
$('#grouplist').html(htmlsections.join(''));
|
|
2425
|
+
$('#add').click(function() {
|
|
2426
|
+
const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
|
|
2427
|
+
editGroupName(maxind+1, 'Group ' + maxind+1, true);
|
|
2428
|
+
showGroupList(false);
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
$("#modalgrouplist a.btn[name='save']").unbind('click');
|
|
2432
|
+
$("#modalgrouplist a.btn[name='save']").click(() => {
|
|
2433
|
+
});
|
|
2434
|
+
if (show) $('#modalgrouplist').modal('open');
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
let waitingTimeout, waitingInt;
|
|
2249
2438
|
|
|
2250
2439
|
function showWaitingDialog(text, timeout){
|
|
2251
2440
|
let countDown = timeout;
|
|
2252
|
-
|
|
2441
|
+
waitingInt = setInterval(function() {
|
|
2253
2442
|
countDown -= 1;
|
|
2254
2443
|
const percent = 100-100*countDown/timeout;
|
|
2255
2444
|
$('#waiting_progress_line').css('width', `${percent}%`);
|
|
2256
2445
|
}, 1000);
|
|
2257
|
-
setTimeout(function() {
|
|
2446
|
+
waitingTimeout = setTimeout(function() {
|
|
2258
2447
|
$('#waiting_progress_line').css('width', `0%`);
|
|
2259
2448
|
clearTimeout(waitingInt);
|
|
2449
|
+
clearTimeout(waitingTimeout);
|
|
2260
2450
|
$('#modalWaiting').modal('close');
|
|
2261
2451
|
}, timeout*1000);
|
|
2262
2452
|
$('#waiting_message').text(text);
|
|
@@ -2264,6 +2454,8 @@ function showWaitingDialog(text, timeout){
|
|
|
2264
2454
|
}
|
|
2265
2455
|
|
|
2266
2456
|
function closeWaitingDialog(){
|
|
2457
|
+
if (waitingInt) clearTimeout(waitingInt);
|
|
2458
|
+
if (waitingTimeout) clearTimeout(waitingTimeout);
|
|
2267
2459
|
$('#modalWaiting').modal('close');
|
|
2268
2460
|
}
|
|
2269
2461
|
|
|
@@ -2427,10 +2619,10 @@ function showExclude() {
|
|
|
2427
2619
|
const exclude_dev = devices.find((d) => d.common.type == exclude_id) || {common: {name: exclude_id}};
|
|
2428
2620
|
// exclude_icon = (exclude_dev.icon) ? `<img src="${exclude_dev.icon}" width="64px">` : '';
|
|
2429
2621
|
|
|
2430
|
-
const modelUrl = (!exclude_id) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${exclude_id}.html" target="_blank" rel="noopener noreferrer">${exclude_id}</a>`;
|
|
2622
|
+
const modelUrl = (!exclude_id) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(exclude_id)}.html" target="_blank" rel="noopener noreferrer">${exclude_id}</a>`;
|
|
2431
2623
|
|
|
2432
2624
|
const card = `
|
|
2433
|
-
<div id="${exclude_id}" class="exclude col s12 m6 l4 xl3">
|
|
2625
|
+
<div id="${exclude_id}" class="exclude col s12 m6 l4 xl3" style="height: 135px;padding-bottom: 10px;">
|
|
2434
2626
|
<div class="card hoverable">
|
|
2435
2627
|
<div class="card-content zcard">
|
|
2436
2628
|
<i class="left"><img src="${exclude_dev.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';"></i>
|
|
@@ -2546,6 +2738,7 @@ function getDashCard(dev, groupImage) {
|
|
|
2546
2738
|
id = dev._id,
|
|
2547
2739
|
type = dev.common.type,
|
|
2548
2740
|
img_src = (groupImage ? groupImage: dev.icon || dev.common.icon),
|
|
2741
|
+
isActive = (dev.common.deactivated ? false : true),
|
|
2549
2742
|
rooms = [],
|
|
2550
2743
|
lang = systemLang || 'en';
|
|
2551
2744
|
const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
|
|
@@ -2555,12 +2748,12 @@ function getDashCard(dev, groupImage) {
|
|
|
2555
2748
|
nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
|
|
2556
2749
|
battery_cls = getBatteryCls(dev.battery),
|
|
2557
2750
|
lqi_cls = getLQICls(dev.link_quality),
|
|
2558
|
-
battery = (dev.battery) ? `<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>` : '',
|
|
2559
|
-
lq = (dev.link_quality
|
|
2560
|
-
status = (dev.link_quality
|
|
2561
|
-
permitJoinBtn = (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>' : '',
|
|
2751
|
+
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>` : '',
|
|
2752
|
+
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 ? '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>':''),
|
|
2753
|
+
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>`),
|
|
2754
|
+
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>' : '',
|
|
2562
2755
|
infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
|
|
2563
|
-
idleTime = (dev.link_quality_lc > 0) ? `<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>` : '';
|
|
2756
|
+
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>` : '';
|
|
2564
2757
|
const info = (dev.statesDef) ? dev.statesDef.map((stateDef)=>{
|
|
2565
2758
|
const id = stateDef.id;
|
|
2566
2759
|
const sid = id.split('.').join('_');
|
|
@@ -2587,7 +2780,7 @@ function getDashCard(dev, groupImage) {
|
|
|
2587
2780
|
return `<li><span class="label dash truncate">${stateDef.name}</span><span id=${sid} oid=${id} class="state">${val}</span></li>`;
|
|
2588
2781
|
}).join('') : '';
|
|
2589
2782
|
const dashCard = `
|
|
2590
|
-
<div class="card-content zcard">
|
|
2783
|
+
<div class="card-content zcard ${isActive?'':'bg_red'}">
|
|
2591
2784
|
<div class="flip" style="cursor: pointer">
|
|
2592
2785
|
<span class="top right small" style="border-radius: 50%">
|
|
2593
2786
|
${idleTime}
|
|
@@ -2600,7 +2793,7 @@ function getDashCard(dev, groupImage) {
|
|
|
2600
2793
|
<i class="left">${image}</i>
|
|
2601
2794
|
<div style="min-height:88px; font-size: 0.8em; height: 130px; overflow-y: auto" class="truncate">
|
|
2602
2795
|
<ul>
|
|
2603
|
-
${info}
|
|
2796
|
+
${(isActive?info:'Device deactivated')}
|
|
2604
2797
|
</ul>
|
|
2605
2798
|
</div>
|
|
2606
2799
|
<div class="footer right-align"></div>
|
|
@@ -2698,3 +2891,36 @@ function removeDevice(id) {
|
|
|
2698
2891
|
}
|
|
2699
2892
|
}
|
|
2700
2893
|
}
|
|
2894
|
+
|
|
2895
|
+
function swapActive(id) {
|
|
2896
|
+
const dev = getDeviceByID(id);
|
|
2897
|
+
if (dev && dev.common) {
|
|
2898
|
+
dev.common.deactivated = !(dev.common.deactivated)
|
|
2899
|
+
sendTo(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
|
|
2900
|
+
showDevices();
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
function reconfigureDlg(id) {
|
|
2906
|
+
const text = translateWord(`Do you really want to reconfigure device?`);
|
|
2907
|
+
$('#modalreconfigure').find('p').text(text);
|
|
2908
|
+
$("#modalreconfigure a.btn[name='yes']").unbind('click');
|
|
2909
|
+
$("#modalreconfigure a.btn[name='yes']").click(() => {
|
|
2910
|
+
reconfigureDevice(id, force);
|
|
2911
|
+
});
|
|
2912
|
+
$('#modalreconfigure').modal('open');
|
|
2913
|
+
Materialize.updateTextFields();
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
function reconfigureDevice(id) {
|
|
2917
|
+
sendTo(namespace, 'reconfigure', {id: id}, function (msg) {
|
|
2918
|
+
closeWaitingDialog();
|
|
2919
|
+
if (msg) {
|
|
2920
|
+
if (msg.error) {
|
|
2921
|
+
showMessage(msg.error, _('Error'));
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
});
|
|
2925
|
+
showWaitingDialog('Device is being reconfigure', 30);
|
|
2926
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/admin/img/tuya_rb280.png
CHANGED
|
Binary file
|