From 616f6aab7640640c4ade338dfd08293288df3da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 23 Nov 2023 20:35:35 +0200 Subject: [PATCH] Add Huawei LTE restart and clear traffic statistics buttons (#91967) * Add Huawei LTE restart and clear traffic statistics buttons Deprecate corresponding services in favour of these. * Change to be removed service warnings to issues * Add tests * Update planned service remove versions --- .../components/huawei_lte/__init__.py | 30 ++++++ homeassistant/components/huawei_lte/button.py | 97 +++++++++++++++++++ homeassistant/components/huawei_lte/const.py | 3 + .../components/huawei_lte/strings.json | 6 ++ tests/components/huawei_lte/__init__.py | 22 +++++ tests/components/huawei_lte/test_button.py | 76 +++++++++++++++ tests/components/huawei_lte/test_switches.py | 22 +---- 7 files changed, 236 insertions(+), 20 deletions(-) create mode 100644 homeassistant/components/huawei_lte/button.py create mode 100644 tests/components/huawei_lte/test_button.py diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 929ca0193af..d0d1ce71161 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -50,6 +50,7 @@ from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.issue_registry import IssueSeverity, create_issue from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType @@ -57,6 +58,8 @@ from .const import ( ADMIN_SERVICES, ALL_KEYS, ATTR_CONFIG_ENTRY_ID, + BUTTON_KEY_CLEAR_TRAFFIC_STATISTICS, + BUTTON_KEY_RESTART, CONF_MANUFACTURER, CONF_UNAUTHENTICATED_MODE, CONNECTION_TIMEOUT, @@ -127,6 +130,7 @@ SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_URL): cv.url}) PLATFORMS = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH, @@ -524,12 +528,38 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return if service.service == SERVICE_CLEAR_TRAFFIC_STATISTICS: + create_issue( + hass, + DOMAIN, + "service_clear_traffic_statistics_moved_to_button", + breaks_in_ha_version="2024.2.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="service_changed_to_button", + translation_placeholders={ + "service": service.service, + "button": BUTTON_KEY_CLEAR_TRAFFIC_STATISTICS, + }, + ) if router.suspended: _LOGGER.debug("%s: ignored, integration suspended", service.service) return result = router.client.monitoring.set_clear_traffic() _LOGGER.debug("%s: %s", service.service, result) elif service.service == SERVICE_REBOOT: + create_issue( + hass, + DOMAIN, + "service_reboot_moved_to_button", + breaks_in_ha_version="2024.2.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="service_changed_to_button", + translation_placeholders={ + "service": service.service, + "button": BUTTON_KEY_RESTART, + }, + ) if router.suspended: _LOGGER.debug("%s: ignored, integration suspended", service.service) return diff --git a/homeassistant/components/huawei_lte/button.py b/homeassistant/components/huawei_lte/button.py new file mode 100644 index 00000000000..f494836e80d --- /dev/null +++ b/homeassistant/components/huawei_lte/button.py @@ -0,0 +1,97 @@ +"""Huawei LTE buttons.""" + +from __future__ import annotations + +import logging + +from huawei_lte_api.enums.device import ControlModeEnum + +from homeassistant.components.button import ( + ButtonDeviceClass, + ButtonEntity, + ButtonEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_platform + +from . import HuaweiLteBaseEntityWithDevice +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: entity_platform.AddEntitiesCallback, +) -> None: + """Set up Huawei LTE buttons.""" + router = hass.data[DOMAIN].routers[config_entry.entry_id] + buttons = [ + ClearTrafficStatisticsButton(router), + RestartButton(router), + ] + async_add_entities(buttons) + + +class BaseButton(HuaweiLteBaseEntityWithDevice, ButtonEntity): + """Huawei LTE button base class.""" + + @property + def _device_unique_id(self) -> str: + """Return unique ID for entity within a router.""" + return f"button-{self.entity_description.key}" + + async def async_update(self) -> None: + """Update is not necessary for button entities.""" + + def press(self) -> None: + """Press button.""" + if self.router.suspended: + _LOGGER.debug( + "%s: ignored, integration suspended", self.entity_description.key + ) + return + result = self._press() + _LOGGER.debug("%s: %s", self.entity_description.key, result) + + def _press(self) -> str: + """Invoke low level action of button press.""" + raise NotImplementedError + + +BUTTON_KEY_CLEAR_TRAFFIC_STATISTICS = "clear_traffic_statistics" + + +class ClearTrafficStatisticsButton(BaseButton): + """Huawei LTE clear traffic statistics button.""" + + entity_description = ButtonEntityDescription( + key=BUTTON_KEY_CLEAR_TRAFFIC_STATISTICS, + name="Clear traffic statistics", + entity_category=EntityCategory.CONFIG, + ) + + def _press(self) -> str: + """Call clear traffic statistics endpoint.""" + return self.router.client.monitoring.set_clear_traffic() + + +BUTTON_KEY_RESTART = "restart" + + +class RestartButton(BaseButton): + """Huawei LTE restart button.""" + + entity_description = ButtonEntityDescription( + key=BUTTON_KEY_RESTART, + name="Restart", + device_class=ButtonDeviceClass.RESTART, + entity_category=EntityCategory.CONFIG, + ) + + def _press(self) -> str: + """Call restart endpoint.""" + return self.router.client.device.set_control(ControlModeEnum.REBOOT) diff --git a/homeassistant/components/huawei_lte/const.py b/homeassistant/components/huawei_lte/const.py index 53cc0efb919..eba0f3ce90b 100644 --- a/homeassistant/components/huawei_lte/const.py +++ b/homeassistant/components/huawei_lte/const.py @@ -79,3 +79,6 @@ ALL_KEYS = ( | SWITCH_KEYS | {KEY_DEVICE_BASIC_INFORMATION} ) + +BUTTON_KEY_CLEAR_TRAFFIC_STATISTICS = "clear_traffic_statistics" +BUTTON_KEY_RESTART = "restart" diff --git a/homeassistant/components/huawei_lte/strings.json b/homeassistant/components/huawei_lte/strings.json index f188eb9e17b..1e43aa818e9 100644 --- a/homeassistant/components/huawei_lte/strings.json +++ b/homeassistant/components/huawei_lte/strings.json @@ -279,6 +279,12 @@ } } }, + "issues": { + "service_changed_to_button": { + "title": "Service changed to a button", + "description": "The {service} service is deprecated, use the corresponding {button} button instead." + } + }, "services": { "clear_traffic_statistics": { "name": "Clear traffic statistics", diff --git a/tests/components/huawei_lte/__init__.py b/tests/components/huawei_lte/__init__.py index 79602ecfb44..2d43a5eade1 100644 --- a/tests/components/huawei_lte/__init__.py +++ b/tests/components/huawei_lte/__init__.py @@ -1 +1,23 @@ """Tests for the huawei_lte component.""" + +from unittest.mock import MagicMock + +from huawei_lte_api.enums.cradle import ConnectionStatusEnum + + +def magic_client(multi_basic_settings_value: dict) -> MagicMock: + """Mock huawei_lte.Client.""" + information = MagicMock(return_value={"SerialNumber": "test-serial-number"}) + check_notifications = MagicMock(return_value={"SmsStorageFull": 0}) + status = MagicMock( + return_value={"ConnectionStatus": ConnectionStatusEnum.CONNECTED.value} + ) + multi_basic_settings = MagicMock(return_value=multi_basic_settings_value) + wifi_feature_switch = MagicMock(return_value={"wifi24g_switch_enable": 1}) + device = MagicMock(information=information) + monitoring = MagicMock(check_notifications=check_notifications, status=status) + wlan = MagicMock( + multi_basic_settings=multi_basic_settings, + wifi_feature_switch=wifi_feature_switch, + ) + return MagicMock(device=device, monitoring=monitoring, wlan=wlan) diff --git a/tests/components/huawei_lte/test_button.py b/tests/components/huawei_lte/test_button.py new file mode 100644 index 00000000000..982fba166c3 --- /dev/null +++ b/tests/components/huawei_lte/test_button.py @@ -0,0 +1,76 @@ +"""Tests for the Huawei LTE switches.""" +from unittest.mock import MagicMock, patch + +from huawei_lte_api.enums.device import ControlModeEnum + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.components.huawei_lte.const import ( + BUTTON_KEY_CLEAR_TRAFFIC_STATISTICS, + BUTTON_KEY_RESTART, + DOMAIN, + SERVICE_SUSPEND_INTEGRATION, +) +from homeassistant.const import ATTR_ENTITY_ID, CONF_URL +from homeassistant.core import HomeAssistant + +from . import magic_client + +from tests.common import MockConfigEntry + +MOCK_CONF_URL = "http://huawei-lte.example.com" + + +@patch("homeassistant.components.huawei_lte.Connection", MagicMock()) +@patch("homeassistant.components.huawei_lte.Client", return_value=magic_client({})) +async def test_clear_traffic_statistics(client, hass: HomeAssistant) -> None: + """Test clear traffic statistics button.""" + huawei_lte = MockConfigEntry(domain=DOMAIN, data={CONF_URL: MOCK_CONF_URL}) + huawei_lte.add_to_hass(hass) + await hass.config_entries.async_setup(huawei_lte.entry_id) + await hass.async_block_till_done() + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: f"button.lte_{BUTTON_KEY_CLEAR_TRAFFIC_STATISTICS}"}, + blocking=True, + ) + await hass.async_block_till_done() + client.return_value.monitoring.set_clear_traffic.assert_called_once() + + client.return_value.monitoring.set_clear_traffic.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SUSPEND_INTEGRATION, + {CONF_URL: MOCK_CONF_URL}, + blocking=True, + ) + await hass.async_block_till_done() + client.return_value.monitoring.set_clear_traffic.assert_not_called() + + +@patch("homeassistant.components.huawei_lte.Connection", MagicMock()) +@patch("homeassistant.components.huawei_lte.Client", return_value=magic_client({})) +async def test_restart(client, hass: HomeAssistant) -> None: + """Test restart button.""" + huawei_lte = MockConfigEntry(domain=DOMAIN, data={CONF_URL: MOCK_CONF_URL}) + huawei_lte.add_to_hass(hass) + await hass.config_entries.async_setup(huawei_lte.entry_id) + await hass.async_block_till_done() + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: f"button.lte_{BUTTON_KEY_RESTART}"}, + blocking=True, + ) + await hass.async_block_till_done() + client.return_value.device.set_control.assert_called_with(ControlModeEnum.REBOOT) + + client.return_value.device.set_control.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SUSPEND_INTEGRATION, + {CONF_URL: MOCK_CONF_URL}, + blocking=True, + ) + await hass.async_block_till_done() + client.return_value.device.set_control.assert_not_called() diff --git a/tests/components/huawei_lte/test_switches.py b/tests/components/huawei_lte/test_switches.py index e686c2356e6..acaffdbd0ba 100644 --- a/tests/components/huawei_lte/test_switches.py +++ b/tests/components/huawei_lte/test_switches.py @@ -1,8 +1,6 @@ """Tests for the Huawei LTE switches.""" from unittest.mock import MagicMock, patch -from huawei_lte_api.enums.cradle import ConnectionStatusEnum - from homeassistant.components.huawei_lte.const import DOMAIN from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, @@ -13,29 +11,13 @@ from homeassistant.const import ATTR_ENTITY_ID, CONF_URL, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from . import magic_client + from tests.common import MockConfigEntry SWITCH_WIFI_GUEST_NETWORK = "switch.lte_wi_fi_guest_network" -def magic_client(multi_basic_settings_value: dict) -> MagicMock: - """Mock huawei_lte.Client.""" - information = MagicMock(return_value={"SerialNumber": "test-serial-number"}) - check_notifications = MagicMock(return_value={"SmsStorageFull": 0}) - status = MagicMock( - return_value={"ConnectionStatus": ConnectionStatusEnum.CONNECTED.value} - ) - multi_basic_settings = MagicMock(return_value=multi_basic_settings_value) - wifi_feature_switch = MagicMock(return_value={"wifi24g_switch_enable": 1}) - device = MagicMock(information=information) - monitoring = MagicMock(check_notifications=check_notifications, status=status) - wlan = MagicMock( - multi_basic_settings=multi_basic_settings, - wifi_feature_switch=wifi_feature_switch, - ) - return MagicMock(device=device, monitoring=monitoring, wlan=wlan) - - @patch("homeassistant.components.huawei_lte.Connection", MagicMock()) @patch("homeassistant.components.huawei_lte.Client", return_value=magic_client({})) async def test_huawei_lte_wifi_guest_network_config_entry_when_network_is_not_present(