iobroker.zigbee2mqtt 2.13.4 → 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
@@ -301,819 +301,849 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
301
301
  scenes = scenes.concat(devicesMessag.endpoints[key].scenes);
302
302
  }
303
303
  }
304
-
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) {
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';
330
314
  pushToStates({
331
- id: `${stateName}_toggle`,
332
- prop: `${stateName}_toggle`,
333
- name: `Toggle state of the ${stateName}`,
315
+ id: stateName,
316
+ name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
334
317
  options: ['transition'],
335
318
  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',
319
+ role: 'switch',
587
320
  write: true,
588
321
  read: true,
589
322
  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);
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) {
684
330
  pushToStates({
685
- id: actionName,
686
- prop: 'action',
687
- name: `Triggered action ${value.replace('*_', endpointName)}`,
331
+ id: `${stateName}_toggle`,
332
+ prop: `${stateName}_toggle`,
333
+ name: `Toggle state of the ${stateName}`,
334
+ options: ['transition'],
688
335
  icon: undefined,
689
336
  role: 'button',
690
- write: false,
337
+ write: true,
691
338
  read: true,
692
339
  type: 'boolean',
693
- def: false,
694
- isEvent: true,
695
- getter: payload => (payload.action === actionName) ? true : undefined,
696
- }, expose.access);
340
+ def: true,
341
+ setattr: stateName,
342
+ setter: (value) => (value) ? prop.value_toggle : undefined
343
+ });
697
344
  }
345
+ break;
698
346
  }
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'))) {
347
+ case 'brightness': {
348
+ const stateName = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
721
349
  pushToStates({
722
- id: actionName.replace(/\*/g, ''),
723
- prop: 'action',
724
- name: actionName,
350
+ id: stateName,
351
+ name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
352
+ options: ['transition'],
725
353
  icon: undefined,
726
- role: 'button',
727
- write: false,
354
+ role: 'level.dimmer',
355
+ write: true,
728
356
  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;
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]);
742
364
  },
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`)) {
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`;
747
371
  pushToStates({
748
- id: actionName.replace(/\*/g, ''),
749
- prop: 'action',
750
- name: actionName,
372
+ id: `${stateName}_move`,
373
+ prop: brmPropName,
374
+ name: 'Increases or decreases the brightness by X units per second',
751
375
  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);
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;
767
401
  }
768
- // is press -> release state ?
769
- else if (config.simplePressReleaseState == true && actionName.endsWith('press') && expose.values.find((x) => x == actionName.replace('press', 'release'))) {
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';
770
406
  pushToStates({
771
- id: actionName.replace(/\*/g, ''),
772
- prop: 'action',
773
- name: actionName,
407
+ id: stateName,
408
+ prop: propName,
409
+ name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
410
+ options: ['transition'],
774
411
  icon: undefined,
775
- role: 'button',
776
- write: false,
412
+ role: 'level.color.temperature',
413
+ write: true,
777
414
  read: true,
778
- def: false,
779
- type: 'boolean',
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
+ },
780
423
  getter: (payload) => {
781
- if (payload.action === actionName) {
782
- return true;
424
+ if (payload[colorMode] != 'color_temp') {
425
+ return undefined;
783
426
  }
784
- if (payload.action === actionName.replace('press', 'release')) {
785
- return false;
427
+ if (config.useKelvin == true) {
428
+ return utils.miredKelvinConversion(payload[propName]);
429
+ } else {
430
+ return payload[propName];
786
431
  }
787
- return undefined;
788
432
  },
789
- }, expose.access);
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;
790
449
  }
791
- else if (actionName == 'color_temperature_move') {
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';
792
454
  pushToStates({
793
- id: 'color_temperature_move',
794
- prop: 'action',
795
- name: 'Color temperature move value',
455
+ id: stateName,
456
+ prop: propName,
457
+ name: `${prop.description} ${expose.endpoint ? `(${expose.endpoint})` : ''}`.trim(),
458
+ //options: ['transition'],
796
459
  icon: undefined,
797
460
  role: 'level.color.temperature',
798
- write: false,
461
+ write: true,
799
462
  read: true,
800
463
  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,
464
+ min: 0,
465
+ max: 65535,
466
+ def: undefined,
804
467
  unit: config.useKelvin == true ? 'K' : 'mired',
805
- isEvent: true,
468
+ setter: (value) => {
469
+ return utils.toMired(value);
470
+ },
806
471
  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
- }
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];
818
479
  }
819
480
  },
820
- }, expose.access);
821
-
481
+ }, prop.access);
482
+ break;
822
483
  }
823
- else if (actionName == 'color_move') {
484
+ case 'color_xy': {
485
+ const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
486
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
824
487
  pushToStates({
825
- id: 'color_move',
826
- prop: 'action',
827
- name: 'Color move value',
488
+ id: stateName,
489
+ name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
490
+ options: ['transition'],
828
491
  icon: undefined,
829
492
  role: 'level.color.rgb',
830
- write: false,
493
+ write: true,
831
494
  read: true,
832
495
  type: 'string',
833
- def: '#ffffff',
834
- isEvent: true,
835
- getter: (payload) => {
836
- if (payload.action != 'color_move') {
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) {
837
511
  return undefined;
838
512
  }
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);
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);
842
515
  return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
843
- }
844
- else {
516
+ } else {
845
517
  return undefined;
846
518
  }
847
- }
848
- }, expose.access);
519
+ },
520
+ epname: expose.endpoint,
521
+ }, prop.access);
522
+ break;
849
523
  }
850
- else if (actionName == 'brightness_move_to_level') {
524
+ case 'color_hs': {
525
+ const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
526
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
851
527
  pushToStates({
852
- id: 'brightness_move_to_level',
853
- name: 'Brightness move to level',
528
+ id: stateName,
529
+ name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
530
+ options: ['transition'],
854
531
  icon: undefined,
855
- role: 'level.dimmer',
856
- write: false,
532
+ role: 'level.color.rgb',
533
+ write: true,
857
534
  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') {
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) {
866
549
  return undefined;
867
550
  }
868
551
 
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;
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));
893
554
  }
894
555
 
895
- if (payload.action_level) {
896
- return payload.action_saturation;
897
- } else {
898
- return undefined;
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]);
899
559
  }
900
- }
901
- }, expose.access);
902
- }
903
- // else if (actionName == 'hue_move') {
904
- // pushToStates({
905
- // id: 'hue_move',
906
- // name: 'Hue move rate',
907
- // icon: undefined,
908
- // role: 'level.color.hue',
909
- // write: false,
910
- // read: true,
911
- // type: 'number',
912
- // min: 0,
913
- // max: 360,
914
- // def: 0,
915
- // isEvent: true,
916
- // getter: (payload) => {
917
- // if (payload.action != 'hue_move') {
918
- // return undefined;
919
- // }
920
-
921
- // if (payload.action_level) {
922
- // return payload.action_level;
923
- // } else {
924
- // return undefined;
925
- // }
926
- // }
927
- // }, expose.access);
928
- // }
929
- else {
930
- pushToStates({
931
- id: actionName.replace(/\*/g, ''),
932
- prop: 'action',
933
- name: actionName,
934
- icon: undefined,
935
- role: 'button',
936
- write: false,
937
- read: true,
938
- type: 'boolean',
939
- def: false,
940
- isEvent: true,
941
- getter: payload => (payload.action === actionName) ? true : undefined,
942
- }, expose.access);
560
+ return undefined;
561
+
562
+ },
563
+ }, prop.access);
564
+ break;
943
565
  }
566
+ default:
567
+ pushToStates(genState(prop), prop.access);
568
+ break;
944
569
  }
945
- // Can the device simulated_brightness?
946
- if (definition.options && definition.options.find(x => x.property == 'simulated_brightness')) {
947
- pushToStates(statesDefs.simulated_brightness, z2mAccess.STATE);
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;
948
599
  }
949
- state = null;
950
- break;
951
600
  }
952
- default:
601
+ break;
602
+
603
+ case 'numeric':
604
+ if (expose.endpoint) {
953
605
  state = genState(expose);
954
- break;
955
- }
956
- if (state) pushToStates(state, expose.access);
957
- break;
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;
958
657
 
959
- case 'binary':
960
- if (expose.endpoint) {
961
- state = genState(expose);
962
- } else {
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':
963
671
  switch (expose.name) {
964
- case 'contact':
965
- state = statesDefs.contact;
966
- pushToStates(statesDefs.opened, expose.access);
967
- break;
672
+ case 'action': {
968
673
 
969
- case 'battery_low':
970
- state = statesDefs.heiman_batt_low;
971
- break;
674
+ if (!Array.isArray(expose.values)) {
675
+ break;
676
+ }
972
677
 
973
- case 'tamper':
974
- state = statesDefs.tamper;
975
- break;
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
+ }
976
701
 
977
- case 'water_leak':
978
- state = statesDefs.water_detected;
979
- break;
702
+ for (const actionName of expose.values) {
980
703
 
981
- case 'lock':
982
- state = statesDefs.child_lock;
983
- break;
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
+ }
984
708
 
985
- case 'occupancy':
986
- state = statesDefs.occupancy;
987
- break;
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
+ }
988
713
 
989
- default:
990
- state = genState(expose);
991
- break;
992
- }
993
- }
994
- if (state) pushToStates(state, expose.access);
995
- break;
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
+ }
996
718
 
997
- case 'text':
998
- state = genState(expose);
999
- pushToStates(state, expose.access);
1000
- break;
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
+ }
1001
810
 
1002
- case 'lock':
1003
- case 'fan':
1004
- case 'cover':
1005
- for (const prop of expose.features) {
1006
- switch (prop.name) {
1007
- case 'state':
1008
- pushToStates(genState(prop, 'switch'), prop.access);
1009
- // features contains TOGGLE?
1010
- if (prop.value_toggle) {
1011
- pushToStates({
1012
- id: `${prop.property}_toggle`,
1013
- prop: `${prop.property}_toggle`,
1014
- name: `Toggle state of the ${prop.property}`,
1015
- icon: undefined,
1016
- role: 'button',
1017
- write: true,
1018
- read: true,
1019
- def: true,
1020
- type: 'boolean',
1021
- setattr: prop.property,
1022
- setter: (value) => (value) ? prop.value_toggle : undefined
1023
- });
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
+ }
1024
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;
1025
977
  break;
978
+ }
1026
979
  default:
1027
- pushToStates(genState(prop), prop.access);
980
+ state = genState(expose);
1028
981
  break;
1029
982
  }
1030
- }
1031
- break;
983
+ if (state) pushToStates(state, expose.access);
984
+ break;
1032
985
 
1033
- case 'climate':
1034
- for (const prop of expose.features) {
1035
- switch (prop.name) {
1036
- case 'away_mode':
1037
- pushToStates(statesDefs.climate_away_mode, prop.access);
1038
- break;
1039
- case 'system_mode':
1040
- pushToStates(statesDefs.climate_system_mode, prop.access);
1041
- break;
1042
- case 'running_mode':
1043
- pushToStates(statesDefs.climate_running_mode, prop.access);
1044
- break;
1045
- case 'local_temperature':
1046
- pushToStates(statesDefs.hvacThermostat_local_temp, prop.access);
1047
- break;
1048
- case 'local_temperature_calibration':
1049
- pushToStates(statesDefs.hvacThermostat_local_temp_calibration, prop.access);
1050
- break;
1051
- default:
1052
- {
1053
- if (prop.name.includes('heating_setpoint')) {
1054
- pushToStates(genState(prop, 'level.temperature'), prop.access);
1055
- } else {
1056
- pushToStates(genState(prop), prop.access);
1057
- }
1058
- }
1059
- break;
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
+ }
1060
1020
  }
1061
- }
1062
- break;
1021
+ if (state) pushToStates(state, expose.access);
1022
+ break;
1063
1023
 
1064
- case 'composite':
1065
- for (const prop of expose.features) {
1066
- // let state;
1067
- // if (prop.type == 'list' && prop.features) {
1068
- // const listName = prop.name;
1069
-
1070
- // for (const feature of prop.features) {
1071
- // genState(feature, 'state', `${listName}_${feature.name}`, prop.description);
1072
- // }
1073
- // }
1074
-
1075
- const state = genState(prop);
1076
- // Workaround for FP1 new state (region_upsert)
1077
- if (!state) {
1078
- break;
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
+ }
1079
1057
  }
1080
- state.prop = expose.property;
1081
- state.inOptions = true;
1082
- // I'm not fully sure, as it really needed, but
1083
- state.setterOpt = (value, options) => {
1084
- const result = {};
1085
- options[prop.property] = value;
1086
- result[expose.property] = options;
1087
- return result;
1088
- };
1089
- // if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
1090
- if (prop.access & z2mAccess.SET) {
1091
- state.setter = (value, options) => {
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) => {
1092
1111
  const result = {};
1093
1112
  options[prop.property] = value;
1094
1113
  result[expose.property] = options;
1095
1114
  return result;
1096
1115
  };
1097
- state.setattr = expose.property;
1098
- }
1099
- // if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
1100
- if (prop.access & z2mAccess.STATE) {
1101
- state.getter = payload => {
1102
- if ((payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
1103
- return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined;
1104
- } else {
1105
- return undefined;
1106
- }
1107
- };
1108
- } else {
1109
- state.getter = _payload => { return undefined; };
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);
1110
1139
  }
1111
- pushToStates(state, prop.access);
1112
- }
1113
- break;
1114
- default:
1115
- console.log(`Unhandled expose type ${expose.type} for device ${deviceID}`);
1140
+ break;
1141
+ default:
1142
+ console.log(`Unhandled expose type ${expose.type} for device ${deviceID}`);
1143
+ }
1116
1144
  }
1145
+ } catch (err) {
1146
+ console.log(`ERROR in expose for device ${deviceID} : ${err}`);
1117
1147
  }
1118
1148
 
1119
1149
  // If necessary, add states defined for this device model.