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>
This commit is contained in:
Jeef 2022-06-29 09:51:39 -06:00 committed by GitHub
parent fa678d0408
commit b6f16f87a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 126 additions and 73 deletions

View File

@ -2,15 +2,22 @@
from __future__ import annotations from __future__ import annotations
from aiohttp import ClientConnectionError from aiohttp import ClientConnectionError
from intellifire4py import IntellifireAsync, IntellifireControlAsync from intellifire4py import IntellifireControlAsync
from intellifire4py.exceptions import LoginException from intellifire4py.exceptions import LoginException
from intellifire4py.intellifire import IntellifireAPICloud, IntellifireAPILocal
from homeassistant.config_entries import ConfigEntry 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.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import DOMAIN, LOGGER from .const import CONF_USER_ID, DOMAIN, LOGGER
from .coordinator import IntellifireDataUpdateCoordinator from .coordinator import IntellifireDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH] 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) LOGGER.debug("Old config entry format detected: %s", entry.unique_id)
raise ConfigEntryAuthFailed raise ConfigEntryAuthFailed
# Define the API Objects
read_object = IntellifireAsync(entry.data[CONF_HOST])
ift_control = IntellifireControlAsync( ift_control = IntellifireControlAsync(
fireplace_ip=entry.data[CONF_HOST], fireplace_ip=entry.data[CONF_HOST],
) )
@ -42,9 +47,46 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
finally: finally:
await ift_control.close() 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 # Define the update coordinator
coordinator = IntellifireDataUpdateCoordinator( coordinator = IntellifireDataUpdateCoordinator(
hass=hass, read_api=read_object, control_api=ift_control hass=hass,
api=api,
) )
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()

View File

@ -6,20 +6,17 @@ from dataclasses import dataclass
from typing import Any from typing import Any
from aiohttp import ClientConnectionError from aiohttp import ClientConnectionError
from intellifire4py import ( from intellifire4py import AsyncUDPFireplaceFinder
AsyncUDPFireplaceFinder,
IntellifireAsync,
IntellifireControlAsync,
)
from intellifire4py.exceptions import LoginException from intellifire4py.exceptions import LoginException
from intellifire4py.intellifire import IntellifireAPICloud, IntellifireAPILocal
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.dhcp import DhcpServiceInfo 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 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}) 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. 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() await api.poll()
serial = api.data.serial serial = api.data.serial
LOGGER.debug("Found a fireplace: %s", 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 self, *, host: str, username: str, password: str, serial: str
): ):
"""Validate username/password against api.""" """Validate username/password against api."""
ift_control = IntellifireControlAsync(fireplace_ip=host)
LOGGER.debug("Attempting login to iftapi with: %s", username) 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 # Update or Create
existing_entry = await self.async_set_unique_id(serial) existing_entry = await self.async_set_unique_id(serial)

View File

@ -5,6 +5,8 @@ import logging
DOMAIN = "intellifire" DOMAIN = "intellifire"
CONF_USER_ID = "user_id"
LOGGER = logging.getLogger(__package__) LOGGER = logging.getLogger(__package__)
CONF_SERIAL = "serial" CONF_SERIAL = "serial"

View File

@ -5,11 +5,8 @@ from datetime import timedelta
from aiohttp import ClientConnectionError from aiohttp import ClientConnectionError
from async_timeout import timeout from async_timeout import timeout
from intellifire4py import ( from intellifire4py import IntellifirePollData
IntellifireAsync, from intellifire4py.intellifire import IntellifireAPILocal
IntellifireControlAsync,
IntellifirePollData,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
@ -24,8 +21,7 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
read_api: IntellifireAsync, api: IntellifireAPILocal,
control_api: IntellifireControlAsync,
) -> None: ) -> None:
"""Initialize the Coordinator.""" """Initialize the Coordinator."""
super().__init__( super().__init__(
@ -34,27 +30,37 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData
name=DOMAIN, name=DOMAIN,
update_interval=timedelta(seconds=15), update_interval=timedelta(seconds=15),
) )
self._read_api = read_api self._api = api
self._control_api = control_api
async def _async_update_data(self) -> IntellifirePollData: async def _async_update_data(self) -> IntellifirePollData:
LOGGER.debug("Calling update loop on IntelliFire")
async with timeout(100): 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: try:
await self._read_api.poll() await self._api.poll()
except (ConnectionError, ClientConnectionError) as exception: except (ConnectionError, ClientConnectionError) as exception:
raise UpdateFailed from exception raise UpdateFailed from exception
return self._read_api.data
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 @property
def read_api(self) -> IntellifireAsync: def read_api(self) -> IntellifireAPILocal:
"""Return the Status API pointer.""" """Return the Status API pointer."""
return self._read_api return self._api
@property @property
def control_api(self) -> IntellifireControlAsync: def control_api(self) -> IntellifireAPILocal:
"""Return the control API.""" """Return the control API."""
return self._control_api return self._api
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
@ -65,5 +71,5 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData
name="IntelliFire Fireplace", name="IntelliFire Fireplace",
identifiers={("IntelliFire", f"{self.read_api.data.serial}]")}, identifiers={("IntelliFire", f"{self.read_api.data.serial}]")},
sw_version=self.read_api.data.fw_ver_str, 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",
) )

View File

@ -3,7 +3,7 @@
"name": "IntelliFire", "name": "IntelliFire",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/intellifire", "documentation": "https://www.home-assistant.io/integrations/intellifire",
"requirements": ["intellifire4py==1.0.2"], "requirements": ["intellifire4py==2.0.0"],
"codeowners": ["@jeeftor"], "codeowners": ["@jeeftor"],
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["intellifire4py"], "loggers": ["intellifire4py"],

View File

@ -5,7 +5,8 @@ from collections.abc import Awaitable, Callable
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any 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.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -21,8 +22,8 @@ from .entity import IntellifireEntity
class IntellifireSwitchRequiredKeysMixin: class IntellifireSwitchRequiredKeysMixin:
"""Mixin for required keys.""" """Mixin for required keys."""
on_fn: Callable[[IntellifireControlAsync], Awaitable] on_fn: Callable[[IntellifireAPILocal], Awaitable]
off_fn: Callable[[IntellifireControlAsync], Awaitable] off_fn: Callable[[IntellifireAPILocal], Awaitable]
value_fn: Callable[[IntellifirePollData], bool] value_fn: Callable[[IntellifirePollData], bool]
@ -37,24 +38,16 @@ INTELLIFIRE_SWITCHES: tuple[IntellifireSwitchEntityDescription, ...] = (
IntellifireSwitchEntityDescription( IntellifireSwitchEntityDescription(
key="on_off", key="on_off",
name="Flame", name="Flame",
on_fn=lambda control_api: control_api.flame_on( on_fn=lambda control_api: control_api.flame_on(),
fireplace=control_api.default_fireplace off_fn=lambda control_api: control_api.flame_off(),
),
off_fn=lambda control_api: control_api.flame_off(
fireplace=control_api.default_fireplace
),
value_fn=lambda data: data.is_on, value_fn=lambda data: data.is_on,
), ),
IntellifireSwitchEntityDescription( IntellifireSwitchEntityDescription(
key="pilot", key="pilot",
name="Pilot Light", name="Pilot Light",
icon="mdi:fire-alert", icon="mdi:fire-alert",
on_fn=lambda control_api: control_api.pilot_on( on_fn=lambda control_api: control_api.pilot_on(),
fireplace=control_api.default_fireplace off_fn=lambda control_api: control_api.pilot_off(),
),
off_fn=lambda control_api: control_api.pilot_off(
fireplace=control_api.default_fireplace
),
value_fn=lambda data: data.pilot_on, value_fn=lambda data: data.pilot_on,
), ),
) )
@ -82,10 +75,12 @@ class IntellifireSwitch(IntellifireEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the switch.""" """Turn on the switch."""
await self.entity_description.on_fn(self.coordinator.control_api) 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: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the switch.""" """Turn off the switch."""
await self.entity_description.off_fn(self.coordinator.control_api) await self.entity_description.off_fn(self.coordinator.control_api)
await self.async_update_ha_state(force_refresh=True)
@property @property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:

View File

@ -894,7 +894,7 @@ influxdb==5.3.1
insteon-frontend-home-assistant==0.1.1 insteon-frontend-home-assistant==0.1.1
# homeassistant.components.intellifire # homeassistant.components.intellifire
intellifire4py==1.0.2 intellifire4py==2.0.0
# homeassistant.components.iotawatt # homeassistant.components.iotawatt
iotawattpy==0.1.0 iotawattpy==0.1.0

View File

@ -637,7 +637,7 @@ influxdb==5.3.1
insteon-frontend-home-assistant==0.1.1 insteon-frontend-home-assistant==0.1.1
# homeassistant.components.intellifire # homeassistant.components.intellifire
intellifire4py==1.0.2 intellifire4py==2.0.0
# homeassistant.components.iotawatt # homeassistant.components.iotawatt
iotawattpy==0.1.0 iotawattpy==0.1.0

View File

@ -44,7 +44,7 @@ def mock_intellifire_config_flow() -> Generator[None, MagicMock, None]:
data_mock.serial = "12345" data_mock.serial = "12345"
with patch( with patch(
"homeassistant.components.intellifire.config_flow.IntellifireAsync", "homeassistant.components.intellifire.config_flow.IntellifireAPILocal",
autospec=True, autospec=True,
) as intellifire_mock: ) as intellifire_mock:
intellifire = intellifire_mock.return_value intellifire = intellifire_mock.return_value

View File

@ -6,8 +6,8 @@ from intellifire4py.exceptions import LoginException
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import dhcp from homeassistant.components import dhcp
from homeassistant.components.intellifire.config_flow import MANUAL_ENTRY_STRING from homeassistant.components.intellifire.config_flow import MANUAL_ENTRY_STRING
from homeassistant.components.intellifire.const import DOMAIN from homeassistant.components.intellifire.const import CONF_USER_ID, DOMAIN
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.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import ( from homeassistant.data_entry_flow import (
RESULT_TYPE_ABORT, RESULT_TYPE_ABORT,
@ -20,9 +20,10 @@ from tests.components.intellifire.conftest import mock_api_connection_error
@patch.multiple( @patch.multiple(
"homeassistant.components.intellifire.config_flow.IntellifireControlAsync", "homeassistant.components.intellifire.config_flow.IntellifireAPICloud",
login=AsyncMock(), 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( async def test_no_discovery(
hass: HomeAssistant, hass: HomeAssistant,
@ -64,14 +65,17 @@ async def test_no_discovery(
CONF_HOST: "1.1.1.1", CONF_HOST: "1.1.1.1",
CONF_USERNAME: "test", CONF_USERNAME: "test",
CONF_PASSWORD: "AROONIE", CONF_PASSWORD: "AROONIE",
CONF_API_KEY: "key",
CONF_USER_ID: "intellifire",
} }
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
@patch.multiple( @patch.multiple(
"homeassistant.components.intellifire.config_flow.IntellifireControlAsync", "homeassistant.components.intellifire.config_flow.IntellifireAPICloud",
login=AsyncMock(side_effect=mock_api_connection_error()), 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( async def test_single_discovery(
hass: HomeAssistant, hass: HomeAssistant,
@ -101,8 +105,10 @@ async def test_single_discovery(
@patch.multiple( @patch.multiple(
"homeassistant.components.intellifire.config_flow.IntellifireControlAsync", "homeassistant.components.intellifire.config_flow.IntellifireAPICloud",
login=AsyncMock(side_effect=LoginException()), 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( async def test_single_discovery_loign_error(
hass: HomeAssistant, hass: HomeAssistant,
@ -265,9 +271,10 @@ async def test_picker_already_discovered(
@patch.multiple( @patch.multiple(
"homeassistant.components.intellifire.config_flow.IntellifireControlAsync", "homeassistant.components.intellifire.config_flow.IntellifireAPICloud",
login=AsyncMock(), 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( async def test_reauth_flow(
hass: HomeAssistant, hass: HomeAssistant,