iobroker.zigbee 1.10.14 → 2.0.1
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 +52 -7
- package/admin/admin.js +312 -125
- package/admin/img/PTM 215Z.png +0 -0
- package/admin/img/group_0.png +0 -0
- package/admin/img/group_x.png +0 -0
- package/admin/img/philips_hue_lom001.png +0 -0
- package/admin/index_m.html +95 -45
- package/admin/tab_m.html +117 -49
- package/docs/de/img/Zigbee_config_de.png +0 -0
- package/docs/de/img/Zigbee_tab_de.png +0 -0
- package/docs/en/img/Zigbee_config_en.png +0 -0
- package/docs/en/img/Zigbee_tab_en.png +0 -0
- package/docs/tutorial/groups-1.png +0 -0
- package/docs/tutorial/groups-2.png +0 -0
- package/docs/tutorial/tab-dev-1.png +0 -0
- package/io-package.json +43 -55
- package/lib/colors.js +7 -0
- package/lib/commands.js +125 -15
- package/lib/developer.js +0 -0
- package/lib/devices.js +78 -74
- package/lib/exclude.js +30 -54
- package/lib/exposes.js +224 -249
- package/lib/groups.js +80 -25
- package/lib/localConfig.js +295 -0
- package/lib/ota.js +0 -0
- package/lib/statescontroller.js +412 -185
- package/lib/utils.js +1 -1
- package/lib/zbDeviceAvailability.js +15 -23
- package/lib/zbDeviceConfigure.js +0 -0
- package/lib/zigbeecontroller.js +396 -252
- package/main.js +159 -54
- package/package.json +6 -5
package/lib/zigbeecontroller.js
CHANGED
|
@@ -11,8 +11,12 @@ const DeviceAvailabilityExt = require('./zbDeviceAvailability');
|
|
|
11
11
|
const DeviceConfigureExt = require('./zbDeviceConfigure');
|
|
12
12
|
const DeviceEventExt = require('./zbDeviceEvent');
|
|
13
13
|
const DelayedActionExt = require('./zbDelayedAction');
|
|
14
|
+
const Groups = require('./groups');
|
|
15
|
+
|
|
14
16
|
const utils = require('./utils');
|
|
15
17
|
const { waitForDebugger } = require('inspector');
|
|
18
|
+
const { table } = require('console');
|
|
19
|
+
const { extract } = require('tar');
|
|
16
20
|
const groupConverters = [
|
|
17
21
|
zigbeeHerdsmanConverters.toZigbee.light_onoff_brightness,
|
|
18
22
|
zigbeeHerdsmanConverters.toZigbee.light_color_colortemp,
|
|
@@ -68,6 +72,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
68
72
|
new DeviceEventExt(this, {}),
|
|
69
73
|
new DelayedActionExt(this, {}),
|
|
70
74
|
];
|
|
75
|
+
this.herdsmanTimeoutRegexp = new RegExp(/(\d+)ms/);
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
configure(options) {
|
|
@@ -129,6 +134,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
129
134
|
this.herdsman.on('deviceJoined', this.handleDeviceJoined.bind(this));
|
|
130
135
|
this.herdsman.on('deviceLeave', this.handleDeviceLeave.bind(this));
|
|
131
136
|
this.herdsman.on('message', this.handleMessage.bind(this));
|
|
137
|
+
this.herdsman.on('permitJoinChanged', this.handlePermitJoinChanged.bind(this));
|
|
132
138
|
|
|
133
139
|
await this.herdsman.start();
|
|
134
140
|
|
|
@@ -147,10 +153,16 @@ class ZigbeeController extends EventEmitter {
|
|
|
147
153
|
i--;
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
this.
|
|
156
|
+
this.info(`Zigbee network parameters: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${extPanIDDebug}`);
|
|
151
157
|
} catch (e) {
|
|
158
|
+
try {
|
|
159
|
+
await this.herdsman.stop();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
this.warn(`Starting zigbee-herdsman problem : ${error && error.message ? error.message : 'no error message'}`)
|
|
163
|
+
}
|
|
152
164
|
this.sendError(e);
|
|
153
|
-
this.error(`Starting zigbee-herdsman problem : ${
|
|
165
|
+
this.error(`Starting zigbee-herdsman problem : ${(e && e.message ? e.message : 'no error message')}`);
|
|
154
166
|
throw 'Error herdsman start';
|
|
155
167
|
}
|
|
156
168
|
// Check if we have to turn off the LED
|
|
@@ -197,38 +209,55 @@ class ZigbeeController extends EventEmitter {
|
|
|
197
209
|
// Call extensions
|
|
198
210
|
this.callExtensionMethod('onZigbeeStarted', []);
|
|
199
211
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
212
|
+
const deviceIterator = this.getClientIterator();
|
|
213
|
+
let deviceCount = 0;
|
|
214
|
+
try {
|
|
215
|
+
for (const device of deviceIterator) {
|
|
216
|
+
deviceCount++;
|
|
217
|
+
const entity = await this.resolveEntity(device);
|
|
218
|
+
if (!entity) {
|
|
219
|
+
this.warn('failed to resolve Entity for ' + device.ieeeAddr);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
this.adapter.getObject(device.ieeeAddr.substr(2), (err, obj) => {
|
|
223
|
+
if (obj && obj.common && obj.common.deactivated) {
|
|
224
|
+
this.callExtensionMethod('deregisterDevicePing', [device, entity]);
|
|
225
|
+
} else {
|
|
226
|
+
this.callExtensionMethod('registerDevicePing', [device, entity]);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
// ensure that objects for all found clients are present
|
|
207
230
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.adapter.getObject(device.ieeeAddr.substr(2), (err, obj) => {
|
|
211
|
-
if (obj && obj.common && obj.common.deactivated) {
|
|
212
|
-
this.callExtensionMethod('deregisterDevicePing', [device, entity]);
|
|
213
|
-
} else {
|
|
214
|
-
this.callExtensionMethod('registerDevicePing', [device, entity]);
|
|
231
|
+
if (entity.mapped) {
|
|
232
|
+
this.emit('new', entity);
|
|
215
233
|
}
|
|
216
|
-
|
|
217
|
-
|
|
234
|
+
this.info(
|
|
235
|
+
(entity.device.ieeeAddr) +
|
|
236
|
+
` (addr ${entity.device.networkAddress}): ` +
|
|
237
|
+
(entity.mapped ?
|
|
238
|
+
`${entity.mapped.model} - ${entity.mapped.vendor} ${entity.mapped.description} ` :
|
|
239
|
+
`Unsupported (model ${entity.device.modelID})`) +
|
|
240
|
+
`(${entity.device.type})`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
218
243
|
|
|
219
|
-
|
|
220
|
-
|
|
244
|
+
// Log zigbee clients on startup
|
|
245
|
+
// const devices = await this.getClients();
|
|
246
|
+
if (deviceCount > 0) {
|
|
247
|
+
this.info(`Currently ${deviceCount} devices are joined:`);
|
|
248
|
+
} else {
|
|
249
|
+
this.info(`Currently no devices.`);
|
|
221
250
|
}
|
|
222
|
-
this.info(
|
|
223
|
-
(entity.device.ieeeAddr) +
|
|
224
|
-
` (addr ${entity.device.networkAddress}): ` +
|
|
225
|
-
(entity.mapped ?
|
|
226
|
-
`${entity.mapped.model} - ${entity.mapped.vendor} ${entity.mapped.description} ` :
|
|
227
|
-
`Not supported (model ${entity.device.modelID})`) +
|
|
228
|
-
`(${entity.device.type})`
|
|
229
|
-
);
|
|
230
251
|
}
|
|
231
|
-
|
|
252
|
+
catch (error) {
|
|
253
|
+
this.error('error iterating devices : '+ (error && error.message ? error.message: 'no reason given'));
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
this.getGroups();
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
this.error('error iterating groups : '+ (error && error.message ? error.message: 'no reason given'));
|
|
260
|
+
}
|
|
232
261
|
this.emit('ready');
|
|
233
262
|
}
|
|
234
263
|
|
|
@@ -256,6 +285,12 @@ class ZigbeeController extends EventEmitter {
|
|
|
256
285
|
this.adapter.sendError(error, message);
|
|
257
286
|
}
|
|
258
287
|
|
|
288
|
+
filterHerdsmanError(message) {
|
|
289
|
+
if (typeof message != 'string' || message == '') return 'no error message';
|
|
290
|
+
if (message.match(this.herdsmanTimeoutRegexp)) return 'Timeout';
|
|
291
|
+
return message;
|
|
292
|
+
}
|
|
293
|
+
|
|
259
294
|
callExtensionMethod(method, parameters) {
|
|
260
295
|
const result = [];
|
|
261
296
|
for (const extension of this.extensions) {
|
|
@@ -288,10 +323,24 @@ class ZigbeeController extends EventEmitter {
|
|
|
288
323
|
}
|
|
289
324
|
}
|
|
290
325
|
|
|
326
|
+
getClientIterator(all) {
|
|
327
|
+
if (this.herdsman.database) {
|
|
328
|
+
return this.herdsman.getDevicesIterator( function(device) { return (all? true: device.type !== 'Coordinator')});
|
|
329
|
+
} else {
|
|
330
|
+
return [].values();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
291
334
|
async getGroups() {
|
|
292
335
|
try {
|
|
293
336
|
if (this.herdsman) {
|
|
294
|
-
|
|
337
|
+
const rv = [];
|
|
338
|
+
const groupIterator = this.herdsman.getGroupsIterator();
|
|
339
|
+
for (const g of groupIterator) {
|
|
340
|
+
const members = await this.getGroupMembersFromController(g.groupID);
|
|
341
|
+
rv.push({ id: g.groupID, size: (members ? members.length : 0), stateName: 'group_'+g.groupID});
|
|
342
|
+
}
|
|
343
|
+
return rv;
|
|
295
344
|
} else {
|
|
296
345
|
return null;
|
|
297
346
|
}
|
|
@@ -314,32 +363,55 @@ class ZigbeeController extends EventEmitter {
|
|
|
314
363
|
|
|
315
364
|
async getGroupByID(id) {
|
|
316
365
|
try {
|
|
317
|
-
|
|
366
|
+
const rv = this.herdsman.getGroupByID(id);
|
|
367
|
+
return rv;
|
|
318
368
|
} catch (error) {
|
|
319
369
|
this.sendError(error);
|
|
370
|
+
// this.error('error getting group for ' + id + ' ' + (error && error.message ? error.message : 'without message'));
|
|
320
371
|
return undefined;
|
|
321
372
|
}
|
|
322
373
|
}
|
|
323
374
|
|
|
324
375
|
async verifyGroupExists(id) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
376
|
+
try {
|
|
377
|
+
const nid = typeof id === 'number' ? id : parseInt(id);
|
|
378
|
+
let group = await this.herdsman.getGroupByID(nid);
|
|
379
|
+
if (!group) {
|
|
380
|
+
group = await this.herdsman.createGroup(nid);
|
|
381
|
+
group.toZigbee = groupConverters;
|
|
382
|
+
group.model = 'group';
|
|
383
|
+
this.debug(`verifyGroupExists: created group ${nid}`);
|
|
384
|
+
} else {
|
|
385
|
+
this.debug(`verifyGroupExists: group ${nid} exists`);
|
|
386
|
+
}
|
|
387
|
+
return group
|
|
334
388
|
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
this.error(`verifyGroupExists: ${error && error.message ? error.message : 'unspecified error'}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async rebuildGroupIcon(grp) {
|
|
395
|
+
const g = (typeof grp === 'number') ? this.herdsman.getGroupByID(grp) : grp;
|
|
396
|
+
if (typeof g === 'object' && g.groupID)
|
|
397
|
+
{
|
|
398
|
+
const members = await this.getGroupMembersFromController(g.groupID);
|
|
399
|
+
return `img/group_${members.length}.png`
|
|
400
|
+
}
|
|
401
|
+
return 'img/group_x.png';
|
|
335
402
|
}
|
|
336
403
|
|
|
337
404
|
async addPairingCode(code) {
|
|
338
405
|
this.debug(`calling addPairingCode with ${code}`);
|
|
339
406
|
if (code) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
407
|
+
try {
|
|
408
|
+
await this.herdsman.addInstallCode(code);
|
|
409
|
+
this.info(`added code ${code} for pairing`);
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
this.error(`addPairingCode: ${error && error.message ? error.message : 'unspecified error'}`);
|
|
414
|
+
}
|
|
343
415
|
}
|
|
344
416
|
return false;
|
|
345
417
|
}
|
|
@@ -347,7 +419,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
347
419
|
async getGroupMembersFromController(id) {
|
|
348
420
|
const members = [];
|
|
349
421
|
try {
|
|
350
|
-
const group = await this.getGroupByID(id);
|
|
422
|
+
const group = await this.getGroupByID(Number(id));
|
|
351
423
|
if (group) {
|
|
352
424
|
const groupMembers = group.members;
|
|
353
425
|
for (const member of groupMembers) {
|
|
@@ -369,7 +441,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
369
441
|
} catch (error) {
|
|
370
442
|
this.sendError(error);
|
|
371
443
|
if (error) {
|
|
372
|
-
this.error(`getGroupMembersFromController:
|
|
444
|
+
this.error(`getGroupMembersFromController: ${(error && error.message ? error.message : 'no error message')} ${(error && error.stack ? error.stack : 'no call stack')}`);
|
|
373
445
|
} else {
|
|
374
446
|
this.error('unidentified error in getGroupMembersFromController');
|
|
375
447
|
}
|
|
@@ -378,22 +450,78 @@ class ZigbeeController extends EventEmitter {
|
|
|
378
450
|
}
|
|
379
451
|
|
|
380
452
|
getDevice(key) {
|
|
381
|
-
|
|
453
|
+
try {
|
|
454
|
+
return this.herdsman.getDeviceByIeeeAddr(key);
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
this.error(`getDeviceByIeeeAddr: ${(error && error.message ? error.message : 'no error message')}`);
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
382
460
|
}
|
|
383
461
|
|
|
384
462
|
getDevicesByType(type) {
|
|
385
|
-
|
|
463
|
+
try {
|
|
464
|
+
return this.herdsman.getDevicesByType(type);
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
this.error(`getDevicesByType: ${(error && error.message ? error.message : 'no error message')}`);
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
386
470
|
}
|
|
387
471
|
|
|
388
472
|
getDeviceByNetworkAddress(networkAddress) {
|
|
389
|
-
|
|
473
|
+
try {
|
|
474
|
+
return this.herdsman.getDeviceByNetworkAddress(networkAddress);
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
this.error(`getDeviceByNetworkAddress: ${(error && error.message ? error.message : 'no error message')}`);
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
390
480
|
}
|
|
391
481
|
|
|
392
|
-
async
|
|
393
|
-
|
|
394
|
-
|
|
482
|
+
async analyzeKey(key) {
|
|
483
|
+
const rv = {
|
|
484
|
+
kind: 'device',
|
|
485
|
+
key: key,
|
|
486
|
+
message: 'success'
|
|
487
|
+
}
|
|
488
|
+
if (typeof key === 'object') return rv;
|
|
489
|
+
if (typeof key === 'number') {
|
|
490
|
+
rv.kind = 'group';
|
|
491
|
+
return rv;
|
|
492
|
+
}
|
|
395
493
|
if (typeof key === 'string') {
|
|
396
494
|
if (key === 'coordinator') {
|
|
495
|
+
rv.kind = 'coordinator'
|
|
496
|
+
return rv;
|
|
497
|
+
};
|
|
498
|
+
const kp = key.split('_');
|
|
499
|
+
if (kp[0] === 'group' && kp.length > 1) {
|
|
500
|
+
rv.kind = 'group';
|
|
501
|
+
rv.key = Number(kp[1]);
|
|
502
|
+
return rv;
|
|
503
|
+
}
|
|
504
|
+
if (key.startsWith('0x')) {
|
|
505
|
+
rv.kind = 'ieee'
|
|
506
|
+
return rv;
|
|
507
|
+
};
|
|
508
|
+
if (key.trim().length === 16) {
|
|
509
|
+
rv.key = `0x${key.trim()}`
|
|
510
|
+
rv.kind = 'ieee';
|
|
511
|
+
return rv;
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
rv.message = 'failed';
|
|
515
|
+
return rv;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async resolveEntity(key, ep) {
|
|
519
|
+
// this.warn('resolve entity with key of tyoe ' + typeof (key));
|
|
520
|
+
try {
|
|
521
|
+
const _key = await this.analyzeKey(key);
|
|
522
|
+
if (_key.message !== 'success') return undefined;
|
|
523
|
+
|
|
524
|
+
if (_key.kind == 'coordinator') {
|
|
397
525
|
const coordinator = this.herdsman.getDevicesByType('Coordinator')[0];
|
|
398
526
|
return {
|
|
399
527
|
type: 'device',
|
|
@@ -401,83 +529,86 @@ class ZigbeeController extends EventEmitter {
|
|
|
401
529
|
endpoint: coordinator.getEndpoint(1),
|
|
402
530
|
name: 'Coordinator',
|
|
403
531
|
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
532
|
+
}
|
|
533
|
+
if (_key.kind === 'group') {
|
|
534
|
+
let group = await this.herdsman.getGroupByID(_key.key);
|
|
535
|
+
if (!group) group = await this.herdsman.createGroup(_key.key);
|
|
536
|
+
group.toZigbee = groupConverters;
|
|
537
|
+
group.model = 'group';
|
|
538
|
+
return {
|
|
539
|
+
type: 'group',
|
|
540
|
+
mapped: group,
|
|
541
|
+
group,
|
|
542
|
+
name: `Group ${_key.key}`,
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
}
|
|
546
|
+
if (_key.kind === 'ieee') _key.key = await this.herdsman.getDeviceByIeeeAddr(_key.key);
|
|
547
|
+
const device = _key.key;
|
|
548
|
+
if (device) {
|
|
549
|
+
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
|
550
|
+
const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
|
|
551
|
+
let endpoint;
|
|
552
|
+
if (endpoints && ep != undefined && endpoints[ep]) {
|
|
553
|
+
endpoint = device.getEndpoint(endpoints[ep]);
|
|
554
|
+
} else if (endpoints && endpoints['default']) {
|
|
555
|
+
endpoint = device.getEndpoint(endpoints['default']);
|
|
556
|
+
} else {
|
|
557
|
+
const epNum = parseInt(ep);
|
|
558
|
+
if (!isNaN(epNum)) {
|
|
559
|
+
endpoint = device.getEndpoint(epNum);
|
|
414
560
|
} else {
|
|
415
|
-
|
|
416
|
-
if (!isNaN(epNum)) {
|
|
417
|
-
endpoint = device.getEndpoint(epNum);
|
|
418
|
-
} else {
|
|
419
|
-
endpoint = device.endpoints[0];
|
|
420
|
-
}
|
|
561
|
+
endpoint = device.endpoints[0];
|
|
421
562
|
}
|
|
422
|
-
return {
|
|
423
|
-
type: 'device',
|
|
424
|
-
device,
|
|
425
|
-
mapped,
|
|
426
|
-
endpoint,
|
|
427
|
-
endpoints: device.endpoints,
|
|
428
|
-
name: key,
|
|
429
|
-
};
|
|
430
563
|
}
|
|
564
|
+
return {
|
|
565
|
+
type: 'device',
|
|
566
|
+
device,
|
|
567
|
+
mapped,
|
|
568
|
+
endpoint,
|
|
569
|
+
endpoints: device.endpoints,
|
|
570
|
+
name: device._ieeeAddr,
|
|
571
|
+
};
|
|
431
572
|
}
|
|
432
|
-
} else if (typeof key === 'number') {
|
|
433
|
-
let group = await this.herdsman.getGroupByID(key);
|
|
434
|
-
if (!group) group = await this.herdsman.createGroup(key);
|
|
435
|
-
group.toZigbee = groupConverters;
|
|
436
|
-
group.model = 'group';
|
|
437
|
-
return {
|
|
438
|
-
type: 'group',
|
|
439
|
-
mapped: group,
|
|
440
|
-
group,
|
|
441
|
-
name: `Group ${key}`,
|
|
442
|
-
};
|
|
443
|
-
} else {
|
|
444
|
-
let mapped;
|
|
445
|
-
try {
|
|
446
|
-
mapped = await zigbeeHerdsmanConverters.findByDevice(key);
|
|
447
|
-
} catch (err) {
|
|
448
|
-
this.error(`zigbeeHerdsmanConverters findByDevice ${key.ieeeAddr}`);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return {
|
|
452
|
-
type: 'device',
|
|
453
|
-
device: key,
|
|
454
|
-
mapped: mapped,
|
|
455
|
-
name: key.type === 'Coordinator' ? 'Coordinator' : key.ieeeAddr,
|
|
456
|
-
};
|
|
457
573
|
}
|
|
574
|
+
catch (error)
|
|
575
|
+
{
|
|
576
|
+
this.error('Resolve entity error: ' + (error && error.message ? error.message : 'no reason given'))
|
|
577
|
+
}
|
|
578
|
+
return undefined;
|
|
458
579
|
}
|
|
459
580
|
|
|
460
581
|
async incMsgHandler(message) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
582
|
+
try {
|
|
583
|
+
this.debug('incoming msg', message);
|
|
584
|
+
const device = await this.herdsman.getDeviceByIeeeAddr(message.srcaddr);
|
|
585
|
+
if (!device) {
|
|
586
|
+
this.debug('Message without device!');
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
// We can't handle devices without modelId.
|
|
590
|
+
if (!device.modelId) {
|
|
591
|
+
this.debug('Message without modelId!');
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
this.event('msg', device.ieeeAddr, message, {
|
|
595
|
+
modelId: device.modelId
|
|
596
|
+
});
|
|
466
597
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
this.debug('Message without modelId!');
|
|
470
|
-
return;
|
|
598
|
+
catch (error) {
|
|
599
|
+
this.error('incMsgHandler: ' + (error && error.message ? error.message : 'no error message'));
|
|
471
600
|
}
|
|
472
|
-
this.event('msg', device.ieeeAddr, message, {
|
|
473
|
-
modelId: device.modelId
|
|
474
|
-
});
|
|
475
601
|
}
|
|
476
602
|
|
|
477
603
|
// Stop controller
|
|
478
604
|
async stop() {
|
|
479
605
|
// Call extensions
|
|
480
|
-
|
|
606
|
+
try {
|
|
607
|
+
await this.callExtensionMethod('stop', []);
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
this.error('unable to call extension Method stop: ' + (error && error.message ? error.message : 'no error message'));
|
|
611
|
+
}
|
|
481
612
|
|
|
482
613
|
try {
|
|
483
614
|
await this.permitJoin(0);
|
|
@@ -503,55 +634,46 @@ class ZigbeeController extends EventEmitter {
|
|
|
503
634
|
|
|
504
635
|
// Permit join
|
|
505
636
|
async permitJoin(permitTime, devid, failure) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
} else {
|
|
513
|
-
permitDev = '';
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (permitTime) {
|
|
518
|
-
this.info('Zigbee: allowing new devices to join.');
|
|
519
|
-
} else {
|
|
520
|
-
this.info('Zigbee: disabling joining new devices.');
|
|
637
|
+
try {
|
|
638
|
+
this._permitJoinTime = permitTime;
|
|
639
|
+
await this.herdsman.permitJoin(permitTime);
|
|
640
|
+
} catch (e) {
|
|
641
|
+
this.sendError(e);
|
|
642
|
+
this.error(`Failed to open the network: ${e.stack}`);
|
|
521
643
|
}
|
|
644
|
+
}
|
|
522
645
|
|
|
646
|
+
async handlePermitJoinChanged(data)
|
|
647
|
+
{
|
|
523
648
|
try {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
this.
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
this.
|
|
530
|
-
|
|
531
|
-
this.
|
|
532
|
-
|
|
533
|
-
await this.herdsman.permitJoin(false);
|
|
534
|
-
}
|
|
535
|
-
this._permitJoinTime -= 1;
|
|
536
|
-
}, 1000);
|
|
537
|
-
} else if (this.herdsman.getPermitJoin()) {
|
|
538
|
-
if (permitTime) {
|
|
539
|
-
this.info('Joining already permitted');
|
|
540
|
-
} else {
|
|
541
|
-
clearInterval(this._permitJoinInterval);
|
|
542
|
-
await this.herdsman.permitJoin(false, permitDev);
|
|
649
|
+
this.debug(`Event handlePermitJoinChanged received with ${JSON.stringify(data)}`);
|
|
650
|
+
if (data.permitted) {
|
|
651
|
+
if (!this._permitJoinInterval) {
|
|
652
|
+
this.emit('pairing',`Pairing possible for ${this._permitJoinTime} seconds`)
|
|
653
|
+
this.info(`Opening zigbee Network for ${this._permitJoinTime} seconds`)
|
|
654
|
+
this._permitJoinInterval = setInterval(async () => {
|
|
655
|
+
this.emit('pairing', 'Pairing time left', this._permitJoinTime);
|
|
656
|
+
this._permitJoinTime -= 1;
|
|
657
|
+
}, 1000);
|
|
543
658
|
}
|
|
544
659
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
660
|
+
else {
|
|
661
|
+
this.info(`Closing Zigbee network, ${this._permitJoinTime} seconds remaining`)
|
|
662
|
+
clearInterval(this._permitJoinInterval);
|
|
663
|
+
this._permitJoinInterval = null;
|
|
664
|
+
// this.emit('pairing', 'Pairing time left', 0);
|
|
665
|
+
this.emit('pairing', 'Closing network.',0);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
this.error(`Error in handlePermitJoinChanged: ${error.message}`);
|
|
548
670
|
}
|
|
549
671
|
}
|
|
550
672
|
|
|
551
673
|
// Remove device
|
|
552
674
|
async remove(deviceID, force, callback) {
|
|
553
675
|
try {
|
|
554
|
-
const device =
|
|
676
|
+
const device = this.herdsman.getDeviceByIeeeAddr(deviceID);
|
|
555
677
|
if (device) {
|
|
556
678
|
try {
|
|
557
679
|
await device.removeFromNetwork();
|
|
@@ -569,7 +691,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
569
691
|
}
|
|
570
692
|
|
|
571
693
|
try {
|
|
572
|
-
|
|
694
|
+
device.removeFromDatabase();
|
|
573
695
|
} catch (error) {
|
|
574
696
|
this.sendError(error);
|
|
575
697
|
// skip error
|
|
@@ -605,7 +727,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
605
727
|
);
|
|
606
728
|
} catch (error) {
|
|
607
729
|
this.sendError(error);
|
|
608
|
-
this.error(`Failed to handleDeviceLeave ${error.
|
|
730
|
+
this.error(`Failed to handleDeviceLeave ${error && error.message ? error.message : 'no error message given'}`);
|
|
609
731
|
}
|
|
610
732
|
}
|
|
611
733
|
|
|
@@ -654,46 +776,51 @@ class ZigbeeController extends EventEmitter {
|
|
|
654
776
|
this.warn(`Blocked interview for '${message.ieeeAddr}' because the network is closed`);
|
|
655
777
|
return;
|
|
656
778
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
779
|
+
try {
|
|
780
|
+
const entity = await this.resolveEntity(message.device || message.ieeeAddr);
|
|
781
|
+
const friendlyName = entity.name;
|
|
782
|
+
if (message.status === 'successful') {
|
|
783
|
+
this.info(`Successfully interviewed '${friendlyName}', device has successfully been paired`);
|
|
784
|
+
|
|
785
|
+
if (entity.mapped) {
|
|
786
|
+
const {vendor, description, model} = entity.mapped;
|
|
787
|
+
this.info(
|
|
788
|
+
`Device '${friendlyName}' is supported, identified as: ${vendor} ${description} (${model})`
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
const log = {friendly_name: friendlyName, model, vendor, description, supported: true};
|
|
792
|
+
this.emit('pairing', 'Interview successful', JSON.stringify(log));
|
|
793
|
+
entity.device.modelID = entity.device._modelID;
|
|
794
|
+
this.emit('new', entity);
|
|
795
|
+
// send to extensions again (for configure)
|
|
796
|
+
this.callExtensionMethod(
|
|
797
|
+
'onZigbeeEvent',
|
|
798
|
+
[message, entity ? entity.mapped : null],
|
|
799
|
+
);
|
|
800
|
+
} else {
|
|
801
|
+
this.debug(
|
|
802
|
+
`Device '${friendlyName}' with Zigbee model '${message.device.modelID}' is NOT supported, ` +
|
|
803
|
+
`please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`
|
|
804
|
+
);
|
|
805
|
+
const frName = {friendly_name: friendlyName, supported: false};
|
|
806
|
+
this.emit('pairing', 'Interview successful', JSON.stringify(frName));
|
|
807
|
+
entity.device.modelID = entity.device._modelID;
|
|
808
|
+
this.emit('new', entity);
|
|
809
|
+
}
|
|
810
|
+
} else if (message.status === 'failed') {
|
|
811
|
+
this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! `);
|
|
812
|
+
//this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! ${message.error}`);
|
|
813
|
+
this.emit('pairing', 'Interview failed', friendlyName);
|
|
677
814
|
} else {
|
|
678
|
-
|
|
679
|
-
`
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const frName = {friendly_name: friendlyName, supported: false};
|
|
683
|
-
this.emit('pairing', 'Interview successful', JSON.stringify(frName));
|
|
684
|
-
entity.device.modelID = entity.device._modelID;
|
|
685
|
-
this.emit('new', entity);
|
|
686
|
-
}
|
|
687
|
-
} else if (message.status === 'failed') {
|
|
688
|
-
this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! `);
|
|
689
|
-
//this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! ${message.error}`);
|
|
690
|
-
this.emit('pairing', 'Interview failed', friendlyName);
|
|
691
|
-
} else {
|
|
692
|
-
if (message.status === 'started') {
|
|
693
|
-
this.info(`Starting interview of '${friendlyName}'`);
|
|
694
|
-
this.emit('pairing', 'Interview started', friendlyName);
|
|
815
|
+
if (message.status === 'started') {
|
|
816
|
+
this.info(`Starting interview of '${friendlyName}'`);
|
|
817
|
+
this.emit('pairing', 'Interview started', friendlyName);
|
|
818
|
+
}
|
|
695
819
|
}
|
|
696
820
|
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
this.error('handleDeviceInterview: ' + (error && error.message ? error.message : 'no error message'));
|
|
823
|
+
}
|
|
697
824
|
}
|
|
698
825
|
|
|
699
826
|
async handleMessage(data) {
|
|
@@ -716,20 +843,25 @@ class ZigbeeController extends EventEmitter {
|
|
|
716
843
|
|
|
717
844
|
async getMap(callback) {
|
|
718
845
|
try {
|
|
846
|
+
this.info('Collecting Map Data');
|
|
719
847
|
const devices = this.herdsman.getDevices(true);
|
|
720
848
|
const lqis = [];
|
|
721
849
|
const routing = [];
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
850
|
+
const errors = [];
|
|
851
|
+
|
|
852
|
+
await Promise.all(devices.filter((d) => d.type !== 'EndDevice').map(async device =>
|
|
853
|
+
{
|
|
854
|
+
let resolved = await this.resolveEntity(device, 0);
|
|
855
|
+
if (!resolved) {
|
|
856
|
+
resolved = { name:'unresolved device', device:device }
|
|
857
|
+
this.warn('resolve Entity failed for ' + device.ieeeAddr)
|
|
858
|
+
}
|
|
725
859
|
let result;
|
|
726
860
|
|
|
727
861
|
try {
|
|
728
862
|
result = await device.lqi();
|
|
729
863
|
} catch (error) {
|
|
730
|
-
this.
|
|
731
|
-
error && this.debug(`Failed to execute LQI for '${resolved.name}'. ${safeJsonStringify(error.stack)}`);
|
|
732
|
-
|
|
864
|
+
errors.push(`Failed to execute LQI for '${resolved ? resolved.name : 'unresolved device'} (${resolved ? resolved.device.modelID : 'unknown'}') : ${this.filterHerdsmanError(error.message)}.`);
|
|
733
865
|
lqis.push({
|
|
734
866
|
parent: 'undefined',
|
|
735
867
|
networkAddress: 0,
|
|
@@ -745,7 +877,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
745
877
|
for (const dev of result.neighbors) {
|
|
746
878
|
if (dev !== undefined && dev.ieeeAddr !== '0xffffffffffffffff') {
|
|
747
879
|
lqis.push({
|
|
748
|
-
parent: resolved.device.ieeeAddr,
|
|
880
|
+
parent: (resolved ? resolved.device.ieeeAddr : undefined),
|
|
749
881
|
networkAddress: dev.networkAddress,
|
|
750
882
|
ieeeAddr: dev.ieeeAddr,
|
|
751
883
|
lqi: dev.linkquality,
|
|
@@ -757,18 +889,14 @@ class ZigbeeController extends EventEmitter {
|
|
|
757
889
|
}
|
|
758
890
|
}
|
|
759
891
|
|
|
760
|
-
this.debug(`LQI succeeded for '${resolved.name}'`);
|
|
761
|
-
|
|
762
892
|
try {
|
|
763
893
|
result = await device.routingTable();
|
|
764
894
|
} catch (error) {
|
|
765
|
-
this.sendError(error);
|
|
766
895
|
if (error) {
|
|
767
|
-
|
|
896
|
+
errors.push(`Failed to collect routing table for '${resolved ? resolved.name : 'unresolved device'} (${resolved ? resolved.device.modelID : 'unknown'}') : ${this.filterHerdsmanError(error.message)}`);
|
|
768
897
|
}
|
|
769
898
|
}
|
|
770
899
|
|
|
771
|
-
this.debug(`Routing for '${resolved.name}': ${safeJsonStringify(result)}`);
|
|
772
900
|
if (result !== undefined) {
|
|
773
901
|
if (result.table !== undefined) {
|
|
774
902
|
for (const dev of result.table) {
|
|
@@ -781,17 +909,24 @@ class ZigbeeController extends EventEmitter {
|
|
|
781
909
|
}
|
|
782
910
|
}
|
|
783
911
|
}
|
|
784
|
-
this.debug(`Routing table succeeded for '${resolved.name}'`);
|
|
785
912
|
}
|
|
786
|
-
|
|
913
|
+
));
|
|
787
914
|
|
|
788
|
-
callback && callback({lqis, routing});
|
|
915
|
+
callback && callback({lqis, routing, errors});
|
|
916
|
+
if (errors.length) {
|
|
917
|
+
this.warn(`Map Data collection complete with ${errors.length} issues:`);
|
|
918
|
+
for (const msg of errors)
|
|
919
|
+
this.warn(msg);
|
|
920
|
+
}
|
|
921
|
+
else
|
|
922
|
+
this.info('Map data collection complete');
|
|
789
923
|
} catch (error) {
|
|
790
924
|
this.sendError(error);
|
|
791
|
-
this.
|
|
925
|
+
this.error(`Failed to get map: ${safeJsonStringify(error.stack)}`);
|
|
792
926
|
}
|
|
793
927
|
}
|
|
794
928
|
|
|
929
|
+
|
|
795
930
|
async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback, zclSeqNum) {
|
|
796
931
|
const entity = await this.resolveEntity(deviceID, ep);
|
|
797
932
|
const device = entity.device;
|
|
@@ -815,38 +950,35 @@ class ZigbeeController extends EventEmitter {
|
|
|
815
950
|
cfg = {};
|
|
816
951
|
}
|
|
817
952
|
|
|
818
|
-
|
|
819
|
-
cfg.disableDefaultResponse = true;
|
|
953
|
+
try {
|
|
820
954
|
|
|
821
|
-
if (
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
955
|
+
if (type === 'foundation') {
|
|
956
|
+
cfg.disableDefaultResponse = true;
|
|
957
|
+
let result;
|
|
958
|
+
if (cmd === 'configReport') {
|
|
959
|
+
result = await endpoint.configureReporting(cid, zclData, cfg);
|
|
960
|
+
} else {
|
|
961
|
+
if (cmd === 'read' && !Array.isArray(zclData))
|
|
962
|
+
result = await endpoint[cmd](cid, Object.keys(zclData), cfg);
|
|
963
|
+
else
|
|
964
|
+
result = await endpoint[cmd](cid, zclData, cfg);
|
|
965
|
+
}
|
|
966
|
+
callback && callback(undefined, result);
|
|
967
|
+
} else if (type === 'functionalResp') {
|
|
968
|
+
cfg.disableDefaultResponse = false;
|
|
969
|
+
const result = await endpoint.commandResponse(cid, cmd, zclData, cfg, zclSeqNum);
|
|
970
|
+
callback && callback(undefined, result);
|
|
834
971
|
} else {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
result = await endpoint[cmd](cid, zclData, cfg);
|
|
972
|
+
cfg.disableDefaultResponse = false;
|
|
973
|
+
const result = await endpoint.command(cid, cmd, zclData, cfg);
|
|
974
|
+
callback && callback(undefined, result);
|
|
839
975
|
}
|
|
840
|
-
callback && callback(undefined, result);
|
|
841
|
-
} else if (type === 'functionalResp') {
|
|
842
|
-
cfg.disableDefaultResponse = false;
|
|
843
|
-
const result = await endpoint.commandResponse(cid, cmd, zclData, cfg, zclSeqNum);
|
|
844
|
-
callback && callback(undefined, result);
|
|
845
|
-
} else {
|
|
846
|
-
cfg.disableDefaultResponse = false;
|
|
847
|
-
const result = await endpoint.command(cid, cmd, zclData, cfg);
|
|
848
|
-
callback && callback(undefined, result);
|
|
849
976
|
}
|
|
977
|
+
catch (error)
|
|
978
|
+
{
|
|
979
|
+
this.log.error(`error sending ${type} ${cmd} to endpoint: ${(error && error.message ? error.message : 'no error message')} ${(error && error.stack ? error.stack : 'no call stack')}`)
|
|
980
|
+
}
|
|
981
|
+
|
|
850
982
|
}
|
|
851
983
|
|
|
852
984
|
async addDevToGroup(devId, groupId, epid) {
|
|
@@ -906,6 +1038,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
906
1038
|
entity = await this.resolveEntity(devId);
|
|
907
1039
|
const group = await this.resolveEntity(groupId);
|
|
908
1040
|
|
|
1041
|
+
/*
|
|
909
1042
|
const members = await this.getGroupMembersFromController(groupId);
|
|
910
1043
|
const memberIDs = [];
|
|
911
1044
|
for (const member of members) {
|
|
@@ -914,7 +1047,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
914
1047
|
|
|
915
1048
|
this.debug(`removeDevFromGroup - entity: ${utils.getEntityInfo(entity)}`);
|
|
916
1049
|
this.debug(`removeDevFromGroup ${groupId} with ${memberIDs.length} members ${safeJsonStringify(memberIDs)}`);
|
|
917
|
-
|
|
1050
|
+
*/
|
|
918
1051
|
if (epid != undefined) {
|
|
919
1052
|
for (const ep of entity.endpoints) {
|
|
920
1053
|
this.debug(`checking ep ${ep.ID} of ${devId} (${epid})`);
|
|
@@ -927,6 +1060,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
927
1060
|
await entity.endpoint.removeFromGroup(group.mapped);
|
|
928
1061
|
this.info(`removing endpoint ${entity.endpoint.ID} of ${devId} from group ${groupId}`);
|
|
929
1062
|
}
|
|
1063
|
+
|
|
930
1064
|
} catch (error) {
|
|
931
1065
|
this.sendError(error);
|
|
932
1066
|
this.error(`Exception when trying remove ${devId} (ep ${epid ? epid : (entity ? entity.endpoint.ID : '')}) from group ${devId}`, error);
|
|
@@ -957,16 +1091,21 @@ class ZigbeeController extends EventEmitter {
|
|
|
957
1091
|
target = !target ? this.getCoordinator() : target;
|
|
958
1092
|
|
|
959
1093
|
this.debug(`Binding ${log}`);
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1094
|
+
try {
|
|
1095
|
+
ep.bind(cluster, target, error => {
|
|
1096
|
+
if (error) {
|
|
1097
|
+
this.sendError(error);
|
|
1098
|
+
this.error(`Failed to bind ${log} - (${error})`);
|
|
1099
|
+
} else {
|
|
1100
|
+
this.debug(`Successfully bound ${log}`);
|
|
1101
|
+
}
|
|
967
1102
|
|
|
968
|
-
|
|
969
|
-
|
|
1103
|
+
callback(error);
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
catch (error) {
|
|
1107
|
+
callback(error)
|
|
1108
|
+
}
|
|
970
1109
|
}
|
|
971
1110
|
|
|
972
1111
|
unbind(ep, cluster, target, callback) {
|
|
@@ -974,15 +1113,20 @@ class ZigbeeController extends EventEmitter {
|
|
|
974
1113
|
target = !target ? this.getCoordinator() : target;
|
|
975
1114
|
|
|
976
1115
|
this.debug(`Unbinding ${log}`);
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1116
|
+
try {
|
|
1117
|
+
ep.unbind(cluster, target, (error) => {
|
|
1118
|
+
if (error) {
|
|
1119
|
+
this.error(`Failed to unbind ${log} - (${error})`);
|
|
1120
|
+
} else {
|
|
1121
|
+
this.debug(`Successfully unbound ${log}`);
|
|
1122
|
+
}
|
|
983
1123
|
|
|
984
|
-
|
|
985
|
-
|
|
1124
|
+
callback(error);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
catch (error) {
|
|
1128
|
+
callback (error)
|
|
1129
|
+
}
|
|
986
1130
|
}
|
|
987
1131
|
|
|
988
1132
|
reset(mode, callback) {
|