Simplify nest event handling (#44367)
* Simplify nest event handling Use device specific update callbacks rather than a global callback. The motivation is to prepare for a follow up change that will store camera specific event tokens on the camera itself, so that a service can later fetch event specific image snapshots, which would be difficult to send across the event bus. * Increase nest camera test coverage * Remove unnecessary device updates for nest cameras * Remove unused imports * Fix device id check to look at returned entry * Remove unused imports after rebase * Partial revert of nest event simplification * Push more update logic into the nest library * Revert nest device_info changes * Revert test changes to restore global update behavior * Bump nest library version to support new callback interfacespull/44566/head
parent
9531b08f2a
commit
51b88337ca
|
@ -3,7 +3,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from google_nest_sdm.event import AsyncEventCallback, EventMessage
|
from google_nest_sdm.event import EventMessage
|
||||||
from google_nest_sdm.exceptions import AuthException, GoogleNestException
|
from google_nest_sdm.exceptions import AuthException, GoogleNestException
|
||||||
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
|
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -24,7 +24,6 @@ from homeassistant.helpers import (
|
||||||
config_entry_oauth2_flow,
|
config_entry_oauth2_flow,
|
||||||
config_validation as cv,
|
config_validation as cv,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
|
||||||
|
|
||||||
from . import api, config_flow
|
from . import api, config_flow
|
||||||
from .const import (
|
from .const import (
|
||||||
|
@ -34,7 +33,6 @@ from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
OAUTH2_AUTHORIZE,
|
OAUTH2_AUTHORIZE,
|
||||||
OAUTH2_TOKEN,
|
OAUTH2_TOKEN,
|
||||||
SIGNAL_NEST_UPDATE,
|
|
||||||
)
|
)
|
||||||
from .events import EVENT_NAME_MAP, NEST_EVENT
|
from .events import EVENT_NAME_MAP, NEST_EVENT
|
||||||
from .legacy import async_setup_legacy, async_setup_legacy_entry
|
from .legacy import async_setup_legacy, async_setup_legacy_entry
|
||||||
|
@ -106,7 +104,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class SignalUpdateCallback(AsyncEventCallback):
|
class SignalUpdateCallback:
|
||||||
"""An EventCallback invoked when new events arrive from subscriber."""
|
"""An EventCallback invoked when new events arrive from subscriber."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant):
|
def __init__(self, hass: HomeAssistant):
|
||||||
|
@ -116,17 +114,8 @@ class SignalUpdateCallback(AsyncEventCallback):
|
||||||
async def async_handle_event(self, event_message: EventMessage):
|
async def async_handle_event(self, event_message: EventMessage):
|
||||||
"""Process an incoming EventMessage."""
|
"""Process an incoming EventMessage."""
|
||||||
if not event_message.resource_update_name:
|
if not event_message.resource_update_name:
|
||||||
_LOGGER.debug("Ignoring event with no device_id")
|
|
||||||
return
|
return
|
||||||
device_id = event_message.resource_update_name
|
device_id = event_message.resource_update_name
|
||||||
_LOGGER.debug("Update for %s @ %s", device_id, event_message.timestamp)
|
|
||||||
traits = event_message.resource_update_traits
|
|
||||||
if traits:
|
|
||||||
_LOGGER.debug("Trait update %s", traits.keys())
|
|
||||||
# This event triggered an update to a device that changed some
|
|
||||||
# properties which the DeviceManager should already have received.
|
|
||||||
# Send a signal to refresh state of all listening devices.
|
|
||||||
async_dispatcher_send(self._hass, SIGNAL_NEST_UPDATE)
|
|
||||||
events = event_message.resource_update_events
|
events = event_message.resource_update_events
|
||||||
if not events:
|
if not events:
|
||||||
return
|
return
|
||||||
|
@ -134,7 +123,6 @@ class SignalUpdateCallback(AsyncEventCallback):
|
||||||
device_registry = await self._hass.helpers.device_registry.async_get_registry()
|
device_registry = await self._hass.helpers.device_registry.async_get_registry()
|
||||||
device_entry = device_registry.async_get_device({(DOMAIN, device_id)}, ())
|
device_entry = device_registry.async_get_device({(DOMAIN, device_id)}, ())
|
||||||
if not device_entry:
|
if not device_entry:
|
||||||
_LOGGER.debug("Ignoring event for unregistered device '%s'", device_id)
|
|
||||||
return
|
return
|
||||||
for event in events:
|
for event in events:
|
||||||
event_type = EVENT_NAME_MAP.get(event)
|
event_type = EVENT_NAME_MAP.get(event)
|
||||||
|
@ -170,7 +158,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
subscriber = GoogleNestSubscriber(
|
subscriber = GoogleNestSubscriber(
|
||||||
auth, config[CONF_PROJECT_ID], config[CONF_SUBSCRIBER_ID]
|
auth, config[CONF_PROJECT_ID], config[CONF_SUBSCRIBER_ID]
|
||||||
)
|
)
|
||||||
subscriber.set_update_callback(SignalUpdateCallback(hass))
|
callback = SignalUpdateCallback(hass)
|
||||||
|
subscriber.set_update_callback(callback.async_handle_event)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await subscriber.start_async()
|
await subscriber.start_async()
|
||||||
|
|
|
@ -13,12 +13,11 @@ from homeassistant.components.camera import SUPPORT_STREAM, Camera
|
||||||
from homeassistant.components.ffmpeg import async_get_image
|
from homeassistant.components.ffmpeg import async_get_image
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
||||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE
|
from .const import DATA_SUBSCRIBER, DOMAIN
|
||||||
from .device_info import DeviceInfo
|
from .device_info import DeviceInfo
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -151,13 +150,8 @@ class NestCamera(Camera):
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Run when entity is added to register update signal handler."""
|
"""Run when entity is added to register update signal handler."""
|
||||||
# Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted
|
|
||||||
# here to re-fresh the signals from _device. Unregister this callback
|
|
||||||
# when the entity is removed.
|
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(
|
self._device.add_update_listener(self.async_write_ha_state)
|
||||||
self.hass, SIGNAL_NEST_UPDATE, self.async_write_ha_state
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_camera_image(self):
|
async def async_camera_image(self):
|
||||||
|
|
|
@ -36,10 +36,9 @@ from homeassistant.components.climate.const import (
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE
|
from .const import DATA_SUBSCRIBER, DOMAIN
|
||||||
from .device_info import DeviceInfo
|
from .device_info import DeviceInfo
|
||||||
|
|
||||||
# Mapping for sdm.devices.traits.ThermostatMode mode field
|
# Mapping for sdm.devices.traits.ThermostatMode mode field
|
||||||
|
@ -126,16 +125,9 @@ class ThermostatEntity(ClimateEntity):
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Run when entity is added to register update signal handler."""
|
"""Run when entity is added to register update signal handler."""
|
||||||
# Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted
|
|
||||||
# here to re-fresh the signals from _device. Unregister this callback
|
|
||||||
# when the entity is removed.
|
|
||||||
self._supported_features = self._get_supported_features()
|
self._supported_features = self._get_supported_features()
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(
|
self._device.add_update_listener(self.async_write_ha_state)
|
||||||
self.hass,
|
|
||||||
SIGNAL_NEST_UPDATE,
|
|
||||||
self.async_write_ha_state,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/nest",
|
"documentation": "https://www.home-assistant.io/integrations/nest",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"python-nest==4.1.0",
|
"python-nest==4.1.0",
|
||||||
"google-nest-sdm==0.2.1"
|
"google-nest-sdm==0.2.5"
|
||||||
],
|
],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@awarecan",
|
"@awarecan",
|
||||||
|
|
|
@ -15,11 +15,10 @@ from homeassistant.const import (
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE
|
from .const import DATA_SUBSCRIBER, DOMAIN
|
||||||
from .device_info import DeviceInfo
|
from .device_info import DeviceInfo
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -80,15 +79,8 @@ class SensorBase(Entity):
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Run when entity is added to register update signal handler."""
|
"""Run when entity is added to register update signal handler."""
|
||||||
# Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted
|
|
||||||
# here to re-fresh the signals from _device. Unregister this callback
|
|
||||||
# when the entity is removed.
|
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(
|
self._device.add_update_listener(self.async_write_ha_state)
|
||||||
self.hass,
|
|
||||||
SIGNAL_NEST_UPDATE,
|
|
||||||
self.async_write_ha_state,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -681,7 +681,7 @@ google-cloud-pubsub==2.1.0
|
||||||
google-cloud-texttospeech==0.4.0
|
google-cloud-texttospeech==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==0.2.1
|
google-nest-sdm==0.2.5
|
||||||
|
|
||||||
# homeassistant.components.google_travel_time
|
# homeassistant.components.google_travel_time
|
||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
|
|
|
@ -352,7 +352,7 @@ google-api-python-client==1.6.4
|
||||||
google-cloud-pubsub==2.1.0
|
google-cloud-pubsub==2.1.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==0.2.1
|
google-nest-sdm==0.2.5
|
||||||
|
|
||||||
# homeassistant.components.gree
|
# homeassistant.components.gree
|
||||||
greeclimate==0.10.3
|
greeclimate==0.10.3
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
"""Common libraries for test setup."""
|
"""Common libraries for test setup."""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from typing import Awaitable, Callable
|
||||||
|
|
||||||
from google_nest_sdm.device_manager import DeviceManager
|
from google_nest_sdm.device_manager import DeviceManager
|
||||||
from google_nest_sdm.event import AsyncEventCallback, EventMessage
|
from google_nest_sdm.event import EventMessage
|
||||||
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
|
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
|
||||||
|
|
||||||
from homeassistant.components.nest import DOMAIN
|
from homeassistant.components.nest import DOMAIN
|
||||||
|
@ -59,9 +60,8 @@ class FakeSubscriber(GoogleNestSubscriber):
|
||||||
def __init__(self, device_manager: FakeDeviceManager):
|
def __init__(self, device_manager: FakeDeviceManager):
|
||||||
"""Initialize Fake Subscriber."""
|
"""Initialize Fake Subscriber."""
|
||||||
self._device_manager = device_manager
|
self._device_manager = device_manager
|
||||||
self._callback = None
|
|
||||||
|
|
||||||
def set_update_callback(self, callback: AsyncEventCallback):
|
def set_update_callback(self, callback: Callable[[EventMessage], Awaitable[None]]):
|
||||||
"""Capture the callback set by Home Assistant."""
|
"""Capture the callback set by Home Assistant."""
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ class FakeSubscriber(GoogleNestSubscriber):
|
||||||
"""Simulate a received pubsub message, invoked by tests."""
|
"""Simulate a received pubsub message, invoked by tests."""
|
||||||
# Update device state, then invoke HomeAssistant to refresh
|
# Update device state, then invoke HomeAssistant to refresh
|
||||||
await self._device_manager.async_handle_event(event_message)
|
await self._device_manager.async_handle_event(event_message)
|
||||||
await self._callback.async_handle_event(event_message)
|
await self._callback(event_message)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_sdm_platform(hass, platform, devices={}, structures={}):
|
async def async_setup_sdm_platform(hass, platform, devices={}, structures={}):
|
||||||
|
|
|
@ -7,7 +7,8 @@ import homeassistant.components.automation as automation
|
||||||
from homeassistant.components.device_automation.exceptions import (
|
from homeassistant.components.device_automation.exceptions import (
|
||||||
InvalidDeviceAutomationConfig,
|
InvalidDeviceAutomationConfig,
|
||||||
)
|
)
|
||||||
from homeassistant.components.nest import DOMAIN, NEST_EVENT
|
from homeassistant.components.nest import DOMAIN
|
||||||
|
from homeassistant.components.nest.events import NEST_EVENT
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .common import async_setup_sdm_platform
|
from .common import async_setup_sdm_platform
|
||||||
|
|
Loading…
Reference in New Issue