From 8ba6fd79350f010b9cb8abf18df754e9bbf0a804 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Tue, 26 Sep 2023 03:15:20 -0400 Subject: [PATCH] Add device info to Hydrawise (#100828) * Add device info to Hydrawise * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker * Remove _attr_has_entity_name --------- Co-authored-by: Joost Lekkerkerker --- .../components/hydrawise/__init__.py | 6 +- .../components/hydrawise/binary_sensor.py | 1 + homeassistant/components/hydrawise/const.py | 2 + homeassistant/components/hydrawise/entity.py | 16 ++-- tests/components/hydrawise/conftest.py | 91 ++++++++++++++++++- tests/components/hydrawise/test_device.py | 36 ++++++++ 6 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 tests/components/hydrawise/test_device.py diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py index 560046e9c2b9bc..bc3c62cfb9f7d5 100644 --- a/homeassistant/components/hydrawise/__init__.py +++ b/homeassistant/components/hydrawise/__init__.py @@ -1,7 +1,7 @@ """Support for Hydrawise cloud.""" -from pydrawise.legacy import LegacyHydrawise +from pydrawise import legacy from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol @@ -54,7 +54,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b """Set up Hydrawise from a config entry.""" access_token = config_entry.data[CONF_API_KEY] try: - hydrawise = await hass.async_add_executor_job(LegacyHydrawise, access_token) + hydrawise = await hass.async_add_executor_job( + legacy.LegacyHydrawise, access_token + ) except (ConnectTimeout, HTTPError) as ex: LOGGER.error("Unable to connect to Hydrawise cloud service: %s", str(ex)) raise ConfigEntryNotReady( diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py index 06683ff0345998..8a5d6fe2f83639 100644 --- a/homeassistant/components/hydrawise/binary_sensor.py +++ b/homeassistant/components/hydrawise/binary_sensor.py @@ -77,6 +77,7 @@ async def async_setup_entry( data=hydrawise.current_controller, coordinator=coordinator, description=BINARY_SENSOR_STATUS, + device_id_key="controller_id", ) ] diff --git a/homeassistant/components/hydrawise/const.py b/homeassistant/components/hydrawise/const.py index ccf3eb5bac09fa..dc53d847b1f804 100644 --- a/homeassistant/components/hydrawise/const.py +++ b/homeassistant/components/hydrawise/const.py @@ -11,6 +11,8 @@ DOMAIN = "hydrawise" DEFAULT_WATERING_TIME = 15 +MANUFACTURER = "Hydrawise" + SCAN_INTERVAL = timedelta(seconds=120) SIGNAL_UPDATE_HYDRAWISE = "hydrawise_update" diff --git a/homeassistant/components/hydrawise/entity.py b/homeassistant/components/hydrawise/entity.py index 98b66069913b85..db07faef6d06ac 100644 --- a/homeassistant/components/hydrawise/entity.py +++ b/homeassistant/components/hydrawise/entity.py @@ -3,9 +3,11 @@ from typing import Any +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity +from .const import DOMAIN, MANUFACTURER from .coordinator import HydrawiseDataUpdateCoordinator @@ -20,14 +22,16 @@ def __init__( data: dict[str, Any], coordinator: HydrawiseDataUpdateCoordinator, description: EntityDescription, + device_id_key: str = "relay_id", ) -> None: """Initialize the Hydrawise entity.""" super().__init__(coordinator=coordinator) self.data = data self.entity_description = description - self._attr_name = f"{self.data['name']} {description.name}" - - @property - def extra_state_attributes(self) -> dict[str, Any]: - """Return the state attributes.""" - return {"identifier": self.data.get("relay")} + self._device_id = str(data.get(device_id_key)) + self._attr_unique_id = f"{self._device_id}_{description.key}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self._device_id)}, + name=data["name"], + manufacturer=MANUFACTURER, + ) diff --git a/tests/components/hydrawise/conftest.py b/tests/components/hydrawise/conftest.py index b6e22ec7b80280..309890181526e4 100644 --- a/tests/components/hydrawise/conftest.py +++ b/tests/components/hydrawise/conftest.py @@ -1,10 +1,17 @@ """Common fixtures for the Hydrawise tests.""" from collections.abc import Generator -from unittest.mock import AsyncMock, patch +from typing import Any +from unittest.mock import AsyncMock, Mock, patch import pytest +from homeassistant.components.hydrawise.const import DOMAIN +from homeassistant.const import CONF_API_KEY +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + @pytest.fixture def mock_setup_entry() -> Generator[AsyncMock, None, None]: @@ -13,3 +20,85 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: "homeassistant.components.hydrawise.async_setup_entry", return_value=True ) as mock_setup_entry: yield mock_setup_entry + + +@pytest.fixture +def mock_pydrawise( + mock_controller: dict[str, Any], + mock_zones: list[dict[str, Any]], +) -> Generator[Mock, None, None]: + """Mock LegacyHydrawise.""" + with patch("pydrawise.legacy.LegacyHydrawise", autospec=True) as mock_pydrawise: + mock_pydrawise.return_value.controller_info = {"controllers": [mock_controller]} + mock_pydrawise.return_value.current_controller = mock_controller + mock_pydrawise.return_value.controller_status = {"relays": mock_zones} + mock_pydrawise.return_value.relays = mock_zones + yield mock_pydrawise.return_value + + +@pytest.fixture +def mock_controller() -> dict[str, Any]: + """Mock Hydrawise controller.""" + return { + "name": "Home Controller", + "last_contact": 1693292420, + "serial_number": "0310b36090", + "controller_id": 52496, + "status": "Unknown", + } + + +@pytest.fixture +def mock_zones() -> list[dict[str, Any]]: + """Mock Hydrawise zones.""" + return [ + { + "name": "Zone One", + "period": 259200, + "relay": 1, + "relay_id": 5965394, + "run": 1800, + "stop": 1, + "time": 330597, + "timestr": "Sat", + "type": 1, + }, + { + "name": "Zone Two", + "period": 259200, + "relay": 2, + "relay_id": 5965395, + "run": 1788, + "stop": 1, + "time": 1, + "timestr": "Now", + "type": 106, + }, + ] + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Mock ConfigEntry.""" + return MockConfigEntry( + title="Hydrawise", + domain=DOMAIN, + data={ + CONF_API_KEY: "abc123", + }, + unique_id="hydrawise-customerid", + ) + + +@pytest.fixture +async def mock_added_config_entry( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_pydrawise: Mock, +) -> MockConfigEntry: + """Mock ConfigEntry that's been added to HA.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert DOMAIN in hass.config_entries.async_domains() + return mock_config_entry diff --git a/tests/components/hydrawise/test_device.py b/tests/components/hydrawise/test_device.py new file mode 100644 index 00000000000000..05c402faca779e --- /dev/null +++ b/tests/components/hydrawise/test_device.py @@ -0,0 +1,36 @@ +"""Tests for Hydrawise devices.""" + +from unittest.mock import Mock + +from homeassistant.components.hydrawise.const import DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr + + +def test_zones_in_device_registry( + hass: HomeAssistant, mock_added_config_entry: ConfigEntry, mock_pydrawise: Mock +) -> None: + """Test that devices are added to the device registry.""" + device_registry = dr.async_get(hass) + + device1 = device_registry.async_get_device(identifiers={(DOMAIN, "5965394")}) + assert device1 is not None + assert device1.name == "Zone One" + assert device1.manufacturer == "Hydrawise" + + device2 = device_registry.async_get_device(identifiers={(DOMAIN, "5965395")}) + assert device2 is not None + assert device2.name == "Zone Two" + assert device2.manufacturer == "Hydrawise" + + +def test_controller_in_device_registry( + hass: HomeAssistant, mock_added_config_entry: ConfigEntry, mock_pydrawise: Mock +) -> None: + """Test that devices are added to the device registry.""" + device_registry = dr.async_get(hass) + device = device_registry.async_get_device(identifiers={(DOMAIN, "52496")}) + assert device is not None + assert device.name == "Home Controller" + assert device.manufacturer == "Hydrawise"