Implement state error handling in HomeWizard (#84991)

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
pull/85955/head
Duco Sebel 2023-01-15 17:30:27 +01:00 committed by GitHub
parent 1a0bce715a
commit 209c47383d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 446 additions and 58 deletions

View File

@ -5,7 +5,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH, ConfigEnt
from homeassistant.const import CONF_IP_ADDRESS
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers import entity_registry as er
from .const import DOMAIN, PLATFORMS
from .coordinator import HWEnergyDeviceUpdateCoordinator as Coordinator
@ -64,12 +64,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.async_create_task(hass.config_entries.async_remove(old_config_entry_id))
# Create coordinator
coordinator = Coordinator(hass, entry.entry_id, entry.data[CONF_IP_ADDRESS])
coordinator = Coordinator(hass, entry, entry.data[CONF_IP_ADDRESS])
try:
await coordinator.async_config_entry_first_refresh()
except ConfigEntryNotReady:
await coordinator.api.close()
if coordinator.api_disabled:
@ -86,17 +85,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
# Register device
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
name=entry.title,
manufacturer="HomeWizard",
sw_version=coordinator.data["device"].firmware_version,
model=coordinator.data["device"].product_type,
identifiers={(DOMAIN, coordinator.data["device"].serial)},
)
# Finalize
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View File

@ -7,10 +7,11 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import HWEnergyDeviceUpdateCoordinator
from .entity import HomeWizardEntity
from .helpers import homewizard_exception_handler
_LOGGER = logging.getLogger(__name__)
@ -26,13 +27,9 @@ async def async_setup_entry(
async_add_entities([HomeWizardIdentifyButton(coordinator, entry)])
class HomeWizardIdentifyButton(
CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], ButtonEntity
):
class HomeWizardIdentifyButton(HomeWizardEntity, ButtonEntity):
"""Representation of a identify button."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: HWEnergyDeviceUpdateCoordinator,
@ -41,17 +38,11 @@ class HomeWizardIdentifyButton(
"""Initialize button."""
super().__init__(coordinator)
self._attr_unique_id = f"{entry.unique_id}_identify"
self._attr_device_info = {
"name": entry.title,
"manufacturer": "HomeWizard",
"sw_version": coordinator.data["device"].firmware_version,
"model": coordinator.data["device"].product_type,
"identifiers": {(DOMAIN, coordinator.data["device"].serial)},
}
self._attr_name = "Identify"
self._attr_icon = "mdi:magnify"
self._attr_entity_category = EntityCategory.DIAGNOSTIC
@homewizard_exception_handler
async def async_press(self) -> None:
"""Identify the device."""
await self.coordinator.api.identify()

View File

@ -6,9 +6,9 @@ import logging
from homewizard_energy import HomeWizardEnergy
from homewizard_energy.errors import DisabledError, RequestError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, UPDATE_INTERVAL, DeviceResponseEntry
@ -25,22 +25,15 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]
def __init__(
self,
hass: HomeAssistant,
entry_id: str,
entry: ConfigEntry,
host: str,
) -> None:
"""Initialize Update Coordinator."""
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL)
self.entry_id = entry_id
self.entry = entry
self.api = HomeWizardEnergy(host, clientsession=async_get_clientsession(hass))
@property
def device_info(self) -> DeviceInfo:
"""Return device_info."""
return DeviceInfo(
identifiers={(DOMAIN, self.data["device"].serial)},
)
async def _async_update_data(self) -> DeviceResponseEntry:
"""Fetch all device and sensor data from api."""
@ -66,7 +59,7 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]
# Do not reload when performing first refresh
if self.data is not None:
await self.hass.config_entries.async_reload(self.entry_id)
await self.hass.config_entries.async_reload(self.entry.entry_id)
raise UpdateFailed(ex) from ex

View File

@ -0,0 +1,25 @@
"""Base entity for the HomeWizard integration."""
from __future__ import annotations
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import HWEnergyDeviceUpdateCoordinator
class HomeWizardEntity(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator]):
"""Defines a HomeWizard entity."""
_attr_has_entity_name = True
def __init__(self, coordinator: HWEnergyDeviceUpdateCoordinator) -> None:
"""Initialize the HomeWizard entity."""
super().__init__(coordinator=coordinator)
self._attr_device_info = DeviceInfo(
name=coordinator.entry.title,
manufacturer="HomeWizard",
sw_version=coordinator.data["device"].firmware_version,
model=coordinator.data["device"].product_type,
identifiers={(DOMAIN, coordinator.data["device"].serial)},
)

View File

@ -0,0 +1,39 @@
"""Helpers for HomeWizard."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
from typing import Any, TypeVar
from homewizard_energy.errors import DisabledError, RequestError
from typing_extensions import Concatenate, ParamSpec
from homeassistant.exceptions import HomeAssistantError
from .entity import HomeWizardEntity
_HomeWizardEntityT = TypeVar("_HomeWizardEntityT", bound=HomeWizardEntity)
_P = ParamSpec("_P")
def homewizard_exception_handler(
func: Callable[Concatenate[_HomeWizardEntityT, _P], Coroutine[Any, Any, Any]]
) -> Callable[Concatenate[_HomeWizardEntityT, _P], Coroutine[Any, Any, None]]:
"""Decorate HomeWizard Energy calls to handle HomeWizardEnergy exceptions.
A decorator that wraps the passed in function, catches HomeWizardEnergy errors,
and reloads the integration when the API was disabled so the reauth flow is triggered.
"""
async def handler(
self: _HomeWizardEntityT, *args: _P.args, **kwargs: _P.kwargs
) -> None:
try:
await func(self, *args, **kwargs)
except RequestError as ex:
raise HomeAssistantError from ex
except DisabledError as ex:
await self.hass.config_entries.async_reload(self.coordinator.entry.entry_id)
raise HomeAssistantError from ex
return handler

View File

@ -9,10 +9,11 @@ from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import HWEnergyDeviceUpdateCoordinator
from .entity import HomeWizardEntity
from .helpers import homewizard_exception_handler
async def async_setup_entry(
@ -31,13 +32,10 @@ async def async_setup_entry(
)
class HWEnergyNumberEntity(
CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], NumberEntity
):
class HWEnergyNumberEntity(HomeWizardEntity, NumberEntity):
"""Representation of status light number."""
_attr_entity_category = EntityCategory.CONFIG
_attr_has_entity_name = True
def __init__(
self,
@ -50,8 +48,8 @@ class HWEnergyNumberEntity(
self._attr_name = "Status light brightness"
self._attr_native_unit_of_measurement = PERCENTAGE
self._attr_icon = "mdi:lightbulb-on"
self._attr_device_info = coordinator.device_info
@homewizard_exception_handler
async def async_set_native_value(self, value: float) -> None:
"""Set a new value."""
await self.coordinator.api.state_set(brightness=value * (255 / 100))

View File

@ -15,10 +15,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, DeviceResponseEntry
from .coordinator import HWEnergyDeviceUpdateCoordinator
from .entity import HomeWizardEntity
PARALLEL_UPDATES = 1
@ -145,11 +145,9 @@ async def async_setup_entry(
async_add_entities(entities)
class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorEntity):
class HWEnergySensor(HomeWizardEntity, SensorEntity):
"""Representation of a HomeWizard Sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: HWEnergyDeviceUpdateCoordinator,
@ -165,7 +163,6 @@ class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorE
# Config attributes.
self.data_type = description.key
self._attr_unique_id = f"{entry.unique_id}_{description.key}"
self._attr_device_info = coordinator.device_info
# Special case for export, not everyone has solarpanels
# The chance that 'export' is non-zero when you have solar panels is nil

View File

@ -8,10 +8,11 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import HWEnergyDeviceUpdateCoordinator
from .entity import HomeWizardEntity
from .helpers import homewizard_exception_handler
async def async_setup_entry(
@ -34,13 +35,9 @@ async def async_setup_entry(
async_add_entities(entities)
class HWEnergySwitchEntity(
CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SwitchEntity
):
class HWEnergySwitchEntity(HomeWizardEntity, SwitchEntity):
"""Representation switchable entity."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: HWEnergyDeviceUpdateCoordinator,
@ -50,7 +47,6 @@ class HWEnergySwitchEntity(
"""Initialize the switch."""
super().__init__(coordinator)
self._attr_unique_id = f"{entry.unique_id}_{key}"
self._attr_device_info = coordinator.device_info
class HWEnergyMainSwitchEntity(HWEnergySwitchEntity):
@ -64,11 +60,13 @@ class HWEnergyMainSwitchEntity(HWEnergySwitchEntity):
"""Initialize the switch."""
super().__init__(coordinator, entry, "power_on")
@homewizard_exception_handler
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.coordinator.api.state_set(power_on=True)
await self.coordinator.async_refresh()
@homewizard_exception_handler
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.coordinator.api.state_set(power_on=False)
@ -107,11 +105,13 @@ class HWEnergySwitchLockEntity(HWEnergySwitchEntity):
"""Initialize the switch."""
super().__init__(coordinator, entry, "switch_lock")
@homewizard_exception_handler
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn switch-lock on."""
await self.coordinator.api.state_set(switch_lock=True)
await self.coordinator.async_refresh()
@homewizard_exception_handler
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn switch-lock off."""
await self.coordinator.api.state_set(switch_lock=False)
@ -146,11 +146,13 @@ class HWEnergyEnableCloudEntity(HWEnergySwitchEntity):
self.hass = hass
self.entry = entry
@homewizard_exception_handler
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn cloud connection on."""
await self.coordinator.api.system_set(cloud_enabled=True)
await self.coordinator.async_refresh()
@homewizard_exception_handler
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn cloud connection off."""
await self.coordinator.api.system_set(cloud_enabled=False)

View File

@ -1,8 +1,12 @@
"""Test the identify button for HomeWizard."""
from unittest.mock import patch
from homewizard_energy.errors import DisabledError, RequestError
from pytest import raises
from homeassistant.components import button
from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNKNOWN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from .generator import get_mock_device
@ -60,8 +64,8 @@ async def test_identify_button_is_loaded(
assert entry.unique_id == "aabbccddeeff_identify"
async def test_cloud_connection_on_off(hass, mock_config_entry_data, mock_config_entry):
"""Test the creation and values of the Litter-Robot button."""
async def test_identify_press(hass, mock_config_entry_data, mock_config_entry):
"""Test button press is handled correctly."""
api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02")
@ -89,3 +93,79 @@ async def test_cloud_connection_on_off(hass, mock_config_entry_data, mock_config
blocking=True,
)
assert api.identify.call_count == 1
async def test_identify_press_catches_requesterror(
hass, mock_config_entry_data, mock_config_entry
):
"""Test button press is handled RequestError correctly."""
api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02")
with patch(
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
return_value=api,
):
entry = mock_config_entry
entry.data = mock_config_entry_data
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert (
hass.states.get("button.product_name_aabbccddeeff_identify").state
== STATE_UNKNOWN
)
# Raise RequestError when identify is called
api.identify.side_effect = RequestError()
assert api.identify.call_count == 0
with raises(HomeAssistantError):
await hass.services.async_call(
button.DOMAIN,
button.SERVICE_PRESS,
{"entity_id": "button.product_name_aabbccddeeff_identify"},
blocking=True,
)
assert api.identify.call_count == 1
async def test_identify_press_catches_disablederror(
hass, mock_config_entry_data, mock_config_entry
):
"""Test button press is handled DisabledError correctly."""
api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02")
with patch(
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
return_value=api,
):
entry = mock_config_entry
entry.data = mock_config_entry_data
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert (
hass.states.get("button.product_name_aabbccddeeff_identify").state
== STATE_UNKNOWN
)
# Raise RequestError when identify is called
api.identify.side_effect = DisabledError()
assert api.identify.call_count == 0
with raises(HomeAssistantError):
await hass.services.async_call(
button.DOMAIN,
button.SERVICE_PRESS,
{"entity_id": "button.product_name_aabbccddeeff_identify"},
blocking=True,
)
assert api.identify.call_count == 1

View File

@ -2,11 +2,14 @@
from unittest.mock import AsyncMock, patch
from homewizard_energy.errors import DisabledError, RequestError
from homewizard_energy.models import State
from pytest import raises
from homeassistant.components import number
from homeassistant.components.number import ATTR_VALUE, SERVICE_SET_VALUE
from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from .generator import get_mock_device
@ -139,3 +142,118 @@ async def test_brightness_level_set(hass, mock_config_entry_data, mock_config_en
== "0"
)
assert len(api.state_set.mock_calls) == 2
async def test_brightness_level_set_catches_requesterror(
hass, mock_config_entry_data, mock_config_entry
):
"""Test entity raises HomeAssistantError when RequestError was raised."""
api = get_mock_device()
api.state = AsyncMock(return_value=State.from_dict({"brightness": 255}))
api.state_set = AsyncMock(side_effect=RequestError())
with patch(
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
return_value=api,
):
entry = mock_config_entry
entry.data = mock_config_entry_data
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
# Set level halfway
with raises(HomeAssistantError):
await hass.services.async_call(
number.DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness",
ATTR_VALUE: 50,
},
blocking=True,
)
async def test_brightness_level_set_catches_disablederror(
hass, mock_config_entry_data, mock_config_entry
):
"""Test entity raises HomeAssistantError when DisabledError was raised."""
api = get_mock_device()
api.state = AsyncMock(return_value=State.from_dict({"brightness": 255}))
api.state_set = AsyncMock(side_effect=DisabledError())
with patch(
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
return_value=api,
):
entry = mock_config_entry
entry.data = mock_config_entry_data
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
# Set level halfway
with raises(HomeAssistantError):
await hass.services.async_call(
number.DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness",
ATTR_VALUE: 50,
},
blocking=True,
)
async def test_brightness_level_set_catches_invalid_value(
hass, mock_config_entry_data, mock_config_entry
):
"""Test entity raises ValueError when value was invalid."""
api = get_mock_device()
api.state = AsyncMock(return_value=State.from_dict({"brightness": 255}))
def state_set(brightness):
api.state = AsyncMock(return_value=State.from_dict({"brightness": brightness}))
api.state_set = AsyncMock(side_effect=state_set)
with patch(
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
return_value=api,
):
entry = mock_config_entry
entry.data = mock_config_entry_data
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
with raises(ValueError):
await hass.services.async_call(
number.DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness",
ATTR_VALUE: -1,
},
blocking=True,
)
with raises(ValueError):
await hass.services.async_call(
number.DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness",
ATTR_VALUE: 101,
},
blocking=True,
)

View File

@ -2,7 +2,9 @@
from unittest.mock import AsyncMock, patch
from homewizard_energy.errors import DisabledError, RequestError
from homewizard_energy.models import State, System
from pytest import raises
from homeassistant.components import switch
from homeassistant.components.switch import SwitchDeviceClass
@ -16,6 +18,7 @@ from homeassistant.const import (
STATE_ON,
STATE_UNAVAILABLE,
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from .generator import get_mock_device
@ -346,3 +349,157 @@ async def test_cloud_connection_on_off(hass, mock_config_entry_data, mock_config
== STATE_OFF
)
assert len(api.system_set.mock_calls) == 2
async def test_switch_handles_requesterror(
hass, mock_config_entry_data, mock_config_entry
):
"""Test entity raises HomeAssistantError when RequestError was raised."""
api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02")
api.state = AsyncMock(
return_value=State.from_dict({"power_on": False, "switch_lock": False})
)
api.system = AsyncMock(return_value=System.from_dict({"cloud_enabled": False}))
api.state_set = AsyncMock(side_effect=RequestError())
api.system_set = AsyncMock(side_effect=RequestError())
with patch(
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
return_value=api,
):
entry = mock_config_entry
entry.data = mock_config_entry_data
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
# Power on toggle
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_ON,
{"entity_id": "switch.product_name_aabbccddeeff"},
blocking=True,
)
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_OFF,
{"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"},
blocking=True,
)
# Switch Lock toggle
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_ON,
{"entity_id": "switch.product_name_aabbccddeeff_switch_lock"},
blocking=True,
)
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_OFF,
{"entity_id": "switch.product_name_aabbccddeeff_switch_lock"},
blocking=True,
)
# Disable Cloud toggle
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_ON,
{"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"},
blocking=True,
)
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_OFF,
{"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"},
blocking=True,
)
async def test_switch_handles_disablederror(
hass, mock_config_entry_data, mock_config_entry
):
"""Test entity raises HomeAssistantError when Disabled was raised."""
api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02")
api.state = AsyncMock(
return_value=State.from_dict({"power_on": False, "switch_lock": False})
)
api.system = AsyncMock(return_value=System.from_dict({"cloud_enabled": False}))
api.state_set = AsyncMock(side_effect=DisabledError())
api.system_set = AsyncMock(side_effect=DisabledError())
with patch(
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
return_value=api,
):
entry = mock_config_entry
entry.data = mock_config_entry_data
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
# Power on toggle
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_ON,
{"entity_id": "switch.product_name_aabbccddeeff"},
blocking=True,
)
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_OFF,
{"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"},
blocking=True,
)
# Switch Lock toggle
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_ON,
{"entity_id": "switch.product_name_aabbccddeeff_switch_lock"},
blocking=True,
)
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_OFF,
{"entity_id": "switch.product_name_aabbccddeeff_switch_lock"},
blocking=True,
)
# Disable Cloud toggle
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_ON,
{"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"},
blocking=True,
)
with raises(HomeAssistantError):
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_OFF,
{"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"},
blocking=True,
)