mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Bump aioshelly to 4.0.0 (#80423)
* Bump aioshelly to 4.0.0 * Remove leftover * Fix number platform * Set last_update_success to false upon failure in number and climate * Set last_update_success upon failurie in entity
This commit is contained in:
parent
2c43606922
commit
aea7a9af18
@ -1,16 +1,12 @@
|
|||||||
"""The Shelly integration."""
|
"""The Shelly integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
from aiohttp import ClientResponseError
|
|
||||||
import aioshelly
|
import aioshelly
|
||||||
from aioshelly.block_device import BlockDevice
|
from aioshelly.block_device import BlockDevice
|
||||||
from aioshelly.exceptions import AuthRequired, InvalidAuthError
|
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
|
||||||
from aioshelly.rpc_device import RpcDevice
|
from aioshelly.rpc_device import RpcDevice
|
||||||
import async_timeout
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -23,7 +19,6 @@ from homeassistant.helpers.typing import ConfigType
|
|||||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
AIOSHELLY_DEVICE_TIMEOUT_SEC,
|
|
||||||
CONF_COAP_PORT,
|
CONF_COAP_PORT,
|
||||||
CONF_SLEEP_PERIOD,
|
CONF_SLEEP_PERIOD,
|
||||||
DATA_CONFIG_ENTRY,
|
DATA_CONFIG_ENTRY,
|
||||||
@ -185,20 +180,11 @@ async def _async_setup_block_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
|
|||||||
# Not a sleeping device, finish setup
|
# Not a sleeping device, finish setup
|
||||||
LOGGER.debug("Setting up online block device %s", entry.title)
|
LOGGER.debug("Setting up online block device %s", entry.title)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
await device.initialize()
|
||||||
await device.initialize()
|
except DeviceConnectionError as err:
|
||||||
await device.update_status()
|
raise ConfigEntryNotReady(repr(err)) from err
|
||||||
except asyncio.TimeoutError as err:
|
except InvalidAuthError as err:
|
||||||
raise ConfigEntryNotReady(
|
raise ConfigEntryAuthFailed(repr(err)) from err
|
||||||
str(err) or "Timeout during device setup"
|
|
||||||
) from err
|
|
||||||
except OSError as err:
|
|
||||||
raise ConfigEntryNotReady(str(err) or "Error during device setup") from err
|
|
||||||
except AuthRequired as err:
|
|
||||||
raise ConfigEntryAuthFailed from err
|
|
||||||
except ClientResponseError as err:
|
|
||||||
if err.status == HTTPStatus.UNAUTHORIZED:
|
|
||||||
raise ConfigEntryAuthFailed from err
|
|
||||||
|
|
||||||
_async_block_device_setup()
|
_async_block_device_setup()
|
||||||
elif sleep_period is None or device_entry is None:
|
elif sleep_period is None or device_entry is None:
|
||||||
@ -283,16 +269,12 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> boo
|
|||||||
# Not a sleeping device, finish setup
|
# Not a sleeping device, finish setup
|
||||||
LOGGER.debug("Setting up online RPC device %s", entry.title)
|
LOGGER.debug("Setting up online RPC device %s", entry.title)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
await device.initialize()
|
||||||
await device.initialize()
|
except DeviceConnectionError as err:
|
||||||
except asyncio.TimeoutError as err:
|
raise ConfigEntryNotReady(repr(err)) from err
|
||||||
raise ConfigEntryNotReady(
|
except InvalidAuthError as err:
|
||||||
str(err) or "Timeout during device setup"
|
raise ConfigEntryAuthFailed(repr(err)) from err
|
||||||
) from err
|
|
||||||
except OSError as err:
|
|
||||||
raise ConfigEntryNotReady(str(err) or "Error during device setup") from err
|
|
||||||
except (AuthRequired, InvalidAuthError) as err:
|
|
||||||
raise ConfigEntryAuthFailed from err
|
|
||||||
_async_rpc_device_setup()
|
_async_rpc_device_setup()
|
||||||
elif sleep_period is None or device_entry is None:
|
elif sleep_period is None or device_entry is None:
|
||||||
# Need to get sleep info or first time sleeping device setup, wait for device
|
# Need to get sleep info or first time sleeping device setup, wait for device
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
"""Climate support for Shelly."""
|
"""Climate support for Shelly."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from aioshelly.block_device import Block
|
from aioshelly.block_device import Block
|
||||||
from aioshelly.exceptions import AuthRequired
|
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
|
||||||
import async_timeout
|
|
||||||
|
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
DOMAIN as CLIMATE_DOMAIN,
|
DOMAIN as CLIMATE_DOMAIN,
|
||||||
@ -20,13 +18,14 @@ from homeassistant.components.climate import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
from homeassistant.core import HomeAssistant, State, callback
|
from homeassistant.core import HomeAssistant, State, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry, entity_registry
|
from homeassistant.helpers import device_registry, entity_registry
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import AIOSHELLY_DEVICE_TIMEOUT_SEC, LOGGER, SHTRV_01_TEMPERATURE_SETTINGS
|
from .const import LOGGER, SHTRV_01_TEMPERATURE_SETTINGS
|
||||||
from .coordinator import ShellyBlockCoordinator, get_entry_data
|
from .coordinator import ShellyBlockCoordinator, get_entry_data
|
||||||
from .utils import get_device_entry_gen
|
from .utils import get_device_entry_gen
|
||||||
|
|
||||||
@ -238,19 +237,16 @@ class BlockSleepingClimate(
|
|||||||
"""Set block state (HTTP request)."""
|
"""Set block state (HTTP request)."""
|
||||||
LOGGER.debug("Setting state for entity %s, state: %s", self.name, kwargs)
|
LOGGER.debug("Setting state for entity %s, state: %s", self.name, kwargs)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
return await self.coordinator.device.http_request(
|
||||||
return await self.coordinator.device.http_request(
|
"get", f"thermostat/{self._channel}", kwargs
|
||||||
"get", f"thermostat/{self._channel}", kwargs
|
|
||||||
)
|
|
||||||
except (asyncio.TimeoutError, OSError) as err:
|
|
||||||
LOGGER.error(
|
|
||||||
"Setting state for entity %s failed, state: %s, error: %s",
|
|
||||||
self.name,
|
|
||||||
kwargs,
|
|
||||||
repr(err),
|
|
||||||
)
|
)
|
||||||
|
except DeviceConnectionError as err:
|
||||||
self.coordinator.last_update_success = False
|
self.coordinator.last_update_success = False
|
||||||
return None
|
raise HomeAssistantError(
|
||||||
|
f"Setting state for entity {self.name} failed, state: {kwargs}, error: {repr(err)}"
|
||||||
|
) from err
|
||||||
|
except InvalidAuthError:
|
||||||
|
self.coordinator.entry.async_start_reauth(self.hass)
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@ -327,7 +323,7 @@ class BlockSleepingClimate(
|
|||||||
int(self.block.channel)
|
int(self.block.channel)
|
||||||
]["schedule_profile_names"],
|
]["schedule_profile_names"],
|
||||||
]
|
]
|
||||||
except AuthRequired:
|
except InvalidAuthError:
|
||||||
self.coordinator.entry.async_start_reauth(self.hass)
|
self.coordinator.entry.async_start_reauth(self.hass)
|
||||||
else:
|
else:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
"""Config flow for Shelly integration."""
|
"""Config flow for Shelly integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import aioshelly
|
import aioshelly
|
||||||
from aioshelly.block_device import BlockDevice
|
from aioshelly.block_device import BlockDevice
|
||||||
|
from aioshelly.exceptions import (
|
||||||
|
DeviceConnectionError,
|
||||||
|
FirmwareUnsupported,
|
||||||
|
InvalidAuthError,
|
||||||
|
)
|
||||||
from aioshelly.rpc_device import RpcDevice
|
from aioshelly.rpc_device import RpcDevice
|
||||||
import async_timeout
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
@ -20,7 +21,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
from .const import AIOSHELLY_DEVICE_TIMEOUT_SEC, CONF_SLEEP_PERIOD, DOMAIN, LOGGER
|
from .const import CONF_SLEEP_PERIOD, DOMAIN, LOGGER
|
||||||
from .utils import (
|
from .utils import (
|
||||||
get_block_device_name,
|
get_block_device_name,
|
||||||
get_block_device_sleep_period,
|
get_block_device_sleep_period,
|
||||||
@ -35,8 +36,6 @@ from .utils import (
|
|||||||
|
|
||||||
HOST_SCHEMA: Final = vol.Schema({vol.Required(CONF_HOST): str})
|
HOST_SCHEMA: Final = vol.Schema({vol.Required(CONF_HOST): str})
|
||||||
|
|
||||||
HTTP_CONNECT_ERRORS: Final = (asyncio.TimeoutError, aiohttp.ClientError)
|
|
||||||
|
|
||||||
|
|
||||||
async def validate_input(
|
async def validate_input(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -54,39 +53,38 @@ async def validate_input(
|
|||||||
data.get(CONF_PASSWORD),
|
data.get(CONF_PASSWORD),
|
||||||
)
|
)
|
||||||
|
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
if get_info_gen(info) == 2:
|
||||||
if get_info_gen(info) == 2:
|
ws_context = await get_ws_context(hass)
|
||||||
ws_context = await get_ws_context(hass)
|
rpc_device = await RpcDevice.create(
|
||||||
rpc_device = await RpcDevice.create(
|
|
||||||
aiohttp_client.async_get_clientsession(hass),
|
|
||||||
ws_context,
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
await rpc_device.shutdown()
|
|
||||||
assert rpc_device.shelly
|
|
||||||
|
|
||||||
return {
|
|
||||||
"title": get_rpc_device_name(rpc_device),
|
|
||||||
CONF_SLEEP_PERIOD: get_rpc_device_sleep_period(rpc_device.config),
|
|
||||||
"model": rpc_device.shelly.get("model"),
|
|
||||||
"gen": 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Gen1
|
|
||||||
coap_context = await get_coap_context(hass)
|
|
||||||
block_device = await BlockDevice.create(
|
|
||||||
aiohttp_client.async_get_clientsession(hass),
|
aiohttp_client.async_get_clientsession(hass),
|
||||||
coap_context,
|
ws_context,
|
||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
block_device.shutdown()
|
await rpc_device.shutdown()
|
||||||
|
assert rpc_device.shelly
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": get_block_device_name(block_device),
|
"title": get_rpc_device_name(rpc_device),
|
||||||
CONF_SLEEP_PERIOD: get_block_device_sleep_period(block_device.settings),
|
CONF_SLEEP_PERIOD: get_rpc_device_sleep_period(rpc_device.config),
|
||||||
"model": block_device.model,
|
"model": rpc_device.shelly.get("model"),
|
||||||
"gen": 1,
|
"gen": 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Gen1
|
||||||
|
coap_context = await get_coap_context(hass)
|
||||||
|
block_device = await BlockDevice.create(
|
||||||
|
aiohttp_client.async_get_clientsession(hass),
|
||||||
|
coap_context,
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
block_device.shutdown()
|
||||||
|
return {
|
||||||
|
"title": get_block_device_name(block_device),
|
||||||
|
CONF_SLEEP_PERIOD: get_block_device_sleep_period(block_device.settings),
|
||||||
|
"model": block_device.model,
|
||||||
|
"gen": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Shelly."""
|
"""Handle a config flow for Shelly."""
|
||||||
@ -107,9 +105,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
host: str = user_input[CONF_HOST]
|
host: str = user_input[CONF_HOST]
|
||||||
try:
|
try:
|
||||||
self.info = await self._async_get_info(host)
|
self.info = await self._async_get_info(host)
|
||||||
except HTTP_CONNECT_ERRORS:
|
except DeviceConnectionError:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
except aioshelly.exceptions.FirmwareUnsupported:
|
except FirmwareUnsupported:
|
||||||
return self.async_abort(reason="unsupported_firmware")
|
return self.async_abort(reason="unsupported_firmware")
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
LOGGER.exception("Unexpected exception")
|
LOGGER.exception("Unexpected exception")
|
||||||
@ -125,7 +123,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
device_info = await validate_input(
|
device_info = await validate_input(
|
||||||
self.hass, self.host, self.info, {}
|
self.hass, self.host, self.info, {}
|
||||||
)
|
)
|
||||||
except HTTP_CONNECT_ERRORS:
|
except DeviceConnectionError:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
LOGGER.exception("Unexpected exception")
|
LOGGER.exception("Unexpected exception")
|
||||||
@ -159,16 +157,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
device_info = await validate_input(
|
device_info = await validate_input(
|
||||||
self.hass, self.host, self.info, user_input
|
self.hass, self.host, self.info, user_input
|
||||||
)
|
)
|
||||||
except aiohttp.ClientResponseError as error:
|
except InvalidAuthError:
|
||||||
if error.status == HTTPStatus.UNAUTHORIZED:
|
|
||||||
errors["base"] = "invalid_auth"
|
|
||||||
else:
|
|
||||||
errors["base"] = "cannot_connect"
|
|
||||||
except aioshelly.exceptions.InvalidAuthError:
|
|
||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
except HTTP_CONNECT_ERRORS:
|
except DeviceConnectionError:
|
||||||
errors["base"] = "cannot_connect"
|
|
||||||
except aioshelly.exceptions.JSONRPCError:
|
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
LOGGER.exception("Unexpected exception")
|
LOGGER.exception("Unexpected exception")
|
||||||
@ -210,9 +201,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
host = discovery_info.host
|
host = discovery_info.host
|
||||||
try:
|
try:
|
||||||
self.info = await self._async_get_info(host)
|
self.info = await self._async_get_info(host)
|
||||||
except HTTP_CONNECT_ERRORS:
|
except DeviceConnectionError:
|
||||||
return self.async_abort(reason="cannot_connect")
|
return self.async_abort(reason="cannot_connect")
|
||||||
except aioshelly.exceptions.FirmwareUnsupported:
|
except FirmwareUnsupported:
|
||||||
return self.async_abort(reason="unsupported_firmware")
|
return self.async_abort(reason="unsupported_firmware")
|
||||||
|
|
||||||
await self.async_set_unique_id(self.info["mac"])
|
await self.async_set_unique_id(self.info["mac"])
|
||||||
@ -231,7 +222,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.device_info = await validate_input(self.hass, self.host, self.info, {})
|
self.device_info = await validate_input(self.hass, self.host, self.info, {})
|
||||||
except HTTP_CONNECT_ERRORS:
|
except DeviceConnectionError:
|
||||||
return self.async_abort(reason="cannot_connect")
|
return self.async_abort(reason="cannot_connect")
|
||||||
|
|
||||||
return await self.async_step_confirm_discovery()
|
return await self.async_step_confirm_discovery()
|
||||||
@ -284,23 +275,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
try:
|
try:
|
||||||
info = await self._async_get_info(host)
|
info = await self._async_get_info(host)
|
||||||
except (
|
except (DeviceConnectionError, InvalidAuthError, FirmwareUnsupported):
|
||||||
asyncio.TimeoutError,
|
|
||||||
aiohttp.ClientError,
|
|
||||||
aioshelly.exceptions.FirmwareUnsupported,
|
|
||||||
):
|
|
||||||
return self.async_abort(reason="reauth_unsuccessful")
|
return self.async_abort(reason="reauth_unsuccessful")
|
||||||
|
|
||||||
if self.entry.data.get("gen", 1) != 1:
|
if self.entry.data.get("gen", 1) != 1:
|
||||||
user_input[CONF_USERNAME] = "admin"
|
user_input[CONF_USERNAME] = "admin"
|
||||||
try:
|
try:
|
||||||
await validate_input(self.hass, host, info, user_input)
|
await validate_input(self.hass, host, info, user_input)
|
||||||
except (
|
except (DeviceConnectionError, InvalidAuthError, FirmwareUnsupported):
|
||||||
aiohttp.ClientResponseError,
|
|
||||||
aioshelly.exceptions.InvalidAuthError,
|
|
||||||
asyncio.TimeoutError,
|
|
||||||
aiohttp.ClientError,
|
|
||||||
):
|
|
||||||
return self.async_abort(reason="reauth_unsuccessful")
|
return self.async_abort(reason="reauth_unsuccessful")
|
||||||
else:
|
else:
|
||||||
self.hass.config_entries.async_update_entry(
|
self.hass.config_entries.async_update_entry(
|
||||||
@ -325,7 +307,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def _async_get_info(self, host: str) -> dict[str, Any]:
|
async def _async_get_info(self, host: str) -> dict[str, Any]:
|
||||||
"""Get info from shelly device."""
|
"""Get info from shelly device."""
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
return await aioshelly.common.get_info(
|
||||||
return await aioshelly.common.get_info(
|
aiohttp_client.async_get_clientsession(self.hass), host
|
||||||
aiohttp_client.async_get_clientsession(self.hass), host
|
)
|
||||||
)
|
|
||||||
|
@ -46,18 +46,12 @@ DUAL_MODE_LIGHT_MODELS: Final = (
|
|||||||
"SHCB-1",
|
"SHCB-1",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Used in "_async_update_data" as timeout for polling data from devices.
|
|
||||||
POLLING_TIMEOUT_SEC: Final = 18
|
|
||||||
|
|
||||||
# Refresh interval for REST sensors
|
# Refresh interval for REST sensors
|
||||||
REST_SENSORS_UPDATE_INTERVAL: Final = 60
|
REST_SENSORS_UPDATE_INTERVAL: Final = 60
|
||||||
|
|
||||||
# Refresh interval for RPC polling sensors
|
# Refresh interval for RPC polling sensors
|
||||||
RPC_SENSORS_POLLING_INTERVAL: Final = 60
|
RPC_SENSORS_POLLING_INTERVAL: Final = 60
|
||||||
|
|
||||||
# Timeout used for aioshelly calls
|
|
||||||
AIOSHELLY_DEVICE_TIMEOUT_SEC: Final = 10
|
|
||||||
|
|
||||||
# Multiplier used to calculate the "update_interval" for sleeping devices.
|
# Multiplier used to calculate the "update_interval" for sleeping devices.
|
||||||
SLEEP_PERIOD_MULTIPLIER: Final = 1.2
|
SLEEP_PERIOD_MULTIPLIER: Final = 1.2
|
||||||
CONF_SLEEP_PERIOD: Final = "sleep_period"
|
CONF_SLEEP_PERIOD: Final = "sleep_period"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""Coordinators for the Shelly integration."""
|
"""Coordinators for the Shelly integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections.abc import Coroutine
|
from collections.abc import Coroutine
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -9,18 +8,18 @@ from typing import Any, cast
|
|||||||
|
|
||||||
import aioshelly
|
import aioshelly
|
||||||
from aioshelly.block_device import BlockDevice
|
from aioshelly.block_device import BlockDevice
|
||||||
|
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
|
||||||
from aioshelly.rpc_device import RpcDevice
|
from aioshelly.rpc_device import RpcDevice
|
||||||
import async_timeout
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_DEVICE_ID, CONF_HOST, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import ATTR_DEVICE_ID, CONF_HOST, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import Event, HomeAssistant, callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry
|
from homeassistant.helpers import device_registry
|
||||||
from homeassistant.helpers.debounce import Debouncer
|
from homeassistant.helpers.debounce import Debouncer
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
AIOSHELLY_DEVICE_TIMEOUT_SEC,
|
|
||||||
ATTR_BETA,
|
ATTR_BETA,
|
||||||
ATTR_CHANNEL,
|
ATTR_CHANNEL,
|
||||||
ATTR_CLICK_TYPE,
|
ATTR_CLICK_TYPE,
|
||||||
@ -36,7 +35,6 @@ from .const import (
|
|||||||
INPUTS_EVENTS_DICT,
|
INPUTS_EVENTS_DICT,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
MODELS_SUPPORTING_LIGHT_EFFECTS,
|
MODELS_SUPPORTING_LIGHT_EFFECTS,
|
||||||
POLLING_TIMEOUT_SEC,
|
|
||||||
REST_SENSORS_UPDATE_INTERVAL,
|
REST_SENSORS_UPDATE_INTERVAL,
|
||||||
RPC_INPUTS_EVENTS_TYPES,
|
RPC_INPUTS_EVENTS_TYPES,
|
||||||
RPC_RECONNECT_INTERVAL,
|
RPC_RECONNECT_INTERVAL,
|
||||||
@ -212,11 +210,13 @@ class ShellyBlockCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
LOGGER.debug("Polling Shelly Block Device - %s", self.name)
|
LOGGER.debug("Polling Shelly Block Device - %s", self.name)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(POLLING_TIMEOUT_SEC):
|
await self.device.update()
|
||||||
await self.device.update()
|
except DeviceConnectionError as err:
|
||||||
device_update_info(self.hass, self.device, self.entry)
|
raise UpdateFailed(f"Error fetching data: {repr(err)}") from err
|
||||||
except OSError as err:
|
except InvalidAuthError:
|
||||||
raise UpdateFailed("Error fetching data") from err
|
self.entry.async_start_reauth(self.hass)
|
||||||
|
else:
|
||||||
|
device_update_info(self.hass, self.device, self.entry)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self) -> str:
|
def model(self) -> str:
|
||||||
@ -278,11 +278,13 @@ class ShellyBlockCoordinator(DataUpdateCoordinator):
|
|||||||
new_version,
|
new_version,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
result = await self.device.trigger_ota_update(beta=beta)
|
||||||
result = await self.device.trigger_ota_update(beta=beta)
|
except DeviceConnectionError as err:
|
||||||
except (asyncio.TimeoutError, OSError) as err:
|
raise HomeAssistantError(f"Error starting OTA update: {repr(err)}") from err
|
||||||
LOGGER.exception("Error while perform ota update: %s", err)
|
except InvalidAuthError:
|
||||||
LOGGER.debug("Result of OTA update call: %s", result)
|
self.entry.async_start_reauth(self.hass)
|
||||||
|
else:
|
||||||
|
LOGGER.debug("Result of OTA update call: %s", result)
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
"""Shutdown the coordinator."""
|
"""Shutdown the coordinator."""
|
||||||
@ -323,20 +325,22 @@ class ShellyRestCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def _async_update_data(self) -> None:
|
async def _async_update_data(self) -> None:
|
||||||
"""Fetch data."""
|
"""Fetch data."""
|
||||||
|
LOGGER.debug("REST update for %s", self.name)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
await self.device.update_status()
|
||||||
LOGGER.debug("REST update for %s", self.name)
|
|
||||||
await self.device.update_status()
|
|
||||||
|
|
||||||
if self.device.status["uptime"] > 2 * REST_SENSORS_UPDATE_INTERVAL:
|
if self.device.status["uptime"] > 2 * REST_SENSORS_UPDATE_INTERVAL:
|
||||||
return
|
return
|
||||||
old_firmware = self.device.firmware_version
|
old_firmware = self.device.firmware_version
|
||||||
await self.device.update_shelly()
|
await self.device.update_shelly()
|
||||||
if self.device.firmware_version == old_firmware:
|
if self.device.firmware_version == old_firmware:
|
||||||
return
|
return
|
||||||
device_update_info(self.hass, self.device, self.entry)
|
except DeviceConnectionError as err:
|
||||||
except OSError as err:
|
raise UpdateFailed(f"Error fetching data: {repr(err)}") from err
|
||||||
raise UpdateFailed("Error fetching data") from err
|
except InvalidAuthError:
|
||||||
|
self.entry.async_start_reauth(self.hass)
|
||||||
|
else:
|
||||||
|
device_update_info(self.hass, self.device, self.entry)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mac(self) -> str:
|
def mac(self) -> str:
|
||||||
@ -436,13 +440,14 @@ class ShellyRpcCoordinator(DataUpdateCoordinator):
|
|||||||
if self.device.connected:
|
if self.device.connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
LOGGER.debug("Reconnecting to Shelly RPC Device - %s", self.name)
|
||||||
try:
|
try:
|
||||||
LOGGER.debug("Reconnecting to Shelly RPC Device - %s", self.name)
|
await self.device.initialize()
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
device_update_info(self.hass, self.device, self.entry)
|
||||||
await self.device.initialize()
|
except DeviceConnectionError as err:
|
||||||
device_update_info(self.hass, self.device, self.entry)
|
raise UpdateFailed(f"Device disconnected: {repr(err)}") from err
|
||||||
except OSError as err:
|
except InvalidAuthError:
|
||||||
raise UpdateFailed("Device disconnected") from err
|
self.entry.async_start_reauth(self.hass)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self) -> str:
|
def model(self) -> str:
|
||||||
@ -503,12 +508,13 @@ class ShellyRpcCoordinator(DataUpdateCoordinator):
|
|||||||
new_version,
|
new_version,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
await self.device.trigger_ota_update(beta=beta)
|
||||||
await self.device.trigger_ota_update(beta=beta)
|
except DeviceConnectionError as err:
|
||||||
except (asyncio.TimeoutError, OSError) as err:
|
raise HomeAssistantError(f"Error starting OTA update: {repr(err)}") from err
|
||||||
LOGGER.exception("Error while perform ota update: %s", err)
|
except InvalidAuthError:
|
||||||
|
self.entry.async_start_reauth(self.hass)
|
||||||
LOGGER.debug("OTA update call successful")
|
else:
|
||||||
|
LOGGER.debug("OTA update call successful")
|
||||||
|
|
||||||
async def shutdown(self) -> None:
|
async def shutdown(self) -> None:
|
||||||
"""Shutdown the coordinator."""
|
"""Shutdown the coordinator."""
|
||||||
@ -544,12 +550,13 @@ class ShellyRpcPollingCoordinator(DataUpdateCoordinator):
|
|||||||
if not self.device.connected:
|
if not self.device.connected:
|
||||||
raise UpdateFailed("Device disconnected")
|
raise UpdateFailed("Device disconnected")
|
||||||
|
|
||||||
|
LOGGER.debug("Polling Shelly RPC Device - %s", self.name)
|
||||||
try:
|
try:
|
||||||
LOGGER.debug("Polling Shelly RPC Device - %s", self.name)
|
await self.device.update_status()
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
except DeviceConnectionError as err:
|
||||||
await self.device.update_status()
|
raise UpdateFailed(f"Device disconnected: {repr(err)}") from err
|
||||||
except (OSError, aioshelly.exceptions.RPCTimeout) as err:
|
except InvalidAuthError:
|
||||||
raise UpdateFailed("Device disconnected") from err
|
self.entry.async_start_reauth(self.hass)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self) -> str:
|
def model(self) -> str:
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
"""Shelly entity helper."""
|
"""Shelly entity helper."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections.abc import Callable, Mapping
|
from collections.abc import Callable, Mapping
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from aioshelly.block_device import Block
|
from aioshelly.block_device import Block
|
||||||
import async_timeout
|
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry, entity, entity_registry
|
from homeassistant.helpers import device_registry, entity, entity_registry
|
||||||
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
@ -19,7 +19,7 @@ from homeassistant.helpers.restore_state import RestoreEntity
|
|||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import AIOSHELLY_DEVICE_TIMEOUT_SEC, CONF_SLEEP_PERIOD, LOGGER
|
from .const import CONF_SLEEP_PERIOD, LOGGER
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
ShellyBlockCoordinator,
|
ShellyBlockCoordinator,
|
||||||
ShellyRpcCoordinator,
|
ShellyRpcCoordinator,
|
||||||
@ -362,17 +362,14 @@ class ShellyBlockEntity(CoordinatorEntity[ShellyBlockCoordinator]):
|
|||||||
"""Set block state (HTTP request)."""
|
"""Set block state (HTTP request)."""
|
||||||
LOGGER.debug("Setting state for entity %s, state: %s", self.name, kwargs)
|
LOGGER.debug("Setting state for entity %s, state: %s", self.name, kwargs)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
return await self.block.set_state(**kwargs)
|
||||||
return await self.block.set_state(**kwargs)
|
except DeviceConnectionError as err:
|
||||||
except (asyncio.TimeoutError, OSError) as err:
|
|
||||||
LOGGER.error(
|
|
||||||
"Setting state for entity %s failed, state: %s, error: %s",
|
|
||||||
self.name,
|
|
||||||
kwargs,
|
|
||||||
repr(err),
|
|
||||||
)
|
|
||||||
self.coordinator.last_update_success = False
|
self.coordinator.last_update_success = False
|
||||||
return None
|
raise HomeAssistantError(
|
||||||
|
f"Setting state for entity {self.name} failed, state: {kwargs}, error: {repr(err)}"
|
||||||
|
) from err
|
||||||
|
except InvalidAuthError:
|
||||||
|
self.coordinator.entry.async_start_reauth(self.hass)
|
||||||
|
|
||||||
|
|
||||||
class ShellyRpcEntity(entity.Entity):
|
class ShellyRpcEntity(entity.Entity):
|
||||||
@ -425,18 +422,14 @@ class ShellyRpcEntity(entity.Entity):
|
|||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
return await self.coordinator.device.call_rpc(method, params)
|
||||||
return await self.coordinator.device.call_rpc(method, params)
|
except DeviceConnectionError as err:
|
||||||
except asyncio.TimeoutError as err:
|
|
||||||
LOGGER.error(
|
|
||||||
"Call RPC for entity %s failed, method: %s, params: %s, error: %s",
|
|
||||||
self.name,
|
|
||||||
method,
|
|
||||||
params,
|
|
||||||
repr(err),
|
|
||||||
)
|
|
||||||
self.coordinator.last_update_success = False
|
self.coordinator.last_update_success = False
|
||||||
return None
|
raise HomeAssistantError(
|
||||||
|
f"Call RPC for entity {self.name} failed, method: {method}, params: {params}, error: {repr(err)}"
|
||||||
|
) from err
|
||||||
|
except InvalidAuthError:
|
||||||
|
self.coordinator.entry.async_start_reauth(self.hass)
|
||||||
|
|
||||||
|
|
||||||
class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
|
class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Shelly",
|
"name": "Shelly",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/shelly",
|
"documentation": "https://www.home-assistant.io/integrations/shelly",
|
||||||
"requirements": ["aioshelly==3.0.0"],
|
"requirements": ["aioshelly==4.0.0"],
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
"""Number for Shelly."""
|
"""Number for Shelly."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Final, cast
|
from typing import Any, Final, cast
|
||||||
|
|
||||||
import async_timeout
|
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
|
||||||
|
|
||||||
from homeassistant.components.number import (
|
from homeassistant.components.number import (
|
||||||
NumberEntity,
|
NumberEntity,
|
||||||
@ -15,11 +14,12 @@ from homeassistant.components.number import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import PERCENTAGE
|
from homeassistant.const import PERCENTAGE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity import EntityCategory
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||||
|
|
||||||
from .const import AIOSHELLY_DEVICE_TIMEOUT_SEC, CONF_SLEEP_PERIOD, LOGGER
|
from .const import CONF_SLEEP_PERIOD, LOGGER
|
||||||
from .entity import (
|
from .entity import (
|
||||||
BlockEntityDescription,
|
BlockEntityDescription,
|
||||||
ShellySleepingBlockAttributeEntity,
|
ShellySleepingBlockAttributeEntity,
|
||||||
@ -115,15 +115,13 @@ class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, NumberEntity):
|
|||||||
|
|
||||||
async def _set_state_full_path(self, path: str, params: Any) -> Any:
|
async def _set_state_full_path(self, path: str, params: Any) -> Any:
|
||||||
"""Set block state (HTTP request)."""
|
"""Set block state (HTTP request)."""
|
||||||
|
|
||||||
LOGGER.debug("Setting state for entity %s, state: %s", self.name, params)
|
LOGGER.debug("Setting state for entity %s, state: %s", self.name, params)
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC):
|
return await self.coordinator.device.http_request("get", path, params)
|
||||||
return await self.coordinator.device.http_request("get", path, params)
|
except DeviceConnectionError as err:
|
||||||
except (asyncio.TimeoutError, OSError) as err:
|
self.coordinator.last_update_success = False
|
||||||
LOGGER.error(
|
raise HomeAssistantError(
|
||||||
"Setting state for entity %s failed, state: %s, error: %s",
|
f"Setting state for entity {self.name} failed, state: {params}, error: {repr(err)}"
|
||||||
self.name,
|
) from err
|
||||||
params,
|
except InvalidAuthError:
|
||||||
repr(err),
|
self.coordinator.entry.async_start_reauth(self.hass)
|
||||||
)
|
|
||||||
|
@ -255,7 +255,7 @@ aiosenseme==0.6.1
|
|||||||
aiosenz==1.0.0
|
aiosenz==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==3.0.0
|
aioshelly==4.0.0
|
||||||
|
|
||||||
# homeassistant.components.skybell
|
# homeassistant.components.skybell
|
||||||
aioskybell==22.7.0
|
aioskybell==22.7.0
|
||||||
|
@ -230,7 +230,7 @@ aiosenseme==0.6.1
|
|||||||
aiosenz==1.0.0
|
aiosenz==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==3.0.0
|
aioshelly==4.0.0
|
||||||
|
|
||||||
# homeassistant.components.skybell
|
# homeassistant.components.skybell
|
||||||
aioskybell==22.7.0
|
aioskybell==22.7.0
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
"""Test the Shelly config flow."""
|
"""Test the Shelly config flow."""
|
||||||
import asyncio
|
|
||||||
from http import HTTPStatus
|
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import aiohttp
|
from aioshelly.exceptions import (
|
||||||
import aioshelly
|
DeviceConnectionError,
|
||||||
|
FirmwareUnsupported,
|
||||||
|
InvalidAuthError,
|
||||||
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
@ -207,7 +208,7 @@ async def test_form_auth(hass, test_data):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")]
|
"error", [(DeviceConnectionError, "cannot_connect"), (ValueError, "unknown")]
|
||||||
)
|
)
|
||||||
async def test_form_errors_get_info(hass, error):
|
async def test_form_errors_get_info(hass, error):
|
||||||
"""Test we handle errors."""
|
"""Test we handle errors."""
|
||||||
@ -324,7 +325,7 @@ async def test_form_missing_model_key_zeroconf(hass, caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")]
|
"error", [(DeviceConnectionError, "cannot_connect"), (ValueError, "unknown")]
|
||||||
)
|
)
|
||||||
async def test_form_errors_test_connection(hass, error):
|
async def test_form_errors_test_connection(hass, error):
|
||||||
"""Test we handle errors."""
|
"""Test we handle errors."""
|
||||||
@ -431,10 +432,7 @@ async def test_form_firmware_unsupported(hass):
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch("aioshelly.common.get_info", side_effect=FirmwareUnsupported):
|
||||||
"aioshelly.common.get_info",
|
|
||||||
side_effect=aioshelly.exceptions.FirmwareUnsupported,
|
|
||||||
):
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{"host": "1.1.1.1"},
|
{"host": "1.1.1.1"},
|
||||||
@ -447,15 +445,8 @@ async def test_form_firmware_unsupported(hass):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error",
|
"error",
|
||||||
[
|
[
|
||||||
(
|
(InvalidAuthError, "invalid_auth"),
|
||||||
aiohttp.ClientResponseError(Mock(), (), status=HTTPStatus.BAD_REQUEST),
|
(DeviceConnectionError, "cannot_connect"),
|
||||||
"cannot_connect",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
aiohttp.ClientResponseError(Mock(), (), status=HTTPStatus.UNAUTHORIZED),
|
|
||||||
"invalid_auth",
|
|
||||||
),
|
|
||||||
(asyncio.TimeoutError, "cannot_connect"),
|
|
||||||
(ValueError, "unknown"),
|
(ValueError, "unknown"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -490,15 +481,8 @@ async def test_form_auth_errors_test_connection_gen1(hass, error):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error",
|
"error",
|
||||||
[
|
[
|
||||||
(
|
(DeviceConnectionError, "cannot_connect"),
|
||||||
aioshelly.exceptions.JSONRPCError(code=400),
|
(InvalidAuthError, "invalid_auth"),
|
||||||
"cannot_connect",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
aioshelly.exceptions.InvalidAuthError(code=401),
|
|
||||||
"invalid_auth",
|
|
||||||
),
|
|
||||||
(asyncio.TimeoutError, "cannot_connect"),
|
|
||||||
(ValueError, "unknown"),
|
(ValueError, "unknown"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -647,20 +631,8 @@ async def test_zeroconf_sleeping_device(hass):
|
|||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
async def test_zeroconf_sleeping_device_error(hass):
|
||||||
"error",
|
|
||||||
[
|
|
||||||
(
|
|
||||||
aiohttp.ClientResponseError(Mock(), (), status=HTTPStatus.BAD_REQUEST),
|
|
||||||
"cannot_connect",
|
|
||||||
),
|
|
||||||
(asyncio.TimeoutError, "cannot_connect"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_zeroconf_sleeping_device_error(hass, error):
|
|
||||||
"""Test sleeping device configuration via zeroconf with error."""
|
"""Test sleeping device configuration via zeroconf with error."""
|
||||||
exc = error
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"aioshelly.common.get_info",
|
"aioshelly.common.get_info",
|
||||||
return_value={
|
return_value={
|
||||||
@ -671,7 +643,7 @@ async def test_zeroconf_sleeping_device_error(hass, error):
|
|||||||
},
|
},
|
||||||
), patch(
|
), patch(
|
||||||
"aioshelly.block_device.BlockDevice.create",
|
"aioshelly.block_device.BlockDevice.create",
|
||||||
new=AsyncMock(side_effect=exc),
|
new=AsyncMock(side_effect=DeviceConnectionError),
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -708,10 +680,7 @@ async def test_zeroconf_already_configured(hass):
|
|||||||
|
|
||||||
async def test_zeroconf_firmware_unsupported(hass):
|
async def test_zeroconf_firmware_unsupported(hass):
|
||||||
"""Test we abort if device firmware is unsupported."""
|
"""Test we abort if device firmware is unsupported."""
|
||||||
with patch(
|
with patch("aioshelly.common.get_info", side_effect=FirmwareUnsupported):
|
||||||
"aioshelly.common.get_info",
|
|
||||||
side_effect=aioshelly.exceptions.FirmwareUnsupported,
|
|
||||||
):
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
data=DISCOVERY_INFO,
|
data=DISCOVERY_INFO,
|
||||||
@ -724,7 +693,7 @@ async def test_zeroconf_firmware_unsupported(hass):
|
|||||||
|
|
||||||
async def test_zeroconf_cannot_connect(hass):
|
async def test_zeroconf_cannot_connect(hass):
|
||||||
"""Test we get the form."""
|
"""Test we get the form."""
|
||||||
with patch("aioshelly.common.get_info", side_effect=asyncio.TimeoutError):
|
with patch("aioshelly.common.get_info", side_effect=DeviceConnectionError):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
data=DISCOVERY_INFO,
|
data=DISCOVERY_INFO,
|
||||||
@ -840,21 +809,13 @@ async def test_reauth_successful(hass, test_data):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_data",
|
"test_data",
|
||||||
[
|
[
|
||||||
(
|
(1, {"username": "test user", "password": "test1 password"}),
|
||||||
1,
|
(2, {"password": "test2 password"}),
|
||||||
{"username": "test user", "password": "test1 password"},
|
|
||||||
aioshelly.exceptions.InvalidAuthError(code=HTTPStatus.UNAUTHORIZED.value),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
2,
|
|
||||||
{"password": "test2 password"},
|
|
||||||
aiohttp.ClientResponseError(Mock(), (), status=HTTPStatus.UNAUTHORIZED),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_reauth_unsuccessful(hass, test_data):
|
async def test_reauth_unsuccessful(hass, test_data):
|
||||||
"""Test reauthentication flow failed."""
|
"""Test reauthentication flow failed."""
|
||||||
gen, user_input, exc = test_data
|
gen, user_input = test_data
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain="shelly", unique_id="test-mac", data={"host": "0.0.0.0", "gen": gen}
|
domain="shelly", unique_id="test-mac", data={"host": "0.0.0.0", "gen": gen}
|
||||||
)
|
)
|
||||||
@ -865,9 +826,10 @@ async def test_reauth_unsuccessful(hass, test_data):
|
|||||||
return_value={"mac": "test-mac", "type": "SHSW-1", "auth": True, "gen": gen},
|
return_value={"mac": "test-mac", "type": "SHSW-1", "auth": True, "gen": gen},
|
||||||
), patch(
|
), patch(
|
||||||
"aioshelly.block_device.BlockDevice.create",
|
"aioshelly.block_device.BlockDevice.create",
|
||||||
new=AsyncMock(side_effect=exc),
|
new=AsyncMock(side_effect=InvalidAuthError),
|
||||||
), patch(
|
), patch(
|
||||||
"aioshelly.rpc_device.RpcDevice.create", new=AsyncMock(side_effect=exc)
|
"aioshelly.rpc_device.RpcDevice.create",
|
||||||
|
new=AsyncMock(side_effect=InvalidAuthError),
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -889,11 +851,7 @@ async def test_reauth_unsuccessful(hass, test_data):
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error",
|
"error",
|
||||||
[
|
[DeviceConnectionError, FirmwareUnsupported],
|
||||||
asyncio.TimeoutError,
|
|
||||||
aiohttp.ClientError,
|
|
||||||
aioshelly.exceptions.FirmwareUnsupported,
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
async def test_reauth_get_info_error(hass, error):
|
async def test_reauth_get_info_error(hass, error):
|
||||||
"""Test reauthentication flow failed with error in get_info()."""
|
"""Test reauthentication flow failed with error in get_info()."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user