zigbee-herdsman-converters 14.0.368 → 14.0.372

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.
@@ -1164,7 +1164,8 @@ const converters = {
1164
1164
 
1165
1165
  if (options.simulated_brightness) {
1166
1166
  globalStore.putValue(msg.endpoint, 'simulated_brightness_brightness', msg.data.level);
1167
- payload.brightness = msg.data.level;
1167
+ const property = postfixWithEndpointName('brightness', msg, model);
1168
+ payload[property] = msg.data.level;
1168
1169
  }
1169
1170
 
1170
1171
  return payload;
@@ -1195,7 +1196,8 @@ const converters = {
1195
1196
  brightness += delta;
1196
1197
  brightness = numberWithinRange(brightness, 0, 255);
1197
1198
  globalStore.putValue(msg.endpoint, 'simulated_brightness_brightness', brightness);
1198
- publish({brightness});
1199
+ const property = postfixWithEndpointName('brightness', msg, model);
1200
+ publish({[property]: brightness});
1199
1201
  }, intervalOpts);
1200
1202
 
1201
1203
  globalStore.putValue(msg.endpoint, 'simulated_brightness_timer', timer);
@@ -1225,7 +1227,8 @@ const converters = {
1225
1227
  brightness += delta;
1226
1228
  brightness = numberWithinRange(brightness, 0, 255);
1227
1229
  globalStore.putValue(msg.endpoint, 'simulated_brightness_brightness', brightness);
1228
- payload.brightness = brightness;
1230
+ const property = postfixWithEndpointName('brightness', msg, model);
1231
+ payload[property] = brightness;
1229
1232
  }
1230
1233
 
1231
1234
  return payload;
@@ -2574,6 +2577,28 @@ const converters = {
2574
2577
  }
2575
2578
  },
2576
2579
  },
2580
+ wls100z_water_leak: {
2581
+ cluster: 'manuSpecificTuya',
2582
+ type: ['commandDataResponse', 'commandDataReport'],
2583
+ convert: (model, msg, publish, options, meta) => {
2584
+ const result = {};
2585
+ for (const dpValue of msg.data.dpValues) {
2586
+ const value = tuya.getDataValue(dpValue);
2587
+ switch (dpValue.dp) {
2588
+ case tuya.dataPoints.wlsWaterLeak:
2589
+ result.water_leak = value < 1;
2590
+ break;
2591
+ case tuya.dataPoints.wlsBatteryPercentage:
2592
+ result.battery = value;
2593
+ break;
2594
+ default:
2595
+ meta.logger.warn(`zigbee-herdsman-converters:wls100z_water_leak:` +
2596
+ `NOT RECOGNIZED DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`);
2597
+ }
2598
+ }
2599
+ return result;
2600
+ },
2601
+ },
2577
2602
  livolo_switch_state: {
2578
2603
  cluster: 'genOnOff',
2579
2604
  type: ['attributeReport', 'readResponse'],
@@ -4679,17 +4704,21 @@ const converters = {
4679
4704
  },
4680
4705
  tuya_smoke: {
4681
4706
  cluster: 'manuSpecificTuya',
4682
- type: ['commandDataResponse'],
4707
+ type: ['commandDataResponse', 'commandDataReport'],
4683
4708
  convert: (model, msg, publish, options, meta) => {
4684
- const dpValue = tuya.firstDpValue(msg, meta, 'tuya_smoke');
4685
- const dp = dpValue.dp;
4686
- const value = tuya.getDataValue(dpValue);
4687
- switch (dp) {
4688
- case tuya.dataPoints.state:
4689
- return {smoke: value === 0};
4690
- default:
4691
- meta.logger.warn(`zigbee-herdsman-converters:tuya_smoke: Unrecognized DP #${ dp} with data ${JSON.stringify(dpValue)}`);
4709
+ const result = {};
4710
+ for (const dpValue of msg.data.dpValues) {
4711
+ const dp = dpValue.dp;
4712
+ const value = tuya.getDataValue(dpValue);
4713
+ switch (dp) {
4714
+ case tuya.dataPoints.state:
4715
+ result.smoke = value === 0;
4716
+ break;
4717
+ default:
4718
+ meta.logger.warn(`zigbee-herdsman-converters:tuya_smoke: Unrecognized DP #${ dp} with data ${JSON.stringify(dpValue)}`);
4719
+ }
4692
4720
  }
4721
+ return result;
4693
4722
  },
4694
4723
  },
4695
4724
  tuya_woox_smoke: {
@@ -4988,7 +5017,8 @@ const converters = {
4988
5017
  aqara_opple: {
4989
5018
  cluster: 'aqaraOpple',
4990
5019
  type: ['attributeReport', 'readResponse'],
4991
- options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature')],
5020
+ options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature'),
5021
+ exposes.options.precision('illuminance'), exposes.options.calibration('illuminance', 'percentual')],
4992
5022
  convert: (model, msg, publish, options, meta) => {
4993
5023
  const payload = {};
4994
5024
  if (msg.data.hasOwnProperty('247')) {
@@ -5113,6 +5143,8 @@ const converters = {
5113
5143
  if (['QBKG19LM', 'QBKG20LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM'].includes(model.model)) {
5114
5144
  const mapping = model.model === 'QBCZ15LM' ? 'usb' : 'right';
5115
5145
  payload[`state_${mapping}`] = value === 1 ? 'ON' : 'OFF';
5146
+ } else if (['RTCGQ12LM'].includes(model.model)) {
5147
+ payload.illuminance = calibrateAndPrecisionRoundOptions(value, options, 'illuminance');
5116
5148
  }
5117
5149
  } else if (index === 149) {
5118
5150
  payload.energy = precisionRound(value, 2); // 0x95
@@ -5128,7 +5160,7 @@ const converters = {
5128
5160
  if (msg.data.hasOwnProperty('0')) payload.detection_period = msg.data['0'];
5129
5161
  if (msg.data.hasOwnProperty('4')) payload.mode_switch = {4: 'anti_flicker_mode', 1: 'quick_mode'}[msg.data['4']];
5130
5162
  if (msg.data.hasOwnProperty('10')) payload.switch_type = {1: 'toggle', 2: 'momentary'}[msg.data['10']];
5131
- if (msg.data.hasOwnProperty('258')) payload.detection_interval = msg.data['258'];
5163
+ if (msg.data.hasOwnProperty('258')) payload.occupancy_timeout = msg.data['258'];
5132
5164
  if (msg.data.hasOwnProperty('268')) payload.motion_sensitivity = {1: 'low', 2: 'medium', 3: 'high'}[msg.data['268']];
5133
5165
  if (msg.data.hasOwnProperty('512')) {
5134
5166
  if (['ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM'].includes(model.model)) {
@@ -5281,6 +5313,65 @@ const converters = {
5281
5313
  return payload;
5282
5314
  },
5283
5315
  },
5316
+ RTCGQ12LM_occupancy_illuminance: {
5317
+ cluster: 'aqaraOpple',
5318
+ type: ['attributeReport', 'readResponse'],
5319
+ options: [exposes.options.precision('illuminance'), exposes.options.calibration('illuminance', 'percentual')],
5320
+ convert: (model, msg, publish, options, meta) => {
5321
+ if (msg.data.hasOwnProperty('illuminance')) {
5322
+ // The occupancy sensor only sends a message when motion detected.
5323
+ // Therefore we need to publish the no_motion detected by ourselves.
5324
+ const timeout = meta && meta.state && meta.state.hasOwnProperty('occupancy_timeout') ? meta.state.occupancy_timeout : 60;
5325
+
5326
+ // Stop existing timers because motion is detected and set a new one.
5327
+ globalStore.getValue(msg.endpoint, 'timers', []).forEach((t) => clearTimeout(t));
5328
+ globalStore.putValue(msg.endpoint, 'timers', []);
5329
+
5330
+ if (timeout !== 0) {
5331
+ const timer = setTimeout(() => {
5332
+ publish({occupancy: false});
5333
+ }, timeout * 1000);
5334
+
5335
+ globalStore.getValue(msg.endpoint, 'timers').push(timer);
5336
+ }
5337
+
5338
+ const illuminance = msg.data['illuminance'] - 65536;
5339
+ return {occupancy: true, illuminance: calibrateAndPrecisionRoundOptions(illuminance, options, 'illuminance')};
5340
+ }
5341
+ },
5342
+ },
5343
+ RTCGQ13LM_occupancy: {
5344
+ // This is for occupancy sensor that only send a message when motion detected,
5345
+ // but do not send a motion stop.
5346
+ // Therefore we need to publish the no_motion detected by ourselves.
5347
+ cluster: 'msOccupancySensing',
5348
+ type: ['attributeReport', 'readResponse'],
5349
+ convert: (model, msg, publish, options, meta) => {
5350
+ if (msg.data.occupancy !== 1) {
5351
+ // In case of 0 no occupancy is reported.
5352
+ // https://github.com/Koenkk/zigbee2mqtt/issues/467
5353
+ return;
5354
+ }
5355
+
5356
+ // The occupancy sensor only sends a message when motion detected.
5357
+ // Therefore we need to publish the no_motion detected by ourselves.
5358
+ const timeout = meta && meta.state && meta.state.hasOwnProperty('occupancy_timeout') ? meta.state.occupancy_timeout : 60;
5359
+
5360
+ // Stop existing timers because motion is detected and set a new one.
5361
+ globalStore.getValue(msg.endpoint, 'timers', []).forEach((t) => clearTimeout(t));
5362
+ globalStore.putValue(msg.endpoint, 'timers', []);
5363
+
5364
+ if (timeout !== 0) {
5365
+ const timer = setTimeout(() => {
5366
+ publish({occupancy: false});
5367
+ }, timeout * 1000);
5368
+
5369
+ globalStore.getValue(msg.endpoint, 'timers').push(timer);
5370
+ }
5371
+
5372
+ return {occupancy: true};
5373
+ },
5374
+ },
5284
5375
  xiaomi_WXKG01LM_action: {
5285
5376
  cluster: 'genOnOff',
5286
5377
  type: ['attributeReport', 'readResponse'],
@@ -5943,7 +6034,13 @@ const converters = {
5943
6034
  ZMCSW032D_cover_position: {
5944
6035
  cluster: 'closuresWindowCovering',
5945
6036
  type: ['attributeReport', 'readResponse'],
5946
- options: [exposes.options.invert_cover()],
6037
+ options: [
6038
+ exposes.options.invert_cover(),
6039
+ exposes.numeric('time_close')
6040
+ .withDescription(`Set the full closing time of the roller shutter (e.g. set it to 20) (value is in s).`),
6041
+ exposes.numeric('time_open')
6042
+ .withDescription(`Set the full opening time of the roller shutter (e.g. set it to 21) (value is in s).`),
6043
+ ],
5947
6044
  convert: (model, msg, publish, options, meta) => {
5948
6045
  const result = {};
5949
6046
  const timeCoverSetMiddle = 60;
@@ -7437,6 +7534,25 @@ const converters = {
7437
7534
  }
7438
7535
  },
7439
7536
  },
7537
+ tuya_smart_vibration_sensor: {
7538
+ cluster: 'manuSpecificTuya',
7539
+ type: ['commandGetData', 'commandDataResponse', 'raw'],
7540
+ convert: (model, msg, publish, options, meta) => {
7541
+ const dp = msg.data.dp;
7542
+ const value = tuya.getDataValue(msg.data.datatype, msg.data.data);
7543
+ switch (dp) {
7544
+ case tuya.dataPoints.state:
7545
+ return {contact: Boolean(value)};
7546
+ case tuya.dataPoints.thitBatteryPercentage:
7547
+ return {battery: value};
7548
+ case tuya.dataPoints.tuyaVibration:
7549
+ return {vibration: Boolean(value)};
7550
+ default:
7551
+ meta.logger.warn(`zigbee-herdsman-converters:tuya_smart_vibration_sensor: NOT RECOGNIZED ` +
7552
+ `DP #${dp} with data ${JSON.stringify(msg.data)}`);
7553
+ }
7554
+ },
7555
+ },
7440
7556
  moes_thermostat_tv: {
7441
7557
  cluster: 'manuSpecificTuya',
7442
7558
  type: ['commandDataResponse', 'commandDataReport', 'raw'],
@@ -7527,7 +7643,11 @@ const converters = {
7527
7643
  cluster: 'genAnalogInput',
7528
7644
  type: ['attributeReport', 'readResponse'],
7529
7645
  convert: (model, msg, publish, options, meta) => {
7530
- return {people: msg.data.presentValue};
7646
+ const lookup = {'0': 'idle', '1': 'in', '2': 'out'};
7647
+ const value = precisionRound(parseFloat(msg.data['presentValue']), 1);
7648
+ let result = null;
7649
+ result = {people: precisionRound(msg.data.presentValue, 0), status: lookup[value*10%10]};
7650
+ return result;
7531
7651
  },
7532
7652
  },
7533
7653
  sihas_action: {
@@ -7712,6 +7832,40 @@ const converters = {
7712
7832
  };
7713
7833
  },
7714
7834
  },
7835
+ tuya_light_wz5: {
7836
+ cluster: 'manuSpecificTuya',
7837
+ type: ['commandDataResponse', 'commandDataReport'],
7838
+ convert: (model, msg, publish, options, meta) => {
7839
+ const separateWhite = (model.meta && model.meta.separateWhite);
7840
+ const result = {};
7841
+ // eslint-disable-next-line no-unused-vars
7842
+ for (const [i, dpValue] of msg.data.dpValues.entries()) {
7843
+ const dp = dpValue.dp;
7844
+ const value = tuya.getDataValue(dpValue);
7845
+ if (dp === tuya.dataPoints.state) {
7846
+ result.state = value ? 'ON': 'OFF';
7847
+ } else if (dp === tuya.dataPoints.silvercrestSetBrightness) {
7848
+ const brightness = mapNumberRange(value, 0, 1000, 0, 255);
7849
+ if (separateWhite) {
7850
+ result.white_brightness = brightness;
7851
+ } else {
7852
+ result.brightness = brightness;
7853
+ }
7854
+ } else if (dp === tuya.dataPoints.silvercrestSetColor) {
7855
+ const h = parseInt(value.substring(0, 4), 16);
7856
+ const s = parseInt(value.substring(4, 8), 16);
7857
+ const b = parseInt(value.substring(8, 12), 16);
7858
+ result.color_mode = 'hs';
7859
+ result.color = {hue: h, saturation: mapNumberRange(s, 0, 1000, 0, 100)};
7860
+ result.brightness = mapNumberRange(b, 0, 1000, 0, 255);
7861
+ } else if (dp === tuya.dataPoints.silvercrestSetColorTemp) {
7862
+ const [colorTempMin, colorTempMax] = [250, 454];
7863
+ result.color_temp = mapNumberRange(value, 0, 1000, colorTempMax, colorTempMin);
7864
+ }
7865
+ }
7866
+ return result;
7867
+ },
7868
+ },
7715
7869
  // #endregion
7716
7870
 
7717
7871
  // #region Ignore converters (these message dont need parsing).
@@ -7865,6 +8019,11 @@ const converters = {
7865
8019
  type: ['commandMcuSyncTime'],
7866
8020
  convert: (model, msg, publish, options, meta) => null,
7867
8021
  },
8022
+ ignore_tuya_raw: {
8023
+ cluster: 'manuSpecificTuya',
8024
+ type: ['raw'],
8025
+ convert: (model, msg, publish, options, meta) => null,
8026
+ },
7868
8027
  // #endregion
7869
8028
  };
7870
8029
 
@@ -2076,12 +2076,12 @@ const converters = {
2076
2076
  await entity.read('aqaraOpple', [0x0000], manufacturerOptions.xiaomi);
2077
2077
  },
2078
2078
  },
2079
- RTCGQ13LM_detection_interval: {
2080
- key: ['detection_interval'],
2079
+ aqara_occupancy_timeout: {
2080
+ key: ['occupancy_timeout'],
2081
2081
  convertSet: async (entity, key, value, meta) => {
2082
2082
  value *= 1;
2083
2083
  await entity.write('aqaraOpple', {0x0102: {value: [value], type: 0x20}}, manufacturerOptions.xiaomi);
2084
- return {state: {detection_interval: value}};
2084
+ return {state: {occupancy_timeout: value}};
2085
2085
  },
2086
2086
  convertGet: async (entity, key, meta) => {
2087
2087
  await entity.read('aqaraOpple', [0x0102], manufacturerOptions.xiaomi);
@@ -6404,6 +6404,133 @@ const converters = {
6404
6404
  return {state: {click_mode: value}};
6405
6405
  },
6406
6406
  },
6407
+ tuya_light_wz5: {
6408
+ key: ['color', 'color_temp', 'brightness', 'white_brightness'],
6409
+ convertSet: async (entity, key, value, meta) => {
6410
+ const separateWhite = (meta.mapped.meta && meta.mapped.meta.separateWhite);
6411
+ if (key == 'white_brightness' || (!separateWhite && (key == 'brightness'))) {
6412
+ // upscale to 1000
6413
+ let newValue;
6414
+ if (value >= 0 && value <= 255) {
6415
+ newValue = utils.mapNumberRange(value, 0, 255, 0, 1000);
6416
+ } else {
6417
+ throw new Error('Dimmer brightness is out of range 0..255');
6418
+ }
6419
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.silvercrestChangeMode, tuya.silvercrestModes.white);
6420
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.dimmerLevel, newValue);
6421
+
6422
+ return {state: {white_brightness: value}};
6423
+ } else if (key == 'color_temp') {
6424
+ const [colorTempMin, colorTempMax] = [250, 454];
6425
+ const preset = {
6426
+ 'warmest': colorTempMax,
6427
+ 'warm': 454,
6428
+ 'neutral': 370,
6429
+ 'cool': 250,
6430
+ 'coolest': colorTempMin,
6431
+ };
6432
+ if (typeof value === 'string' && isNaN(value)) {
6433
+ const presetName = value.toLowerCase();
6434
+ if (presetName in preset) {
6435
+ value = preset[presetName];
6436
+ } else {
6437
+ throw new Error(`Unknown preset '${value}'`);
6438
+ }
6439
+ } else {
6440
+ value = light.clampColorTemp(Number(value), colorTempMin, colorTempMax, meta.logger);
6441
+ }
6442
+ const data = utils.mapNumberRange(value, colorTempMax, colorTempMin, 0, 1000);
6443
+
6444
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.silvercrestChangeMode, tuya.silvercrestModes.white);
6445
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.silvercrestSetColorTemp, data);
6446
+
6447
+ return {state: {color_temp: value}};
6448
+ } else if (key == 'color' || (separateWhite && (key == 'brightness'))) {
6449
+ const newState = {};
6450
+ if (key == 'brightness') {
6451
+ newState.brightness = value;
6452
+ } else if (key == 'color') {
6453
+ newState.color = value;
6454
+ newState.color_mode = 'hs';
6455
+ }
6456
+
6457
+ const make4sizedString = (v) => {
6458
+ if (v.length >= 4) {
6459
+ return v;
6460
+ } else if (v.length === 3) {
6461
+ return '0' + v;
6462
+ } else if (v.length === 2) {
6463
+ return '00' + v;
6464
+ } else if (v.length === 1) {
6465
+ return '000' + v;
6466
+ } else {
6467
+ return '0000';
6468
+ }
6469
+ };
6470
+
6471
+ const fillInHSB = (h, s, b, state) => {
6472
+ // Define default values. Device expects leading zero in string.
6473
+ const hsb = {
6474
+ h: '0168', // 360
6475
+ s: '03e8', // 1000
6476
+ b: '03e8', // 1000
6477
+ };
6478
+
6479
+ if (h) {
6480
+ // The device expects 0-359
6481
+ if (h >= 360) {
6482
+ h = 359;
6483
+ }
6484
+ hsb.h = make4sizedString(h.toString(16));
6485
+ } else if (state.color && state.color.hue) {
6486
+ hsb.h = make4sizedString(state.color.hue.toString(16));
6487
+ }
6488
+
6489
+ // Device expects 0-1000, saturation normally is 0-100 so we expect that from the user
6490
+ // The device expects a round number, otherwise everything breaks
6491
+ if (s) {
6492
+ hsb.s = make4sizedString(utils.mapNumberRange(s, 0, 100, 0, 1000).toString(16));
6493
+ } else if (state.color && state.color.saturation) {
6494
+ hsb.s = make4sizedString(utils.mapNumberRange(state.color.saturation, 0, 100, 0, 1000).toString(16));
6495
+ }
6496
+
6497
+ // Scale 0-255 to 0-1000 what the device expects.
6498
+ if (b) {
6499
+ hsb.b = make4sizedString(utils.mapNumberRange(b, 0, 255, 0, 1000).toString(16));
6500
+ } else if (state.brightness) {
6501
+ hsb.b = make4sizedString(utils.mapNumberRange(state.brightness, 0, 255, 0, 1000).toString(16));
6502
+ }
6503
+
6504
+ return hsb;
6505
+ };
6506
+
6507
+ const hsb = fillInHSB(
6508
+ value.h || value.hue || null,
6509
+ value.s || value.saturation || null,
6510
+ value.b || value.brightness || (key == 'brightness') ? value : null,
6511
+ meta.state,
6512
+ );
6513
+
6514
+
6515
+ let data = [];
6516
+ data = data.concat(tuya.convertStringToHexArray(hsb.h));
6517
+ data = data.concat(tuya.convertStringToHexArray(hsb.s));
6518
+ data = data.concat(tuya.convertStringToHexArray(hsb.b));
6519
+
6520
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.silvercrestChangeMode, tuya.silvercrestModes.color);
6521
+ await tuya.sendDataPointStringBuffer(entity, tuya.dataPoints.silvercrestSetColor, data);
6522
+
6523
+ if (separateWhite && meta.state.white_brightness != undefined) {
6524
+ // restore white state
6525
+ const newValue = utils.mapNumberRange(meta.state.white_brightness, 0, 255, 0, 1000);
6526
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.silvercrestChangeMode, tuya.silvercrestModes.white);
6527
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.dimmerLevel, newValue);
6528
+ }
6529
+
6530
+ return {state: newState};
6531
+ }
6532
+ },
6533
+ },
6407
6534
  // #endregion
6408
6535
 
6409
6536
  // #region Ignore converters
package/devices/aldi.js CHANGED
@@ -12,14 +12,6 @@ module.exports = [
12
12
  extend: extend.light_onoff_brightness_colortemp_color({disableColorTempStartup: true}),
13
13
  meta: {applyRedFix: true},
14
14
  },
15
- {
16
- fingerprint: [{modelID: 'TS0505B', manufacturerName: '_TZ3000_kohbva1f'}],
17
- model: 'L122CB63H11A9.0W',
18
- vendor: 'Aldi',
19
- description: 'LIGHTWAY smart home LED-lamp - bulb',
20
- extend: extend.light_onoff_brightness_colortemp_color({disableColorTempStartup: true}),
21
- meta: {applyRedFix: true},
22
- },
23
15
  {
24
16
  fingerprint: [{modelID: 'TS0505B', manufacturerName: '_TZ3000_iivsrikg'}],
25
17
  model: 'L122AA63H11A6.5W',
package/devices/awox.js CHANGED
@@ -10,6 +10,11 @@ module.exports = [
10
10
  },
11
11
  {
12
12
  fingerprint: [
13
+ {type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [
14
+ {ID: 1, profileID: 260, deviceID: 269, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096, 64599, 10], outputClusters: [6]},
15
+ {ID: 3, profileID: 4751, deviceID: 269, inputClusters: [65360, 65361], outputClusters: [65360, 65361]},
16
+ {ID: 242, profileID: 41440, deviceID: 97, inputClusters: [], outputClusters: [33]},
17
+ ]},
13
18
  {type: 'Router', manufacturerName: 'AwoX', modelID: 'TLSR82xx', endpoints: [
14
19
  {ID: 1, profileID: 260, deviceID: 258, inputClusters: [0, 3, 4, 5, 6, 8, 768, 4096], outputClusters: [6, 25]},
15
20
  {ID: 3, profileID: 49152, deviceID: 258, inputClusters: [65360, 65361], outputClusters: [65360, 65361]},
@@ -14,7 +14,11 @@ module.exports = [
14
14
  description: 'Light switch with neutral',
15
15
  fromZigbee: [fz.identify, fz.on_off, fz.K4003C_binary_input],
16
16
  toZigbee: [tz.on_off, tz.legrand_settingAlwaysEnableLed, tz.legrand_settingEnableLedIfOn, tz.legrand_identify],
17
- exposes: [e.switch(), e.action(['identify', 'on', 'off'])],
17
+ exposes: [
18
+ e.switch(), e.action(['identify', 'on', 'off']),
19
+ exposes.binary('permanent_led', ea.STATE_SET, 'ON', 'OFF').withDescription('Enable or disable the permanent blue LED'),
20
+ exposes.binary('led_when_on', ea.STATE_SET, 'ON', 'OFF').withDescription('Enables the LED when the light is on'),
21
+ ],
18
22
  configure: async (device, coordinatorEndpoint, logger) => {
19
23
  const endpoint = device.getEndpoint(1);
20
24
  await reporting.bind(endpoint, coordinatorEndpoint, ['genIdentify', 'genOnOff', 'genBinaryInput']);
@@ -26,7 +30,12 @@ module.exports = [
26
30
  vendor: 'BTicino',
27
31
  description: 'Dimmer switch with neutral',
28
32
  extend: extend.light_onoff_brightness({noConfigure: true}),
29
- exposes: [e.light_brightness()],
33
+ exposes: [
34
+ e.light_brightness(),
35
+ exposes.binary('dimmer_enabled', ea.STATE_SET, 'ON', 'OFF').withDescription('Allow the device to change brightness'),
36
+ exposes.binary('permanent_led', ea.STATE_SET, 'ON', 'OFF').withDescription('Enable or disable the permanent blue LED'),
37
+ exposes.binary('led_when_on', ea.STATE_SET, 'ON', 'OFF').withDescription('Enables the LED when the light is on'),
38
+ ],
30
39
  fromZigbee: [fz.brightness, fz.identify, fz.on_off],
31
40
  toZigbee: [tz.light_onoff_brightness, tz.legrand_settingAlwaysEnableLed, tz.legrand_settingEnableLedIfOn,
32
41
  tz.legrand_settingEnableDimmer, tz.legrand_identify],
@@ -196,12 +196,15 @@ module.exports = [
196
196
  tz.thermostat_occupied_heating_setpoint, tz.thermostat_occupied_cooling_setpoint,
197
197
  tz.thermostat_setpoint_raise_lower, tz.thermostat_remote_sensing,
198
198
  tz.thermostat_control_sequence_of_operation, tz.thermostat_system_mode,
199
- tz.thermostat_relay_status_log, tz.fan_mode, tz.thermostat_running_state],
200
- exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 1).withLocalTemperature()
201
- .withSystemMode(['off', 'heat', 'cool', 'emergency_heating'])
202
- .withRunningState(['idle', 'heat', 'cool', 'fan_only']).withFanMode(['auto', 'on'])
203
- .withSetpoint('occupied_cooling_setpoint', 10, 30, 1)
204
- .withLocalTemperatureCalibration(-30, 30, 0.1)],
199
+ tz.thermostat_relay_status_log, tz.fan_mode, tz.thermostat_running_state, tz.thermostat_temperature_setpoint_hold],
200
+ exposes: [e.battery(),
201
+ exposes.binary('temperature_setpoint_hold', ea.ALL, true, false)
202
+ .withDescription('Prevent changes. `false` = run normally. `true` = prevent from making changes.'),
203
+ exposes.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 1).withLocalTemperature()
204
+ .withSystemMode(['off', 'heat', 'cool', 'emergency_heating'])
205
+ .withRunningState(['idle', 'heat', 'cool', 'fan_only']).withFanMode(['auto', 'on'])
206
+ .withSetpoint('occupied_cooling_setpoint', 10, 30, 1)
207
+ .withLocalTemperatureCalibration(-30, 30, 0.1)],
205
208
  meta: {battery: {voltageToPercentage: '3V_1500_2800'}},
206
209
  configure: async (device, coordinatorEndpoint, logger) => {
207
210
  const endpoint = device.getEndpoint(1);
@@ -210,6 +213,7 @@ module.exports = [
210
213
  await reporting.thermostatRunningState(endpoint);
211
214
  await reporting.thermostatTemperature(endpoint);
212
215
  await reporting.fanMode(endpoint);
216
+ await reporting.thermostatTemperatureSetpointHold(endpoint);
213
217
  },
214
218
  },
215
219
  {
package/devices/ecodim.js CHANGED
@@ -121,4 +121,11 @@ module.exports = [
121
121
  toZigbee: [],
122
122
  meta: {multiEndpoint: true, battery: {dontDividePercentage: true}},
123
123
  },
124
+ {
125
+ fingerprint: [{modelID: 'TS0501B', manufacturerName: '_TZ3210_yluvwhjc'}],
126
+ model: 'ED-10042',
127
+ vendor: 'EcoDim',
128
+ description: 'Zigbee LED filament light dimmable E27, globe G125, flame 2200K',
129
+ extend: extend.light_onoff_brightness(),
130
+ },
124
131
  ];
package/devices/innr.js CHANGED
@@ -79,6 +79,14 @@ module.exports = [
79
79
  extend: extend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 555], supportsHS: true}),
80
80
  meta: {enhancedHue: false, applyRedFix: true, turnsOffAtBrightness1: true},
81
81
  },
82
+ {
83
+ zigbeeModel: ['RB 251 C'],
84
+ model: 'RB 251 C',
85
+ vendor: 'Innr',
86
+ description: 'E14 bulb RGBW',
87
+ extend: extend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 555], supportsHS: true}),
88
+ meta: {enhancedHue: false, applyRedFix: true, turnsOffAtBrightness1: true},
89
+ },
82
90
  {
83
91
  zigbeeModel: ['RB 265'],
84
92
  model: 'RB 265',
package/devices/iris.js CHANGED
@@ -74,7 +74,7 @@ module.exports = [
74
74
  exposes: [e.contact(), e.battery_low(), e.tamper(), e.temperature(), e.battery()],
75
75
  },
76
76
  {
77
- zigbeeModel: ['3450-L'],
77
+ zigbeeModel: ['3450-L', '3450-L2'],
78
78
  model: '3450-L',
79
79
  vendor: 'Iris',
80
80
  description: 'Smart fob',
package/devices/konke.js CHANGED
@@ -73,4 +73,13 @@ module.exports = [
73
73
  toZigbee: [],
74
74
  exposes: [e.water_leak(), e.battery_low(), e.tamper()],
75
75
  },
76
+ {
77
+ fingerprint: [{modelID: 'TS0222', manufacturerName: '_TYZB01_fi5yftwv'}],
78
+ model: 'KK-ES-J01W',
79
+ vendor: 'Konke',
80
+ description: 'Room temperature, relative humidity and illuminance sensor',
81
+ fromZigbee: [fz.battery, fz.illuminance, fz.humidity, fz.temperature],
82
+ toZigbee: [],
83
+ exposes: [e.battery(), e.illuminance(), e.illuminance_lux().withUnit('lx'), e.humidity(), e.temperature()],
84
+ },
76
85
  ];
package/devices/nodon.js CHANGED
@@ -16,6 +16,8 @@ module.exports = [
16
16
  configure: async (device, coordinatorEndpoint, logger) => {
17
17
  const endpoint = device.getEndpoint(1);
18
18
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'closuresWindowCovering']);
19
+ await reporting.currentPositionLiftPercentage(endpoint);
20
+ await reporting.currentPositionTiltPercentage(endpoint);
19
21
  },
20
22
  exposes: [e.cover_position()],
21
23
  },
package/devices/nous.js CHANGED
@@ -7,6 +7,15 @@ const e = exposes.presets;
7
7
  const ea = exposes.access;
8
8
 
9
9
  module.exports = [
10
+ {
11
+ fingerprint: [{modelID: 'TS0201', manufacturerName: '_TZ3000_lbtpiody'}],
12
+ model: 'E5',
13
+ vendor: 'Nous',
14
+ description: 'Temperature & humidity',
15
+ fromZigbee: [fz.temperature, fz.humidity, fz.battery],
16
+ toZigbee: [],
17
+ exposes: [e.temperature(), e.humidity(), e.battery()],
18
+ },
10
19
  {
11
20
  fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_nnrfa68v'}],
12
21
  model: 'E6',
package/devices/nue_3a.js CHANGED
@@ -157,6 +157,12 @@ module.exports = [
157
157
  },
158
158
  },
159
159
  {
160
+ fingerprint: [
161
+ {type: 'Router', manufacturerName: '3A Smart Home DE', modelID: 'LXN-2S27LX1.0', endpoints: [
162
+ {ID: 11, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 4096, 8], outputClusters: [25]},
163
+ {ID: 12, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 8], outputClusters: [25]},
164
+ ]},
165
+ ],
160
166
  zigbeeModel: ['FNB56-ZSW02LX2.0'],
161
167
  model: 'HGZB-42',
162
168
  vendor: 'Nue / 3A',
@@ -283,7 +289,12 @@ module.exports = [
283
289
  extend: extend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 370]}),
284
290
  },
285
291
  {
286
- zigbeeModel: ['LXN-2S27LX1.0'],
292
+ fingerprint: [
293
+ {type: 'Router', manufacturerName: '3A Smart Home DE', modelID: 'LXN-2S27LX1.0', endpoints: [
294
+ {ID: 1, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 4096, 8], outputClusters: [0]},
295
+ {ID: 2, profileID: 49246, deviceID: 0, inputClusters: [0, 4, 3, 6, 5, 4096, 8], outputClusters: [0]},
296
+ ]},
297
+ ],
287
298
  model: 'NUE-AUWZO2',
288
299
  vendor: 'Nue / 3A',
289
300
  description: 'Smart Zigbee double power point',
package/devices/osram.js CHANGED
@@ -178,11 +178,11 @@ module.exports = [
178
178
  ota: ota.ledvance,
179
179
  },
180
180
  {
181
- zigbeeModel: ['Surface Light TW'],
181
+ zigbeeModel: ['Surface Light TW', 'ZLO-CeilingTW-OS'],
182
182
  model: 'AB401130055',
183
183
  vendor: 'OSRAM',
184
184
  description: 'LIGHTIFY Surface Light LED Tunable White',
185
- extend: extend.ledvance.light_onoff_brightness_colortemp(),
185
+ extend: extend.ledvance.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}),
186
186
  ota: ota.ledvance,
187
187
  },
188
188
  {
package/devices/owon.js CHANGED
@@ -106,15 +106,14 @@ module.exports = [
106
106
  zigbeeModel: ['THS317-ET'],
107
107
  model: 'THS317-ET',
108
108
  vendor: 'OWON',
109
- description: 'Temperature and humidity sensor',
110
- fromZigbee: [fz.temperature, fz.humidity, fz.battery],
109
+ description: 'Temperature sensor',
110
+ fromZigbee: [fz.temperature, fz.battery],
111
111
  toZigbee: [],
112
- exposes: [e.battery(), e.temperature(), e.humidity()],
112
+ exposes: [e.battery(), e.temperature()],
113
113
  configure: async (device, coordinatorEndpoint, logger) => {
114
- const endpoint = device.getEndpoint(2);
115
- await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement', 'msRelativeHumidity', 'genPowerCfg']);
114
+ const endpoint = device.getEndpoint(3);
115
+ await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement', 'genPowerCfg']);
116
116
  await reporting.temperature(endpoint);
117
- await reporting.humidity(endpoint);
118
117
  await reporting.batteryVoltage(endpoint);
119
118
  await reporting.batteryPercentageRemaining(endpoint);
120
119
  device.powerSource = 'Battery';
@@ -29,6 +29,24 @@ const hueExtend = {
29
29
  };
30
30
 
31
31
  module.exports = [
32
+ {
33
+ zigbeeModel: ['929003047501'],
34
+ model: '929003047501',
35
+ vendor: 'Philips',
36
+ description: 'Centura recessed spotlight',
37
+ meta: {turnsOffAtBrightness1: true},
38
+ extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 500]}),
39
+ ota: ota.zigbeeOTA,
40
+ },
41
+ {
42
+ zigbeeModel: ['915005996401'],
43
+ model: '915005996401',
44
+ vendor: 'Philips',
45
+ description: 'Hue white ambiance ceiling light Enrave S with Bluetooth',
46
+ meta: {turnsOffAtBrightness1: true},
47
+ extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
48
+ ota: ota.zigbeeOTA,
49
+ },
32
50
  {
33
51
  zigbeeModel: ['929003054001'],
34
52
  model: '929003054001',
@@ -2315,7 +2333,7 @@ module.exports = [
2315
2333
  ota: ota.zigbeeOTA,
2316
2334
  },
2317
2335
  {
2318
- zigbeeModel: ['5309331P6'],
2336
+ zigbeeModel: ['5309331P6', '5309330P6'],
2319
2337
  model: '5309331P6',
2320
2338
  vendor: 'Philips',
2321
2339
  description: 'Hue White ambiance Runner triple spotlight',
@@ -2324,7 +2342,7 @@ module.exports = [
2324
2342
  ota: ota.zigbeeOTA,
2325
2343
  },
2326
2344
  {
2327
- zigbeeModel: ['5309230P6'],
2345
+ zigbeeModel: ['5309230P6', '5309231P6'],
2328
2346
  model: '5309230P6',
2329
2347
  vendor: 'Philips',
2330
2348
  description: 'Hue White ambiance Runner double spotlight',
@@ -2369,7 +2387,7 @@ module.exports = [
2369
2387
  ota: ota.zigbeeOTA,
2370
2388
  },
2371
2389
  {
2372
- zigbeeModel: ['5309030P9'],
2390
+ zigbeeModel: ['5309030P9', '5309031P9', '5309030P6', '5309031P6'],
2373
2391
  model: '5309030P9',
2374
2392
  vendor: 'Philips',
2375
2393
  description: 'Hue White ambiance Runner single spotlight',
@@ -610,4 +610,24 @@ module.exports = [
610
610
  await reporting.readMeteringMultiplierDivisor(endpoint);
611
611
  },
612
612
  },
613
+ {
614
+ zigbeeModel: ['LK/OUTLET/1'],
615
+ model: '545D6115',
616
+ vendor: 'Schneider Electric',
617
+ description: 'LK FUGA wiser wireless socket outlet',
618
+ fromZigbee: [fz.on_off, fz.electrical_measurement, fz.EKO09738_metering, fz.power_on_behavior],
619
+ toZigbee: [tz.on_off, tz.power_on_behavior],
620
+ exposes: [e.switch(), e.power(), e.energy(), e.current(), e.voltage(),
621
+ exposes.enum('power_on_behavior', ea.ALL, ['off', 'previous', 'on'])
622
+ .withDescription('Controls the behaviour when the device is powered on')],
623
+ configure: async (device, coordinatorEndpoint, logger) => {
624
+ const endpoint = device.getEndpoint(6);
625
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
626
+ await reporting.onOff(endpoint);
627
+ // Unit supports acVoltage and acCurrent, but only acCurrent divisor/multiplier can be read
628
+ await endpoint.read('haElectricalMeasurement', ['acCurrentDivisor', 'acCurrentMultiplier']);
629
+ await reporting.readMeteringMultiplierDivisor(endpoint);
630
+ await reporting.currentSummDelivered(endpoint, {min: 60, change: 1});
631
+ },
632
+ },
613
633
  ];
@@ -24,6 +24,7 @@ module.exports = [
24
24
  await endpoint.configureReporting('genAnalogInput', payload);
25
25
  },
26
26
  exposes: [e.battery(), e.battery_voltage(),
27
+ exposes.enum('status', ea.STATE, ['idle', 'in', 'out']).withDescription('Currently status'),
27
28
  exposes.numeric('people', ea.ALL).withValueMin(0).withValueMax(50).withDescription('People count')],
28
29
  },
29
30
  {
@@ -0,0 +1,74 @@
1
+ const exposes = require('../lib/exposes');
2
+ const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/legacy').fromZigbee};
3
+ const tz = require('../converters/toZigbee');
4
+ const ea = exposes.access;
5
+
6
+ module.exports = [
7
+ {
8
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_6qoazbre'}],
9
+ model: 'WZ5_dim',
10
+ vendor: 'Skydance',
11
+ description: 'Zigbee & RF 5 in 1 LED controller (DIM mode)',
12
+ fromZigbee: [fz.tuya_light_wz5],
13
+ toZigbee: [tz.tuya_dimmer_state, tz.tuya_light_wz5],
14
+ exposes: [
15
+ exposes.light().withBrightness().setAccess('state',
16
+ ea.STATE_SET).setAccess('brightness', ea.STATE_SET),
17
+ ],
18
+ },
19
+ {
20
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_gz3n0tzf'}],
21
+ model: 'WZ5_cct',
22
+ vendor: 'Skydance',
23
+ description: 'Zigbee & RF 5 in 1 LED controller (CCT mode)',
24
+ fromZigbee: [fz.tuya_light_wz5],
25
+ toZigbee: [tz.tuya_dimmer_state, tz.tuya_light_wz5],
26
+ exposes: [
27
+ exposes.light().withBrightness().setAccess('state',
28
+ ea.STATE_SET).setAccess('brightness', ea.STATE_SET).withColorTemp([250, 454]).setAccess('color_temp', ea.STATE_SET),
29
+ ],
30
+ },
31
+ {
32
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_9hghastn'}],
33
+ model: 'WZ5_rgb',
34
+ vendor: 'Skydance',
35
+ description: 'Zigbee & RF 5 in 1 LED controller (RGB mode)',
36
+ fromZigbee: [fz.tuya_light_wz5],
37
+ toZigbee: [tz.tuya_dimmer_state, tz.tuya_light_wz5],
38
+ exposes: [
39
+ exposes.light().withBrightness().setAccess('state', ea.STATE_SET).setAccess('brightness',
40
+ ea.STATE_SET).withColor('hs'),
41
+ ],
42
+ },
43
+ {
44
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_3thxjahu'}],
45
+ model: 'WZ5_rgbw',
46
+ vendor: 'Skydance',
47
+ description: 'Zigbee & RF 5 in 1 LED controller (RGBW mode)',
48
+ fromZigbee: [fz.tuya_light_wz5],
49
+ toZigbee: [tz.tuya_dimmer_state, tz.tuya_light_wz5],
50
+ exposes: [
51
+ exposes.light().withBrightness().setAccess('state', ea.STATE_SET).setAccess('brightness',
52
+ ea.STATE_SET).withColor('hs'),
53
+ exposes.numeric('white_brightness', ea.STATE_SET).withValueMin(0).withValueMax(254).withDescription(
54
+ 'White brightness of this light'),
55
+ ],
56
+ meta: {separateWhite: true},
57
+ },
58
+ {
59
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_mde0utnv'}],
60
+ model: 'WZ5_rgbcct',
61
+ vendor: 'Skydance',
62
+ description: 'Zigbee & RF 5 in 1 LED controller (RGB+CCT mode)',
63
+ fromZigbee: [fz.tuya_light_wz5],
64
+ toZigbee: [tz.tuya_dimmer_state, tz.tuya_light_wz5],
65
+ exposes: [
66
+ exposes.light().withBrightness().setAccess('state', ea.STATE_SET).setAccess('brightness',
67
+ ea.STATE_SET).withColor('hs').withColorTemp([250, 454]).setAccess('color_temp',
68
+ ea.STATE_SET),
69
+ exposes.numeric('white_brightness', ea.STATE_SET).withValueMin(0).withValueMax(254).withDescription(
70
+ 'White brightness of this light'),
71
+ ],
72
+ meta: {separateWhite: true},
73
+ },
74
+ ];
@@ -6,6 +6,22 @@ const extend = require('../lib/extend');
6
6
  const e = exposes.presets;
7
7
 
8
8
  module.exports = [
9
+ {
10
+ zigbeeModel: ['ON/OFF(2CH)'],
11
+ model: 'UP-SA-9127D',
12
+ vendor: 'Sunricher',
13
+ description: 'LED-trading 2 channel AC switch',
14
+ extend: extend.switch(),
15
+ exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2')],
16
+ endpoint: (device) => {
17
+ return {'l1': 1, 'l2': 2};
18
+ },
19
+ meta: {multiEndpoint: true},
20
+ configure: async (device, coordinatorEndpoint, logger) => {
21
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']);
22
+ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']);
23
+ },
24
+ },
9
25
  {
10
26
  zigbeeModel: ['HK-ZD-CCT-A'],
11
27
  model: 'HK-ZD-CCT-A',
package/devices/tuya.js CHANGED
@@ -12,7 +12,7 @@ const utils = require('../lib/utils');
12
12
 
13
13
  const TS011Fplugs = ['_TZ3000_5f43h46b', '_TZ3000_cphmq0q7', '_TZ3000_dpo1ysak', '_TZ3000_ew3ldmgx', '_TZ3000_gjnozsaz',
14
14
  '_TZ3000_jvzvulen', '_TZ3000_mraovvmm', '_TZ3000_nfnmi125', '_TZ3000_ps3dmato', '_TZ3000_w0qqde0g', '_TZ3000_u5u4cakc',
15
- '_TZ3000_rdtixbnu', '_TZ3000_typdpbpg', '_TZ3000_v1pdxuqq'];
15
+ '_TZ3000_rdtixbnu', '_TZ3000_typdpbpg', '_TZ3000_v1pdxuqq', '_TZ3000_bfn1w0mm'];
16
16
 
17
17
  const tzLocal = {
18
18
  TS0504B_color: {
@@ -158,6 +158,7 @@ module.exports = [
158
158
  {modelID: 'TS0505B', manufacturerName: '_TZ3000_589kq4ul'},
159
159
  {modelID: 'TS0505B', manufacturerName: '_TZ3000_1mtktxdk'},
160
160
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_remypqqm'},
161
+ {modelID: 'TS0505B', manufacturerName: '_TZ3000_kohbva1f'},
161
162
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_0rn9qhnu'},
162
163
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_bicjqpg4'},
163
164
  {modelID: 'TS0505B', manufacturerName: '_TZ3000_cmaky9gq'},
@@ -166,7 +167,10 @@ module.exports = [
166
167
  vendor: 'TuYa',
167
168
  description: 'Zigbee RGB+CCT light',
168
169
  whiteLabel: [{vendor: 'Mercator Ikuü', model: 'SMD4106W-RGB-ZB'},
169
- {vendor: 'TuYa', model: 'A5C-21F7-01'}, {vendor: 'Mercator Ikuü', model: 'S9E27LED9W-RGB-Z'}],
170
+ {vendor: 'TuYa', model: 'A5C-21F7-01'}, {vendor: 'Mercator Ikuü', model: 'S9E27LED9W-RGB-Z'},
171
+ {vendor: 'Aldi', model: 'L122CB63H11A9.0W', description: 'LIGHTWAY smart home LED-lamp - bulb'},
172
+ {vendor: 'Lidl', model: '14153706L', description: 'Livarno smart LED ceiling light'},
173
+ ],
170
174
  extend: extend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 500]}),
171
175
  meta: {applyRedFix: true, enhancedHue: false},
172
176
  },
@@ -660,6 +664,20 @@ module.exports = [
660
664
  toZigbee: [],
661
665
  whiteLabel: [{vendor: 'Neo', model: 'NAS-WS02B0'}],
662
666
  },
667
+ {
668
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_jthf7vb6'}],
669
+ model: 'WLS-100z',
670
+ vendor: 'TuYa',
671
+ description: 'Water leak sensor',
672
+ fromZigbee: [fz.ignore_basic_report, fz.ignore_tuya_raw, fz.wls100z_water_leak],
673
+ toZigbee: [],
674
+ onEvent: tuya.onEventSetTime,
675
+ configure: async (device, coordinatorEndpoint, logger) => {
676
+ const endpoint = device.getEndpoint(1);
677
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']);
678
+ },
679
+ exposes: [e.battery(), e.water_leak()],
680
+ },
663
681
  {
664
682
  zigbeeModel: ['TS0001'],
665
683
  model: 'TS0001',
@@ -1151,7 +1169,8 @@ module.exports = [
1151
1169
  },
1152
1170
  {
1153
1171
  fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_byzdayie'},
1154
- {modelID: 'TS0601', manufacturerName: '_TZE200_fsb6zw01'}],
1172
+ {modelID: 'TS0601', manufacturerName: '_TZE200_fsb6zw01'},
1173
+ {modelID: 'TS0601', manufacturerName: '_TZE200_ewxhg6o9'}],
1155
1174
  model: 'TS0601_din',
1156
1175
  vendor: 'TuYa',
1157
1176
  description: 'Zigbee smart energy meter DDS238-2 Zigbee',
@@ -1677,7 +1696,9 @@ module.exports = [
1677
1696
  ],
1678
1697
  },
1679
1698
  {
1680
- fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_vrfecyku'}],
1699
+ fingerprint: [
1700
+ {modelID: 'TS0601', manufacturerName: '_TZE200_vrfecyku'},
1701
+ {modelID: 'TS0601', manufacturerName: '_TZE200_lu01t0zl'}],
1681
1702
  model: 'MIR-HE200-TY',
1682
1703
  vendor: 'TuYa',
1683
1704
  description: 'Human presence sensor',
@@ -1805,4 +1826,13 @@ module.exports = [
1805
1826
  toZigbee: [],
1806
1827
  exposes: [e.action(['toggle', 'brightness_step_up', 'brightness_step_down'])],
1807
1828
  },
1829
+ {
1830
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_kzm5w4iz'}],
1831
+ model: 'TS0601_vibration_sensor',
1832
+ vendor: 'TuYa',
1833
+ description: 'Smart vibration sensor',
1834
+ fromZigbee: [fz.tuya_smart_vibration_sensor],
1835
+ toZigbee: [],
1836
+ exposes: [e.contact(), e.battery(), e.vibration()],
1837
+ },
1808
1838
  ];
package/devices/xiaomi.js CHANGED
@@ -89,6 +89,22 @@ module.exports = [
89
89
  e.power_outage_memory().withAccess(ea.STATE_SET)]),
90
90
  ota: ota.zigbeeOTA,
91
91
  },
92
+ {
93
+ zigbeeModel: ['lumi.light.acn003'],
94
+ model: 'ZNXDD01LM',
95
+ vendor: 'Xiaomi',
96
+ description: 'Aqara ceiling light L1-350',
97
+ extend: xiaomiExtend.light_onoff_brightness_colortemp({disableEffect: true, colorTempRange: [153, 370]}),
98
+ ota: ota.zigbeeOTA,
99
+ },
100
+ {
101
+ zigbeeModel: ['lumi.light.cwac02'],
102
+ model: 'ZNLDP13LM',
103
+ vendor: 'Xiaomi',
104
+ description: 'Aqara T1 smart LED bulb',
105
+ extend: xiaomiExtend.light_onoff_brightness_colortemp({disableEffect: true, colorTempRange: [153, 370]}),
106
+ ota: ota.zigbeeOTA,
107
+ },
92
108
  {
93
109
  zigbeeModel: ['lumi.light.cwopcn01'],
94
110
  model: 'XDD11LM',
@@ -670,6 +686,11 @@ module.exports = [
670
686
  exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
671
687
  .withDescription('Decoupled mode'),
672
688
  ],
689
+ configure: async (device, coordinatorEndpoint, logger) => {
690
+ device.type = 'Router';
691
+ device.powerSource = 'Mains (single phase)';
692
+ device.save();
693
+ },
673
694
  },
674
695
  {
675
696
  zigbeeModel: ['lumi.switch.b2nacn02'],
@@ -852,19 +873,17 @@ module.exports = [
852
873
  zigbeeModel: ['lumi.motion.agl02'],
853
874
  model: 'RTCGQ12LM',
854
875
  vendor: 'Xiaomi',
855
- description: 'Aqara T1 human body movement and illuminance sensor (illuminance not supported for now)',
856
- fromZigbee: [fz.occupancy, fz.occupancy_timeout, fz.battery],
857
- toZigbee: [tz.occupancy_timeout],
858
- exposes: [e.occupancy(), e.battery(),
859
- exposes.numeric('occupancy_timeout', exposes.access.ALL).withValueMin(0).withValueMax(65535).withUnit('s')
860
- .withDescription('Time in seconds till occupancy goes to false')],
876
+ description: 'Aqara T1 human body movement and illuminance sensor',
877
+ fromZigbee: [fz.RTCGQ12LM_occupancy_illuminance, fz.aqara_opple, fz.battery],
878
+ toZigbee: [tz.aqara_occupancy_timeout],
879
+ exposes: [e.occupancy(), e.illuminance().withUnit('lx').withDescription('Measured illuminance in lux'),
880
+ exposes.numeric('occupancy_timeout', ea.ALL).withValueMin(2).withValueMax(65535).withUnit('s')
881
+ .withDescription('Time in seconds till occupancy goes to false'), e.battery()],
861
882
  meta: {battery: {voltageToPercentage: '3V_2100'}},
862
883
  configure: async (device, coordinatorEndpoint, logger) => {
863
884
  const endpoint = device.getEndpoint(1);
864
- await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'msOccupancySensing']);
865
- await reporting.occupancy(endpoint);
866
- await reporting.batteryVoltage(endpoint);
867
- await endpoint.read('msOccupancySensing', ['pirOToUDelay']);
885
+ await endpoint.read('genPowerCfg', ['batteryVoltage']);
886
+ await endpoint.read('aqaraOpple', [0x0102], {manufacturerCode: 0x115f});
868
887
  },
869
888
  },
870
889
  {
@@ -872,15 +891,15 @@ module.exports = [
872
891
  model: 'RTCGQ13LM',
873
892
  vendor: 'Xiaomi',
874
893
  description: 'Aqara high precision motion sensor',
875
- fromZigbee: [fz.occupancy_with_timeout, fz.aqara_opple, fz.battery],
876
- toZigbee: [tz.RTCGQ13LM_detection_interval, tz.RTCGQ13LM_motion_sensitivity],
877
- exposes: [e.occupancy(), e.battery(),
878
- exposes.enum('motion_sensitivity', exposes.access.ALL, ['low', 'medium', 'high']),
879
- exposes.numeric('detection_interval', exposes.access.ALL).withValueMin(2).withValueMax(180).withUnit('s')
880
- .withDescription('Time interval for detecting actions')],
894
+ fromZigbee: [fz.RTCGQ13LM_occupancy, fz.aqara_opple, fz.battery],
895
+ toZigbee: [tz.aqara_occupancy_timeout, tz.RTCGQ13LM_motion_sensitivity],
896
+ exposes: [e.occupancy(), exposes.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']),
897
+ exposes.numeric('occupancy_timeout', ea.ALL).withValueMin(2).withValueMax(65535).withUnit('s')
898
+ .withDescription('Time in seconds till occupancy goes to false'), e.battery()],
881
899
  meta: {battery: {voltageToPercentage: '3V_2100'}},
882
900
  configure: async (device, coordinatorEndpoint, logger) => {
883
901
  const endpoint = device.getEndpoint(1);
902
+ await endpoint.read('genPowerCfg', ['batteryVoltage']);
884
903
  await endpoint.read('aqaraOpple', [0x0102], {manufacturerCode: 0x115f});
885
904
  await endpoint.read('aqaraOpple', [0x010c], {manufacturerCode: 0x115f});
886
905
  },
@@ -9,7 +9,7 @@ const tuya = require('../lib/tuya');
9
9
  const fzLocal = {
10
10
  ZMRM02: {
11
11
  cluster: 'manuSpecificTuya',
12
- type: ['commandGetData', 'commandSetDataResponse'],
12
+ type: ['commandGetData', 'commandSetDataResponse', 'commandDataResponse'],
13
13
  convert: (model, msg, publish, options, meta) => {
14
14
  const dpValue = tuya.firstDpValue(msg, meta, 'ZMRM02');
15
15
  const button = dpValue.dp;
package/lib/exposes.js CHANGED
@@ -527,7 +527,7 @@ module.exports = {
527
527
  current: () => new Numeric('current', access.STATE).withUnit('A').withDescription('Instantaneous measured electrical current'),
528
528
  current_phase_b: () => new Numeric('current_phase_b', access.STATE).withUnit('A').withDescription('Instantaneous measured electrical current on phase B'),
529
529
  current_phase_c: () => new Numeric('current_phase_c', access.STATE).withUnit('A').withDescription('Instantaneous measured electrical current on phase C'),
530
- deadzone_temperature: () => new Numeric('deadzone_temperature', access.STATE_SET).withUnit('°C').withDescription('The delta between local_temperature and current_heating_setpoint to trigger Heat').withValueMin(0.1).withValueMax(5),
530
+ deadzone_temperature: () => new Numeric('deadzone_temperature', access.STATE_SET).withUnit('°C').withDescription('The delta between local_temperature and current_heating_setpoint to trigger Heat').withValueMin(0).withValueMax(5).withValueStep(1),
531
531
  device_temperature: () => new Numeric('device_temperature', access.STATE).withUnit('°C').withDescription('Temperature of the device'),
532
532
  eco2: () => new Numeric('eco2', access.STATE).withUnit('ppm').withDescription('Measured eCO2 value'),
533
533
  eco_mode: () => new Binary('eco_mode', access.STATE_SET, 'ON', 'OFF').withDescription('ECO mode (energy saving mode)'),
@@ -2,17 +2,56 @@ const url = 'https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/index.js
2
2
  const assert = require('assert');
3
3
  const common = require('./common');
4
4
  const axios = common.getAxios();
5
+ const fs = require('fs');
6
+ const URI = require('uri-js');
7
+
8
+ let overrideFileName = null;
5
9
 
6
10
  /**
7
11
  * Helper functions
8
12
  */
9
13
 
14
+
15
+ function isValidUrl(url) {
16
+ let parsed;
17
+ try {
18
+ parsed = URI.parse(url);
19
+ } catch (_) {
20
+ return false;
21
+ }
22
+ return parsed.scheme === 'http' || parsed.scheme === 'https';
23
+ }
24
+
25
+ async function getFile(urlOrName) {
26
+ if (isValidUrl(urlOrName)) {
27
+ return (await axios.get(urlOrName)).data;
28
+ }
29
+
30
+ return JSON.parse(fs.readFileSync(urlOrName));
31
+ }
32
+
33
+ async function getIndex(logger) {
34
+ const index = (await axios.get(url)).data;
35
+
36
+ logger.debug(`ZigbeeOTA: downloaded main index`);
37
+
38
+ if (overrideFileName) {
39
+ logger.debug(`ZigbeeOTA: Loading override index ${overrideFileName}`);
40
+ const localIndex = await getFile(overrideFileName);
41
+
42
+ // Resulting index will have overriden items first
43
+ return localIndex.concat(index);
44
+ }
45
+
46
+ return index;
47
+ }
48
+
10
49
  async function getImageMeta(current, logger, device) {
11
50
  const modelId = device.modelID;
12
51
  const imageType = current.imageType;
13
52
  const manufacturerCode = current.manufacturerCode;
14
53
  const manufacturerName = device.manufacturerName;
15
- const images = (await axios.get(url)).data;
54
+ const images = await getIndex(logger);
16
55
 
17
56
  // NOTE: Officially an image can be determined with a combination of manufacturerCode and imageType.
18
57
  // However Gledopto pro products use the same imageType (0) for every device while the image is different.
@@ -46,4 +85,7 @@ module.exports = {
46
85
  getImageMeta,
47
86
  isUpdateAvailable,
48
87
  updateToLatest,
88
+ useIndexOverride: (indexFileName) => {
89
+ overrideFileName = indexFileName;
90
+ },
49
91
  };
package/lib/tuya.js CHANGED
@@ -86,6 +86,9 @@ async function onEventMeasurementPoll(type, data, device, options) {
86
86
  try {
87
87
  await endpoint.read('haElectricalMeasurement', ['rmsVoltage', 'rmsCurrent', 'activePower']);
88
88
  await endpoint.read('seMetering', ['currentSummDelivered']);
89
+ if (device.manufacturerName === '_TZ3000_u5u4cakc') {
90
+ await endpoint.read('genOnOff', ['onOff']);
91
+ }
89
92
  } catch (error) {/* Do nothing*/}
90
93
  }, seconds*1000);
91
94
  globalStore.putValue(device, 'interval', interval);
@@ -101,11 +104,14 @@ async function onEventSetTime(type, data, device) {
101
104
  const localTime = utcTime - (new Date()).getTimezoneOffset() * 60;
102
105
  const endpoint = device.getEndpoint(1);
103
106
 
104
- await endpoint.command('manuSpecificTuya', 'mcuSyncTime', {
105
- 'seq': 8, // FIXME: Seems that need to set same seq as in request
106
- 'utc': utcTime,
107
- 'local': localTime,
108
- });
107
+ const payload = {
108
+ payloadSize: 8,
109
+ payload: [
110
+ ...convertDecimalValueTo4ByteHexArray(utcTime),
111
+ ...convertDecimalValueTo4ByteHexArray(localTime),
112
+ ],
113
+ };
114
+ await endpoint.command('manuSpecificTuya', 'mcuSyncTime', payload, {});
109
115
  } catch (error) {
110
116
  // endpoint.command can throw an error which needs to
111
117
  // be caught or the zigbee-herdsman may crash
@@ -134,11 +140,14 @@ async function onEventSetLocalTime(type, data, device) {
134
140
  const localTime = utcTime - (new Date()).getTimezoneOffset() * 60;
135
141
  const endpoint = device.getEndpoint(1);
136
142
 
137
- await endpoint.command('manuSpecificTuya', 'mcuSyncTime', {
138
- 'seq': 8, // FIXME: Seems that need to set same seq as in request
139
- 'utc': utcTime,
140
- 'local': localTime,
141
- });
143
+ const payload = {
144
+ payloadSize: 8,
145
+ payload: [
146
+ ...convertDecimalValueTo4ByteHexArray(utcTime),
147
+ ...convertDecimalValueTo4ByteHexArray(localTime),
148
+ ],
149
+ };
150
+ await endpoint.command('manuSpecificTuya', 'mcuSyncTime', payload, {});
142
151
  } catch (error) {
143
152
  // endpoint.command can throw an error which needs to
144
153
  // be caught or the zigbee-herdsman may crash
@@ -375,6 +384,7 @@ const dataPoints = {
375
384
  // Silvercrest
376
385
  silvercrestChangeMode: 2,
377
386
  silvercrestSetBrightness: 3,
387
+ silvercrestSetColorTemp: 4,
378
388
  silvercrestSetColor: 5,
379
389
  silvercrestSetEffect: 6,
380
390
  // Fantem
@@ -515,12 +525,16 @@ const dataPoints = {
515
525
  nousMinTemp: 11,
516
526
  nousTempAlarm: 14,
517
527
  nousTempSensitivity: 19,
518
-
519
528
  // TUYA / HUMIDITY/ILLUMINANCE/TEMPERATURE SENSOR
520
529
  thitBatteryPercentage: 3,
521
530
  thitIlluminanceLux: 7,
522
531
  thitHumidity: 9,
523
532
  thitTemperature: 8,
533
+ // TUYA SMART VIBRATION SENSOR
534
+ tuyaVibration: 10,
535
+ // TUYA WLS-100z Water Leak Sensor
536
+ wlsWaterLeak: 1,
537
+ wlsBatteryPercentage: 4,
524
538
  };
525
539
 
526
540
  const thermostatWeekFormat = {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.368",
3
+ "version": "14.0.372",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.368",
3
+ "version": "14.0.372",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [