iobroker.zigbee 3.1.5 → 3.2.0
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 +33 -15
- package/admin/admin.js +672 -224
- package/admin/img/group.png +0 -0
- package/admin/img/restore_backup.png +0 -0
- package/admin/index_m.html +161 -9
- package/admin/tab_m.html +7 -8
- package/docs/de/img/edit_grp.png +0 -0
- package/docs/de/img/edit_image.png +0 -0
- package/docs/de/readme.md +2 -2
- package/docs/en/readme.md +2 -2
- package/io-package.json +27 -27
- package/lib/DeviceDebug.js +1 -1
- package/lib/backup.js +55 -26
- package/lib/commands.js +150 -92
- package/lib/devices.js +10 -1
- package/lib/exclude.js +2 -1
- package/lib/exposes.js +20 -4
- package/lib/groups.js +2 -2
- package/lib/localConfig.js +56 -18
- package/lib/statescontroller.js +81 -50
- package/lib/utils.js +41 -0
- package/lib/zbDelayedAction.js +1 -1
- package/lib/zbDeviceAvailability.js +3 -3
- package/lib/zbDeviceEvent.js +4 -2
- package/lib/zigbeecontroller.js +44 -20
- package/main.js +56 -37
- package/package.json +2 -4
package/admin/admin.js
CHANGED
|
@@ -9,6 +9,7 @@ const Materialize = (typeof M !== 'undefined') ? M : Materialize,
|
|
|
9
9
|
namespace = 'zigbee.' + instance,
|
|
10
10
|
namespaceLen = namespace.length;
|
|
11
11
|
let devices = [],
|
|
12
|
+
models = [],
|
|
12
13
|
debugDevices = [],
|
|
13
14
|
messages = [],
|
|
14
15
|
map = {},
|
|
@@ -17,6 +18,7 @@ let devices = [],
|
|
|
17
18
|
network,
|
|
18
19
|
networkEvents,
|
|
19
20
|
responseCodes = false,
|
|
21
|
+
localConfigData = {},
|
|
20
22
|
groups = {},
|
|
21
23
|
devGroups = {}, // eslint-disable-line prefer-const
|
|
22
24
|
binding = [],
|
|
@@ -92,12 +94,12 @@ function keepAlive(callback) {
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
function startKeepalive() {
|
|
95
|
-
return setInterval(
|
|
97
|
+
return setInterval(() => UpdateAdapterAlive(false), 120000);
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
function UpdateAdapterAlive(state) {
|
|
99
|
-
if (connectionStatus.connected === state) return;
|
|
100
101
|
connectionStatus.time = Date.now();
|
|
102
|
+
if (connectionStatus.connected === state) return;
|
|
101
103
|
if (state) {
|
|
102
104
|
$('#adapterStopped_btn').addClass('hide');
|
|
103
105
|
$('#code_pairing').removeClass('disabled');
|
|
@@ -180,7 +182,11 @@ function getLQICls(value) {
|
|
|
180
182
|
|
|
181
183
|
function sanitizeModelParameter(parameter) {
|
|
182
184
|
const replaceByUnderscore = /[\s/]/g;
|
|
183
|
-
|
|
185
|
+
try {
|
|
186
|
+
return parameter.replace(replaceByUnderscore, '_');
|
|
187
|
+
}
|
|
188
|
+
catch { /* intentionally empty*/ }
|
|
189
|
+
return parameter;
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
/////
|
|
@@ -189,88 +195,281 @@ function sanitizeModelParameter(parameter) {
|
|
|
189
195
|
//
|
|
190
196
|
////
|
|
191
197
|
|
|
198
|
+
const LocalDataDisplayValues = {
|
|
199
|
+
unfoldedModels : [], // [{ model:plug01, devices: true/false, options: true/false}]
|
|
200
|
+
unfoldedDevices : [], // [{ device:0xdeadbeefdeadbeef, show: true/false}]
|
|
201
|
+
buttonSet: new Set(),
|
|
202
|
+
showModels: true,
|
|
203
|
+
sortedKeys : [],
|
|
204
|
+
sortMethod: function (a, b) { return 0 },
|
|
205
|
+
filterMethod: function (a) { return true },
|
|
206
|
+
}
|
|
192
207
|
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
devicesByModel[modelID].devices.push(dev);
|
|
200
|
-
else devicesByModel[modelID] = {devices:[dev], icon:dev.common.icon};
|
|
208
|
+
function updateFoldModel(model, devices, options) {
|
|
209
|
+
const m = LocalDataDisplayValues.unfoldedModels.find((c) => c.model === model);
|
|
210
|
+
if (!m) {
|
|
211
|
+
const ml = {model, devices: (devices === undefined ? false: devices), options: (options === undefined ? false : options)}
|
|
212
|
+
LocalDataDisplayValues.unfoldedModels.push(ml)
|
|
213
|
+
return ml;;
|
|
201
214
|
}
|
|
202
|
-
|
|
215
|
+
if (devices) m.devices = !m.devices;
|
|
216
|
+
if (options) m.options = !m.options;
|
|
217
|
+
return m;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
function getModelData(data, models, keys) {
|
|
203
222
|
const Html = [];
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
223
|
+
const s = new Set();
|
|
224
|
+
for (const k of keys) {
|
|
225
|
+
const model = models[k];
|
|
226
|
+
const key = model.model.model;
|
|
227
|
+
//console.warn(`getmodeldata: model is ${key}, sO: ${JSON.stringify(model.setOptions)}`);
|
|
228
|
+
const numOptions = Object.keys(model.setOptions).length + ((typeof model.setOptions.options === 'object' && model.setOptions.options != null) ? Object.keys(model.setOptions.options).length-1 : 0);
|
|
229
|
+
const foldData = updateFoldModel(key, undefined, undefined);
|
|
230
|
+
let numrows = 1;
|
|
231
|
+
if (foldData.devices) numrows += model.devices.length;
|
|
232
|
+
if (numOptions > 0) numrows += 1;
|
|
233
|
+
if (foldData.options) numrows += numOptions;
|
|
234
|
+
const d_btn_name = `d_toggle_${k}`;
|
|
235
|
+
const e_btn_name = `m_edit_${k}`;
|
|
236
|
+
const d_btn_tip = `fold / unfold devices of ${key}`;
|
|
237
|
+
const e_btn_tip = `edit model ${key}`;
|
|
238
|
+
const d_btn = btnParam(d_btn_name, d_btn_tip, foldData.devices ? 'expand_less' : 'expand_more', false);
|
|
239
|
+
const e_btn = btnParam(e_btn_name, e_btn_tip, 'edit', 'green', false)
|
|
240
|
+
LocalDataDisplayValues.buttonSet.add(d_btn_name);
|
|
241
|
+
LocalDataDisplayValues.buttonSet.add(e_btn_name);
|
|
242
|
+
const devtxt = (model.devices.length && !foldData.devices) ? `${model.devices.length} ${model.model.type}${model.devices.length > 1 ? 's' : ''}` : '';
|
|
243
|
+
Html.push(`<tr id="datarowodd">
|
|
244
|
+
<td rowspan="${numrows}" width="15%"><img src=${model.model.icon} class="dev_list"></td>
|
|
245
|
+
<td colspan="2">Model ${key}</td><td>${devtxt}</td>
|
|
246
|
+
<td>${d_btn} ${e_btn}</td></tr>`)
|
|
247
|
+
let cnt = 0;
|
|
248
|
+
if (foldData.devices) {
|
|
249
|
+
let isOdd = false;
|
|
250
|
+
for (const dev of model.devices) {
|
|
251
|
+
let devieee = dev._id.replace(`${namespace}.`, '');
|
|
252
|
+
|
|
253
|
+
if (devieee == undefined) devieee = 'unknown' + cnt++;
|
|
254
|
+
//LocalDataDisplayValues.buttonSet.add(`d_delete_${devieee}`);
|
|
255
|
+
LocalDataDisplayValues.buttonSet.add(`d_delall_${k}-${devieee}`);
|
|
256
|
+
LocalDataDisplayValues.buttonSet.add(`d_disen_${k}-${devieee}`);
|
|
257
|
+
|
|
258
|
+
//const bn = btnParam(`d_delete_${devieee}`, `delete device ${devieee}`, 'delete', 'red darken-4', false);
|
|
259
|
+
const bna = btnParam(`d_delall_${k}-${devieee}`, `completely delete device ${devieee}`, 'delete_forever', 'red accent-4', false);
|
|
260
|
+
const bta = !dev.common.deactivated ? btnParam(`d_disen_${k}-${devieee}`, `disable device ${devieee}`, 'power_settings_new', 'green accent-4', false) : btnParam(`d_disen_${k}-${devieee}`, `enable device ${devieee}`, 'power_settings_new', 'red accent-4', false);
|
|
261
|
+
Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}${dev.common.deactivated ? '_red' : ''}"><td width="1%"><i class="material-icons small">devices</i></td><td width="25%">${devieee}</td><td width="45%">${dev.common.name}</td><td width="10%">${bna}${bta}<td></tr>`)
|
|
262
|
+
isOdd = !isOdd;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (numOptions > 0) {
|
|
266
|
+
const o_btn_name = `o_toggle_${k}`;
|
|
267
|
+
const o_btn_tip = `fold / unfold options for Model ${key}`;
|
|
268
|
+
LocalDataDisplayValues.buttonSet.add(o_btn_name);
|
|
269
|
+
const opttxt = (numOptions > 0 && !(foldData.options)) ? `${numOptions} global option${numOptions > 1 ? 's' : ''}` :''
|
|
270
|
+
Html.push(`<tr id="datarowodd">
|
|
271
|
+
<td colspan="2">Model ${key}</td><td>${opttxt}</td>
|
|
272
|
+
<td>${btnParam(o_btn_name, o_btn_tip, foldData.options ? 'expand_less' : 'expand_more')}</td></tr>`)
|
|
273
|
+
if (foldData.options) {
|
|
274
|
+
let isOdd = false;
|
|
275
|
+
for (const key of Object.keys(model.setOptions)) {
|
|
276
|
+
if (typeof model.setOptions[key] === 'object') {
|
|
277
|
+
const oo = model.setOptions[key];
|
|
278
|
+
for (const ok of Object.keys(oo)) {
|
|
279
|
+
LocalDataDisplayValues.buttonSet.add(`o_delete_${k}-${ok}`);
|
|
280
|
+
const btn = btnParam(`o_delete_${k}-${ok}`, `delete option ${ok}`, 'delete', 'red darken-4', false);
|
|
281
|
+
Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}"><td width="1%"><i class="material-icons small">blur_circular</i></td><td width="25%">${ok}</td><td width="45%" ${oo[ok] === undefined ? 'id="datared">"not set on model"' : '>'+oo[ok]}</td><td>${btn}</td></tr>`)
|
|
282
|
+
isOdd = !isOdd;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
LocalDataDisplayValues.buttonSet.add(`l_delete_${k}-${key}`);
|
|
287
|
+
const btn = btnParam(`l_delete_${k}-${key}`, `delete option ${key}`, 'delete', 'red darken-4', false);
|
|
288
|
+
if (key==='icon') {
|
|
289
|
+
const icontext = model.setOptions[key] === undefined ? 'id="datared">"not set on model"' : `>${model.setOptions[key]}`;
|
|
290
|
+
const icon = model.setOptions[key]=== undefined ? '' : `<img src=${model.setOptions[key]} height="32px" class="sml_list">`;
|
|
291
|
+
Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}"><td width="1%"><i class="material-icons small">blur_circular</i></td><td width="25%">${key}</td><td valign="middle" width="45%" ${icontext}</td><td>${btn}${icon}</td></tr>`)
|
|
292
|
+
}
|
|
293
|
+
else
|
|
294
|
+
Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}"><td width="1%"><i class="material-icons small">blur_circular</i></td><td width="25%">${key}</td><td width="45%" ${model.setOptions[key] === undefined ? 'id="datared">"not set on model"' : '>'+model.setOptions[key]}</td><td>${btn}</td></tr>`)
|
|
295
|
+
isOdd = !isOdd;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
}
|
|
218
302
|
return Html;
|
|
219
303
|
}
|
|
304
|
+
|
|
305
|
+
function btnParam(id, tooltip, icon, color, disabled) {
|
|
306
|
+
return `<a id="${id}" class="btn-floating waves-effect waves-light right ${color ? color : 'blue'} ${disabled ? 'disabled ' : ''}tooltipped center-align hoverable translateT" title="${tooltip}"><i class="material-icons large">${icon}</i></a>`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
220
310
|
function getDeviceData(deviceList, withIcon) {
|
|
221
|
-
const Html = [
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
311
|
+
const Html = [];
|
|
312
|
+
return Html;
|
|
313
|
+
/*for (const dev of deviceList) {
|
|
314
|
+
const rowspan = dev.options ? Object.keys(dev.options).length + 2 : 2;
|
|
315
|
+
const iconLink = `<img src=${dev.common.icon} class="dev_list">`;
|
|
316
|
+
const devieee = dev._id.replace(`${namespace}.`, '');
|
|
317
|
+
const o_btn_name = `do_toggle_${devieee}`;
|
|
318
|
+
const o_btn_tip = `fold / unfold options for ${devieee}`;
|
|
319
|
+
LocalDataDisplayValues.buttonSet.add(o_btn_name);
|
|
320
|
+
const bn = `f_edit_${devieee}`
|
|
321
|
+
LocalDataDisplayValues.buttonSet.add(bn);
|
|
322
|
+
|
|
323
|
+
Html.push(`<tr id="datarowodd"><td rowspan="${rowspan}">${iconLink}</td><td colspan="2">${dev.common.name} (${devieee})</td><td>${btnParam(o_btn_name, o_btn_tip, LocalDataDisplayValues.unfoldedDevices ? 'do_not_disturb' : 'add_circle')}</td></tr>`);
|
|
324
|
+
Html.push(`<tr id="dataroweven"><td colspan="2">Device flags</td><td>${btnParam(bn, 'edit flags','edit')}</td></tr>`);
|
|
325
|
+
//console.warn(`dev is ${JSON.stringify(dev)}`);
|
|
326
|
+
if (dev.options && LocalDataDisplayValues.unfoldedDevices[devieee]) {
|
|
228
327
|
for (const o of dev.options) {
|
|
229
|
-
|
|
328
|
+
const bn = `o_edit_${devieee}.${o.key}`
|
|
329
|
+
LocalDataDisplayValues.buttonSet.add(bn);
|
|
330
|
+
Html.push(`<tr id="datarowopt"><td>${o.key}></td><td>${o.value}</td><td>${btnParam(bn, 'edit flags','edit')}</td></tr>`);
|
|
230
331
|
}
|
|
231
|
-
Html.push(`</div>`);
|
|
232
332
|
}
|
|
233
|
-
Html.push(`</div>`)
|
|
234
333
|
}
|
|
235
|
-
Html
|
|
236
|
-
return Html;
|
|
334
|
+
return Html;*/
|
|
237
335
|
}
|
|
238
|
-
|
|
239
|
-
|
|
336
|
+
|
|
337
|
+
function sortAndFilter(filter, sort) {
|
|
338
|
+
const fFun = filter || LocalDataDisplayValues.filterMethod;
|
|
339
|
+
console.warn('once:='+JSON.stringify(models['m_0'].setOptions))
|
|
340
|
+
console.warn('twice:='+ JSON.stringify(models['m_1'].setOptions))
|
|
341
|
+
let filterMap = LocalDataDisplayValues.sortedKeys = Object.keys(models);
|
|
342
|
+
if (LocalDataDisplayValues.searchVal && LocalDataDisplayValues.searchVal.length) {
|
|
343
|
+
filterMap = filterMap.filter((a) => {
|
|
344
|
+
return models[a]?.model?.model?.toLowerCase().includes(LocalDataDisplayValues.searchVal)
|
|
345
|
+
});
|
|
346
|
+
console.warn(`${JSON.stringify(LocalDataDisplayValues.searchVal)} - ${JSON.stringify(models['m_1'].model)}`);
|
|
347
|
+
}
|
|
348
|
+
if (typeof fFun == 'function') {
|
|
349
|
+
console.warn(`${JSON.stringify(filterMap)} - ${JSON.stringify(models['m_1'].model)}`);
|
|
350
|
+
filterMap = filterMap.filter(fFun);
|
|
351
|
+
}
|
|
352
|
+
console.warn(JSON.stringify(filterMap));
|
|
353
|
+
const sFun = sort || LocalDataDisplayValues.sortMethod;
|
|
354
|
+
if (typeof sFun == 'function') {
|
|
355
|
+
console.warn(`${JSON.stringify(filterMap)} - ${JSON.stringify(models['m_1'].model)}`);
|
|
356
|
+
filterMap = filterMap.sort(sFun);
|
|
357
|
+
}
|
|
358
|
+
console.warn(JSON.stringify(filterMap));
|
|
359
|
+
if (typeof filter == 'function') LocalDataDisplayValues.filterMethod = filter;
|
|
360
|
+
if (typeof sort == 'function') LocalDataDisplayValues.sortMethod = sort;
|
|
361
|
+
return filterMap;
|
|
240
362
|
}
|
|
241
363
|
|
|
242
364
|
function showLocalData() {
|
|
243
|
-
|
|
244
|
-
|
|
365
|
+
LocalDataDisplayValues.buttonSet.clear();
|
|
366
|
+
;
|
|
367
|
+
const ModelHtml = getModelData(devices, models, sortAndFilter(undefined, undefined));
|
|
368
|
+
const DeviceHtml = getDeviceData(devices);
|
|
369
|
+
const sm = LocalDataDisplayValues.showModels;
|
|
370
|
+
//const dmtoggle = btnParam('t_all_models', 'Refresh models', 'developer_board');
|
|
371
|
+
|
|
372
|
+
const RowSpan = sm ? ModelHtml.length +2 : DeviceHtml.length + 2;
|
|
245
373
|
const Html = [];
|
|
246
374
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
Html.push
|
|
256
|
-
Html.push
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
375
|
+
if (sm) {
|
|
376
|
+
Html.push(`<table style="width:100%"><tr id="datatable"><th rowspan="${RowSpan}"> </th><th colspan=4></th><th></th><th rowspan="${RowSpan}"> </th></tr>`);
|
|
377
|
+
Html.push(ModelHtml.join(''));
|
|
378
|
+
}
|
|
379
|
+
/*else {
|
|
380
|
+
Html.push(`<table style="width:100%"><tr id="datatable"><th rowspan="${RowSpan}"> </th><th colspan=4>Device Data</th><th>${dmtoggle}</th><th rowspan="${RowSpan}"> </th></tr>`)
|
|
381
|
+
Html.push(DeviceHtml.join(''));
|
|
382
|
+
}*/
|
|
383
|
+
Html.push(`<tr id="datatable"><td colspan="5"></td></tr>`)
|
|
384
|
+
Html.push('</table>');
|
|
385
|
+
//Html.push('</div></div>');
|
|
386
|
+
$('#tab-overrides-content').html(Html.join(''));
|
|
387
|
+
|
|
388
|
+
/*$('#t_all_models').click(function () {
|
|
389
|
+
//LocalDataDisplayValues.showModels = !LocalDataDisplayValues.showModels;
|
|
390
|
+
getDevices();
|
|
391
|
+
});*/
|
|
392
|
+
|
|
393
|
+
//console.warn(`lddv is ${JSON.stringify(LocalDataDisplayValues)}`)
|
|
394
|
+
for (const item of LocalDataDisplayValues.buttonSet) {
|
|
395
|
+
if (item.startsWith('d_toggle_')) $(`#${item}`).click(function () {
|
|
396
|
+
const key = item.replace('d_toggle_', '');
|
|
397
|
+
//console.warn(`clicked ${item}`);
|
|
398
|
+
updateFoldModel(models[key].model.model, true, false)
|
|
399
|
+
showLocalData();
|
|
400
|
+
});
|
|
401
|
+
if (item.startsWith('o_toggle_')) $(`#${item}`).click(function () {
|
|
402
|
+
//console.warn(`clicked ${item}`);
|
|
403
|
+
const key = item.substring(9);
|
|
404
|
+
updateFoldModel(models[key].model.model, false, true)
|
|
405
|
+
showLocalData();
|
|
406
|
+
})
|
|
407
|
+
if (item.startsWith('do_toggle_')) $(`#${item}`).click(function () {
|
|
408
|
+
//console.warn(`clicked ${item}`);
|
|
409
|
+
const key = item.substring(10);
|
|
410
|
+
if (LocalDataDisplayValues.unfoldedDevices.hasOwnProperty(key))
|
|
411
|
+
LocalDataDisplayValues.unfoldedDevices[key] =! LocalDataDisplayValues.unfoldedDevices[key];
|
|
412
|
+
else
|
|
413
|
+
LocalDataDisplayValues.unfoldedDevices[key] = true;
|
|
414
|
+
showLocalData();
|
|
415
|
+
})
|
|
416
|
+
if (item.startsWith('m_edit_')) $(`#${item}`).click(function () {
|
|
417
|
+
//console.warn(`clicked ${item}`);
|
|
418
|
+
const key = item.substring(7);
|
|
419
|
+
editDeviceOptions(models[key], true);
|
|
420
|
+
})
|
|
421
|
+
if (item.startsWith('o_delete_')) {
|
|
422
|
+
console.warn(`adding click to ${item}`)
|
|
423
|
+
$(`#${item}`).click(function () {
|
|
424
|
+
console.warn(`clicked ${item}`);
|
|
425
|
+
const keys = item.replace('o_delete_', '').split('-');
|
|
426
|
+
const model = models[keys[0]]?.model.model;
|
|
427
|
+
const option = keys[1];
|
|
428
|
+
const sOptions = models[keys[0]]?.setOptions || {};
|
|
429
|
+
const options = models[keys[0]]?.setOptions?.options || {};
|
|
430
|
+
//options[option] = '##REMOVE##';
|
|
431
|
+
console.warn(`clicked ${item} - options are ${JSON.stringify(options)}`);
|
|
432
|
+
delete options[option];
|
|
433
|
+
updateLocalConfigItems(model, sOptions || {}, true);
|
|
434
|
+
showLocalData();
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
if (item.startsWith('l_delete_')) $(`#${item}`).click(function () {
|
|
438
|
+
const keys = item.replace('l_delete_', '').split('-');
|
|
439
|
+
const model = models[keys[0]]?.model.model;
|
|
440
|
+
const option = keys[1];
|
|
441
|
+
const options = models[keys[0]].setOptions;
|
|
442
|
+
options[option] = '##REMOVE##';
|
|
443
|
+
console.warn(`clicked ${item} - options are ${JSON.stringify(options)}`);
|
|
444
|
+
updateLocalConfigItems(model, options || {}, true)
|
|
445
|
+
delete options[option];
|
|
446
|
+
showLocalData();
|
|
447
|
+
})
|
|
448
|
+
if (item.startsWith('d_disen_')) {
|
|
449
|
+
console.warn(`adding click to ${item}`)
|
|
450
|
+
$(`#${item}`).click(function () {
|
|
451
|
+
console.warn(`clicked ${item}`);
|
|
452
|
+
const keys = item.replace('d_disen_', '').split('-');
|
|
453
|
+
const model = models[keys[0]];
|
|
454
|
+
const device = model.devices.find( (d) => d.native.id === keys[1]);
|
|
455
|
+
swapActive(keys[1]);
|
|
456
|
+
device.common.deactivated = !device.common.deactivated
|
|
457
|
+
showLocalData();
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
if (item.startsWith('d_delall_')) {
|
|
463
|
+
console.warn(`adding click to ${item}`)
|
|
464
|
+
$(`#${item}`).click(function () {
|
|
465
|
+
console.warn(`clicked ${item}`);
|
|
466
|
+
const keys = item.replace('d_delall_', '').split('-');
|
|
467
|
+
const model = models[keys[0]];
|
|
468
|
+
const device = model.devices.find( (d) => d.native.id === keys[1]);
|
|
469
|
+
deleteConfirmation(keys[1], device.common.name, keys[1], models[keys[0]].devices.count <=1 ? models[keys[0]]?.model.model : undefined);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
274
473
|
}
|
|
275
474
|
|
|
276
475
|
/////
|
|
@@ -298,6 +497,7 @@ function getCard(dev) {
|
|
|
298
497
|
rooms.push(dev.rooms[r]);
|
|
299
498
|
}
|
|
300
499
|
}
|
|
500
|
+
const NoInterviewIcon = dev.info?.device?.interviewstate != 'SUCCESSFUL' ? `<div class="col tool"><i class="material-icons icon-red">perm_device_information</i></div>` : ``;
|
|
301
501
|
const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
|
|
302
502
|
const rid = id.split('.').join('_');
|
|
303
503
|
const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
|
|
@@ -321,9 +521,15 @@ function getCard(dev) {
|
|
|
321
521
|
${roomInfo}
|
|
322
522
|
</ul>
|
|
323
523
|
</div>`,
|
|
324
|
-
deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive ? '
|
|
325
|
-
debugBtn = `<button name="swapdebug" class="right btn-flat btn-small tooltipped" title="${(isDebug > -1 ? (isDebug > 0) ?'
|
|
326
|
-
infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : ''
|
|
524
|
+
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>`,
|
|
525
|
+
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>`,
|
|
526
|
+
infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
|
|
527
|
+
reconfigureButton = dev.info.mapped.hasConfigure ? `<button name="reconfigure" class="right btn-flat btn-small tooltipped" title="reconfigure">
|
|
528
|
+
<i class="material-icons icon-red">sync</i>
|
|
529
|
+
</button>` : ``,
|
|
530
|
+
groupButton = dev.info?.device?.isGroupable ? ` <button name="edit" class="right btn-flat btn-small tooltipped" title="edit group membership">
|
|
531
|
+
<i class="material-icons icon-black">group_work</i>
|
|
532
|
+
</button>` : ``;
|
|
327
533
|
|
|
328
534
|
const dashCard = getDashCard(dev);
|
|
329
535
|
const card = `<div id="${id}" class="device">
|
|
@@ -333,6 +539,7 @@ function getCard(dev) {
|
|
|
333
539
|
<div class="card-content zcard">
|
|
334
540
|
<div class="flip" style="cursor: pointer">
|
|
335
541
|
<span class="top right small" style="border-radius: 50%">
|
|
542
|
+
${NoInterviewIcon}
|
|
336
543
|
${battery}
|
|
337
544
|
<!--${lq}-->
|
|
338
545
|
${status}
|
|
@@ -347,20 +554,15 @@ function getCard(dev) {
|
|
|
347
554
|
<div class="card-action">
|
|
348
555
|
<div class="card-reveal-buttons">
|
|
349
556
|
${infoBtn}
|
|
350
|
-
|
|
351
557
|
<span class="left fw_info"></span>
|
|
352
|
-
<button name="delete" class="right btn-flat btn-small tooltipped" title="
|
|
558
|
+
<button name="delete" class="right btn-flat btn-small tooltipped" title="delete device">
|
|
353
559
|
<i class="material-icons icon-red">delete</i>
|
|
354
560
|
</button>
|
|
355
|
-
|
|
561
|
+
${groupButton}
|
|
562
|
+
<button name="swapimage" class="right btn-flat btn-small tooltipped" title="edit device options">
|
|
356
563
|
<i class="material-icons icon-black">edit</i>
|
|
357
564
|
</button>
|
|
358
|
-
|
|
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>
|
|
565
|
+
${reconfigureButton}
|
|
364
566
|
${deactBtn}
|
|
365
567
|
${debugBtn}
|
|
366
568
|
</div>
|
|
@@ -395,7 +597,7 @@ function getCoordinatorCard(dev) {
|
|
|
395
597
|
<li><span class="label">ZHC / ZH:</span><span>${coordinatorinfo.converters} / ${coordinatorinfo.herdsman}</span></li>
|
|
396
598
|
</ul>
|
|
397
599
|
</div>`,
|
|
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>',
|
|
600
|
+
permitJoinBtn = '<div class="col tool"><button name="joinCard" class="waves-effect btn-small btn-flat right hoverable green tooltipped" title="open network"><i class="material-icons icon-green">leak_add</i></button></div>',
|
|
399
601
|
//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>`,
|
|
400
602
|
card = `<div id="${id}" class="device">
|
|
401
603
|
<div class="card hoverable">
|
|
@@ -448,6 +650,7 @@ function getGroupCard(dev) {
|
|
|
448
650
|
;
|
|
449
651
|
info = info.concat(` ${roomInfo}</ul>
|
|
450
652
|
</div>`);
|
|
653
|
+
const infoBtn = `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>`;
|
|
451
654
|
const image = `<img src="${dev.common.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
|
|
452
655
|
const dashCard = getDashCard(dev, dev.common.icon, memberCount > 0);
|
|
453
656
|
const card = `<div id="${id}" class="device group">
|
|
@@ -469,14 +672,15 @@ function getGroupCard(dev) {
|
|
|
469
672
|
</div>
|
|
470
673
|
<div class="card-action">
|
|
471
674
|
<div class="card-reveal-buttons">
|
|
472
|
-
|
|
473
|
-
|
|
675
|
+
${infoBtn}
|
|
676
|
+
<button name="deletegrp" class="right btn-flat btn-small tooltipped" title="delete group">
|
|
677
|
+
<i class="material-icons icon-red">delete</i>
|
|
474
678
|
</button>
|
|
475
|
-
<button name="editgrp" class="right btn-flat btn-small">
|
|
476
|
-
<i class="material-icons
|
|
679
|
+
<button name="editgrp" class="right btn-flat btn-small tooltipped" title="edit group members">
|
|
680
|
+
<i class="material-icons">group_work</i>
|
|
477
681
|
</button>
|
|
478
|
-
<button name="swapimage" class="right btn-flat btn-small tooltipped" title="
|
|
479
|
-
<i class="material-icons icon-black">
|
|
682
|
+
<button name="swapimage" class="right btn-flat btn-small tooltipped" title="edit group options">
|
|
683
|
+
<i class="material-icons icon-black">edit</i>
|
|
480
684
|
</button>
|
|
481
685
|
</div>
|
|
482
686
|
</div>
|
|
@@ -506,10 +710,11 @@ function getDashCard(dev, groupImage, groupstatus) {
|
|
|
506
710
|
rooms = [],
|
|
507
711
|
lang = systemLang || 'en';
|
|
508
712
|
const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
|
|
509
|
-
const permitJoinBtn = dev.
|
|
510
|
-
const device_queryBtn = dev.
|
|
713
|
+
const permitJoinBtn = dev.info?.device?.type == 'EndDevice' || dev.common.type == 'group' ? '' : `<div class="col tool"><button name="joinCard" class="waves-effect btn-small btn-flat right hoverable green tooltipped" title="open network on device"><i class="material-icons icon-green">leak_add</i></button></div>`;
|
|
714
|
+
const device_queryBtn = dev.info?.device?.type == 'EndDevice' || dev.common.type == 'group' ? '' : `<div class="col tool"><button name="deviceQuery" class="waves-effect btn-small btn-flat right hoverable green tooltipped" title="trigger device query"><i class="material-icons icon-green">play_for_work</i></button></div>`;
|
|
511
715
|
const rid = id.split('.').join('_');
|
|
512
716
|
const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
|
|
717
|
+
const NoInterviewIcon = (dev.info?.device?.interviewstate != 'SUCCESSFUL' && dev.common.type != 'group') ? `<div class="col tool"><i class="material-icons icon-red">perm_device_information</i></div>` : ``;
|
|
513
718
|
const image = `<img src="${img_src}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
|
|
514
719
|
nwk = (dev.info && dev.info.device) ? dev.info.device.nwk : undefined,
|
|
515
720
|
battery_cls = getBatteryCls(dev.battery),
|
|
@@ -574,6 +779,7 @@ function getDashCard(dev, groupImage, groupstatus) {
|
|
|
574
779
|
</span>
|
|
575
780
|
<div class="flip">
|
|
576
781
|
<span class="top right small" style="border-radius: 50%">
|
|
782
|
+
${NoInterviewIcon}
|
|
577
783
|
${idleTime}
|
|
578
784
|
${battery}
|
|
579
785
|
${lq}
|
|
@@ -719,7 +925,7 @@ function showDevInfo(id) {
|
|
|
719
925
|
// section Confirmations
|
|
720
926
|
//
|
|
721
927
|
////
|
|
722
|
-
function deleteConfirmation(id, name) {
|
|
928
|
+
function deleteConfirmation(id, name, dev, model) {
|
|
723
929
|
const text = translateWord('Do you really want to delete device') + ' "' + name + '" (' + id + ')?';
|
|
724
930
|
$('#modaldelete').find('p').text(text);
|
|
725
931
|
$('#force').prop('checked', false);
|
|
@@ -727,7 +933,7 @@ function deleteConfirmation(id, name) {
|
|
|
727
933
|
$('#modaldelete a.btn[name=\'yes\']').unbind('click');
|
|
728
934
|
$('#modaldelete a.btn[name=\'yes\']').click(() => {
|
|
729
935
|
const force = $('#force').prop('checked');
|
|
730
|
-
deleteZigbeeDevice(id, force);
|
|
936
|
+
deleteZigbeeDevice(id, force, dev, model);
|
|
731
937
|
});
|
|
732
938
|
$('#modaldelete').modal('open');
|
|
733
939
|
Materialize.updateTextFields();
|
|
@@ -782,7 +988,7 @@ function EndPointIDfromEndPoint(ep) {
|
|
|
782
988
|
|
|
783
989
|
|
|
784
990
|
|
|
785
|
-
function
|
|
991
|
+
function editGroupMembers(id, name) {
|
|
786
992
|
|
|
787
993
|
function updateGroupables(groupables) {
|
|
788
994
|
const html = [];
|
|
@@ -849,8 +1055,8 @@ function GenerateGroupChange(oldmembers, newmembers) {
|
|
|
849
1055
|
return grpchng;
|
|
850
1056
|
}
|
|
851
1057
|
|
|
852
|
-
function deleteZigbeeDevice(id, force) {
|
|
853
|
-
sendToWrapper(namespace, 'deleteZigbeeDevice', {id: id, force: force}, function (msg) {
|
|
1058
|
+
function deleteZigbeeDevice(id, force, devOpts, modelOpts) {
|
|
1059
|
+
sendToWrapper(namespace, 'deleteZigbeeDevice', {id: id, force: force, dev:devOpts, model:modelOpts}, function (msg) {
|
|
854
1060
|
closeWaitingDialog();
|
|
855
1061
|
if (msg) {
|
|
856
1062
|
if (msg.error) {
|
|
@@ -860,7 +1066,7 @@ function deleteZigbeeDevice(id, force) {
|
|
|
860
1066
|
}
|
|
861
1067
|
}
|
|
862
1068
|
});
|
|
863
|
-
showWaitingDialog('Device is being removed',
|
|
1069
|
+
showWaitingDialog('Device is being removed', 30);
|
|
864
1070
|
}
|
|
865
1071
|
|
|
866
1072
|
|
|
@@ -966,6 +1172,7 @@ function showDevices() {
|
|
|
966
1172
|
return room;
|
|
967
1173
|
}
|
|
968
1174
|
}).filter((item) => item != undefined));
|
|
1175
|
+
//console.warn(`rooms is ${JSON.stringify(allRooms)}`);
|
|
969
1176
|
const roomSelector = $('#room-filter');
|
|
970
1177
|
roomSelector.empty();
|
|
971
1178
|
roomSelector.append(`<li class="device-order-item" data-type="All" tabindex="0"><a class="translate" data-lang="All">All</a></li>`);
|
|
@@ -988,12 +1195,14 @@ function showDevices() {
|
|
|
988
1195
|
|
|
989
1196
|
const element = $('#devices');
|
|
990
1197
|
|
|
991
|
-
if (
|
|
1198
|
+
if ($('tab-main')) try {
|
|
992
1199
|
shuffleInstance = devices && devices.length ? new Shuffle(element, {
|
|
993
1200
|
itemSelector: '.device',
|
|
994
1201
|
sizer: '.js-shuffle-sizer',
|
|
995
1202
|
}) : undefined;
|
|
996
1203
|
doFilter();
|
|
1204
|
+
} catch {
|
|
1205
|
+
// empty.
|
|
997
1206
|
}
|
|
998
1207
|
|
|
999
1208
|
const getDevName = function (dev_block) {
|
|
@@ -1015,7 +1224,7 @@ function showDevices() {
|
|
|
1015
1224
|
const dev_block = $(this).parents('div.device');
|
|
1016
1225
|
const id = getDevId(dev_block);
|
|
1017
1226
|
const name = getDevName(dev_block);
|
|
1018
|
-
|
|
1227
|
+
editGroupMembers(id, name);
|
|
1019
1228
|
});
|
|
1020
1229
|
$('.card-reveal-buttons button[name=\'swapdebug\']').click(function () {
|
|
1021
1230
|
const dev_block = $(this).parents('div.device');
|
|
@@ -1027,7 +1236,7 @@ function showDevices() {
|
|
|
1027
1236
|
$('.card-reveal-buttons button[name=\'swapimage\']').click(function () {
|
|
1028
1237
|
const dev_block = $(this).parents('div.device');
|
|
1029
1238
|
const id = getDevId(dev_block);
|
|
1030
|
-
|
|
1239
|
+
editDeviceOptions(id, false);
|
|
1031
1240
|
});
|
|
1032
1241
|
|
|
1033
1242
|
$('.card-reveal-buttons button[name=\'editgrp\']').click(function () {
|
|
@@ -1048,12 +1257,6 @@ function showDevices() {
|
|
|
1048
1257
|
sendTo(namespace, 'setState', {id: `${getDevId(dev_block)}.device_query`, val: true}, function (data) {
|
|
1049
1258
|
//console.log(data);
|
|
1050
1259
|
}); });
|
|
1051
|
-
$('#modalpairing a.btn[name=\'extendpairing\']').click(function () {
|
|
1052
|
-
openNetwork();
|
|
1053
|
-
});
|
|
1054
|
-
$('#modalpairing a.btn[name=\'endpairing\']').click(function () {
|
|
1055
|
-
stopPairing();
|
|
1056
|
-
});
|
|
1057
1260
|
$('.card-reveal-buttons button[name=\'info\']').click(function () {
|
|
1058
1261
|
const dev_block = $(this).parents('div.device');
|
|
1059
1262
|
showDevInfo(getDevId(dev_block));
|
|
@@ -1146,22 +1349,22 @@ function letsPairingWithCode(code) {
|
|
|
1146
1349
|
}
|
|
1147
1350
|
|
|
1148
1351
|
function openNetwork() {
|
|
1149
|
-
messages = [];
|
|
1150
1352
|
sendToWrapper(namespace, 'letsPairing', {stop:false}, function (msg) {
|
|
1151
1353
|
if (msg && msg.error) {
|
|
1152
1354
|
showMessage(msg.error, _('Error'));
|
|
1153
1355
|
}
|
|
1154
|
-
else showPairingProcess();
|
|
1356
|
+
//else showPairingProcess();
|
|
1155
1357
|
});
|
|
1156
1358
|
}
|
|
1157
1359
|
|
|
1158
1360
|
function stopPairing() {
|
|
1159
|
-
messages = [];
|
|
1160
1361
|
sendToWrapper(namespace, 'letsPairing', {stop:true}, function (msg) {
|
|
1161
1362
|
if (msg && msg.error) {
|
|
1162
1363
|
showMessage(msg.error, _('Error'));
|
|
1163
1364
|
}
|
|
1164
1365
|
});
|
|
1366
|
+
$('#pairing').html('<i class="material-icons">leak_add</i>');
|
|
1367
|
+
|
|
1165
1368
|
}
|
|
1166
1369
|
|
|
1167
1370
|
function touchlinkReset() {
|
|
@@ -1226,21 +1429,24 @@ async function toggleDebugDevice(id) {
|
|
|
1226
1429
|
}
|
|
1227
1430
|
|
|
1228
1431
|
function updateLocalConfigItems(device, data, global) {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1432
|
+
if (data != {})
|
|
1433
|
+
sendToWrapper(namespace, 'updateLocalConfigItems', {target: device, data:data, global:global}, function(msg) {
|
|
1434
|
+
if (msg && msg.hasOwnProperty.error) {
|
|
1435
|
+
showMessage(msg.error, _('Error'));
|
|
1436
|
+
}
|
|
1437
|
+
getDevices();
|
|
1438
|
+
});
|
|
1235
1439
|
}
|
|
1236
1440
|
|
|
1237
|
-
async function
|
|
1441
|
+
async function editDeviceOptions(id, isModel) {
|
|
1442
|
+
//console.warn(`selectImageOverride on ${JSON.stringify(id)}`);
|
|
1238
1443
|
|
|
1239
1444
|
// start local functions
|
|
1240
1445
|
function removeOption(k) {
|
|
1446
|
+
const model = dialogData.model;
|
|
1241
1447
|
if (k && device_options.hasOwnProperty(k)) {
|
|
1242
|
-
if (
|
|
1243
|
-
availableOptions.push(device_options[k].key)
|
|
1448
|
+
if (device_options[k].key === 'legacy' || (model && model.options && model.options.includes(device_options[k].key)) && !device_options[k].isCustom)
|
|
1449
|
+
dialogData.availableOptions.push(device_options[k].key)
|
|
1244
1450
|
delete device_options[k];
|
|
1245
1451
|
}
|
|
1246
1452
|
}
|
|
@@ -1253,12 +1459,15 @@ async function selectImageOverride(id) {
|
|
|
1253
1459
|
key = `o${idx++}`;
|
|
1254
1460
|
}
|
|
1255
1461
|
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);
|
|
1462
|
+
device_options[key] = { key:optionName, value:'', isCustom:optionName==='custom', expose:getExposeFromOptions(optionName)};
|
|
1463
|
+
idx = dialogData.availableOptions.indexOf(optionName);
|
|
1464
|
+
if (idx > -1 && !device_options[key].isCustom) dialogData.availableOptions.splice(idx, 1);
|
|
1465
|
+
console.warn(`addOption added ${JSON.stringify(device_options)}`)
|
|
1259
1466
|
}
|
|
1260
1467
|
|
|
1468
|
+
|
|
1261
1469
|
function updateOptions(candidates) {
|
|
1470
|
+
console.warn(`update Options with ${JSON.stringify(candidates)}`)
|
|
1262
1471
|
if (candidates.length > 0) {
|
|
1263
1472
|
$('#chooseimage').find('.new_options_available').removeClass('hide');
|
|
1264
1473
|
list2select('#option_Selector', candidates, [], (key, val) => { return val; }, (key, val) => { return val; })
|
|
@@ -1267,54 +1476,123 @@ async function selectImageOverride(id) {
|
|
|
1267
1476
|
$('#chooseimage').find('.new_options_available').addClass('hide');
|
|
1268
1477
|
}
|
|
1269
1478
|
const html_options=[];
|
|
1479
|
+
const checkboxButtons = [];
|
|
1270
1480
|
|
|
1271
1481
|
for (const k of Object.keys(device_options)) {
|
|
1482
|
+
const expose = device_options[k].expose === undefined ? getExposeFromOptions(device_options[k].key) : device_options[k].expose;
|
|
1483
|
+
const disabled = device_options[k]?.isCustom ? '' : 'disabled ';
|
|
1484
|
+
console.warn(`option for ${k} is ${JSON.stringify(device_options[k])}`);
|
|
1272
1485
|
html_options.push(`<div class="row">`);
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1486
|
+
switch (expose.type) {
|
|
1487
|
+
case 'numeric':
|
|
1488
|
+
html_options.push(`<div class="input-field col s5 m5 l5"><input ${disabled}id="option_key_${k}" type="text" class="value" /><label for="option_key_${k}">Option</label></div>`)
|
|
1489
|
+
html_options.push(`<div class="input-field col s5 m5 l5"><input id="option_value_${k}" type="number"${expose.value_min != undefined ? ' min="'+expose.value_min+'"' : ''}${expose.value_max != undefined ? ' max="'+expose.value_max+'"' : ''}${expose.value_step != undefined ? ' step="'+expose.value_step+'"' : ''} class="value" /><label>${expose.label ? expose.label : 'Value'}</label></div>`)
|
|
1490
|
+
break;
|
|
1491
|
+
case 'binary': {
|
|
1492
|
+
html_options.push(`<div class="input-field col s5 m5 l5">
|
|
1493
|
+
<input ${disabled}id="option_key_${k}" type="text" class="value" />
|
|
1494
|
+
<label for="option_key_${k}">Option</label></div>`);
|
|
1495
|
+
const dok = device_options[k];
|
|
1496
|
+
if (dok.vOn=== undefined) dok.vOn = (expose.value_on === undefined ? 'true' : String(expose.value_on));
|
|
1497
|
+
if (dok.vOff=== undefined) dok.vOff = (expose.value_off === undefined ? 'false' : String(expose.value_off));
|
|
1498
|
+
if (dok.value != dok.vOn) dok.value = dok.vOff;
|
|
1499
|
+
html_options.push(`<div class="col s5 m5 l5"><a id="option_value_${k}" class="btn-large value">${dok.value}</a></div>`);
|
|
1500
|
+
checkboxButtons.push(`option_value_${k}`);
|
|
1501
|
+
break;
|
|
1502
|
+
}
|
|
1503
|
+
default:
|
|
1504
|
+
html_options.push(`<div class="input-field col s5 m5 l5"><input ${disabled}id="option_key_${k}" type="text" class="value" /><label for="option_key_${k}">Option</label></div>`)
|
|
1505
|
+
html_options.push(`<div class="input-field col s5 m5 l5"><input id="option_value_${k}" type="text" class="value" /><label for="option_value_${k}">${expose.label ? expose.label : 'Value'}</label></div>`)
|
|
1506
|
+
break;
|
|
1507
|
+
}
|
|
1508
|
+
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
1509
|
html_options.push(`</div>`)
|
|
1277
1510
|
}
|
|
1278
1511
|
$('#chooseimage').find('.options_grid').html(html_options.join(''));
|
|
1512
|
+
for (const item of checkboxButtons) {
|
|
1513
|
+
$(`#${item}`).unbind('click');
|
|
1514
|
+
$(`#${item}`).click(() => {
|
|
1515
|
+
const key = item.replace('option_value_', '');
|
|
1516
|
+
const dok = device_options[key];
|
|
1517
|
+
const oval = $(`#option_value_${key}`).html();
|
|
1518
|
+
const val = $(`#option_value_${key}`).html()=== dok.vOn ? dok.vOff : dok.vOn;
|
|
1519
|
+
dok.value = val;
|
|
1520
|
+
console.warn(`${item} clicked: ${JSON.stringify(dok)} => ${val} from ${oval}`);
|
|
1521
|
+
$(`#${item}`).html(val);
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1279
1525
|
if (html_options.length > 0) {
|
|
1280
1526
|
for (const k of Object.keys(device_options)) {
|
|
1527
|
+
if (device_options[k].isCustom) $(`#option_key_${k}`).removeClass('disabled')
|
|
1281
1528
|
$(`#option_key_${k}`).val(device_options[k].key);
|
|
1282
|
-
|
|
1529
|
+
if (device_options[k].expose?.type != 'binary') {
|
|
1530
|
+
const value = $(`#option_value_${k}.value`);
|
|
1531
|
+
/* if (value.attr('type') === 'checkbox') {
|
|
1532
|
+
console.warn(`oval for ${k} : ${device_options[k].value}`);
|
|
1533
|
+
value.prop('checked', Boolean(device_options[k].value));
|
|
1534
|
+
}
|
|
1535
|
+
else*/
|
|
1536
|
+
value.val(device_options[k].value);
|
|
1537
|
+
}
|
|
1283
1538
|
$(`#option_rem_${k}`).unbind('click');
|
|
1284
|
-
$(`#option_rem_${k}`).click(() => {
|
|
1539
|
+
$(`#option_rem_${k}`).click(() => {
|
|
1540
|
+
removeOption(k);
|
|
1541
|
+
updateOptions(dialogData.availableOptions);
|
|
1542
|
+
});
|
|
1285
1543
|
}
|
|
1286
1544
|
}
|
|
1287
1545
|
}
|
|
1288
1546
|
|
|
1547
|
+
function getExposeFromOptions(option) {
|
|
1548
|
+
const rv = dialogData.model.optionExposes.find((expose) => expose.name === option);
|
|
1549
|
+
console.warn(`GEFO: ${option} results in ${JSON.stringify(rv)}`);
|
|
1550
|
+
if (rv) return rv;
|
|
1551
|
+
return { type:option === 'legacy' ? 'binary' : 'string' };
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1289
1554
|
function getOptionsFromUI(_do, _so) {
|
|
1290
1555
|
const _no = {};
|
|
1291
1556
|
let changed = false;
|
|
1557
|
+
console.warn(`${changed} : ${JSON.stringify(_do)} - ${JSON.stringify(_no)}`)
|
|
1292
1558
|
for (const k of Object.keys(_do)) {
|
|
1293
1559
|
const key = $(`#option_key_${k}`).val();
|
|
1294
|
-
_do[k].key = key;
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1560
|
+
if (_do[k].isCustom) _do[k].key = key;
|
|
1561
|
+
else if (_do[k].key != key) {
|
|
1562
|
+
console.warn(`_illegal Keys: ${key}, ${_do[k].key}`)
|
|
1563
|
+
continue;
|
|
1298
1564
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1565
|
+
console.warn(`_legal Keys: ${key}, ${_do[k].key}`)
|
|
1566
|
+
if (_do[k].expose?.type === 'binary') {
|
|
1567
|
+
_do[k].value = $(`#option_value_${k}`).html();
|
|
1301
1568
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1569
|
+
else
|
|
1570
|
+
{
|
|
1571
|
+
const val = $(`#option_value_${k}`).val();
|
|
1572
|
+
try {
|
|
1573
|
+
_do[k].value = JSON.parse(val);
|
|
1574
|
+
}
|
|
1575
|
+
catch {
|
|
1576
|
+
_do[k].value = val;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
if (_do[k].key.length > 0) {
|
|
1580
|
+
console.warn(`dok: ${_do[k].key} : ${_do[k].value}`);
|
|
1581
|
+
_no[key] = _do[k].value;
|
|
1582
|
+
changed |= (_no[key] != _so[key]);
|
|
1305
1583
|
}
|
|
1306
1584
|
}
|
|
1307
1585
|
changed |= (Object.keys(_no).length != Object.keys(_so).length);
|
|
1308
|
-
console.warn(`${changed} : ${JSON.stringify(
|
|
1586
|
+
console.warn(`${changed ? 'changed': 'unchanged'} : ${JSON.stringify(_so)} - ${JSON.stringify(_no)}`)
|
|
1309
1587
|
if (changed) return _no;
|
|
1310
1588
|
return undefined;
|
|
1311
1589
|
}
|
|
1312
1590
|
|
|
1313
|
-
function updateImageSelection(
|
|
1314
|
-
const default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
|
|
1315
|
-
if (
|
|
1316
|
-
imagedata.unshift( { file:'none', name:'default', data:
|
|
1317
|
-
imagedata.unshift( { file:'current', name:'current', data:
|
|
1591
|
+
function updateImageSelection(dData, imagedata) {
|
|
1592
|
+
// const default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
|
|
1593
|
+
if (dData.legacyIcon) imagedata.unshift( { file:dData.legacyIcon, name:'legacy', data:dData.legacyIcon});
|
|
1594
|
+
imagedata.unshift( { file:'none', name:'default', data:dData.defaultIcon});
|
|
1595
|
+
imagedata.unshift( { file:'current', name:'current', data:dData.icon});
|
|
1318
1596
|
|
|
1319
1597
|
list2select('#images', imagedata, selectItems,
|
|
1320
1598
|
function (key, image) {
|
|
@@ -1337,39 +1615,65 @@ async function selectImageOverride(id) {
|
|
|
1337
1615
|
const device_options = {};
|
|
1338
1616
|
const received_options = {};
|
|
1339
1617
|
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1618
|
+
const dialogData = {};
|
|
1619
|
+
|
|
1620
|
+
if (isModel) {
|
|
1621
|
+
const model = id.model;
|
|
1622
|
+
dialogData.model = model;
|
|
1623
|
+
dialogData.availableOptions = model.options.slice() || [];
|
|
1624
|
+
dialogData.availableOptions.push('custom');
|
|
1625
|
+
if (model.hasLegacyDef) dialogData.availableOptions.push('legacy');
|
|
1626
|
+
dialogData.setOptions = {};
|
|
1627
|
+
for (const k in Object.keys(id.setOptions))
|
|
1628
|
+
if (k == 'icon' || k == 'name') continue;
|
|
1629
|
+
else dialogData.setOptions[k] = id.setOptions[k];
|
|
1630
|
+
dialogData.name = id.setOptions.name || id.name || 'unset';
|
|
1631
|
+
dialogData.icon = id.setOptions.icon || model.icon || 'img/dummyDevice.jpg';
|
|
1632
|
+
dialogData.legacyIcon = id.devices[0].legacyIcon;
|
|
1633
|
+
id = id.model.model;
|
|
1634
|
+
} else
|
|
1635
|
+
{
|
|
1636
|
+
const dev = devices.find((d) => d._id == id);
|
|
1637
|
+
dialogData.model = dev.info.mapped;
|
|
1638
|
+
dialogData.availableOptions = (dev.info.mapped ? dev.info.mapped.options.slice() || []:[]);
|
|
1639
|
+
dialogData.name = dev.common.name;
|
|
1640
|
+
dialogData.icon = dev.common.icon || dev.icon;
|
|
1641
|
+
dialogData.default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
|
|
1642
|
+
dialogData.legacyIcon = dev.legacyIcon;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
const imghtml = `<img src="${dialogData.icon}" width="80px">`;
|
|
1343
1646
|
//console.error(imghtml)
|
|
1344
1647
|
const selectItems= [''];
|
|
1345
|
-
$('#chooseimage').find('input[id=\'d_name\']').val(
|
|
1648
|
+
$('#chooseimage').find('input[id=\'d_name\']').val(dialogData.name);
|
|
1346
1649
|
$('#chooseimage').find('.currentIcon').html(imghtml);
|
|
1347
1650
|
$('#option_add_1084').unbind('click');
|
|
1348
1651
|
$('#option_add_1084').click(() => {
|
|
1349
1652
|
getOptionsFromUI(device_options, received_options);
|
|
1350
1653
|
addOption();
|
|
1351
|
-
updateOptions(availableOptions)
|
|
1654
|
+
updateOptions(dialogData.availableOptions);
|
|
1352
1655
|
});
|
|
1353
1656
|
|
|
1354
1657
|
|
|
1355
1658
|
|
|
1356
1659
|
sendToWrapper(namespace, 'getLocalImages', {}, function(msg) {
|
|
1357
1660
|
if (msg && msg.imageData) {
|
|
1358
|
-
updateImageSelection(
|
|
1661
|
+
updateImageSelection(dialogData , msg.imageData);
|
|
1359
1662
|
|
|
1360
1663
|
$('#chooseimage a.btn[name=\'save\']').unbind('click');
|
|
1361
1664
|
$('#chooseimage a.btn[name=\'save\']').click(() => {
|
|
1362
1665
|
const image = $('#chooseimage').find('#images option:selected').val();
|
|
1363
|
-
const global = $('#chooseimage').find('#globaloverride').prop('checked');
|
|
1666
|
+
//const global = $('#chooseimage').find('#globaloverride').prop('checked');
|
|
1364
1667
|
const name = $('#chooseimage').find('input[id=\'d_name\']').val();
|
|
1365
1668
|
const data = {};
|
|
1366
1669
|
if (image != 'current') data.icon= image;
|
|
1367
|
-
if (name !=
|
|
1368
|
-
|
|
1670
|
+
if (name != dialogData.name) data.name = name;
|
|
1671
|
+
const changedOptions = getOptionsFromUI(device_options, received_options);
|
|
1672
|
+
if (changedOptions != undefined) data.options = changedOptions;
|
|
1369
1673
|
|
|
1370
|
-
updateLocalConfigItems(id, data,
|
|
1674
|
+
updateLocalConfigItems(id, data, isModel);
|
|
1371
1675
|
});
|
|
1372
|
-
sendToWrapper(namespace, 'getLocalConfigItems', { target:id, global:
|
|
1676
|
+
sendToWrapper(namespace, 'getLocalConfigItems', { target:id, global:isModel, key:'options' }, function (msg) {
|
|
1373
1677
|
if (msg) {
|
|
1374
1678
|
if (msg.error) showMessage(msg.error, '_Error');
|
|
1375
1679
|
Object.keys(device_options).forEach(key => delete device_options[key]);
|
|
@@ -1378,15 +1682,15 @@ async function selectImageOverride(id) {
|
|
|
1378
1682
|
let cnt = 1;
|
|
1379
1683
|
for (const key in msg.options)
|
|
1380
1684
|
{
|
|
1381
|
-
const idx = availableOptions.indexOf(key);
|
|
1685
|
+
const idx = dialogData.availableOptions.indexOf(key);
|
|
1382
1686
|
console.warn(`key ${key} : index : ${idx}`);
|
|
1383
|
-
if (idx > -1) availableOptions.splice(idx,1);
|
|
1687
|
+
if (idx > -1) dialogData.availableOptions.splice(idx,1);
|
|
1384
1688
|
received_options[key]=msg.options[key];
|
|
1385
1689
|
device_options[`o${cnt}`] = { key:key, value:msg.options[key]}
|
|
1386
1690
|
cnt++;
|
|
1387
1691
|
}
|
|
1388
1692
|
}
|
|
1389
|
-
updateOptions(availableOptions);
|
|
1693
|
+
updateOptions(dialogData.availableOptions);
|
|
1390
1694
|
} else showMessage('callback without message');
|
|
1391
1695
|
$('#chooseimage').modal('open');
|
|
1392
1696
|
Materialize.updateTextFields();
|
|
@@ -1503,6 +1807,7 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
|
|
|
1503
1807
|
}
|
|
1504
1808
|
|
|
1505
1809
|
function displayDebugMessages(msg) {
|
|
1810
|
+
console.warn('displayDebugMessages called with '+ JSON.stringify(msg));
|
|
1506
1811
|
const buttonNames = [];
|
|
1507
1812
|
const idButtons = [];
|
|
1508
1813
|
if (msg.byId) {
|
|
@@ -1692,55 +1997,33 @@ function getDevices() {
|
|
|
1692
1997
|
coordinatorinfo = msg;
|
|
1693
1998
|
updateStartButton()
|
|
1694
1999
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
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
|
-
}
|
|
1735
|
-
updateStartButton();
|
|
1736
|
-
showDevices();
|
|
1737
|
-
showLocalData();
|
|
1738
|
-
UpdateAdapterAlive(true)
|
|
2000
|
+
});
|
|
2001
|
+
sendToWrapper(namespace, 'getLocalConfigItems', {getAllData:true}, function(msg) {
|
|
2002
|
+
if (msg.hasOwnProperty('by_id') && msg.hasOwnProperty('by_model'))
|
|
2003
|
+
localConfigData = msg;
|
|
2004
|
+
})
|
|
2005
|
+
sendToWrapper(namespace, 'getDevices', {}, function (msg) {
|
|
2006
|
+
if (msg) {
|
|
2007
|
+
extractDevicesData(msg);
|
|
2008
|
+
if (msg.error) {
|
|
2009
|
+
errorData.push(msg.error);
|
|
2010
|
+
isHerdsmanRunning = false;
|
|
2011
|
+
} else {
|
|
2012
|
+
isHerdsmanRunning = true;
|
|
2013
|
+
getBinding();
|
|
1739
2014
|
}
|
|
1740
|
-
|
|
2015
|
+
updateStartButton();
|
|
2016
|
+
displayDebugMessages(debugMessages);
|
|
2017
|
+
showDevices();
|
|
2018
|
+
LocalDataDisplayValues.sortedKeys = Object.keys(models);
|
|
2019
|
+
showLocalData();
|
|
2020
|
+
UpdateAdapterAlive(true)
|
|
2021
|
+
}
|
|
1741
2022
|
});
|
|
1742
2023
|
}
|
|
1743
2024
|
|
|
2025
|
+
|
|
2026
|
+
|
|
1744
2027
|
if (lockout.timeoutid) {
|
|
1745
2028
|
clearTimeout(lockout.timeoutid);
|
|
1746
2029
|
}
|
|
@@ -1753,6 +2036,38 @@ function getDevices() {
|
|
|
1753
2036
|
|
|
1754
2037
|
}
|
|
1755
2038
|
|
|
2039
|
+
function extractDevicesData(msg) {
|
|
2040
|
+
devices = msg.devices ? msg.devices : [];
|
|
2041
|
+
// check if stashed error messages are sent alongside
|
|
2042
|
+
if (msg.clean)
|
|
2043
|
+
$('#state_cleanup_btn').removeClass('hide');
|
|
2044
|
+
else
|
|
2045
|
+
$('#state_cleanup_btn').addClass('hide');
|
|
2046
|
+
if (msg.errors && msg.errors.length > 0) {
|
|
2047
|
+
$('#show_errors_btn').removeClass('hide');
|
|
2048
|
+
errorData = msg.errors;
|
|
2049
|
+
}
|
|
2050
|
+
else {
|
|
2051
|
+
$('#show_errors_btn').addClass('hide');
|
|
2052
|
+
}
|
|
2053
|
+
//check if debug messages are sent alongside
|
|
2054
|
+
if (msg && typeof (msg.debugDevices == 'array')) {
|
|
2055
|
+
debugDevices = msg.debugDevices;
|
|
2056
|
+
}
|
|
2057
|
+
else
|
|
2058
|
+
debugDevices = [];
|
|
2059
|
+
|
|
2060
|
+
if (msg.deviceDebugData) {
|
|
2061
|
+
debugMessages = { byId: msg.deviceDebugData };
|
|
2062
|
+
displayDebugMessages(debugMessages);
|
|
2063
|
+
}
|
|
2064
|
+
if (msg.models) models = msg.models;
|
|
2065
|
+
lockout.isActive = false;
|
|
2066
|
+
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
|
|
2070
|
+
|
|
1756
2071
|
function getNamedColors() {
|
|
1757
2072
|
sendToWrapper(namespace, 'getNamedColors', {}, function(msg) {
|
|
1758
2073
|
if (msg && typeof msg.colors) {
|
|
@@ -1862,7 +2177,7 @@ function load(settings, onChange) {
|
|
|
1862
2177
|
getComPorts(onChange);
|
|
1863
2178
|
|
|
1864
2179
|
//dialog = new MatDialog({EndingTop: '50%'});
|
|
1865
|
-
const keepAliveHandle = startKeepalive();
|
|
2180
|
+
//const keepAliveHandle = startKeepalive();
|
|
1866
2181
|
keepAlive(() => {
|
|
1867
2182
|
getDevices();
|
|
1868
2183
|
getNamedColors();
|
|
@@ -1871,6 +2186,10 @@ function load(settings, onChange) {
|
|
|
1871
2186
|
groups = data.groups || {};
|
|
1872
2187
|
//showGroups();
|
|
1873
2188
|
});
|
|
2189
|
+
sendToWrapper(namespace, 'getLibData', {key: 'cidList'}, function (data) {
|
|
2190
|
+
cidList = data.list;
|
|
2191
|
+
});
|
|
2192
|
+
|
|
1874
2193
|
})
|
|
1875
2194
|
|
|
1876
2195
|
//getDebugMessages();
|
|
@@ -1924,8 +2243,10 @@ function load(settings, onChange) {
|
|
|
1924
2243
|
});
|
|
1925
2244
|
$('#pairing').click(function () {
|
|
1926
2245
|
if (!$('#pairing').hasClass('pulse')) {
|
|
2246
|
+
messages = [];
|
|
1927
2247
|
openNetwork();
|
|
1928
|
-
}
|
|
2248
|
+
}
|
|
2249
|
+
showPairingProcess();
|
|
1929
2250
|
});
|
|
1930
2251
|
|
|
1931
2252
|
$('#refresh').click(function () {
|
|
@@ -1940,6 +2261,10 @@ function load(settings, onChange) {
|
|
|
1940
2261
|
resetConfirmation();
|
|
1941
2262
|
});
|
|
1942
2263
|
|
|
2264
|
+
$('#restore-backup-btn').click(function () {
|
|
2265
|
+
selectBackup();
|
|
2266
|
+
});
|
|
2267
|
+
|
|
1943
2268
|
$('#deleteNVRam-btn').click(function () {
|
|
1944
2269
|
deleteNvBackupConfirmation();
|
|
1945
2270
|
});
|
|
@@ -2007,6 +2332,65 @@ function load(settings, onChange) {
|
|
|
2007
2332
|
$('#device-filter-btn').text($(this).text());
|
|
2008
2333
|
doFilter();
|
|
2009
2334
|
});
|
|
2335
|
+
$('#model-search').keyup(function (event) {
|
|
2336
|
+
LocalDataDisplayValues.searchVal = event.target.value.toLowerCase();
|
|
2337
|
+
if (!LocalDataDisplayValues.searchTimeout)
|
|
2338
|
+
LocalDataDisplayValues.searchTimeout = setTimeout(() => { LocalDataDisplayValues.searchTimeout = null; showLocalData(); }, 250);
|
|
2339
|
+
});
|
|
2340
|
+
$('#model-sort a').click(function () {
|
|
2341
|
+
const t = $(this).text();
|
|
2342
|
+
$('#model-sort-btn').text(t);
|
|
2343
|
+
switch (t) {
|
|
2344
|
+
case 'by type':
|
|
2345
|
+
LocalDataDisplayValues.sortMethod = function(a,b) {
|
|
2346
|
+
if (models[a].model?.type == models[b].model?.type) return (models[a].model?.model > models[b].model?.model ? 1 : -1);
|
|
2347
|
+
return (models[a].model?.type > models[b].model?.type ? 1 : -1);
|
|
2348
|
+
};
|
|
2349
|
+
break;
|
|
2350
|
+
|
|
2351
|
+
case 'by device count':
|
|
2352
|
+
LocalDataDisplayValues.sortMethod = function(a,b) {
|
|
2353
|
+
if (models[a].setOptions?.length == models[b].setOptions?.length) return (models[a].model?.model > models[b].model?.model ? 1 : -1);
|
|
2354
|
+
return (models[a].setOptions?.length > models[b].setOptions?.length?1:-1);
|
|
2355
|
+
};
|
|
2356
|
+
break;
|
|
2357
|
+
case 'by option count':
|
|
2358
|
+
LocalDataDisplayValues.sortMethod = function(a,b) {
|
|
2359
|
+
if (models[a].devices?.length == models[b].devices?.length) return (models[a].model?.model > models[b].model?.model ? 1 : -1);
|
|
2360
|
+
return (models[a].devices?.length > models[b].devices?.length ? 1 : -1);
|
|
2361
|
+
};
|
|
2362
|
+
break;
|
|
2363
|
+
default:
|
|
2364
|
+
LocalDataDisplayValues.sortMethod = undefined;
|
|
2365
|
+
}
|
|
2366
|
+
showLocalData();
|
|
2367
|
+
});
|
|
2368
|
+
$('#refresh_models_btn').click(function () {
|
|
2369
|
+
getDevices();
|
|
2370
|
+
});
|
|
2371
|
+
$('#model-filter a').click(function () {
|
|
2372
|
+
const t = $(this).text();
|
|
2373
|
+
$('#model-filter-btn').text(t);
|
|
2374
|
+
switch (t) {
|
|
2375
|
+
case 'Groups':
|
|
2376
|
+
LocalDataDisplayValues.filterMethod = function(a) { return models[a].model.model== 'group'};
|
|
2377
|
+
break;
|
|
2378
|
+
case 'Routers':
|
|
2379
|
+
LocalDataDisplayValues.filterMethod = function(a) { return models[a].model.type == 'Router'};
|
|
2380
|
+
break;
|
|
2381
|
+
case 'End Devices':
|
|
2382
|
+
LocalDataDisplayValues.filterMethod = function(a) { return models[a].model.type == 'EndDevice'};
|
|
2383
|
+
break;
|
|
2384
|
+
case 'with options':
|
|
2385
|
+
LocalDataDisplayValues.filterMethod = function(a) { return models[a].setOptions && Object.keys(models[a].setOptions).length > 0 };
|
|
2386
|
+
break;
|
|
2387
|
+
case 'without options':
|
|
2388
|
+
LocalDataDisplayValues.filterMethod = function(a) { return !(models[a].setOptions && Object.keys(models[a].setOptions).length > 0) };
|
|
2389
|
+
break;
|
|
2390
|
+
default: LocalDataDisplayValues.filterMethod = undefined;
|
|
2391
|
+
}
|
|
2392
|
+
showLocalData();
|
|
2393
|
+
});
|
|
2010
2394
|
});
|
|
2011
2395
|
|
|
2012
2396
|
const text = $('#pairing').attr('data-tooltip');
|
|
@@ -2058,6 +2442,14 @@ function showPairingProcess(noextrabuttons) {
|
|
|
2058
2442
|
dismissible: false
|
|
2059
2443
|
});
|
|
2060
2444
|
|
|
2445
|
+
$('#modalpairing a.btn[name=\'extendpairing\']').unbind('click');
|
|
2446
|
+
$('#modalpairing a.btn[name=\'extendpairing\']').click(function () {
|
|
2447
|
+
openNetwork();
|
|
2448
|
+
});
|
|
2449
|
+
$('#modalpairing a.btn[name=\'endpairing\']').unbind('click');
|
|
2450
|
+
$('#modalpairing a.btn[name=\'endpairing\']').click(function () {
|
|
2451
|
+
stopPairing();
|
|
2452
|
+
});
|
|
2061
2453
|
if (noextrabuttons) {
|
|
2062
2454
|
$('#modalpairing').find('.endpairing').addClass('hide');
|
|
2063
2455
|
$('#modalpairing').find('.extendpairing').addClass('hide');
|
|
@@ -2185,6 +2577,7 @@ socket.emit('subscribeObjects', namespace + '.*');
|
|
|
2185
2577
|
|
|
2186
2578
|
// react to changes
|
|
2187
2579
|
socket.on('stateChange', function (id, state) {
|
|
2580
|
+
UpdateAdapterAlive(true);
|
|
2188
2581
|
// only watch our own states
|
|
2189
2582
|
if (id.substring(0, namespaceLen) !== namespace) return;
|
|
2190
2583
|
if (state) {
|
|
@@ -2220,6 +2613,9 @@ socket.on('stateChange', function (id, state) {
|
|
|
2220
2613
|
isHerdsmanRunning = false;
|
|
2221
2614
|
updateStartButton();
|
|
2222
2615
|
}
|
|
2616
|
+
if (state.val === 'Closing network.') {
|
|
2617
|
+
getDevices();
|
|
2618
|
+
}
|
|
2223
2619
|
}
|
|
2224
2620
|
} else {
|
|
2225
2621
|
const devId = getDevId(id);
|
|
@@ -2247,6 +2643,7 @@ socket.on('stateChange', function (id, state) {
|
|
|
2247
2643
|
|
|
2248
2644
|
|
|
2249
2645
|
socket.on('objectChange', function (id, obj) {
|
|
2646
|
+
UpdateAdapterAlive(true);
|
|
2250
2647
|
if (id.substring(0, namespaceLen) !== namespace) return;
|
|
2251
2648
|
if (obj && obj.type == 'device') { // && obj.common.type !== 'group') {
|
|
2252
2649
|
updateDevice(id);
|
|
@@ -2257,6 +2654,7 @@ socket.on('objectChange', function (id, obj) {
|
|
|
2257
2654
|
if (elems.length === 3) {
|
|
2258
2655
|
removeDevice(id);
|
|
2259
2656
|
showDevices();
|
|
2657
|
+
showLocalData();
|
|
2260
2658
|
}
|
|
2261
2659
|
}
|
|
2262
2660
|
});
|
|
@@ -2324,7 +2722,7 @@ function showNetworkMap(devices, map) {
|
|
|
2324
2722
|
node.label = 'Coordinator';
|
|
2325
2723
|
// delete node.color;
|
|
2326
2724
|
}
|
|
2327
|
-
console.warn(`node for device ${JSON.stringify(node)}`)
|
|
2725
|
+
//console.warn(`node for device ${JSON.stringify(node)}`)
|
|
2328
2726
|
return node;
|
|
2329
2727
|
};
|
|
2330
2728
|
|
|
@@ -3173,6 +3571,42 @@ function resetConfirmation() {
|
|
|
3173
3571
|
});
|
|
3174
3572
|
}
|
|
3175
3573
|
|
|
3574
|
+
|
|
3575
|
+
function selectBackup() {
|
|
3576
|
+
sendToWrapper(namespace, 'listbackups', {}, function(msg) {
|
|
3577
|
+
const candidates = {};
|
|
3578
|
+
for (const fn of msg.files) {
|
|
3579
|
+
const m = fn.matchAll(/backup_([0-9]+)_([0-9]+)_([0-9]+)-([0-9]+)_([0-9]+)_([0-9]+)/gm);
|
|
3580
|
+
console.warn(`m is ${JSON.stringify(m)}`);
|
|
3581
|
+
if (m) {
|
|
3582
|
+
candidates[`${m[3]}.${m[2]}.${m[1]} ${m[4]}:${m[5]}`] = fn;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
console.warn('candidates is ' + JSON.stringify(candidates));
|
|
3586
|
+
list2select('#backup_Selector', msg.files, [], (key, val) => { return val; }, (key, val) => { return val; })
|
|
3587
|
+
$('#modalrestore').modal('open');
|
|
3588
|
+
const btn = $('#modalrestore .modal-content a.btn-large');
|
|
3589
|
+
btn.unbind('click')
|
|
3590
|
+
btn.click(function (e) {
|
|
3591
|
+
const name = $('#backup_Selector').val();
|
|
3592
|
+
console.warn(` filename is ${name}`);
|
|
3593
|
+
$('#modalrestore').modal('close');
|
|
3594
|
+
showWaitingDialog(`Attempting to restore the backup from ${name}`, 180000);
|
|
3595
|
+
const start = Date.now();
|
|
3596
|
+
sendToWrapper(namespace, 'restore', {name}, function(msg) {
|
|
3597
|
+
closeWaitingDialog();
|
|
3598
|
+
if (msg.error) {
|
|
3599
|
+
showMessage(msg.error, _('Error'))
|
|
3600
|
+
}
|
|
3601
|
+
else {
|
|
3602
|
+
const duration = Date.now() - start;
|
|
3603
|
+
showMessage(`Restored configuration from backup after ${duration / 1000} s`, 'Restore successful');
|
|
3604
|
+
}
|
|
3605
|
+
})
|
|
3606
|
+
})
|
|
3607
|
+
})
|
|
3608
|
+
|
|
3609
|
+
}
|
|
3176
3610
|
function showViewConfig() {
|
|
3177
3611
|
$('#modalviewconfig').modal('open');
|
|
3178
3612
|
}
|
|
@@ -3559,14 +3993,13 @@ function genDevInfo(device) {
|
|
|
3559
3993
|
if (value === undefined) {
|
|
3560
3994
|
return '';
|
|
3561
3995
|
} else {
|
|
3562
|
-
|
|
3996
|
+
const label = `${name=='' ? ' ' : name + ':'}`;
|
|
3997
|
+
return `<li><span class="label">${label.replace('_',' ')}</span><span>${value}</span></li>`;
|
|
3563
3998
|
}
|
|
3564
3999
|
};
|
|
3565
4000
|
const genRowValues = function (name, value) {
|
|
3566
|
-
if (value
|
|
3567
|
-
|
|
3568
|
-
} else {
|
|
3569
|
-
let label = `${name}:`;
|
|
4001
|
+
if (Array.isArray(value)) {
|
|
4002
|
+
let label = `${name=='' ? ' ' : name + ':'}`;
|
|
3570
4003
|
try {
|
|
3571
4004
|
return value.map((val) => {
|
|
3572
4005
|
const row = `<li><span class="label">${label}</span><span>${val}</span></li>`;
|
|
@@ -3578,6 +4011,7 @@ function genDevInfo(device) {
|
|
|
3578
4011
|
return `<li><span class="label">${label}</span><span>${JSON.stringify(value)}</span></li>`
|
|
3579
4012
|
}
|
|
3580
4013
|
}
|
|
4014
|
+
else return '';
|
|
3581
4015
|
};
|
|
3582
4016
|
const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(mapped.model)}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
|
|
3583
4017
|
const mappedInfo = [];
|
|
@@ -3589,7 +4023,7 @@ function genDevInfo(device) {
|
|
|
3589
4023
|
if (item == 'model')
|
|
3590
4024
|
mappedInfo.push(genRow(item,modelUrl));
|
|
3591
4025
|
else
|
|
3592
|
-
mappedInfo.push(genRow(item,mapped[item]));
|
|
4026
|
+
if (typeof mapped[item] != 'object') mappedInfo.push(genRow(item,mapped[item]));
|
|
3593
4027
|
}
|
|
3594
4028
|
mappedInfo.push(
|
|
3595
4029
|
` </ul>
|
|
@@ -3611,7 +4045,7 @@ function genDevInfo(device) {
|
|
|
3611
4045
|
const imgSrc = device.icon || device.common.icon;
|
|
3612
4046
|
const imgInfo = (imgSrc) ? `<img src=${imgSrc} width='150px' onerror="this.onerror=null;this.src='img/unavailable.png';"><div class="divider"></div>` : '';
|
|
3613
4047
|
const info =[
|
|
3614
|
-
`<div class="col s12 m6 l6 xl6">
|
|
4048
|
+
`<div class="col ${device.memberinfo != undefined ? 's12 m12 l12 xl12':'s12 m6 l6 xl6'}">
|
|
3615
4049
|
${imgInfo}
|
|
3616
4050
|
${mappedInfo.join('')}
|
|
3617
4051
|
<div class="divider"></div>
|
|
@@ -3620,8 +4054,19 @@ function genDevInfo(device) {
|
|
|
3620
4054
|
for (const item in dev) {
|
|
3621
4055
|
info.push(genRow(item, dev[item]));
|
|
3622
4056
|
}
|
|
3623
|
-
|
|
3624
|
-
|
|
4057
|
+
if (device.memberinfo != undefined) {
|
|
4058
|
+
const memberCount = (device.memberinfo.length);
|
|
4059
|
+
if (memberCount != 1) info.push(genRow(`Members`, `${memberCount}`));
|
|
4060
|
+
for (let m = 0; m < device.memberinfo.length; m++) {
|
|
4061
|
+
const dev = getDeviceByIEEE(device.memberinfo[m].ieee);
|
|
4062
|
+
const epid = device.memberinfo[m].epid;
|
|
4063
|
+
const epname = Array.isArray(dev.info.endpoints) ? `:${dev.info.endpoints.find((item) => item.ID == epid)?.epName}` : undefined;
|
|
4064
|
+
info.push(genRow(`Member${memberCount > 1 ? ' ' + (m+1) : ''}`, `${device.memberinfo[m].device}${epname ? epname : ''} - ${device.memberinfo[m].ieee}.${epid}`));
|
|
4065
|
+
}
|
|
4066
|
+
info.push(`</div>
|
|
4067
|
+
</div>`);
|
|
4068
|
+
}
|
|
4069
|
+
else info.push(`${genRow('configured', (device.isConfigured), true)}</ul>
|
|
3625
4070
|
</div>
|
|
3626
4071
|
</div>
|
|
3627
4072
|
<div class="col s12 m6 l6 xl6">
|
|
@@ -3951,20 +4396,22 @@ function sortByTitle(element) {
|
|
|
3951
4396
|
|
|
3952
4397
|
|
|
3953
4398
|
function updateDevice(id) {
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
if (devs
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
4399
|
+
if (devices.length > 0)
|
|
4400
|
+
sendToWrapper(namespace, 'getDevice', {id: id}, function (msg) {
|
|
4401
|
+
if (msg) {
|
|
4402
|
+
const devs = msg.devices;
|
|
4403
|
+
if (devs) {
|
|
4404
|
+
if (devs.error) {
|
|
4405
|
+
showMessage(devs.error, _('Error'));
|
|
4406
|
+
} else {
|
|
4407
|
+
removeDevice(id);
|
|
4408
|
+
devs.forEach(dev => devices.push(dev));
|
|
4409
|
+
showDevices();
|
|
4410
|
+
}
|
|
3964
4411
|
}
|
|
3965
4412
|
}
|
|
3966
|
-
}
|
|
3967
|
-
})
|
|
4413
|
+
});
|
|
4414
|
+
else sendToWrapper(namespace, 'getDevices', {}, extractDevicesData)
|
|
3968
4415
|
}
|
|
3969
4416
|
|
|
3970
4417
|
function removeDevice(id) {
|
|
@@ -3978,7 +4425,8 @@ function removeDevice(id) {
|
|
|
3978
4425
|
}
|
|
3979
4426
|
|
|
3980
4427
|
function swapActive(id) {
|
|
3981
|
-
const dev = getDeviceByID(id);
|
|
4428
|
+
const dev = getDeviceByID(id) || getDeviceByIEEE(`0x${id}`);
|
|
4429
|
+
console.warn(`swap_active for ${id} -> ${JSON.stringify(dev)}`);
|
|
3982
4430
|
if (dev && dev.common) {
|
|
3983
4431
|
dev.common.deactivated = !(dev.common.deactivated);
|
|
3984
4432
|
sendToWrapper(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
|