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.
@@ -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}`);
151
- } catch (error) {
152
- this.sendError(error);
153
- this.error(`Starting zigbee-herdsman problem : ${(error && error.message ? error.message : 'no error message')}`);
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
- // 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,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
- 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
  }
298
347
  } catch (error) {
299
348
  this.sendError(error);
300
- this.error(`error in getGroups: ${(error && error.message ? error.message : 'no error message')} ${(error && error.stack ? error.stack : 'no call stack')}`);
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: ${(error && error.message ? error.message : 'no error message')} ${(error && error.stack ? error.stack : 'no call stack')}`);
360
+ this.error(`error in removeGroupById: ${error}`);
312
361
  }
313
362
  }
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
  }
@@ -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
- // assert(typeof key === 'string' || key.constructor.name === 'Device', `Wrong type '${typeof key}'`);
519
+ // this.warn('resolve entity with key of tyoe ' + typeof (key));
419
520
  try {
420
- if (typeof key === 'string') {
421
- if (key === 'coordinator') {
422
- const coordinator = this.herdsman.getDevicesByType('Coordinator')[0];
423
- return {
424
- type: 'device',
425
- device: coordinator,
426
- endpoint: coordinator.getEndpoint(1),
427
- name: 'Coordinator',
428
- };
429
- } else {
430
- const device = await this.herdsman.getDeviceByIeeeAddr(key);
431
- if (device) {
432
- const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
433
- const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
434
- let endpoint;
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: key,
479
- mapped: mapped,
480
- name: key.type === 'Coordinator' ? 'Coordinator' : key.ieeeAddr,
566
+ device,
567
+ mapped,
568
+ endpoint,
569
+ endpoints: device.endpoints,
570
+ name: device._ieeeAddr,
481
571
  };
482
572
  }
483
573
  }
484
- catch {
485
- return undefined;
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
- return;
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
- 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
+ }
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
- let permitDev;
542
- if (isFunction(devid) && !isFunction(failure)) {
543
- failure = devid;
544
- } else {
545
- if (devid != '') {
546
- permitDev = this.getDevice(devid);
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
- if (permitTime && !this.herdsman.getPermitJoin()) {
560
- clearInterval(this._permitJoinInterval);
561
- this._permitJoinTime = permitTime;
562
- await this.herdsman.permitJoin(true, permitDev, this._permitJoinTime);
563
- this._permitJoinInterval = setInterval(async () => {
564
- this.emit('pairing', 'Pairing time left', this._permitJoinTime);
565
- if (this._permitJoinTime === 0) {
566
- this.info('Zigbee: stop joining');
567
- clearInterval(this._permitJoinInterval);
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
- } catch (e) {
581
- this.sendError(e);
582
- 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}`);
583
670
  }
584
671
  }
585
672
 
586
673
  // Remove device
587
674
  async remove(deviceID, force, callback) {
588
675
  try {
589
- const device = await this.herdsman.getDeviceByIeeeAddr(deviceID);
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
- await device.removeFromDatabase();
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.stack}`);
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
- const entity = await this.resolveEntity(message.device || message.ieeeAddr);
693
- const friendlyName = entity.name;
694
- if (message.status === 'successful') {
695
- this.info(`Successfully interviewed '${friendlyName}', device has successfully been paired`);
696
-
697
- if (entity.mapped) {
698
- const {vendor, description, model} = entity.mapped;
699
- this.info(
700
- `Device '${friendlyName}' is supported, identified as: ${vendor} ${description} (${model})`
701
- );
702
-
703
- const log = {friendly_name: friendlyName, model, vendor, description, supported: true};
704
- this.emit('pairing', 'Interview successful', JSON.stringify(log));
705
- entity.device.modelID = entity.device._modelID;
706
- this.emit('new', entity);
707
- // send to extensions again (for configure)
708
- this.callExtensionMethod(
709
- 'onZigbeeEvent',
710
- [message, entity ? entity.mapped : null],
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
- this.debug(
714
- `Device '${friendlyName}' with Zigbee model '${message.device.modelID}' is NOT supported, ` +
715
- `please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`
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
- for (const device of devices.filter((d) => d.type !== 'EndDevice')) {
759
- 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
+ }
760
859
  let result;
761
860
 
762
861
  try {
763
862
  result = await device.lqi();
764
863
  } catch (error) {
765
- this.sendError(error);
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
- 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)}`);
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
- this.debug(`Get map succeeded ${safeJsonStringify(lqis)}`);
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.debug(`Failed to get map: ${safeJsonStringify(error.stack)}`);
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}: ${(error && error.message ? error.message : 'no error message')} ${(error && error.stack ? error.stack : 'no call stack')}`};
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