iobroker.zigbee 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -10
- package/admin/admin.js +312 -125
- package/admin/img/PTM 215Z.png +0 -0
- package/admin/img/group_0.png +0 -0
- package/admin/img/group_x.png +0 -0
- package/admin/img/philips_hue_lom001.png +0 -0
- package/admin/index_m.html +95 -45
- package/admin/tab_m.html +116 -48
- package/docs/de/img/Zigbee_config_de.png +0 -0
- package/docs/de/img/Zigbee_tab_de.png +0 -0
- package/docs/de/readme.md +21 -28
- package/docs/en/img/Zigbee_config_en.png +0 -0
- package/docs/en/img/Zigbee_tab_en.png +0 -0
- package/docs/tutorial/groups-1.png +0 -0
- package/docs/tutorial/groups-2.png +0 -0
- package/docs/tutorial/tab-dev-1.png +0 -0
- package/io-package.json +55 -41
- package/lib/binding.js +1 -1
- package/lib/colors.js +7 -0
- package/lib/commands.js +136 -20
- package/lib/developer.js +0 -0
- package/lib/devices.js +88 -74
- package/lib/exclude.js +30 -54
- package/lib/exposes.js +247 -290
- package/lib/groups.js +84 -29
- package/lib/localConfig.js +301 -0
- package/lib/ota.js +5 -4
- package/lib/statescontroller.js +452 -185
- package/lib/utils.js +5 -3
- package/lib/zbDeviceAvailability.js +16 -30
- package/lib/zbDeviceConfigure.js +55 -28
- package/lib/zbDeviceEvent.js +2 -13
- package/lib/zigbeecontroller.js +335 -214
- package/main.js +181 -65
- package/package.json +8 -7
package/lib/zigbeecontroller.js
CHANGED
|
@@ -11,8 +11,12 @@ const DeviceAvailabilityExt = require('./zbDeviceAvailability');
|
|
|
11
11
|
const DeviceConfigureExt = require('./zbDeviceConfigure');
|
|
12
12
|
const DeviceEventExt = require('./zbDeviceEvent');
|
|
13
13
|
const DelayedActionExt = require('./zbDelayedAction');
|
|
14
|
+
const Groups = require('./groups');
|
|
15
|
+
|
|
14
16
|
const utils = require('./utils');
|
|
15
17
|
const { waitForDebugger } = require('inspector');
|
|
18
|
+
const { table } = require('console');
|
|
19
|
+
const { extract } = require('tar');
|
|
16
20
|
const groupConverters = [
|
|
17
21
|
zigbeeHerdsmanConverters.toZigbee.light_onoff_brightness,
|
|
18
22
|
zigbeeHerdsmanConverters.toZigbee.light_color_colortemp,
|
|
@@ -68,6 +72,18 @@ class ZigbeeController extends EventEmitter {
|
|
|
68
72
|
new DeviceEventExt(this, {}),
|
|
69
73
|
new DelayedActionExt(this, {}),
|
|
70
74
|
];
|
|
75
|
+
this.herdsmanTimeoutRegexp = new RegExp(/(\d+)ms/);
|
|
76
|
+
this.herdsmanLogSettings = {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
ByteArrayToString(data) {
|
|
81
|
+
return data.map(function (x) {
|
|
82
|
+
x = x + 0x100 + 1; // twos complement
|
|
83
|
+
x = x.toString(16); // to hex
|
|
84
|
+
x = ('00'+x).substr(-2); // zero-pad to 8-digits
|
|
85
|
+
return x
|
|
86
|
+
}).join('');
|
|
71
87
|
}
|
|
72
88
|
|
|
73
89
|
configure(options) {
|
|
@@ -105,8 +121,9 @@ class ZigbeeController extends EventEmitter {
|
|
|
105
121
|
}
|
|
106
122
|
this.disableLed = options.disableLed;
|
|
107
123
|
this.warnOnDeviceAnnouncement = options.warnOnDeviceAnnouncement;
|
|
108
|
-
|
|
109
|
-
this.
|
|
124
|
+
this.herdsmanLogSettings.panID = herdsmanSettings.network.panID;
|
|
125
|
+
this.herdsmanLogSettings.channel = herdsmanSettings.network.channelList[0];
|
|
126
|
+
this.herdsmanLogSettings.extendedPanID = this.ByteArrayToString(herdsmanSettings.network.extendedPanID);
|
|
110
127
|
this.herdsman = new ZigbeeHerdsman.Controller(herdsmanSettings, this.adapter.log);
|
|
111
128
|
this.callExtensionMethod('setOptions', [{
|
|
112
129
|
disableActivePing: options.disablePing,
|
|
@@ -129,12 +146,12 @@ class ZigbeeController extends EventEmitter {
|
|
|
129
146
|
this.herdsman.on('deviceJoined', this.handleDeviceJoined.bind(this));
|
|
130
147
|
this.herdsman.on('deviceLeave', this.handleDeviceLeave.bind(this));
|
|
131
148
|
this.herdsman.on('message', this.handleMessage.bind(this));
|
|
149
|
+
this.herdsman.on('permitJoinChanged', this.handlePermitJoinChanged.bind(this));
|
|
132
150
|
|
|
151
|
+
this.info('Starting Zigbee-Herdsman');
|
|
133
152
|
await this.herdsman.start();
|
|
134
|
-
|
|
135
|
-
this.debug('zigbee-herdsman started');
|
|
136
153
|
this.herdsmanStarted = true;
|
|
137
|
-
this.info(`Coordinator firmware version: ${JSON.stringify(await this.herdsman.getCoordinatorVersion())}`);
|
|
154
|
+
this.info(`Zigbee-Herdsman started successfully with Coordinator firmware version: ${JSON.stringify(await this.herdsman.getCoordinatorVersion())}`);
|
|
138
155
|
|
|
139
156
|
// debug info from herdsman getNetworkParameters
|
|
140
157
|
const debNetworkParam = JSON.parse(JSON.stringify(await this.herdsman.getNetworkParameters()));
|
|
@@ -146,11 +163,32 @@ class ZigbeeController extends EventEmitter {
|
|
|
146
163
|
extPanIDDebug += extendedPanIDDebug[i];
|
|
147
164
|
i--;
|
|
148
165
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
this.debug(`Network parameters: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${extPanIDDebug}`);
|
|
167
|
+
} catch (e) {
|
|
168
|
+
try {
|
|
169
|
+
const debNetworkParam = JSON.parse(JSON.stringify(await this.herdsman.getNetworkParameters()));
|
|
170
|
+
const extendedPanIDDebug = typeof debNetworkParam.extendedPanID === 'string' ? debNetworkParam.extendedPanID.replace('0x', '') : debNetworkParam.extendedPanID;
|
|
171
|
+
|
|
172
|
+
let extPanIDDebug = '';
|
|
173
|
+
for (let i = extendedPanIDDebug.length - 1; i >= 0; i--) {
|
|
174
|
+
extPanIDDebug += extendedPanIDDebug[i - 1];
|
|
175
|
+
extPanIDDebug += extendedPanIDDebug[i];
|
|
176
|
+
i--;
|
|
177
|
+
}
|
|
178
|
+
this.warn(`Network parameters in Config : panID=${this.herdsmanLogSettings.panID} channel=${this.herdsmanLogSettings.channel} extendedPanID=${this.herdsmanLogSettings.extendedPanID}`)
|
|
179
|
+
this.warn(`Network parameters on Coordinator: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${extPanIDDebug}`);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
this.warn(`Unable to obtain herdsman settings`)
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
await this.herdsman.stop();
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
this.warn('unable to stop zigbee-herdsman after failed startup');
|
|
189
|
+
}
|
|
190
|
+
this.sendError(e);
|
|
191
|
+
this.error(`Starting zigbee-herdsman problem : ${(e && e.message ? e.message : 'no error message')}`);
|
|
154
192
|
throw 'Error herdsman start';
|
|
155
193
|
}
|
|
156
194
|
// Check if we have to turn off the LED
|
|
@@ -195,40 +233,60 @@ class ZigbeeController extends EventEmitter {
|
|
|
195
233
|
}
|
|
196
234
|
|
|
197
235
|
// Call extensions
|
|
198
|
-
this.callExtensionMethod('onZigbeeStarted', []);
|
|
199
236
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
237
|
+
const deviceIterator = this.getClientIterator();
|
|
238
|
+
let deviceCount = 0;
|
|
239
|
+
try {
|
|
240
|
+
for (const device of deviceIterator) {
|
|
241
|
+
deviceCount++;
|
|
242
|
+
// get the model description for the known devices
|
|
243
|
+
const entity = await this.resolveEntity(device);
|
|
244
|
+
if (!entity) {
|
|
245
|
+
this.warn('failed to resolve Entity for ' + device.ieeeAddr);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
//await this.adapter.stController.AddModelFromHerdsman(device, entity.mapped.model);
|
|
207
249
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
250
|
+
this.adapter.getObject(device.ieeeAddr.substr(2), (err, obj) => {
|
|
251
|
+
if (obj && obj.common && obj.common.deactivated) {
|
|
252
|
+
this.callExtensionMethod('deregisterDevicePing', [device, entity]);
|
|
253
|
+
} else {
|
|
254
|
+
this.callExtensionMethod('registerDevicePing', [device, entity]);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
// ensure that objects for all found clients are present
|
|
258
|
+
|
|
259
|
+
if (entity.mapped) {
|
|
260
|
+
this.emit('new', entity);
|
|
215
261
|
}
|
|
216
|
-
|
|
217
|
-
|
|
262
|
+
this.info(
|
|
263
|
+
(entity.device.ieeeAddr) +
|
|
264
|
+
` (addr ${entity.device.networkAddress}): ` +
|
|
265
|
+
(entity.mapped ?
|
|
266
|
+
`${entity.mapped.model} - ${entity.mapped.vendor} ${entity.mapped.description} ` :
|
|
267
|
+
`Unsupported (model ${entity.device.modelID})`) +
|
|
268
|
+
`(${entity.device.type})`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
218
271
|
|
|
219
|
-
|
|
220
|
-
|
|
272
|
+
// Log zigbee clients on startup
|
|
273
|
+
// const devices = await this.getClients();
|
|
274
|
+
if (deviceCount > 0) {
|
|
275
|
+
this.info(`Currently ${deviceCount} devices are joined:`);
|
|
276
|
+
} else {
|
|
277
|
+
this.info(`Currently no devices.`);
|
|
221
278
|
}
|
|
222
|
-
this.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
279
|
+
this.callExtensionMethod('onZigbeeStarted', []);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
this.error('error iterating devices : '+ (error && error.message ? error.message: 'no reason given'));
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
this.getGroups();
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
this.error('error iterating groups : '+ (error && error.message ? error.message: 'no reason given'));
|
|
230
289
|
}
|
|
231
|
-
|
|
232
290
|
this.emit('ready');
|
|
233
291
|
}
|
|
234
292
|
|
|
@@ -256,6 +314,12 @@ class ZigbeeController extends EventEmitter {
|
|
|
256
314
|
this.adapter.sendError(error, message);
|
|
257
315
|
}
|
|
258
316
|
|
|
317
|
+
filterHerdsmanError(message) {
|
|
318
|
+
if (typeof message != 'string' || message == '') return 'no error message';
|
|
319
|
+
if (message.match(this.herdsmanTimeoutRegexp)) return 'Timeout';
|
|
320
|
+
return message;
|
|
321
|
+
}
|
|
322
|
+
|
|
259
323
|
callExtensionMethod(method, parameters) {
|
|
260
324
|
const result = [];
|
|
261
325
|
for (const extension of this.extensions) {
|
|
@@ -288,16 +352,30 @@ class ZigbeeController extends EventEmitter {
|
|
|
288
352
|
}
|
|
289
353
|
}
|
|
290
354
|
|
|
355
|
+
getClientIterator(all) {
|
|
356
|
+
if (this.herdsman.database) {
|
|
357
|
+
return this.herdsman.getDevicesIterator( function(device) { return (all? true: device.type !== 'Coordinator')});
|
|
358
|
+
} else {
|
|
359
|
+
return [].values();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
291
363
|
async getGroups() {
|
|
292
364
|
try {
|
|
293
365
|
if (this.herdsman) {
|
|
294
|
-
|
|
366
|
+
const rv = [];
|
|
367
|
+
const groupIterator = this.herdsman.getGroupsIterator();
|
|
368
|
+
for (const g of groupIterator) {
|
|
369
|
+
const members = await this.getGroupMembersFromController(g.groupID);
|
|
370
|
+
rv.push({ id: g.groupID, size: (members ? members.length : 0), stateName: 'group_'+g.groupID});
|
|
371
|
+
}
|
|
372
|
+
return rv;
|
|
295
373
|
} else {
|
|
296
374
|
return null;
|
|
297
375
|
}
|
|
298
376
|
} catch (error) {
|
|
299
377
|
this.sendError(error);
|
|
300
|
-
this.error(
|
|
378
|
+
this.error(JSON.stringify(error));
|
|
301
379
|
return undefined;
|
|
302
380
|
}
|
|
303
381
|
}
|
|
@@ -308,15 +386,17 @@ class ZigbeeController extends EventEmitter {
|
|
|
308
386
|
group && group.removeFromNetwork();
|
|
309
387
|
} catch (error) {
|
|
310
388
|
this.sendError(error);
|
|
311
|
-
this.error(`error in removeGroupById: ${
|
|
389
|
+
this.error(`error in removeGroupById: ${error}`);
|
|
312
390
|
}
|
|
313
391
|
}
|
|
314
392
|
|
|
315
393
|
async getGroupByID(id) {
|
|
316
394
|
try {
|
|
317
|
-
|
|
395
|
+
const rv = this.herdsman.getGroupByID(id);
|
|
396
|
+
return rv;
|
|
318
397
|
} catch (error) {
|
|
319
398
|
this.sendError(error);
|
|
399
|
+
// this.error('error getting group for ' + id + ' ' + (error && error.message ? error.message : 'without message'));
|
|
320
400
|
return undefined;
|
|
321
401
|
}
|
|
322
402
|
}
|
|
@@ -333,12 +413,23 @@ class ZigbeeController extends EventEmitter {
|
|
|
333
413
|
} else {
|
|
334
414
|
this.debug(`verifyGroupExists: group ${nid} exists`);
|
|
335
415
|
}
|
|
416
|
+
return group
|
|
336
417
|
}
|
|
337
418
|
catch (error) {
|
|
338
419
|
this.error(`verifyGroupExists: ${error && error.message ? error.message : 'unspecified error'}`);
|
|
339
420
|
}
|
|
340
421
|
}
|
|
341
422
|
|
|
423
|
+
async rebuildGroupIcon(grp) {
|
|
424
|
+
const g = (typeof grp === 'number') ? this.herdsman.getGroupByID(grp) : grp;
|
|
425
|
+
if (typeof g === 'object' && g.groupID)
|
|
426
|
+
{
|
|
427
|
+
const members = await this.getGroupMembersFromController(g.groupID);
|
|
428
|
+
return `img/group_${members.length}.png`
|
|
429
|
+
}
|
|
430
|
+
return 'img/group_x.png';
|
|
431
|
+
}
|
|
432
|
+
|
|
342
433
|
async addPairingCode(code) {
|
|
343
434
|
this.debug(`calling addPairingCode with ${code}`);
|
|
344
435
|
if (code) {
|
|
@@ -357,7 +448,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
357
448
|
async getGroupMembersFromController(id) {
|
|
358
449
|
const members = [];
|
|
359
450
|
try {
|
|
360
|
-
const group = await this.getGroupByID(id);
|
|
451
|
+
const group = await this.getGroupByID(Number(id));
|
|
361
452
|
if (group) {
|
|
362
453
|
const groupMembers = group.members;
|
|
363
454
|
for (const member of groupMembers) {
|
|
@@ -391,7 +482,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
391
482
|
try {
|
|
392
483
|
return this.herdsman.getDeviceByIeeeAddr(key);
|
|
393
484
|
}
|
|
394
|
-
catch {
|
|
485
|
+
catch (error) {
|
|
486
|
+
this.error(`getDeviceByIeeeAddr: ${(error && error.message ? error.message : 'no error message')}`);
|
|
395
487
|
return undefined;
|
|
396
488
|
}
|
|
397
489
|
}
|
|
@@ -400,7 +492,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
400
492
|
try {
|
|
401
493
|
return this.herdsman.getDevicesByType(type);
|
|
402
494
|
}
|
|
403
|
-
catch {
|
|
495
|
+
catch (error) {
|
|
496
|
+
this.error(`getDevicesByType: ${(error && error.message ? error.message : 'no error message')}`);
|
|
404
497
|
return undefined;
|
|
405
498
|
}
|
|
406
499
|
}
|
|
@@ -409,82 +502,109 @@ class ZigbeeController extends EventEmitter {
|
|
|
409
502
|
try {
|
|
410
503
|
return this.herdsman.getDeviceByNetworkAddress(networkAddress);
|
|
411
504
|
}
|
|
412
|
-
catch {
|
|
505
|
+
catch (error) {
|
|
506
|
+
this.error(`getDeviceByNetworkAddress: ${(error && error.message ? error.message : 'no error message')}`);
|
|
413
507
|
return undefined;
|
|
414
508
|
}
|
|
415
509
|
}
|
|
416
510
|
|
|
511
|
+
async analyzeKey(key) {
|
|
512
|
+
const rv = {
|
|
513
|
+
kind: 'device',
|
|
514
|
+
key: key,
|
|
515
|
+
message: 'success'
|
|
516
|
+
}
|
|
517
|
+
if (typeof key === 'object') return rv;
|
|
518
|
+
if (typeof key === 'number') {
|
|
519
|
+
rv.kind = 'group';
|
|
520
|
+
return rv;
|
|
521
|
+
}
|
|
522
|
+
if (typeof key === 'string') {
|
|
523
|
+
if (key === 'coordinator') {
|
|
524
|
+
rv.kind = 'coordinator'
|
|
525
|
+
return rv;
|
|
526
|
+
};
|
|
527
|
+
const kp = key.split('_');
|
|
528
|
+
if (kp[0] === 'group' && kp.length > 1) {
|
|
529
|
+
rv.kind = 'group';
|
|
530
|
+
rv.key = Number(kp[1]);
|
|
531
|
+
return rv;
|
|
532
|
+
}
|
|
533
|
+
if (key.startsWith('0x')) {
|
|
534
|
+
rv.kind = 'ieee'
|
|
535
|
+
return rv;
|
|
536
|
+
};
|
|
537
|
+
if (key.trim().length === 16) {
|
|
538
|
+
rv.key = `0x${key.trim()}`
|
|
539
|
+
rv.kind = 'ieee';
|
|
540
|
+
return rv;
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
rv.message = 'failed';
|
|
544
|
+
return rv;
|
|
545
|
+
}
|
|
546
|
+
|
|
417
547
|
async resolveEntity(key, ep) {
|
|
418
|
-
//
|
|
548
|
+
// this.warn('resolve entity with key of tyoe ' + typeof (key));
|
|
419
549
|
try {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (endpoints && ep != undefined && endpoints[ep]) {
|
|
436
|
-
endpoint = device.getEndpoint(endpoints[ep]);
|
|
437
|
-
} else if (endpoints && endpoints['default']) {
|
|
438
|
-
endpoint = device.getEndpoint(endpoints['default']);
|
|
439
|
-
} else {
|
|
440
|
-
const epNum = parseInt(ep);
|
|
441
|
-
if (!isNaN(epNum)) {
|
|
442
|
-
endpoint = device.getEndpoint(epNum);
|
|
443
|
-
} else {
|
|
444
|
-
endpoint = device.endpoints[0];
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return {
|
|
448
|
-
type: 'device',
|
|
449
|
-
device,
|
|
450
|
-
mapped,
|
|
451
|
-
endpoint,
|
|
452
|
-
endpoints: device.endpoints,
|
|
453
|
-
name: key,
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
} else if (typeof key === 'number') {
|
|
458
|
-
let group = await this.herdsman.getGroupByID(key);
|
|
459
|
-
if (!group) group = await this.herdsman.createGroup(key);
|
|
550
|
+
const _key = await this.analyzeKey(key);
|
|
551
|
+
if (_key.message !== 'success') return undefined;
|
|
552
|
+
|
|
553
|
+
if (_key.kind == 'coordinator') {
|
|
554
|
+
const coordinator = this.herdsman.getDevicesByType('Coordinator')[0];
|
|
555
|
+
return {
|
|
556
|
+
type: 'device',
|
|
557
|
+
device: coordinator,
|
|
558
|
+
endpoint: coordinator.getEndpoint(1),
|
|
559
|
+
name: 'Coordinator',
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
if (_key.kind === 'group') {
|
|
563
|
+
let group = await this.herdsman.getGroupByID(_key.key);
|
|
564
|
+
if (!group) group = await this.herdsman.createGroup(_key.key);
|
|
460
565
|
group.toZigbee = groupConverters;
|
|
461
566
|
group.model = 'group';
|
|
462
567
|
return {
|
|
463
568
|
type: 'group',
|
|
464
569
|
mapped: group,
|
|
465
570
|
group,
|
|
466
|
-
name: `Group ${key}`,
|
|
571
|
+
name: `Group ${_key.key}`,
|
|
467
572
|
};
|
|
468
|
-
} else {
|
|
469
|
-
let mapped;
|
|
470
|
-
try {
|
|
471
|
-
mapped = await zigbeeHerdsmanConverters.findByDevice(key);
|
|
472
|
-
} catch (err) {
|
|
473
|
-
this.error(`zigbeeHerdsmanConverters findByDevice ${key.ieeeAddr}`);
|
|
474
|
-
}
|
|
475
573
|
|
|
574
|
+
}
|
|
575
|
+
if (_key.kind === 'ieee') _key.key = await this.herdsman.getDeviceByIeeeAddr(_key.key);
|
|
576
|
+
const device = _key.key;
|
|
577
|
+
if (device) {
|
|
578
|
+
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
|
579
|
+
const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
|
|
580
|
+
let endpoint;
|
|
581
|
+
if (endpoints && ep != undefined && endpoints[ep]) {
|
|
582
|
+
endpoint = device.getEndpoint(endpoints[ep]);
|
|
583
|
+
} else if (endpoints && endpoints['default']) {
|
|
584
|
+
endpoint = device.getEndpoint(endpoints['default']);
|
|
585
|
+
} else {
|
|
586
|
+
const epNum = parseInt(ep);
|
|
587
|
+
if (!isNaN(epNum)) {
|
|
588
|
+
endpoint = device.getEndpoint(epNum);
|
|
589
|
+
} else {
|
|
590
|
+
endpoint = device.endpoints[0];
|
|
591
|
+
}
|
|
592
|
+
}
|
|
476
593
|
return {
|
|
477
594
|
type: 'device',
|
|
478
|
-
device
|
|
479
|
-
mapped
|
|
480
|
-
|
|
595
|
+
device,
|
|
596
|
+
mapped,
|
|
597
|
+
endpoint,
|
|
598
|
+
endpoints: device.endpoints,
|
|
599
|
+
name: device._ieeeAddr,
|
|
481
600
|
};
|
|
482
601
|
}
|
|
483
602
|
}
|
|
484
|
-
catch
|
|
485
|
-
|
|
603
|
+
catch (error)
|
|
604
|
+
{
|
|
605
|
+
this.error('Resolve entity error: ' + (error && error.message ? error.message : 'no reason given'))
|
|
486
606
|
}
|
|
487
|
-
|
|
607
|
+
return undefined;
|
|
488
608
|
}
|
|
489
609
|
|
|
490
610
|
async incMsgHandler(message) {
|
|
@@ -504,15 +624,20 @@ class ZigbeeController extends EventEmitter {
|
|
|
504
624
|
modelId: device.modelId
|
|
505
625
|
});
|
|
506
626
|
}
|
|
507
|
-
catch {
|
|
508
|
-
|
|
627
|
+
catch (error) {
|
|
628
|
+
this.error('incMsgHandler: ' + (error && error.message ? error.message : 'no error message'));
|
|
509
629
|
}
|
|
510
630
|
}
|
|
511
631
|
|
|
512
632
|
// Stop controller
|
|
513
633
|
async stop() {
|
|
514
634
|
// Call extensions
|
|
515
|
-
|
|
635
|
+
try {
|
|
636
|
+
await this.callExtensionMethod('stop', []);
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
this.error('unable to call extension Method stop: ' + (error && error.message ? error.message : 'no error message'));
|
|
640
|
+
}
|
|
516
641
|
|
|
517
642
|
try {
|
|
518
643
|
await this.permitJoin(0);
|
|
@@ -538,55 +663,46 @@ class ZigbeeController extends EventEmitter {
|
|
|
538
663
|
|
|
539
664
|
// Permit join
|
|
540
665
|
async permitJoin(permitTime, devid, failure) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
} else {
|
|
548
|
-
permitDev = '';
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (permitTime) {
|
|
553
|
-
this.info('Zigbee: allowing new devices to join.');
|
|
554
|
-
} else {
|
|
555
|
-
this.info('Zigbee: disabling joining new devices.');
|
|
666
|
+
try {
|
|
667
|
+
this._permitJoinTime = permitTime;
|
|
668
|
+
await this.herdsman.permitJoin(permitTime);
|
|
669
|
+
} catch (e) {
|
|
670
|
+
this.sendError(e);
|
|
671
|
+
this.error(`Failed to open the network: ${e.stack}`);
|
|
556
672
|
}
|
|
673
|
+
}
|
|
557
674
|
|
|
675
|
+
async handlePermitJoinChanged(data)
|
|
676
|
+
{
|
|
558
677
|
try {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
this.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
this.
|
|
565
|
-
|
|
566
|
-
this.
|
|
567
|
-
|
|
568
|
-
await this.herdsman.permitJoin(false);
|
|
569
|
-
}
|
|
570
|
-
this._permitJoinTime -= 1;
|
|
571
|
-
}, 1000);
|
|
572
|
-
} else if (this.herdsman.getPermitJoin()) {
|
|
573
|
-
if (permitTime) {
|
|
574
|
-
this.info('Joining already permitted');
|
|
575
|
-
} else {
|
|
576
|
-
clearInterval(this._permitJoinInterval);
|
|
577
|
-
await this.herdsman.permitJoin(false, permitDev);
|
|
678
|
+
this.debug(`Event handlePermitJoinChanged received with ${JSON.stringify(data)}`);
|
|
679
|
+
if (data.permitted) {
|
|
680
|
+
if (!this._permitJoinInterval) {
|
|
681
|
+
this.emit('pairing',`Pairing possible for ${this._permitJoinTime} seconds`)
|
|
682
|
+
this.info(`Opening zigbee Network for ${this._permitJoinTime} seconds`)
|
|
683
|
+
this._permitJoinInterval = setInterval(async () => {
|
|
684
|
+
this.emit('pairing', 'Pairing time left', this._permitJoinTime);
|
|
685
|
+
this._permitJoinTime -= 1;
|
|
686
|
+
}, 1000);
|
|
578
687
|
}
|
|
579
688
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
689
|
+
else {
|
|
690
|
+
this.info(`Closing Zigbee network, ${this._permitJoinTime} seconds remaining`)
|
|
691
|
+
clearInterval(this._permitJoinInterval);
|
|
692
|
+
this._permitJoinInterval = null;
|
|
693
|
+
// this.emit('pairing', 'Pairing time left', 0);
|
|
694
|
+
this.emit('pairing', 'Closing network.',0);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
this.error(`Error in handlePermitJoinChanged: ${error.message}`);
|
|
583
699
|
}
|
|
584
700
|
}
|
|
585
701
|
|
|
586
702
|
// Remove device
|
|
587
703
|
async remove(deviceID, force, callback) {
|
|
588
704
|
try {
|
|
589
|
-
const device =
|
|
705
|
+
const device = this.herdsman.getDeviceByIeeeAddr(deviceID);
|
|
590
706
|
if (device) {
|
|
591
707
|
try {
|
|
592
708
|
await device.removeFromNetwork();
|
|
@@ -604,7 +720,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
604
720
|
}
|
|
605
721
|
|
|
606
722
|
try {
|
|
607
|
-
|
|
723
|
+
device.removeFromDatabase();
|
|
608
724
|
} catch (error) {
|
|
609
725
|
this.sendError(error);
|
|
610
726
|
// skip error
|
|
@@ -640,7 +756,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
640
756
|
);
|
|
641
757
|
} catch (error) {
|
|
642
758
|
this.sendError(error);
|
|
643
|
-
this.error(`Failed to handleDeviceLeave ${error.
|
|
759
|
+
this.error(`Failed to handleDeviceLeave ${error && error.message ? error.message : 'no error message given'}`);
|
|
644
760
|
}
|
|
645
761
|
}
|
|
646
762
|
|
|
@@ -689,46 +805,51 @@ class ZigbeeController extends EventEmitter {
|
|
|
689
805
|
this.warn(`Blocked interview for '${message.ieeeAddr}' because the network is closed`);
|
|
690
806
|
return;
|
|
691
807
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
808
|
+
try {
|
|
809
|
+
const entity = await this.resolveEntity(message.device || message.ieeeAddr);
|
|
810
|
+
const friendlyName = entity.name;
|
|
811
|
+
if (message.status === 'successful') {
|
|
812
|
+
this.info(`Successfully interviewed '${friendlyName}', device has successfully been paired`);
|
|
813
|
+
|
|
814
|
+
if (entity.mapped) {
|
|
815
|
+
const {vendor, description, model} = entity.mapped;
|
|
816
|
+
this.info(
|
|
817
|
+
`Device '${friendlyName}' is supported, identified as: ${vendor} ${description} (${model})`
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
const log = {friendly_name: friendlyName, model, vendor, description, supported: true};
|
|
821
|
+
this.emit('pairing', 'Interview successful', JSON.stringify(log));
|
|
822
|
+
entity.device.modelID = entity.device._modelID;
|
|
823
|
+
this.emit('new', entity);
|
|
824
|
+
// send to extensions again (for configure)
|
|
825
|
+
this.callExtensionMethod(
|
|
826
|
+
'onZigbeeEvent',
|
|
827
|
+
[message, entity ? entity.mapped : null],
|
|
828
|
+
);
|
|
829
|
+
} else {
|
|
830
|
+
this.debug(
|
|
831
|
+
`Device '${friendlyName}' with Zigbee model '${message.device.modelID}' is NOT supported, ` +
|
|
832
|
+
`please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`
|
|
833
|
+
);
|
|
834
|
+
const frName = {friendly_name: friendlyName, supported: false};
|
|
835
|
+
this.emit('pairing', 'Interview successful', JSON.stringify(frName));
|
|
836
|
+
entity.device.modelID = entity.device._modelID;
|
|
837
|
+
this.emit('new', entity);
|
|
838
|
+
}
|
|
839
|
+
} else if (message.status === 'failed') {
|
|
840
|
+
this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! `);
|
|
841
|
+
//this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! ${message.error}`);
|
|
842
|
+
this.emit('pairing', 'Interview failed', friendlyName);
|
|
712
843
|
} else {
|
|
713
|
-
|
|
714
|
-
`
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
const frName = {friendly_name: friendlyName, supported: false};
|
|
718
|
-
this.emit('pairing', 'Interview successful', JSON.stringify(frName));
|
|
719
|
-
entity.device.modelID = entity.device._modelID;
|
|
720
|
-
this.emit('new', entity);
|
|
721
|
-
}
|
|
722
|
-
} else if (message.status === 'failed') {
|
|
723
|
-
this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! `);
|
|
724
|
-
//this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! ${message.error}`);
|
|
725
|
-
this.emit('pairing', 'Interview failed', friendlyName);
|
|
726
|
-
} else {
|
|
727
|
-
if (message.status === 'started') {
|
|
728
|
-
this.info(`Starting interview of '${friendlyName}'`);
|
|
729
|
-
this.emit('pairing', 'Interview started', friendlyName);
|
|
844
|
+
if (message.status === 'started') {
|
|
845
|
+
this.info(`Starting interview of '${friendlyName}'`);
|
|
846
|
+
this.emit('pairing', 'Interview started', friendlyName);
|
|
847
|
+
}
|
|
730
848
|
}
|
|
731
849
|
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
this.error('handleDeviceInterview: ' + (error && error.message ? error.message : 'no error message'));
|
|
852
|
+
}
|
|
732
853
|
}
|
|
733
854
|
|
|
734
855
|
async handleMessage(data) {
|
|
@@ -751,20 +872,25 @@ class ZigbeeController extends EventEmitter {
|
|
|
751
872
|
|
|
752
873
|
async getMap(callback) {
|
|
753
874
|
try {
|
|
875
|
+
this.info('Collecting Map Data');
|
|
754
876
|
const devices = this.herdsman.getDevices(true);
|
|
755
877
|
const lqis = [];
|
|
756
878
|
const routing = [];
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
879
|
+
const errors = [];
|
|
880
|
+
|
|
881
|
+
await Promise.all(devices.filter((d) => d.type !== 'EndDevice').map(async device =>
|
|
882
|
+
{
|
|
883
|
+
let resolved = await this.resolveEntity(device, 0);
|
|
884
|
+
if (!resolved) {
|
|
885
|
+
resolved = { name:'unresolved device', device:device }
|
|
886
|
+
this.warn('resolve Entity failed for ' + device.ieeeAddr)
|
|
887
|
+
}
|
|
760
888
|
let result;
|
|
761
889
|
|
|
762
890
|
try {
|
|
763
891
|
result = await device.lqi();
|
|
764
892
|
} catch (error) {
|
|
765
|
-
this.
|
|
766
|
-
error && this.debug(`Failed to execute LQI for '${resolved.name}'. ${safeJsonStringify(error.stack)}`);
|
|
767
|
-
|
|
893
|
+
errors.push(`Failed to execute LQI for '${resolved ? resolved.name : 'unresolved device'} (${resolved ? resolved.device.modelID : 'unknown'}') : ${this.filterHerdsmanError(error.message)}.`);
|
|
768
894
|
lqis.push({
|
|
769
895
|
parent: 'undefined',
|
|
770
896
|
networkAddress: 0,
|
|
@@ -780,7 +906,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
780
906
|
for (const dev of result.neighbors) {
|
|
781
907
|
if (dev !== undefined && dev.ieeeAddr !== '0xffffffffffffffff') {
|
|
782
908
|
lqis.push({
|
|
783
|
-
parent: resolved.device.ieeeAddr,
|
|
909
|
+
parent: (resolved ? resolved.device.ieeeAddr : undefined),
|
|
784
910
|
networkAddress: dev.networkAddress,
|
|
785
911
|
ieeeAddr: dev.ieeeAddr,
|
|
786
912
|
lqi: dev.linkquality,
|
|
@@ -792,18 +918,14 @@ class ZigbeeController extends EventEmitter {
|
|
|
792
918
|
}
|
|
793
919
|
}
|
|
794
920
|
|
|
795
|
-
this.debug(`LQI succeeded for '${resolved.name}'`);
|
|
796
|
-
|
|
797
921
|
try {
|
|
798
922
|
result = await device.routingTable();
|
|
799
923
|
} catch (error) {
|
|
800
|
-
this.sendError(error);
|
|
801
924
|
if (error) {
|
|
802
|
-
|
|
925
|
+
errors.push(`Failed to collect routing table for '${resolved ? resolved.name : 'unresolved device'} (${resolved ? resolved.device.modelID : 'unknown'}') : ${this.filterHerdsmanError(error.message)}`);
|
|
803
926
|
}
|
|
804
927
|
}
|
|
805
928
|
|
|
806
|
-
this.debug(`Routing for '${resolved.name}': ${safeJsonStringify(result)}`);
|
|
807
929
|
if (result !== undefined) {
|
|
808
930
|
if (result.table !== undefined) {
|
|
809
931
|
for (const dev of result.table) {
|
|
@@ -816,17 +938,24 @@ class ZigbeeController extends EventEmitter {
|
|
|
816
938
|
}
|
|
817
939
|
}
|
|
818
940
|
}
|
|
819
|
-
this.debug(`Routing table succeeded for '${resolved.name}'`);
|
|
820
941
|
}
|
|
821
|
-
|
|
942
|
+
));
|
|
822
943
|
|
|
823
|
-
callback && callback({lqis, routing});
|
|
944
|
+
callback && callback({lqis, routing, errors});
|
|
945
|
+
if (errors.length) {
|
|
946
|
+
this.warn(`Map Data collection complete with ${errors.length} issues:`);
|
|
947
|
+
for (const msg of errors)
|
|
948
|
+
this.warn(msg);
|
|
949
|
+
}
|
|
950
|
+
else
|
|
951
|
+
this.info('Map data collection complete');
|
|
824
952
|
} catch (error) {
|
|
825
953
|
this.sendError(error);
|
|
826
|
-
this.
|
|
954
|
+
this.error(`Failed to get map: ${safeJsonStringify(error.stack)}`);
|
|
827
955
|
}
|
|
828
956
|
}
|
|
829
957
|
|
|
958
|
+
|
|
830
959
|
async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback, zclSeqNum) {
|
|
831
960
|
const entity = await this.resolveEntity(deviceID, ep);
|
|
832
961
|
const device = entity.device;
|
|
@@ -849,20 +978,11 @@ class ZigbeeController extends EventEmitter {
|
|
|
849
978
|
if (cfg == null) {
|
|
850
979
|
cfg = {};
|
|
851
980
|
}
|
|
981
|
+
|
|
852
982
|
try {
|
|
983
|
+
|
|
853
984
|
if (type === 'foundation') {
|
|
854
985
|
cfg.disableDefaultResponse = true;
|
|
855
|
-
/*
|
|
856
|
-
if (cmd === 'read' && !Array.isArray(zclData)) {
|
|
857
|
-
// needs to be iterable (string[] | number [])
|
|
858
|
-
zclData[Symbol.iterator] = function* () {
|
|
859
|
-
let k;
|
|
860
|
-
for (k in this) {
|
|
861
|
-
yield k;
|
|
862
|
-
}
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
*/
|
|
866
986
|
let result;
|
|
867
987
|
if (cmd === 'configReport') {
|
|
868
988
|
result = await endpoint.configureReporting(cid, zclData, cfg);
|
|
@@ -935,7 +1055,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
935
1055
|
} catch (error) {
|
|
936
1056
|
this.sendError(error);
|
|
937
1057
|
this.error(`Exception when trying to Add ${devId} to group ${groupId}`, error);
|
|
938
|
-
return {error: `Failed to add ${devId} to group ${groupId}: ${
|
|
1058
|
+
return {error: `Failed to add ${devId} to group ${groupId}: ${JSON.stringify(error)}`};
|
|
939
1059
|
}
|
|
940
1060
|
return {};
|
|
941
1061
|
}
|
|
@@ -947,6 +1067,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
947
1067
|
entity = await this.resolveEntity(devId);
|
|
948
1068
|
const group = await this.resolveEntity(groupId);
|
|
949
1069
|
|
|
1070
|
+
/*
|
|
950
1071
|
const members = await this.getGroupMembersFromController(groupId);
|
|
951
1072
|
const memberIDs = [];
|
|
952
1073
|
for (const member of members) {
|
|
@@ -955,7 +1076,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
955
1076
|
|
|
956
1077
|
this.debug(`removeDevFromGroup - entity: ${utils.getEntityInfo(entity)}`);
|
|
957
1078
|
this.debug(`removeDevFromGroup ${groupId} with ${memberIDs.length} members ${safeJsonStringify(memberIDs)}`);
|
|
958
|
-
|
|
1079
|
+
*/
|
|
959
1080
|
if (epid != undefined) {
|
|
960
1081
|
for (const ep of entity.endpoints) {
|
|
961
1082
|
this.debug(`checking ep ${ep.ID} of ${devId} (${epid})`);
|
|
@@ -968,6 +1089,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
968
1089
|
await entity.endpoint.removeFromGroup(group.mapped);
|
|
969
1090
|
this.info(`removing endpoint ${entity.endpoint.ID} of ${devId} from group ${groupId}`);
|
|
970
1091
|
}
|
|
1092
|
+
|
|
971
1093
|
} catch (error) {
|
|
972
1094
|
this.sendError(error);
|
|
973
1095
|
this.error(`Exception when trying remove ${devId} (ep ${epid ? epid : (entity ? entity.endpoint.ID : '')}) from group ${devId}`, error);
|
|
@@ -1011,7 +1133,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
1011
1133
|
});
|
|
1012
1134
|
}
|
|
1013
1135
|
catch (error) {
|
|
1014
|
-
callback(error)
|
|
1136
|
+
callback(error)
|
|
1015
1137
|
}
|
|
1016
1138
|
}
|
|
1017
1139
|
|
|
@@ -1031,9 +1153,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
1031
1153
|
callback(error);
|
|
1032
1154
|
});
|
|
1033
1155
|
}
|
|
1034
|
-
catch (error)
|
|
1035
|
-
|
|
1036
|
-
callback(error);
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
callback (error)
|
|
1037
1158
|
}
|
|
1038
1159
|
}
|
|
1039
1160
|
|