zigbee-herdsman-converters 14.0.410 → 14.0.414

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.
@@ -616,6 +616,25 @@ const converters = {
616
616
  return result;
617
617
  },
618
618
  },
619
+ meter_identification: {
620
+ cluster: 'haMeterIdentification',
621
+ type: ['readResponse'],
622
+ convert: (model, msg, publish, options, meta) => {
623
+ const result = {};
624
+ const elements = [
625
+ /* 0x000A*/ 'softwareRevision',
626
+ /* 0x000D*/ 'availablePower',
627
+ /* 0x000E*/ 'powerThreshold',
628
+ ];
629
+ for (const at of elements) {
630
+ const atSnake = at.split(/(?=[A-Z])/).join('_').toLowerCase();
631
+ if (msg.data[at]) {
632
+ result[atSnake] = msg.data[at];
633
+ }
634
+ }
635
+ return result;
636
+ },
637
+ },
619
638
  metering: {
620
639
  /**
621
640
  * When using this converter also add the following to the configure method of the device:
@@ -2395,6 +2414,32 @@ const converters = {
2395
2414
  }
2396
2415
  },
2397
2416
  },
2417
+ moes_switch: {
2418
+ cluster: 'manuSpecificTuya',
2419
+ type: ['commandDataResponse', 'commandDataReport'],
2420
+ convert: (model, msg, publish, options, meta) => {
2421
+ const dpValue = tuya.firstDpValue(msg, meta, 'moes_switch');
2422
+ const dp = dpValue.dp;
2423
+
2424
+ // tuya_switch datapoints
2425
+ if (dp >= 1 && dp <= 4) {
2426
+ return null;
2427
+ }
2428
+
2429
+ const value = tuya.getDataValue(dpValue);
2430
+
2431
+ switch (dp) {
2432
+ case tuya.dataPoints.moesSwitchPowerOnBehavior:
2433
+ return {power_on_behavior: tuya.moesSwitch.powerOnBehavior[value]};
2434
+ case tuya.dataPoints.moesSwitchIndicateLight:
2435
+ return {indicate_light: tuya.moesSwitch.indicateLight[value]};
2436
+ default:
2437
+ meta.logger.warn(`fromZigbee:moes_switch: NOT RECOGNIZED DP #${
2438
+ dp} with data ${JSON.stringify(dpValue)}`);
2439
+ break;
2440
+ }
2441
+ },
2442
+ },
2398
2443
  eurotronic_thermostat: {
2399
2444
  cluster: 'hvacThermostat',
2400
2445
  type: ['attributeReport', 'readResponse'],
@@ -2598,6 +2643,31 @@ const converters = {
2598
2643
  }
2599
2644
  },
2600
2645
  },
2646
+ ts011f_plug_indicator_mode: {
2647
+ cluster: 'genOnOff',
2648
+ type: ['attributeReport', 'readResponse'],
2649
+ convert: (model, msg, publish, options, meta) => {
2650
+ const property = 'tuyaBacklightMode'; // 0x8001 or 32769
2651
+ if (msg.data.hasOwnProperty(property)) {
2652
+ const value = msg.data[property];
2653
+ const lookup = {0: 'off', 1: 'off/on', 2: 'on/off', 3: 'on'};
2654
+ if (lookup.hasOwnProperty(value)) {
2655
+ return {indicator_mode: lookup[value]};
2656
+ }
2657
+ }
2658
+ },
2659
+ },
2660
+ ts011f_plug_child_mode: {
2661
+ cluster: 'genOnOff',
2662
+ type: ['attributeReport', 'readResponse'],
2663
+ convert: (model, msg, publish, options, meta) => {
2664
+ const property = (0x8000).toString(); // 32768
2665
+ if (msg.data.hasOwnProperty(property)) {
2666
+ const value = msg.data[property];
2667
+ return {child_lock: value ? 'LOCK' : 'UNLOCK'};
2668
+ }
2669
+ },
2670
+ },
2601
2671
  WSZ01_on_off_action: {
2602
2672
  cluster: 65029,
2603
2673
  type: 'raw',
@@ -5020,27 +5090,28 @@ const converters = {
5020
5090
  }
5021
5091
  },
5022
5092
  },
5023
- legrand_device_mode: {
5093
+ legrand_cluster_fc01: {
5024
5094
  cluster: 'manuSpecificLegrandDevices',
5025
5095
  type: ['readResponse'],
5026
5096
  convert: (model, msg, publish, options, meta) => {
5027
5097
  const payload = {};
5028
- const option0 = msg.data['0'];
5029
- // Beware that mode depends on device type
5030
- // contactor
5031
- if (option0 === 0x0003) payload.device_mode = 'switch';
5032
- else if (option0 === 0x0004) payload.device_mode = 'auto';
5033
- // dimmer
5034
- else if (option0 === 0x0101) payload.device_mode = 'dimmer_on';
5035
- else if (option0 === 0x0100) payload.device_mode = 'dimmer_off';
5036
- // pilot wire
5037
- else if (option0 === 0x0002) payload.device_mode = 'pilot_on';
5038
- else if (option0 === 0x0001) payload.device_mode = 'pilot_off';
5039
- // unknown case
5040
- else {
5041
- meta.logger.warn(`device_mode ${option0} not recognized, please fix me`);
5042
- payload.device_mode = 'unknown';
5098
+
5099
+ if (msg.data.hasOwnProperty('0')) {
5100
+ const option0 = msg.data['0'];
5101
+
5102
+ if (option0 === 0x0001) payload.device_mode = 'pilot_off';
5103
+ else if (option0 === 0x0002) payload.device_mode = 'pilot_on';
5104
+ else if (option0 === 0x0003) payload.device_mode = 'switch';
5105
+ else if (option0 === 0x0004) payload.device_mode = 'auto';
5106
+ else if (option0 === 0x0100) payload.device_mode = 'dimmer_off';
5107
+ else if (option0 === 0x0101) payload.device_mode = 'dimmer_on';
5108
+ else {
5109
+ meta.logger.warn(`device_mode ${option0} not recognized, please fix me`);
5110
+ payload.device_mode = 'unknown';
5111
+ }
5043
5112
  }
5113
+ if (msg.data.hasOwnProperty('1')) payload.permanent_led = msg.data['1'] === 0x00 ? 'OFF' : 'ON';
5114
+ if (msg.data.hasOwnProperty('2')) payload.led_when_on = msg.data['2'] === 0x00 ? 'OFF' : 'ON';
5044
5115
  return payload;
5045
5116
  },
5046
5117
  },
@@ -5290,8 +5361,11 @@ const converters = {
5290
5361
  if (index == 1) {
5291
5362
  payload.voltage = value;
5292
5363
  payload.battery = batteryVoltageToPercentage(value, '3V_2100');
5293
- } else if (index === 3) payload.temperature = calibrateAndPrecisionRoundOptions(value, options, 'temperature'); // 0x03
5294
- else if (index === 5) {
5364
+ } else if (index === 3) {
5365
+ if (!['WXCJKG11LM ', 'WXCJKG12LM', 'WXCJKG13LM'].includes(model.model)) {
5366
+ payload.temperature = calibrateAndPrecisionRoundOptions(value, options, 'temperature'); // 0x03
5367
+ }
5368
+ } else if (index === 5) {
5295
5369
  if (['JT-BZ-01AQ/A', 'RTCZCGQ11LM'].includes(model.model)) payload.power_outage_count = value;
5296
5370
  } else if (index === 100) {
5297
5371
  if (['QBKG20LM', 'QBKG31LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM'].includes(model.model)) {
@@ -5323,8 +5397,9 @@ const converters = {
5323
5397
  payload.presence_event = {0: 'enter', 1: 'leave', 2: 'left_enter', 3: 'right_leave', 4: 'right_enter',
5324
5398
  5: 'left_leave', 6: 'approach', 7: 'away', 255: null}[value];
5325
5399
  }
5326
- } else if (index ===103) payload.monitoring_mode = value === 1 ? 'left_right' : 'undirected'; // RTCZCGQ11LM
5327
- else if (index === 105) {
5400
+ } else if (index === 103) {
5401
+ if (['RTCZCGQ11LM'].includes(model.model)) payload.monitoring_mode = value === 1 ? 'left_right' : 'undirected';
5402
+ } else if (index === 105) {
5328
5403
  if (['RTCGQ13LM'].includes(model.model)) {
5329
5404
  payload.motion_sensitivity = {1: 'low', 2: 'medium', 3: 'high'}[value];
5330
5405
  } else if (['RTCZCGQ11LM'].includes(model.model)) {
@@ -8260,79 +8335,89 @@ const converters = {
8260
8335
  return {action};
8261
8336
  },
8262
8337
  },
8263
- ZMAM02: {
8338
+ ZMAM02_cover: {
8264
8339
  cluster: 'manuSpecificTuya',
8265
- type: ['commandDataResponse', 'commandDataReport', 'commandSetDataresponse'],
8340
+ type: ['commandDataReport', 'commandDataResponse'],
8341
+ options: [exposes.options.invert_cover()],
8266
8342
  convert: (model, msg, publish, options, meta) => {
8267
- const result = {};
8268
- // let value = tuya.getDataValue(dpValue);
8269
- // let result = null;
8270
- for (const dpValue of msg.data.dpValues) {
8271
- const value = tuya.getDataValue(dpValue);
8272
- switch (dpValue.dp) {
8273
- case tuya.dataPoints.AM02Control:
8274
- result.control = tuya.ZMAM02.AM02Control[value];
8275
- break;
8276
- case tuya.dataPoints.AM02PercentControl:
8277
- result.percent_control = value;
8343
+ const dpValue = tuya.firstDpValue(msg, meta, 'ZMAM02_cover');
8344
+ const dp = dpValue.dp;
8345
+ const value = tuya.getDataValue(dpValue);
8346
+ switch (dp) {
8347
+ case tuya.dataPoints.coverPosition: // Started moving to position (triggered from Zigbee)
8348
+ case tuya.dataPoints.coverArrived: { // Arrived at position
8349
+ const running = dp === tuya.dataPoints.coverArrived ? false : true;
8350
+ const invert = tuya.isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover;
8351
+ const position = invert ? 100 - (value & 0xFF) : (value & 0xFF);
8352
+ if (position > 0 && position <= 100) {
8353
+ return {running, position, state: 'OPEN'};
8354
+ } else if (position == 0) { // Report fully closed
8355
+ return {running, position, state: 'CLOSE'};
8356
+ } else {
8357
+ return {running}; // Not calibrated yet, no position is available
8358
+ }
8359
+ }
8360
+ case tuya.dataPoints.coverSpeed: // Cover is reporting its current speed setting
8361
+ return {motor_speed: value};
8362
+ case tuya.dataPoints.state: // Ignore the cover state, it's not reliable between different covers!
8363
+ case tuya.dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers!
8364
+ break;
8365
+ case tuya.dataPoints.config: // Returned by configuration set; ignore
8366
+ break;
8367
+ case tuya.dataPoints.AM02MotorWorkingMode:
8368
+ switch (value) {
8369
+ case 0: // continuous 1
8370
+ return {motor_working_mode: 'continuous'};
8371
+ case 1: // intermittently
8372
+ return {motor_working_mode: 'intermittently'};
8373
+ default:
8374
+ meta.logger.warn('ZMAM02: ' +
8375
+ `Mode ${value} is not recognized.`);
8278
8376
  break;
8279
- case tuya.dataPoints.AM02ControlBackMode:
8280
- result.control_back_mode = tuya.ZMAM02.AM02Direction[value];
8377
+ }
8378
+ break;
8379
+ case tuya.dataPoints.AM02Border:
8380
+ switch (value) {
8381
+ case 0: // up
8382
+ return {border: 'up'};
8383
+ case 1: // down
8384
+ return {border: 'down'};
8385
+ case 2: // down_delete
8386
+ return {border: 'down_delete'};
8387
+ default:
8388
+ meta.logger.warn('zigbee-herdsman-converters:ZM_AM_02: ' +
8389
+ `Mode ${value} is not recognized.`);
8281
8390
  break;
8282
- case tuya.dataPoints.AM02MotorWorkingMode:
8283
- switch (value) {
8284
- case 0: // continuous 1
8285
- result.motor_working_mode = 'continuous';
8286
- break;
8287
- case 1: // intermittently
8288
- result.motor_working_mode = 'intermittently';
8289
- break;
8290
- default:
8291
- meta.logger.warn('zigbee-herdsman-converters:ZM_AM_02: ' +
8292
- `Mode ${value} is not recognized.`);
8293
- break;
8294
- }
8391
+ }
8392
+ break;
8393
+ case tuya.dataPoints.AM02Direction:
8394
+ switch (value) {
8395
+ case 0:
8396
+ return {motor_direction: 'forward'};
8397
+ case 1:
8398
+ return {motor_direction: 'back'};
8399
+ default:
8400
+ meta.logger.warn('ZMAM02: ' +
8401
+ `Mode ${value} is not recognized.`);
8295
8402
  break;
8296
- case tuya.dataPoints.AM02Border:
8297
- switch (value) {
8298
- case 0: // up
8299
- result.border = 'up';
8300
- break;
8301
- case 1: // down
8302
- result.border = 'down';
8303
- break;
8304
- case 2: // down_delete
8305
- result.border = 'down_delete';
8306
- break;
8307
- default:
8308
- meta.logger.warn('zigbee-herdsman-converters:ZM_AM_02: ' +
8403
+ }
8404
+ break;
8405
+ case tuya.dataPoints.AM02Mode:
8406
+ switch (value) {
8407
+ case 0: // morning
8408
+ return {mode: 'morning'};
8409
+ case 1: // night
8410
+ return {mode: 'night'};
8411
+ default:
8412
+ meta.logger.warn('zigbee-herdsman-converters:ZM_AM_02: ' +
8309
8413
  `Mode ${value} is not recognized.`);
8310
- break;
8311
- }
8312
- break;
8313
- case tuya.dataPoints.AM02PercentState:
8314
- result.percent_state = value;
8315
8414
  break;
8316
- case tuya.dataPoints.AM02Mode:
8317
- switch (value) {
8318
- case 0: // morning
8319
- result.mode = 'morning';
8320
- break;
8321
- case 1: // night
8322
- result.mode = 'night';
8323
- break;
8324
- default:
8325
- meta.logger.warn('zigbee-herdsman-converters:ZM_AM_02: ' +
8326
- `Mode ${value} is not recognized.`);
8327
- break;
8328
- }
8329
- break;
8330
- default:
8331
- meta.logger.warn(`fromZigbee.Zemismart Shader Konverter (Zm_AM02): NOT RECOGNIZED ` +
8332
- `DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`);
8333
8415
  }
8416
+ break;
8417
+ default: // Unknown code
8418
+ meta.logger.warn(`ZMAM02_cover: Unhandled DP #${dp} for ${meta.device.manufacturerName}:
8419
+ ${JSON.stringify(dpValue)}`);
8334
8420
  }
8335
- return result;
8336
8421
  },
8337
8422
  },
8338
8423
  // #endregion
@@ -2211,7 +2211,7 @@ const converters = {
2211
2211
  key: ['led_disabled_night'],
2212
2212
  convertSet: async (entity, key, value, meta) => {
2213
2213
  if (['ZNCZ04LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM',
2214
- 'QBKG31LM', 'QBKG34LM', 'DLKZMK11LM'].includes(meta.mapped.model)) {
2214
+ 'QBKG31LM', 'QBKG34LM', 'DLKZMK11LM', 'SSM-U01'].includes(meta.mapped.model)) {
2215
2215
  await entity.write('aqaraOpple', {0x0203: {value: value ? 1 : 0, type: 0x10}}, manufacturerOptions.xiaomi);
2216
2216
  } else if (['ZNCZ11LM'].includes(meta.mapped.model)) {
2217
2217
  const payload = value ?
@@ -2226,7 +2226,7 @@ const converters = {
2226
2226
  },
2227
2227
  convertGet: async (entity, key, meta) => {
2228
2228
  if (['ZNCZ04LM', 'ZNCZ15LM', 'QBCZ15LM', 'QBCZ14LM', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM',
2229
- 'QBKG31LM', 'QBKG34LM', 'DLKZMK11LM'].includes(meta.mapped.model)) {
2229
+ 'QBKG31LM', 'QBKG34LM', 'DLKZMK11LM', 'SSM-U01'].includes(meta.mapped.model)) {
2230
2230
  await entity.read('aqaraOpple', [0x0203], manufacturerOptions.xiaomi);
2231
2231
  } else {
2232
2232
  throw new Error('Not supported');
@@ -3206,6 +3206,30 @@ const converters = {
3206
3206
  await entity.read('genOnOff', ['moesStartUpOnOff']);
3207
3207
  },
3208
3208
  },
3209
+ moes_switch: {
3210
+ key: ['power_on_behavior', 'indicate_light'],
3211
+ convertSet: async (entity, key, value, meta) => {
3212
+ switch (key) {
3213
+ case 'power_on_behavior':
3214
+ await tuya.sendDataPointEnum(
3215
+ entity,
3216
+ tuya.dataPoints.moesSwitchPowerOnBehavior,
3217
+ utils.getKey(tuya.moesSwitch.powerOnBehavior, value),
3218
+ );
3219
+ break;
3220
+ case 'indicate_light':
3221
+ await tuya.sendDataPointEnum(
3222
+ entity,
3223
+ tuya.dataPoints.moesSwitchIndicateLight,
3224
+ utils.getKey(tuya.moesSwitch.indicateLight, value),
3225
+ );
3226
+ break;
3227
+ default:
3228
+ meta.logger.warn(`toZigbee.moes_switch: Unhandled Key ${key}`);
3229
+ break;
3230
+ }
3231
+ },
3232
+ },
3209
3233
  moes_thermostat_sensor: {
3210
3234
  key: ['sensor'],
3211
3235
  convertSet: async (entity, key, value, meta) => {
@@ -4499,6 +4523,10 @@ const converters = {
4499
4523
  const enableLedIfOn = value === 'ON' || (value === 'OFF' ? false : !!value);
4500
4524
  const payload = {1: {value: enableLedIfOn, type: 16}};
4501
4525
  await entity.write('manuSpecificLegrandDevices', payload, manufacturerOptions.legrand);
4526
+ return {state: {'permanent_led': value}};
4527
+ },
4528
+ convertGet: async (entity, key, meta) => {
4529
+ await entity.read('manuSpecificLegrandDevices', [0x0001], manufacturerOptions.legrand);
4502
4530
  },
4503
4531
  },
4504
4532
  legrand_settingEnableLedIfOn: {
@@ -4510,15 +4538,10 @@ const converters = {
4510
4538
  const enableLedIfOn = value === 'ON' || (value === 'OFF' ? false : !!value);
4511
4539
  const payload = {2: {value: enableLedIfOn, type: 16}};
4512
4540
  await entity.write('manuSpecificLegrandDevices', payload, manufacturerOptions.legrand);
4541
+ return {state: {'led_when_on': value}};
4513
4542
  },
4514
- },
4515
- legrand_settingEnableDimmer: {
4516
- key: ['dimmer_enabled'],
4517
- convertSet: async (entity, key, value, meta) => {
4518
- // enable the dimmer, requires a recent firmware on the device
4519
- const enableDimmer = value === 'ON' || (value === 'OFF' ? false : !!value);
4520
- const payload = {0: {value: enableDimmer ? 0x0101 : 0x0100, type: 9}};
4521
- await entity.write('manuSpecificLegrandDevices', payload, manufacturerOptions.legrand);
4543
+ convertGet: async (entity, key, meta) => {
4544
+ await entity.read('manuSpecificLegrandDevices', [0x0002], manufacturerOptions.legrand);
4522
4545
  },
4523
4546
  },
4524
4547
  legrand_deviceMode: {
@@ -5971,6 +5994,32 @@ const converters = {
5971
5994
  await entity.read('genOnOff', ['tuyaBacklightMode']);
5972
5995
  },
5973
5996
  },
5997
+ ts011f_plug_indicator_mode: {
5998
+ key: ['indicator_mode'],
5999
+ convertSet: async (entity, key, value, meta) => {
6000
+ if (typeof value === 'string') {
6001
+ value = value.toLowerCase();
6002
+ const lookup = {'off': 0, 'off/on': 1, 'on/off': 2, 'on': 3};
6003
+ utils.validateValue(value, Object.keys(lookup));
6004
+ value = lookup[value];
6005
+ }
6006
+
6007
+ if (typeof value === 'number' && value >= 0 && value <= 3) {
6008
+ await entity.write('genOnOff', {tuyaBacklightMode: value});
6009
+ } else {
6010
+ meta.logger.warn(`toZigbee.ts011f_plug_indicator_mode: Unsupported value ${value}`);
6011
+ }
6012
+ },
6013
+ convertGet: async (entity, key, meta) => {
6014
+ await entity.read('genOnOff', ['tuyaBacklightMode']);
6015
+ },
6016
+ },
6017
+ ts011f_plug_child_mode: {
6018
+ key: ['child_lock'],
6019
+ convertSet: async (entity, key, value, meta) => {
6020
+ await entity.write('genOnOff', {0x8000: {value: value === 'LOCK', type: 0x10}});
6021
+ },
6022
+ },
5974
6023
  hy_thermostat: {
5975
6024
  key: [
5976
6025
  'child_lock', 'current_heating_setpoint', 'local_temperature_calibration',
@@ -6875,30 +6924,56 @@ const converters = {
6875
6924
  }
6876
6925
  },
6877
6926
  },
6878
- ZMAM02: {
6879
- key: ['control', 'percent_control', 'mode', 'control_back_mode', 'border', 'motor_working_mode'],
6927
+ ZMAM02_cover: {
6928
+ key: ['state', 'position', 'mode', 'motor_direction', 'border', 'motor_working_mode'],
6929
+ options: [exposes.options.invert_cover()],
6880
6930
  convertSet: async (entity, key, value, meta) => {
6931
+ if (key === 'position') {
6932
+ if (value >= 0 && value <= 100) {
6933
+ const invert = tuya.isCoverInverted(meta.device.manufacturerName) ?
6934
+ !meta.options.invert_cover : meta.options.invert_cover;
6935
+
6936
+ value = invert ? 100 - value : value;
6937
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.coverPosition, value);
6938
+ } else {
6939
+ throw new Error('TuYa_cover_control: Curtain motor position is out of range');
6940
+ }
6941
+ } else if (key === 'state') {
6942
+ const stateEnums = tuya.getCoverStateEnums(meta.device.manufacturerName);
6943
+ meta.logger.debug(`ZMAM02: Using state enums for ${meta.device.manufacturerName}:
6944
+ ${JSON.stringify(stateEnums)}`);
6945
+ value = value.toLowerCase();
6946
+ switch (value) {
6947
+ case 'close':
6948
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02Control, stateEnums.close);
6949
+ break;
6950
+ case 'open':
6951
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02Control, stateEnums.open);
6952
+ break;
6953
+ case 'stop':
6954
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02Control, stateEnums.stop);
6955
+ break;
6956
+ default:
6957
+ throw new Error('ZMAM02: Invalid command received');
6958
+ }
6959
+ }
6881
6960
  switch (key) {
6882
- case 'control':
6883
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02Control, utils.getKey(tuya.ZMAM02.AM02Control, value));
6884
- break;
6885
- case 'percent_control':
6886
- await tuya.sendDataPointValue(entity, tuya.dataPoints.AM02PercentState, value);
6887
- break;
6888
6961
  case 'mode':
6889
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02Mode, utils.getKey(tuya.ZMAM02.AM02Mode, value));
6962
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02Mode, utils.getKey(tuya.ZMLookups.AM02Mode, value));
6890
6963
  break;
6891
- case 'control_back_mode':
6892
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02ControlBackMode, utils.getKey(tuya.ZMAM02.AM02Direction, value));
6964
+ case 'motor_direction':
6965
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02Direction, utils.getKey(tuya.ZMLookups.AM02Direction, value));
6893
6966
  break;
6894
6967
  case 'border':
6895
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02Border, utils.getKey(tuya.ZMAM02.AM02Border, value));
6968
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02Border, utils.getKey(tuya.ZMLookups.AM02Border, value));
6896
6969
  break;
6897
6970
  case 'motor_working_mode':
6898
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.AM02MotorWorkingMode, utils.getKey(tuya.ZMAM02.AM02MotorMode, value));
6971
+ await tuya.sendDataPointEnum(
6972
+ entity,
6973
+ tuya.dataPoints.AM02MotorWorkingMode,
6974
+ utils.getKey(tuya.ZMLookups.AM02MotorWorkingMode,
6975
+ value));
6899
6976
  break;
6900
- default: // Unknown Key
6901
- meta.logger.warn(`toZigbee.ZMAM02: Unhandled Key ${key}`);
6902
6977
  }
6903
6978
  },
6904
6979
  },
@@ -12,12 +12,12 @@ module.exports = [
12
12
  model: 'K4003C/L4003C/N4003C/NT4003C',
13
13
  vendor: 'BTicino',
14
14
  description: 'Light switch with neutral',
15
- fromZigbee: [fz.identify, fz.on_off, fz.K4003C_binary_input],
15
+ fromZigbee: [fz.identify, fz.on_off, fz.K4003C_binary_input, fz.legrand_cluster_fc01],
16
16
  toZigbee: [tz.on_off, tz.legrand_settingAlwaysEnableLed, tz.legrand_settingEnableLedIfOn, tz.legrand_identify],
17
17
  exposes: [
18
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'),
19
+ exposes.binary('permanent_led', ea.ALL, 'ON', 'OFF').withDescription('Enable or disable the permanent blue LED'),
20
+ exposes.binary('led_when_on', ea.ALL, 'ON', 'OFF').withDescription('Enables the LED when the light is on'),
21
21
  ],
22
22
  configure: async (device, coordinatorEndpoint, logger) => {
23
23
  const endpoint = device.getEndpoint(1);
@@ -30,17 +30,17 @@ module.exports = [
30
30
  vendor: 'BTicino',
31
31
  description: 'Dimmer switch with neutral',
32
32
  extend: extend.light_onoff_brightness({noConfigure: true}),
33
- fromZigbee: [fz.brightness, fz.identify, fz.on_off, fz.lighting_ballast_configuration],
33
+ fromZigbee: [fz.brightness, fz.identify, fz.on_off, fz.lighting_ballast_configuration, fz.legrand_cluster_fc01],
34
34
  toZigbee: [tz.light_onoff_brightness, tz.legrand_settingAlwaysEnableLed, tz.legrand_settingEnableLedIfOn,
35
- tz.legrand_settingEnableDimmer, tz.legrand_identify, tz.ballast_config],
35
+ tz.legrand_deviceMode, tz.legrand_identify, tz.ballast_config],
36
36
  exposes: [e.light_brightness(),
37
37
  exposes.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254)
38
38
  .withDescription('Specifies the minimum brightness value'),
39
39
  exposes.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254)
40
40
  .withDescription('Specifies the maximum brightness value'),
41
- exposes.binary('dimmer_enabled', ea.STATE_SET, 'ON', 'OFF').withDescription('Allow the device to change brightness'),
42
- exposes.binary('permanent_led', ea.STATE_SET, 'ON', 'OFF').withDescription('Enable or disable the permanent blue LED'),
43
- exposes.binary('led_when_on', ea.STATE_SET, 'ON', 'OFF').withDescription('Enables the LED when the light is on')],
41
+ exposes.binary('device_mode', ea.ALL, 'dimmer_on', 'dimmer_off').withDescription('Allow the device to change brightness'),
42
+ exposes.binary('permanent_led', ea.ALL, 'ON', 'OFF').withDescription('Enable or disable the permanent blue LED'),
43
+ exposes.binary('led_when_on', ea.ALL, 'ON', 'OFF').withDescription('Enables the LED when the light is on')],
44
44
  configure: async (device, coordinatorEndpoint, logger) => {
45
45
  await extend.light_onoff_brightness().configure(device, coordinatorEndpoint, logger);
46
46
  const endpoint = device.getEndpoint(1);
@@ -76,10 +76,10 @@ module.exports = [
76
76
  description: 'DIN power consumption module (same as Legrand 412015)',
77
77
  vendor: 'BTicino',
78
78
  extend: extend.switch(),
79
- fromZigbee: [fz.identify, fz.on_off, fz.electrical_measurement, fz.legrand_device_mode, fz.ignore_basic_report, fz.ignore_genOta],
79
+ fromZigbee: [fz.identify, fz.on_off, fz.electrical_measurement, fz.legrand_cluster_fc01, fz.ignore_basic_report, fz.ignore_genOta],
80
80
  toZigbee: [tz.legrand_deviceMode, tz.on_off, tz.legrand_identify, tz.electrical_measurement_power],
81
81
  exposes: [exposes.switch().withState('state', true, 'On/off (works only if device is in "switch" mode)'),
82
- e.power().withAccess(ea.STATE_GET), exposes.enum( 'device_mode', ea.ALL, ['switch', 'auto'])
82
+ e.power().withAccess(ea.STATE_GET), exposes.enum('device_mode', ea.ALL, ['switch', 'auto'])
83
83
  .withDescription('switch: allow on/off, auto will use wired action via C1/C2 on contactor for example with HC/HP')],
84
84
  configure: async (device, coordinatorEndpoint, logger) => {
85
85
  const endpoint = device.getEndpoint(1);
@@ -375,4 +375,44 @@ module.exports = [
375
375
  },
376
376
  exposes: [e.battery(), e.illuminance(), e.temperature(), e.humidity(), e.pressure()],
377
377
  },
378
+ {
379
+ zigbeeModel: ['EFEKTA_eFlower_Pro'],
380
+ model: 'EFEKTA_eFlower_Pro',
381
+ vendor: 'Custom devices (DiY)',
382
+ description: '[Plant Wattering Sensor with e-ink display 2.13](https://efektalab.com/eFlowerPro)',
383
+ fromZigbee: [fz.temperature, fz.humidity, fz.illuminance, fz.soil_moisture, fz.battery],
384
+ toZigbee: [tz.factory_reset],
385
+ configure: async (device, coordinatorEndpoint, logger) => {
386
+ const firstEndpoint = device.getEndpoint(1);
387
+ await reporting.bind(firstEndpoint, coordinatorEndpoint, [
388
+ 'genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'msIlluminanceMeasurement', 'msSoilMoisture']);
389
+ const overides = {min: 0, max: 21600, change: 0};
390
+ await reporting.batteryVoltage(firstEndpoint, overides);
391
+ await reporting.batteryPercentageRemaining(firstEndpoint, overides);
392
+ await reporting.temperature(firstEndpoint, overides);
393
+ await reporting.humidity(firstEndpoint, overides);
394
+ await reporting.illuminance(firstEndpoint, overides);
395
+ await reporting.soil_moisture(firstEndpoint, overides);
396
+ },
397
+ exposes: [e.soil_moisture(), e.battery(), e.illuminance(), e.temperature(), e.humidity()],
398
+ },
399
+ {
400
+ zigbeeModel: ['EFEKTA_eTH102'],
401
+ model: 'EFEKTA_eTH102',
402
+ vendor: 'Custom devices (DiY)',
403
+ description: '[Mini digital thermometer & hygrometer with e-ink1.02](http://efektalab.com/eTH102)',
404
+ fromZigbee: [fz.temperature, fz.humidity, fz.battery],
405
+ toZigbee: [tz.factory_reset],
406
+ configure: async (device, coordinatorEndpoint, logger) => {
407
+ const endpoint = device.getEndpoint(1);
408
+ await reporting.bind(endpoint, coordinatorEndpoint, [
409
+ 'genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity']);
410
+ const overides = {min: 0, max: 21600, change: 0};
411
+ await reporting.batteryVoltage(endpoint, overides);
412
+ await reporting.batteryPercentageRemaining(endpoint, overides);
413
+ await reporting.temperature(endpoint, overides);
414
+ await reporting.humidity(endpoint, overides);
415
+ },
416
+ exposes: [e.battery(), e.temperature(), e.humidity()],
417
+ },
378
418
  ];
@@ -62,10 +62,14 @@ const gledoptoExtend = {
62
62
  }),
63
63
  };
64
64
 
65
- const configureReadModelID = async (device) => {
65
+ const configureReadModelID = async (device, coordinatorEndpoint, logger) => {
66
66
  // https://github.com/Koenkk/zigbee-herdsman-converters/issues/3016#issuecomment-1027726604
67
67
  const endpoint = device.endpoints[0];
68
- await endpoint.read('genBasic', ['modelId']);
68
+ const oldModel = device.modelID;
69
+ const newModel = (await endpoint.read('genBasic', ['modelId'])).modelId;
70
+ if (oldModel != newModel) {
71
+ logger.info(`Detected Gledopto device mode change, from '${oldModel}' to '${newModel}'`);
72
+ }
69
73
  };
70
74
 
71
75
  module.exports = [
@@ -125,7 +129,7 @@ module.exports = [
125
129
  extend: gledoptoExtend.light_onoff_brightness_colortemp({noConfigure: true}),
126
130
  configure: async (device, coordinatorEndpoint, logger) => {
127
131
  await extend.light_onoff_brightness_colortemp().configure(device, coordinatorEndpoint, logger);
128
- await configureReadModelID(device);
132
+ await configureReadModelID(device, coordinatorEndpoint, logger);
129
133
  },
130
134
  },
131
135
  {
@@ -192,7 +196,7 @@ module.exports = [
192
196
  extend: gledoptoExtend.light_onoff_brightness_colortemp_color({noConfigure: true}),
193
197
  configure: async (device, coordinatorEndpoint, logger) => {
194
198
  await extend.light_onoff_brightness_colortemp_color().configure(device, coordinatorEndpoint, logger);
195
- await configureReadModelID(device);
199
+ await configureReadModelID(device, coordinatorEndpoint, logger);
196
200
  },
197
201
  },
198
202
  {
@@ -254,7 +258,7 @@ module.exports = [
254
258
  extend: gledoptoExtend.light_onoff_brightness_color({noConfigure: true}),
255
259
  configure: async (device, coordinatorEndpoint, logger) => {
256
260
  await extend.light_onoff_brightness_color().configure(device, coordinatorEndpoint, logger);
257
- await configureReadModelID(device);
261
+ await configureReadModelID(device, coordinatorEndpoint, logger);
258
262
  },
259
263
  },
260
264
  {
@@ -267,7 +271,7 @@ module.exports = [
267
271
  meta: {disableDefaultResponse: true},
268
272
  configure: async (device, coordinatorEndpoint, logger) => {
269
273
  await extend.light_onoff_brightness_colortemp_color().configure(device, coordinatorEndpoint, logger);
270
- await configureReadModelID(device);
274
+ await configureReadModelID(device, coordinatorEndpoint, logger);
271
275
  },
272
276
  },
273
277
  {
@@ -292,7 +296,7 @@ module.exports = [
292
296
  extend: gledoptoExtend.light_onoff_brightness({noConfigure: true}),
293
297
  configure: async (device, coordinatorEndpoint, logger) => {
294
298
  await extend.light_onoff_brightness().configure(device, coordinatorEndpoint, logger);
295
- await configureReadModelID(device);
299
+ await configureReadModelID(device, coordinatorEndpoint, logger);
296
300
  },
297
301
  },
298
302
  {