* 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
105 lines
3.8 KiB
Python
105 lines
3.8 KiB
Python
"""Library for extracting device specific information common to entities."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
|
|
from google_nest_sdm.device import Device
|
|
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 CONNECTIVITY_TRAIT_OFFLINE, DATA_DEVICE_MANAGER, DOMAIN
|
|
|
|
DEVICE_TYPE_MAP: dict[str, str] = {
|
|
"sdm.devices.types.CAMERA": "Camera",
|
|
"sdm.devices.types.DISPLAY": "Display",
|
|
"sdm.devices.types.DOORBELL": "Doorbell",
|
|
"sdm.devices.types.THERMOSTAT": "Thermostat",
|
|
}
|
|
|
|
|
|
class NestDeviceInfo:
|
|
"""Provide device info from the SDM device, shared across platforms."""
|
|
|
|
device_brand = "Google Nest"
|
|
|
|
def __init__(self, device: Device) -> None:
|
|
"""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."""
|
|
return DeviceInfo(
|
|
# The API "name" field is a unique device identifier.
|
|
identifiers={(DOMAIN, self._device.name)},
|
|
manufacturer=self.device_brand,
|
|
model=self.device_model,
|
|
name=self.device_name,
|
|
suggested_area=self.suggested_area,
|
|
)
|
|
|
|
@property
|
|
def device_name(self) -> str | None:
|
|
"""Return the name of the physical device that includes the sensor."""
|
|
if InfoTrait.NAME in self._device.traits:
|
|
trait: InfoTrait = self._device.traits[InfoTrait.NAME]
|
|
if trait.custom_name:
|
|
return str(trait.custom_name)
|
|
# Build a name from the room/structure if not set explicitly
|
|
if area := self.suggested_area:
|
|
return area
|
|
return self.device_model
|
|
|
|
@property
|
|
def device_model(self) -> str | None:
|
|
"""Return device model information."""
|
|
# The API intentionally returns minimal information about specific
|
|
# devices, instead relying on traits, but we can infer a generic model
|
|
# name based on the type
|
|
return DEVICE_TYPE_MAP.get(self._device.type)
|
|
|
|
@property
|
|
def suggested_area(self) -> str | None:
|
|
"""Return device suggested area based on the Google Home room."""
|
|
if parent_relations := self._device.parent_relations:
|
|
items = sorted(parent_relations.items())
|
|
names = [name for id, name in items]
|
|
return " ".join(names)
|
|
return None
|
|
|
|
|
|
@callback
|
|
def async_nest_devices(hass: HomeAssistant) -> Mapping[str, Device]:
|
|
"""Return a mapping of all nest devices for all config entries."""
|
|
devices = {}
|
|
for entry_id in hass.data[DOMAIN]:
|
|
if not (device_manager := hass.data[DOMAIN][entry_id].get(DATA_DEVICE_MANAGER)):
|
|
continue
|
|
devices.update(
|
|
{device.name: device for device in device_manager.devices.values()}
|
|
)
|
|
return devices
|
|
|
|
|
|
@callback
|
|
def async_nest_devices_by_device_id(hass: HomeAssistant) -> Mapping[str, Device]:
|
|
"""Return a mapping of all nest devices by home assistant device id, for all config entries."""
|
|
device_registry = dr.async_get(hass)
|
|
devices = {}
|
|
for nest_device_id, device in async_nest_devices(hass).items():
|
|
if device_entry := device_registry.async_get_device({(DOMAIN, nest_device_id)}):
|
|
devices[device_entry.id] = device
|
|
return devices
|