iobroker.zigbee 3.1.2 → 3.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -1
- package/admin/admin.js +1020 -729
- package/admin/index_m.html +55 -155
- package/admin/tab_m.html +161 -242
- package/io-package.json +42 -39
- package/lib/DeviceDebug.js +24 -2
- package/lib/binding.js +7 -7
- package/lib/commands.js +319 -255
- package/lib/developer.js +1 -1
- package/lib/devices.js +2 -2
- package/lib/exclude.js +1 -1
- package/lib/exposes.js +54 -24
- package/lib/groups.js +26 -28
- package/lib/localConfig.js +8 -8
- package/lib/networkmap.js +10 -2
- package/lib/statescontroller.js +135 -91
- package/lib/zbDelayedAction.js +4 -4
- package/lib/zbDeviceAvailability.js +32 -33
- package/lib/zbDeviceConfigure.js +7 -0
- package/lib/zbDeviceEvent.js +38 -6
- package/lib/zigbeecontroller.js +98 -46
- package/main.js +43 -57
- package/package.json +6 -9
- package/lib/tools.js +0 -55
package/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,22 @@ 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;
|
|
179
|
+
case 'aliveCheck':
|
|
180
|
+
this.adapter.sendTo(obj.from, obj.command, {msg:'success'}, obj.callback);
|
|
181
|
+
break;
|
|
173
182
|
default:
|
|
174
|
-
|
|
183
|
+
this.debug(`Commands: Command ${obj.command} is unknown`);
|
|
175
184
|
//this.adapter.sendTo(obj.from, obj.command, obj.message, obj.callback);
|
|
176
185
|
break;
|
|
177
186
|
}
|
|
@@ -289,6 +298,7 @@ class Commands {
|
|
|
289
298
|
|
|
290
299
|
if (await this.zbController.permitJoin(cTimer, devId)) {
|
|
291
300
|
this.adapter.setState('info.pairingMode', cTimer > 0, true);
|
|
301
|
+
if (devId) this.zbController.emit(`Pairing started for ${devId}`);
|
|
292
302
|
this.adapter.sendTo(from, command, cTimer ? 'Start pairing!':'Stop pairing!', callback);
|
|
293
303
|
}
|
|
294
304
|
else {
|
|
@@ -329,196 +339,256 @@ class Commands {
|
|
|
329
339
|
}
|
|
330
340
|
}
|
|
331
341
|
|
|
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
342
|
|
|
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
|
-
}
|
|
343
|
+
async handleDeviceforInfo(device, states) {
|
|
344
|
+
}
|
|
451
345
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
346
|
+
async handleGroupforInfo(group, groups) {
|
|
347
|
+
group.icon = 'img/group.png';
|
|
348
|
+
group.vendor = 'ioBroker';
|
|
349
|
+
// get group members and store them
|
|
350
|
+
const match = /zigbee.\d.group_([0-9]+)/.exec(group._id);
|
|
351
|
+
if (match && match.length > 1) {
|
|
352
|
+
const groupID = Number(match[1]);
|
|
353
|
+
const groupmembers = await this.zbController.getGroupMembersFromController(groupID);
|
|
354
|
+
this.debug(`group members for group ${groupID}: ${JSON.stringify(groupmembers)}`);
|
|
355
|
+
if (groupmembers && groupmembers.length > 0) {
|
|
356
|
+
const memberinfo = [];
|
|
357
|
+
for (const member of groupmembers) {
|
|
358
|
+
if (member && typeof member.ieee === 'string') {
|
|
359
|
+
const memberId = member.ieee.substr(2);
|
|
360
|
+
const device = await this.adapter.getObjectAsync(`${this.adapter.namespace}.${member.ieee.substr(2)}`);
|
|
361
|
+
const item = groups[memberId] || { groups:[], gep: { }};
|
|
362
|
+
const gep = item.gep[member.epid] || [];
|
|
363
|
+
|
|
364
|
+
if (!item.groups.includes(groupID)) item.groups.push(groupID);
|
|
365
|
+
if (!gep.includes(`${groupID}`)) gep.push(`${groupID}`);
|
|
366
|
+
item.gep[member.epid] = gep;
|
|
367
|
+
groups[memberId] = item;
|
|
474
368
|
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
|
-
}
|
|
369
|
+
member.device = device.common.name;
|
|
370
|
+
} else {
|
|
371
|
+
member.device = 'unknown';
|
|
499
372
|
}
|
|
373
|
+
memberinfo.push(member);
|
|
500
374
|
}
|
|
501
|
-
|
|
375
|
+
}
|
|
376
|
+
group.memberinfo = memberinfo;
|
|
377
|
+
this.debug(`memberinfo for ${match[1]}: ${JSON.stringify(group.memberinfo)}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async fillInfo(device, device_stateDefs, all_states) {
|
|
383
|
+
device.statesDef = (device_stateDefs || []).filter(stateDef => {
|
|
384
|
+
const sid = stateDef._id.replace(this.adapter.namespace + '.', '');
|
|
385
|
+
const names = sid.split('.');
|
|
386
|
+
if (stateDef.common.color || names.length > 2) return false;
|
|
387
|
+
return !disallowedDashStates.includes(names.pop());
|
|
388
|
+
}).map(stateDef => {
|
|
389
|
+
const name = stateDef.common.name;
|
|
390
|
+
const devname = device.common.name;
|
|
391
|
+
// replace state
|
|
392
|
+
return {
|
|
393
|
+
id: stateDef._id,
|
|
394
|
+
name: typeof name === 'string' ? name.replace(devname, '') : name,
|
|
395
|
+
type: stateDef.common.type,
|
|
396
|
+
read: stateDef.common.read,
|
|
397
|
+
write: stateDef.common.write,
|
|
398
|
+
val: all_states[stateDef._id] ? all_states[stateDef._id].val : undefined,
|
|
399
|
+
role: stateDef.common.role,
|
|
400
|
+
unit: stateDef.common.unit,
|
|
401
|
+
states: stateDef.common.states,
|
|
402
|
+
};
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const id = getZbId(device._id);
|
|
406
|
+
device.info = this.buildDeviceInfo(await this.zbController.resolveEntity(id));
|
|
407
|
+
// check configuration
|
|
408
|
+
try {
|
|
409
|
+
if (device.info) {
|
|
410
|
+
const result = await this.zbController.callExtensionMethod(
|
|
411
|
+
'shouldConfigure',
|
|
412
|
+
[device.info.device, device.info.mapped],
|
|
413
|
+
);
|
|
414
|
+
if (result.length > 0) device.isConfigured = !result[0];
|
|
415
|
+
}
|
|
416
|
+
} catch (error) {
|
|
417
|
+
this.warn('error calling shouldConfigure: ' + error && error.message ? error.message : 'no error message');
|
|
418
|
+
}
|
|
419
|
+
device.paired = !!device.info;
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
buildDeviceInfo(device) {
|
|
425
|
+
const rv = {};
|
|
426
|
+
try {
|
|
427
|
+
rv.device = {
|
|
428
|
+
modelZigbee:device.device.modelID,
|
|
429
|
+
type:device.device.type,
|
|
430
|
+
ieee:device.device.ieeeAddr,
|
|
431
|
+
nwk:device.device.networkAddress,
|
|
432
|
+
manuf_id:device.device.maufacturerID,
|
|
433
|
+
manuf_name:device.device.manufacturerName,
|
|
434
|
+
manufacturer:device.mapped ? device.mapped.vendor : '',
|
|
435
|
+
power:device.device.powerSource,
|
|
436
|
+
app_version:device.device.applicationVersion,
|
|
437
|
+
hard_version:device.device.hardwareVersion,
|
|
438
|
+
zcl_version:device.device.zclVersion,
|
|
439
|
+
stack_version:device.device.stack_version,
|
|
440
|
+
date_code:device.device.dateCode,
|
|
441
|
+
build:device.device.softwareBuildID,
|
|
442
|
+
interviewstate:device.device.interviewState || 'UNKNOWN',
|
|
443
|
+
}
|
|
444
|
+
rv.endpoints = [];
|
|
445
|
+
for (const ep_idx in device.endpoints) {
|
|
446
|
+
const ep = device.endpoints[ep_idx];
|
|
447
|
+
rv.endpoints.push({
|
|
448
|
+
ID:ep.ID,
|
|
449
|
+
profile:ep.profileID,
|
|
450
|
+
input_clusters:ep.inputClusters,
|
|
451
|
+
output_clusters:ep.outputClusters,
|
|
502
452
|
})
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
453
|
+
}
|
|
454
|
+
if (device.mapped) {
|
|
455
|
+
rv.mapped = {
|
|
456
|
+
model:device.mapped.model,
|
|
457
|
+
description:device.mapped.description,
|
|
458
|
+
//fingerprint:JSON.stringify(device.mapped.fingerprint),
|
|
459
|
+
vendor:device.mapped.vendor,
|
|
460
|
+
hasOnEvent:device.mapped.onEvent != undefined,
|
|
461
|
+
hasConfigure:device.mapped.configure != undefined,
|
|
462
|
+
options:[],
|
|
463
|
+
}
|
|
464
|
+
if (device.mapped.options && typeof (device.mapped.options == 'object')) {
|
|
465
|
+
const optionDesc = [];
|
|
466
|
+
for (const option of device.mapped.options) {
|
|
467
|
+
if (option.name)
|
|
468
|
+
rv.mapped.options.push(option.name);
|
|
510
469
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
rv.mapped = {
|
|
474
|
+
model:device.name,
|
|
475
|
+
description:device.name,
|
|
476
|
+
vendor:'not set',
|
|
477
|
+
hasOnEvent: false,
|
|
478
|
+
hasConfigure: false,
|
|
479
|
+
options:[],
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
if (device && device.name === 'Coordinator') return rv;
|
|
485
|
+
const dev = device ? device.device || {} : {}
|
|
486
|
+
const msg = device ? `device ${device.name} (${dev.ieeeAddr}, NWK ${dev.networkAddres}, ID: ${dev.ID})` : 'undefined device';
|
|
487
|
+
this.warn(`Error ${error && error.message ? error.message + ' ' : ''}building device info for ${msg}`);
|
|
488
|
+
}
|
|
489
|
+
return rv;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async appendDevicesWithoutObjects(devices, client) {
|
|
493
|
+
const device = await this.zbController.resolveEntity(client.ieeeAddr);
|
|
494
|
+
if (!device || !device.device) {
|
|
495
|
+
return;
|
|
519
496
|
}
|
|
497
|
+
const exists = devices.find((dev) => (dev._id && device.device.ieeeAddr === getZbId(dev._id)));
|
|
498
|
+
if (!exists) {
|
|
499
|
+
const coordinatorData = {
|
|
500
|
+
_id : `${this.adapter.namespace}.${device.device.ieeeAddr.substring(2)}`,
|
|
501
|
+
paired: true,
|
|
502
|
+
info: this.buildDeviceInfo(device),
|
|
503
|
+
native: { id: device.device.ieeeAddr.substring(2) },
|
|
504
|
+
mapped : {},
|
|
505
|
+
statesDev: [],
|
|
506
|
+
}
|
|
507
|
+
if (device.device.name === 'Coordinator') {
|
|
508
|
+
coordinatorData.icon = 'zigbee.png';
|
|
509
|
+
coordinatorData.common = { name: undefined, type: undefined };
|
|
510
|
+
} else {
|
|
511
|
+
coordinatorData.common = { name: 'Coordinator', type: 'Coordinator' };
|
|
512
|
+
coordinatorData.icon= 'img/unknown.png';
|
|
513
|
+
}
|
|
514
|
+
devices.push(coordinatorData);
|
|
515
|
+
}
|
|
516
|
+
|
|
520
517
|
}
|
|
521
518
|
|
|
519
|
+
async getDevices(from, command, id, callback) {
|
|
520
|
+
this.debug(`getDevices called from ${from} with command ${JSON.stringify(command)}${id ? ' and id '+JSON.stringify(id) : ' without ID'}`);
|
|
521
|
+
if (!(this.zbController && this.zbController.herdsmanStarted)) {
|
|
522
|
+
this.adapter.sendTo(from, command, {error: 'No active connection to Zigbee Hardware!'}, callback);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const rooms = await this.adapter.getEnumsAsync('enum.rooms') || {};
|
|
526
|
+
const deviceObjects = (id ? [await this.adapter.getObjectAsync(id)] : await this.adapter.getDevicesAsync());
|
|
527
|
+
const all_states = id ? await this.adapter.getStatesAsync(id + '.*') : await this.adapter.getStatesAsync('*');
|
|
528
|
+
const all_stateDefs = id ? await this.adapter.getStatesOfAsync(id) : await this.adapter.getStatesOfAsync();
|
|
529
|
+
const illegalDevices = [];
|
|
530
|
+
const groups = {};
|
|
531
|
+
const PromiseChain = [];
|
|
532
|
+
for (const devInfo of deviceObjects) {
|
|
533
|
+
if (devInfo._id.indexOf('group') > -1) {
|
|
534
|
+
PromiseChain.push(this.handleGroupforInfo(devInfo, groups));
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
const modelDesc = statesMapping.findModel(devInfo.common.type);
|
|
538
|
+
devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
|
|
539
|
+
devInfo.vendor = modelDesc ? modelDesc.vendor : '';
|
|
540
|
+
const legacyDesc = statesMapping.findModel(devInfo.common.type, true);
|
|
541
|
+
devInfo.legacyIcon = (legacyDesc && legacyDesc.icon) ? legacyDesc.icon : undefined;
|
|
542
|
+
const lq_state = all_states[`${devInfo._id}.link_quality`];
|
|
543
|
+
devInfo.link_quality = lq_state ? lq_state.val : -1;
|
|
544
|
+
devInfo.link_quality_lc = lq_state ? lq_state.lc : undefined;
|
|
545
|
+
const battery_state = all_states[`${devInfo._id}.battery`];
|
|
546
|
+
devInfo.battery = battery_state ? battery_state.val : undefined;
|
|
547
|
+
devInfo.rooms = [];
|
|
548
|
+
for (const room in rooms) {
|
|
549
|
+
if (!rooms.hasOwnProperty(room) ||
|
|
550
|
+
!rooms[room] ||
|
|
551
|
+
!rooms[room].common ||
|
|
552
|
+
!rooms[room].common.members
|
|
553
|
+
) {
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (rooms[room].common.members.includes(devInfo._id)) {
|
|
557
|
+
devInfo.rooms.push(rooms[room].common.name);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
}
|
|
562
|
+
PromiseChain.push(this.fillInfo(devInfo, all_stateDefs.filter(item => item._id.startsWith(devInfo._id)),all_states));
|
|
563
|
+
}
|
|
564
|
+
if (!id) {
|
|
565
|
+
for (const client of this.zbController.getClientIterator(true)) {
|
|
566
|
+
PromiseChain.push(this.appendDevicesWithoutObjects(deviceObjects,client))
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
await Promise.all(PromiseChain);
|
|
571
|
+
|
|
572
|
+
for (const groupmember in groups) {
|
|
573
|
+
const device = deviceObjects.find(dev => (groupmember === dev.native.id));
|
|
574
|
+
if (device) {
|
|
575
|
+
device.groups = groups[groupmember].groups;
|
|
576
|
+
device.groups_by_ep = groups[groupmember].gep;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
this.debug(`getDevices contains ${deviceObjects.length} Devices`);
|
|
582
|
+
const rv = { devices:deviceObjects, inLog:this.adapter.deviceDebug.logStatus, }
|
|
583
|
+
if (!id) rv.byId = this.adapter.deviceDebug.collectDebugData();
|
|
584
|
+
if (this.stController) {
|
|
585
|
+
rv.clean = this.stController.CleanupRequired();
|
|
586
|
+
rv.errors = this.stController.getStashedErrors();
|
|
587
|
+
rv.debugDevices = this.stController.debugDevices;
|
|
588
|
+
}
|
|
589
|
+
this.adapter.sendTo(from, command, rv, callback);
|
|
590
|
+
|
|
591
|
+
}
|
|
522
592
|
|
|
523
593
|
async getCoordinatorInfo(from, command, callback) {
|
|
524
594
|
const coordinatorinfo = {
|
|
@@ -548,6 +618,7 @@ class Commands {
|
|
|
548
618
|
coordinatorinfo.port = obj.native.port;
|
|
549
619
|
coordinatorinfo.type = obj.native.adapterType;
|
|
550
620
|
coordinatorinfo.channel = obj.native.channel;
|
|
621
|
+
coordinatorinfo.autostart = this.adapter.config.autostart;
|
|
551
622
|
coordinatorinfo.installedVersion = obj.common.version;
|
|
552
623
|
if (coordinatorVersion && coordinatorVersion.type && coordinatorVersion.meta) {
|
|
553
624
|
coordinatorinfo.type = coordinatorVersion.type;
|
|
@@ -588,8 +659,8 @@ class Commands {
|
|
|
588
659
|
}
|
|
589
660
|
}
|
|
590
661
|
else {
|
|
591
|
-
coordinatorinfo.version = 'not connected';
|
|
592
|
-
coordinatorinfo.revision = 'not connected';
|
|
662
|
+
coordinatorinfo.version = this.adapter.config.autostart ? 'not connected' : 'autostart not set';
|
|
663
|
+
coordinatorinfo.revision = this.adapter.config.autostart ? 'not connected' : 'autostart not set';
|
|
593
664
|
|
|
594
665
|
}
|
|
595
666
|
} catch {
|
|
@@ -611,14 +682,14 @@ class Commands {
|
|
|
611
682
|
}
|
|
612
683
|
}
|
|
613
684
|
|
|
614
|
-
|
|
685
|
+
deleteZigbeeDevice(from, command, msg, callback) {
|
|
615
686
|
if (this.zbController && this.zbController.herdsmanStarted && this.stController) {
|
|
616
|
-
this.debug(`
|
|
687
|
+
this.debug(`deleteZigbeeDevice message: ${JSON.stringify(msg)}`);
|
|
617
688
|
const id = msg.id;
|
|
618
689
|
const force = msg.force;
|
|
619
690
|
const sysid = id.replace(this.adapter.namespace + '.', '0x');
|
|
620
691
|
const devId = id.replace(this.adapter.namespace + '.', '');
|
|
621
|
-
this.debug(`
|
|
692
|
+
this.debug(`deleteZigbeeDevice sysid: ${sysid}`);
|
|
622
693
|
const dev = this.zbController.getDevice(sysid);
|
|
623
694
|
if (!dev) {
|
|
624
695
|
this.info(`Attempted to delete device ${devId} - the device is not known to the zigbee controller.`);
|
|
@@ -736,7 +807,6 @@ class Commands {
|
|
|
736
807
|
this.debug(`updateLocalConfigItems : ${JSON.stringify(msg)}`);
|
|
737
808
|
const target = msg.target.replace(`${this.adapter.namespace}.`, '');
|
|
738
809
|
const entity = await this.zbController.resolveEntity(target);
|
|
739
|
-
//this.warn('entity for ' + target + ' is '+ JSON.stringify(entity))
|
|
740
810
|
if (entity && !entity.mapped) {
|
|
741
811
|
this.warn('unable to set local Override for device whithout mapped model');
|
|
742
812
|
return;
|
|
@@ -744,17 +814,44 @@ class Commands {
|
|
|
744
814
|
if (msg.data)
|
|
745
815
|
{
|
|
746
816
|
for (const prop in msg.data) {
|
|
817
|
+
if (prop==='options') {
|
|
818
|
+
// we need to trigger the option change
|
|
819
|
+
const changed_from = entity.options;
|
|
820
|
+
const changed_to = msg.data.prop;
|
|
821
|
+
/*
|
|
822
|
+
const allKeys = Object.keys(changed_from);
|
|
823
|
+
for (const nk of Object.keys(changed_to)) {
|
|
824
|
+
if (allKeys.includes(nk)) continue;
|
|
825
|
+
allKeys.push(nk);
|
|
826
|
+
}
|
|
827
|
+
for (const key of allKeys) {
|
|
828
|
+
if (changed_from.hasOwnProperty(key) && changed_to.hasOwnProperty(key) && changed_from[key] == changed_to[key])
|
|
829
|
+
{
|
|
830
|
+
delete changed_from[key];
|
|
831
|
+
delete changed_to[key];
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
*/
|
|
835
|
+
this.zbController.callExtensionMethod(
|
|
836
|
+
'onZigbeeEvent',
|
|
837
|
+
[{'device': entity.device, 'type': 'deviceOptionsChanged', from: changed_from, to:changed_to || {}, }, entity ? entity.mapped : null]);
|
|
838
|
+
}
|
|
747
839
|
this.debug('enumerating data: ' + prop);
|
|
748
840
|
await this.stController.localConfig.updateLocalOverride(target, (entity ? entity.mapped.model : 'group'), prop, msg.data[prop], msg.global);
|
|
749
841
|
}
|
|
750
842
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
843
|
+
try {
|
|
844
|
+
if (entity) {
|
|
845
|
+
this.debug('updateLocalConfigItems with Entity');
|
|
846
|
+
this.stController.updateDev(target, entity.mapped.model, entity.mapped.model, () => {this.adapter.sendTo(from, command, {}, callback)});
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
this.debug('updateLocalConfigItems without Entity');
|
|
850
|
+
this.stController.updateDev(target, undefined, 'group',() => {this.adapter.sendTo(from, command, {}, callback)});
|
|
851
|
+
}
|
|
754
852
|
}
|
|
755
|
-
|
|
756
|
-
this.
|
|
757
|
-
this.stController.updateDev(target, undefined, 'group',() => {this.adapter.sendTo(from, command, {}, callback)});
|
|
853
|
+
catch (error) {
|
|
854
|
+
this.adapter.sendTo(from, command, {err: error.message}, callback);
|
|
758
855
|
}
|
|
759
856
|
}
|
|
760
857
|
}
|
|
@@ -824,65 +921,32 @@ class Commands {
|
|
|
824
921
|
this.adapter.logToPairing(`Error: ${result.error}`)
|
|
825
922
|
}
|
|
826
923
|
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
|
-
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async triggerIconDownload(obj) {
|
|
927
|
+
if (!this.stController) {
|
|
928
|
+
this.adapter.sendTo(obj.from, obj.command, {msg:'No States controller'}, obj.callback);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const clients = await this.adapter.getDevicesAsync();
|
|
932
|
+
const Promises = [];
|
|
933
|
+
for (const client of clients) {
|
|
934
|
+
if (client.common.modelIcon && client.common.icon && client.common.modelIcon.startsWith('http')) {
|
|
935
|
+
const filestatus = await this.adapter.fileExistsAsync(this.adapter.namespace, client.common.icon);
|
|
936
|
+
if (!filestatus)
|
|
937
|
+
Promises.push(this.stController.downloadIconToAdmin(client.common.modelIcon, client.common.icon))
|
|
883
938
|
}
|
|
939
|
+
|
|
940
|
+
}
|
|
941
|
+
const NumDownloads = Promises.length;
|
|
942
|
+
if (NumDownloads) {
|
|
943
|
+
this.adapter.sendTo(obj.from, obj.command, {msg:`${NumDownloads} downloads triggered.`}, obj.callback);
|
|
944
|
+
Promise.all(Promises);
|
|
884
945
|
}
|
|
885
|
-
|
|
946
|
+
else {
|
|
947
|
+
this.adapter.sendTo(obj.from, obj.command, {msg:'Nothing to download'}, obj.callback);
|
|
948
|
+
}
|
|
949
|
+
|
|
886
950
|
}
|
|
887
951
|
}
|
|
888
952
|
|