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.pypull/79244/head
parent
1a9bcafbd2
commit
504ce8e93a
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue