Handle Shelly BLE errors during connect and disconnect (#119174)

This commit is contained in:
Shay Levy 2024-06-09 12:59:40 +03:00 committed by GitHub
parent b937fc0cfe
commit 04222c32b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 64 additions and 10 deletions

View file

@ -2,7 +2,6 @@
from __future__ import annotations
import contextlib
from typing import Final
from aioshelly.block_device import BlockDevice
@ -301,13 +300,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ShellyConfigEntry) -> b
entry, platforms
):
if shelly_entry_data.rpc:
with contextlib.suppress(DeviceConnectionError):
# If the device is restarting or has gone offline before
# the ping/pong timeout happens, the shutdown command
# will fail, but we don't care since we are unloading
# and if we setup again, we will fix anything that is
# in an inconsistent state at that time.
await shelly_entry_data.rpc.shutdown()
await shelly_entry_data.rpc.shutdown()
return unload_ok

View file

@ -627,7 +627,13 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
if self.connected: # Already connected
return
self.connected = True
await self._async_run_connected_events()
try:
await self._async_run_connected_events()
except DeviceConnectionError as err:
LOGGER.error(
"Error running connected events for device %s: %s", self.name, err
)
self.last_update_success = False
async def _async_run_connected_events(self) -> None:
"""Run connected events.
@ -701,10 +707,18 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
if self.device.connected:
try:
await async_stop_scanner(self.device)
await super().shutdown()
except InvalidAuthError:
self.entry.async_start_reauth(self.hass)
return
await super().shutdown()
except DeviceConnectionError as err:
# If the device is restarting or has gone offline before
# the ping/pong timeout happens, the shutdown command
# will fail, but we don't care since we are unloading
# and if we setup again, we will fix anything that is
# in an inconsistent state at that time.
LOGGER.debug("Error during shutdown for device %s: %s", self.name, err)
return
await self._async_disconnected(False)

View file

@ -15,12 +15,14 @@ from homeassistant.components.shelly.const import (
ATTR_CLICK_TYPE,
ATTR_DEVICE,
ATTR_GENERATION,
CONF_BLE_SCANNER_MODE,
DOMAIN,
ENTRY_RELOAD_COOLDOWN,
MAX_PUSH_UPDATE_FAILURES,
RPC_RECONNECT_INTERVAL,
SLEEP_PERIOD_MULTIPLIER,
UPDATE_PERIOD_MULTIPLIER,
BLEScannerMode,
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import ATTR_DEVICE_ID, STATE_ON, STATE_UNAVAILABLE
@ -485,6 +487,25 @@ async def test_rpc_reload_with_invalid_auth(
assert flow["context"].get("entry_id") == entry.entry_id
async def test_rpc_connection_error_during_unload(
hass: HomeAssistant, mock_rpc_device: Mock, caplog: pytest.LogCaptureFixture
) -> None:
"""Test RPC DeviceConnectionError suppressed during config entry unload."""
entry = await init_integration(hass, 2)
assert entry.state is ConfigEntryState.LOADED
with patch(
"homeassistant.components.shelly.coordinator.async_stop_scanner",
side_effect=DeviceConnectionError,
):
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert "Error during shutdown for device" in caplog.text
assert entry.state is ConfigEntryState.NOT_LOADED
async def test_rpc_click_event(
hass: HomeAssistant,
mock_rpc_device: Mock,
@ -713,6 +734,32 @@ async def test_rpc_reconnect_error(
assert get_entity_state(hass, "switch.test_switch_0") == STATE_UNAVAILABLE
async def test_rpc_error_running_connected_events(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_rpc_device: Mock,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test RPC error while running connected events."""
with patch(
"homeassistant.components.shelly.coordinator.async_ensure_ble_enabled",
side_effect=DeviceConnectionError,
):
await init_integration(
hass, 2, options={CONF_BLE_SCANNER_MODE: BLEScannerMode.ACTIVE}
)
assert "Error running connected events for device" in caplog.text
assert get_entity_state(hass, "switch.test_switch_0") == STATE_UNAVAILABLE
# Move time to generate reconnect without error
freezer.tick(timedelta(seconds=RPC_RECONNECT_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert get_entity_state(hass, "switch.test_switch_0") == STATE_ON
async def test_rpc_polling_connection_error(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,