diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index e40db60d5ed..3113cb2dd40 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -117,6 +117,11 @@ class ThermostatEntity(ClimateEntity): """Return device specific attributes.""" return self._device_info.device_info + @property + def available(self) -> bool: + """Return device availability.""" + return self._device_info.available + async def async_added_to_hass(self) -> None: """Run when entity is added to register update signal handler.""" self._attr_supported_features = self._get_supported_features() diff --git a/homeassistant/components/nest/const.py b/homeassistant/components/nest/const.py index 64c27c1643b..853e778977d 100644 --- a/homeassistant/components/nest/const.py +++ b/homeassistant/components/nest/const.py @@ -14,6 +14,8 @@ CONF_SUBSCRIBER_ID = "subscriber_id" CONF_SUBSCRIBER_ID_IMPORTED = "subscriber_id_imported" CONF_CLOUD_PROJECT_ID = "cloud_project_id" +CONNECTIVITY_TRAIT_OFFLINE = "OFFLINE" + SIGNAL_NEST_UPDATE = "nest_update" # For the Google Nest Device Access API diff --git a/homeassistant/components/nest/device_info.py b/homeassistant/components/nest/device_info.py index 2d2b01d3849..e269b76fcc4 100644 --- a/homeassistant/components/nest/device_info.py +++ b/homeassistant/components/nest/device_info.py @@ -5,13 +5,13 @@ from __future__ import annotations from collections.abc import Mapping from google_nest_sdm.device import Device -from google_nest_sdm.device_traits import InfoTrait +from google_nest_sdm.device_traits import ConnectivityTrait, InfoTrait from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo -from .const import DATA_DEVICE_MANAGER, DOMAIN +from .const import CONNECTIVITY_TRAIT_OFFLINE, DATA_DEVICE_MANAGER, DOMAIN DEVICE_TYPE_MAP: dict[str, str] = { "sdm.devices.types.CAMERA": "Camera", @@ -30,6 +30,15 @@ class NestDeviceInfo: """Initialize the DeviceInfo.""" self._device = device + @property + def available(self) -> bool: + """Return device availability.""" + if ConnectivityTrait.NAME in self._device.traits: + trait: ConnectivityTrait = self._device.traits[ConnectivityTrait.NAME] + if trait.status == CONNECTIVITY_TRAIT_OFFLINE: + return False + return True + @property def device_info(self) -> DeviceInfo: """Return device specific attributes.""" diff --git a/homeassistant/components/nest/sensor_sdm.py b/homeassistant/components/nest/sensor_sdm.py index 11edc9f3506..b36e9103196 100644 --- a/homeassistant/components/nest/sensor_sdm.py +++ b/homeassistant/components/nest/sensor_sdm.py @@ -62,6 +62,11 @@ class SensorBase(SensorEntity): self._attr_unique_id = f"{device.name}-{self.device_class}" self._attr_device_info = self._device_info.device_info + @property + def available(self) -> bool: + """Return the device availability.""" + return self._device_info.available + async def async_added_to_hass(self) -> None: """Run when entity is added to register update signal handler.""" self.async_on_remove( diff --git a/tests/components/nest/test_climate_sdm.py b/tests/components/nest/test_climate_sdm.py index 440855f6ab7..4ac58171fcd 100644 --- a/tests/components/nest/test_climate_sdm.py +++ b/tests/components/nest/test_climate_sdm.py @@ -34,7 +34,11 @@ from homeassistant.components.climate import ( HVACAction, HVACMode, ) -from homeassistant.const import ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE +from homeassistant.const import ( + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, + STATE_UNAVAILABLE, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -1442,3 +1446,63 @@ async def test_thermostat_hvac_mode_failure( with pytest.raises(HomeAssistantError): await common.async_set_preset_mode(hass, PRESET_ECO) await hass.async_block_till_done() + + +async def test_thermostat_available( + hass: HomeAssistant, setup_platform: PlatformSetup, create_device: CreateDevice +): + """Test a thermostat that is available.""" + create_device.create( + { + "sdm.devices.traits.ThermostatHvac": { + "status": "COOLING", + }, + "sdm.devices.traits.ThermostatMode": { + "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], + "mode": "COOL", + }, + "sdm.devices.traits.Temperature": { + "ambientTemperatureCelsius": 29.9, + }, + "sdm.devices.traits.ThermostatTemperatureSetpoint": { + "coolCelsius": 28.0, + }, + "sdm.devices.traits.Connectivity": {"status": "ONLINE"}, + }, + ) + await setup_platform() + + assert len(hass.states.async_all()) == 1 + thermostat = hass.states.get("climate.my_thermostat") + assert thermostat is not None + assert thermostat.state == HVACMode.COOL + + +async def test_thermostat_unavailable( + hass: HomeAssistant, setup_platform: PlatformSetup, create_device: CreateDevice +): + """Test a thermostat that is unavailable.""" + create_device.create( + { + "sdm.devices.traits.ThermostatHvac": { + "status": "COOLING", + }, + "sdm.devices.traits.ThermostatMode": { + "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], + "mode": "COOL", + }, + "sdm.devices.traits.Temperature": { + "ambientTemperatureCelsius": 29.9, + }, + "sdm.devices.traits.ThermostatTemperatureSetpoint": { + "coolCelsius": 28.0, + }, + "sdm.devices.traits.Connectivity": {"status": "OFFLINE"}, + }, + ) + await setup_platform() + + assert len(hass.states.async_all()) == 1 + thermostat = hass.states.get("climate.my_thermostat") + assert thermostat is not None + assert thermostat.state == STATE_UNAVAILABLE diff --git a/tests/components/nest/test_sensor_sdm.py b/tests/components/nest/test_sensor_sdm.py index d1a89317959..c3698cf4123 100644 --- a/tests/components/nest/test_sensor_sdm.py +++ b/tests/components/nest/test_sensor_sdm.py @@ -20,6 +20,7 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, + STATE_UNAVAILABLE, TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant @@ -90,6 +91,58 @@ async def test_thermostat_device( assert device.identifiers == {("nest", DEVICE_ID)} +async def test_thermostat_device_available( + hass: HomeAssistant, create_device: CreateDevice, setup_platform: PlatformSetup +): + """Test a thermostat with temperature and humidity sensors that is Online.""" + create_device.create( + { + "sdm.devices.traits.Temperature": { + "ambientTemperatureCelsius": 25.1, + }, + "sdm.devices.traits.Humidity": { + "ambientHumidityPercent": 35.0, + }, + "sdm.devices.traits.Connectivity": {"status": "ONLINE"}, + } + ) + await setup_platform() + + temperature = hass.states.get("sensor.my_sensor_temperature") + assert temperature is not None + assert temperature.state == "25.1" + + humidity = hass.states.get("sensor.my_sensor_humidity") + assert humidity is not None + assert humidity.state == "35" + + +async def test_thermostat_device_unavailable( + hass: HomeAssistant, create_device: CreateDevice, setup_platform: PlatformSetup +): + """Test a thermostat with temperature and humidity sensors that is Offline.""" + create_device.create( + { + "sdm.devices.traits.Temperature": { + "ambientTemperatureCelsius": 25.1, + }, + "sdm.devices.traits.Humidity": { + "ambientHumidityPercent": 35.0, + }, + "sdm.devices.traits.Connectivity": {"status": "OFFLINE"}, + } + ) + await setup_platform() + + temperature = hass.states.get("sensor.my_sensor_temperature") + assert temperature is not None + assert temperature.state == STATE_UNAVAILABLE + + humidity = hass.states.get("sensor.my_sensor_humidity") + assert humidity is not None + assert humidity.state == STATE_UNAVAILABLE + + async def test_no_devices(hass: HomeAssistant, setup_platform: PlatformSetup): """Test no devices returned by the api.""" await setup_platform()