iobroker.zigbee2mqtt 2.13.5 → 2.13.10

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
@@ -11,28 +11,28 @@ const getNonGenDevStatesDefs = require('./nonGenericDevicesExtension').getStateD
11
11
  // https://www.zigbee2mqtt.io/guide/usage/exposes.html#access
12
12
  const z2mAccess = {
13
13
  /**
14
- * Bit 0: The property can be found in the published state of this device
15
- */
14
+ * Bit 0: The property can be found in the published state of this device
15
+ */
16
16
  STATE: 1,
17
17
  /**
18
- * Bit 1: The property can be set with a /set command
19
- */
18
+ * Bit 1: The property can be set with a /set command
19
+ */
20
20
  SET: 2,
21
21
  /**
22
- * Bit 2: The property can be retrieved with a /get command
23
- */
22
+ * Bit 2: The property can be retrieved with a /get command
23
+ */
24
24
  GET: 4,
25
25
  /**
26
- * Bitwise inclusive OR of STATE and SET : 0b001 | 0b010
27
- */
26
+ * Bitwise inclusive OR of STATE and SET : 0b001 | 0b010
27
+ */
28
28
  STATE_SET: 3,
29
29
  /**
30
- * Bitwise inclusive OR of STATE and GET : 0b001 | 0b100
31
- */
30
+ * Bitwise inclusive OR of STATE and GET : 0b001 | 0b100
31
+ */
32
32
  STATE_GET: 5,
33
33
  /**
34
- * Bitwise inclusive OR of STATE and GET and SET : 0b001 | 0b100 | 0b010
35
- */
34
+ * Bitwise inclusive OR of STATE and GET and SET : 0b001 | 0b100 | 0b010
35
+ */
36
36
  ALL: 7,
37
37
  };
38
38
 
@@ -40,14 +40,14 @@ function genState(expose, role, name, desc) {
40
40
  let state;
41
41
  const readable = (expose.access & z2mAccess.STATE) > 0;
42
42
  const writable = (expose.access & z2mAccess.SET) > 0;
43
- const stname = (name || expose.property);
43
+ const stname = name || expose.property;
44
44
 
45
45
  if (typeof stname !== 'string') {
46
46
  return;
47
47
  }
48
48
 
49
49
  const stateId = stname.replace(/\*/g, '');
50
- const stateName = (desc || expose.description || expose.name);
50
+ const stateName = desc || expose.description || expose.name;
51
51
  const propName = expose.property;
52
52
 
53
53
  switch (expose.type) {
@@ -64,13 +64,14 @@ function genState(expose, role, name, desc) {
64
64
  };
65
65
 
66
66
  if (readable) {
67
- state.getter = (payload) => (payload[propName] === (expose.value_on || 'ON'));
67
+ state.getter = (payload) => payload[propName] === (expose.value_on || 'ON');
68
68
  } else {
69
- state.getter = (_payload) => (undefined);
69
+ state.getter = (_payload) => undefined;
70
70
  }
71
71
 
72
72
  if (writable) {
73
- state.setter = (payload) => (payload) ? (expose.value_on || 'ON') : ((expose.value_off != undefined) ? expose.value_off : 'OFF');
73
+ state.setter = (payload) =>
74
+ payload ? expose.value_on || 'ON' : expose.value_off != undefined ? expose.value_off : 'OFF';
74
75
  state.setattr = expose.property;
75
76
  }
76
77
 
@@ -108,7 +109,7 @@ function genState(expose, role, name, desc) {
108
109
  role: role || 'state',
109
110
  write: writable,
110
111
  read: true,
111
- states: {}
112
+ states: {},
112
113
  };
113
114
 
114
115
  for (const val of expose.values) {
@@ -118,7 +119,7 @@ function genState(expose, role, name, desc) {
118
119
  } else {
119
120
  state.states[val] = val;
120
121
  }
121
- state.type = typeof (val);
122
+ state.type = typeof val;
122
123
  }
123
124
 
124
125
  if (expose.endpoint) {
@@ -139,7 +140,9 @@ function genState(expose, role, name, desc) {
139
140
  };
140
141
  if (propName == 'action') {
141
142
  state.isEvent = true;
142
- state.getter = (payload) => { return payload[propName]; };
143
+ state.getter = (payload) => {
144
+ return payload[propName];
145
+ };
143
146
  }
144
147
  if (expose.endpoint) {
145
148
  state.epname = expose.endpoint;
@@ -182,7 +185,6 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
182
185
  const disabled = devicesMessag.disabled && devicesMessag.disabled == true;
183
186
  const description = devicesMessag.description ? devicesMessag.description : undefined;
184
187
 
185
-
186
188
  function pushToStates(state, access) {
187
189
  if (state === undefined) {
188
190
  return 0;
@@ -190,7 +192,7 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
190
192
  if (access === undefined) access = z2mAccess.ALL;
191
193
  state.readable = (access & z2mAccess.STATE) > 0;
192
194
  state.writable = (access & z2mAccess.SET) > 0;
193
- const stateExists = states.findIndex((x, _index, _array) => (x.id === state.id));
195
+ const stateExists = states.findIndex((x, _index, _array) => x.id === state.id);
194
196
 
195
197
  if (stateExists < 0) {
196
198
  state.write = state.writable;
@@ -207,14 +209,13 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
207
209
  if (!state.readable) {
208
210
  if (state.hasOwnProperty('getter')) {
209
211
  //to awid some worning on unprocessed data
210
- state.getter = _payload => (undefined);
212
+ state.getter = (_payload) => undefined;
211
213
  }
212
214
  }
213
215
 
214
216
  return states.push(state);
215
217
  } else {
216
-
217
- if ((state.readable) && (!states[stateExists].readable)) {
218
+ if (state.readable && !states[stateExists].readable) {
218
219
  states[stateExists].read = state.read;
219
220
  // as state is readable, it can't be button or event
220
221
  if (states[stateExists].role === 'button') {
@@ -233,7 +234,7 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
233
234
  // trying to remove the `prop` property, as main key for get and set,
234
235
  // as it can be different in new and old states, and leave only:
235
236
  // setattr for old and id for new
236
- if ((state.hasOwnProperty('prop')) && (state.prop === state.id)) {
237
+ if (state.hasOwnProperty('prop') && state.prop === state.id) {
237
238
  if (states[stateExists].hasOwnProperty('prop')) {
238
239
  if (states[stateExists].prop !== states[stateExists].id) {
239
240
  if (!states[stateExists].hasOwnProperty('setattr')) {
@@ -248,7 +249,7 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
248
249
  states[stateExists].readable = true;
249
250
  }
250
251
 
251
- if ((state.writable) && (!states[stateExists].writable)) {
252
+ if (state.writable && !states[stateExists].writable) {
252
253
  states[stateExists].write = state.writable;
253
254
  // use new state `setter`
254
255
  if (state.hasOwnProperty('setter')) {
@@ -267,7 +268,10 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
267
268
 
268
269
  // as we have new state, responsible for set, we have to use new `isOption`
269
270
  // or remove it
270
- if (((!state.hasOwnProperty('isOption')) || (state.isOptions === false)) && (states[stateExists].hasOwnProperty('isOption'))) {
271
+ if (
272
+ (!state.hasOwnProperty('isOption') || state.isOptions === false) &&
273
+ states[stateExists].hasOwnProperty('isOption')
274
+ ) {
271
275
  delete states[stateExists].isOption;
272
276
  } else {
273
277
  states[stateExists].isOption = state.isOption;
@@ -302,848 +306,1002 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
302
306
  }
303
307
  }
304
308
  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') {
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') {
309
+ for (const expose of definition.exposes) {
310
+ let state;
311
+
312
+ switch (expose.type) {
313
+ case 'light':
314
+ for (const prop of expose.features) {
315
+ switch (prop.name) {
316
+ case 'state': {
317
+ const stateName = expose.endpoint ? `state_${expose.endpoint}` : 'state';
318
+ pushToStates(
319
+ {
320
+ id: stateName,
321
+ name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
322
+ options: ['transition'],
323
+ icon: undefined,
324
+ role: 'switch',
325
+ write: true,
326
+ read: true,
327
+ type: 'boolean',
328
+ getter: (payload) => payload[stateName] === (prop.value_on || 'ON'),
329
+ setter: (value) =>
330
+ value
331
+ ? prop.value_on || 'ON'
332
+ : prop.value_off != undefined
333
+ ? prop.value_off
334
+ : 'OFF',
335
+ epname: expose.endpoint,
336
+ //setattr: stateName,
337
+ },
338
+ prop.access
339
+ );
340
+ // features contains TOGGLE?
341
+ if (prop.value_toggle) {
342
+ pushToStates({
343
+ id: `${stateName}_toggle`,
344
+ prop: `${stateName}_toggle`,
345
+ name: `Toggle state of the ${stateName}`,
346
+ options: ['transition'],
347
+ icon: undefined,
348
+ role: 'button',
349
+ write: true,
350
+ read: true,
351
+ type: 'boolean',
352
+ def: true,
353
+ setattr: stateName,
354
+ setter: (value) => (value ? prop.value_toggle : undefined),
355
+ });
356
+ }
357
+ break;
358
+ }
359
+ case 'brightness': {
360
+ const stateName = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
361
+ pushToStates(
362
+ {
363
+ id: stateName,
364
+ name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
365
+ options: ['transition'],
366
+ icon: undefined,
367
+ role: 'level.dimmer',
368
+ write: true,
369
+ read: true,
370
+ type: 'number',
371
+ min: 0, // ignore expose.value_min
372
+ max: 100, // ignore expose.value_max
373
+ def: 100,
374
+ unit: '%',
375
+ getter: (value) => {
376
+ return utils.bulbLevelToAdapterLevel(value[stateName]);
377
+ },
378
+ setter: (value) => {
379
+ return utils.adapterLevelToBulbLevel(value);
380
+ },
381
+ },
382
+ prop.access
383
+ );
384
+ // brightnessMoveOnOff
385
+ const brmPropName =
386
+ config.brightnessMoveOnOff == true
387
+ ? `${stateName}_move_onoff`
388
+ : `${stateName}_move`;
389
+ pushToStates(
390
+ {
391
+ id: `${stateName}_move`,
392
+ prop: brmPropName,
393
+ name: 'Increases or decreases the brightness by X units per second',
394
+ icon: undefined,
395
+ role: 'state',
396
+ write: true,
397
+ read: false,
398
+ type: 'number',
399
+ min: -50,
400
+ max: 50,
401
+ def: 0,
402
+ },
403
+ z2mAccess.SET
404
+ );
405
+ // brightnessStepOnOff
406
+ const brspropName =
407
+ config.brightnessStepOnOff == true
408
+ ? `${stateName}_step_onoff`
409
+ : `${stateName}_step`;
410
+ pushToStates(
411
+ {
412
+ id: `${stateName}_step`,
413
+ prop: brspropName,
414
+ name: 'Increases or decreases brightness by X steps',
415
+ options: ['transition'],
416
+ icon: undefined,
417
+ role: 'state',
418
+ write: true,
419
+ read: false,
420
+ type: 'number',
421
+ min: -255,
422
+ max: 255,
423
+ def: 0,
424
+ },
425
+ z2mAccess.SET
426
+ );
427
+ break;
428
+ }
429
+ case 'color_temp': {
430
+ const stateName = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
431
+ const propName = expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp';
432
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
433
+ pushToStates(
434
+ {
435
+ id: stateName,
436
+ prop: propName,
437
+ name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
438
+ options: ['transition'],
439
+ icon: undefined,
440
+ role: 'level.color.temperature',
441
+ write: true,
442
+ read: true,
443
+ type: 'number',
444
+ min:
445
+ config.useKelvin == true
446
+ ? utils.miredKelvinConversion(prop.value_max)
447
+ : prop.value_min,
448
+ max:
449
+ config.useKelvin == true
450
+ ? utils.miredKelvinConversion(prop.value_min)
451
+ : prop.value_max,
452
+ def:
453
+ config.useKelvin == true
454
+ ? utils.miredKelvinConversion(prop.value_min)
455
+ : prop.value_max,
456
+ unit: config.useKelvin == true ? 'K' : 'mired',
457
+ setter: (value) => {
458
+ return utils.toMired(value);
459
+ },
460
+ getter: (payload) => {
461
+ if (payload[colorMode] != 'color_temp') {
462
+ return undefined;
463
+ }
464
+ if (config.useKelvin == true) {
465
+ return utils.miredKelvinConversion(payload[propName]);
466
+ } else {
467
+ return payload[propName];
468
+ }
469
+ },
470
+ },
471
+ prop.access
472
+ );
473
+ // Colortemp
474
+ pushToStates(
475
+ {
476
+ id: `${stateName}_move`,
477
+ prop: `${propName}_move`,
478
+ name: 'Colortemp change',
479
+ icon: undefined,
480
+ role: 'state',
481
+ write: true,
482
+ read: false,
483
+ type: 'number',
484
+ min: -50,
485
+ max: 50,
486
+ def: 0,
487
+ },
488
+ prop.access
489
+ );
490
+ break;
491
+ }
492
+ case 'color_temp_startup': {
493
+ const stateName = expose.endpoint
494
+ ? `colortempstartup_${expose.endpoint}`
495
+ : 'colortempstartup';
496
+ const propName = expose.endpoint
497
+ ? `color_temp_startup_${expose.endpoint}`
498
+ : 'color_temp_startup';
499
+ //const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
500
+ pushToStates(
501
+ {
502
+ id: stateName,
503
+ prop: propName,
504
+ name: `${prop.description} ${expose.endpoint ? `(${expose.endpoint})` : ''}`.trim(),
505
+ //options: ['transition'],
506
+ icon: undefined,
507
+ role: 'level.color.temperature',
508
+ write: true,
509
+ read: true,
510
+ type: 'number',
511
+ min: 0,
512
+ max: 65535,
513
+ def: undefined,
514
+ unit: config.useKelvin == true ? 'K' : 'mired',
515
+ setter: (value) => {
516
+ return utils.toMired(value);
517
+ },
518
+ getter: (payload) => {
519
+ //if (payload[colorMode] != 'color_temp') {
520
+ // return undefined;
521
+ //}
522
+ if (config.useKelvin == true) {
523
+ return utils.miredKelvinConversion(payload[propName]);
524
+ } else {
525
+ return payload[propName];
526
+ }
527
+ },
528
+ },
529
+ prop.access
530
+ );
531
+ break;
532
+ }
533
+ case 'color_xy': {
534
+ const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
535
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
536
+ pushToStates(
537
+ {
538
+ id: stateName,
539
+ name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
540
+ options: ['transition'],
541
+ icon: undefined,
542
+ role: 'level.color.rgb',
543
+ write: true,
544
+ read: true,
545
+ type: 'string',
546
+ def: '#ff00ff',
547
+ setter: (value) => {
548
+ let xy = [0, 0];
549
+ const rgbcolor = colors.ParseColor(value);
550
+
551
+ xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
552
+ return {
553
+ x: xy[0],
554
+ y: xy[1],
555
+ };
556
+ },
557
+ getter: (payload) => {
558
+ if (payload[colorMode] != 'xy' && config.colorTempSyncColor == false) {
559
+ return undefined;
560
+ }
561
+ if (
562
+ payload[stateName] &&
563
+ payload[stateName].hasOwnProperty('x') &&
564
+ payload[stateName].hasOwnProperty('y')
565
+ ) {
566
+ const colorval = rgb.cie_to_rgb(
567
+ payload[stateName].x,
568
+ payload[stateName].y
569
+ );
570
+ return (
571
+ '#' +
572
+ utils.decimalToHex(colorval[0]) +
573
+ utils.decimalToHex(colorval[1]) +
574
+ utils.decimalToHex(colorval[2])
575
+ );
576
+ } else {
577
+ return undefined;
578
+ }
579
+ },
580
+ epname: expose.endpoint,
581
+ },
582
+ prop.access
583
+ );
584
+ break;
585
+ }
586
+ case 'color_hs': {
587
+ const stateName = expose.endpoint ? `color_${expose.endpoint}` : 'color';
588
+ const colorMode = expose.endpoint ? `color_mode_${expose.endpoint}` : 'color_mode';
589
+ pushToStates(
590
+ {
591
+ id: stateName,
592
+ name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
593
+ options: ['transition'],
594
+ icon: undefined,
595
+ role: 'level.color.rgb',
596
+ write: true,
597
+ read: true,
598
+ type: 'string',
599
+ def: '#ff00ff',
600
+ setter: (value) => {
601
+ const _rgb = colors.ParseColor(value);
602
+ const hsv = rgb.rgbToHSV(_rgb.r, _rgb.g, _rgb.b, true);
603
+ return {
604
+ h: Math.min(Math.max(hsv.h, 1), 359),
605
+ s: hsv.s,
606
+ //b: Math.round(hsv.v * 2.55),
607
+ };
608
+ },
609
+ getter: (payload) => {
610
+ if (
611
+ !['hs', 'xy'].includes(payload[colorMode]) &&
612
+ config.colorTempSyncColor == false
613
+ ) {
614
+ return undefined;
615
+ }
616
+
617
+ if (
618
+ payload[stateName] &&
619
+ payload[stateName].hasOwnProperty('h') &&
620
+ payload[stateName].hasOwnProperty('s') &
621
+ payload[stateName].hasOwnProperty('b')
622
+ ) {
623
+ return rgb.hsvToRGBString(
624
+ payload[stateName].h,
625
+ payload[stateName].s,
626
+ Math.round(payload[stateName].b / 2.55)
627
+ );
628
+ }
629
+
630
+ if (
631
+ payload[stateName] &&
632
+ payload[stateName].hasOwnProperty('x') &&
633
+ payload[stateName].hasOwnProperty('y')
634
+ ) {
635
+ const colorval = rgb.cie_to_rgb(
636
+ payload[stateName].x,
637
+ payload[stateName].y
638
+ );
639
+ return (
640
+ '#' +
641
+ utils.decimalToHex(colorval[0]) +
642
+ utils.decimalToHex(colorval[1]) +
643
+ utils.decimalToHex(colorval[2])
644
+ );
645
+ }
919
646
  return undefined;
920
- }
647
+ },
648
+ },
649
+ prop.access
650
+ );
651
+ break;
652
+ }
653
+ default:
654
+ pushToStates(genState(prop), prop.access);
655
+ break;
656
+ }
657
+ }
658
+ pushToStates(statesDefs.transition, z2mAccess.SET);
659
+ break;
660
+
661
+ case 'switch':
662
+ for (const prop of expose.features) {
663
+ switch (prop.name) {
664
+ case 'state':
665
+ pushToStates(genState(prop, 'switch'), prop.access);
666
+ // features contains TOGGLE?
667
+ if (prop.value_toggle) {
668
+ pushToStates({
669
+ id: `${prop.property}_toggle`,
670
+ prop: `${prop.property}_toggle`,
671
+ name: `Toggle state of the ${prop.property}`,
672
+ icon: undefined,
673
+ role: 'button',
674
+ write: true,
675
+ read: true,
676
+ type: 'boolean',
677
+ def: true,
678
+ setattr: prop.property,
679
+ setter: (value) => (value ? prop.value_toggle : undefined),
680
+ });
681
+ }
682
+ break;
683
+ default:
684
+ pushToStates(genState(prop), prop.access);
685
+ break;
686
+ }
687
+ }
688
+ break;
689
+
690
+ case 'numeric':
691
+ if (expose.endpoint) {
692
+ state = genState(expose);
693
+ } else {
694
+ switch (expose.name) {
695
+ case 'linkquality':
696
+ state = statesDefs.link_quality;
697
+ break;
698
+
699
+ case 'battery':
700
+ state = statesDefs.battery;
701
+ break;
702
+
703
+ case 'temperature':
704
+ state = statesDefs.temperature;
705
+ break;
706
+
707
+ case 'device_temperature':
708
+ state = statesDefs.device_temperature;
709
+ break;
710
+
711
+ case 'humidity':
712
+ state = statesDefs.humidity;
713
+ break;
714
+
715
+ case 'pressure':
716
+ state = statesDefs.pressure;
717
+ break;
718
+
719
+ case 'illuminance':
720
+ state = statesDefs.illuminance_raw;
721
+ break;
722
+
723
+ case 'illuminance_lux':
724
+ state = statesDefs.illuminance;
725
+ break;
726
+
727
+ case 'power':
728
+ state = statesDefs.load_power;
729
+ break;
730
+
731
+ case 'current':
732
+ state = statesDefs.load_current;
733
+ break;
734
+
735
+ case 'voltage':
736
+ state = statesDefs.voltage;
737
+ if (power_source == 'Battery') {
738
+ state = statesDefs.battery_voltage;
739
+ }
740
+ if (expose.unit == 'mV') {
741
+ state.getter = (payload) => payload.voltage / 1000;
742
+ }
743
+ break;
744
+
745
+ case 'energy':
746
+ state = statesDefs.energy;
747
+ break;
748
+
749
+ default:
750
+ state = genState(expose);
751
+ break;
752
+ }
753
+ }
754
+ if (state) pushToStates(state, expose.access);
755
+ break;
756
+
757
+ case 'enum':
758
+ switch (expose.name) {
759
+ case 'action': {
760
+ if (!Array.isArray(expose.values)) {
761
+ break;
762
+ }
921
763
 
922
- if (payload.action_enhanced_hue) {
923
- return payload.action_enhanced_hue;
924
- } else {
925
- return undefined;
926
- }
764
+ // Support for DIYRuZ Device
765
+ const wildcardValues = expose.values.filter((x) => x.startsWith('*'));
766
+ if (wildcardValues && wildcardValues.length > 0) {
767
+ for (const endpointName of [
768
+ ...new Set(definition.exposes.filter((x) => x.endpoint).map((x) => x.endpoint)),
769
+ ]) {
770
+ for (const value of wildcardValues) {
771
+ const actionName = value.replace('*', endpointName);
772
+ pushToStates(
773
+ {
774
+ id: actionName,
775
+ prop: 'action',
776
+ name: `Triggered action ${value.replace('*_', endpointName)}`,
777
+ icon: undefined,
778
+ role: 'button',
779
+ write: false,
780
+ read: true,
781
+ type: 'boolean',
782
+ def: false,
783
+ isEvent: true,
784
+ getter: (payload) => (payload.action === actionName ? true : undefined),
785
+ },
786
+ expose.access
787
+ );
927
788
  }
928
- }, expose.access);
789
+ }
790
+ break;
791
+ }
792
+
793
+ for (const actionName of expose.values) {
794
+ // is release -> hold state? - skip
795
+ if (
796
+ config.simpleHoldReleaseState == true &&
797
+ actionName.endsWith('release') &&
798
+ expose.values.find((x) => x == actionName.replace('release', 'hold'))
799
+ ) {
800
+ continue;
801
+ }
802
+
803
+ // is stop - move state? - skip
804
+ if (
805
+ config.simpleMoveStopState == true &&
806
+ actionName.endsWith('stop') &&
807
+ expose.values.find((x) => x.includes(actionName.replace('stop', 'move')))
808
+ ) {
809
+ continue;
810
+ }
811
+
812
+ // is release -> press state? - skip
813
+ if (
814
+ config.simplePressReleaseState == true &&
815
+ actionName.endsWith('release') &&
816
+ expose.values.find((x) => x == actionName.replace('release', 'press'))
817
+ ) {
818
+ continue;
819
+ }
820
+
821
+ // is hold -> release state ?
822
+ if (
823
+ config.simpleHoldReleaseState == true &&
824
+ actionName.endsWith('hold') &&
825
+ expose.values.find((x) => x == actionName.replace('hold', 'release'))
826
+ ) {
827
+ pushToStates(
828
+ {
829
+ id: actionName.replace(/\*/g, ''),
830
+ prop: 'action',
831
+ name: actionName,
832
+ icon: undefined,
833
+ role: 'button',
834
+ write: false,
835
+ read: true,
836
+ def: false,
837
+ type: 'boolean',
838
+ getter: (payload) => {
839
+ if (payload.action === actionName) {
840
+ return true;
841
+ }
842
+ if (payload.action === actionName.replace('hold', 'release')) {
843
+ return false;
844
+ }
845
+ if (payload.action === `${actionName}_release`) {
846
+ return false;
847
+ }
848
+ return undefined;
849
+ },
850
+ },
851
+ expose.access
852
+ );
853
+ }
854
+ // is move -> stop state ?
855
+ else if (
856
+ config.simpleMoveStopState == true &&
857
+ actionName.includes('move') &&
858
+ expose.values.find((x) => x == `${actionName.split('_')[0]}_stop`)
859
+ ) {
860
+ pushToStates(
861
+ {
862
+ id: actionName.replace(/\*/g, ''),
863
+ prop: 'action',
864
+ name: actionName,
865
+ icon: undefined,
866
+ role: 'button',
867
+ write: false,
868
+ read: true,
869
+ def: false,
870
+ type: 'boolean',
871
+ getter: (payload) => {
872
+ if (payload.action === actionName) {
873
+ return true;
874
+ }
875
+ if (payload.action === `${actionName.split('_')[0]}_stop`) {
876
+ return false;
877
+ }
878
+ return undefined;
879
+ },
880
+ },
881
+ expose.access
882
+ );
883
+ }
884
+ // is press -> release state ?
885
+ else if (
886
+ config.simplePressReleaseState == true &&
887
+ actionName.endsWith('press') &&
888
+ expose.values.find((x) => x == actionName.replace('press', 'release'))
889
+ ) {
890
+ pushToStates(
891
+ {
892
+ id: actionName.replace(/\*/g, ''),
893
+ prop: 'action',
894
+ name: actionName,
895
+ icon: undefined,
896
+ role: 'button',
897
+ write: false,
898
+ read: true,
899
+ def: false,
900
+ type: 'boolean',
901
+ getter: (payload) => {
902
+ if (payload.action === actionName) {
903
+ return true;
904
+ }
905
+ if (payload.action === actionName.replace('press', 'release')) {
906
+ return false;
907
+ }
908
+ return undefined;
909
+ },
910
+ },
911
+ expose.access
912
+ );
913
+ } else if (actionName == 'color_temperature_move') {
914
+ pushToStates(
915
+ {
916
+ id: 'color_temperature_move',
917
+ prop: 'action',
918
+ name: 'Color temperature move value',
919
+ icon: undefined,
920
+ role: 'level.color.temperature',
921
+ write: false,
922
+ read: true,
923
+ type: 'number',
924
+ def: config.useKelvin == true ? utils.miredKelvinConversion(150) : 500,
925
+ min: config.useKelvin == true ? utils.miredKelvinConversion(500) : 150,
926
+ max: config.useKelvin == true ? utils.miredKelvinConversion(150) : 500,
927
+ unit: config.useKelvin == true ? 'K' : 'mired',
928
+ isEvent: true,
929
+ getter: (payload) => {
930
+ if (payload.action != 'color_temperature_move') {
931
+ return undefined;
932
+ }
933
+
934
+ if (payload.action_color_temperature) {
935
+ if (config.useKelvin == true) {
936
+ return utils.miredKelvinConversion(
937
+ payload.action_color_temperature
938
+ );
939
+ } else {
940
+ return payload.action_color_temperature;
941
+ }
942
+ }
943
+ },
944
+ },
945
+ expose.access
946
+ );
947
+ } else if (actionName == 'color_move') {
948
+ pushToStates(
949
+ {
950
+ id: 'color_move',
951
+ prop: 'action',
952
+ name: 'Color move value',
953
+ icon: undefined,
954
+ role: 'level.color.rgb',
955
+ write: false,
956
+ read: true,
957
+ type: 'string',
958
+ def: '#ffffff',
959
+ isEvent: true,
960
+ getter: (payload) => {
961
+ if (payload.action != 'color_move') {
962
+ return undefined;
963
+ }
964
+
965
+ if (
966
+ payload.action_color &&
967
+ payload.action_color.hasOwnProperty('x') &&
968
+ payload.action_color.hasOwnProperty('y')
969
+ ) {
970
+ const colorval = rgb.cie_to_rgb(
971
+ payload.action_color.x,
972
+ payload.action_color.y
973
+ );
974
+ return (
975
+ '#' +
976
+ utils.decimalToHex(colorval[0]) +
977
+ utils.decimalToHex(colorval[1]) +
978
+ utils.decimalToHex(colorval[2])
979
+ );
980
+ } else {
981
+ return undefined;
982
+ }
983
+ },
984
+ },
985
+ expose.access
986
+ );
987
+ } else if (actionName == 'brightness_move_to_level') {
988
+ pushToStates(
989
+ {
990
+ id: 'brightness_move_to_level',
991
+ name: 'Brightness move to level',
992
+ icon: undefined,
993
+ role: 'level.dimmer',
994
+ write: false,
995
+ read: true,
996
+ type: 'number',
997
+ min: 0,
998
+ max: 100,
999
+ def: 100,
1000
+ unit: '%',
1001
+ isEvent: true,
1002
+ getter: (payload) => {
1003
+ if (payload.action != 'brightness_move_to_level') {
1004
+ return undefined;
1005
+ }
1006
+
1007
+ if (payload.action_level) {
1008
+ return utils.bulbLevelToAdapterLevel(payload.action_level);
1009
+ } else {
1010
+ return undefined;
1011
+ }
1012
+ },
1013
+ },
1014
+ expose.access
1015
+ );
1016
+ } else if (actionName == 'move_to_saturation') {
1017
+ pushToStates(
1018
+ {
1019
+ id: 'move_to_saturation',
1020
+ name: 'Move to level saturation',
1021
+ icon: undefined,
1022
+ role: 'level.color.saturation',
1023
+ write: false,
1024
+ read: true,
1025
+ type: 'number',
1026
+ // min: 0,
1027
+ // max: 100,
1028
+ def: 0,
1029
+ isEvent: true,
1030
+ getter: (payload) => {
1031
+ if (payload.action != 'move_to_saturation') {
1032
+ return undefined;
1033
+ }
1034
+
1035
+ if (payload.action_level) {
1036
+ return payload.action_saturation;
1037
+ } else {
1038
+ return undefined;
1039
+ }
1040
+ },
1041
+ },
1042
+ expose.access
1043
+ );
1044
+ } else if (actionName == 'enhanced_move_to_hue_and_saturation') {
1045
+ pushToStates(
1046
+ {
1047
+ id: 'enhanced_move_to_hue_and_saturation',
1048
+ prop: 'action',
1049
+ name: 'Enhanced move to hue and saturation value',
1050
+ icon: undefined,
1051
+ role: 'level.color.hue',
1052
+ write: false,
1053
+ read: true,
1054
+ type: 'number',
1055
+ min: 0,
1056
+ max: 65536,
1057
+ def: 0,
1058
+ isEvent: true,
1059
+ getter: (payload) => {
1060
+ if (payload.action != 'enhanced_move_to_hue_and_saturation') {
1061
+ return undefined;
1062
+ }
1063
+
1064
+ if (payload.action_enhanced_hue) {
1065
+ return payload.action_enhanced_hue;
1066
+ } else {
1067
+ return undefined;
1068
+ }
1069
+ },
1070
+ },
1071
+ expose.access
1072
+ );
1073
+ }
1074
+ // else if (actionName == 'hue_move') {
1075
+ // pushToStates({
1076
+ // id: 'hue_move',
1077
+ // name: 'Hue move rate',
1078
+ // icon: undefined,
1079
+ // role: 'level.color.hue',
1080
+ // write: false,
1081
+ // read: true,
1082
+ // type: 'number',
1083
+ // min: 0,
1084
+ // max: 360,
1085
+ // def: 0,
1086
+ // isEvent: true,
1087
+ // getter: (payload) => {
1088
+ // if (payload.action != 'hue_move') {
1089
+ // return undefined;
1090
+ // }
1091
+
1092
+ // if (payload.action_level) {
1093
+ // return payload.action_level;
1094
+ // } else {
1095
+ // return undefined;
1096
+ // }
1097
+ // }
1098
+ // }, expose.access);
1099
+ // }
1100
+ else {
1101
+ pushToStates(
1102
+ {
1103
+ id: actionName.replace(/\*/g, ''),
1104
+ prop: 'action',
1105
+ name: actionName,
1106
+ icon: undefined,
1107
+ role: 'button',
1108
+ write: false,
1109
+ read: true,
1110
+ type: 'boolean',
1111
+ def: false,
1112
+ isEvent: true,
1113
+ getter: (payload) => (payload.action === actionName ? true : undefined),
1114
+ },
1115
+ expose.access
1116
+ );
1117
+ }
929
1118
  }
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
- }
1119
+ // Can the device simulated_brightness?
1120
+ if (
1121
+ definition.options &&
1122
+ definition.options.find((x) => x.property == 'simulated_brightness')
1123
+ ) {
1124
+ pushToStates(statesDefs.simulated_brightness, z2mAccess.STATE);
1125
+ }
1126
+ state = null;
1127
+ break;
1128
+ }
1129
+ default:
1130
+ state = genState(expose);
1131
+ break;
1132
+ }
1133
+ if (state) pushToStates(state, expose.access);
1134
+ break;
1135
+
1136
+ case 'binary':
1137
+ if (expose.endpoint) {
1138
+ state = genState(expose);
1139
+ } else {
1140
+ switch (expose.name) {
1141
+ case 'contact':
1142
+ state = statesDefs.contact;
1143
+ pushToStates(statesDefs.opened, expose.access);
1144
+ break;
1145
+
1146
+ case 'battery_low':
1147
+ state = statesDefs.heiman_batt_low;
1148
+ break;
1149
+
1150
+ case 'tamper':
1151
+ state = statesDefs.tamper;
1152
+ break;
1153
+
1154
+ case 'water_leak':
1155
+ state = statesDefs.water_detected;
1156
+ break;
1157
+
1158
+ case 'lock':
1159
+ state = statesDefs.child_lock;
1160
+ break;
1161
+
1162
+ case 'occupancy':
1163
+ state = statesDefs.occupancy;
1164
+ break;
1165
+
1166
+ default:
1167
+ state = genState(expose);
1168
+ break;
1169
+ }
1170
+ }
1171
+ if (state) pushToStates(state, expose.access);
1172
+ break;
1173
+
1174
+ case 'text':
1175
+ state = genState(expose);
1176
+ pushToStates(state, expose.access);
1177
+ break;
1178
+
1179
+ case 'lock':
1180
+ case 'fan':
1181
+ case 'cover':
1182
+ for (const prop of expose.features) {
1183
+ switch (prop.name) {
1184
+ case 'state':
1185
+ pushToStates(genState(prop, 'switch'), prop.access);
1186
+ // features contains TOGGLE?
1187
+ if (prop.value_toggle) {
1188
+ pushToStates({
1189
+ id: `${prop.property}_toggle`,
1190
+ prop: `${prop.property}_toggle`,
1191
+ name: `Toggle state of the ${prop.property}`,
1192
+ icon: undefined,
1193
+ role: 'button',
1194
+ write: true,
1195
+ read: true,
1196
+ def: true,
1197
+ type: 'boolean',
1198
+ setattr: prop.property,
1199
+ setter: (value) => (value ? prop.value_toggle : undefined),
1200
+ });
1201
+ }
1202
+ break;
1203
+ default:
1204
+ pushToStates(genState(prop), prop.access);
1205
+ break;
1206
+ }
1207
+ }
1208
+ break;
1209
+
1210
+ case 'climate':
1211
+ for (const prop of expose.features) {
1212
+ switch (prop.name) {
1213
+ case 'away_mode':
1214
+ pushToStates(statesDefs.climate_away_mode, prop.access);
1215
+ break;
1216
+ case 'system_mode':
1217
+ pushToStates(statesDefs.climate_system_mode, prop.access);
1218
+ break;
1219
+ case 'running_mode':
1220
+ pushToStates(statesDefs.climate_running_mode, prop.access);
1221
+ break;
1222
+ case 'local_temperature':
1223
+ pushToStates(statesDefs.hvacThermostat_local_temp, prop.access);
1224
+ break;
1225
+ case 'local_temperature_calibration':
1226
+ pushToStates(statesDefs.hvacThermostat_local_temp_calibration, prop.access);
1227
+ break;
1228
+ default:
1229
+ {
1230
+ if (prop.name.includes('heating_setpoint')) {
1231
+ pushToStates(genState(prop, 'level.temperature'), prop.access);
1232
+ } else {
1233
+ pushToStates(genState(prop), prop.access);
1234
+ }
1235
+ }
1236
+ break;
1237
+ }
1238
+ }
1239
+ break;
1240
+
1241
+ case 'composite':
1242
+ for (const prop of expose.features) {
1243
+ // let state;
1244
+ // if (prop.type == 'list' && prop.features) {
1245
+ // const listName = prop.name;
1246
+
1247
+ // for (const feature of prop.features) {
1248
+ // genState(feature, 'state', `${listName}_${feature.name}`, prop.description);
1249
+ // }
1250
+ // }
1251
+
1252
+ const state = genState(prop);
1253
+ // Workaround for FP1 new state (region_upsert)
1254
+ if (!state) {
1255
+ break;
1256
+ }
1257
+ state.prop = expose.property;
1258
+ state.inOptions = true;
1259
+ // I'm not fully sure, as it really needed, but
1260
+ state.setterOpt = (value, options) => {
1261
+ const result = {};
1262
+ options[prop.property] = value;
1263
+ result[expose.property] = options;
1264
+ return result;
1265
+ };
1266
+ // if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
1267
+ if (prop.access & z2mAccess.SET) {
1268
+ state.setter = (value, options) => {
1269
+ const result = {};
1270
+ options[prop.property] = value;
1271
+ result[expose.property] = options;
1272
+ return result;
1273
+ };
1274
+ state.setattr = expose.property;
1275
+ }
1276
+ // if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
1277
+ if (prop.access & z2mAccess.STATE) {
1278
+ state.getter = (payload) => {
1279
+ if (
1280
+ payload.hasOwnProperty(expose.property) &&
1281
+ payload[expose.property] !== null &&
1282
+ payload[expose.property].hasOwnProperty(prop.property)
1283
+ ) {
1284
+ return !isNaN(payload[expose.property][prop.property])
1285
+ ? payload[expose.property][prop.property]
1286
+ : undefined;
1287
+ } else {
1288
+ return undefined;
1289
+ }
1290
+ };
1291
+ } else {
1292
+ state.getter = (_payload) => {
1293
+ return undefined;
1294
+ };
1295
+ }
1296
+ pushToStates(state, prop.access);
1297
+ }
1298
+ break;
1299
+ default:
1300
+ console.log(`Unhandled expose type ${expose.type} for device ${deviceID}`);
1301
+ }
1302
+ }
1145
1303
  } catch (err) {
1146
- console.log(`ERROR in expose ${expose.type} for device ${deviceID} : ${err}`);
1304
+ console.log(`ERROR in expose for device ${deviceID} : ${err}`);
1147
1305
  }
1148
1306
 
1149
1307
  // If necessary, add states defined for this device model.
@@ -1169,7 +1327,7 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
1169
1327
  read: true,
1170
1328
  def: true,
1171
1329
  type: 'boolean',
1172
- setter: (value) => (value) ? scene.id : undefined
1330
+ setter: (value) => (value ? scene.id : undefined),
1173
1331
  });
1174
1332
  }
1175
1333
 
@@ -1186,7 +1344,6 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
1186
1344
  return newDevice;
1187
1345
  }
1188
1346
 
1189
-
1190
1347
  module.exports = {
1191
1348
  createDeviceFromExposes: createDeviceFromExposes,
1192
1349
  };