iobroker.zigbee 1.5.6 → 1.6.8

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/.devcontainer/devcontainer.json +35 -35
  2. package/.devcontainer/docker-compose.yml +51 -51
  3. package/.devcontainer/iobroker/Dockerfile +1 -1
  4. package/.devcontainer/nginx/nginx.conf +32 -32
  5. package/.devcontainer/parcel/Dockerfile +8 -8
  6. package/.devcontainer/parcel/run.sh +6 -6
  7. package/.eslintignore +1 -1
  8. package/.eslintrc.json +36 -36
  9. package/.github/FUNDING.yml +3 -3
  10. package/.github/stale.yml +13 -13
  11. package/.github/workflows/test-and-release.yml +151 -151
  12. package/.travis/wiki.sh +27 -27
  13. package/LICENSE +21 -21
  14. package/README.md +385 -353
  15. package/admin/adapter-settings.js +244 -208
  16. package/admin/admin.js +2704 -2714
  17. package/admin/img/R7060.png +0 -0
  18. package/admin/img/WHD02.png +0 -0
  19. package/admin/img/ikea_E1812.png +0 -0
  20. package/admin/img/philips_hue_lom001.png +0 -0
  21. package/admin/img/tuya_rb280.png +0 -0
  22. package/admin/index.html +159 -159
  23. package/admin/index_m.html +1055 -965
  24. package/admin/moment.min.js +1 -1
  25. package/admin/shuffle.min.js +2 -2
  26. package/admin/tab_m.html +1025 -934
  27. package/admin/vis-network.min.js +26 -26
  28. package/admin/words.js +106 -106
  29. package/docs/de/readme.md +27 -27
  30. package/docs/en/readme.md +30 -30
  31. package/docs/flashing_via_arduino_(en).md +110 -110
  32. package/docs/ru/readme.md +28 -28
  33. package/docs/tutorial/groups-1.png +0 -0
  34. package/docs/tutorial/groups-2.png +0 -0
  35. package/docs/tutorial/tab-dev-1.png +0 -0
  36. package/docs/tutorial/zigbee.png +0 -0
  37. package/io-package.json +317 -290
  38. package/lib/backup.js +132 -132
  39. package/lib/binding.js +325 -325
  40. package/lib/colors.js +460 -460
  41. package/lib/commands.js +435 -434
  42. package/lib/developer.js +148 -144
  43. package/lib/devices.js +3119 -3109
  44. package/lib/exclude.js +168 -168
  45. package/lib/exposes.js +204 -51
  46. package/lib/groups.js +316 -316
  47. package/lib/json.js +60 -60
  48. package/lib/networkmap.js +56 -56
  49. package/lib/ota.js +153 -153
  50. package/lib/rgb.js +225 -225
  51. package/lib/seriallist.js +37 -37
  52. package/lib/states.js +6381 -6322
  53. package/lib/statescontroller.js +502 -495
  54. package/lib/tools.js +54 -54
  55. package/lib/utils.js +151 -132
  56. package/lib/zbBaseExtension.js +31 -27
  57. package/lib/zbDelayedAction.js +151 -146
  58. package/lib/zbDeviceAvailability.js +306 -304
  59. package/lib/zbDeviceConfigure.js +148 -143
  60. package/lib/zbDeviceEvent.js +43 -43
  61. package/lib/zigbeecontroller.js +856 -822
  62. package/main.js +113 -39
  63. package/package.json +74 -73
  64. package/support/docgen.js +93 -93
@@ -1,495 +1,502 @@
1
- 'use strict';
2
-
3
- const EventEmitter = require('events').EventEmitter;
4
- const statesMapping = require('./devices');
5
- const getAdId = require('./utils').getAdId;
6
- const getZbId = require('./utils').getZbId;
7
-
8
-
9
- class StatesController extends EventEmitter {
10
- constructor(adapter) {
11
- super();
12
- this.adapter = adapter;
13
- this.adapter.on('stateChange', this.onStateChange.bind(this));
14
- this.query_device_block = [];
15
- this.debugDevices = undefined;
16
- }
17
-
18
- info(message, data) {
19
- this.emit('log', 'info', message, data);
20
- }
21
-
22
- error(message, data) {
23
- this.emit('log', 'error', message, data);
24
- }
25
-
26
- debug(message, data) {
27
- this.emit('log', 'debug', message, data);
28
- }
29
-
30
- warn(message, data) {
31
- this.emit('log', 'warn', message, data);
32
- }
33
-
34
- getDebugDevices() {
35
- this.debugDevices = [];
36
- this.adapter.getState(this.adapter.namespace + '.info.debugmessages', (err, state) => {
37
- if (state) {
38
- if (typeof(state.val) == 'string' && state.val.length > 2) this.debugDevices = state.val.split(';');
39
- this.info('debug devices set to ' + JSON.stringify(this.debugDevices));
40
- } else {
41
- this.adapter.setObject('info.debugmessages', {
42
- 'type': 'state',
43
- 'common': {
44
- 'name': 'Log changes as warnings for',
45
- 'role': '',
46
- 'type': 'string',
47
- 'read': true,
48
- 'write': true,
49
- },
50
- 'native': {},
51
- });
52
- }
53
- });
54
-
55
- }
56
- onStateChange(id, state){
57
- if (!this.adapter.zbController || !this.adapter.zbController.connected()) return;
58
- if (this.debugDevices === undefined) this.getDebugDevices();
59
- if (state && !state.ack) {
60
- if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) return;
61
- if (id.endsWith('debugmessages')) {
62
- if (typeof(state.val) == 'string' && state.val.length > 2)
63
- this.debugDevices = state.val.split(';');
64
- else {
65
- this.debugDevices = [];
66
- }
67
- return;
68
- }
69
- for (const addressPart of this.debugDevices) {
70
- if (typeof(id) == 'string' && id.indexOf(addressPart) > -1)
71
- {
72
- this.warn(`ELEVATED: User stateChange ${id} ${JSON.stringify(state)}`);
73
- break;
74
- }
75
- }
76
- this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
77
- const devId = getAdId(this.adapter, id); // iobroker device id
78
- let deviceId = getZbId(id); // zigbee device id
79
- // const stateKey = id.split('.')[3];
80
- const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
81
- if (arr[1] === undefined) {
82
- this.warn(`unable to extract id from state ${id}`);
83
- return;
84
- }
85
- const stateKey = arr[1];
86
- this.adapter.getObject(devId, (err, obj) => {
87
- if (obj) {
88
- const model = obj.common.type;
89
- if (!model) return;
90
- if (model === 'group') {
91
- deviceId = parseInt(deviceId.replace('0xgroup_', ''));
92
- }
93
- this.collectOptions(id.split('.')[2], model, options => {
94
- this.publishFromState(deviceId, model, stateKey, state, options);
95
- });
96
- }
97
- });
98
- }
99
- }
100
-
101
- async collectOptions(devId, model, callback) {
102
- const result = {};
103
- // find model states for options and get it values
104
- const devStates = await this.getDevStates('0x'+devId, model);
105
- if (!devStates) {
106
- callback(result);
107
- return;
108
- }
109
- const states = devStates.states.filter((statedesc) => statedesc.isOption || statedesc.inOptions);
110
- if (!states) {
111
- callback(result);
112
- return;
113
- }
114
- let cnt = 0;
115
- const len = states.length;
116
- states.forEach(statedesc => {
117
- const id = this.adapter.namespace + '.' + devId + '.' + statedesc.id;
118
- this.adapter.getState(id, (err, state) => {
119
- cnt = cnt + 1;
120
- if (!err && state) {
121
- result[statedesc.id] = state.val;
122
- }
123
- if (cnt === len) {
124
- callback(result);
125
- }
126
- });
127
- });
128
- if (!len) callback(result);
129
- }
130
-
131
- async getDevStates(deviceId, model) {
132
- try {
133
- let states = [];
134
- let stateModel;
135
- if (model === 'group') {
136
- states = statesMapping.groupStates;
137
- } else {
138
- stateModel = statesMapping.findModel(model);
139
- if (!stateModel) {
140
- this.error('Device ' + deviceId + ' "' + model + '" not described in statesMapping.');
141
- return;
142
- }
143
- states = stateModel.states;
144
- if (typeof states === 'function' && !states.prototype) {
145
- const entity = await this.adapter.zbController.resolveEntity(deviceId);
146
- if (entity)
147
- states = states(entity);
148
- }
149
- }
150
- return {states: states, stateModel: stateModel};
151
- } catch (error) {
152
- this.error(`Error getDevStates for ${deviceId}. Error: ${error.stack}`);
153
- }
154
- }
155
-
156
- async publishFromState(deviceId, model, stateKey, state, options) {
157
- if (this.debugDevices === undefined) this.getDebugDevices();
158
- this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
159
- for (const addressPart of this.debugDevices) {
160
- if (typeof(deviceId) == 'string' && deviceId.indexOf(addressPart) > -1)
161
- {
162
- this.warn(`ELEVATED Change state '${stateKey}' at device ${deviceId} type '${model}'`);
163
- break;
164
- }
165
- }
166
- const devStates = await this.getDevStates(deviceId, model);
167
- if (!devStates) {
168
- return;
169
- }
170
- const commonStates = statesMapping.commonStates.find((statedesc) => stateKey === statedesc.id);
171
- const stateDesc = (commonStates === undefined ? devStates.states.find((statedesc) => stateKey === statedesc.id) : commonStates);
172
- const stateModel = devStates.stateModel;
173
- if (!stateDesc) {
174
- this.error(`No state available for '${model}' with key '${stateKey}'`);
175
- return;
176
- }
177
-
178
- const value = state.val;
179
- if (value === undefined || value === '')
180
- return;
181
- let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0}];
182
- if (stateModel && stateModel.linkedStates) {
183
- stateModel.linkedStates.forEach((linkedFunct) => {
184
- try {
185
- if (typeof linkedFunct === 'function') {
186
- const res = linkedFunct(stateDesc, value, options, this.adapter.config.disableQueue);
187
- if (res) {
188
- stateList = stateList.concat(res);
189
- }
190
- }
191
- else {
192
- this.warn('publish from State - LinkedState is not a function ' + JSON.stringify(linkedFunct));
193
- }
194
- } catch (e) {
195
- this.error('Exception caught in publishfromstate');
196
- }
197
-
198
- });
199
- // sort by index
200
- stateList.sort((a, b) => {
201
- return a.index - b.index;
202
- });
203
- }
204
-
205
- // holds the states for for read after write requests
206
- let readAfterWriteStates = [];
207
- if (stateModel && stateModel.readAfterWriteStates) {
208
- stateModel.readAfterWriteStates.forEach((readAfterWriteStateDesc) => {
209
- readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id);
210
- });
211
- }
212
-
213
- this.emit('changed', deviceId, model, stateModel, stateList, options);
214
- }
215
-
216
- renameDevice(id, newName) {
217
- this.adapter.extendObject(id, {common: {name: newName}});
218
- }
219
-
220
- deleteDeviceStates(devId, callback) {
221
- this.adapter.getStatesOf(devId, (err, states) => {
222
- if (!err && states) {
223
- states.forEach((state) => {
224
- this.adapter.deleteState(devId, null, state._id);
225
- });
226
- }
227
- this.adapter.deleteDevice(devId, () => {
228
- callback && callback();
229
- });
230
- });
231
- }
232
- // eslint-disable-next-line no-unused-vars
233
- async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback) {
234
- const devStates = await this.getDevStates(ieeeAddr, model);
235
- const commonStates = statesMapping.commonStates;
236
- const devId = ieeeAddr.substr(2);
237
- this.adapter.getStatesOf(devId, (err, states) => {
238
- if (!err && states) {
239
- states.forEach((state) => {
240
- let statename = state._id;
241
- const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
242
- if (arr[1] === undefined) {
243
- this.warn(`unable to extract id from state ${statename}`);
244
- const idx = statename.lastIndexOf('.');
245
- if (idx > -1) statename = statename.slice(idx+1);
246
- } else {
247
- statename = arr[1];
248
- }
249
- if (commonStates.find((statedesc) => statename === statedesc.id) === undefined &&
250
- devStates.states.find((statedesc) => statename === statedesc.id) === undefined && statename != 'groups') {
251
- if (state.common.hasOwnProperty('custom') && !force) {
252
- this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
253
- } else {
254
- this.info(`deleting disconnected state ${JSON.stringify(statename)} of ${devId} `);
255
- this.adapter.deleteState(devId, null, state._id);
256
- }
257
- }
258
- else {
259
- this.debug(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
260
- }
261
- });
262
- }
263
- });
264
- }
265
-
266
- updateStateWithTimeout(dev_id, name, value, common, timeout, outValue) {
267
- this.updateState(dev_id, name, value, common);
268
- setTimeout(() => this.updateState(dev_id, name, outValue, common), timeout);
269
- }
270
-
271
- updateState(devId, name, value, common) {
272
- this.adapter.getObject(devId, (err, obj) => {
273
- if (obj) {
274
- const new_common = {name: name};
275
- const id = devId + '.' + name;
276
- const new_name = obj.common.name;
277
- if (common) {
278
- if (common.name !== undefined) {
279
- new_common.name = common.name;
280
- }
281
- if (common.type !== undefined) {
282
- new_common.type = common.type;
283
- }
284
- if (common.unit !== undefined) {
285
- new_common.unit = common.unit;
286
- }
287
- if (common.states !== undefined) {
288
- new_common.states = common.states;
289
- }
290
- if (common.read !== undefined) {
291
- new_common.read = common.read;
292
- }
293
- if (common.write !== undefined) {
294
- new_common.write = common.write;
295
- }
296
- if (common.role !== undefined) {
297
- new_common.role = common.role;
298
- }
299
- if (common.min !== undefined) {
300
- new_common.min = common.min;
301
- }
302
- if (common.max !== undefined) {
303
- new_common.max = common.max;
304
- }
305
- if (common.icon !== undefined) {
306
- new_common.icon = common.icon;
307
- }
308
- }
309
- // check if state exist
310
- this.adapter.getObject(id, (err, stobj) => {
311
- let hasChanges = false;
312
- if (stobj) {
313
- // update state - not change name and role (user can it changed)
314
- if (stobj.common.name)
315
- delete new_common.name;
316
- else
317
- new_common.name = new_name + ' ' + new_common.name;
318
- delete new_common.role;
319
-
320
- // check whether any common property is different
321
- if (stobj.common) {
322
- for (const property in new_common) {
323
- if (stobj.common.hasOwnProperty(property)) {
324
- if (stobj.common[property] === new_common[property]) {
325
- delete new_common[property];
326
- } else {
327
- hasChanges = true;
328
- }
329
- }
330
- }
331
- }
332
- } else {
333
- hasChanges = true;
334
- }
335
-
336
- // only change object when any common property has changed
337
- if (hasChanges) {
338
- this.adapter.extendObject(id, {type: 'state', common: new_common, native: {} }, () => {
339
- value !== undefined && this.adapter.setState(id, value, true);
340
- });
341
- } else if (value !== undefined) {
342
- if (typeof(value) !== 'object') {
343
- this.adapter.setState(id, value, true);
344
- } else this.warn('set state with object for id :' + JSON.stringify(id) + ' '+ JSON.stringify(value));
345
-
346
- }
347
-
348
- });
349
- } else {
350
- this.debug(`Wrong device ${devId} ${JSON.stringify(obj)}`);
351
- }
352
- });
353
- }
354
-
355
- updateDev(dev_id, dev_name, model, callback) {
356
- const id = '' + dev_id;
357
- const modelDesc = statesMapping.findModel(model);
358
- let icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
359
- // clear icon if it external
360
- icon = (icon.startsWith('http')) ? undefined : icon;
361
- this.adapter.setObjectNotExists(id, {
362
- type: 'device',
363
- // actually this is an error, so device.common has no attribute type. It must be in native part
364
- common: {name: dev_name, type: model, icon: icon},
365
- native: {id: dev_id}
366
- }, () => {
367
- // update type and icon
368
- this.adapter.extendObject(id, {common: {type: model, icon: icon}}, callback);
369
- });
370
- }
371
-
372
- async syncDevStates(dev, model) {
373
- const devId = dev.ieeeAddr.substr(2),
374
- hasGroups = dev.type === 'Router';
375
- // devId - iobroker device id
376
- const devStates = await this.getDevStates(dev.ieeeAddr, model);
377
- if (!devStates) {
378
- return;
379
- }
380
- const states = statesMapping.commonStates.concat(devStates.states)
381
- .concat((hasGroups) ? [statesMapping.groupsState] : []);
382
-
383
- for (const stateInd in states) {
384
- if (!states.hasOwnProperty(stateInd)) continue;
385
-
386
- const statedesc = states[stateInd];
387
- if (statedesc === undefined)
388
- {
389
- this.error(`syncDevStates: Illegal state in ${JSON.stringify(dev)} - ${JSON.stringify(stateInd)}`);
390
- return;
391
- }
392
- // Filter out non routers or devices that are battery driven for the availability flag
393
- if (statedesc.id === 'available')
394
- if (!(dev.type === 'Router') || dev.powerSource === 'Battery')
395
- continue;
396
- // lazy states
397
- if (statedesc.lazy) continue;
398
-
399
- const common = {
400
- name: statedesc.name,
401
- type: statedesc.type,
402
- unit: statedesc.unit,
403
- read: statedesc.read,
404
- write: statedesc.write,
405
- icon: statedesc.icon,
406
- role: statedesc.role,
407
- min: statedesc.min,
408
- max: statedesc.max,
409
- states: statedesc.states,
410
- };
411
- this.updateState(devId, statedesc.id, undefined, common);
412
- }
413
- }
414
-
415
-
416
- async getExcludeExposes(allExcludesObj) {
417
- statesMapping.fillStatesWithExposes(allExcludesObj);
418
- }
419
-
420
-
421
- async publishToState(devId, model, payload) {
422
- const devStates = await this.getDevStates('0x'+devId, model);
423
- let has_debug=false;
424
- if (this.debugDevices === undefined) this.getDebugDevices();
425
- for (const addressPart of this.debugDevices) {
426
- if (typeof(devId) == 'string' && devId.indexOf(addressPart) > -1)
427
- {
428
- this.warn(`ELEVATED publishToState: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
429
- has_debug = true;
430
- break;
431
- }
432
- }
433
- if (!devStates) {
434
- return;
435
- }
436
- // find states for payload
437
- if (devStates.states !== undefined) {
438
- const states = statesMapping.commonStates.concat(
439
- devStates.states.filter((statedesc) => payload.hasOwnProperty(statedesc.prop || statedesc.id))
440
- );
441
- for (const stateInd in states) {
442
- const statedesc = states[stateInd];
443
- let value;
444
- if (statedesc.getter) {
445
- value = statedesc.getter(payload);
446
- } else {
447
- value = payload[statedesc.prop || statedesc.id];
448
- }
449
- // checking value
450
- if (value === undefined) continue;
451
- let stateID = statedesc.id;
452
-
453
- if (has_debug) {
454
- this.warn(`ELEVATED publishToState: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`);
455
- }
456
-
457
- const common = {
458
- name: statedesc.name,
459
- type: statedesc.type,
460
- unit: statedesc.unit,
461
- read: statedesc.read,
462
- write: statedesc.write,
463
- icon: statedesc.icon,
464
- role: statedesc.role,
465
- min: statedesc.min,
466
- max: statedesc.max,
467
- };
468
- if ( (typeof(value)== 'object') && (value.hasOwnProperty('stateid'))) {
469
- stateID = stateID + '.' + value.stateid;
470
- if (value.hasOwnProperty('unit')) common.unit = value.unit;
471
- common.name = (value.name? value.name:value.stateid);
472
- common.role = (value.role ? 'value.'+value.role:'number');
473
- value = value.value;
474
-
475
- }
476
- // if need return value to back after timeout
477
- if (statedesc.isEvent) {
478
- this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, !value);
479
- } else {
480
- if (statedesc.prepublish) {
481
- this.collectOptions(devId, model, (options) => {
482
- statedesc.prepublish(devId, value, (newvalue) => {
483
- this.updateState(devId, stateID, newvalue, common);
484
- }, options);
485
- });
486
- } else {
487
- this.updateState(devId, stateID, value, common);
488
- }
489
- }
490
- }
491
- }
492
- }
493
- }
494
-
495
- module.exports = StatesController;
1
+ 'use strict';
2
+
3
+ const EventEmitter = require('events').EventEmitter;
4
+ const statesMapping = require('./devices');
5
+ const getAdId = require('./utils').getAdId;
6
+ const getZbId = require('./utils').getZbId;
7
+
8
+
9
+ class StatesController extends EventEmitter {
10
+ constructor(adapter) {
11
+ super();
12
+ this.adapter = adapter;
13
+ this.adapter.on('stateChange', this.onStateChange.bind(this));
14
+ this.query_device_block = [];
15
+ this.debugDevices = undefined;
16
+ }
17
+
18
+ info(message, data) {
19
+ this.emit('log', 'info', message, data);
20
+ }
21
+
22
+ error(message, data) {
23
+ this.emit('log', 'error', message, data);
24
+ }
25
+
26
+ debug(message, data) {
27
+ this.emit('log', 'debug', message, data);
28
+ }
29
+
30
+ warn(message, data) {
31
+ this.emit('log', 'warn', message, data);
32
+ }
33
+
34
+ sendError(error, message) {
35
+ this.adapter.sendError(error, message);
36
+ }
37
+
38
+ getDebugDevices() {
39
+ this.debugDevices = [];
40
+ this.adapter.getState(this.adapter.namespace + '.info.debugmessages', (err, state) => {
41
+ if (state) {
42
+ if (typeof(state.val) == 'string' && state.val.length > 2) this.debugDevices = state.val.split(';');
43
+ this.info('debug devices set to ' + JSON.stringify(this.debugDevices));
44
+ } else {
45
+ this.adapter.setObject('info.debugmessages', {
46
+ 'type': 'state',
47
+ 'common': {
48
+ 'name': 'Log changes as warnings for',
49
+ 'role': '',
50
+ 'type': 'string',
51
+ 'read': true,
52
+ 'write': true,
53
+ },
54
+ 'native': {},
55
+ });
56
+ }
57
+ });
58
+
59
+ }
60
+ onStateChange(id, state){
61
+ if (!this.adapter.zbController || !this.adapter.zbController.connected()) return;
62
+ if (this.debugDevices === undefined) this.getDebugDevices();
63
+ if (state && !state.ack) {
64
+ if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) return;
65
+ if (id.endsWith('debugmessages')) {
66
+ if (typeof(state.val) == 'string' && state.val.length > 2)
67
+ this.debugDevices = state.val.split(';');
68
+ else {
69
+ this.debugDevices = [];
70
+ }
71
+ return;
72
+ }
73
+ for (const addressPart of this.debugDevices) {
74
+ if (typeof(id) == 'string' && id.indexOf(addressPart) > -1)
75
+ {
76
+ this.warn(`ELEVATED: User stateChange ${id} ${JSON.stringify(state)}`);
77
+ break;
78
+ }
79
+ }
80
+ this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
81
+ const devId = getAdId(this.adapter, id); // iobroker device id
82
+ let deviceId = getZbId(id); // zigbee device id
83
+ // const stateKey = id.split('.')[3];
84
+ const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
85
+ if (arr[1] === undefined) {
86
+ this.warn(`unable to extract id from state ${id}`);
87
+ return;
88
+ }
89
+ const stateKey = arr[1];
90
+ this.adapter.getObject(devId, (err, obj) => {
91
+ if (obj) {
92
+ const model = obj.common.type;
93
+ if (!model) return;
94
+ if (model === 'group') {
95
+ deviceId = parseInt(deviceId.replace('0xgroup_', ''));
96
+ }
97
+ this.collectOptions(id.split('.')[2], model, options => {
98
+ this.publishFromState(deviceId, model, stateKey, state, options);
99
+ });
100
+ }
101
+ });
102
+ }
103
+ }
104
+
105
+ async collectOptions(devId, model, callback) {
106
+ const result = {};
107
+ // find model states for options and get it values
108
+ const devStates = await this.getDevStates('0x'+devId, model);
109
+ if (!devStates) {
110
+ callback(result);
111
+ return;
112
+ }
113
+ const states = devStates.states.filter((statedesc) => statedesc.isOption || statedesc.inOptions);
114
+ if (!states) {
115
+ callback(result);
116
+ return;
117
+ }
118
+ let cnt = 0;
119
+ const len = states.length;
120
+ states.forEach(statedesc => {
121
+ const id = this.adapter.namespace + '.' + devId + '.' + statedesc.id;
122
+ this.adapter.getState(id, (err, state) => {
123
+ cnt = cnt + 1;
124
+ if (!err && state) {
125
+ result[statedesc.id] = state.val;
126
+ }
127
+ if (cnt === len) {
128
+ callback(result);
129
+ }
130
+ });
131
+ });
132
+ if (!len) callback(result);
133
+ }
134
+
135
+ async getDevStates(deviceId, model) {
136
+ try {
137
+ let states = [];
138
+ let stateModel;
139
+ if (model === 'group') {
140
+ states = statesMapping.groupStates;
141
+ } else {
142
+ stateModel = statesMapping.findModel(model);
143
+ if (!stateModel) {
144
+ this.error('Device ' + deviceId + ' "' + model + '" not described in statesMapping.');
145
+ states = statesMapping.commonStates;
146
+ } else {
147
+ states = stateModel.states;
148
+ }
149
+ if (typeof states === 'function' && !states.prototype) {
150
+ const entity = await this.adapter.zbController.resolveEntity(deviceId);
151
+ if (entity)
152
+ states = states(entity);
153
+ }
154
+ }
155
+ return {states: states, stateModel: stateModel};
156
+ } catch (error) {
157
+ this.sendError(error);
158
+ this.error(`Error getDevStates for ${deviceId}. Error: ${error.stack}`);
159
+ }
160
+ }
161
+
162
+ async publishFromState(deviceId, model, stateKey, state, options) {
163
+ if (this.debugDevices === undefined) this.getDebugDevices();
164
+ this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
165
+ for (const addressPart of this.debugDevices) {
166
+ if (typeof(deviceId) == 'string' && deviceId.indexOf(addressPart) > -1)
167
+ {
168
+ this.warn(`ELEVATED Change state '${stateKey}' at device ${deviceId} type '${model}'`);
169
+ break;
170
+ }
171
+ }
172
+ const devStates = await this.getDevStates(deviceId, model);
173
+ if (!devStates) {
174
+ return;
175
+ }
176
+ const commonStates = statesMapping.commonStates.find((statedesc) => stateKey === statedesc.id);
177
+ const stateDesc = (commonStates === undefined ? devStates.states.find((statedesc) => stateKey === statedesc.id) : commonStates);
178
+ const stateModel = devStates.stateModel;
179
+ if (!stateDesc) {
180
+ this.error(`No state available for '${model}' with key '${stateKey}'`);
181
+ return;
182
+ }
183
+
184
+ const value = state.val;
185
+ if (value === undefined || value === '')
186
+ return;
187
+ let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0}];
188
+ if (stateModel && stateModel.linkedStates) {
189
+ stateModel.linkedStates.forEach((linkedFunct) => {
190
+ try {
191
+ if (typeof linkedFunct === 'function') {
192
+ const res = linkedFunct(stateDesc, value, options, this.adapter.config.disableQueue);
193
+ if (res) {
194
+ stateList = stateList.concat(res);
195
+ }
196
+ }
197
+ else {
198
+ this.warn('publish from State - LinkedState is not a function ' + JSON.stringify(linkedFunct));
199
+ }
200
+ } catch (e) {
201
+ this.sendError(e);
202
+ this.error('Exception caught in publishfromstate');
203
+ }
204
+
205
+ });
206
+ // sort by index
207
+ stateList.sort((a, b) => {
208
+ return a.index - b.index;
209
+ });
210
+ }
211
+
212
+ // holds the states for for read after write requests
213
+ let readAfterWriteStates = [];
214
+ if (stateModel && stateModel.readAfterWriteStates) {
215
+ stateModel.readAfterWriteStates.forEach((readAfterWriteStateDesc) => {
216
+ readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id);
217
+ });
218
+ }
219
+
220
+ this.emit('changed', deviceId, model, stateModel, stateList, options);
221
+ }
222
+
223
+ renameDevice(id, newName) {
224
+ this.adapter.extendObject(id, {common: {name: newName}});
225
+ }
226
+
227
+ deleteDeviceStates(devId, callback) {
228
+ this.adapter.getStatesOf(devId, (err, states) => {
229
+ if (!err && states) {
230
+ states.forEach((state) => {
231
+ this.adapter.deleteState(devId, null, state._id);
232
+ });
233
+ }
234
+ this.adapter.deleteDevice(devId, () => {
235
+ callback && callback();
236
+ });
237
+ });
238
+ }
239
+ // eslint-disable-next-line no-unused-vars
240
+ async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback) {
241
+ const devStates = await this.getDevStates(ieeeAddr, model);
242
+ const commonStates = statesMapping.commonStates;
243
+ const devId = ieeeAddr.substr(2);
244
+ this.adapter.getStatesOf(devId, (err, states) => {
245
+ if (!err && states) {
246
+ states.forEach((state) => {
247
+ let statename = state._id;
248
+ const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
249
+ if (arr[1] === undefined) {
250
+ this.warn(`unable to extract id from state ${statename}`);
251
+ const idx = statename.lastIndexOf('.');
252
+ if (idx > -1) statename = statename.slice(idx+1);
253
+ } else {
254
+ statename = arr[1];
255
+ }
256
+ if (commonStates.find((statedesc) => statename === statedesc.id) === undefined &&
257
+ devStates.states.find((statedesc) => statename === statedesc.id) === undefined && statename != 'groups') {
258
+ if (state.common.hasOwnProperty('custom') && !force) {
259
+ this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
260
+ } else {
261
+ this.info(`deleting disconnected state ${JSON.stringify(statename)} of ${devId} `);
262
+ this.adapter.deleteState(devId, null, state._id);
263
+ }
264
+ }
265
+ else {
266
+ this.debug(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
267
+ }
268
+ });
269
+ }
270
+ });
271
+ }
272
+
273
+ updateStateWithTimeout(dev_id, name, value, common, timeout, outValue) {
274
+ this.updateState(dev_id, name, value, common);
275
+ setTimeout(() => this.updateState(dev_id, name, outValue, common), timeout);
276
+ }
277
+
278
+ updateState(devId, name, value, common) {
279
+ this.adapter.getObject(devId, (err, obj) => {
280
+ if (obj) {
281
+ const new_common = {name: name};
282
+ const id = devId + '.' + name;
283
+ const new_name = obj.common.name;
284
+ if (common) {
285
+ if (common.name !== undefined) {
286
+ new_common.name = common.name;
287
+ }
288
+ if (common.type !== undefined) {
289
+ new_common.type = common.type;
290
+ }
291
+ if (common.unit !== undefined) {
292
+ new_common.unit = common.unit;
293
+ }
294
+ if (common.states !== undefined) {
295
+ new_common.states = common.states;
296
+ }
297
+ if (common.read !== undefined) {
298
+ new_common.read = common.read;
299
+ }
300
+ if (common.write !== undefined) {
301
+ new_common.write = common.write;
302
+ }
303
+ if (common.role !== undefined) {
304
+ new_common.role = common.role;
305
+ }
306
+ if (common.min !== undefined) {
307
+ new_common.min = common.min;
308
+ }
309
+ if (common.max !== undefined) {
310
+ new_common.max = common.max;
311
+ }
312
+ if (common.icon !== undefined) {
313
+ new_common.icon = common.icon;
314
+ }
315
+ }
316
+ // check if state exist
317
+ this.adapter.getObject(id, (err, stobj) => {
318
+ let hasChanges = false;
319
+ if (stobj) {
320
+ // update state - not change name and role (user can it changed)
321
+ if (stobj.common.name)
322
+ delete new_common.name;
323
+ else
324
+ new_common.name = new_name + ' ' + new_common.name;
325
+ delete new_common.role;
326
+
327
+ // check whether any common property is different
328
+ if (stobj.common) {
329
+ for (const property in new_common) {
330
+ if (stobj.common.hasOwnProperty(property)) {
331
+ if (stobj.common[property] === new_common[property]) {
332
+ delete new_common[property];
333
+ } else {
334
+ hasChanges = true;
335
+ }
336
+ }
337
+ }
338
+ }
339
+ } else {
340
+ hasChanges = true;
341
+ }
342
+
343
+ // only change object when any common property has changed
344
+ if (hasChanges) {
345
+ this.adapter.extendObject(id, {type: 'state', common: new_common, native: {} }, () => {
346
+ value !== undefined && this.adapter.setState(id, value, true);
347
+ });
348
+ } else if (value !== undefined) {
349
+ if (typeof(value) !== 'object') {
350
+ this.adapter.setState(id, value, true);
351
+ } else this.warn('set state with object for id :' + JSON.stringify(id) + ' '+ JSON.stringify(value));
352
+
353
+ }
354
+
355
+ });
356
+ } else {
357
+ this.debug(`Wrong device ${devId} ${JSON.stringify(obj)}`);
358
+ }
359
+ });
360
+ }
361
+
362
+ updateDev(dev_id, dev_name, model, callback) {
363
+ const id = '' + dev_id;
364
+ const modelDesc = statesMapping.findModel(model);
365
+ let icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
366
+ // clear icon if it external
367
+ icon = (icon.startsWith('http')) ? undefined : icon;
368
+ this.adapter.setObjectNotExists(id, {
369
+ type: 'device',
370
+ // actually this is an error, so device.common has no attribute type. It must be in native part
371
+ common: {name: dev_name, type: model, icon: icon},
372
+ native: {id: dev_id}
373
+ }, () => {
374
+ // update type and icon
375
+ this.adapter.extendObject(id, {common: {type: model, icon: icon}}, callback);
376
+ });
377
+ }
378
+
379
+ async syncDevStates(dev, model) {
380
+ const devId = dev.ieeeAddr.substr(2),
381
+ hasGroups = dev.type === 'Router';
382
+ // devId - iobroker device id
383
+ const devStates = await this.getDevStates(dev.ieeeAddr, model);
384
+ if (!devStates) {
385
+ return;
386
+ }
387
+ const states = statesMapping.commonStates.concat(devStates.states)
388
+ .concat((hasGroups) ? [statesMapping.groupsState] : []);
389
+
390
+ for (const stateInd in states) {
391
+ if (!states.hasOwnProperty(stateInd)) continue;
392
+
393
+ const statedesc = states[stateInd];
394
+ if (statedesc === undefined)
395
+ {
396
+ this.error(`syncDevStates: Illegal state in ${JSON.stringify(dev)} - ${JSON.stringify(stateInd)}`);
397
+ return;
398
+ }
399
+ // Filter out non routers or devices that are battery driven for the availability flag
400
+ if (statedesc.id === 'available')
401
+ if (!(dev.type === 'Router') || dev.powerSource === 'Battery')
402
+ continue;
403
+ // lazy states
404
+ if (statedesc.lazy) continue;
405
+
406
+ const common = {
407
+ name: statedesc.name,
408
+ type: statedesc.type,
409
+ unit: statedesc.unit,
410
+ read: statedesc.read,
411
+ write: statedesc.write,
412
+ icon: statedesc.icon,
413
+ role: statedesc.role,
414
+ min: statedesc.min,
415
+ max: statedesc.max,
416
+ states: statedesc.states,
417
+ };
418
+ this.updateState(devId, statedesc.id, undefined, common);
419
+ }
420
+ }
421
+
422
+
423
+ async getExcludeExposes(allExcludesObj) {
424
+ statesMapping.fillStatesWithExposes(allExcludesObj);
425
+ }
426
+
427
+
428
+ async publishToState(devId, model, payload) {
429
+ const devStates = await this.getDevStates('0x'+devId, model);
430
+ let has_debug=false;
431
+ if (this.debugDevices === undefined) this.getDebugDevices();
432
+ for (const addressPart of this.debugDevices) {
433
+ if (typeof(devId) == 'string' && devId.indexOf(addressPart) > -1)
434
+ {
435
+ this.warn(`ELEVATED publishToState: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
436
+ has_debug = true;
437
+ break;
438
+ }
439
+ }
440
+ if (!devStates) {
441
+ return;
442
+ }
443
+ // find states for payload
444
+ if (devStates.states !== undefined) {
445
+ const states = statesMapping.commonStates.concat(
446
+ devStates.states.filter((statedesc) => payload.hasOwnProperty(statedesc.prop || statedesc.id))
447
+ );
448
+ for (const stateInd in states) {
449
+ const statedesc = states[stateInd];
450
+ let value;
451
+ if (statedesc.getter) {
452
+ value = statedesc.getter(payload);
453
+ } else {
454
+ value = payload[statedesc.prop || statedesc.id];
455
+ }
456
+ // checking value
457
+ if (value === undefined) continue;
458
+ let stateID = statedesc.id;
459
+
460
+ if (has_debug) {
461
+ this.warn(`ELEVATED publishToState: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`);
462
+ }
463
+
464
+ const common = {
465
+ name: statedesc.name,
466
+ type: statedesc.type,
467
+ unit: statedesc.unit,
468
+ read: statedesc.read,
469
+ write: statedesc.write,
470
+ icon: statedesc.icon,
471
+ role: statedesc.role,
472
+ min: statedesc.min,
473
+ max: statedesc.max,
474
+ };
475
+ if ( (typeof(value)== 'object') && (value.hasOwnProperty('stateid'))) {
476
+ stateID = stateID + '.' + value.stateid;
477
+ if (value.hasOwnProperty('unit')) common.unit = value.unit;
478
+ common.name = (value.name? value.name:value.stateid);
479
+ common.role = (value.role ? 'value.'+value.role:'number');
480
+ value = value.value;
481
+
482
+ }
483
+ // if need return value to back after timeout
484
+ if (statedesc.isEvent) {
485
+ this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, !value);
486
+ } else {
487
+ if (statedesc.prepublish) {
488
+ this.collectOptions(devId, model, (options) => {
489
+ statedesc.prepublish(devId, value, (newvalue) => {
490
+ this.updateState(devId, stateID, newvalue, common);
491
+ }, options);
492
+ });
493
+ } else {
494
+ this.updateState(devId, stateID, value, common);
495
+ }
496
+ }
497
+ }
498
+ }
499
+ }
500
+ }
501
+
502
+ module.exports = StatesController;