mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 01:37:08 +00:00
Centralize exception handling in Plugwise (#82694)
This commit is contained in:
parent
fd3e996a1e
commit
13458dc722
@ -12,11 +12,15 @@ from plugwise.exceptions import (
|
||||
UnsupportedDeviceError,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER
|
||||
from .const import DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DEFAULT_USERNAME, DOMAIN, LOGGER
|
||||
|
||||
|
||||
class PlugwiseData(NamedTuple):
|
||||
@ -29,15 +33,15 @@ class PlugwiseData(NamedTuple):
|
||||
class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[PlugwiseData]):
|
||||
"""Class to manage fetching Plugwise data from single endpoint."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api: Smile) -> None:
|
||||
_connected: bool = False
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=api.smile_name or DOMAIN,
|
||||
update_interval=DEFAULT_SCAN_INTERVAL.get(
|
||||
str(api.smile_type), timedelta(seconds=60)
|
||||
),
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=60),
|
||||
# Don't refresh immediately, give the device time to process
|
||||
# the change in state before we query it.
|
||||
request_refresh_debouncer=Debouncer(
|
||||
@ -47,22 +51,41 @@ class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[PlugwiseData]):
|
||||
immediate=False,
|
||||
),
|
||||
)
|
||||
self.api = api
|
||||
|
||||
self.api = Smile(
|
||||
host=entry.data[CONF_HOST],
|
||||
username=entry.data.get(CONF_USERNAME, DEFAULT_USERNAME),
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
port=entry.data.get(CONF_PORT, DEFAULT_PORT),
|
||||
timeout=30,
|
||||
websession=async_get_clientsession(hass, verify_ssl=False),
|
||||
)
|
||||
|
||||
async def _connect(self) -> None:
|
||||
"""Connect to the Plugwise Smile."""
|
||||
self._connected = await self.api.connect()
|
||||
self.api.get_all_devices()
|
||||
self.name = self.api.smile_name
|
||||
self.update_interval = DEFAULT_SCAN_INTERVAL.get(
|
||||
str(self.api.smile_type), timedelta(seconds=60)
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> PlugwiseData:
|
||||
"""Fetch data from Plugwise."""
|
||||
try:
|
||||
if not self._connected:
|
||||
await self._connect()
|
||||
data = await self.api.async_update()
|
||||
except InvalidAuthentication as err:
|
||||
raise UpdateFailed("Authentication failed") from err
|
||||
raise ConfigEntryError("Invalid username or Smile ID") from err
|
||||
except (InvalidXMLError, ResponseError) as err:
|
||||
raise UpdateFailed(
|
||||
"Invalid XML data, or error indication received for the Plugwise Adam/Smile/Stretch"
|
||||
) from err
|
||||
except UnsupportedDeviceError as err:
|
||||
raise UpdateFailed("Device with unsupported firmware") from err
|
||||
raise ConfigEntryError("Device with unsupported firmware") from err
|
||||
except ConnectionFailedError as err:
|
||||
raise UpdateFailed("Failed to connect") from err
|
||||
raise UpdateFailed("Failed to connect to the Plugwise Smile") from err
|
||||
return PlugwiseData(
|
||||
gateway=cast(GatewayData, data[0]),
|
||||
devices=cast(dict[str, DeviceData], data[1]),
|
||||
|
@ -3,30 +3,11 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from plugwise.exceptions import (
|
||||
ConnectionFailedError,
|
||||
InvalidAuthentication,
|
||||
InvalidXMLError,
|
||||
ResponseError,
|
||||
UnsupportedDeviceError,
|
||||
)
|
||||
from plugwise.smile import Smile
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import (
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_USERNAME,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
PLATFORMS_GATEWAY,
|
||||
Platform,
|
||||
)
|
||||
from .const import DOMAIN, LOGGER, PLATFORMS_GATEWAY, Platform
|
||||
from .coordinator import PlugwiseDataUpdateCoordinator
|
||||
|
||||
|
||||
@ -34,37 +15,7 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Plugwise Smiles from a config entry."""
|
||||
await er.async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry)
|
||||
|
||||
websession = async_get_clientsession(hass, verify_ssl=False)
|
||||
api = Smile(
|
||||
host=entry.data[CONF_HOST],
|
||||
username=entry.data.get(CONF_USERNAME, DEFAULT_USERNAME),
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
port=entry.data.get(CONF_PORT, DEFAULT_PORT),
|
||||
timeout=30,
|
||||
websession=websession,
|
||||
)
|
||||
|
||||
try:
|
||||
connected = await api.connect()
|
||||
except ConnectionFailedError as err:
|
||||
raise ConfigEntryNotReady("Failed to connect to the Plugwise Smile") from err
|
||||
except InvalidAuthentication as err:
|
||||
raise HomeAssistantError("Invalid username or Smile ID") from err
|
||||
except (InvalidXMLError, ResponseError) as err:
|
||||
raise ConfigEntryNotReady(
|
||||
"Error while communicating to the Plugwise Smile"
|
||||
) from err
|
||||
except UnsupportedDeviceError as err:
|
||||
raise HomeAssistantError("Device with unsupported firmware") from err
|
||||
|
||||
if not connected:
|
||||
raise ConfigEntryNotReady("Unable to connect to Smile")
|
||||
api.get_all_devices()
|
||||
|
||||
if entry.unique_id is None and api.smile_version[0] != "1.8.0":
|
||||
hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname)
|
||||
|
||||
coordinator = PlugwiseDataUpdateCoordinator(hass, api)
|
||||
coordinator = PlugwiseDataUpdateCoordinator(hass, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
migrate_sensor_entities(hass, coordinator)
|
||||
|
||||
@ -73,11 +24,11 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, str(api.gateway_id))},
|
||||
identifiers={(DOMAIN, str(coordinator.api.gateway_id))},
|
||||
manufacturer="Plugwise",
|
||||
model=api.smile_model,
|
||||
name=api.smile_name,
|
||||
sw_version=api.smile_version[0],
|
||||
model=coordinator.api.smile_model,
|
||||
name=coordinator.api.smile_name,
|
||||
sw_version=coordinator.api.smile_version[0],
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS_GATEWAY)
|
||||
|
@ -75,7 +75,7 @@ def mock_smile_adam() -> Generator[None, MagicMock, None]:
|
||||
chosen_env = "adam_multiple_devices_per_zone"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.gateway.Smile", autospec=True
|
||||
"homeassistant.components.plugwise.coordinator.Smile", autospec=True
|
||||
) as smile_mock:
|
||||
smile = smile_mock.return_value
|
||||
|
||||
@ -101,7 +101,7 @@ def mock_smile_adam_2() -> Generator[None, MagicMock, None]:
|
||||
chosen_env = "m_adam_heating"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.gateway.Smile", autospec=True
|
||||
"homeassistant.components.plugwise.coordinator.Smile", autospec=True
|
||||
) as smile_mock:
|
||||
smile = smile_mock.return_value
|
||||
|
||||
@ -127,7 +127,7 @@ def mock_smile_adam_3() -> Generator[None, MagicMock, None]:
|
||||
chosen_env = "m_adam_cooling"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.gateway.Smile", autospec=True
|
||||
"homeassistant.components.plugwise.coordinator.Smile", autospec=True
|
||||
) as smile_mock:
|
||||
smile = smile_mock.return_value
|
||||
|
||||
@ -152,7 +152,7 @@ def mock_smile_anna() -> Generator[None, MagicMock, None]:
|
||||
"""Create a Mock Anna environment for testing exceptions."""
|
||||
chosen_env = "anna_heatpump_heating"
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.gateway.Smile", autospec=True
|
||||
"homeassistant.components.plugwise.coordinator.Smile", autospec=True
|
||||
) as smile_mock:
|
||||
smile = smile_mock.return_value
|
||||
|
||||
@ -177,7 +177,7 @@ def mock_smile_anna_2() -> Generator[None, MagicMock, None]:
|
||||
"""Create a 2nd Mock Anna environment for testing exceptions."""
|
||||
chosen_env = "m_anna_heatpump_cooling"
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.gateway.Smile", autospec=True
|
||||
"homeassistant.components.plugwise.coordinator.Smile", autospec=True
|
||||
) as smile_mock:
|
||||
smile = smile_mock.return_value
|
||||
|
||||
@ -202,7 +202,7 @@ def mock_smile_anna_3() -> Generator[None, MagicMock, None]:
|
||||
"""Create a 3rd Mock Anna environment for testing exceptions."""
|
||||
chosen_env = "m_anna_heatpump_idle"
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.gateway.Smile", autospec=True
|
||||
"homeassistant.components.plugwise.coordinator.Smile", autospec=True
|
||||
) as smile_mock:
|
||||
smile = smile_mock.return_value
|
||||
|
||||
@ -227,7 +227,7 @@ def mock_smile_p1() -> Generator[None, MagicMock, None]:
|
||||
"""Create a Mock P1 DSMR environment for testing exceptions."""
|
||||
chosen_env = "p1v3_full_option"
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.gateway.Smile", autospec=True
|
||||
"homeassistant.components.plugwise.coordinator.Smile", autospec=True
|
||||
) as smile_mock:
|
||||
smile = smile_mock.return_value
|
||||
|
||||
@ -252,7 +252,7 @@ def mock_stretch() -> Generator[None, MagicMock, None]:
|
||||
"""Create a Mock Stretch environment for testing exceptions."""
|
||||
chosen_env = "stretch_v31"
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.gateway.Smile", autospec=True
|
||||
"homeassistant.components.plugwise.coordinator.Smile", autospec=True
|
||||
) as smile_mock:
|
||||
smile = smile_mock.return_value
|
||||
|
||||
|
@ -59,33 +59,6 @@ async def test_gateway_config_entry_not_ready(
|
||||
mock_smile_anna: MagicMock,
|
||||
side_effect: Exception,
|
||||
entry_state: ConfigEntryState,
|
||||
) -> None:
|
||||
"""Test the Plugwise configuration entry not ready."""
|
||||
mock_smile_anna.connect.side_effect = side_effect
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_smile_anna.connect.mock_calls) == 1
|
||||
assert mock_config_entry.state is entry_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"side_effect",
|
||||
[
|
||||
(ConnectionFailedError),
|
||||
(InvalidAuthentication),
|
||||
(InvalidXMLError),
|
||||
(ResponseError),
|
||||
(UnsupportedDeviceError),
|
||||
],
|
||||
)
|
||||
async def test_coord_config_entry_not_ready(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_smile_anna: MagicMock,
|
||||
side_effect: Exception,
|
||||
) -> None:
|
||||
"""Test the Plugwise configuration entry not ready."""
|
||||
mock_smile_anna.async_update.side_effect = side_effect
|
||||
@ -95,7 +68,7 @@ async def test_coord_config_entry_not_ready(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_smile_anna.connect.mock_calls) == 1
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert mock_config_entry.state is entry_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
Loading…
x
Reference in New Issue
Block a user