diff --git a/homeassistant/components/homewizard/__init__.py b/homeassistant/components/homewizard/__init__.py index b50d87a940d..4a087988b61 100644 --- a/homeassistant/components/homewizard/__init__.py +++ b/homeassistant/components/homewizard/__init__.py @@ -1,14 +1,11 @@ """The Homewizard integration.""" import logging -from aiohwenergy import DisabledError - from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry as er -from homeassistant.helpers.update_coordinator import UpdateFailed from .const import DOMAIN, PLATFORMS from .coordinator import HWEnergyDeviceUpdateCoordinator as Coordinator @@ -69,16 +66,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Create coordinator coordinator = Coordinator(hass, entry.data[CONF_IP_ADDRESS]) try: - await coordinator.initialize_api() - - except DisabledError: - _LOGGER.error("API is disabled, enable API in HomeWizard Energy app") - return False - - except UpdateFailed as ex: - raise ConfigEntryNotReady from ex - - await coordinator.async_config_entry_first_refresh() + await coordinator.async_config_entry_first_refresh() + except ConfigEntryNotReady: + await coordinator.api.close() + raise # Finalize hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/homewizard/config_flow.py b/homeassistant/components/homewizard/config_flow.py index 6ab485f534f..df883baf3b1 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -4,9 +4,8 @@ from __future__ import annotations import logging from typing import Any -import aiohwenergy -from aiohwenergy.hwenergy import SUPPORTED_DEVICES -import async_timeout +from homewizard_energy import HomeWizardEnergy +from homewizard_energy.errors import DisabledError, UnsupportedError from voluptuous import Required, Schema from homeassistant import config_entries @@ -175,16 +174,19 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # Make connection with device # This is to test the connection and to get info for unique_id - energy_api = aiohwenergy.HomeWizardEnergy(ip_address) + energy_api = HomeWizardEnergy(ip_address) try: - with async_timeout.timeout(10): - await energy_api.initialize() + device = await energy_api.device() - except aiohwenergy.DisabledError as ex: + except DisabledError as ex: _LOGGER.error("API disabled, API must be enabled in the app") raise AbortFlow("api_not_enabled") from ex + except UnsupportedError as ex: + _LOGGER.error("API version unsuppored") + raise AbortFlow("unsupported_api_version") from ex + except Exception as ex: _LOGGER.exception( "Error connecting with Energy Device at %s", @@ -195,25 +197,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): finally: await energy_api.close() - if energy_api.device is None: - _LOGGER.error("Initialization failed") - raise AbortFlow("unknown_error") - - # Validate metadata - if energy_api.device.api_version != "v1": - raise AbortFlow("unsupported_api_version") - - if energy_api.device.product_type not in SUPPORTED_DEVICES: - _LOGGER.error( - "Device (%s) not supported by integration", - energy_api.device.product_type, - ) - raise AbortFlow("device_not_supported") - return { - CONF_PRODUCT_NAME: energy_api.device.product_name, - CONF_PRODUCT_TYPE: energy_api.device.product_type, - CONF_SERIAL: energy_api.device.serial, + CONF_PRODUCT_NAME: device.product_name, + CONF_PRODUCT_TYPE: device.product_type, + CONF_SERIAL: device.serial, } async def _async_set_and_check_unique_id(self, entry_info: dict[str, Any]) -> None: diff --git a/homeassistant/components/homewizard/const.py b/homeassistant/components/homewizard/const.py index 75c522a211e..c1c788d371b 100644 --- a/homeassistant/components/homewizard/const.py +++ b/homeassistant/components/homewizard/const.py @@ -5,10 +5,9 @@ from datetime import timedelta from typing import TypedDict # Set up. -from aiohwenergy.device import Device +from homewizard_energy.models import Data, Device, State from homeassistant.const import Platform -from homeassistant.helpers.typing import StateType DOMAIN = "homewizard" PLATFORMS = [Platform.SENSOR, Platform.SWITCH] @@ -29,4 +28,5 @@ class DeviceResponseEntry(TypedDict): """Dict describing a single response entry.""" device: Device - data: dict[str, StateType] + data: Data + state: State diff --git a/homeassistant/components/homewizard/coordinator.py b/homeassistant/components/homewizard/coordinator.py index 2cce88cbe36..e12edda63ae 100644 --- a/homeassistant/components/homewizard/coordinator.py +++ b/homeassistant/components/homewizard/coordinator.py @@ -1,11 +1,10 @@ """Update coordinator for HomeWizard.""" from __future__ import annotations -import asyncio import logging -import aiohwenergy -import async_timeout +from homewizard_energy import HomeWizardEnergy +from homewizard_energy.errors import DisabledError from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -19,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]): """Gather data for the energy device.""" - api: aiohwenergy.HomeWizardEnergy + api: HomeWizardEnergy def __init__( self, @@ -29,56 +28,20 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry] """Initialize Update Coordinator.""" super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) - - session = async_get_clientsession(hass) - self.api = aiohwenergy.HomeWizardEnergy(host, clientsession=session) + self.api = HomeWizardEnergy(host, clientsession=async_get_clientsession(hass)) async def _async_update_data(self) -> DeviceResponseEntry: """Fetch all device and sensor data from api.""" - async with async_timeout.timeout(10): - - if self.api.device is None: - await self.initialize_api() - - # Update all properties - try: - if not await self.api.update(): - raise UpdateFailed("Failed to communicate with device") - - except aiohwenergy.DisabledError as ex: - raise UpdateFailed( - "API disabled, API must be enabled in the app" - ) from ex - + # Update all properties + try: data: DeviceResponseEntry = { - "device": self.api.device, - "data": {}, + "device": await self.api.device(), + "data": await self.api.data(), + "state": await self.api.state(), } - for datapoint in self.api.data.available_datapoints: - data["data"][datapoint] = getattr(self.api.data, datapoint) + except DisabledError as ex: + raise UpdateFailed("API disabled, API must be enabled in the app") from ex return data - - async def initialize_api(self) -> aiohwenergy: - """Initialize API and validate connection.""" - - try: - await self.api.initialize() - - except (asyncio.TimeoutError, aiohwenergy.RequestError) as ex: - raise UpdateFailed( - f"Error connecting to the Energy device at {self.api.host}" - ) from ex - - except aiohwenergy.DisabledError as ex: - raise ex - - except aiohwenergy.AiohwenergyException as ex: - raise UpdateFailed("Unknown Energy API error occurred") from ex - - except Exception as ex: - raise UpdateFailed( - f"Unknown error connecting with Energy Device at {self.api.host}" - ) from ex diff --git a/homeassistant/components/homewizard/manifest.json b/homeassistant/components/homewizard/manifest.json index 1bd856334b7..e426234b449 100644 --- a/homeassistant/components/homewizard/manifest.json +++ b/homeassistant/components/homewizard/manifest.json @@ -4,9 +4,9 @@ "documentation": "https://www.home-assistant.io/integrations/homewizard", "codeowners": ["@DCSBL"], "dependencies": [], - "requirements": ["aiohwenergy==0.8.0"], + "requirements": ["python-homewizard-energy==1.0.3"], "zeroconf": ["_hwenergy._tcp.local."], "config_flow": true, "iot_class": "local_polling", - "loggers": ["aiohwenergy"] + "loggers": ["homewizard_energy"] } diff --git a/homeassistant/components/homewizard/sensor.py b/homeassistant/components/homewizard/sensor.py index 34479dd6b0a..b7f759f6a0e 100644 --- a/homeassistant/components/homewizard/sensor.py +++ b/homeassistant/components/homewizard/sensor.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Final +from typing import Final, cast from homeassistant.components.sensor import ( SensorDeviceClass, @@ -129,12 +129,9 @@ async def async_setup_entry( coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] entities = [] - if coordinator.api.data is not None: + if coordinator.data["data"] is not None: for description in SENSORS: - if ( - description.key in coordinator.api.data.available_datapoints - and getattr(coordinator.api.data, description.key) is not None - ): + if getattr(coordinator.data["data"], description.key) is not None: entities.append(HWEnergySensor(coordinator, entry, description)) async_add_entities(entities) @@ -165,7 +162,7 @@ class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorE "total_power_export_t1_kwh", "total_power_export_t2_kwh", ]: - if self.data["data"][self.data_type] == 0: + if self.native_value == 0: self._attr_entity_registry_enabled_default = False @property @@ -187,9 +184,9 @@ class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorE @property def native_value(self) -> StateType: """Return state of meter.""" - return self.data["data"][self.data_type] + return cast(StateType, getattr(self.data["data"], self.data_type)) @property def available(self) -> bool: """Return availability of meter.""" - return super().available and self.data_type in self.data["data"] + return super().available and self.native_value is not None diff --git a/homeassistant/components/homewizard/switch.py b/homeassistant/components/homewizard/switch.py index 3c6b1a1c5dc..eb2e9c49afe 100644 --- a/homeassistant/components/homewizard/switch.py +++ b/homeassistant/components/homewizard/switch.py @@ -22,7 +22,7 @@ async def async_setup_entry( """Set up switches.""" coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - if coordinator.api.state: + if coordinator.data["state"]: async_add_entities( [ HWEnergyMainSwitchEntity(coordinator, entry), @@ -70,12 +70,12 @@ class HWEnergyMainSwitchEntity(HWEnergySwitchEntity): 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.api.state_set(power_on=True) await self.coordinator.async_refresh() async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" - await self.coordinator.api.state.set(power_on=False) + await self.coordinator.api.state_set(power_on=False) await self.coordinator.async_refresh() @property @@ -85,12 +85,12 @@ class HWEnergyMainSwitchEntity(HWEnergySwitchEntity): This switch becomes unavailable when switch_lock is enabled. """ - return super().available and not self.coordinator.api.state.switch_lock + return super().available and not self.coordinator.data["state"].switch_lock @property def is_on(self) -> bool: """Return true if switch is on.""" - return bool(self.coordinator.api.state.power_on) + return bool(self.coordinator.data["state"].power_on) class HWEnergySwitchLockEntity(HWEnergySwitchEntity): @@ -115,15 +115,15 @@ class HWEnergySwitchLockEntity(HWEnergySwitchEntity): 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.api.state_set(switch_lock=True) await self.coordinator.async_refresh() async def async_turn_off(self, **kwargs: Any) -> None: """Turn switch-lock off.""" - await self.coordinator.api.state.set(switch_lock=False) + await self.coordinator.api.state_set(switch_lock=False) await self.coordinator.async_refresh() @property def is_on(self) -> bool: """Return true if switch is on.""" - return bool(self.coordinator.api.state.switch_lock) + return bool(self.coordinator.data["state"].switch_lock) diff --git a/requirements_all.txt b/requirements_all.txt index 38cc91c2574..a52d465fac6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -171,9 +171,6 @@ aiohttp_cors==0.7.0 # homeassistant.components.hue aiohue==4.4.1 -# homeassistant.components.homewizard -aiohwenergy==0.8.0 - # homeassistant.components.imap aioimaplib==0.9.0 @@ -1903,6 +1900,9 @@ python-gc100==1.0.3a0 # homeassistant.components.gitlab_ci python-gitlab==1.6.0 +# homeassistant.components.homewizard +python-homewizard-energy==1.0.3 + # homeassistant.components.hp_ilo python-hpilo==4.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cefa305a5b9..897ffe50074 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -155,9 +155,6 @@ aiohttp_cors==0.7.0 # homeassistant.components.hue aiohue==4.4.1 -# homeassistant.components.homewizard -aiohwenergy==0.8.0 - # homeassistant.components.apache_kafka aiokafka==0.6.0 @@ -1265,6 +1262,9 @@ python-ecobee-api==0.2.14 # homeassistant.components.darksky python-forecastio==1.4.0 +# homeassistant.components.homewizard +python-homewizard-energy==1.0.3 + # homeassistant.components.izone python-izone==1.2.3 diff --git a/tests/components/homewizard/generator.py b/tests/components/homewizard/generator.py index 74d33c9e609..0f94580ad84 100644 --- a/tests/components/homewizard/generator.py +++ b/tests/components/homewizard/generator.py @@ -2,6 +2,8 @@ from unittest.mock import AsyncMock +from homewizard_energy.models import Device + def get_mock_device( serial="aabbccddeeff", @@ -13,15 +15,18 @@ def get_mock_device( mock_device = AsyncMock() mock_device.host = host - mock_device.device.product_name = product_name - mock_device.device.product_type = product_type - mock_device.device.serial = serial - mock_device.device.api_version = "v1" - mock_device.device.firmware_version = "1.00" + mock_device.device = AsyncMock( + return_value=Device( + product_name=product_name, + product_type=product_type, + serial=serial, + api_version="V1", + firmware_version="1.00", + ) + ) + mock_device.data = AsyncMock(return_value=None) + mock_device.state = AsyncMock(return_value=None) - mock_device.state = None - - mock_device.initialize = AsyncMock() mock_device.close = AsyncMock() return mock_device diff --git a/tests/components/homewizard/test_config_flow.py b/tests/components/homewizard/test_config_flow.py index d0dc7d04509..d2e7d4c58ae 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -2,7 +2,7 @@ import logging from unittest.mock import patch -from aiohwenergy import DisabledError +from homewizard_energy.errors import DisabledError, UnsupportedError from homeassistant import config_entries from homeassistant.components import zeroconf @@ -33,7 +33,10 @@ async def test_manual_flow_works(hass, aioclient_mock): assert result["type"] == "form" assert result["step_id"] == "user" - with patch("aiohwenergy.HomeWizardEnergy", return_value=device,), patch( + with patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=device, + ), patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, ) as mock_setup_entry: @@ -42,12 +45,12 @@ async def test_manual_flow_works(hass, aioclient_mock): ) assert result["type"] == "create_entry" - assert result["title"] == f"{device.device.product_name} (aabbccddeeff)" + assert result["title"] == "P1 meter (aabbccddeeff)" assert result["data"][CONF_IP_ADDRESS] == "2.2.2.2" assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert len(device.initialize.mock_calls) == 1 + assert len(device.device.mock_calls) == 1 assert len(device.close.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -72,7 +75,10 @@ async def test_discovery_flow_works(hass, aioclient_mock): }, ) - with patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + with patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=get_mock_device(), + ): flow = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, @@ -82,7 +88,10 @@ async def test_discovery_flow_works(hass, aioclient_mock): with patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, - ), patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + ), patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=get_mock_device(), + ): result = await hass.config_entries.flow.async_configure( flow["flow_id"], user_input=None ) @@ -92,7 +101,10 @@ async def test_discovery_flow_works(hass, aioclient_mock): with patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, - ), patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + ), patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=get_mock_device(), + ): result = await hass.config_entries.flow.async_configure( flow["flow_id"], user_input={"ip_address": "192.168.43.183"} ) @@ -113,7 +125,10 @@ async def test_config_flow_imports_entry(aioclient_mock, hass): mock_entry = MockConfigEntry(domain="homewizard_energy", data={"host": "1.2.3.4"}) mock_entry.add_to_hass(hass) - with patch("aiohwenergy.HomeWizardEnergy", return_value=device,), patch( + with patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=device, + ), patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, ) as mock_setup_entry: @@ -127,11 +142,11 @@ async def test_config_flow_imports_entry(aioclient_mock, hass): ) assert result["type"] == "create_entry" - assert result["title"] == f"{device.device.product_name} (aabbccddeeff)" + assert result["title"] == "P1 meter (aabbccddeeff)" assert result["data"][CONF_IP_ADDRESS] == "1.2.3.4" assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert len(device.initialize.mock_calls) == 1 + assert len(device.device.mock_calls) == 1 assert len(device.close.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -166,7 +181,10 @@ async def test_discovery_disabled_api(hass, aioclient_mock): with patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, - ), patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + ), patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=get_mock_device(), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"ip_address": "192.168.43.183"} ) @@ -240,7 +258,7 @@ async def test_check_disabled_api(hass, aioclient_mock): raise DisabledError device = get_mock_device() - device.initialize.side_effect = mock_initialize + device.device.side_effect = mock_initialize result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -250,7 +268,7 @@ async def test_check_disabled_api(hass, aioclient_mock): assert result["step_id"] == "user" with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", return_value=device, ): result = await hass.config_entries.flow.async_configure( @@ -268,7 +286,7 @@ async def test_check_error_handling_api(hass, aioclient_mock): raise Exception() device = get_mock_device() - device.initialize.side_effect = mock_initialize + device.device.side_effect = mock_initialize result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -278,32 +296,7 @@ async def test_check_error_handling_api(hass, aioclient_mock): assert result["step_id"] == "user" with patch( - "aiohwenergy.HomeWizardEnergy", - return_value=device, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_IP_ADDRESS: "2.2.2.2"} - ) - - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "unknown_error" - - -async def test_check_detects_unexpected_api_response(hass, aioclient_mock): - """Test check detecting device endpoint failed fetching data.""" - - device = get_mock_device() - device.device = None - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - - with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", return_value=device, ): result = await hass.config_entries.flow.async_configure( @@ -317,8 +310,11 @@ async def test_check_detects_unexpected_api_response(hass, aioclient_mock): async def test_check_detects_invalid_api(hass, aioclient_mock): """Test check detecting device endpoint failed fetching data.""" + def mock_initialize(): + raise UnsupportedError + device = get_mock_device() - device.device.api_version = "not_v1" + device.device.side_effect = mock_initialize result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -328,7 +324,7 @@ async def test_check_detects_invalid_api(hass, aioclient_mock): assert result["step_id"] == "user" with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", return_value=device, ): result = await hass.config_entries.flow.async_configure( @@ -337,27 +333,3 @@ async def test_check_detects_invalid_api(hass, aioclient_mock): assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "unsupported_api_version" - - -async def test_check_detects_unsuported_device(hass, aioclient_mock): - """Test check detecting device endpoint failed fetching data.""" - - device = get_mock_device(product_type="not_an_energy_device") - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - - with patch( - "aiohwenergy.HomeWizardEnergy", - return_value=device, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_IP_ADDRESS: "2.2.2.2"} - ) - - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "device_not_supported" diff --git a/tests/components/homewizard/test_init.py b/tests/components/homewizard/test_init.py index 984a5431004..c7cc5bd7bdb 100644 --- a/tests/components/homewizard/test_init.py +++ b/tests/components/homewizard/test_init.py @@ -2,7 +2,7 @@ from asyncio import TimeoutError from unittest.mock import patch -from aiohwenergy import AiohwenergyException, DisabledError +from homewizard_energy.errors import DisabledError, HomeWizardEnergyException from homeassistant import config_entries from homeassistant.components.homewizard.const import DOMAIN @@ -28,7 +28,7 @@ async def test_load_unload(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) @@ -50,7 +50,7 @@ async def test_load_failed_host_unavailable(aioclient_mock, hass): raise TimeoutError() device = get_mock_device() - device.initialize.side_effect = MockInitialize + device.device.side_effect = MockInitialize entry = MockConfigEntry( domain=DOMAIN, @@ -60,7 +60,7 @@ async def test_load_failed_host_unavailable(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) @@ -127,7 +127,7 @@ async def test_init_accepts_and_migrates_old_entry(aioclient_mock, hass): # Add the entry_id to trigger migration with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(imported_entry.entry_id) @@ -168,7 +168,7 @@ async def test_load_detect_api_disabled(aioclient_mock, hass): raise DisabledError() device = get_mock_device() - device.initialize.side_effect = MockInitialize + device.device.side_effect = MockInitialize entry = MockConfigEntry( domain=DOMAIN, @@ -178,24 +178,24 @@ async def test_load_detect_api_disabled(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert entry.state is ConfigEntryState.SETUP_ERROR + assert entry.state is ConfigEntryState.SETUP_RETRY -async def test_load_handles_aiohwenergy_exception(aioclient_mock, hass): +async def test_load_handles_homewizardenergy_exception(aioclient_mock, hass): """Test setup handles exception from API.""" def MockInitialize(): - raise AiohwenergyException() + raise HomeWizardEnergyException() device = get_mock_device() - device.initialize.side_effect = MockInitialize + device.device.side_effect = MockInitialize entry = MockConfigEntry( domain=DOMAIN, @@ -205,7 +205,7 @@ async def test_load_handles_aiohwenergy_exception(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) @@ -222,7 +222,7 @@ async def test_load_handles_generic_exception(aioclient_mock, hass): raise Exception() device = get_mock_device() - device.initialize.side_effect = MockInitialize + device.device.side_effect = MockInitialize entry = MockConfigEntry( domain=DOMAIN, @@ -232,7 +232,7 @@ async def test_load_handles_generic_exception(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) @@ -256,7 +256,7 @@ async def test_load_handles_initialization_error(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/homewizard/test_sensor.py b/tests/components/homewizard/test_sensor.py index c1a98c07108..8195aa11708 100644 --- a/tests/components/homewizard/test_sensor.py +++ b/tests/components/homewizard/test_sensor.py @@ -3,7 +3,8 @@ from datetime import timedelta from unittest.mock import AsyncMock, patch -from aiohwenergy.errors import DisabledError +from homewizard_energy.errors import DisabledError, RequestError +from homewizard_energy.models import Data from homeassistant.components.sensor import ( ATTR_STATE_CLASS, @@ -36,13 +37,10 @@ async def test_sensor_entity_smr_version( """Test entity loads smr version.""" api = get_mock_device() - api.data.available_datapoints = [ - "smr_version", - ] - api.data.smr_version = 50 + api.data = AsyncMock(return_value=Data.from_dict({"smr_version": 50})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -77,13 +75,10 @@ async def test_sensor_entity_meter_model( """Test entity loads meter model.""" api = get_mock_device() - api.data.available_datapoints = [ - "meter_model", - ] - api.data.meter_model = "Model X" + api.data = AsyncMock(return_value=Data.from_dict({"meter_model": "Model X"})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -118,13 +113,10 @@ async def test_sensor_entity_wifi_ssid(hass, mock_config_entry_data, mock_config """Test entity loads wifi ssid.""" api = get_mock_device() - api.data.available_datapoints = [ - "wifi_ssid", - ] - api.data.wifi_ssid = "My Wifi" + api.data = AsyncMock(return_value=Data.from_dict({"wifi_ssid": "My Wifi"})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -159,13 +151,10 @@ async def test_sensor_entity_wifi_strength( """Test entity loads wifi strength.""" api = get_mock_device() - api.data.available_datapoints = [ - "wifi_strength", - ] - api.data.wifi_strength = 42 + api.data = AsyncMock(return_value=Data.from_dict({"wifi_strength": 42})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -189,13 +178,12 @@ async def test_sensor_entity_total_power_import_t1_kwh( """Test entity loads total power import t1.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_import_t1_kwh", - ] - api.data.total_power_import_t1_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_import_t1_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -232,13 +220,12 @@ async def test_sensor_entity_total_power_import_t2_kwh( """Test entity loads total power import t2.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_import_t2_kwh", - ] - api.data.total_power_import_t2_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_import_t2_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -275,13 +262,12 @@ async def test_sensor_entity_total_power_export_t1_kwh( """Test entity loads total power export t1.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_export_t1_kwh", - ] - api.data.total_power_export_t1_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_export_t1_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -318,13 +304,12 @@ async def test_sensor_entity_total_power_export_t2_kwh( """Test entity loads total power export t2.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_export_t2_kwh", - ] - api.data.total_power_export_t2_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_export_t2_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -361,13 +346,10 @@ async def test_sensor_entity_active_power( """Test entity loads active power.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_w", - ] - api.data.active_power_w = 123.123 + api.data = AsyncMock(return_value=Data.from_dict({"active_power_w": 123.123})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -402,13 +384,10 @@ async def test_sensor_entity_active_power_l1( """Test entity loads active power l1.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_l1_w", - ] - api.data.active_power_l1_w = 123.123 + api.data = AsyncMock(return_value=Data.from_dict({"active_power_l1_w": 123.123})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -445,13 +424,10 @@ async def test_sensor_entity_active_power_l2( """Test entity loads active power l2.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_l2_w", - ] - api.data.active_power_l2_w = 456.456 + api.data = AsyncMock(return_value=Data.from_dict({"active_power_l2_w": 456.456})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -488,13 +464,10 @@ async def test_sensor_entity_active_power_l3( """Test entity loads active power l3.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_l3_w", - ] - api.data.active_power_l3_w = 789.789 + api.data = AsyncMock(return_value=Data.from_dict({"active_power_l3_w": 789.789})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -529,13 +502,10 @@ async def test_sensor_entity_total_gas(hass, mock_config_entry_data, mock_config """Test entity loads total gas.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_gas_m3", - ] - api.data.total_gas_m3 = 50 + api.data = AsyncMock(return_value=Data.from_dict({"total_gas_m3": 50})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -570,17 +540,14 @@ async def test_sensor_entity_disabled_when_null( """Test sensor disables data with null by default.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_l2_w", - "active_power_l3_w", - "total_gas_m3", - ] - api.data.active_power_l2_w = None - api.data.active_power_l3_w = None - api.data.total_gas_m3 = None + api.data = AsyncMock( + return_value=Data.from_dict( + {"active_power_l2_w": None, "active_power_l3_w": None, "total_gas_m3": None} + ) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -612,15 +579,14 @@ async def test_sensor_entity_export_disabled_when_unused( """Test sensor disables export if value is 0.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_export_t1_kwh", - "total_power_export_t2_kwh", - ] - api.data.total_power_export_t1_kwh = 0 - api.data.total_power_export_t2_kwh = 0 + api.data = AsyncMock( + return_value=Data.from_dict( + {"total_power_export_t1_kwh": 0, "total_power_export_t2_kwh": 0} + ) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -649,17 +615,14 @@ async def test_sensors_unreachable(hass, mock_config_entry_data, mock_config_ent """Test sensor handles api unreachable.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_import_t1_kwh", - ] - api.data.total_power_import_t1_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_import_t1_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): - api.update = AsyncMock(return_value=True) - entry = mock_config_entry entry.data = mock_config_entry_data entry.add_to_hass(hass) @@ -675,7 +638,7 @@ async def test_sensors_unreachable(hass, mock_config_entry_data, mock_config_ent == "1234.123" ) - api.update = AsyncMock(return_value=False) + api.data.side_effect = RequestError async_fire_time_changed(hass, utcnow + timedelta(seconds=5)) await hass.async_block_till_done() assert ( @@ -685,7 +648,7 @@ async def test_sensors_unreachable(hass, mock_config_entry_data, mock_config_ent == "unavailable" ) - api.update = AsyncMock(return_value=True) + api.data.side_effect = None async_fire_time_changed(hass, utcnow + timedelta(seconds=10)) await hass.async_block_till_done() assert ( @@ -700,17 +663,14 @@ async def test_api_disabled(hass, mock_config_entry_data, mock_config_entry): """Test sensor handles api unreachable.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_import_t1_kwh", - ] - api.data.total_power_import_t1_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_import_t1_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): - api.update = AsyncMock(return_value=True) - entry = mock_config_entry entry.data = mock_config_entry_data entry.add_to_hass(hass) @@ -726,7 +686,7 @@ async def test_api_disabled(hass, mock_config_entry_data, mock_config_entry): == "1234.123" ) - api.update = AsyncMock(side_effect=DisabledError) + api.data.side_effect = DisabledError async_fire_time_changed(hass, utcnow + timedelta(seconds=5)) await hass.async_block_till_done() assert ( @@ -736,7 +696,7 @@ async def test_api_disabled(hass, mock_config_entry_data, mock_config_entry): == "unavailable" ) - api.update = AsyncMock(return_value=True) + api.data.side_effect = None async_fire_time_changed(hass, utcnow + timedelta(seconds=10)) await hass.async_block_till_done() assert ( diff --git a/tests/components/homewizard/test_switch.py b/tests/components/homewizard/test_switch.py index f3792a9d75b..118f0774a47 100644 --- a/tests/components/homewizard/test_switch.py +++ b/tests/components/homewizard/test_switch.py @@ -2,6 +2,8 @@ from unittest.mock import AsyncMock, patch +from homewizard_energy.models import State + from homeassistant.components import switch from homeassistant.components.switch import DEVICE_CLASS_OUTLET, DEVICE_CLASS_SWITCH from homeassistant.const import ( @@ -27,7 +29,7 @@ async def test_switch_entity_not_loaded_when_not_available( api = get_mock_device() with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -48,13 +50,12 @@ async def test_switch_loads_entities(hass, mock_config_entry_data, mock_config_e """Test entity loads smr version.""" api = get_mock_device() - api.state = AsyncMock() - - api.state.power_on = False - api.state.switch_lock = False + api.state = AsyncMock( + return_value=State.from_dict({"power_on": False, "switch_lock": False}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -104,17 +105,19 @@ async def test_switch_power_on_off(hass, mock_config_entry_data, mock_config_ent """Test entity turns switch on and off.""" api = get_mock_device() - api.state = AsyncMock() - api.state.power_on = False - api.state.switch_lock = False + api.state = AsyncMock( + return_value=State.from_dict({"power_on": False, "switch_lock": False}) + ) - def set_power_on(power_on): - api.state.power_on = power_on + def state_set(power_on): + api.state = AsyncMock( + return_value=State.from_dict({"power_on": power_on, "switch_lock": False}) + ) - api.state.set = AsyncMock(side_effect=set_power_on) + api.state_set = AsyncMock(side_effect=state_set) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -138,7 +141,7 @@ async def test_switch_power_on_off(hass, mock_config_entry_data, mock_config_ent ) await hass.async_block_till_done() - assert len(api.state.set.mock_calls) == 1 + assert len(api.state_set.mock_calls) == 1 assert ( hass.states.get("switch.product_name_aabbccddeeff_switch").state == STATE_ON ) @@ -156,7 +159,7 @@ async def test_switch_power_on_off(hass, mock_config_entry_data, mock_config_ent hass.states.get("switch.product_name_aabbccddeeff_switch").state == STATE_OFF ) - assert len(api.state.set.mock_calls) == 2 + assert len(api.state_set.mock_calls) == 2 async def test_switch_lock_power_on_off( @@ -165,17 +168,19 @@ async def test_switch_lock_power_on_off( """Test entity turns switch on and off.""" api = get_mock_device() - api.state = AsyncMock() - api.state.power_on = False - api.state.switch_lock = False + api.state = AsyncMock( + return_value=State.from_dict({"power_on": False, "switch_lock": False}) + ) - def set_switch_lock(switch_lock): - api.state.switch_lock = switch_lock + def state_set(switch_lock): + api.state = AsyncMock( + return_value=State.from_dict({"power_on": True, "switch_lock": switch_lock}) + ) - api.state.set = AsyncMock(side_effect=set_switch_lock) + api.state_set = AsyncMock(side_effect=state_set) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -199,7 +204,7 @@ async def test_switch_lock_power_on_off( ) await hass.async_block_till_done() - assert len(api.state.set.mock_calls) == 1 + assert len(api.state_set.mock_calls) == 1 assert ( hass.states.get("switch.product_name_aabbccddeeff_switch_lock").state == STATE_ON @@ -218,7 +223,7 @@ async def test_switch_lock_power_on_off( hass.states.get("switch.product_name_aabbccddeeff_switch_lock").state == STATE_OFF ) - assert len(api.state.set.mock_calls) == 2 + assert len(api.state_set.mock_calls) == 2 async def test_switch_lock_sets_power_on_unavailable( @@ -227,17 +232,19 @@ async def test_switch_lock_sets_power_on_unavailable( """Test entity turns switch on and off.""" api = get_mock_device() - api.state = AsyncMock() - api.state.power_on = True - api.state.switch_lock = False + api.state = AsyncMock( + return_value=State.from_dict({"power_on": True, "switch_lock": False}) + ) - def set_switch_lock(switch_lock): - api.state.switch_lock = switch_lock + def state_set(switch_lock): + api.state = AsyncMock( + return_value=State.from_dict({"power_on": True, "switch_lock": switch_lock}) + ) - api.state.set = AsyncMock(side_effect=set_switch_lock) + api.state_set = AsyncMock(side_effect=state_set) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -264,7 +271,7 @@ async def test_switch_lock_sets_power_on_unavailable( ) await hass.async_block_till_done() - assert len(api.state.set.mock_calls) == 1 + assert len(api.state_set.mock_calls) == 1 assert ( hass.states.get("switch.product_name_aabbccddeeff_switch").state == STATE_UNAVAILABLE @@ -290,4 +297,4 @@ async def test_switch_lock_sets_power_on_unavailable( hass.states.get("switch.product_name_aabbccddeeff_switch_lock").state == STATE_OFF ) - assert len(api.state.set.mock_calls) == 2 + assert len(api.state_set.mock_calls) == 2