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 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()

View File

@ -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)

View File

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

View File

@ -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",
)

View File

@ -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"],

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,