Skip to content

Commit

Permalink
Handle Shelly BLE errors during connect and disconnect (#119174)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecode authored Jun 9, 2024
1 parent b937fc0 commit 04222c3
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 10 deletions.
9 changes: 1 addition & 8 deletions homeassistant/components/shelly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import contextlib
from typing import Final

from aioshelly.block_device import BlockDevice
Expand Down Expand Up @@ -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

Expand Down
18 changes: 16 additions & 2 deletions homeassistant/components/shelly/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,13 @@ async def _async_connected(self) -> None:
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.
Expand Down Expand Up @@ -701,10 +707,18 @@ async def shutdown(self) -> None:
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)


Expand Down
47 changes: 47 additions & 0 deletions tests/components/shelly/test_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 04222c3

Please sign in to comment.