zigbee-herdsman-converters 14.0.692 → 14.0.693

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.
@@ -383,9 +383,11 @@ const converters = {
383
383
  device_temperature: {
384
384
  cluster: 'genDeviceTempCfg',
385
385
  type: ['attributeReport', 'readResponse'],
386
+ options: [exposes.options.calibration('device_temperature')],
386
387
  convert: (model, msg, publish, options, meta) => {
387
388
  if (msg.data.hasOwnProperty('currentTemperature')) {
388
- return {device_temperature: parseInt(msg.data['currentTemperature'])};
389
+ const value = parseInt(msg.data['currentTemperature']);
390
+ return {device_temperature: calibrateAndPrecisionRoundOptions(value, options, 'device_temperature')};
389
391
  }
390
392
  },
391
393
  },
@@ -648,7 +650,7 @@ const converters = {
648
650
  const result = converters.metering.convert(model, msg, publish, options, meta);
649
651
  // Filter incorrect 0 energy values reported by the device:
650
652
  // https://github.com/Koenkk/zigbee2mqtt/issues/7852
651
- if (result.energy === 0) {
653
+ if (result && result.energy === 0) {
652
654
  delete result.energy;
653
655
  }
654
656
  return result;
@@ -680,6 +682,17 @@ const converters = {
680
682
  */
681
683
  cluster: 'seMetering',
682
684
  type: ['attributeReport', 'readResponse'],
685
+ options: (definition) => {
686
+ const result = [];
687
+ if (definition.exposes.find((e) => e.name === 'power')) {
688
+ result.push(exposes.options.precision('power'), exposes.options.calibration('power', 'percentual'));
689
+ }
690
+ if (definition.exposes.find((e) => e.name === 'energy')) {
691
+ result.push(exposes.options.precision('energy'), exposes.options.calibration('energy', 'percentual'));
692
+ }
693
+ return result;
694
+ },
695
+
683
696
  convert: (model, msg, publish, options, meta) => {
684
697
  if (utils.hasAlreadyProcessedMessage(msg, model)) return;
685
698
  const payload = {};
@@ -692,7 +705,7 @@ const converters = {
692
705
  if (factor != null) {
693
706
  power = (power * factor) * 1000; // kWh to Watt
694
707
  }
695
- payload.power = precisionRound(power, 2);
708
+ payload.power = calibrateAndPrecisionRoundOptions(power, options, 'power');
696
709
  }
697
710
 
698
711
  if (factor != null && (msg.data.hasOwnProperty('currentSummDelivered') ||
@@ -708,7 +721,7 @@ const converters = {
708
721
  const value = (parseInt(data[0]) << 32) + parseInt(data[1]);
709
722
  energy -= value * factor;
710
723
  }
711
- payload.energy = precisionRound(energy, 2);
724
+ payload.energy = calibrateAndPrecisionRoundOptions(energy, options, 'energy');
712
725
  }
713
726
 
714
727
  return payload;
@@ -735,8 +748,11 @@ const converters = {
735
748
  */
736
749
  cluster: 'haElectricalMeasurement',
737
750
  type: ['attributeReport', 'readResponse'],
738
- options: [exposes.options.calibration('power', 'percentual'), exposes.options.calibration('current', 'percentual'),
739
- exposes.options.calibration('voltage', 'percentual')],
751
+ options: [
752
+ exposes.options.calibration('power', 'percentual'), exposes.options.precision('power'),
753
+ exposes.options.calibration('current', 'percentual'), exposes.options.precision('current'),
754
+ exposes.options.calibration('voltage', 'percentual'), exposes.options.precision('voltage'),
755
+ ],
740
756
  convert: (model, msg, publish, options, meta) => {
741
757
  if (utils.hasAlreadyProcessedMessage(msg, model)) return;
742
758
  const getFactor = (key) => {
@@ -5322,47 +5338,23 @@ const converters = {
5322
5338
  xiaomi_power: {
5323
5339
  cluster: 'genAnalogInput',
5324
5340
  type: ['attributeReport', 'readResponse'],
5341
+ options: [exposes.options.calibration('power', 'percentual'), exposes.options.precision('power')],
5325
5342
  convert: (model, msg, publish, options, meta) => {
5326
- return {power: precisionRound(msg.data['presentValue'], 2)};
5343
+ return {power: calibrateAndPrecisionRoundOptions(msg.data['presentValue'], options, 'power')};
5327
5344
  },
5328
5345
  },
5329
5346
  xiaomi_basic: {
5330
5347
  cluster: 'genBasic',
5331
5348
  type: ['attributeReport', 'readResponse'],
5332
- options: (definition) => {
5333
- const result = [];
5334
- if (definition.exposes.find((e) => e.name === 'temperature')) {
5335
- result.push(exposes.options.precision('temperature'), exposes.options.calibration('temperature'));
5336
- }
5337
- if (definition.exposes.find((e) => e.name === 'device_temperature')) {
5338
- result.push(exposes.options.calibration('device_temperature'));
5339
- }
5340
- if (definition.exposes.find((e) => e.name === 'illuminance')) {
5341
- result.push(exposes.options.calibration('illuminance', 'percentual'));
5342
- }
5343
- if (definition.exposes.find((e) => e.name === 'illuminance_lux')) {
5344
- result.push(exposes.options.calibration('illuminance_lux', 'percentual'));
5345
- }
5346
- return result;
5347
- },
5349
+ options: xiaomi.numericAttributes2Options,
5348
5350
  convert: (model, msg, publish, options, meta) => {
5349
- const payload = xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
5350
- return payload;
5351
+ return xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
5351
5352
  },
5352
5353
  },
5353
5354
  xiaomi_basic_raw: {
5354
5355
  cluster: 'genBasic',
5355
5356
  type: ['raw'],
5356
- options: (definition) => {
5357
- const result = [];
5358
- if (definition.exposes.find((e) => e.name === 'temperature')) {
5359
- result.push(exposes.options.precision('temperature'), exposes.options.calibration('temperature'));
5360
- }
5361
- if (definition.exposes.find((e) => e.name === 'device_temperature')) {
5362
- result.push(exposes.options.calibration('device_temperature'));
5363
- }
5364
- return result;
5365
- },
5357
+ options: xiaomi.numericAttributes2Options,
5366
5358
  convert: (model, msg, publish, options, meta) => {
5367
5359
  let payload = {};
5368
5360
  if (Buffer.isBuffer(msg.data)) {
@@ -5375,22 +5367,9 @@ const converters = {
5375
5367
  aqara_opple: {
5376
5368
  cluster: 'aqaraOpple',
5377
5369
  type: ['attributeReport', 'readResponse'],
5378
- options: (definition) => {
5379
- const result = [];
5380
- if (definition.exposes.find((e) => e.name === 'temperature')) {
5381
- result.push(exposes.options.precision('temperature'), exposes.options.calibration('temperature'));
5382
- }
5383
- if (definition.exposes.find((e) => e.name === 'device_temperature')) {
5384
- result.push(exposes.options.calibration('device_temperature'));
5385
- }
5386
- if (definition.exposes.find((e) => e.name === 'illuminance')) {
5387
- result.push(exposes.options.calibration('illuminance', 'percentual'));
5388
- }
5389
- return result;
5390
- },
5370
+ options: xiaomi.numericAttributes2Options,
5391
5371
  convert: (model, msg, publish, options, meta) => {
5392
- const payload = xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
5393
- return payload;
5372
+ return xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
5394
5373
  },
5395
5374
  },
5396
5375
  xiaomi_on_off_action: {
@@ -2588,6 +2588,18 @@ const converters = {
2588
2588
  }
2589
2589
  },
2590
2590
  },
2591
+ xiaomi_curtain_battery_voltage: {
2592
+ key: ['voltage'],
2593
+ convertGet: async (entity, key, meta) => {
2594
+ switch (meta.mapped.model) {
2595
+ case 'ZNCLBL01LM':
2596
+ await entity.read('aqaraOpple', [0x040B], manufacturerOptions.xiaomi);
2597
+ break;
2598
+ default:
2599
+ throw new Error(`xiaomi_curtain_battery_voltage - unsupported model: ${meta.mapped.model}`);
2600
+ }
2601
+ },
2602
+ },
2591
2603
  xiaomi_curtain_acn002_charging_status: {
2592
2604
  key: ['charging_status'],
2593
2605
  convertGet: async (entity, key, meta) => {
@@ -6513,10 +6525,12 @@ const converters = {
6513
6525
  }
6514
6526
  },
6515
6527
  },
6516
- ZNCLBL01LM_battery_voltage: {
6517
- key: ['voltage'],
6518
- convertGet: async (entity, key, meta) => {
6519
- await entity.read('aqaraOpple', [0x040B], manufacturerOptions.xiaomi);
6528
+ ZNCLBL01LM_hooks_lock: {
6529
+ key: ['hooks_lock'],
6530
+ convertSet: async (entity, key, value, meta) => {
6531
+ const lookup = {'UNLOCK': 0, 'LOCK': 1};
6532
+ await entity.write('aqaraOpple', {0x0427: {value: lookup[value], type: 0x20}}, manufacturerOptions.xiaomi);
6533
+ return {state: {[key]: value}};
6520
6534
  },
6521
6535
  },
6522
6536
  ZNCLBL01LM_hooks_state: {
@@ -6525,6 +6539,32 @@ const converters = {
6525
6539
  await entity.read('aqaraOpple', [0x0428], manufacturerOptions.xiaomi);
6526
6540
  },
6527
6541
  },
6542
+ ZNCLBL01LM_hand_open: {
6543
+ key: ['hand_open'],
6544
+ convertSet: async (entity, key, value, meta) => {
6545
+ await entity.write('aqaraOpple', {0x0401: {value: !value, type: 0x10}}, manufacturerOptions.xiaomi);
6546
+ },
6547
+ convertGet: async (entity, key, meta) => {
6548
+ await entity.read('aqaraOpple', [0x0401], manufacturerOptions.xiaomi);
6549
+ },
6550
+ },
6551
+ ZNCLBL01LM_limits_calibration: {
6552
+ key: ['limits_calibration'],
6553
+ convertSet: async (entity, key, value, meta) => {
6554
+ switch (value) {
6555
+ case 'start':
6556
+ await entity.write('aqaraOpple', {0x0407: {value: 0x01, type: 0x20}}, manufacturerOptions.xiaomi);
6557
+ break;
6558
+ case 'end':
6559
+ await entity.write('aqaraOpple', {0x0407: {value: 0x02, type: 0x20}}, manufacturerOptions.xiaomi);
6560
+ break;
6561
+ case 'reset':
6562
+ await entity.write('aqaraOpple', {0x0407: {value: 0x00, type: 0x20}}, manufacturerOptions.xiaomi);
6563
+ // also? await entity.write('aqaraOpple', {0x0402: {value: 0x00, type: 0x10}}, manufacturerOptions.xiaomi);
6564
+ break;
6565
+ }
6566
+ },
6567
+ },
6528
6568
  wiser_vact_calibrate_valve: {
6529
6569
  key: ['calibrate_valve'],
6530
6570
  convertSet: async (entity, key, value, meta) => {
package/devices/namron.js CHANGED
@@ -5,6 +5,7 @@ const tz = require('../converters/toZigbee');
5
5
  const constants = require('../lib/constants');
6
6
  const reporting = require('../lib/reporting');
7
7
  const globalStore = require('../lib/store');
8
+ const ota = require('../lib/ota');
8
9
  const utils = require('../lib/utils');
9
10
  const extend = require('../lib/extend');
10
11
  const ea = exposes.access;
@@ -159,6 +160,7 @@ module.exports = [
159
160
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
160
161
  await reporting.onOff(endpoint);
161
162
  },
163
+ ota: ota.zigbeeOTA,
162
164
  },
163
165
  {
164
166
  zigbeeModel: ['1402755'],
@@ -190,6 +192,7 @@ module.exports = [
190
192
  endpoint: (device) => {
191
193
  return {l1: 1, l2: 2, l3: 3, l4: 4};
192
194
  },
195
+ ota: ota.zigbeeOTA,
193
196
  },
194
197
  {
195
198
  zigbeeModel: ['4512721'],
@@ -207,6 +210,7 @@ module.exports = [
207
210
  endpoint: (device) => {
208
211
  return {l1: 1, l2: 2, l3: 3, l4: 4};
209
212
  },
213
+ ota: ota.zigbeeOTA,
210
214
  },
211
215
  {
212
216
  zigbeeModel: ['4512701'],
@@ -231,7 +235,7 @@ module.exports = [
231
235
  zigbeeModel: ['4512719'],
232
236
  model: '4512719',
233
237
  vendor: 'Namron',
234
- description: 'Zigbee 2 channel switch K4 white',
238
+ description: 'Zigbee 2 channel switch K4 (white)',
235
239
  fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop],
236
240
  meta: {multiEndpoint: true},
237
241
  exposes: [e.battery(), e.action(['on_l1', 'off_l1', 'brightness_move_up_l1', 'brightness_move_down_l1', 'brightness_stop_l1',
@@ -240,6 +244,7 @@ module.exports = [
240
244
  endpoint: (device) => {
241
245
  return {l1: 1, l2: 2};
242
246
  },
247
+ ota: ota.zigbeeOTA,
243
248
  },
244
249
  {
245
250
  zigbeeModel: ['4512726'],
@@ -259,12 +264,13 @@ module.exports = [
259
264
  await reporting.batteryPercentageRemaining(endpoint);
260
265
  await reporting.batteryVoltage(endpoint);
261
266
  },
267
+ ota: ota.zigbeeOTA,
262
268
  },
263
269
  {
264
270
  zigbeeModel: ['4512729'],
265
271
  model: '4512729',
266
272
  vendor: 'Namron',
267
- description: 'Zigbee 2 channel switch K4 white',
273
+ description: 'Zigbee 2 channel switch K4 (black)',
268
274
  fromZigbee: [fz.command_on, fz.command_off, fz.battery, fz.command_move, fz.command_stop],
269
275
  meta: {multiEndpoint: true},
270
276
  exposes: [e.battery(), e.action(['on_l1', 'off_l1', 'brightness_move_up_l1', 'brightness_move_down_l1', 'brightness_stop_l1',
@@ -273,6 +279,7 @@ module.exports = [
273
279
  endpoint: (device) => {
274
280
  return {l1: 1, l2: 2};
275
281
  },
282
+ ota: ota.zigbeeOTA,
276
283
  },
277
284
  {
278
285
  zigbeeModel: ['4512706'],
@@ -589,6 +596,7 @@ module.exports = [
589
596
  await endpoint.read('hvacThermostat', [0x1008, 0x1009, 0x100A, 0x100B], options);
590
597
  await endpoint.read('hvacThermostat', [0x2001, 0x2002], options);
591
598
  },
599
+ ota: ota.zigbeeOTA,
592
600
  },
593
601
  {
594
602
  zigbeeModel: ['4512735'],
@@ -769,7 +769,7 @@ module.exports = [
769
769
  extend: hueExtend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 500]}),
770
770
  },
771
771
  {
772
- zigbeeModel: ['LCG002'],
772
+ zigbeeModel: ['LCG002', '929003047701'],
773
773
  model: '929001953101',
774
774
  vendor: 'Philips',
775
775
  description: 'Hue White and Color Ambiance GU10',
@@ -2013,11 +2013,7 @@ module.exports = [
2013
2013
  exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(0).withValueMax(65535)],
2014
2014
  toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity, tz.hue_motion_led_indication],
2015
2015
  endpoint: (device) => {
2016
- return {
2017
- 'default': 2, // default
2018
- 'ep1': 1,
2019
- 'ep2': 2, // e.g. for write to msOccupancySensing
2020
- };
2016
+ return {'default': 2, 'ep1': 1, 'ep2': 2};
2021
2017
  },
2022
2018
  configure: async (device, coordinatorEndpoint, logger) => {
2023
2019
  const endpoint = device.getEndpoint(2);
@@ -2046,11 +2042,7 @@ module.exports = [
2046
2042
  exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(0).withValueMax(65535)],
2047
2043
  toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity, tz.hue_motion_led_indication],
2048
2044
  endpoint: (device) => {
2049
- return {
2050
- 'default': 2, // default
2051
- 'ep1': 1,
2052
- 'ep2': 2, // e.g. for write to msOccupancySensing
2053
- };
2045
+ return {'default': 2, 'ep1': 1, 'ep2': 2};
2054
2046
  },
2055
2047
  configure: async (device, coordinatorEndpoint, logger) => {
2056
2048
  const endpoint = device.getEndpoint(2);
@@ -2078,13 +2070,6 @@ module.exports = [
2078
2070
  exposes.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'),
2079
2071
  exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(0).withValueMax(65535)],
2080
2072
  toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity, tz.hue_motion_led_indication],
2081
- endpoint: (device) => {
2082
- return {
2083
- 'default': 2, // default
2084
- 'ep1': 1,
2085
- 'ep2': 2, // e.g. for write to msOccupancySensing
2086
- };
2087
- },
2088
2073
  configure: async (device, coordinatorEndpoint, logger) => {
2089
2074
  const endpoint = device.getEndpoint(2);
2090
2075
  const binds = ['genPowerCfg', 'msIlluminanceMeasurement', 'msTemperatureMeasurement', 'msOccupancySensing'];
@@ -2111,13 +2096,6 @@ module.exports = [
2111
2096
  exposes.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'),
2112
2097
  exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(0).withValueMax(65535)],
2113
2098
  toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity, tz.hue_motion_led_indication],
2114
- endpoint: (device) => {
2115
- return {
2116
- 'default': 2, // default
2117
- 'ep1': 1,
2118
- 'ep2': 2, // e.g. for write to msOccupancySensing
2119
- };
2120
- },
2121
2099
  configure: async (device, coordinatorEndpoint, logger) => {
2122
2100
  const endpoint = device.getEndpoint(2);
2123
2101
  const binds = ['genPowerCfg', 'msIlluminanceMeasurement', 'msTemperatureMeasurement', 'msOccupancySensing'];
@@ -3011,6 +2989,13 @@ module.exports = [
3011
2989
  description: 'Hue Gradient Signe floor lamp (white)',
3012
2990
  extend: hueExtend.light_onoff_brightness_colortemp_color_gradient({colorTempRange: [153, 500]}),
3013
2991
  },
2992
+ {
2993
+ zigbeeModel: ['915005987301'],
2994
+ model: '915005987301',
2995
+ vendor: 'Philips',
2996
+ description: 'Hue Gradient Signe table lamp (white)',
2997
+ extend: hueExtend.light_onoff_brightness_colortemp_color_gradient({colorTempRange: [153, 500]}),
2998
+ },
3014
2999
  {
3015
3000
  zigbeeModel: ['929003526301'],
3016
3001
  model: '929003526301',
@@ -3032,4 +3017,11 @@ module.exports = [
3032
3017
  description: 'Hue white ambiance Pillar spotlight with Bluetooth (black)',
3033
3018
  extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
3034
3019
  },
3020
+ {
3021
+ zigbeeModel: ['8719514288232'],
3022
+ model: '929002469216',
3023
+ vendor: 'Philips',
3024
+ description: 'Hue white E27 1100lm with Bluetooth',
3025
+ extend: hueExtend.light_onoff_brightness(),
3026
+ },
3035
3027
  ];
package/devices/sinope.js CHANGED
@@ -1017,4 +1017,26 @@ module.exports = [
1017
1017
  } catch (error) {/* Do Nothing */}
1018
1018
  },
1019
1019
  },
1020
+ {
1021
+ zigbeeModel: ['RM3500ZB'],
1022
+ model: 'RM3500ZB',
1023
+ vendor: 'Sinopé',
1024
+ description: 'Calypso smart water heater controller',
1025
+ fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ias_water_leak_alarm_1, fz.temperature],
1026
+ toZigbee: [tz.on_off],
1027
+ exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.water_leak(), e.temperature()],
1028
+ configure: async (device, coordinatorEndpoint) => {
1029
+ const endpoint = device.getEndpoint(1);
1030
+ const binds = ['genOnOff', 'haElectricalMeasurement', 'seMetering', 'Temperature'];
1031
+ await reporting.bind(endpoint, coordinatorEndpoint, binds);
1032
+ await reporting.onOff(endpoint);
1033
+ await reporting.readEletricalMeasurementMultiplierDivisors(endpoint);
1034
+ await reporting.activePower(endpoint);
1035
+ await reporting.rmsCurrent(endpoint);
1036
+ await reporting.rmsVoltage(endpoint);
1037
+ await reporting.readMeteringMultiplierDivisor(endpoint);
1038
+ await reporting.currentSummDelivered(endpoint);
1039
+ await reporting.temperature(endpoint, {min: 60, max: 3600, change: 1});
1040
+ },
1041
+ },
1020
1042
  ];
@@ -102,17 +102,12 @@ module.exports = [
102
102
  model: '3RSP019BZ',
103
103
  vendor: 'Third Reality',
104
104
  description: 'Zigbee / BLE smart plug',
105
- fromZigbee: [fz.on_off, fz.electrical_measurement],
106
- toZigbee: [tz.on_off],
105
+ extend: extend.switch(),
107
106
  ota: ota.zigbeeOTA,
108
- exposes: [e.switch(), e.power(), e.current(), e.voltage()],
109
107
  configure: async (device, coordinatorEndpoint, logger) => {
110
108
  const endpoint = device.getEndpoint(1);
111
- await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement']);
109
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
112
110
  await reporting.onOff(endpoint);
113
- await reporting.activePower(endpoint);
114
- await reporting.rmsCurrent(endpoint);
115
- await reporting.rmsVoltage(endpoint);
116
111
  },
117
112
  },
118
113
  {
@@ -158,4 +153,21 @@ module.exports = [
158
153
  exposes: [e.battery(), e.temperature(), e.humidity(), e.battery_voltage()],
159
154
  ota: ota.zigbeeOTA,
160
155
  },
156
+ {
157
+ zigbeeModel: ['3RSP02028BZ'],
158
+ model: '3RSP02028BZ',
159
+ vendor: 'Third Reality',
160
+ description: 'Zigbee / BLE smart plug with power',
161
+ fromZigbee: [fz.on_off, fz.electrical_measurement],
162
+ toZigbee: [tz.on_off],
163
+ exposes: [e.switch(), e.power(), e.current(), e.voltage()],
164
+ configure: async (device, coordinatorEndpoint, logger) => {
165
+ const endpoint = device.getEndpoint(1);
166
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement']);
167
+ await reporting.onOff(endpoint);
168
+ await reporting.activePower(endpoint);
169
+ await reporting.rmsCurrent(endpoint);
170
+ await reporting.rmsVoltage(endpoint);
171
+ },
172
+ },
161
173
  ];
package/devices/tuya.js CHANGED
@@ -989,7 +989,8 @@ module.exports = [
989
989
  {vendor: 'Zemismart', model: 'LXZB-ZB-09A', description: 'Zemismart LED Surface Mounted Downlight 9W RGBW'},
990
990
  {vendor: 'Feconn', model: 'FE-GU10-5W', description: 'Zigbee GU10 5W smart bulb'},
991
991
  ],
992
- extend: extend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 500], disableColorTempStartup: true}),
992
+ extend: extend.light_onoff_brightness_colortemp_color(
993
+ {colorTempRange: [153, 500], disableColorTempStartup: true, disablePowerOnBehavior: true}),
993
994
  meta: {applyRedFix: true, enhancedHue: false},
994
995
  },
995
996
  {
@@ -998,7 +999,7 @@ module.exports = [
998
999
  vendor: 'TuYa',
999
1000
  description: 'Zigbee RGB light',
1000
1001
  whiteLabel: [{vendor: 'BTF-Lighting', model: 'C03Z'}],
1001
- extend: extend.light_onoff_brightness_color(),
1002
+ extend: extend.light_onoff_brightness_color({disablePowerOnBehavior: true}),
1002
1003
  // Requires red fix: https://github.com/Koenkk/zigbee2mqtt/issues/5962#issue-796462106
1003
1004
  meta: {applyRedFix: true, enhancedHue: false},
1004
1005
  },
@@ -1007,7 +1008,7 @@ module.exports = [
1007
1008
  model: 'TS0504B',
1008
1009
  vendor: 'TuYa',
1009
1010
  description: 'Zigbee RGBW light',
1010
- extend: extend.light_onoff_brightness_color(),
1011
+ extend: extend.light_onoff_brightness_color({disablePowerOnBehavior: true}),
1011
1012
  toZigbee: utils.replaceInArray(extend.light_onoff_brightness_color().toZigbee, [tz.light_color], [tzLocal.TS0504B_color]),
1012
1013
  meta: {applyRedFix: true},
1013
1014
  },
@@ -1016,14 +1017,14 @@ module.exports = [
1016
1017
  model: 'TS0501A',
1017
1018
  description: 'Zigbee light',
1018
1019
  vendor: 'TuYa',
1019
- extend: extend.light_onoff_brightness(),
1020
+ extend: extend.light_onoff_brightness({disablePowerOnBehavior: true}),
1020
1021
  },
1021
1022
  {
1022
1023
  zigbeeModel: ['TS0501B'],
1023
1024
  model: 'TS0501B',
1024
1025
  description: 'Zigbee light',
1025
1026
  vendor: 'TuYa',
1026
- extend: extend.light_onoff_brightness(),
1027
+ extend: extend.light_onoff_brightness({disablePowerOnBehavior: true}),
1027
1028
  },
1028
1029
  {
1029
1030
  fingerprint: tuya.fingerprint('TS0202', ['_TZ3210_cwamkvua']),
@@ -1558,7 +1559,7 @@ module.exports = [
1558
1559
  model: 'TS0503A',
1559
1560
  vendor: 'TuYa',
1560
1561
  description: 'Led strip controller',
1561
- extend: extend.light_onoff_brightness_color(),
1562
+ extend: extend.light_onoff_brightness_color({disablePowerOnBehavior: true}),
1562
1563
  meta: {applyRedFix: true},
1563
1564
  },
1564
1565
  {
@@ -1575,7 +1576,7 @@ module.exports = [
1575
1576
  model: 'TS0502A',
1576
1577
  vendor: 'TuYa',
1577
1578
  description: 'Light controller',
1578
- extend: extend.light_onoff_brightness_colortemp(),
1579
+ extend: extend.light_onoff_brightness_colortemp({disablePowerOnBehavior: true}),
1579
1580
  },
1580
1581
  {
1581
1582
  zigbeeModel: ['TS0502B'],
@@ -1586,14 +1587,14 @@ module.exports = [
1586
1587
  {vendor: 'Mercator Ikuü', model: 'SMI7040', description: 'Ford Batten Light'},
1587
1588
  {vendor: 'Mercator Ikuü', model: 'SMD9300', description: 'Donovan Panel Light'},
1588
1589
  ],
1589
- extend: extend.light_onoff_brightness_colortemp({colorTempRange: [153, 500]}),
1590
+ extend: extend.light_onoff_brightness_colortemp({colorTempRange: [153, 500], disablePowerOnBehavior: true}),
1590
1591
  },
1591
1592
  {
1592
1593
  zigbeeModel: ['TS0504A'],
1593
1594
  model: 'TS0504A',
1594
1595
  vendor: 'TuYa',
1595
1596
  description: 'RGBW LED controller',
1596
- extend: extend.light_onoff_brightness_colortemp_color(),
1597
+ extend: extend.light_onoff_brightness_colortemp_color({disablePowerOnBehavior: true}),
1597
1598
  },
1598
1599
  {
1599
1600
  fingerprint: [{modelID: 'TS0505A', manufacturerName: '_TZ3000_sosdczdl'}],
@@ -1609,7 +1610,7 @@ module.exports = [
1609
1610
  model: 'TS0505A',
1610
1611
  vendor: 'TuYa',
1611
1612
  description: 'RGB+CCT light controller',
1612
- extend: extend.light_onoff_brightness_colortemp_color(),
1613
+ extend: extend.light_onoff_brightness_colortemp_color({disablePowerOnBehavior: true}),
1613
1614
  },
1614
1615
  {
1615
1616
  fingerprint: [{manufacturerName: '_TZ2000_a476raq2'}],
@@ -3657,7 +3658,7 @@ module.exports = [
3657
3658
  },
3658
3659
  },
3659
3660
  {
3660
- fingerprint: [{modelID: 'TS110E', manufacturerName: '_TZ3210_zxbtub8r'}],
3661
+ fingerprint: tuya.fingerprint('TS110E', ['_TZ3210_zxbtub8r', '_TZ3210_k1msuvg6']),
3661
3662
  model: 'TS110E_1gang_1',
3662
3663
  vendor: 'TuYa',
3663
3664
  description: '1 channel dimmer',
package/devices/xiaomi.js CHANGED
@@ -1939,22 +1939,38 @@ module.exports = [
1939
1939
  vendor: 'Xiaomi',
1940
1940
  whiteLabel: [{vendor: 'Xiaomi', model: 'CM-M01'}],
1941
1941
  description: 'Aqara curtain driver E1',
1942
- fromZigbee: [fz.battery, fz.xiaomi_curtain_position_tilt, fz.aqara_opple, fz.power_source],
1943
- toZigbee: [tz.xiaomi_curtain_position_state, tz.ZNCLBL01LM_battery_voltage, tz.ZNCLBL01LM_hooks_state,
1944
- tz.power_source, tz.battery_percentage_remaining],
1945
- exposes: [e.cover_position().setAccess('state', ea.ALL), e.battery().withAccess(ea.STATE_GET),
1942
+ fromZigbee: [
1943
+ fz.battery,
1944
+ fz.xiaomi_curtain_position_tilt,
1945
+ fz.aqara_opple,
1946
+ fz.power_source,
1947
+ ],
1948
+ toZigbee: [
1949
+ tz.xiaomi_curtain_position_state,
1950
+ tz.xiaomi_curtain_battery_voltage,
1951
+ tz.ZNCLBL01LM_hooks_lock,
1952
+ tz.ZNCLBL01LM_hooks_state,
1953
+ tz.ZNCLBL01LM_hand_open,
1954
+ tz.ZNCLBL01LM_limits_calibration,
1955
+ tz.power_source,
1956
+ tz.battery_percentage_remaining,
1957
+ ],
1958
+ exposes: [
1959
+ e.cover_position().setAccess('state', ea.ALL),
1960
+ exposes.binary('hand_open', ea.ALL, true, false).withDescription('Pulling curtains by hand starts the motor'),
1961
+ exposes.enum('limits_calibration', ea.SET, ['start', 'end', 'reset']).withDescription('Calibrate the position limits'),
1962
+ e.battery().withAccess(ea.STATE_GET),
1946
1963
  e.battery_voltage().withAccess(ea.STATE_GET),
1947
1964
  e.device_temperature(),
1948
1965
  e.action(['manual_open', 'manual_close']),
1949
- exposes.enum('motor_state', ea.STATE, ['stopped', 'opening', 'closing', 'pause'])
1950
- .withDescription('Motor state'),
1951
- exposes.binary('running', ea.STATE, true, false)
1952
- .withDescription('Whether the motor is moving or not'),
1953
- exposes.enum('hooks_state', ea.STATE_GET, ['unlocked', 'locked', 'locking', 'unlocking'])
1954
- .withDescription('Hooks state'),
1966
+ exposes.enum('motor_state', ea.STATE, ['stopped', 'opening', 'closing', 'pause']).withDescription('Motor state'),
1967
+ exposes.binary('running', ea.STATE, true, false).withDescription('Whether the motor is moving or not'),
1968
+ exposes.enum('hooks_lock', ea.STATE_SET, ['LOCK', 'UNLOCK']).withDescription('Lock the curtain driver hooks'),
1969
+ exposes.enum('hooks_state', ea.STATE_GET, ['unlocked', 'locked', 'locking', 'unlocking']).withDescription('Hooks state'),
1955
1970
  exposes.numeric('target_position', ea.STATE).withUnit('%').withDescription('Target position'),
1956
1971
  exposes.enum('power_source', ea.STATE_GET, ['battery', 'dc_source']).withDescription('The current power source'),
1957
- exposes.binary('charging', ea.STATE_GET, true, false).withDescription('The current charging state')],
1972
+ exposes.binary('charging', ea.STATE_GET, true, false).withDescription('The current charging state'),
1973
+ ],
1958
1974
  configure: async (device, coordinatorEndpoint, logger) => {
1959
1975
  const endpoint = device.getEndpoint(1);
1960
1976
  await endpoint.read('genPowerCfg', ['batteryPercentageRemaining']);
package/lib/utils.js CHANGED
@@ -77,10 +77,11 @@ function hasAlreadyProcessedMessage(msg, model, ID=null, key=null) {
77
77
 
78
78
  const calibrateAndPrecisionRoundOptionsDefaultPrecision = {
79
79
  temperature: 2, humidity: 2, pressure: 1, pm25: 0, power: 2, current: 2, current_phase_b: 2, current_phase_c: 2,
80
- voltage: 2, voltage_phase_b: 2, voltage_phase_c: 2, power_phase_b: 2, power_phase_c: 2,
80
+ voltage: 2, voltage_phase_b: 2, voltage_phase_c: 2, power_phase_b: 2, power_phase_c: 2, energy: 2,
81
81
  };
82
82
  function calibrateAndPrecisionRoundOptionsIsPercentual(type) {
83
- return type.startsWith('current') || type.startsWith('voltage') || type.startsWith('power') || type.startsWith('illuminance');
83
+ return type.startsWith('current') || type.startsWith('energy') || type.startsWith('voltage') || type.startsWith('power') ||
84
+ type.startsWith('illuminance');
84
85
  }
85
86
  function calibrateAndPrecisionRoundOptions(number, options, type) {
86
87
  // Calibrate
package/lib/xiaomi.js CHANGED
@@ -3,11 +3,14 @@
3
3
  const {
4
4
  batteryVoltageToPercentage,
5
5
  calibrateAndPrecisionRoundOptions,
6
+ calibrateAndPrecisionRoundOptionsIsPercentual,
6
7
  postfixWithEndpointName,
7
8
  precisionRound,
8
9
  getKey,
9
10
  } = require('./utils');
10
11
 
12
+ const exposes = require('../lib/exposes');
13
+
11
14
  const buffer2DataObject = (meta, model, buffer) => {
12
15
  const dataObject = {};
13
16
 
@@ -337,27 +340,27 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
337
340
  }
338
341
  break;
339
342
  case '149':
340
- payload.energy = precisionRound(value, 2); // 0x95
343
+ payload.energy = calibrateAndPrecisionRoundOptions(value, options, 'energy'); // 0x95
341
344
  // Consumption is deprecated
342
345
  payload.consumption = payload.energy;
343
346
  break;
344
347
  case '150':
345
348
  if (!['JTYJ-GD-01LM/BW'].includes(model.model)) {
346
- payload.voltage = precisionRound(value * 0.1, 1); // 0x96
349
+ payload.voltage = calibrateAndPrecisionRoundOptions(value * 0.1, options, 'voltage'); // 0x96
347
350
  }
348
351
  break;
349
352
  case '151':
350
353
  if (['LLKZMK11LM'].includes(model.model)) {
351
- payload.current = precisionRound(value, 4);
354
+ payload.current = calibrateAndPrecisionRoundOptions(value, options, 'current');
352
355
  } else {
353
- payload.current = precisionRound(value * 0.001, 4);
356
+ payload.current = calibrateAndPrecisionRoundOptions(value * 0.001, options, 'current');
354
357
  }
355
358
  break;
356
359
  case '152':
357
360
  if (['DJT11LM'].includes(model.model)) {
358
361
  // We don't know what implies for this device, it contains values like 30, 50,... that don't seem to change
359
362
  } else {
360
- payload.power = precisionRound(value, 2); // 0x98
363
+ payload.power = calibrateAndPrecisionRoundOptions(value, options, 'power'); // 0x98
361
364
  }
362
365
  break;
363
366
  case '154':
@@ -545,10 +548,15 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
545
548
  payload.button_switch_mode = value === 1 ? 'relay_and_usb' : 'relay';
546
549
  break;
547
550
  case '1025':
548
- payload.options = {...payload.options, // next values update only when curtain finished initial setup and knows current position
549
- reverse_direction: value[2] == '\u0001',
550
- hand_open: value[5] == '\u0000',
551
- };
551
+ if (['ZNCLBL01LM'].includes(model.model)) {
552
+ payload.hand_open = !value;
553
+ } else {
554
+ // next values update only when curtain finished initial setup and knows current position
555
+ payload.options = {...payload.options,
556
+ reverse_direction: value[2] == '\u0001',
557
+ hand_open: value[5] == '\u0000',
558
+ };
559
+ }
552
560
  break;
553
561
  case '1028':
554
562
  payload = {...payload,
@@ -597,9 +605,15 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
597
605
  {1: 'manual_open', 2: 'manual_close'})[value];
598
606
  }
599
607
  break;
608
+ case '1063':
609
+ if (['ZNCLBL01LM'].includes(model.model)) {
610
+ payload.hooks_lock = {0: 'UNLOCK', 1: 'LOCK'}[value];
611
+ }
612
+ break;
600
613
  case '1064':
601
614
  if (['ZNCLBL01LM'].includes(model.model)) {
602
615
  payload.hooks_state = {0: 'unlocked', 1: 'locked', 2: 'locking', 3: 'unlocking'}[value];
616
+ payload.hooks_lock = {0: 'UNLOCK', 1: 'LOCK', 2: 'UNLOCK', 3: 'LOCK'}[value];
603
617
  }
604
618
  break;
605
619
  case '1289':
@@ -650,8 +664,29 @@ const VOCKQJK11LMDisplayUnit = {
650
664
  'ppb_fahrenheit': 0x11, // ppb, °F
651
665
  };
652
666
 
667
+ const numericAttributes2Options = (definition) => {
668
+ const supported = ['temperature', 'device_temperature', 'illuminance', 'illuminance_lux',
669
+ 'pressure', 'power', 'current', 'voltage', 'energy', 'power'];
670
+ const precisionSupported = ['temperature', 'humidity', 'pressure', 'power', 'current', 'voltage', 'energy', 'power'];
671
+ const result = [];
672
+ for (const expose of definition.exposes) {
673
+ // only eletrical measurement voltage is supported, not battery
674
+ const isBatteryVoltage = expose.name === 'voltage' && definition.meta && definition.meta.battery;
675
+ if (supported.includes(expose.name) && !isBatteryVoltage) {
676
+ const type = calibrateAndPrecisionRoundOptionsIsPercentual(expose.name) ? 'percentual' : 'absolute';
677
+ result.push(exposes.options.calibration(expose.name, type));
678
+ if (precisionSupported.includes(expose.name)) {
679
+ result.push(exposes.options.precision(expose.name));
680
+ }
681
+ }
682
+ }
683
+
684
+ return result;
685
+ };
686
+
653
687
  module.exports = {
654
688
  buffer2DataObject,
655
689
  numericAttributes2Payload,
690
+ numericAttributes2Options,
656
691
  VOCKQJK11LMDisplayUnit,
657
692
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.692",
3
+ "version": "14.0.693",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [