iobroker.zigbee2mqtt 2.1.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/exposes.js CHANGED
@@ -6,873 +6,915 @@ const statesDefs = require('./states').states;
6
6
  const rgb = require('./rgb');
7
7
  const utils = require('./utils');
8
8
  const colors = require('./colors');
9
-
10
- const blacklistSimulatedBrightness = ['MFKZQ01LM'];
9
+ const getNonGenDevStatesDefs = require('./nonGenericDevicesExtension').getStateDefinition;
10
+
11
+ // https://www.zigbee2mqtt.io/guide/usage/exposes.html#access
12
+ const z2mAccess = {
13
+ /**
14
+ * Bit 0: The property can be found in the published state of this device
15
+ */
16
+ STATE: 1,
17
+ /**
18
+ * Bit 1: The property can be set with a /set command
19
+ */
20
+ SET: 2,
21
+ /**
22
+ * Bit 2: The property can be retrieved with a /get command
23
+ */
24
+ GET: 4,
25
+ /**
26
+ * Bitwise inclusive OR of STATE and SET : 0b001 | 0b010
27
+ */
28
+ STATE_SET: 3,
29
+ /**
30
+ * Bitwise inclusive OR of STATE and GET : 0b001 | 0b100
31
+ */
32
+ STATE_GET: 5,
33
+ /**
34
+ * Bitwise inclusive OR of STATE and GET and SET : 0b001 | 0b100 | 0b010
35
+ */
36
+ ALL: 7,
37
+ };
11
38
 
12
39
  function genState(expose, role, name, desc) {
13
- let state;
14
- const readable = true; //expose.access > 0;
15
- const writable = expose.access > 1;
16
- const stname = (name || expose.property);
17
-
18
- if (typeof stname !== 'string') {
19
- return;
20
- }
21
-
22
- const stateId = stname.replace(/\*/g, '');
23
- const stateName = (desc || expose.description || expose.name);
24
- const propName = expose.property;
25
-
26
- switch (expose.type) {
27
- case 'binary':
28
- state = {
29
- id: stateId,
30
- prop: propName,
31
- name: stateName,
32
- icon: undefined,
33
- role: role || 'state',
34
- write: writable,
35
- read: true,
36
- type: 'boolean',
37
- };
38
-
39
- if (readable) {
40
- state.getter = (payload) => (payload[propName] === (expose.value_on || 'ON'));
41
- } else {
42
- state.getter = (_payload) => (undefined);
43
- }
44
-
45
- if (writable) {
46
- state.setter = (payload) => (payload) ? (expose.value_on || 'ON') : ((expose.value_off != undefined) ? expose.value_off : 'OFF');
47
- state.setattr = expose.name;
48
- }
49
-
50
- if (expose.endpoint) {
51
- state.epname = expose.endpoint;
52
- }
53
-
54
- break;
55
-
56
- case 'numeric':
57
- state = {
58
- id: stateId,
59
- prop: propName,
60
- name: stateName,
61
- icon: undefined,
62
- role: role || 'state',
63
- write: writable,
64
- read: true,
65
- type: 'number',
66
- min: expose.value_min || 0,
67
- max: expose.value_max,
68
- unit: expose.unit,
69
- };
70
-
71
- if (expose.endpoint) {
72
- state.epname = expose.endpoint;
73
- }
74
- break;
75
-
76
- case 'enum':
77
- state = {
78
- id: stateId,
79
- prop: propName,
80
- name: stateName,
81
- icon: undefined,
82
- role: role || 'state',
83
- write: writable,
84
- read: true,
85
- type: 'string',
86
- states: {}
87
- };
88
-
89
- for (const val of expose.values) {
90
- state.states[val] = val;
91
- state.type = typeof (val);
92
- }
93
-
94
- if (expose.endpoint) {
95
- state.epname = expose.endpoint;
96
- state.setattr = expose.name;
97
- }
98
- break;
99
-
100
- case 'text':
101
- state = {
102
- id: stateId,
103
- prop: propName,
104
- name: stateName,
105
- icon: undefined,
106
- role: role || 'state',
107
- write: writable,
108
- read: true,
109
- type: 'string',
110
- };
111
- if (expose.endpoint) {
112
- state.epname = expose.endpoint;
113
- }
114
- break;
115
-
116
- default:
117
- break;
118
- }
119
-
120
- return state;
40
+ let state;
41
+ const readable = (expose.access & z2mAccess.STATE) > 0;
42
+ const writable = (expose.access & z2mAccess.SET) > 0;
43
+ const stname = (name || expose.property);
44
+
45
+ if (typeof stname !== 'string') {
46
+ return;
47
+ }
48
+
49
+ const stateId = stname.replace(/\*/g, '');
50
+ const stateName = (desc || expose.description || expose.name);
51
+ const propName = expose.property;
52
+
53
+ switch (expose.type) {
54
+ case 'binary':
55
+ state = {
56
+ id: stateId,
57
+ prop: propName,
58
+ name: stateName,
59
+ icon: undefined,
60
+ role: role || 'state',
61
+ write: writable,
62
+ read: true,
63
+ type: 'boolean',
64
+ };
65
+
66
+ if (readable) {
67
+ state.getter = (payload) => (payload[propName] === (expose.value_on || 'ON'));
68
+ } else {
69
+ state.getter = (_payload) => (undefined);
70
+ }
71
+
72
+ if (writable) {
73
+ state.setter = (payload) => (payload) ? (expose.value_on || 'ON') : ((expose.value_off != undefined) ? expose.value_off : 'OFF');
74
+ state.setattr = expose.name;
75
+ }
76
+
77
+ if (expose.endpoint) {
78
+ state.epname = expose.endpoint;
79
+ }
80
+
81
+ break;
82
+
83
+ case 'numeric':
84
+ state = {
85
+ id: stateId,
86
+ prop: propName,
87
+ name: stateName,
88
+ icon: undefined,
89
+ role: role || 'state',
90
+ write: writable,
91
+ read: true,
92
+ type: 'number',
93
+ min: expose.value_min || 0,
94
+ max: expose.value_max,
95
+ unit: expose.unit,
96
+ };
97
+
98
+ if (expose.endpoint) {
99
+ state.epname = expose.endpoint;
100
+ }
101
+ break;
102
+
103
+ case 'enum':
104
+ state = {
105
+ id: stateId,
106
+ prop: propName,
107
+ name: stateName,
108
+ icon: undefined,
109
+ role: role || 'state',
110
+ write: writable,
111
+ read: true,
112
+ type: 'string',
113
+ states: {}
114
+ };
115
+
116
+ for (const val of expose.values) {
117
+ state.states[val] = val;
118
+ state.type = typeof (val);
119
+ }
120
+
121
+ if (expose.endpoint) {
122
+ state.epname = expose.endpoint;
123
+ state.setattr = expose.name;
124
+ }
125
+ break;
126
+
127
+ case 'text':
128
+ state = {
129
+ id: stateId,
130
+ prop: propName,
131
+ name: stateName,
132
+ icon: undefined,
133
+ role: role || 'state',
134
+ write: writable,
135
+ read: true,
136
+ type: 'string',
137
+ };
138
+ if (expose.endpoint) {
139
+ state.epname = expose.endpoint;
140
+ }
141
+ break;
142
+
143
+ default:
144
+ break;
145
+ }
146
+
147
+ return state;
121
148
  }
122
149
 
123
150
  function createFromExposes(deviceID, ieee_address, definitions, power_source, scenes, config) {
124
- const states = [];
125
- // make the different (set and get) part of state is updatable if different exposes is used for get and set
126
- // as example:
127
- // ...
128
- // exposes.binary('some_option', ea.STATE, true, false).withDescription('Some Option'),
129
- // exposes.composite('options', 'options')
130
- // .withDescription('Some composite Options')
131
- // .withFeature(exposes.binary('some_option', ea.SET, true, false).withDescription('Some Option'))
132
- //in this case one state - `some_option` has two different exposes for set an get, we have to combine it ...
133
-
134
- function pushToStates(state, access) {
135
- if (state === undefined) {
136
- return 0;
137
- }
138
-
139
- state.readable = true;
140
- state.writable = access > 1;
141
- const stateExists = states.findIndex((x, _index, _array) => (x.id === state.id));
142
-
143
- if (stateExists < 0) {
144
- state.write = state.writable;
145
- if (!state.writable) {
146
- if (state.hasOwnProperty('setter')) {
147
- delete state.setter;
148
- }
149
-
150
- if (state.hasOwnProperty('setattr')) {
151
- delete state.setattr;
152
- }
153
- }
154
-
155
- if (!state.readable) {
156
- if (state.hasOwnProperty('getter')) {
157
- //to awid some worning on unprocessed data
158
- state.getter = _payload => (undefined);
159
- }
160
- }
161
-
162
- return states.push(state);
163
- } else {
164
-
165
- if ((state.readable) && (!states[stateExists].readable)) {
166
- states[stateExists].read = state.read;
167
- // as state is readable, it can't be button or event
168
- if (states[stateExists].role === 'button') {
169
- states[stateExists].role = state.role;
170
- }
171
-
172
- if (states[stateExists].hasOwnProperty('isEvent')) {
173
- delete states[stateExists].isEvent;
174
- }
175
-
176
- // we have to use the getter from "new" state
177
- if (state.hasOwnProperty('getter')) {
178
- states[stateExists].getter = state.getter;
179
- }
180
-
181
- // trying to remove the `prop` property, as main key for get and set,
182
- // as it can be different in new and old states, and leave only:
183
- // setattr for old and id for new
184
- if ((state.hasOwnProperty('prop')) && (state.prop === state.id)) {
185
- if (states[stateExists].hasOwnProperty('prop')) {
186
- if (states[stateExists].prop !== states[stateExists].id) {
187
- if (!states[stateExists].hasOwnProperty('setattr')) {
188
- states[stateExists].setattr = states[stateExists].prop;
189
- }
190
- }
191
- delete states[stateExists].prop;
192
- }
193
- } else if (state.hasOwnProperty('prop')) {
194
- states[stateExists].prop = state.prop;
195
- }
196
- states[stateExists].readable = true;
197
- }
198
-
199
- if ((state.writable) && (!states[stateExists].writable)) {
200
- states[stateExists].write = state.writable;
201
- // use new state `setter`
202
- if (state.hasOwnProperty('setter')) {
203
- states[stateExists].setter = state.setter;
204
- }
205
-
206
- // use new state `setterOpt`
207
- if (state.hasOwnProperty('setterOpt')) {
208
- states[stateExists].setterOpt = state.setterOpt;
209
- }
210
-
211
- // use new state `inOptions`
212
- if (state.hasOwnProperty('inOptions')) {
213
- states[stateExists].inOptions = state.inOptions;
214
- }
215
-
216
- // as we have new state, responsible for set, we have to use new `isOption`
217
- // or remove it
218
- if (((!state.hasOwnProperty('isOption')) || (state.isOptions === false)) && (states[stateExists].hasOwnProperty('isOption'))) {
219
- delete states[stateExists].isOption;
220
- } else {
221
- states[stateExists].isOption = state.isOption;
222
- }
223
-
224
- // use new `setattr` or `prop` as `setattr`
225
- if (state.hasOwnProperty('setattr')) {
226
- states[stateExists].setattr = state.setattr;
227
- } else if (state.hasOwnProperty('prop')) {
228
- states[stateExists].setattr = state.prop;
229
- }
230
-
231
- // remove `prop` equal to if, due to prop is uses as key in set and get
232
- if (states[stateExists].prop === states[stateExists].id) {
233
- delete states[stateExists].prop;
234
- }
235
-
236
- if (state.hasOwnProperty('epname')) {
237
- states[stateExists].epname = state.epname;
238
- }
239
- states[stateExists].writable = true;
240
- }
241
-
242
- return states.length;
243
- }
244
- }
245
-
246
- for (const expose of definitions.exposes) {
247
- let state;
248
-
249
- switch (expose.type) {
250
- case 'light':
251
- for (const prop of expose.features) {
252
- switch (prop.name) {
253
- case 'state': {
254
- const stateNameS = expose.endpoint ? `state_${expose.endpoint}` : 'state';
255
- pushToStates({
256
- id: stateNameS,
257
- name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
258
- icon: undefined,
259
- role: 'switch',
260
- write: true,
261
- read: true,
262
- type: 'boolean',
263
- getter: (payload) => (payload[stateNameS] === (prop.value_on || 'ON')),
264
- setter: (value) => (value) ? prop.value_on || 'ON' : ((prop.value_off != undefined) ? prop.value_off : 'OFF'),
265
- epname: expose.endpoint,
266
- setattr: 'state',
267
- }, prop.access);
268
- break;
269
- }
270
- case 'brightness': {
271
- const stateNameB = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
272
- pushToStates({
273
- id: stateNameB,
274
- name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
275
- icon: undefined,
276
- role: 'level.dimmer',
277
- write: true,
278
- read: true,
279
- type: 'number',
280
- min: 0, // ignore expose.value_min
281
- max: 100, // ignore expose.value_max
282
- inOptions: true,
283
- unit: '%',
284
- getter: (value) => {
285
- return utils.bulbLevelToAdapterLevel(value[stateNameB]);
286
- },
287
- setter: (value) => {
288
- return utils.adapterLevelToBulbLevel(value);
289
- },
290
- setterOpt: (value, options) => {
291
- const hasTransitionTime = options && options.hasOwnProperty('transition_time');
292
- const transitionTime = hasTransitionTime ? options.transition_time : 0;
293
- const preparedOptions = { ...options, transition: transitionTime };
294
- preparedOptions.brightness = utils.adapterLevelToBulbLevel(value);
295
- return preparedOptions;
296
- },
297
- readResponse: (resp) => {
298
- const respObj = resp[0];
299
- if (respObj.status === 0 && respObj.attrData != undefined) {
300
- return utils.bulbLevelToAdapterLevel(respObj.attrData);
301
- }
302
- },
303
- epname: expose.endpoint,
304
- setattr: 'brightness',
305
- }, prop.access);
306
- pushToStates(statesDefs.brightness_move, prop.access);
307
- break;
308
- }
309
- case 'color_temp': {
310
- const stateNameT = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
311
- pushToStates({
312
- id: stateNameT,
313
- prop: expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp',
314
- name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
315
- icon: undefined,
316
- role: 'level.color.temperature',
317
- write: true,
318
- read: true,
319
- type: 'number',
320
- min: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_max) : prop.value_min,
321
- max: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_min) : prop.value_max,
322
- unit: config.useKelvin == true ? 'K' : 'mired',
323
- setter: (value) => {
324
- return utils.toMired(value);
325
- },
326
- // setterOpt: (_value, options) => {
327
- // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
328
- // const transitionTime = hasTransitionTime ? options.transition_time : 0;
329
- // return { ...options, transition: transitionTime };
330
- // },
331
- getter: (payload) => {
332
- if (payload.color_mode != 'color_temp') {
333
- return undefined;
334
- }
335
- if (config.useKelvin == true) {
336
- return utils.miredKelvinConversion(payload.color_temp);
337
- } else {
338
- return payload.color_temp;
339
- }
340
- },
341
- epname: expose.endpoint,
342
- setattr: 'color_temp',
343
- }, prop.access);
344
- pushToStates(statesDefs.colortemp_move, prop.access);
345
- break;
346
- }
347
- case 'color_xy': {
348
- const stateNameC = expose.endpoint ? `color_${expose.endpoint}` : 'color';
349
- pushToStates({
350
- id: stateNameC,
351
- prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
352
- name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
353
- icon: undefined,
354
- role: 'level.color.rgb',
355
- write: true,
356
- read: true,
357
- type: 'string',
358
- setter: (value) => {
359
-
360
- let xy = [0, 0];
361
- const rgbcolor = colors.ParseColor(value);
362
-
363
- xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
364
- return {
365
- x: xy[0],
366
- y: xy[1]
367
- };
368
-
369
- },
370
- getter: payload => {
371
- if (payload.color_mode != 'xy' && config.colorTempSyncColor == false) {
372
- return undefined;
373
- }
374
- if (payload.color && payload.color.hasOwnProperty('x') && payload.color.hasOwnProperty('y')) {
375
- const colorval = rgb.cie_to_rgb(payload.color.x, payload.color.y);
376
- return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
377
- } else {
378
- return undefined;
379
- }
380
- },
381
- epname: expose.endpoint,
382
- setattr: 'color',
383
- }, 2);
384
- break;
385
- }
386
- // case 'color_hs': {
387
- // const stateNameH = expose.endpoint ? `color_${expose.endpoint}` : 'color';
388
- // pushToStates({
389
- // id: stateNameH,
390
- // prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
391
- // name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
392
- // icon: undefined,
393
- // role: 'level.color.rgb',
394
- // write: true,
395
- // read: true,
396
- // type: 'string',
397
- // setter: (value) => {
398
- // const _rgb = colors.ParseColor(value);
399
- // const hsv = rgb.rgbToHSV(_rgb.r, _rgb.g, _rgb.b, true);
400
- // return {
401
- // hue: Math.min(Math.max(hsv.h, 1), 359),
402
- // saturation: hsv.s,
403
- // // brightness: Math.floor(hsv.v * 2.55),
404
-
405
- // };
406
- // },
407
- // setterOpt: (_value, options) => {
408
- // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
409
- // const transitionTime = hasTransitionTime ? options.transition_time : 0;
410
- // return { ...options, transition: transitionTime };
411
- // },
412
- // epname: expose.endpoint,
413
- // setattr: 'color',
414
- // }, prop.access);
415
- // pushToStates({
416
- // id: expose.endpoint ? `hue_${expose.endpoint}` : 'hue',
417
- // prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
418
- // name: `Hue ${expose.endpoint ? expose.endpoint : ''}`.trim(),
419
- // icon: undefined,
420
- // role: 'level.color.hue',
421
- // write: true,
422
- // read: false,
423
- // type: 'number',
424
- // min: 0,
425
- // max: 360,
426
- // inOptions: true,
427
- // setter: (value, options) => {
428
- // return {
429
- // hue: value,
430
- // saturation: options.saturation,
431
- // };
432
- // },
433
- // setterOpt: (_value, options) => {
434
- // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
435
- // const transitionTime = hasTransitionTime ? options.transition_time : 0;
436
- // const hasHueCalibrationTable = options && options.hasOwnProperty('hue_calibration');
437
- // if (hasHueCalibrationTable)
438
- // try {
439
- // return { ...options, transition: transitionTime, hue_correction: JSON.parse(options.hue_calibration) };
440
- // }
441
- // catch (err) {
442
- // const hue_correction_table = [];
443
- // options.hue_calibration.split(',').forEach(element => {
444
- // const match = /([0-9]+):([0-9]+)/.exec(element);
445
- // if (match && match.length == 3)
446
- // hue_correction_table.push({ in: Number(match[1]), out: Number(match[2]) });
447
- // });
448
- // if (hue_correction_table.length > 0)
449
- // return { ...options, transition: transitionTime, hue_correction: hue_correction_table };
450
- // }
451
- // return { ...options, transition: transitionTime };
452
- // },
453
-
454
- // }, prop.access);
455
- // pushToStates({
456
- // id: expose.endpoint ? `saturation_${expose.endpoint}` : 'saturation',
457
- // prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
458
- // name: `Saturation ${expose.endpoint ? expose.endpoint : ''}`.trim(),
459
- // icon: undefined,
460
- // role: 'level.color.saturation',
461
- // write: true,
462
- // read: false,
463
- // type: 'number',
464
- // min: 0,
465
- // max: 100,
466
- // inOptions: true,
467
- // setter: (value, options) => {
468
- // return {
469
- // hue: options.hue,
470
- // saturation: value,
471
- // };
472
- // },
473
- // setterOpt: (_value, options) => {
474
- // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
475
- // const transitionTime = hasTransitionTime ? options.transition_time : 0;
476
- // const hasHueCalibrationTable = options && options.hasOwnProperty('hue_calibration');
477
- // if (hasHueCalibrationTable)
478
- // try {
479
- // return { ...options, transition: transitionTime, hue_correction: JSON.parse(options.hue_calibration) };
480
- // }
481
- // catch (err) {
482
- // const hue_correction_table = [];
483
- // options.hue_calibration.split(',').forEach(element => {
484
- // const match = /([0-9]+):([0-9]+)/.exec(element);
485
- // if (match && match.length == 3)
486
- // hue_correction_table.push({ in: Number(match[1]), out: Number(match[2]) });
487
- // });
488
- // if (hue_correction_table.length > 0)
489
- // return { ...options, transition: transitionTime, hue_correction: hue_correction_table };
490
- // }
491
- // return { ...options, transition: transitionTime };
492
- // },
493
-
494
- // }, prop.access);
495
- // pushToStates(statesDefs.hue_move, prop.access);
496
- // pushToStates(statesDefs.saturation_move, prop.access);
497
- // pushToStates({
498
- // id: 'hue_calibration',
499
- // prop: 'color',
500
- // name: 'Hue color calibration table',
501
- // icon: undefined,
502
- // role: 'table',
503
- // write: true,
504
- // read: false,
505
- // type: 'string',
506
- // inOptions: true,
507
- // setter: (_value, options) => {
508
- // return {
509
- // hue: options.hue,
510
- // saturation: options.saturation,
511
- // };
512
- // },
513
- // setterOpt: (_value, options) => {
514
- // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
515
- // const transitionTime = hasTransitionTime ? options.transition_time : 0;
516
- // const hasHueCalibrationTable = options && options.hasOwnProperty('hue_calibration');
517
- // if (hasHueCalibrationTable)
518
- // try {
519
- // return { ...options, transition: transitionTime, hue_correction: JSON.parse(options.hue_calibration) };
520
- // }
521
- // catch (err) {
522
- // const hue_correction_table = [];
523
- // options.hue_calibration.split(',').forEach(element => {
524
- // const match = /([0-9]+):([0-9]+)/.exec(element);
525
- // if (match && match.length == 3)
526
- // hue_correction_table.push({ in: Number(match[1]), out: Number(match[2]) });
527
- // });
528
- // if (hue_correction_table.length > 0)
529
- // return { ...options, transition: transitionTime, hue_correction: hue_correction_table };
530
- // }
531
- // return { ...options, transition: transitionTime };
532
- // },
533
-
534
- // }, prop.access);
535
- // break;
536
- // }
537
- default:
538
- pushToStates(genState(prop), prop.access);
539
- break;
540
- }
541
- }
542
- // First don't create a transition_time datapoint, this will be set in the backend
543
- //pushToStates(statesDefs.transition_time, 2);
544
- break;
545
-
546
- case 'switch':
547
- for (const prop of expose.features) {
548
- switch (prop.name) {
549
- case 'state':
550
- pushToStates(genState(prop, 'switch'), prop.access);
551
- break;
552
- default:
553
- pushToStates(genState(prop), prop.access);
554
- break;
555
- }
556
- }
557
- break;
558
-
559
- case 'numeric':
560
- if (expose.endpoint) {
561
- state = genState(expose);
562
- } else {
563
- switch (expose.name) {
564
- case 'linkquality':
565
- state = statesDefs.link_quality;
566
- break;
567
-
568
- case 'battery':
569
- state = statesDefs.battery;
570
- break;
571
-
572
- case 'temperature':
573
- state = statesDefs.temperature;
574
- break;
575
-
576
- case 'device_temperature':
577
- state = statesDefs.device_temperature;
578
- break;
579
-
580
- case 'humidity':
581
- state = statesDefs.humidity;
582
- break;
583
-
584
- case 'pressure':
585
- state = statesDefs.pressure;
586
- break;
587
-
588
- case 'illuminance':
589
- state = statesDefs.illuminance_raw;
590
- break;
591
-
592
- case 'illuminance_lux':
593
- state = statesDefs.illuminance;
594
- break;
595
-
596
- case 'power':
597
- state = statesDefs.load_power;
598
- break;
599
-
600
- case 'current':
601
- state = statesDefs.load_current;
602
- break;
603
-
604
- case 'voltage':
605
- state = statesDefs.voltage;
606
- if (power_source == 'Battery') {
607
- state = statesDefs.battery_voltage;
608
- }
609
- if (expose.unit == 'mV') {
610
- state.getter = payload => payload.voltage / 1000;
611
- }
612
- break;
613
-
614
- case 'energy':
615
- state = statesDefs.energy;
616
- break;
617
-
618
- default:
619
- state = genState(expose);
620
- break;
621
- }
622
- }
623
- if (state) pushToStates(state, expose.access);
624
- break;
625
-
626
- case 'enum':
627
- switch (expose.name) {
628
- case 'action': {
629
-
630
- // Ansatz:
631
-
632
- // Action aufspalten in 2 Blöcke:
633
- // Action (bekommt text ausser hold und release, auto reset nach 250 ms)
634
- // Hold: wird gesetzt bei hold, gelöscht bei passendem Release
635
-
636
- if (!Array.isArray(expose.values)) break;
637
- const hasHold = expose.values.find((actionName) => actionName.includes('hold'));
638
- const hasRelease = expose.values.find((actionName) => actionName.includes('release'));
639
- for (const actionName of expose.values) {
640
- // is release state ? - skip
641
- if (hasHold && hasRelease && actionName.includes('release')) continue;
642
- // is hold state ?
643
- if (hasHold && hasRelease && actionName.includes('hold')) {
644
- state = {
645
- id: actionName.replace(/\*/g, ''),
646
- prop: 'action',
647
- name: actionName,
648
- icon: undefined,
649
- role: 'button',
650
- write: false,
651
- read: true,
652
- def: false,
653
- type: 'boolean',
654
- getter: (payload) => {
655
- if (payload.action === actionName) {
656
- return true;
657
- }
658
- if (payload.action === actionName.replace('hold', 'release')) {
659
- return false;
660
- }
661
- if (payload.action === `${actionName}_release`) {
662
- return false;
663
- }
664
- return undefined;
665
- },
666
- };
667
- } else {
668
- state = {
669
- id: actionName.replace(/\*/g, ''),
670
- prop: 'action',
671
- name: actionName,
672
- icon: undefined,
673
- role: 'button',
674
- write: false,
675
- read: true,
676
- type: 'boolean',
677
- def: false,
678
- isEvent: true,
679
- getter: payload => (payload.action === actionName) ? true : undefined,
680
- };
681
- }
682
- pushToStates(state, expose.access);
683
- }
684
- if (!blacklistSimulatedBrightness.includes(definitions.model)) {
685
- pushToStates({
686
- id: 'simulated_brightness',
687
- prop: 'brightness',
688
- name: 'Simulated brightness',
689
- icon: undefined,
690
- role: 'level.dimmer',
691
- write: true,
692
- read: true,
693
- type: 'number',
694
- unit: '%',
695
- def: 0,
696
- getter: payload => {
697
- return utils.bulbLevelToAdapterLevel(payload.brightness);
698
- },
699
- }, 1);
700
- }
701
- state = null;
702
- break;
703
- }
704
- default:
705
- state = genState(expose);
706
- break;
707
- }
708
- if (state) pushToStates(state, expose.access);
709
- break;
710
-
711
- case 'binary':
712
- if (expose.endpoint) {
713
- state = genState(expose);
714
- } else {
715
- switch (expose.name) {
716
- case 'contact':
717
- state = statesDefs.contact;
718
- pushToStates(statesDefs.opened, 1);
719
- break;
720
-
721
- case 'battery_low':
722
- state = statesDefs.heiman_batt_low;
723
- break;
724
-
725
- case 'tamper':
726
- state = statesDefs.tamper;
727
- break;
728
-
729
- case 'water_leak':
730
- state = statesDefs.water_detected;
731
- break;
732
-
733
- case 'lock':
734
- state = statesDefs.child_lock;
735
- break;
736
-
737
- case 'occupancy':
738
- state = statesDefs.occupancy;
739
- break;
740
-
741
- default:
742
- state = genState(expose);
743
- break;
744
- }
745
- }
746
- if (state) pushToStates(state, expose.access);
747
- break;
748
-
749
- case 'text':
750
- state = genState(expose);
751
- pushToStates(state, expose.access);
752
- break;
753
-
754
- case 'lock':
755
- case 'fan':
756
- case 'cover':
757
- for (const prop of expose.features) {
758
- switch (prop.name) {
759
- case 'state':
760
- pushToStates(genState(prop, 'switch'), prop.access);
761
- break;
762
- default:
763
- pushToStates(genState(prop), prop.access);
764
- break;
765
- }
766
- }
767
- break;
768
-
769
- case 'climate':
770
- for (const prop of expose.features) {
771
- switch (prop.name) {
772
- case 'away_mode':
773
- pushToStates(statesDefs.climate_away_mode, prop.access);
774
- break;
775
- case 'system_mode':
776
- pushToStates(statesDefs.climate_system_mode, prop.access);
777
- break;
778
- case 'running_mode':
779
- pushToStates(statesDefs.climate_running_mode, prop.access);
780
- break;
781
- default:
782
- {
783
- if (prop.name.includes('heating_setpoint')) {
784
- pushToStates(genState(prop, 'level.temperature'), prop.access);
785
- } else {
786
- pushToStates(genState(prop), prop.access);
787
- }
788
- }
789
- break;
790
- }
791
- }
792
- break;
793
-
794
- case 'composite':
795
- for (const prop of expose.features) {
796
- const st = genState(prop);
797
- st.prop = expose.property;
798
- st.inOptions = true;
799
- // I'm not fully sure, as it really needed, but
800
- st.setterOpt = (value, options) => {
801
- const result = {};
802
- options[prop.property] = value;
803
- result[expose.property] = options;
804
- return result;
805
- };
806
- // if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
807
- if (prop.access & 2) {
808
- st.setter = (value, options) => {
809
- const result = {};
810
- options[prop.property] = value;
811
- result[expose.property] = options;
812
- return result;
813
- };
814
- st.setattr = expose.property;
815
- }
816
- // if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
817
- if (prop.access & 1) {
818
- st.getter = payload => {
819
- if ((payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
820
- return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined;
821
- } else {
822
- return undefined;
823
- }
824
- };
825
- } else {
826
- st.getter = _payload => { return undefined; };
827
- }
828
- pushToStates(st, prop.access);
829
- }
830
- break;
831
- default:
832
- console.log(`Unhandled expose type ${expose.type} for device ${deviceID}`);
833
- }
834
- }
835
-
836
- const icon = utils.getDeviceIcon(definitions);
837
-
838
- // Add default states
839
- pushToStates(statesDefs.available, 1);
840
-
841
- const newDevice = {
842
- id: deviceID,
843
- ieee_address: ieee_address,
844
- icon: icon,
845
- power_source: power_source,
846
- states: states,
847
- };
848
-
849
- // Create buttons for scenes
850
- for (const scene of scenes) {
851
- const sceneSate = {
852
- id: `scene_${scene.id}`,
853
- prop: `scene_recall`,
854
- name: scene.name,
855
- icon: undefined,
856
- role: 'button',
857
- write: true,
858
- read: true,
859
- type: 'boolean',
860
- setter: (value) => (value) ? scene.id : undefined
861
- };
862
- // @ts-ignore
863
- newDevice.states.push(sceneSate);
864
- }
865
-
866
- return newDevice;
151
+ const states = [];
152
+ // make the different (set and get) part of state is updatable if different exposes is used for get and set
153
+ // as example:
154
+ // ...
155
+ // exposes.binary('some_option', ea.STATE, true, false).withDescription('Some Option'),
156
+ // exposes.composite('options', 'options')
157
+ // .withDescription('Some composite Options')
158
+ // .withFeature(exposes.binary('some_option', ea.SET, true, false).withDescription('Some Option'))
159
+ //in this case one state - `some_option` has two different exposes for set an get, we have to combine it ...
160
+
161
+ function pushToStates(state, access) {
162
+ if (state === undefined) {
163
+ return 0;
164
+ }
165
+ if (access === undefined) access = z2mAccess.ALL;
166
+ state.readable = (access & z2mAccess.STATE) > 0;
167
+ state.writable = (access & z2mAccess.SET) > 0;
168
+ const stateExists = states.findIndex((x, _index, _array) => (x.id === state.id));
169
+
170
+ if (stateExists < 0) {
171
+ state.write = state.writable;
172
+ if (!state.writable) {
173
+ if (state.hasOwnProperty('setter')) {
174
+ delete state.setter;
175
+ }
176
+
177
+ if (state.hasOwnProperty('setattr')) {
178
+ delete state.setattr;
179
+ }
180
+ }
181
+
182
+ if (!state.readable) {
183
+ if (state.hasOwnProperty('getter')) {
184
+ //to awid some worning on unprocessed data
185
+ state.getter = _payload => (undefined);
186
+ }
187
+ }
188
+
189
+ return states.push(state);
190
+ } else {
191
+
192
+ if ((state.readable) && (!states[stateExists].readable)) {
193
+ states[stateExists].read = state.read;
194
+ // as state is readable, it can't be button or event
195
+ if (states[stateExists].role === 'button') {
196
+ states[stateExists].role = state.role;
197
+ }
198
+
199
+ if (states[stateExists].hasOwnProperty('isEvent')) {
200
+ delete states[stateExists].isEvent;
201
+ }
202
+
203
+ // we have to use the getter from "new" state
204
+ if (state.hasOwnProperty('getter')) {
205
+ states[stateExists].getter = state.getter;
206
+ }
207
+
208
+ // trying to remove the `prop` property, as main key for get and set,
209
+ // as it can be different in new and old states, and leave only:
210
+ // setattr for old and id for new
211
+ if ((state.hasOwnProperty('prop')) && (state.prop === state.id)) {
212
+ if (states[stateExists].hasOwnProperty('prop')) {
213
+ if (states[stateExists].prop !== states[stateExists].id) {
214
+ if (!states[stateExists].hasOwnProperty('setattr')) {
215
+ states[stateExists].setattr = states[stateExists].prop;
216
+ }
217
+ }
218
+ delete states[stateExists].prop;
219
+ }
220
+ } else if (state.hasOwnProperty('prop')) {
221
+ states[stateExists].prop = state.prop;
222
+ }
223
+ states[stateExists].readable = true;
224
+ }
225
+
226
+ if ((state.writable) && (!states[stateExists].writable)) {
227
+ states[stateExists].write = state.writable;
228
+ // use new state `setter`
229
+ if (state.hasOwnProperty('setter')) {
230
+ states[stateExists].setter = state.setter;
231
+ }
232
+
233
+ // use new state `setterOpt`
234
+ if (state.hasOwnProperty('setterOpt')) {
235
+ states[stateExists].setterOpt = state.setterOpt;
236
+ }
237
+
238
+ // use new state `inOptions`
239
+ if (state.hasOwnProperty('inOptions')) {
240
+ states[stateExists].inOptions = state.inOptions;
241
+ }
242
+
243
+ // as we have new state, responsible for set, we have to use new `isOption`
244
+ // or remove it
245
+ if (((!state.hasOwnProperty('isOption')) || (state.isOptions === false)) && (states[stateExists].hasOwnProperty('isOption'))) {
246
+ delete states[stateExists].isOption;
247
+ } else {
248
+ states[stateExists].isOption = state.isOption;
249
+ }
250
+
251
+ // use new `setattr` or `prop` as `setattr`
252
+ if (state.hasOwnProperty('setattr')) {
253
+ states[stateExists].setattr = state.setattr;
254
+ } else if (state.hasOwnProperty('prop')) {
255
+ states[stateExists].setattr = state.prop;
256
+ }
257
+
258
+ // remove `prop` equal to if, due to prop is uses as key in set and get
259
+ if (states[stateExists].prop === states[stateExists].id) {
260
+ delete states[stateExists].prop;
261
+ }
262
+
263
+ if (state.hasOwnProperty('epname')) {
264
+ states[stateExists].epname = state.epname;
265
+ }
266
+ states[stateExists].writable = true;
267
+ }
268
+
269
+ return states.length;
270
+ }
271
+ }
272
+
273
+ for (const expose of definitions.exposes) {
274
+ let state;
275
+
276
+ switch (expose.type) {
277
+ case 'light':
278
+ for (const prop of expose.features) {
279
+ switch (prop.name) {
280
+ case 'state': {
281
+ const stateNameS = expose.endpoint ? `state_${expose.endpoint}` : 'state';
282
+ pushToStates({
283
+ id: stateNameS,
284
+ name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
285
+ icon: undefined,
286
+ role: 'switch',
287
+ write: true,
288
+ read: true,
289
+ type: 'boolean',
290
+ getter: (payload) => (payload[stateNameS] === (prop.value_on || 'ON')),
291
+ setter: (value) => (value) ? prop.value_on || 'ON' : ((prop.value_off != undefined) ? prop.value_off : 'OFF'),
292
+ epname: expose.endpoint,
293
+ setattr: 'state',
294
+ }, prop.access);
295
+ break;
296
+ }
297
+ case 'brightness': {
298
+ const stateNameB = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
299
+ pushToStates({
300
+ id: stateNameB,
301
+ name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
302
+ icon: undefined,
303
+ role: 'level.dimmer',
304
+ write: true,
305
+ read: true,
306
+ type: 'number',
307
+ min: 0, // ignore expose.value_min
308
+ max: 100, // ignore expose.value_max
309
+ inOptions: true,
310
+ unit: '%',
311
+ getter: (value) => {
312
+ return utils.bulbLevelToAdapterLevel(value[stateNameB]);
313
+ },
314
+ setter: (value) => {
315
+ return utils.adapterLevelToBulbLevel(value);
316
+ },
317
+ setterOpt: (value, options) => {
318
+ const hasTransitionTime = options && options.hasOwnProperty('transition_time');
319
+ const transitionTime = hasTransitionTime ? options.transition_time : 0;
320
+ const preparedOptions = { ...options, transition: transitionTime };
321
+ preparedOptions.brightness = utils.adapterLevelToBulbLevel(value);
322
+ return preparedOptions;
323
+ },
324
+ readResponse: (resp) => {
325
+ const respObj = resp[0];
326
+ if (respObj.status === 0 && respObj.attrData != undefined) {
327
+ return utils.bulbLevelToAdapterLevel(respObj.attrData);
328
+ }
329
+ },
330
+ epname: expose.endpoint,
331
+ setattr: 'brightness',
332
+ }, prop.access);
333
+ pushToStates(statesDefs.brightness_move, prop.access);
334
+ break;
335
+ }
336
+ case 'color_temp': {
337
+ const stateNameT = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
338
+ pushToStates({
339
+ id: stateNameT,
340
+ prop: expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp',
341
+ name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
342
+ icon: undefined,
343
+ role: 'level.color.temperature',
344
+ write: true,
345
+ read: true,
346
+ type: 'number',
347
+ min: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_max) : prop.value_min,
348
+ max: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_min) : prop.value_max,
349
+ unit: config.useKelvin == true ? 'K' : 'mired',
350
+ setter: (value) => {
351
+ return utils.toMired(value);
352
+ },
353
+ // setterOpt: (_value, options) => {
354
+ // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
355
+ // const transitionTime = hasTransitionTime ? options.transition_time : 0;
356
+ // return { ...options, transition: transitionTime };
357
+ // },
358
+ getter: (payload) => {
359
+ if (payload.color_mode != 'color_temp') {
360
+ return undefined;
361
+ }
362
+ if (config.useKelvin == true) {
363
+ return utils.miredKelvinConversion(payload.color_temp);
364
+ } else {
365
+ return payload.color_temp;
366
+ }
367
+ },
368
+ epname: expose.endpoint,
369
+ setattr: 'color_temp',
370
+ }, prop.access);
371
+ pushToStates(statesDefs.colortemp_move, prop.access);
372
+ break;
373
+ }
374
+ case 'color_xy': {
375
+ const stateNameC = expose.endpoint ? `color_${expose.endpoint}` : 'color';
376
+ pushToStates({
377
+ id: stateNameC,
378
+ prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
379
+ name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
380
+ icon: undefined,
381
+ role: 'level.color.rgb',
382
+ write: true,
383
+ read: true,
384
+ type: 'string',
385
+ setter: (value) => {
386
+
387
+ let xy = [0, 0];
388
+ const rgbcolor = colors.ParseColor(value);
389
+
390
+ xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
391
+ return {
392
+ x: xy[0],
393
+ y: xy[1]
394
+ };
395
+
396
+ },
397
+ getter: payload => {
398
+ if (payload.color_mode != 'xy' && config.colorTempSyncColor == false) {
399
+ return undefined;
400
+ }
401
+ if (payload.color && payload.color.hasOwnProperty('x') && payload.color.hasOwnProperty('y')) {
402
+ const colorval = rgb.cie_to_rgb(payload.color.x, payload.color.y);
403
+ return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
404
+ } else {
405
+ return undefined;
406
+ }
407
+ },
408
+ epname: expose.endpoint,
409
+ setattr: 'color',
410
+ }, prop.access);
411
+ break;
412
+ }
413
+ case 'color_hs': {
414
+ const stateNameH = expose.endpoint ? `color_${expose.endpoint}` : 'color';
415
+ pushToStates({
416
+ id: stateNameH,
417
+ prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
418
+ name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
419
+ icon: undefined,
420
+ role: 'level.color.rgb',
421
+ write: true,
422
+ read: true,
423
+ type: 'string',
424
+ setter: (value) => {
425
+ const _rgb = colors.ParseColor(value);
426
+ const hsv = rgb.rgbToHSV(_rgb.r, _rgb.g, _rgb.b, true);
427
+ return {
428
+ h: Math.min(Math.max(hsv.h, 1), 359),
429
+ s: hsv.s,
430
+ //b: Math.round(hsv.v * 2.55),
431
+
432
+ };
433
+ },
434
+ getter: payload => {
435
+ if (payload.color_mode != 'hs') {
436
+ return undefined;
437
+ }
438
+ if (payload.color && payload.color.hasOwnProperty('h') && payload.color.hasOwnProperty('s') & payload.color.hasOwnProperty('b')) {
439
+ return rgb.hsvToRGBString(payload.color.h, payload.color.s, Math.round(payload.color.b / 2.55));
440
+ } else {
441
+ return undefined;
442
+ }
443
+ },
444
+ epname: expose.endpoint,
445
+ setattr: 'color',
446
+ }, prop.access);
447
+ // pushToStates({
448
+ // id: expose.endpoint ? `hue_${expose.endpoint}` : 'hue',
449
+ // prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
450
+ // name: `Hue ${expose.endpoint ? expose.endpoint : ''}`.trim(),
451
+ // icon: undefined,
452
+ // role: 'level.color.hue',
453
+ // write: true,
454
+ // read: false,
455
+ // type: 'number',
456
+ // min: 0,
457
+ // max: 360,
458
+ // inOptions: true,
459
+ // setter: (value, options) => {
460
+ // return {
461
+ // hue: value,
462
+ // saturation: options.saturation,
463
+ // };
464
+ // },
465
+ // setterOpt: (_value, options) => {
466
+ // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
467
+ // const transitionTime = hasTransitionTime ? options.transition_time : 0;
468
+ // const hasHueCalibrationTable = options && options.hasOwnProperty('hue_calibration');
469
+ // if (hasHueCalibrationTable)
470
+ // try {
471
+ // return { ...options, transition: transitionTime, hue_correction: JSON.parse(options.hue_calibration) };
472
+ // }
473
+ // catch (err) {
474
+ // const hue_correction_table = [];
475
+ // options.hue_calibration.split(',').forEach(element => {
476
+ // const match = /([0-9]+):([0-9]+)/.exec(element);
477
+ // if (match && match.length == 3)
478
+ // hue_correction_table.push({ in: Number(match[1]), out: Number(match[2]) });
479
+ // });
480
+ // if (hue_correction_table.length > 0)
481
+ // return { ...options, transition: transitionTime, hue_correction: hue_correction_table };
482
+ // }
483
+ // return { ...options, transition: transitionTime };
484
+ // },
485
+
486
+ // }, prop.access);
487
+ // pushToStates({
488
+ // id: expose.endpoint ? `saturation_${expose.endpoint}` : 'saturation',
489
+ // prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
490
+ // name: `Saturation ${expose.endpoint ? expose.endpoint : ''}`.trim(),
491
+ // icon: undefined,
492
+ // role: 'level.color.saturation',
493
+ // write: true,
494
+ // read: false,
495
+ // type: 'number',
496
+ // min: 0,
497
+ // max: 100,
498
+ // inOptions: true,
499
+ // setter: (value, options) => {
500
+ // return {
501
+ // hue: options.hue,
502
+ // saturation: value,
503
+ // };
504
+ // },
505
+ // setterOpt: (_value, options) => {
506
+ // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
507
+ // const transitionTime = hasTransitionTime ? options.transition_time : 0;
508
+ // const hasHueCalibrationTable = options && options.hasOwnProperty('hue_calibration');
509
+ // if (hasHueCalibrationTable)
510
+ // try {
511
+ // return { ...options, transition: transitionTime, hue_correction: JSON.parse(options.hue_calibration) };
512
+ // }
513
+ // catch (err) {
514
+ // const hue_correction_table = [];
515
+ // options.hue_calibration.split(',').forEach(element => {
516
+ // const match = /([0-9]+):([0-9]+)/.exec(element);
517
+ // if (match && match.length == 3)
518
+ // hue_correction_table.push({ in: Number(match[1]), out: Number(match[2]) });
519
+ // });
520
+ // if (hue_correction_table.length > 0)
521
+ // return { ...options, transition: transitionTime, hue_correction: hue_correction_table };
522
+ // }
523
+ // return { ...options, transition: transitionTime };
524
+ // },
525
+
526
+ // }, 2);
527
+ // pushToStates(statesDefs.hue_move, 2);
528
+ // pushToStates(statesDefs.saturation_move, 2);
529
+ // pushToStates({
530
+ // id: 'hue_calibration',
531
+ // prop: 'color',
532
+ // name: 'Hue color calibration table',
533
+ // icon: undefined,
534
+ // role: 'table',
535
+ // write: true,
536
+ // read: false,
537
+ // type: 'string',
538
+ // inOptions: true,
539
+ // setter: (_value, options) => {
540
+ // return {
541
+ // hue: options.hue,
542
+ // saturation: options.saturation,
543
+ // };
544
+ // },
545
+ // setterOpt: (_value, options) => {
546
+ // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
547
+ // const transitionTime = hasTransitionTime ? options.transition_time : 0;
548
+ // const hasHueCalibrationTable = options && options.hasOwnProperty('hue_calibration');
549
+ // if (hasHueCalibrationTable)
550
+ // try {
551
+ // return { ...options, transition: transitionTime, hue_correction: JSON.parse(options.hue_calibration) };
552
+ // }
553
+ // catch (err) {
554
+ // const hue_correction_table = [];
555
+ // options.hue_calibration.split(',').forEach(element => {
556
+ // const match = /([0-9]+):([0-9]+)/.exec(element);
557
+ // if (match && match.length == 3)
558
+ // hue_correction_table.push({ in: Number(match[1]), out: Number(match[2]) });
559
+ // });
560
+ // if (hue_correction_table.length > 0)
561
+ // return { ...options, transition: transitionTime, hue_correction: hue_correction_table };
562
+ // }
563
+ // return { ...options, transition: transitionTime };
564
+ // },
565
+
566
+ // }, prop.access);
567
+ break;
568
+ }
569
+ default:
570
+ pushToStates(genState(prop), prop.access);
571
+ break;
572
+ }
573
+ }
574
+ // First don't create a transition_time datapoint, this will be set in the backend
575
+ //pushToStates(statesDefs.transition_time, 2);
576
+ break;
577
+
578
+ case 'switch':
579
+ for (const prop of expose.features) {
580
+ switch (prop.name) {
581
+ case 'state':
582
+ pushToStates(genState(prop, 'switch'), prop.access);
583
+ break;
584
+ default:
585
+ pushToStates(genState(prop), prop.access);
586
+ break;
587
+ }
588
+ }
589
+ break;
590
+
591
+ case 'numeric':
592
+ if (expose.endpoint) {
593
+ state = genState(expose);
594
+ } else {
595
+ switch (expose.name) {
596
+ case 'linkquality':
597
+ state = statesDefs.link_quality;
598
+ break;
599
+
600
+ case 'battery':
601
+ state = statesDefs.battery;
602
+ break;
603
+
604
+ case 'temperature':
605
+ state = statesDefs.temperature;
606
+ break;
607
+
608
+ case 'device_temperature':
609
+ state = statesDefs.device_temperature;
610
+ break;
611
+
612
+ case 'humidity':
613
+ state = statesDefs.humidity;
614
+ break;
615
+
616
+ case 'pressure':
617
+ state = statesDefs.pressure;
618
+ break;
619
+
620
+ case 'illuminance':
621
+ state = statesDefs.illuminance_raw;
622
+ break;
623
+
624
+ case 'illuminance_lux':
625
+ state = statesDefs.illuminance;
626
+ break;
627
+
628
+ case 'power':
629
+ state = statesDefs.load_power;
630
+ break;
631
+
632
+ case 'current':
633
+ state = statesDefs.load_current;
634
+ break;
635
+
636
+ case 'voltage':
637
+ state = statesDefs.voltage;
638
+ if (power_source == 'Battery') {
639
+ state = statesDefs.battery_voltage;
640
+ }
641
+ if (expose.unit == 'mV') {
642
+ state.getter = payload => payload.voltage / 1000;
643
+ }
644
+ break;
645
+
646
+ case 'energy':
647
+ state = statesDefs.energy;
648
+ break;
649
+
650
+ default:
651
+ state = genState(expose);
652
+ break;
653
+ }
654
+ }
655
+ if (state) pushToStates(state, expose.access);
656
+ break;
657
+
658
+ case 'enum':
659
+ switch (expose.name) {
660
+ case 'action': {
661
+
662
+ // Ansatz:
663
+
664
+ // Action aufspalten in 2 Blöcke:
665
+ // Action (bekommt text ausser hold und release, auto reset nach 250 ms)
666
+ // Hold: wird gesetzt bei hold, gelöscht bei passendem Release
667
+
668
+ if (!Array.isArray(expose.values)) break;
669
+ const hasHold = expose.values.find((actionName) => actionName.includes('hold'));
670
+ const hasRelease = expose.values.find((actionName) => actionName.includes('release'));
671
+ for (const actionName of expose.values) {
672
+ // is release state ? - skip
673
+ if (hasHold && hasRelease && actionName.includes('release')) continue;
674
+ // is hold state ?
675
+ if (hasHold && hasRelease && actionName.includes('hold')) {
676
+ state = {
677
+ id: actionName.replace(/\*/g, ''),
678
+ prop: 'action',
679
+ name: actionName,
680
+ icon: undefined,
681
+ role: 'button',
682
+ write: false,
683
+ read: true,
684
+ def: false,
685
+ type: 'boolean',
686
+ getter: (payload) => {
687
+ if (payload.action === actionName) {
688
+ return true;
689
+ }
690
+ if (payload.action === actionName.replace('hold', 'release')) {
691
+ return false;
692
+ }
693
+ if (payload.action === `${actionName}_release`) {
694
+ return false;
695
+ }
696
+ return undefined;
697
+ },
698
+ };
699
+ } else {
700
+ state = {
701
+ id: actionName.replace(/\*/g, ''),
702
+ prop: 'action',
703
+ name: actionName,
704
+ icon: undefined,
705
+ role: 'button',
706
+ write: false,
707
+ read: true,
708
+ type: 'boolean',
709
+ def: false,
710
+ isEvent: true,
711
+ getter: payload => (payload.action === actionName) ? true : undefined,
712
+ };
713
+ }
714
+ pushToStates(state, expose.access);
715
+ }
716
+ // Can the device simulated_brightness?
717
+ if (definitions.options && definitions.options.find(x => x.property == 'simulated_brightness')) {
718
+ pushToStates({
719
+ id: 'simulated_brightness',
720
+ prop: 'brightness',
721
+ name: 'Simulated brightness',
722
+ icon: undefined,
723
+ role: 'level.dimmer',
724
+ write: true,
725
+ read: true,
726
+ type: 'number',
727
+ unit: '%',
728
+ def: 0,
729
+ getter: payload => {
730
+ return utils.bulbLevelToAdapterLevel(payload.brightness);
731
+ },
732
+ }, expose.access);
733
+ }
734
+ state = null;
735
+ break;
736
+ }
737
+ default:
738
+ state = genState(expose);
739
+ break;
740
+ }
741
+ if (state) pushToStates(state, expose.access);
742
+ break;
743
+
744
+ case 'binary':
745
+ if (expose.endpoint) {
746
+ state = genState(expose);
747
+ } else {
748
+ switch (expose.name) {
749
+ case 'contact':
750
+ state = statesDefs.contact;
751
+ pushToStates(statesDefs.opened, expose.access);
752
+ break;
753
+
754
+ case 'battery_low':
755
+ state = statesDefs.heiman_batt_low;
756
+ break;
757
+
758
+ case 'tamper':
759
+ state = statesDefs.tamper;
760
+ break;
761
+
762
+ case 'water_leak':
763
+ state = statesDefs.water_detected;
764
+ break;
765
+
766
+ case 'lock':
767
+ state = statesDefs.child_lock;
768
+ break;
769
+
770
+ case 'occupancy':
771
+ state = statesDefs.occupancy;
772
+ break;
773
+
774
+ default:
775
+ state = genState(expose);
776
+ break;
777
+ }
778
+ }
779
+ if (state) pushToStates(state, expose.access);
780
+ break;
781
+
782
+ case 'text':
783
+ state = genState(expose);
784
+ pushToStates(state, expose.access);
785
+ break;
786
+
787
+ case 'lock':
788
+ case 'fan':
789
+ case 'cover':
790
+ for (const prop of expose.features) {
791
+ switch (prop.name) {
792
+ case 'state':
793
+ pushToStates(genState(prop, 'switch'), prop.access);
794
+ break;
795
+ default:
796
+ pushToStates(genState(prop), prop.access);
797
+ break;
798
+ }
799
+ }
800
+ break;
801
+
802
+ case 'climate':
803
+ for (const prop of expose.features) {
804
+ switch (prop.name) {
805
+ case 'away_mode':
806
+ pushToStates(statesDefs.climate_away_mode, prop.access);
807
+ break;
808
+ case 'system_mode':
809
+ pushToStates(statesDefs.climate_system_mode, prop.access);
810
+ break;
811
+ case 'running_mode':
812
+ pushToStates(statesDefs.climate_running_mode, prop.access);
813
+ break;
814
+ case 'local_temperature':
815
+ pushToStates(statesDefs.hvacThermostat_local_temp, prop.access);
816
+ break;
817
+ case 'local_temperature_calibration':
818
+ pushToStates(statesDefs.hvacThermostat_local_temp_calibration, prop.access);
819
+ break;
820
+ default:
821
+ {
822
+ if (prop.name.includes('heating_setpoint')) {
823
+ pushToStates(genState(prop, 'level.temperature'), prop.access);
824
+ } else {
825
+ pushToStates(genState(prop), prop.access);
826
+ }
827
+ }
828
+ break;
829
+ }
830
+ }
831
+ break;
832
+
833
+ case 'composite':
834
+ for (const prop of expose.features) {
835
+ const st = genState(prop);
836
+ st.prop = expose.property;
837
+ st.inOptions = true;
838
+ // I'm not fully sure, as it really needed, but
839
+ st.setterOpt = (value, options) => {
840
+ const result = {};
841
+ options[prop.property] = value;
842
+ result[expose.property] = options;
843
+ return result;
844
+ };
845
+ // if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
846
+ if (prop.access & z2mAccess.SET) {
847
+ st.setter = (value, options) => {
848
+ const result = {};
849
+ options[prop.property] = value;
850
+ result[expose.property] = options;
851
+ return result;
852
+ };
853
+ st.setattr = expose.property;
854
+ }
855
+ // if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
856
+ if (prop.access & z2mAccess.STATE) {
857
+ st.getter = payload => {
858
+ if ((payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
859
+ return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined;
860
+ } else {
861
+ return undefined;
862
+ }
863
+ };
864
+ } else {
865
+ st.getter = _payload => { return undefined; };
866
+ }
867
+ pushToStates(st, prop.access);
868
+ }
869
+ break;
870
+ default:
871
+ console.log(`Unhandled expose type ${expose.type} for device ${deviceID}`);
872
+ }
873
+ }
874
+
875
+ // If necessary, add states defined for this device model.
876
+ // Unfortunately this is necessary for some device models because they do not adhere to the standard
877
+ for (const state of getNonGenDevStatesDefs(definitions.model)) {
878
+ pushToStates(state, state.write ? z2mAccess.SET : z2mAccess.STATE);
879
+ }
880
+
881
+ // Add default states
882
+ pushToStates(statesDefs.available, z2mAccess.STATE);
883
+
884
+ const newDevice = {
885
+ id: deviceID,
886
+ ieee_address: ieee_address,
887
+ power_source: power_source,
888
+ states: states,
889
+ };
890
+
891
+ // Create buttons for scenes
892
+ for (const scene of scenes) {
893
+ const sceneSate = {
894
+ id: `scene_${scene.id}`,
895
+ prop: `scene_recall`,
896
+ name: scene.name,
897
+ icon: undefined,
898
+ role: 'button',
899
+ write: true,
900
+ read: true,
901
+ type: 'boolean',
902
+ setter: (value) => (value) ? scene.id : undefined
903
+ };
904
+ // @ts-ignore
905
+ newDevice.states.push(sceneSate);
906
+ }
907
+
908
+ return newDevice;
867
909
  }
868
910
 
869
911
  function defineDeviceFromExposes(devices, deviceID, ieee_address, definitions, power_source, scenes, config) {
870
- if (definitions.hasOwnProperty('exposes')) {
871
- const newDevice = createFromExposes(deviceID, ieee_address, definitions, power_source, scenes, config);
872
- devices.push(newDevice);
873
- }
912
+ if (definitions.hasOwnProperty('exposes')) {
913
+ const newDevice = createFromExposes(deviceID, ieee_address, definitions, power_source, scenes, config);
914
+ devices.push(newDevice);
915
+ }
874
916
  }
875
917
 
876
918
  module.exports = {
877
- defineDeviceFromExposes: defineDeviceFromExposes,
878
- };
919
+ defineDeviceFromExposes: defineDeviceFromExposes,
920
+ };