Avoid starting a bluetooth poll when Home Assistant is stopping (#88819)

* Avoid starting a bluetooth poll when Home Assistant is stopping

* tests
This commit is contained in:
J. Nick Koston 2023-02-26 21:02:52 -06:00 committed by GitHub
parent f8934175cb
commit 1d1c553d9b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 2 deletions

View file

@ -106,6 +106,8 @@ class ActiveBluetoothDataUpdateCoordinator(
def needs_poll(self, service_info: BluetoothServiceInfoBleak) -> bool:
"""Return true if time to try and poll."""
if self.hass.is_stopping:
return False
poll_age: float | None = None
if self._last_poll:
poll_age = monotonic_time_coarse() - self._last_poll

View file

@ -99,6 +99,8 @@ class ActiveBluetoothProcessorCoordinator(
def needs_poll(self, service_info: BluetoothServiceInfoBleak) -> bool:
"""Return true if time to try and poll."""
if self.hass.is_stopping:
return False
poll_age: float | None = None
if self._last_poll:
poll_age = monotonic_time_coarse() - self._last_poll

View file

@ -19,7 +19,7 @@ from homeassistant.components.bluetooth.active_update_coordinator import (
_T,
ActiveBluetoothDataUpdateCoordinator,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import CoreState, HomeAssistant
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from homeassistant.setup import async_setup_component
@ -395,3 +395,58 @@ async def test_polling_rejecting_the_first_time(
cancel()
unregister_listener()
async def test_no_polling_after_stop_event(
hass: HomeAssistant,
mock_bleak_scanner_start: MagicMock,
mock_bluetooth_adapters: None,
) -> None:
"""Test we do not poll after the stop event."""
await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
needs_poll_calls = 0
def _needs_poll(
service_info: BluetoothServiceInfoBleak, seconds_since_last_poll: float | None
) -> bool:
nonlocal needs_poll_calls
needs_poll_calls += 1
return True
async def _poll_method(service_info: BluetoothServiceInfoBleak) -> dict[str, Any]:
return {"fake": "data"}
coordinator = MyCoordinator(
hass=hass,
logger=_LOGGER,
address="aa:bb:cc:dd:ee:ff",
mode=BluetoothScanningMode.ACTIVE,
needs_poll_method=_needs_poll,
poll_method=_poll_method,
)
assert coordinator.available is False # no data yet
mock_listener = MagicMock()
unregister_listener = coordinator.async_add_listener(mock_listener)
cancel = coordinator.async_start()
assert needs_poll_calls == 0
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi}
assert coordinator.data == {"fake": "data"}
assert needs_poll_calls == 1
hass.state = CoreState.stopping
await hass.async_block_till_done()
assert needs_poll_calls == 1
# Should not generate a poll now
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2)
await hass.async_block_till_done()
assert needs_poll_calls == 1
cancel()
unregister_listener()

View file

@ -16,7 +16,7 @@ from homeassistant.components.bluetooth import (
from homeassistant.components.bluetooth.active_update_processor import (
ActiveBluetoothProcessorCoordinator,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import CoreState, HomeAssistant
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from homeassistant.setup import async_setup_component
@ -384,3 +384,65 @@ async def test_rate_limit(
assert async_handle_update.mock_calls[-1] == call({"testdata": 1})
cancel()
async def test_no_polling_after_stop_event(
hass: HomeAssistant,
mock_bleak_scanner_start: MagicMock,
mock_bluetooth_adapters: None,
) -> None:
"""Test we do not poll after the stop event."""
await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
needs_poll_calls = 0
def _update_method(service_info: BluetoothServiceInfoBleak):
return {"testdata": 0}
def _poll_needed(*args, **kwargs):
nonlocal needs_poll_calls
needs_poll_calls += 1
return True
async def _poll(*args, **kwargs):
return {"testdata": 1}
coordinator = ActiveBluetoothProcessorCoordinator(
hass,
_LOGGER,
address="aa:bb:cc:dd:ee:ff",
mode=BluetoothScanningMode.ACTIVE,
update_method=_update_method,
needs_poll_method=_poll_needed,
poll_method=_poll,
)
assert coordinator.available is False # no data yet
processor = MagicMock()
coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update
cancel = coordinator.async_start()
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert needs_poll_calls == 1
assert coordinator.available is True
# async_handle_update should have been called twice
# The first time, it was passed the data from parsing the advertisement
# The second time, it was passed the data from polling
assert len(async_handle_update.mock_calls) == 2
assert async_handle_update.mock_calls[0] == call({"testdata": 0})
assert async_handle_update.mock_calls[1] == call({"testdata": 1})
hass.state = CoreState.stopping
await hass.async_block_till_done()
assert needs_poll_calls == 1
# Should not generate a poll now that CoreState is stopping
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2)
await hass.async_block_till_done()
assert needs_poll_calls == 1
cancel()