iobroker.zigbee 3.0.5 → 3.1.4

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/lib/commands.js CHANGED
@@ -7,10 +7,12 @@ const fs = require('fs');
7
7
  const statesMapping = require('./devices');
8
8
  const utils = require('@iobroker/adapter-core'); // Get common adapter utils
9
9
  const colors = require('./colors.js');
10
+ /* currently not needed, kept for referencce
10
11
  const dns = require('dns');
11
12
  const net = require('net');
12
- const { access, constants } =require('fs');
13
-
13
+ const access = fs.access;
14
+ const constants = fs.constants;
15
+ */
14
16
  const disallowedDashStates = [
15
17
  'link_quality', 'available', 'battery', 'groups', 'device_query',
16
18
  'hue_move', 'color_temp_move', 'satuation_move', 'brightness_move', 'brightness_step', 'hue_calibration',
@@ -82,9 +84,9 @@ class Commands {
82
84
  this.renameDevice(obj.from, obj.command, obj.message, obj.callback);
83
85
  }
84
86
  break;
85
- case 'deleteDevice':
87
+ case 'deleteZigbeeDevice':
86
88
  if (obj.message && typeof obj.message === 'object') {
87
- this.deleteDevice(obj.from, obj.command, obj.message, obj.callback);
89
+ this.deleteZigbeeDevice(obj.from, obj.command, obj.message, obj.callback);
88
90
  }
89
91
  break;
90
92
  case 'getChannels':
@@ -163,15 +165,19 @@ class Commands {
163
165
  if (this.stController) this.adapter.sendTo(obj.from, obj.command, {clean:this.stController.CleanupRequired(), errors:this.stController.getStashedErrors()}, obj.callback);
164
166
  // NO Break - returning the debug-data as well is intentional
165
167
  case 'getDebugMessages':
166
- this.adapter.sendTo(obj.from, obj.command, {byId:this.adapter.deviceDebug.collectDebugData( obj.message.inlog)},obj.callback);
168
+ this.adapter.sendTo(obj.from, obj.command, {byId:this.adapter.deviceDebug.collectDebugData( obj.message.inlog, obj.message.del )},obj.callback);
167
169
  break;
168
170
  case 'testConnection':
169
171
  this.testConnection(obj.from, obj.command, obj.message, obj.callback);
170
172
  break;
171
173
  case 'readNVRam':
172
174
  this.readNvBackup(obj.from, obj.command, obj.message, obj.callback);
175
+ break;
176
+ case 'downloadIcons':
177
+ this.triggerIconDownload(obj);
178
+ break;
173
179
  default:
174
- //this.warn(`Commands: Command ${obj.command} is unknown`);
180
+ this.debug(`Commands: Command ${obj.command} is unknown`);
175
181
  //this.adapter.sendTo(obj.from, obj.command, obj.message, obj.callback);
176
182
  break;
177
183
  }
@@ -240,12 +246,20 @@ class Commands {
240
246
  }
241
247
 
242
248
  async letsPairing(from, command, message, callback) {
243
- if (this.zbController) {
249
+ if (this.zbController && this.zbController.herdsmanStarted) {
244
250
  let devId = '';
245
251
  if (message) {
246
252
  if (message.id && message.id != undefined) {
247
253
  devId = getZbId(message.id);
248
254
  }
255
+ if (typeof devId == 'number') {
256
+ this.adapter.sendTo(
257
+ from, command,
258
+ {error: 'Pairing on a group is not supported'},
259
+ callback
260
+ );
261
+ return;
262
+ }
249
263
  if (message.code && message.code != undefined) {
250
264
  try {
251
265
  this.debug(`letsPairing called with code ${message.code}`);
@@ -273,23 +287,29 @@ class Commands {
273
287
  }
274
288
  // allow devices to join the network within 60 secs
275
289
  // this.adapter.logToPairing('Pairing started ' + devId, true);
276
-
277
290
  let cTimer = Number(this.adapter.config.countDown);
278
291
  if (!this.adapter.config.countDown || !cTimer) {
279
292
  cTimer = 60;
280
293
  }
294
+ if (message.stop) cTimer = 0;
281
295
 
282
- this.zbController.permitJoin(cTimer, devId, err => {
283
- if (!err) {
284
- // set pairing mode on
285
- this.adapter.setState('info.pairingMode', true);
286
- }
287
- });
288
- this.adapter.sendTo(from, command, 'Start pairing!', callback);
289
- } else {
296
+ if (await this.zbController.permitJoin(cTimer, devId)) {
297
+ this.adapter.setState('info.pairingMode', cTimer > 0, true);
298
+ if (devId) this.zbController.emit(`Pairing started for ${devId}`);
299
+ this.adapter.sendTo(from, command, cTimer ? 'Start pairing!':'Stop pairing!', callback);
300
+ }
301
+ else {
302
+ this.adapter.sendTo(
303
+ from, command,
304
+ {error: 'Error opening the network'},
305
+ callback
306
+ );
307
+ }
308
+ }
309
+ else {
290
310
  this.adapter.sendTo(
291
311
  from, command,
292
- {error: 'You need to setup serial port and start the adapter before pairing!'},
312
+ {error: 'No connection to zigbee Hardware!'},
293
313
  callback
294
314
  );
295
315
  }
@@ -316,268 +336,310 @@ class Commands {
316
336
  }
317
337
  }
318
338
 
319
- async getDevices(from, command, id, callback) {
320
- if (this.zbController && this.zbController.herdsmanStarted) {
321
- this.debug(`getDevices called from ${from} with command ${JSON.stringify(command)} and id ${JSON.stringify(id)}`);
322
- const pairedDevices = await this.zbController.getClients(true);
323
- const groups = {};
324
- let rooms;
325
- this.adapter.getEnumsAsync('enum.rooms')
326
- .then(enums => {
327
- // rooms
328
- rooms = enums['enum.rooms'];
329
- })
330
- // get all adapter devices
331
- .then(() => this.adapter.getDevicesAsync())
332
- .then(async result => {
333
- const alls = id ? await this.adapter.getStatesAsync(id + '.*') : await this.adapter.getStatesAsync('*');
334
- const allst = id ? await this.adapter.getStatesOfAsync(id) : await this.adapter.getStatesOfAsync();
335
- result = result.filter(item => !id || id === item._id);
336
- // get device states and groups
337
- result.forEach(async devInfo => {
338
- if (devInfo._id) {
339
- // battery and link_quality
340
- const lqState = alls[`${devInfo._id}.link_quality`];
341
- devInfo.link_quality = lqState ? lqState.val : undefined;
342
- devInfo.link_quality_lc = lqState ? lqState.lc : undefined;
343
- const batState = alls[`${devInfo._id}.battery`];
344
- devInfo.battery = batState ? batState.val : undefined;
345
- // devInfo.states = states || {};
346
-
347
- const states = allst.filter(item => item._id.startsWith(devInfo._id));
348
-
349
- // put only allowed states
350
- devInfo.statesDef = (states || []).filter(stateDef => {
351
- const sid = stateDef._id.replace(this.adapter.namespace + '.', '');
352
- const names = sid.split('.');
353
- if (stateDef.common.color || names.length > 2) return false;
354
- return !disallowedDashStates.includes(names.pop());
355
-
356
- }).map(stateDef => {
357
- const name = stateDef.common.name;
358
- const devname = devInfo.common.name;
359
- // replace state
360
- return {
361
- id: stateDef._id,
362
- name: typeof name === 'string' ? name.replace(devname, '') : name,
363
- type: stateDef.common.type,
364
- read: stateDef.common.read,
365
- write: stateDef.common.write,
366
- val: alls[stateDef._id] ? alls[stateDef._id].val : undefined,
367
- role: stateDef.common.role,
368
- unit: stateDef.common.unit,
369
- states: stateDef.common.states,
370
- };
371
- });
372
- }
373
- });
374
- return result;
375
- })
376
- .then(async result => {
377
- // combine info
378
- const devices = [];
379
- for (const devInfo of result) {
380
- if (devInfo._id.indexOf('group') > 0) {
381
- devInfo.icon = 'img/group.png';
382
- devInfo.vendor = 'ioBroker';
383
- // get group members and store them
384
- const match = /zigbee.\d.group_([0-9]+)/.exec(devInfo._id);
385
- if (match && match.length > 1) {
386
- const groupID = Number(match[1]);
387
- const groupmembers = await this.zbController.getGroupMembersFromController(groupID);
388
- this.debug(`group members for group ${groupID}: ${JSON.stringify(groupmembers)}`);
389
- if (groupmembers && groupmembers.length > 0) {
390
- const memberinfo = [];
391
- for (const member of groupmembers) {
392
- if (member && typeof member.ieee === 'string') {
393
- if (groups) {
394
- const grouparray = groups[member.ieee];
395
- if (grouparray) {
396
- if (!grouparray.includes(groupID)) {
397
- groups[member.ieee].push(groupID);
398
- }
399
- } else {
400
- groups[member.ieee] = [groupID];
401
- }
402
- }
403
- const device = await this.adapter.getObjectAsync(`${this.adapter.namespace}.${member.ieee.substr(2)}`);
404
- if (device) {
405
- member.device = device.common.name;
406
- } else {
407
- member.device = 'unknown';
408
- }
409
- memberinfo.push(member);
410
- }
411
- }
412
- devInfo.memberinfo = memberinfo;
413
- this.debug(`memberinfo for ${match[1]}: ${JSON.stringify(devInfo.memberinfo)}`);
414
- }
415
- }
416
- } else {
417
- const modelDesc = statesMapping.findModel(devInfo.common.type);
418
- devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
419
- devInfo.vendor = modelDesc ? modelDesc.vendor : '';
420
- const legacyDesc = statesMapping.findModel(devInfo.common.type, true);
421
- devInfo.legacyIcon = (legacyDesc && legacyDesc.icon) ? legacyDesc.icon : undefined;
422
- }
423
339
 
424
- const id = getZbId(devInfo._id);
425
- devInfo.info = await this.zbController.resolveEntity(id);
426
- // check configuration
427
- try {
428
- if (devInfo.info) {
429
- const result = await this.zbController.callExtensionMethod(
430
- 'shouldConfigure',
431
- [devInfo.info.device, devInfo.info.mapped],
432
- );
433
- if (result.length > 0) devInfo.isConfigured = !result[0];
434
- }
435
- } catch (error) {
436
- this.warn('error calling shouldConfigure: ' + error && error.message ? error.message : 'no error message');
437
- }
340
+ async handleDeviceforInfo(device, states) {
341
+ }
438
342
 
439
- devInfo.rooms = [];
440
- for (const room in rooms) {
441
- if (!rooms.hasOwnProperty(room) ||
442
- !rooms[room] ||
443
- !rooms[room].common ||
444
- !rooms[room].common.members
445
- ) {
446
- continue;
447
- }
448
- if (rooms[room].common.members.includes(devInfo._id)) {
449
- devInfo.rooms.push(rooms[room].common.name);
450
- }
451
- }
452
- devInfo.paired = !!devInfo.info;
453
- devices.push(devInfo);
454
- }
455
- return devices;
456
- })
457
- .then(async (devices) => {
458
- // fill group info
459
- for (const groupdev in groups) {
460
- const device = devices.find(dev => (groupdev === getZbId(dev._id)));
343
+ async handleGroupforInfo(group, groups) {
344
+ group.icon = 'img/group.png';
345
+ group.vendor = 'ioBroker';
346
+ // get group members and store them
347
+ const match = /zigbee.\d.group_([0-9]+)/.exec(group._id);
348
+ if (match && match.length > 1) {
349
+ const groupID = Number(match[1]);
350
+ const groupmembers = await this.zbController.getGroupMembersFromController(groupID);
351
+ this.debug(`group members for group ${groupID}: ${JSON.stringify(groupmembers)}`);
352
+ if (groupmembers && groupmembers.length > 0) {
353
+ const memberinfo = [];
354
+ for (const member of groupmembers) {
355
+ if (member && typeof member.ieee === 'string') {
356
+ const memberId = member.ieee.substr(2);
357
+ const device = await this.adapter.getObjectAsync(`${this.adapter.namespace}.${member.ieee.substr(2)}`);
358
+ if (groups.hasOwnProperty(memberId)) { if (!groups[memberId].includes(groupID)) groups[memberId].push(groupID)} else groups[memberId] = [groupID]
461
359
  if (device) {
462
- device.groups = groups[groupdev];
463
- }
464
- }
465
- // append devices that paired but not created
466
- if (!id) {
467
- for (const d of pairedDevices) {
468
- const device = await this.zbController.resolveEntity(d.ieeeAddr);
469
- if (!device || !device.device) {
470
- continue;
471
- }
472
- const exists = devices.find((dev) => (dev._id && device.device.ieeeAddr === getZbId(dev._id)));
473
- if (!exists) {
474
- devices.push({
475
- _id: device.device.ieeeAddr,
476
- icon: 'img/unknown.png',
477
- paired: true,
478
- info: device,
479
- common: {
480
- name: undefined,
481
- type: undefined,
482
- },
483
- native: {}
484
- });
485
- }
360
+ member.device = device.common.name;
361
+ } else {
362
+ member.device = 'unknown';
486
363
  }
364
+ memberinfo.push(member);
487
365
  }
488
- return devices;
489
- })
490
- .then(devices => {
491
- this.debug(`getDevices contains ${devices.length} Devices`);
492
- const rv = { devices:devices, inLog:this.adapter.deviceDebug.logStatus, byId:this.adapter.deviceDebug.collectDebugData() }
493
- if (this.stController) {
494
- rv.clean = this.stController.CleanupRequired();
495
- rv.errors = this.stController.getStashedErrors();
496
- rv.debugDevices = this.stController.debugDevices;
497
- }
498
- this.adapter.sendTo(from, command, rv, callback);
499
- })
500
- .catch(err => {
501
- this.error(`getDevices error: ${err.stack}`);
502
- this.adapter.sendTo(from, command, {error: `Error enumerating devices : ${err && err.message ? err.message : ''}`}, callback);
503
- });
504
- } else {
505
- this.adapter.sendTo(from, command, {error: 'No active connection to Zigbee Hardware!'}, callback);
366
+ }
367
+ group.memberinfo = memberinfo;
368
+ this.debug(`memberinfo for ${match[1]}: ${JSON.stringify(group.memberinfo)}`);
369
+ }
506
370
  }
507
371
  }
508
372
 
509
-
510
- async getCoordinatorInfo(from, command, callback) {
511
- if (this.zbController && this.zbController.herdsmanStarted) {
512
- const coordinatorinfo = {
513
- installSource: 'IADefault_1',
514
- channel: '-1',
515
- port: 'Default_1',
516
- installedVersion: 'Default_1',
517
- type: 'Default_1',
518
- revision: 'Default_1',
519
- version: '9-9.9.9.9'
373
+ async fillInfo(device, device_stateDefs, all_states) {
374
+ device.statesDef = (device_stateDefs || []).filter(stateDef => {
375
+ const sid = stateDef._id.replace(this.adapter.namespace + '.', '');
376
+ const names = sid.split('.');
377
+ if (stateDef.common.color || names.length > 2) return false;
378
+ return !disallowedDashStates.includes(names.pop());
379
+ }).map(stateDef => {
380
+ const name = stateDef.common.name;
381
+ const devname = device.common.name;
382
+ // replace state
383
+ return {
384
+ id: stateDef._id,
385
+ name: typeof name === 'string' ? name.replace(devname, '') : name,
386
+ type: stateDef.common.type,
387
+ read: stateDef.common.read,
388
+ write: stateDef.common.write,
389
+ val: all_states[stateDef._id] ? all_states[stateDef._id].val : undefined,
390
+ role: stateDef.common.role,
391
+ unit: stateDef.common.unit,
392
+ states: stateDef.common.states,
520
393
  };
394
+ });
395
+
396
+ const id = getZbId(device._id);
397
+ device.info = this.buildDeviceInfo(await this.zbController.resolveEntity(id));
398
+ // check configuration
399
+ try {
400
+ if (device.info) {
401
+ const result = await this.zbController.callExtensionMethod(
402
+ 'shouldConfigure',
403
+ [device.info.device, device.info.mapped],
404
+ );
405
+ if (result.length > 0) device.isConfigured = !result[0];
406
+ }
407
+ } catch (error) {
408
+ this.warn('error calling shouldConfigure: ' + error && error.message ? error.message : 'no error message');
409
+ }
410
+ device.paired = !!device.info;
521
411
 
522
- const coordinatorVersion = await this.adapter.zbController.herdsman.getCoordinatorVersion();
523
412
 
524
- await this.adapter.getForeignObject(`system.adapter.${this.adapter.namespace}`, (err, obj) => {
525
- if (!err && obj) {
526
- if (obj.common.installedFrom && obj.common.installedFrom.includes('://')) {
527
- const instFrom = obj.common.installedFrom;
528
- coordinatorinfo.installSource = instFrom.replace('tarball', 'commit');
529
- } else {
530
- coordinatorinfo.installSource = obj.common.installedFrom;
413
+ }
414
+
415
+ buildDeviceInfo(device) {
416
+ const rv = {};
417
+ try {
418
+ rv.device = {
419
+ modelZigbee:device.device.modelID,
420
+ type:device.device.type,
421
+ ieee:device.device.ieeeAddr,
422
+ nwk:device.device.networkAddress,
423
+ manuf_id:device.device.maufacturerID,
424
+ manuf_name:device.device.manufacturerName,
425
+ manufacturer:device.mapped.vendor,
426
+ power:device.device.powerSource,
427
+ app_version:device.device.applicationVersion,
428
+ hard_version:device.device.hardwareVersion,
429
+ zcl_version:device.device.zclVersion,
430
+ stack_version:device.device.stack_version,
431
+ date_code:device.device.dateCode,
432
+ build:device.device.softwareBuildID,
433
+ interviewstate:device.device.interviewState || 'UNKNOWN',
434
+ }
435
+ rv.endpoints = [];
436
+ for (const ep_idx in device.endpoints) {
437
+ const ep = device.endpoints[ep_idx];
438
+ rv.endpoints.push({
439
+ ID:ep.ID,
440
+ profile:ep.profileID,
441
+ input_clusters:ep.inputClusters,
442
+ output_clusters:ep.outputClusters,
443
+ })
444
+ }
445
+ rv.mapped = {
446
+ model:device.mapped.model,
447
+ description:device.mapped.description,
448
+ //fingerprint:JSON.stringify(device.mapped.fingerprint),
449
+ vendor:device.mapped.vendor,
450
+ hasOnEvent:device.mapped.onEvent != undefined,
451
+ hasConfigure:device.mapped.configure != undefined,
452
+ options:[],
453
+ }
454
+ if (device.mapped.options && typeof (device.mapped.options == 'object')) {
455
+ const optionDesc = [];
456
+ for (const option of device.mapped.options) {
457
+ if (option.name)
458
+ rv.mapped.options.push(option.name);
459
+ }
460
+ }
461
+ }
462
+ catch (error) {
463
+ this.warn(`Error ${error && error.message + ' ' ? error.message : ''}building device info for ${JSON.stringify(device)}`);
464
+ }
465
+ return rv;
466
+ }
467
+
468
+ async appendDevicesWithoutObjects(devices, client) {
469
+ const device = await this.zbController.resolveEntity(client.ieeeAddr);
470
+ if (!device || !device.device) {
471
+ return;
472
+ }
473
+ const exists = devices.find((dev) => (dev._id && device.device.ieeeAddr === getZbId(dev._id)));
474
+ if (!exists) {
475
+ devices.push({
476
+ _id: device.device.ieeeAddr,
477
+ icon: 'img/unknown.png',
478
+ paired: true,
479
+ info: this.buildDeviceInfo(device),
480
+ common: {
481
+ name: undefined,
482
+ type: undefined,
483
+ },
484
+ native: {}
485
+ });
486
+ }
487
+
488
+ }
489
+
490
+ async getDevices(from, command, id, callback) {
491
+ this.debug(`getDevices called from ${from} with command ${JSON.stringify(command)}${id ? ' and id '+JSON.stringify(id) : ' without ID'}`);
492
+ if (!(this.zbController && this.zbController.herdsmanStarted)) {
493
+ this.adapter.sendTo(from, command, {error: 'No active connection to Zigbee Hardware!'}, callback);
494
+ return;
495
+ }
496
+ const rooms = await this.adapter.getEnumsAsync('enum.rooms') || {};
497
+ const deviceObjects = (id ? [await this.adapter.getObjectAsync(id)] : await this.adapter.getDevicesAsync());
498
+ const all_states = id ? await this.adapter.getStatesAsync(id + '.*') : await this.adapter.getStatesAsync('*');
499
+ const all_stateDefs = id ? await this.adapter.getStatesOfAsync(id) : await this.adapter.getStatesOfAsync();
500
+ const illegalDevices = [];
501
+ const groups = {};
502
+ const PromiseChain = [];
503
+ for (const devInfo of deviceObjects) {
504
+ if (devInfo._id.indexOf('group') > -1) {
505
+ PromiseChain.push(this.handleGroupforInfo(devInfo, groups));
506
+ }
507
+ else {
508
+ const modelDesc = statesMapping.findModel(devInfo.common.type);
509
+ devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
510
+ devInfo.vendor = modelDesc ? modelDesc.vendor : '';
511
+ const legacyDesc = statesMapping.findModel(devInfo.common.type, true);
512
+ devInfo.legacyIcon = (legacyDesc && legacyDesc.icon) ? legacyDesc.icon : undefined;
513
+ const lq_state = all_states[`${devInfo._id}.link_quality`];
514
+ devInfo.link_quality = lq_state ? lq_state.val : -1;
515
+ devInfo.link_quality_lc = lq_state ? lq_state.lc : undefined;
516
+ const battery_state = all_states[`${devInfo._id}.battery`];
517
+ devInfo.battery = battery_state ? battery_state.val : undefined;
518
+ devInfo.rooms = [];
519
+ for (const room in rooms) {
520
+ if (!rooms.hasOwnProperty(room) ||
521
+ !rooms[room] ||
522
+ !rooms[room].common ||
523
+ !rooms[room].common.members
524
+ ) {
525
+ continue;
526
+ }
527
+ if (rooms[room].common.members.includes(devInfo._id)) {
528
+ devInfo.rooms.push(rooms[room].common.name);
531
529
  }
532
530
  }
533
- try {
534
- coordinatorinfo.port = obj.native.port;
535
- coordinatorinfo.channel = obj.native.channel;
536
- coordinatorinfo.installedVersion = obj.native.version;
537
- if (coordinatorVersion && coordinatorVersion.type && coordinatorVersion.meta) {
538
- coordinatorinfo.type = coordinatorVersion.type;
539
- const meta = coordinatorVersion.meta;
540
- if (meta) {
541
- if (meta.hasOwnProperty('revision')) {
542
- coordinatorinfo.revision = meta.revision;
543
- }
544
- let vt = 'x-';
545
- if (meta.hasOwnProperty('transportrev')) {
546
- vt = meta.transportrev + '-';
547
- }
548
- if (meta.hasOwnProperty('product')) {
549
- vt = vt + meta.product + '.';
550
- } else {
551
- vt = vt + 'x.';
552
- }
553
- if (meta.hasOwnProperty('majorrel')) {
554
- vt = vt + meta.majorrel + '.';
555
- } else {
556
- vt = vt + 'x.';
557
- }
558
- if (meta.hasOwnProperty('minorrel')) {
559
- vt = vt + meta.minorrel + '.';
560
- } else {
561
- vt = vt + 'x.';
562
- }
563
- if (meta.hasOwnProperty('maintrel')) {
564
- vt = vt + meta.maintrel + '.';
565
- } else {
566
- vt = vt + 'x.';
567
- }
568
- coordinatorinfo.version = vt;
531
+
532
+ }
533
+ PromiseChain.push(this.fillInfo(devInfo, all_stateDefs.filter(item => item._id.startsWith(devInfo._id)),all_states));
534
+ }
535
+ if (!id) {
536
+ for (const client of this.zbController.getClientIterator(false)) {
537
+ PromiseChain.push(this.appendDevicesWithoutObjects(deviceObjects,client))
538
+ }
539
+ }
540
+
541
+ await Promise.all(PromiseChain);
542
+
543
+ for (const groupmember in groups) {
544
+ const device = deviceObjects.find(dev => (groupmember === dev.native.id));
545
+ if (device) {
546
+ device.groups = groups[groupmember];
547
+ }
548
+ }
549
+
550
+
551
+ this.debug(`getDevices contains ${deviceObjects.length} Devices`);
552
+ const rv = { devices:deviceObjects, inLog:this.adapter.deviceDebug.logStatus, }
553
+ if (!id) rv.byId = this.adapter.deviceDebug.collectDebugData();
554
+ if (this.stController) {
555
+ rv.clean = this.stController.CleanupRequired();
556
+ rv.errors = this.stController.getStashedErrors();
557
+ rv.debugDevices = this.stController.debugDevices;
558
+ }
559
+ this.adapter.sendTo(from, command, rv, callback);
560
+
561
+ }
562
+
563
+ async getCoordinatorInfo(from, command, callback) {
564
+ const coordinatorinfo = {
565
+ installSource: 'IADefault_1',
566
+ channel: '-1',
567
+ port: 'Default_1',
568
+ installedVersion: 'Default_1',
569
+ type: 'Default_1',
570
+ revision: 'unknown',
571
+ version: 'unknown',
572
+ herdsman: this.adapter.zhversion,
573
+ converters: this.adapter.zhcversion,
574
+ };
575
+
576
+ const coordinatorVersion = this.zbController && this.zbController.herdsmanStarted ? await this.adapter.zbController.herdsman.getCoordinatorVersion() : {};
577
+
578
+ await this.adapter.getForeignObject(`system.adapter.${this.adapter.namespace}`, (err, obj) => {
579
+ if (!err && obj) {
580
+ if (obj.common.installedFrom && obj.common.installedFrom.includes('://')) {
581
+ const instFrom = obj.common.installedFrom;
582
+ coordinatorinfo.installSource = instFrom.replace('tarball', 'commit');
583
+ } else {
584
+ coordinatorinfo.installSource = obj.common.installedFrom;
585
+ }
586
+ }
587
+ try {
588
+ coordinatorinfo.port = obj.native.port;
589
+ coordinatorinfo.type = obj.native.adapterType;
590
+ coordinatorinfo.channel = obj.native.channel;
591
+ coordinatorinfo.autostart = this.adapter.config.autostart;
592
+ coordinatorinfo.installedVersion = obj.common.version;
593
+ if (coordinatorVersion && coordinatorVersion.type && coordinatorVersion.meta) {
594
+ coordinatorinfo.type = coordinatorVersion.type;
595
+ const meta = coordinatorVersion.meta;
596
+ if (typeof meta == 'object') {
597
+ if (meta.hasOwnProperty('revision')) {
598
+ coordinatorinfo.revision = meta.revision;
599
+ }
600
+ let vt = 'x-';
601
+ if (meta.hasOwnProperty('transportrev')) {
602
+ vt = meta.transportrev + '-';
603
+ }
604
+ if (meta.hasOwnProperty('product')) {
605
+ vt = vt + meta.product + '.';
606
+ } else {
607
+ vt = vt + 'x.';
608
+ }
609
+ if (meta.hasOwnProperty('majorrel')) {
610
+ vt = vt + meta.majorrel + '.';
611
+ } else {
612
+ vt = vt + 'x.';
613
+ }
614
+ if (meta.hasOwnProperty('minorrel')) {
615
+ vt = vt + meta.minorrel + '.';
616
+ } else {
617
+ vt = vt + 'x.';
618
+ }
619
+ if (meta.hasOwnProperty('maintrel')) {
620
+ vt = vt + meta.maintrel + '.';
621
+ } else {
622
+ vt = vt + 'x.';
569
623
  }
624
+ coordinatorinfo.version = vt;
625
+ }
626
+ else {
627
+ coordinatorinfo.version = 'illegal data';
628
+ coordinatorinfo.revision = 'illegal data';
570
629
  }
571
- } catch {
572
- this.warn('exception raised in getCoordinatorInfo');
573
630
  }
631
+ else {
632
+ coordinatorinfo.version = this.adapter.config.autostart ? 'not connected' : 'autostart not set';
633
+ coordinatorinfo.revision = this.adapter.config.autostart ? 'not connected' : 'autostart not set';
574
634
 
575
- this.debug(`getCoordinatorInfo result: ${JSON.stringify(coordinatorinfo)}`);
576
- this.adapter.sendTo(from, command, coordinatorinfo, callback);
577
- });
578
- } else {
579
- this.adapter.sendTo(from, command, {error: 'No active connection to Zigbee Hardware!'}, callback);
580
- }
635
+ }
636
+ } catch {
637
+ this.warn('exception raised in getCoordinatorInfo');
638
+ }
639
+
640
+ this.debug(`getCoordinatorInfo result: ${JSON.stringify(coordinatorinfo)}`);
641
+ this.adapter.sendTo(from, command, coordinatorinfo, callback);
642
+ });
581
643
  }
582
644
 
583
645
 
@@ -590,28 +652,29 @@ class Commands {
590
652
  }
591
653
  }
592
654
 
593
- deleteDevice(from, command, msg, callback) {
655
+ deleteZigbeeDevice(from, command, msg, callback) {
594
656
  if (this.zbController && this.zbController.herdsmanStarted && this.stController) {
595
- this.debug(`deleteDevice message: ${JSON.stringify(msg)}`);
657
+ this.debug(`deleteZigbeeDevice message: ${JSON.stringify(msg)}`);
596
658
  const id = msg.id;
597
659
  const force = msg.force;
598
660
  const sysid = id.replace(this.adapter.namespace + '.', '0x');
599
661
  const devId = id.replace(this.adapter.namespace + '.', '');
600
- this.debug(`deleteDevice sysid: ${sysid}`);
662
+ this.debug(`deleteZigbeeDevice sysid: ${sysid}`);
601
663
  const dev = this.zbController.getDevice(sysid);
602
664
  if (!dev) {
603
- this.debug('Not found!');
604
- this.debug(`Try delete dev ${devId} from iobroker.`);
665
+ this.info(`Attempted to delete device ${devId} - the device is not known to the zigbee controller.`);
605
666
  this.stController.deleteObj(devId, () =>
606
667
  this.adapter.sendTo(from, command, {}, callback));
607
668
  return;
608
669
  }
670
+ this.info(`${force ? 'Force removing' : 'Gracefully removing '} device ${devId} from the network.`);
609
671
  this.zbController.remove(sysid, force, err => {
610
672
  if (!err) {
611
- this.stController.deleteObj(devId, () =>
612
- this.adapter.sendTo(from, command, {}, callback));
673
+ this.info('Device removed from the network, deleting objects.')
674
+ this.stController.deleteObj(devId, () => {
675
+ this.adapter.sendTo(from, command, {}, callback);
676
+ });
613
677
  } else {
614
- this.debug(`Error on remove! ${err}`);
615
678
  this.adapter.sendTo(from, command, {error: err}, callback);
616
679
  }
617
680
  });
@@ -712,7 +775,7 @@ class Commands {
712
775
  async updateLocalConfigItems(from, command, msg, callback) {
713
776
  if (this.stController) {
714
777
  this.debug(`updateLocalConfigItems : ${JSON.stringify(msg)}`);
715
- const target = msg.global ? msg.target : msg.target.replace(`${this.adapter.namespace}.`, '');
778
+ const target = msg.target.replace(`${this.adapter.namespace}.`, '');
716
779
  const entity = await this.zbController.resolveEntity(target);
717
780
  //this.warn('entity for ' + target + ' is '+ JSON.stringify(entity))
718
781
  if (entity && !entity.mapped) {
@@ -722,6 +785,28 @@ class Commands {
722
785
  if (msg.data)
723
786
  {
724
787
  for (const prop in msg.data) {
788
+ if (prop==='options') {
789
+ // we need to trigger the option change
790
+ const changed_from = entity.options;
791
+ const changed_to = msg.data.prop;
792
+ /*
793
+ const allKeys = Object.keys(changed_from);
794
+ for (const nk of Object.keys(changed_to)) {
795
+ if (allKeys.includes(nk)) continue;
796
+ allKeys.push(nk);
797
+ }
798
+ for (const key of allKeys) {
799
+ if (changed_from.hasOwnProperty(key) && changed_to.hasOwnProperty(key) && changed_from[key] == changed_to[key])
800
+ {
801
+ delete changed_from[key];
802
+ delete changed_to[key];
803
+ }
804
+ }
805
+ */
806
+ this.zbController.callExtensionMethod(
807
+ 'onZigbeeEvent',
808
+ [{'device': entity.device, 'type': 'deviceOptionsChanged', from: changed_from, to:changed_to || {}, }, entity ? entity.mapped : null]);
809
+ }
725
810
  this.debug('enumerating data: ' + prop);
726
811
  await this.stController.localConfig.updateLocalOverride(target, (entity ? entity.mapped.model : 'group'), prop, msg.data[prop], msg.global);
727
812
  }
@@ -796,63 +881,38 @@ class Commands {
796
881
  }
797
882
 
798
883
  async testConnection(from, command, msg, callback) {
799
- this.debug(`TestConnection with ${JSON.stringify(msg)}`);
800
- if (msg && msg.address) {
801
- const netAddress = getNetAddress(msg.address);
802
- if (netAddress && netAddress.host) {
803
- this.adapter.logToPairing(`attempting dns lookup for ${netAddress.host}`);
804
- this.debug(`attempting dns lookup for ${netAddress.host}`);
805
- dns.lookup(netAddress.host, (err, ip, _) => {
806
- if (err) {
807
- const msg = `Unable to resolve name: ${err && err.message ? err.message : 'no message'}`;
808
- this.error(msg);
809
- this.adapter.logToPairing(`Error: ${msg}`);
810
- this.adapter.sendTo(from, command, {error:msg}, callback);
811
- return;
812
- }
813
- this.debug(`dns lookup for ${msg.address} produced ${ip}`);
814
- this.adapter.logToPairing(`dns lookup for ${msg.address} produced ${ip}`);
815
- const client = new net.Socket();
816
- this.debug(`attempting to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`);
817
- client.connect(netAddress.port, ip, () => {
818
- client.destroy()
819
- this.adapter.logToPairing(`connected successfully to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`);
820
- this.debug(`connected successfully to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`);
821
- this.adapter.sendTo(from, command, {}, callback)
822
- })
823
- client.on('error', (error) => {
824
- const msg = `unable to connect to ${ip} port ${netAddress.port ? netAddress.port : 80} : ${error && error.message ? error.message : 'no message given'}`
825
- this.error(msg);
826
- this.adapter.logToPairing(`Error: ${msg}`);
827
- this.adapter.sendTo(from, command, {error:msg}, callback);
828
- });
829
- })
830
- }
831
- else
832
- {
833
- try {
834
- const port = msg.address.trim();
835
- this.adapter.logToPairing(`reading access rights for ${port}`);
836
- access(port, constants.R_OK | constants.W_OK, (error) => {
837
- if (error) {
838
- const msg = `unable to access ${port} : ${error && error.message ? error.message : 'no message given'}`;
839
- this.error(msg);
840
- this.adapter.logToPairing(`Error: ${msg}`);
841
- this.adapter.sendTo(from, command, {error:msg}, callback);
842
- return;
843
- }
844
- this.adapter.logToPairing(`read and write access available for ${port}`);
845
- this.adapter.sendTo(from, command, {}, callback);
846
- });
847
- }
848
- catch (error) {
849
- const msg = `File access error: ${error && error.message ? error.message : 'no message given'}`;
850
- this.error(msg);
851
- this.adapter.logToPairing(`Error: ${msg}`);
852
- this.adapter.sendTo(from, command, {error:msg}, callback);
853
- }
884
+ const result = await this.adapter.testConnection(msg.address, true);
885
+ if (result.error) {
886
+ this.error(result.error);
887
+ this.adapter.logToPairing(`Error: ${result.error}`)
888
+ }
889
+ this.adapter.sendTo(from, command, result, callback);
890
+ }
891
+
892
+ async triggerIconDownload(obj) {
893
+ if (!this.stController) {
894
+ this.adapter.sendTo(obj.from, obj.command, {msg:'No States controller'}, obj.callback);
895
+ return;
896
+ }
897
+ const clients = await this.adapter.getDevicesAsync();
898
+ const Promises = [];
899
+ for (const client of clients) {
900
+ if (client.common.modelIcon && client.common.icon && client.common.modelIcon.startsWith('http')) {
901
+ const filestatus = await this.adapter.fileExistsAsync(this.adapter.namespace, client.common.icon);
902
+ if (!filestatus)
903
+ Promises.push(this.stController.downloadIconToAdmin(client.common.modelIcon, client.common.icon))
854
904
  }
905
+
855
906
  }
907
+ const NumDownloads = Promises.length;
908
+ if (NumDownloads) {
909
+ this.adapter.sendTo(obj.from, obj.command, {msg:`${NumDownloads} downloads triggered.`}, obj.callback);
910
+ Promise.all(Promises);
911
+ }
912
+ else {
913
+ this.adapter.sendTo(obj.from, obj.command, {msg:'Nothing to download'}, obj.callback);
914
+ }
915
+
856
916
  }
857
917
  }
858
918