zigbee-herdsman-converters 14.0.544 → 14.0.547

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.
@@ -1667,6 +1667,34 @@ const converters = {
1667
1667
  return result;
1668
1668
  },
1669
1669
  },
1670
+ power_source: {
1671
+ cluster: 'genBasic',
1672
+ type: ['attributeReport', 'readResponse'],
1673
+ convert: (model, msg, publish, options, meta) => {
1674
+ const payload = {};
1675
+ if (msg.data.hasOwnProperty('powerSource')) {
1676
+ const value = msg.data['powerSource'];
1677
+ const lookup = {
1678
+ 0: 'unknown',
1679
+ 1: 'mains_single_phase',
1680
+ 2: 'mains_three_phase',
1681
+ 3: 'battery',
1682
+ 4: 'dc_source',
1683
+ 5: 'emergency_mains_constantly_powered',
1684
+ 6: 'emergency_mains_and_transfer_switch',
1685
+ };
1686
+ payload.power_source = lookup[value];
1687
+
1688
+ if (['R7051'].includes(model.model)) {
1689
+ payload.ac_connected = value === 2;
1690
+ } else if (['ZNCLBL01LM'].includes(model.model)) {
1691
+ payload.charging = value === 4;
1692
+ }
1693
+ }
1694
+
1695
+ return payload;
1696
+ },
1697
+ },
1670
1698
  // #endregion
1671
1699
 
1672
1700
  // #region Non-generic converters
@@ -2261,8 +2289,14 @@ const converters = {
2261
2289
  case tuya.dataPoints.coverArrived: { // Arrived at position
2262
2290
  const invert = tuya.isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover;
2263
2291
  const position = invert ? 100 - (value & 0xFF) : (value & 0xFF);
2264
- let running = position == 0 || position == 100;
2265
- if (dp === tuya.dataPoints.coverArrived) running = false;
2292
+ const running = dp !== tuya.dataPoints.coverArrived;
2293
+
2294
+ // Not all covers report coverArrived, so set running to false if device doesn't report position for a few seconds
2295
+ clearTimeout(globalStore.getValue(msg.endpoint, 'running_timer'));
2296
+ if (running) {
2297
+ const timer = setTimeout(() => publish({running: false}), 3 * 1000);
2298
+ globalStore.putValue(msg.endpoint, 'running_timer', timer);
2299
+ }
2266
2300
 
2267
2301
  if (position > 0 && position <= 100) {
2268
2302
  return {running, position, state: 'OPEN'};
@@ -2743,16 +2777,6 @@ const converters = {
2743
2777
  return result;
2744
2778
  },
2745
2779
  },
2746
- ts0219_power_source: {
2747
- cluster: 'genBasic',
2748
- type: 'attributeReport',
2749
- convert: (model, msg, publish, options, meta) => {
2750
- const powerSource = msg.data.powerSource;
2751
- return {
2752
- ac_connected: powerSource === 2 ? true : false,
2753
- };
2754
- },
2755
- },
2756
2780
  tuya_cover_options: {
2757
2781
  cluster: 'closuresWindowCovering',
2758
2782
  type: ['attributeReport', 'readResponse'],
@@ -3967,7 +3991,7 @@ const converters = {
3967
3991
  return {deadzone_temperature: value};
3968
3992
  case tuya.dataPoints.moesLocalTemp:
3969
3993
  temperature = value & 1<<15 ? value - (1<<16) + 1 : value;
3970
- if (meta.device.manufacturerName !== '_TZE200_ye5jkfsb') {
3994
+ if (!['_TZE200_ztvwu4nk', '_TZE200_ye5jkfsb'].includes(meta.device.manufacturerName)) {
3971
3995
  // https://github.com/Koenkk/zigbee2mqtt/issues/11980
3972
3996
  temperature = temperature / 10;
3973
3997
  }
@@ -5941,7 +5965,10 @@ const converters = {
5941
5965
  const invert = model.meta && model.meta.coverInverted ? !options.invert_cover : options.invert_cover;
5942
5966
  if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] <= 100) {
5943
5967
  const value = msg.data['currentPositionLiftPercentage'];
5944
- result[postfixWithEndpointName('position', msg, model)] = invert ? 100 - value : value;
5968
+ const position = invert ? 100 - value : value;
5969
+ const state = invert ? (position > 0 ? 'CLOSE' : 'OPEN') : (position > 0 ? 'OPEN' : 'CLOSE');
5970
+ result[postfixWithEndpointName('position', msg, model)] = position;
5971
+ result[postfixWithEndpointName('state', msg, model)] = state;
5945
5972
  }
5946
5973
  if (msg.data.hasOwnProperty('currentPositionTiltPercentage') && msg.data['currentPositionTiltPercentage'] <= 100) {
5947
5974
  const value = msg.data['currentPositionTiltPercentage'];
@@ -7447,23 +7474,6 @@ const converters = {
7447
7474
  }
7448
7475
  },
7449
7476
  },
7450
- woox_R7060: {
7451
- cluster: 'manuSpecificTuya',
7452
- type: ['commandActiveStatusReport'],
7453
- convert: (model, msg, publish, options, meta) => {
7454
- const dpValue = tuya.firstDpValue(msg, meta, 'woox_R7060');
7455
- const dp = dpValue.dp;
7456
- const value = tuya.getDataValue(dpValue);
7457
-
7458
- switch (dp) {
7459
- case tuya.dataPoints.wooxSwitch:
7460
- return {state: value === 2 ? 'OFF' : 'ON'};
7461
- default:
7462
- meta.logger.warn(`zigbee-herdsman-converters:WooxR7060: Unrecognized DP #${
7463
- dp} with data ${JSON.stringify(dpValue)}`);
7464
- }
7465
- },
7466
- },
7467
7477
  idlock: {
7468
7478
  cluster: 'closuresDoorLock',
7469
7479
  type: ['attributeReport', 'readResponse'],
@@ -91,6 +91,12 @@ const converters = {
91
91
  entity.commandResponse('ssIasAce', 'panelStatusChanged', payload);
92
92
  },
93
93
  },
94
+ battery_percentage_remaining: {
95
+ key: ['battery'],
96
+ convertGet: async (entity, key, meta) => {
97
+ await entity.read('genPowerCfg', ['batteryPercentageRemaining']);
98
+ },
99
+ },
94
100
  power_on_behavior: {
95
101
  key: ['power_on_behavior'],
96
102
  convertSet: async (entity, key, value, meta) => {
@@ -853,6 +859,7 @@ const converters = {
853
859
  state = meta.message.state = brightness === 0 ? 'off' : 'on';
854
860
  }
855
861
 
862
+ let publishBrightness = brightness !== undefined;
856
863
  const targetState = state === 'toggle' ? (meta.state.state === 'ON' ? 'off' : 'on') : state;
857
864
  if (targetState === 'off') {
858
865
  // Simulate 'Off' with transition via 'MoveToLevelWithOnOff', otherwise just use 'Off'.
@@ -890,6 +897,8 @@ const converters = {
890
897
  } catch (e) {
891
898
  // OnLevel not supported
892
899
  }
900
+ // Published state might have gotten clobbered by reporting.
901
+ publishBrightness = true;
893
902
  }
894
903
  }
895
904
 
@@ -921,7 +930,7 @@ const converters = {
921
930
  );
922
931
 
923
932
  const result = {state: {}, readAfterWriteTime: transition.time * 100};
924
- if (brightness !== 0) {
933
+ if (publishBrightness) {
925
934
  result.state.brightness = Number(brightness);
926
935
  }
927
936
  if (state !== null) {
@@ -2477,7 +2486,7 @@ const converters = {
2477
2486
  await entity.command('closuresWindowCovering', 'stop', {}, utils.getOptions(meta.mapped, entity));
2478
2487
  }
2479
2488
 
2480
- if (!['ZNCLDJ11LM', 'ZNJLBL01LM'].includes(meta.mapped.model)) {
2489
+ if (!['ZNCLDJ11LM', 'ZNJLBL01LM', 'ZNCLBL01LM'].includes(meta.mapped.model)) {
2481
2490
  // The code below is originally added for ZNCLDJ11LM (Koenkk/zigbee2mqtt#4585).
2482
2491
  // However, in Koenkk/zigbee-herdsman-converters#4039 it was replaced by reading
2483
2492
  // directly from currentPositionLiftPercentage, so that device is excluded.
@@ -2490,6 +2499,8 @@ const converters = {
2490
2499
  // Xiaomi curtain does not send position update on stop, request this.
2491
2500
  await entity.read('genAnalogOutput', [0x0055]);
2492
2501
  }
2502
+
2503
+ return {state: {state: 'STOP'}};
2493
2504
  } else {
2494
2505
  const lookup = {'open': 100, 'close': 0, 'on': 100, 'off': 0};
2495
2506
 
@@ -2497,12 +2508,21 @@ const converters = {
2497
2508
  value = lookup.hasOwnProperty(value) ? lookup[value] : value;
2498
2509
  value = meta.options.invert_cover ? 100 - value : value;
2499
2510
 
2500
- const payload = {0x0055: {value, type: 0x39}};
2501
- await entity.write('genAnalogOutput', payload);
2511
+ if (['ZNCLBL01LM'].includes(meta.mapped.model)) {
2512
+ await entity.command('closuresWindowCovering', 'goToLiftPercentage', {percentageliftvalue: value},
2513
+ utils.getOptions(meta.mapped, entity));
2514
+ } else {
2515
+ const payload = {0x0055: {value, type: 0x39}};
2516
+ await entity.write('genAnalogOutput', payload);
2517
+ }
2502
2518
  }
2503
2519
  },
2504
2520
  convertGet: async (entity, key, meta) => {
2505
- await entity.read('genAnalogOutput', [0x0055]);
2521
+ if (['ZNCLBL01LM'].includes(meta.mapped.model)) {
2522
+ await entity.read('closuresWindowCovering', ['currentPositionLiftPercentage']);
2523
+ } else {
2524
+ await entity.read('genAnalogOutput', [0x0055]);
2525
+ }
2506
2526
  },
2507
2527
  },
2508
2528
  xiaomi_curtain_acn002_charging_status: {
@@ -5310,6 +5330,12 @@ const converters = {
5310
5330
  }
5311
5331
  },
5312
5332
  },
5333
+ power_source: {
5334
+ key: ['power_source', 'charging'],
5335
+ convertGet: async (entity, key, meta) => {
5336
+ await entity.read('genBasic', ['powerSource']);
5337
+ },
5338
+ },
5313
5339
  ts0201_temperature_humidity_alarm: {
5314
5340
  key: ['alarm_humidity_max', 'alarm_humidity_min', 'alarm_temperature_max', 'alarm_temperature_min'],
5315
5341
  convertSet: async (entity, key, value, meta) => {
@@ -6532,6 +6558,18 @@ const converters = {
6532
6558
  }
6533
6559
  },
6534
6560
  },
6561
+ ZNCLBL01LM_battery_voltage: {
6562
+ key: ['voltage'],
6563
+ convertGet: async (entity, key, meta) => {
6564
+ await entity.read('aqaraOpple', [0x040B], manufacturerOptions.xiaomi);
6565
+ },
6566
+ },
6567
+ ZNCLBL01LM_hooks_state: {
6568
+ key: ['hooks_state'],
6569
+ convertGet: async (entity, key, meta) => {
6570
+ await entity.read('aqaraOpple', [0x0428], manufacturerOptions.xiaomi);
6571
+ },
6572
+ },
6535
6573
  wiser_vact_calibrate_valve: {
6536
6574
  key: ['calibrate_valve'],
6537
6575
  convertSet: async (entity, key, value, meta) => {
package/devices/alecto.js CHANGED
@@ -5,6 +5,59 @@ const tuya = require('../lib/tuya');
5
5
  const e = exposes.presets;
6
6
  const ea = exposes.access;
7
7
 
8
+ const fzLocal = {
9
+ tuya_alecto_smoke: {
10
+ cluster: 'manuSpecificTuya',
11
+ type: ['commandDataResponse', 'commandDataReport'],
12
+ convert: (model, msg, publish, options, meta) => {
13
+ const dpValue = tuya.firstDpValue(msg, meta, 'tuya_alecto_smoke');
14
+ const dp = dpValue.dp;
15
+ const value = tuya.getDataValue(dpValue);
16
+ switch (dp) {
17
+ case tuya.dataPoints.alectoSmokeState:
18
+ return {smoke_state: {0: 'alarm', 1: 'normal'}[value]};
19
+ case tuya.dataPoints.alectoSmokeValue:
20
+ return {smoke_value: value};
21
+ case tuya.dataPoints.alectoSelfChecking:
22
+ return {self_checking: value};
23
+ case tuya.dataPoints.alectoCheckingResult:
24
+ return {checking_result: {0: 'checking', 1: 'check_success', 2: 'check_failure', 3: 'others'}[value]};
25
+ case tuya.dataPoints.alectoSmokeTest:
26
+ return {smoke_test: value};
27
+ case tuya.dataPoints.alectoLifecycle:
28
+ return {lifecycle: value};
29
+ case tuya.dataPoints.alectoBatteryPercentage:
30
+ return {battery: value};
31
+ case tuya.dataPoints.alectoBatteryState:
32
+ return {battery_state: {0: 'low', 1: 'middle', 2: 'high'}[value]};
33
+ case tuya.dataPoints.alectoSilence:
34
+ return {silence: value};
35
+ default:
36
+ meta.logger.warn(`zigbee-herdsman-converters:tuya_alecto_smoke: Unrecognized ` +
37
+ `DP #${ dp} with data ${JSON.stringiy(msg.data)}`);
38
+ }
39
+ },
40
+ },
41
+ };
42
+
43
+ const tzLocal = {
44
+ tuya_alecto_smoke: {
45
+ key: ['self_checking', 'silence'],
46
+ convertSet: async (entity, key, value, meta) => {
47
+ switch (key) {
48
+ case 'self_checking':
49
+ await tuya.sendDataPointBool(entity, tuya.dataPoints.alectoSelfChecking, value);
50
+ break;
51
+ case 'silence':
52
+ await tuya.sendDataPointBool(entity, tuya.dataPoints.alectoSilence, value);
53
+ break;
54
+ default: // Unknown key
55
+ throw new Error(`zigbee-herdsman-converters:tuya_alecto_smoke: Unhandled key ${key}`);
56
+ }
57
+ },
58
+ },
59
+ };
60
+
8
61
  module.exports = [
9
62
  {
10
63
  fingerprint: [
@@ -23,4 +76,21 @@ module.exports = [
23
76
  .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET).withLocalTemperature(ea.STATE)
24
77
  .withSystemMode(['off', 'auto', 'heat'], ea.STATE_SET)],
25
78
  },
79
+ {
80
+ fingerprint: [{modelID: 'tbrwrfv\u0000', manufacturerName: '_TYST11_qtbrwrfv'}],
81
+ model: 'SMART-SMOKE10',
82
+ vendor: 'Alecto',
83
+ description: 'Smoke detector',
84
+ fromZigbee: [fzLocal.tuya_alecto_smoke],
85
+ toZigbee: [tzLocal.tuya_alecto_smoke],
86
+ meta: {},
87
+ exposes: [exposes.text('smoke_state', ea.STATE, ['alarm', 'normal']),
88
+ exposes.text('battery_state', ea.STATE, ['low', 'middle', 'high']),
89
+ exposes.text('checking_result', ea.STATE, ['checking', 'check_success', 'check_failure', 'others']),
90
+ exposes.numeric('smoke_value', ea.STATE),
91
+ exposes.numeric('battery', ea.STATE),
92
+ exposes.binary('lifecycle', ea.STATE, true, false),
93
+ exposes.binary('self_checking', ea.STATE_SET, true, false),
94
+ exposes.binary('silence', ea.STATE_SET, true, false)],
95
+ },
26
96
  ];
@@ -24,13 +24,13 @@ module.exports = [
24
24
  .withDescription('On/off state of the switch'),
25
25
  e.child_lock(),
26
26
  e.window_detection(),
27
+ e.away_mode(),
27
28
  exposes.climate()
28
29
  .withSetpoint('current_heating_setpoint', 5, 35, 1, ea.STATE_SET)
29
30
  .withLocalTemperature(ea.STATE)
30
31
  .withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
31
32
  .withSystemMode(['heat', 'auto'], ea.STATE_SET)
32
33
  .withRunningState(['idle', 'heat'], ea.STATE)
33
- .withAwayMode()
34
34
  .withSensor(['internal', 'external', 'both']),
35
35
  exposes.numeric('external_temperature', ea.STATE)
36
36
  .withUnit('°C')
@@ -234,13 +234,17 @@ module.exports = [
234
234
  model: 'WISZB-120',
235
235
  vendor: 'Develco',
236
236
  description: 'Window sensor',
237
- fromZigbee: [fz.ias_contact_alarm_1, fz.temperature],
237
+ fromZigbee: [fz.ias_contact_alarm_1, fz.battery, fz.temperature],
238
238
  toZigbee: [],
239
- exposes: [e.contact(), e.battery_low(), e.tamper(), e.temperature()],
239
+ exposes: [e.contact(), e.battery(), e.battery_low(), e.tamper(), e.temperature()],
240
+ meta: {battery: {voltageToPercentage: '3V_2500'}},
240
241
  configure: async (device, coordinatorEndpoint, logger) => {
241
- const endpoint = device.getEndpoint(38);
242
- await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement']);
243
- await reporting.temperature(endpoint);
242
+ const endpoint35 = device.getEndpoint(35);
243
+ const endpoint38 = device.getEndpoint(38);
244
+ await reporting.bind(endpoint35, coordinatorEndpoint, ['genPowerCfg']);
245
+ await reporting.bind(endpoint38, coordinatorEndpoint, ['msTemperatureMeasurement']);
246
+ await reporting.batteryVoltage(endpoint35);
247
+ await reporting.temperature(endpoint38);
244
248
  },
245
249
  },
246
250
  {
@@ -0,0 +1,18 @@
1
+ const reporting = require('../lib/reporting');
2
+ const extend = require('../lib/extend');
3
+
4
+ module.exports = [
5
+ {
6
+ zigbeeModel: ['DimmerSwitch-2Gang-ZB3.0'],
7
+ model: 'D086-ZG',
8
+ vendor: 'HZC Electric',
9
+ description: 'Zigbee dual dimmer',
10
+ extend: extend.light_onoff_brightness({noConfigure: true}),
11
+ configure: async (device, coordinatorEndpoint, logger) => {
12
+ await extend.light_onoff_brightness().configure(device, coordinatorEndpoint, logger);
13
+ const endpoint = device.getEndpoint(1);
14
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']);
15
+ await reporting.onOff(endpoint);
16
+ },
17
+ },
18
+ ];
package/devices/ikea.js CHANGED
@@ -292,10 +292,10 @@ module.exports = [
292
292
  extend: tradfriExtend.light_onoff_brightness_colortemp(),
293
293
  },
294
294
  {
295
- zigbeeModel: ['TRADFRIbulbE27WSglobeopal1055lm', 'TRADFRIbulbE26WSglobeopal1100lm'],
295
+ zigbeeModel: ['TRADFRIbulbE27WSglobeopal1055lm', 'TRADFRIbulbE26WSglobeopal1100lm', 'TRADFRIbulbE26WSglobeopal1160lm'],
296
296
  model: 'LED2003G10',
297
297
  vendor: 'IKEA',
298
- description: 'TRADFRI LED bulb E26/27 1100/1055 lumen, dimmable, white spectrum, opal white',
298
+ description: 'TRADFRI LED bulb E26/27 1100/1055/1160 lumen, dimmable, white spectrum, opal white',
299
299
  extend: tradfriExtend.light_onoff_brightness_colortemp(),
300
300
  },
301
301
  {
@@ -870,10 +870,10 @@ module.exports = [
870
870
  },
871
871
  },
872
872
  {
873
- zigbeeModel: ['TRADFRIbulbE14WScandleopal470lm'],
873
+ zigbeeModel: ['TRADFRIbulbE14WScandleopal470lm', 'TRADFRIbulbE12WScandleopal450lm'],
874
874
  model: 'LED1949C5',
875
875
  vendor: 'IKEA',
876
- description: 'TRADFRI LED bulb E14 470 lumen, wireless dimmable white spectrum/chandelier opal white',
876
+ description: 'TRADFRI LED bulb E12/E14 450/470 lumen, wireless dimmable white spectrum/chandelier opal white',
877
877
  extend: tradfriExtend.light_onoff_brightness_colortemp(),
878
878
  },
879
879
  {
package/devices/immax.js CHANGED
@@ -97,10 +97,10 @@ module.exports = [
97
97
  weeklyScheduleFirstDayDpId: tuya.dataPoints.schedule,
98
98
  },
99
99
  },
100
- exposes: [e.battery_low(), e.child_lock(), exposes.climate()
100
+ exposes: [e.battery_low(), e.child_lock(), e.away_mode(), exposes.climate()
101
101
  .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET)
102
102
  .withLocalTemperature(ea.STATE).withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET)
103
- .withRunningState(['idle', 'heat'], ea.STATE).withAwayMode()],
103
+ .withRunningState(['idle', 'heat'], ea.STATE)],
104
104
  },
105
105
  {
106
106
  zigbeeModel: ['Bulb-RGB+CCT-ZB3.0'],
@@ -143,4 +143,12 @@ module.exports = [
143
143
  extend: extend.ledvance.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}),
144
144
  ota: ota.ledvance,
145
145
  },
146
+ {
147
+ zigbeeModel: ['CLA60 TW Value'],
148
+ model: 'AC23684',
149
+ vendor: 'LEDVANCE',
150
+ description: 'Classic E26 tunable white 9w 800 lumen',
151
+ extend: extend.ledvance.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}),
152
+ ota: ota.ledvance,
153
+ },
146
154
  ];
package/devices/niko.js CHANGED
@@ -234,4 +234,18 @@ module.exports = [
234
234
  await reporting.brightness(endpoint);
235
235
  },
236
236
  },
237
+ {
238
+ zigbeeModel: ['Connectable motor control,3A'],
239
+ model: '552-72301',
240
+ vendor: 'Niko',
241
+ description: 'Connectable motor control',
242
+ fromZigbee: [fz.cover_position_tilt, fz.battery],
243
+ toZigbee: [tz.cover_state, tz.cover_position_tilt],
244
+ configure: async (device, coordinatorEndpoint, logger) => {
245
+ const endpoint = device.getEndpoint(1);
246
+ await reporting.bind(endpoint, coordinatorEndpoint, ['closuresWindowCovering']);
247
+ await reporting.currentPositionLiftPercentage(endpoint);
248
+ },
249
+ exposes: [e.cover_position()],
250
+ },
237
251
  ];
@@ -42,13 +42,12 @@ module.exports = [
42
42
  const endpoint = device.getEndpoint(1);
43
43
  await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']);
44
44
  },
45
- exposes: [e.battery_low(), e.window_detection(), e.child_lock(),
45
+ exposes: [e.battery_low(), e.window_detection(), e.child_lock(), e.away_mode(),
46
46
  exposes.binary('heating', ea.STATE, 'ON', 'OFF').withDescription('Device valve is open or closed (heating or not)'),
47
47
  exposes.climate()
48
48
  .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET).withLocalTemperature(ea.STATE)
49
49
  .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET)
50
50
  // Range is -6 - 6 and step 1: https://github.com/Koenkk/zigbee2mqtt/issues/11777
51
- .withLocalTemperatureCalibration(-6, 6, 1, ea.STATE_SET)
52
- .withAwayMode()],
51
+ .withLocalTemperatureCalibration(-6, 6, 1, ea.STATE_SET)],
53
52
  },
54
53
  ];
package/devices/tuya.js CHANGED
@@ -882,6 +882,7 @@ module.exports = [
882
882
  {modelID: 'TS0502B', manufacturerName: '_TZ3210_qjdimezy'},
883
883
  {modelID: 'TS0502B', manufacturerName: '_TZ3210_psgq7ysz'},
884
884
  {modelID: 'TS0502B', manufacturerName: '_TZ3000_zw7wr5uo'},
885
+ {modelID: 'TS0502B', manufacturerName: '_TZ3210_ok08rifa'},
885
886
  {modelID: 'TS0502B', manufacturerName: '_TZ3210_pz9zmxjj'},
886
887
  {modelID: 'TS0502B', manufacturerName: '_TZ3000_fzwhym79'},
887
888
  {modelID: 'TS0502B', manufacturerName: '_TZ3000_ogceypug'},
@@ -1358,7 +1359,7 @@ module.exports = [
1358
1359
  'to the desired temperature. If you want TRV to properly regulate the temperature you need to use mode `auto` ' +
1359
1360
  'instead setting the desired temperature.')
1360
1361
  .withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET)
1361
- .withAwayMode().withPreset(['schedule', 'manual', 'boost', 'complex', 'comfort', 'eco'])
1362
+ .withPreset(['schedule', 'manual', 'boost', 'complex', 'comfort', 'eco'])
1362
1363
  .withRunningState(['idle', 'heat'], ea.STATE),
1363
1364
  e.auto_lock(), e.away_mode(), e.away_preset_days(), e.boost_time(), e.comfort_temperature(), e.eco_temperature(), e.force(),
1364
1365
  e.max_temperature(), e.min_temperature(), e.away_preset_temperature(),
@@ -1438,10 +1439,9 @@ module.exports = [
1438
1439
  weeklyScheduleFirstDayDpId: tuya.dataPoints.schedule,
1439
1440
  },
1440
1441
  },
1441
- exposes: [e.child_lock(), exposes.climate().withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET)
1442
+ exposes: [e.child_lock(), e.away_mode(), exposes.climate().withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET)
1442
1443
  .withLocalTemperature(ea.STATE)
1443
- .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE)
1444
- .withAwayMode()],
1444
+ .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE)],
1445
1445
  },
1446
1446
  {
1447
1447
  fingerprint: [{modelID: 'dpplnsn\u0000', manufacturerName: '_TYST11_2dpplnsn'},
@@ -1462,9 +1462,9 @@ module.exports = [
1462
1462
  },
1463
1463
  },
1464
1464
  exposes: [
1465
- e.battery_low(), e.child_lock(), exposes.climate()
1465
+ e.battery_low(), e.child_lock(), e.away_mode(), exposes.climate()
1466
1466
  .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET)
1467
- .withLocalTemperature(ea.STATE).withAwayMode()
1467
+ .withLocalTemperature(ea.STATE)
1468
1468
  .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE),
1469
1469
  ],
1470
1470
  },
@@ -2099,7 +2099,8 @@ module.exports = [
2099
2099
  },
2100
2100
  {
2101
2101
  fingerprint: [{modelID: 'TS0210', manufacturerName: '_TYZB01_3zv6oleo'},
2102
- {modelID: 'TS0210', manufacturerName: '_TYZB01_j9xxahcl'}],
2102
+ {modelID: 'TS0210', manufacturerName: '_TYZB01_j9xxahcl'},
2103
+ {modelID: 'TS0210', manufacturerName: '_TYZB01_kulduhbj'}],
2103
2104
  model: 'TS0210',
2104
2105
  vendor: 'TuYa',
2105
2106
  description: 'Vibration sensor',
package/devices/woox.js CHANGED
@@ -52,6 +52,25 @@ const fzLocal = {
52
52
  return result;
53
53
  },
54
54
  },
55
+ woox_R7060: {
56
+ cluster: 'manuSpecificTuya',
57
+ type: ['commandActiveStatusReport'],
58
+ convert: (model, msg, publish, options, meta) => {
59
+ const dpValue = tuya.firstDpValue(msg, meta, 'woox_R7060');
60
+ const dp = dpValue.dp;
61
+ const value = tuya.getDataValue(dpValue);
62
+
63
+ switch (dp) {
64
+ case tuya.dataPoints.wooxSwitch:
65
+ return {state: value === 2 ? 'OFF' : 'ON'};
66
+ case 101:
67
+ return {battery: value};
68
+ default:
69
+ meta.logger.warn(`zigbee-herdsman-converters:WooxR7060: Unrecognized DP #${
70
+ dp} with data ${JSON.stringify(dpValue)}`);
71
+ }
72
+ },
73
+ },
55
74
  };
56
75
 
57
76
  const tzLocal = {
@@ -82,10 +101,10 @@ module.exports = [
82
101
  model: 'R7060',
83
102
  vendor: 'Woox',
84
103
  description: 'Smart garden irrigation control',
85
- fromZigbee: [fz.on_off, fz.ignore_tuya_set_time, fz.ignore_basic_report, fz.woox_R7060, fz.battery],
104
+ fromZigbee: [fz.on_off, fz.ignore_tuya_set_time, fz.ignore_basic_report, fzLocal.woox_R7060],
86
105
  toZigbee: [tz.on_off],
87
106
  onEvent: tuya.onEventSetTime,
88
- exposes: [e.switch(), e.battery(), e.battery_voltage()],
107
+ exposes: [e.switch(), e.battery()],
89
108
  meta: {disableDefaultResponse: true},
90
109
  },
91
110
  {
@@ -138,7 +157,7 @@ module.exports = [
138
157
  model: 'R7051',
139
158
  vendor: 'Woox',
140
159
  description: 'Smart siren',
141
- fromZigbee: [fz.battery, fz.ts0216_siren, fz.ias_alarm_only_alarm_1, fz.ts0219_power_source],
160
+ fromZigbee: [fz.battery, fz.ts0216_siren, fz.ias_alarm_only_alarm_1, fz.power_source],
142
161
  toZigbee: [tz.warning, tz.ts0216_volume],
143
162
  exposes: [e.battery(), e.battery_voltage(), e.warning(), exposes.binary('alarm', ea.STATE, true, false),
144
163
  exposes.binary('ac_connected', ea.STATE, true, false).withDescription('Is the device plugged in'),
package/devices/xiaomi.js CHANGED
@@ -466,7 +466,7 @@ module.exports = [
466
466
  fz.legacy.QBKG04LM_QBKG11LM_click, fz.xiaomi_basic, fz.xiaomi_operation_mode_basic,
467
467
  fz.legacy.QBKG11LM_click, fz.ignore_multistate_report, fz.xiaomi_power],
468
468
  exposes: [
469
- e.switch(), e.power().withAccess(ea.STATE_GET), e.device_temperature(),
469
+ e.switch(), e.power().withAccess(ea.STATE_GET), e.device_temperature(), e.energy(),
470
470
  e.action(['single', 'double', 'release', 'hold']),
471
471
  exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
472
472
  .withDescription('Decoupled mode'),
@@ -522,7 +522,7 @@ module.exports = [
522
522
  exposes: [
523
523
  e.switch().withEndpoint('left'),
524
524
  e.switch().withEndpoint('right'),
525
- e.device_temperature(),
525
+ e.device_temperature(), e.energy(),
526
526
  e.power().withAccess(ea.STATE_GET),
527
527
  e.action(['single_left', 'single_right', 'single_both', 'double_left', 'double_right', 'double_both',
528
528
  'hold_left', 'hold_right', 'hold_both', 'release_left', 'release_right', 'release_both']),
@@ -929,11 +929,15 @@ module.exports = [
929
929
  toZigbee: [tz.aqara_detection_interval, tz.aqara_motion_sensitivity, tz.RTCGQ14LM_trigger_indicator],
930
930
  exposes: [e.occupancy(), e.illuminance_lux().withProperty('illuminance'),
931
931
  e.illuminance().withUnit('lx').withDescription('Measured illuminance in lux'),
932
- exposes.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']),
932
+ exposes.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high'])
933
+ .withDescription('. Press pairing button right before changing this otherwise it will fail.'),
933
934
  exposes.numeric('detection_interval', ea.ALL).withValueMin(2).withValueMax(65535).withUnit('s')
934
- .withDescription('Time interval for detecting actions'),
935
+ .withDescription('Time interval for detecting actions. ' +
936
+ 'Press pairing button right before changing this otherwise it will fail.'),
935
937
  exposes.binary('trigger_indicator', ea.ALL, true, false).withDescription('When this option is enabled then ' +
936
- 'blue LED will blink once when motion is detected'), e.device_temperature(), e.battery(), e.battery_voltage()],
938
+ 'blue LED will blink once when motion is detected. ' +
939
+ 'Press pairing button right before changing this otherwise it will fail.'),
940
+ e.device_temperature(), e.battery(), e.battery_voltage()],
937
941
  meta: {battery: {voltageToPercentage: '3V_2850_3000_log'}},
938
942
  configure: async (device, coordinatorEndpoint, logger) => {
939
943
  const endpoint = device.getEndpoint(1);
@@ -1381,6 +1385,37 @@ module.exports = [
1381
1385
  },
1382
1386
  ota: ota.zigbeeOTA,
1383
1387
  },
1388
+ {
1389
+ zigbeeModel: ['lumi.curtain.acn003'],
1390
+ model: 'ZNCLBL01LM',
1391
+ vendor: 'Xiaomi',
1392
+ description: 'Aqara curtain driver E1',
1393
+ fromZigbee: [fz.battery, fz.xiaomi_curtain_position_tilt, fz.aqara_opple, fz.power_source],
1394
+ toZigbee: [tz.xiaomi_curtain_position_state, tz.ZNCLBL01LM_battery_voltage, tz.ZNCLBL01LM_hooks_state,
1395
+ tz.power_source, tz.battery_percentage_remaining],
1396
+ exposes: [e.cover_position().setAccess('state', ea.ALL), e.battery().withAccess(ea.STATE_GET),
1397
+ e.battery_voltage().withAccess(ea.STATE_GET),
1398
+ e.device_temperature(),
1399
+ e.action(['manual_open', 'manual_close']),
1400
+ exposes.enum('motor_state', ea.STATE, ['stopped', 'opening', 'closing'])
1401
+ .withDescription('Motor state'),
1402
+ exposes.binary('running', ea.STATE, true, false)
1403
+ .withDescription('Whether the motor is moving or not'),
1404
+ exposes.enum('hooks_state', ea.STATE_GET, ['unlocked', 'locked', 'locking', 'unlocking'])
1405
+ .withDescription('Hooks state'),
1406
+ exposes.numeric('target_position', ea.STATE).withUnit('%').withDescription('Target position'),
1407
+ exposes.enum('power_source', ea.STATE_GET, ['battery', 'dc_source']).withDescription('The current power source'),
1408
+ exposes.binary('charging', ea.STATE_GET, true, false).withDescription('The current charging state')],
1409
+ configure: async (device, coordinatorEndpoint, logger) => {
1410
+ const endpoint = device.getEndpoint(1);
1411
+ await endpoint.read('genPowerCfg', ['batteryPercentageRemaining']);
1412
+ await endpoint.read('aqaraOpple', [0x040B], {manufacturerCode: 0x115f});
1413
+ await endpoint.read('aqaraOpple', [0x0428], {manufacturerCode: 0x115f});
1414
+ await endpoint.read('genBasic', ['powerSource']);
1415
+ await endpoint.read('closuresWindowCovering', ['currentPositionLiftPercentage']);
1416
+ },
1417
+ ota: ota.zigbeeOTA,
1418
+ },
1384
1419
  {
1385
1420
  zigbeeModel: ['lumi.relay.c2acn01'],
1386
1421
  model: 'LLKZMK11LM',
package/devices/yale.js CHANGED
@@ -125,7 +125,7 @@ module.exports = [
125
125
  extend: lockExtend(),
126
126
  },
127
127
  {
128
- zigbeeModel: ['c700000202'],
128
+ zigbeeModel: ['c700000202', '06ffff2029'],
129
129
  model: 'YDF40',
130
130
  vendor: 'Yale',
131
131
  description: 'Real living lock / Intelligent biometric digital lock',
package/lib/exposes.js CHANGED
@@ -427,12 +427,6 @@ class Climate extends Base {
427
427
  return this;
428
428
  }
429
429
 
430
- withAwayMode() {
431
- assert(!this.endpoint, 'Cannot add feature after adding endpoint');
432
- this.features.push(new Binary('away_mode', access.STATE_SET, 'ON', 'OFF').withDescription('Away mode'));
433
- return this;
434
- }
435
-
436
430
  withPiHeatingDemand(access=a.STATE) {
437
431
  assert(!this.endpoint, 'Cannot add feature after adding endpoint');
438
432
  this.features.push(new Numeric('pi_heating_demand', access).withValueMin(0).withValueMax(100).withUnit('%').withDescription('Position of the valve (= demanded heat) where 0% is fully closed and 100% is fully open'));
package/lib/tuya.js CHANGED
@@ -695,6 +695,16 @@ const dataPoints = {
695
695
  lmsSensitivity: 9,
696
696
  lmsKeepTime: 10,
697
697
  lmsIlluminance: 12,
698
+ // Alecto SMART-SMOKE10
699
+ alectoSmokeState: 1,
700
+ alectoSmokeValue: 2,
701
+ alectoSelfChecking: 8,
702
+ alectoCheckingResult: 9,
703
+ alectoSmokeTest: 11,
704
+ alectoLifecycle: 12,
705
+ alectoBatteryState: 14,
706
+ alectoBatteryPercentage: 15,
707
+ alectoSilence: 16,
698
708
  };
699
709
 
700
710
  const thermostatWeekFormat = {
package/lib/xiaomi.js CHANGED
@@ -204,7 +204,7 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
204
204
  payload[`state_${mapping}`] = value === 1 ? 'ON' : 'OFF';
205
205
  } else if (['WXKG14LM', 'WXKG16LM', 'WXKG17LM'].includes(model.model)) {
206
206
  payload.click_mode = {1: 'fast', 2: 'multi'}[value];
207
- } else if (['WXCJKG11LM', 'WXCJKG12LM', 'WXCJKG13LM', 'ZNMS12LM', 'RTCGQ14LM'].includes(model.model)) {
207
+ } else if (['WXCJKG11LM', 'WXCJKG12LM', 'WXCJKG13LM', 'ZNMS12LM', 'RTCGQ14LM', 'ZNCLBL01LM'].includes(model.model)) {
208
208
  // We don't know what the value means for these devices.
209
209
  // https://github.com/Koenkk/zigbee2mqtt/issues/11126
210
210
  // https://github.com/Koenkk/zigbee2mqtt/issues/12279
@@ -265,6 +265,9 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
265
265
  }
266
266
  } else if (['ZNJLBL01LM', 'ZNCLDJ12LM'].includes(model.model)) {
267
267
  payload.battery = value;
268
+ } else if (['ZNCLBL01LM'].includes(model.model)) {
269
+ const battery = value / 2;
270
+ payload.battery = precisionRound(battery, 2);
268
271
  } else if (['RTCZCGQ11LM'].includes(model.model)) {
269
272
  payload.presence = {0: false, 1: true, 255: null}[value];
270
273
  }
@@ -308,6 +311,10 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
308
311
  case '107':
309
312
  if (['RTCGQ14LM'].includes(model.model)) {
310
313
  payload.trigger_indicator = value === 1;
314
+ } else if (['ZNCLBL01LM'].includes(model.model)) {
315
+ const position = options.invert_cover ? 100 - value : value;
316
+ payload.position = position;
317
+ payload.state = options.invert_cover ? (position > 0 ? 'CLOSE' : 'OPEN') : (position > 0 ? 'OPEN' : 'CLOSE');
311
318
  }
312
319
  break;
313
320
  case '149':
@@ -521,6 +528,41 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
521
528
  payload.battery = value;
522
529
  }
523
530
  break;
531
+ case '1035':
532
+ if (['ZNCLBL01LM'].includes(model.model)) {
533
+ payload.voltage = value;
534
+ }
535
+ break;
536
+ case '1055':
537
+ if (['ZNCLBL01LM'].includes(model.model)) {
538
+ payload.target_position = options.invert_cover ? 100 - value : value;
539
+ }
540
+ break;
541
+ case '1056':
542
+ if (['ZNCLBL01LM'].includes(model.model)) {
543
+ // This is the "target_state" attribute, which takes the following values: 0: 'OPEN', 1: 'CLOSE', 2: 'STOP'.
544
+ // It is not used because the values 0 and 1 are not always reported.
545
+ // https://github.com/Koenkk/zigbee-herdsman-converters/pull/4307
546
+ }
547
+ break;
548
+ case '1057':
549
+ if (['ZNCLBL01LM'].includes(model.model)) {
550
+ payload.motor_state = (options.invert_cover ? {0: 'opening', 1: 'closing', 2: 'stopped'} :
551
+ {0: 'closing', 1: 'opening', 2: 'stopped'})[value];
552
+ payload.running = value < 2 ? true : false;
553
+ }
554
+ break;
555
+ case '1061':
556
+ if (['ZNCLBL01LM'].includes(model.model)) {
557
+ payload.action = (options.invert_cover ? {1: 'manual_close', 2: 'manual_open'} :
558
+ {1: 'manual_open', 2: 'manual_close'})[value];
559
+ }
560
+ break;
561
+ case '1064':
562
+ if (['ZNCLBL01LM'].includes(model.model)) {
563
+ payload.hooks_state = {0: 'unlocked', 1: 'locked', 2: 'locking', 3: 'unlocking'}[value];
564
+ }
565
+ break;
524
566
  case '1289':
525
567
  payload.dimmer_mode = {3: 'rgbw', 1: 'dual_ct'}[value];
526
568
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.544",
3
+ "version": "14.0.547",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [