From b6f16f87a786b007e744b214974189e08d4b7e73 Mon Sep 17 00:00:00 2001 From: Jeef <jeeftor@users.noreply.github.com> Date: Wed, 29 Jun 2022 09:51:39 -0600 Subject: [PATCH] Bump intellifire4py to 2.0.0 (#72563) * Enable Flame/Pilot switch * Enable Flame/Pilot switch * Update homeassistant/components/intellifire/switch.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/intellifire/switch.py Thats a great fix! Co-authored-by: J. Nick Koston <nick@koston.org> * write not update * fixed forced upates * removed data field * Refactor to support update to backing library * pre-push-ninja-style * moving over * fixed coverage * removed tuple junk * re-added description * Update homeassistant/components/intellifire/translations/en.json Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> * adressing PR comments * actually store generated values * Update homeassistant/components/intellifire/__init__.py Way better option! Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> --- .../components/intellifire/__init__.py | 54 ++++++++++++++++--- .../components/intellifire/config_flow.py | 35 ++++++------ homeassistant/components/intellifire/const.py | 2 + .../components/intellifire/coordinator.py | 48 +++++++++-------- .../components/intellifire/manifest.json | 2 +- .../components/intellifire/switch.py | 25 ++++----- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/intellifire/conftest.py | 2 +- .../intellifire/test_config_flow.py | 27 ++++++---- 10 files changed, 126 insertions(+), 73 deletions(-) diff --git a/homeassistant/components/intellifire/__init__.py b/homeassistant/components/intellifire/__init__.py index 83c6e05f572..034e74c2aa6 100644 --- a/homeassistant/components/intellifire/__init__.py +++ b/homeassistant/components/intellifire/__init__.py @@ -2,15 +2,22 @@ from __future__ import annotations from aiohttp import ClientConnectionError -from intellifire4py import IntellifireAsync, IntellifireControlAsync +from intellifire4py import IntellifireControlAsync from intellifire4py.exceptions import LoginException +from intellifire4py.intellifire import IntellifireAPICloud, IntellifireAPILocal from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + Platform, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from .const import DOMAIN, LOGGER +from .const import CONF_USER_ID, DOMAIN, LOGGER from .coordinator import IntellifireDataUpdateCoordinator PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH] @@ -24,8 +31,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: LOGGER.debug("Old config entry format detected: %s", entry.unique_id) raise ConfigEntryAuthFailed - # Define the API Objects - read_object = IntellifireAsync(entry.data[CONF_HOST]) ift_control = IntellifireControlAsync( fireplace_ip=entry.data[CONF_HOST], ) @@ -42,9 +47,46 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: finally: await ift_control.close() + # Extract API Key and User_ID from ift_control + # Eventually this will migrate to using IntellifireAPICloud + + if CONF_USER_ID not in entry.data or CONF_API_KEY not in entry.data: + LOGGER.info( + "Updating intellifire config entry for %s with api information", + entry.unique_id, + ) + cloud_api = IntellifireAPICloud() + await cloud_api.login( + username=entry.data[CONF_USERNAME], + password=entry.data[CONF_PASSWORD], + ) + api_key = cloud_api.get_fireplace_api_key() + user_id = cloud_api.get_user_id() + # Update data entry + hass.config_entries.async_update_entry( + entry, + data={ + **entry.data, + CONF_API_KEY: api_key, + CONF_USER_ID: user_id, + }, + ) + + else: + api_key = entry.data[CONF_API_KEY] + user_id = entry.data[CONF_USER_ID] + + # Instantiate local control + api = IntellifireAPILocal( + fireplace_ip=entry.data[CONF_HOST], + api_key=api_key, + user_id=user_id, + ) + # Define the update coordinator coordinator = IntellifireDataUpdateCoordinator( - hass=hass, read_api=read_object, control_api=ift_control + hass=hass, + api=api, ) await coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/intellifire/config_flow.py b/homeassistant/components/intellifire/config_flow.py index df13795a4ed..23bd92b0715 100644 --- a/homeassistant/components/intellifire/config_flow.py +++ b/homeassistant/components/intellifire/config_flow.py @@ -6,20 +6,17 @@ from dataclasses import dataclass from typing import Any from aiohttp import ClientConnectionError -from intellifire4py import ( - AsyncUDPFireplaceFinder, - IntellifireAsync, - IntellifireControlAsync, -) +from intellifire4py import AsyncUDPFireplaceFinder from intellifire4py.exceptions import LoginException +from intellifire4py.intellifire import IntellifireAPICloud, IntellifireAPILocal import voluptuous as vol from homeassistant import config_entries from homeassistant.components.dhcp import DhcpServiceInfo -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult -from .const import DOMAIN, LOGGER +from .const import CONF_USER_ID, DOMAIN, LOGGER STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str}) @@ -39,7 +36,8 @@ async def validate_host_input(host: str) -> str: Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. """ - api = IntellifireAsync(host) + LOGGER.debug("Instantiating IntellifireAPI with host: [%s]", host) + api = IntellifireAPILocal(fireplace_ip=host) await api.poll() serial = api.data.serial LOGGER.debug("Found a fireplace: %s", serial) @@ -83,17 +81,20 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, *, host: str, username: str, password: str, serial: str ): """Validate username/password against api.""" - ift_control = IntellifireControlAsync(fireplace_ip=host) - LOGGER.debug("Attempting login to iftapi with: %s", username) - # This can throw an error which will be handled above - try: - await ift_control.login(username=username, password=password) - await ift_control.get_username() - finally: - await ift_control.close() - data = {CONF_HOST: host, CONF_PASSWORD: password, CONF_USERNAME: username} + ift_cloud = IntellifireAPICloud() + await ift_cloud.login(username=username, password=password) + api_key = ift_cloud.get_fireplace_api_key() + user_id = ift_cloud.get_user_id() + + data = { + CONF_HOST: host, + CONF_PASSWORD: password, + CONF_USERNAME: username, + CONF_API_KEY: api_key, + CONF_USER_ID: user_id, + } # Update or Create existing_entry = await self.async_set_unique_id(serial) diff --git a/homeassistant/components/intellifire/const.py b/homeassistant/components/intellifire/const.py index fe715c3ce8a..2e9a2fabc06 100644 --- a/homeassistant/components/intellifire/const.py +++ b/homeassistant/components/intellifire/const.py @@ -5,6 +5,8 @@ import logging DOMAIN = "intellifire" +CONF_USER_ID = "user_id" + LOGGER = logging.getLogger(__package__) CONF_SERIAL = "serial" diff --git a/homeassistant/components/intellifire/coordinator.py b/homeassistant/components/intellifire/coordinator.py index 9b74bd81653..39f197285d4 100644 --- a/homeassistant/components/intellifire/coordinator.py +++ b/homeassistant/components/intellifire/coordinator.py @@ -5,11 +5,8 @@ from datetime import timedelta from aiohttp import ClientConnectionError from async_timeout import timeout -from intellifire4py import ( - IntellifireAsync, - IntellifireControlAsync, - IntellifirePollData, -) +from intellifire4py import IntellifirePollData +from intellifire4py.intellifire import IntellifireAPILocal from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo @@ -24,8 +21,7 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData def __init__( self, hass: HomeAssistant, - read_api: IntellifireAsync, - control_api: IntellifireControlAsync, + api: IntellifireAPILocal, ) -> None: """Initialize the Coordinator.""" super().__init__( @@ -34,27 +30,37 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData name=DOMAIN, update_interval=timedelta(seconds=15), ) - self._read_api = read_api - self._control_api = control_api + self._api = api async def _async_update_data(self) -> IntellifirePollData: - LOGGER.debug("Calling update loop on IntelliFire") - async with timeout(100): - try: - await self._read_api.poll() - except (ConnectionError, ClientConnectionError) as exception: - raise UpdateFailed from exception - return self._read_api.data + + if not self._api.is_polling_in_background: + LOGGER.info("Starting Intellifire Background Polling Loop") + await self._api.start_background_polling() + + # Don't return uninitialized poll data + async with timeout(15): + try: + await self._api.poll() + except (ConnectionError, ClientConnectionError) as exception: + raise UpdateFailed from exception + + LOGGER.info("Failure Count %d", self._api.failed_poll_attempts) + if self._api.failed_poll_attempts > 10: + LOGGER.debug("Too many polling errors - raising exception") + raise UpdateFailed + + return self._api.data @property - def read_api(self) -> IntellifireAsync: + def read_api(self) -> IntellifireAPILocal: """Return the Status API pointer.""" - return self._read_api + return self._api @property - def control_api(self) -> IntellifireControlAsync: + def control_api(self) -> IntellifireAPILocal: """Return the control API.""" - return self._control_api + return self._api @property def device_info(self) -> DeviceInfo: @@ -65,5 +71,5 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData name="IntelliFire Fireplace", identifiers={("IntelliFire", f"{self.read_api.data.serial}]")}, sw_version=self.read_api.data.fw_ver_str, - configuration_url=f"http://{self.read_api.ip}/poll", + configuration_url=f"http://{self._api.fireplace_ip}/poll", ) diff --git a/homeassistant/components/intellifire/manifest.json b/homeassistant/components/intellifire/manifest.json index 388ce0c86cb..cd1a12a36bf 100644 --- a/homeassistant/components/intellifire/manifest.json +++ b/homeassistant/components/intellifire/manifest.json @@ -3,7 +3,7 @@ "name": "IntelliFire", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/intellifire", - "requirements": ["intellifire4py==1.0.2"], + "requirements": ["intellifire4py==2.0.0"], "codeowners": ["@jeeftor"], "iot_class": "local_polling", "loggers": ["intellifire4py"], diff --git a/homeassistant/components/intellifire/switch.py b/homeassistant/components/intellifire/switch.py index 9c196a59fd4..ef0363696c4 100644 --- a/homeassistant/components/intellifire/switch.py +++ b/homeassistant/components/intellifire/switch.py @@ -5,7 +5,8 @@ from collections.abc import Awaitable, Callable from dataclasses import dataclass from typing import Any -from intellifire4py import IntellifireControlAsync, IntellifirePollData +from intellifire4py import IntellifirePollData +from intellifire4py.intellifire import IntellifireAPILocal from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry @@ -21,8 +22,8 @@ from .entity import IntellifireEntity class IntellifireSwitchRequiredKeysMixin: """Mixin for required keys.""" - on_fn: Callable[[IntellifireControlAsync], Awaitable] - off_fn: Callable[[IntellifireControlAsync], Awaitable] + on_fn: Callable[[IntellifireAPILocal], Awaitable] + off_fn: Callable[[IntellifireAPILocal], Awaitable] value_fn: Callable[[IntellifirePollData], bool] @@ -37,24 +38,16 @@ INTELLIFIRE_SWITCHES: tuple[IntellifireSwitchEntityDescription, ...] = ( IntellifireSwitchEntityDescription( key="on_off", name="Flame", - on_fn=lambda control_api: control_api.flame_on( - fireplace=control_api.default_fireplace - ), - off_fn=lambda control_api: control_api.flame_off( - fireplace=control_api.default_fireplace - ), + on_fn=lambda control_api: control_api.flame_on(), + off_fn=lambda control_api: control_api.flame_off(), value_fn=lambda data: data.is_on, ), IntellifireSwitchEntityDescription( key="pilot", name="Pilot Light", icon="mdi:fire-alert", - on_fn=lambda control_api: control_api.pilot_on( - fireplace=control_api.default_fireplace - ), - off_fn=lambda control_api: control_api.pilot_off( - fireplace=control_api.default_fireplace - ), + on_fn=lambda control_api: control_api.pilot_on(), + off_fn=lambda control_api: control_api.pilot_off(), value_fn=lambda data: data.pilot_on, ), ) @@ -82,10 +75,12 @@ class IntellifireSwitch(IntellifireEntity, SwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the switch.""" await self.entity_description.on_fn(self.coordinator.control_api) + await self.async_update_ha_state(force_refresh=True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the switch.""" await self.entity_description.off_fn(self.coordinator.control_api) + await self.async_update_ha_state(force_refresh=True) @property def is_on(self) -> bool | None: diff --git a/requirements_all.txt b/requirements_all.txt index d758df0a97c..064f686dc49 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -894,7 +894,7 @@ influxdb==5.3.1 insteon-frontend-home-assistant==0.1.1 # homeassistant.components.intellifire -intellifire4py==1.0.2 +intellifire4py==2.0.0 # homeassistant.components.iotawatt iotawattpy==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c2cf31965f6..f579b9395d9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -637,7 +637,7 @@ influxdb==5.3.1 insteon-frontend-home-assistant==0.1.1 # homeassistant.components.intellifire -intellifire4py==1.0.2 +intellifire4py==2.0.0 # homeassistant.components.iotawatt iotawattpy==0.1.0 diff --git a/tests/components/intellifire/conftest.py b/tests/components/intellifire/conftest.py index 3f73834226c..8940acd9d8e 100644 --- a/tests/components/intellifire/conftest.py +++ b/tests/components/intellifire/conftest.py @@ -44,7 +44,7 @@ def mock_intellifire_config_flow() -> Generator[None, MagicMock, None]: data_mock.serial = "12345" with patch( - "homeassistant.components.intellifire.config_flow.IntellifireAsync", + "homeassistant.components.intellifire.config_flow.IntellifireAPILocal", autospec=True, ) as intellifire_mock: intellifire = intellifire_mock.return_value diff --git a/tests/components/intellifire/test_config_flow.py b/tests/components/intellifire/test_config_flow.py index 2f48e645708..06fcbea5bfa 100644 --- a/tests/components/intellifire/test_config_flow.py +++ b/tests/components/intellifire/test_config_flow.py @@ -6,8 +6,8 @@ from intellifire4py.exceptions import LoginException from homeassistant import config_entries from homeassistant.components import dhcp from homeassistant.components.intellifire.config_flow import MANUAL_ENTRY_STRING -from homeassistant.components.intellifire.const import DOMAIN -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.components.intellifire.const import CONF_USER_ID, DOMAIN +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -20,9 +20,10 @@ from tests.components.intellifire.conftest import mock_api_connection_error @patch.multiple( - "homeassistant.components.intellifire.config_flow.IntellifireControlAsync", + "homeassistant.components.intellifire.config_flow.IntellifireAPICloud", login=AsyncMock(), - get_username=AsyncMock(return_value="intellifire"), + get_user_id=MagicMock(return_value="intellifire"), + get_fireplace_api_key=MagicMock(return_value="key"), ) async def test_no_discovery( hass: HomeAssistant, @@ -64,14 +65,17 @@ async def test_no_discovery( CONF_HOST: "1.1.1.1", CONF_USERNAME: "test", CONF_PASSWORD: "AROONIE", + CONF_API_KEY: "key", + CONF_USER_ID: "intellifire", } assert len(mock_setup_entry.mock_calls) == 1 @patch.multiple( - "homeassistant.components.intellifire.config_flow.IntellifireControlAsync", + "homeassistant.components.intellifire.config_flow.IntellifireAPICloud", login=AsyncMock(side_effect=mock_api_connection_error()), - get_username=AsyncMock(return_value="intellifire"), + get_user_id=MagicMock(return_value="intellifire"), + get_fireplace_api_key=MagicMock(return_value="key"), ) async def test_single_discovery( hass: HomeAssistant, @@ -101,8 +105,10 @@ async def test_single_discovery( @patch.multiple( - "homeassistant.components.intellifire.config_flow.IntellifireControlAsync", - login=AsyncMock(side_effect=LoginException()), + "homeassistant.components.intellifire.config_flow.IntellifireAPICloud", + login=AsyncMock(side_effect=LoginException), + get_user_id=MagicMock(return_value="intellifire"), + get_fireplace_api_key=MagicMock(return_value="key"), ) async def test_single_discovery_loign_error( hass: HomeAssistant, @@ -265,9 +271,10 @@ async def test_picker_already_discovered( @patch.multiple( - "homeassistant.components.intellifire.config_flow.IntellifireControlAsync", + "homeassistant.components.intellifire.config_flow.IntellifireAPICloud", login=AsyncMock(), - get_username=AsyncMock(return_value="intellifire"), + get_user_id=MagicMock(return_value="intellifire"), + get_fireplace_api_key=MagicMock(return_value="key"), ) async def test_reauth_flow( hass: HomeAssistant, -- GitLab