Update to iaqualink 0.5.0 (#80304)

* Update to iaqualink 0.5.0.

* Boolean conditional style fix

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Fix black formatting

* Update iaqualink tests after update to 0.5.x

* Remove debug print statements

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
pull/80480/head
Florent Thoumie 2022-10-17 01:14:29 -07:00 committed by GitHub
parent bbb0b2a0e1
commit abec592a24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 110 deletions

View File

@ -14,8 +14,8 @@ from iaqualink.device import (
AqualinkDevice,
AqualinkLight,
AqualinkSensor,
AqualinkSwitch,
AqualinkThermostat,
AqualinkToggle,
)
from iaqualink.exception import AqualinkServiceException
from typing_extensions import Concatenate, ParamSpec
@ -29,7 +29,6 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
@ -56,7 +55,9 @@ PLATFORMS = [
]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry( # noqa: C901
hass: HomeAssistant, entry: ConfigEntry
) -> bool:
"""Set up Aqualink from a config entry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
@ -70,17 +71,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
sensors = hass.data[DOMAIN][SENSOR_DOMAIN] = []
switches = hass.data[DOMAIN][SWITCH_DOMAIN] = []
session = async_get_clientsession(hass)
aqualink = AqualinkClient(username, password, session)
aqualink = AqualinkClient(username, password)
try:
await aqualink.login()
except AqualinkServiceException as login_exception:
_LOGGER.error("Failed to login: %s", login_exception)
await aqualink.close()
return False
except (
asyncio.TimeoutError,
aiohttp.client_exceptions.ClientConnectorError,
) as aio_exception:
await aqualink.close()
raise ConfigEntryNotReady(
f"Error while attempting login: {aio_exception}"
) from aio_exception
@ -88,6 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try:
systems = await aqualink.get_systems()
except AqualinkServiceException as svc_exception:
await aqualink.close()
raise ConfigEntryNotReady(
f"Error while attempting to retrieve systems list: {svc_exception}"
) from svc_exception
@ -95,27 +98,29 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
systems = list(systems.values())
if not systems:
_LOGGER.error("No systems detected or supported")
await aqualink.close()
return False
# Only supporting the first system for now.
try:
devices = await systems[0].get_devices()
except AqualinkServiceException as svc_exception:
raise ConfigEntryNotReady(
f"Error while attempting to retrieve devices list: {svc_exception}"
) from svc_exception
for system in systems:
try:
devices = await system.get_devices()
except AqualinkServiceException as svc_exception:
await aqualink.close()
raise ConfigEntryNotReady(
f"Error while attempting to retrieve devices list: {svc_exception}"
) from svc_exception
for dev in devices.values():
if isinstance(dev, AqualinkThermostat):
climates += [dev]
elif isinstance(dev, AqualinkLight):
lights += [dev]
elif isinstance(dev, AqualinkBinarySensor):
binary_sensors += [dev]
elif isinstance(dev, AqualinkSensor):
sensors += [dev]
elif isinstance(dev, AqualinkToggle):
switches += [dev]
for dev in devices.values():
if isinstance(dev, AqualinkThermostat):
climates += [dev]
elif isinstance(dev, AqualinkLight):
lights += [dev]
elif isinstance(dev, AqualinkSwitch):
switches += [dev]
elif isinstance(dev, AqualinkBinarySensor):
binary_sensors += [dev]
elif isinstance(dev, AqualinkSensor):
sensors += [dev]
platforms = []
if binary_sensors:
@ -134,23 +139,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.debug("Got %s switches: %s", len(switches), switches)
platforms.append(Platform.SWITCH)
hass.data[DOMAIN]["client"] = aqualink
await hass.config_entries.async_forward_entry_setups(entry, platforms)
async def _async_systems_update(now):
"""Refresh internal state for all systems."""
prev = systems[0].online
for system in systems:
prev = system.online
try:
await systems[0].update()
except AqualinkServiceException as svc_exception:
if prev is not None:
_LOGGER.warning("Failed to refresh iAqualink state: %s", svc_exception)
else:
cur = systems[0].online
if cur is True and prev is not True:
_LOGGER.warning("Reconnected to iAqualink")
try:
await system.update()
except AqualinkServiceException as svc_exception:
if prev is not None:
_LOGGER.warning(
"Failed to refresh system %s state: %s",
system.serial,
svc_exception,
)
else:
cur = system.online
if cur and not prev:
_LOGGER.warning("System %s reconnected to iAqualink", system.serial)
async_dispatcher_send(hass, DOMAIN)
async_dispatcher_send(hass, DOMAIN)
async_track_time_interval(hass, _async_systems_update, UPDATE_INTERVAL)
@ -159,6 +171,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
aqualink = hass.data[DOMAIN]["client"]
await aqualink.close()
platforms_to_unload = [
platform for platform in PLATFORMS if platform in hass.data[DOMAIN]
]
@ -226,8 +241,8 @@ class AqualinkEntity(Entity):
"""Return the device info."""
return DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
manufacturer="Jandy",
model=self.dev.__class__.__name__.replace("Aqualink", ""),
manufacturer=self.dev.manufacturer,
model=self.dev.model,
name=self.name,
via_device=(DOMAIN, self.dev.system.serial),
)

View File

@ -4,14 +4,6 @@ from __future__ import annotations
import logging
from typing import Any
from iaqualink.const import (
AQUALINK_TEMP_CELSIUS_HIGH,
AQUALINK_TEMP_CELSIUS_LOW,
AQUALINK_TEMP_FAHRENHEIT_HIGH,
AQUALINK_TEMP_FAHRENHEIT_LOW,
)
from iaqualink.device import AqualinkHeater, AqualinkPump, AqualinkSensor, AqualinkState
from homeassistant.components.climate import (
DOMAIN as CLIMATE_DOMAIN,
ClimateEntity,
@ -55,17 +47,10 @@ class HassAqualinkThermostat(AqualinkEntity, ClimateEntity):
"""Return the name of the thermostat."""
return self.dev.label.split(" ")[0]
@property
def pump(self) -> AqualinkPump:
"""Return the pump device for the current thermostat."""
pump = f"{self.name.lower()}_pump"
return self.dev.system.devices[pump]
@property
def hvac_mode(self) -> HVACMode:
"""Return the current HVAC mode."""
state = AqualinkState(self.heater.state)
if state == AqualinkState.ON:
if self.dev.is_on is True:
return HVACMode.HEAT
return HVACMode.OFF
@ -73,32 +58,28 @@ class HassAqualinkThermostat(AqualinkEntity, ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Turn the underlying heater switch on or off."""
if hvac_mode == HVACMode.HEAT:
await await_or_reraise(self.heater.turn_on())
await await_or_reraise(self.dev.turn_on())
elif hvac_mode == HVACMode.OFF:
await await_or_reraise(self.heater.turn_off())
await await_or_reraise(self.dev.turn_off())
else:
_LOGGER.warning("Unknown operation mode: %s", hvac_mode)
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
if self.dev.system.temp_unit == "F":
if self.dev.unit == "F":
return TEMP_FAHRENHEIT
return TEMP_CELSIUS
@property
def min_temp(self) -> int:
"""Return the minimum temperature supported by the thermostat."""
if self.temperature_unit == TEMP_FAHRENHEIT:
return AQUALINK_TEMP_FAHRENHEIT_LOW
return AQUALINK_TEMP_CELSIUS_LOW
return self.dev.min_temperature
@property
def max_temp(self) -> int:
"""Return the minimum temperature supported by the thermostat."""
if self.temperature_unit == TEMP_FAHRENHEIT:
return AQUALINK_TEMP_FAHRENHEIT_HIGH
return AQUALINK_TEMP_CELSIUS_HIGH
return self.dev.max_temperature
@property
def target_temperature(self) -> float:
@ -110,21 +91,9 @@ class HassAqualinkThermostat(AqualinkEntity, ClimateEntity):
"""Set new target temperature."""
await await_or_reraise(self.dev.set_temperature(int(kwargs[ATTR_TEMPERATURE])))
@property
def sensor(self) -> AqualinkSensor:
"""Return the sensor device for the current thermostat."""
sensor = f"{self.name.lower()}_temp"
return self.dev.system.devices[sensor]
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
if self.sensor.state != "":
return float(self.sensor.state)
if self.dev.current_temperature != "":
return float(self.dev.current_temperature)
return None
@property
def heater(self) -> AqualinkHeater:
"""Return the heater device for the current thermostat."""
heater = f"{self.name.lower()}_heater"
return self.dev.system.devices[heater]

View File

@ -2,4 +2,4 @@
from datetime import timedelta
DOMAIN = "iaqualink"
UPDATE_INTERVAL = timedelta(seconds=30)
UPDATE_INTERVAL = timedelta(seconds=15)

View File

@ -90,7 +90,7 @@ class HassAqualinkLight(AqualinkEntity, LightEntity):
@property
def color_mode(self) -> ColorMode:
"""Return the color mode of the light."""
if self.dev.is_dimmer:
if self.dev.supports_brightness:
return ColorMode.BRIGHTNESS
return ColorMode.ONOFF
@ -102,7 +102,7 @@ class HassAqualinkLight(AqualinkEntity, LightEntity):
@property
def supported_features(self) -> int:
"""Return the list of features supported by the light."""
if self.dev.is_color:
if self.dev.supports_effect:
return LightEntityFeature.EFFECT
return 0

View File

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/iaqualink/",
"codeowners": ["@flz"],
"requirements": ["iaqualink==0.4.1"],
"requirements": ["iaqualink==0.5.0"],
"iot_class": "cloud_polling",
"loggers": ["iaqualink"]
}

View File

@ -901,7 +901,7 @@ hyperion-py==0.7.5
iammeter==0.1.7
# homeassistant.components.iaqualink
iaqualink==0.4.1
iaqualink==0.5.0
# homeassistant.components.ibeacon
ibeacon_ble==0.7.4

View File

@ -672,7 +672,7 @@ huawei-lte-api==1.6.3
hyperion-py==0.7.5
# homeassistant.components.iaqualink
iaqualink==0.4.1
iaqualink==0.5.0
# homeassistant.components.ibeacon
ibeacon_ble==0.7.4

View File

@ -1,6 +1,6 @@
"""Configuration for iAqualink tests."""
import random
from unittest.mock import AsyncMock
from unittest.mock import AsyncMock, PropertyMock, patch
from iaqualink.client import AqualinkClient
from iaqualink.device import AqualinkDevice
@ -47,14 +47,31 @@ def get_aqualink_system(aqualink, cls=None, data=None):
return cls(aqualink=aqualink, data=data)
def get_aqualink_device(system, cls=None, data=None):
def get_aqualink_device(system, name, cls=None, data=None):
"""Create aqualink device."""
if cls is None:
cls = AqualinkDevice
# AqualinkDevice doesn't implement some of the properties since it's left to
# sub-classes for them to do. Provide a basic implementation here for the
# benefits of the test suite.
attrs = {
"name": name,
"manufacturer": "Jandy",
"model": "Device",
"label": name.upper(),
}
for k, v in attrs.items():
patcher = patch.object(cls, k, new_callable=PropertyMock)
mock = patcher.start()
mock.return_value = v
if data is None:
data = {}
data["name"] = name
return cls(system=system, data=data)
@ -72,7 +89,7 @@ def config_fixture():
@pytest.fixture(name="config_entry")
def config_entry_fixture():
"""Create a mock HEOS config entry."""
"""Create a mock config entry."""
return MockConfigEntry(
domain=DOMAIN,
data=MOCK_DATA,

View File

@ -4,15 +4,15 @@ import asyncio
import logging
from unittest.mock import AsyncMock, patch
from iaqualink.device import (
AqualinkAuxToggle,
AqualinkBinarySensor,
AqualinkDevice,
AqualinkLightToggle,
AqualinkSensor,
AqualinkThermostat,
)
from iaqualink.exception import AqualinkServiceException
from iaqualink.systems.iaqua.device import (
IaquaAuxSwitch,
IaquaBinarySensor,
IaquaLightSwitch,
IaquaSensor,
IaquaThermostat,
)
from iaqualink.systems.iaqua.system import IaquaSystem
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
@ -101,7 +101,7 @@ async def test_setup_devices_exception(hass, config_entry, client):
"""Test setup encountering an exception while retrieving devices."""
config_entry.add_to_hass(hass)
system = get_aqualink_system(client)
system = get_aqualink_system(client, cls=IaquaSystem)
systems = {system.serial: system}
with patch(
@ -124,10 +124,10 @@ async def test_setup_all_good_no_recognized_devices(hass, config_entry, client):
"""Test setup ending in no devices recognized."""
config_entry.add_to_hass(hass)
system = get_aqualink_system(client)
system = get_aqualink_system(client, cls=IaquaSystem)
systems = {system.serial: system}
device = get_aqualink_device(system, AqualinkDevice, data={"name": "dev_1"})
device = get_aqualink_device(system, name="dev_1")
devices = {device.name: device}
with patch(
@ -161,19 +161,15 @@ async def test_setup_all_good_all_device_types(hass, config_entry, client):
"""Test setup ending in one device of each type recognized."""
config_entry.add_to_hass(hass)
system = get_aqualink_system(client)
system = get_aqualink_system(client, cls=IaquaSystem)
systems = {system.serial: system}
devices = [
get_aqualink_device(system, AqualinkAuxToggle, data={"name": "aux_1"}),
get_aqualink_device(
system, AqualinkBinarySensor, data={"name": "freeze_protection"}
),
get_aqualink_device(system, AqualinkLightToggle, data={"name": "aux_2"}),
get_aqualink_device(system, AqualinkSensor, data={"name": "ph"}),
get_aqualink_device(
system, AqualinkThermostat, data={"name": "pool_set_point"}
),
get_aqualink_device(system, name="aux_1", cls=IaquaAuxSwitch),
get_aqualink_device(system, name="freeze_protection", cls=IaquaBinarySensor),
get_aqualink_device(system, name="aux_2", cls=IaquaLightSwitch),
get_aqualink_device(system, name="ph", cls=IaquaSensor),
get_aqualink_device(system, name="pool_set_point", cls=IaquaThermostat),
]
devices = {d.name: d for d in devices}
@ -207,7 +203,7 @@ async def test_multiple_updates(hass, config_entry, caplog, client):
"""Test all possible results of online status transition after update."""
config_entry.add_to_hass(hass)
system = get_aqualink_system(client)
system = get_aqualink_system(client, cls=IaquaSystem)
systems = {system.serial: system}
system.get_devices = AsyncMock(return_value={})
@ -269,7 +265,7 @@ async def test_multiple_updates(hass, config_entry, caplog, client):
system.update.side_effect = set_online_to_true
await _ffwd_next_update_interval(hass)
assert len(caplog.records) == 1
assert "Reconnected" in caplog.text
assert "reconnected" in caplog.text
# False -> None / ServiceException
system.online = False
@ -292,7 +288,7 @@ async def test_multiple_updates(hass, config_entry, caplog, client):
system.update.side_effect = set_online_to_true
await _ffwd_next_update_interval(hass)
assert len(caplog.records) == 1
assert "Reconnected" in caplog.text
assert "reconnected" in caplog.text
# None -> False
system.online = None
@ -311,11 +307,11 @@ async def test_entity_assumed_and_available(hass, config_entry, client):
"""Test assumed_state and_available properties for all values of online."""
config_entry.add_to_hass(hass)
system = get_aqualink_system(client)
system = get_aqualink_system(client, cls=IaquaSystem)
systems = {system.serial: system}
light = get_aqualink_device(
system, AqualinkLightToggle, data={"name": "aux_1", "state": "1"}
system, name="aux_1", cls=IaquaLightSwitch, data={"state": "1"}
)
devices = {d.name: d for d in [light]}
system.get_devices = AsyncMock(return_value=devices)