iobroker.zigbee 1.6.8 → 1.6.16

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 (64) hide show
  1. package/.eslintignore +1 -1
  2. package/.eslintrc.json +36 -36
  3. package/.github/FUNDING.yml +3 -3
  4. package/.github/stale.yml +13 -13
  5. package/.github/workflows/test-and-release.yml +151 -151
  6. package/.travis/wiki.sh +27 -27
  7. package/LICENSE +21 -21
  8. package/README.md +424 -385
  9. package/admin/adapter-settings.js +244 -244
  10. package/admin/admin.js +2926 -2704
  11. package/admin/img/14153905L.png +0 -0
  12. package/admin/img/81855.png +0 -0
  13. package/admin/img/HG06338.png +0 -0
  14. package/admin/img/TI0001-cover.png +0 -0
  15. package/admin/img/philips_hue_lom001.png +0 -0
  16. package/admin/index.html +159 -159
  17. package/admin/index_m.html +1158 -1055
  18. package/admin/moment.min.js +1 -1
  19. package/admin/shuffle.min.js +2 -2
  20. package/admin/tab_m.html +944 -1025
  21. package/admin/vis-network.min.js +26 -26
  22. package/admin/words.js +108 -107
  23. package/docs/de/readme.md +27 -27
  24. package/docs/en/readme.md +30 -30
  25. package/docs/flashing_via_arduino_(en).md +110 -110
  26. package/docs/ru/readme.md +28 -28
  27. package/docs/tutorial/groups-1.png +0 -0
  28. package/docs/tutorial/groups-2.png +0 -0
  29. package/docs/tutorial/tab-dev-1.png +0 -0
  30. package/docs/tutorial/zigbee.png +0 -0
  31. package/io-package.json +355 -317
  32. package/lib/backup.js +132 -132
  33. package/lib/binding.js +325 -325
  34. package/lib/colors.js +460 -460
  35. package/lib/commands.js +501 -435
  36. package/lib/developer.js +148 -148
  37. package/lib/devices.js +3144 -3119
  38. package/lib/exclude.js +168 -168
  39. package/lib/exposes.js +13 -9
  40. package/lib/groups.js +342 -316
  41. package/lib/json.js +60 -60
  42. package/lib/networkmap.js +56 -56
  43. package/lib/ota.js +179 -153
  44. package/lib/rgb.js +255 -225
  45. package/lib/seriallist.js +37 -37
  46. package/lib/states.js +6407 -6381
  47. package/lib/statescontroller.js +627 -502
  48. package/lib/tools.js +54 -54
  49. package/lib/utils.js +151 -151
  50. package/lib/zbBaseExtension.js +32 -31
  51. package/lib/zbDelayedAction.js +152 -151
  52. package/lib/zbDeviceAvailability.js +318 -306
  53. package/lib/zbDeviceConfigure.js +152 -148
  54. package/lib/zbDeviceEvent.js +49 -43
  55. package/lib/zigbeecontroller.js +946 -856
  56. package/main.js +8 -4
  57. package/package.json +77 -74
  58. package/support/docgen.js +93 -93
  59. package/.devcontainer/devcontainer.json +0 -36
  60. package/.devcontainer/docker-compose.yml +0 -51
  61. package/.devcontainer/iobroker/Dockerfile +0 -2
  62. package/.devcontainer/nginx/nginx.conf +0 -33
  63. package/.devcontainer/parcel/Dockerfile +0 -9
  64. package/.devcontainer/parcel/run.sh +0 -7
package/lib/commands.js CHANGED
@@ -1,435 +1,501 @@
1
- 'use strict';
2
-
3
- const getZbId = require('./utils').getZbId;
4
- const statesMapping = require('./devices');
5
- const disallowedDashStates = [
6
- 'link_quality', 'available', 'battery', 'groups', 'device_query',
7
- 'hue_move', 'color_temp_move', 'satuation_move', 'brightness_move', 'brightness_step', 'hue_calibration',
8
- 'msg_from_zigbee',
9
- ];
10
-
11
- class Commands {
12
- constructor(adapter) {
13
- this.adapter = adapter;
14
- this.adapter.on('message', this.onMessage.bind(this));
15
- }
16
-
17
- start(zbController, stController) {
18
- this.zbController = zbController;
19
- this.stController = stController;
20
- }
21
-
22
- stop() {
23
- delete this.zbController;
24
- delete this.stController;
25
- }
26
-
27
- info(msg) {
28
- this.adapter.log.info(msg);
29
- }
30
-
31
- error(msg) {
32
- this.adapter.log.error(msg);
33
- }
34
-
35
- debug(msg) {
36
- this.adapter.log.debug(msg);
37
- }
38
-
39
- warn(msg) {
40
- this.adapter.log.warn(msg);
41
- }
42
-
43
- /**
44
- * @param {ioBroker.Message} obj
45
- */
46
- onMessage(obj) {
47
- if (typeof obj === 'object' && obj.command) {
48
- switch (obj.command) {
49
- case 'letsPairing':
50
- if (obj && obj.message && typeof obj.message === 'object') {
51
- this.letsPairing(obj.from, obj.command, obj.message, obj.callback);
52
- }
53
- break;
54
- case 'touchlinkReset':
55
- if (obj && obj.message && typeof obj.message === 'object') {
56
- this.touchlinkReset(obj.from, obj.command, obj.message, obj.callback);
57
- }
58
- break;
59
- case 'getDevices':
60
- if (obj && obj.message && typeof obj.message === 'object') {
61
- this.getDevices(obj.from, obj.command, null, obj.callback);
62
- }
63
- break;
64
- case 'renameDevice':
65
- if (obj && obj.message && typeof obj.message === 'object') {
66
- this.renameDevice(obj.from, obj.command, obj.message, obj.callback);
67
- }
68
- break;
69
- case 'deleteDevice':
70
- if (obj && obj.message && typeof obj.message === 'object') {
71
- this.deleteDevice(obj.from, obj.command, obj.message, obj.callback);
72
- }
73
- break;
74
- case 'getChannels':
75
- if (obj && obj.message && typeof obj.message === 'object') {
76
- this.getChannels(obj.from, obj.command, obj.message, obj.callback);
77
- }
78
- break;
79
- case 'getCoordinatorInfo':
80
- if (obj && obj.message && typeof obj.message === 'object') {
81
- this.getCoordinatorInfo(obj.from, obj.command, obj.callback);
82
- }
83
- break;
84
- case 'cleanDeviceStates':
85
- if (obj && obj.message && typeof obj.message === 'object') {
86
- this.cleanDeviceStates(obj.from, obj.command, obj.message, obj.callback);
87
- }
88
- break;
89
- case 'setState':
90
- if (obj && obj.message && typeof obj.message === 'object' && obj.message.id) {
91
- this.adapter.setState(obj.message.id, obj.message.val, false, obj.callback);
92
- }
93
- break;
94
- case 'getDevice':
95
- if (obj && obj.message && typeof obj.message === 'object' && obj.message.id) {
96
- this.getDevices(obj.from, obj.command, obj.message.id, obj.callback);
97
- }
98
- break;
99
- }
100
- }
101
- }
102
-
103
- letsPairing(from, command, message, callback) {
104
- if (this.zbController) {
105
- let devId = '';
106
- if (message && message.id) {
107
- devId = getZbId(message.id);
108
- }
109
- // allow devices to join the network within 60 secs
110
- this.adapter.logToPairing('Pairing started ' + devId, true);
111
-
112
- let cTimer = Number(this.adapter.config.countDown);
113
- if (!this.adapter.config.countDown || cTimer == 0) {
114
- cTimer = 60;
115
- }
116
-
117
- this.zbController.permitJoin(cTimer, devId, (err) => {
118
- if (!err) {
119
- // set pairing mode on
120
- this.adapter.setState('info.pairingMode', true);
121
- }
122
- });
123
- this.adapter.sendTo(from, command, 'Start pairing!', callback);
124
- } else {
125
- this.adapter.sendTo(
126
- from, command,
127
- {error: 'You need to setup serial port and start the adapter before pairing!'},
128
- callback
129
- );
130
- }
131
- }
132
-
133
- touchlinkReset(from, command, message, callback) {
134
- if (this.zbController) {
135
- // allow devices to join the network within 60 secs
136
- this.adapter.logToPairing('Touchlink reset started ', true);
137
-
138
- let cTimer = Number(this.adapter.config.countDown);
139
- if (!this.adapter.config.countDown || cTimer == 0) {
140
- cTimer = 60;
141
- }
142
-
143
- this.zbController.touchlinkReset(cTimer);
144
- this.adapter.sendTo(from, command, 'Start touchlink reset and pairing!', callback);
145
- } else {
146
- this.adapter.sendTo(
147
- from, command,
148
- {error: 'You need to setup serial port and start the adapter before pairing!'},
149
- callback
150
- );
151
- }
152
- }
153
-
154
- async getDevices(from, command, id, callback) {
155
- if (this.zbController) {
156
- const pairedDevices = await this.zbController.getClients(true);
157
- const groups = {};
158
- let rooms;
159
- this.adapter.getEnumsAsync('enum.rooms')
160
- .then((enums) => {
161
- // rooms
162
- rooms = enums['enum.rooms'];
163
- })
164
- .then(() => {
165
- // get all adapter devices
166
- return this.adapter.getDevicesAsync();
167
- })
168
- .then(async (result) => {
169
- const alls = (id) ? await this.adapter.getStatesAsync(id+'.*') : await this.adapter.getStatesAsync('*');
170
- const allst = (id) ? await this.adapter.getStatesOfAsync(id) : await this.adapter.getStatesOfAsync();
171
- result = result.filter((item)=>!id || id == item._id);
172
- // get device states and groups
173
- result.forEach(async (devInfo) => {
174
- if (devInfo._id) {
175
- // groups
176
- const grState = alls[`${devInfo._id}.groups`];
177
- if (grState && grState.val) {
178
- groups[devInfo._id] = JSON.parse(grState.val);
179
- }
180
- // battery and link_quality
181
- const lqState = alls[`${devInfo._id}.link_quality`];
182
- devInfo.link_quality = (lqState) ? lqState.val: undefined;
183
- devInfo.link_quality_lc = (lqState) ? lqState.lc: undefined;
184
- const batState = alls[`${devInfo._id}.battery`];
185
- devInfo.battery = (batState) ? batState.val: undefined;
186
- // devInfo.states = states || {};
187
-
188
- const states = allst.filter((item)=>item._id.startsWith(devInfo._id));
189
-
190
- // put only allowed states
191
- devInfo.statesDef = (states || []).filter((stateDef)=>{
192
- const sid = stateDef._id;
193
- const name = sid.split('.').pop();
194
- if (disallowedDashStates.includes(name)) return false;
195
- return true;
196
- }).map((stateDef)=>{
197
- const name = stateDef.common.name;
198
- const devname = devInfo.common.name;
199
- // replace state
200
- return {
201
- id: stateDef._id,
202
- name: (typeof name === 'string') ? name.replace(devname, '') : name,
203
- type: stateDef.common.type,
204
- read: stateDef.common.read,
205
- write: stateDef.common.write,
206
- val: (alls[stateDef._id]) ? alls[stateDef._id].val : undefined,
207
- role: stateDef.common.role,
208
- unit: stateDef.common.unit,
209
- states: stateDef.common.states,
210
- };
211
- });
212
- }
213
- });
214
- return result;
215
- })
216
- .then(async (result) => {
217
- // combine info
218
- const devices = [];
219
- for (const devInfo of result) {
220
- if (devInfo._id.indexOf('group') > 0) {
221
- devInfo.icon = 'img/group.png';
222
- devInfo.vendor = 'ioBroker';
223
- // get group members and store them
224
- const match = /zigbee.\d.group_([0-9]+)/.exec(devInfo._id);
225
- if (match && match.length > 1) {
226
- const groupmembers = await this.zbController.getGroupMembersFromController(match[1]);
227
- this.debug('group members: ' + JSON.stringify(groupmembers));
228
- if (groupmembers && groupmembers.length > 0) {
229
- const memberinfo = [];
230
- for (const member of groupmembers) {
231
- const device = await this.adapter.getObjectAsync(this.adapter.namespace + '.' + member.device.substr(2));
232
- if (device) {
233
- memberinfo.push({name: device.common.name, ieee:member.device});
234
- }
235
- else {
236
- memberinfo.push({name:'unknown', ieee:member.device});
237
- }
238
- }
239
- devInfo.memberinfo = memberinfo;
240
- this.debug('memberinfo: ' + JSON.stringify(devInfo.memberinfo));
241
- }
242
- }
243
- }
244
- else
245
- {
246
- const modelDesc = statesMapping.findModel(devInfo.common.type);
247
- devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
248
- devInfo.vendor = (modelDesc) ? modelDesc.vendor : '';
249
- }
250
- const id = getZbId(devInfo._id);
251
- devInfo.info = await this.zbController.resolveEntity(id);
252
-
253
- devInfo.rooms = [];
254
- for (const room in rooms) {
255
- if (!rooms.hasOwnProperty(room) ||
256
- !rooms[room] ||
257
- !rooms[room].common ||
258
- !rooms[room].common.members) {
259
- continue;
260
- }
261
- if (rooms[room].common.members.indexOf(devInfo._id) !== -1) {
262
- devInfo.rooms.push(rooms[room].common.name);
263
- }
264
- }
265
- devInfo.paired = !!devInfo.info;
266
- devInfo.groups = groups[devInfo._id];
267
- devices.push(devInfo);
268
- }
269
- return devices;
270
- })
271
- .then(async (devices) => {
272
- // append devices that paired but not created
273
- if (!id) {
274
- for (const d of pairedDevices) {
275
- const device = await this.zbController.resolveEntity(d.ieeeAddr);
276
- if (!device) continue;
277
- const exists = devices.find((dev) => (dev._id && device.device.ieeeAddr === getZbId(dev._id)));
278
- if (!exists) {
279
- devices.push({
280
- _id: device.device.ieeeAddr,
281
- icon: 'img/unknown.png',
282
- paired: true,
283
- info: device,
284
- common: {
285
- name: undefined,
286
- type: undefined,
287
- },
288
- native: {}
289
- });
290
- }
291
- }
292
- }
293
- return devices;
294
- })
295
- .then((devices) => {
296
- this.debug('getDevices result: ' + JSON.stringify(devices));
297
- this.adapter.sendTo(from, command, devices, callback);
298
- })
299
- .catch((err) => {
300
- this.error('getDevices error: ' + err.stack);
301
- });
302
- } else {
303
- this.adapter.sendTo(from, command, {error: 'You need save and run adapter before pairing!'}, callback);
304
- }
305
- }
306
-
307
-
308
- async getCoordinatorInfo(from, command, callback) {
309
- if (this.zbController) {
310
- const coordinatorinfo = {
311
- installSource: 'IADefault_1',
312
- channel: '-1',
313
- port: 'Default_1',
314
- installedVersion: 'Default_1',
315
- type: 'Default_1',
316
- revision: 'Default_1',
317
- version: '9-9.9.9.9'
318
- };
319
-
320
- const coordinatorVersion = await this.adapter.zbController.herdsman.getCoordinatorVersion();
321
-
322
- await this.adapter.getForeignObject('system.adapter.' + this.adapter.namespace, (err, obj) => {
323
- if (!err && obj) {
324
- if (obj.common.installedFrom && obj.common.installedFrom.includes('://')) {
325
- const instFrom = obj.common.installedFrom;
326
- coordinatorinfo.installSource = instFrom.replace('tarball','commit');
327
- } else {
328
- coordinatorinfo.installSource = obj.common.installedFrom;
329
- }
330
- }
331
- try
332
- {
333
- coordinatorinfo.port = obj.native.port;
334
- coordinatorinfo.channel = obj.native.channel;
335
- coordinatorinfo.installedVersion = obj.native.version;
336
- if (coordinatorVersion && coordinatorVersion.type && coordinatorVersion.meta) {
337
- coordinatorinfo.type = coordinatorVersion.type;
338
- const meta = coordinatorVersion.meta;
339
- if (meta) {
340
- if (meta.hasOwnProperty('revision')) coordinatorinfo.revision = meta.revision;
341
- let vt = 'x-';
342
- if (meta.hasOwnProperty('transportrev')) vt = meta.transportrev + '-';
343
- if (meta.hasOwnProperty('product')) vt = vt + meta.product + '.';
344
- else vt = vt + 'x.';
345
- if (meta.hasOwnProperty('majorrel')) vt = vt + meta.majorrel + '.';
346
- else vt = vt + 'x.';
347
- if (meta.hasOwnProperty('minorrel')) vt = vt + meta.minorrel + '.';
348
- else vt = vt + 'x.';
349
- if (meta.hasOwnProperty('maintrel')) vt = vt + meta.maintrel + '.';
350
- else vt = vt + 'x.';
351
- coordinatorinfo.version = vt;
352
- }
353
- }
354
- }
355
- catch { this.warn('exception raised in getCoordinatorInfo');}
356
-
357
- this.debug('getCoorinatorInfo result: ' + JSON.stringify(coordinatorinfo));
358
- this.adapter.sendTo(from, command, coordinatorinfo, callback);
359
- });
360
- } else {
361
- this.adapter.sendTo(from, command, {error: 'You need save and run adapter before pairing!'}, callback);
362
- }
363
- }
364
-
365
-
366
- renameDevice(from, command, msg, callback) {
367
- if (this.stController) {
368
- const id = msg.id;
369
- const newName = msg.name;
370
- this.stController.renameDevice(id, newName);
371
- this.adapter.sendTo(from, command, {}, callback);
372
- }
373
- }
374
-
375
- deleteDevice(from, command, msg, callback) {
376
- if (this.zbController && this.stController) {
377
- this.debug('deleteDevice message: ' + JSON.stringify(msg));
378
- const id = msg.id;
379
- const force = msg.force;
380
- const sysid = id.replace(this.adapter.namespace + '.', '0x');
381
- const devId = id.replace(this.adapter.namespace + '.', '');
382
- this.debug('deleteDevice sysid: ' + sysid);
383
- const dev = this.zbController.getDevice(sysid);
384
- if (!dev) {
385
- this.debug('Not found!');
386
- this.debug('Try delete dev ' + devId + ' from iobroker.');
387
- this.stController.deleteDeviceStates(devId, () => {
388
- this.adapter.sendTo(from, command, {}, callback);
389
- });
390
- return;
391
- }
392
- this.zbController.remove(sysid, force, (err) => {
393
- if (!err) {
394
- this.stController.deleteDeviceStates(devId, () => {
395
- this.adapter.sendTo(from, command, {}, callback);
396
- });
397
- } else {
398
- this.debug('Error on remove! ' + err);
399
- this.adapter.sendTo(from, command, {error: err}, callback);
400
- }
401
- });
402
- } else {
403
- this.adapter.sendTo(from, command, {error: 'You need to save and start the adapter!'}, callback);
404
- }
405
- }
406
-
407
- async cleanDeviceStates(from, command, msg, callback) {
408
- this.info(`State cleanup with ${JSON.stringify(msg)}`);
409
- const devicesFromDB = await this.zbController.getClients(false);
410
- for (const device of devicesFromDB) {
411
- const entity = await this.zbController.resolveEntity(device);
412
- if (entity) {
413
- const model = (entity.mapped) ? entity.mapped.model : entity.device.modelID;
414
- await this.stController.deleteOrphanedDeviceStates(device.ieeeAddr, model, msg.force);
415
- }
416
- }
417
- this.adapter.sendTo(from, command, {}, callback);
418
- }
419
-
420
- async getChannels(from, command, message, callback) {
421
- if (this.zbController) {
422
- const result = await this.zbController.getChannelsEnergy();
423
- this.debug('getChannels result: ' + JSON.stringify(result));
424
- this.adapter.sendTo(from, command, result, callback);
425
- } else {
426
- this.adapter.sendTo(
427
- from, command,
428
- {error: 'You need to setup serial port and start the adapter before pairing!'},
429
- callback
430
- );
431
- }
432
- }
433
- }
434
-
435
- module.exports = Commands;
1
+ 'use strict';
2
+
3
+ const getZbId = require('./utils').getZbId;
4
+ const statesMapping = require('./devices');
5
+ const disallowedDashStates = [
6
+ 'link_quality', 'available', 'battery', 'groups', 'device_query',
7
+ 'hue_move', 'color_temp_move', 'satuation_move', 'brightness_move', 'brightness_step', 'hue_calibration',
8
+ 'msg_from_zigbee',
9
+ ];
10
+
11
+ class Commands {
12
+ constructor(adapter) {
13
+ this.adapter = adapter;
14
+ this.adapter.on('message', this.onMessage.bind(this));
15
+ }
16
+
17
+ start(zbController, stController) {
18
+ this.zbController = zbController;
19
+ this.stController = stController;
20
+ }
21
+
22
+ stop() {
23
+ delete this.zbController;
24
+ delete this.stController;
25
+ }
26
+
27
+ info(msg) {
28
+ this.adapter.log.info(msg);
29
+ }
30
+
31
+ error(msg) {
32
+ this.adapter.log.error(msg);
33
+ }
34
+
35
+ debug(msg) {
36
+ this.adapter.log.debug(msg);
37
+ }
38
+
39
+ warn(msg) {
40
+ this.adapter.log.warn(msg);
41
+ }
42
+
43
+ /**
44
+ * @param {ioBroker.Message} obj
45
+ */
46
+ onMessage(obj) {
47
+ if (typeof obj === 'object' && obj.command) {
48
+ switch (obj.command) {
49
+ case 'letsPairing':
50
+ if (obj && obj.message && typeof obj.message === 'object') {
51
+ this.letsPairing(obj.from, obj.command, obj.message, obj.callback);
52
+ }
53
+ break;
54
+ case 'touchlinkReset':
55
+ if (obj && obj.message && typeof obj.message === 'object') {
56
+ this.touchlinkReset(obj.from, obj.command, obj.message, obj.callback);
57
+ }
58
+ break;
59
+ case 'getDevices':
60
+ if (obj && obj.message && typeof obj.message === 'object') {
61
+ this.getDevices(obj.from, obj.command, null, obj.callback);
62
+ }
63
+ break;
64
+ case 'renameDevice':
65
+ if (obj && obj.message && typeof obj.message === 'object') {
66
+ this.renameDevice(obj.from, obj.command, obj.message, obj.callback);
67
+ }
68
+ break;
69
+ case 'deleteDevice':
70
+ if (obj && obj.message && typeof obj.message === 'object') {
71
+ this.deleteDevice(obj.from, obj.command, obj.message, obj.callback);
72
+ }
73
+ break;
74
+ case 'getChannels':
75
+ if (obj && obj.message && typeof obj.message === 'object') {
76
+ this.getChannels(obj.from, obj.command, obj.message, obj.callback);
77
+ }
78
+ break;
79
+ case 'getCoordinatorInfo':
80
+ if (obj && obj.message && typeof obj.message === 'object') {
81
+ this.getCoordinatorInfo(obj.from, obj.command, obj.callback);
82
+ }
83
+ break;
84
+ case 'cleanDeviceStates':
85
+ if (obj && obj.message && typeof obj.message === 'object') {
86
+ this.cleanDeviceStates(obj.from, obj.command, obj.message, obj.callback);
87
+ }
88
+ break;
89
+ case 'setState':
90
+ if (obj && obj.message && typeof obj.message === 'object' && obj.message.id) {
91
+ // this.adapter.setState(obj.message.id, obj.message.val, false, obj.callback);
92
+ this.stController.setState_typed(obj.message.id, obj.message.val, false, undefined, obj.callback);
93
+ }
94
+ break;
95
+ case 'getDevice':
96
+ if (obj && obj.message && typeof obj.message === 'object' && obj.message.id) {
97
+ this.getDevices(obj.from, obj.command, obj.message.id, obj.callback);
98
+ }
99
+ break;
100
+ case 'reconfigure':
101
+ if (obj && obj.message && typeof obj.message === 'object') {
102
+ this.reconfigure(obj.from, obj.command, obj.message, obj.callback);
103
+ }
104
+ break;
105
+ case 'setDeviceActivated':
106
+ if (obj && obj.message && typeof obj.message === 'object') {
107
+ this.setDeviceActivated(obj.from, obj.command, obj.message, obj.callback);
108
+ }
109
+ break;
110
+
111
+ }
112
+ }
113
+ }
114
+
115
+
116
+ letsPairing(from, command, message, callback) {
117
+ if (this.zbController) {
118
+ let devId = '';
119
+ if (message && message.id) {
120
+ devId = getZbId(message.id);
121
+ }
122
+ // allow devices to join the network within 60 secs
123
+ this.adapter.logToPairing('Pairing started ' + devId, true);
124
+
125
+ let cTimer = Number(this.adapter.config.countDown);
126
+ if (!this.adapter.config.countDown || cTimer == 0) {
127
+ cTimer = 60;
128
+ }
129
+
130
+ this.zbController.permitJoin(cTimer, devId, (err) => {
131
+ if (!err) {
132
+ // set pairing mode on
133
+ this.adapter.setState('info.pairingMode', true);
134
+ }
135
+ });
136
+ this.adapter.sendTo(from, command, 'Start pairing!', callback);
137
+ } else {
138
+ this.adapter.sendTo(
139
+ from, command,
140
+ {error: 'You need to setup serial port and start the adapter before pairing!'},
141
+ callback
142
+ );
143
+ }
144
+ }
145
+
146
+ touchlinkReset(from, command, message, callback) {
147
+ if (this.zbController) {
148
+ // allow devices to join the network within 60 secs
149
+ this.adapter.logToPairing('Touchlink reset started ', true);
150
+
151
+ let cTimer = Number(this.adapter.config.countDown);
152
+ if (!this.adapter.config.countDown || cTimer == 0) {
153
+ cTimer = 60;
154
+ }
155
+
156
+ this.zbController.touchlinkReset(cTimer);
157
+ this.adapter.sendTo(from, command, 'Start touchlink reset and pairing!', callback);
158
+ } else {
159
+ this.adapter.sendTo(
160
+ from, command,
161
+ {error: 'You need to setup serial port and start the adapter before pairing!'},
162
+ callback
163
+ );
164
+ }
165
+ }
166
+
167
+ async getDevices(from, command, id, callback) {
168
+ if (this.zbController) {
169
+ const pairedDevices = await this.zbController.getClients(true);
170
+ const groups = {};
171
+ let rooms;
172
+ this.adapter.getEnumsAsync('enum.rooms')
173
+ .then((enums) => {
174
+ // rooms
175
+ rooms = enums['enum.rooms'];
176
+ })
177
+ .then(() => {
178
+ // get all adapter devices
179
+ return this.adapter.getDevicesAsync();
180
+ })
181
+ .then(async (result) => {
182
+ const alls = (id) ? await this.adapter.getStatesAsync(id+'.*') : await this.adapter.getStatesAsync('*');
183
+ const allst = (id) ? await this.adapter.getStatesOfAsync(id) : await this.adapter.getStatesOfAsync();
184
+ result = result.filter((item)=>!id || id == item._id);
185
+ // get device states and groups
186
+ result.forEach(async (devInfo) => {
187
+ if (devInfo._id) {
188
+ // groups
189
+ // const grState = alls[`${devInfo._id}.groups`];
190
+ // if (grState && grState.val) {
191
+ // groups[devInfo._id] = JSON.parse(grState.val);
192
+ // }
193
+ // battery and link_quality
194
+ const lqState = alls[`${devInfo._id}.link_quality`];
195
+ devInfo.link_quality = (lqState) ? lqState.val: undefined;
196
+ devInfo.link_quality_lc = (lqState) ? lqState.lc: undefined;
197
+ const batState = alls[`${devInfo._id}.battery`];
198
+ devInfo.battery = (batState) ? batState.val: undefined;
199
+ // devInfo.states = states || {};
200
+
201
+ const states = allst.filter((item)=>item._id.startsWith(devInfo._id));
202
+
203
+ // put only allowed states
204
+ devInfo.statesDef = (states || []).filter((stateDef)=>{
205
+ const sid = stateDef._id;
206
+ const name = sid.split('.').pop();
207
+ if (disallowedDashStates.includes(name)) return false;
208
+ return true;
209
+ }).map((stateDef)=>{
210
+ const name = stateDef.common.name;
211
+ const devname = devInfo.common.name;
212
+ // replace state
213
+ return {
214
+ id: stateDef._id,
215
+ name: (typeof name === 'string') ? name.replace(devname, '') : name,
216
+ type: stateDef.common.type,
217
+ read: stateDef.common.read,
218
+ write: stateDef.common.write,
219
+ val: (alls[stateDef._id]) ? alls[stateDef._id].val : undefined,
220
+ role: stateDef.common.role,
221
+ unit: stateDef.common.unit,
222
+ states: stateDef.common.states,
223
+ };
224
+ });
225
+ }
226
+ });
227
+ return result;
228
+ })
229
+ .then(async (result) => {
230
+ // combine info
231
+ const devices = [];
232
+ for (const devInfo of result) {
233
+ if (devInfo._id.indexOf('group') > 0) {
234
+ devInfo.icon = 'img/group.png';
235
+ devInfo.vendor = 'ioBroker';
236
+ // get group members and store them
237
+ const match = /zigbee.\d.group_([0-9]+)/.exec(devInfo._id);
238
+ if (match && match.length > 1) {
239
+ const groupmembers = await this.zbController.getGroupMembersFromController(match[1]);
240
+ this.debug('group members: ' + JSON.stringify(groupmembers));
241
+ if (groupmembers && groupmembers.length > 0) {
242
+ const memberinfo = [];
243
+ for (const member of groupmembers) {
244
+ if (groups) {
245
+ const grouparray = groups[member.ieee];
246
+ if (grouparray)
247
+ {
248
+ if (!grouparray.includes(match[1])) {
249
+ groups[member.ieee].push(match[1])
250
+ }
251
+ }
252
+ else groups[member.ieee] = [match[1]];
253
+ }
254
+ const device = await this.adapter.getObjectAsync(this.adapter.namespace + '.' + member.ieee.substr(2));
255
+ if (device) {
256
+ member.device = device.common.name;
257
+ }
258
+ else {
259
+ member.device = 'unknown';
260
+ }
261
+ memberinfo.push(member);
262
+ }
263
+ devInfo.memberinfo = memberinfo;
264
+ this.debug('memberinfo: ' + JSON.stringify(devInfo.memberinfo));
265
+ }
266
+ }
267
+ }
268
+ else
269
+ {
270
+ const modelDesc = statesMapping.findModel(devInfo.common.type);
271
+ devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
272
+ devInfo.vendor = (modelDesc) ? modelDesc.vendor : '';
273
+ }
274
+ const id = getZbId(devInfo._id);
275
+ devInfo.info = await this.zbController.resolveEntity(id);
276
+
277
+ devInfo.rooms = [];
278
+ for (const room in rooms) {
279
+ if (!rooms.hasOwnProperty(room) ||
280
+ !rooms[room] ||
281
+ !rooms[room].common ||
282
+ !rooms[room].common.members) {
283
+ continue;
284
+ }
285
+ if (rooms[room].common.members.indexOf(devInfo._id) !== -1) {
286
+ devInfo.rooms.push(rooms[room].common.name);
287
+ }
288
+ }
289
+ devInfo.paired = !!devInfo.info;
290
+ // devInfo.groups = groups[devInfo._id];
291
+ devices.push(devInfo);
292
+ }
293
+ return devices;
294
+ })
295
+ .then(async (devices) => {
296
+ // fill group info
297
+ for (const groupdev in groups) {
298
+ this.debug('GetDevices scanning group ' + groupdev + ' ' + JSON.stringify(groups[groupdev]))
299
+ const device = devices.find((dev) =>( groupdev === getZbId(dev._id) ))
300
+ if (device) {
301
+ device.groups = groups[groupdev]
302
+ this.debug('adding group info to device ' + groupdev);
303
+ }
304
+ }
305
+ // append devices that paired but not created
306
+ if (!id) {
307
+ for (const d of pairedDevices) {
308
+ const device = await this.zbController.resolveEntity(d.ieeeAddr);
309
+ if (!device) continue;
310
+ const exists = devices.find((dev) => (dev._id && device.device.ieeeAddr === getZbId(dev._id)));
311
+ if (!exists) {
312
+ devices.push({
313
+ _id: device.device.ieeeAddr,
314
+ icon: 'img/unknown.png',
315
+ paired: true,
316
+ info: device,
317
+ common: {
318
+ name: undefined,
319
+ type: undefined,
320
+ },
321
+ native: {}
322
+ });
323
+ }
324
+ }
325
+ }
326
+ return devices;
327
+ })
328
+ .then((devices) => {
329
+ this.debug('getDevices result: ' + JSON.stringify(devices));
330
+ this.adapter.sendTo(from, command, devices, callback);
331
+ })
332
+ .catch((err) => {
333
+ this.error('getDevices error: ' + err.stack);
334
+ });
335
+ } else {
336
+ this.adapter.sendTo(from, command, {error: 'You need save and run adapter before pairing!'}, callback);
337
+ }
338
+ }
339
+
340
+
341
+ async getCoordinatorInfo(from, command, callback) {
342
+ if (this.zbController) {
343
+ const coordinatorinfo = {
344
+ installSource: 'IADefault_1',
345
+ channel: '-1',
346
+ port: 'Default_1',
347
+ installedVersion: 'Default_1',
348
+ type: 'Default_1',
349
+ revision: 'Default_1',
350
+ version: '9-9.9.9.9'
351
+ };
352
+
353
+ const coordinatorVersion = await this.adapter.zbController.herdsman.getCoordinatorVersion();
354
+
355
+ await this.adapter.getForeignObject('system.adapter.' + this.adapter.namespace, (err, obj) => {
356
+ if (!err && obj) {
357
+ if (obj.common.installedFrom && obj.common.installedFrom.includes('://')) {
358
+ const instFrom = obj.common.installedFrom;
359
+ coordinatorinfo.installSource = instFrom.replace('tarball','commit');
360
+ } else {
361
+ coordinatorinfo.installSource = obj.common.installedFrom;
362
+ }
363
+ }
364
+ try
365
+ {
366
+ coordinatorinfo.port = obj.native.port;
367
+ coordinatorinfo.channel = obj.native.channel;
368
+ coordinatorinfo.installedVersion = obj.native.version;
369
+ if (coordinatorVersion && coordinatorVersion.type && coordinatorVersion.meta) {
370
+ coordinatorinfo.type = coordinatorVersion.type;
371
+ const meta = coordinatorVersion.meta;
372
+ if (meta) {
373
+ if (meta.hasOwnProperty('revision')) coordinatorinfo.revision = meta.revision;
374
+ let vt = 'x-';
375
+ if (meta.hasOwnProperty('transportrev')) vt = meta.transportrev + '-';
376
+ if (meta.hasOwnProperty('product')) vt = vt + meta.product + '.';
377
+ else vt = vt + 'x.';
378
+ if (meta.hasOwnProperty('majorrel')) vt = vt + meta.majorrel + '.';
379
+ else vt = vt + 'x.';
380
+ if (meta.hasOwnProperty('minorrel')) vt = vt + meta.minorrel + '.';
381
+ else vt = vt + 'x.';
382
+ if (meta.hasOwnProperty('maintrel')) vt = vt + meta.maintrel + '.';
383
+ else vt = vt + 'x.';
384
+ coordinatorinfo.version = vt;
385
+ }
386
+ }
387
+ }
388
+ catch { this.warn('exception raised in getCoordinatorInfo');}
389
+
390
+ this.debug('getCoorinatorInfo result: ' + JSON.stringify(coordinatorinfo));
391
+ this.adapter.sendTo(from, command, coordinatorinfo, callback);
392
+ });
393
+ } else {
394
+ this.adapter.sendTo(from, command, {error: 'You need save and run adapter before pairing!'}, callback);
395
+ }
396
+ }
397
+
398
+
399
+ renameDevice(from, command, msg, callback) {
400
+ if (this.stController) {
401
+ const id = msg.id;
402
+ const newName = msg.name;
403
+ this.stController.renameDevice(id, newName);
404
+ this.adapter.sendTo(from, command, {}, callback);
405
+ }
406
+ }
407
+
408
+ deleteDevice(from, command, msg, callback) {
409
+ if (this.zbController && this.stController) {
410
+ this.debug('deleteDevice message: ' + JSON.stringify(msg));
411
+ const id = msg.id;
412
+ const force = msg.force;
413
+ const sysid = id.replace(this.adapter.namespace + '.', '0x');
414
+ const devId = id.replace(this.adapter.namespace + '.', '');
415
+ this.debug('deleteDevice sysid: ' + sysid);
416
+ const dev = this.zbController.getDevice(sysid);
417
+ if (!dev) {
418
+ this.debug('Not found!');
419
+ this.debug('Try delete dev ' + devId + ' from iobroker.');
420
+ this.stController.deleteDeviceStates(devId, () => {
421
+ this.adapter.sendTo(from, command, {}, callback);
422
+ });
423
+ return;
424
+ }
425
+ this.zbController.remove(sysid, force, (err) => {
426
+ if (!err) {
427
+ this.stController.deleteDeviceStates(devId, () => {
428
+ this.adapter.sendTo(from, command, {}, callback);
429
+ });
430
+ } else {
431
+ this.debug('Error on remove! ' + err);
432
+ this.adapter.sendTo(from, command, {error: err}, callback);
433
+ }
434
+ });
435
+ } else {
436
+ this.adapter.sendTo(from, command, {error: 'You need to save and start the adapter!'}, callback);
437
+ }
438
+ }
439
+
440
+ async cleanDeviceStates(from, command, msg, callback) {
441
+ this.info(`State cleanup with ${JSON.stringify(msg)}`);
442
+ const devicesFromDB = await this.zbController.getClients(false);
443
+ for (const device of devicesFromDB) {
444
+ const entity = await this.zbController.resolveEntity(device);
445
+ if (entity) {
446
+ const model = (entity.mapped) ? entity.mapped.model : entity.device.modelID;
447
+ await this.stController.deleteOrphanedDeviceStates(device.ieeeAddr, model, msg.force);
448
+ }
449
+ }
450
+ this.adapter.sendTo(from, command, {}, callback);
451
+ }
452
+
453
+ async getChannels(from, command, message, callback) {
454
+ if (this.zbController) {
455
+ const result = await this.zbController.getChannelsEnergy();
456
+ this.debug('getChannels result: ' + JSON.stringify(result));
457
+ this.adapter.sendTo(from, command, result, callback);
458
+ } else {
459
+ this.adapter.sendTo(
460
+ from, command,
461
+ {error: 'You need to setup serial port and start the adapter before pairing!'},
462
+ callback
463
+ );
464
+ }
465
+ }
466
+
467
+ async setDeviceActivated(from, command, msg, callback) {
468
+ if (this.stController) {
469
+ const id = msg.id;
470
+ const targetstate = msg.deactivated;
471
+ this.stController.setDeviceActivated(id, targetstate);
472
+ this.adapter.sendTo(from, command, {}, callback);
473
+ }
474
+ }
475
+
476
+ async reconfigure(from, command, msg, callback) {
477
+ if (this.zbController) {
478
+ const devid = getZbId(msg.id);
479
+ this.debug(`Reconfigure ${devid}`);
480
+ const entity = await this.zbController.resolveEntity(devid);
481
+ if (entity) {
482
+ try {
483
+ await this.zbController.callExtensionMethod(
484
+ 'doConfigure',
485
+ [entity.device, entity.mapped],
486
+ );
487
+ this.adapter.sendTo(from, command, {}, callback);
488
+ } catch (error) {
489
+ const errmsg = `Reconfigure failed ${entity.device.ieeeAddr} ${entity.device.modelID}, ` +
490
+ `(${error.stack})`;
491
+ this.error(errmsg);
492
+ this.adapter.sendTo(from, command, {error: errmsg}, callback);
493
+ }
494
+ } else {
495
+ this.adapter.sendTo(from, command, {error: 'No device'}, callback);
496
+ }
497
+ }
498
+ }
499
+ }
500
+
501
+ module.exports = Commands;