zigbee-herdsman-converters 14.0.313 → 14.0.317

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.
@@ -648,6 +648,26 @@ const converters = {
648
648
  return payload;
649
649
  },
650
650
  },
651
+ develco_metering: {
652
+ cluster: 'seMetering',
653
+ type: ['attributeReport', 'readResponse'],
654
+ convert: (model, msg, publish, options, meta) => {
655
+ const result = {};
656
+ if (msg.data.hasOwnProperty('develcoPulseConfiguration')) {
657
+ result[postfixWithEndpointName('pulse_configuration', msg, model)] =
658
+ msg.data['develcoPulseConfiguration'];
659
+ }
660
+
661
+ if (msg.data.hasOwnProperty('develcoInterfaceMode')) {
662
+ result[postfixWithEndpointName('interface_mode', msg, model)] =
663
+ constants.develcoInterfaceMode.hasOwnProperty(msg.data['develcoInterfaceMode']) ?
664
+ constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] :
665
+ msg.data['develcoInterfaceMode'];
666
+ }
667
+
668
+ return result;
669
+ },
670
+ },
651
671
  electrical_measurement: {
652
672
  /**
653
673
  * When using this converter also add the following to the configure method of the device:
@@ -2485,6 +2505,22 @@ const converters = {
2485
2505
  }
2486
2506
  },
2487
2507
  },
2508
+ livolo_curtain_switch_state: {
2509
+ cluster: 'genPowerCfg',
2510
+ type: ['raw'],
2511
+ convert: (model, msg, publish, options, meta) => {
2512
+ const stateHeader = Buffer.from([122, 209]);
2513
+ if (msg.data.indexOf(stateHeader) === 0) {
2514
+ if (msg.data[10] === 5) {
2515
+ const status = msg.data[14];
2516
+ return {
2517
+ state_left: status === 1 ? 'ON' : 'OFF',
2518
+ state_right: status === 0 ? 'ON' : 'OFF',
2519
+ };
2520
+ }
2521
+ }
2522
+ },
2523
+ },
2488
2524
  livolo_dimmer_state: {
2489
2525
  cluster: 'genPowerCfg',
2490
2526
  type: ['raw'],
@@ -2592,16 +2628,24 @@ const converters = {
2592
2628
  [124,210,21,216,128, 199,147,3,24,0,75,18,0, 19,7,0] after interview
2593
2629
  [122,209, 199,147,3,24,0,75,18,0, 7,1,6,1,0,11] off
2594
2630
  [122,209, 199,147,3,24,0,75,18,0, 7,1,6,1,1,11] on
2631
+
2595
2632
  new switch
2596
2633
  [124,210,21,216,128, 228,41,3,24,0,75,18,0, 19,1,0] after interview
2597
2634
  [122,209, 228,41,3,24,0,75,18,0, 7,1,0,1,0,11] off
2598
2635
  [122,209, 228,41,3,24,0,75,18,0, 7,1,0,1,1,11] on
2636
+
2599
2637
  old switch
2600
2638
  [124,210,21,216,128, 170, 10,2,24,0,75,18,0, 17,0,1] after interview
2601
2639
  [124,210,21,216,0, 18, 15,5,24,0,75,18,0, 34,0,0] left: 0, right: 0
2602
2640
  [124,210,21,216,0, 18, 15,5,24,0,75,18,0, 34,0,1] left: 1, right: 0
2603
2641
  [124,210,21,216,0, 18, 15,5,24,0,75,18,0, 34,0,2] left: 0, right: 1
2604
2642
  [124,210,21,216,0, 18, 15,5,24,0,75,18,0, 34,0,3] left: 1, right: 1
2643
+
2644
+ curtain switch
2645
+ [124,210,21,216,128, 110,74,116,33,0,75,18,0, 19,5,0] after interview
2646
+ [122,209, 110,74,116,33,0,75,18,0, 5,1,5,0,2,11] left: 0, right: 0 (off)
2647
+ [122,209, 110,74,116,33,0,75,18,0, 5,1,5,0,1,11] left: 1, right: 0 (left on)
2648
+ [122,209, 110,74,116,33,0,75,18,0, 5,1,5,0,0,11] left: 0, right: 1 (right on)
2605
2649
  */
2606
2650
  const malformedHeader = Buffer.from([0x7c, 0xd2, 0x15, 0xd8, 0x00]);
2607
2651
  const infoHeader = Buffer.from([0x7c, 0xd2, 0x15, 0xd8, 0x80]);
@@ -2630,6 +2674,12 @@ const converters = {
2630
2674
  meta.device.modelID = 'TI0001-switch-2gang';
2631
2675
  meta.device.save();
2632
2676
  }
2677
+ if (msg.data.includes(Buffer.from([19, 5, 0]), 13)) {
2678
+ if (meta.logger) meta.logger.debug('Detected Livolo Curtain Switch');
2679
+ // curtain switch, hack
2680
+ meta.device.modelID = 'TI0001-curtain-switch';
2681
+ meta.device.save();
2682
+ }
2633
2683
  if (msg.data.includes(Buffer.from([19, 20, 0]), 13)) {
2634
2684
  // new dimmer, hack
2635
2685
  meta.device.modelID = 'TI0001-dimmer';
@@ -3705,7 +3755,7 @@ const converters = {
3705
3755
  case tuya.dataPoints.haozeeLocalTemp:
3706
3756
  return {local_temperature: (value / 10).toFixed(1)};
3707
3757
  case tuya.dataPoints.haozeeBoostHeatingCountdown:
3708
- // quick heating countdown
3758
+ // quick heating countdown - not supported by this device
3709
3759
  return {boost_heating_countdown: value};
3710
3760
  case tuya.dataPoints.haozeeWindowDetection:
3711
3761
  // window check
@@ -3782,11 +3832,11 @@ const converters = {
3782
3832
  // working status 0 - pause 1 -working
3783
3833
  return {'heating': value ? 'ON' : 'OFF'};
3784
3834
  case tuya.dataPoints.haozeeBoostHeating:
3785
- // rapid heating -> boolean
3786
- break;
3835
+ // rapid heating -> boolean - not supported by this device
3836
+ return {'boost_heating': value ? 'ON' : 'OFF'};
3787
3837
  case tuya.dataPoints.haozeeTempCalibration:
3788
3838
  // temperature calibration
3789
- break;
3839
+ return {'local_temperature_calibration': ( value/10 ).toFixed(1)};
3790
3840
  case tuya.dataPoints.haozeeValvePosition:
3791
3841
  // valve position
3792
3842
  return {'position': value};
@@ -4020,32 +4070,33 @@ const converters = {
4020
4070
  minutes: value[2],
4021
4071
  },
4022
4072
  };
4023
- case tuya.dataPoints.scheduleWorkday: // set schedule for workdays [6,0,20,8,0,15,11,30,15,12,30,15,17,30,20,22,0,15]
4024
- // 6:00 - 20*, 8:00 - 15*, 11:30 - 15*, 12:30 - 15*, 17:30 - 20*, 22:00 - 15*
4025
- // Top bits in hours have special meaning
4026
- // 8: ??
4027
- // 7: Current schedule indicator
4028
- return {workdays: [
4029
- {hour: value[0] & 0x3F, minute: value[1], temperature: value[2]},
4030
- {hour: value[3] & 0x3F, minute: value[4], temperature: value[5]},
4031
- {hour: value[6] & 0x3F, minute: value[7], temperature: value[8]},
4032
- {hour: value[9] & 0x3F, minute: value[10], temperature: value[11]},
4033
- {hour: value[12] & 0x3F, minute: value[13], temperature: value[14]},
4034
- {hour: value[15] & 0x3F, minute: value[16], temperature: value[17]},
4035
- ]};
4036
- case tuya.dataPoints.scheduleHoliday: // set schedule for holidays [6,0,20,8,0,15,11,30,15,12,30,15,17,30,20,22,0,15]
4073
+ case tuya.dataPoints.scheduleWorkday: // set schedule for workdays/holidays [6,0,20,8,0,15,11,30,15,12,30,15,17,30,20,22,0,15]
4074
+ case tuya.dataPoints.scheduleHoliday: {
4037
4075
  // 6:00 - 20*, 8:00 - 15*, 11:30 - 15*, 12:30 - 15*, 17:30 - 20*, 22:00 - 15*
4038
4076
  // Top bits in hours have special meaning
4039
- // 8: ??
4040
- // 7: Current schedule indicator
4041
- return {holidays: [
4042
- {hour: value[0] & 0x3F, minute: value[1], temperature: value[2]},
4043
- {hour: value[3] & 0x3F, minute: value[4], temperature: value[5]},
4044
- {hour: value[6] & 0x3F, minute: value[7], temperature: value[8]},
4045
- {hour: value[9] & 0x3F, minute: value[10], temperature: value[11]},
4046
- {hour: value[12] & 0x3F, minute: value[13], temperature: value[14]},
4047
- {hour: value[15] & 0x3F, minute: value[16], temperature: value[17]},
4048
- ]};
4077
+ // 6: Current schedule indicator
4078
+ const items = [];
4079
+ const programmingMode = [];
4080
+
4081
+ for (let i = 0; i < 6; i++) {
4082
+ const item = {hour: value[i*3] & 0x3F, minute: value[i*3+1], temperature: value[i*3+2]};
4083
+ if (value[i*3] & 0x40) {
4084
+ item['current'] = true;
4085
+ }
4086
+
4087
+ items[i] = item;
4088
+ programmingMode[i] =
4089
+ item['hour'].toString().padStart(2, '0') + ':' +
4090
+ item['minute'].toString().padStart(2, '0') + '/' +
4091
+ item['temperature'] + '°C';
4092
+ }
4093
+
4094
+ if (dp == tuya.dataPoints.scheduleWorkday) {
4095
+ return {workdays: items, workdays_schedule: programmingMode.join(' ')};
4096
+ } else {
4097
+ return {holidays: items, holidays_schedule: programmingMode.join(' ')};
4098
+ }
4099
+ }
4049
4100
  case tuya.dataPoints.childLock:
4050
4101
  return {child_lock: value ? 'LOCK' : 'UNLOCK'};
4051
4102
  case tuya.dataPoints.siterwellWindowDetection:
@@ -4244,6 +4295,33 @@ const converters = {
4244
4295
  }
4245
4296
  },
4246
4297
  },
4298
+ ikea_pm25: {
4299
+ cluster: 'manuSpecificIkeaPM25Measurement',
4300
+ type: ['attributeReport', 'readResponse'],
4301
+ options: [exposes.options.precision('pm25'), exposes.options.calibration('pm25')],
4302
+ convert: (model, msg, publish, options, meta) => {
4303
+ if (msg.data['measuredValue']) {
4304
+ const pm25 = parseFloat(msg.data['measuredValue']) / 100.0;
4305
+ const pm25Property = postfixWithEndpointName('pm25', msg, model);
4306
+
4307
+ // Air Quality Scale (ikea app):
4308
+ // 0-35=Good, 35-80=OK, 80+=Not Good
4309
+ let airQuality;
4310
+ const airQualityProperty = postfixWithEndpointName('air_quality', msg, model);
4311
+ if (pm25 <= 35) {
4312
+ airQuality = 'good';
4313
+ } else if (pm25 <= 80) {
4314
+ airQuality = 'ok';
4315
+ } else if (pm25 < 65535) {
4316
+ airQuality = 'not_good';
4317
+ } else {
4318
+ airQuality = 'unknown';
4319
+ }
4320
+
4321
+ return {[pm25Property]: calibrateAndPrecisionRoundOptions(pm25, options, 'pm25'), [airQualityProperty]: airQuality};
4322
+ }
4323
+ },
4324
+ },
4247
4325
  E1524_E1810_levelctrl: {
4248
4326
  cluster: 'genLevelCtrl',
4249
4327
  type: [
@@ -5961,6 +6039,11 @@ const converters = {
5961
6039
  };
5962
6040
 
5963
6041
  const data = utf8FromStr(msg['data']['16896']);
6042
+
6043
+ clearTimeout(globalStore.getValue(msg.endpoint, 'timer'));
6044
+ const timer = setTimeout(() => publish({action: 'lock', state: 'LOCK'}), 2 * 1000);
6045
+ globalStore.putValue(msg.endpoint, 'timer', timer);
6046
+
5964
6047
  return {
5965
6048
  action: 'unlock',
5966
6049
  action_user: data[3],
@@ -5969,6 +6052,77 @@ const converters = {
5969
6052
  };
5970
6053
  },
5971
6054
  },
6055
+ javis_microwave_sensor: {
6056
+ cluster: 'manuSpecificTuya',
6057
+ type: ['commandSetDataResponse', 'commandGetData'],
6058
+ convert: (model, msg, publish, options, meta) => {
6059
+ const dp = msg.data.dp;
6060
+ const value = tuya.getDataValue(msg.data.datatype, msg.data.data);
6061
+ const lookup = {
6062
+ 0: 'no_motion',
6063
+ 1: 'big_motion',
6064
+ 2: 'minor_motion',
6065
+ 3: 'breathing',
6066
+ 4: 'abnormal_state',
6067
+ 5: 'initializing',
6068
+ 6: 'initialization_completed',
6069
+ };
6070
+ switch (dp) {
6071
+ case 1:
6072
+ return {
6073
+ states: lookup[value],
6074
+ occupancy: (0 < value && value < 5) ? true: false,
6075
+ };
6076
+ case 2:
6077
+ return {
6078
+ sensitivity: value,
6079
+ };
6080
+ case 101:
6081
+ return {
6082
+ illuminance_lux: value,
6083
+ };
6084
+ case 102:
6085
+ if (meta.device.manufacturerName === '_TZE200_kagkgk0i') {
6086
+ return {
6087
+ illuminance_calibration: value,
6088
+ };
6089
+ } else {
6090
+ return {
6091
+ keep_time: value,
6092
+ };
6093
+ }
6094
+ case 103:
6095
+ return {
6096
+ led_enable: value == 1 ? true : false,
6097
+ };
6098
+ case 104:
6099
+ return {illuminance_lux: value};
6100
+ case 105:
6101
+ return {
6102
+ illuminance_calibration: value,
6103
+ };
6104
+ case 106:
6105
+ if (meta.device.manufacturerName === '_TZE200_kagkgk0i') {
6106
+ return {
6107
+ keep_time: value,
6108
+ };
6109
+ } else {
6110
+ break;
6111
+ }
6112
+ case 107:
6113
+ if (meta.device.manufacturerName === '_TZE200_kagkgk0i') {
6114
+ return {
6115
+ led_enable: value == 1 ? true : false,
6116
+ };
6117
+ } else {
6118
+ break;
6119
+ }
6120
+ default:
6121
+ meta.logger.warn(`zigbee-herdsman-converters:javis_microwave_sensor: NOT RECOGNIZED ` +
6122
+ `DP #${dp} with data ${JSON.stringify(msg.data)}`);
6123
+ }
6124
+ },
6125
+ },
5972
6126
  diyruz_freepad_config: {
5973
6127
  cluster: 'genOnOffSwitchCfg',
5974
6128
  type: ['readResponse'],
@@ -6198,12 +6352,34 @@ const converters = {
6198
6352
  DJT11LM_vibration: {
6199
6353
  cluster: 'closuresDoorLock',
6200
6354
  type: ['attributeReport', 'readResponse'],
6355
+ options: [exposes.options.vibration_timeout()],
6201
6356
  convert: (model, msg, publish, options, meta) => {
6202
6357
  const result = {};
6203
6358
 
6204
6359
  if (msg.data['85']) {
6205
6360
  const vibrationLookup = {1: 'vibration', 2: 'tilt', 3: 'drop'};
6206
6361
  result.action = vibrationLookup[msg.data['85']];
6362
+
6363
+ // Device only sends a message when vibration is detected.
6364
+ // Therefore we need to publish a no_vibration message on our own.
6365
+ if (result.action === 'vibration') {
6366
+ result.vibration = true;
6367
+
6368
+ const timeout = options && options.hasOwnProperty('vibration_timeout') ? options.vibration_timeout : 90;
6369
+
6370
+ // Stop any existing timer cause vibration detected
6371
+ clearTimeout(globalStore.getValue(msg.endpoint, 'vibration_timer', null));
6372
+ globalStore.putValue(msg.endpoint, 'vibration_timer', null);
6373
+
6374
+ // Set new timer to publish no_vibration message
6375
+ if (timeout !== 0) {
6376
+ const timer = setTimeout(() => {
6377
+ publish({vibration: false});
6378
+ }, timeout * 1000);
6379
+
6380
+ globalStore.putValue(msg.endpoint, 'vibration_timer', timer);
6381
+ }
6382
+ }
6207
6383
  }
6208
6384
 
6209
6385
  if (msg.data['1283']) {
@@ -15,6 +15,7 @@ const manufacturerOptions = {
15
15
  osram: {manufacturerCode: herdsman.Zcl.ManufacturerCode.OSRAM},
16
16
  eurotronic: {manufacturerCode: herdsman.Zcl.ManufacturerCode.JENNIC},
17
17
  danfoss: {manufacturerCode: herdsman.Zcl.ManufacturerCode.DANFOSS},
18
+ develco: {manufacturerCode: herdsman.Zcl.ManufacturerCode.DEVELCO},
18
19
  hue: {manufacturerCode: herdsman.Zcl.ManufacturerCode.PHILIPS},
19
20
  sinope: {manufacturerCode: herdsman.Zcl.ManufacturerCode.SINOPE_TECH},
20
21
  /*
@@ -1910,6 +1911,12 @@ const converters = {
1910
1911
  await entity.read('genBasic', [0x0033], manufacturerOptions.hue);
1911
1912
  },
1912
1913
  },
1914
+ ikea_pm25: {
1915
+ key: ['pm25', 'air_quality'],
1916
+ convertGet: async (entity, key, meta) => {
1917
+ await entity.read('manuSpecificIkeaPM25Measurement', ['measuredValue']);
1918
+ },
1919
+ },
1913
1920
  RTCGQ13LM_motion_sensitivity: {
1914
1921
  key: ['motion_sensitivity'],
1915
1922
  convertSet: async (entity, key, value, meta) => {
@@ -2569,6 +2576,34 @@ const converters = {
2569
2576
  await entity.read('haDiagnostic', ['danfossMultimasterRole'], manufacturerOptions.danfoss);
2570
2577
  },
2571
2578
  },
2579
+ develco_pulse_configuration: {
2580
+ key: ['pulse_configuration'],
2581
+ convertSet: async (entity, key, value, meta) => {
2582
+ await entity.write('seMetering', {'develcoPulseConfiguration': value}, manufacturerOptions.develco);
2583
+ return {readAfterWriteTime: 200, state: {'pulse_configuration': value}};
2584
+ },
2585
+ convertGet: async (entity, key, meta) => {
2586
+ await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions.develco);
2587
+ },
2588
+ },
2589
+ develco_interface_mode: {
2590
+ key: ['interface_mode'],
2591
+ convertSet: async (entity, key, value, meta) => {
2592
+ const payload = {'develcoInterfaceMode': utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)};
2593
+ await entity.write('seMetering', payload, manufacturerOptions.develco);
2594
+ return {readAfterWriteTime: 200, state: {'interface_mode': value}};
2595
+ },
2596
+ convertGet: async (entity, key, meta) => {
2597
+ await entity.read('seMetering', ['develcoInterfaceMode'], manufacturerOptions.develco);
2598
+ },
2599
+ },
2600
+ develco_current_summation: {
2601
+ key: ['current_summation'],
2602
+ convertSet: async (entity, key, value, meta) => {
2603
+ await entity.write('seMetering', {'develcoCurrentSummation': value}, manufacturerOptions.develco);
2604
+ return {state: {'current_summation': value}};
2605
+ },
2606
+ },
2572
2607
  ZMCSW032D_cover_position: {
2573
2608
  key: ['position', 'tilt'],
2574
2609
  convertSet: async (entity, key, value, meta) => {
@@ -2762,9 +2797,9 @@ const converters = {
2762
2797
  await tuya.sendDataPointBool(entity, tuya.dataPoints.tvChildLock, value === 'LOCK');
2763
2798
  break;
2764
2799
  case 'local_temperature_calibration':
2765
- // value = Math.round(value);
2766
- value = (value < 0) ? 0xFFFFFFFF + value + 1 : value;
2767
- await tuya.sendDataPointValue(entity, tuya.dataPoints.tvTempCalibration, value * 10);
2800
+ if (value > 0) value = value*10;
2801
+ if (value < 0) value = value*10 + 0x100000000;
2802
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.tvTempCalibration, value);
2768
2803
  break;
2769
2804
  case 'current_heating_setpoint':
2770
2805
  await tuya.sendDataPointValue(entity, tuya.dataPoints.tvHeatingSetpoint, value * 10);
@@ -2807,7 +2842,7 @@ const converters = {
2807
2842
  haozee_thermostat_system_mode: {
2808
2843
  key: ['preset'],
2809
2844
  convertSet: async (entity, key, value, meta) => {
2810
- const lookup = {0: 'auto', 1: 'manual', 2: 'off', 3: 'on'};
2845
+ const lookup = {'auto': 0, 'manual': 1, 'off': 2, 'on': 3};
2811
2846
  await tuya.sendDataPointEnum(entity, tuya.dataPoints.haozeeSystemMode, lookup[value]);
2812
2847
  },
2813
2848
  },
@@ -2845,7 +2880,7 @@ const converters = {
2845
2880
  haozee_thermostat_temperature_calibration: {
2846
2881
  key: ['local_temperature_calibration'],
2847
2882
  convertSet: async (entity, key, value, meta) => {
2848
- let temp = Math.round(value * 1);
2883
+ let temp = Math.round(value * 10);
2849
2884
  if (temp < 0) {
2850
2885
  temp = 0xFFFFFFFF + temp + 1;
2851
2886
  }
@@ -4557,6 +4592,34 @@ const converters = {
4557
4592
  }
4558
4593
  },
4559
4594
  },
4595
+ tuya_thermostat_schedule_programming_mode: { // payload example "00:20/5°C 01:20/5°C 6:59/15°C 18:00/5°C 20:00/5°C 23:30/5°C"
4596
+ key: ['workdays_schedule', 'holidays_schedule'],
4597
+ convertSet: async (entity, key, value, meta) => {
4598
+ const dpId =
4599
+ (key === 'workdays_schedule') ?
4600
+ tuya.dataPoints.scheduleWorkday :
4601
+ tuya.dataPoints.scheduleHoliday;
4602
+ const payload = [];
4603
+ const items = value.split(' ');
4604
+
4605
+ for (let i = 0; i < 6; i++) {
4606
+ const hourTemperature = items[i].split('/');
4607
+ const hourMinute = hourTemperature[0].split(':', 2);
4608
+ const hour = parseInt(hourMinute[0]);
4609
+ const minute = parseInt(hourMinute[1]);
4610
+ const temperature = parseInt(hourTemperature[1]);
4611
+
4612
+ if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || temperature < 5 || temperature >= 35) {
4613
+ throw new Error('Invalid hour, minute or temperature of:' + items[i]);
4614
+ }
4615
+
4616
+ payload[i*3] = hour;
4617
+ payload[i*3+1] = minute;
4618
+ payload[i*3+2] = temperature;
4619
+ }
4620
+ tuya.sendDataPointRaw(entity, dpId, payload);
4621
+ },
4622
+ },
4560
4623
  tuya_thermostat_week: {
4561
4624
  key: ['week'],
4562
4625
  convertSet: async (entity, key, value, meta) => {
@@ -5236,8 +5299,6 @@ const converters = {
5236
5299
  saswell_thermostat_calibration: {
5237
5300
  key: ['local_temperature_calibration'],
5238
5301
  convertSet: async (entity, key, value, meta) => {
5239
- if (value > 6) value = 6;
5240
- if (value < -6) value = -6;
5241
5302
  if (value < 0) value = 0xFFFFFFFF + value + 1;
5242
5303
  await tuya.sendDataPointValue(entity, tuya.dataPoints.saswellTempCalibration, value);
5243
5304
  },
@@ -6080,6 +6141,46 @@ const converters = {
6080
6141
  }
6081
6142
  },
6082
6143
  },
6144
+ javis_microwave_sensor: {
6145
+ key: [
6146
+ 'illuminance_calibration', 'led_enable',
6147
+ 'sensitivity', 'keep_time',
6148
+ ],
6149
+ convertSet: async (entity, key, value, meta) => {
6150
+ switch (key) {
6151
+ case 'illuminance_calibration':// (10--100) sensor illuminance sensitivity
6152
+ if (meta.device.manufacturerName === '_TZE200_kagkgk0i') {
6153
+ await tuya.sendDataPointRaw(entity, 102, [value]);
6154
+ break;
6155
+ } else {
6156
+ tuya.sendDataPointRaw(entity, 105, [value]);
6157
+ break;
6158
+ }
6159
+ case 'led_enable':// OK (value true/false or 1/0)
6160
+ if (meta.device.manufacturerName === '_TZE200_kagkgk0i') {
6161
+ await tuya.sendDataPointRaw(entity, 107, [value ? 1 : 0]);
6162
+ break;
6163
+ } else {
6164
+ await tuya.sendDataPointRaw(entity, 103, [value ? 1 : 0]);
6165
+ break;
6166
+ }
6167
+
6168
+ case 'sensitivity':// value: 25, 50, 75, 100
6169
+ await tuya.sendDataPointRaw(entity, 2, [value]);
6170
+ break;
6171
+ case 'keep_time': // value 0 --> 7 corresponding 5s, 30s, 1, 3, 5, 10, 20, 30 min
6172
+ if (meta.device.manufacturerName === '_TZE200_kagkgk0i') {
6173
+ await tuya.sendDataPointRaw(entity, 106, [value]);
6174
+ break;
6175
+ } else {
6176
+ await tuya.sendDataPointRaw(entity, 102, [value]);
6177
+ break;
6178
+ }
6179
+ default: // Unknown key
6180
+ throw new Error(`Unhandled key ${key}`);
6181
+ }
6182
+ },
6183
+ },
6083
6184
  moes_thermostat_tv: {
6084
6185
  key: [
6085
6186
  'system_mode', 'window_detection', 'frost_detection', 'child_lock',
@@ -5,6 +5,31 @@ const reporting = require('../lib/reporting');
5
5
  const extend = require('../lib/extend');
6
6
  const e = exposes.presets;
7
7
 
8
+ const batteryRotaryDimmer = (...endpointsIds) => ({
9
+ fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_step_color_temperature],
10
+ toZigbee: [],
11
+ exposes: [e.battery(), e.action([
12
+ 'on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down'])],
13
+ configure: async (device, coordinatorEndpoint, logger) => {
14
+ const endpoints = endpointsIds.map((endpoint) => device.getEndpoint(endpoint));
15
+
16
+ // Battery level is only reported on first endpoint
17
+ await reporting.batteryVoltage(endpoints[0]);
18
+
19
+ for await (const endpoint of endpoints) {
20
+ logger.debug(`processing endpoint ${endpoint.ID}`);
21
+ await reporting.bind(endpoint, coordinatorEndpoint,
22
+ ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl']);
23
+
24
+ // The default is for the device to also report the on/off and
25
+ // brightness at the same time as sending on/off and step commands.
26
+ // Disable the reporting by setting the max interval to 0xFFFF.
27
+ await reporting.brightness(endpoint, {max: 0xFFFF});
28
+ await reporting.onOff(endpoint, {max: 0xFFFF});
29
+ }
30
+ },
31
+ });
32
+
8
33
  module.exports = [
9
34
  {
10
35
  zigbeeModel: ['TWGU10Bulb50AU'],
@@ -167,38 +192,20 @@ module.exports = [
167
192
  model: 'AU-A1ZBR1GW',
168
193
  vendor: 'Aurora Lighting',
169
194
  description: 'AOne one gang wireless battery rotary dimmer',
170
- fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_step_color_temperature],
171
- toZigbee: [],
172
- exposes: [e.battery(), e.action([
173
- 'on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down'])],
174
195
  meta: {battery: {voltageToPercentage: '3V_2100'}},
175
- configure: async (device, coordinatorEndpoint, logger) => {
176
- const endpoint1 = device.getEndpoint(1);
177
- await reporting.bind(endpoint1, coordinatorEndpoint,
178
- ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'genPowerCfg']);
179
- await reporting.batteryVoltage(endpoint1);
180
- },
196
+ // One gang battery rotary dimmer with endpoint ID 1
197
+ ...batteryRotaryDimmer(1),
181
198
  },
182
199
  {
183
200
  zigbeeModel: ['2GBatteryDimmer50AU'],
184
201
  model: 'AU-A1ZBR2GW',
185
202
  vendor: 'Aurora Lighting',
186
203
  description: 'AOne two gang wireless battery rotary dimmer',
187
- fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_step_color_temperature],
188
- toZigbee: [],
189
- exposes: [e.battery(), e.action([
190
- 'on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down'])],
191
204
  meta: {multiEndpoint: true, battery: {voltageToPercentage: '3V_2100'}},
192
205
  endpoint: (device) => {
193
206
  return {'right': 1, 'left': 2};
194
207
  },
195
- configure: async (device, coordinatorEndpoint, logger) => {
196
- const endpoint1 = device.getEndpoint(1);
197
- await reporting.bind(endpoint1, coordinatorEndpoint,
198
- ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'genPowerCfg']);
199
- await reporting.batteryVoltage(endpoint1);
200
- const endpoint2 = device.getEndpoint(2);
201
- await reporting.bind(endpoint2, coordinatorEndpoint, ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl']);
202
- },
208
+ // Two gang battery rotary dimmer with endpoint IDs 1 and 2
209
+ ...batteryRotaryDimmer(1, 2),
203
210
  },
204
211
  ];
package/devices/bitron.js CHANGED
@@ -190,7 +190,7 @@ module.exports = [
190
190
  tz.thermostat_running_state, tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode],
191
191
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5).withLocalTemperature()
192
192
  .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat', 'cool'])
193
- .withLocalTemperatureCalibration(), e.keypad_lockout()],
193
+ .withLocalTemperatureCalibration(-20, 20, 1), e.keypad_lockout()],
194
194
  meta: {battery: {voltageToPercentage: '3V_2500_3200'}},
195
195
  configure: async (device, coordinatorEndpoint, logger) => {
196
196
  const endpoint = device.getEndpoint(1);
package/devices/candeo.js CHANGED
@@ -13,7 +13,12 @@ module.exports = [
13
13
  const endpoint = device.getEndpoint(1);
14
14
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']);
15
15
  await reporting.onOff(endpoint);
16
- await reporting.brightness(endpoint);
16
+
17
+ // The default reporting from the Candeo dimmer can result in a lot of Zigbee traffic
18
+ // to the extent that reported brighness values can arrive at the endpoint out of order
19
+ // giving the appearance that the values are jumping around.
20
+ // Limit the reporting to once a second to give a smoother reporting of brightness.
21
+ await reporting.brightness(endpoint, {min: 1});
17
22
  },
18
23
  },
19
24
  ];
@@ -154,7 +154,8 @@ module.exports = [
154
154
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 1).withLocalTemperature()
155
155
  .withSystemMode(['off', 'heat', 'cool', 'emergency_heating'])
156
156
  .withRunningState(['idle', 'heat', 'cool', 'fan_only']).withFanMode(['auto', 'on'])
157
- .withSetpoint('occupied_cooling_setpoint', 10, 30, 1).withLocalTemperatureCalibration()],
157
+ .withSetpoint('occupied_cooling_setpoint', 10, 30, 1)
158
+ .withLocalTemperatureCalibration(-20, 20, 1)],
158
159
  meta: {battery: {voltageToPercentage: '3V_1500_2800'}},
159
160
  configure: async (device, coordinatorEndpoint, logger) => {
160
161
  const endpoint = device.getEndpoint(1);
@@ -179,7 +180,8 @@ module.exports = [
179
180
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 1).withLocalTemperature()
180
181
  .withSystemMode(['off', 'heat', 'cool', 'emergency_heating'])
181
182
  .withRunningState(['idle', 'heat', 'cool', 'fan_only']).withFanMode(['auto', 'on'])
182
- .withSetpoint('occupied_cooling_setpoint', 10, 30, 1).withLocalTemperatureCalibration()],
183
+ .withSetpoint('occupied_cooling_setpoint', 10, 30, 1)
184
+ .withLocalTemperatureCalibration(-20, 20, 1)],
183
185
  meta: {battery: {voltageToPercentage: '3V_1500_2800'}},
184
186
  configure: async (device, coordinatorEndpoint, logger) => {
185
187
  const endpoint = device.getEndpoint(1);
@@ -297,15 +297,28 @@ module.exports = [
297
297
  model: 'ZHEMI101',
298
298
  vendor: 'Develco',
299
299
  description: 'Energy meter',
300
- fromZigbee: [fz.metering],
301
- toZigbee: [],
300
+ fromZigbee: [fz.metering, fz.develco_metering],
301
+ toZigbee: [tz.develco_pulse_configuration, tz.develco_interface_mode, tz.develco_current_summation],
302
+ endpoint: (device) => {
303
+ return {'default': 2};
304
+ },
302
305
  configure: async (device, coordinatorEndpoint, logger) => {
303
306
  const endpoint = device.getEndpoint(2);
304
307
  await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']);
305
308
  await reporting.instantaneousDemand(endpoint);
306
309
  await reporting.readMeteringMultiplierDivisor(endpoint);
307
310
  },
308
- exposes: [e.power(), e.energy()],
311
+ exposes: [
312
+ e.power(),
313
+ e.energy(),
314
+ exposes.numeric('pulse_configuration', ea.ALL).withValueMin(0).withValueMax(65535)
315
+ .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'),
316
+ exposes.enum('interface_mode', ea.ALL,
317
+ ['electricity', 'gas', 'water', 'kamstrup-kmp', 'linky', 'IEC62056-21', 'DSMR-2.3', 'DSMR-4.0'])
318
+ .withDescription('Operating mode/probe'),
319
+ exposes.numeric('current_summation', ea.SET)
320
+ .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh'),
321
+ ],
309
322
  },
310
323
  {
311
324
  zigbeeModel: ['SMRZB-332'],
package/devices/ecozy.js CHANGED
@@ -18,7 +18,8 @@ module.exports = [
18
18
  tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_relay_status_log,
19
19
  tz.thermostat_pi_heating_demand, tz.thermostat_running_state],
20
20
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 1).withLocalTemperature()
21
- .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withLocalTemperatureCalibration()
21
+ .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat'])
22
+ .withLocalTemperatureCalibration(-20, 20, 1)
22
23
  .withPiHeatingDemand(ea.STATE_GET)],
23
24
  configure: async (device, coordinatorEndpoint, logger) => {
24
25
  const endpoint = device.getEndpoint(3);