Compare commits

...

4 Commits

Author SHA1 Message Date
Ville Skyttä
a0b64dbfe1 Raise errors on interactive entity actions when suspended 2025-11-23 18:02:38 +02:00
Ville Skyttä
84d3b02ffb Mark interactive entities unavailable when suspended 2025-11-23 18:02:37 +02:00
Ville Skyttä
ede2f465c1 Normalize action parameter URL
For consistency with integration setup.
2025-11-23 18:02:37 +02:00
Ville Skyttä
fcb3451525 Raise errors on action call problems 2025-11-23 18:02:37 +02:00
6 changed files with 43 additions and 24 deletions

View File

@@ -20,6 +20,7 @@ from huawei_lte_api.exceptions import (
ResponseErrorNotSupportedException,
)
from requests.exceptions import Timeout
from url_normalize import url_normalize
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
@@ -39,7 +40,11 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
ServiceValidationError,
)
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
@@ -90,7 +95,7 @@ SCAN_INTERVAL = timedelta(seconds=30)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_URL): cv.url})
SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_URL): cv.string})
PLATFORMS = [
Platform.BINARY_SENSOR,
@@ -465,26 +470,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
because the latter is not available anywhere in the UI.
"""
routers = hass.data[DOMAIN].routers
if url := service.data.get(CONF_URL):
if url := url_normalize(service.data.get(CONF_URL), default_scheme="http"):
router = next(
(router for router in routers.values() if router.url == url), None
)
elif not routers:
_LOGGER.error("%s: no routers configured", service.service)
return
raise ServiceValidationError("No routers configured")
elif len(routers) == 1:
router = next(iter(routers.values()))
else:
_LOGGER.error(
"%s: more than one router configured, must specify one of URLs %s",
service.service,
sorted(router.url for router in routers.values()),
raise ServiceValidationError(
f"More than one router configured, must specify one of URLs {sorted(router.url for router in routers.values())}"
)
return
if not router:
_LOGGER.error("%s: router %s unavailable", service.service, url)
return
raise ServiceValidationError(f"Router {url} not available")
was_suspended = router.suspended
if service.service == SERVICE_RESUME_INTEGRATION:
# Login will be handled automatically on demand
router.suspended = False
@@ -494,7 +495,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
router.suspended = True
_LOGGER.debug("%s: %s", service.service, "done")
else:
_LOGGER.error("%s: unsupported service", service.service)
raise ServiceValidationError(f"Unsupported service {service.service}")
if was_suspended != router.suspended:
# Make interactive entities' availability update
dispatcher_send(hass, UPDATE_SIGNAL, router.config_entry.unique_id)
for service in ADMIN_SERVICES:
async_register_admin_service(

View File

@@ -14,10 +14,11 @@ from homeassistant.components.button import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_platform
from .const import DOMAIN
from .entity import HuaweiLteBaseEntityWithDevice
from .entity import HuaweiLteBaseInteractiveEntity
_LOGGER = logging.getLogger(__name__)
@@ -36,7 +37,7 @@ async def async_setup_entry(
async_add_entities(buttons)
class BaseButton(HuaweiLteBaseEntityWithDevice, ButtonEntity):
class BaseButton(HuaweiLteBaseInteractiveEntity, ButtonEntity):
"""Huawei LTE button base class."""
@property
@@ -50,10 +51,7 @@ class BaseButton(HuaweiLteBaseEntityWithDevice, ButtonEntity):
def press(self) -> None:
"""Press button."""
if self.router.suspended:
_LOGGER.debug(
"%s: ignored, integration suspended", self.entity_description.key
)
return
raise ServiceValidationError("Integration is suspended")
result = self._press()
_LOGGER.debug("%s: %s", self.entity_description.key, result)

View File

@@ -66,3 +66,12 @@ class HuaweiLteBaseEntityWithDevice(HuaweiLteBaseEntity):
connections=self.router.device_connections,
identifiers=self.router.device_identifiers,
)
class HuaweiLteBaseInteractiveEntity(HuaweiLteBaseEntityWithDevice):
"""Base interactive entity."""
@property
def available(self) -> bool:
"""Return if entity is available."""
return super().available and not self.router.suspended

View File

@@ -17,12 +17,13 @@ from homeassistant.components.select import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import Router
from .const import DOMAIN, KEY_NET_NET_MODE
from .entity import HuaweiLteBaseEntityWithDevice
from .entity import HuaweiLteBaseInteractiveEntity
_LOGGER = logging.getLogger(__name__)
@@ -74,7 +75,7 @@ async def async_setup_entry(
async_add_entities(selects, True)
class HuaweiLteSelectEntity(HuaweiLteBaseEntityWithDevice, SelectEntity):
class HuaweiLteSelectEntity(HuaweiLteBaseInteractiveEntity, SelectEntity):
"""Huawei LTE select entity."""
entity_description: HuaweiSelectEntityDescription
@@ -95,6 +96,8 @@ class HuaweiLteSelectEntity(HuaweiLteBaseEntityWithDevice, SelectEntity):
def select_option(self, option: str) -> None:
"""Change the selected option."""
if self.router.suspended:
raise ServiceValidationError("Integration is suspended")
self.entity_description.setter_fn(option)
@property

View File

@@ -12,6 +12,7 @@ from homeassistant.components.switch import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -20,7 +21,7 @@ from .const import (
KEY_DIALUP_MOBILE_DATASWITCH,
KEY_WLAN_WIFI_GUEST_NETWORK_SWITCH,
)
from .entity import HuaweiLteBaseEntityWithDevice
from .entity import HuaweiLteBaseInteractiveEntity
_LOGGER = logging.getLogger(__name__)
@@ -43,7 +44,7 @@ async def async_setup_entry(
async_add_entities(switches, True)
class HuaweiLteBaseSwitch(HuaweiLteBaseEntityWithDevice, SwitchEntity):
class HuaweiLteBaseSwitch(HuaweiLteBaseInteractiveEntity, SwitchEntity):
"""Huawei LTE switch device base class."""
key: str
@@ -57,10 +58,14 @@ class HuaweiLteBaseSwitch(HuaweiLteBaseEntityWithDevice, SwitchEntity):
def turn_on(self, **kwargs: Any) -> None:
"""Turn switch on."""
if self.router.suspended:
raise ServiceValidationError("Integration is suspended")
self._turn(state=True)
def turn_off(self, **kwargs: Any) -> None:
"""Turn switch off."""
if self.router.suspended:
raise ServiceValidationError("Integration is suspended")
self._turn(state=False)
async def async_added_to_hass(self) -> None:

View File

@@ -18,7 +18,7 @@ from . import magic_client
from tests.common import MockConfigEntry
MOCK_CONF_URL = "http://huawei-lte.example.com"
MOCK_CONF_URL = "http://192.168.1.1/"
@patch("homeassistant.components.huawei_lte.Connection", MagicMock())