1313from homeassistant .components .climate .const import (
1414 ClimateEntityFeature ,
1515 HVACMode ,
16+ HVACAction ,
1617 PRESET_AWAY ,
1718 PRESET_NONE ,
1819)
2425
2526from .const import DOMAIN
2627from .devices import TuyaBLEData , TuyaBLEEntity , TuyaBLEProductInfo
27- from .tuya_ble import TuyaBLEDataPointType , TuyaBLEDevice
28+ from .tuya_ble import TuyaBLEDataPoint , TuyaBLEDataPointType , TuyaBLEDevice
2829
2930_LOGGER = logging .getLogger (__name__ )
3031
@@ -47,7 +48,7 @@ class TuyaBLEClimateMapping:
4748 target_temperature_dp_id : int = 0
4849 target_temperature_coefficient : float = 1.0
4950 target_temperature_max : float = 30.0
50- target_temperature_min : float = - 20.0
51+ target_temperature_min : float = 5
5152 target_temperature_step : float = 1.0
5253
5354 current_humidity_dp_id : int = 0
@@ -67,18 +68,57 @@ class TuyaBLECategoryClimateMapping:
6768mapping : dict [str , TuyaBLECategoryClimateMapping ] = {
6869 "wk" : TuyaBLECategoryClimateMapping (
6970 products = {
70- "drlajpqc" : [ # Thermostatic Radiator Valve
71+ "drlajpqc" : [
72+ # Thermostatic Radiator Valve
73+ # - [x] 8 - Window
74+ # - [x] 10 - Antifreeze
75+ # - [x] 27 - Calibration
76+ # - [x] 40 - Lock
77+ # - [x] 101 - Switch
78+ # - [x] 102 - Current
79+ # - [x] 103 - Target
80+ # - [ ] 104 - Heating time
81+ # - [x] 105 - Battery power alarm
82+ # - [x] 106 - Away
83+ # - [x] 107 - Programming mode
84+ # - [x] 108 - Programming switch
85+ # - [ ] 109 - Programming data (deprecated - do not delete)
86+ # - [ ] 110 - Historical data protocol (Day-Target temperature)
87+ # - [ ] 111 - System Time Synchronization
88+ # - [ ] 112 - Historical data (Week-Target temperature)
89+ # - [ ] 113 - Historical data (Month-Target temperature)
90+ # - [ ] 114 - Historical data (Year-Target temperature)
91+ # - [ ] 115 - Historical data (Day-Current temperature)
92+ # - [ ] 116 - Historical data (Week-Current temperature)
93+ # - [ ] 117 - Historical data (Month-Current temperature)
94+ # - [ ] 118 - Historical data (Year-Current temperature)
95+ # - [ ] 119 - Historical data (Day-motor opening degree)
96+ # - [ ] 120 - Historical data (Week-motor opening degree)
97+ # - [ ] 121 - Historical data (Month-motor opening degree)
98+ # - [ ] 122 - Historical data (Year-motor opening degree)
99+ # - [ ] 123 - Programming data (Monday)
100+ # - [ ] 124 - Programming data (Tuseday)
101+ # - [ ] 125 - Programming data (Wednesday)
102+ # - [ ] 126 - Programming data (Thursday)
103+ # - [ ] 127 - Programming data (Friday)
104+ # - [ ] 128 - Programming data (Saturday)
105+ # - [ ] 129 - Programming data (Sunday)
106+ # - [x] 130 - Water scale
71107 TuyaBLEClimateMapping (
72108 description = ClimateEntityDescription (
73109 key = "thermostatic_radiator_valve" ,
74110 ),
75- hvac_switch_dp_id = 17 ,
111+ hvac_switch_dp_id = 101 ,
76112 hvac_switch_mode = HVACMode .HEAT ,
77- # preset_mode_dp_ids={PRESET_AWAY: 106},
113+ hvac_modes = [HVACMode .OFF , HVACMode .HEAT ],
114+ preset_mode_dp_ids = {PRESET_AWAY : 106 , PRESET_NONE : 106 },
78115 current_temperature_dp_id = 102 ,
79116 current_temperature_coefficient = 10.0 ,
117+ target_temperature_coefficient = 10.0 ,
118+ target_temperature_step = 0.5 ,
80119 target_temperature_dp_id = 103 ,
81120 target_temperature_min = 5.0 ,
121+ target_temperature_max = 30.0 ,
82122 ),
83123 ],
84124 },
@@ -113,7 +153,9 @@ def __init__(
113153 ) -> None :
114154 super ().__init__ (hass , coordinator , device , product , mapping .description )
115155 self ._mapping = mapping
116- self ._attr_hvac_mode = HVACMode .OFF
156+ self ._attr_hvac_mode = HVACMode .HEAT
157+ self ._attr_preset_mode = PRESET_NONE
158+ self ._attr_hvac_action = HVACAction .HEATING
117159
118160 if mapping .hvac_mode_dp_id and mapping .hvac_modes :
119161 self ._attr_hvac_modes = mapping .hvac_modes
@@ -185,20 +227,32 @@ def _handle_coordinator_update(self) -> None:
185227
186228 if self ._mapping .preset_mode_dp_ids :
187229 current_preset_mode = PRESET_NONE
188- for preset_mode , dp_id in self ._mapping .preset_mode_dp_ids :
230+ for preset_mode , dp_id in self ._mapping .preset_mode_dp_ids . items () :
189231 datapoint = self ._device .datapoints [dp_id ]
190232 if datapoint and datapoint .value :
191233 current_preset_mode = preset_mode
192234 break
193235 self ._attr_preset_mode = current_preset_mode
194236
237+ try :
238+ if (
239+ self ._attr_preset_mode == PRESET_AWAY
240+ or self ._attr_hvac_mode == HVACMode .OFF
241+ or self ._attr_target_temperature <= self ._attr_current_temperature
242+ ):
243+ self ._attr_hvac_action = HVACAction .IDLE
244+ else :
245+ self ._attr_hvac_action = HVACAction .HEATING
246+ except :
247+ pass
248+
195249 self .async_write_ha_state ()
196250
197251 async def async_set_temperature (self , ** kwargs ) -> None :
198252 """Set new target temperature."""
199253 if self ._mapping .target_temperature_dp_id != 0 :
200254 int_value = int (
201- kwargs ["temperature" ] * self ._mapping .target_humidity_coefficient
255+ kwargs ["temperature" ] * self ._mapping .target_temperature_coefficient
202256 )
203257 datapoint = self ._device .datapoints .get_or_create (
204258 self ._mapping .target_temperature_dp_id ,
@@ -248,15 +302,37 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
248302 async def async_set_preset_mode (self , preset_mode : str ) -> None :
249303 """Set new preset mode."""
250304 if self ._mapping .preset_mode_dp_ids :
251- for dp_preset_mode , dp_id in self ._mapping .preset_mode_dp_ids :
252- bool_value = dp_preset_mode == preset_mode
253- datapoint = self ._device .datapoints .get_or_create (
254- dp_id ,
255- TuyaBLEDataPointType .DT_BOOL ,
256- bool_value ,
257- )
258- if datapoint :
259- self ._hass .create_task (datapoint .set_value (bool_value ))
305+ datapoint : TuyaBLEDataPoint | None = None
306+ bool_value = False
307+
308+ keys = [x for x in self ._mapping .preset_mode_dp_ids .keys ()]
309+ values = [
310+ x for x in self ._mapping .preset_mode_dp_ids .values ()
311+ ] # Get all DP IDs
312+ # TRVs with only Away and None modes can be set with a single datapoint and use a single DP ID
313+ if all (values [0 ] == elem for elem in values ) and keys [0 ] == PRESET_AWAY :
314+ for dp_id in values :
315+ bool_value = preset_mode == PRESET_AWAY
316+ datapoint = self ._device .datapoints .get_or_create (
317+ dp_id ,
318+ TuyaBLEDataPointType .DT_BOOL ,
319+ bool_value ,
320+ )
321+ break
322+ else :
323+ if self ._mapping .preset_mode_dp_ids :
324+ for (
325+ dp_preset_mode ,
326+ dp_id ,
327+ ) in self ._mapping .preset_mode_dp_ids .items ():
328+ bool_value = dp_preset_mode == preset_mode
329+ datapoint = self ._device .datapoints .get_or_create (
330+ dp_id ,
331+ TuyaBLEDataPointType .DT_BOOL ,
332+ bool_value ,
333+ )
334+ if datapoint :
335+ self ._hass .create_task (datapoint .set_value (bool_value ))
260336
261337
262338async def async_setup_entry (
0 commit comments