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/README.md +19 -1
- package/admin/admin.js +1020 -729
- package/admin/index_m.html +55 -155
- package/admin/tab_m.html +161 -242
- package/io-package.json +42 -39
- package/lib/DeviceDebug.js +24 -2
- package/lib/binding.js +7 -7
- package/lib/commands.js +319 -255
- package/lib/developer.js +1 -1
- package/lib/devices.js +2 -2
- package/lib/exclude.js +1 -1
- package/lib/exposes.js +54 -24
- package/lib/groups.js +26 -28
- package/lib/localConfig.js +8 -8
- package/lib/networkmap.js +10 -2
- package/lib/statescontroller.js +135 -91
- package/lib/zbDelayedAction.js +4 -4
- package/lib/zbDeviceAvailability.js +32 -33
- package/lib/zbDeviceConfigure.js +7 -0
- package/lib/zbDeviceEvent.js +38 -6
- package/lib/zigbeecontroller.js +98 -46
- package/main.js +43 -57
- package/package.json +6 -9
- package/lib/tools.js +0 -55
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', '
|
|
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
|
-
|
|
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
|
|
144
|
+
function getDeviceByIEEE(ieeeAddr) {
|
|
79
145
|
return devices.find((devInfo) => {
|
|
80
146
|
try {
|
|
81
|
-
return devInfo.info.device.
|
|
147
|
+
return devInfo.info.device.ieee == ieeeAddr;
|
|
82
148
|
} catch (e) {
|
|
83
|
-
|
|
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.
|
|
158
|
+
return devInfo.info.device.nwk == nwk;
|
|
93
159
|
} catch (e) {
|
|
94
|
-
|
|
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"> 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 =
|
|
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(',') || ' ';
|
|
179
436
|
let memberCount = 0;
|
|
180
|
-
let info = `<div style="min-height:88px; font-size: 0.8em;
|
|
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(`
|
|
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
|
|
233
|
-
|
|
234
|
-
return parameter.replace(replaceByUnderscore, '_');
|
|
489
|
+
function getDeviceCards() {
|
|
490
|
+
return $('#devices .device').not('.group');
|
|
235
491
|
}
|
|
236
492
|
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
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 =
|
|
242
|
-
type =
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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(',') || ' ';
|
|
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/${
|
|
261
|
-
const image = `<img src="${img_src}" width="
|
|
262
|
-
nwk = (dev.info && dev.info.device) ? dev.info.device.
|
|
263
|
-
battery_cls =
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
822
|
+
const groupsById = {};
|
|
601
823
|
if (groupables.length > 0) {
|
|
602
|
-
for (
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
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,
|
|
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
|
|
638
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1132
|
+
sendToWrapper(namespace, 'checkOtaAvail', {devId: devId}, callback);
|
|
904
1133
|
}
|
|
905
1134
|
}
|
|
906
1135
|
|
|
907
1136
|
function letsPairingWithCode(code) {
|
|
908
1137
|
messages = [];
|
|
909
|
-
|
|
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
|
|
1148
|
+
function openNetwork() {
|
|
920
1149
|
messages = [];
|
|
921
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
988
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1355
|
+
|
|
1356
|
+
sendToWrapper(namespace, 'getLocalImages', {}, function(msg) {
|
|
1018
1357
|
if (msg && msg.imageData) {
|
|
1019
|
-
|
|
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
|
-
|
|
1052
|
-
|
|
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(' ')
|
|
@@ -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
|
-
|
|
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="
|
|
1109
|
-
const ofbutton = `<a id="hi_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="
|
|
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' : ' ';
|
|
1111
|
-
return
|
|
1461
|
+
return {html:`<thead id="dbgtable"><tr><td> </td><td>Incoming messages</td><td> </td><td> </td><td>${dataHide}</td><td>${ifbutton}</td><td>${ofbutton}</td></tr><tr><td>ID</td><td>Zigbee Payload</td><td> </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(' ')
|
|
@@ -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
|
-
|
|
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="
|
|
1147
|
-
const ofbutton = `<a id="ho_${devID}" class="btn-floating waves-effect waves-light blue tooltipped center-align hoverable translateT" title="
|
|
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' : ' ';
|
|
1149
|
-
return
|
|
1502
|
+
return { html:`<thead id="dbgtable"><tr><td> </td><td>Outgoing messages</td><td> </td><td> </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
|
|
1162
|
-
const
|
|
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> </td><td> </td><td> </td><td> </td><td> </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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
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
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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
|
-
|
|
1271
|
-
|
|
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
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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
|
-
|
|
1307
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
$('#
|
|
1693
|
-
$('#
|
|
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
|
-
$('#
|
|
1711
|
-
$('#
|
|
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:
|
|
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.
|
|
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 =
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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.
|
|
2646
|
+
if (device.info.device && device.info.device.type === 'Coordinator') {
|
|
2191
2647
|
return null;
|
|
2192
2648
|
}
|
|
2193
|
-
|
|
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.
|
|
2656
|
+
return device.native.id;
|
|
2200
2657
|
});
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
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
|
-
|
|
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
|
|
2757
|
+
return this.value ===obj.native.id;
|
|
2300
2758
|
});
|
|
2301
2759
|
|
|
2302
|
-
const epList = device ? device.info.
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
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 =
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
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
|
|
3130
|
-
const ep =
|
|
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.
|
|
3136
|
-
${genRowValues('input clusters', ep.
|
|
3137
|
-
${genRowValues('output clusters', ep.
|
|
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
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3984
|
+
sendToWrapper(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
|
|
3686
3985
|
showDevices();
|
|
3687
3986
|
});
|
|
3688
3987
|
}
|
|
3689
3988
|
}
|
|
3690
3989
|
|
|
3691
|
-
function
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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'))
|