iobroker.zigbee 1.10.14 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/exposes.js CHANGED
@@ -16,6 +16,7 @@ function genState(expose, role, name, desc) {
16
16
  const stateId = stname.replace(/\*/g, '');
17
17
  const stateName = (desc || expose.description || expose.name);
18
18
  const propName = expose.property;
19
+ // 'switch' | 'lock' | 'binary' | 'list' | 'numeric' | 'enum' | 'text' | 'composite' | 'light' | 'cover' | 'fan' | 'climate';
19
20
  switch (expose.type) {
20
21
  case 'binary':
21
22
  state = {
@@ -275,12 +276,24 @@ function createFromExposes(model, def) {
275
276
 
276
277
  return newDev;
277
278
 
278
-
279
+ function hasMultipleProperties(obj, prop, len) {
280
+ const l = (len ? len: Object.keys.length(obj));
281
+ if (l != prop.length) return false;
282
+ for (const key of prop) {
283
+ if (!obj.hasOwnProperty(key)) return false;
284
+ }
285
+ return true;
286
+ };
279
287
 
280
288
  function genStateFromExpose(expose) {
281
289
  let state;
282
290
  switch (expose.type) {
283
- case 'light':
291
+ case 'light': {
292
+ let hasColorXY = false;
293
+ let hasColorHS = false;
294
+ let colorXYprop = undefined;
295
+ let colorHSprop = undefined;
296
+
284
297
  for (const prop of expose.features) {
285
298
  switch (prop.name) {
286
299
  case 'state': {
@@ -364,237 +377,14 @@ function createFromExposes(model, def) {
364
377
  pushToStates(statesDefs.colortemp_move, prop.access);
365
378
  break;
366
379
  }
367
- case 'color_xy': {
368
- const stateNameC = expose.endpoint ? `color_${expose.endpoint}` : 'color';
369
- pushToStates({
370
- id: stateNameC,
371
- prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
372
- name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
373
- icon: undefined,
374
- role: 'level.color.rgb',
375
- write: true,
376
- read: true,
377
- type: 'string',
378
- setter: value => {
379
- // convert RGB to XY for set
380
- /*
381
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(value);
382
- let xy = [0, 0];
383
- if (result) {
384
- const r = parseInt(result[1], 16),
385
- g = parseInt(result[2], 16),
386
- b = parseInt(result[3], 16);
387
- xy = rgb.rgb_to_cie(r, g, b);
388
- }
389
- return {
390
- x: xy[0],
391
- y: xy[1]
392
- };
393
- */
394
- let xy = [0, 0];
395
- const rgbcolor = colors.ParseColor(value);
396
-
397
- xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
398
- return {
399
- x: xy[0],
400
- y: xy[1],
401
- };
402
- },
403
- setterOpt: (value, options) => {
404
- const hasTransitionTime = options && options.hasOwnProperty('transition_time');
405
- const transitionTime = hasTransitionTime ? options.transition_time : 0;
406
- return {...options, transition: transitionTime};
407
- },
408
- getter: payload => {
409
- if (payload.color && payload.color.hasOwnProperty('x') && payload.color.hasOwnProperty('y')) {
410
- const colorval = rgb.cie_to_rgb(payload.color.x, payload.color.y);
411
- return `#${utils.decimalToHex(colorval[0])}${utils.decimalToHex(colorval[1])}${utils.decimalToHex(colorval[2])}`;
412
- } else {
413
- return undefined;
414
- }
415
- },
416
- epname: expose.endpoint,
417
- setattr: 'color',
418
- }, prop.access);
419
- break;
420
- }
380
+ case 'color_xy':
381
+ colorXYprop = prop;
382
+ hasColorXY = true; break;
421
383
  case 'color_hs': {
422
- const stateNameH = expose.endpoint ? `color_${expose.endpoint}` : 'color';
423
- pushToStates({
424
- id: stateNameH,
425
- prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
426
- name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
427
- icon: undefined,
428
- role: 'level.color.rgb',
429
- write: true,
430
- read: true,
431
- type: 'string',
432
- setter: value => {
433
- const _rgb = colors.ParseColor(value);
434
- const hsv = rgb.rgbToHSV(_rgb.r, _rgb.g, _rgb.b, true);
435
- return {
436
- hue: Math.min(Math.max(hsv.h, 1), 359),
437
- saturation: hsv.s,
438
- // brightness: Math.floor(hsv.v * 2.55),
439
- };
440
- },
441
- setterOpt: (value, options) => {
442
- const hasTransitionTime = options && options.hasOwnProperty('transition_time');
443
- const transitionTime = hasTransitionTime ? options.transition_time : 0;
444
- return {...options, transition: transitionTime};
445
- },
446
- epname: expose.endpoint,
447
- setattr: 'color',
448
- }, prop.access);
449
- pushToStates({
450
- id: expose.endpoint ? `hue_${expose.endpoint}` : 'hue',
451
- prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
452
- name: `Hue ${expose.endpoint || ''}`.trim(),
453
- icon: undefined,
454
- role: 'level.color.hue',
455
- write: true,
456
- read: false,
457
- type: 'number',
458
- min: 0,
459
- max: 360,
460
- inOptions: true,
461
- setter: (value, options) => {
462
- return {
463
- hue: value,
464
- saturation: options.saturation,
465
- };
466
- },
467
- setterOpt: (value, options) => {
468
- const hasTransitionTime = options && options.hasOwnProperty('transition_time');
469
- const transitionTime = hasTransitionTime ? options.transition_time : 0;
470
- const hasHueCalibrationTable = options && options.hasOwnProperty('hue_calibration');
471
- if (hasHueCalibrationTable)
472
- try {
473
- return {
474
- ...options,
475
- transition: transitionTime,
476
- hue_correction: JSON.parse(options.hue_calibration)
477
- };
478
- } catch {
479
- const hue_correction_table = [];
480
- options.hue_calibration.split(',').forEach(element => {
481
- const match = /([0-9]+):([0-9]+)/.exec(element);
482
- if (match && match.length === 3)
483
- hue_correction_table.push({
484
- in: Number(match[1]),
485
- out: Number(match[2])
486
- });
487
- });
488
- if (hue_correction_table.length > 0) {
489
- return {
490
- ...options,
491
- transition: transitionTime,
492
- hue_correction: hue_correction_table
493
- };
494
- }
495
- }
496
- return {...options, transition: transitionTime};
497
- },
498
-
499
- }, prop.access);
500
- pushToStates({
501
- id: expose.endpoint ? `saturation_${expose.endpoint}` : 'saturation',
502
- prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
503
- name: `Saturation ${expose.endpoint ? expose.endpoint : ''}`.trim(),
504
- icon: undefined,
505
- role: 'level.color.saturation',
506
- write: true,
507
- read: false,
508
- type: 'number',
509
- min: 0,
510
- max: 100,
511
- inOptions: true,
512
- setter: (value, options) => ({
513
- hue: options.hue,
514
- saturation: value,
515
- }),
516
- setterOpt: (value, options) => {
517
- const hasTransitionTime = options && options.hasOwnProperty('transition_time');
518
- const transitionTime = hasTransitionTime ? options.transition_time : 0;
519
- const hasHueCalibrationTable = options && options.hasOwnProperty('hue_calibration');
520
- if (hasHueCalibrationTable)
521
- try {
522
- return {
523
- ...options,
524
- transition: transitionTime,
525
- hue_correction: JSON.parse(options.hue_calibration)
526
- };
527
- } catch {
528
- const hue_correction_table = [];
529
- options.hue_calibration.split(',').forEach(element => {
530
- const match = /([0-9]+):([0-9]+)/.exec(element);
531
- if (match && match.length === 3)
532
- hue_correction_table.push({
533
- in: Number(match[1]),
534
- out: Number(match[2])
535
- });
536
- });
537
- if (hue_correction_table.length > 0) {
538
- return {
539
- ...options,
540
- transition: transitionTime,
541
- hue_correction: hue_correction_table
542
- };
543
- }
544
- }
545
- return {...options, transition: transitionTime};
546
- },
547
-
548
- }, prop.access);
384
+ colorHSprop = prop;
385
+ hasColorHS = true;
549
386
  pushToStates(statesDefs.hue_move, prop.access);
550
387
  pushToStates(statesDefs.saturation_move, prop.access);
551
- pushToStates({
552
- id: 'hue_calibration',
553
- prop: 'color',
554
- name: 'Hue color calibration table',
555
- icon: undefined,
556
- role: 'table',
557
- write: true,
558
- read: false,
559
- type: 'string',
560
- inOptions: true,
561
- setter: (value, options) => ({
562
- hue: options.hue,
563
- saturation: options.saturation,
564
- }),
565
- setterOpt: (value, options) => {
566
- const hasTransitionTime = options && options.hasOwnProperty('transition_time');
567
- const transitionTime = hasTransitionTime ? options.transition_time : 0;
568
- const hasHueCalibrationTable = options && options.hasOwnProperty('hue_calibration');
569
- if (hasHueCalibrationTable)
570
- try {
571
- return {
572
- ...options,
573
- transition: transitionTime,
574
- hue_correction: JSON.parse(options.hue_calibration)
575
- };
576
- } catch {
577
- const hue_correction_table = [];
578
- options.hue_calibration.split(',').forEach(element => {
579
- const match = /([0-9]+):([0-9]+)/.exec(element);
580
- if (match && match.length === 3) {
581
- hue_correction_table.push({
582
- in: Number(match[1]),
583
- out: Number(match[2])
584
- });
585
- }
586
- });
587
- if (hue_correction_table.length > 0) {
588
- return {
589
- ...options,
590
- transition: transitionTime,
591
- hue_correction: hue_correction_table
592
- };
593
- }
594
- }
595
- return {...options, transition: transitionTime};
596
- },
597
- }, prop.access);
598
388
  break;
599
389
  }
600
390
  default:
@@ -602,9 +392,181 @@ function createFromExposes(model, def) {
602
392
  break;
603
393
  }
604
394
  }
395
+ if (hasColorXY || hasColorHS) {
396
+ const nameWithEp = expose.endpoint ? `color_${expose.endpoint}` : 'color';
397
+ pushToStates({
398
+ id: nameWithEp,
399
+ name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
400
+ icon: undefined,
401
+ role: 'level.color.rgb',
402
+ write: true,
403
+ read: true,
404
+ type: 'string',
405
+ setter: value => {
406
+ try {
407
+ // JSON
408
+ const colorJSON = JSON.parse(value.replaceAll("'",'"'));
409
+ const numProp = Object.keys(colorJSON).length;
410
+ if (hasMultipleProperties(colorJSON, ['hsb'], numProp)) return colorJSON;
411
+ if (hasMultipleProperties(colorJSON, ['hsl'], numProp)) return colorJSON;
412
+ if (hasMultipleProperties(colorJSON, ['hsv'], numProp)) return colorJSON;
413
+ if (hasMultipleProperties(colorJSON, ['h','s','b'], numProp)) return colorJSON;
414
+ if (hasMultipleProperties(colorJSON, ['h','s','v'], numProp)) return colorJSON;
415
+ if (hasMultipleProperties(colorJSON, ['h','s','l'], numProp)) return colorJSON;
416
+ if (hasMultipleProperties(colorJSON, ['hue', 'saturation'], numProp)) return colorJSON;
417
+ if (hasMultipleProperties(colorJSON, ['hex'], numProp)) return colorJSON;
418
+ if (hasMultipleProperties(colorJSON, ['rgb'], numProp)) return colorJSON;
419
+ if (hasMultipleProperties(colorJSON, ['x', 'y'], numProp)) return colorJSON;
420
+ if (hasMultipleProperties(colorJSON, ['r', 'g', 'b'], numProp)) return colorJSON;
421
+ //return { json:colorJSON, numProp:numProp, value:value };
422
+ }
423
+ catch (error) {
424
+ //return { error: error.message };
425
+ };
426
+ // hex or named color
427
+ const rgbcolor = colors.ParseColor(value);
428
+ return rgbcolor;
429
+ },
430
+ setterOpt: (value, options) => {
431
+ const hasTransitionTime = options && options.hasOwnProperty('transition_time');
432
+ const transitionTime = hasTransitionTime ? options.transition_time : 0;
433
+ return {...options, transition: transitionTime};
434
+ },
435
+ getter: payload => {
436
+ if (typeof payload.color == 'object') {
437
+ const colorJSON = payload.color;
438
+ const color = JSON.stringify(colorJSON)
439
+ const numProp = Object.keys(colorJSON);
440
+ if (hasMultipleProperties(colorJSON, ['hsb'], numProp)) return color;
441
+ if (hasMultipleProperties(colorJSON, ['hsl'], numProp)) return color;
442
+ if (hasMultipleProperties(colorJSON, ['hsv'], numProp)) return color;
443
+ if (hasMultipleProperties(colorJSON, ['h','s','b'], numProp)) return color;
444
+ if (hasMultipleProperties(colorJSON, ['h','s','v'], numProp)) return color;
445
+ if (hasMultipleProperties(colorJSON, ['h','s','l'], numProp)) return color;
446
+ if (hasMultipleProperties(colorJSON, ['hue', 'saturation'], numProp)) return color;
447
+ if (hasMultipleProperties(colorJSON, ['hex'], numProp)) return color;
448
+ if (hasMultipleProperties(colorJSON, ['rgb'], numProp)) return color;
449
+ if (hasMultipleProperties(colorJSON, ['x', 'y'], numProp)) return color;
450
+ if (hasMultipleProperties(colorJSON, ['r', 'g', 'b'], numProp)) return color;
451
+ }
452
+ return undefined;
453
+ },
454
+ epname: expose.endpoint,
455
+ setattr: 'color',
456
+ }, (colorHSprop ? colorHSprop.access : colorXYprop.access));
457
+ if (hasColorXY) {
458
+ let channelWithEp = expose.endpoint ? `color_xy_${expose.endpoint}` : 'color_xy';
459
+ pushToStates({
460
+ id: `${channelWithEp}.x`,
461
+ name: `X`,
462
+ icon: undefined,
463
+ role: 'level.color',
464
+ write: true,
465
+ read: true,
466
+ type: 'number',
467
+ min: 0,
468
+ max: 1,
469
+ compositeKey: channelWithEp,
470
+ compositeTimeout: 500,
471
+ compositeState: 'color'
472
+ }, colorXYprop.access);
473
+ pushToStates({
474
+ id: `${channelWithEp}.y`,
475
+ name: `Y`,
476
+ icon: undefined,
477
+ role: 'level.color',
478
+ write: true,
479
+ read: true,
480
+ type: 'number',
481
+ min: 0,
482
+ max: 1,
483
+ compositeKey: channelWithEp,
484
+ compositeTimeout: 500,
485
+ compositeState: 'color'
486
+ }, colorXYprop.access);
487
+ channelWithEp = expose.endpoint ? `color_rgb_${expose.endpoint}` : 'color_rgb';
488
+ pushToStates({
489
+ id: `${channelWithEp}.r`,
490
+ name: `Red`,
491
+ icon: undefined,
492
+ role: 'level.color.red',
493
+ write: true,
494
+ read: true,
495
+ type: 'number',
496
+ min: 0,
497
+ max: 255,
498
+ compositeKey: channelWithEp,
499
+ compositeTimeout: 500,
500
+ compositeState: 'color',
501
+ composites: [`${channelWithEp}.r`,`${channelWithEp}.g`]
502
+ }, colorXYprop.access);
503
+ pushToStates({
504
+ id: `${channelWithEp}.g`,
505
+ name: `Green`,
506
+ icon: undefined,
507
+ role: 'level.color.green',
508
+ write: true,
509
+ read: true,
510
+ type: 'number',
511
+ min: 0,
512
+ max: 255,
513
+ compositeKey: channelWithEp,
514
+ compositeTimeout: 500,
515
+ compositeState: 'color',
516
+ composites: [`${channelWithEp}.x`,`${channelWithEp}.y`]
517
+ });
518
+ pushToStates({
519
+ id: `${channelWithEp}.b`,
520
+ name: `Blue`,
521
+ icon: undefined,
522
+ role: 'level.color.blue',
523
+ write: true,
524
+ read: true,
525
+ type: 'number',
526
+ min: 0,
527
+ max: 255,
528
+ compositeKey: channelWithEp,
529
+ compositeTimeout: 500,
530
+ compositeState: 'color'
531
+ }, colorXYprop.access);
532
+ }
533
+ if (hasColorHS) {
534
+ const channelWithEp = expose.endpoint ? `color_hs_${expose.endpoint}` : 'color_hs';
535
+ pushToStates({
536
+ id: `${channelWithEp}.hue`,
537
+ name: `Hue`,
538
+ icon: undefined,
539
+ role: 'level.color.hue',
540
+ write: true,
541
+ read: true,
542
+ type: 'number',
543
+ min: 0,
544
+ max: 360,
545
+ compositeKey: channelWithEp,
546
+ compositeTimeout: 500,
547
+ compositeState: 'color'
548
+ }, colorHSprop.access);
549
+ pushToStates({
550
+ id: `${channelWithEp}.saturation`,
551
+ name: `Saturation`,
552
+ icon: undefined,
553
+ role: 'level.color',
554
+ write: true,
555
+ read: true,
556
+ type: 'number',
557
+ min: 0,
558
+ max: 100,
559
+ compositeKey: channelWithEp,
560
+ compositeTimeout: 500,
561
+ compositeState: 'color'
562
+ }, colorHSprop.access);
563
+
564
+ }
565
+ }
566
+
605
567
  pushToStates(statesDefs.transition_time, ea.STATE_SET);
606
568
  break;
607
-
569
+ }
608
570
  case 'switch':
609
571
  for (const prop of expose.features) {
610
572
  switch (prop.name) {
@@ -671,6 +633,7 @@ function createFromExposes(model, def) {
671
633
 
672
634
  case 'enum':
673
635
  switch (expose.name) {
636
+
674
637
  case 'action': {
675
638
  // Ansatz:
676
639
 
@@ -681,12 +644,15 @@ function createFromExposes(model, def) {
681
644
  if (!Array.isArray(expose.values)) break;
682
645
  const hasHold = expose.values.find((actionName) => actionName.includes('hold'));
683
646
  const hasRelease = expose.values.find((actionName) => actionName.includes('release'));
647
+ const hasPress = expose.values.find((actionName) => actionName.includes('press'));
648
+ const hasPressRelease = expose.values.find((actionName) => actionName.includes('press_release'));
684
649
  for (const actionName of expose.values) {
685
650
  // is release state ? - skip
686
651
  if (hasHold && hasRelease && actionName.includes('release')) continue;
687
652
  // is hold state ?
688
653
  if (hasHold && hasRelease && actionName.includes('hold')) {
689
654
  const releaseActionName = actionName.replace('hold', 'release');
655
+ const releaseActionName2 = actionName.concat('_release');
690
656
  state = {
691
657
  id: actionName.replace(/\*/g, ''),
692
658
  prop: 'action',
@@ -696,9 +662,11 @@ function createFromExposes(model, def) {
696
662
  write: false,
697
663
  read: true,
698
664
  type: 'boolean',
699
- getter: payload => payload.action === actionName ? true : (payload.action === releaseActionName ? false : undefined),
665
+ getter: payload => payload.action === actionName ? true : (payload.action === releaseActionName || payload.action === releaseActionName2 ? false : undefined),
700
666
  };
701
- } else {
667
+ } else if (hasPress && hasPressRelease && actionName.includes('press')) {
668
+ let getterKey = actionName.concat('_release');
669
+ if (expose.values.indexOf(getterKey) < 0) getterKey = actionName;
702
670
  state = {
703
671
  id: actionName.replace(/\*/g, ''),
704
672
  prop: 'action',
@@ -708,10 +676,23 @@ function createFromExposes(model, def) {
708
676
  write: false,
709
677
  read: true,
710
678
  type: 'boolean',
679
+ getter: payload => payload.action === getterKey ? true : undefined,
680
+ isEvent: true,
681
+ };
682
+ } else {
683
+ state = {
684
+ id: actionName.replace(/\*/g, ''),
685
+ prop: 'action',
686
+ name: actionName,
687
+ icon: undefined,
688
+ role: 'button',
689
+ write: false,
690
+ read: true,
691
+ type: 'boolean',
711
692
  getter: payload => payload.action === actionName ? true : undefined,
712
693
  isEvent: true,
713
694
  };
714
- }
695
+ };
715
696
  pushToStates(state, expose.access);
716
697
  }
717
698
  state = null;
@@ -893,16 +874,14 @@ function createFromExposes(model, def) {
893
874
  }
894
875
 
895
876
  }
896
- function applyExposes(mappedDevices, byModel, allExcludesObj) {
897
- // for exclude search
898
- const allExcludesStr = JSON.stringify(allExcludesObj);
899
- // create or update device from exposes
877
+ function applyExposes(mappedDevices, byModel) {
878
+ // create or device from exposes
900
879
  for (const deviceDef of zigbeeHerdsmanConverters.definitions) {
901
- applyDeviceDef(mappedDevices, byModel, allExcludesStr, deviceDef);
880
+ applyDeviceDef(mappedDevices, byModel, deviceDef);
902
881
 
903
882
  if (deviceDef.hasOwnProperty('whiteLabel')) {
904
883
  for (const deviceWhiteLabel of deviceDef.whiteLabel) {
905
- applyDeviceDef(mappedDevices, byModel, allExcludesStr, {
884
+ applyDeviceDef(mappedDevices, byModel, {
906
885
  ...deviceDef,
907
886
  model: deviceWhiteLabel.model,
908
887
  vendor: deviceWhiteLabel.vendor,
@@ -913,20 +892,16 @@ function applyExposes(mappedDevices, byModel, allExcludesObj) {
913
892
  }
914
893
  }
915
894
 
916
- function applyDeviceDef(mappedDevices, byModel, allExcludesStr, deviceDef) {
895
+ function applyDeviceDef(mappedDevices, byModel, deviceDef) {
917
896
  const stripModel = utils.getModelRegEx(deviceDef.model);
918
897
  const existsMap = byModel.get(stripModel);
919
898
 
920
- if ((deviceDef.hasOwnProperty('exposes') && (!existsMap || !existsMap.hasOwnProperty('states'))) || allExcludesStr.indexOf(stripModel) > 0) {
899
+ if (deviceDef.hasOwnProperty('exposes') && (!existsMap || !existsMap.hasOwnProperty('states'))) {
921
900
  try {
922
901
  const newDevice = createFromExposes(stripModel, deviceDef);
923
- if (!existsMap) {
924
- mappedDevices.push(newDevice);
925
- byModel.set(stripModel, newDevice);
926
- } else {
927
- existsMap.states = newDevice.states;
928
- existsMap.exposed = true;
929
- }
902
+ mappedDevices.push(newDevice);
903
+ byModel.set(stripModel, newDevice);
904
+
930
905
  } catch (e) {
931
906
  console.log(`Wrong expose device definition ${deviceDef.vendor} ${stripModel}`);
932
907
  }