Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ omit =
homeassistant/components/hydrawise/*
homeassistant/components/hyperion/light.py
homeassistant/components/ialarm/alarm_control_panel.py
homeassistant/components/iaqualink/climate.py
homeassistant/components/icloud/device_tracker.py
homeassistant/components/idteck_prox/*
homeassistant/components/ifttt/*
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ homeassistant/components/http/* @home-assistant/core
homeassistant/components/huawei_lte/* @scop
homeassistant/components/huawei_router/* @abmantis
homeassistant/components/hue/* @balloob
homeassistant/components/iaqualink/* @flz
homeassistant/components/ign_sismologia/* @exxamalte
homeassistant/components/incomfort/* @zxdavb
homeassistant/components/influxdb/* @fabaff
Expand Down
21 changes: 21 additions & 0 deletions homeassistant/components/iaqualink/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"config": {
"title": "Jandy iAqualink",
"step": {
"user": {
"title": "Connect to iAqualink",
"description": "Please enter the username and password for your iAqualink account.",
"data": {
"username": "Username / Email Address",
"password": "Password"
}
}
},
"error": {
"connection_failure": "Unable to connect to iAqualink. Check your username and password."
},
"abort": {
"already_setup": "You can only configure a single iAqualink connection."
}
}
}
107 changes: 107 additions & 0 deletions homeassistant/components/iaqualink/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Component to embed Aqualink devices."""
import asyncio
import logging

from aiohttp import CookieJar
import voluptuous as vol


Comment thread
flz marked this conversation as resolved.
Outdated
from iaqualink import AqualinkClient, AqualinkLoginException, AqualinkThermostat

from homeassistant import config_entries
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers.aiohttp_client import async_create_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType, HomeAssistantType

from .const import DOMAIN


_LOGGER = logging.getLogger(__name__)

ATTR_CONFIG = "config"
PARALLEL_UPDATES = 0

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
}
)
},
extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass: HomeAssistantType, config: ConfigType) -> None:
"""Set up the Aqualink component."""
conf = config.get(DOMAIN)

hass.data[DOMAIN] = {}
hass.data[DOMAIN][ATTR_CONFIG] = conf
Comment thread
flz marked this conversation as resolved.
Outdated

if conf is not None:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=conf
)
)

return True


async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> None:
"""Set up Aqualink from a config entry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]

# These will contain the initialized devices
climates = hass.data[DOMAIN][CLIMATE_DOMAIN] = []

session = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True))
aqualink = AqualinkClient(username, password, session)
try:
await aqualink.login()
except AqualinkLoginException as login_exception:
_LOGGER.error(
"Exception raised while attempting to login: %s", str(login_exception)
Comment thread
flz marked this conversation as resolved.
Outdated
)
return False

systems = await aqualink.get_systems()
systems = list(systems.values())
if not systems:
_LOGGER.error("No systems detected or supported")
return False

# Only supporting the first system for now.
devices = await systems[0].get_devices()

for dev in devices.values():
if isinstance(dev, AqualinkThermostat):
climates += [dev]

forward_setup = hass.config_entries.async_forward_entry_setup
if climates:
_LOGGER.debug("Got %s climates: %s", len(climates), climates)
hass.async_create_task(forward_setup(entry, CLIMATE_DOMAIN))

return True


async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
forward_unload = hass.config_entries.async_forward_entry_unload

tasks = []

if hass.data[DOMAIN][CLIMATE_DOMAIN]:
tasks += [forward_unload(entry, CLIMATE_DOMAIN)]

hass.data[DOMAIN].clear()

return all(await asyncio.gather(*tasks))
156 changes: 156 additions & 0 deletions homeassistant/components/iaqualink/climate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""Support for Aqualink Thermostats."""
import logging
from typing import TYPE_CHECKING, List, Optional

from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
DOMAIN,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.helpers.typing import HomeAssistantType

from .const import DOMAIN as AQUALINK_DOMAIN, CLIMATE_SUPPORTED_MODES

_LOGGER = logging.getLogger(__name__)

PARALLEL_UPDATES = 0

if TYPE_CHECKING:
from iaqualink import (
AqualinkHeater,
AqualinkPump,
AqualinkSensor,
AqualinkThermostat,
)


async def async_setup_entry(
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
) -> None:
"""Set up discovered switches."""
devs = []
for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]:
devs.append(HassAqualinkThermostat(dev))
async_add_entities(devs, True)


class HassAqualinkThermostat(ClimateDevice):
"""Representation of a thermostat."""

def __init__(self, dev: "AqualinkThermostat"):
"""Initialize the thermostat."""
self.dev = dev

@property
def name(self) -> str:
"""Return the name of the thermostat."""
return self.dev.label.split(" ")[0]

async def async_update(self) -> None:
"""Update the internal state of the thermostat.

The API update() command refreshes the state of all devices so we
only update if this is the main thermostat to avoid unnecessary
calls.
"""
if self.name != "Pool":
return
await self.dev.system.update()

@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE

@property
def hvac_modes(self) -> List[str]:
"""Return the list of supported HVAC modes."""
return CLIMATE_SUPPORTED_MODES

@property
def pump(self) -> "AqualinkPump":
"""Return the pump device for the current thermostat."""
pump = self.name.lower() + "_pump"
Comment thread
flz marked this conversation as resolved.
Outdated
return self.dev.system.devices[pump]

@property
def hvac_mode(self) -> str:
"""Return the current HVAC mode."""
from iaqualink import AqualinkState
Comment thread
flz marked this conversation as resolved.
Outdated

state = AqualinkState(self.heater.state)
if state == AqualinkState.ON:
return HVAC_MODE_HEAT
return HVAC_MODE_OFF

async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Turn the underlying heater switch on or off."""
if hvac_mode == HVAC_MODE_HEAT:
await self.heater.turn_on()
elif hvac_mode == HVAC_MODE_OFF:
await self.heater.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":
return TEMP_FAHRENHEIT
return TEMP_CELSIUS

@property
def min_temp(self) -> int:
"""Return the minimum temperature supported by the thermostat."""
from iaqualink.const import (
Comment thread
flz marked this conversation as resolved.
Outdated
AQUALINK_TEMP_CELSIUS_LOW,
AQUALINK_TEMP_FAHRENHEIT_LOW,
)

if self.temperature_unit == TEMP_FAHRENHEIT:
return AQUALINK_TEMP_FAHRENHEIT_LOW
return AQUALINK_TEMP_CELSIUS_LOW

@property
def max_temp(self) -> int:
"""Return the minimum temperature supported by the thermostat."""
from iaqualink.const import (
AQUALINK_TEMP_CELSIUS_HIGH,
AQUALINK_TEMP_FAHRENHEIT_HIGH,
)

if self.temperature_unit == TEMP_FAHRENHEIT:
return AQUALINK_TEMP_FAHRENHEIT_HIGH
return AQUALINK_TEMP_CELSIUS_HIGH

@property
def target_temperature(self) -> float:
"""Return the current target temperature."""
return float(self.dev.state)

async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
await self.dev.set_temperature(int(kwargs[ATTR_TEMPERATURE]))

@property
def sensor(self) -> "AqualinkSensor":
"""Return the sensor device for the current thermostat."""
sensor = self.name.lower() + "_temp"
Comment thread
flz marked this conversation as resolved.
Outdated
return self.dev.system.devices[sensor]

@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
if self.sensor.state != "":
return float(self.sensor.state)
return None

@property
def heater(self) -> "AqualinkHeater":
"""Return the heater device for the current thermostat."""
heater = self.name.lower() + "_heater"
Comment thread
flz marked this conversation as resolved.
Outdated
return self.dev.system.devices[heater]
53 changes: 53 additions & 0 deletions homeassistant/components/iaqualink/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Config flow to configure zone component."""

from typing import Optional

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import ConfigType

from .const import DOMAIN


@config_entries.HANDLERS.register(DOMAIN)
class AqualinkFlowHandler(config_entries.ConfigFlow):
"""Aqualink config flow."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

async def async_step_user(self, user_input: Optional[ConfigType] = None):
"""Handle a flow start."""
from iaqualink import AqualinkClient, AqualinkLoginException
Comment thread
flz marked this conversation as resolved.
Outdated

# Supporting a single account.
entries = self.hass.config_entries.async_entries(DOMAIN)
if entries:
return self.async_abort(reason="already_setup")

errors = {}

if user_input is not None:
username = user_input[CONF_USERNAME]
password = user_input[CONF_PASSWORD]

try:
aqualink = AqualinkClient(username, password)
await aqualink.login()
return self.async_create_entry(title=username, data=user_input)
except AqualinkLoginException:
errors["base"] = "connection_failure"

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
),
errors=errors,
)

async def async_step_import(self, user_input: Optional[ConfigType] = None):
"""Occurs when an entry is setup through config."""
return self.async_step_user(user_input)
5 changes: 5 additions & 0 deletions homeassistant/components/iaqualink/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Constants for the the iaqualink component."""
from homeassistant.components.climate.const import HVAC_MODE_HEAT, HVAC_MODE_OFF

DOMAIN = "iaqualink"
CLIMATE_SUPPORTED_MODES = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
13 changes: 13 additions & 0 deletions homeassistant/components/iaqualink/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"domain": "iaqualink",
"name": "Jandy iAqualink",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/iaqualink/",
"dependencies": [],
"codeowners": [
"@flz"
],
"requirements": [
"iaqualink==0.2.9"
]
}
21 changes: 21 additions & 0 deletions homeassistant/components/iaqualink/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"config": {
"title": "Jandy iAqualink",
"step": {
"user": {
"title": "Connect to iAqualink",
"description": "Please enter the username and password for your iAqualink account.",
"data": {
"username": "Username / Email Address",
"password": "Password"
}
}
},
"error": {
"connection_failure": "Unable to connect to iAqualink. Check your username and password."
},
"abort": {
"already_setup": "You can only configure a single iAqualink connection."
}
}
}
Loading