1 """Support for Google Nest SDM climate devices."""
3 from __future__
import annotations
5 from typing
import Any, cast
7 from google_nest_sdm.device
import Device
8 from google_nest_sdm.device_manager
import DeviceManager
9 from google_nest_sdm.device_traits
import FanTrait, TemperatureTrait
10 from google_nest_sdm.exceptions
import ApiException
11 from google_nest_sdm.thermostat_traits
import (
15 ThermostatTemperatureSetpointTrait,
20 ATTR_TARGET_TEMP_HIGH,
37 from .const
import DATA_DEVICE_MANAGER, DOMAIN
38 from .device_info
import NestDeviceInfo
41 THERMOSTAT_MODE_MAP: dict[str, HVACMode] = {
43 "HEAT": HVACMode.HEAT,
44 "COOL": HVACMode.COOL,
45 "HEATCOOL": HVACMode.HEAT_COOL,
47 THERMOSTAT_INV_MODE_MAP = {v: k
for k, v
in THERMOSTAT_MODE_MAP.items()}
50 THERMOSTAT_ECO_MODE =
"MANUAL_ECO"
53 THERMOSTAT_HVAC_STATUS_MAP = {
54 "OFF": HVACAction.OFF,
55 "HEATING": HVACAction.HEATING,
56 "COOLING": HVACAction.COOLING,
59 THERMOSTAT_RANGE_MODES = [HVACMode.HEAT_COOL, HVACMode.AUTO]
62 "MANUAL_ECO": PRESET_ECO,
65 PRESET_INV_MODE_MAP = {v: k
for k, v
in PRESET_MODE_MAP.items()}
71 FAN_INV_MODE_MAP = {v: k
for k, v
in FAN_MODE_MAP.items()}
72 FAN_INV_MODES =
list(FAN_INV_MODE_MAP)
74 MAX_FAN_DURATION = 43200
77 MIN_TEMP_RANGE = 1.66667
81 hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
83 """Set up the client entities."""
85 device_manager: DeviceManager = hass.data[DOMAIN][entry.entry_id][
91 for device
in device_manager.devices.values()
92 if ThermostatHvacTrait.NAME
in device.traits
97 """A nest thermostat climate entity."""
99 _attr_min_temp = MIN_TEMP
100 _attr_max_temp = MAX_TEMP
101 _attr_has_entity_name =
True
102 _attr_should_poll =
False
104 _enable_turn_on_off_backwards_compatibility =
False
107 """Initialize ThermostatEntity."""
114 if mode_trait := device.traits.get(ThermostatModeTrait.NAME):
116 THERMOSTAT_MODE_MAP[mode]
117 for mode
in mode_trait.available_modes
118 if mode
in THERMOSTAT_MODE_MAP
125 """Return device availability."""
129 """Run when entity is added to register update signal handler."""
137 """Return the current temperature."""
138 if TemperatureTrait.NAME
not in self.
_device_device.traits:
140 trait: TemperatureTrait = self.
_device_device.traits[TemperatureTrait.NAME]
141 return trait.ambient_temperature_celsius
145 """Return the temperature currently set to be reached."""
149 return trait.heat_celsius
151 return trait.cool_celsius
156 """Return the upper bound target temperature."""
161 return trait.cool_celsius
165 """Return the lower bound target temperature."""
170 return trait.heat_celsius
175 ) -> ThermostatEcoTrait | ThermostatTemperatureSetpointTrait | None:
176 """Return the correct trait with a target temp depending on mode."""
179 and ThermostatEcoTrait.NAME
in self.
_device_device.traits
182 ThermostatEcoTrait, self.
_device_device.traits[ThermostatEcoTrait.NAME]
184 if ThermostatTemperatureSetpointTrait.NAME
in self.
_device_device.traits:
186 ThermostatTemperatureSetpointTrait,
187 self.
_device_device.traits[ThermostatTemperatureSetpointTrait.NAME],
193 """Return the current operation (e.g. heat, cool, idle)."""
194 hvac_mode = HVACMode.OFF
195 if ThermostatModeTrait.NAME
in self.
_device_device.traits:
196 trait = self.
_device_device.traits[ThermostatModeTrait.NAME]
197 if trait.mode
in THERMOSTAT_MODE_MAP:
198 hvac_mode = THERMOSTAT_MODE_MAP[trait.mode]
203 """Return the current HVAC action (heating, cooling)."""
204 trait = self.
_device_device.traits[ThermostatHvacTrait.NAME]
206 return HVACAction.IDLE
207 return THERMOSTAT_HVAC_STATUS_MAP.get(trait.status)
211 """Return the current active preset."""
212 if ThermostatEcoTrait.NAME
in self.
_device_device.traits:
213 trait = self.
_device_device.traits[ThermostatEcoTrait.NAME]
214 return PRESET_MODE_MAP.get(trait.mode, PRESET_NONE)
219 """Return the available presets."""
220 if ThermostatEcoTrait.NAME
not in self.
_device_device.traits:
223 PRESET_MODE_MAP[mode]
224 for mode
in self.
_device_device.traits[ThermostatEcoTrait.NAME].available_modes
225 if mode
in PRESET_MODE_MAP
230 """Return the current fan mode."""
233 and FanTrait.NAME
in self.
_device_device.traits
235 trait = self.
_device_device.traits[FanTrait.NAME]
236 return FAN_MODE_MAP.get(trait.timer_mode, FAN_OFF)
241 """Return the list of available fan modes."""
244 and FanTrait.NAME
in self.
_device_device.traits
250 """Compute the bitmap of supported features from the current state."""
251 features = ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
252 if HVACMode.HEAT_COOL
in self.
hvac_modeshvac_modes:
253 features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
255 features |= ClimateEntityFeature.TARGET_TEMPERATURE
256 if ThermostatEcoTrait.NAME
in self.
_device_device.traits:
257 features |= ClimateEntityFeature.PRESET_MODE
258 if FanTrait.NAME
in self.
_device_device.traits:
260 fan_trait = self.
_device_device.traits[FanTrait.NAME]
261 if fan_trait.timer_mode
is not None:
262 features |= ClimateEntityFeature.FAN_MODE
266 """Set new target hvac mode."""
267 if hvac_mode
not in self.
hvac_modeshvac_modes:
268 raise ValueError(f
"Unsupported hvac_mode '{hvac_mode}'")
269 api_mode = THERMOSTAT_INV_MODE_MAP[hvac_mode]
270 trait = self.
_device_device.traits[ThermostatModeTrait.NAME]
272 await trait.set_mode(api_mode)
273 except ApiException
as err:
275 f
"Error setting {self.entity_id} HVAC mode to {hvac_mode}: {err}"
279 """Set new target temperature."""
281 if kwargs.get(ATTR_HVAC_MODE)
is not None:
282 hvac_mode = kwargs[ATTR_HVAC_MODE]
284 low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
285 high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
286 temp = kwargs.get(ATTR_TEMPERATURE)
287 if ThermostatTemperatureSetpointTrait.NAME
not in self.
_device_device.traits:
289 f
"Error setting {self.entity_id} temperature to {kwargs}: "
290 "Unable to find setpoint trait."
292 trait = self.
_device_device.traits[ThermostatTemperatureSetpointTrait.NAME]
295 if low_temp
and high_temp:
296 if high_temp - low_temp < MIN_TEMP_RANGE:
300 high_temp = low_temp + MIN_TEMP_RANGE
302 low_temp = high_temp - MIN_TEMP_RANGE
303 await trait.set_range(low_temp, high_temp)
304 elif hvac_mode == HVACMode.COOL
and temp:
305 await trait.set_cool(temp)
306 elif hvac_mode == HVACMode.HEAT
and temp:
307 await trait.set_heat(temp)
308 except ApiException
as err:
310 f
"Error setting {self.entity_id} temperature to {kwargs}: {err}"
314 """Set new target preset mode."""
316 raise ValueError(f
"Unsupported preset_mode '{preset_mode}'")
319 trait = self.
_device_device.traits[ThermostatEcoTrait.NAME]
321 await trait.set_mode(PRESET_INV_MODE_MAP[preset_mode])
322 except ApiException
as err:
324 f
"Error setting {self.entity_id} preset mode to {preset_mode}: {err}"
328 """Set new target fan mode."""
330 raise ValueError(f
"Unsupported fan_mode '{fan_mode}'")
333 "Cannot turn on fan, please set an HVAC mode (e.g. heat/cool) first"
335 trait = self.
_device_device.traits[FanTrait.NAME]
337 if fan_mode != FAN_OFF:
338 duration = MAX_FAN_DURATION
340 await trait.set_timer(FAN_INV_MODE_MAP[fan_mode], duration=duration)
341 except ApiException
as err:
343 f
"Error setting {self.entity_id} fan mode to {fan_mode}: {err}"
list[str]|None fan_modes(self)
ClimateEntityFeature supported_features(self)
float|None target_temperature_high(self)
None async_set_hvac_mode(self, HVACMode hvac_mode)
HVACMode|None hvac_mode(self)
list[str]|None preset_modes(self)
list[HVACMode] hvac_modes(self)
str|None preset_mode(self)
None async_added_to_hass(self)
ThermostatEcoTrait|ThermostatTemperatureSetpointTrait|None _target_temperature_trait(self)
None async_set_fan_mode(self, str fan_mode)
float|None current_temperature(self)
HVACAction|None hvac_action(self)
ClimateEntityFeature _get_supported_features(self)
float|None target_temperature_high(self)
None async_set_preset_mode(self, str preset_mode)
list[str] preset_modes(self)
None async_set_hvac_mode(self, HVACMode hvac_mode)
list[str] fan_modes(self)
None async_set_temperature(self, **Any kwargs)
None __init__(self, Device device)
float|None target_temperature_low(self)
float|None target_temperature(self)
None async_write_ha_state(self)
int|None supported_features(self)
None async_on_remove(self, CALLBACK_TYPE func)
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)