From af1023074ed60a5ce0cf6fce02e9fbdafdbb8610 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Apr 2024 15:30:03 -1000 Subject: [PATCH] Add an event_filter to google_assistant state reporting (#115160) * adjust * fix test since it happens sooner now * remove debug * remove unneeded test change * reduce * reduce --- .../google_assistant/report_state.py | 68 ++++++++++++------- .../google_assistant/test_report_state.py | 9 +-- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/google_assistant/report_state.py b/homeassistant/components/google_assistant/report_state.py index 26a341bd7b6..1230b9a272e 100644 --- a/homeassistant/components/google_assistant/report_state.py +++ b/homeassistant/components/google_assistant/report_state.py @@ -4,12 +4,19 @@ from __future__ import annotations from collections import deque import logging -from typing import Any +from typing import TYPE_CHECKING, Any from uuid import uuid4 -from homeassistant.const import MATCH_ALL -from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, State, callback -from homeassistant.helpers.event import async_call_later, async_track_state_change +from homeassistant.const import EVENT_STATE_CHANGED +from homeassistant.core import ( + CALLBACK_TYPE, + Event, + EventStateChangedData, + HassJob, + HomeAssistant, + callback, +) +from homeassistant.helpers.event import async_call_later from homeassistant.helpers.significant_change import create_checker from .const import DOMAIN @@ -31,7 +38,9 @@ _LOGGER = logging.getLogger(__name__) @callback -def async_enable_report_state(hass: HomeAssistant, google_config: AbstractConfig): +def async_enable_report_state( + hass: HomeAssistant, google_config: AbstractConfig +) -> CALLBACK_TYPE: """Enable state and notification reporting.""" checker = None unsub_pending: CALLBACK_TYPE | None = None @@ -60,33 +69,36 @@ def async_enable_report_state(hass: HomeAssistant, google_config: AbstractConfig report_states_job = HassJob(report_states) - async def async_entity_state_listener( - changed_entity: str, old_state: State | None, new_state: State | None - ) -> None: - nonlocal unsub_pending, checker - - if not hass.is_running: - return - - if not new_state: - return - - if not google_config.should_expose(new_state): - return - - if not ( - entity := async_get_google_entity_if_supported_cached( + @callback + def _async_entity_state_filter(data: EventStateChangedData) -> bool: + return bool( + hass.is_running + and (new_state := data["new_state"]) + and google_config.should_expose(new_state) + and async_get_google_entity_if_supported_cached( hass, google_config, new_state ) - ): - return + ) + + async def _async_entity_state_listener(event: Event[EventStateChangedData]) -> None: + """Handle state changes.""" + nonlocal unsub_pending, checker + data = event.data + new_state = data["new_state"] + if TYPE_CHECKING: + assert new_state is not None # verified in filter + entity = async_get_google_entity_if_supported_cached( + hass, google_config, new_state + ) + if TYPE_CHECKING: + assert entity is not None # verified in filter # We only trigger notifications on changes in the state value, not attributes. # This is mainly designed for our event entity types # We need to synchronize notifications using a `SYNC` response, # together with other state changes. if ( - old_state + (old_state := data["old_state"]) and old_state.state != new_state.state and (notifications := entity.notifications_serialize()) is not None ): @@ -106,6 +118,7 @@ def async_enable_report_state(hass: HomeAssistant, google_config: AbstractConfig result, ) + changed_entity = data["entity_id"] try: entity_data = entity.query_serialize() except SmartHomeError as err: @@ -173,7 +186,12 @@ def async_enable_report_state(hass: HomeAssistant, google_config: AbstractConfig await google_config.async_report_state_all({"devices": {"states": entities}}) - unsub = async_track_state_change(hass, MATCH_ALL, async_entity_state_listener) + unsub = hass.bus.async_listen( + EVENT_STATE_CHANGED, + _async_entity_state_listener, + event_filter=_async_entity_state_filter, + run_immediately=True, + ) unsub = async_call_later( hass, INITIAL_REPORT_DELAY, HassJob(initial_report, cancel_on_shutdown=True) diff --git a/tests/components/google_assistant/test_report_state.py b/tests/components/google_assistant/test_report_state.py index 758ebf63db9..9c8a0f951cc 100644 --- a/tests/components/google_assistant/test_report_state.py +++ b/tests/components/google_assistant/test_report_state.py @@ -216,6 +216,10 @@ async def test_report_notifications( hass, datetime.fromisoformat("2023-08-01T01:01:00+00:00") ) await hass.async_block_till_done() + for call in mock_report_state.mock_calls: + if "states" in call[1][0]["devices"]: + states = call[1][0]["devices"]["states"] + assert states["event.doorbell"] == {"online": True} # Test the notification request failed caplog.clear() @@ -233,12 +237,10 @@ async def test_report_notifications( hass, datetime.fromisoformat("2023-08-01T01:03:00+00:00") ) await hass.async_block_till_done() - assert len(mock_report_state.mock_calls) == 2 + assert len(mock_report_state.mock_calls) == 1 for call in mock_report_state.mock_calls: if "notifications" in call[1][0]["devices"]: notifications = call[1][0]["devices"]["notifications"] - elif "states" in call[1][0]["devices"]: - states = call[1][0]["devices"]["states"] assert notifications["event.doorbell"] == { "ObjectDetection": { "objects": {"unclassified": 1}, @@ -246,7 +248,6 @@ async def test_report_notifications( "detectionTimestamp": epoc_event_time * 1000, } } - assert states["event.doorbell"] == {"online": True} assert "Sending event notification for entity event.doorbell" in caplog.text assert ( "Unable to send notification with result code: 500, check log for more info"