mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Fix startup race in BLE integrations (#75780)
This commit is contained in:
parent
157f7292d7
commit
1e85ddabfd
@ -42,17 +42,6 @@ class PassiveBluetoothDataUpdateCoordinator(BasePassiveBluetoothCoordinator):
|
||||
super()._async_handle_unavailable(address)
|
||||
self.async_update_listeners()
|
||||
|
||||
@callback
|
||||
def async_start(self) -> CALLBACK_TYPE:
|
||||
"""Start the data updater."""
|
||||
self._async_start()
|
||||
|
||||
@callback
|
||||
def _async_cancel() -> None:
|
||||
self._async_stop()
|
||||
|
||||
return _async_cancel
|
||||
|
||||
@callback
|
||||
def async_add_listener(
|
||||
self, update_callback: CALLBACK_TYPE, context: Any = None
|
||||
|
@ -78,21 +78,10 @@ class PassiveBluetoothProcessorCoordinator(BasePassiveBluetoothCoordinator):
|
||||
def remove_processor() -> None:
|
||||
"""Remove a processor."""
|
||||
self._processors.remove(processor)
|
||||
self._async_handle_processors_changed()
|
||||
|
||||
self._processors.append(processor)
|
||||
self._async_handle_processors_changed()
|
||||
return remove_processor
|
||||
|
||||
@callback
|
||||
def _async_handle_processors_changed(self) -> None:
|
||||
"""Handle processors changed."""
|
||||
running = bool(self._cancel_bluetooth_advertisements)
|
||||
if running and not self._processors:
|
||||
self._async_stop()
|
||||
elif not running and self._processors:
|
||||
self._async_start()
|
||||
|
||||
@callback
|
||||
def _async_handle_unavailable(self, address: str) -> None:
|
||||
"""Handle the device going unavailable."""
|
||||
|
@ -38,6 +38,17 @@ class BasePassiveBluetoothCoordinator:
|
||||
self._present = False
|
||||
self.last_seen = 0.0
|
||||
|
||||
@callback
|
||||
def async_start(self) -> CALLBACK_TYPE:
|
||||
"""Start the data updater."""
|
||||
self._async_start()
|
||||
|
||||
@callback
|
||||
def _async_cancel() -> None:
|
||||
self._async_stop()
|
||||
|
||||
return _async_cancel
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if the device is available."""
|
||||
|
@ -21,7 +21,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Govee BLE device from a config entry."""
|
||||
address = entry.unique_id
|
||||
assert address is not None
|
||||
hass.data.setdefault(DOMAIN, {})[
|
||||
coordinator = hass.data.setdefault(DOMAIN, {})[
|
||||
entry.entry_id
|
||||
] = PassiveBluetoothProcessorCoordinator(
|
||||
hass,
|
||||
@ -29,6 +29,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
address=address,
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(
|
||||
coordinator.async_start()
|
||||
) # only start after all platforms have had a chance to subscribe
|
||||
return True
|
||||
|
||||
|
||||
|
@ -135,12 +135,12 @@ async def async_setup_entry(
|
||||
data.update(service_info)
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
entry.async_on_unload(
|
||||
processor.async_add_entities_listener(
|
||||
GoveeBluetoothSensorEntity, async_add_entities
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
|
||||
|
||||
class GoveeBluetoothSensorEntity(
|
||||
|
@ -21,7 +21,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up INKBIRD BLE device from a config entry."""
|
||||
address = entry.unique_id
|
||||
assert address is not None
|
||||
hass.data.setdefault(DOMAIN, {})[
|
||||
coordinator = hass.data.setdefault(DOMAIN, {})[
|
||||
entry.entry_id
|
||||
] = PassiveBluetoothProcessorCoordinator(
|
||||
hass,
|
||||
@ -29,6 +29,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
address=address,
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(
|
||||
coordinator.async_start()
|
||||
) # only start after all platforms have had a chance to subscribe
|
||||
return True
|
||||
|
||||
|
||||
|
@ -135,12 +135,12 @@ async def async_setup_entry(
|
||||
data.update(service_info)
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
entry.async_on_unload(
|
||||
processor.async_add_entities_listener(
|
||||
INKBIRDBluetoothSensorEntity, async_add_entities
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
|
||||
|
||||
class INKBIRDBluetoothSensorEntity(
|
||||
|
@ -21,7 +21,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Moat BLE device from a config entry."""
|
||||
address = entry.unique_id
|
||||
assert address is not None
|
||||
hass.data.setdefault(DOMAIN, {})[
|
||||
coordinator = hass.data.setdefault(DOMAIN, {})[
|
||||
entry.entry_id
|
||||
] = PassiveBluetoothProcessorCoordinator(
|
||||
hass,
|
||||
@ -29,6 +29,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
address=address,
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(
|
||||
coordinator.async_start()
|
||||
) # only start after all platforms have had a chance to subscribe
|
||||
return True
|
||||
|
||||
|
||||
|
@ -142,12 +142,12 @@ async def async_setup_entry(
|
||||
data.update(service_info)
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
entry.async_on_unload(
|
||||
processor.async_add_entities_listener(
|
||||
MoatBluetoothSensorEntity, async_add_entities
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
|
||||
|
||||
class MoatBluetoothSensorEntity(
|
||||
|
@ -21,7 +21,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up SensorPush BLE device from a config entry."""
|
||||
address = entry.unique_id
|
||||
assert address is not None
|
||||
hass.data.setdefault(DOMAIN, {})[
|
||||
coordinator = hass.data.setdefault(DOMAIN, {})[
|
||||
entry.entry_id
|
||||
] = PassiveBluetoothProcessorCoordinator(
|
||||
hass,
|
||||
@ -29,6 +29,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
address=address,
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(
|
||||
coordinator.async_start()
|
||||
) # only start after all platforms have had a chance to subscribe
|
||||
return True
|
||||
|
||||
|
||||
|
@ -136,12 +136,12 @@ async def async_setup_entry(
|
||||
data.update(service_info)
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
entry.async_on_unload(
|
||||
processor.async_add_entities_listener(
|
||||
SensorPushBluetoothSensorEntity, async_add_entities
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
|
||||
|
||||
class SensorPushBluetoothSensorEntity(
|
||||
|
@ -21,7 +21,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Xiaomi BLE device from a config entry."""
|
||||
address = entry.unique_id
|
||||
assert address is not None
|
||||
hass.data.setdefault(DOMAIN, {})[
|
||||
coordinator = hass.data.setdefault(DOMAIN, {})[
|
||||
entry.entry_id
|
||||
] = PassiveBluetoothProcessorCoordinator(
|
||||
hass,
|
||||
@ -29,6 +29,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
address=address,
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(
|
||||
coordinator.async_start()
|
||||
) # only start after all platforms have had a chance to subscribe
|
||||
return True
|
||||
|
||||
|
||||
|
@ -167,12 +167,12 @@ async def async_setup_entry(
|
||||
data.update(service_info)
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
entry.async_on_unload(
|
||||
processor.async_add_entities_listener(
|
||||
XiaomiBluetoothSensorEntity, async_add_entities
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(coordinator.async_register_processor(processor))
|
||||
|
||||
|
||||
class XiaomiBluetoothSensorEntity(
|
||||
|
@ -107,6 +107,7 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
|
||||
_async_register_callback,
|
||||
):
|
||||
unregister_processor = coordinator.async_register_processor(processor)
|
||||
cancel_coordinator = coordinator.async_start()
|
||||
|
||||
entity_key = PassiveBluetoothEntityKey("temperature", None)
|
||||
entity_key_events = []
|
||||
@ -171,6 +172,7 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
|
||||
assert coordinator.available is True
|
||||
|
||||
unregister_processor()
|
||||
cancel_coordinator()
|
||||
|
||||
|
||||
async def test_unavailable_after_no_data(hass, mock_bleak_scanner_start):
|
||||
@ -206,6 +208,7 @@ async def test_unavailable_after_no_data(hass, mock_bleak_scanner_start):
|
||||
_async_register_callback,
|
||||
):
|
||||
unregister_processor = coordinator.async_register_processor(processor)
|
||||
cancel_coordinator = coordinator.async_start()
|
||||
|
||||
mock_entity = MagicMock()
|
||||
mock_add_entities = MagicMock()
|
||||
@ -259,6 +262,7 @@ async def test_unavailable_after_no_data(hass, mock_bleak_scanner_start):
|
||||
assert processor.available is False
|
||||
|
||||
unregister_processor()
|
||||
cancel_coordinator()
|
||||
|
||||
|
||||
async def test_no_updates_once_stopping(hass, mock_bleak_scanner_start):
|
||||
@ -290,6 +294,7 @@ async def test_no_updates_once_stopping(hass, mock_bleak_scanner_start):
|
||||
_async_register_callback,
|
||||
):
|
||||
unregister_processor = coordinator.async_register_processor(processor)
|
||||
cancel_coordinator = coordinator.async_start()
|
||||
|
||||
all_events = []
|
||||
|
||||
@ -310,6 +315,7 @@ async def test_no_updates_once_stopping(hass, mock_bleak_scanner_start):
|
||||
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
|
||||
assert len(all_events) == 1
|
||||
unregister_processor()
|
||||
cancel_coordinator()
|
||||
|
||||
|
||||
async def test_exception_from_update_method(hass, caplog, mock_bleak_scanner_start):
|
||||
@ -346,6 +352,7 @@ async def test_exception_from_update_method(hass, caplog, mock_bleak_scanner_sta
|
||||
_async_register_callback,
|
||||
):
|
||||
unregister_processor = coordinator.async_register_processor(processor)
|
||||
cancel_coordinator = coordinator.async_start()
|
||||
|
||||
processor.async_add_listener(MagicMock())
|
||||
|
||||
@ -361,6 +368,7 @@ async def test_exception_from_update_method(hass, caplog, mock_bleak_scanner_sta
|
||||
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
|
||||
assert processor.available is True
|
||||
unregister_processor()
|
||||
cancel_coordinator()
|
||||
|
||||
|
||||
async def test_bad_data_from_update_method(hass, mock_bleak_scanner_start):
|
||||
@ -397,6 +405,7 @@ async def test_bad_data_from_update_method(hass, mock_bleak_scanner_start):
|
||||
_async_register_callback,
|
||||
):
|
||||
unregister_processor = coordinator.async_register_processor(processor)
|
||||
cancel_coordinator = coordinator.async_start()
|
||||
|
||||
processor.async_add_listener(MagicMock())
|
||||
|
||||
@ -413,6 +422,7 @@ async def test_bad_data_from_update_method(hass, mock_bleak_scanner_start):
|
||||
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
|
||||
assert processor.available is True
|
||||
unregister_processor()
|
||||
cancel_coordinator()
|
||||
|
||||
|
||||
GOVEE_B5178_REMOTE_SERVICE_INFO = BluetoothServiceInfo(
|
||||
@ -737,6 +747,7 @@ async def test_integration_with_entity(hass, mock_bleak_scanner_start):
|
||||
_async_register_callback,
|
||||
):
|
||||
coordinator.async_register_processor(processor)
|
||||
cancel_coordinator = coordinator.async_start()
|
||||
|
||||
processor.async_add_listener(MagicMock())
|
||||
|
||||
@ -781,6 +792,7 @@ async def test_integration_with_entity(hass, mock_bleak_scanner_start):
|
||||
assert entity_one.entity_key == PassiveBluetoothEntityKey(
|
||||
key="temperature", device_id="remote"
|
||||
)
|
||||
cancel_coordinator()
|
||||
|
||||
|
||||
NO_DEVICES_BLUETOOTH_SERVICE_INFO = BluetoothServiceInfo(
|
||||
@ -845,6 +857,7 @@ async def test_integration_with_entity_without_a_device(hass, mock_bleak_scanner
|
||||
_async_register_callback,
|
||||
):
|
||||
coordinator.async_register_processor(processor)
|
||||
cancel_coordinator = coordinator.async_start()
|
||||
|
||||
mock_add_entities = MagicMock()
|
||||
|
||||
@ -873,6 +886,7 @@ async def test_integration_with_entity_without_a_device(hass, mock_bleak_scanner
|
||||
assert entity_one.entity_key == PassiveBluetoothEntityKey(
|
||||
key="temperature", device_id=None
|
||||
)
|
||||
cancel_coordinator()
|
||||
|
||||
|
||||
async def test_passive_bluetooth_entity_with_entity_platform(
|
||||
@ -907,6 +921,7 @@ async def test_passive_bluetooth_entity_with_entity_platform(
|
||||
_async_register_callback,
|
||||
):
|
||||
coordinator.async_register_processor(processor)
|
||||
cancel_coordinator = coordinator.async_start()
|
||||
|
||||
processor.async_add_entities_listener(
|
||||
PassiveBluetoothProcessorEntity,
|
||||
@ -926,6 +941,7 @@ async def test_passive_bluetooth_entity_with_entity_platform(
|
||||
hass.states.get("test_domain.test_platform_aa_bb_cc_dd_ee_ff_pressure")
|
||||
is not None
|
||||
)
|
||||
cancel_coordinator()
|
||||
|
||||
|
||||
SENSOR_PASSIVE_BLUETOOTH_DATA_UPDATE = PassiveBluetoothDataUpdate(
|
||||
@ -999,6 +1015,7 @@ async def test_integration_multiple_entity_platforms(hass, mock_bleak_scanner_st
|
||||
):
|
||||
coordinator.async_register_processor(binary_sensor_processor)
|
||||
coordinator.async_register_processor(sesnor_processor)
|
||||
cancel_coordinator = coordinator.async_start()
|
||||
|
||||
binary_sensor_processor.async_add_listener(MagicMock())
|
||||
sesnor_processor.async_add_listener(MagicMock())
|
||||
@ -1056,3 +1073,4 @@ async def test_integration_multiple_entity_platforms(hass, mock_bleak_scanner_st
|
||||
assert binary_sensor_entity_one.entity_key == PassiveBluetoothEntityKey(
|
||||
key="motion", device_id=None
|
||||
)
|
||||
cancel_coordinator()
|
||||
|
Loading…
x
Reference in New Issue
Block a user