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
parent
bbb0b2a0e1
commit
abec592a24
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
from datetime import timedelta
|
||||
|
||||
DOMAIN = "iaqualink"
|
||||
UPDATE_INTERVAL = timedelta(seconds=30)
|
||||
UPDATE_INTERVAL = timedelta(seconds=15)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue