zigbee-herdsman-converters 14.0.513 → 14.0.516

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.
@@ -532,6 +532,24 @@ const converters = {
532
532
  }
533
533
  }
534
534
 
535
+ // onLevel - range 0x00 to 0xff - optional
536
+ // Any value outside of MinLevel to MaxLevel, including 0xff and 0x00, is interpreted as "previous".
537
+ if (msg.data.hasOwnProperty('onLevel') && (msg.data['onLevel'] !== undefined)) {
538
+ result.level_config.on_level = Number(msg.data['onLevel']);
539
+ if (result.level_config.on_level === 255) {
540
+ result.level_config.on_level = 'previous';
541
+ }
542
+ }
543
+
544
+ // options - 8-bit map
545
+ // bit 0: ExecuteIfOff - when 0, Move commands are ignored if the device is off;
546
+ // when 1, CurrentLevel can be changed while the device is off.
547
+ // bit 1: CoupleColorTempToLevel - when 1, changes to level also change color temperature.
548
+ // (What this means is not defined, but it's most likely to be "dim to warm".)
549
+ if (msg.data.hasOwnProperty('options') && msg.data['options'] !== undefined) {
550
+ result.level_config.execute_if_off = !!(Number(msg.data['options']) & 1);
551
+ }
552
+
535
553
  if (Object.keys(result.level_config).length > 0) {
536
554
  return result;
537
555
  }
@@ -4341,7 +4359,8 @@ const converters = {
4341
4359
  // DP2: Smart Air Box: co2, Smart Air Housekeeper: MP25
4342
4360
  case tuya.dataPoints.tuyaSabCO2:
4343
4361
  if (meta.device.manufacturerName === '_TZE200_dwcarsat') {
4344
- if (value === 0xaaab) return; // Ignore: https://github.com/Koenkk/zigbee2mqtt/issues/11033#issuecomment-1109808552
4362
+ // Ignore: https://github.com/Koenkk/zigbee2mqtt/issues/11033#issuecomment-1109808552
4363
+ if (value === 0xaaac || value === 0xaaab) return;
4345
4364
  return {pm25: calibrateAndPrecisionRoundOptions(value, options, 'pm25')};
4346
4365
  } else if (meta.device.manufacturerName === '_TZE200_ryfmq5rl') {
4347
4366
  return {formaldehyd: calibrateAndPrecisionRoundOptions(value, options, 'formaldehyd') / 100};
@@ -5909,6 +5928,29 @@ const converters = {
5909
5928
  return result;
5910
5929
  },
5911
5930
  },
5931
+ xiaomi_curtain_hagl04_status: {
5932
+ cluster: 'genMultistateOutput',
5933
+ type: ['attributeReport'],
5934
+ convert: (model, msg, publish, options, meta) => {
5935
+ let running = false;
5936
+ const data = msg.data;
5937
+ const lookup = {
5938
+ 0: 'closing',
5939
+ 1: 'opening',
5940
+ 2: 'stop',
5941
+ };
5942
+ if (data && data.hasOwnProperty('presentValue')) {
5943
+ const value = data['presentValue'];
5944
+ if (value < 2) {
5945
+ running = true;
5946
+ }
5947
+ return {
5948
+ motor_state: lookup[value],
5949
+ running: running,
5950
+ };
5951
+ }
5952
+ },
5953
+ },
5912
5954
  xiaomi_curtain_acn002_status: {
5913
5955
  cluster: 'genMultistateOutput',
5914
5956
  type: ['attributeReport'],
@@ -8528,6 +8570,41 @@ const converters = {
8528
8570
  return result;
8529
8571
  },
8530
8572
  },
8573
+ moes_cover: {
8574
+ cluster: 'manuSpecificTuya',
8575
+ type: ['commandDataResponse', 'commandDataReport'],
8576
+ options: [exposes.options.invert_cover()],
8577
+ convert: (model, msg, publish, options, meta) => {
8578
+ const dpValue = tuya.firstDpValue(msg, meta, 'moes_cover');
8579
+ const dp = dpValue.dp;
8580
+ const value = tuya.getDataValue(dpValue);
8581
+ let result = null;
8582
+ switch (dp) {
8583
+ case tuya.dataPoints.coverPosition: {
8584
+ const invert = !tuya.isCoverInverted(meta.device.manufacturerName) ?
8585
+ !options.invert_cover : options.invert_cover;
8586
+ const position = invert ? 100 - value : value;
8587
+ result = {position: position};
8588
+ break;
8589
+ }
8590
+ case tuya.dataPoints.state:
8591
+ result = {state: {0: 'OPEN', 1: 'STOP', 2: 'CLOSE'}[value], running: {0: true, 1: false, 2: true}[value]};
8592
+ break;
8593
+ case tuya.dataPoints.moesCoverBacklight:
8594
+ result = {backlight: {false: 'OFF', true: 'ON'}[value]};
8595
+ break;
8596
+ case tuya.dataPoints.moesCoverCalibration:
8597
+ result = {calibration: {0: 'ON', 1: 'OFF'}[value]};
8598
+ break;
8599
+ case tuya.dataPoints.moesCoverMotorReversal:
8600
+ result = {motor_reversal: {0: 'OFF', 1: 'ON'}[value]};
8601
+ break;
8602
+ default:
8603
+ meta.logger.warn(`fromZigbee.moes_cover: NOT RECOGNIZED DP ${dp} with data ${JSON.stringify(dpValue)}`);
8604
+ }
8605
+ return result;
8606
+ },
8607
+ },
8531
8608
  // #endregion
8532
8609
 
8533
8610
  // #region Ignore converters (these message dont need parsing).
@@ -535,12 +535,41 @@ const converters = {
535
535
  Object.assign(state, {current_level_startup: startUpCurrentLevelValue});
536
536
  }
537
537
 
538
+ // onLevel - range 0x00 to 0xff - optional
539
+ // Any value outside of MinLevel to MaxLevel, including 0xff and 0x00, is interpreted as "previous".
540
+ if (value.hasOwnProperty('on_level')) {
541
+ let onLevel = value.on_level;
542
+ if (typeof onLevel === 'string' && onLevel.toLowerCase() == 'previous') {
543
+ onLevel = 255;
544
+ } else {
545
+ onLevel = Number(onLevel);
546
+ }
547
+ if (onLevel > 255) onLevel = 254;
548
+ if (onLevel < 1) onLevel = 1;
549
+ await entity.write('genLevelCtrl', {onLevel}, utils.getOptions(meta.mapped, entity));
550
+ Object.assign(state, {on_level: onLevel == 255 ? 'previous' : onLevel});
551
+ }
552
+
553
+ // options - 8-bit map
554
+ // bit 0: ExecuteIfOff - when 0, Move commands are ignored if the device is off;
555
+ // when 1, CurrentLevel can be changed while the device is off.
556
+ // bit 1: CoupleColorTempToLevel - when 1, changes to level also change color temperature.
557
+ // (What this means is not defined, but it's most likely to be "dim to warm".)
558
+ if (value.hasOwnProperty('execute_if_off')) {
559
+ const executeIfOffValue = !!value.execute_if_off;
560
+ await entity.write('genLevelCtrl', {options: executeIfOffValue ? 1 : 0}, utils.getOptions(meta.mapped, entity));
561
+ Object.assign(state, {execute_if_off: executeIfOffValue});
562
+ }
563
+
538
564
  if (Object.keys(state).length > 0) {
539
565
  return {state: {level_config: state}};
540
566
  }
541
567
  },
542
568
  convertGet: async (entity, key, meta) => {
543
- for (const attribute of ['onOffTransitionTime', 'onTransitionTime', 'offTransitionTime', 'startUpCurrentLevel']) {
569
+ for (const attribute of [
570
+ 'onOffTransitionTime', 'onTransitionTime', 'offTransitionTime', 'startUpCurrentLevel',
571
+ 'onLevel', 'options',
572
+ ]) {
544
573
  try {
545
574
  await entity.read('genLevelCtrl', [attribute]);
546
575
  } catch (ex) {
@@ -7463,6 +7492,43 @@ const converters = {
7463
7492
  }
7464
7493
  },
7465
7494
  },
7495
+ moes_cover: {
7496
+ key: ['backlight', 'calibration', 'motor_reversal', 'state', 'position'],
7497
+ options: [exposes.options.invert_cover()],
7498
+ convertSet: async (entity, key, value, meta) => {
7499
+ switch (key) {
7500
+ case 'position':
7501
+ if (value >= 0 && value <= 100) {
7502
+ const invert = !tuya.isCoverInverted(meta.device.manufacturerName) ?
7503
+ !meta.options.invert_cover : meta.options.invert_cover;
7504
+ const position = invert ? 100 - value : value;
7505
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.coverPosition, position);
7506
+ return {position: value};
7507
+ }
7508
+ break;
7509
+ case 'state': {
7510
+ const state = {'OPEN': 0, 'STOP': 1, 'CLOSE': 2}[value.toUpperCase()];
7511
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.state, state);
7512
+ break;
7513
+ }
7514
+ case 'backlight': {
7515
+ const backlight = value.toUpperCase() === 'ON' ? true : false;
7516
+ await tuya.sendDataPointBool(entity, tuya.dataPoints.moesCoverBacklight, backlight);
7517
+ return {backlight: value};
7518
+ }
7519
+ case 'calibration': {
7520
+ const calibration = value.toUpperCase() === 'ON' ? 0 : 1;
7521
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.moesCoverCalibration, calibration);
7522
+ break;
7523
+ }
7524
+ case 'motor_reversal': {
7525
+ const motorReversal = value.toUpperCase() === 'ON' ? 1 : 0;
7526
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.moesCoverMotorReversal, motorReversal);
7527
+ return {motor_reversal: value};
7528
+ }
7529
+ }
7530
+ },
7531
+ },
7466
7532
  // #endregion
7467
7533
 
7468
7534
  // #region Ignore converters
@@ -22,5 +22,6 @@ module.exports = [
22
22
  exposes: [e.lock(), e.battery(), e.sound_volume(),
23
23
  e.action(['zigbee_unlock', 'lock', 'rfid_unlock', 'keypad_unlock']),
24
24
  exposes.binary('auto_relock', ea.STATE_SET, true, false).withDescription('Auto relock after 7 seconds.')],
25
+ whiteLabel: [{vendor: 'Datek Wireless', model: 'EasyCode903G2.1'}],
25
26
  },
26
27
  ];
package/devices/ecodim.js CHANGED
@@ -126,4 +126,11 @@ module.exports = [
126
126
  description: 'Zigbee LED filament light dimmable E27, globe G125, flame 2200K',
127
127
  extend: extend.light_onoff_brightness(),
128
128
  },
129
+ {
130
+ fingerprint: [{modelID: 'CCT Light', manufacturerName: 'ZigBee/CCT', manufacturerID: 4137}],
131
+ model: 'ED-10041',
132
+ vendor: 'EcoDim',
133
+ description: 'Zigbee LED filament light dimmable E27, edison ST64, flame 2200K',
134
+ extend: extend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
135
+ },
129
136
  ];
package/devices/ikea.js CHANGED
@@ -36,9 +36,12 @@ const bulbOnEvent = async (type, data, device, options, state) => {
36
36
 
37
37
  // NOTE: execute_if_off default is false
38
38
  // we only restore if true, to save unneeded network writes
39
- if (state.color_options.execute_if_off === true) {
39
+ if (state.color_options !== undefined && state.color_options.execute_if_off === true) {
40
40
  device.endpoints[0].write('lightingColorCtrl', {'options': 1});
41
41
  }
42
+ if (state.level_config !== undefined && state.level_config.execute_if_off === true) {
43
+ device.endpoints[0].write('genLevelCtrl', {'options': 1});
44
+ }
42
45
  }
43
46
  };
44
47