iobroker.zigbee 1.7.6 → 1.8.0

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