iobroker.zigbee 3.0.5 → 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 +34 -0
- package/admin/admin.js +475 -230
- package/admin/i18n/de/translations.json +16 -16
- package/admin/index_m.html +84 -91
- package/admin/tab_m.html +38 -16
- package/docs/de/readme.md +1 -1
- package/docs/en/readme.md +4 -2
- package/io-package.json +35 -28
- package/lib/DeviceDebug.js +25 -2
- package/lib/binding.js +8 -8
- package/lib/commands.js +386 -326
- package/lib/developer.js +2 -2
- package/lib/devices.js +13 -9
- package/lib/exclude.js +1 -1
- package/lib/exposes.js +56 -24
- package/lib/groups.js +408 -73
- package/lib/localConfig.js +23 -12
- package/lib/networkmap.js +10 -2
- package/lib/states.js +32 -2
- package/lib/statescontroller.js +361 -209
- package/lib/utils.js +7 -5
- package/lib/zbDelayedAction.js +4 -4
- package/lib/zbDeviceAvailability.js +102 -46
- package/lib/zbDeviceConfigure.js +7 -0
- package/lib/zbDeviceEvent.js +40 -7
- package/lib/zigbeecontroller.js +552 -75
- package/main.js +168 -505
- package/package.json +8 -11
- 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
|
}
|
|
@@ -240,12 +246,20 @@ class Commands {
|
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
async letsPairing(from, command, message, callback) {
|
|
243
|
-
if (this.zbController) {
|
|
249
|
+
if (this.zbController && this.zbController.herdsmanStarted) {
|
|
244
250
|
let devId = '';
|
|
245
251
|
if (message) {
|
|
246
252
|
if (message.id && message.id != undefined) {
|
|
247
253
|
devId = getZbId(message.id);
|
|
248
254
|
}
|
|
255
|
+
if (typeof devId == 'number') {
|
|
256
|
+
this.adapter.sendTo(
|
|
257
|
+
from, command,
|
|
258
|
+
{error: 'Pairing on a group is not supported'},
|
|
259
|
+
callback
|
|
260
|
+
);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
249
263
|
if (message.code && message.code != undefined) {
|
|
250
264
|
try {
|
|
251
265
|
this.debug(`letsPairing called with code ${message.code}`);
|
|
@@ -273,23 +287,29 @@ class Commands {
|
|
|
273
287
|
}
|
|
274
288
|
// allow devices to join the network within 60 secs
|
|
275
289
|
// this.adapter.logToPairing('Pairing started ' + devId, true);
|
|
276
|
-
|
|
277
290
|
let cTimer = Number(this.adapter.config.countDown);
|
|
278
291
|
if (!this.adapter.config.countDown || !cTimer) {
|
|
279
292
|
cTimer = 60;
|
|
280
293
|
}
|
|
294
|
+
if (message.stop) cTimer = 0;
|
|
281
295
|
|
|
282
|
-
this.zbController.permitJoin(cTimer, devId
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
296
|
+
if (await this.zbController.permitJoin(cTimer, devId)) {
|
|
297
|
+
this.adapter.setState('info.pairingMode', cTimer > 0, true);
|
|
298
|
+
if (devId) this.zbController.emit(`Pairing started for ${devId}`);
|
|
299
|
+
this.adapter.sendTo(from, command, cTimer ? 'Start pairing!':'Stop pairing!', callback);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
this.adapter.sendTo(
|
|
303
|
+
from, command,
|
|
304
|
+
{error: 'Error opening the network'},
|
|
305
|
+
callback
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
290
310
|
this.adapter.sendTo(
|
|
291
311
|
from, command,
|
|
292
|
-
{error: '
|
|
312
|
+
{error: 'No connection to zigbee Hardware!'},
|
|
293
313
|
callback
|
|
294
314
|
);
|
|
295
315
|
}
|
|
@@ -316,268 +336,310 @@ class Commands {
|
|
|
316
336
|
}
|
|
317
337
|
}
|
|
318
338
|
|
|
319
|
-
async getDevices(from, command, id, callback) {
|
|
320
|
-
if (this.zbController && this.zbController.herdsmanStarted) {
|
|
321
|
-
this.debug(`getDevices called from ${from} with command ${JSON.stringify(command)} and id ${JSON.stringify(id)}`);
|
|
322
|
-
const pairedDevices = await this.zbController.getClients(true);
|
|
323
|
-
const groups = {};
|
|
324
|
-
let rooms;
|
|
325
|
-
this.adapter.getEnumsAsync('enum.rooms')
|
|
326
|
-
.then(enums => {
|
|
327
|
-
// rooms
|
|
328
|
-
rooms = enums['enum.rooms'];
|
|
329
|
-
})
|
|
330
|
-
// get all adapter devices
|
|
331
|
-
.then(() => this.adapter.getDevicesAsync())
|
|
332
|
-
.then(async result => {
|
|
333
|
-
const alls = id ? await this.adapter.getStatesAsync(id + '.*') : await this.adapter.getStatesAsync('*');
|
|
334
|
-
const allst = id ? await this.adapter.getStatesOfAsync(id) : await this.adapter.getStatesOfAsync();
|
|
335
|
-
result = result.filter(item => !id || id === item._id);
|
|
336
|
-
// get device states and groups
|
|
337
|
-
result.forEach(async devInfo => {
|
|
338
|
-
if (devInfo._id) {
|
|
339
|
-
// battery and link_quality
|
|
340
|
-
const lqState = alls[`${devInfo._id}.link_quality`];
|
|
341
|
-
devInfo.link_quality = lqState ? lqState.val : undefined;
|
|
342
|
-
devInfo.link_quality_lc = lqState ? lqState.lc : undefined;
|
|
343
|
-
const batState = alls[`${devInfo._id}.battery`];
|
|
344
|
-
devInfo.battery = batState ? batState.val : undefined;
|
|
345
|
-
// devInfo.states = states || {};
|
|
346
|
-
|
|
347
|
-
const states = allst.filter(item => item._id.startsWith(devInfo._id));
|
|
348
|
-
|
|
349
|
-
// put only allowed states
|
|
350
|
-
devInfo.statesDef = (states || []).filter(stateDef => {
|
|
351
|
-
const sid = stateDef._id.replace(this.adapter.namespace + '.', '');
|
|
352
|
-
const names = sid.split('.');
|
|
353
|
-
if (stateDef.common.color || names.length > 2) return false;
|
|
354
|
-
return !disallowedDashStates.includes(names.pop());
|
|
355
|
-
|
|
356
|
-
}).map(stateDef => {
|
|
357
|
-
const name = stateDef.common.name;
|
|
358
|
-
const devname = devInfo.common.name;
|
|
359
|
-
// replace state
|
|
360
|
-
return {
|
|
361
|
-
id: stateDef._id,
|
|
362
|
-
name: typeof name === 'string' ? name.replace(devname, '') : name,
|
|
363
|
-
type: stateDef.common.type,
|
|
364
|
-
read: stateDef.common.read,
|
|
365
|
-
write: stateDef.common.write,
|
|
366
|
-
val: alls[stateDef._id] ? alls[stateDef._id].val : undefined,
|
|
367
|
-
role: stateDef.common.role,
|
|
368
|
-
unit: stateDef.common.unit,
|
|
369
|
-
states: stateDef.common.states,
|
|
370
|
-
};
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
return result;
|
|
375
|
-
})
|
|
376
|
-
.then(async result => {
|
|
377
|
-
// combine info
|
|
378
|
-
const devices = [];
|
|
379
|
-
for (const devInfo of result) {
|
|
380
|
-
if (devInfo._id.indexOf('group') > 0) {
|
|
381
|
-
devInfo.icon = 'img/group.png';
|
|
382
|
-
devInfo.vendor = 'ioBroker';
|
|
383
|
-
// get group members and store them
|
|
384
|
-
const match = /zigbee.\d.group_([0-9]+)/.exec(devInfo._id);
|
|
385
|
-
if (match && match.length > 1) {
|
|
386
|
-
const groupID = Number(match[1]);
|
|
387
|
-
const groupmembers = await this.zbController.getGroupMembersFromController(groupID);
|
|
388
|
-
this.debug(`group members for group ${groupID}: ${JSON.stringify(groupmembers)}`);
|
|
389
|
-
if (groupmembers && groupmembers.length > 0) {
|
|
390
|
-
const memberinfo = [];
|
|
391
|
-
for (const member of groupmembers) {
|
|
392
|
-
if (member && typeof member.ieee === 'string') {
|
|
393
|
-
if (groups) {
|
|
394
|
-
const grouparray = groups[member.ieee];
|
|
395
|
-
if (grouparray) {
|
|
396
|
-
if (!grouparray.includes(groupID)) {
|
|
397
|
-
groups[member.ieee].push(groupID);
|
|
398
|
-
}
|
|
399
|
-
} else {
|
|
400
|
-
groups[member.ieee] = [groupID];
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
const device = await this.adapter.getObjectAsync(`${this.adapter.namespace}.${member.ieee.substr(2)}`);
|
|
404
|
-
if (device) {
|
|
405
|
-
member.device = device.common.name;
|
|
406
|
-
} else {
|
|
407
|
-
member.device = 'unknown';
|
|
408
|
-
}
|
|
409
|
-
memberinfo.push(member);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
devInfo.memberinfo = memberinfo;
|
|
413
|
-
this.debug(`memberinfo for ${match[1]}: ${JSON.stringify(devInfo.memberinfo)}`);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
} else {
|
|
417
|
-
const modelDesc = statesMapping.findModel(devInfo.common.type);
|
|
418
|
-
devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
|
|
419
|
-
devInfo.vendor = modelDesc ? modelDesc.vendor : '';
|
|
420
|
-
const legacyDesc = statesMapping.findModel(devInfo.common.type, true);
|
|
421
|
-
devInfo.legacyIcon = (legacyDesc && legacyDesc.icon) ? legacyDesc.icon : undefined;
|
|
422
|
-
}
|
|
423
339
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// check configuration
|
|
427
|
-
try {
|
|
428
|
-
if (devInfo.info) {
|
|
429
|
-
const result = await this.zbController.callExtensionMethod(
|
|
430
|
-
'shouldConfigure',
|
|
431
|
-
[devInfo.info.device, devInfo.info.mapped],
|
|
432
|
-
);
|
|
433
|
-
if (result.length > 0) devInfo.isConfigured = !result[0];
|
|
434
|
-
}
|
|
435
|
-
} catch (error) {
|
|
436
|
-
this.warn('error calling shouldConfigure: ' + error && error.message ? error.message : 'no error message');
|
|
437
|
-
}
|
|
340
|
+
async handleDeviceforInfo(device, states) {
|
|
341
|
+
}
|
|
438
342
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
return devices;
|
|
456
|
-
})
|
|
457
|
-
.then(async (devices) => {
|
|
458
|
-
// fill group info
|
|
459
|
-
for (const groupdev in groups) {
|
|
460
|
-
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]
|
|
461
359
|
if (device) {
|
|
462
|
-
device
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// append devices that paired but not created
|
|
466
|
-
if (!id) {
|
|
467
|
-
for (const d of pairedDevices) {
|
|
468
|
-
const device = await this.zbController.resolveEntity(d.ieeeAddr);
|
|
469
|
-
if (!device || !device.device) {
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
const exists = devices.find((dev) => (dev._id && device.device.ieeeAddr === getZbId(dev._id)));
|
|
473
|
-
if (!exists) {
|
|
474
|
-
devices.push({
|
|
475
|
-
_id: device.device.ieeeAddr,
|
|
476
|
-
icon: 'img/unknown.png',
|
|
477
|
-
paired: true,
|
|
478
|
-
info: device,
|
|
479
|
-
common: {
|
|
480
|
-
name: undefined,
|
|
481
|
-
type: undefined,
|
|
482
|
-
},
|
|
483
|
-
native: {}
|
|
484
|
-
});
|
|
485
|
-
}
|
|
360
|
+
member.device = device.common.name;
|
|
361
|
+
} else {
|
|
362
|
+
member.device = 'unknown';
|
|
486
363
|
}
|
|
364
|
+
memberinfo.push(member);
|
|
487
365
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
.
|
|
491
|
-
|
|
492
|
-
const rv = { devices:devices, inLog:this.adapter.deviceDebug.logStatus, byId:this.adapter.deviceDebug.collectDebugData() }
|
|
493
|
-
if (this.stController) {
|
|
494
|
-
rv.clean = this.stController.CleanupRequired();
|
|
495
|
-
rv.errors = this.stController.getStashedErrors();
|
|
496
|
-
rv.debugDevices = this.stController.debugDevices;
|
|
497
|
-
}
|
|
498
|
-
this.adapter.sendTo(from, command, rv, callback);
|
|
499
|
-
})
|
|
500
|
-
.catch(err => {
|
|
501
|
-
this.error(`getDevices error: ${err.stack}`);
|
|
502
|
-
this.adapter.sendTo(from, command, {error: `Error enumerating devices : ${err && err.message ? err.message : ''}`}, callback);
|
|
503
|
-
});
|
|
504
|
-
} else {
|
|
505
|
-
this.adapter.sendTo(from, command, {error: 'No active connection to Zigbee Hardware!'}, callback);
|
|
366
|
+
}
|
|
367
|
+
group.memberinfo = memberinfo;
|
|
368
|
+
this.debug(`memberinfo for ${match[1]}: ${JSON.stringify(group.memberinfo)}`);
|
|
369
|
+
}
|
|
506
370
|
}
|
|
507
371
|
}
|
|
508
372
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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,
|
|
520
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;
|
|
521
411
|
|
|
522
|
-
const coordinatorVersion = await this.adapter.zbController.herdsman.getCoordinatorVersion();
|
|
523
412
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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,
|
|
443
|
+
})
|
|
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)}`);
|
|
464
|
+
}
|
|
465
|
+
return rv;
|
|
466
|
+
}
|
|
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);
|
|
531
529
|
}
|
|
532
530
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
+
}
|
|
562
|
+
|
|
563
|
+
async getCoordinatorInfo(from, command, callback) {
|
|
564
|
+
const coordinatorinfo = {
|
|
565
|
+
installSource: 'IADefault_1',
|
|
566
|
+
channel: '-1',
|
|
567
|
+
port: 'Default_1',
|
|
568
|
+
installedVersion: 'Default_1',
|
|
569
|
+
type: 'Default_1',
|
|
570
|
+
revision: 'unknown',
|
|
571
|
+
version: 'unknown',
|
|
572
|
+
herdsman: this.adapter.zhversion,
|
|
573
|
+
converters: this.adapter.zhcversion,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const coordinatorVersion = this.zbController && this.zbController.herdsmanStarted ? await this.adapter.zbController.herdsman.getCoordinatorVersion() : {};
|
|
577
|
+
|
|
578
|
+
await this.adapter.getForeignObject(`system.adapter.${this.adapter.namespace}`, (err, obj) => {
|
|
579
|
+
if (!err && obj) {
|
|
580
|
+
if (obj.common.installedFrom && obj.common.installedFrom.includes('://')) {
|
|
581
|
+
const instFrom = obj.common.installedFrom;
|
|
582
|
+
coordinatorinfo.installSource = instFrom.replace('tarball', 'commit');
|
|
583
|
+
} else {
|
|
584
|
+
coordinatorinfo.installSource = obj.common.installedFrom;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
coordinatorinfo.port = obj.native.port;
|
|
589
|
+
coordinatorinfo.type = obj.native.adapterType;
|
|
590
|
+
coordinatorinfo.channel = obj.native.channel;
|
|
591
|
+
coordinatorinfo.autostart = this.adapter.config.autostart;
|
|
592
|
+
coordinatorinfo.installedVersion = obj.common.version;
|
|
593
|
+
if (coordinatorVersion && coordinatorVersion.type && coordinatorVersion.meta) {
|
|
594
|
+
coordinatorinfo.type = coordinatorVersion.type;
|
|
595
|
+
const meta = coordinatorVersion.meta;
|
|
596
|
+
if (typeof meta == 'object') {
|
|
597
|
+
if (meta.hasOwnProperty('revision')) {
|
|
598
|
+
coordinatorinfo.revision = meta.revision;
|
|
599
|
+
}
|
|
600
|
+
let vt = 'x-';
|
|
601
|
+
if (meta.hasOwnProperty('transportrev')) {
|
|
602
|
+
vt = meta.transportrev + '-';
|
|
603
|
+
}
|
|
604
|
+
if (meta.hasOwnProperty('product')) {
|
|
605
|
+
vt = vt + meta.product + '.';
|
|
606
|
+
} else {
|
|
607
|
+
vt = vt + 'x.';
|
|
608
|
+
}
|
|
609
|
+
if (meta.hasOwnProperty('majorrel')) {
|
|
610
|
+
vt = vt + meta.majorrel + '.';
|
|
611
|
+
} else {
|
|
612
|
+
vt = vt + 'x.';
|
|
613
|
+
}
|
|
614
|
+
if (meta.hasOwnProperty('minorrel')) {
|
|
615
|
+
vt = vt + meta.minorrel + '.';
|
|
616
|
+
} else {
|
|
617
|
+
vt = vt + 'x.';
|
|
618
|
+
}
|
|
619
|
+
if (meta.hasOwnProperty('maintrel')) {
|
|
620
|
+
vt = vt + meta.maintrel + '.';
|
|
621
|
+
} else {
|
|
622
|
+
vt = vt + 'x.';
|
|
569
623
|
}
|
|
624
|
+
coordinatorinfo.version = vt;
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
coordinatorinfo.version = 'illegal data';
|
|
628
|
+
coordinatorinfo.revision = 'illegal data';
|
|
570
629
|
}
|
|
571
|
-
} catch {
|
|
572
|
-
this.warn('exception raised in getCoordinatorInfo');
|
|
573
630
|
}
|
|
631
|
+
else {
|
|
632
|
+
coordinatorinfo.version = this.adapter.config.autostart ? 'not connected' : 'autostart not set';
|
|
633
|
+
coordinatorinfo.revision = this.adapter.config.autostart ? 'not connected' : 'autostart not set';
|
|
574
634
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
635
|
+
}
|
|
636
|
+
} catch {
|
|
637
|
+
this.warn('exception raised in getCoordinatorInfo');
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
this.debug(`getCoordinatorInfo result: ${JSON.stringify(coordinatorinfo)}`);
|
|
641
|
+
this.adapter.sendTo(from, command, coordinatorinfo, callback);
|
|
642
|
+
});
|
|
581
643
|
}
|
|
582
644
|
|
|
583
645
|
|
|
@@ -590,28 +652,29 @@ class Commands {
|
|
|
590
652
|
}
|
|
591
653
|
}
|
|
592
654
|
|
|
593
|
-
|
|
655
|
+
deleteZigbeeDevice(from, command, msg, callback) {
|
|
594
656
|
if (this.zbController && this.zbController.herdsmanStarted && this.stController) {
|
|
595
|
-
this.debug(`
|
|
657
|
+
this.debug(`deleteZigbeeDevice message: ${JSON.stringify(msg)}`);
|
|
596
658
|
const id = msg.id;
|
|
597
659
|
const force = msg.force;
|
|
598
660
|
const sysid = id.replace(this.adapter.namespace + '.', '0x');
|
|
599
661
|
const devId = id.replace(this.adapter.namespace + '.', '');
|
|
600
|
-
this.debug(`
|
|
662
|
+
this.debug(`deleteZigbeeDevice sysid: ${sysid}`);
|
|
601
663
|
const dev = this.zbController.getDevice(sysid);
|
|
602
664
|
if (!dev) {
|
|
603
|
-
this.
|
|
604
|
-
this.debug(`Try delete dev ${devId} from iobroker.`);
|
|
665
|
+
this.info(`Attempted to delete device ${devId} - the device is not known to the zigbee controller.`);
|
|
605
666
|
this.stController.deleteObj(devId, () =>
|
|
606
667
|
this.adapter.sendTo(from, command, {}, callback));
|
|
607
668
|
return;
|
|
608
669
|
}
|
|
670
|
+
this.info(`${force ? 'Force removing' : 'Gracefully removing '} device ${devId} from the network.`);
|
|
609
671
|
this.zbController.remove(sysid, force, err => {
|
|
610
672
|
if (!err) {
|
|
611
|
-
this.
|
|
612
|
-
|
|
673
|
+
this.info('Device removed from the network, deleting objects.')
|
|
674
|
+
this.stController.deleteObj(devId, () => {
|
|
675
|
+
this.adapter.sendTo(from, command, {}, callback);
|
|
676
|
+
});
|
|
613
677
|
} else {
|
|
614
|
-
this.debug(`Error on remove! ${err}`);
|
|
615
678
|
this.adapter.sendTo(from, command, {error: err}, callback);
|
|
616
679
|
}
|
|
617
680
|
});
|
|
@@ -712,7 +775,7 @@ class Commands {
|
|
|
712
775
|
async updateLocalConfigItems(from, command, msg, callback) {
|
|
713
776
|
if (this.stController) {
|
|
714
777
|
this.debug(`updateLocalConfigItems : ${JSON.stringify(msg)}`);
|
|
715
|
-
const target = msg.
|
|
778
|
+
const target = msg.target.replace(`${this.adapter.namespace}.`, '');
|
|
716
779
|
const entity = await this.zbController.resolveEntity(target);
|
|
717
780
|
//this.warn('entity for ' + target + ' is '+ JSON.stringify(entity))
|
|
718
781
|
if (entity && !entity.mapped) {
|
|
@@ -722,6 +785,28 @@ class Commands {
|
|
|
722
785
|
if (msg.data)
|
|
723
786
|
{
|
|
724
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
|
+
}
|
|
725
810
|
this.debug('enumerating data: ' + prop);
|
|
726
811
|
await this.stController.localConfig.updateLocalOverride(target, (entity ? entity.mapped.model : 'group'), prop, msg.data[prop], msg.global);
|
|
727
812
|
}
|
|
@@ -796,63 +881,38 @@ class Commands {
|
|
|
796
881
|
}
|
|
797
882
|
|
|
798
883
|
async testConnection(from, command, msg, callback) {
|
|
799
|
-
|
|
800
|
-
if (
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
this.adapter.logToPairing(`connected successfully to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`);
|
|
820
|
-
this.debug(`connected successfully to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`);
|
|
821
|
-
this.adapter.sendTo(from, command, {}, callback)
|
|
822
|
-
})
|
|
823
|
-
client.on('error', (error) => {
|
|
824
|
-
const msg = `unable to connect to ${ip} port ${netAddress.port ? netAddress.port : 80} : ${error && error.message ? error.message : 'no message given'}`
|
|
825
|
-
this.error(msg);
|
|
826
|
-
this.adapter.logToPairing(`Error: ${msg}`);
|
|
827
|
-
this.adapter.sendTo(from, command, {error:msg}, callback);
|
|
828
|
-
});
|
|
829
|
-
})
|
|
830
|
-
}
|
|
831
|
-
else
|
|
832
|
-
{
|
|
833
|
-
try {
|
|
834
|
-
const port = msg.address.trim();
|
|
835
|
-
this.adapter.logToPairing(`reading access rights for ${port}`);
|
|
836
|
-
access(port, constants.R_OK | constants.W_OK, (error) => {
|
|
837
|
-
if (error) {
|
|
838
|
-
const msg = `unable to access ${port} : ${error && error.message ? error.message : 'no message given'}`;
|
|
839
|
-
this.error(msg);
|
|
840
|
-
this.adapter.logToPairing(`Error: ${msg}`);
|
|
841
|
-
this.adapter.sendTo(from, command, {error:msg}, callback);
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
this.adapter.logToPairing(`read and write access available for ${port}`);
|
|
845
|
-
this.adapter.sendTo(from, command, {}, callback);
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
catch (error) {
|
|
849
|
-
const msg = `File access error: ${error && error.message ? error.message : 'no message given'}`;
|
|
850
|
-
this.error(msg);
|
|
851
|
-
this.adapter.logToPairing(`Error: ${msg}`);
|
|
852
|
-
this.adapter.sendTo(from, command, {error:msg}, callback);
|
|
853
|
-
}
|
|
884
|
+
const result = await this.adapter.testConnection(msg.address, true);
|
|
885
|
+
if (result.error) {
|
|
886
|
+
this.error(result.error);
|
|
887
|
+
this.adapter.logToPairing(`Error: ${result.error}`)
|
|
888
|
+
}
|
|
889
|
+
this.adapter.sendTo(from, command, result, callback);
|
|
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))
|
|
854
904
|
}
|
|
905
|
+
|
|
855
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);
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
this.adapter.sendTo(obj.from, obj.command, {msg:'Nothing to download'}, obj.callback);
|
|
914
|
+
}
|
|
915
|
+
|
|
856
916
|
}
|
|
857
917
|
}
|
|
858
918
|
|