1
mirror of https://github.com/home-assistant/core synced 2024-08-15 18:25:44 +02:00

Set nest entities as unavailable on lost connection (#78773)

* NEST - Issues with lost internet connectivity #70479

Update Climate and Sensor entities to be unavailable when the device connectivity trait indicates the device is offline.

The prior behavior, the last known values would be displayed indefinitely if the device lost internet connectivity.  This was creating the illusion that the device was still connected.  With this change, the Home Assistant entities will become unavailable when the device loses connectivity.

* Update formatting

* Add doc strings, fix indentation

* Fix doc strings

* Update test_climate_sdm.py

* Update test_climate_sdm.py

* Update test_sensor_sdm.py

* Update test_sensor_sdm.py

* more formatting fixes

* Place availability logic in mixin

1. Consolidate repeated code into mixin and apply mixin to Climate and Sensor entities
2. Return true instead of super.available()
3. No unit test changes required to maintain code coverage

* Define self._device is mixin to make linter happier

* Remove logger used for debugging

* restore whitespace

* Fix test due to underlying merge change

* Update availability_mixin.py

* Move availability logic into device_info

* Update sensor_sdm.py
This commit is contained in:
PeteRager 2022-09-28 22:23:11 -04:00 committed by GitHub
parent 1a9bcafbd2
commit 504ce8e93a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 141 additions and 3 deletions

View File

@ -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()

View File

@ -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

View File

@ -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."""

View File

@ -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(

View File

@ -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

View File

@ -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()