Increase static type coverage for nest integration (#53475)

Co-authored-by: Mick Vleeshouwer <mick@imick.nl>
Co-authored-by: Franck Nijhof <git@frenck.dev>
pull/53534/head
Allen Porter 2021-07-26 16:43:52 -07:00 committed by GitHub
parent d4c4263730
commit 6376b4be5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 146 additions and 110 deletions

View File

@ -69,7 +69,7 @@ CONFIG_SCHEMA = vol.Schema(
PLATFORMS = ["sensor", "camera", "climate"]
async def async_setup(hass: HomeAssistant, config: dict):
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""Set up Nest components with dispatch between old/new flows."""
hass.data[DOMAIN] = {}
@ -109,7 +109,7 @@ class SignalUpdateCallback:
"""Initialize EventCallback."""
self._hass = hass
async def async_handle_event(self, event_message: EventMessage):
async def async_handle_event(self, event_message: EventMessage) -> None:
"""Process an incoming EventMessage."""
if not event_message.resource_update_name:
return
@ -194,7 +194,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if DATA_SDM not in entry.data:
# Legacy API

View File

@ -29,13 +29,13 @@ class AsyncConfigEntryAuth(AbstractAuth):
self._client_id = client_id
self._client_secret = client_secret
async def async_get_access_token(self):
async def async_get_access_token(self) -> str:
"""Return a valid access token for SDM API."""
if not self._oauth_session.valid_token:
await self._oauth_session.async_ensure_token_valid()
return self._oauth_session.token["access_token"]
async def async_get_creds(self):
async def async_get_creds(self) -> Credentials:
"""Return an OAuth credential for Pub/Sub Subscriber."""
# We don't have a way for Home Assistant to refresh creds on behalf
# of the google pub/sub subscriber. Instead, build a full

View File

@ -2,13 +2,14 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_SDM
from .legacy.binary_sensor import async_setup_legacy_entry
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the binary sensors."""
assert DATA_SDM not in entry.data

View File

@ -2,6 +2,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .camera_sdm import async_setup_sdm_entry
from .const import DATA_SDM
@ -9,7 +10,7 @@ from .legacy.camera import async_setup_legacy_entry
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the cameras."""
if DATA_SDM not in entry.data:

View File

@ -3,11 +3,14 @@ from __future__ import annotations
import datetime
import logging
from typing import Any, Callable
from google_nest_sdm.camera_traits import (
CameraEventImageTrait,
CameraImageTrait,
CameraLiveStreamTrait,
CameraMotionTrait,
RtspStream,
)
from google_nest_sdm.device import Device
from google_nest_sdm.exceptions import GoogleNestException
@ -18,11 +21,13 @@ from homeassistant.components.ffmpeg import async_get_image
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
from .const import DATA_SUBSCRIBER, DOMAIN
from .device_info import DeviceInfo
from .device_info import NestDeviceInfo
_LOGGER = logging.getLogger(__name__)
@ -31,7 +36,7 @@ STREAM_EXPIRATION_BUFFER = datetime.timedelta(seconds=30)
async def async_setup_sdm_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the cameras."""
@ -60,13 +65,13 @@ class NestCamera(Camera):
"""Initialize the camera."""
super().__init__()
self._device = device
self._device_info = DeviceInfo(device)
self._stream = None
self._stream_refresh_unsub = None
self._device_info = NestDeviceInfo(device)
self._stream: RtspStream | None = None
self._stream_refresh_unsub: Callable[[], None] | None = None
# Cache of most recent event image
self._event_id = None
self._event_image_bytes = None
self._event_image_cleanup_unsub = None
self._event_id: str | None = None
self._event_image_bytes: bytes | None = None
self._event_image_cleanup_unsub: Callable[[], None] | None = None
@property
def should_poll(self) -> bool:
@ -74,40 +79,40 @@ class NestCamera(Camera):
return False
@property
def unique_id(self) -> str | None:
def unique_id(self) -> str:
"""Return a unique ID."""
# The API "name" field is a unique device identifier.
return f"{self._device.name}-camera"
@property
def name(self):
def name(self) -> str | None:
"""Return the name of the camera."""
return self._device_info.device_name
@property
def device_info(self):
def device_info(self) -> DeviceInfo:
"""Return device specific attributes."""
return self._device_info.device_info
@property
def brand(self):
def brand(self) -> str | None:
"""Return the camera brand."""
return self._device_info.device_brand
@property
def model(self):
def model(self) -> str | None:
"""Return the camera model."""
return self._device_info.device_model
@property
def supported_features(self):
def supported_features(self) -> int:
"""Flag supported features."""
supported_features = 0
if CameraLiveStreamTrait.NAME in self._device.traits:
supported_features |= SUPPORT_STREAM
return supported_features
async def stream_source(self):
async def stream_source(self) -> str | None:
"""Return the source of the stream."""
if CameraLiveStreamTrait.NAME not in self._device.traits:
return None
@ -120,8 +125,9 @@ class NestCamera(Camera):
_LOGGER.warning("Stream already expired")
return self._stream.rtsp_stream_url
def _schedule_stream_refresh(self):
def _schedule_stream_refresh(self) -> None:
"""Schedules an alarm to refresh the stream url before expiration."""
assert self._stream
_LOGGER.debug("New stream url expires at %s", self._stream.expires_at)
refresh_time = self._stream.expires_at - STREAM_EXPIRATION_BUFFER
# Schedule an alarm to extend the stream
@ -134,7 +140,7 @@ class NestCamera(Camera):
refresh_time,
)
async def _handle_stream_refresh(self, now):
async def _handle_stream_refresh(self, now: datetime.datetime) -> None:
"""Alarm that fires to check if the stream should be refreshed."""
if not self._stream:
return
@ -154,7 +160,7 @@ class NestCamera(Camera):
self.stream.update_source(self._stream.rtsp_stream_url)
self._schedule_stream_refresh()
async def async_will_remove_from_hass(self):
async def async_will_remove_from_hass(self) -> None:
"""Invalidates the RTSP token when unloaded."""
if self._stream:
_LOGGER.debug("Invalidating stream")
@ -166,13 +172,13 @@ class NestCamera(Camera):
if self._event_image_cleanup_unsub is not None:
self._event_image_cleanup_unsub()
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Run when entity is added to register update signal handler."""
self.async_on_remove(
self._device.add_update_listener(self.async_write_ha_state)
)
async def async_camera_image(self):
async def async_camera_image(self) -> bytes | None:
"""Return bytes of camera image."""
# Returns the snapshot of the last event for ~30 seconds after the event
active_event_image = await self._async_active_event_image()
@ -184,7 +190,7 @@ class NestCamera(Camera):
return None
return await async_get_image(self.hass, stream_url, output_format=IMAGE_JPEG)
async def _async_active_event_image(self):
async def _async_active_event_image(self) -> bytes | None:
"""Return image from any active events happening."""
if CameraEventImageTrait.NAME not in self._device.traits:
return None
@ -204,7 +210,9 @@ class NestCamera(Camera):
self._schedule_event_image_cleanup(event.expires_at)
return image_bytes
async def _async_fetch_active_event_image(self, trait):
async def _async_fetch_active_event_image(
self, trait: CameraMotionTrait
) -> bytes | None:
"""Return image bytes for an active event."""
try:
event_image = await trait.generate_active_event_image()
@ -219,7 +227,7 @@ class NestCamera(Camera):
_LOGGER.debug("Unable to fetch event image: %s", err)
return None
def _schedule_event_image_cleanup(self, point_in_time):
def _schedule_event_image_cleanup(self, point_in_time: datetime.datetime) -> None:
"""Schedules an alarm to remove the image bytes from memory, honoring expiration."""
if self._event_image_cleanup_unsub is not None:
self._event_image_cleanup_unsub()
@ -229,7 +237,7 @@ class NestCamera(Camera):
point_in_time,
)
def _handle_event_image_cleanup(self, now):
def _handle_event_image_cleanup(self, now: Any) -> None:
"""Clear images cached from events and scheduled callback."""
self._event_id = None
self._event_image_bytes = None

View File

@ -2,6 +2,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .climate_sdm import async_setup_sdm_entry
from .const import DATA_SDM
@ -9,7 +10,7 @@ from .legacy.climate import async_setup_legacy_entry
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the climate platform."""
if DATA_SDM not in entry.data:

View File

@ -1,6 +1,8 @@
"""Support for Google Nest SDM climate devices."""
from __future__ import annotations
from typing import Any
from google_nest_sdm.device import Device
from google_nest_sdm.device_traits import FanTrait, TemperatureTrait
from google_nest_sdm.exceptions import GoogleNestException
@ -37,12 +39,14 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_SUBSCRIBER, DOMAIN
from .device_info import DeviceInfo
from .device_info import NestDeviceInfo
# Mapping for sdm.devices.traits.ThermostatMode mode field
THERMOSTAT_MODE_MAP = {
THERMOSTAT_MODE_MAP: dict[str, str] = {
"OFF": HVAC_MODE_OFF,
"HEAT": HVAC_MODE_HEAT,
"COOL": HVAC_MODE_COOL,
@ -78,7 +82,7 @@ MAX_FAN_DURATION = 43200 # 15 hours is the max in the SDM API
async def async_setup_sdm_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the client entities."""
@ -101,7 +105,7 @@ class ThermostatEntity(ClimateEntity):
def __init__(self, device: Device) -> None:
"""Initialize ThermostatEntity."""
self._device = device
self._device_info = DeviceInfo(device)
self._device_info = NestDeviceInfo(device)
self._supported_features = 0
@property
@ -116,16 +120,16 @@ class ThermostatEntity(ClimateEntity):
return self._device.name
@property
def name(self):
def name(self) -> str | None:
"""Return the name of the entity."""
return self._device_info.device_name
@property
def device_info(self):
def device_info(self) -> DeviceInfo:
"""Return device specific attributes."""
return self._device_info.device_info
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Run when entity is added to register update signal handler."""
self._supported_features = self._get_supported_features()
self.async_on_remove(
@ -133,20 +137,20 @@ class ThermostatEntity(ClimateEntity):
)
@property
def temperature_unit(self):
def temperature_unit(self) -> str:
"""Return the unit of temperature measurement for the system."""
return TEMP_CELSIUS
@property
def current_temperature(self):
def current_temperature(self) -> float | None:
"""Return the current temperature."""
if TemperatureTrait.NAME not in self._device.traits:
return None
trait = self._device.traits[TemperatureTrait.NAME]
trait: TemperatureTrait = self._device.traits[TemperatureTrait.NAME]
return trait.ambient_temperature_celsius
@property
def target_temperature(self):
def target_temperature(self) -> float | None:
"""Return the temperature currently set to be reached."""
trait = self._target_temperature_trait
if not trait:
@ -158,7 +162,7 @@ class ThermostatEntity(ClimateEntity):
return None
@property
def target_temperature_high(self):
def target_temperature_high(self) -> float | None:
"""Return the upper bound target temperature."""
if self.hvac_mode != HVAC_MODE_HEAT_COOL:
return None
@ -168,7 +172,7 @@ class ThermostatEntity(ClimateEntity):
return trait.cool_celsius
@property
def target_temperature_low(self):
def target_temperature_low(self) -> float | None:
"""Return the lower bound target temperature."""
if self.hvac_mode != HVAC_MODE_HEAT_COOL:
return None
@ -178,7 +182,9 @@ class ThermostatEntity(ClimateEntity):
return trait.heat_celsius
@property
def _target_temperature_trait(self):
def _target_temperature_trait(
self,
) -> ThermostatEcoTrait | ThermostatTemperatureSetpointTrait | None:
"""Return the correct trait with a target temp depending on mode."""
if (
self.preset_mode == PRESET_ECO
@ -190,7 +196,7 @@ class ThermostatEntity(ClimateEntity):
return None
@property
def hvac_mode(self):
def hvac_mode(self) -> str:
"""Return the current operation (e.g. heat, cool, idle)."""
hvac_mode = HVAC_MODE_OFF
if ThermostatModeTrait.NAME in self._device.traits:
@ -202,7 +208,7 @@ class ThermostatEntity(ClimateEntity):
return hvac_mode
@property
def hvac_modes(self):
def hvac_modes(self) -> list[str]:
"""List of available operation modes."""
supported_modes = []
for mode in self._get_device_hvac_modes:
@ -213,7 +219,7 @@ class ThermostatEntity(ClimateEntity):
return supported_modes
@property
def _get_device_hvac_modes(self):
def _get_device_hvac_modes(self) -> set[str]:
"""Return the set of SDM API hvac modes supported by the device."""
modes = []
if ThermostatModeTrait.NAME in self._device.traits:
@ -222,7 +228,7 @@ class ThermostatEntity(ClimateEntity):
return set(modes)
@property
def hvac_action(self):
def hvac_action(self) -> str | None:
"""Return the current HVAC action (heating, cooling)."""
trait = self._device.traits[ThermostatHvacTrait.NAME]
if trait.status in THERMOSTAT_HVAC_STATUS_MAP:
@ -230,7 +236,7 @@ class ThermostatEntity(ClimateEntity):
return None
@property
def preset_mode(self):
def preset_mode(self) -> str:
"""Return the current active preset."""
if ThermostatEcoTrait.NAME in self._device.traits:
trait = self._device.traits[ThermostatEcoTrait.NAME]
@ -238,7 +244,7 @@ class ThermostatEntity(ClimateEntity):
return PRESET_NONE
@property
def preset_modes(self):
def preset_modes(self) -> list[str]:
"""Return the available presets."""
modes = []
if ThermostatEcoTrait.NAME in self._device.traits:
@ -249,7 +255,7 @@ class ThermostatEntity(ClimateEntity):
return modes
@property
def fan_mode(self):
def fan_mode(self) -> str:
"""Return the current fan mode."""
if FanTrait.NAME in self._device.traits:
trait = self._device.traits[FanTrait.NAME]
@ -257,7 +263,7 @@ class ThermostatEntity(ClimateEntity):
return FAN_OFF
@property
def fan_modes(self):
def fan_modes(self) -> list[str]:
"""Return the list of available fan modes."""
modes = []
if FanTrait.NAME in self._device.traits:
@ -265,11 +271,11 @@ class ThermostatEntity(ClimateEntity):
return modes
@property
def supported_features(self):
def supported_features(self) -> int:
"""Bitmap of supported features."""
return self._supported_features
def _get_supported_features(self):
def _get_supported_features(self) -> int:
"""Compute the bitmap of supported features from the current state."""
features = 0
if HVAC_MODE_HEAT_COOL in self.hvac_modes:
@ -285,7 +291,7 @@ class ThermostatEntity(ClimateEntity):
features |= SUPPORT_FAN_MODE
return features
async def async_set_hvac_mode(self, hvac_mode):
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
if hvac_mode not in self.hvac_modes:
raise ValueError(f"Unsupported hvac_mode '{hvac_mode}'")
@ -297,7 +303,7 @@ class ThermostatEntity(ClimateEntity):
trait = self._device.traits[ThermostatModeTrait.NAME]
await trait.set_mode(api_mode)
async def async_set_temperature(self, **kwargs):
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
@ -313,14 +319,14 @@ class ThermostatEntity(ClimateEntity):
elif self.hvac_mode == HVAC_MODE_HEAT and temp:
await trait.set_heat(temp)
async def async_set_preset_mode(self, preset_mode):
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new target preset mode."""
if preset_mode not in self.preset_modes:
raise ValueError(f"Unsupported preset_mode '{preset_mode}'")
trait = self._device.traits[ThermostatEcoTrait.NAME]
await trait.set_mode(PRESET_INV_MODE_MAP[preset_mode])
async def async_set_fan_mode(self, fan_mode):
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
if fan_mode not in self.fan_modes:
raise ValueError(f"Unsupported fan_mode '{fan_mode}'")

View File

@ -35,7 +35,13 @@ _LOGGER = logging.getLogger(__name__)
@callback
def register_flow_implementation(hass, domain, name, gen_authorize_url, convert_code):
def register_flow_implementation(
hass: HomeAssistant,
domain: str,
name: str,
gen_authorize_url: str,
convert_code: str,
) -> None:
"""Register a flow implementation for legacy api.
domain: Domain of the component responsible for the implementation.
@ -74,7 +80,7 @@ class NestFlowHandler(
DOMAIN = DOMAIN
VERSION = 1
def __init__(self):
def __init__(self) -> None:
"""Initialize NestFlowHandler."""
super().__init__()
# When invoked for reauth, allows updating an existing config entry

View File

@ -1,11 +1,15 @@
"""Library for extracting device specific information common to entities."""
from __future__ import annotations
from google_nest_sdm.device import Device
from google_nest_sdm.device_traits import InfoTrait
from homeassistant.helpers.entity import DeviceInfo
from .const import DOMAIN
DEVICE_TYPE_MAP = {
DEVICE_TYPE_MAP: dict[str, str] = {
"sdm.devices.types.CAMERA": "Camera",
"sdm.devices.types.DISPLAY": "Display",
"sdm.devices.types.DOORBELL": "Doorbell",
@ -13,7 +17,7 @@ DEVICE_TYPE_MAP = {
}
class DeviceInfo:
class NestDeviceInfo:
"""Provide device info from the SDM device, shared across platforms."""
device_brand = "Google Nest"
@ -23,21 +27,23 @@ class DeviceInfo:
self._device = device
@property
def device_info(self):
def device_info(self) -> DeviceInfo:
"""Return device specific attributes."""
return {
# The API "name" field is a unique device identifier.
"identifiers": {(DOMAIN, self._device.name)},
"name": self.device_name,
"manufacturer": self.device_brand,
"model": self.device_model,
}
return DeviceInfo(
{
# The API "name" field is a unique device identifier.
"identifiers": {(DOMAIN, self._device.name)},
"name": self.device_name,
"manufacturer": self.device_brand,
"model": self.device_model,
}
)
@property
def device_name(self):
def device_name(self) -> str:
"""Return the name of the physical device that includes the sensor."""
if InfoTrait.NAME in self._device.traits:
trait = self._device.traits[InfoTrait.NAME]
trait: InfoTrait = self._device.traits[InfoTrait.NAME]
if trait.custom_name:
return trait.custom_name
# Build a name from the room/structure. Note: This room/structure name
@ -50,9 +56,9 @@ class DeviceInfo:
return self.device_model
@property
def device_model(self):
def device_model(self) -> str:
"""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)
return DEVICE_TYPE_MAP.get(self._device.type, "Unknown")

View File

@ -11,6 +11,7 @@ from homeassistant.components.device_automation.exceptions import (
from homeassistant.components.homeassistant.triggers import event as event_trigger
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers.device_registry import DeviceRegistry
from homeassistant.helpers.typing import ConfigType
from .const import DATA_SUBSCRIBER, DOMAIN
@ -29,11 +30,14 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
async def async_get_nest_device_id(hass: HomeAssistant, device_id: str) -> str | None:
"""Get the nest API device_id from the HomeAssistant device_id."""
device_registry = await hass.helpers.device_registry.async_get_registry()
device_registry: DeviceRegistry = (
await hass.helpers.device_registry.async_get_registry()
)
device = device_registry.async_get(device_id)
for (domain, unique_id) in device.identifiers:
if domain == DOMAIN:
return unique_id
if device:
for (domain, unique_id) in device.identifiers:
if domain == DOMAIN:
return unique_id
return None

View File

@ -96,7 +96,7 @@ def nest_update_event_broker(hass, nest):
_LOGGER.debug("Stop listening for nest.update_event")
async def async_setup_legacy(hass, config):
async def async_setup_legacy(hass, config) -> bool:
"""Set up Nest components using the legacy nest API."""
if DOMAIN not in config:
return True
@ -122,7 +122,7 @@ async def async_setup_legacy(hass, config):
return True
async def async_setup_legacy_entry(hass, entry):
async def async_setup_legacy_entry(hass, entry) -> bool:
"""Set up Nest from legacy config entry."""
nest = Nest(access_token=entry.data["tokens"]["access_token"])

View File

@ -61,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""
async def async_setup_legacy_entry(hass, entry, async_add_entities):
async def async_setup_legacy_entry(hass, entry, async_add_entities) -> None:
"""Set up a Nest binary sensor based on a config entry."""
nest = hass.data[DATA_NEST]

View File

@ -23,7 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""
async def async_setup_legacy_entry(hass, entry, async_add_entities):
async def async_setup_legacy_entry(hass, entry, async_add_entities) -> None:
"""Set up a Nest sensor based on a config entry."""
camera_devices = await hass.async_add_executor_job(hass.data[DATA_NEST].cameras)
cameras = [NestCamera(structure, device) for structure, device in camera_devices]

View File

@ -74,7 +74,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""
async def async_setup_legacy_entry(hass, entry, async_add_entities):
async def async_setup_legacy_entry(hass, entry, async_add_entities) -> None:
"""Set up the Nest climate device based on a config entry."""
temp_unit = hass.config.units.temperature_unit

View File

@ -77,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""
async def async_setup_legacy_entry(hass, entry, async_add_entities):
async def async_setup_legacy_entry(hass, entry, async_add_entities) -> None:
"""Set up a Nest sensor based on a config entry."""
nest = hass.data[DATA_NEST]

View File

@ -2,6 +2,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_SDM
from .legacy.sensor import async_setup_legacy_entry
@ -9,7 +10,7 @@ from .sensor_sdm import async_setup_sdm_entry
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the sensors."""
if DATA_SDM not in entry.data:

View File

@ -17,9 +17,11 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_SUBSCRIBER, DOMAIN
from .device_info import DeviceInfo
from .device_info import NestDeviceInfo
_LOGGER = logging.getLogger(__name__)
@ -33,7 +35,7 @@ DEVICE_TYPE_MAP = {
async def async_setup_sdm_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the sensors."""
@ -59,7 +61,7 @@ class SensorBase(SensorEntity):
def __init__(self, device: Device) -> None:
"""Initialize the sensor."""
self._device = device
self._device_info = DeviceInfo(device)
self._device_info = NestDeviceInfo(device)
@property
def should_poll(self) -> bool:
@ -73,11 +75,11 @@ class SensorBase(SensorEntity):
return f"{self._device.name}-{self.device_class}"
@property
def device_info(self):
def device_info(self) -> DeviceInfo:
"""Return device specific attributes."""
return self._device_info.device_info
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Run when entity is added to register update signal handler."""
self.async_on_remove(
self._device.add_update_listener(self.async_write_ha_state)
@ -88,23 +90,23 @@ class TemperatureSensor(SensorBase):
"""Representation of a Temperature Sensor."""
@property
def name(self):
def name(self) -> str:
"""Return the name of the sensor."""
return f"{self._device_info.device_name} Temperature"
@property
def state(self):
def state(self) -> float:
"""Return the state of the sensor."""
trait = self._device.traits[TemperatureTrait.NAME]
trait: TemperatureTrait = self._device.traits[TemperatureTrait.NAME]
return trait.ambient_temperature_celsius
@property
def unit_of_measurement(self):
def unit_of_measurement(self) -> str:
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def device_class(self):
def device_class(self) -> str:
"""Return the class of this device."""
return DEVICE_CLASS_TEMPERATURE
@ -119,22 +121,22 @@ class HumiditySensor(SensorBase):
return f"{self._device.name}-humidity"
@property
def name(self):
def name(self) -> str:
"""Return the name of the sensor."""
return f"{self._device_info.device_name} Humidity"
@property
def state(self):
def state(self) -> float:
"""Return the state of the sensor."""
trait = self._device.traits[HumidityTrait.NAME]
trait: HumidityTrait = self._device.traits[HumidityTrait.NAME]
return trait.ambient_humidity_percent
@property
def unit_of_measurement(self):
def unit_of_measurement(self) -> str:
"""Return the unit of measurement."""
return PERCENTAGE
@property
def device_class(self):
def device_class(self) -> str:
"""Return the class of this device."""
return DEVICE_CLASS_HUMIDITY

View File

@ -2,7 +2,7 @@
from google_nest_sdm.device import Device
from homeassistant.components.nest.device_info import DeviceInfo
from homeassistant.components.nest.device_info import NestDeviceInfo
def test_device_custom_name():
@ -20,7 +20,7 @@ def test_device_custom_name():
auth=None,
)
device_info = DeviceInfo(device)
device_info = NestDeviceInfo(device)
assert device_info.device_name == "My Doorbell"
assert device_info.device_model == "Doorbell"
assert device_info.device_brand == "Google Nest"
@ -45,7 +45,7 @@ def test_device_name_room():
auth=None,
)
device_info = DeviceInfo(device)
device_info = NestDeviceInfo(device)
assert device_info.device_name == "Some Room"
assert device_info.device_model == "Doorbell"
assert device_info.device_brand == "Google Nest"
@ -64,7 +64,7 @@ def test_device_no_name():
auth=None,
)
device_info = DeviceInfo(device)
device_info = NestDeviceInfo(device)
assert device_info.device_name == "Doorbell"
assert device_info.device_model == "Doorbell"
assert device_info.device_brand == "Google Nest"
@ -91,13 +91,13 @@ def test_device_invalid_type():
auth=None,
)
device_info = DeviceInfo(device)
device_info = NestDeviceInfo(device)
assert device_info.device_name == "My Doorbell"
assert device_info.device_model is None
assert device_info.device_model == "Unknown"
assert device_info.device_brand == "Google Nest"
assert device_info.device_info == {
"identifiers": {("nest", "some-device-id")},
"name": "My Doorbell",
"manufacturer": "Google Nest",
"model": None,
"model": "Unknown",
}

View File

@ -208,5 +208,5 @@ async def test_device_with_unknown_type(hass):
device_registry = dr.async_get(hass)
device = device_registry.async_get(entry.device_id)
assert device.name == "My Sensor"
assert device.model is None
assert device.model == "Unknown"
assert device.identifiers == {("nest", "some-device-id")}