iobroker.zigbee 2.0.0 → 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 +27 -9
- 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 +116 -48
- 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 +40 -39
- package/lib/binding.js +1 -1
- package/lib/colors.js +7 -0
- package/lib/commands.js +127 -17
- package/lib/developer.js +0 -0
- package/lib/devices.js +78 -74
- package/lib/exclude.js +30 -54
- package/lib/exposes.js +203 -246
- package/lib/groups.js +84 -29
- package/lib/localConfig.js +295 -0
- package/lib/ota.js +0 -0
- package/lib/statescontroller.js +410 -183
- package/lib/utils.js +1 -1
- package/lib/zbDeviceAvailability.js +15 -23
- package/lib/zbDeviceConfigure.js +0 -0
- package/lib/zbDeviceEvent.js +2 -13
- package/lib/zigbeecontroller.js +299 -207
- package/main.js +145 -56
- package/package.json +8 -7
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.
|
|
151
|
-
} catch (
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
this.info(`Zigbee network parameters: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${extPanIDDebug}`);
|
|
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
|
+
}
|
|
164
|
+
this.sendError(e);
|
|
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,16 +323,30 @@ 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
|
}
|
|
298
347
|
} catch (error) {
|
|
299
348
|
this.sendError(error);
|
|
300
|
-
this.error(
|
|
349
|
+
this.error(JSON.stringify(error));
|
|
301
350
|
return undefined;
|
|
302
351
|
}
|
|
303
352
|
}
|
|
@@ -308,15 +357,17 @@ class ZigbeeController extends EventEmitter {
|
|
|
308
357
|
group && group.removeFromNetwork();
|
|
309
358
|
} catch (error) {
|
|
310
359
|
this.sendError(error);
|
|
311
|
-
this.error(`error in removeGroupById: ${
|
|
360
|
+
this.error(`error in removeGroupById: ${error}`);
|
|
312
361
|
}
|
|
313
362
|
}
|
|
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
|
}
|
|
@@ -333,12 +384,23 @@ class ZigbeeController extends EventEmitter {
|
|
|
333
384
|
} else {
|
|
334
385
|
this.debug(`verifyGroupExists: group ${nid} exists`);
|
|
335
386
|
}
|
|
387
|
+
return group
|
|
336
388
|
}
|
|
337
389
|
catch (error) {
|
|
338
390
|
this.error(`verifyGroupExists: ${error && error.message ? error.message : 'unspecified error'}`);
|
|
339
391
|
}
|
|
340
392
|
}
|
|
341
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';
|
|
402
|
+
}
|
|
403
|
+
|
|
342
404
|
async addPairingCode(code) {
|
|
343
405
|
this.debug(`calling addPairingCode with ${code}`);
|
|
344
406
|
if (code) {
|
|
@@ -357,7 +419,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
357
419
|
async getGroupMembersFromController(id) {
|
|
358
420
|
const members = [];
|
|
359
421
|
try {
|
|
360
|
-
const group = await this.getGroupByID(id);
|
|
422
|
+
const group = await this.getGroupByID(Number(id));
|
|
361
423
|
if (group) {
|
|
362
424
|
const groupMembers = group.members;
|
|
363
425
|
for (const member of groupMembers) {
|
|
@@ -391,7 +453,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
391
453
|
try {
|
|
392
454
|
return this.herdsman.getDeviceByIeeeAddr(key);
|
|
393
455
|
}
|
|
394
|
-
catch {
|
|
456
|
+
catch (error) {
|
|
457
|
+
this.error(`getDeviceByIeeeAddr: ${(error && error.message ? error.message : 'no error message')}`);
|
|
395
458
|
return undefined;
|
|
396
459
|
}
|
|
397
460
|
}
|
|
@@ -400,7 +463,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
400
463
|
try {
|
|
401
464
|
return this.herdsman.getDevicesByType(type);
|
|
402
465
|
}
|
|
403
|
-
catch {
|
|
466
|
+
catch (error) {
|
|
467
|
+
this.error(`getDevicesByType: ${(error && error.message ? error.message : 'no error message')}`);
|
|
404
468
|
return undefined;
|
|
405
469
|
}
|
|
406
470
|
}
|
|
@@ -409,82 +473,109 @@ class ZigbeeController extends EventEmitter {
|
|
|
409
473
|
try {
|
|
410
474
|
return this.herdsman.getDeviceByNetworkAddress(networkAddress);
|
|
411
475
|
}
|
|
412
|
-
catch {
|
|
476
|
+
catch (error) {
|
|
477
|
+
this.error(`getDeviceByNetworkAddress: ${(error && error.message ? error.message : 'no error message')}`);
|
|
413
478
|
return undefined;
|
|
414
479
|
}
|
|
415
480
|
}
|
|
416
481
|
|
|
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
|
+
}
|
|
493
|
+
if (typeof key === 'string') {
|
|
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
|
+
|
|
417
518
|
async resolveEntity(key, ep) {
|
|
418
|
-
//
|
|
519
|
+
// this.warn('resolve entity with key of tyoe ' + typeof (key));
|
|
419
520
|
try {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (endpoints && ep != undefined && endpoints[ep]) {
|
|
436
|
-
endpoint = device.getEndpoint(endpoints[ep]);
|
|
437
|
-
} else if (endpoints && endpoints['default']) {
|
|
438
|
-
endpoint = device.getEndpoint(endpoints['default']);
|
|
439
|
-
} else {
|
|
440
|
-
const epNum = parseInt(ep);
|
|
441
|
-
if (!isNaN(epNum)) {
|
|
442
|
-
endpoint = device.getEndpoint(epNum);
|
|
443
|
-
} else {
|
|
444
|
-
endpoint = device.endpoints[0];
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return {
|
|
448
|
-
type: 'device',
|
|
449
|
-
device,
|
|
450
|
-
mapped,
|
|
451
|
-
endpoint,
|
|
452
|
-
endpoints: device.endpoints,
|
|
453
|
-
name: key,
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
} else if (typeof key === 'number') {
|
|
458
|
-
let group = await this.herdsman.getGroupByID(key);
|
|
459
|
-
if (!group) group = await this.herdsman.createGroup(key);
|
|
521
|
+
const _key = await this.analyzeKey(key);
|
|
522
|
+
if (_key.message !== 'success') return undefined;
|
|
523
|
+
|
|
524
|
+
if (_key.kind == 'coordinator') {
|
|
525
|
+
const coordinator = this.herdsman.getDevicesByType('Coordinator')[0];
|
|
526
|
+
return {
|
|
527
|
+
type: 'device',
|
|
528
|
+
device: coordinator,
|
|
529
|
+
endpoint: coordinator.getEndpoint(1),
|
|
530
|
+
name: 'Coordinator',
|
|
531
|
+
};
|
|
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);
|
|
460
536
|
group.toZigbee = groupConverters;
|
|
461
537
|
group.model = 'group';
|
|
462
538
|
return {
|
|
463
539
|
type: 'group',
|
|
464
540
|
mapped: group,
|
|
465
541
|
group,
|
|
466
|
-
name: `Group ${key}`,
|
|
542
|
+
name: `Group ${_key.key}`,
|
|
467
543
|
};
|
|
468
|
-
} else {
|
|
469
|
-
let mapped;
|
|
470
|
-
try {
|
|
471
|
-
mapped = await zigbeeHerdsmanConverters.findByDevice(key);
|
|
472
|
-
} catch (err) {
|
|
473
|
-
this.error(`zigbeeHerdsmanConverters findByDevice ${key.ieeeAddr}`);
|
|
474
|
-
}
|
|
475
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);
|
|
560
|
+
} else {
|
|
561
|
+
endpoint = device.endpoints[0];
|
|
562
|
+
}
|
|
563
|
+
}
|
|
476
564
|
return {
|
|
477
565
|
type: 'device',
|
|
478
|
-
device
|
|
479
|
-
mapped
|
|
480
|
-
|
|
566
|
+
device,
|
|
567
|
+
mapped,
|
|
568
|
+
endpoint,
|
|
569
|
+
endpoints: device.endpoints,
|
|
570
|
+
name: device._ieeeAddr,
|
|
481
571
|
};
|
|
482
572
|
}
|
|
483
573
|
}
|
|
484
|
-
catch
|
|
485
|
-
|
|
574
|
+
catch (error)
|
|
575
|
+
{
|
|
576
|
+
this.error('Resolve entity error: ' + (error && error.message ? error.message : 'no reason given'))
|
|
486
577
|
}
|
|
487
|
-
|
|
578
|
+
return undefined;
|
|
488
579
|
}
|
|
489
580
|
|
|
490
581
|
async incMsgHandler(message) {
|
|
@@ -504,15 +595,20 @@ class ZigbeeController extends EventEmitter {
|
|
|
504
595
|
modelId: device.modelId
|
|
505
596
|
});
|
|
506
597
|
}
|
|
507
|
-
catch {
|
|
508
|
-
|
|
598
|
+
catch (error) {
|
|
599
|
+
this.error('incMsgHandler: ' + (error && error.message ? error.message : 'no error message'));
|
|
509
600
|
}
|
|
510
601
|
}
|
|
511
602
|
|
|
512
603
|
// Stop controller
|
|
513
604
|
async stop() {
|
|
514
605
|
// Call extensions
|
|
515
|
-
|
|
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
|
+
}
|
|
516
612
|
|
|
517
613
|
try {
|
|
518
614
|
await this.permitJoin(0);
|
|
@@ -538,55 +634,46 @@ class ZigbeeController extends EventEmitter {
|
|
|
538
634
|
|
|
539
635
|
// Permit join
|
|
540
636
|
async permitJoin(permitTime, devid, failure) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
} else {
|
|
548
|
-
permitDev = '';
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (permitTime) {
|
|
553
|
-
this.info('Zigbee: allowing new devices to join.');
|
|
554
|
-
} else {
|
|
555
|
-
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}`);
|
|
556
643
|
}
|
|
644
|
+
}
|
|
557
645
|
|
|
646
|
+
async handlePermitJoinChanged(data)
|
|
647
|
+
{
|
|
558
648
|
try {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
this.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
this.
|
|
565
|
-
|
|
566
|
-
this.
|
|
567
|
-
|
|
568
|
-
await this.herdsman.permitJoin(false);
|
|
569
|
-
}
|
|
570
|
-
this._permitJoinTime -= 1;
|
|
571
|
-
}, 1000);
|
|
572
|
-
} else if (this.herdsman.getPermitJoin()) {
|
|
573
|
-
if (permitTime) {
|
|
574
|
-
this.info('Joining already permitted');
|
|
575
|
-
} else {
|
|
576
|
-
clearInterval(this._permitJoinInterval);
|
|
577
|
-
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);
|
|
578
658
|
}
|
|
579
659
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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}`);
|
|
583
670
|
}
|
|
584
671
|
}
|
|
585
672
|
|
|
586
673
|
// Remove device
|
|
587
674
|
async remove(deviceID, force, callback) {
|
|
588
675
|
try {
|
|
589
|
-
const device =
|
|
676
|
+
const device = this.herdsman.getDeviceByIeeeAddr(deviceID);
|
|
590
677
|
if (device) {
|
|
591
678
|
try {
|
|
592
679
|
await device.removeFromNetwork();
|
|
@@ -604,7 +691,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
604
691
|
}
|
|
605
692
|
|
|
606
693
|
try {
|
|
607
|
-
|
|
694
|
+
device.removeFromDatabase();
|
|
608
695
|
} catch (error) {
|
|
609
696
|
this.sendError(error);
|
|
610
697
|
// skip error
|
|
@@ -640,7 +727,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
640
727
|
);
|
|
641
728
|
} catch (error) {
|
|
642
729
|
this.sendError(error);
|
|
643
|
-
this.error(`Failed to handleDeviceLeave ${error.
|
|
730
|
+
this.error(`Failed to handleDeviceLeave ${error && error.message ? error.message : 'no error message given'}`);
|
|
644
731
|
}
|
|
645
732
|
}
|
|
646
733
|
|
|
@@ -689,46 +776,51 @@ class ZigbeeController extends EventEmitter {
|
|
|
689
776
|
this.warn(`Blocked interview for '${message.ieeeAddr}' because the network is closed`);
|
|
690
777
|
return;
|
|
691
778
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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);
|
|
712
814
|
} else {
|
|
713
|
-
|
|
714
|
-
`
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
const frName = {friendly_name: friendlyName, supported: false};
|
|
718
|
-
this.emit('pairing', 'Interview successful', JSON.stringify(frName));
|
|
719
|
-
entity.device.modelID = entity.device._modelID;
|
|
720
|
-
this.emit('new', entity);
|
|
721
|
-
}
|
|
722
|
-
} else if (message.status === 'failed') {
|
|
723
|
-
this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! `);
|
|
724
|
-
//this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! ${message.error}`);
|
|
725
|
-
this.emit('pairing', 'Interview failed', friendlyName);
|
|
726
|
-
} else {
|
|
727
|
-
if (message.status === 'started') {
|
|
728
|
-
this.info(`Starting interview of '${friendlyName}'`);
|
|
729
|
-
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
|
+
}
|
|
730
819
|
}
|
|
731
820
|
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
this.error('handleDeviceInterview: ' + (error && error.message ? error.message : 'no error message'));
|
|
823
|
+
}
|
|
732
824
|
}
|
|
733
825
|
|
|
734
826
|
async handleMessage(data) {
|
|
@@ -751,20 +843,25 @@ class ZigbeeController extends EventEmitter {
|
|
|
751
843
|
|
|
752
844
|
async getMap(callback) {
|
|
753
845
|
try {
|
|
846
|
+
this.info('Collecting Map Data');
|
|
754
847
|
const devices = this.herdsman.getDevices(true);
|
|
755
848
|
const lqis = [];
|
|
756
849
|
const routing = [];
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
+
}
|
|
760
859
|
let result;
|
|
761
860
|
|
|
762
861
|
try {
|
|
763
862
|
result = await device.lqi();
|
|
764
863
|
} catch (error) {
|
|
765
|
-
this.
|
|
766
|
-
error && this.debug(`Failed to execute LQI for '${resolved.name}'. ${safeJsonStringify(error.stack)}`);
|
|
767
|
-
|
|
864
|
+
errors.push(`Failed to execute LQI for '${resolved ? resolved.name : 'unresolved device'} (${resolved ? resolved.device.modelID : 'unknown'}') : ${this.filterHerdsmanError(error.message)}.`);
|
|
768
865
|
lqis.push({
|
|
769
866
|
parent: 'undefined',
|
|
770
867
|
networkAddress: 0,
|
|
@@ -780,7 +877,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
780
877
|
for (const dev of result.neighbors) {
|
|
781
878
|
if (dev !== undefined && dev.ieeeAddr !== '0xffffffffffffffff') {
|
|
782
879
|
lqis.push({
|
|
783
|
-
parent: resolved.device.ieeeAddr,
|
|
880
|
+
parent: (resolved ? resolved.device.ieeeAddr : undefined),
|
|
784
881
|
networkAddress: dev.networkAddress,
|
|
785
882
|
ieeeAddr: dev.ieeeAddr,
|
|
786
883
|
lqi: dev.linkquality,
|
|
@@ -792,18 +889,14 @@ class ZigbeeController extends EventEmitter {
|
|
|
792
889
|
}
|
|
793
890
|
}
|
|
794
891
|
|
|
795
|
-
this.debug(`LQI succeeded for '${resolved.name}'`);
|
|
796
|
-
|
|
797
892
|
try {
|
|
798
893
|
result = await device.routingTable();
|
|
799
894
|
} catch (error) {
|
|
800
|
-
this.sendError(error);
|
|
801
895
|
if (error) {
|
|
802
|
-
|
|
896
|
+
errors.push(`Failed to collect routing table for '${resolved ? resolved.name : 'unresolved device'} (${resolved ? resolved.device.modelID : 'unknown'}') : ${this.filterHerdsmanError(error.message)}`);
|
|
803
897
|
}
|
|
804
898
|
}
|
|
805
899
|
|
|
806
|
-
this.debug(`Routing for '${resolved.name}': ${safeJsonStringify(result)}`);
|
|
807
900
|
if (result !== undefined) {
|
|
808
901
|
if (result.table !== undefined) {
|
|
809
902
|
for (const dev of result.table) {
|
|
@@ -816,17 +909,24 @@ class ZigbeeController extends EventEmitter {
|
|
|
816
909
|
}
|
|
817
910
|
}
|
|
818
911
|
}
|
|
819
|
-
this.debug(`Routing table succeeded for '${resolved.name}'`);
|
|
820
912
|
}
|
|
821
|
-
|
|
913
|
+
));
|
|
822
914
|
|
|
823
|
-
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');
|
|
824
923
|
} catch (error) {
|
|
825
924
|
this.sendError(error);
|
|
826
|
-
this.
|
|
925
|
+
this.error(`Failed to get map: ${safeJsonStringify(error.stack)}`);
|
|
827
926
|
}
|
|
828
927
|
}
|
|
829
928
|
|
|
929
|
+
|
|
830
930
|
async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback, zclSeqNum) {
|
|
831
931
|
const entity = await this.resolveEntity(deviceID, ep);
|
|
832
932
|
const device = entity.device;
|
|
@@ -849,20 +949,11 @@ class ZigbeeController extends EventEmitter {
|
|
|
849
949
|
if (cfg == null) {
|
|
850
950
|
cfg = {};
|
|
851
951
|
}
|
|
952
|
+
|
|
852
953
|
try {
|
|
954
|
+
|
|
853
955
|
if (type === 'foundation') {
|
|
854
956
|
cfg.disableDefaultResponse = true;
|
|
855
|
-
/*
|
|
856
|
-
if (cmd === 'read' && !Array.isArray(zclData)) {
|
|
857
|
-
// needs to be iterable (string[] | number [])
|
|
858
|
-
zclData[Symbol.iterator] = function* () {
|
|
859
|
-
let k;
|
|
860
|
-
for (k in this) {
|
|
861
|
-
yield k;
|
|
862
|
-
}
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
*/
|
|
866
957
|
let result;
|
|
867
958
|
if (cmd === 'configReport') {
|
|
868
959
|
result = await endpoint.configureReporting(cid, zclData, cfg);
|
|
@@ -935,7 +1026,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
935
1026
|
} catch (error) {
|
|
936
1027
|
this.sendError(error);
|
|
937
1028
|
this.error(`Exception when trying to Add ${devId} to group ${groupId}`, error);
|
|
938
|
-
return {error: `Failed to add ${devId} to group ${groupId}: ${
|
|
1029
|
+
return {error: `Failed to add ${devId} to group ${groupId}: ${JSON.stringify(error)}`};
|
|
939
1030
|
}
|
|
940
1031
|
return {};
|
|
941
1032
|
}
|
|
@@ -947,6 +1038,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
947
1038
|
entity = await this.resolveEntity(devId);
|
|
948
1039
|
const group = await this.resolveEntity(groupId);
|
|
949
1040
|
|
|
1041
|
+
/*
|
|
950
1042
|
const members = await this.getGroupMembersFromController(groupId);
|
|
951
1043
|
const memberIDs = [];
|
|
952
1044
|
for (const member of members) {
|
|
@@ -955,7 +1047,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
955
1047
|
|
|
956
1048
|
this.debug(`removeDevFromGroup - entity: ${utils.getEntityInfo(entity)}`);
|
|
957
1049
|
this.debug(`removeDevFromGroup ${groupId} with ${memberIDs.length} members ${safeJsonStringify(memberIDs)}`);
|
|
958
|
-
|
|
1050
|
+
*/
|
|
959
1051
|
if (epid != undefined) {
|
|
960
1052
|
for (const ep of entity.endpoints) {
|
|
961
1053
|
this.debug(`checking ep ${ep.ID} of ${devId} (${epid})`);
|
|
@@ -968,6 +1060,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
968
1060
|
await entity.endpoint.removeFromGroup(group.mapped);
|
|
969
1061
|
this.info(`removing endpoint ${entity.endpoint.ID} of ${devId} from group ${groupId}`);
|
|
970
1062
|
}
|
|
1063
|
+
|
|
971
1064
|
} catch (error) {
|
|
972
1065
|
this.sendError(error);
|
|
973
1066
|
this.error(`Exception when trying remove ${devId} (ep ${epid ? epid : (entity ? entity.endpoint.ID : '')}) from group ${devId}`, error);
|
|
@@ -1011,7 +1104,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
1011
1104
|
});
|
|
1012
1105
|
}
|
|
1013
1106
|
catch (error) {
|
|
1014
|
-
callback(error)
|
|
1107
|
+
callback(error)
|
|
1015
1108
|
}
|
|
1016
1109
|
}
|
|
1017
1110
|
|
|
@@ -1031,9 +1124,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
1031
1124
|
callback(error);
|
|
1032
1125
|
});
|
|
1033
1126
|
}
|
|
1034
|
-
catch (error)
|
|
1035
|
-
|
|
1036
|
-
callback(error);
|
|
1127
|
+
catch (error) {
|
|
1128
|
+
callback (error)
|
|
1037
1129
|
}
|
|
1038
1130
|
}
|
|
1039
1131
|
|