zigbee-herdsman-converters 14.0.294 → 14.0.298

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.
@@ -108,6 +108,26 @@ const converters = {
108
108
  if (msg.data.hasOwnProperty('tempSetpointHoldDuration')) {
109
109
  result[postfixWithEndpointName('temperature_setpoint_hold_duration', msg, model)] = msg.data['tempSetpointHoldDuration'];
110
110
  }
111
+ if (msg.data.hasOwnProperty('minHeatSetpointLimit')) {
112
+ let value = precisionRound(msg.data['minHeatSetpointLimit'], 2) / 100;
113
+ value = value < -250 ? 0 : value;
114
+ result[postfixWithEndpointName('min_heat_setpoint_limit', msg, model)] = value;
115
+ }
116
+ if (msg.data.hasOwnProperty('maxHeatSetpointLimit')) {
117
+ let value = precisionRound(msg.data['maxHeatSetpointLimit'], 2) / 100;
118
+ value = value < -250 ? 0 : value;
119
+ result[postfixWithEndpointName('max_heat_setpoint_limit', msg, model)] = value;
120
+ }
121
+ if (msg.data.hasOwnProperty('absMinHeatSetpointLimit')) {
122
+ let value = precisionRound(msg.data['absMinHeatSetpointLimit'], 2) / 100;
123
+ value = value < -250 ? 0 : value;
124
+ result[postfixWithEndpointName('abs_min_heat_setpoint_limit', msg, model)] = value;
125
+ }
126
+ if (msg.data.hasOwnProperty('absMaxHeatSetpointLimit')) {
127
+ let value = precisionRound(msg.data['absMaxHeatSetpointLimit'], 2) / 100;
128
+ value = value < -250 ? 0 : value;
129
+ result[postfixWithEndpointName('abs_max_heat_setpoint_limit', msg, model)] = value;
130
+ }
111
131
  return result;
112
132
  },
113
133
  },
@@ -3004,9 +3024,71 @@ const converters = {
3004
3024
  result[postfixWithEndpointName('running_state', msg, model)] = 'idle';
3005
3025
  }
3006
3026
  }
3027
+ if (msg.data.hasOwnProperty('danfossLoadBalancingEnable')) {
3028
+ result[postfixWithEndpointName('load_balancing_enable', msg, model)] = (msg.data['danfossLoadBalancingEnable'] === 1);
3029
+ }
3030
+ if (msg.data.hasOwnProperty('danfossLoadRoomMean')) {
3031
+ result[postfixWithEndpointName('load_room_mean', msg, model)] = msg.data['danfossLoadRoomMean'];
3032
+ }
3007
3033
  if (msg.data.hasOwnProperty('danfossLoadEstimate')) {
3008
3034
  result[postfixWithEndpointName('load_estimate', msg, model)] = msg.data['danfossLoadEstimate'];
3009
3035
  }
3036
+ // Danfoss Icon Converters
3037
+ if (msg.data.hasOwnProperty('danfossRoomStatusCode')) {
3038
+ result[postfixWithEndpointName('room_status_code', msg, model)] =
3039
+ constants.danfossRoomStatusCode.hasOwnProperty(msg.data['danfossRoomStatusCode']) ?
3040
+ constants.danfossRoomStatusCode[msg.data['danfossRoomStatusCode']] :
3041
+ msg.data['danfossRoomStatusCode'];
3042
+ }
3043
+ if (msg.data.hasOwnProperty('danfossOutputStatus')) {
3044
+ result[postfixWithEndpointName('output_status', msg, model)] =
3045
+ constants.danfossOutputStatus.hasOwnProperty(msg.data['danfossOutputStatus']) ?
3046
+ constants.danfossOutputStatus[msg.data['danfossOutputStatus']] :
3047
+ msg.data['danfossOutputStatus'];
3048
+ }
3049
+ return result;
3050
+ },
3051
+ },
3052
+ danfoss_icon_battery: {
3053
+ cluster: 'genPowerCfg',
3054
+ type: ['attributeReport', 'readResponse'],
3055
+ convert: (model, msg, publish, options, meta) => {
3056
+ const result = {};
3057
+ if (msg.data.hasOwnProperty('batteryPercentageRemaining')) {
3058
+ // Some devices do not comply to the ZCL and report a
3059
+ // batteryPercentageRemaining of 100 when the battery is full (should be 200).
3060
+ const dontDividePercentage = model.meta && model.meta.battery && model.meta.battery.dontDividePercentage;
3061
+ let percentage = msg.data['batteryPercentageRemaining'];
3062
+ percentage = dontDividePercentage ? percentage : percentage / 2;
3063
+
3064
+ result[postfixWithEndpointName('battery', msg, model)] = precisionRound(percentage, 2);
3065
+ }
3066
+ return result;
3067
+ },
3068
+ },
3069
+ danfoss_icon_regulator: {
3070
+ cluster: 'haDiagnostic',
3071
+ type: ['attributeReport', 'readResponse'],
3072
+ convert: (model, msg, publish, options, meta) => {
3073
+ const result = {};
3074
+ if (msg.data.hasOwnProperty('danfossSystemStatusCode')) {
3075
+ result[postfixWithEndpointName('system_status_code', msg, model)] =
3076
+ constants.danfossSystemStatusCode.hasOwnProperty(msg.data['danfossSystemStatusCode']) ?
3077
+ constants.danfossSystemStatusCode[msg.data['danfossSystemStatusCode']] :
3078
+ msg.data['danfossSystemStatusCode'];
3079
+ }
3080
+ if (msg.data.hasOwnProperty('danfossSystemStatusWater')) {
3081
+ result[postfixWithEndpointName('system_status_water', msg, model)] =
3082
+ constants.danfossSystemStatusWater.hasOwnProperty(msg.data['danfossSystemStatusWater']) ?
3083
+ constants.danfossSystemStatusWater[msg.data['danfossSystemStatusWater']] :
3084
+ msg.data['danfossSystemStatusWater'];
3085
+ }
3086
+ if (msg.data.hasOwnProperty('danfossMultimasterRole')) {
3087
+ result[postfixWithEndpointName('multimaster_role', msg, model)] =
3088
+ constants.danfossMultimasterRole.hasOwnProperty(msg.data['danfossMultimasterRole']) ?
3089
+ constants.danfossMultimasterRole[msg.data['danfossMultimasterRole']] :
3090
+ msg.data['danfossMultimasterRole'];
3091
+ }
3010
3092
  return result;
3011
3093
  },
3012
3094
  },
@@ -3456,6 +3538,121 @@ const converters = {
3456
3538
  }
3457
3539
  },
3458
3540
  },
3541
+ haozee_thermostat: {
3542
+ cluster: 'manuSpecificTuya',
3543
+ type: ['commandGetData', 'commandSetDataResponse'],
3544
+ convert: (model, msg, publish, options, meta) => {
3545
+ const dp = msg.data.dp; // First we get the data point ID
3546
+ const value = tuya.getDataValue(msg.data.datatype, msg.data.data);
3547
+ const presetLookup = {0: 'auto', 1: 'manual', 2: 'off', 3: 'on'};
3548
+ switch (dp) {
3549
+ case tuya.dataPoints.haozeeSystemMode:
3550
+ return {preset: presetLookup[value]};
3551
+ case tuya.dataPoints.haozeeHeatingSetpoint:
3552
+ return {current_heating_setpoint: (value / 10).toFixed(1)};
3553
+ case tuya.dataPoints.haozeeLocalTemp:
3554
+ return {local_temperature: (value / 10).toFixed(1)};
3555
+ case tuya.dataPoints.haozeeBoostHeatingCountdown:
3556
+ // quick heating countdown
3557
+ return {boost_heating_countdown: value};
3558
+ case tuya.dataPoints.haozeeWindowDetection:
3559
+ // window check
3560
+ return {window_detection: value ? 'ON' : 'OFF'};
3561
+ case tuya.dataPoints.haozeeWindowState:
3562
+ // window state
3563
+ return {window: value ? 'OPEN' : 'CLOSED'};
3564
+ case tuya.dataPoints.haozeeChildLock:
3565
+ return {child_lock: value ? 'LOCK' : 'UNLOCK'};
3566
+ case tuya.dataPoints.haozeeBattery:
3567
+ // battery
3568
+ return {battery: value};
3569
+ case tuya.dataPoints.haozeeFaultAlarm:
3570
+ return {error: value ? 'ON': 'OFF'};
3571
+ case tuya.dataPoints.haozeeScheduleMonday:
3572
+ // Monday
3573
+ return {
3574
+ 'monday_schedule': ' ' + value[1] + 'h:' + value[2] + 'm ' + value[4] / 10 + '°C' +
3575
+ ', ' + value[5] + 'h:' + value[6] + 'm ' + value[8] / 10 + '°C' +
3576
+ ', ' + value[9] + 'h:' + value[10] + 'm ' + value[12] / 10 + '°C' +
3577
+ ', ' + value[13] + 'h:' + value[14] + 'm ' + value[16] / 10 + '°C ',
3578
+ };
3579
+ case tuya.dataPoints.haozeeScheduleTuesday:
3580
+ // Tuesday
3581
+ return {
3582
+ 'tuesday_schedule': ' ' + value[1] + 'h:' + value[2] + 'm ' + value[4] / 10 + '°C' +
3583
+ ', ' + value[5] + 'h:' + value[6] + 'm ' + value[8] / 10 + '°C' +
3584
+ ', ' + value[9] + 'h:' + value[10] + 'm ' + value[12] / 10 + '°C' +
3585
+ ', ' + value[13] + 'h:' + value[14] + 'm ' + value[16] / 10 + '°C ',
3586
+ };
3587
+ case tuya.dataPoints.haozeeScheduleWednesday:
3588
+ // wednesday
3589
+ return {
3590
+ 'wednesday_schedule': ' ' + value[1] + 'h:' + value[2] + 'm ' + value[4] / 10 + '°C' +
3591
+ ', ' + value[5] + 'h:' + value[6] + 'm ' + value[8] / 10 + '°C' +
3592
+ ', ' + value[9] + 'h:' + value[10] + 'm ' + value[12] / 10 + '°C' +
3593
+ ', ' + value[13] + 'h:' + value[14] + 'm ' + value[16] / 10 + '°C ',
3594
+ };
3595
+ case tuya.dataPoints.haozeeScheduleThursday:
3596
+ // Thursday
3597
+ return {
3598
+ 'thursday_schedule': ' ' + value[1] + 'h:' + value[2] + 'm ' + value[4] / 10 + '°C' +
3599
+ ', ' + value[5] + 'h:' + value[6] + 'm ' + value[8] / 10 + '°C' +
3600
+ ', ' + value[9] + 'h:' + value[10] + 'm ' + value[12] / 10 + '°C' +
3601
+ ', ' + value[13] + 'h:' + value[14] + 'm ' + value[16] / 10 + '°C ',
3602
+ };
3603
+ case tuya.dataPoints.haozeeScheduleFriday:
3604
+ // Friday
3605
+ return {
3606
+ 'friday_schedule': ' ' + value[1] + 'h:' + value[2] + 'm ' + value[4] / 10 + '°C' +
3607
+ ', ' + value[5] + 'h:' + value[6] + 'm ' + value[8] / 10 + '°C' +
3608
+ ', ' + value[9] + 'h:' + value[10] + 'm ' + value[12] / 10 + '°C' +
3609
+ ', ' + value[13] + 'h:' + value[14] + 'm ' + value[16] / 10 + '°C ',
3610
+ };
3611
+ case tuya.dataPoints.haozeeScheduleSaturday:
3612
+ // Saturday
3613
+ return {
3614
+ 'saturday_schedule': ' ' + value[1] + 'h:' + value[2] + 'm ' + value[4] / 10 + '°C' +
3615
+ ', ' + value[5] + 'h:' + value[6] + 'm ' + value[8] / 10 + '°C' +
3616
+ ', ' + value[9] + 'h:' + value[10] + 'm ' + value[12] / 10 + '°C' +
3617
+ ', ' + value[13] + 'h:' + value[14] + 'm ' + value[16] / 10 + '°C ',
3618
+ };
3619
+ case tuya.dataPoints.haozeeScheduleSunday:
3620
+
3621
+ // Sunday
3622
+ return {
3623
+ 'sunday_schedule': ' ' + value[1] + 'h:' + value[2] + 'm ' + value[4] / 10 + '°C' +
3624
+ ', ' + value[5] + 'h:' + value[6] + 'm ' + value[8] / 10 + '°C' +
3625
+ ', ' + value[9] + 'h:' + value[10] + 'm ' + value[12] / 10 + '°C' +
3626
+ ', ' + value[13] + 'h:' + value[14] + 'm ' + value[16] / 10 + '°C ',
3627
+ };
3628
+
3629
+ case tuya.dataPoints.haozeeRunningState:
3630
+ // working status 0 - pause 1 -working
3631
+ return {'heating': value ? 'ON' : 'OFF'};
3632
+ case tuya.dataPoints.haozeeBoostHeating:
3633
+ // rapid heating -> boolean
3634
+ break;
3635
+ case tuya.dataPoints.haozeeTempCalibration:
3636
+ // temperature calibration
3637
+ break;
3638
+ case tuya.dataPoints.haozeeValvePosition:
3639
+ // valve position
3640
+ return {'position': value};
3641
+ case tuya.dataPoints.haozeeMinTemp:
3642
+ // lower limit temperature
3643
+ return {'min_temperature': ( value/10 ).toFixed(1)};
3644
+ case tuya.dataPoints.haozeeMaxTemp:
3645
+ // max limit temperature
3646
+ return {'max_temperature': ( value/10 ).toFixed(1)};
3647
+ case tuya.dataPoints.haozeeSoftVersion:
3648
+ // software
3649
+ break;
3650
+ default:
3651
+ meta.logger.warn(`zigbee-herdsman-converters:haozee: NOT RECOGNIZED DP #${
3652
+ dp} with data ${JSON.stringify(msg.data)}`);
3653
+ }
3654
+ },
3655
+ },
3459
3656
  tuya_air_quality: {
3460
3657
  cluster: 'manuSpecificTuya',
3461
3658
  type: ['commandSetDataResponse', 'commandGetData'],
@@ -5366,22 +5563,30 @@ const converters = {
5366
5563
  tradfri_occupancy: {
5367
5564
  cluster: 'genOnOff',
5368
5565
  type: 'commandOnWithTimedOff',
5369
- options: [exposes.options.occupancy_timeout()],
5566
+ options: [exposes.options.occupancy_timeout(), exposes.options.illuminance_below_threshold_check()],
5370
5567
  convert: (model, msg, publish, options, meta) => {
5371
- if (msg.data.ctrlbits === 1) return;
5568
+ const onlyWhenOnFlag = (msg.data.ctrlbits & 1) != 0;
5569
+ if (onlyWhenOnFlag &&
5570
+ (!options || !options.hasOwnProperty('illuminance_below_threshold_check') ||
5571
+ options.illuminance_below_threshold_check) &&
5572
+ !globalStore.hasValue(msg.endpoint, 'timer')) return;
5372
5573
 
5373
5574
  const timeout = options && options.hasOwnProperty('occupancy_timeout') ?
5374
5575
  options.occupancy_timeout : msg.data.ontime / 10;
5375
5576
 
5376
5577
  // Stop existing timer because motion is detected and set a new one.
5377
5578
  clearTimeout(globalStore.getValue(msg.endpoint, 'timer'));
5579
+ globalStore.clearValue(msg.endpoint, 'timer');
5378
5580
 
5379
5581
  if (timeout !== 0) {
5380
- const timer = setTimeout(() => publish({occupancy: false}), timeout * 1000);
5582
+ const timer = setTimeout(() => {
5583
+ publish({occupancy: false});
5584
+ globalStore.clearValue(msg.endpoint, 'timer');
5585
+ }, timeout * 1000);
5381
5586
  globalStore.putValue(msg.endpoint, 'timer', timer);
5382
5587
  }
5383
5588
 
5384
- return {occupancy: true};
5589
+ return {occupancy: true, illuminance_above_threshold: onlyWhenOnFlag};
5385
5590
  },
5386
5591
  },
5387
5592
  almond_click: {
@@ -6937,7 +7142,11 @@ const converters = {
6937
7142
  convert: (model, msg, publish, options, meta) => {
6938
7143
  const lookup = {'commandToggle': 'long', 'commandOn': 'double', 'commandOff': 'single'};
6939
7144
  let buttonMapping = null;
6940
- if (model.model === 'MSM-300ZB') {
7145
+ if (model.model === 'SBM300ZB2') {
7146
+ buttonMapping = {1: '1', 2: '2'};
7147
+ } else if (model.model === 'SBM300ZB3') {
7148
+ buttonMapping = {1: '1', 2: '2', 3: '3'};
7149
+ } else if (model.model === 'MSM-300ZB') {
6941
7150
  buttonMapping = {1: '1', 2: '2', 3: '3', 4: '4'};
6942
7151
  }
6943
7152
  const button = buttonMapping ? `${buttonMapping[msg.endpoint.ID]}_` : '';
@@ -7048,6 +7257,17 @@ const converters = {
7048
7257
  return result;
7049
7258
  },
7050
7259
  },
7260
+ tuya_operation_mode: {
7261
+ cluster: 'genOnOff',
7262
+ type: ['attributeReport', 'readResponse'],
7263
+ convert: (model, msg, publish, options, meta) => {
7264
+ if (msg.data.hasOwnProperty('tuyaOperationMode')) {
7265
+ const value = msg.data['tuyaOperationMode'];
7266
+ const lookup = {0: 'command', 1: 'event'};
7267
+ return {operation_mode: lookup[value]};
7268
+ }
7269
+ },
7270
+ },
7051
7271
  // #endregion
7052
7272
 
7053
7273
  // #region Ignore converters (these message dont need parsing).
@@ -1327,6 +1327,38 @@ const converters = {
1327
1327
  await entity.read('hvacThermostat', ['runningMode']);
1328
1328
  },
1329
1329
  },
1330
+ thermostat_min_heat_setpoint_limit: {
1331
+ key: ['min_heat_setpoint_limit'],
1332
+ convertSet: async (entity, key, value, meta) => {
1333
+ let result;
1334
+ if (meta.options.thermostat_unit === 'fahrenheit') {
1335
+ result = utils.normalizeCelsiusVersionOfFahrenheit(value) * 100;
1336
+ } else {
1337
+ result = (Math.round((value * 2).toFixed(1)) / 2).toFixed(1) * 100;
1338
+ }
1339
+ const minHeatSetpointLimit = result;
1340
+ await entity.write('hvacThermostat', {minHeatSetpointLimit});
1341
+ },
1342
+ convertGet: async (entity, key, meta) => {
1343
+ await entity.read('hvacThermostat', ['minHeatSetpointLimit']);
1344
+ },
1345
+ },
1346
+ thermostat_max_heat_setpoint_limit: {
1347
+ key: ['max_heat_setpoint_limit'],
1348
+ convertSet: async (entity, key, value, meta) => {
1349
+ let result;
1350
+ if (meta.options.thermostat_unit === 'fahrenheit') {
1351
+ result = utils.normalizeCelsiusVersionOfFahrenheit(value) * 100;
1352
+ } else {
1353
+ result = (Math.round((value * 2).toFixed(1)) / 2).toFixed(1) * 100;
1354
+ }
1355
+ const maxHeatSetpointLimit = result;
1356
+ await entity.write('hvacThermostat', {maxHeatSetpointLimit});
1357
+ },
1358
+ convertGet: async (entity, key, meta) => {
1359
+ await entity.read('hvacThermostat', ['maxHeatSetpointLimit']);
1360
+ },
1361
+ },
1330
1362
  electrical_measurement_power: {
1331
1363
  key: ['power'],
1332
1364
  convertGet: async (entity, key, meta) => {
@@ -2478,12 +2510,62 @@ const converters = {
2478
2510
  await entity.read('hvacThermostat', ['danfossWindowOpenExternal'], manufacturerOptions.danfoss);
2479
2511
  },
2480
2512
  },
2513
+ danfoss_load_balancing_enable: {
2514
+ key: ['load_balancing_enable'],
2515
+ convertSet: async (entity, key, value, meta) => {
2516
+ await entity.write('hvacThermostat', {'danfossLoadBalancingEnable': value}, manufacturerOptions.danfoss);
2517
+ return {readAfterWriteTime: 200, state: {'load_balancing_enable': value}};
2518
+ },
2519
+ convertGet: async (entity, key, meta) => {
2520
+ await entity.read('hvacThermostat', ['danfossLoadBalancingEnable'], manufacturerOptions.danfoss);
2521
+ },
2522
+ },
2523
+ danfoss_load_room_mean: {
2524
+ key: ['load_room_mean'],
2525
+ convertSet: async (entity, key, value, meta) => {
2526
+ await entity.write('hvacThermostat', {'danfossLoadRoomMean': value}, manufacturerOptions.danfoss);
2527
+ return {readAfterWriteTime: 200, state: {'load_room_mean': value}};
2528
+ },
2529
+ convertGet: async (entity, key, meta) => {
2530
+ await entity.read('hvacThermostat', ['danfossLoadRoomMean'], manufacturerOptions.danfoss);
2531
+ },
2532
+ },
2481
2533
  danfoss_load_estimate: {
2482
2534
  key: ['load_estimate'],
2483
2535
  convertGet: async (entity, key, meta) => {
2484
2536
  await entity.read('hvacThermostat', ['danfossLoadEstimate'], manufacturerOptions.danfoss);
2485
2537
  },
2486
2538
  },
2539
+ danfoss_output_status: {
2540
+ key: ['output_status'],
2541
+ convertGet: async (entity, key, meta) => {
2542
+ await entity.read('hvacThermostat', ['danfossOutputStatus'], manufacturerOptions.danfoss);
2543
+ },
2544
+ },
2545
+ danfoss_room_status_code: {
2546
+ key: ['room_status_code'],
2547
+ convertGet: async (entity, key, meta) => {
2548
+ await entity.read('hvacThermostat', ['danfossRoomStatusCode'], manufacturerOptions.danfoss);
2549
+ },
2550
+ },
2551
+ danfoss_system_status_code: {
2552
+ key: ['system_status_code'],
2553
+ convertGet: async (entity, key, meta) => {
2554
+ await entity.read('haDiagnostic', ['danfossSystemStatusCode'], manufacturerOptions.danfoss);
2555
+ },
2556
+ },
2557
+ danfoss_system_status_water: {
2558
+ key: ['system_status_water'],
2559
+ convertGet: async (entity, key, meta) => {
2560
+ await entity.read('haDiagnostic', ['danfossSystemStatusWater'], manufacturerOptions.danfoss);
2561
+ },
2562
+ },
2563
+ danfoss_multimaster_role: {
2564
+ key: ['multimaster_role'],
2565
+ convertGet: async (entity, key, meta) => {
2566
+ await entity.read('haDiagnostic', ['danfossMultimasterRole'], manufacturerOptions.danfoss);
2567
+ },
2568
+ },
2487
2569
  ZMCSW032D_cover_position: {
2488
2570
  key: ['position', 'tilt'],
2489
2571
  convertSet: async (entity, key, value, meta) => {
@@ -2650,6 +2732,68 @@ const converters = {
2650
2732
  await tuya.sendDataPointValue(entity, tuya.dataPoints.moesSminTempSet, temp);
2651
2733
  },
2652
2734
  },
2735
+ haozee_thermostat_system_mode: {
2736
+ key: ['preset'],
2737
+ convertSet: async (entity, key, value, meta) => {
2738
+ const lookup = {0: 'auto', 1: 'manual', 2: 'off', 3: 'on'};
2739
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.haozeeSystemMode, lookup[value]);
2740
+ },
2741
+ },
2742
+ haozee_thermostat_current_heating_setpoint: {
2743
+ key: ['current_heating_setpoint'],
2744
+ convertSet: async (entity, key, value, meta) => {
2745
+ const temp = Math.round(value*10);
2746
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.haozeeHeatingSetpoint, temp);
2747
+ },
2748
+ },
2749
+ haozee_thermostat_boost_heating: {
2750
+ key: ['boost_heating'],
2751
+ convertSet: async (entity, key, value, meta) => {
2752
+ await tuya.sendDataPointBool(entity, tuya.dataPoints.haozeeBoostHeating, value === 'ON');
2753
+ },
2754
+ },
2755
+ haozee_thermostat_boost_heating_countdown: {
2756
+ key: ['boost_heating_countdown'],
2757
+ convertSet: async (entity, key, value, meta) => {
2758
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.haozeeBoostHeatingCountdown, value);
2759
+ },
2760
+ },
2761
+ haozee_thermostat_window_detection: {
2762
+ key: ['window_detection'],
2763
+ convertSet: async (entity, key, value, meta) => {
2764
+ await tuya.sendDataPointBool(entity, tuya.dataPoints.haozeeWindowDetection, value === 'ON');
2765
+ },
2766
+ },
2767
+ haozee_thermostat_child_lock: {
2768
+ key: ['child_lock'],
2769
+ convertSet: async (entity, key, value, meta) => {
2770
+ await tuya.sendDataPointBool(entity, tuya.dataPoints.haozeeChildLock, value === 'LOCK');
2771
+ },
2772
+ },
2773
+ haozee_thermostat_temperature_calibration: {
2774
+ key: ['local_temperature_calibration'],
2775
+ convertSet: async (entity, key, value, meta) => {
2776
+ let temp = Math.round(value * 1);
2777
+ if (temp < 0) {
2778
+ temp = 0xFFFFFFFF + temp + 1;
2779
+ }
2780
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.haozeeTempCalibration, temp);
2781
+ },
2782
+ },
2783
+ haozee_thermostat_max_temperature: {
2784
+ key: ['max_temperature'],
2785
+ convertSet: async (entity, key, value, meta) => {
2786
+ const temp = Math.round(value*10);
2787
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.haozeeMaxTemp, temp);
2788
+ },
2789
+ },
2790
+ haozee_thermostat_min_temperature: {
2791
+ key: ['min_temperature'],
2792
+ convertSet: async (entity, key, value, meta) => {
2793
+ const temp = Math.round(value*10);
2794
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.haozeeMinTemp, temp);
2795
+ },
2796
+ },
2653
2797
  hgkg_thermostat_standby: {
2654
2798
  key: ['system_mode'],
2655
2799
  convertSet: async (entity, key, value, meta) => {
@@ -3144,7 +3288,7 @@ const converters = {
3144
3288
  },
3145
3289
  },
3146
3290
  eurotronic_valve_position: {
3147
- key: ['eurotronic_valve_position'],
3291
+ key: ['eurotronic_valve_position', 'valve_position'],
3148
3292
  convertSet: async (entity, key, value, meta) => {
3149
3293
  const payload = {0x4001: {value, type: 0x20}};
3150
3294
  await entity.write('hvacThermostat', payload, manufacturerOptions.eurotronic);
@@ -3154,7 +3298,7 @@ const converters = {
3154
3298
  },
3155
3299
  },
3156
3300
  eurotronic_trv_mode: {
3157
- key: ['eurotronic_trv_mode'],
3301
+ key: ['eurotronic_trv_mode', 'trv_mode'],
3158
3302
  convertSet: async (entity, key, value, meta) => {
3159
3303
  const payload = {0x4000: {value, type: 0x30}};
3160
3304
  await entity.write('hvacThermostat', payload, manufacturerOptions.eurotronic);
@@ -5954,6 +6098,22 @@ const converters = {
5954
6098
  await endpoint.read('genAnalogInput', ['presentValue']);
5955
6099
  },
5956
6100
  },
6101
+ tuya_operation_mode: {
6102
+ key: ['operation_mode'],
6103
+ convertSet: async (entity, key, value, meta) => {
6104
+ // modes:
6105
+ // 0 - 'command' mode. keys send commands. useful for group control
6106
+ // 1 - 'event' mode. keys send events. useful for handling
6107
+ const lookup = {command: 0, event: 1};
6108
+ const endpoint = meta.device.getEndpoint(1);
6109
+ await endpoint.write('genOnOff', {'tuyaOperationMode': lookup[value.toLowerCase()]});
6110
+ return {state: {operation_mode: value.toLowerCase()}};
6111
+ },
6112
+ convertGet: async (entity, key, meta) => {
6113
+ const endpoint = meta.device.getEndpoint(1);
6114
+ await endpoint.read('genOnOff', ['tuyaOperationMode']);
6115
+ },
6116
+ },
5957
6117
  // #endregion
5958
6118
 
5959
6119
  // #region Ignore converters
package/devices/adeo.js CHANGED
@@ -76,4 +76,13 @@ module.exports = [
76
76
  },
77
77
  exposes: [e.power(), e.switch(), e.energy()],
78
78
  },
79
+ {
80
+ zigbeeModel: ['LDSENK10'],
81
+ model: 'LDSENK10',
82
+ vendor: 'ADEO',
83
+ description: 'LEXMAN motion sensor',
84
+ fromZigbee: [fz.ias_occupancy_alarm_1],
85
+ toZigbee: [],
86
+ exposes: [e.occupancy(), e.battery_low(), e.tamper()],
87
+ },
79
88
  ];
@@ -20,7 +20,7 @@ module.exports = [
20
20
  tz.danfoss_heat_available, tz.danfoss_heat_required, tz.danfoss_day_of_week, tz.danfoss_trigger_time,
21
21
  tz.danfoss_window_open_internal, tz.danfoss_window_open_external, tz.danfoss_load_estimate,
22
22
  tz.danfoss_viewing_direction, tz.danfoss_external_measured_room_sensor, tz.thermostat_keypad_lockout,
23
- tz.thermostat_system_mode],
23
+ tz.thermostat_system_mode, tz.danfoss_load_balancing_enable, tz.danfoss_load_room_mean],
24
24
  exposes: [e.battery(), e.keypad_lockout(),
25
25
  exposes.binary('mounted_mode_active', ea.STATE_GET, true, false)
26
26
  .withDescription('Is the unit in mounting mode. This is set to `false` for mounted (already on ' +
@@ -57,6 +57,11 @@ module.exports = [
57
57
  exposes.numeric('algorithm_scale_factor', ea.ALL).withValueMin(1).withValueMax(10)
58
58
  .withDescription('Scale factor of setpoint filter timeconstant ("aggressiveness" of control algorithm) '+
59
59
  '1= Quick ... 5=Moderate ... 10=Slow'),
60
+ exposes.binary('load_balancing_enable', ea.ALL, true, false)
61
+ .withDescription('Whether or not the thermostat acts as standalone thermostat or shares load with other ' +
62
+ 'thermostats in the room. The gateway must update load_room_mean if enabled.'),
63
+ exposes.numeric('load_room_mean', ea.ALL)
64
+ .withDescription('Mean radiator load for room calculated by gateway for load balancing purposes'),
60
65
  exposes.numeric('load_estimate', ea.STATE_GET)
61
66
  .withDescription('Load estimate on this radiator')],
62
67
  ota: ota.zigbeeOTA,
@@ -106,6 +111,8 @@ module.exports = [
106
111
  'danfossMountedModeControl',
107
112
  'danfossMountedModeActive',
108
113
  'danfossExternalMeasuredRoomSensor',
114
+ 'danfossLoadBalancingEnable',
115
+ 'danfossLoadRoomMean',
109
116
  ], options);
110
117
 
111
118
  // read systemMode to have an initial value
@@ -122,4 +129,133 @@ module.exports = [
122
129
  endpoint.write('genTime', values);
123
130
  },
124
131
  },
132
+ {
133
+ fingerprint: [
134
+ {modelID: '0x8020', manufacturerName: 'Danfoss'}, // RT24V Display
135
+ {modelID: '0x8021', manufacturerName: 'Danfoss'}, // RT24V Display Floor sensor
136
+ {modelID: '0x8030', manufacturerName: 'Danfoss'}, // RTbattery Display
137
+ {modelID: '0x8031', manufacturerName: 'Danfoss'}, // RTbattery Display Infrared
138
+ {modelID: '0x8034', manufacturerName: 'Danfoss'}, // RTbattery Dial
139
+ {modelID: '0x8035', manufacturerName: 'Danfoss'}], // RTbattery Dial Infrared
140
+ model: 'Icon',
141
+ vendor: 'Danfoss',
142
+ description: 'Icon floor heating (regulator, Zigbee module & thermostats)',
143
+ fromZigbee: [
144
+ fz.danfoss_icon_regulator,
145
+ fz.danfoss_thermostat,
146
+ fz.danfoss_icon_battery,
147
+ fz.thermostat],
148
+ toZigbee: [
149
+ tz.thermostat_local_temperature,
150
+ tz.thermostat_occupied_heating_setpoint,
151
+ tz.thermostat_system_mode,
152
+ tz.thermostat_min_heat_setpoint_limit,
153
+ tz.thermostat_max_heat_setpoint_limit,
154
+ tz.danfoss_output_status,
155
+ tz.danfoss_room_status_code,
156
+ tz.danfoss_system_status_water,
157
+ tz.danfoss_system_status_code,
158
+ tz.danfoss_multimaster_role,
159
+ ],
160
+ meta: {multiEndpoint: true},
161
+ // ota: ota.zigbeeOTA,
162
+ endpoint: (device) => {
163
+ return {
164
+ 'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5,
165
+ 'l6': 6, 'l7': 7, 'l8': 8, 'l9': 9, 'l10': 10,
166
+ 'l11': 11, 'l12': 12, 'l13': 13, 'l14': 14, 'l15': 15, 'l16': 232,
167
+ };
168
+ },
169
+ exposes: [].concat(((endpointsCount) => {
170
+ const features = [];
171
+ for (let i = 1; i <= endpointsCount; i++) {
172
+ const epName = `l${i}`;
173
+ if (i!=16) {
174
+ features.push(e.battery().withEndpoint(epName));
175
+ features.push(exposes.climate().withSetpoint('occupied_heating_setpoint', 4, 30, 0.5)
176
+ .withLocalTemperature().withSystemMode(['heat']).withEndpoint(epName));
177
+ features.push(exposes.numeric('abs_min_heat_setpoint_limit', ea.STATE)
178
+ .withUnit('°C').withEndpoint(epName)
179
+ .withDescription('Absolute min temperature allowed on the device'));
180
+ features.push(exposes.numeric('abs_max_heat_setpoint_limit', ea.STATE)
181
+ .withUnit('°C').withEndpoint(epName)
182
+ .withDescription('Absolute max temperature allowed on the device'));
183
+ features.push(exposes.numeric('min_heat_setpoint_limit', ea.ALL)
184
+ .withValueMin(4).withValueMax(30).withValueStep(0.5).withUnit('°C')
185
+ .withEndpoint(epName).withDescription('Min temperature limit set on the device'));
186
+ features.push(exposes.numeric('max_heat_setpoint_limit', ea.ALL)
187
+ .withValueMin(4).withValueMax(30).withValueStep(0.5).withUnit('°C')
188
+ .withEndpoint(epName).withDescription('Max temperature limit set on the device'));
189
+ features.push(exposes.enum('setpoint_change_source', ea.STATE, ['manual', 'schedule', 'externally'])
190
+ .withEndpoint(epName));
191
+ features.push(exposes.enum('output_status', ea.STATE_GET, ['inactive', 'active'])
192
+ .withEndpoint(epName).withDescription('Danfoss Output Status [Active vs Inactive])'));
193
+ features.push(exposes.enum('room_status_code', ea.STATE_GET, ['no_error', 'missing_rt',
194
+ 'rt_touch_error', 'floor_sensor_short_circuit', 'floor_sensor_disconnected'])
195
+ .withEndpoint(epName).withDescription('Thermostat status'));
196
+ } else {
197
+ features.push(exposes.enum('system_status_code', ea.STATE_GET, ['no_error', 'missing_expansion_board',
198
+ 'missing_radio_module', 'missing_command_module', 'missing_master_rail', 'missing_slave_rail_no_1',
199
+ 'missing_slave_rail_no_2', 'pt1000_input_short_circuit', 'pt1000_input_open_circuit',
200
+ 'error_on_one_or_more_output']).withEndpoint('l16').withDescription('Regulator Status'));
201
+ features.push(exposes.enum('system_status_water', ea.STATE_GET, ['hot_water_flow_in_pipes', 'cool_water_flow_in_pipes'])
202
+ .withEndpoint('l16').withDescription('Water Status of Regulator'));
203
+ features.push(exposes.enum('multimaster_role', ea.STATE_GET, ['invalid_unused', 'master', 'slave_1', 'slave_2'])
204
+ .withEndpoint('l16').withDescription('Regulator role (Master vs Slave)'));
205
+ }
206
+ }
207
+
208
+ return features;
209
+ })(16)),
210
+ configure: async (device, coordinatorEndpoint, logger) => {
211
+ const options = {manufacturerCode: 0x1246};
212
+
213
+ for (let i = 1; i <= 15; i++) {
214
+ const endpoint = device.getEndpoint(i);
215
+ if (typeof endpoint !== 'undefined') {
216
+ await reporting.bind(endpoint, coordinatorEndpoint,
217
+ ['genPowerCfg', 'hvacThermostat', 'hvacUserInterfaceCfg']);
218
+ await reporting.batteryPercentageRemaining(endpoint,
219
+ {min: constants.repInterval.HOUR, max: 43200, change: 1});
220
+ await reporting.thermostatTemperature(endpoint,
221
+ {min: 0, max: constants.repInterval.MINUTES_10, change: 10});
222
+ await reporting.thermostatOccupiedHeatingSetpoint(endpoint,
223
+ {min: 0, max: constants.repInterval.MINUTES_10, change: 10});
224
+
225
+ await endpoint.configureReporting('hvacThermostat', [{
226
+ attribute: 'danfossOutputStatus',
227
+ minimumReportInterval: constants.repInterval.MINUTE,
228
+ maximumReportInterval: constants.repInterval.MINUTES_10,
229
+ reportableChange: 1,
230
+ }], options);
231
+
232
+ // Danfoss Icon Thermostat Specific
233
+ await endpoint.read('hvacThermostat', [
234
+ 'danfossOutputStatus',
235
+ 'danfossRoomStatusCode'], options);
236
+
237
+ // Standard Thermostat
238
+ await endpoint.read('hvacThermostat', ['localTemp']);
239
+ await endpoint.read('hvacThermostat', ['occupiedHeatingSetpoint']);
240
+ await endpoint.read('hvacThermostat', ['systemMode']);
241
+ await endpoint.read('hvacThermostat', ['setpointChangeSource']);
242
+ await endpoint.read('hvacThermostat', ['absMinHeatSetpointLimit']);
243
+ await endpoint.read('hvacThermostat', ['absMaxHeatSetpointLimit']);
244
+ await endpoint.read('hvacThermostat', ['minHeatSetpointLimit']);
245
+ await endpoint.read('hvacThermostat', ['maxHeatSetpointLimit']);
246
+ await endpoint.read('genPowerCfg', ['batteryPercentageRemaining']);
247
+ }
248
+ }
249
+
250
+ // Danfoss Icon Regulator Specific
251
+ const endpoint232 = device.getEndpoint(232);
252
+
253
+ await reporting.bind(endpoint232, coordinatorEndpoint, ['haDiagnostic']);
254
+
255
+ await endpoint232.read('haDiagnostic', [
256
+ 'danfossSystemStatusCode',
257
+ 'danfossSystemStatusWater',
258
+ 'danfossMultimasterRole'], options);
259
+ },
260
+ },
125
261
  ];
@@ -21,13 +21,13 @@ module.exports = [
21
21
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature()
22
22
  .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withLocalTemperatureCalibration()
23
23
  .withPiHeatingDemand(),
24
- exposes.enum('eurotronic_trv_mode', exposes.access.ALL, [1, 2])
25
- .withDescription('Select between direct control of the valve via the `eurotronic_valve_position` or automatic control of the '+
24
+ exposes.enum('trv_mode', exposes.access.ALL, [1, 2])
25
+ .withDescription('Select between direct control of the valve via the `valve_position` or automatic control of the '+
26
26
  'valve based on the `current_heating_setpoint`. For manual control set the value to 1, for automatic control set the value '+
27
27
  'to 2 (the default). When switched to manual mode the display shows a value from 0 (valve closed) to 100 (valve fully open) '+
28
28
  'and the buttons on the device are disabled.'),
29
- exposes.numeric('eurotronic_valve_position', exposes.access.ALL).withValueMin(0).withValueMax(255)
30
- .withDescription('Directly control the radiator valve when `eurotronic_trv_mode` is set to 1. The values range from 0 (valve '+
29
+ exposes.numeric('valve_position', exposes.access.ALL).withValueMin(0).withValueMax(255)
30
+ .withDescription('Directly control the radiator valve when `trv_mode` is set to 1. The values range from 0 (valve '+
31
31
  'closed) to 255 (valve fully open)')],
32
32
  ota: ota.zigbeeOTA,
33
33
  configure: async (device, coordinatorEndpoint, logger) => {
package/devices/heiman.js CHANGED
@@ -10,6 +10,7 @@ const ea = exposes.access;
10
10
 
11
11
  module.exports = [
12
12
  {
13
+ fingerprint: [{modelID: 'TS0212', manufacturerName: '_TYZB01_wpmo3ja3'}],
13
14
  zigbeeModel: ['CO_V15', 'CO_YDLV10', 'CO_V16', '1ccaa94c49a84abaa9e38687913947ba'],
14
15
  model: 'HS1CA-M',
15
16
  description: 'Smart carbon monoxide sensor',
package/devices/ikea.js CHANGED
@@ -486,7 +486,9 @@ module.exports = [
486
486
  toZigbee: [],
487
487
  exposes: [e.battery(), e.occupancy(),
488
488
  exposes.numeric('requested_brightness_level', ea.STATE).withValueMin(76).withValueMax(254),
489
- exposes.numeric('requested_brightness_percent', ea.STATE).withValueMin(30).withValueMax(100)],
489
+ exposes.numeric('requested_brightness_percent', ea.STATE).withValueMin(30).withValueMax(100),
490
+ exposes.binary('illuminance_above_threshold', ea.STATE, true, false)
491
+ .withDescription('Indicates whether the device detected bright light (works only in night mode)')],
490
492
  ota: ota.tradfri,
491
493
  meta: {battery: {dontDividePercentage: true}},
492
494
  configure: async (device, coordinatorEndpoint, logger) => {
@@ -1769,6 +1769,20 @@ module.exports = [
1769
1769
  },
1770
1770
  ota: ota.zigbeeOTA,
1771
1771
  },
1772
+ {
1773
+ zigbeeModel: ['LOM007'],
1774
+ model: '929003050601',
1775
+ vendor: 'Philips',
1776
+ description: 'Hue smart plug',
1777
+ extend: extend.switch(),
1778
+ toZigbee: [tz.on_off].concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
1779
+ configure: async (device, coordinatorEndpoint, logger) => {
1780
+ const endpoint = device.getEndpoint(11);
1781
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
1782
+ await reporting.onOff(endpoint);
1783
+ },
1784
+ ota: ota.zigbeeOTA,
1785
+ },
1772
1786
  {
1773
1787
  zigbeeModel: ['LLC014'],
1774
1788
  model: '7099860PH',
@@ -2165,4 +2179,22 @@ module.exports = [
2165
2179
  meta: {turnsOffAtBrightness1: true},
2166
2180
  ota: ota.zigbeeOTA,
2167
2181
  },
2182
+ {
2183
+ zigbeeModel: ['LTF001'],
2184
+ model: '6109231C5',
2185
+ vendor: 'Philips',
2186
+ description: 'Hue white ambiance Apogee square',
2187
+ meta: {turnsOffAtBrightness1: true},
2188
+ extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
2189
+ ota: ota.zigbeeOTA,
2190
+ },
2191
+ {
2192
+ zigbeeModel: ['LTF002'],
2193
+ model: '6109331C5',
2194
+ vendor: 'Philips',
2195
+ description: 'Hue white ambiance Apogee round',
2196
+ meta: {turnsOffAtBrightness1: true},
2197
+ extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
2198
+ ota: ota.zigbeeOTA,
2199
+ },
2168
2200
  ];
@@ -236,4 +236,50 @@ module.exports = [
236
236
  await reporting.batteryVoltage(endpoint, {min: 30, max: 21600, change: 1});
237
237
  },
238
238
  },
239
+ {
240
+ zigbeeModel: ['SBM300ZB1'],
241
+ model: 'SBM300ZB1',
242
+ vendor: 'ShinaSystem',
243
+ description: 'SiHAS remote control',
244
+ meta: {battery: {voltageToPercentage: '3V_2100'}},
245
+ fromZigbee: [fz.battery, fz.sihas_action],
246
+ toZigbee: [],
247
+ configure: async (device, coordinatorEndpoint, logger) => {
248
+ const endpoint = device.getEndpoint(1);
249
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']);
250
+ await reporting.batteryVoltage(endpoint);
251
+ },
252
+ exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'long'])],
253
+ },
254
+ {
255
+ zigbeeModel: ['SBM300ZB2'],
256
+ model: 'SBM300ZB2',
257
+ vendor: 'ShinaSystem',
258
+ description: 'SiHAS remote control 2 button',
259
+ fromZigbee: [fz.sihas_action, fz.battery],
260
+ toZigbee: [],
261
+ exposes: [e.action(['1_single', '1_double', '1_long', '2_single', '2_double', '2_long']), e.battery(), e.battery_voltage()],
262
+ meta: {battery: {voltageToPercentage: '3V_2100'}},
263
+ configure: async (device, coordinatorEndpoint, logger) => {
264
+ const endpoint = device.getEndpoint(1);
265
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']);
266
+ await reporting.batteryVoltage(endpoint, {min: 30, max: 21600, change: 1});
267
+ },
268
+ },
269
+ {
270
+ zigbeeModel: ['SBM300ZB3'],
271
+ model: 'SBM300ZB3',
272
+ vendor: 'ShinaSystem',
273
+ description: 'SiHAS remote control 3 button',
274
+ fromZigbee: [fz.sihas_action, fz.battery],
275
+ toZigbee: [],
276
+ exposes: [e.action(['1_single', '1_double', '1_long', '2_single', '2_double', '2_long',
277
+ '3_single', '3_double', '3_long']), e.battery(), e.battery_voltage()],
278
+ meta: {battery: {voltageToPercentage: '3V_2100'}},
279
+ configure: async (device, coordinatorEndpoint, logger) => {
280
+ const endpoint = device.getEndpoint(1);
281
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']);
282
+ await reporting.batteryVoltage(endpoint, {min: 30, max: 21600, change: 1});
283
+ },
284
+ },
239
285
  ];
@@ -1,5 +1,7 @@
1
1
  const exposes = require('../lib/exposes');
2
2
  const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/legacy').fromZigbee};
3
+ const reporting = require('../lib/reporting');
4
+ const extend = require('../lib/extend');
3
5
  const e = exposes.presets;
4
6
 
5
7
  module.exports = [
@@ -21,4 +23,17 @@ module.exports = [
21
23
  return {l1: 1, l2: 2, l3: 3, l4: 4};
22
24
  },
23
25
  },
26
+ {
27
+ zigbeeModel: ['S24013'],
28
+ model: 'S24013',
29
+ vendor: 'The Light Group',
30
+ description: 'SLC SmartOne AC dimmer mini 200W Zigbee LN',
31
+ extend: extend.light_onoff_brightness({noConfigure: true}),
32
+ configure: async (device, coordinatorEndpoint, logger) => {
33
+ await extend.light_onoff_brightness().configure(device, coordinatorEndpoint, logger);
34
+ const endpoint = device.getEndpoint(1);
35
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']);
36
+ await reporting.onOff(endpoint);
37
+ },
38
+ },
24
39
  ];
package/devices/tuya.js CHANGED
@@ -109,7 +109,9 @@ module.exports = [
109
109
  {modelID: 'TS0505B', manufacturerName: '_TZ3000_x2fqbdun'},
110
110
  {modelID: 'TS0505B', manufacturerName: '_TZ3000_589kq4ul'},
111
111
  {modelID: 'TS0505B', manufacturerName: '_TZ3000_1mtktxdk'},
112
- {modelID: 'TS0505B', manufacturerName: '_TZ3210_bicjqpg4'}],
112
+ {modelID: 'TS0505B', manufacturerName: '_TZ3210_bicjqpg4'},
113
+ {modelID: 'TS0505B', manufacturerName: '_TZ3000_cmaky9gq'},
114
+ {modelID: 'TS0505B', manufacturerName: '_TZ3000_tza2vjxx'}],
113
115
  model: 'TS0505B',
114
116
  vendor: 'TuYa',
115
117
  description: 'Zigbee RGB+CCT light',
@@ -150,7 +152,8 @@ module.exports = [
150
152
  {modelID: 'TS0202', manufacturerName: '_TYZB01_2b8f6cio'},
151
153
  {modelID: 'TS0202', manufacturerName: '_TYZB01_dl7cejts'},
152
154
  {modelID: 'TS0202', manufacturerName: '_TYZB01_qjqgmqxr'},
153
- {modelID: 'TS0202', manufacturerName: '_TZ3000_mmtwjmaq'}],
155
+ {modelID: 'TS0202', manufacturerName: '_TZ3000_mmtwjmaq'},
156
+ {modelID: 'WHD02', manufacturerName: '_TZ3000_hktqahrq'}],
154
157
  model: 'TS0202',
155
158
  vendor: 'TuYa',
156
159
  description: 'Motion sensor',
@@ -608,7 +611,7 @@ module.exports = [
608
611
  },
609
612
  },
610
613
  {
611
- fingerprint: [{modelID: 'TS0002', manufacturerName: '_TZ3000_01gpyda5'}],
614
+ fingerprint: [{modelID: 'TS0002', manufacturerName: '_TZ3000_01gpyda5'}, {modelID: 'TS0002', manufacturerName: '_TZ3000_bvrlqyj7'}],
612
615
  model: 'TS0002_switch_module',
613
616
  vendor: 'TuYa',
614
617
  description: '2 gang switch module',
@@ -820,6 +823,46 @@ module.exports = [
820
823
  .withSystemMode(['off', 'heat', 'auto'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE),
821
824
  ],
822
825
  },
826
+ {
827
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_a4bpgplm'}],
828
+ model: 'TS0601',
829
+ vendor: 'TS0601_thermostat_1',
830
+ description: 'Thermostatic radiator valve',
831
+ onEvent: tuya.onEventSetLocalTime,
832
+ fromZigbee: [fz.ignore_basic_report, fz.ignore_tuya_set_time, fz.haozee_thermostat],
833
+ toZigbee: [
834
+ tz.haozee_thermostat_system_mode, tz.haozee_thermostat_current_heating_setpoint, tz.haozee_thermostat_boost_heating,
835
+ tz.haozee_thermostat_boost_heating_countdown, tz.haozee_thermostat_window_detection,
836
+ tz.haozee_thermostat_child_lock, tz.haozee_thermostat_temperature_calibration, tz.haozee_thermostat_max_temperature,
837
+ tz.haozee_thermostat_min_temperature,
838
+ ],
839
+ exposes: [
840
+ e.battery(), e.child_lock(), e.max_temperature(), e.min_temperature(),
841
+ e.position(), e.window_detection(),
842
+ exposes.binary('window', ea.STATE, 'CLOSED', 'OPEN').withDescription('Window status closed or open '),
843
+ exposes.binary('heating', ea.STATE, 'ON', 'OFF').withDescription('Device valve is open or closed (heating or not)'),
844
+ exposes.climate()
845
+ .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET)
846
+ .withLocalTemperatureCalibration(ea.STATE_SET).withPreset(['auto', 'manual', 'off', 'on'],
847
+ 'MANUAL MODE ☝ - In this mode, the device executes manual temperature setting. ' +
848
+ 'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' +
849
+ 'AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' +
850
+ 'ON - In this mode, the thermostat stays open ' +
851
+ 'OFF - In this mode, the thermostat stays closed'),
852
+ exposes.composite('programming_mode').withDescription('Auto MODE ⏱ - In this mode, ' +
853
+ 'the device executes a preset week programming temperature time and temperature. ')
854
+ .withFeature(exposes.text('monday_schedule', ea.STATE))
855
+ .withFeature(exposes.text('tuesday_schedule', ea.STATE))
856
+ .withFeature(exposes.text('wednesday_schedule', ea.STATE))
857
+ .withFeature(exposes.text('thursday_schedule', ea.STATE))
858
+ .withFeature(exposes.text('friday_schedule', ea.STATE))
859
+ .withFeature(exposes.text('saturday_schedule', ea.STATE))
860
+ .withFeature(exposes.text('sunday_schedule', ea.STATE)),
861
+ exposes.binary('boost_heating', ea.STATE_SET, 'ON', 'OFF').withDescription('Boost Heating: press and hold "+" for 3 seconds, ' +
862
+ 'the device will enter the boost heating mode, and the ▷╵◁ will flash. The countdown will be displayed in the APP'),
863
+ exposes.numeric('boost_heating_countdown', ea.STATE_SET).withUnit('min').withDescription('Countdown in minutes'),
864
+ ],
865
+ },
823
866
  {
824
867
  zigbeeModel: ['TS0121'],
825
868
  model: 'TS0121_plug',
@@ -916,6 +959,7 @@ module.exports = [
916
959
  toZigbee: [tz.on_off, tz.tuya_switch_power_outage_memory],
917
960
  configure: async (device, coordinatorEndpoint, logger) => {
918
961
  const endpoint = device.getEndpoint(1);
962
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
919
963
  endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acCurrentDivisor: 1000, acCurrentMultiplier: 1});
920
964
  endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
921
965
  device.save();
@@ -1413,20 +1457,26 @@ module.exports = [
1413
1457
  model: 'YSR-MINI-Z',
1414
1458
  vendor: 'TuYa',
1415
1459
  description: '2 in 1 dimming remote control and scene control',
1416
- exposes: [e.battery(), e.action([
1417
- 'on', 'off',
1418
- 'brightness_move_up', 'brightness_step_up', 'brightness_step_down', 'brightness_move_down', 'brightness_stop',
1419
- 'color_temperature_step_down', 'color_temperature_step_up',
1420
- '1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold',
1421
- '3_single', '3_double', '3_hold', '4_single', '4_double', '4_hold',
1422
- ])],
1460
+ exposes: [
1461
+ e.battery(),
1462
+ e.action(['on', 'off',
1463
+ 'brightness_move_up', 'brightness_step_up', 'brightness_step_down', 'brightness_move_down', 'brightness_stop',
1464
+ 'color_temperature_step_down', 'color_temperature_step_up',
1465
+ '1_single', '1_double', '1_hold', '2_single', '2_double', '2_hold',
1466
+ '3_single', '3_double', '3_hold', '4_single', '4_double', '4_hold',
1467
+ ]),
1468
+ exposes.enum('operation_mode', ea.ALL, ['command', 'event']).withDescription(
1469
+ 'Operation mode: "command" - for group control, "event" - for clicks'),
1470
+ ],
1423
1471
  fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop,
1424
- fz.command_step_color_temperature, fz.tuya_on_off_action],
1425
- toZigbee: [],
1472
+ fz.command_step_color_temperature, fz.tuya_on_off_action, fz.tuya_operation_mode],
1473
+ toZigbee: [tz.tuya_operation_mode],
1474
+ onEvent: tuya.onEventSetLocalTime,
1426
1475
  configure: async (device, coordinatorEndpoint, logger) => {
1427
1476
  const endpoint = device.getEndpoint(1);
1428
1477
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']);
1429
1478
  await reporting.batteryPercentageRemaining(endpoint);
1479
+ await endpoint.read('genOnOff', ['tuyaOperationMode']);
1430
1480
  },
1431
1481
  },
1432
1482
  {
package/devices/yale.js CHANGED
@@ -100,6 +100,13 @@ module.exports = [
100
100
  description: 'Assure lock SL',
101
101
  extend: lockExtend(),
102
102
  },
103
+ {
104
+ zigbeeModel: ['YRL226 TS'],
105
+ model: 'YRL226 TS',
106
+ vendor: 'Yale',
107
+ description: 'Assure lock SL',
108
+ extend: lockExtend(),
109
+ },
103
110
  {
104
111
  // Appears to be a slightly rebranded Assure lock SL
105
112
  // Just with Lockwood | Assa Abloy branding instead of Yale
package/lib/constants.js CHANGED
@@ -91,6 +91,44 @@ const danfossWindowOpen = {
91
91
  4: 'external_open',
92
92
  };
93
93
 
94
+ const danfossRoomStatusCode = {
95
+ 0x0000: 'no_error',
96
+ 0x0101: 'missing_rt',
97
+ 0x0201: 'rt_touch_error',
98
+ 0x0401: 'floor_sensor_short_circuit',
99
+ 0x0801: 'floor_sensor_disconnected',
100
+ };
101
+
102
+ const danfossOutputStatus = {
103
+ 0: 'inactive',
104
+ 1: 'active',
105
+ };
106
+
107
+ const danfossSystemStatusWater = {
108
+ 0: 'hot_water_flow_in_pipes',
109
+ 1: 'cool_water_flow_in_pipes',
110
+ };
111
+
112
+ const danfossSystemStatusCode = {
113
+ 0x0000: 'no_error',
114
+ 0x0101: 'missing_expansion_board',
115
+ 0x0201: 'missing_radio_module',
116
+ 0x0401: 'missing_command_module',
117
+ 0x0801: 'missing_master_rail',
118
+ 0x1001: 'missing_slave_rail_no_1',
119
+ 0x2001: 'missing_slave_rail_no_2',
120
+ 0x4001: 'pt1000_input_short_circuit',
121
+ 0x8001: 'pt1000_input_open_circuit',
122
+ 0x0102: 'error_on_one_or_more_output',
123
+ };
124
+
125
+ const danfossMultimasterRole = {
126
+ 0: 'invalid_unused',
127
+ 1: 'master',
128
+ 2: 'slave_1',
129
+ 3: 'slave_2',
130
+ };
131
+
94
132
  const keypadLockoutMode = {
95
133
  0: 'unlock',
96
134
  1: 'lock1',
@@ -196,6 +234,11 @@ module.exports = {
196
234
  fanMode,
197
235
  temperatureDisplayMode,
198
236
  danfossWindowOpen,
237
+ danfossRoomStatusCode,
238
+ danfossOutputStatus,
239
+ danfossSystemStatusWater,
240
+ danfossSystemStatusCode,
241
+ danfossMultimasterRole,
199
242
  keypadLockoutMode,
200
243
  lockSourceName,
201
244
  armMode,
package/lib/exposes.js CHANGED
@@ -486,6 +486,7 @@ module.exports = {
486
486
  transition: () => new Numeric(`transition`, access.SET).withValueMin(0).withDescription('Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).'),
487
487
  legacy: () => new Binary(`legacy`, access.SET, true, false).withDescription(`Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).`),
488
488
  measurement_poll_interval: () => new Numeric(`measurement_poll_interval`, access.SET).withValueMin(0).withDescription(`This device does not support reporting electric measurements so it is polled instead. The default poll interval is 60 seconds.`),
489
+ illuminance_below_threshold_check: () => new Binary(`illuminance_below_threshold_check`, access.SET, true, false).withDescription(`Set to false to also send messages when illuminance is above threshold in night mode (default true).`),
489
490
  },
490
491
  presets: {
491
492
  action: (values) => new Enum('action', access.STATE, values).withDescription('Triggered action (e.g. a button click)'),
package/lib/tuya.js CHANGED
@@ -423,6 +423,30 @@ const dataPoints = {
423
423
  tvThursdaySchedule: 113,
424
424
  tvSaturdaySchedule: 114,
425
425
  tvBoostMode: 115,
426
+ // Haozee TS0601 TRV
427
+ haozeeSystemMode: 1,
428
+ haozeeHeatingSetpoint: 2,
429
+ haozeeLocalTemp: 3,
430
+ haozeeBoostHeating: 4,
431
+ haozeeBoostHeatingCountdown: 5,
432
+ haozeeRunningState: 6,
433
+ haozeeWindowState: 7,
434
+ haozeeWindowDetection: 8,
435
+ haozeeChildLock: 12,
436
+ haozeeBattery: 13,
437
+ haozeeFaultAlarm: 14,
438
+ haozeeMinTemp: 15,
439
+ haozeeMaxTemp: 16,
440
+ haozeeScheduleMonday: 17,
441
+ haozeeScheduleTuesday: 18,
442
+ haozeeScheduleWednesday: 19,
443
+ haozeeScheduleThursday: 20,
444
+ haozeeScheduleFriday: 21,
445
+ haozeeScheduleSaturday: 22,
446
+ haozeeScheduleSunday: 23,
447
+ haozeeTempCalibration: 101,
448
+ haozeeValvePosition: 102,
449
+ haozeeSoftVersion: 150,
426
450
  // HOCH / WDYK DIN Rail
427
451
  hochCountdownTimer: 9,
428
452
  hochFaultCode: 26,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.294",
3
+ "version": "14.0.298",
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.294",
3
+ "version": "14.0.298",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [