iobroker.zigbee2mqtt 2.2.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/exposes.js CHANGED
@@ -71,7 +71,7 @@ function genState(expose, role, name, desc) {
71
71
 
72
72
  if (writable) {
73
73
  state.setter = (payload) => (payload) ? (expose.value_on || 'ON') : ((expose.value_off != undefined) ? expose.value_off : 'OFF');
74
- state.setattr = expose.name;
74
+ state.setattr = expose.property;
75
75
  }
76
76
 
77
77
  if (expose.endpoint) {
@@ -109,7 +109,6 @@ function genState(expose, role, name, desc) {
109
109
  role: role || 'state',
110
110
  write: writable,
111
111
  read: true,
112
- type: 'string',
113
112
  states: {}
114
113
  };
115
114
 
@@ -118,6 +117,21 @@ function genState(expose, role, name, desc) {
118
117
  state.type = typeof (val);
119
118
  }
120
119
 
120
+ switch (state.type) {
121
+ case 'boolean':
122
+ state.def = false;
123
+ break;
124
+ case 'number':
125
+ state.def = 0;
126
+ break;
127
+ case 'object':
128
+ state.def = {};
129
+ break;
130
+ case 'string':
131
+ state.def = '';
132
+ break;
133
+ }
134
+
121
135
  if (expose.endpoint) {
122
136
  state.epname = expose.endpoint;
123
137
  state.setattr = expose.name;
@@ -149,15 +163,6 @@ function genState(expose, role, name, desc) {
149
163
 
150
164
  function createFromExposes(deviceID, ieee_address, definitions, power_source, scenes, config) {
151
165
  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
166
  function pushToStates(state, access) {
162
167
  if (state === undefined) {
163
168
  return 0;
@@ -278,26 +283,42 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
278
283
  for (const prop of expose.features) {
279
284
  switch (prop.name) {
280
285
  case 'state': {
281
- const stateNameS = expose.endpoint ? `state_${expose.endpoint}` : 'state';
286
+ const stateName = expose.endpoint ? `state_${expose.endpoint}` : 'state';
282
287
  pushToStates({
283
- id: stateNameS,
288
+ id: stateName,
284
289
  name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
285
290
  icon: undefined,
286
291
  role: 'switch',
287
292
  write: true,
288
293
  read: true,
289
294
  type: 'boolean',
290
- getter: (payload) => (payload[stateNameS] === (prop.value_on || 'ON')),
295
+ getter: (payload) => (payload[stateName] === (prop.value_on || 'ON')),
291
296
  setter: (value) => (value) ? prop.value_on || 'ON' : ((prop.value_off != undefined) ? prop.value_off : 'OFF'),
292
297
  epname: expose.endpoint,
293
- setattr: 'state',
298
+ //setattr: stateName,
294
299
  }, prop.access);
300
+ // features contains TOGGLE?
301
+ if (prop.value_toggle) {
302
+ pushToStates({
303
+ id: `${stateName}_toggle`,
304
+ prop: `${stateName}_toggle`,
305
+ name: `Toggle state of the ${stateName}`,
306
+ icon: undefined,
307
+ role: 'button',
308
+ write: true,
309
+ read: true,
310
+ type: 'boolean',
311
+ def: true,
312
+ setattr: stateName,
313
+ setter: (value) => (value) ? prop.value_toggle : undefined
314
+ });
315
+ }
295
316
  break;
296
317
  }
297
318
  case 'brightness': {
298
- const stateNameB = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
319
+ const stateName = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
299
320
  pushToStates({
300
- id: stateNameB,
321
+ id: stateName,
301
322
  name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
302
323
  icon: undefined,
303
324
  role: 'level.dimmer',
@@ -306,38 +327,40 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
306
327
  type: 'number',
307
328
  min: 0, // ignore expose.value_min
308
329
  max: 100, // ignore expose.value_max
330
+ def: 100,
309
331
  inOptions: true,
310
332
  unit: '%',
311
333
  getter: (value) => {
312
- return utils.bulbLevelToAdapterLevel(value[stateNameB]);
334
+ return utils.bulbLevelToAdapterLevel(value[stateName]);
313
335
  },
314
336
  setter: (value) => {
315
337
  return utils.adapterLevelToBulbLevel(value);
316
338
  },
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
339
  }, prop.access);
333
- pushToStates(statesDefs.brightness_move, prop.access);
340
+ // brightnessMoveOnOff
341
+ const propName = config.brightnessMoveOnOff == true ? `${stateName}_move_onoff` : `${stateName}_move`;
342
+ pushToStates({
343
+ id: `${stateName}_move`,
344
+ prop: propName,
345
+ name: 'Dimming',
346
+ icon: undefined,
347
+ role: 'state',
348
+ write: true,
349
+ read: false,
350
+ type: 'number',
351
+ min: -50,
352
+ max: 50,
353
+ def: 0
354
+ }, prop.access);
334
355
  break;
335
356
  }
336
357
  case 'color_temp': {
337
- const stateNameT = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
358
+ const stateName = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
359
+ const propName = expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp';
360
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
338
361
  pushToStates({
339
- id: stateNameT,
340
- prop: expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp',
362
+ id: stateName,
363
+ prop: propName,
341
364
  name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
342
365
  icon: undefined,
343
366
  role: 'level.color.temperature',
@@ -346,42 +369,50 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
346
369
  type: 'number',
347
370
  min: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_max) : prop.value_min,
348
371
  max: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_min) : prop.value_max,
372
+ def: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_min) : prop.value_max,
349
373
  unit: config.useKelvin == true ? 'K' : 'mired',
350
374
  setter: (value) => {
351
375
  return utils.toMired(value);
352
376
  },
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
377
  getter: (payload) => {
359
- if (payload.color_mode != 'color_temp') {
378
+ if (payload[colorMode] != 'color_temp') {
360
379
  return undefined;
361
380
  }
362
381
  if (config.useKelvin == true) {
363
- return utils.miredKelvinConversion(payload.color_temp);
382
+ return utils.miredKelvinConversion(payload[propName]);
364
383
  } else {
365
- return payload.color_temp;
384
+ return payload[propName];
366
385
  }
367
386
  },
368
- epname: expose.endpoint,
369
- setattr: 'color_temp',
370
387
  }, prop.access);
371
- pushToStates(statesDefs.colortemp_move, prop.access);
388
+ // Colortemp
389
+ pushToStates({
390
+ id: `${stateName}_move`,
391
+ prop: `${propName}_move`,
392
+ name: 'Colortemp change',
393
+ icon: undefined,
394
+ role: 'state',
395
+ write: true,
396
+ read: false,
397
+ type: 'number',
398
+ min: -50,
399
+ max: 50,
400
+ def: 0
401
+ }, prop.access);
372
402
  break;
373
403
  }
374
404
  case 'color_xy': {
375
- const stateNameC = expose.endpoint ? `color_${expose.endpoint}` : 'color';
405
+ const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
406
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
376
407
  pushToStates({
377
- id: stateNameC,
378
- prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
408
+ id: stateName,
379
409
  name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
380
410
  icon: undefined,
381
411
  role: 'level.color.rgb',
382
412
  write: true,
383
413
  read: true,
384
414
  type: 'string',
415
+ def: '#ff00ff',
385
416
  setter: (value) => {
386
417
 
387
418
  let xy = [0, 0];
@@ -395,32 +426,32 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
395
426
 
396
427
  },
397
428
  getter: payload => {
398
- if (payload.color_mode != 'xy' && config.colorTempSyncColor == false) {
429
+ if (payload[colorMode] != 'xy' && config.colorTempSyncColor == false) {
399
430
  return undefined;
400
431
  }
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);
432
+ if (payload[stateName] && payload[stateName].hasOwnProperty('x') && payload[stateName].hasOwnProperty('y')) {
433
+ const colorval = rgb.cie_to_rgb(payload[stateName].x, payload[stateName].y);
403
434
  return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
404
435
  } else {
405
436
  return undefined;
406
437
  }
407
438
  },
408
439
  epname: expose.endpoint,
409
- setattr: 'color',
410
440
  }, prop.access);
411
441
  break;
412
442
  }
413
443
  case 'color_hs': {
414
- const stateNameH = expose.endpoint ? `color_${expose.endpoint}` : 'color';
444
+ const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
445
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
415
446
  pushToStates({
416
- id: stateNameH,
417
- prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
447
+ id: stateName,
418
448
  name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
419
449
  icon: undefined,
420
450
  role: 'level.color.rgb',
421
451
  write: true,
422
452
  read: true,
423
453
  type: 'string',
454
+ def: '#ff00ff',
424
455
  setter: (value) => {
425
456
  const _rgb = colors.ParseColor(value);
426
457
  const hsv = rgb.rgbToHSV(_rgb.r, _rgb.g, _rgb.b, true);
@@ -432,138 +463,22 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
432
463
  };
433
464
  },
434
465
  getter: payload => {
435
- if (payload.color_mode != 'hs') {
466
+ if (!['hs', 'xy'].includes(payload[colorMode]) && config.colorTempSyncColor == false) {
436
467
  return undefined;
437
468
  }
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;
469
+
470
+ if (payload[stateName] && payload[stateName].hasOwnProperty('h') && payload[stateName].hasOwnProperty('s') & payload[stateName].hasOwnProperty('b')) {
471
+ return rgb.hsvToRGBString(payload[stateName].h, payload[stateName].s, Math.round(payload[stateName].b / 2.55));
472
+ }
473
+
474
+ if (payload[stateName] && payload[stateName].hasOwnProperty('x') && payload[stateName].hasOwnProperty('y')) {
475
+ const colorval = rgb.cie_to_rgb(payload[stateName].x, payload[stateName].y);
476
+ return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
442
477
  }
478
+ return undefined;
479
+
443
480
  },
444
- epname: expose.endpoint,
445
- setattr: 'color',
446
481
  }, 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
482
  break;
568
483
  }
569
484
  default:
@@ -580,6 +495,22 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
580
495
  switch (prop.name) {
581
496
  case 'state':
582
497
  pushToStates(genState(prop, 'switch'), prop.access);
498
+ // features contains TOGGLE?
499
+ if (prop.value_toggle) {
500
+ pushToStates({
501
+ id: `${prop.property}_toggle`,
502
+ prop: `${prop.property}_toggle`,
503
+ name: `Toggle state of the ${prop.property}`,
504
+ icon: undefined,
505
+ role: 'button',
506
+ write: true,
507
+ read: true,
508
+ type: 'boolean',
509
+ def: true,
510
+ setattr: prop.property,
511
+ setter: (value) => (value) ? prop.value_toggle : undefined
512
+ });
513
+ }
583
514
  break;
584
515
  default:
585
516
  pushToStates(genState(prop), prop.access);
@@ -659,21 +590,22 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
659
590
  switch (expose.name) {
660
591
  case 'action': {
661
592
 
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
593
+ if (!Array.isArray(expose.values)) {
594
+ break;
595
+ }
667
596
 
668
- if (!Array.isArray(expose.values)) break;
669
597
  const hasHold = expose.values.find((actionName) => actionName.includes('hold'));
670
598
  const hasRelease = expose.values.find((actionName) => actionName.includes('release'));
599
+
671
600
  for (const actionName of expose.values) {
672
601
  // is release state ? - skip
673
- if (hasHold && hasRelease && actionName.includes('release')) continue;
602
+ if (hasHold && hasRelease && actionName.includes('release')) {
603
+ continue;
604
+ }
605
+
674
606
  // is hold state ?
675
607
  if (hasHold && hasRelease && actionName.includes('hold')) {
676
- state = {
608
+ pushToStates({
677
609
  id: actionName.replace(/\*/g, ''),
678
610
  prop: 'action',
679
611
  name: actionName,
@@ -695,9 +627,69 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
695
627
  }
696
628
  return undefined;
697
629
  },
698
- };
699
- } else {
700
- state = {
630
+ }, expose.access);
631
+ }
632
+ else if (actionName.includes('color_temperature_move')) {
633
+ pushToStates({
634
+ id: 'color_temperature_move',
635
+ prop: 'action',
636
+ name: 'Color temperature move value',
637
+ icon: undefined,
638
+ role: 'level.color.temperature',
639
+ write: false,
640
+ read: true,
641
+ type: 'number',
642
+ def: config.useKelvin == true ? utils.miredKelvinConversion(150) : 500,
643
+ min: config.useKelvin == true ? utils.miredKelvinConversion(500) : 150,
644
+ max: config.useKelvin == true ? utils.miredKelvinConversion(150) : 500,
645
+ unit: config.useKelvin == true ? 'K' : 'mired',
646
+ isEvent: true,
647
+ getter: (payload) => {
648
+ if (payload.action != 'color_temperature_move') {
649
+ return undefined;
650
+ }
651
+
652
+ if (payload.action_color_temperature) {
653
+ if (config.useKelvin == true) {
654
+ return utils.miredKelvinConversion(payload.action_color_temperature);
655
+ }
656
+ else {
657
+ return payload.action_color_temperature;
658
+ }
659
+ }
660
+ },
661
+ }, expose.access);
662
+
663
+ }
664
+ else if (actionName.includes('color_move')) {
665
+ pushToStates({
666
+ id: 'color_move',
667
+ prop: 'action',
668
+ name: 'Color move value',
669
+ icon: undefined,
670
+ role: 'level.color.rgb',
671
+ write: false,
672
+ read: true,
673
+ type: 'string',
674
+ def: '#ffffff',
675
+ isEvent: true,
676
+ getter: (payload) => {
677
+ if (payload.action != 'color_move') {
678
+ return undefined;
679
+ }
680
+
681
+ if (payload.action_color && payload.action_color.hasOwnProperty('x') && payload.action_color.hasOwnProperty('y')) {
682
+ const colorval = rgb.cie_to_rgb(payload.action_color.x, payload.action_color.y);
683
+ return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
684
+ }
685
+ else {
686
+ return undefined;
687
+ }
688
+ }
689
+ }, expose.access);
690
+ }
691
+ else {
692
+ pushToStates({
701
693
  id: actionName.replace(/\*/g, ''),
702
694
  prop: 'action',
703
695
  name: actionName,
@@ -709,27 +701,12 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
709
701
  def: false,
710
702
  isEvent: true,
711
703
  getter: payload => (payload.action === actionName) ? true : undefined,
712
- };
704
+ }, expose.access);
713
705
  }
714
- pushToStates(state, expose.access);
715
706
  }
716
707
  // Can the device simulated_brightness?
717
708
  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);
709
+ pushToStates(statesDefs.simulated_brightness, z2mAccess.STATE);
733
710
  }
734
711
  state = null;
735
712
  break;
@@ -791,6 +768,22 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
791
768
  switch (prop.name) {
792
769
  case 'state':
793
770
  pushToStates(genState(prop, 'switch'), prop.access);
771
+ // features contains TOGGLE?
772
+ if (prop.value_toggle) {
773
+ pushToStates({
774
+ id: `${prop.property}_toggle`,
775
+ prop: `${prop.property}_toggle`,
776
+ name: `Toggle state of the ${prop.property}`,
777
+ icon: undefined,
778
+ role: 'button',
779
+ write: true,
780
+ read: true,
781
+ def: true,
782
+ type: 'boolean',
783
+ setattr: prop.property,
784
+ setter: (value) => (value) ? prop.value_toggle : undefined
785
+ });
786
+ }
794
787
  break;
795
788
  default:
796
789
  pushToStates(genState(prop), prop.access);
@@ -881,16 +874,9 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
881
874
  // Add default states
882
875
  pushToStates(statesDefs.available, z2mAccess.STATE);
883
876
 
884
- const newDevice = {
885
- id: deviceID,
886
- ieee_address: ieee_address,
887
- power_source: power_source,
888
- states: states,
889
- };
890
-
891
877
  // Create buttons for scenes
892
878
  for (const scene of scenes) {
893
- const sceneSate = {
879
+ pushToStates({
894
880
  id: `scene_${scene.id}`,
895
881
  prop: `scene_recall`,
896
882
  name: scene.name,
@@ -898,13 +884,19 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
898
884
  role: 'button',
899
885
  write: true,
900
886
  read: true,
887
+ def: true,
901
888
  type: 'boolean',
902
889
  setter: (value) => (value) ? scene.id : undefined
903
- };
904
- // @ts-ignore
905
- newDevice.states.push(sceneSate);
890
+ });
906
891
  }
907
892
 
893
+ const newDevice = {
894
+ id: deviceID,
895
+ ieee_address: ieee_address,
896
+ power_source: power_source,
897
+ states: states,
898
+ };
899
+
908
900
  return newDevice;
909
901
  }
910
902