iobroker.zigbee2mqtt 2.13.5 → 2.13.6

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
@@ -302,848 +302,848 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
302
302
  }
303
303
  }
304
304
  try {
305
- for (const expose of definition.exposes) {
306
- let state;
307
-
308
- switch (expose.type) {
309
- case 'light':
310
- for (const prop of expose.features) {
311
- switch (prop.name) {
312
- case 'state': {
313
- const stateName = expose.endpoint ? `state_${expose.endpoint}` : 'state';
314
- pushToStates({
315
- id: stateName,
316
- name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
317
- options: ['transition'],
318
- icon: undefined,
319
- role: 'switch',
320
- write: true,
321
- read: true,
322
- type: 'boolean',
323
- getter: (payload) => (payload[stateName] === (prop.value_on || 'ON')),
324
- setter: (value) => (value) ? prop.value_on || 'ON' : ((prop.value_off != undefined) ? prop.value_off : 'OFF'),
325
- epname: expose.endpoint,
326
- //setattr: stateName,
327
- }, prop.access);
328
- // features contains TOGGLE?
329
- if (prop.value_toggle) {
330
- pushToStates({
331
- id: `${stateName}_toggle`,
332
- prop: `${stateName}_toggle`,
333
- name: `Toggle state of the ${stateName}`,
334
- options: ['transition'],
335
- icon: undefined,
336
- role: 'button',
337
- write: true,
338
- read: true,
339
- type: 'boolean',
340
- def: true,
341
- setattr: stateName,
342
- setter: (value) => (value) ? prop.value_toggle : undefined
343
- });
344
- }
345
- break;
346
- }
347
- case 'brightness': {
348
- const stateName = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
349
- pushToStates({
350
- id: stateName,
351
- name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
352
- options: ['transition'],
353
- icon: undefined,
354
- role: 'level.dimmer',
355
- write: true,
356
- read: true,
357
- type: 'number',
358
- min: 0, // ignore expose.value_min
359
- max: 100, // ignore expose.value_max
360
- def: 100,
361
- unit: '%',
362
- getter: (value) => {
363
- return utils.bulbLevelToAdapterLevel(value[stateName]);
364
- },
365
- setter: (value) => {
366
- return utils.adapterLevelToBulbLevel(value);
367
- },
368
- }, prop.access);
369
- // brightnessMoveOnOff
370
- const brmPropName = config.brightnessMoveOnOff == true ? `${stateName}_move_onoff` : `${stateName}_move`;
371
- pushToStates({
372
- id: `${stateName}_move`,
373
- prop: brmPropName,
374
- name: 'Increases or decreases the brightness by X units per second',
375
- icon: undefined,
376
- role: 'state',
377
- write: true,
378
- read: false,
379
- type: 'number',
380
- min: -50,
381
- max: 50,
382
- def: 0
383
- }, z2mAccess.SET);
384
- // brightnessStepOnOff
385
- const brspropName = config.brightnessStepOnOff == true ? `${stateName}_step_onoff` : `${stateName}_step`;
386
- pushToStates({
387
- id: `${stateName}_step`,
388
- prop: brspropName,
389
- name: 'Increases or decreases brightness by X steps',
390
- options: ['transition'],
391
- icon: undefined,
392
- role: 'state',
393
- write: true,
394
- read: false,
395
- type: 'number',
396
- min: -255,
397
- max: 255,
398
- def: 0
399
- }, z2mAccess.SET);
400
- break;
401
- }
402
- case 'color_temp': {
403
- const stateName = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
404
- const propName = expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp';
405
- const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
406
- pushToStates({
407
- id: stateName,
408
- prop: propName,
409
- name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
410
- options: ['transition'],
411
- icon: undefined,
412
- role: 'level.color.temperature',
413
- write: true,
414
- read: true,
415
- type: 'number',
416
- min: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_max) : prop.value_min,
417
- max: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_min) : prop.value_max,
418
- def: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_min) : prop.value_max,
419
- unit: config.useKelvin == true ? 'K' : 'mired',
420
- setter: (value) => {
421
- return utils.toMired(value);
422
- },
423
- getter: (payload) => {
424
- if (payload[colorMode] != 'color_temp') {
425
- return undefined;
426
- }
427
- if (config.useKelvin == true) {
428
- return utils.miredKelvinConversion(payload[propName]);
429
- } else {
430
- return payload[propName];
431
- }
432
- },
433
- }, prop.access);
434
- // Colortemp
435
- pushToStates({
436
- id: `${stateName}_move`,
437
- prop: `${propName}_move`,
438
- name: 'Colortemp change',
439
- icon: undefined,
440
- role: 'state',
441
- write: true,
442
- read: false,
443
- type: 'number',
444
- min: -50,
445
- max: 50,
446
- def: 0
447
- }, prop.access);
448
- break;
449
- }
450
- case 'color_temp_startup': {
451
- const stateName = expose.endpoint ? `colortempstartup_${expose.endpoint}` : 'colortempstartup';
452
- const propName = expose.endpoint ? `color_temp_startup_${expose.endpoint}` : 'color_temp_startup';
453
- //const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
454
- pushToStates({
455
- id: stateName,
456
- prop: propName,
457
- name: `${prop.description} ${expose.endpoint ? `(${expose.endpoint})` : ''}`.trim(),
458
- //options: ['transition'],
459
- icon: undefined,
460
- role: 'level.color.temperature',
461
- write: true,
462
- read: true,
463
- type: 'number',
464
- min: 0,
465
- max: 65535,
466
- def: undefined,
467
- unit: config.useKelvin == true ? 'K' : 'mired',
468
- setter: (value) => {
469
- return utils.toMired(value);
470
- },
471
- getter: (payload) => {
472
- //if (payload[colorMode] != 'color_temp') {
473
- // return undefined;
474
- //}
475
- if (config.useKelvin == true) {
476
- return utils.miredKelvinConversion(payload[propName]);
477
- } else {
478
- return payload[propName];
479
- }
480
- },
481
- }, prop.access);
482
- break;
483
- }
484
- case 'color_xy': {
485
- const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
486
- const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
487
- pushToStates({
488
- id: stateName,
489
- name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
490
- options: ['transition'],
491
- icon: undefined,
492
- role: 'level.color.rgb',
493
- write: true,
494
- read: true,
495
- type: 'string',
496
- def: '#ff00ff',
497
- setter: (value) => {
498
-
499
- let xy = [0, 0];
500
- const rgbcolor = colors.ParseColor(value);
501
-
502
- xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
503
- return {
504
- x: xy[0],
505
- y: xy[1]
506
- };
507
-
508
- },
509
- getter: payload => {
510
- if (payload[colorMode] != 'xy' && config.colorTempSyncColor == false) {
511
- return undefined;
512
- }
513
- if (payload[stateName] && payload[stateName].hasOwnProperty('x') && payload[stateName].hasOwnProperty('y')) {
514
- const colorval = rgb.cie_to_rgb(payload[stateName].x, payload[stateName].y);
515
- return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
516
- } else {
517
- return undefined;
518
- }
519
- },
520
- epname: expose.endpoint,
521
- }, prop.access);
522
- break;
523
- }
524
- case 'color_hs': {
525
- const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
526
- const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
527
- pushToStates({
528
- id: stateName,
529
- name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
530
- options: ['transition'],
531
- icon: undefined,
532
- role: 'level.color.rgb',
533
- write: true,
534
- read: true,
535
- type: 'string',
536
- def: '#ff00ff',
537
- setter: (value) => {
538
- const _rgb = colors.ParseColor(value);
539
- const hsv = rgb.rgbToHSV(_rgb.r, _rgb.g, _rgb.b, true);
540
- return {
541
- h: Math.min(Math.max(hsv.h, 1), 359),
542
- s: hsv.s,
543
- //b: Math.round(hsv.v * 2.55),
544
-
545
- };
546
- },
547
- getter: payload => {
548
- if (!['hs', 'xy'].includes(payload[colorMode]) && config.colorTempSyncColor == false) {
549
- return undefined;
550
- }
551
-
552
- if (payload[stateName] && payload[stateName].hasOwnProperty('h') && payload[stateName].hasOwnProperty('s') & payload[stateName].hasOwnProperty('b')) {
553
- return rgb.hsvToRGBString(payload[stateName].h, payload[stateName].s, Math.round(payload[stateName].b / 2.55));
554
- }
555
-
556
- if (payload[stateName] && payload[stateName].hasOwnProperty('x') && payload[stateName].hasOwnProperty('y')) {
557
- const colorval = rgb.cie_to_rgb(payload[stateName].x, payload[stateName].y);
558
- return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
559
- }
560
- return undefined;
561
-
562
- },
563
- }, prop.access);
564
- break;
565
- }
566
- default:
567
- pushToStates(genState(prop), prop.access);
568
- break;
569
- }
570
- }
571
- pushToStates(statesDefs.transition, z2mAccess.SET);
572
- break;
573
-
574
- case 'switch':
575
- for (const prop of expose.features) {
576
- switch (prop.name) {
577
- case 'state':
578
- pushToStates(genState(prop, 'switch'), prop.access);
579
- // features contains TOGGLE?
580
- if (prop.value_toggle) {
581
- pushToStates({
582
- id: `${prop.property}_toggle`,
583
- prop: `${prop.property}_toggle`,
584
- name: `Toggle state of the ${prop.property}`,
585
- icon: undefined,
586
- role: 'button',
587
- write: true,
588
- read: true,
589
- type: 'boolean',
590
- def: true,
591
- setattr: prop.property,
592
- setter: (value) => (value) ? prop.value_toggle : undefined
593
- });
594
- }
595
- break;
596
- default:
597
- pushToStates(genState(prop), prop.access);
598
- break;
599
- }
600
- }
601
- break;
602
-
603
- case 'numeric':
604
- if (expose.endpoint) {
605
- state = genState(expose);
606
- } else {
607
- switch (expose.name) {
608
- case 'linkquality':
609
- state = statesDefs.link_quality;
610
- break;
611
-
612
- case 'battery':
613
- state = statesDefs.battery;
614
- break;
615
-
616
- case 'temperature':
617
- state = statesDefs.temperature;
618
- break;
619
-
620
- case 'device_temperature':
621
- state = statesDefs.device_temperature;
622
- break;
623
-
624
- case 'humidity':
625
- state = statesDefs.humidity;
626
- break;
627
-
628
- case 'pressure':
629
- state = statesDefs.pressure;
630
- break;
631
-
632
- case 'illuminance':
633
- state = statesDefs.illuminance_raw;
634
- break;
635
-
636
- case 'illuminance_lux':
637
- state = statesDefs.illuminance;
638
- break;
639
-
640
- case 'power':
641
- state = statesDefs.load_power;
642
- break;
643
-
644
- case 'current':
645
- state = statesDefs.load_current;
646
- break;
647
-
648
- case 'voltage':
649
- state = statesDefs.voltage;
650
- if (power_source == 'Battery') {
651
- state = statesDefs.battery_voltage;
652
- }
653
- if (expose.unit == 'mV') {
654
- state.getter = payload => payload.voltage / 1000;
655
- }
656
- break;
657
-
658
- case 'energy':
659
- state = statesDefs.energy;
660
- break;
661
-
662
- default:
663
- state = genState(expose);
664
- break;
665
- }
666
- }
667
- if (state) pushToStates(state, expose.access);
668
- break;
669
-
670
- case 'enum':
671
- switch (expose.name) {
672
- case 'action': {
673
-
674
- if (!Array.isArray(expose.values)) {
675
- break;
676
- }
677
-
678
- // Support for DIYRuZ Device
679
- const wildcardValues = expose.values.filter(x => x.startsWith('*'));
680
- if (wildcardValues && wildcardValues.length > 0) {
681
- for (const endpointName of [...new Set(definition.exposes.filter(x => x.endpoint).map(x => x.endpoint))]) {
682
- for (const value of wildcardValues) {
683
- const actionName = value.replace('*', endpointName);
684
- pushToStates({
685
- id: actionName,
686
- prop: 'action',
687
- name: `Triggered action ${value.replace('*_', endpointName)}`,
688
- icon: undefined,
689
- role: 'button',
690
- write: false,
691
- read: true,
692
- type: 'boolean',
693
- def: false,
694
- isEvent: true,
695
- getter: payload => (payload.action === actionName) ? true : undefined,
696
- }, expose.access);
697
- }
698
- }
699
- break;
700
- }
701
-
702
- for (const actionName of expose.values) {
703
-
704
- // is release -> hold state? - skip
705
- if (config.simpleHoldReleaseState == true && actionName.endsWith('release') && expose.values.find((x) => x == actionName.replace('release', 'hold'))) {
706
- continue;
707
- }
708
-
709
- // is stop - move state? - skip
710
- if (config.simpleMoveStopState == true && actionName.endsWith('stop') && expose.values.find((x) => x.includes(actionName.replace('stop', 'move')))) {
711
- continue;
712
- }
713
-
714
- // is release -> press state? - skip
715
- if (config.simplePressReleaseState == true && actionName.endsWith('release') && expose.values.find((x) => x == actionName.replace('release', 'press'))) {
716
- continue;
717
- }
718
-
719
- // is hold -> release state ?
720
- if (config.simpleHoldReleaseState == true && actionName.endsWith('hold') && expose.values.find((x) => x == actionName.replace('hold', 'release'))) {
721
- pushToStates({
722
- id: actionName.replace(/\*/g, ''),
723
- prop: 'action',
724
- name: actionName,
725
- icon: undefined,
726
- role: 'button',
727
- write: false,
728
- read: true,
729
- def: false,
730
- type: 'boolean',
731
- getter: (payload) => {
732
- if (payload.action === actionName) {
733
- return true;
734
- }
735
- if (payload.action === actionName.replace('hold', 'release')) {
736
- return false;
737
- }
738
- if (payload.action === `${actionName}_release`) {
739
- return false;
740
- }
741
- return undefined;
742
- },
743
- }, expose.access);
744
- }
745
- // is move -> stop state ?
746
- else if (config.simpleMoveStopState == true && actionName.includes('move') && expose.values.find((x) => x == `${actionName.split('_')[0]}_stop`)) {
747
- pushToStates({
748
- id: actionName.replace(/\*/g, ''),
749
- prop: 'action',
750
- name: actionName,
751
- icon: undefined,
752
- role: 'button',
753
- write: false,
754
- read: true,
755
- def: false,
756
- type: 'boolean',
757
- getter: (payload) => {
758
- if (payload.action === actionName) {
759
- return true;
760
- }
761
- if (payload.action === `${actionName.split('_')[0]}_stop`) {
762
- return false;
763
- }
764
- return undefined;
765
- },
766
- }, expose.access);
767
- }
768
- // is press -> release state ?
769
- else if (config.simplePressReleaseState == true && actionName.endsWith('press') && expose.values.find((x) => x == actionName.replace('press', 'release'))) {
770
- pushToStates({
771
- id: actionName.replace(/\*/g, ''),
772
- prop: 'action',
773
- name: actionName,
774
- icon: undefined,
775
- role: 'button',
776
- write: false,
777
- read: true,
778
- def: false,
779
- type: 'boolean',
780
- getter: (payload) => {
781
- if (payload.action === actionName) {
782
- return true;
783
- }
784
- if (payload.action === actionName.replace('press', 'release')) {
785
- return false;
786
- }
787
- return undefined;
788
- },
789
- }, expose.access);
790
- }
791
- else if (actionName == 'color_temperature_move') {
792
- pushToStates({
793
- id: 'color_temperature_move',
794
- prop: 'action',
795
- name: 'Color temperature move value',
796
- icon: undefined,
797
- role: 'level.color.temperature',
798
- write: false,
799
- read: true,
800
- type: 'number',
801
- def: config.useKelvin == true ? utils.miredKelvinConversion(150) : 500,
802
- min: config.useKelvin == true ? utils.miredKelvinConversion(500) : 150,
803
- max: config.useKelvin == true ? utils.miredKelvinConversion(150) : 500,
804
- unit: config.useKelvin == true ? 'K' : 'mired',
805
- isEvent: true,
806
- getter: (payload) => {
807
- if (payload.action != 'color_temperature_move') {
808
- return undefined;
809
- }
810
-
811
- if (payload.action_color_temperature) {
812
- if (config.useKelvin == true) {
813
- return utils.miredKelvinConversion(payload.action_color_temperature);
814
- }
815
- else {
816
- return payload.action_color_temperature;
817
- }
818
- }
819
- },
820
- }, expose.access);
821
-
822
- }
823
- else if (actionName == 'color_move') {
824
- pushToStates({
825
- id: 'color_move',
826
- prop: 'action',
827
- name: 'Color move value',
828
- icon: undefined,
829
- role: 'level.color.rgb',
830
- write: false,
831
- read: true,
832
- type: 'string',
833
- def: '#ffffff',
834
- isEvent: true,
835
- getter: (payload) => {
836
- if (payload.action != 'color_move') {
837
- return undefined;
838
- }
839
-
840
- if (payload.action_color && payload.action_color.hasOwnProperty('x') && payload.action_color.hasOwnProperty('y')) {
841
- const colorval = rgb.cie_to_rgb(payload.action_color.x, payload.action_color.y);
842
- return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
843
- }
844
- else {
845
- return undefined;
846
- }
847
- }
848
- }, expose.access);
849
- }
850
- else if (actionName == 'brightness_move_to_level') {
851
- pushToStates({
852
- id: 'brightness_move_to_level',
853
- name: 'Brightness move to level',
854
- icon: undefined,
855
- role: 'level.dimmer',
856
- write: false,
857
- read: true,
858
- type: 'number',
859
- min: 0,
860
- max: 100,
861
- def: 100,
862
- unit: '%',
863
- isEvent: true,
864
- getter: (payload) => {
865
- if (payload.action != 'brightness_move_to_level') {
866
- return undefined;
867
- }
868
-
869
- if (payload.action_level) {
870
- return utils.bulbLevelToAdapterLevel(payload.action_level);
871
- } else {
872
- return undefined;
873
- }
874
- }
875
- }, expose.access);
876
- }
877
- else if (actionName == 'move_to_saturation') {
878
- pushToStates({
879
- id: 'move_to_saturation',
880
- name: 'Move to level saturation',
881
- icon: undefined,
882
- role: 'level.color.saturation',
883
- write: false,
884
- read: true,
885
- type: 'number',
886
- // min: 0,
887
- // max: 100,
888
- def: 0,
889
- isEvent: true,
890
- getter: (payload) => {
891
- if (payload.action != 'move_to_saturation') {
892
- return undefined;
893
- }
894
-
895
- if (payload.action_level) {
896
- return payload.action_saturation;
897
- } else {
898
- return undefined;
899
- }
900
- }
901
- }, expose.access);
902
- }
903
- else if (actionName == 'enhanced_move_to_hue_and_saturation') {
305
+ for (const expose of definition.exposes) {
306
+ let state;
307
+
308
+ switch (expose.type) {
309
+ case 'light':
310
+ for (const prop of expose.features) {
311
+ switch (prop.name) {
312
+ case 'state': {
313
+ const stateName = expose.endpoint ? `state_${expose.endpoint}` : 'state';
904
314
  pushToStates({
905
- id: 'enhanced_move_to_hue_and_saturation',
906
- prop: 'action',
907
- name: 'Enhanced move to hue and saturation value',
315
+ id: stateName,
316
+ name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
317
+ options: ['transition'],
908
318
  icon: undefined,
909
- role: 'level.color.hue',
910
- write: false,
319
+ role: 'switch',
320
+ write: true,
321
+ read: true,
322
+ type: 'boolean',
323
+ getter: (payload) => (payload[stateName] === (prop.value_on || 'ON')),
324
+ setter: (value) => (value) ? prop.value_on || 'ON' : ((prop.value_off != undefined) ? prop.value_off : 'OFF'),
325
+ epname: expose.endpoint,
326
+ //setattr: stateName,
327
+ }, prop.access);
328
+ // features contains TOGGLE?
329
+ if (prop.value_toggle) {
330
+ pushToStates({
331
+ id: `${stateName}_toggle`,
332
+ prop: `${stateName}_toggle`,
333
+ name: `Toggle state of the ${stateName}`,
334
+ options: ['transition'],
335
+ icon: undefined,
336
+ role: 'button',
337
+ write: true,
338
+ read: true,
339
+ type: 'boolean',
340
+ def: true,
341
+ setattr: stateName,
342
+ setter: (value) => (value) ? prop.value_toggle : undefined
343
+ });
344
+ }
345
+ break;
346
+ }
347
+ case 'brightness': {
348
+ const stateName = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
349
+ pushToStates({
350
+ id: stateName,
351
+ name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
352
+ options: ['transition'],
353
+ icon: undefined,
354
+ role: 'level.dimmer',
355
+ write: true,
356
+ read: true,
357
+ type: 'number',
358
+ min: 0, // ignore expose.value_min
359
+ max: 100, // ignore expose.value_max
360
+ def: 100,
361
+ unit: '%',
362
+ getter: (value) => {
363
+ return utils.bulbLevelToAdapterLevel(value[stateName]);
364
+ },
365
+ setter: (value) => {
366
+ return utils.adapterLevelToBulbLevel(value);
367
+ },
368
+ }, prop.access);
369
+ // brightnessMoveOnOff
370
+ const brmPropName = config.brightnessMoveOnOff == true ? `${stateName}_move_onoff` : `${stateName}_move`;
371
+ pushToStates({
372
+ id: `${stateName}_move`,
373
+ prop: brmPropName,
374
+ name: 'Increases or decreases the brightness by X units per second',
375
+ icon: undefined,
376
+ role: 'state',
377
+ write: true,
378
+ read: false,
379
+ type: 'number',
380
+ min: -50,
381
+ max: 50,
382
+ def: 0
383
+ }, z2mAccess.SET);
384
+ // brightnessStepOnOff
385
+ const brspropName = config.brightnessStepOnOff == true ? `${stateName}_step_onoff` : `${stateName}_step`;
386
+ pushToStates({
387
+ id: `${stateName}_step`,
388
+ prop: brspropName,
389
+ name: 'Increases or decreases brightness by X steps',
390
+ options: ['transition'],
391
+ icon: undefined,
392
+ role: 'state',
393
+ write: true,
394
+ read: false,
395
+ type: 'number',
396
+ min: -255,
397
+ max: 255,
398
+ def: 0
399
+ }, z2mAccess.SET);
400
+ break;
401
+ }
402
+ case 'color_temp': {
403
+ const stateName = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
404
+ const propName = expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp';
405
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
406
+ pushToStates({
407
+ id: stateName,
408
+ prop: propName,
409
+ name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
410
+ options: ['transition'],
411
+ icon: undefined,
412
+ role: 'level.color.temperature',
413
+ write: true,
414
+ read: true,
415
+ type: 'number',
416
+ min: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_max) : prop.value_min,
417
+ max: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_min) : prop.value_max,
418
+ def: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_min) : prop.value_max,
419
+ unit: config.useKelvin == true ? 'K' : 'mired',
420
+ setter: (value) => {
421
+ return utils.toMired(value);
422
+ },
423
+ getter: (payload) => {
424
+ if (payload[colorMode] != 'color_temp') {
425
+ return undefined;
426
+ }
427
+ if (config.useKelvin == true) {
428
+ return utils.miredKelvinConversion(payload[propName]);
429
+ } else {
430
+ return payload[propName];
431
+ }
432
+ },
433
+ }, prop.access);
434
+ // Colortemp
435
+ pushToStates({
436
+ id: `${stateName}_move`,
437
+ prop: `${propName}_move`,
438
+ name: 'Colortemp change',
439
+ icon: undefined,
440
+ role: 'state',
441
+ write: true,
442
+ read: false,
443
+ type: 'number',
444
+ min: -50,
445
+ max: 50,
446
+ def: 0
447
+ }, prop.access);
448
+ break;
449
+ }
450
+ case 'color_temp_startup': {
451
+ const stateName = expose.endpoint ? `colortempstartup_${expose.endpoint}` : 'colortempstartup';
452
+ const propName = expose.endpoint ? `color_temp_startup_${expose.endpoint}` : 'color_temp_startup';
453
+ //const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
454
+ pushToStates({
455
+ id: stateName,
456
+ prop: propName,
457
+ name: `${prop.description} ${expose.endpoint ? `(${expose.endpoint})` : ''}`.trim(),
458
+ //options: ['transition'],
459
+ icon: undefined,
460
+ role: 'level.color.temperature',
461
+ write: true,
911
462
  read: true,
912
463
  type: 'number',
913
464
  min: 0,
914
- max: 65536,
915
- def: 0,
916
- isEvent: true,
465
+ max: 65535,
466
+ def: undefined,
467
+ unit: config.useKelvin == true ? 'K' : 'mired',
468
+ setter: (value) => {
469
+ return utils.toMired(value);
470
+ },
917
471
  getter: (payload) => {
918
- if (payload.action != 'enhanced_move_to_hue_and_saturation') {
472
+ //if (payload[colorMode] != 'color_temp') {
473
+ // return undefined;
474
+ //}
475
+ if (config.useKelvin == true) {
476
+ return utils.miredKelvinConversion(payload[propName]);
477
+ } else {
478
+ return payload[propName];
479
+ }
480
+ },
481
+ }, prop.access);
482
+ break;
483
+ }
484
+ case 'color_xy': {
485
+ const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
486
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
487
+ pushToStates({
488
+ id: stateName,
489
+ name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
490
+ options: ['transition'],
491
+ icon: undefined,
492
+ role: 'level.color.rgb',
493
+ write: true,
494
+ read: true,
495
+ type: 'string',
496
+ def: '#ff00ff',
497
+ setter: (value) => {
498
+
499
+ let xy = [0, 0];
500
+ const rgbcolor = colors.ParseColor(value);
501
+
502
+ xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
503
+ return {
504
+ x: xy[0],
505
+ y: xy[1]
506
+ };
507
+
508
+ },
509
+ getter: payload => {
510
+ if (payload[colorMode] != 'xy' && config.colorTempSyncColor == false) {
919
511
  return undefined;
920
512
  }
921
-
922
- if (payload.action_enhanced_hue) {
923
- return payload.action_enhanced_hue;
513
+ if (payload[stateName] && payload[stateName].hasOwnProperty('x') && payload[stateName].hasOwnProperty('y')) {
514
+ const colorval = rgb.cie_to_rgb(payload[stateName].x, payload[stateName].y);
515
+ return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
924
516
  } else {
925
517
  return undefined;
926
518
  }
519
+ },
520
+ epname: expose.endpoint,
521
+ }, prop.access);
522
+ break;
523
+ }
524
+ case 'color_hs': {
525
+ const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
526
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
527
+ pushToStates({
528
+ id: stateName,
529
+ name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
530
+ options: ['transition'],
531
+ icon: undefined,
532
+ role: 'level.color.rgb',
533
+ write: true,
534
+ read: true,
535
+ type: 'string',
536
+ def: '#ff00ff',
537
+ setter: (value) => {
538
+ const _rgb = colors.ParseColor(value);
539
+ const hsv = rgb.rgbToHSV(_rgb.r, _rgb.g, _rgb.b, true);
540
+ return {
541
+ h: Math.min(Math.max(hsv.h, 1), 359),
542
+ s: hsv.s,
543
+ //b: Math.round(hsv.v * 2.55),
544
+
545
+ };
546
+ },
547
+ getter: payload => {
548
+ if (!['hs', 'xy'].includes(payload[colorMode]) && config.colorTempSyncColor == false) {
549
+ return undefined;
550
+ }
551
+
552
+ if (payload[stateName] && payload[stateName].hasOwnProperty('h') && payload[stateName].hasOwnProperty('s') & payload[stateName].hasOwnProperty('b')) {
553
+ return rgb.hsvToRGBString(payload[stateName].h, payload[stateName].s, Math.round(payload[stateName].b / 2.55));
554
+ }
555
+
556
+ if (payload[stateName] && payload[stateName].hasOwnProperty('x') && payload[stateName].hasOwnProperty('y')) {
557
+ const colorval = rgb.cie_to_rgb(payload[stateName].x, payload[stateName].y);
558
+ return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
559
+ }
560
+ return undefined;
561
+
562
+ },
563
+ }, prop.access);
564
+ break;
565
+ }
566
+ default:
567
+ pushToStates(genState(prop), prop.access);
568
+ break;
569
+ }
570
+ }
571
+ pushToStates(statesDefs.transition, z2mAccess.SET);
572
+ break;
573
+
574
+ case 'switch':
575
+ for (const prop of expose.features) {
576
+ switch (prop.name) {
577
+ case 'state':
578
+ pushToStates(genState(prop, 'switch'), prop.access);
579
+ // features contains TOGGLE?
580
+ if (prop.value_toggle) {
581
+ pushToStates({
582
+ id: `${prop.property}_toggle`,
583
+ prop: `${prop.property}_toggle`,
584
+ name: `Toggle state of the ${prop.property}`,
585
+ icon: undefined,
586
+ role: 'button',
587
+ write: true,
588
+ read: true,
589
+ type: 'boolean',
590
+ def: true,
591
+ setattr: prop.property,
592
+ setter: (value) => (value) ? prop.value_toggle : undefined
593
+ });
594
+ }
595
+ break;
596
+ default:
597
+ pushToStates(genState(prop), prop.access);
598
+ break;
599
+ }
600
+ }
601
+ break;
602
+
603
+ case 'numeric':
604
+ if (expose.endpoint) {
605
+ state = genState(expose);
606
+ } else {
607
+ switch (expose.name) {
608
+ case 'linkquality':
609
+ state = statesDefs.link_quality;
610
+ break;
611
+
612
+ case 'battery':
613
+ state = statesDefs.battery;
614
+ break;
615
+
616
+ case 'temperature':
617
+ state = statesDefs.temperature;
618
+ break;
619
+
620
+ case 'device_temperature':
621
+ state = statesDefs.device_temperature;
622
+ break;
623
+
624
+ case 'humidity':
625
+ state = statesDefs.humidity;
626
+ break;
627
+
628
+ case 'pressure':
629
+ state = statesDefs.pressure;
630
+ break;
631
+
632
+ case 'illuminance':
633
+ state = statesDefs.illuminance_raw;
634
+ break;
635
+
636
+ case 'illuminance_lux':
637
+ state = statesDefs.illuminance;
638
+ break;
639
+
640
+ case 'power':
641
+ state = statesDefs.load_power;
642
+ break;
643
+
644
+ case 'current':
645
+ state = statesDefs.load_current;
646
+ break;
647
+
648
+ case 'voltage':
649
+ state = statesDefs.voltage;
650
+ if (power_source == 'Battery') {
651
+ state = statesDefs.battery_voltage;
652
+ }
653
+ if (expose.unit == 'mV') {
654
+ state.getter = payload => payload.voltage / 1000;
655
+ }
656
+ break;
657
+
658
+ case 'energy':
659
+ state = statesDefs.energy;
660
+ break;
661
+
662
+ default:
663
+ state = genState(expose);
664
+ break;
665
+ }
666
+ }
667
+ if (state) pushToStates(state, expose.access);
668
+ break;
669
+
670
+ case 'enum':
671
+ switch (expose.name) {
672
+ case 'action': {
673
+
674
+ if (!Array.isArray(expose.values)) {
675
+ break;
676
+ }
677
+
678
+ // Support for DIYRuZ Device
679
+ const wildcardValues = expose.values.filter(x => x.startsWith('*'));
680
+ if (wildcardValues && wildcardValues.length > 0) {
681
+ for (const endpointName of [...new Set(definition.exposes.filter(x => x.endpoint).map(x => x.endpoint))]) {
682
+ for (const value of wildcardValues) {
683
+ const actionName = value.replace('*', endpointName);
684
+ pushToStates({
685
+ id: actionName,
686
+ prop: 'action',
687
+ name: `Triggered action ${value.replace('*_', endpointName)}`,
688
+ icon: undefined,
689
+ role: 'button',
690
+ write: false,
691
+ read: true,
692
+ type: 'boolean',
693
+ def: false,
694
+ isEvent: true,
695
+ getter: payload => (payload.action === actionName) ? true : undefined,
696
+ }, expose.access);
927
697
  }
928
- }, expose.access);
698
+ }
699
+ break;
929
700
  }
930
- // else if (actionName == 'hue_move') {
931
- // pushToStates({
932
- // id: 'hue_move',
933
- // name: 'Hue move rate',
934
- // icon: undefined,
935
- // role: 'level.color.hue',
936
- // write: false,
937
- // read: true,
938
- // type: 'number',
939
- // min: 0,
940
- // max: 360,
941
- // def: 0,
942
- // isEvent: true,
943
- // getter: (payload) => {
944
- // if (payload.action != 'hue_move') {
945
- // return undefined;
946
- // }
947
-
948
- // if (payload.action_level) {
949
- // return payload.action_level;
950
- // } else {
951
- // return undefined;
952
- // }
953
- // }
954
- // }, expose.access);
955
- // }
956
- else {
957
- pushToStates({
958
- id: actionName.replace(/\*/g, ''),
959
- prop: 'action',
960
- name: actionName,
961
- icon: undefined,
962
- role: 'button',
963
- write: false,
964
- read: true,
965
- type: 'boolean',
966
- def: false,
967
- isEvent: true,
968
- getter: payload => (payload.action === actionName) ? true : undefined,
969
- }, expose.access);
970
- }
971
- }
972
- // Can the device simulated_brightness?
973
- if (definition.options && definition.options.find(x => x.property == 'simulated_brightness')) {
974
- pushToStates(statesDefs.simulated_brightness, z2mAccess.STATE);
975
- }
976
- state = null;
977
- break;
978
- }
979
- default:
980
- state = genState(expose);
981
- break;
982
- }
983
- if (state) pushToStates(state, expose.access);
984
- break;
985
-
986
- case 'binary':
987
- if (expose.endpoint) {
988
- state = genState(expose);
989
- } else {
990
- switch (expose.name) {
991
- case 'contact':
992
- state = statesDefs.contact;
993
- pushToStates(statesDefs.opened, expose.access);
994
- break;
995
-
996
- case 'battery_low':
997
- state = statesDefs.heiman_batt_low;
998
- break;
999
-
1000
- case 'tamper':
1001
- state = statesDefs.tamper;
1002
- break;
1003
-
1004
- case 'water_leak':
1005
- state = statesDefs.water_detected;
1006
- break;
1007
-
1008
- case 'lock':
1009
- state = statesDefs.child_lock;
1010
- break;
1011
-
1012
- case 'occupancy':
1013
- state = statesDefs.occupancy;
1014
- break;
1015
-
1016
- default:
1017
- state = genState(expose);
1018
- break;
1019
- }
1020
- }
1021
- if (state) pushToStates(state, expose.access);
1022
- break;
1023
-
1024
- case 'text':
1025
- state = genState(expose);
1026
- pushToStates(state, expose.access);
1027
- break;
1028
-
1029
- case 'lock':
1030
- case 'fan':
1031
- case 'cover':
1032
- for (const prop of expose.features) {
1033
- switch (prop.name) {
1034
- case 'state':
1035
- pushToStates(genState(prop, 'switch'), prop.access);
1036
- // features contains TOGGLE?
1037
- if (prop.value_toggle) {
1038
- pushToStates({
1039
- id: `${prop.property}_toggle`,
1040
- prop: `${prop.property}_toggle`,
1041
- name: `Toggle state of the ${prop.property}`,
1042
- icon: undefined,
1043
- role: 'button',
1044
- write: true,
1045
- read: true,
1046
- def: true,
1047
- type: 'boolean',
1048
- setattr: prop.property,
1049
- setter: (value) => (value) ? prop.value_toggle : undefined
1050
- });
1051
- }
1052
- break;
1053
- default:
1054
- pushToStates(genState(prop), prop.access);
1055
- break;
1056
- }
1057
- }
1058
- break;
1059
-
1060
- case 'climate':
1061
- for (const prop of expose.features) {
1062
- switch (prop.name) {
1063
- case 'away_mode':
1064
- pushToStates(statesDefs.climate_away_mode, prop.access);
1065
- break;
1066
- case 'system_mode':
1067
- pushToStates(statesDefs.climate_system_mode, prop.access);
1068
- break;
1069
- case 'running_mode':
1070
- pushToStates(statesDefs.climate_running_mode, prop.access);
1071
- break;
1072
- case 'local_temperature':
1073
- pushToStates(statesDefs.hvacThermostat_local_temp, prop.access);
1074
- break;
1075
- case 'local_temperature_calibration':
1076
- pushToStates(statesDefs.hvacThermostat_local_temp_calibration, prop.access);
1077
- break;
1078
- default:
1079
- {
1080
- if (prop.name.includes('heating_setpoint')) {
1081
- pushToStates(genState(prop, 'level.temperature'), prop.access);
1082
- } else {
1083
- pushToStates(genState(prop), prop.access);
1084
- }
1085
- }
1086
- break;
1087
- }
1088
- }
1089
- break;
1090
-
1091
- case 'composite':
1092
- for (const prop of expose.features) {
1093
- // let state;
1094
- // if (prop.type == 'list' && prop.features) {
1095
- // const listName = prop.name;
1096
-
1097
- // for (const feature of prop.features) {
1098
- // genState(feature, 'state', `${listName}_${feature.name}`, prop.description);
1099
- // }
1100
- // }
1101
-
1102
- const state = genState(prop);
1103
- // Workaround for FP1 new state (region_upsert)
1104
- if (!state) {
1105
- break;
1106
- }
1107
- state.prop = expose.property;
1108
- state.inOptions = true;
1109
- // I'm not fully sure, as it really needed, but
1110
- state.setterOpt = (value, options) => {
1111
- const result = {};
1112
- options[prop.property] = value;
1113
- result[expose.property] = options;
1114
- return result;
1115
- };
1116
- // if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
1117
- if (prop.access & z2mAccess.SET) {
1118
- state.setter = (value, options) => {
1119
- const result = {};
1120
- options[prop.property] = value;
1121
- result[expose.property] = options;
1122
- return result;
1123
- };
1124
- state.setattr = expose.property;
1125
- }
1126
- // if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
1127
- if (prop.access & z2mAccess.STATE) {
1128
- state.getter = payload => {
1129
- if ((payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
1130
- return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined;
1131
- } else {
1132
- return undefined;
1133
- }
1134
- };
1135
- } else {
1136
- state.getter = _payload => { return undefined; };
1137
- }
1138
- pushToStates(state, prop.access);
1139
- }
1140
- break;
1141
- default:
1142
- console.log(`Unhandled expose type ${expose.type} for device ${deviceID}`);
1143
- }
1144
- }
701
+
702
+ for (const actionName of expose.values) {
703
+
704
+ // is release -> hold state? - skip
705
+ if (config.simpleHoldReleaseState == true && actionName.endsWith('release') && expose.values.find((x) => x == actionName.replace('release', 'hold'))) {
706
+ continue;
707
+ }
708
+
709
+ // is stop - move state? - skip
710
+ if (config.simpleMoveStopState == true && actionName.endsWith('stop') && expose.values.find((x) => x.includes(actionName.replace('stop', 'move')))) {
711
+ continue;
712
+ }
713
+
714
+ // is release -> press state? - skip
715
+ if (config.simplePressReleaseState == true && actionName.endsWith('release') && expose.values.find((x) => x == actionName.replace('release', 'press'))) {
716
+ continue;
717
+ }
718
+
719
+ // is hold -> release state ?
720
+ if (config.simpleHoldReleaseState == true && actionName.endsWith('hold') && expose.values.find((x) => x == actionName.replace('hold', 'release'))) {
721
+ pushToStates({
722
+ id: actionName.replace(/\*/g, ''),
723
+ prop: 'action',
724
+ name: actionName,
725
+ icon: undefined,
726
+ role: 'button',
727
+ write: false,
728
+ read: true,
729
+ def: false,
730
+ type: 'boolean',
731
+ getter: (payload) => {
732
+ if (payload.action === actionName) {
733
+ return true;
734
+ }
735
+ if (payload.action === actionName.replace('hold', 'release')) {
736
+ return false;
737
+ }
738
+ if (payload.action === `${actionName}_release`) {
739
+ return false;
740
+ }
741
+ return undefined;
742
+ },
743
+ }, expose.access);
744
+ }
745
+ // is move -> stop state ?
746
+ else if (config.simpleMoveStopState == true && actionName.includes('move') && expose.values.find((x) => x == `${actionName.split('_')[0]}_stop`)) {
747
+ pushToStates({
748
+ id: actionName.replace(/\*/g, ''),
749
+ prop: 'action',
750
+ name: actionName,
751
+ icon: undefined,
752
+ role: 'button',
753
+ write: false,
754
+ read: true,
755
+ def: false,
756
+ type: 'boolean',
757
+ getter: (payload) => {
758
+ if (payload.action === actionName) {
759
+ return true;
760
+ }
761
+ if (payload.action === `${actionName.split('_')[0]}_stop`) {
762
+ return false;
763
+ }
764
+ return undefined;
765
+ },
766
+ }, expose.access);
767
+ }
768
+ // is press -> release state ?
769
+ else if (config.simplePressReleaseState == true && actionName.endsWith('press') && expose.values.find((x) => x == actionName.replace('press', 'release'))) {
770
+ pushToStates({
771
+ id: actionName.replace(/\*/g, ''),
772
+ prop: 'action',
773
+ name: actionName,
774
+ icon: undefined,
775
+ role: 'button',
776
+ write: false,
777
+ read: true,
778
+ def: false,
779
+ type: 'boolean',
780
+ getter: (payload) => {
781
+ if (payload.action === actionName) {
782
+ return true;
783
+ }
784
+ if (payload.action === actionName.replace('press', 'release')) {
785
+ return false;
786
+ }
787
+ return undefined;
788
+ },
789
+ }, expose.access);
790
+ }
791
+ else if (actionName == 'color_temperature_move') {
792
+ pushToStates({
793
+ id: 'color_temperature_move',
794
+ prop: 'action',
795
+ name: 'Color temperature move value',
796
+ icon: undefined,
797
+ role: 'level.color.temperature',
798
+ write: false,
799
+ read: true,
800
+ type: 'number',
801
+ def: config.useKelvin == true ? utils.miredKelvinConversion(150) : 500,
802
+ min: config.useKelvin == true ? utils.miredKelvinConversion(500) : 150,
803
+ max: config.useKelvin == true ? utils.miredKelvinConversion(150) : 500,
804
+ unit: config.useKelvin == true ? 'K' : 'mired',
805
+ isEvent: true,
806
+ getter: (payload) => {
807
+ if (payload.action != 'color_temperature_move') {
808
+ return undefined;
809
+ }
810
+
811
+ if (payload.action_color_temperature) {
812
+ if (config.useKelvin == true) {
813
+ return utils.miredKelvinConversion(payload.action_color_temperature);
814
+ }
815
+ else {
816
+ return payload.action_color_temperature;
817
+ }
818
+ }
819
+ },
820
+ }, expose.access);
821
+
822
+ }
823
+ else if (actionName == 'color_move') {
824
+ pushToStates({
825
+ id: 'color_move',
826
+ prop: 'action',
827
+ name: 'Color move value',
828
+ icon: undefined,
829
+ role: 'level.color.rgb',
830
+ write: false,
831
+ read: true,
832
+ type: 'string',
833
+ def: '#ffffff',
834
+ isEvent: true,
835
+ getter: (payload) => {
836
+ if (payload.action != 'color_move') {
837
+ return undefined;
838
+ }
839
+
840
+ if (payload.action_color && payload.action_color.hasOwnProperty('x') && payload.action_color.hasOwnProperty('y')) {
841
+ const colorval = rgb.cie_to_rgb(payload.action_color.x, payload.action_color.y);
842
+ return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
843
+ }
844
+ else {
845
+ return undefined;
846
+ }
847
+ }
848
+ }, expose.access);
849
+ }
850
+ else if (actionName == 'brightness_move_to_level') {
851
+ pushToStates({
852
+ id: 'brightness_move_to_level',
853
+ name: 'Brightness move to level',
854
+ icon: undefined,
855
+ role: 'level.dimmer',
856
+ write: false,
857
+ read: true,
858
+ type: 'number',
859
+ min: 0,
860
+ max: 100,
861
+ def: 100,
862
+ unit: '%',
863
+ isEvent: true,
864
+ getter: (payload) => {
865
+ if (payload.action != 'brightness_move_to_level') {
866
+ return undefined;
867
+ }
868
+
869
+ if (payload.action_level) {
870
+ return utils.bulbLevelToAdapterLevel(payload.action_level);
871
+ } else {
872
+ return undefined;
873
+ }
874
+ }
875
+ }, expose.access);
876
+ }
877
+ else if (actionName == 'move_to_saturation') {
878
+ pushToStates({
879
+ id: 'move_to_saturation',
880
+ name: 'Move to level saturation',
881
+ icon: undefined,
882
+ role: 'level.color.saturation',
883
+ write: false,
884
+ read: true,
885
+ type: 'number',
886
+ // min: 0,
887
+ // max: 100,
888
+ def: 0,
889
+ isEvent: true,
890
+ getter: (payload) => {
891
+ if (payload.action != 'move_to_saturation') {
892
+ return undefined;
893
+ }
894
+
895
+ if (payload.action_level) {
896
+ return payload.action_saturation;
897
+ } else {
898
+ return undefined;
899
+ }
900
+ }
901
+ }, expose.access);
902
+ }
903
+ else if (actionName == 'enhanced_move_to_hue_and_saturation') {
904
+ pushToStates({
905
+ id: 'enhanced_move_to_hue_and_saturation',
906
+ prop: 'action',
907
+ name: 'Enhanced move to hue and saturation value',
908
+ icon: undefined,
909
+ role: 'level.color.hue',
910
+ write: false,
911
+ read: true,
912
+ type: 'number',
913
+ min: 0,
914
+ max: 65536,
915
+ def: 0,
916
+ isEvent: true,
917
+ getter: (payload) => {
918
+ if (payload.action != 'enhanced_move_to_hue_and_saturation') {
919
+ return undefined;
920
+ }
921
+
922
+ if (payload.action_enhanced_hue) {
923
+ return payload.action_enhanced_hue;
924
+ } else {
925
+ return undefined;
926
+ }
927
+ }
928
+ }, expose.access);
929
+ }
930
+ // else if (actionName == 'hue_move') {
931
+ // pushToStates({
932
+ // id: 'hue_move',
933
+ // name: 'Hue move rate',
934
+ // icon: undefined,
935
+ // role: 'level.color.hue',
936
+ // write: false,
937
+ // read: true,
938
+ // type: 'number',
939
+ // min: 0,
940
+ // max: 360,
941
+ // def: 0,
942
+ // isEvent: true,
943
+ // getter: (payload) => {
944
+ // if (payload.action != 'hue_move') {
945
+ // return undefined;
946
+ // }
947
+
948
+ // if (payload.action_level) {
949
+ // return payload.action_level;
950
+ // } else {
951
+ // return undefined;
952
+ // }
953
+ // }
954
+ // }, expose.access);
955
+ // }
956
+ else {
957
+ pushToStates({
958
+ id: actionName.replace(/\*/g, ''),
959
+ prop: 'action',
960
+ name: actionName,
961
+ icon: undefined,
962
+ role: 'button',
963
+ write: false,
964
+ read: true,
965
+ type: 'boolean',
966
+ def: false,
967
+ isEvent: true,
968
+ getter: payload => (payload.action === actionName) ? true : undefined,
969
+ }, expose.access);
970
+ }
971
+ }
972
+ // Can the device simulated_brightness?
973
+ if (definition.options && definition.options.find(x => x.property == 'simulated_brightness')) {
974
+ pushToStates(statesDefs.simulated_brightness, z2mAccess.STATE);
975
+ }
976
+ state = null;
977
+ break;
978
+ }
979
+ default:
980
+ state = genState(expose);
981
+ break;
982
+ }
983
+ if (state) pushToStates(state, expose.access);
984
+ break;
985
+
986
+ case 'binary':
987
+ if (expose.endpoint) {
988
+ state = genState(expose);
989
+ } else {
990
+ switch (expose.name) {
991
+ case 'contact':
992
+ state = statesDefs.contact;
993
+ pushToStates(statesDefs.opened, expose.access);
994
+ break;
995
+
996
+ case 'battery_low':
997
+ state = statesDefs.heiman_batt_low;
998
+ break;
999
+
1000
+ case 'tamper':
1001
+ state = statesDefs.tamper;
1002
+ break;
1003
+
1004
+ case 'water_leak':
1005
+ state = statesDefs.water_detected;
1006
+ break;
1007
+
1008
+ case 'lock':
1009
+ state = statesDefs.child_lock;
1010
+ break;
1011
+
1012
+ case 'occupancy':
1013
+ state = statesDefs.occupancy;
1014
+ break;
1015
+
1016
+ default:
1017
+ state = genState(expose);
1018
+ break;
1019
+ }
1020
+ }
1021
+ if (state) pushToStates(state, expose.access);
1022
+ break;
1023
+
1024
+ case 'text':
1025
+ state = genState(expose);
1026
+ pushToStates(state, expose.access);
1027
+ break;
1028
+
1029
+ case 'lock':
1030
+ case 'fan':
1031
+ case 'cover':
1032
+ for (const prop of expose.features) {
1033
+ switch (prop.name) {
1034
+ case 'state':
1035
+ pushToStates(genState(prop, 'switch'), prop.access);
1036
+ // features contains TOGGLE?
1037
+ if (prop.value_toggle) {
1038
+ pushToStates({
1039
+ id: `${prop.property}_toggle`,
1040
+ prop: `${prop.property}_toggle`,
1041
+ name: `Toggle state of the ${prop.property}`,
1042
+ icon: undefined,
1043
+ role: 'button',
1044
+ write: true,
1045
+ read: true,
1046
+ def: true,
1047
+ type: 'boolean',
1048
+ setattr: prop.property,
1049
+ setter: (value) => (value) ? prop.value_toggle : undefined
1050
+ });
1051
+ }
1052
+ break;
1053
+ default:
1054
+ pushToStates(genState(prop), prop.access);
1055
+ break;
1056
+ }
1057
+ }
1058
+ break;
1059
+
1060
+ case 'climate':
1061
+ for (const prop of expose.features) {
1062
+ switch (prop.name) {
1063
+ case 'away_mode':
1064
+ pushToStates(statesDefs.climate_away_mode, prop.access);
1065
+ break;
1066
+ case 'system_mode':
1067
+ pushToStates(statesDefs.climate_system_mode, prop.access);
1068
+ break;
1069
+ case 'running_mode':
1070
+ pushToStates(statesDefs.climate_running_mode, prop.access);
1071
+ break;
1072
+ case 'local_temperature':
1073
+ pushToStates(statesDefs.hvacThermostat_local_temp, prop.access);
1074
+ break;
1075
+ case 'local_temperature_calibration':
1076
+ pushToStates(statesDefs.hvacThermostat_local_temp_calibration, prop.access);
1077
+ break;
1078
+ default:
1079
+ {
1080
+ if (prop.name.includes('heating_setpoint')) {
1081
+ pushToStates(genState(prop, 'level.temperature'), prop.access);
1082
+ } else {
1083
+ pushToStates(genState(prop), prop.access);
1084
+ }
1085
+ }
1086
+ break;
1087
+ }
1088
+ }
1089
+ break;
1090
+
1091
+ case 'composite':
1092
+ for (const prop of expose.features) {
1093
+ // let state;
1094
+ // if (prop.type == 'list' && prop.features) {
1095
+ // const listName = prop.name;
1096
+
1097
+ // for (const feature of prop.features) {
1098
+ // genState(feature, 'state', `${listName}_${feature.name}`, prop.description);
1099
+ // }
1100
+ // }
1101
+
1102
+ const state = genState(prop);
1103
+ // Workaround for FP1 new state (region_upsert)
1104
+ if (!state) {
1105
+ break;
1106
+ }
1107
+ state.prop = expose.property;
1108
+ state.inOptions = true;
1109
+ // I'm not fully sure, as it really needed, but
1110
+ state.setterOpt = (value, options) => {
1111
+ const result = {};
1112
+ options[prop.property] = value;
1113
+ result[expose.property] = options;
1114
+ return result;
1115
+ };
1116
+ // if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
1117
+ if (prop.access & z2mAccess.SET) {
1118
+ state.setter = (value, options) => {
1119
+ const result = {};
1120
+ options[prop.property] = value;
1121
+ result[expose.property] = options;
1122
+ return result;
1123
+ };
1124
+ state.setattr = expose.property;
1125
+ }
1126
+ // if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
1127
+ if (prop.access & z2mAccess.STATE) {
1128
+ state.getter = payload => {
1129
+ if ((payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
1130
+ return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined;
1131
+ } else {
1132
+ return undefined;
1133
+ }
1134
+ };
1135
+ } else {
1136
+ state.getter = _payload => { return undefined; };
1137
+ }
1138
+ pushToStates(state, prop.access);
1139
+ }
1140
+ break;
1141
+ default:
1142
+ console.log(`Unhandled expose type ${expose.type} for device ${deviceID}`);
1143
+ }
1144
+ }
1145
1145
  } catch (err) {
1146
- console.log(`ERROR in expose ${expose.type} for device ${deviceID} : ${err}`);
1146
+ console.log(`ERROR in expose for device ${deviceID} : ${err}`);
1147
1147
  }
1148
1148
 
1149
1149
  // If necessary, add states defined for this device model.