iobroker.zigbee 1.9.3 → 1.9.5

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