Skip to content

Commit

Permalink
Add an event filter to the alexa state report state change listener (h…
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco and jbouwh authored Apr 7, 2024
1 parent f617000 commit 5630b36
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 37 deletions.
49 changes: 34 additions & 15 deletions homeassistant/components/alexa/state_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@
import aiohttp

from homeassistant.components import event
from homeassistant.const import MATCH_ALL, STATE_ON
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, State, callback
from homeassistant.const import EVENT_STATE_CHANGED, STATE_ON
from homeassistant.core import (
CALLBACK_TYPE,
Event,
EventStateChangedData,
HomeAssistant,
State,
callback,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.significant_change import create_checker
import homeassistant.util.dt as dt_util
from homeassistant.util.json import JsonObjectType, json_loads_object
Expand Down Expand Up @@ -265,28 +271,35 @@ def extra_significant_check(

checker = await create_checker(hass, DOMAIN, extra_significant_check)

async def async_entity_state_listener(
changed_entity: str,
old_state: State | None,
new_state: State | None,
) -> None:
@callback
def _async_entity_state_filter(data: EventStateChangedData) -> bool:
if not hass.is_running:
return
return False

if not new_state:
return
if not (new_state := data["new_state"]):
return False

if new_state.domain not in ENTITY_ADAPTERS:
return
return False

changed_entity = data["entity_id"]
if not smart_home_config.should_expose(changed_entity):
_LOGGER.debug("Not exposing %s because filtered by config", changed_entity)
return
return False

return True

async def _async_entity_state_listener(
event_: Event[EventStateChangedData],
) -> None:
data = event_.data
new_state = data["new_state"]
if TYPE_CHECKING:
assert new_state is not None

alexa_changed_entity: AlexaEntity = ENTITY_ADAPTERS[new_state.domain](
hass, smart_home_config, new_state
)

# Determine how entity should be reported on
should_report = False
should_doorbell = False
Expand All @@ -303,6 +316,7 @@ async def async_entity_state_listener(
return

if should_doorbell:
old_state = data["old_state"]
if (
new_state.domain == event.DOMAIN
or new_state.state == STATE_ON
Expand All @@ -324,7 +338,12 @@ async def async_entity_state_listener(
hass, smart_home_config, alexa_changed_entity, alexa_properties
)

return async_track_state_change(hass, MATCH_ALL, async_entity_state_listener)
return hass.bus.async_listen(
EVENT_STATE_CHANGED,
_async_entity_state_listener,
event_filter=_async_entity_state_filter,
run_immediately=True,
)


async def async_send_changereport_message(
Expand Down
44 changes: 22 additions & 22 deletions tests/components/alexa/test_state_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,14 @@ async def test_report_state_unsets_authorized_on_error(
config = get_default_config(hass)
await state_report.async_enable_proactive_mode(hass, config)

config._store.set_authorized.assert_not_called()

hass.states.async_set(
"binary_sensor.test_contact",
"off",
{"friendly_name": "Test Contact Sensor", "device_class": "door"},
)

config._store.set_authorized.assert_not_called()

# To trigger event listener
await hass.async_block_till_done()
config._store.set_authorized.assert_called_once_with(False)
Expand All @@ -215,15 +215,15 @@ async def test_report_state_unsets_authorized_on_access_token_error(

await state_report.async_enable_proactive_mode(hass, config)

hass.states.async_set(
"binary_sensor.test_contact",
"off",
{"friendly_name": "Test Contact Sensor", "device_class": "door"},
)

config._store.set_authorized.assert_not_called()

with patch.object(config, "async_get_access_token", AsyncMock(side_effect=exc)):
hass.states.async_set(
"binary_sensor.test_contact",
"off",
{"friendly_name": "Test Contact Sensor", "device_class": "door"},
)

# To trigger event listener
await hass.async_block_till_done()
config._store.set_authorized.assert_called_once_with(False)
Expand Down Expand Up @@ -731,39 +731,39 @@ async def test_proactive_mode_filter_states(
assert len(aioclient_mock.mock_calls) == 0

# hass not running should not report
current_state = hass.state
hass.set_state(core.CoreState.stopping)
await hass.async_block_till_done()
await hass.async_block_till_done()
hass.states.async_set(
"binary_sensor.test_contact",
"off",
{"friendly_name": "Test Contact Sensor", "device_class": "door"},
)

current_state = hass.state
hass.set_state(core.CoreState.stopping)
await hass.async_block_till_done()
await hass.async_block_till_done()
hass.set_state(current_state)
assert len(aioclient_mock.mock_calls) == 0

# unsupported entity should not report
hass.states.async_set(
"binary_sensor.test_contact",
"on",
{"friendly_name": "Test Contact Sensor", "device_class": "door"},
)
with patch.dict(
"homeassistant.components.alexa.state_report.ENTITY_ADAPTERS", {}, clear=True
):
hass.states.async_set(
"binary_sensor.test_contact",
"on",
{"friendly_name": "Test Contact Sensor", "device_class": "door"},
)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert len(aioclient_mock.mock_calls) == 0

# Not exposed by config should not report
hass.states.async_set(
"binary_sensor.test_contact",
"off",
{"friendly_name": "Test Contact Sensor", "device_class": "door"},
)
with patch.object(config, "should_expose", return_value=False):
hass.states.async_set(
"binary_sensor.test_contact",
"off",
{"friendly_name": "Test Contact Sensor", "device_class": "door"},
)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert len(aioclient_mock.mock_calls) == 0
Expand Down

0 comments on commit 5630b36

Please sign in to comment.