iobroker.zigbee 1.8.23 → 1.8.24

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.
Files changed (59) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +418 -415
  3. package/admin/adapter-settings.js +244 -244
  4. package/admin/admin.js +2984 -2981
  5. package/admin/i18n/de/translations.json +108 -108
  6. package/admin/i18n/en/translations.json +108 -108
  7. package/admin/i18n/es/translations.json +102 -102
  8. package/admin/i18n/fr/translations.json +108 -108
  9. package/admin/i18n/it/translations.json +102 -102
  10. package/admin/i18n/nl/translations.json +108 -108
  11. package/admin/i18n/pl/translations.json +108 -108
  12. package/admin/i18n/pt/translations.json +102 -102
  13. package/admin/i18n/ru/translations.json +108 -108
  14. package/admin/i18n/uk/translations.json +108 -108
  15. package/admin/i18n/zh-cn/translations.json +102 -102
  16. package/admin/img/philips_hue_lom001.png +0 -0
  17. package/admin/index.html +163 -159
  18. package/admin/index_m.html +1360 -1356
  19. package/admin/moment.min.js +1 -1
  20. package/admin/shuffle.min.js +2 -2
  21. package/admin/tab_m.html +1013 -1009
  22. package/admin/vis-network.min.css +1 -1
  23. package/admin/vis-network.min.js +27 -27
  24. package/admin/words.js +110 -110
  25. package/docs/de/basedocu.md +19 -19
  26. package/docs/de/readme.md +126 -126
  27. package/docs/en/readme.md +128 -128
  28. package/docs/flashing_via_arduino_(en).md +110 -110
  29. package/docs/ru/readme.md +28 -28
  30. package/docs/tutorial/groups-1.png +0 -0
  31. package/docs/tutorial/groups-2.png +0 -0
  32. package/docs/tutorial/tab-dev-1.png +0 -0
  33. package/io-package.json +28 -27
  34. package/lib/backup.js +171 -171
  35. package/lib/binding.js +319 -319
  36. package/lib/colors.js +465 -465
  37. package/lib/commands.js +534 -534
  38. package/lib/developer.js +145 -145
  39. package/lib/devices.js +3135 -3135
  40. package/lib/exclude.js +162 -162
  41. package/lib/exposes.js +913 -913
  42. package/lib/groups.js +345 -345
  43. package/lib/json.js +59 -59
  44. package/lib/networkmap.js +55 -55
  45. package/lib/ota.js +198 -198
  46. package/lib/rgb.js +297 -297
  47. package/lib/seriallist.js +48 -48
  48. package/lib/states.js +6420 -6420
  49. package/lib/statescontroller.js +672 -672
  50. package/lib/tools.js +54 -54
  51. package/lib/utils.js +165 -165
  52. package/lib/zbBaseExtension.js +36 -36
  53. package/lib/zbDelayedAction.js +144 -144
  54. package/lib/zbDeviceAvailability.js +319 -319
  55. package/lib/zbDeviceConfigure.js +149 -147
  56. package/lib/zbDeviceEvent.js +48 -48
  57. package/lib/zigbeecontroller.js +994 -989
  58. package/package.json +8 -8
  59. package/support/docgen.js +93 -93
@@ -1,989 +1,994 @@
1
- 'use strict';
2
-
3
- const pathLib = require('path');
4
- const ZigbeeHerdsman = require('zigbee-herdsman');
5
- const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
6
- const EventEmitter = require('events').EventEmitter;
7
- const safeJsonStringify = require('./json');
8
- const DeviceAvailabilityExt = require('./zbDeviceAvailability');
9
- const DeviceConfigureExt = require('./zbDeviceConfigure');
10
- const DeviceEventExt = require('./zbDeviceEvent');
11
- const DelayedActionExt = require('./zbDelayedAction');
12
- const utils = require('./utils');
13
- const groupConverters = [
14
- zigbeeHerdsmanConverters.toZigbeeConverters.light_onoff_brightness,
15
- zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp,
16
- zigbeeHerdsmanConverters.toZigbeeConverters.light_color,
17
- zigbeeHerdsmanConverters.toZigbeeConverters.light_alert,
18
- zigbeeHerdsmanConverters.toZigbeeConverters.ignore_transition,
19
- zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_move,
20
- zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_step,
21
- zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_move,
22
- zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_step,
23
- zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_startup
24
- ];
25
-
26
- function isFunction(functionToCheck) {
27
- return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
28
- }
29
-
30
- class ZigbeeController extends EventEmitter {
31
- /*
32
- events:
33
-
34
- log - log (level, msg, data)
35
- event - preparsed device events (type, dev, msg, data)
36
- new - new device connected to network (id, msg)
37
- leave - device leave the network (id, msg)
38
- join - join countdown (counter)
39
- ready - connection successfull ()
40
- */
41
- constructor(adapter) {
42
- super();
43
- this.adapter = adapter;
44
- this._permitJoinTime = 0;
45
- this.herdsmanStarted = false;
46
- this.extensions = [
47
- new DeviceAvailabilityExt(this, {}),
48
- new DeviceConfigureExt(this, {}),
49
- new DeviceEventExt(this, {}),
50
- new DelayedActionExt(this, {}),
51
- ];
52
- }
53
-
54
- configure(options) {
55
- const herdsmanSettings = {
56
- network: {
57
- panID: options.net.panId,
58
- extendedPanID: options.net.extPanId,
59
- channelList: options.net.channelList,
60
- networkKey: options.net.precfgkey,
61
- },
62
- databasePath: pathLib.join(options.dbDir, options.dbPath),
63
- backupPath: pathLib.join(options.dbDir, options.backupPath),
64
- serialPort: {
65
- baudRate: options.sp.baudRate,
66
- rtscts: options.sp.rtscts,
67
- path: options.sp.port,
68
- adapter: options.sp.adapter,
69
- },
70
- adapter: {
71
- forceStartWithInconsistentAdapterConfiguration: options.startWithInconsistent
72
- },
73
- };
74
- // https://github.com/ioBroker/ioBroker.zigbee/issues/668
75
- if (!options.extPanIdFix) {
76
- delete herdsmanSettings.network.extendedPanID;
77
- herdsmanSettings.network.extenedPanID = options.net.extPanId;
78
- }
79
-
80
- if (options.transmitPower == undefined) {
81
- this.transmitPower = 0;
82
- } else {
83
- this.transmitPower = options.transmitPower;
84
- }
85
- this.disableLed = options.disableLed;
86
-
87
- this.debug(`Using zigbee-herdsman with settings: ${JSON.stringify(herdsmanSettings)}`);
88
- this.herdsman = new ZigbeeHerdsman.Controller(herdsmanSettings, this.adapter.log);
89
- this.callExtensionMethod('setOptions', [{
90
- disableActivePing: options.disablePing,
91
- disableForcedPing: false,
92
- pingTimeout: 300,
93
- pingCount: 3
94
- }]);
95
- }
96
-
97
- // Start controller
98
- async start() {
99
- try {
100
- //this.debug(`Using zigbee-herdsman with settings2: ${JSON.stringify(this)}`);
101
- this.debug(`Starting zigbee-herdsman...`);
102
-
103
- // install event handlers before start
104
- this.herdsman.on('adapterDisconnected', this.handleDisconnected.bind(this));
105
- this.herdsman.on('deviceAnnounce', this.handleDeviceAnnounce.bind(this));
106
- this.herdsman.on('deviceInterview', this.handleDeviceInterview.bind(this));
107
- this.herdsman.on('deviceJoined', this.handleDeviceJoined.bind(this));
108
- this.herdsman.on('deviceLeave', this.handleDeviceLeave.bind(this));
109
- this.herdsman.on('message', this.handleMessage.bind(this));
110
-
111
- await this.herdsman.start();
112
-
113
- this.debug('zigbee-herdsman started');
114
- this.herdsmanStarted = true;
115
- this.info(`Coordinator firmware version: ${JSON.stringify(await this.herdsman.getCoordinatorVersion())}`);
116
-
117
- // debug info from herdsman getNetworkParameters
118
- const debNetworkParam = JSON.parse(JSON.stringify(await this.herdsman.getNetworkParameters()));
119
- const extendedPanIDDebug = typeof debNetworkParam.extendedPanID === 'string' ? debNetworkParam.extendedPanID.replace('0x', '') : debNetworkParam.extendedPanID;
120
-
121
- let extPanIDDebug = '';
122
- for (let i = extendedPanIDDebug.length - 1; i >= 0; i--) {
123
- extPanIDDebug += extendedPanIDDebug[i - 1];
124
- extPanIDDebug += extendedPanIDDebug[i];
125
- i--;
126
- }
127
-
128
- this.debug(`Zigbee network parameters: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${extPanIDDebug}`);
129
- } catch (e) {
130
- this.sendError(e);
131
- this.error(`Starting zigbee-herdsman problem : ${JSON.stringify(e.message)}`);
132
- throw 'Error herdsman start';
133
- }
134
- // Check if we have to turn off the LED
135
- try {
136
- if (this.disableLed) {
137
- this.info('Disable LED');
138
- await this.herdsman.setLED(false);
139
- } else {
140
- await this.herdsman.setLED(true);
141
- }
142
- } catch (e) {
143
- this.info('Unable to disable LED, unsupported function.');
144
- this.sendError(e);
145
- }
146
-
147
- // only for CC1352P and CC26X2R1 transmit power
148
- let powerText = 'normal';
149
-
150
- if (this.transmitPower != '0') {
151
- switch (this.transmitPower) {
152
- case '-22':
153
- powerText = 'low';
154
- break;
155
- case '19':
156
- powerText = 'high';
157
- break;
158
- case '20':
159
- powerText = 'high+';
160
- break;
161
- default:
162
- powerText = 'normal';
163
- }
164
- }
165
-
166
-
167
- this.info(` --> transmitPower : ${powerText}`);
168
- try {
169
- await this.herdsman.setTransmitPower(this.transmitPower);
170
- } catch (e) {
171
- this.sendError(e);
172
- this.info('Unable to set transmit power, unsupported function.');
173
- }
174
-
175
- // Call extensions
176
- this.callExtensionMethod('onZigbeeStarted', []);
177
-
178
- // Log zigbee clients on startup
179
- const devices = await this.getClients();
180
- if (devices.length > 0) {
181
- this.info(`Currently ${devices.length} devices are joined:`);
182
- } else {
183
- this.info(`Currently no devices.`);
184
- }
185
-
186
- for (const device of devices) {
187
- const entity = await this.resolveEntity(device);
188
- this.adapter.getObject(device.ieeeAddr.substr(2), (err, obj) => {
189
- if (obj && obj.common && obj.common.deactivated) {
190
- this.callExtensionMethod('deregisterDevicePing', [device, entity]);
191
- } else {
192
- this.callExtensionMethod('registerDevicePing', [device, entity]);
193
- }
194
- });
195
- // ensure that objects for all found clients are present
196
-
197
- if (entity.mapped) {
198
- this.emit('new', entity);
199
- }
200
- this.info(
201
- (entity.device.ieeeAddr) +
202
- ` (addr ${entity.device.networkAddress}): ` +
203
- (entity.mapped ?
204
- `${entity.mapped.model} - ${entity.mapped.vendor} ${entity.mapped.description} ` :
205
- `Not supported (model ${entity.device.modelID})`) +
206
- `(${entity.device.type})`
207
- );
208
- }
209
-
210
- this.emit('ready');
211
- }
212
-
213
- info(message, data) {
214
- this.emit('log', 'info', message, data);
215
- }
216
-
217
- error(message, data) {
218
- this.emit('log', 'error', message, data);
219
- }
220
-
221
- debug(message, data) {
222
- this.emit('log', 'debug', message, data);
223
- }
224
-
225
- warn(message, data) {
226
- this.emit('log', 'warn', message, data);
227
- }
228
-
229
- event(type, dev, message, data) {
230
- this.emit('event', type, dev, message, data);
231
- }
232
-
233
- sendError(error, message) {
234
- this.adapter.sendError(error, message);
235
- }
236
-
237
- callExtensionMethod(method, parameters) {
238
- const result = [];
239
- for (const extension of this.extensions) {
240
- if (extension[method]) {
241
- try {
242
- if (parameters !== undefined) {
243
- result.push(extension[method](...parameters));
244
- } else {
245
- result.push(extension[method]());
246
- }
247
- } catch (error) {
248
- this.sendError(error);
249
- this.error(`Failed to call '${extension.constructor.name}' '${method}' (${error.stack})`);
250
- }
251
- }
252
- }
253
- return Promise.all(result);
254
- }
255
-
256
- async getClients(all) {
257
- if (this.herdsman.database) {
258
- const devices = await this.herdsman.getDevices();
259
- if (all) {
260
- return devices;
261
- } else {
262
- return devices.filter(device => device.type !== 'Coordinator');
263
- }
264
- } else {
265
- return [];
266
- }
267
- }
268
-
269
- async getGroups() {
270
- try {
271
- if (this.herdsman) {
272
- return await this.herdsman.getGroups();
273
- } else {
274
- return null;
275
- }
276
- } catch (error) {
277
- this.sendError(error);
278
- this.error(JSON.stringify(error));
279
- return undefined;
280
- }
281
- }
282
-
283
- async removeGroupById(id) {
284
- const group = await this.getGroupByID(id);
285
- try {
286
- group && group.removeFromDatabase();
287
- } catch (error) {
288
- this.sendError(error);
289
- this.error(`error in removeGroupById: ${error}`);
290
- }
291
- }
292
-
293
- async getGroupByID(id) {
294
- try {
295
- return this.herdsman.getGroupByID(id);
296
- } catch (error) {
297
- this.sendError(error);
298
- return undefined;
299
- }
300
- }
301
-
302
- async verifyGroupExists(id) {
303
- const nid = typeof id === 'number' ? id : parseInt(id);
304
- let group = await this.herdsman.getGroupByID(nid);
305
- if (!group) {
306
- group = await this.herdsman.createGroup(nid);
307
- group.toZigbee = groupConverters;
308
- group.model = 'group';
309
- this.debug(`verifyGroupExists: created group ${nid}`);
310
- } else {
311
- this.debug(`verifyGroupExists: group ${nid} exists`);
312
- }
313
- }
314
-
315
- async addPairingCode(code) {
316
- this.debug(`calling addPairingCode with ${code}`);
317
- if (code) {
318
- await this.herdsman.addInstallCode(code);
319
- this.info(`added code ${code} for pairing`);
320
- return true;
321
- }
322
- return false;
323
- }
324
-
325
- async getGroupMembersFromController(id) {
326
- const members = [];
327
- try {
328
- const group = await this.getGroupByID(id);
329
- if (group) {
330
- const groupMembers = group.members;
331
- for (const member of groupMembers) {
332
- const epid = member.ID ? member.ID : -1;
333
- const nwk = member.deviceNetworkAddress;
334
- const device = this.getDeviceByNetworkAddress(nwk);
335
- if (device && device.ieeeAddr) {
336
- members.push({
337
- ieee: device.ieeeAddr,
338
- model: device.modelID,
339
- epid,
340
- ep: member
341
- });
342
- }
343
- }
344
- } else {
345
- return undefined;
346
- }
347
- } catch (error) {
348
- this.sendError(error);
349
- if (error) {
350
- this.error(`getGroupMembersFromController: error is ${JSON.stringify(error)} ${JSON.stringify(new Error().stack)}`);
351
- } else {
352
- this.error('unidentified error in getGroupMembersFromController');
353
- }
354
- }
355
- return members;
356
- }
357
-
358
- getDevice(key) {
359
- return this.herdsman.getDeviceByIeeeAddr(key);
360
- }
361
-
362
- getDevicesByType(type) {
363
- return this.herdsman.getDevicesByType(type);
364
- }
365
-
366
- getDeviceByNetworkAddress(networkAddress) {
367
- return this.herdsman.getDeviceByNetworkAddress(networkAddress);
368
- }
369
-
370
- async resolveEntity(key, ep) {
371
- // assert(typeof key === 'string' || key.constructor.name === 'Device', `Wrong type '${typeof key}'`);
372
-
373
- if (typeof key === 'string') {
374
- if (key === 'coordinator') {
375
- const coordinator = this.herdsman.getDevicesByType('Coordinator')[0];
376
- return {
377
- type: 'device',
378
- device: coordinator,
379
- endpoint: coordinator.getEndpoint(1),
380
- name: 'Coordinator',
381
- };
382
- } else {
383
- const device = await this.herdsman.getDeviceByIeeeAddr(key);
384
- if (device) {
385
- const mapped = zigbeeHerdsmanConverters.findByDevice(device);
386
- const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
387
- let endpoint;
388
- if (endpoints && ep != undefined && endpoints[ep]) {
389
- endpoint = device.getEndpoint(endpoints[ep]);
390
- } else if (endpoints && endpoints['default']) {
391
- endpoint = device.getEndpoint(endpoints['default']);
392
- } else {
393
- const epNum = parseInt(ep);
394
- if (!isNaN(epNum)) {
395
- endpoint = device.getEndpoint(epNum);
396
- } else {
397
- endpoint = device.endpoints[0];
398
- }
399
- }
400
- return {
401
- type: 'device',
402
- device,
403
- mapped,
404
- endpoint,
405
- endpoints: device.endpoints,
406
- name: key,
407
- };
408
- }
409
- }
410
- } else if (typeof key === 'number') {
411
- let group = await this.herdsman.getGroupByID(key);
412
- if (!group) group = await this.herdsman.createGroup(key);
413
- group.toZigbee = groupConverters;
414
- group.model = 'group';
415
- return {
416
- type: 'group',
417
- mapped: group,
418
- group,
419
- name: `Group ${key}`,
420
- };
421
- } else {
422
- return {
423
- type: 'device',
424
- device: key,
425
- mapped: zigbeeHerdsmanConverters.findByDevice(key),
426
- name: key.type === 'Coordinator' ? 'Coordinator' : key.ieeeAddr,
427
- };
428
- }
429
- }
430
-
431
- async incMsgHandler(message) {
432
- this.debug('incoming msg', message);
433
- const device = await this.herdsman.getDeviceByIeeeAddr(message.srcaddr);
434
- if (!device) {
435
- this.debug('Message without device!');
436
- return;
437
- }
438
- // We can't handle devices without modelId.
439
- if (!device.modelId) {
440
- this.debug('Message without modelId!');
441
- return;
442
- }
443
- this.event('msg', device.ieeeAddr, message, {
444
- modelId: device.modelId
445
- });
446
- }
447
-
448
- // Stop controller
449
- async stop() {
450
- // Call extensions
451
- await this.callExtensionMethod('stop', []);
452
-
453
- try {
454
- await this.permitJoin(0);
455
- await this.herdsman.stop();
456
- } catch (error) {
457
- this.sendError(error);
458
- if (this.herdsmanStarted) {
459
- this.error(`Failed to stop zigbee (${error.stack})`);
460
- } else {
461
- this.warn(`Failed to stop zigbee during startup`);
462
- }
463
- }
464
- }
465
-
466
- async handleDisconnected() {
467
- this.herdsmanStarted = false;
468
- this.emit('disconnect');
469
- }
470
-
471
- connected() {
472
- return this.herdsmanStarted;
473
- }
474
-
475
- // Permit join
476
- async permitJoin(permitTime, devid, failure) {
477
- let permitDev;
478
- if (isFunction(devid) && !isFunction(failure)) {
479
- failure = devid;
480
- } else {
481
- permitDev = this.getDevice(devid);
482
- }
483
-
484
- if (permitTime) {
485
- this.info('Zigbee: allowing new devices to join.');
486
- } else {
487
- this.info('Zigbee: disabling joining new devices.');
488
- }
489
-
490
- try {
491
- if (permitTime && !this.herdsman.getPermitJoin()) {
492
- clearInterval(this._permitJoinInterval);
493
- this._permitJoinTime = permitTime;
494
- await this.herdsman.permitJoin(true, permitDev);
495
- this._permitJoinInterval = setInterval(async () => {
496
- this.emit('pairing', 'Pairing time left', this._permitJoinTime);
497
- if (this._permitJoinTime === 0) {
498
- this.info('Zigbee: stop joining');
499
- clearInterval(this._permitJoinInterval);
500
- await this.herdsman.permitJoin(false);
501
- }
502
- this._permitJoinTime -= 1;
503
- }, 1000);
504
- } else if (this.herdsman.getPermitJoin()) {
505
- if (permitTime) {
506
- this.info('Joining already permitted');
507
- } else {
508
- clearInterval(this._permitJoinInterval);
509
- await this.herdsman.permitJoin(false, permitDev);
510
- }
511
- }
512
- } catch (e) {
513
- this.sendError(e);
514
- this.error(`Failed to open the network: ${e.stack}`);
515
- }
516
- }
517
-
518
- // Remove device
519
- async remove(deviceID, force, callback) {
520
- try {
521
- const device = await this.herdsman.getDeviceByIeeeAddr(deviceID);
522
- if (device) {
523
- try {
524
- await this.herdsman.adapter.removeDevice(device.networkAddress, device.ieeeAddr);
525
- } catch (error) {
526
- this.sendError(error);
527
- if (error)
528
- this.debug(`Failed to remove device ${error.stack}`);
529
- // skip error if force
530
- if (!force) {
531
- throw error;
532
- } else {
533
- this.debug(`Force remove`);
534
- }
535
- }
536
-
537
- try {
538
- await device.removeFromDatabase();
539
- } catch (error) {
540
- this.sendError(error);
541
- // skip error
542
- if (error)
543
- this.debug(`Failed to remove from DB ${error.stack}`);
544
- }
545
- this.debug('Remove successful.');
546
- callback && callback();
547
- this.callExtensionMethod(
548
- 'onDeviceRemove',
549
- [device],
550
- );
551
- }
552
- } catch (error) {
553
- this.sendError(error);
554
- this.error(`Failed to remove ${error.stack}`);
555
- callback && callback(`Failed to remove ${error.stack}`);
556
- }
557
- }
558
-
559
- // Zigbee events
560
- async handleDeviceLeave(message) {
561
- try {
562
- this.debug('handleDeviceLeave', message);
563
- const entity = await this.resolveEntity(message.device || message.ieeeAddr);
564
- const friendlyName = entity ? entity.name : message.ieeeAddr;
565
- this.debug(`Device '${friendlyName}' left the network`);
566
- this.emit('leave', message.ieeeAddr);
567
- // Call extensions
568
- this.callExtensionMethod(
569
- 'onDeviceLeave',
570
- [message, entity],
571
- );
572
- } catch (error) {
573
- this.sendError(error);
574
- this.error(`Failed to handleDeviceLeave ${error.stack}`);
575
- }
576
- }
577
-
578
- async handleDeviceAnnounce(message) {
579
- this.debug('handleDeviceAnnounce', message);
580
- const entity = await this.resolveEntity(message.device || message.ieeeAddr);
581
- const friendlyName = entity.name;
582
- this.warn(`Device '${friendlyName}' announced itself`);
583
-
584
- try {
585
- if (entity && entity.mapped) {
586
- this.callExtensionMethod(
587
- 'onZigbeeEvent',
588
- [{'device': message.device, 'type': 'deviceAnnounce'}, entity ? entity.mapped : null]);
589
- }
590
- } catch (error) {
591
- this.sendError(error);
592
- this.error(`Failed to handleDeviceLeave ${error.stack}`);
593
- }
594
-
595
- this.emit('pairing', `Device '${friendlyName}' announced itself`);
596
- if (!this.herdsman.getPermitJoin()) {
597
- this.callExtensionMethod('registerDevicePing', [message.device, entity]);
598
- }
599
- // if has modelID so can create device
600
- if (entity.device && entity.device._modelID) {
601
- entity.device.modelID = entity.device._modelID;
602
- this.emit('new', entity);
603
- }
604
- }
605
-
606
- async handleDeviceJoined(message) {
607
- this.debug('handleDeviceJoined', message);
608
- //const entity = await this.resolveEntity(message.device || message.ieeeAddr);
609
- //this.emit('new', entity);
610
- }
611
-
612
- async handleDeviceInterview(message) {
613
- this.debug('handleDeviceInterview', message);
614
- // safeguard: We do not allow to start an interview if the network is not opened
615
- if (message.status === 'started' && !this.herdsman.getPermitJoin()) {
616
- this.warn(`Blocked interview for '${message.ieeeAddr}' because the network is closed`);
617
- return;
618
- }
619
- const entity = await this.resolveEntity(message.device || message.ieeeAddr);
620
- const friendlyName = entity.name;
621
- if (message.status === 'successful') {
622
- this.info(`Successfully interviewed '${friendlyName}', device has successfully been paired`);
623
-
624
- if (entity.mapped) {
625
- const {vendor, description, model} = entity.mapped;
626
- this.info(
627
- `Device '${friendlyName}' is supported, identified as: ${vendor} ${description} (${model})`
628
- );
629
-
630
- const log = {friendly_name: friendlyName, model, vendor, description, supported: true};
631
- this.emit('pairing', 'Interview successful', JSON.stringify(log));
632
- entity.device.modelID = entity.device._modelID;
633
- this.emit('new', entity);
634
- // send to extensions again (for configure)
635
- this.callExtensionMethod(
636
- 'onZigbeeEvent',
637
- [message, entity ? entity.mapped : null],
638
- );
639
- } else {
640
- this.debug(
641
- `Device '${friendlyName}' with Zigbee model '${message.device.modelID}' is NOT supported, ` +
642
- `please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`
643
- );
644
- this.emit('pairing', 'Interview successful', {friendly_name: friendlyName, supported: false});
645
- entity.device.modelID = entity.device._modelID;
646
- this.emit('new', entity);
647
- }
648
- } else if (message.status === 'failed') {
649
- this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. ${message.error}`);
650
- this.emit('pairing', 'Interview failed', friendlyName);
651
- } else {
652
- if (message.status === 'started') {
653
- this.info(`Starting interview of '${friendlyName}'`);
654
- this.emit('pairing', 'Interview started', friendlyName);
655
- }
656
- }
657
- }
658
-
659
- async handleMessage(data) {
660
- this.debug(`handleMessage`, data);
661
- const entity = await this.resolveEntity(data.device || data.ieeeAddr);
662
- const name = (entity && entity._modelID) ? entity._modelID : data.device.ieeeAddr;
663
- this.debug(
664
- `Received Zigbee message from '${name}', type '${data.type}', cluster '${data.cluster}'` +
665
- `, data '${JSON.stringify(data.data)}' from endpoint ${data.endpoint.ID}` +
666
- (data.hasOwnProperty('groupID') ? ` with groupID ${data.groupID}` : ``)
667
- );
668
- this.event(data.type, entity, data);
669
-
670
- // Call extensions
671
- this.callExtensionMethod(
672
- 'onZigbeeEvent',
673
- [data, entity ? entity.mapped : null],
674
- );
675
- }
676
-
677
- async getMap(callback) {
678
- try {
679
- const devices = this.herdsman.getDevices(true);
680
- const lqis = [];
681
- const routing = [];
682
-
683
- for (const device of devices.filter((d) => d.type !== 'EndDevice')) {
684
- const resolved = await this.resolveEntity(device);
685
- let result;
686
-
687
- try {
688
- result = await device.lqi();
689
- } catch (error) {
690
- this.sendError(error);
691
- error && this.debug(`Failed to execute LQI for '${resolved.name}'. ${safeJsonStringify(error.stack)}`);
692
-
693
- lqis.push({
694
- parent: 'undefined',
695
- networkAddress: 0,
696
- ieeeAddr: device.ieeeAddr,
697
- lqi: 'undefined',
698
- relationship: 0,
699
- depth: 0,
700
- status: 'offline',
701
- });
702
- }
703
-
704
- if (result !== undefined) {
705
- for (const dev of result.neighbors) {
706
- if (dev !== undefined && dev.ieeeAddr !== '0xffffffffffffffff') {
707
- lqis.push({
708
- parent: resolved.device.ieeeAddr,
709
- networkAddress: dev.networkAddress,
710
- ieeeAddr: dev.ieeeAddr,
711
- lqi: dev.linkquality,
712
- relationship: dev.relationship,
713
- depth: dev.depth,
714
- status: dev.linkquality > 0 ? 'online' : 'offline',
715
- });
716
- }
717
- }
718
- }
719
-
720
- this.debug(`LQI succeeded for '${resolved.name}'`);
721
-
722
- try {
723
- result = await device.routingTable();
724
- } catch (error) {
725
- this.sendError(error);
726
- if (error) {
727
- this.debug(`Failed to execute routing table for '${resolved.name}'. ${safeJsonStringify(error.stack)}`);
728
- }
729
- }
730
-
731
- this.debug(`Routing for '${resolved.name}': ${safeJsonStringify(result)}`);
732
- if (result !== undefined) {
733
- if (result.table !== undefined) {
734
- for (const dev of result.table) {
735
- routing.push({
736
- source: resolved.device.ieeeAddr,
737
- destination: dev.destinationAddress,
738
- nextHop: dev.nextHop,
739
- status: dev.status,
740
- });
741
- }
742
- }
743
- }
744
- this.debug(`Routing table succeeded for '${resolved.name}'`);
745
- }
746
- this.debug(`Get map succeeded ${safeJsonStringify(lqis)}`);
747
-
748
- callback && callback({lqis, routing});
749
- } catch (error) {
750
- this.sendError(error);
751
- this.debug(`Failed to get map: ${safeJsonStringify(error.stack)}`);
752
- }
753
- }
754
-
755
- async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback, zclSeqNum) {
756
- const entity = await this.resolveEntity(deviceID, ep);
757
- const device = entity.device;
758
- const endpoint = entity.endpoint;
759
- if (!device) {
760
- this.error(
761
- `Zigbee cannot publish message to device because '${deviceID}' is not known`
762
- );
763
- return;
764
- }
765
- if (!endpoint) {
766
- this.error(
767
- `Zigbee cannot publish message to endpoint because '${ep}' is not known`
768
- );
769
- return;
770
- }
771
-
772
- this.debug(`Zigbee publish to '${deviceID}', ${cid} - cmd ${cmd} - payload ${JSON.stringify(zclData)} - cfg ${JSON.stringify(cfg)} - endpoint ${ep}`);
773
-
774
- if (cfg == null) {
775
- cfg = {};
776
- }
777
-
778
- if (type === 'foundation') {
779
- cfg.disableDefaultResponse = true;
780
- if (cmd === 'read' && !Array.isArray(zclData)) {
781
- // needs to be iterable (string[] | number [])
782
- zclData[Symbol.iterator] = function* () {
783
- let k;
784
- for (k in this) {
785
- yield k;
786
- }
787
- };
788
- }
789
- let result;
790
- if (cmd === 'configReport') {
791
- result = await endpoint.configureReporting(cid, zclData, cfg);
792
- } else {
793
- result = await endpoint[cmd](cid, zclData, cfg);
794
- }
795
- callback && callback(undefined, result);
796
- } else if (type === 'functionalResp') {
797
- cfg.disableDefaultResponse = false;
798
- const result = await endpoint.commandResponse(cid, cmd, zclData, cfg, zclSeqNum);
799
- callback && callback(undefined, result);
800
- } else {
801
- cfg.disableDefaultResponse = false;
802
- const result = await endpoint.command(cid, cmd, zclData, cfg);
803
- callback && callback(undefined, result);
804
- }
805
- }
806
-
807
- async addDevToGroup(devId, groupId, epid) {
808
- try {
809
- this.debug(`called addDevToGroup with ${devId}, ${groupId}, ${epid}`);
810
- const entity = await this.resolveEntity(devId);
811
- const group = await this.resolveEntity(groupId);
812
- this.debug(`addDevFromGroup - entity: ${utils.getEntityInfo(entity)}`);
813
- // generate group debug info and display it
814
- const members = await this.getGroupMembersFromController(groupId);
815
- let memberIDs = [];
816
- for (const member of members) {
817
- memberIDs.push(member.ieee);
818
- }
819
- this.debug(`addDevToGroup ${groupId} with ${memberIDs.length} members ${safeJsonStringify(memberIDs)}`);
820
- if (epid != undefined) {
821
- for (const ep of entity.endpoints) {
822
- this.debug(`checking ep ${ep.ID} of ${devId} (${epid})`);
823
- if (ep.ID == epid) {
824
- if (ep.inputClusters.includes(4) || ep.outputClusters.includes(4)) {
825
- this.debug(`adding endpoint ${ep.ID} (${epid}) to group ${groupId}`);
826
- await (ep.addToGroup(group.mapped));
827
- }
828
- else this.error(`cluster genGroups not supported for endpoint ${epid} of ${devId}`);
829
- }
830
- }
831
- } else {
832
- if (entity.endpoint.inputClusters.includes(4)) {
833
- this.info(`adding endpoint ${entity.endpoint.ID} of ${devId} to group`);
834
- await entity.endpoint.addToGroup(group.mapped);
835
- } else {
836
- let added = false;
837
- for (const ep of entity.endpoints) {
838
- if (ep.inputClusters.includes(4)) {
839
- await ep.addToGroup(group.mapped);
840
- added = true;
841
- break;
842
- }
843
- }
844
- if (!added) {
845
- throw ('cluster genGroups not supported');
846
- }
847
- }
848
- }
849
- } catch (error) {
850
- this.sendError(error);
851
- this.error(`Exception when trying to Add ${devId} to group ${groupId}`, error);
852
- return {error: `Failed to add ${devId} to group ${groupId}: ${JSON.stringify(error)}`};
853
- }
854
- return {};
855
- }
856
-
857
- async removeDevFromGroup(devId, groupId, epid) {
858
- this.debug(`removeDevFromGroup with ${devId}, ${groupId}, ${epid}`);
859
- let entity;
860
- try {
861
- entity = await this.resolveEntity(devId);
862
- const group = await this.resolveEntity(groupId);
863
-
864
- const members = await this.getGroupMembersFromController(groupId);
865
- let memberIDs = [];
866
- for (const member of members) {
867
- memberIDs.push(member.ieee);
868
- }
869
-
870
- this.debug(`removeDevFromGroup - entity: ${utils.getEntityInfo(entity)}`);
871
- this.debug(`removeDevFromGroup ${groupId} with ${memberIDs.length} members ${safeJsonStringify(memberIDs)}`);
872
-
873
- if (epid != undefined) {
874
- for (const ep of entity.endpoints) {
875
- this.debug(`checking ep ${ep.ID} of ${devId} (${epid})`);
876
- if (ep.ID == epid && (ep.inputClusters.includes(4) || ep.outputClusters.includes(4))) {
877
- await ep.removeFromGroup(group.mapped);
878
- this.info(`removing endpoint ${ep.ID} of ${devId} from group ${groupId}`);
879
- }
880
- }
881
- } else {
882
- await entity.endpoint.removeFromGroup(group.mapped);
883
- this.info(`removing endpoint ${entity.endpoint.ID} of ${devId} from group ${groupId}`);
884
- }
885
- } catch (error) {
886
- this.sendError(error);
887
- this.error(`Exception when trying remove ${devId} (ep ${epid ? epid : (entity ? entity.endpoint.ID : '')}) from group ${devId}`, error);
888
- return {error: `Failed to remove dev ${devId} (ep ${epid ? epid : (entity ? entity.endpoint.ID : '')}) from group ${devId}`};
889
- }
890
- return {};
891
- }
892
-
893
- async removeDevFromAllGroups(devId) {
894
- try {
895
- const entity = await this.resolveEntity(devId);
896
- this.debug(`entity: ${safeJsonStringify(entity)}`);
897
- for (const ep of entity.endpoints) {
898
- if (ep.inputClusters.includes(4) || ep.outputClusters.includes(4)) {
899
- await ep.removefromAllGroups();
900
- }
901
- }
902
- } catch (error) {
903
- this.sendError(error);
904
- this.error(`Exception when trying remove ${devId} from all groups`, error);
905
- return {error: `Failed to remove dev ${devId} from all groups: ${error}`};
906
- }
907
- return {};
908
- }
909
-
910
- bind(ep, cluster, target, callback) {
911
- const log = ` ${ep.device.ieeeAddr} - ${cluster}`;
912
- target = !target ? this.getCoordinator() : target;
913
-
914
- this.debug(`Binding ${log}`);
915
- ep.bind(cluster, target, error => {
916
- if (error) {
917
- this.sendError(error);
918
- this.error(`Failed to bind ${log} - (${error})`);
919
- } else {
920
- this.debug(`Successfully bound ${log}`);
921
- }
922
-
923
- callback(error);
924
- });
925
- }
926
-
927
- unbind(ep, cluster, target, callback) {
928
- const log = ` ${ep.device.ieeeAddr} - ${cluster}`;
929
- target = !target ? this.getCoordinator() : target;
930
-
931
- this.debug(`Unbinding ${log}`);
932
- ep.unbind(cluster, target, (error) => {
933
- if (error) {
934
- this.error(`Failed to unbind ${log} - (${error})`);
935
- } else {
936
- this.debug(`Successfully unbound ${log}`);
937
- }
938
-
939
- callback(error);
940
- });
941
- }
942
-
943
- reset(mode, callback) {
944
- try {
945
- this.herdsman.reset(mode);
946
- callback && callback();
947
- } catch (error) {
948
- this.sendError(error);
949
- this.error(`Failed to reset ${error.stack}`);
950
- callback && callback(error);
951
- }
952
- }
953
-
954
- async touchlinkReset(permitTime) {
955
- try {
956
- await this.herdsman.touchlinkFactoryResetFirst();
957
- this.permitJoin(permitTime);
958
- } catch (error) {
959
- this.sendError(error);
960
- this.error(`Failed to touchlinkReset ${error.stack}`);
961
- }
962
- }
963
-
964
- async getChannelsEnergy() {
965
- const payload = {
966
- dstaddr: 0x0,
967
- dstaddrmode: 0x02,
968
- channelmask: 0x07FFF800,
969
- scanduration: 0x5,
970
- scancount: 1,
971
- nwkmanageraddr: 0x0000
972
- };
973
- const energyScan = this.herdsman.adapter.znp.waitFor(
974
- 2, // unpi_1.Constants.Type.AREQ,
975
- 5, // Subsystem.ZDO,
976
- 'mgmtNwkUpdateNotify'
977
- );
978
- await this.herdsman.adapter.znp.request(
979
- 0x5, // Subsystem.ZDO
980
- 'mgmtNwkUpdateReq',
981
- payload,
982
- energyScan.ID
983
- );
984
- const result = await energyScan.start().promise;
985
- return result.payload;
986
- }
987
- }
988
-
989
- module.exports = ZigbeeController;
1
+ 'use strict';
2
+
3
+ const pathLib = require('path');
4
+ const ZigbeeHerdsman = require('zigbee-herdsman');
5
+ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
6
+ const EventEmitter = require('events').EventEmitter;
7
+ const safeJsonStringify = require('./json');
8
+ const DeviceAvailabilityExt = require('./zbDeviceAvailability');
9
+ const DeviceConfigureExt = require('./zbDeviceConfigure');
10
+ const DeviceEventExt = require('./zbDeviceEvent');
11
+ const DelayedActionExt = require('./zbDelayedAction');
12
+ const utils = require('./utils');
13
+ const groupConverters = [
14
+ zigbeeHerdsmanConverters.toZigbeeConverters.light_onoff_brightness,
15
+ zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp,
16
+ zigbeeHerdsmanConverters.toZigbeeConverters.light_color,
17
+ zigbeeHerdsmanConverters.toZigbeeConverters.light_alert,
18
+ zigbeeHerdsmanConverters.toZigbeeConverters.ignore_transition,
19
+ zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_move,
20
+ zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_step,
21
+ zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_move,
22
+ zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_step,
23
+ zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_startup
24
+ ];
25
+
26
+ function isFunction(functionToCheck) {
27
+ return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
28
+ }
29
+
30
+ class ZigbeeController extends EventEmitter {
31
+ /*
32
+ events:
33
+
34
+ log - log (level, msg, data)
35
+ event - preparsed device events (type, dev, msg, data)
36
+ new - new device connected to network (id, msg)
37
+ leave - device leave the network (id, msg)
38
+ join - join countdown (counter)
39
+ ready - connection successfull ()
40
+ */
41
+ constructor(adapter) {
42
+ super();
43
+ this.adapter = adapter;
44
+ this._permitJoinTime = 0;
45
+ this.herdsmanStarted = false;
46
+ this.extensions = [
47
+ new DeviceAvailabilityExt(this, {}),
48
+ new DeviceConfigureExt(this, {}),
49
+ new DeviceEventExt(this, {}),
50
+ new DelayedActionExt(this, {}),
51
+ ];
52
+ }
53
+
54
+ configure(options) {
55
+ const herdsmanSettings = {
56
+ network: {
57
+ panID: options.net.panId,
58
+ extendedPanID: options.net.extPanId,
59
+ channelList: options.net.channelList,
60
+ networkKey: options.net.precfgkey,
61
+ },
62
+ databasePath: pathLib.join(options.dbDir, options.dbPath),
63
+ backupPath: pathLib.join(options.dbDir, options.backupPath),
64
+ serialPort: {
65
+ baudRate: options.sp.baudRate,
66
+ rtscts: options.sp.rtscts,
67
+ path: options.sp.port,
68
+ adapter: options.sp.adapter,
69
+ },
70
+ adapter: {
71
+ forceStartWithInconsistentAdapterConfiguration: options.startWithInconsistent
72
+ },
73
+ };
74
+ // https://github.com/ioBroker/ioBroker.zigbee/issues/668
75
+ if (!options.extPanIdFix) {
76
+ delete herdsmanSettings.network.extendedPanID;
77
+ herdsmanSettings.network.extenedPanID = options.net.extPanId;
78
+ }
79
+
80
+ if (options.transmitPower == undefined) {
81
+ this.transmitPower = 0;
82
+ } else {
83
+ this.transmitPower = options.transmitPower;
84
+ }
85
+ this.disableLed = options.disableLed;
86
+ this.warnOnDeviceAnnouncement = options.warnOnDeviceAnnouncement;
87
+
88
+ this.debug(`Using zigbee-herdsman with settings: ${JSON.stringify(herdsmanSettings)}`);
89
+ this.herdsman = new ZigbeeHerdsman.Controller(herdsmanSettings, this.adapter.log);
90
+ this.callExtensionMethod('setOptions', [{
91
+ disableActivePing: options.disablePing,
92
+ disableForcedPing: false,
93
+ pingTimeout: 300,
94
+ pingCount: 3
95
+ }]);
96
+ }
97
+
98
+ // Start controller
99
+ async start() {
100
+ try {
101
+ //this.debug(`Using zigbee-herdsman with settings2: ${JSON.stringify(this)}`);
102
+ this.debug(`Starting zigbee-herdsman...`);
103
+
104
+ // install event handlers before start
105
+ this.herdsman.on('adapterDisconnected', this.handleDisconnected.bind(this));
106
+ this.herdsman.on('deviceAnnounce', this.handleDeviceAnnounce.bind(this));
107
+ this.herdsman.on('deviceInterview', this.handleDeviceInterview.bind(this));
108
+ this.herdsman.on('deviceJoined', this.handleDeviceJoined.bind(this));
109
+ this.herdsman.on('deviceLeave', this.handleDeviceLeave.bind(this));
110
+ this.herdsman.on('message', this.handleMessage.bind(this));
111
+
112
+ await this.herdsman.start();
113
+
114
+ this.debug('zigbee-herdsman started');
115
+ this.herdsmanStarted = true;
116
+ this.info(`Coordinator firmware version: ${JSON.stringify(await this.herdsman.getCoordinatorVersion())}`);
117
+
118
+ // debug info from herdsman getNetworkParameters
119
+ const debNetworkParam = JSON.parse(JSON.stringify(await this.herdsman.getNetworkParameters()));
120
+ const extendedPanIDDebug = typeof debNetworkParam.extendedPanID === 'string' ? debNetworkParam.extendedPanID.replace('0x', '') : debNetworkParam.extendedPanID;
121
+
122
+ let extPanIDDebug = '';
123
+ for (let i = extendedPanIDDebug.length - 1; i >= 0; i--) {
124
+ extPanIDDebug += extendedPanIDDebug[i - 1];
125
+ extPanIDDebug += extendedPanIDDebug[i];
126
+ i--;
127
+ }
128
+
129
+ this.debug(`Zigbee network parameters: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${extPanIDDebug}`);
130
+ } catch (e) {
131
+ this.sendError(e);
132
+ this.error(`Starting zigbee-herdsman problem : ${JSON.stringify(e.message)}`);
133
+ throw 'Error herdsman start';
134
+ }
135
+ // Check if we have to turn off the LED
136
+ try {
137
+ if (this.disableLed) {
138
+ this.info('Disable LED');
139
+ await this.herdsman.setLED(false);
140
+ } else {
141
+ await this.herdsman.setLED(true);
142
+ }
143
+ } catch (e) {
144
+ this.info('Unable to disable LED, unsupported function.');
145
+ this.sendError(e);
146
+ }
147
+
148
+ // only for CC1352P and CC26X2R1 transmit power
149
+ let powerText = 'normal';
150
+
151
+ if (this.transmitPower != '0') {
152
+ switch (this.transmitPower) {
153
+ case '-22':
154
+ powerText = 'low';
155
+ break;
156
+ case '19':
157
+ powerText = 'high';
158
+ break;
159
+ case '20':
160
+ powerText = 'high+';
161
+ break;
162
+ default:
163
+ powerText = 'normal';
164
+ }
165
+ }
166
+
167
+
168
+ this.info(` --> transmitPower : ${powerText}`);
169
+ try {
170
+ await this.herdsman.setTransmitPower(this.transmitPower);
171
+ } catch (e) {
172
+ this.sendError(e);
173
+ this.info('Unable to set transmit power, unsupported function.');
174
+ }
175
+
176
+ // Call extensions
177
+ this.callExtensionMethod('onZigbeeStarted', []);
178
+
179
+ // Log zigbee clients on startup
180
+ const devices = await this.getClients();
181
+ if (devices.length > 0) {
182
+ this.info(`Currently ${devices.length} devices are joined:`);
183
+ } else {
184
+ this.info(`Currently no devices.`);
185
+ }
186
+
187
+ for (const device of devices) {
188
+ const entity = await this.resolveEntity(device);
189
+ this.adapter.getObject(device.ieeeAddr.substr(2), (err, obj) => {
190
+ if (obj && obj.common && obj.common.deactivated) {
191
+ this.callExtensionMethod('deregisterDevicePing', [device, entity]);
192
+ } else {
193
+ this.callExtensionMethod('registerDevicePing', [device, entity]);
194
+ }
195
+ });
196
+ // ensure that objects for all found clients are present
197
+
198
+ if (entity.mapped) {
199
+ this.emit('new', entity);
200
+ }
201
+ this.info(
202
+ (entity.device.ieeeAddr) +
203
+ ` (addr ${entity.device.networkAddress}): ` +
204
+ (entity.mapped ?
205
+ `${entity.mapped.model} - ${entity.mapped.vendor} ${entity.mapped.description} ` :
206
+ `Not supported (model ${entity.device.modelID})`) +
207
+ `(${entity.device.type})`
208
+ );
209
+ }
210
+
211
+ this.emit('ready');
212
+ }
213
+
214
+ info(message, data) {
215
+ this.emit('log', 'info', message, data);
216
+ }
217
+
218
+ error(message, data) {
219
+ this.emit('log', 'error', message, data);
220
+ }
221
+
222
+ debug(message, data) {
223
+ this.emit('log', 'debug', message, data);
224
+ }
225
+
226
+ warn(message, data) {
227
+ this.emit('log', 'warn', message, data);
228
+ }
229
+
230
+ event(type, dev, message, data) {
231
+ this.emit('event', type, dev, message, data);
232
+ }
233
+
234
+ sendError(error, message) {
235
+ this.adapter.sendError(error, message);
236
+ }
237
+
238
+ callExtensionMethod(method, parameters) {
239
+ const result = [];
240
+ for (const extension of this.extensions) {
241
+ if (extension[method]) {
242
+ try {
243
+ if (parameters !== undefined) {
244
+ result.push(extension[method](...parameters));
245
+ } else {
246
+ result.push(extension[method]());
247
+ }
248
+ } catch (error) {
249
+ this.sendError(error);
250
+ this.error(`Failed to call '${extension.constructor.name}' '${method}' (${error.stack})`);
251
+ }
252
+ }
253
+ }
254
+ return Promise.all(result);
255
+ }
256
+
257
+ async getClients(all) {
258
+ if (this.herdsman.database) {
259
+ const devices = await this.herdsman.getDevices();
260
+ if (all) {
261
+ return devices;
262
+ } else {
263
+ return devices.filter(device => device.type !== 'Coordinator');
264
+ }
265
+ } else {
266
+ return [];
267
+ }
268
+ }
269
+
270
+ async getGroups() {
271
+ try {
272
+ if (this.herdsman) {
273
+ return await this.herdsman.getGroups();
274
+ } else {
275
+ return null;
276
+ }
277
+ } catch (error) {
278
+ this.sendError(error);
279
+ this.error(JSON.stringify(error));
280
+ return undefined;
281
+ }
282
+ }
283
+
284
+ async removeGroupById(id) {
285
+ const group = await this.getGroupByID(id);
286
+ try {
287
+ group && group.removeFromDatabase();
288
+ } catch (error) {
289
+ this.sendError(error);
290
+ this.error(`error in removeGroupById: ${error}`);
291
+ }
292
+ }
293
+
294
+ async getGroupByID(id) {
295
+ try {
296
+ return this.herdsman.getGroupByID(id);
297
+ } catch (error) {
298
+ this.sendError(error);
299
+ return undefined;
300
+ }
301
+ }
302
+
303
+ async verifyGroupExists(id) {
304
+ const nid = typeof id === 'number' ? id : parseInt(id);
305
+ let group = await this.herdsman.getGroupByID(nid);
306
+ if (!group) {
307
+ group = await this.herdsman.createGroup(nid);
308
+ group.toZigbee = groupConverters;
309
+ group.model = 'group';
310
+ this.debug(`verifyGroupExists: created group ${nid}`);
311
+ } else {
312
+ this.debug(`verifyGroupExists: group ${nid} exists`);
313
+ }
314
+ }
315
+
316
+ async addPairingCode(code) {
317
+ this.debug(`calling addPairingCode with ${code}`);
318
+ if (code) {
319
+ await this.herdsman.addInstallCode(code);
320
+ this.info(`added code ${code} for pairing`);
321
+ return true;
322
+ }
323
+ return false;
324
+ }
325
+
326
+ async getGroupMembersFromController(id) {
327
+ const members = [];
328
+ try {
329
+ const group = await this.getGroupByID(id);
330
+ if (group) {
331
+ const groupMembers = group.members;
332
+ for (const member of groupMembers) {
333
+ const epid = member.ID ? member.ID : -1;
334
+ const nwk = member.deviceNetworkAddress;
335
+ const device = this.getDeviceByNetworkAddress(nwk);
336
+ if (device && device.ieeeAddr) {
337
+ members.push({
338
+ ieee: device.ieeeAddr,
339
+ model: device.modelID,
340
+ epid,
341
+ ep: member
342
+ });
343
+ }
344
+ }
345
+ } else {
346
+ return undefined;
347
+ }
348
+ } catch (error) {
349
+ this.sendError(error);
350
+ if (error) {
351
+ this.error(`getGroupMembersFromController: error is ${JSON.stringify(error)} ${JSON.stringify(new Error().stack)}`);
352
+ } else {
353
+ this.error('unidentified error in getGroupMembersFromController');
354
+ }
355
+ }
356
+ return members;
357
+ }
358
+
359
+ getDevice(key) {
360
+ return this.herdsman.getDeviceByIeeeAddr(key);
361
+ }
362
+
363
+ getDevicesByType(type) {
364
+ return this.herdsman.getDevicesByType(type);
365
+ }
366
+
367
+ getDeviceByNetworkAddress(networkAddress) {
368
+ return this.herdsman.getDeviceByNetworkAddress(networkAddress);
369
+ }
370
+
371
+ async resolveEntity(key, ep) {
372
+ // assert(typeof key === 'string' || key.constructor.name === 'Device', `Wrong type '${typeof key}'`);
373
+
374
+ if (typeof key === 'string') {
375
+ if (key === 'coordinator') {
376
+ const coordinator = this.herdsman.getDevicesByType('Coordinator')[0];
377
+ return {
378
+ type: 'device',
379
+ device: coordinator,
380
+ endpoint: coordinator.getEndpoint(1),
381
+ name: 'Coordinator',
382
+ };
383
+ } else {
384
+ const device = await this.herdsman.getDeviceByIeeeAddr(key);
385
+ if (device) {
386
+ const mapped = zigbeeHerdsmanConverters.findByDevice(device);
387
+ const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
388
+ let endpoint;
389
+ if (endpoints && ep != undefined && endpoints[ep]) {
390
+ endpoint = device.getEndpoint(endpoints[ep]);
391
+ } else if (endpoints && endpoints['default']) {
392
+ endpoint = device.getEndpoint(endpoints['default']);
393
+ } else {
394
+ const epNum = parseInt(ep);
395
+ if (!isNaN(epNum)) {
396
+ endpoint = device.getEndpoint(epNum);
397
+ } else {
398
+ endpoint = device.endpoints[0];
399
+ }
400
+ }
401
+ return {
402
+ type: 'device',
403
+ device,
404
+ mapped,
405
+ endpoint,
406
+ endpoints: device.endpoints,
407
+ name: key,
408
+ };
409
+ }
410
+ }
411
+ } else if (typeof key === 'number') {
412
+ let group = await this.herdsman.getGroupByID(key);
413
+ if (!group) group = await this.herdsman.createGroup(key);
414
+ group.toZigbee = groupConverters;
415
+ group.model = 'group';
416
+ return {
417
+ type: 'group',
418
+ mapped: group,
419
+ group,
420
+ name: `Group ${key}`,
421
+ };
422
+ } else {
423
+ return {
424
+ type: 'device',
425
+ device: key,
426
+ mapped: zigbeeHerdsmanConverters.findByDevice(key),
427
+ name: key.type === 'Coordinator' ? 'Coordinator' : key.ieeeAddr,
428
+ };
429
+ }
430
+ }
431
+
432
+ async incMsgHandler(message) {
433
+ this.debug('incoming msg', message);
434
+ const device = await this.herdsman.getDeviceByIeeeAddr(message.srcaddr);
435
+ if (!device) {
436
+ this.debug('Message without device!');
437
+ return;
438
+ }
439
+ // We can't handle devices without modelId.
440
+ if (!device.modelId) {
441
+ this.debug('Message without modelId!');
442
+ return;
443
+ }
444
+ this.event('msg', device.ieeeAddr, message, {
445
+ modelId: device.modelId
446
+ });
447
+ }
448
+
449
+ // Stop controller
450
+ async stop() {
451
+ // Call extensions
452
+ await this.callExtensionMethod('stop', []);
453
+
454
+ try {
455
+ await this.permitJoin(0);
456
+ await this.herdsman.stop();
457
+ } catch (error) {
458
+ this.sendError(error);
459
+ if (this.herdsmanStarted) {
460
+ this.error(`Failed to stop zigbee (${error.stack})`);
461
+ } else {
462
+ this.warn(`Failed to stop zigbee during startup`);
463
+ }
464
+ }
465
+ }
466
+
467
+ async handleDisconnected() {
468
+ this.herdsmanStarted = false;
469
+ this.emit('disconnect');
470
+ }
471
+
472
+ connected() {
473
+ return this.herdsmanStarted;
474
+ }
475
+
476
+ // Permit join
477
+ async permitJoin(permitTime, devid, failure) {
478
+ let permitDev;
479
+ if (isFunction(devid) && !isFunction(failure)) {
480
+ failure = devid;
481
+ } else {
482
+ permitDev = this.getDevice(devid);
483
+ }
484
+
485
+ if (permitTime) {
486
+ this.info('Zigbee: allowing new devices to join.');
487
+ } else {
488
+ this.info('Zigbee: disabling joining new devices.');
489
+ }
490
+
491
+ try {
492
+ if (permitTime && !this.herdsman.getPermitJoin()) {
493
+ clearInterval(this._permitJoinInterval);
494
+ this._permitJoinTime = permitTime;
495
+ await this.herdsman.permitJoin(true, permitDev);
496
+ this._permitJoinInterval = setInterval(async () => {
497
+ this.emit('pairing', 'Pairing time left', this._permitJoinTime);
498
+ if (this._permitJoinTime === 0) {
499
+ this.info('Zigbee: stop joining');
500
+ clearInterval(this._permitJoinInterval);
501
+ await this.herdsman.permitJoin(false);
502
+ }
503
+ this._permitJoinTime -= 1;
504
+ }, 1000);
505
+ } else if (this.herdsman.getPermitJoin()) {
506
+ if (permitTime) {
507
+ this.info('Joining already permitted');
508
+ } else {
509
+ clearInterval(this._permitJoinInterval);
510
+ await this.herdsman.permitJoin(false, permitDev);
511
+ }
512
+ }
513
+ } catch (e) {
514
+ this.sendError(e);
515
+ this.error(`Failed to open the network: ${e.stack}`);
516
+ }
517
+ }
518
+
519
+ // Remove device
520
+ async remove(deviceID, force, callback) {
521
+ try {
522
+ const device = await this.herdsman.getDeviceByIeeeAddr(deviceID);
523
+ if (device) {
524
+ try {
525
+ await this.herdsman.adapter.removeDevice(device.networkAddress, device.ieeeAddr);
526
+ } catch (error) {
527
+ this.sendError(error);
528
+ if (error)
529
+ this.debug(`Failed to remove device ${error.stack}`);
530
+ // skip error if force
531
+ if (!force) {
532
+ throw error;
533
+ } else {
534
+ this.debug(`Force remove`);
535
+ }
536
+ }
537
+
538
+ try {
539
+ await device.removeFromDatabase();
540
+ } catch (error) {
541
+ this.sendError(error);
542
+ // skip error
543
+ if (error)
544
+ this.debug(`Failed to remove from DB ${error.stack}`);
545
+ }
546
+ this.debug('Remove successful.');
547
+ callback && callback();
548
+ this.callExtensionMethod(
549
+ 'onDeviceRemove',
550
+ [device],
551
+ );
552
+ }
553
+ } catch (error) {
554
+ this.sendError(error);
555
+ this.error(`Failed to remove ${error.stack}`);
556
+ callback && callback(`Failed to remove ${error.stack}`);
557
+ }
558
+ }
559
+
560
+ // Zigbee events
561
+ async handleDeviceLeave(message) {
562
+ try {
563
+ this.debug('handleDeviceLeave', message);
564
+ const entity = await this.resolveEntity(message.device || message.ieeeAddr);
565
+ const friendlyName = entity ? entity.name : message.ieeeAddr;
566
+ this.debug(`Device '${friendlyName}' left the network`);
567
+ this.emit('leave', message.ieeeAddr);
568
+ // Call extensions
569
+ this.callExtensionMethod(
570
+ 'onDeviceLeave',
571
+ [message, entity],
572
+ );
573
+ } catch (error) {
574
+ this.sendError(error);
575
+ this.error(`Failed to handleDeviceLeave ${error.stack}`);
576
+ }
577
+ }
578
+
579
+ async handleDeviceAnnounce(message) {
580
+ this.debug('handleDeviceAnnounce', message);
581
+ const entity = await this.resolveEntity(message.device || message.ieeeAddr);
582
+ const friendlyName = entity.name;
583
+ if (this.warnOnDeviceAnnouncement) {
584
+ this.warn(`Device '${friendlyName}' announced itself`);
585
+ } else {
586
+ this.info(`Device '${friendlyName}' announced itself`);
587
+ }
588
+
589
+ try {
590
+ if (entity && entity.mapped) {
591
+ this.callExtensionMethod(
592
+ 'onZigbeeEvent',
593
+ [{'device': message.device, 'type': 'deviceAnnounce'}, entity ? entity.mapped : null]);
594
+ }
595
+ } catch (error) {
596
+ this.sendError(error);
597
+ this.error(`Failed to handleDeviceLeave ${error.stack}`);
598
+ }
599
+
600
+ this.emit('pairing', `Device '${friendlyName}' announced itself`);
601
+ if (!this.herdsman.getPermitJoin()) {
602
+ this.callExtensionMethod('registerDevicePing', [message.device, entity]);
603
+ }
604
+ // if has modelID so can create device
605
+ if (entity.device && entity.device._modelID) {
606
+ entity.device.modelID = entity.device._modelID;
607
+ this.emit('new', entity);
608
+ }
609
+ }
610
+
611
+ async handleDeviceJoined(message) {
612
+ this.debug('handleDeviceJoined', message);
613
+ //const entity = await this.resolveEntity(message.device || message.ieeeAddr);
614
+ //this.emit('new', entity);
615
+ }
616
+
617
+ async handleDeviceInterview(message) {
618
+ this.debug('handleDeviceInterview', message);
619
+ // safeguard: We do not allow to start an interview if the network is not opened
620
+ if (message.status === 'started' && !this.herdsman.getPermitJoin()) {
621
+ this.warn(`Blocked interview for '${message.ieeeAddr}' because the network is closed`);
622
+ return;
623
+ }
624
+ const entity = await this.resolveEntity(message.device || message.ieeeAddr);
625
+ const friendlyName = entity.name;
626
+ if (message.status === 'successful') {
627
+ this.info(`Successfully interviewed '${friendlyName}', device has successfully been paired`);
628
+
629
+ if (entity.mapped) {
630
+ const {vendor, description, model} = entity.mapped;
631
+ this.info(
632
+ `Device '${friendlyName}' is supported, identified as: ${vendor} ${description} (${model})`
633
+ );
634
+
635
+ const log = {friendly_name: friendlyName, model, vendor, description, supported: true};
636
+ this.emit('pairing', 'Interview successful', JSON.stringify(log));
637
+ entity.device.modelID = entity.device._modelID;
638
+ this.emit('new', entity);
639
+ // send to extensions again (for configure)
640
+ this.callExtensionMethod(
641
+ 'onZigbeeEvent',
642
+ [message, entity ? entity.mapped : null],
643
+ );
644
+ } else {
645
+ this.debug(
646
+ `Device '${friendlyName}' with Zigbee model '${message.device.modelID}' is NOT supported, ` +
647
+ `please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`
648
+ );
649
+ this.emit('pairing', 'Interview successful', {friendly_name: friendlyName, supported: false});
650
+ entity.device.modelID = entity.device._modelID;
651
+ this.emit('new', entity);
652
+ }
653
+ } else if (message.status === 'failed') {
654
+ this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. ${message.error}`);
655
+ this.emit('pairing', 'Interview failed', friendlyName);
656
+ } else {
657
+ if (message.status === 'started') {
658
+ this.info(`Starting interview of '${friendlyName}'`);
659
+ this.emit('pairing', 'Interview started', friendlyName);
660
+ }
661
+ }
662
+ }
663
+
664
+ async handleMessage(data) {
665
+ this.debug(`handleMessage`, data);
666
+ const entity = await this.resolveEntity(data.device || data.ieeeAddr);
667
+ const name = (entity && entity._modelID) ? entity._modelID : data.device.ieeeAddr;
668
+ this.debug(
669
+ `Received Zigbee message from '${name}', type '${data.type}', cluster '${data.cluster}'` +
670
+ `, data '${JSON.stringify(data.data)}' from endpoint ${data.endpoint.ID}` +
671
+ (data.hasOwnProperty('groupID') ? ` with groupID ${data.groupID}` : ``)
672
+ );
673
+ this.event(data.type, entity, data);
674
+
675
+ // Call extensions
676
+ this.callExtensionMethod(
677
+ 'onZigbeeEvent',
678
+ [data, entity ? entity.mapped : null],
679
+ );
680
+ }
681
+
682
+ async getMap(callback) {
683
+ try {
684
+ const devices = this.herdsman.getDevices(true);
685
+ const lqis = [];
686
+ const routing = [];
687
+
688
+ for (const device of devices.filter((d) => d.type !== 'EndDevice')) {
689
+ const resolved = await this.resolveEntity(device);
690
+ let result;
691
+
692
+ try {
693
+ result = await device.lqi();
694
+ } catch (error) {
695
+ this.sendError(error);
696
+ error && this.debug(`Failed to execute LQI for '${resolved.name}'. ${safeJsonStringify(error.stack)}`);
697
+
698
+ lqis.push({
699
+ parent: 'undefined',
700
+ networkAddress: 0,
701
+ ieeeAddr: device.ieeeAddr,
702
+ lqi: 'undefined',
703
+ relationship: 0,
704
+ depth: 0,
705
+ status: 'offline',
706
+ });
707
+ }
708
+
709
+ if (result !== undefined) {
710
+ for (const dev of result.neighbors) {
711
+ if (dev !== undefined && dev.ieeeAddr !== '0xffffffffffffffff') {
712
+ lqis.push({
713
+ parent: resolved.device.ieeeAddr,
714
+ networkAddress: dev.networkAddress,
715
+ ieeeAddr: dev.ieeeAddr,
716
+ lqi: dev.linkquality,
717
+ relationship: dev.relationship,
718
+ depth: dev.depth,
719
+ status: dev.linkquality > 0 ? 'online' : 'offline',
720
+ });
721
+ }
722
+ }
723
+ }
724
+
725
+ this.debug(`LQI succeeded for '${resolved.name}'`);
726
+
727
+ try {
728
+ result = await device.routingTable();
729
+ } catch (error) {
730
+ this.sendError(error);
731
+ if (error) {
732
+ this.debug(`Failed to execute routing table for '${resolved.name}'. ${safeJsonStringify(error.stack)}`);
733
+ }
734
+ }
735
+
736
+ this.debug(`Routing for '${resolved.name}': ${safeJsonStringify(result)}`);
737
+ if (result !== undefined) {
738
+ if (result.table !== undefined) {
739
+ for (const dev of result.table) {
740
+ routing.push({
741
+ source: resolved.device.ieeeAddr,
742
+ destination: dev.destinationAddress,
743
+ nextHop: dev.nextHop,
744
+ status: dev.status,
745
+ });
746
+ }
747
+ }
748
+ }
749
+ this.debug(`Routing table succeeded for '${resolved.name}'`);
750
+ }
751
+ this.debug(`Get map succeeded ${safeJsonStringify(lqis)}`);
752
+
753
+ callback && callback({lqis, routing});
754
+ } catch (error) {
755
+ this.sendError(error);
756
+ this.debug(`Failed to get map: ${safeJsonStringify(error.stack)}`);
757
+ }
758
+ }
759
+
760
+ async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback, zclSeqNum) {
761
+ const entity = await this.resolveEntity(deviceID, ep);
762
+ const device = entity.device;
763
+ const endpoint = entity.endpoint;
764
+ if (!device) {
765
+ this.error(
766
+ `Zigbee cannot publish message to device because '${deviceID}' is not known`
767
+ );
768
+ return;
769
+ }
770
+ if (!endpoint) {
771
+ this.error(
772
+ `Zigbee cannot publish message to endpoint because '${ep}' is not known`
773
+ );
774
+ return;
775
+ }
776
+
777
+ this.debug(`Zigbee publish to '${deviceID}', ${cid} - cmd ${cmd} - payload ${JSON.stringify(zclData)} - cfg ${JSON.stringify(cfg)} - endpoint ${ep}`);
778
+
779
+ if (cfg == null) {
780
+ cfg = {};
781
+ }
782
+
783
+ if (type === 'foundation') {
784
+ cfg.disableDefaultResponse = true;
785
+ if (cmd === 'read' && !Array.isArray(zclData)) {
786
+ // needs to be iterable (string[] | number [])
787
+ zclData[Symbol.iterator] = function* () {
788
+ let k;
789
+ for (k in this) {
790
+ yield k;
791
+ }
792
+ };
793
+ }
794
+ let result;
795
+ if (cmd === 'configReport') {
796
+ result = await endpoint.configureReporting(cid, zclData, cfg);
797
+ } else {
798
+ result = await endpoint[cmd](cid, zclData, cfg);
799
+ }
800
+ callback && callback(undefined, result);
801
+ } else if (type === 'functionalResp') {
802
+ cfg.disableDefaultResponse = false;
803
+ const result = await endpoint.commandResponse(cid, cmd, zclData, cfg, zclSeqNum);
804
+ callback && callback(undefined, result);
805
+ } else {
806
+ cfg.disableDefaultResponse = false;
807
+ const result = await endpoint.command(cid, cmd, zclData, cfg);
808
+ callback && callback(undefined, result);
809
+ }
810
+ }
811
+
812
+ async addDevToGroup(devId, groupId, epid) {
813
+ try {
814
+ this.debug(`called addDevToGroup with ${devId}, ${groupId}, ${epid}`);
815
+ const entity = await this.resolveEntity(devId);
816
+ const group = await this.resolveEntity(groupId);
817
+ this.debug(`addDevFromGroup - entity: ${utils.getEntityInfo(entity)}`);
818
+ // generate group debug info and display it
819
+ const members = await this.getGroupMembersFromController(groupId);
820
+ let memberIDs = [];
821
+ for (const member of members) {
822
+ memberIDs.push(member.ieee);
823
+ }
824
+ this.debug(`addDevToGroup ${groupId} with ${memberIDs.length} members ${safeJsonStringify(memberIDs)}`);
825
+ if (epid != undefined) {
826
+ for (const ep of entity.endpoints) {
827
+ this.debug(`checking ep ${ep.ID} of ${devId} (${epid})`);
828
+ if (ep.ID == epid) {
829
+ if (ep.inputClusters.includes(4) || ep.outputClusters.includes(4)) {
830
+ this.debug(`adding endpoint ${ep.ID} (${epid}) to group ${groupId}`);
831
+ await (ep.addToGroup(group.mapped));
832
+ }
833
+ else this.error(`cluster genGroups not supported for endpoint ${epid} of ${devId}`);
834
+ }
835
+ }
836
+ } else {
837
+ if (entity.endpoint.inputClusters.includes(4)) {
838
+ this.info(`adding endpoint ${entity.endpoint.ID} of ${devId} to group`);
839
+ await entity.endpoint.addToGroup(group.mapped);
840
+ } else {
841
+ let added = false;
842
+ for (const ep of entity.endpoints) {
843
+ if (ep.inputClusters.includes(4)) {
844
+ await ep.addToGroup(group.mapped);
845
+ added = true;
846
+ break;
847
+ }
848
+ }
849
+ if (!added) {
850
+ throw ('cluster genGroups not supported');
851
+ }
852
+ }
853
+ }
854
+ } catch (error) {
855
+ this.sendError(error);
856
+ this.error(`Exception when trying to Add ${devId} to group ${groupId}`, error);
857
+ return {error: `Failed to add ${devId} to group ${groupId}: ${JSON.stringify(error)}`};
858
+ }
859
+ return {};
860
+ }
861
+
862
+ async removeDevFromGroup(devId, groupId, epid) {
863
+ this.debug(`removeDevFromGroup with ${devId}, ${groupId}, ${epid}`);
864
+ let entity;
865
+ try {
866
+ entity = await this.resolveEntity(devId);
867
+ const group = await this.resolveEntity(groupId);
868
+
869
+ const members = await this.getGroupMembersFromController(groupId);
870
+ let memberIDs = [];
871
+ for (const member of members) {
872
+ memberIDs.push(member.ieee);
873
+ }
874
+
875
+ this.debug(`removeDevFromGroup - entity: ${utils.getEntityInfo(entity)}`);
876
+ this.debug(`removeDevFromGroup ${groupId} with ${memberIDs.length} members ${safeJsonStringify(memberIDs)}`);
877
+
878
+ if (epid != undefined) {
879
+ for (const ep of entity.endpoints) {
880
+ this.debug(`checking ep ${ep.ID} of ${devId} (${epid})`);
881
+ if (ep.ID == epid && (ep.inputClusters.includes(4) || ep.outputClusters.includes(4))) {
882
+ await ep.removeFromGroup(group.mapped);
883
+ this.info(`removing endpoint ${ep.ID} of ${devId} from group ${groupId}`);
884
+ }
885
+ }
886
+ } else {
887
+ await entity.endpoint.removeFromGroup(group.mapped);
888
+ this.info(`removing endpoint ${entity.endpoint.ID} of ${devId} from group ${groupId}`);
889
+ }
890
+ } catch (error) {
891
+ this.sendError(error);
892
+ this.error(`Exception when trying remove ${devId} (ep ${epid ? epid : (entity ? entity.endpoint.ID : '')}) from group ${devId}`, error);
893
+ return {error: `Failed to remove dev ${devId} (ep ${epid ? epid : (entity ? entity.endpoint.ID : '')}) from group ${devId}`};
894
+ }
895
+ return {};
896
+ }
897
+
898
+ async removeDevFromAllGroups(devId) {
899
+ try {
900
+ const entity = await this.resolveEntity(devId);
901
+ this.debug(`entity: ${safeJsonStringify(entity)}`);
902
+ for (const ep of entity.endpoints) {
903
+ if (ep.inputClusters.includes(4) || ep.outputClusters.includes(4)) {
904
+ await ep.removefromAllGroups();
905
+ }
906
+ }
907
+ } catch (error) {
908
+ this.sendError(error);
909
+ this.error(`Exception when trying remove ${devId} from all groups`, error);
910
+ return {error: `Failed to remove dev ${devId} from all groups: ${error}`};
911
+ }
912
+ return {};
913
+ }
914
+
915
+ bind(ep, cluster, target, callback) {
916
+ const log = ` ${ep.device.ieeeAddr} - ${cluster}`;
917
+ target = !target ? this.getCoordinator() : target;
918
+
919
+ this.debug(`Binding ${log}`);
920
+ ep.bind(cluster, target, error => {
921
+ if (error) {
922
+ this.sendError(error);
923
+ this.error(`Failed to bind ${log} - (${error})`);
924
+ } else {
925
+ this.debug(`Successfully bound ${log}`);
926
+ }
927
+
928
+ callback(error);
929
+ });
930
+ }
931
+
932
+ unbind(ep, cluster, target, callback) {
933
+ const log = ` ${ep.device.ieeeAddr} - ${cluster}`;
934
+ target = !target ? this.getCoordinator() : target;
935
+
936
+ this.debug(`Unbinding ${log}`);
937
+ ep.unbind(cluster, target, (error) => {
938
+ if (error) {
939
+ this.error(`Failed to unbind ${log} - (${error})`);
940
+ } else {
941
+ this.debug(`Successfully unbound ${log}`);
942
+ }
943
+
944
+ callback(error);
945
+ });
946
+ }
947
+
948
+ reset(mode, callback) {
949
+ try {
950
+ this.herdsman.reset(mode);
951
+ callback && callback();
952
+ } catch (error) {
953
+ this.sendError(error);
954
+ this.error(`Failed to reset ${error.stack}`);
955
+ callback && callback(error);
956
+ }
957
+ }
958
+
959
+ async touchlinkReset(permitTime) {
960
+ try {
961
+ await this.herdsman.touchlinkFactoryResetFirst();
962
+ this.permitJoin(permitTime);
963
+ } catch (error) {
964
+ this.sendError(error);
965
+ this.error(`Failed to touchlinkReset ${error.stack}`);
966
+ }
967
+ }
968
+
969
+ async getChannelsEnergy() {
970
+ const payload = {
971
+ dstaddr: 0x0,
972
+ dstaddrmode: 0x02,
973
+ channelmask: 0x07FFF800,
974
+ scanduration: 0x5,
975
+ scancount: 1,
976
+ nwkmanageraddr: 0x0000
977
+ };
978
+ const energyScan = this.herdsman.adapter.znp.waitFor(
979
+ 2, // unpi_1.Constants.Type.AREQ,
980
+ 5, // Subsystem.ZDO,
981
+ 'mgmtNwkUpdateNotify'
982
+ );
983
+ await this.herdsman.adapter.znp.request(
984
+ 0x5, // Subsystem.ZDO
985
+ 'mgmtNwkUpdateReq',
986
+ payload,
987
+ energyScan.ID
988
+ );
989
+ const result = await energyScan.start().promise;
990
+ return result.payload;
991
+ }
992
+ }
993
+
994
+ module.exports = ZigbeeController;