iobroker.zigbee 3.1.2 → 3.1.4
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 +14 -1
- package/admin/admin.js +369 -191
- package/admin/index_m.html +30 -6
- package/admin/tab_m.html +31 -11
- package/io-package.json +42 -39
- package/lib/DeviceDebug.js +25 -2
- package/lib/binding.js +7 -7
- package/lib/commands.js +279 -249
- 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 +134 -90
- package/lib/zbDelayedAction.js +4 -4
- package/lib/zbDeviceAvailability.js +32 -33
- package/lib/zbDeviceConfigure.js +7 -0
- package/lib/zbDeviceEvent.js +39 -6
- package/lib/zigbeecontroller.js +91 -43
- package/main.js +31 -38
- package/package.json +5 -8
- package/lib/tools.js +0 -55
package/lib/commands.js
CHANGED
|
@@ -7,10 +7,12 @@ const fs = require('fs');
|
|
|
7
7
|
const statesMapping = require('./devices');
|
|
8
8
|
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
|
|
9
9
|
const colors = require('./colors.js');
|
|
10
|
+
/* currently not needed, kept for referencce
|
|
10
11
|
const dns = require('dns');
|
|
11
12
|
const net = require('net');
|
|
12
|
-
const
|
|
13
|
-
|
|
13
|
+
const access = fs.access;
|
|
14
|
+
const constants = fs.constants;
|
|
15
|
+
*/
|
|
14
16
|
const disallowedDashStates = [
|
|
15
17
|
'link_quality', 'available', 'battery', 'groups', 'device_query',
|
|
16
18
|
'hue_move', 'color_temp_move', 'satuation_move', 'brightness_move', 'brightness_step', 'hue_calibration',
|
|
@@ -82,9 +84,9 @@ class Commands {
|
|
|
82
84
|
this.renameDevice(obj.from, obj.command, obj.message, obj.callback);
|
|
83
85
|
}
|
|
84
86
|
break;
|
|
85
|
-
case '
|
|
87
|
+
case 'deleteZigbeeDevice':
|
|
86
88
|
if (obj.message && typeof obj.message === 'object') {
|
|
87
|
-
this.
|
|
89
|
+
this.deleteZigbeeDevice(obj.from, obj.command, obj.message, obj.callback);
|
|
88
90
|
}
|
|
89
91
|
break;
|
|
90
92
|
case 'getChannels':
|
|
@@ -163,15 +165,19 @@ class Commands {
|
|
|
163
165
|
if (this.stController) this.adapter.sendTo(obj.from, obj.command, {clean:this.stController.CleanupRequired(), errors:this.stController.getStashedErrors()}, obj.callback);
|
|
164
166
|
// NO Break - returning the debug-data as well is intentional
|
|
165
167
|
case 'getDebugMessages':
|
|
166
|
-
this.adapter.sendTo(obj.from, obj.command, {byId:this.adapter.deviceDebug.collectDebugData( obj.message.inlog)},obj.callback);
|
|
168
|
+
this.adapter.sendTo(obj.from, obj.command, {byId:this.adapter.deviceDebug.collectDebugData( obj.message.inlog, obj.message.del )},obj.callback);
|
|
167
169
|
break;
|
|
168
170
|
case 'testConnection':
|
|
169
171
|
this.testConnection(obj.from, obj.command, obj.message, obj.callback);
|
|
170
172
|
break;
|
|
171
173
|
case 'readNVRam':
|
|
172
174
|
this.readNvBackup(obj.from, obj.command, obj.message, obj.callback);
|
|
175
|
+
break;
|
|
176
|
+
case 'downloadIcons':
|
|
177
|
+
this.triggerIconDownload(obj);
|
|
178
|
+
break;
|
|
173
179
|
default:
|
|
174
|
-
|
|
180
|
+
this.debug(`Commands: Command ${obj.command} is unknown`);
|
|
175
181
|
//this.adapter.sendTo(obj.from, obj.command, obj.message, obj.callback);
|
|
176
182
|
break;
|
|
177
183
|
}
|
|
@@ -289,6 +295,7 @@ class Commands {
|
|
|
289
295
|
|
|
290
296
|
if (await this.zbController.permitJoin(cTimer, devId)) {
|
|
291
297
|
this.adapter.setState('info.pairingMode', cTimer > 0, true);
|
|
298
|
+
if (devId) this.zbController.emit(`Pairing started for ${devId}`);
|
|
292
299
|
this.adapter.sendTo(from, command, cTimer ? 'Start pairing!':'Stop pairing!', callback);
|
|
293
300
|
}
|
|
294
301
|
else {
|
|
@@ -329,196 +336,229 @@ class Commands {
|
|
|
329
336
|
}
|
|
330
337
|
}
|
|
331
338
|
|
|
332
|
-
async getDevices(from, command, id, callback) {
|
|
333
|
-
if (this.zbController && this.zbController.herdsmanStarted) {
|
|
334
|
-
this.debug(`getDevices called from ${from} with command ${JSON.stringify(command)} and id ${JSON.stringify(id)}`);
|
|
335
|
-
const pairedDevices = await this.zbController.getClients(true);
|
|
336
|
-
const groups = {};
|
|
337
|
-
let rooms;
|
|
338
|
-
this.adapter.getEnumsAsync('enum.rooms')
|
|
339
|
-
.then(enums => {
|
|
340
|
-
// rooms
|
|
341
|
-
rooms = enums['enum.rooms'];
|
|
342
|
-
})
|
|
343
|
-
// get all adapter devices
|
|
344
|
-
.then(() => this.adapter.getDevicesAsync())
|
|
345
|
-
.then(async result => {
|
|
346
|
-
const alls = id ? await this.adapter.getStatesAsync(id + '.*') : await this.adapter.getStatesAsync('*');
|
|
347
|
-
const allst = id ? await this.adapter.getStatesOfAsync(id) : await this.adapter.getStatesOfAsync();
|
|
348
|
-
result = result.filter(item => !id || id === item._id);
|
|
349
|
-
// get device states and groups
|
|
350
|
-
result.forEach(async devInfo => {
|
|
351
|
-
if (devInfo._id) {
|
|
352
|
-
// battery and link_quality
|
|
353
|
-
const lqState = alls[`${devInfo._id}.link_quality`];
|
|
354
|
-
devInfo.link_quality = lqState ? lqState.val : undefined;
|
|
355
|
-
devInfo.link_quality_lc = lqState ? lqState.lc : undefined;
|
|
356
|
-
const batState = alls[`${devInfo._id}.battery`];
|
|
357
|
-
devInfo.battery = batState ? batState.val : undefined;
|
|
358
|
-
// devInfo.states = states || {};
|
|
359
|
-
|
|
360
|
-
const states = allst.filter(item => item._id.startsWith(devInfo._id));
|
|
361
|
-
|
|
362
|
-
// put only allowed states
|
|
363
|
-
devInfo.statesDef = (states || []).filter(stateDef => {
|
|
364
|
-
const sid = stateDef._id.replace(this.adapter.namespace + '.', '');
|
|
365
|
-
const names = sid.split('.');
|
|
366
|
-
if (stateDef.common.color || names.length > 2) return false;
|
|
367
|
-
return !disallowedDashStates.includes(names.pop());
|
|
368
|
-
|
|
369
|
-
}).map(stateDef => {
|
|
370
|
-
const name = stateDef.common.name;
|
|
371
|
-
const devname = devInfo.common.name;
|
|
372
|
-
// replace state
|
|
373
|
-
return {
|
|
374
|
-
id: stateDef._id,
|
|
375
|
-
name: typeof name === 'string' ? name.replace(devname, '') : name,
|
|
376
|
-
type: stateDef.common.type,
|
|
377
|
-
read: stateDef.common.read,
|
|
378
|
-
write: stateDef.common.write,
|
|
379
|
-
val: alls[stateDef._id] ? alls[stateDef._id].val : undefined,
|
|
380
|
-
role: stateDef.common.role,
|
|
381
|
-
unit: stateDef.common.unit,
|
|
382
|
-
states: stateDef.common.states,
|
|
383
|
-
};
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
return result;
|
|
388
|
-
})
|
|
389
|
-
.then(async result => {
|
|
390
|
-
// combine info
|
|
391
|
-
const devices = [];
|
|
392
|
-
for (const devInfo of result) {
|
|
393
|
-
if (devInfo._id.indexOf('group') > 0) {
|
|
394
|
-
devInfo.icon = 'img/group.png';
|
|
395
|
-
devInfo.vendor = 'ioBroker';
|
|
396
|
-
// get group members and store them
|
|
397
|
-
const match = /zigbee.\d.group_([0-9]+)/.exec(devInfo._id);
|
|
398
|
-
if (match && match.length > 1) {
|
|
399
|
-
const groupID = Number(match[1]);
|
|
400
|
-
const groupmembers = await this.zbController.getGroupMembersFromController(groupID);
|
|
401
|
-
this.debug(`group members for group ${groupID}: ${JSON.stringify(groupmembers)}`);
|
|
402
|
-
if (groupmembers && groupmembers.length > 0) {
|
|
403
|
-
const memberinfo = [];
|
|
404
|
-
for (const member of groupmembers) {
|
|
405
|
-
if (member && typeof member.ieee === 'string') {
|
|
406
|
-
if (groups) {
|
|
407
|
-
const grouparray = groups[member.ieee];
|
|
408
|
-
if (grouparray) {
|
|
409
|
-
if (!grouparray.includes(groupID)) {
|
|
410
|
-
groups[member.ieee].push(groupID);
|
|
411
|
-
}
|
|
412
|
-
} else {
|
|
413
|
-
groups[member.ieee] = [groupID];
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
const device = await this.adapter.getObjectAsync(`${this.adapter.namespace}.${member.ieee.substr(2)}`);
|
|
417
|
-
if (device) {
|
|
418
|
-
member.device = device.common.name;
|
|
419
|
-
} else {
|
|
420
|
-
member.device = 'unknown';
|
|
421
|
-
}
|
|
422
|
-
memberinfo.push(member);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
devInfo.memberinfo = memberinfo;
|
|
426
|
-
this.debug(`memberinfo for ${match[1]}: ${JSON.stringify(devInfo.memberinfo)}`);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
} else {
|
|
430
|
-
const modelDesc = statesMapping.findModel(devInfo.common.type);
|
|
431
|
-
devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
|
|
432
|
-
devInfo.vendor = modelDesc ? modelDesc.vendor : '';
|
|
433
|
-
const legacyDesc = statesMapping.findModel(devInfo.common.type, true);
|
|
434
|
-
devInfo.legacyIcon = (legacyDesc && legacyDesc.icon) ? legacyDesc.icon : undefined;
|
|
435
|
-
}
|
|
436
339
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
// check configuration
|
|
440
|
-
try {
|
|
441
|
-
if (devInfo.info) {
|
|
442
|
-
const result = await this.zbController.callExtensionMethod(
|
|
443
|
-
'shouldConfigure',
|
|
444
|
-
[devInfo.info.device, devInfo.info.mapped],
|
|
445
|
-
);
|
|
446
|
-
if (result.length > 0) devInfo.isConfigured = !result[0];
|
|
447
|
-
}
|
|
448
|
-
} catch (error) {
|
|
449
|
-
this.warn('error calling shouldConfigure: ' + error && error.message ? error.message : 'no error message');
|
|
450
|
-
}
|
|
340
|
+
async handleDeviceforInfo(device, states) {
|
|
341
|
+
}
|
|
451
342
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
return devices;
|
|
469
|
-
})
|
|
470
|
-
.then(async (devices) => {
|
|
471
|
-
// fill group info
|
|
472
|
-
for (const groupdev in groups) {
|
|
473
|
-
const device = devices.find(dev => (groupdev === getZbId(dev._id)));
|
|
343
|
+
async handleGroupforInfo(group, groups) {
|
|
344
|
+
group.icon = 'img/group.png';
|
|
345
|
+
group.vendor = 'ioBroker';
|
|
346
|
+
// get group members and store them
|
|
347
|
+
const match = /zigbee.\d.group_([0-9]+)/.exec(group._id);
|
|
348
|
+
if (match && match.length > 1) {
|
|
349
|
+
const groupID = Number(match[1]);
|
|
350
|
+
const groupmembers = await this.zbController.getGroupMembersFromController(groupID);
|
|
351
|
+
this.debug(`group members for group ${groupID}: ${JSON.stringify(groupmembers)}`);
|
|
352
|
+
if (groupmembers && groupmembers.length > 0) {
|
|
353
|
+
const memberinfo = [];
|
|
354
|
+
for (const member of groupmembers) {
|
|
355
|
+
if (member && typeof member.ieee === 'string') {
|
|
356
|
+
const memberId = member.ieee.substr(2);
|
|
357
|
+
const device = await this.adapter.getObjectAsync(`${this.adapter.namespace}.${member.ieee.substr(2)}`);
|
|
358
|
+
if (groups.hasOwnProperty(memberId)) { if (!groups[memberId].includes(groupID)) groups[memberId].push(groupID)} else groups[memberId] = [groupID]
|
|
474
359
|
if (device) {
|
|
475
|
-
device
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// append devices that paired but not created
|
|
479
|
-
if (!id) {
|
|
480
|
-
for (const d of pairedDevices) {
|
|
481
|
-
const device = await this.zbController.resolveEntity(d.ieeeAddr);
|
|
482
|
-
if (!device || !device.device) {
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
485
|
-
const exists = devices.find((dev) => (dev._id && device.device.ieeeAddr === getZbId(dev._id)));
|
|
486
|
-
if (!exists) {
|
|
487
|
-
devices.push({
|
|
488
|
-
_id: device.device.ieeeAddr,
|
|
489
|
-
icon: 'img/unknown.png',
|
|
490
|
-
paired: true,
|
|
491
|
-
info: device,
|
|
492
|
-
common: {
|
|
493
|
-
name: undefined,
|
|
494
|
-
type: undefined,
|
|
495
|
-
},
|
|
496
|
-
native: {}
|
|
497
|
-
});
|
|
498
|
-
}
|
|
360
|
+
member.device = device.common.name;
|
|
361
|
+
} else {
|
|
362
|
+
member.device = 'unknown';
|
|
499
363
|
}
|
|
364
|
+
memberinfo.push(member);
|
|
500
365
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
.
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
366
|
+
}
|
|
367
|
+
group.memberinfo = memberinfo;
|
|
368
|
+
this.debug(`memberinfo for ${match[1]}: ${JSON.stringify(group.memberinfo)}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async fillInfo(device, device_stateDefs, all_states) {
|
|
374
|
+
device.statesDef = (device_stateDefs || []).filter(stateDef => {
|
|
375
|
+
const sid = stateDef._id.replace(this.adapter.namespace + '.', '');
|
|
376
|
+
const names = sid.split('.');
|
|
377
|
+
if (stateDef.common.color || names.length > 2) return false;
|
|
378
|
+
return !disallowedDashStates.includes(names.pop());
|
|
379
|
+
}).map(stateDef => {
|
|
380
|
+
const name = stateDef.common.name;
|
|
381
|
+
const devname = device.common.name;
|
|
382
|
+
// replace state
|
|
383
|
+
return {
|
|
384
|
+
id: stateDef._id,
|
|
385
|
+
name: typeof name === 'string' ? name.replace(devname, '') : name,
|
|
386
|
+
type: stateDef.common.type,
|
|
387
|
+
read: stateDef.common.read,
|
|
388
|
+
write: stateDef.common.write,
|
|
389
|
+
val: all_states[stateDef._id] ? all_states[stateDef._id].val : undefined,
|
|
390
|
+
role: stateDef.common.role,
|
|
391
|
+
unit: stateDef.common.unit,
|
|
392
|
+
states: stateDef.common.states,
|
|
393
|
+
};
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const id = getZbId(device._id);
|
|
397
|
+
device.info = this.buildDeviceInfo(await this.zbController.resolveEntity(id));
|
|
398
|
+
// check configuration
|
|
399
|
+
try {
|
|
400
|
+
if (device.info) {
|
|
401
|
+
const result = await this.zbController.callExtensionMethod(
|
|
402
|
+
'shouldConfigure',
|
|
403
|
+
[device.info.device, device.info.mapped],
|
|
404
|
+
);
|
|
405
|
+
if (result.length > 0) device.isConfigured = !result[0];
|
|
406
|
+
}
|
|
407
|
+
} catch (error) {
|
|
408
|
+
this.warn('error calling shouldConfigure: ' + error && error.message ? error.message : 'no error message');
|
|
409
|
+
}
|
|
410
|
+
device.paired = !!device.info;
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
buildDeviceInfo(device) {
|
|
416
|
+
const rv = {};
|
|
417
|
+
try {
|
|
418
|
+
rv.device = {
|
|
419
|
+
modelZigbee:device.device.modelID,
|
|
420
|
+
type:device.device.type,
|
|
421
|
+
ieee:device.device.ieeeAddr,
|
|
422
|
+
nwk:device.device.networkAddress,
|
|
423
|
+
manuf_id:device.device.maufacturerID,
|
|
424
|
+
manuf_name:device.device.manufacturerName,
|
|
425
|
+
manufacturer:device.mapped.vendor,
|
|
426
|
+
power:device.device.powerSource,
|
|
427
|
+
app_version:device.device.applicationVersion,
|
|
428
|
+
hard_version:device.device.hardwareVersion,
|
|
429
|
+
zcl_version:device.device.zclVersion,
|
|
430
|
+
stack_version:device.device.stack_version,
|
|
431
|
+
date_code:device.device.dateCode,
|
|
432
|
+
build:device.device.softwareBuildID,
|
|
433
|
+
interviewstate:device.device.interviewState || 'UNKNOWN',
|
|
434
|
+
}
|
|
435
|
+
rv.endpoints = [];
|
|
436
|
+
for (const ep_idx in device.endpoints) {
|
|
437
|
+
const ep = device.endpoints[ep_idx];
|
|
438
|
+
rv.endpoints.push({
|
|
439
|
+
ID:ep.ID,
|
|
440
|
+
profile:ep.profileID,
|
|
441
|
+
input_clusters:ep.inputClusters,
|
|
442
|
+
output_clusters:ep.outputClusters,
|
|
512
443
|
})
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
444
|
+
}
|
|
445
|
+
rv.mapped = {
|
|
446
|
+
model:device.mapped.model,
|
|
447
|
+
description:device.mapped.description,
|
|
448
|
+
//fingerprint:JSON.stringify(device.mapped.fingerprint),
|
|
449
|
+
vendor:device.mapped.vendor,
|
|
450
|
+
hasOnEvent:device.mapped.onEvent != undefined,
|
|
451
|
+
hasConfigure:device.mapped.configure != undefined,
|
|
452
|
+
options:[],
|
|
453
|
+
}
|
|
454
|
+
if (device.mapped.options && typeof (device.mapped.options == 'object')) {
|
|
455
|
+
const optionDesc = [];
|
|
456
|
+
for (const option of device.mapped.options) {
|
|
457
|
+
if (option.name)
|
|
458
|
+
rv.mapped.options.push(option.name);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
this.warn(`Error ${error && error.message + ' ' ? error.message : ''}building device info for ${JSON.stringify(device)}`);
|
|
519
464
|
}
|
|
465
|
+
return rv;
|
|
520
466
|
}
|
|
521
467
|
|
|
468
|
+
async appendDevicesWithoutObjects(devices, client) {
|
|
469
|
+
const device = await this.zbController.resolveEntity(client.ieeeAddr);
|
|
470
|
+
if (!device || !device.device) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const exists = devices.find((dev) => (dev._id && device.device.ieeeAddr === getZbId(dev._id)));
|
|
474
|
+
if (!exists) {
|
|
475
|
+
devices.push({
|
|
476
|
+
_id: device.device.ieeeAddr,
|
|
477
|
+
icon: 'img/unknown.png',
|
|
478
|
+
paired: true,
|
|
479
|
+
info: this.buildDeviceInfo(device),
|
|
480
|
+
common: {
|
|
481
|
+
name: undefined,
|
|
482
|
+
type: undefined,
|
|
483
|
+
},
|
|
484
|
+
native: {}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async getDevices(from, command, id, callback) {
|
|
491
|
+
this.debug(`getDevices called from ${from} with command ${JSON.stringify(command)}${id ? ' and id '+JSON.stringify(id) : ' without ID'}`);
|
|
492
|
+
if (!(this.zbController && this.zbController.herdsmanStarted)) {
|
|
493
|
+
this.adapter.sendTo(from, command, {error: 'No active connection to Zigbee Hardware!'}, callback);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const rooms = await this.adapter.getEnumsAsync('enum.rooms') || {};
|
|
497
|
+
const deviceObjects = (id ? [await this.adapter.getObjectAsync(id)] : await this.adapter.getDevicesAsync());
|
|
498
|
+
const all_states = id ? await this.adapter.getStatesAsync(id + '.*') : await this.adapter.getStatesAsync('*');
|
|
499
|
+
const all_stateDefs = id ? await this.adapter.getStatesOfAsync(id) : await this.adapter.getStatesOfAsync();
|
|
500
|
+
const illegalDevices = [];
|
|
501
|
+
const groups = {};
|
|
502
|
+
const PromiseChain = [];
|
|
503
|
+
for (const devInfo of deviceObjects) {
|
|
504
|
+
if (devInfo._id.indexOf('group') > -1) {
|
|
505
|
+
PromiseChain.push(this.handleGroupforInfo(devInfo, groups));
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
const modelDesc = statesMapping.findModel(devInfo.common.type);
|
|
509
|
+
devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
|
|
510
|
+
devInfo.vendor = modelDesc ? modelDesc.vendor : '';
|
|
511
|
+
const legacyDesc = statesMapping.findModel(devInfo.common.type, true);
|
|
512
|
+
devInfo.legacyIcon = (legacyDesc && legacyDesc.icon) ? legacyDesc.icon : undefined;
|
|
513
|
+
const lq_state = all_states[`${devInfo._id}.link_quality`];
|
|
514
|
+
devInfo.link_quality = lq_state ? lq_state.val : -1;
|
|
515
|
+
devInfo.link_quality_lc = lq_state ? lq_state.lc : undefined;
|
|
516
|
+
const battery_state = all_states[`${devInfo._id}.battery`];
|
|
517
|
+
devInfo.battery = battery_state ? battery_state.val : undefined;
|
|
518
|
+
devInfo.rooms = [];
|
|
519
|
+
for (const room in rooms) {
|
|
520
|
+
if (!rooms.hasOwnProperty(room) ||
|
|
521
|
+
!rooms[room] ||
|
|
522
|
+
!rooms[room].common ||
|
|
523
|
+
!rooms[room].common.members
|
|
524
|
+
) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (rooms[room].common.members.includes(devInfo._id)) {
|
|
528
|
+
devInfo.rooms.push(rooms[room].common.name);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
}
|
|
533
|
+
PromiseChain.push(this.fillInfo(devInfo, all_stateDefs.filter(item => item._id.startsWith(devInfo._id)),all_states));
|
|
534
|
+
}
|
|
535
|
+
if (!id) {
|
|
536
|
+
for (const client of this.zbController.getClientIterator(false)) {
|
|
537
|
+
PromiseChain.push(this.appendDevicesWithoutObjects(deviceObjects,client))
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
await Promise.all(PromiseChain);
|
|
542
|
+
|
|
543
|
+
for (const groupmember in groups) {
|
|
544
|
+
const device = deviceObjects.find(dev => (groupmember === dev.native.id));
|
|
545
|
+
if (device) {
|
|
546
|
+
device.groups = groups[groupmember];
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
this.debug(`getDevices contains ${deviceObjects.length} Devices`);
|
|
552
|
+
const rv = { devices:deviceObjects, inLog:this.adapter.deviceDebug.logStatus, }
|
|
553
|
+
if (!id) rv.byId = this.adapter.deviceDebug.collectDebugData();
|
|
554
|
+
if (this.stController) {
|
|
555
|
+
rv.clean = this.stController.CleanupRequired();
|
|
556
|
+
rv.errors = this.stController.getStashedErrors();
|
|
557
|
+
rv.debugDevices = this.stController.debugDevices;
|
|
558
|
+
}
|
|
559
|
+
this.adapter.sendTo(from, command, rv, callback);
|
|
560
|
+
|
|
561
|
+
}
|
|
522
562
|
|
|
523
563
|
async getCoordinatorInfo(from, command, callback) {
|
|
524
564
|
const coordinatorinfo = {
|
|
@@ -548,6 +588,7 @@ class Commands {
|
|
|
548
588
|
coordinatorinfo.port = obj.native.port;
|
|
549
589
|
coordinatorinfo.type = obj.native.adapterType;
|
|
550
590
|
coordinatorinfo.channel = obj.native.channel;
|
|
591
|
+
coordinatorinfo.autostart = this.adapter.config.autostart;
|
|
551
592
|
coordinatorinfo.installedVersion = obj.common.version;
|
|
552
593
|
if (coordinatorVersion && coordinatorVersion.type && coordinatorVersion.meta) {
|
|
553
594
|
coordinatorinfo.type = coordinatorVersion.type;
|
|
@@ -588,8 +629,8 @@ class Commands {
|
|
|
588
629
|
}
|
|
589
630
|
}
|
|
590
631
|
else {
|
|
591
|
-
coordinatorinfo.version = 'not connected';
|
|
592
|
-
coordinatorinfo.revision = 'not connected';
|
|
632
|
+
coordinatorinfo.version = this.adapter.config.autostart ? 'not connected' : 'autostart not set';
|
|
633
|
+
coordinatorinfo.revision = this.adapter.config.autostart ? 'not connected' : 'autostart not set';
|
|
593
634
|
|
|
594
635
|
}
|
|
595
636
|
} catch {
|
|
@@ -611,14 +652,14 @@ class Commands {
|
|
|
611
652
|
}
|
|
612
653
|
}
|
|
613
654
|
|
|
614
|
-
|
|
655
|
+
deleteZigbeeDevice(from, command, msg, callback) {
|
|
615
656
|
if (this.zbController && this.zbController.herdsmanStarted && this.stController) {
|
|
616
|
-
this.debug(`
|
|
657
|
+
this.debug(`deleteZigbeeDevice message: ${JSON.stringify(msg)}`);
|
|
617
658
|
const id = msg.id;
|
|
618
659
|
const force = msg.force;
|
|
619
660
|
const sysid = id.replace(this.adapter.namespace + '.', '0x');
|
|
620
661
|
const devId = id.replace(this.adapter.namespace + '.', '');
|
|
621
|
-
this.debug(`
|
|
662
|
+
this.debug(`deleteZigbeeDevice sysid: ${sysid}`);
|
|
622
663
|
const dev = this.zbController.getDevice(sysid);
|
|
623
664
|
if (!dev) {
|
|
624
665
|
this.info(`Attempted to delete device ${devId} - the device is not known to the zigbee controller.`);
|
|
@@ -744,6 +785,28 @@ class Commands {
|
|
|
744
785
|
if (msg.data)
|
|
745
786
|
{
|
|
746
787
|
for (const prop in msg.data) {
|
|
788
|
+
if (prop==='options') {
|
|
789
|
+
// we need to trigger the option change
|
|
790
|
+
const changed_from = entity.options;
|
|
791
|
+
const changed_to = msg.data.prop;
|
|
792
|
+
/*
|
|
793
|
+
const allKeys = Object.keys(changed_from);
|
|
794
|
+
for (const nk of Object.keys(changed_to)) {
|
|
795
|
+
if (allKeys.includes(nk)) continue;
|
|
796
|
+
allKeys.push(nk);
|
|
797
|
+
}
|
|
798
|
+
for (const key of allKeys) {
|
|
799
|
+
if (changed_from.hasOwnProperty(key) && changed_to.hasOwnProperty(key) && changed_from[key] == changed_to[key])
|
|
800
|
+
{
|
|
801
|
+
delete changed_from[key];
|
|
802
|
+
delete changed_to[key];
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
*/
|
|
806
|
+
this.zbController.callExtensionMethod(
|
|
807
|
+
'onZigbeeEvent',
|
|
808
|
+
[{'device': entity.device, 'type': 'deviceOptionsChanged', from: changed_from, to:changed_to || {}, }, entity ? entity.mapped : null]);
|
|
809
|
+
}
|
|
747
810
|
this.debug('enumerating data: ' + prop);
|
|
748
811
|
await this.stController.localConfig.updateLocalOverride(target, (entity ? entity.mapped.model : 'group'), prop, msg.data[prop], msg.global);
|
|
749
812
|
}
|
|
@@ -824,65 +887,32 @@ class Commands {
|
|
|
824
887
|
this.adapter.logToPairing(`Error: ${result.error}`)
|
|
825
888
|
}
|
|
826
889
|
this.adapter.sendTo(from, command, result, callback);
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
}
|
|
842
|
-
this.debug(`dns lookup for ${msg.address} produced ${ip}`);
|
|
843
|
-
this.adapter.logToPairing(`dns lookup for ${msg.address} produced ${ip}`);
|
|
844
|
-
const client = new net.Socket();
|
|
845
|
-
this.debug(`attempting to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`);
|
|
846
|
-
client.connect(netAddress.port, ip, () => {
|
|
847
|
-
client.destroy()
|
|
848
|
-
this.adapter.logToPairing(`connected successfully to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`);
|
|
849
|
-
this.debug(`connected successfully to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`);
|
|
850
|
-
this.adapter.sendTo(from, command, {}, callback)
|
|
851
|
-
})
|
|
852
|
-
client.on('error', (error) => {
|
|
853
|
-
const msg = `unable to connect to ${ip} port ${netAddress.port ? netAddress.port : 80} : ${error && error.message ? error.message : 'no message given'}`
|
|
854
|
-
this.error(msg);
|
|
855
|
-
this.adapter.logToPairing(`Error: ${msg}`);
|
|
856
|
-
this.adapter.sendTo(from, command, {error:msg}, callback);
|
|
857
|
-
});
|
|
858
|
-
})
|
|
859
|
-
}
|
|
860
|
-
else
|
|
861
|
-
{
|
|
862
|
-
try {
|
|
863
|
-
const port = msg.address.trim();
|
|
864
|
-
this.adapter.logToPairing(`reading access rights for ${port}`);
|
|
865
|
-
access(port, constants.R_OK | constants.W_OK, (error) => {
|
|
866
|
-
if (error) {
|
|
867
|
-
const msg = `unable to access ${port} : ${error && error.message ? error.message : 'no message given'}`;
|
|
868
|
-
this.error(msg);
|
|
869
|
-
this.adapter.logToPairing(`Error: ${msg}`);
|
|
870
|
-
this.adapter.sendTo(from, command, {error:msg}, callback);
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
this.adapter.logToPairing(`read and write access available for ${port}`);
|
|
874
|
-
this.adapter.sendTo(from, command, {}, callback);
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
catch (error) {
|
|
878
|
-
const msg = `File access error: ${error && error.message ? error.message : 'no message given'}`;
|
|
879
|
-
this.error(msg);
|
|
880
|
-
this.adapter.logToPairing(`Error: ${msg}`);
|
|
881
|
-
this.adapter.sendTo(from, command, {error:msg}, callback);
|
|
882
|
-
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
async triggerIconDownload(obj) {
|
|
893
|
+
if (!this.stController) {
|
|
894
|
+
this.adapter.sendTo(obj.from, obj.command, {msg:'No States controller'}, obj.callback);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const clients = await this.adapter.getDevicesAsync();
|
|
898
|
+
const Promises = [];
|
|
899
|
+
for (const client of clients) {
|
|
900
|
+
if (client.common.modelIcon && client.common.icon && client.common.modelIcon.startsWith('http')) {
|
|
901
|
+
const filestatus = await this.adapter.fileExistsAsync(this.adapter.namespace, client.common.icon);
|
|
902
|
+
if (!filestatus)
|
|
903
|
+
Promises.push(this.stController.downloadIconToAdmin(client.common.modelIcon, client.common.icon))
|
|
883
904
|
}
|
|
905
|
+
|
|
906
|
+
}
|
|
907
|
+
const NumDownloads = Promises.length;
|
|
908
|
+
if (NumDownloads) {
|
|
909
|
+
this.adapter.sendTo(obj.from, obj.command, {msg:`${NumDownloads} downloads triggered.`}, obj.callback);
|
|
910
|
+
Promise.all(Promises);
|
|
884
911
|
}
|
|
885
|
-
|
|
912
|
+
else {
|
|
913
|
+
this.adapter.sendTo(obj.from, obj.command, {msg:'Nothing to download'}, obj.callback);
|
|
914
|
+
}
|
|
915
|
+
|
|
886
916
|
}
|
|
887
917
|
}
|
|
888
918
|
|
package/lib/developer.js
CHANGED
|
@@ -106,7 +106,7 @@ class Developer {
|
|
|
106
106
|
const cfg = obj.message.hasOwnProperty('cfg') ? obj.message.cfg : null;
|
|
107
107
|
let publishTarget;
|
|
108
108
|
try {
|
|
109
|
-
publishTarget = this.zbController.getDevice(devId) ? devId : this.zbController.getGroup(parseInt(devId));
|
|
109
|
+
publishTarget = await this.zbController.getDevice(devId) ? devId : await this.zbController.getGroup(parseInt(devId));
|
|
110
110
|
if (!publishTarget) {
|
|
111
111
|
this.adapter.sendTo(obj.from, obj.command, {localErr: `Device or group ${devId} not found!`}, obj.callback);
|
|
112
112
|
return;
|
package/lib/devices.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const states = require('./states.js').states;
|
|
4
4
|
const utils = require('./utils.js');
|
|
5
5
|
const rgb = require('./rgb.js');
|
|
6
|
-
const {
|
|
6
|
+
const { applyExposeForDevice} = require('./exposes.js');
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
// return list of changing states when incoming state is changed
|
|
@@ -3105,7 +3105,7 @@ function fillStatesWithExposes(logger) {
|
|
|
3105
3105
|
async function addExposeToDevices(device, logger, model) {
|
|
3106
3106
|
const s = DevicesByModel.size;
|
|
3107
3107
|
if (s < 1) getByModel();
|
|
3108
|
-
const rv = await applyExposeForDevice(devices, DevicesByModel, device, logger, model);
|
|
3108
|
+
const rv = await applyExposeForDevice(devices, DevicesByModel, device, { logger, model, newCompositeMethod:true });
|
|
3109
3109
|
removeEmptyStates(devices);
|
|
3110
3110
|
return rv;
|
|
3111
3111
|
}
|
package/lib/exclude.js
CHANGED
|
@@ -91,7 +91,7 @@ class Exclude {
|
|
|
91
91
|
|
|
92
92
|
async addExclude(from, command, params, callback) {
|
|
93
93
|
try {
|
|
94
|
-
|
|
94
|
+
this.debug('addExclude message: ' + JSON.stringify(params));
|
|
95
95
|
const exclude_mod = params.exclude_model.common.type;
|
|
96
96
|
this.localConfig.updateLocalOverride(exclude_mod, exclude_mod, 'legacy', exclude_mod, true);
|
|
97
97
|
callback({});
|