Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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."
}
}
}
103 changes: 103 additions & 0 deletions homeassistant/components/iaqualink/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Component to embed Aqualink devices."""
import asyncio
import logging

from aiohttp import CookieJar
import voluptuous as vol

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] = {}

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", login_exception)
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))
150 changes: 150 additions & 0 deletions homeassistant/components/iaqualink/climate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""Support for Aqualink Thermostats."""
import logging
from typing import List, Optional

from iaqualink import (
AqualinkState,
AqualinkHeater,
AqualinkPump,
AqualinkSensor,
AqualinkThermostat,
)
from iaqualink.const import (
AQUALINK_TEMP_CELSIUS_HIGH,
AQUALINK_TEMP_CELSIUS_LOW,
AQUALINK_TEMP_FAHRENHEIT_HIGH,
AQUALINK_TEMP_FAHRENHEIT_LOW,
)

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


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 = f"{self.name.lower()}_pump"
return self.dev.system.devices[pump]

@property
def hvac_mode(self) -> str:
"""Return the current HVAC mode."""
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."""
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."""
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 = f"{self.name.lower()}_temp"
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 = f"{self.name.lower()}_heater"
return self.dev.system.devices[heater]
52 changes: 52 additions & 0 deletions homeassistant/components/iaqualink/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Config flow to configure zone component."""
from typing import Optional

import voluptuous as vol

from iaqualink import AqualinkClient, AqualinkLoginException

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."""
# 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 await 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."
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"homekit_controller",
"homematicip_cloud",
"hue",
"iaqualink",
"ifttt",
"ios",
"ipma",
Expand Down
Loading