Bump aioshelly to 9.0.0 (#114025)

* Update Shelly to use initialize from aioshelly

* Save indentation in _async_device_connect

* Use firmware_supported property from aioshelly

* Review comments

* Bump aioshelly

* Fix lint errors

* Test RPC initialized update
This commit is contained in:
Shay Levy 2024-04-14 18:07:26 +03:00 committed by GitHub
parent 33412dd9f6
commit 291df6dafe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 349 additions and 257 deletions

View File

@ -3,23 +3,22 @@
from __future__ import annotations from __future__ import annotations
import contextlib import contextlib
from typing import Any, Final from typing import Final
from aioshelly.block_device import BlockDevice, BlockUpdateType from aioshelly.block_device import BlockDevice
from aioshelly.common import ConnectionOptions from aioshelly.common import ConnectionOptions
from aioshelly.const import DEFAULT_COAP_PORT, RPC_GENERATIONS from aioshelly.const import DEFAULT_COAP_PORT, RPC_GENERATIONS
from aioshelly.exceptions import ( from aioshelly.exceptions import (
DeviceConnectionError, DeviceConnectionError,
FirmwareUnsupported,
InvalidAuthError, InvalidAuthError,
MacAddressMismatchError, MacAddressMismatchError,
) )
from aioshelly.rpc_device import RpcDevice, RpcUpdateType from aioshelly.rpc_device import RpcDevice
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import issue_registry as ir from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -53,12 +52,9 @@ from .coordinator import (
) )
from .utils import ( from .utils import (
async_create_issue_unsupported_firmware, async_create_issue_unsupported_firmware,
async_shutdown_device,
get_block_device_sleep_period,
get_coap_context, get_coap_context,
get_device_entry_gen, get_device_entry_gen,
get_http_port, get_http_port,
get_rpc_device_wakeup_period,
get_ws_context, get_ws_context,
) )
@ -154,7 +150,6 @@ async def _async_setup_block_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
async_get_clientsession(hass), async_get_clientsession(hass),
coap_context, coap_context,
options, options,
False,
) )
dev_reg = dr_async_get(hass) dev_reg = dr_async_get(hass)
@ -186,57 +181,38 @@ async def _async_setup_block_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
data[CONF_SLEEP_PERIOD] = sleep_period = BLOCK_EXPECTED_SLEEP_PERIOD data[CONF_SLEEP_PERIOD] = sleep_period = BLOCK_EXPECTED_SLEEP_PERIOD
hass.config_entries.async_update_entry(entry, data=data) hass.config_entries.async_update_entry(entry, data=data)
async def _async_block_device_setup() -> None:
"""Set up a block based device that is online."""
shelly_entry_data.block = ShellyBlockCoordinator(hass, entry, device)
shelly_entry_data.block.async_setup()
platforms = BLOCK_SLEEPING_PLATFORMS
if not entry.data.get(CONF_SLEEP_PERIOD):
shelly_entry_data.rest = ShellyRestCoordinator(hass, device, entry)
platforms = BLOCK_PLATFORMS
await hass.config_entries.async_forward_entry_setups(entry, platforms)
@callback
def _async_device_online(_: Any, update_type: BlockUpdateType) -> None:
LOGGER.debug("Device %s is online, resuming setup", entry.title)
shelly_entry_data.device = None
if sleep_period is None:
data = {**entry.data}
data[CONF_SLEEP_PERIOD] = get_block_device_sleep_period(device.settings)
data["model"] = device.settings["device"]["type"]
hass.config_entries.async_update_entry(entry, data=data)
hass.async_create_task(_async_block_device_setup(), eager_start=True)
if sleep_period == 0: if sleep_period == 0:
# 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:
await device.initialize() await device.initialize()
if not device.firmware_supported:
async_create_issue_unsupported_firmware(hass, entry)
raise ConfigEntryNotReady
except (DeviceConnectionError, MacAddressMismatchError) as err: except (DeviceConnectionError, MacAddressMismatchError) as err:
raise ConfigEntryNotReady(repr(err)) from err raise ConfigEntryNotReady(repr(err)) from err
except InvalidAuthError as err: except InvalidAuthError as err:
raise ConfigEntryAuthFailed(repr(err)) from err raise ConfigEntryAuthFailed(repr(err)) from err
except FirmwareUnsupported as err:
async_create_issue_unsupported_firmware(hass, entry)
raise ConfigEntryNotReady from err
await _async_block_device_setup() shelly_entry_data.block = ShellyBlockCoordinator(hass, entry, device)
shelly_entry_data.block.async_setup()
shelly_entry_data.rest = ShellyRestCoordinator(hass, device, entry)
await hass.config_entries.async_forward_entry_setups(entry, BLOCK_PLATFORMS)
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
shelly_entry_data.device = device
LOGGER.debug( LOGGER.debug(
"Setup for device %s will resume when device is online", entry.title "Setup for device %s will resume when device is online", entry.title
) )
device.subscribe_updates(_async_device_online) shelly_entry_data.block = ShellyBlockCoordinator(hass, entry, device)
shelly_entry_data.block.async_setup(BLOCK_SLEEPING_PLATFORMS)
else: else:
# Restore sensors for sleeping device # Restore sensors for sleeping device
LOGGER.debug("Setting up offline block device %s", entry.title) LOGGER.debug("Setting up offline block device %s", entry.title)
await _async_block_device_setup() shelly_entry_data.block = ShellyBlockCoordinator(hass, entry, device)
shelly_entry_data.block.async_setup()
await hass.config_entries.async_forward_entry_setups(
entry, BLOCK_SLEEPING_PLATFORMS
)
ir.async_delete_issue( ir.async_delete_issue(
hass, DOMAIN, FIRMWARE_UNSUPPORTED_ISSUE_ID.format(unique=entry.unique_id) hass, DOMAIN, FIRMWARE_UNSUPPORTED_ISSUE_ID.format(unique=entry.unique_id)
@ -260,7 +236,6 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> boo
async_get_clientsession(hass), async_get_clientsession(hass),
ws_context, ws_context,
options, options,
False,
) )
dev_reg = dr_async_get(hass) dev_reg = dr_async_get(hass)
@ -276,58 +251,38 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> boo
sleep_period = entry.data.get(CONF_SLEEP_PERIOD) sleep_period = entry.data.get(CONF_SLEEP_PERIOD)
shelly_entry_data = get_entry_data(hass)[entry.entry_id] shelly_entry_data = get_entry_data(hass)[entry.entry_id]
async def _async_rpc_device_setup() -> None:
"""Set up a RPC based device that is online."""
shelly_entry_data.rpc = ShellyRpcCoordinator(hass, entry, device)
shelly_entry_data.rpc.async_setup()
platforms = RPC_SLEEPING_PLATFORMS
if not entry.data.get(CONF_SLEEP_PERIOD):
shelly_entry_data.rpc_poll = ShellyRpcPollingCoordinator(
hass, entry, device
)
platforms = RPC_PLATFORMS
await hass.config_entries.async_forward_entry_setups(entry, platforms)
@callback
def _async_device_online(_: Any, update_type: RpcUpdateType) -> None:
LOGGER.debug("Device %s is online, resuming setup", entry.title)
shelly_entry_data.device = None
if sleep_period is None:
data = {**entry.data}
data[CONF_SLEEP_PERIOD] = get_rpc_device_wakeup_period(device.status)
hass.config_entries.async_update_entry(entry, data=data)
hass.async_create_task(_async_rpc_device_setup(), eager_start=True)
if sleep_period == 0: if sleep_period == 0:
# 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:
await device.initialize() await device.initialize()
except FirmwareUnsupported as err: if not device.firmware_supported:
async_create_issue_unsupported_firmware(hass, entry) async_create_issue_unsupported_firmware(hass, entry)
raise ConfigEntryNotReady from err raise ConfigEntryNotReady
except (DeviceConnectionError, MacAddressMismatchError) as err: except (DeviceConnectionError, MacAddressMismatchError) as err:
raise ConfigEntryNotReady(repr(err)) from err raise ConfigEntryNotReady(repr(err)) from err
except InvalidAuthError as err: except InvalidAuthError as err:
raise ConfigEntryAuthFailed(repr(err)) from err raise ConfigEntryAuthFailed(repr(err)) from err
await _async_rpc_device_setup() shelly_entry_data.rpc = ShellyRpcCoordinator(hass, entry, device)
shelly_entry_data.rpc.async_setup()
shelly_entry_data.rpc_poll = ShellyRpcPollingCoordinator(hass, entry, device)
await hass.config_entries.async_forward_entry_setups(entry, RPC_PLATFORMS)
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
shelly_entry_data.device = device
LOGGER.debug( LOGGER.debug(
"Setup for device %s will resume when device is online", entry.title "Setup for device %s will resume when device is online", entry.title
) )
device.subscribe_updates(_async_device_online) shelly_entry_data.rpc = ShellyRpcCoordinator(hass, entry, device)
shelly_entry_data.rpc.async_setup(RPC_SLEEPING_PLATFORMS)
else: else:
# Restore sensors for sleeping device # Restore sensors for sleeping device
LOGGER.debug("Setting up offline block device %s", entry.title) LOGGER.debug("Setting up offline RPC device %s", entry.title)
await _async_rpc_device_setup() shelly_entry_data.rpc = ShellyRpcCoordinator(hass, entry, device)
shelly_entry_data.rpc.async_setup()
await hass.config_entries.async_forward_entry_setups(
entry, RPC_SLEEPING_PLATFORMS
)
ir.async_delete_issue( ir.async_delete_issue(
hass, DOMAIN, FIRMWARE_UNSUPPORTED_ISSUE_ID.format(unique=entry.unique_id) hass, DOMAIN, FIRMWARE_UNSUPPORTED_ISSUE_ID.format(unique=entry.unique_id)
@ -339,11 +294,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
shelly_entry_data = get_entry_data(hass)[entry.entry_id] shelly_entry_data = get_entry_data(hass)[entry.entry_id]
# If device is present, block/rpc coordinator is not setup yet
if (device := shelly_entry_data.device) is not None:
await async_shutdown_device(device)
return True
platforms = RPC_SLEEPING_PLATFORMS platforms = RPC_SLEEPING_PLATFORMS
if not entry.data.get(CONF_SLEEP_PERIOD): if not entry.data.get(CONF_SLEEP_PERIOD):
platforms = RPC_PLATFORMS platforms = RPC_PLATFORMS

View File

@ -11,7 +11,6 @@ from aioshelly.const import BLOCK_GENERATIONS, DEFAULT_HTTP_PORT, RPC_GENERATION
from aioshelly.exceptions import ( from aioshelly.exceptions import (
CustomPortNotSupported, CustomPortNotSupported,
DeviceConnectionError, DeviceConnectionError,
FirmwareUnsupported,
InvalidAuthError, InvalidAuthError,
) )
from aioshelly.rpc_device import RpcDevice from aioshelly.rpc_device import RpcDevice
@ -103,6 +102,7 @@ async def validate_input(
ws_context, ws_context,
options, options,
) )
await rpc_device.initialize()
await rpc_device.shutdown() await rpc_device.shutdown()
sleep_period = get_rpc_device_wakeup_period(rpc_device.status) sleep_period = get_rpc_device_wakeup_period(rpc_device.status)
@ -121,6 +121,7 @@ async def validate_input(
coap_context, coap_context,
options, options,
) )
await block_device.initialize()
block_device.shutdown() block_device.shutdown()
return { return {
"title": block_device.name, "title": block_device.name,
@ -154,8 +155,6 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
self.info = await self._async_get_info(host, port) self.info = await self._async_get_info(host, port)
except DeviceConnectionError: except DeviceConnectionError:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except FirmwareUnsupported:
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")
errors["base"] = "unknown" errors["base"] = "unknown"
@ -287,8 +286,6 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
self.info = await self._async_get_info(host, DEFAULT_HTTP_PORT) self.info = await self._async_get_info(host, DEFAULT_HTTP_PORT)
except DeviceConnectionError: except DeviceConnectionError:
return self.async_abort(reason="cannot_connect") return self.async_abort(reason="cannot_connect")
except FirmwareUnsupported:
return self.async_abort(reason="unsupported_firmware")
if not mac: if not mac:
# We could not get the mac address from the name # We could not get the mac address from the name
@ -366,14 +363,14 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
try: try:
info = await self._async_get_info(host, port) info = await self._async_get_info(host, port)
except (DeviceConnectionError, InvalidAuthError, FirmwareUnsupported): except (DeviceConnectionError, InvalidAuthError):
return self.async_abort(reason="reauth_unsuccessful") return self.async_abort(reason="reauth_unsuccessful")
if get_device_entry_gen(self.entry) != 1: if get_device_entry_gen(self.entry) != 1:
user_input[CONF_USERNAME] = "admin" user_input[CONF_USERNAME] = "admin"
try: try:
await validate_input(self.hass, host, port, info, user_input) await validate_input(self.hass, host, port, info, user_input)
except (DeviceConnectionError, InvalidAuthError, FirmwareUnsupported): except (DeviceConnectionError, InvalidAuthError):
return self.async_abort(reason="reauth_unsuccessful") return self.async_abort(reason="reauth_unsuccessful")
return self.async_update_reload_and_abort( return self.async_update_reload_and_abort(

View File

@ -15,7 +15,12 @@ from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCal
from aioshelly.rpc_device import RpcDevice, RpcUpdateType from aioshelly.rpc_device import RpcDevice, RpcUpdateType
from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import ATTR_DEVICE_ID, CONF_HOST, EVENT_HOMEASSISTANT_STOP from homeassistant.const import (
ATTR_DEVICE_ID,
CONF_HOST,
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
from homeassistant.helpers import issue_registry as ir from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.debounce import Debouncer
@ -58,7 +63,9 @@ from .const import (
BLEScannerMode, BLEScannerMode,
) )
from .utils import ( from .utils import (
async_create_issue_unsupported_firmware,
async_shutdown_device, async_shutdown_device,
get_block_device_sleep_period,
get_device_entry_gen, get_device_entry_gen,
get_http_port, get_http_port,
get_rpc_device_wakeup_period, get_rpc_device_wakeup_period,
@ -73,7 +80,6 @@ class ShellyEntryData:
"""Class for sharing data within a given config entry.""" """Class for sharing data within a given config entry."""
block: ShellyBlockCoordinator | None = None block: ShellyBlockCoordinator | None = None
device: BlockDevice | RpcDevice | None = None
rest: ShellyRestCoordinator | None = None rest: ShellyRestCoordinator | None = None
rpc: ShellyRpcCoordinator | None = None rpc: ShellyRpcCoordinator | None = None
rpc_poll: ShellyRpcPollingCoordinator | None = None rpc_poll: ShellyRpcPollingCoordinator | None = None
@ -98,6 +104,7 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]):
self.entry = entry self.entry = entry
self.device = device self.device = device
self.device_id: str | None = None self.device_id: str | None = None
self._pending_platforms: list[Platform] | None = None
device_name = device.name if device.initialized else entry.title device_name = device.name if device.initialized else entry.title
interval_td = timedelta(seconds=update_interval) interval_td = timedelta(seconds=update_interval)
super().__init__(hass, LOGGER, name=device_name, update_interval=interval_td) super().__init__(hass, LOGGER, name=device_name, update_interval=interval_td)
@ -131,8 +138,9 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]):
"""Sleep period of the device.""" """Sleep period of the device."""
return self.entry.data.get(CONF_SLEEP_PERIOD, 0) return self.entry.data.get(CONF_SLEEP_PERIOD, 0)
def async_setup(self) -> None: def async_setup(self, pending_platforms: list[Platform] | None = None) -> None:
"""Set up the coordinator.""" """Set up the coordinator."""
self._pending_platforms = pending_platforms
dev_reg = dr_async_get(self.hass) dev_reg = dr_async_get(self.hass)
device_entry = dev_reg.async_get_or_create( device_entry = dev_reg.async_get_or_create(
config_entry_id=self.entry.entry_id, config_entry_id=self.entry.entry_id,
@ -146,6 +154,45 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]):
) )
self.device_id = device_entry.id self.device_id = device_entry.id
async def _async_device_connect(self) -> None:
"""Connect to a Shelly Block device."""
LOGGER.debug("Connecting to Shelly Device - %s", self.name)
try:
await self.device.initialize()
update_device_fw_info(self.hass, self.device, self.entry)
except DeviceConnectionError as err:
raise UpdateFailed(f"Device disconnected: {repr(err)}") from err
except InvalidAuthError:
self.entry.async_start_reauth(self.hass)
return
if not self.device.firmware_supported:
async_create_issue_unsupported_firmware(self.hass, self.entry)
return
if not self._pending_platforms:
return
LOGGER.debug("Device %s is online, resuming setup", self.entry.title)
platforms = self._pending_platforms
self._pending_platforms = None
data = {**self.entry.data}
# Update sleep_period
old_sleep_period = data[CONF_SLEEP_PERIOD]
if isinstance(self.device, RpcDevice):
new_sleep_period = get_rpc_device_wakeup_period(self.device.status)
elif isinstance(self.device, BlockDevice):
new_sleep_period = get_block_device_sleep_period(self.device.settings)
if new_sleep_period != old_sleep_period:
data[CONF_SLEEP_PERIOD] = new_sleep_period
self.hass.config_entries.async_update_entry(self.entry, data=data)
# Resume platform setup
await self.hass.config_entries.async_forward_entry_setups(self.entry, platforms)
async def _async_reload_entry(self) -> None: async def _async_reload_entry(self) -> None:
"""Reload entry.""" """Reload entry."""
self._debounced_reload.async_cancel() self._debounced_reload.async_cancel()
@ -179,7 +226,7 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
self._last_cfg_changed: int | None = None self._last_cfg_changed: int | None = None
self._last_mode: str | None = None self._last_mode: str | None = None
self._last_effect: int | None = None self._last_effect: str | None = None
self._last_input_events_count: dict = {} self._last_input_events_count: dict = {}
self._last_target_temp: float | None = None self._last_target_temp: float | None = None
self._push_update_failures: int = 0 self._push_update_failures: int = 0
@ -211,15 +258,14 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
if not self.device.initialized: if not self.device.initialized:
return return
assert self.device.blocks
# For buttons which are battery powered - set initial value for last_event_count # For buttons which are battery powered - set initial value for last_event_count
if self.model in SHBTN_MODELS and self._last_input_events_count.get(1) is None: if self.model in SHBTN_MODELS and self._last_input_events_count.get(1) is None:
for block in self.device.blocks: for block in self.device.blocks:
if block.type != "device": if block.type != "device":
continue continue
if len(block.wakeupEvent) == 1 and block.wakeupEvent[0] == "button": wakeup_event = cast(list, block.wakeupEvent)
if len(wakeup_event) == 1 and wakeup_event[0] == "button":
self._last_input_events_count[1] = -1 self._last_input_events_count[1] = -1
break break
@ -228,7 +274,7 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
cfg_changed = 0 cfg_changed = 0
for block in self.device.blocks: for block in self.device.blocks:
if block.type == "device" and block.cfgChanged is not None: if block.type == "device" and block.cfgChanged is not None:
cfg_changed = block.cfgChanged cfg_changed = cast(int, block.cfgChanged)
# Shelly TRV sends information about changing the configuration for no # Shelly TRV sends information about changing the configuration for no
# reason, reloading the config entry is not needed for it. # reason, reloading the config entry is not needed for it.
@ -314,14 +360,16 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
self, device_: BlockDevice, update_type: BlockUpdateType self, device_: BlockDevice, update_type: BlockUpdateType
) -> None: ) -> None:
"""Handle device update.""" """Handle device update."""
if update_type == BlockUpdateType.COAP_PERIODIC: if update_type is BlockUpdateType.ONLINE:
self.hass.async_create_task(self._async_device_connect(), eager_start=True)
elif update_type is BlockUpdateType.COAP_PERIODIC:
self._push_update_failures = 0 self._push_update_failures = 0
ir.async_delete_issue( ir.async_delete_issue(
self.hass, self.hass,
DOMAIN, DOMAIN,
PUSH_UPDATE_ISSUE_ID.format(unique=self.mac), PUSH_UPDATE_ISSUE_ID.format(unique=self.mac),
) )
elif update_type == BlockUpdateType.COAP_REPLY: elif update_type is BlockUpdateType.COAP_REPLY:
self._push_update_failures += 1 self._push_update_failures += 1
if self._push_update_failures == MAX_PUSH_UPDATE_FAILURES: if self._push_update_failures == MAX_PUSH_UPDATE_FAILURES:
LOGGER.debug( LOGGER.debug(
@ -346,9 +394,9 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
) )
self.async_set_updated_data(None) self.async_set_updated_data(None)
def async_setup(self) -> None: def async_setup(self, pending_platforms: list[Platform] | None = None) -> None:
"""Set up the coordinator.""" """Set up the coordinator."""
super().async_setup() super().async_setup(pending_platforms)
self.device.subscribe_updates(self._async_handle_update) self.device.subscribe_updates(self._async_handle_update)
def shutdown(self) -> None: def shutdown(self) -> None:
@ -538,14 +586,7 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
if self.device.connected: if self.device.connected:
return return
LOGGER.debug("Reconnecting to Shelly RPC Device - %s", self.name) await self._async_device_connect()
try:
await self.device.initialize()
update_device_fw_info(self.hass, self.device, self.entry)
except DeviceConnectionError as err:
raise UpdateFailed(f"Device disconnected: {repr(err)}") from err
except InvalidAuthError:
await self.async_shutdown_device_and_start_reauth()
async def _async_disconnected(self) -> None: async def _async_disconnected(self) -> None:
"""Handle device disconnected.""" """Handle device disconnected."""
@ -612,7 +653,9 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
self, device_: RpcDevice, update_type: RpcUpdateType self, device_: RpcDevice, update_type: RpcUpdateType
) -> None: ) -> None:
"""Handle device update.""" """Handle device update."""
if update_type is RpcUpdateType.INITIALIZED: if update_type is RpcUpdateType.ONLINE:
self.hass.async_create_task(self._async_device_connect(), eager_start=True)
elif update_type is RpcUpdateType.INITIALIZED:
self.hass.async_create_task(self._async_connected(), eager_start=True) self.hass.async_create_task(self._async_connected(), eager_start=True)
self.async_set_updated_data(None) self.async_set_updated_data(None)
elif update_type is RpcUpdateType.DISCONNECTED: elif update_type is RpcUpdateType.DISCONNECTED:
@ -624,9 +667,9 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
elif update_type is RpcUpdateType.EVENT and (event := self.device.event): elif update_type is RpcUpdateType.EVENT and (event := self.device.event):
self._async_device_event_handler(event) self._async_device_event_handler(event)
def async_setup(self) -> None: def async_setup(self, pending_platforms: list[Platform] | None = None) -> None:
"""Set up the coordinator.""" """Set up the coordinator."""
super().async_setup() super().async_setup(pending_platforms)
self.device.subscribe_updates(self._async_handle_update) self.device.subscribe_updates(self._async_handle_update)
if self.device.initialized: if self.device.initialized:
# If we are already initialized, we are connected # If we are already initialized, we are connected

View File

@ -74,7 +74,7 @@ def async_setup_block_attribute_entities(
for block in coordinator.device.blocks: for block in coordinator.device.blocks:
for sensor_id in block.sensor_ids: for sensor_id in block.sensor_ids:
description = sensors.get((block.type, sensor_id)) description = sensors.get((cast(str, block.type), sensor_id))
if description is None: if description is None:
continue continue

View File

@ -78,7 +78,7 @@ def async_setup_block_entry(
for block in coordinator.device.blocks: for block in coordinator.device.blocks:
if block.type == "light": if block.type == "light":
blocks.append(block) blocks.append(block)
elif block.type == "relay": elif block.type == "relay" and block.channel is not None:
if not is_block_channel_type_light( if not is_block_channel_type_light(
coordinator.device.settings, int(block.channel) coordinator.device.settings, int(block.channel)
): ):

View File

@ -9,7 +9,7 @@
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["aioshelly"], "loggers": ["aioshelly"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["aioshelly==8.2.0"], "requirements": ["aioshelly==9.0.0"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_http._tcp.local.", "type": "_http._tcp.local.",

View File

@ -38,7 +38,6 @@
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"unsupported_firmware": "The device is using an unsupported firmware version.",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reauth_unsuccessful": "Re-authentication was unsuccessful, please remove the integration and set it up again." "reauth_unsuccessful": "Re-authentication was unsuccessful, please remove the integration and set it up again."
} }

View File

@ -104,8 +104,12 @@ def async_setup_block_entry(
relay_blocks = [] relay_blocks = []
assert coordinator.device.blocks assert coordinator.device.blocks
for block in coordinator.device.blocks: for block in coordinator.device.blocks:
if block.type != "relay" or is_block_channel_type_light( if (
coordinator.device.settings, int(block.channel) block.type != "relay"
or block.channel is not None
and is_block_channel_type_light(
coordinator.device.settings, int(block.channel)
)
): ):
continue continue

View File

@ -368,7 +368,7 @@ aioruuvigateway==0.1.0
aiosenz==1.0.0 aiosenz==1.0.0
# homeassistant.components.shelly # homeassistant.components.shelly
aioshelly==8.2.0 aioshelly==9.0.0
# homeassistant.components.skybell # homeassistant.components.skybell
aioskybell==22.7.0 aioskybell==22.7.0

View File

@ -341,7 +341,7 @@ aioruuvigateway==0.1.0
aiosenz==1.0.0 aiosenz==1.0.0
# homeassistant.components.shelly # homeassistant.components.shelly
aioshelly==8.2.0 aioshelly==9.0.0
# homeassistant.components.skybell # homeassistant.components.skybell
aioskybell==22.7.0 aioskybell==22.7.0

View File

@ -319,6 +319,11 @@ async def mock_block_device():
{}, BlockUpdateType.COAP_REPLY {}, BlockUpdateType.COAP_REPLY
) )
def online():
block_device_mock.return_value.subscribe_updates.call_args[0][0](
{}, BlockUpdateType.ONLINE
)
device = Mock( device = Mock(
spec=BlockDevice, spec=BlockDevice,
blocks=MOCK_BLOCKS, blocks=MOCK_BLOCKS,
@ -337,6 +342,7 @@ async def mock_block_device():
block_device_mock.return_value.mock_update_reply = Mock( block_device_mock.return_value.mock_update_reply = Mock(
side_effect=update_reply side_effect=update_reply
) )
block_device_mock.return_value.mock_online = Mock(side_effect=online)
yield block_device_mock.return_value yield block_device_mock.return_value
@ -376,16 +382,28 @@ async def mock_rpc_device():
{}, RpcUpdateType.EVENT {}, RpcUpdateType.EVENT
) )
def online():
rpc_device_mock.return_value.subscribe_updates.call_args[0][0](
{}, RpcUpdateType.ONLINE
)
def disconnected(): def disconnected():
rpc_device_mock.return_value.subscribe_updates.call_args[0][0]( rpc_device_mock.return_value.subscribe_updates.call_args[0][0](
{}, RpcUpdateType.DISCONNECTED {}, RpcUpdateType.DISCONNECTED
) )
def initialized():
rpc_device_mock.return_value.subscribe_updates.call_args[0][0](
{}, RpcUpdateType.INITIALIZED
)
device = _mock_rpc_device() device = _mock_rpc_device()
rpc_device_mock.return_value = device rpc_device_mock.return_value = device
rpc_device_mock.return_value.mock_disconnected = Mock(side_effect=disconnected) rpc_device_mock.return_value.mock_disconnected = Mock(side_effect=disconnected)
rpc_device_mock.return_value.mock_update = Mock(side_effect=update) rpc_device_mock.return_value.mock_update = Mock(side_effect=update)
rpc_device_mock.return_value.mock_event = Mock(side_effect=event) rpc_device_mock.return_value.mock_event = Mock(side_effect=event)
rpc_device_mock.return_value.mock_online = Mock(side_effect=online)
rpc_device_mock.return_value.mock_initialized = Mock(side_effect=initialized)
yield rpc_device_mock.return_value yield rpc_device_mock.return_value

View File

@ -144,7 +144,7 @@ async def test_block_sleeping_binary_sensor(
assert hass.states.get(entity_id) is None assert hass.states.get(entity_id) is None
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_OFF assert hass.states.get(entity_id).state == STATE_OFF
@ -180,7 +180,7 @@ async def test_block_restored_sleeping_binary_sensor(
# Make device online # Make device online
monkeypatch.setattr(mock_block_device, "initialized", True) monkeypatch.setattr(mock_block_device, "initialized", True)
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_OFF assert hass.states.get(entity_id).state == STATE_OFF
@ -206,7 +206,7 @@ async def test_block_restored_sleeping_binary_sensor_no_last_state(
# Make device online # Make device online
monkeypatch.setattr(mock_block_device, "initialized", True) monkeypatch.setattr(mock_block_device, "initialized", True)
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_OFF assert hass.states.get(entity_id).state == STATE_OFF
@ -263,6 +263,7 @@ async def test_rpc_sleeping_binary_sensor(
) -> None: ) -> None:
"""Test RPC online sleeping binary sensor.""" """Test RPC online sleeping binary sensor."""
entity_id = f"{BINARY_SENSOR_DOMAIN}.test_name_cloud" entity_id = f"{BINARY_SENSOR_DOMAIN}.test_name_cloud"
monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
config_entry = await init_integration(hass, 2, sleep_period=1000) config_entry = await init_integration(hass, 2, sleep_period=1000)
# Sensor should be created when device is online # Sensor should be created when device is online
@ -273,7 +274,7 @@ async def test_rpc_sleeping_binary_sensor(
) )
# Make device online # Make device online
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_OFF assert hass.states.get(entity_id).state == STATE_OFF
@ -344,6 +345,10 @@ async def test_rpc_restored_sleeping_binary_sensor_no_last_state(
# Make device online # Make device online
monkeypatch.setattr(mock_rpc_device, "initialized", True) monkeypatch.setattr(mock_rpc_device, "initialized", True)
mock_rpc_device.mock_online()
await hass.async_block_till_done()
# Mock update
mock_rpc_device.mock_update() mock_rpc_device.mock_update()
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -64,7 +64,7 @@ async def test_climate_hvac_mode(
await init_integration(hass, 1, sleep_period=1000, model=MODEL_VALVE) await init_integration(hass, 1, sleep_period=1000, model=MODEL_VALVE)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
# Test initial hvac mode - off # Test initial hvac mode - off
@ -125,7 +125,7 @@ async def test_climate_set_temperature(
await init_integration(hass, 1, sleep_period=1000) await init_integration(hass, 1, sleep_period=1000)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
@ -192,7 +192,7 @@ async def test_climate_set_preset_mode(
await init_integration(hass, 1, sleep_period=1000, model=MODEL_VALVE) await init_integration(hass, 1, sleep_period=1000, model=MODEL_VALVE)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
@ -278,7 +278,7 @@ async def test_block_restored_climate(
# Make device online # Make device online
monkeypatch.setattr(mock_block_device, "initialized", True) monkeypatch.setattr(mock_block_device, "initialized", True)
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == HVACMode.OFF assert hass.states.get(entity_id).state == HVACMode.OFF
@ -349,7 +349,7 @@ async def test_block_restored_climate_us_customery(
monkeypatch.setattr(mock_block_device, "initialized", True) monkeypatch.setattr(mock_block_device, "initialized", True)
monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "targetTemp", 4.0) monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "targetTemp", 4.0)
monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "temp", 18.2) monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "temp", 18.2)
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == HVACMode.OFF assert hass.states.get(entity_id).state == HVACMode.OFF
@ -451,7 +451,7 @@ async def test_block_set_mode_connection_error(
await init_integration(hass, 1, sleep_period=1000) await init_integration(hass, 1, sleep_period=1000)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
with pytest.raises(HomeAssistantError): with pytest.raises(HomeAssistantError):
@ -476,7 +476,7 @@ async def test_block_set_mode_auth_error(
entry = await init_integration(hass, 1, sleep_period=1000) entry = await init_integration(hass, 1, sleep_period=1000)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
@ -534,7 +534,7 @@ async def test_block_restored_climate_auth_error(
type(mock_block_device).settings = PropertyMock( type(mock_block_device).settings = PropertyMock(
return_value={}, side_effect=InvalidAuthError return_value={}, side_effect=InvalidAuthError
) )
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
@ -561,7 +561,7 @@ async def test_device_not_calibrated(
await init_integration(hass, 1, sleep_period=1000, model=MODEL_VALVE) await init_integration(hass, 1, sleep_period=1000, model=MODEL_VALVE)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
mock_status = MOCK_STATUS_COAP.copy() mock_status = MOCK_STATUS_COAP.copy()

View File

@ -10,7 +10,6 @@ from aioshelly.const import DEFAULT_HTTP_PORT, MODEL_1, MODEL_PLUS_2PM
from aioshelly.exceptions import ( from aioshelly.exceptions import (
CustomPortNotSupported, CustomPortNotSupported,
DeviceConnectionError, DeviceConnectionError,
FirmwareUnsupported,
InvalidAuthError, InvalidAuthError,
) )
import pytest import pytest
@ -433,25 +432,6 @@ async def test_user_setup_ignored_device(
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_form_firmware_unsupported(hass: HomeAssistant) -> None:
"""Test we abort if device firmware is unsupported."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.shelly.config_flow.get_info",
side_effect=FirmwareUnsupported,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"host": "1.1.1.1"},
)
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "unsupported_firmware"
@pytest.mark.parametrize( @pytest.mark.parametrize(
("exc", "base_error"), ("exc", "base_error"),
[ [
@ -757,22 +737,6 @@ async def test_zeroconf_with_wifi_ap_ip(hass: HomeAssistant) -> None:
assert entry.data["host"] == "2.2.2.2" assert entry.data["host"] == "2.2.2.2"
async def test_zeroconf_firmware_unsupported(hass: HomeAssistant) -> None:
"""Test we abort if device firmware is unsupported."""
with patch(
"homeassistant.components.shelly.config_flow.get_info",
side_effect=FirmwareUnsupported,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
data=DISCOVERY_INFO,
context={"source": config_entries.SOURCE_ZEROCONF},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unsupported_firmware"
async def test_zeroconf_cannot_connect(hass: HomeAssistant) -> None: async def test_zeroconf_cannot_connect(hass: HomeAssistant) -> None:
"""Test we get the form.""" """Test we get the form."""
with patch( with patch(
@ -927,11 +891,7 @@ async def test_reauth_unsuccessful(
assert result["reason"] == "reauth_unsuccessful" assert result["reason"] == "reauth_unsuccessful"
@pytest.mark.parametrize( async def test_reauth_get_info_error(hass: HomeAssistant) -> None:
"error",
[DeviceConnectionError, FirmwareUnsupported],
)
async def test_reauth_get_info_error(hass: HomeAssistant, error: Exception) -> None:
"""Test reauthentication flow failed with error in get_info().""" """Test reauthentication flow failed with error in get_info()."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain="shelly", unique_id="test-mac", data={"host": "0.0.0.0", "gen": 2} domain="shelly", unique_id="test-mac", data={"host": "0.0.0.0", "gen": 2}
@ -940,7 +900,7 @@ async def test_reauth_get_info_error(hass: HomeAssistant, error: Exception) -> N
with patch( with patch(
"homeassistant.components.shelly.config_flow.get_info", "homeassistant.components.shelly.config_flow.get_info",
side_effect=error, side_effect=DeviceConnectionError,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -1154,6 +1114,7 @@ async def test_zeroconf_sleeping_device_not_triggers_refresh(
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
"""Test zeroconf discovery does not triggers refresh for sleeping device.""" """Test zeroconf discovery does not triggers refresh for sleeping device."""
monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
entry = MockConfigEntry( entry = MockConfigEntry(
domain="shelly", domain="shelly",
unique_id="AABBCCDDEEFF", unique_id="AABBCCDDEEFF",
@ -1163,10 +1124,11 @@ async def test_zeroconf_sleeping_device_not_triggers_refresh(
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert "online, resuming setup" in caplog.text assert "online, resuming setup" in caplog.text
assert len(mock_rpc_device.initialize.mock_calls) == 1
with patch( with patch(
"homeassistant.components.shelly.config_flow.get_info", "homeassistant.components.shelly.config_flow.get_info",
@ -1186,7 +1148,7 @@ async def test_zeroconf_sleeping_device_not_triggers_refresh(
hass, dt_util.utcnow() + timedelta(seconds=ENTRY_RELOAD_COOLDOWN) hass, dt_util.utcnow() + timedelta(seconds=ENTRY_RELOAD_COOLDOWN)
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(mock_rpc_device.initialize.mock_calls) == 0 assert len(mock_rpc_device.initialize.mock_calls) == 1
assert "device did not update" not in caplog.text assert "device did not update" not in caplog.text

View File

@ -1,14 +1,10 @@
"""Tests for Shelly coordinator.""" """Tests for Shelly coordinator."""
from datetime import timedelta from datetime import timedelta
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, call, patch
from aioshelly.const import MODEL_BULB, MODEL_BUTTON1 from aioshelly.const import MODEL_BULB, MODEL_BUTTON1
from aioshelly.exceptions import ( from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
DeviceConnectionError,
FirmwareUnsupported,
InvalidAuthError,
)
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
@ -29,13 +25,13 @@ from homeassistant.components.shelly.const import (
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import ATTR_DEVICE_ID, STATE_ON, STATE_UNAVAILABLE from homeassistant.const import ATTR_DEVICE_ID, STATE_ON, STATE_UNAVAILABLE
from homeassistant.core import Event, HomeAssistant from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.device_registry import ( from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC, CONNECTION_NETWORK_MAC,
async_entries_for_config_entry, async_entries_for_config_entry,
async_get as async_get_dev_reg, async_get as async_get_dev_reg,
format_mac, format_mac,
) )
import homeassistant.helpers.issue_registry as ir
from . import ( from . import (
MOCK_MAC, MOCK_MAC,
@ -216,28 +212,25 @@ async def test_block_rest_update_auth_error(
assert flow["context"].get("entry_id") == entry.entry_id assert flow["context"].get("entry_id") == entry.entry_id
async def test_block_firmware_unsupported( async def test_block_sleeping_device_firmware_unsupported(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_block_device: Mock, mock_block_device: Mock,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
issue_registry: ir.IssueRegistry,
) -> None: ) -> None:
"""Test block device polling authentication error.""" """Test block sleeping device firmware not supported."""
monkeypatch.setattr( monkeypatch.setattr(mock_block_device, "firmware_supported", False)
mock_block_device, entry = await init_integration(hass, 1, sleep_period=3600)
"update",
AsyncMock(side_effect=FirmwareUnsupported),
)
entry = await init_integration(hass, 1)
assert entry.state is ConfigEntryState.LOADED # Make device online
mock_block_device.mock_online()
# Move time to generate polling
freezer.tick(timedelta(seconds=UPDATE_PERIOD_MULTIPLIER * 15))
async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
assert (
DOMAIN,
"firmware_unsupported_123456789ABC",
) in issue_registry.issues
async def test_block_polling_connection_error( async def test_block_polling_connection_error(
@ -290,20 +283,28 @@ async def test_block_rest_update_connection_error(
async def test_block_sleeping_device_no_periodic_updates( async def test_block_sleeping_device_no_periodic_updates(
hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_block_device: Mock hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_block_device: Mock,
monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Test block sleeping device no periodic updates.""" """Test block sleeping device no periodic updates."""
entity_id = f"{SENSOR_DOMAIN}.test_name_temperature" entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
await init_integration(hass, 1, sleep_period=1000) monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
await init_integration(hass, 1, sleep_period=3600)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert get_entity_state(hass, entity_id) == "22.1" assert get_entity_state(hass, entity_id) == "22.1"
# Move time to generate polling # Move time to generate polling
freezer.tick(timedelta(seconds=UPDATE_PERIOD_MULTIPLIER * 1000)) freezer.tick(timedelta(seconds=UPDATE_PERIOD_MULTIPLIER * 3600))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -352,7 +353,7 @@ async def test_block_button_click_event(
entry = await init_integration(hass, 1, model=MODEL_BUTTON1, sleep_period=1000) entry = await init_integration(hass, 1, model=MODEL_BUTTON1, sleep_period=1000)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
dev_reg = async_get_dev_reg(hass) dev_reg = async_get_dev_reg(hass)
@ -529,6 +530,7 @@ async def test_rpc_update_entry_sleep_period(
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Test RPC update entry sleep period.""" """Test RPC update entry sleep period."""
monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 600)
entry = await init_integration(hass, 2, sleep_period=600) entry = await init_integration(hass, 2, sleep_period=600)
register_entity( register_entity(
hass, hass,
@ -539,7 +541,7 @@ async def test_rpc_update_entry_sleep_period(
) )
# Make device online # Make device online
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.data["sleep_period"] == 600 assert entry.data["sleep_period"] == 600
@ -554,10 +556,14 @@ async def test_rpc_update_entry_sleep_period(
async def test_rpc_sleeping_device_no_periodic_updates( async def test_rpc_sleeping_device_no_periodic_updates(
hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_rpc_device: Mock hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Test RPC sleeping device no periodic updates.""" """Test RPC sleeping device no periodic updates."""
entity_id = f"{SENSOR_DOMAIN}.test_name_temperature" entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
entry = await init_integration(hass, 2, sleep_period=1000) entry = await init_integration(hass, 2, sleep_period=1000)
register_entity( register_entity(
hass, hass,
@ -568,7 +574,7 @@ async def test_rpc_sleeping_device_no_periodic_updates(
) )
# Make device online # Make device online
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert get_entity_state(hass, entity_id) == "22.9" assert get_entity_state(hass, entity_id) == "22.9"
@ -581,25 +587,25 @@ async def test_rpc_sleeping_device_no_periodic_updates(
assert get_entity_state(hass, entity_id) is STATE_UNAVAILABLE assert get_entity_state(hass, entity_id) is STATE_UNAVAILABLE
async def test_rpc_firmware_unsupported( async def test_rpc_sleeping_device_firmware_unsupported(
hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_rpc_device: Mock hass: HomeAssistant,
mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch,
issue_registry: ir.IssueRegistry,
) -> None: ) -> None:
"""Test RPC update entry unsupported firmware.""" """Test RPC sleeping device firmware not supported."""
entry = await init_integration(hass, 2) monkeypatch.setattr(mock_rpc_device, "firmware_supported", False)
register_entity( entry = await init_integration(hass, 2, sleep_period=3600)
hass,
SENSOR_DOMAIN,
"test_name_temperature",
"temperature:0-temperature_0",
entry,
)
# Move time to generate sleep period update # Make device online
freezer.tick(timedelta(seconds=600 * SLEEP_PERIOD_MULTIPLIER)) mock_rpc_device.mock_online()
async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
assert (
DOMAIN,
"firmware_unsupported_123456789ABC",
) in issue_registry.issues
async def test_rpc_reconnect_auth_error( async def test_rpc_reconnect_auth_error(
@ -753,11 +759,12 @@ async def test_rpc_update_entry_fw_ver(
hass: HomeAssistant, mock_rpc_device: Mock, monkeypatch: pytest.MonkeyPatch hass: HomeAssistant, mock_rpc_device: Mock, monkeypatch: pytest.MonkeyPatch
) -> None: ) -> None:
"""Test RPC update entry firmware version.""" """Test RPC update entry firmware version."""
monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 600)
entry = await init_integration(hass, 2, sleep_period=600) entry = await init_integration(hass, 2, sleep_period=600)
dev_reg = async_get_dev_reg(hass) dev_reg = async_get_dev_reg(hass)
# Make device online # Make device online
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.unique_id assert entry.unique_id
@ -779,3 +786,23 @@ async def test_rpc_update_entry_fw_ver(
) )
assert device assert device
assert device.sw_version == "99.0.0" assert device.sw_version == "99.0.0"
async def test_rpc_runs_connected_events_when_initialized(
hass: HomeAssistant,
mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test RPC runs connected events when initialized."""
monkeypatch.setattr(mock_rpc_device, "initialized", False)
await init_integration(hass, 2)
assert call.script_list() not in mock_rpc_device.mock_calls
# Mock initialized event
monkeypatch.setattr(mock_rpc_device, "initialized", True)
mock_rpc_device.mock_initialized()
await hass.async_block_till_done()
# BLE script list is called during connected events
assert call.script_list() in mock_rpc_device.mock_calls

View File

@ -8,7 +8,6 @@ from aioshelly.common import ConnectionOptions
from aioshelly.const import MODEL_PLUS_2PM from aioshelly.const import MODEL_PLUS_2PM
from aioshelly.exceptions import ( from aioshelly.exceptions import (
DeviceConnectionError, DeviceConnectionError,
FirmwareUnsupported,
InvalidAuthError, InvalidAuthError,
MacAddressMismatchError, MacAddressMismatchError,
) )
@ -27,6 +26,7 @@ from homeassistant.components.shelly.const import (
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import CONF_HOST, CONF_PORT, STATE_ON, STATE_UNAVAILABLE from homeassistant.const import CONF_HOST, CONF_PORT, STATE_ON, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.device_registry import ( from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC, CONNECTION_NETWORK_MAC,
DeviceRegistry, DeviceRegistry,
@ -145,27 +145,46 @@ async def test_setup_entry_not_shelly(
@pytest.mark.parametrize("gen", [1, 2, 3]) @pytest.mark.parametrize("gen", [1, 2, 3])
@pytest.mark.parametrize("side_effect", [DeviceConnectionError, FirmwareUnsupported])
async def test_device_connection_error( async def test_device_connection_error(
hass: HomeAssistant, hass: HomeAssistant,
gen: int, gen: int,
side_effect: Exception,
mock_block_device: Mock, mock_block_device: Mock,
mock_rpc_device: Mock, mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Test device connection error.""" """Test device connection error."""
monkeypatch.setattr( monkeypatch.setattr(
mock_block_device, "initialize", AsyncMock(side_effect=side_effect) mock_block_device, "initialize", AsyncMock(side_effect=DeviceConnectionError)
) )
monkeypatch.setattr( monkeypatch.setattr(
mock_rpc_device, "initialize", AsyncMock(side_effect=side_effect) mock_rpc_device, "initialize", AsyncMock(side_effect=DeviceConnectionError)
) )
entry = await init_integration(hass, gen) entry = await init_integration(hass, gen)
assert entry.state is ConfigEntryState.SETUP_RETRY assert entry.state is ConfigEntryState.SETUP_RETRY
@pytest.mark.parametrize("gen", [1, 2, 3])
async def test_device_unsupported_firmware(
hass: HomeAssistant,
gen: int,
mock_block_device: Mock,
mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch,
issue_registry: ir.IssueRegistry,
) -> None:
"""Test device init with unsupported firmware."""
monkeypatch.setattr(mock_block_device, "firmware_supported", False)
monkeypatch.setattr(mock_rpc_device, "firmware_supported", False)
entry = await init_integration(hass, gen)
assert entry.state is ConfigEntryState.SETUP_RETRY
assert (
DOMAIN,
"firmware_unsupported_123456789ABC",
) in issue_registry.issues
@pytest.mark.parametrize("gen", [1, 2, 3]) @pytest.mark.parametrize("gen", [1, 2, 3])
async def test_mac_mismatch_error( async def test_mac_mismatch_error(
hass: HomeAssistant, hass: HomeAssistant,
@ -217,12 +236,13 @@ async def test_device_auth_error(
assert flow["context"].get("entry_id") == entry.entry_id assert flow["context"].get("entry_id") == entry.entry_id
@pytest.mark.parametrize(("entry_sleep", "device_sleep"), [(None, 0), (1000, 1000)]) @pytest.mark.parametrize(("entry_sleep", "device_sleep"), [(None, 0), (3600, 3600)])
async def test_sleeping_block_device_online( async def test_sleeping_block_device_online(
hass: HomeAssistant, hass: HomeAssistant,
entry_sleep: int | None, entry_sleep: int | None,
device_sleep: int, device_sleep: int,
mock_block_device: Mock, mock_block_device: Mock,
monkeypatch: pytest.MonkeyPatch,
device_reg: DeviceRegistry, device_reg: DeviceRegistry,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
@ -234,10 +254,17 @@ async def test_sleeping_block_device_online(
connections={(CONNECTION_NETWORK_MAC, format_mac(MOCK_MAC))}, connections={(CONNECTION_NETWORK_MAC, format_mac(MOCK_MAC))},
) )
monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": int(device_sleep / 60), "unit": "m"},
)
entry = await init_integration(hass, 1, sleep_period=entry_sleep) entry = await init_integration(hass, 1, sleep_period=entry_sleep)
assert "will resume when device is online" in caplog.text assert "will resume when device is online" in caplog.text
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done()
assert "online, resuming setup" in caplog.text assert "online, resuming setup" in caplog.text
assert entry.data["sleep_period"] == device_sleep assert entry.data["sleep_period"] == device_sleep
@ -248,13 +275,17 @@ async def test_sleeping_rpc_device_online(
entry_sleep: int | None, entry_sleep: int | None,
device_sleep: int, device_sleep: int,
mock_rpc_device: Mock, mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
"""Test sleeping RPC device online.""" """Test sleeping RPC device online."""
monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", device_sleep)
entry = await init_integration(hass, 2, sleep_period=entry_sleep) entry = await init_integration(hass, 2, sleep_period=entry_sleep)
assert "will resume when device is online" in caplog.text assert "will resume when device is online" in caplog.text
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done()
assert "online, resuming setup" in caplog.text assert "online, resuming setup" in caplog.text
assert entry.data["sleep_period"] == device_sleep assert entry.data["sleep_period"] == device_sleep
@ -270,7 +301,9 @@ async def test_sleeping_rpc_device_online_new_firmware(
assert "will resume when device is online" in caplog.text assert "will resume when device is online" in caplog.text
mutate_rpc_device_status(monkeypatch, mock_rpc_device, "sys", "wakeup_period", 1500) mutate_rpc_device_status(monkeypatch, mock_rpc_device, "sys", "wakeup_period", 1500)
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done()
assert "online, resuming setup" in caplog.text assert "online, resuming setup" in caplog.text
assert entry.data["sleep_period"] == 1500 assert entry.data["sleep_period"] == 1500
@ -413,9 +446,12 @@ async def test_entry_missing_port(hass: HomeAssistant) -> None:
} }
entry = MockConfigEntry(domain=DOMAIN, data=data, unique_id=MOCK_MAC) entry = MockConfigEntry(domain=DOMAIN, data=data, unique_id=MOCK_MAC)
entry.add_to_hass(hass) entry.add_to_hass(hass)
with patch( with (
"homeassistant.components.shelly.RpcDevice.create", return_value=Mock() patch("homeassistant.components.shelly.RpcDevice.initialize"),
) as rpc_device_mock: patch(
"homeassistant.components.shelly.RpcDevice.create", return_value=Mock()
) as rpc_device_mock,
):
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -435,9 +471,12 @@ async def test_rpc_entry_custom_port(hass: HomeAssistant) -> None:
} }
entry = MockConfigEntry(domain=DOMAIN, data=data, unique_id=MOCK_MAC) entry = MockConfigEntry(domain=DOMAIN, data=data, unique_id=MOCK_MAC)
entry.add_to_hass(hass) entry.add_to_hass(hass)
with patch( with (
"homeassistant.components.shelly.RpcDevice.create", return_value=Mock() patch("homeassistant.components.shelly.RpcDevice.initialize"),
) as rpc_device_mock: patch(
"homeassistant.components.shelly.RpcDevice.create", return_value=Mock()
) as rpc_device_mock,
):
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -33,12 +33,17 @@ async def test_block_number_update(
) -> None: ) -> None:
"""Test block device number update.""" """Test block device number update."""
entity_id = "number.test_name_valve_position" entity_id = "number.test_name_valve_position"
await init_integration(hass, 1, sleep_period=1000) monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
await init_integration(hass, 1, sleep_period=3600)
assert hass.states.get(entity_id) is None assert hass.states.get(entity_id) is None
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "50" assert hass.states.get(entity_id).state == "50"
@ -93,7 +98,7 @@ async def test_block_restored_number(
# Make device online # Make device online
monkeypatch.setattr(mock_block_device, "initialized", True) monkeypatch.setattr(mock_block_device, "initialized", True)
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "50" assert hass.states.get(entity_id).state == "50"
@ -130,20 +135,27 @@ async def test_block_restored_number_no_last_state(
# Make device online # Make device online
monkeypatch.setattr(mock_block_device, "initialized", True) monkeypatch.setattr(mock_block_device, "initialized", True)
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "50" assert hass.states.get(entity_id).state == "50"
async def test_block_number_set_value( async def test_block_number_set_value(
hass: HomeAssistant, mock_block_device: Mock hass: HomeAssistant,
mock_block_device: Mock,
monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Test block device number set value.""" """Test block device number set value."""
await init_integration(hass, 1, sleep_period=1000) monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
await init_integration(hass, 1, sleep_period=3600)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
mock_block_device.reset_mock() mock_block_device.reset_mock()
@ -162,15 +174,20 @@ async def test_block_set_value_connection_error(
hass: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch hass: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch
) -> None: ) -> None:
"""Test block device set value connection error.""" """Test block device set value connection error."""
monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
monkeypatch.setattr( monkeypatch.setattr(
mock_block_device, mock_block_device,
"http_request", "http_request",
AsyncMock(side_effect=DeviceConnectionError), AsyncMock(side_effect=DeviceConnectionError),
) )
await init_integration(hass, 1, sleep_period=1000) await init_integration(hass, 1, sleep_period=3600)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
with pytest.raises(HomeAssistantError): with pytest.raises(HomeAssistantError):
@ -186,15 +203,20 @@ async def test_block_set_value_auth_error(
hass: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch hass: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch
) -> None: ) -> None:
"""Test block device set value authentication error.""" """Test block device set value authentication error."""
monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
monkeypatch.setattr( monkeypatch.setattr(
mock_block_device, mock_block_device,
"http_request", "http_request",
AsyncMock(side_effect=InvalidAuthError), AsyncMock(side_effect=InvalidAuthError),
) )
entry = await init_integration(hass, 1, sleep_period=1000) entry = await init_integration(hass, 1, sleep_period=3600)
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED

View File

@ -164,7 +164,7 @@ async def test_block_sleeping_sensor(
assert hass.states.get(entity_id) is None assert hass.states.get(entity_id) is None
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "22.1" assert hass.states.get(entity_id).state == "22.1"
@ -206,7 +206,7 @@ async def test_block_restored_sleeping_sensor(
# Make device online # Make device online
monkeypatch.setattr(mock_block_device, "initialized", True) monkeypatch.setattr(mock_block_device, "initialized", True)
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "22.1" assert hass.states.get(entity_id).state == "22.1"
@ -232,7 +232,7 @@ async def test_block_restored_sleeping_sensor_no_last_state(
# Make device online # Make device online
monkeypatch.setattr(mock_block_device, "initialized", True) monkeypatch.setattr(mock_block_device, "initialized", True)
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "22.1" assert hass.states.get(entity_id).state == "22.1"
@ -305,7 +305,7 @@ async def test_block_not_matched_restored_sleeping_sensor(
mock_block_device.blocks[SENSOR_BLOCK_ID], "description", "other_desc" mock_block_device.blocks[SENSOR_BLOCK_ID], "description", "other_desc"
) )
monkeypatch.setattr(mock_block_device, "initialized", True) monkeypatch.setattr(mock_block_device, "initialized", True)
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "20.4" assert hass.states.get(entity_id).state == "20.4"
@ -448,6 +448,7 @@ async def test_rpc_sleeping_sensor(
) -> None: ) -> None:
"""Test RPC online sleeping sensor.""" """Test RPC online sleeping sensor."""
entity_id = f"{SENSOR_DOMAIN}.test_name_temperature" entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
entry = await init_integration(hass, 2, sleep_period=1000) entry = await init_integration(hass, 2, sleep_period=1000)
# Sensor should be created when device is online # Sensor should be created when device is online
@ -462,7 +463,7 @@ async def test_rpc_sleeping_sensor(
) )
# Make device online # Make device online
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "22.9" assert hass.states.get(entity_id).state == "22.9"
@ -501,6 +502,10 @@ async def test_rpc_restored_sleeping_sensor(
# Make device online # Make device online
monkeypatch.setattr(mock_rpc_device, "initialized", True) monkeypatch.setattr(mock_rpc_device, "initialized", True)
mock_rpc_device.mock_online()
await hass.async_block_till_done()
# Mock update
mock_rpc_device.mock_update() mock_rpc_device.mock_update()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -533,6 +538,10 @@ async def test_rpc_restored_sleeping_sensor_no_last_state(
# Make device online # Make device online
monkeypatch.setattr(mock_rpc_device, "initialized", True) monkeypatch.setattr(mock_rpc_device, "initialized", True)
mock_rpc_device.mock_online()
await hass.async_block_till_done()
# Mock update
mock_rpc_device.mock_update() mock_rpc_device.mock_update()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -583,19 +592,21 @@ async def test_rpc_sleeping_update_entity_service(
hass: HomeAssistant, hass: HomeAssistant,
mock_rpc_device: Mock, mock_rpc_device: Mock,
entity_registry: EntityRegistry, entity_registry: EntityRegistry,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
"""Test RPC sleeping device when the update_entity service is used.""" """Test RPC sleeping device when the update_entity service is used."""
await async_setup_component(hass, "homeassistant", {}) await async_setup_component(hass, "homeassistant", {})
entity_id = f"{SENSOR_DOMAIN}.test_name_temperature" entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
await init_integration(hass, 2, sleep_period=1000) await init_integration(hass, 2, sleep_period=1000)
# Entity should be created when device is online # Entity should be created when device is online
assert hass.states.get(entity_id) is None assert hass.states.get(entity_id) is None
# Make device online # Make device online
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
@ -627,19 +638,25 @@ async def test_block_sleeping_update_entity_service(
hass: HomeAssistant, hass: HomeAssistant,
mock_block_device: Mock, mock_block_device: Mock,
entity_registry: EntityRegistry, entity_registry: EntityRegistry,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
"""Test block sleeping device when the update_entity service is used.""" """Test block sleeping device when the update_entity service is used."""
await async_setup_component(hass, "homeassistant", {}) await async_setup_component(hass, "homeassistant", {})
entity_id = f"{SENSOR_DOMAIN}.test_name_temperature" entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
await init_integration(hass, 1, sleep_period=1000) monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
await init_integration(hass, 1, sleep_period=3600)
# Sensor should be created when device is online # Sensor should be created when device is online
assert hass.states.get(entity_id) is None assert hass.states.get(entity_id) is None
# Make device online # Make device online
mock_block_device.mock_update() mock_block_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "22.1" assert hass.states.get(entity_id).state == "22.1"

View File

@ -335,6 +335,7 @@ async def test_rpc_sleeping_update(
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Test RPC sleeping device update entity.""" """Test RPC sleeping device update entity."""
monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
monkeypatch.setitem(mock_rpc_device.shelly, "ver", "1") monkeypatch.setitem(mock_rpc_device.shelly, "ver", "1")
monkeypatch.setitem( monkeypatch.setitem(
mock_rpc_device.status["sys"], mock_rpc_device.status["sys"],
@ -350,7 +351,7 @@ async def test_rpc_sleeping_update(
assert hass.states.get(entity_id) is None assert hass.states.get(entity_id) is None
# Make device online # Make device online
mock_rpc_device.mock_update() mock_rpc_device.mock_online()
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
@ -411,6 +412,10 @@ async def test_rpc_restored_sleeping_update(
# Make device online # Make device online
monkeypatch.setattr(mock_rpc_device, "initialized", True) monkeypatch.setattr(mock_rpc_device, "initialized", True)
mock_rpc_device.mock_online()
await hass.async_block_till_done()
# Mock update
mock_rpc_device.mock_update() mock_rpc_device.mock_update()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -456,6 +461,10 @@ async def test_rpc_restored_sleeping_update_no_last_state(
# Make device online # Make device online
monkeypatch.setattr(mock_rpc_device, "initialized", True) monkeypatch.setattr(mock_rpc_device, "initialized", True)
mock_rpc_device.mock_online()
await hass.async_block_till_done()
# Mock update
mock_rpc_device.mock_update() mock_rpc_device.mock_update()
await hass.async_block_till_done() await hass.async_block_till_done()