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.
@@ -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.debug(`Zigbee network parameters: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${extPanIDDebug}`);
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 : ${JSON.stringify(e.message)}`);
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
- // Log zigbee clients on startup
201
- const devices = await this.getClients();
202
- if (devices.length > 0) {
203
- this.info(`Currently ${devices.length} devices are joined:`);
204
- } else {
205
- this.info(`Currently no devices.`);
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
- for (const device of devices) {
209
- const entity = await this.resolveEntity(device);
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
- // ensure that objects for all found clients are present
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
- if (entity.mapped) {
220
- this.emit('new', entity);
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
- return await this.herdsman.getGroups();
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
- return this.herdsman.getGroupByID(id);
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
- const nid = typeof id === 'number' ? id : parseInt(id);
326
- let group = await this.herdsman.getGroupByID(nid);
327
- if (!group) {
328
- group = await this.herdsman.createGroup(nid);
329
- group.toZigbee = groupConverters;
330
- group.model = 'group';
331
- this.debug(`verifyGroupExists: created group ${nid}`);
332
- } else {
333
- this.debug(`verifyGroupExists: group ${nid} exists`);
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
- await this.herdsman.addInstallCode(code);
341
- this.info(`added code ${code} for pairing`);
342
- return true;
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: error is ${JSON.stringify(error)} ${JSON.stringify(new Error().stack)}`);
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
- return this.herdsman.getDeviceByIeeeAddr(key);
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
- return this.herdsman.getDevicesByType(type);
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
- return this.herdsman.getDeviceByNetworkAddress(networkAddress);
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 resolveEntity(key, ep) {
393
- // assert(typeof key === 'string' || key.constructor.name === 'Device', `Wrong type '${typeof key}'`);
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
- } else {
405
- const device = await this.herdsman.getDeviceByIeeeAddr(key);
406
- if (device) {
407
- const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
408
- const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
409
- let endpoint;
410
- if (endpoints && ep != undefined && endpoints[ep]) {
411
- endpoint = device.getEndpoint(endpoints[ep]);
412
- } else if (endpoints && endpoints['default']) {
413
- endpoint = device.getEndpoint(endpoints['default']);
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
- const epNum = parseInt(ep);
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
- this.debug('incoming msg', message);
462
- const device = await this.herdsman.getDeviceByIeeeAddr(message.srcaddr);
463
- if (!device) {
464
- this.debug('Message without device!');
465
- return;
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
- // We can't handle devices without modelId.
468
- if (!device.modelId) {
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
- await this.callExtensionMethod('stop', []);
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
- let permitDev;
507
- if (isFunction(devid) && !isFunction(failure)) {
508
- failure = devid;
509
- } else {
510
- if (devid != '') {
511
- permitDev = this.getDevice(devid);
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
- if (permitTime && !this.herdsman.getPermitJoin()) {
525
- clearInterval(this._permitJoinInterval);
526
- this._permitJoinTime = permitTime;
527
- await this.herdsman.permitJoin(true, permitDev, this._permitJoinTime);
528
- this._permitJoinInterval = setInterval(async () => {
529
- this.emit('pairing', 'Pairing time left', this._permitJoinTime);
530
- if (this._permitJoinTime === 0) {
531
- this.info('Zigbee: stop joining');
532
- clearInterval(this._permitJoinInterval);
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
- } catch (e) {
546
- this.sendError(e);
547
- this.error(`Failed to open the network: ${e.stack}`);
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 = await this.herdsman.getDeviceByIeeeAddr(deviceID);
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
- await device.removeFromDatabase();
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.stack}`);
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
- const entity = await this.resolveEntity(message.device || message.ieeeAddr);
658
- const friendlyName = entity.name;
659
- if (message.status === 'successful') {
660
- this.info(`Successfully interviewed '${friendlyName}', device has successfully been paired`);
661
-
662
- if (entity.mapped) {
663
- const {vendor, description, model} = entity.mapped;
664
- this.info(
665
- `Device '${friendlyName}' is supported, identified as: ${vendor} ${description} (${model})`
666
- );
667
-
668
- const log = {friendly_name: friendlyName, model, vendor, description, supported: true};
669
- this.emit('pairing', 'Interview successful', JSON.stringify(log));
670
- entity.device.modelID = entity.device._modelID;
671
- this.emit('new', entity);
672
- // send to extensions again (for configure)
673
- this.callExtensionMethod(
674
- 'onZigbeeEvent',
675
- [message, entity ? entity.mapped : null],
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
- this.debug(
679
- `Device '${friendlyName}' with Zigbee model '${message.device.modelID}' is NOT supported, ` +
680
- `please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`
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
- for (const device of devices.filter((d) => d.type !== 'EndDevice')) {
724
- const resolved = await this.resolveEntity(device);
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.sendError(error);
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
- this.debug(`Failed to execute routing table for '${resolved.name}'. ${safeJsonStringify(error.stack)}`);
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
- this.debug(`Get map succeeded ${safeJsonStringify(lqis)}`);
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.debug(`Failed to get map: ${safeJsonStringify(error.stack)}`);
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
- if (type === 'foundation') {
819
- cfg.disableDefaultResponse = true;
953
+ try {
820
954
 
821
- if (cmd === 'read' && !Array.isArray(zclData)) {
822
- /* // needs to be iterable (string[] | number [])
823
- zclData[Symbol.iterator] = function* () {
824
- let k;
825
- for (k in this) {
826
- yield k;
827
- }
828
- };
829
- */
830
- }
831
- let result;
832
- if (cmd === 'configReport') {
833
- result = await endpoint.configureReporting(cid, zclData, cfg);
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
- if (cmd === 'read' && !Array.isArray(zclData))
836
- result = await endpoint[cmd](cid, Object.keys(zclData), cfg);
837
- else
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
- ep.bind(cluster, target, error => {
961
- if (error) {
962
- this.sendError(error);
963
- this.error(`Failed to bind ${log} - (${error})`);
964
- } else {
965
- this.debug(`Successfully bound ${log}`);
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
- callback(error);
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
- ep.unbind(cluster, target, (error) => {
978
- if (error) {
979
- this.error(`Failed to unbind ${log} - (${error})`);
980
- } else {
981
- this.debug(`Successfully unbound ${log}`);
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
- callback(error);
985
- });
1124
+ callback(error);
1125
+ });
1126
+ }
1127
+ catch (error) {
1128
+ callback (error)
1129
+ }
986
1130
  }
987
1131
 
988
1132
  reset(mode, callback) {