Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Shelly BLE errors during connect and disconnect #119174

Merged
merged 1 commit into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
thecode marked this conversation as resolved.
Show resolved Hide resolved

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