iobroker.zigbee 1.8.10 → 1.8.12

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 (104) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +411 -400
  3. package/admin/adapter-settings.js +244 -244
  4. package/admin/admin.js +2981 -2981
  5. package/admin/i18n/de/translations.json +108 -108
  6. package/admin/i18n/en/translations.json +108 -108
  7. package/admin/i18n/es/translations.json +102 -102
  8. package/admin/i18n/fr/translations.json +108 -108
  9. package/admin/i18n/it/translations.json +102 -102
  10. package/admin/i18n/nl/translations.json +108 -108
  11. package/admin/i18n/pl/translations.json +108 -108
  12. package/admin/i18n/pt/translations.json +102 -102
  13. package/admin/i18n/ru/translations.json +108 -108
  14. package/admin/i18n/uk/translations.json +108 -108
  15. package/admin/i18n/zh-cn/translations.json +102 -102
  16. package/admin/img/philips_hue_lom001.png +0 -0
  17. package/admin/index.html +159 -159
  18. package/admin/index_m.html +1356 -1356
  19. package/admin/moment.min.js +1 -1
  20. package/admin/shuffle.min.js +2 -2
  21. package/admin/tab_m.html +1009 -1009
  22. package/admin/vis-network.min.css +1 -1
  23. package/admin/vis-network.min.js +27 -27
  24. package/admin/words.js +110 -110
  25. package/docs/de/basedocu.md +19 -19
  26. package/docs/de/img/Bild10.png +0 -0
  27. package/docs/de/img/Bild12.png +0 -0
  28. package/docs/de/img/Bild13.png +0 -0
  29. package/docs/de/img/Bild14.png +0 -0
  30. package/docs/de/img/Bild15.png +0 -0
  31. package/docs/de/img/Bild16.png +0 -0
  32. package/docs/de/img/Bild17.png +0 -0
  33. package/docs/de/img/Bild18.png +0 -0
  34. package/docs/de/img/Bild19.png +0 -0
  35. package/docs/de/img/Bild2.png +0 -0
  36. package/docs/de/img/Bild20.png +0 -0
  37. package/docs/de/img/Bild21.png +0 -0
  38. package/docs/de/img/Bild22.png +0 -0
  39. package/docs/de/img/Bild23.png +0 -0
  40. package/docs/de/img/Bild24.png +0 -0
  41. package/docs/de/img/Bild25.png +0 -0
  42. package/docs/de/img/Bild26.png +0 -0
  43. package/docs/de/img/Bild28.png +0 -0
  44. package/docs/de/img/Bild3.png +0 -0
  45. package/docs/de/img/Bild30.png +0 -0
  46. package/docs/de/img/Bild31.png +0 -0
  47. package/docs/de/img/Bild32.png +0 -0
  48. package/docs/de/img/Bild33.png +0 -0
  49. package/docs/de/img/Bild34.png +0 -0
  50. package/docs/de/img/Bild35.png +0 -0
  51. package/docs/de/img/Bild36.png +0 -0
  52. package/docs/de/img/Bild37.png +0 -0
  53. package/docs/de/img/Bild4.png +0 -0
  54. package/docs/de/img/Bild5.jpg +0 -0
  55. package/docs/de/img/Bild6.png +0 -0
  56. package/docs/de/img/Bild7.png +0 -0
  57. package/docs/de/img/Bild8.png +0 -0
  58. package/docs/de/img/Bild9.png +0 -0
  59. package/docs/de/img/software1.jpg +0 -0
  60. package/docs/de/img/sonoff.png +0 -0
  61. package/docs/de/readme.md +126 -27
  62. package/docs/en/img/Bild13.png +0 -0
  63. package/docs/en/img/Bild18.png +0 -0
  64. package/docs/en/img/Bild23.png +0 -0
  65. package/docs/en/img/Bild25.png +0 -0
  66. package/docs/en/img/Bild26.png +0 -0
  67. package/docs/en/img/Bild4.png +0 -0
  68. package/docs/en/img/Bild9.png +0 -0
  69. package/docs/en/img/software1.jpg +0 -0
  70. package/docs/en/readme.md +128 -30
  71. package/docs/flashing_via_arduino_(en).md +110 -110
  72. package/docs/ru/readme.md +28 -28
  73. package/docs/tutorial/groups-1.png +0 -0
  74. package/docs/tutorial/groups-2.png +0 -0
  75. package/docs/tutorial/tab-dev-1.png +0 -0
  76. package/io-package.json +9 -17
  77. package/lib/backup.js +171 -171
  78. package/lib/binding.js +319 -319
  79. package/lib/colors.js +465 -465
  80. package/lib/commands.js +534 -534
  81. package/lib/developer.js +145 -145
  82. package/lib/devices.js +3135 -3135
  83. package/lib/exclude.js +162 -162
  84. package/lib/exposes.js +873 -830
  85. package/lib/groups.js +345 -345
  86. package/lib/json.js +59 -59
  87. package/lib/networkmap.js +55 -55
  88. package/lib/ota.js +198 -195
  89. package/lib/rgb.js +297 -297
  90. package/lib/seriallist.js +48 -48
  91. package/lib/states.js +6420 -6420
  92. package/lib/statescontroller.js +693 -693
  93. package/lib/tools.js +54 -54
  94. package/lib/utils.js +163 -163
  95. package/lib/zbBaseExtension.js +36 -36
  96. package/lib/zbDelayedAction.js +144 -144
  97. package/lib/zbDeviceAvailability.js +319 -319
  98. package/lib/zbDeviceConfigure.js +147 -147
  99. package/lib/zbDeviceEvent.js +48 -48
  100. package/lib/zigbeecontroller.js +989 -989
  101. package/main.js +1 -1
  102. package/package.json +10 -10
  103. package/support/docgen.js +93 -93
  104. /package/admin/img/{paumann_spot.png → paulmann_spot.png} +0 -0
@@ -1,693 +1,693 @@
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
- const fs = require('fs');
8
- const request = require('request');
9
-
10
- let savedDeviceNames = {};
11
- let knownUndefinedDevices = {};
12
-
13
- class StatesController extends EventEmitter {
14
- constructor(adapter) {
15
- super();
16
- this.adapter = adapter;
17
- this.adapter.on('stateChange', this.onStateChange.bind(this));
18
- this.query_device_block = [];
19
- this.debugDevices = undefined;
20
- const fn = adapter.expandFileName('dev_names.json');
21
- this.dev_names_fn = fn.replace('.', '_');
22
- this.retTimeoutHandle = null;
23
- fs.readFile(this.dev_names_fn, (err, data) => {
24
- if (!err) {
25
- try {
26
- savedDeviceNames = JSON.parse(data);
27
- } catch {
28
- savedDeviceNames = {};
29
- }
30
- }
31
- });
32
- }
33
-
34
- info(message, data) {
35
- this.emit('log', 'info', message, data);
36
- }
37
-
38
- error(message, data) {
39
- this.emit('log', 'error', message, data);
40
- }
41
-
42
- debug(message, data) {
43
- this.emit('log', 'debug', message, data);
44
- }
45
-
46
- warn(message, data) {
47
- this.emit('log', 'warn', message, data);
48
- }
49
-
50
- sendError(error, message) {
51
- this.adapter.sendError(error, message);
52
- }
53
-
54
- retainDeviceNames() {
55
- clearTimeout(this.retTimeoutHandle);
56
- this.retTimeoutHanlde = setTimeout(() => {
57
- fs.writeFile(this.dev_names_fn, JSON.stringify(savedDeviceNames, null, 2), err => {
58
- if (err) {
59
- this.error(`error saving device names: ${JSON.stringify(err)}`);
60
- } else {
61
- this.debug('saved device names');
62
- }
63
- });
64
- }, 5000);
65
- }
66
-
67
- getDebugDevices() {
68
- this.debugDevices = [];
69
- this.adapter.getState(`${this.adapter.namespace}.info.debugmessages`, (err, state) => {
70
- if (state) {
71
- if (typeof state.val === 'string' && state.val.length > 2) {
72
- this.debugDevices = state.val.split(';');
73
- }
74
- this.info(`debug devices set to ${JSON.stringify(this.debugDevices)}`);
75
- } else {
76
- this.adapter.setObject('info.debugmessages', {
77
- type: 'state',
78
- common: {
79
- name: 'Log changes as warnings for',
80
- role: '',
81
- type: 'string',
82
- read: true,
83
- write: true,
84
- },
85
- native: {},
86
- });
87
- }
88
- });
89
- this.adapter.setObject('info.undefinedDevices', {
90
- type: 'state',
91
- common: {
92
- name: 'Recorded undefined devices',
93
- role: '',
94
- type: 'string',
95
- read: true,
96
- write: false,
97
- },
98
- native: {},
99
- });
100
- this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
101
- }
102
-
103
- onStateChange(id, state) {
104
- if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
105
- return;
106
- }
107
- if (this.debugDevices === undefined) {
108
- this.getDebugDevices();
109
- }
110
- if (state && !state.ack) {
111
- if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
112
- return;
113
- }
114
- if (id.endsWith('debugmessages')) {
115
- if (typeof state.val === 'string' && state.val.length > 2) {
116
- this.debugDevices = state.val.split(';');
117
-
118
- } else {
119
- this.debugDevices = [];
120
- }
121
- return;
122
- }
123
- for (const addressPart of this.debugDevices) {
124
- if (typeof id === 'string' && id.includes(addressPart)) {
125
- this.warn(`ELEVATED: User stateChange ${id} ${JSON.stringify(state)}`);
126
- break;
127
- }
128
- }
129
- this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
130
- const devId = getAdId(this.adapter, id); // iobroker device id
131
- let deviceId = getZbId(id); // zigbee device id
132
- // const stateKey = id.split('.')[3];
133
- const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
134
- if (arr[1] === undefined) {
135
- this.warn(`unable to extract id from state ${id}`);
136
- return;
137
- }
138
- const stateKey = arr[1];
139
- this.adapter.getObject(devId, (err, obj) => {
140
- if (obj) {
141
- const model = obj.common.type;
142
- if (!model) {
143
- return;
144
- }
145
- if (obj.common.deactivated) {
146
- this.debug('State Change detected on deactivated Device - ignored');
147
- return;
148
- }
149
- if (model === 'group') {
150
- deviceId = parseInt(deviceId.replace('0xgroup_', ''));
151
- }
152
- this.collectOptions(id.split('.')[2], model, options =>
153
- this.publishFromState(deviceId, model, stateKey, state, options));
154
- }
155
- });
156
- }
157
- }
158
-
159
- async collectOptions(devId, model, callback) {
160
- const result = {};
161
- // find model states for options and get it values
162
- const devStates = await this.getDevStates('0x' + devId, model);
163
- if (!devStates) {
164
- callback(result);
165
- return;
166
- }
167
- const states = devStates.states.filter(statedesc => statedesc.isOption || statedesc.inOptions);
168
- if (!states) {
169
- callback(result);
170
- return;
171
- }
172
- let cnt = 0;
173
- try {
174
- const len = states.length;
175
- states.forEach(statedesc => {
176
- const id = `${this.adapter.namespace}.${devId}.${statedesc.id}`;
177
- this.adapter.getState(id, (err, state) => {
178
- cnt = cnt + 1;
179
- if (!err && state) {
180
- result[statedesc.id] = state.val;
181
- }
182
- if (cnt === len) {
183
- callback(result);
184
- }
185
- });
186
- });
187
- if (!len) {
188
- callback(result);
189
- }
190
- } catch (error) {
191
- this.sendError(error);
192
- this.error(`Error collectOptions for ${devId}. Error: ${error.stack}`);
193
- }
194
- }
195
-
196
- async getDevStates(deviceId, model) {
197
- try {
198
- let states;
199
- let stateModel;
200
- if (model === 'group') {
201
- states = statesMapping.groupStates;
202
- } else {
203
- stateModel = statesMapping.findModel(model);
204
- if (!stateModel) {
205
- if (knownUndefinedDevices[deviceId]) {
206
- knownUndefinedDevices[deviceId]++;
207
- } else {
208
- knownUndefinedDevices[deviceId] = 1;
209
- this.error(`Device ${deviceId} "${model}" not described in statesMapping.`);
210
- }
211
- this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
212
- states = statesMapping.commonStates;
213
- } else {
214
- states = stateModel.states;
215
- }
216
- if (typeof states === 'function' && !states.prototype) {
217
- const entity = await this.adapter.zbController.resolveEntity(deviceId);
218
- if (entity) {
219
- states = states(entity);
220
- }
221
- }
222
- }
223
- return {states, stateModel};
224
- } catch (error) {
225
- this.sendError(error);
226
- this.error(`Error getDevStates for ${deviceId}. Error: ${error.stack}`);
227
- }
228
- }
229
-
230
- async publishFromState(deviceId, model, stateKey, state, options) {
231
- if (this.debugDevices === undefined) this.getDebugDevices();
232
- this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
233
- for (const addressPart of this.debugDevices) {
234
- if (typeof deviceId === 'string' && deviceId.includes(addressPart)) {
235
- this.warn(`ELEVATED Change state '${stateKey}' at device ${deviceId} type '${model}'`);
236
- break;
237
- }
238
- }
239
- const devStates = await this.getDevStates(deviceId, model);
240
- if (!devStates) {
241
- return;
242
- }
243
- const commonStates = statesMapping.commonStates.find(statedesc => stateKey === statedesc.id);
244
- const stateDesc = (commonStates === undefined ? devStates.states.find(statedesc => stateKey === statedesc.id) : commonStates);
245
- const stateModel = devStates.stateModel;
246
- if (!stateDesc) {
247
- this.error(`No state available for '${model}' with key '${stateKey}'`);
248
- return;
249
- }
250
-
251
- const value = state.val;
252
- if (value === undefined || value === '') {
253
- return;
254
- }
255
- let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0}];
256
- if (stateModel && stateModel.linkedStates) {
257
- stateModel.linkedStates.forEach(linkedFunct => {
258
- try {
259
- if (typeof linkedFunct === 'function') {
260
- const res = linkedFunct(stateDesc, value, options, this.adapter.config.disableQueue);
261
- if (res) {
262
- stateList = stateList.concat(res);
263
- }
264
- } else {
265
- this.warn(`publish from State - LinkedState is not a function ${JSON.stringify(linkedFunct)}`);
266
- }
267
- } catch (e) {
268
- this.sendError(e);
269
- this.error('Exception caught in publishfromstate');
270
- }
271
-
272
- });
273
- // sort by index
274
- stateList.sort((a, b) => a.index - b.index);
275
- }
276
-
277
- // holds the states for read after write requests
278
- let readAfterWriteStates = [];
279
- if (stateModel && stateModel.readAfterWriteStates) {
280
- stateModel.readAfterWriteStates.forEach((readAfterWriteStateDesc) =>
281
- readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id));
282
- }
283
-
284
- this.emit('changed', deviceId, model, stateModel, stateList, options);
285
- }
286
-
287
- renameDevice(id, newName) {
288
- this.storeDeviceName(id, newName);
289
- this.adapter.extendObject(id, {common: {name: newName}});
290
- }
291
-
292
- setDeviceActivated(id, active) {
293
- this.adapter.extendObject(id, {common: {deactivated: active}});
294
- }
295
-
296
- storeDeviceName(id, name) {
297
- savedDeviceNames[id.replace(`${this.adapter.namespace}.`, '')] = name;
298
- this.retainDeviceNames();
299
- }
300
-
301
- verifyDeviceName(id, name) {
302
- const savedId = id.replace(`${this.adapter.namespace}.`, '');
303
- if (!savedDeviceNames.hasOwnProperty(savedId)) {
304
- savedDeviceNames[savedId] = name;
305
- this.retainDeviceNames();
306
- }
307
- return savedDeviceNames[savedId];
308
- }
309
-
310
- deleteDeviceStates(devId, callback) {
311
- this.adapter.getStatesOf(devId, (err, states) => {
312
- if (!err && states) {
313
- states.forEach(state =>
314
- this.adapter.deleteState(devId, null, state._id));
315
- }
316
- this.adapter.deleteDevice(devId, () =>
317
- callback && callback());
318
- });
319
- }
320
-
321
- async deleteDeviceStatesAsync(devId) {
322
- const states = await this.adapter.getStatesOf(devId);
323
- if (states) {
324
- for (const state of states) {
325
- await this.adapter.deleteState(devId, null, state._id);
326
- }
327
- }
328
- await this.adapter.deleteDevice(devId);
329
- }
330
-
331
- // eslint-disable-next-line no-unused-vars
332
- async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback) {
333
- const devStates = await this.getDevStates(ieeeAddr, model);
334
- const commonStates = statesMapping.commonStates;
335
- const devId = ieeeAddr.substr(2);
336
- this.adapter.getStatesOf(devId, (err, states) => {
337
- if (!err && states) {
338
- states.forEach((state) => {
339
- let statename = state._id;
340
- const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
341
- if (arr[1] === undefined) {
342
- this.warn(`unable to extract id from state ${statename}`);
343
- const idx = statename.lastIndexOf('.');
344
- if (idx > -1) {
345
- statename = statename.slice(idx + 1);
346
- }
347
- } else {
348
- statename = arr[1];
349
- }
350
- if (commonStates.find(statedesc => statename === statedesc.id) === undefined &&
351
- devStates.states.find(statedesc => statename === statedesc.id) === undefined
352
- ) {
353
- if (state.common.hasOwnProperty('custom') && !force) {
354
- this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
355
- } else {
356
- this.info(`deleting disconnected state ${JSON.stringify(statename)} of ${devId} `);
357
- this.adapter.deleteState(devId, null, state._id);
358
- }
359
- } else {
360
- this.debug(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
361
- }
362
- });
363
- }
364
- });
365
- }
366
-
367
- updateStateWithTimeout(dev_id, name, value, common, timeout, outValue) {
368
- this.updateState(dev_id, name, value, common);
369
- setTimeout(() => this.updateState(dev_id, name, outValue, common), timeout);
370
- }
371
-
372
- updateState(devId, name, value, common) {
373
- this.adapter.getObject(devId, (err, obj) => {
374
- if (obj) {
375
- if (!obj.common.deactivated) {
376
- const new_common = {name: name};
377
- const id = devId + '.' + name;
378
- const new_name = obj.common.name;
379
- if (common) {
380
- if (common.name !== undefined) {
381
- new_common.name = common.name;
382
- }
383
- if (common.type !== undefined) {
384
- new_common.type = common.type;
385
- }
386
- if (common.unit !== undefined) {
387
- new_common.unit = common.unit;
388
- }
389
- if (common.states !== undefined) {
390
- new_common.states = common.states;
391
- }
392
- if (common.read !== undefined) {
393
- new_common.read = common.read;
394
- }
395
- if (common.write !== undefined) {
396
- new_common.write = common.write;
397
- }
398
- if (common.role !== undefined) {
399
- new_common.role = common.role;
400
- }
401
- if (common.min !== undefined) {
402
- new_common.min = common.min;
403
- }
404
- if (common.max !== undefined) {
405
- new_common.max = common.max;
406
- }
407
- if (common.icon !== undefined) {
408
- new_common.icon = common.icon;
409
- }
410
- }
411
- // check if state exist
412
- this.adapter.getObject(id, (err, stobj) => {
413
- let hasChanges = false;
414
- if (stobj) {
415
- // update state - not change name and role (user can it changed)
416
- if (stobj.common.name) {
417
- delete new_common.name;
418
- } else {
419
- new_common.name = `${new_name} ${new_common.name}`;
420
- }
421
- delete new_common.role;
422
-
423
- // check whether any common property is different
424
- if (stobj.common) {
425
- for (const property in new_common) {
426
- if (stobj.common.hasOwnProperty(property)) {
427
- if (stobj.common[property] === new_common[property]) {
428
- delete new_common[property];
429
- } else {
430
- hasChanges = true;
431
- }
432
- }
433
- }
434
- }
435
- } else {
436
- hasChanges = true;
437
- }
438
-
439
- // only change object when any common property has changed
440
- if (hasChanges) {
441
- this.adapter.extendObject(id, {type: 'state', common: new_common, native: {}}, () =>
442
- value !== undefined && this.setState_typed(id, value, true, stobj ? stobj.common.type : new_common.type));
443
- } else if (value !== undefined) {
444
- this.setState_typed(id, value, true, stobj.common.type);
445
- }
446
-
447
- });
448
- } else {
449
- this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
450
- }
451
- } else {
452
- this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
453
- }
454
- });
455
- }
456
-
457
- setState_typed(id, value, ack, type, callback) {
458
- // never set a null or undefined value
459
- if (value === null || value === undefined) return;
460
- if (!type) {
461
- this.debug('SetState_typed called without type');
462
- // identify datatype, recursively call this function with set datatype
463
- this.adapter.getObject(id, (err, obj) => {
464
- if (obj && obj.common) {
465
- this.setState_typed(id, value, ack, obj.common.type, callback);
466
- } else {
467
- this.setState_typed(id, value, ack, 'noobj', callback);
468
- }
469
- });
470
- return;
471
- }
472
- if (typeof value !== type) {
473
- this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
474
- switch (type) {
475
- case 'number':
476
- value = parseFloat(value);
477
- if (isNaN(value)) {
478
- value = 0;
479
- }
480
- break;
481
- case 'string':
482
- case 'text':
483
- value = JSON.stringify(value);
484
- break;
485
- case 'boolean': {
486
- if (typeof value == 'number') {
487
- value = value !== 0;
488
- break;
489
- }
490
- const sval = JSON.stringify(value).toLowerCase().trim();
491
- value = sval === 'true' || sval === 'yes' || sval === 'on';
492
- }
493
- break;
494
- }
495
- }
496
- this.adapter.setState(id, value, ack, callback);
497
- }
498
-
499
- updateDev(dev_id, dev_name, model, callback) {
500
- const __dev_name = this.verifyDeviceName(dev_id, dev_name);
501
- const id = '' + dev_id;
502
- const modelDesc = statesMapping.findModel(model);
503
- let icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
504
-
505
- // download icon if it external and not undef
506
- if (model === undefined) {
507
- this.warn(`download icon ${__dev_name} for undefined Device not available. Check your devices.`);
508
- } else {
509
- const model_modif = model.replace(/\//g, '-');
510
- const pathToIcon = `${this.adapter.adapterDir}/admin/img/${model_modif}.png`;
511
-
512
- if (icon.startsWith('http')) {
513
- try {
514
- if (!fs.existsSync(pathToIcon)) {
515
- this.warn(`download icon from ${icon} saved into ${pathToIcon}`);
516
- this.downloadIcon(icon, pathToIcon);
517
- }
518
- icon = `img/${model_modif}.png`;
519
- } catch (e) {
520
- this.debug(`ERROR : icon not found from ${icon} saved into ${pathToIcon}`);
521
- }
522
- }
523
- }
524
-
525
- this.adapter.setObjectNotExists(id, {
526
- type: 'device',
527
- // actually this is an error, so device.common has no attribute type. It must be in native part
528
- common: {
529
- name: __dev_name,
530
- type: model,
531
- icon,
532
- color: null,
533
- statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
534
- },
535
- native: {id: dev_id}
536
- }, () => {
537
- // update type and icon
538
- this.adapter.extendObject(id, {
539
- common: {
540
- type: model,
541
- icon,
542
- color: null,
543
- statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
544
- }
545
- }, callback);
546
- });
547
- }
548
-
549
- async downloadIcon(url, image_path) {
550
- if (!fs.existsSync(image_path)) {
551
- return new Promise((resolve, reject) => {
552
- request.head(url, (err, res, body) => {
553
- if (err) {
554
- return reject(err);
555
- }
556
- const stream = request(url);
557
- stream.pipe(
558
- fs.createWriteStream(image_path)
559
- .on('error', err => reject(err)))
560
- .on('close', () => resolve());
561
- });
562
- });
563
- }
564
- }
565
-
566
- async syncDevStates(dev, model) {
567
- const devId = dev.ieeeAddr.substr(2);
568
- // devId - iobroker device id
569
- const devStates = await this.getDevStates(dev.ieeeAddr, model);
570
- if (!devStates) {
571
- return;
572
- }
573
- const states = statesMapping.commonStates.concat(devStates.states);
574
-
575
- for (const stateInd in states) {
576
- if (!states.hasOwnProperty(stateInd)) {
577
- continue;
578
- }
579
-
580
- const statedesc = states[stateInd];
581
- if (statedesc === undefined) {
582
- this.error(`syncDevStates: Illegal state in ${JSON.stringify(dev)} - ${JSON.stringify(stateInd)}`);
583
- return;
584
- }
585
- // Filter out non routers or devices that are battery driven for the availability flag
586
- if (statedesc.id === 'available') {
587
- if (!(dev.type === 'Router') || dev.powerSource === 'Battery') {
588
- continue;
589
- }
590
- }
591
- // lazy states
592
- if (statedesc.lazy) {
593
- continue;
594
- }
595
-
596
- const common = {
597
- name: statedesc.name,
598
- type: statedesc.type,
599
- unit: statedesc.unit,
600
- read: statedesc.read,
601
- write: statedesc.write,
602
- icon: statedesc.icon,
603
- role: statedesc.role,
604
- min: statedesc.min,
605
- max: statedesc.max,
606
- states: statedesc.states,
607
- };
608
- this.updateState(devId, statedesc.id, undefined, common);
609
- }
610
- }
611
-
612
- async getExcludeExposes(allExcludesObj) {
613
- statesMapping.fillStatesWithExposes(allExcludesObj);
614
- }
615
-
616
- async publishToState(devId, model, payload) {
617
- const devStates = await this.getDevStates(`0x${devId}`, model);
618
- let has_debug = false;
619
- if (this.debugDevices === undefined) this.getDebugDevices();
620
- for (const addressPart of this.debugDevices) {
621
- if (typeof devId === 'string' && devId.includes(addressPart)) {
622
- if (payload.hasOwnProperty('msg_from_zigbee')) break;
623
- this.warn(`ELEVATED publishToState: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
624
- has_debug = true;
625
- break;
626
- }
627
- }
628
- if (!devStates) {
629
- return;
630
- }
631
- // find states for payload
632
- if (devStates.states !== undefined) {
633
- const states = statesMapping.commonStates.concat(
634
- devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id)));
635
-
636
- for (const stateInd in states) {
637
- const statedesc = states[stateInd];
638
- let value;
639
- if (statedesc.getter) {
640
- value = statedesc.getter(payload);
641
- } else {
642
- value = payload[statedesc.prop || statedesc.id];
643
- }
644
- // checking value
645
- if (value === undefined || value === null) {
646
- continue;
647
- }
648
- let stateID = statedesc.id;
649
-
650
- if (has_debug && statedesc.id !== 'msg_from_zigbee') {
651
- this.warn(`ELEVATED publishToState: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`);
652
- }
653
-
654
- const common = {
655
- name: statedesc.name,
656
- type: statedesc.type,
657
- unit: statedesc.unit,
658
- read: statedesc.read,
659
- write: statedesc.write,
660
- icon: statedesc.icon,
661
- role: statedesc.role,
662
- min: statedesc.min,
663
- max: statedesc.max,
664
- };
665
-
666
- if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
667
- stateID = `${stateID}.${value.stateid}`;
668
- if (value.hasOwnProperty('unit')) {
669
- common.unit = value.unit;
670
- }
671
- common.name = value.name ? value.name : value.stateid;
672
- common.role = value.role ? `value.${value.role}` : 'number';
673
- value = value.value;
674
- }
675
-
676
- // if needs to return value to back after timeout
677
- if (statedesc.isEvent) {
678
- this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, !value);
679
- } else {
680
- if (statedesc.prepublish) {
681
- this.collectOptions(devId, model, options =>
682
- statedesc.prepublish(devId, value, newvalue =>
683
- this.updateState(devId, stateID, newvalue, common), options));
684
- } else {
685
- this.updateState(devId, stateID, value, common);
686
- }
687
- }
688
- }
689
- }
690
- }
691
- }
692
-
693
- 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
+ const fs = require('fs');
8
+ const request = require('request');
9
+
10
+ let savedDeviceNames = {};
11
+ let knownUndefinedDevices = {};
12
+
13
+ class StatesController extends EventEmitter {
14
+ constructor(adapter) {
15
+ super();
16
+ this.adapter = adapter;
17
+ this.adapter.on('stateChange', this.onStateChange.bind(this));
18
+ this.query_device_block = [];
19
+ this.debugDevices = undefined;
20
+ const fn = adapter.expandFileName('dev_names.json');
21
+ this.dev_names_fn = fn.replace('.', '_');
22
+ this.retTimeoutHandle = null;
23
+ fs.readFile(this.dev_names_fn, (err, data) => {
24
+ if (!err) {
25
+ try {
26
+ savedDeviceNames = JSON.parse(data);
27
+ } catch {
28
+ savedDeviceNames = {};
29
+ }
30
+ }
31
+ });
32
+ }
33
+
34
+ info(message, data) {
35
+ this.emit('log', 'info', message, data);
36
+ }
37
+
38
+ error(message, data) {
39
+ this.emit('log', 'error', message, data);
40
+ }
41
+
42
+ debug(message, data) {
43
+ this.emit('log', 'debug', message, data);
44
+ }
45
+
46
+ warn(message, data) {
47
+ this.emit('log', 'warn', message, data);
48
+ }
49
+
50
+ sendError(error, message) {
51
+ this.adapter.sendError(error, message);
52
+ }
53
+
54
+ retainDeviceNames() {
55
+ clearTimeout(this.retTimeoutHandle);
56
+ this.retTimeoutHanlde = setTimeout(() => {
57
+ fs.writeFile(this.dev_names_fn, JSON.stringify(savedDeviceNames, null, 2), err => {
58
+ if (err) {
59
+ this.error(`error saving device names: ${JSON.stringify(err)}`);
60
+ } else {
61
+ this.debug('saved device names');
62
+ }
63
+ });
64
+ }, 5000);
65
+ }
66
+
67
+ getDebugDevices() {
68
+ this.debugDevices = [];
69
+ this.adapter.getState(`${this.adapter.namespace}.info.debugmessages`, (err, state) => {
70
+ if (state) {
71
+ if (typeof state.val === 'string' && state.val.length > 2) {
72
+ this.debugDevices = state.val.split(';');
73
+ }
74
+ this.info(`debug devices set to ${JSON.stringify(this.debugDevices)}`);
75
+ } else {
76
+ this.adapter.setObject('info.debugmessages', {
77
+ type: 'state',
78
+ common: {
79
+ name: 'Log changes as warnings for',
80
+ role: '',
81
+ type: 'string',
82
+ read: true,
83
+ write: true,
84
+ },
85
+ native: {},
86
+ });
87
+ }
88
+ });
89
+ this.adapter.setObject('info.undefinedDevices', {
90
+ type: 'state',
91
+ common: {
92
+ name: 'Recorded undefined devices',
93
+ role: '',
94
+ type: 'string',
95
+ read: true,
96
+ write: false,
97
+ },
98
+ native: {},
99
+ });
100
+ this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
101
+ }
102
+
103
+ onStateChange(id, state) {
104
+ if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
105
+ return;
106
+ }
107
+ if (this.debugDevices === undefined) {
108
+ this.getDebugDevices();
109
+ }
110
+ if (state && !state.ack) {
111
+ if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
112
+ return;
113
+ }
114
+ if (id.endsWith('debugmessages')) {
115
+ if (typeof state.val === 'string' && state.val.length > 2) {
116
+ this.debugDevices = state.val.split(';');
117
+
118
+ } else {
119
+ this.debugDevices = [];
120
+ }
121
+ return;
122
+ }
123
+ for (const addressPart of this.debugDevices) {
124
+ if (typeof id === 'string' && id.includes(addressPart)) {
125
+ this.warn(`ELEVATED: User stateChange ${id} ${JSON.stringify(state)}`);
126
+ break;
127
+ }
128
+ }
129
+ this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
130
+ const devId = getAdId(this.adapter, id); // iobroker device id
131
+ let deviceId = getZbId(id); // zigbee device id
132
+ // const stateKey = id.split('.')[3];
133
+ const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
134
+ if (arr[1] === undefined) {
135
+ this.warn(`unable to extract id from state ${id}`);
136
+ return;
137
+ }
138
+ const stateKey = arr[1];
139
+ this.adapter.getObject(devId, (err, obj) => {
140
+ if (obj) {
141
+ const model = obj.common.type;
142
+ if (!model) {
143
+ return;
144
+ }
145
+ if (obj.common.deactivated) {
146
+ this.debug('State Change detected on deactivated Device - ignored');
147
+ return;
148
+ }
149
+ if (model === 'group') {
150
+ deviceId = parseInt(deviceId.replace('0xgroup_', ''));
151
+ }
152
+ this.collectOptions(id.split('.')[2], model, options =>
153
+ this.publishFromState(deviceId, model, stateKey, state, options));
154
+ }
155
+ });
156
+ }
157
+ }
158
+
159
+ async collectOptions(devId, model, callback) {
160
+ const result = {};
161
+ // find model states for options and get it values
162
+ const devStates = await this.getDevStates('0x' + devId, model);
163
+ if (!devStates) {
164
+ callback(result);
165
+ return;
166
+ }
167
+ const states = devStates.states.filter(statedesc => statedesc.isOption || statedesc.inOptions);
168
+ if (!states) {
169
+ callback(result);
170
+ return;
171
+ }
172
+ let cnt = 0;
173
+ try {
174
+ const len = states.length;
175
+ states.forEach(statedesc => {
176
+ const id = `${this.adapter.namespace}.${devId}.${statedesc.id}`;
177
+ this.adapter.getState(id, (err, state) => {
178
+ cnt = cnt + 1;
179
+ if (!err && state) {
180
+ result[statedesc.id] = state.val;
181
+ }
182
+ if (cnt === len) {
183
+ callback(result);
184
+ }
185
+ });
186
+ });
187
+ if (!len) {
188
+ callback(result);
189
+ }
190
+ } catch (error) {
191
+ this.sendError(error);
192
+ this.error(`Error collectOptions for ${devId}. Error: ${error.stack}`);
193
+ }
194
+ }
195
+
196
+ async getDevStates(deviceId, model) {
197
+ try {
198
+ let states;
199
+ let stateModel;
200
+ if (model === 'group') {
201
+ states = statesMapping.groupStates;
202
+ } else {
203
+ stateModel = statesMapping.findModel(model);
204
+ if (!stateModel) {
205
+ if (knownUndefinedDevices[deviceId]) {
206
+ knownUndefinedDevices[deviceId]++;
207
+ } else {
208
+ knownUndefinedDevices[deviceId] = 1;
209
+ this.error(`Device ${deviceId} "${model}" not described in statesMapping.`);
210
+ }
211
+ this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
212
+ states = statesMapping.commonStates;
213
+ } else {
214
+ states = stateModel.states;
215
+ }
216
+ if (typeof states === 'function' && !states.prototype) {
217
+ const entity = await this.adapter.zbController.resolveEntity(deviceId);
218
+ if (entity) {
219
+ states = states(entity);
220
+ }
221
+ }
222
+ }
223
+ return {states, stateModel};
224
+ } catch (error) {
225
+ this.sendError(error);
226
+ this.error(`Error getDevStates for ${deviceId}. Error: ${error.stack}`);
227
+ }
228
+ }
229
+
230
+ async publishFromState(deviceId, model, stateKey, state, options) {
231
+ if (this.debugDevices === undefined) this.getDebugDevices();
232
+ this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
233
+ for (const addressPart of this.debugDevices) {
234
+ if (typeof deviceId === 'string' && deviceId.includes(addressPart)) {
235
+ this.warn(`ELEVATED Change state '${stateKey}' at device ${deviceId} type '${model}'`);
236
+ break;
237
+ }
238
+ }
239
+ const devStates = await this.getDevStates(deviceId, model);
240
+ if (!devStates) {
241
+ return;
242
+ }
243
+ const commonStates = statesMapping.commonStates.find(statedesc => stateKey === statedesc.id);
244
+ const stateDesc = (commonStates === undefined ? devStates.states.find(statedesc => stateKey === statedesc.id) : commonStates);
245
+ const stateModel = devStates.stateModel;
246
+ if (!stateDesc) {
247
+ this.error(`No state available for '${model}' with key '${stateKey}'`);
248
+ return;
249
+ }
250
+
251
+ const value = state.val;
252
+ if (value === undefined || value === '') {
253
+ return;
254
+ }
255
+ let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0}];
256
+ if (stateModel && stateModel.linkedStates) {
257
+ stateModel.linkedStates.forEach(linkedFunct => {
258
+ try {
259
+ if (typeof linkedFunct === 'function') {
260
+ const res = linkedFunct(stateDesc, value, options, this.adapter.config.disableQueue);
261
+ if (res) {
262
+ stateList = stateList.concat(res);
263
+ }
264
+ } else {
265
+ this.warn(`publish from State - LinkedState is not a function ${JSON.stringify(linkedFunct)}`);
266
+ }
267
+ } catch (e) {
268
+ this.sendError(e);
269
+ this.error('Exception caught in publishfromstate');
270
+ }
271
+
272
+ });
273
+ // sort by index
274
+ stateList.sort((a, b) => a.index - b.index);
275
+ }
276
+
277
+ // holds the states for read after write requests
278
+ let readAfterWriteStates = [];
279
+ if (stateModel && stateModel.readAfterWriteStates) {
280
+ stateModel.readAfterWriteStates.forEach((readAfterWriteStateDesc) =>
281
+ readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id));
282
+ }
283
+
284
+ this.emit('changed', deviceId, model, stateModel, stateList, options);
285
+ }
286
+
287
+ renameDevice(id, newName) {
288
+ this.storeDeviceName(id, newName);
289
+ this.adapter.extendObject(id, {common: {name: newName}});
290
+ }
291
+
292
+ setDeviceActivated(id, active) {
293
+ this.adapter.extendObject(id, {common: {deactivated: active}});
294
+ }
295
+
296
+ storeDeviceName(id, name) {
297
+ savedDeviceNames[id.replace(`${this.adapter.namespace}.`, '')] = name;
298
+ this.retainDeviceNames();
299
+ }
300
+
301
+ verifyDeviceName(id, name) {
302
+ const savedId = id.replace(`${this.adapter.namespace}.`, '');
303
+ if (!savedDeviceNames.hasOwnProperty(savedId)) {
304
+ savedDeviceNames[savedId] = name;
305
+ this.retainDeviceNames();
306
+ }
307
+ return savedDeviceNames[savedId];
308
+ }
309
+
310
+ deleteDeviceStates(devId, callback) {
311
+ this.adapter.getStatesOf(devId, (err, states) => {
312
+ if (!err && states) {
313
+ states.forEach(state =>
314
+ this.adapter.deleteState(devId, null, state._id));
315
+ }
316
+ this.adapter.deleteDevice(devId, () =>
317
+ callback && callback());
318
+ });
319
+ }
320
+
321
+ async deleteDeviceStatesAsync(devId) {
322
+ const states = await this.adapter.getStatesOf(devId);
323
+ if (states) {
324
+ for (const state of states) {
325
+ await this.adapter.deleteState(devId, null, state._id);
326
+ }
327
+ }
328
+ await this.adapter.deleteDevice(devId);
329
+ }
330
+
331
+ // eslint-disable-next-line no-unused-vars
332
+ async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback) {
333
+ const devStates = await this.getDevStates(ieeeAddr, model);
334
+ const commonStates = statesMapping.commonStates;
335
+ const devId = ieeeAddr.substr(2);
336
+ this.adapter.getStatesOf(devId, (err, states) => {
337
+ if (!err && states) {
338
+ states.forEach((state) => {
339
+ let statename = state._id;
340
+ const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
341
+ if (arr[1] === undefined) {
342
+ this.warn(`unable to extract id from state ${statename}`);
343
+ const idx = statename.lastIndexOf('.');
344
+ if (idx > -1) {
345
+ statename = statename.slice(idx + 1);
346
+ }
347
+ } else {
348
+ statename = arr[1];
349
+ }
350
+ if (commonStates.find(statedesc => statename === statedesc.id) === undefined &&
351
+ devStates.states.find(statedesc => statename === statedesc.id) === undefined
352
+ ) {
353
+ if (state.common.hasOwnProperty('custom') && !force) {
354
+ this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
355
+ } else {
356
+ this.info(`deleting disconnected state ${JSON.stringify(statename)} of ${devId} `);
357
+ this.adapter.deleteState(devId, null, state._id);
358
+ }
359
+ } else {
360
+ this.debug(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
361
+ }
362
+ });
363
+ }
364
+ });
365
+ }
366
+
367
+ updateStateWithTimeout(dev_id, name, value, common, timeout, outValue) {
368
+ this.updateState(dev_id, name, value, common);
369
+ setTimeout(() => this.updateState(dev_id, name, outValue, common), timeout);
370
+ }
371
+
372
+ updateState(devId, name, value, common) {
373
+ this.adapter.getObject(devId, (err, obj) => {
374
+ if (obj) {
375
+ if (!obj.common.deactivated) {
376
+ const new_common = {name: name};
377
+ const id = devId + '.' + name;
378
+ const new_name = obj.common.name;
379
+ if (common) {
380
+ if (common.name !== undefined) {
381
+ new_common.name = common.name;
382
+ }
383
+ if (common.type !== undefined) {
384
+ new_common.type = common.type;
385
+ }
386
+ if (common.unit !== undefined) {
387
+ new_common.unit = common.unit;
388
+ }
389
+ if (common.states !== undefined) {
390
+ new_common.states = common.states;
391
+ }
392
+ if (common.read !== undefined) {
393
+ new_common.read = common.read;
394
+ }
395
+ if (common.write !== undefined) {
396
+ new_common.write = common.write;
397
+ }
398
+ if (common.role !== undefined) {
399
+ new_common.role = common.role;
400
+ }
401
+ if (common.min !== undefined) {
402
+ new_common.min = common.min;
403
+ }
404
+ if (common.max !== undefined) {
405
+ new_common.max = common.max;
406
+ }
407
+ if (common.icon !== undefined) {
408
+ new_common.icon = common.icon;
409
+ }
410
+ }
411
+ // check if state exist
412
+ this.adapter.getObject(id, (err, stobj) => {
413
+ let hasChanges = false;
414
+ if (stobj) {
415
+ // update state - not change name and role (user can it changed)
416
+ if (stobj.common.name) {
417
+ delete new_common.name;
418
+ } else {
419
+ new_common.name = `${new_name} ${new_common.name}`;
420
+ }
421
+ delete new_common.role;
422
+
423
+ // check whether any common property is different
424
+ if (stobj.common) {
425
+ for (const property in new_common) {
426
+ if (stobj.common.hasOwnProperty(property)) {
427
+ if (stobj.common[property] === new_common[property]) {
428
+ delete new_common[property];
429
+ } else {
430
+ hasChanges = true;
431
+ }
432
+ }
433
+ }
434
+ }
435
+ } else {
436
+ hasChanges = true;
437
+ }
438
+
439
+ // only change object when any common property has changed
440
+ if (hasChanges) {
441
+ this.adapter.extendObject(id, {type: 'state', common: new_common, native: {}}, () =>
442
+ value !== undefined && this.setState_typed(id, value, true, stobj ? stobj.common.type : new_common.type));
443
+ } else if (value !== undefined) {
444
+ this.setState_typed(id, value, true, stobj.common.type);
445
+ }
446
+
447
+ });
448
+ } else {
449
+ this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
450
+ }
451
+ } else {
452
+ this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
453
+ }
454
+ });
455
+ }
456
+
457
+ setState_typed(id, value, ack, type, callback) {
458
+ // never set a null or undefined value
459
+ if (value === null || value === undefined) return;
460
+ if (!type) {
461
+ this.debug('SetState_typed called without type');
462
+ // identify datatype, recursively call this function with set datatype
463
+ this.adapter.getObject(id, (err, obj) => {
464
+ if (obj && obj.common) {
465
+ this.setState_typed(id, value, ack, obj.common.type, callback);
466
+ } else {
467
+ this.setState_typed(id, value, ack, 'noobj', callback);
468
+ }
469
+ });
470
+ return;
471
+ }
472
+ if (typeof value !== type) {
473
+ this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
474
+ switch (type) {
475
+ case 'number':
476
+ value = parseFloat(value);
477
+ if (isNaN(value)) {
478
+ value = 0;
479
+ }
480
+ break;
481
+ case 'string':
482
+ case 'text':
483
+ value = JSON.stringify(value);
484
+ break;
485
+ case 'boolean': {
486
+ if (typeof value == 'number') {
487
+ value = value !== 0;
488
+ break;
489
+ }
490
+ const sval = JSON.stringify(value).toLowerCase().trim();
491
+ value = sval === 'true' || sval === 'yes' || sval === 'on';
492
+ }
493
+ break;
494
+ }
495
+ }
496
+ this.adapter.setState(id, value, ack, callback);
497
+ }
498
+
499
+ updateDev(dev_id, dev_name, model, callback) {
500
+ const __dev_name = this.verifyDeviceName(dev_id, dev_name);
501
+ const id = '' + dev_id;
502
+ const modelDesc = statesMapping.findModel(model);
503
+ let icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
504
+
505
+ // download icon if it external and not undef
506
+ if (model === undefined) {
507
+ this.warn(`download icon ${__dev_name} for undefined Device not available. Check your devices.`);
508
+ } else {
509
+ const model_modif = model.replace(/\//g, '-');
510
+ const pathToIcon = `${this.adapter.adapterDir}/admin/img/${model_modif}.png`;
511
+
512
+ if (icon.startsWith('http')) {
513
+ try {
514
+ if (!fs.existsSync(pathToIcon)) {
515
+ this.warn(`download icon from ${icon} saved into ${pathToIcon}`);
516
+ this.downloadIcon(icon, pathToIcon);
517
+ }
518
+ icon = `img/${model_modif}.png`;
519
+ } catch (e) {
520
+ this.debug(`ERROR : icon not found from ${icon} saved into ${pathToIcon}`);
521
+ }
522
+ }
523
+ }
524
+
525
+ this.adapter.setObjectNotExists(id, {
526
+ type: 'device',
527
+ // actually this is an error, so device.common has no attribute type. It must be in native part
528
+ common: {
529
+ name: __dev_name,
530
+ type: model,
531
+ icon,
532
+ color: null,
533
+ statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
534
+ },
535
+ native: {id: dev_id}
536
+ }, () => {
537
+ // update type and icon
538
+ this.adapter.extendObject(id, {
539
+ common: {
540
+ type: model,
541
+ icon,
542
+ color: null,
543
+ statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
544
+ }
545
+ }, callback);
546
+ });
547
+ }
548
+
549
+ async downloadIcon(url, image_path) {
550
+ if (!fs.existsSync(image_path)) {
551
+ return new Promise((resolve, reject) => {
552
+ request.head(url, (err, res, body) => {
553
+ if (err) {
554
+ return reject(err);
555
+ }
556
+ const stream = request(url);
557
+ stream.pipe(
558
+ fs.createWriteStream(image_path)
559
+ .on('error', err => reject(err)))
560
+ .on('close', () => resolve());
561
+ });
562
+ });
563
+ }
564
+ }
565
+
566
+ async syncDevStates(dev, model) {
567
+ const devId = dev.ieeeAddr.substr(2);
568
+ // devId - iobroker device id
569
+ const devStates = await this.getDevStates(dev.ieeeAddr, model);
570
+ if (!devStates) {
571
+ return;
572
+ }
573
+ const states = statesMapping.commonStates.concat(devStates.states);
574
+
575
+ for (const stateInd in states) {
576
+ if (!states.hasOwnProperty(stateInd)) {
577
+ continue;
578
+ }
579
+
580
+ const statedesc = states[stateInd];
581
+ if (statedesc === undefined) {
582
+ this.error(`syncDevStates: Illegal state in ${JSON.stringify(dev)} - ${JSON.stringify(stateInd)}`);
583
+ return;
584
+ }
585
+ // Filter out non routers or devices that are battery driven for the availability flag
586
+ if (statedesc.id === 'available') {
587
+ if (!(dev.type === 'Router') || dev.powerSource === 'Battery') {
588
+ continue;
589
+ }
590
+ }
591
+ // lazy states
592
+ if (statedesc.lazy) {
593
+ continue;
594
+ }
595
+
596
+ const common = {
597
+ name: statedesc.name,
598
+ type: statedesc.type,
599
+ unit: statedesc.unit,
600
+ read: statedesc.read,
601
+ write: statedesc.write,
602
+ icon: statedesc.icon,
603
+ role: statedesc.role,
604
+ min: statedesc.min,
605
+ max: statedesc.max,
606
+ states: statedesc.states,
607
+ };
608
+ this.updateState(devId, statedesc.id, undefined, common);
609
+ }
610
+ }
611
+
612
+ async getExcludeExposes(allExcludesObj) {
613
+ statesMapping.fillStatesWithExposes(allExcludesObj);
614
+ }
615
+
616
+ async publishToState(devId, model, payload) {
617
+ const devStates = await this.getDevStates(`0x${devId}`, model);
618
+ let has_debug = false;
619
+ if (this.debugDevices === undefined) this.getDebugDevices();
620
+ for (const addressPart of this.debugDevices) {
621
+ if (typeof devId === 'string' && devId.includes(addressPart)) {
622
+ if (payload.hasOwnProperty('msg_from_zigbee')) break;
623
+ this.warn(`ELEVATED publishToState: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
624
+ has_debug = true;
625
+ break;
626
+ }
627
+ }
628
+ if (!devStates) {
629
+ return;
630
+ }
631
+ // find states for payload
632
+ if (devStates.states !== undefined) {
633
+ const states = statesMapping.commonStates.concat(
634
+ devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id)));
635
+
636
+ for (const stateInd in states) {
637
+ const statedesc = states[stateInd];
638
+ let value;
639
+ if (statedesc.getter) {
640
+ value = statedesc.getter(payload);
641
+ } else {
642
+ value = payload[statedesc.prop || statedesc.id];
643
+ }
644
+ // checking value
645
+ if (value === undefined || value === null) {
646
+ continue;
647
+ }
648
+ let stateID = statedesc.id;
649
+
650
+ if (has_debug && statedesc.id !== 'msg_from_zigbee') {
651
+ this.warn(`ELEVATED publishToState: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`);
652
+ }
653
+
654
+ const common = {
655
+ name: statedesc.name,
656
+ type: statedesc.type,
657
+ unit: statedesc.unit,
658
+ read: statedesc.read,
659
+ write: statedesc.write,
660
+ icon: statedesc.icon,
661
+ role: statedesc.role,
662
+ min: statedesc.min,
663
+ max: statedesc.max,
664
+ };
665
+
666
+ if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
667
+ stateID = `${stateID}.${value.stateid}`;
668
+ if (value.hasOwnProperty('unit')) {
669
+ common.unit = value.unit;
670
+ }
671
+ common.name = value.name ? value.name : value.stateid;
672
+ common.role = value.role ? `value.${value.role}` : 'number';
673
+ value = value.value;
674
+ }
675
+
676
+ // if needs to return value to back after timeout
677
+ if (statedesc.isEvent) {
678
+ this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, !value);
679
+ } else {
680
+ if (statedesc.prepublish) {
681
+ this.collectOptions(devId, model, options =>
682
+ statedesc.prepublish(devId, value, newvalue =>
683
+ this.updateState(devId, stateID, newvalue, common), options));
684
+ } else {
685
+ this.updateState(devId, stateID, value, common);
686
+ }
687
+ }
688
+ }
689
+ }
690
+ }
691
+ }
692
+
693
+ module.exports = StatesController;