Ensure togrill detects disconnected devices (#153067)

This commit is contained in:
Joakim Plate
2025-09-28 15:34:56 +02:00
committed by Franck Nijhof
parent ef16327b2b
commit 9a969cea63
3 changed files with 94 additions and 8 deletions

View File

@@ -139,7 +139,11 @@ class ToGrillCoordinator(DataUpdateCoordinator[dict[tuple[int, int | None], Pack
raise DeviceNotFound("Unable to find device")
try:
client = await Client.connect(device, self._notify_callback)
client = await Client.connect(
device,
self._notify_callback,
disconnected_callback=self._disconnected_callback,
)
except BleakError as exc:
self.logger.debug("Connection failed", exc_info=True)
raise DeviceNotFound("Unable to connect to device") from exc
@@ -169,9 +173,6 @@ class ToGrillCoordinator(DataUpdateCoordinator[dict[tuple[int, int | None], Pack
self.client = None
async def _get_connected_client(self) -> Client:
if self.client and not self.client.is_connected:
await self.client.disconnect()
self.client = None
if self.client:
return self.client
@@ -196,6 +197,12 @@ class ToGrillCoordinator(DataUpdateCoordinator[dict[tuple[int, int | None], Pack
async def _async_update_data(self) -> dict[tuple[int, int | None], Packet]:
"""Poll the device."""
if self.client and not self.client.is_connected:
await self.client.disconnect()
self.client = None
self._async_request_refresh_soon()
raise DeviceFailed("Device was disconnected")
client = await self._get_connected_client()
try:
await client.request(PacketA0Notify)
@@ -206,6 +213,17 @@ class ToGrillCoordinator(DataUpdateCoordinator[dict[tuple[int, int | None], Pack
raise DeviceFailed(f"Device failed {exc}") from exc
return self.data
@callback
def _async_request_refresh_soon(self) -> None:
self.config_entry.async_create_task(
self.hass, self.async_request_refresh(), eager_start=False
)
@callback
def _disconnected_callback(self) -> None:
"""Handle Bluetooth device being disconnected."""
self._async_request_refresh_soon()
@callback
def _async_handle_bluetooth_event(
self,
@@ -213,5 +231,5 @@ class ToGrillCoordinator(DataUpdateCoordinator[dict[tuple[int, int | None], Pack
change: BluetoothChange,
) -> None:
"""Handle a Bluetooth event."""
if not self.client and isinstance(self.last_exception, DeviceNotFound):
self.hass.async_create_task(self.async_refresh())
if isinstance(self.last_exception, DeviceNotFound):
self._async_request_refresh_soon()

View File

@@ -57,9 +57,18 @@ def mock_client(enable_bluetooth: None, mock_client_class: Mock) -> Generator[Mo
client_object.mocked_notify = None
async def _connect(
address: str, callback: Callable[[Packet], None] | None = None
address: str,
callback: Callable[[Packet], None] | None = None,
disconnected_callback: Callable[[], None] | None = None,
) -> Mock:
client_object.mocked_notify = callback
if disconnected_callback:
def _disconnected_callback():
client_object.is_connected = False
disconnected_callback()
client_object.mocked_disconnected_callback = _disconnected_callback
return client_object
async def _disconnect() -> None:

View File

@@ -1,7 +1,8 @@
"""Test sensors for ToGrill integration."""
from unittest.mock import Mock
from unittest.mock import Mock, patch
from habluetooth import BluetoothServiceInfoBleak
import pytest
from syrupy.assertion import SnapshotAssertion
from togrill_bluetooth.packets import PacketA0Notify, PacketA1Notify
@@ -16,6 +17,16 @@ from tests.common import MockConfigEntry, snapshot_platform
from tests.components.bluetooth import inject_bluetooth_service_info
def patch_async_ble_device_from_address(
return_value: BluetoothServiceInfoBleak | None = None,
):
"""Patch async_ble_device_from_address to return a mocked BluetoothServiceInfoBleak."""
return patch(
"homeassistant.components.bluetooth.async_ble_device_from_address",
return_value=return_value,
)
@pytest.mark.parametrize(
"packets",
[
@@ -57,3 +68,51 @@ async def test_setup(
mock_client.mocked_notify(packet)
await snapshot_platform(hass, entity_registry, snapshot, mock_entry.entry_id)
async def test_device_disconnected(
hass: HomeAssistant,
mock_entry: MockConfigEntry,
mock_client: Mock,
) -> None:
"""Test the switch set."""
inject_bluetooth_service_info(hass, TOGRILL_SERVICE_INFO)
await setup_entry(hass, mock_entry, [Platform.SENSOR])
entity_id = "sensor.pro_05_battery"
state = hass.states.get(entity_id)
assert state
assert state.state == "0"
with patch_async_ble_device_from_address():
mock_client.mocked_disconnected_callback()
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "unavailable"
async def test_device_discovered(
hass: HomeAssistant,
mock_entry: MockConfigEntry,
mock_client: Mock,
) -> None:
"""Test the switch set."""
await setup_entry(hass, mock_entry, [Platform.SENSOR])
entity_id = "sensor.pro_05_battery"
state = hass.states.get(entity_id)
assert state
assert state.state == "unavailable"
inject_bluetooth_service_info(hass, TOGRILL_SERVICE_INFO)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "0"