Add Pentair ScreenLogic integration (#47933)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
f605a3c149
commit
d21d9951ba
18 changed files with 1136 additions and 0 deletions
|
@ -842,6 +842,11 @@ omit =
|
||||||
homeassistant/components/satel_integra/*
|
homeassistant/components/satel_integra/*
|
||||||
homeassistant/components/schluter/*
|
homeassistant/components/schluter/*
|
||||||
homeassistant/components/scrape/sensor.py
|
homeassistant/components/scrape/sensor.py
|
||||||
|
homeassistant/components/screenlogic/__init__.py
|
||||||
|
homeassistant/components/screenlogic/binary_sensor.py
|
||||||
|
homeassistant/components/screenlogic/sensor.py
|
||||||
|
homeassistant/components/screenlogic/switch.py
|
||||||
|
homeassistant/components/screenlogic/water_heater.py
|
||||||
homeassistant/components/scsgate/*
|
homeassistant/components/scsgate/*
|
||||||
homeassistant/components/scsgate/cover.py
|
homeassistant/components/scsgate/cover.py
|
||||||
homeassistant/components/sendgrid/notify.py
|
homeassistant/components/sendgrid/notify.py
|
||||||
|
|
|
@ -401,6 +401,7 @@ homeassistant/components/samsungtv/* @escoand
|
||||||
homeassistant/components/scene/* @home-assistant/core
|
homeassistant/components/scene/* @home-assistant/core
|
||||||
homeassistant/components/schluter/* @prairieapps
|
homeassistant/components/schluter/* @prairieapps
|
||||||
homeassistant/components/scrape/* @fabaff
|
homeassistant/components/scrape/* @fabaff
|
||||||
|
homeassistant/components/screenlogic/* @dieselrabbit
|
||||||
homeassistant/components/script/* @home-assistant/core
|
homeassistant/components/script/* @home-assistant/core
|
||||||
homeassistant/components/search/* @home-assistant/core
|
homeassistant/components/search/* @home-assistant/core
|
||||||
homeassistant/components/sense/* @kbickar
|
homeassistant/components/sense/* @kbickar
|
||||||
|
|
202
homeassistant/components/screenlogic/__init__.py
Normal file
202
homeassistant/components/screenlogic/__init__.py
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
"""The Screenlogic integration."""
|
||||||
|
import asyncio
|
||||||
|
from collections import defaultdict
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from screenlogicpy import ScreenLogicError, ScreenLogicGateway
|
||||||
|
from screenlogicpy.const import (
|
||||||
|
CONTROLLER_HARDWARE,
|
||||||
|
SL_GATEWAY_IP,
|
||||||
|
SL_GATEWAY_NAME,
|
||||||
|
SL_GATEWAY_PORT,
|
||||||
|
)
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
DataUpdateCoordinator,
|
||||||
|
UpdateFailed,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .config_flow import async_discover_gateways_by_unique_id, name_for_mac
|
||||||
|
from .const import DEFAULT_SCAN_INTERVAL, DISCOVERED_GATEWAYS, DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PLATFORMS = ["switch", "sensor", "binary_sensor", "water_heater"]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
|
"""Set up the Screenlogic component."""
|
||||||
|
domain_data = hass.data[DOMAIN] = {}
|
||||||
|
domain_data[DISCOVERED_GATEWAYS] = await async_discover_gateways_by_unique_id(hass)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Set up Screenlogic from a config entry."""
|
||||||
|
mac = entry.unique_id
|
||||||
|
# Attempt to re-discover named gateway to follow IP changes
|
||||||
|
discovered_gateways = hass.data[DOMAIN][DISCOVERED_GATEWAYS]
|
||||||
|
if mac in discovered_gateways:
|
||||||
|
connect_info = discovered_gateways[mac]
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("Gateway rediscovery failed.")
|
||||||
|
# Static connection defined or fallback from discovery
|
||||||
|
connect_info = {
|
||||||
|
SL_GATEWAY_NAME: name_for_mac(mac),
|
||||||
|
SL_GATEWAY_IP: entry.data[CONF_IP_ADDRESS],
|
||||||
|
SL_GATEWAY_PORT: entry.data[CONF_PORT],
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
gateway = ScreenLogicGateway(**connect_info)
|
||||||
|
except ScreenLogicError as ex:
|
||||||
|
_LOGGER.error("Error while connecting to the gateway %s: %s", connect_info, ex)
|
||||||
|
raise ConfigEntryNotReady from ex
|
||||||
|
except AttributeError as ex:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unexpected error while connecting to the gateway %s", connect_info
|
||||||
|
)
|
||||||
|
raise ConfigEntryNotReady from ex
|
||||||
|
|
||||||
|
coordinator = ScreenlogicDataUpdateCoordinator(
|
||||||
|
hass, config_entry=entry, gateway=gateway
|
||||||
|
)
|
||||||
|
|
||||||
|
device_data = defaultdict(list)
|
||||||
|
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
|
for circuit in coordinator.data["circuits"]:
|
||||||
|
device_data["switch"].append(circuit)
|
||||||
|
|
||||||
|
for sensor in coordinator.data["sensors"]:
|
||||||
|
if sensor == "chem_alarm":
|
||||||
|
device_data["binary_sensor"].append(sensor)
|
||||||
|
else:
|
||||||
|
if coordinator.data["sensors"][sensor]["value"] != 0:
|
||||||
|
device_data["sensor"].append(sensor)
|
||||||
|
|
||||||
|
for pump in coordinator.data["pumps"]:
|
||||||
|
if (
|
||||||
|
coordinator.data["pumps"][pump]["data"] != 0
|
||||||
|
and "currentWatts" in coordinator.data["pumps"][pump]
|
||||||
|
):
|
||||||
|
device_data["pump"].append(pump)
|
||||||
|
|
||||||
|
for body in coordinator.data["bodies"]:
|
||||||
|
device_data["water_heater"].append(body)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = {
|
||||||
|
"coordinator": coordinator,
|
||||||
|
"devices": device_data,
|
||||||
|
"listener": entry.add_update_listener(async_update_listener),
|
||||||
|
}
|
||||||
|
|
||||||
|
for component in PLATFORMS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||||
|
for component in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
hass.data[DOMAIN][entry.entry_id]["listener"]()
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Handle options update."""
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
|
"""Class to manage the data update for the Screenlogic component."""
|
||||||
|
|
||||||
|
def __init__(self, hass, *, config_entry, gateway):
|
||||||
|
"""Initialize the Screenlogic Data Update Coordinator."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
self.gateway = gateway
|
||||||
|
self.screenlogic_data = {}
|
||||||
|
interval = timedelta(
|
||||||
|
seconds=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
||||||
|
)
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name=DOMAIN,
|
||||||
|
update_interval=interval,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_update_data(self):
|
||||||
|
"""Fetch data from the Screenlogic gateway."""
|
||||||
|
try:
|
||||||
|
await self.hass.async_add_executor_job(self.gateway.update)
|
||||||
|
return self.gateway.get_data()
|
||||||
|
except ScreenLogicError as error:
|
||||||
|
raise UpdateFailed(error) from error
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenlogicEntity(CoordinatorEntity):
|
||||||
|
"""Base class for all ScreenLogic entities."""
|
||||||
|
|
||||||
|
def __init__(self, coordinator, datakey):
|
||||||
|
"""Initialize of the entity."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._data_key = datakey
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mac(self):
|
||||||
|
"""Mac address."""
|
||||||
|
return self.coordinator.config_entry.unique_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Entity Unique ID."""
|
||||||
|
return f"{self.mac}_{self._data_key}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config_data(self):
|
||||||
|
"""Shortcut for config data."""
|
||||||
|
return self.coordinator.data["config"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gateway(self):
|
||||||
|
"""Return the gateway."""
|
||||||
|
return self.coordinator.gateway
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gateway_name(self):
|
||||||
|
"""Return the configured name of the gateway."""
|
||||||
|
return self.gateway.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return device information for the controller."""
|
||||||
|
controller_type = self.config_data["controler_type"]
|
||||||
|
hardware_type = self.config_data["hardware_type"]
|
||||||
|
return {
|
||||||
|
"connections": {(dr.CONNECTION_NETWORK_MAC, self.mac)},
|
||||||
|
"name": self.gateway_name,
|
||||||
|
"manufacturer": "Pentair",
|
||||||
|
"model": CONTROLLER_HARDWARE[controller_type][hardware_type],
|
||||||
|
}
|
54
homeassistant/components/screenlogic/binary_sensor.py
Normal file
54
homeassistant/components/screenlogic/binary_sensor.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
"""Support for a ScreenLogic Binary Sensor."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from screenlogicpy.const import ON_OFF
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity
|
||||||
|
|
||||||
|
from . import ScreenlogicEntity
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up entry."""
|
||||||
|
entities = []
|
||||||
|
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
coordinator = data["coordinator"]
|
||||||
|
|
||||||
|
for binary_sensor in data["devices"]["binary_sensor"]:
|
||||||
|
entities.append(ScreenLogicBinarySensor(coordinator, binary_sensor))
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity):
|
||||||
|
"""Representation of a ScreenLogic binary sensor entity."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the sensor name."""
|
||||||
|
return f"{self.gateway_name} {self.sensor['name']}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the device class."""
|
||||||
|
device_class = self.sensor.get("hass_type")
|
||||||
|
if device_class in DEVICE_CLASSES:
|
||||||
|
return device_class
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Determine if the sensor is on."""
|
||||||
|
return self.sensor["value"] == ON_OFF.ON
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sensor(self):
|
||||||
|
"""Shortcut to access the sensor data."""
|
||||||
|
return self.sensor_data[self._data_key]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sensor_data(self):
|
||||||
|
"""Shortcut to access the sensors data."""
|
||||||
|
return self.coordinator.data["sensors"]
|
218
homeassistant/components/screenlogic/config_flow.py
Normal file
218
homeassistant/components/screenlogic/config_flow.py
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
"""Config flow for ScreenLogic."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from screenlogicpy import ScreenLogicError, discover
|
||||||
|
from screenlogicpy.const import SL_GATEWAY_IP, SL_GATEWAY_NAME, SL_GATEWAY_PORT
|
||||||
|
from screenlogicpy.requests import login
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS
|
||||||
|
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL
|
||||||
|
from homeassistant.core import callback
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.device_registry import format_mac
|
||||||
|
|
||||||
|
from .const import DEFAULT_SCAN_INTERVAL, MIN_SCAN_INTERVAL
|
||||||
|
from .const import DOMAIN # pylint: disable=unused-import
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
GATEWAY_SELECT_KEY = "selected_gateway"
|
||||||
|
GATEWAY_MANUAL_ENTRY = "manual"
|
||||||
|
|
||||||
|
PENTAIR_OUI = "00-C0-33"
|
||||||
|
|
||||||
|
|
||||||
|
async def async_discover_gateways_by_unique_id(hass):
|
||||||
|
"""Discover gateways and return a dict of them by unique id."""
|
||||||
|
discovered_gateways = {}
|
||||||
|
try:
|
||||||
|
hosts = await hass.async_add_executor_job(discover)
|
||||||
|
_LOGGER.debug("Discovered hosts: %s", hosts)
|
||||||
|
except ScreenLogicError as ex:
|
||||||
|
_LOGGER.debug(ex)
|
||||||
|
return discovered_gateways
|
||||||
|
|
||||||
|
for host in hosts:
|
||||||
|
mac = _extract_mac_from_name(host[SL_GATEWAY_NAME])
|
||||||
|
discovered_gateways[mac] = host
|
||||||
|
|
||||||
|
_LOGGER.debug("Discovered gateways: %s", discovered_gateways)
|
||||||
|
return discovered_gateways
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_mac_from_name(name):
|
||||||
|
return format_mac(f"{PENTAIR_OUI}-{name.split(':')[1].strip()}")
|
||||||
|
|
||||||
|
|
||||||
|
def short_mac(mac):
|
||||||
|
"""Short version of the mac as seen in the app."""
|
||||||
|
return "-".join(mac.split(":")[3:]).upper()
|
||||||
|
|
||||||
|
|
||||||
|
def name_for_mac(mac):
|
||||||
|
"""Derive the gateway name from the mac."""
|
||||||
|
return f"Pentair: {short_mac(mac)}"
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_mac_address(hass, ip_address, port):
|
||||||
|
"""Connect to a screenlogic gateway and return the mac address."""
|
||||||
|
connected_socket = await hass.async_add_executor_job(
|
||||||
|
login.create_socket,
|
||||||
|
ip_address,
|
||||||
|
port,
|
||||||
|
)
|
||||||
|
if not connected_socket:
|
||||||
|
raise ScreenLogicError("Unknown socket error")
|
||||||
|
return await hass.async_add_executor_job(login.gateway_connect, connected_socket)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Config flow to setup screen logic devices."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize ScreenLogic ConfigFlow."""
|
||||||
|
self.discovered_gateways = {}
|
||||||
|
self.discovered_ip = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry):
|
||||||
|
"""Get the options flow for ScreenLogic."""
|
||||||
|
return ScreenLogicOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle the start of the config flow."""
|
||||||
|
self.discovered_gateways = await async_discover_gateways_by_unique_id(self.hass)
|
||||||
|
return await self.async_step_gateway_select()
|
||||||
|
|
||||||
|
async def async_step_dhcp(self, dhcp_discovery):
|
||||||
|
"""Handle dhcp discovery."""
|
||||||
|
mac = _extract_mac_from_name(dhcp_discovery[HOSTNAME])
|
||||||
|
await self.async_set_unique_id(mac)
|
||||||
|
self._abort_if_unique_id_configured(
|
||||||
|
updates={CONF_IP_ADDRESS: dhcp_discovery[IP_ADDRESS]}
|
||||||
|
)
|
||||||
|
self.discovered_ip = dhcp_discovery[IP_ADDRESS]
|
||||||
|
self.context["title_placeholders"] = {"name": dhcp_discovery[HOSTNAME]}
|
||||||
|
return await self.async_step_gateway_entry()
|
||||||
|
|
||||||
|
async def async_step_gateway_select(self, user_input=None):
|
||||||
|
"""Handle the selection of a discovered ScreenLogic gateway."""
|
||||||
|
existing = self._async_current_ids()
|
||||||
|
unconfigured_gateways = {
|
||||||
|
mac: gateway[SL_GATEWAY_NAME]
|
||||||
|
for mac, gateway in self.discovered_gateways.items()
|
||||||
|
if mac not in existing
|
||||||
|
}
|
||||||
|
|
||||||
|
if not unconfigured_gateways:
|
||||||
|
return await self.async_step_gateway_entry()
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
if user_input is not None:
|
||||||
|
if user_input[GATEWAY_SELECT_KEY] == GATEWAY_MANUAL_ENTRY:
|
||||||
|
return await self.async_step_gateway_entry()
|
||||||
|
|
||||||
|
mac = user_input[GATEWAY_SELECT_KEY]
|
||||||
|
selected_gateway = self.discovered_gateways[mac]
|
||||||
|
await self.async_set_unique_id(mac)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=name_for_mac(mac),
|
||||||
|
data={
|
||||||
|
CONF_IP_ADDRESS: selected_gateway[SL_GATEWAY_IP],
|
||||||
|
CONF_PORT: selected_gateway[SL_GATEWAY_PORT],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="gateway_select",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(GATEWAY_SELECT_KEY): vol.In(
|
||||||
|
{
|
||||||
|
**unconfigured_gateways,
|
||||||
|
GATEWAY_MANUAL_ENTRY: "Manually configure a ScreenLogic gateway",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors,
|
||||||
|
description_placeholders={},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_gateway_entry(self, user_input=None):
|
||||||
|
"""Handle the manual entry of a ScreenLogic gateway."""
|
||||||
|
errors = {}
|
||||||
|
ip_address = self.discovered_ip
|
||||||
|
port = 80
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
ip_address = user_input[CONF_IP_ADDRESS]
|
||||||
|
port = user_input[CONF_PORT]
|
||||||
|
try:
|
||||||
|
mac = format_mac(
|
||||||
|
await async_get_mac_address(self.hass, ip_address, port)
|
||||||
|
)
|
||||||
|
except ScreenLogicError as ex:
|
||||||
|
_LOGGER.debug(ex)
|
||||||
|
errors[CONF_IP_ADDRESS] = "cannot_connect"
|
||||||
|
|
||||||
|
if not errors:
|
||||||
|
await self.async_set_unique_id(mac)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=name_for_mac(mac),
|
||||||
|
data={
|
||||||
|
CONF_IP_ADDRESS: ip_address,
|
||||||
|
CONF_PORT: port,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="gateway_entry",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_IP_ADDRESS, default=ip_address): str,
|
||||||
|
vol.Required(CONF_PORT, default=port): int,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors,
|
||||||
|
description_placeholders={},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenLogicOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
"""Handles the options for the ScreenLogic integration."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry: config_entries.ConfigEntry):
|
||||||
|
"""Init the screen logic options flow."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Manage the options."""
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=self.config_entry.title, data=user_input
|
||||||
|
)
|
||||||
|
|
||||||
|
current_interval = self.config_entry.options.get(
|
||||||
|
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
||||||
|
)
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_SCAN_INTERVAL,
|
||||||
|
default=current_interval,
|
||||||
|
): vol.All(cv.positive_int, vol.Clamp(min=MIN_SCAN_INTERVAL))
|
||||||
|
}
|
||||||
|
),
|
||||||
|
description_placeholders={"gateway_name": self.config_entry.title},
|
||||||
|
)
|
7
homeassistant/components/screenlogic/const.py
Normal file
7
homeassistant/components/screenlogic/const.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
"""Constants for the ScreenLogic integration."""
|
||||||
|
|
||||||
|
DOMAIN = "screenlogic"
|
||||||
|
DEFAULT_SCAN_INTERVAL = 30
|
||||||
|
MIN_SCAN_INTERVAL = 10
|
||||||
|
|
||||||
|
DISCOVERED_GATEWAYS = "_discovered_gateways"
|
11
homeassistant/components/screenlogic/manifest.json
Normal file
11
homeassistant/components/screenlogic/manifest.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"domain": "screenlogic",
|
||||||
|
"name": "Pentair ScreenLogic",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/screenlogic",
|
||||||
|
"requirements": ["screenlogicpy==0.1.2"],
|
||||||
|
"codeowners": [
|
||||||
|
"@dieselrabbit"
|
||||||
|
],
|
||||||
|
"dhcp": [{"hostname":"pentair: *","macaddress":"00C033*"}]
|
||||||
|
}
|
107
homeassistant/components/screenlogic/sensor.py
Normal file
107
homeassistant/components/screenlogic/sensor.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
"""Support for a ScreenLogic Sensor."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import DEVICE_CLASSES
|
||||||
|
|
||||||
|
from . import ScreenlogicEntity
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PUMP_SENSORS = ("currentWatts", "currentRPM", "currentGPM")
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up entry."""
|
||||||
|
entities = []
|
||||||
|
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
coordinator = data["coordinator"]
|
||||||
|
# Generic sensors
|
||||||
|
for sensor in data["devices"]["sensor"]:
|
||||||
|
entities.append(ScreenLogicSensor(coordinator, sensor))
|
||||||
|
for pump in data["devices"]["pump"]:
|
||||||
|
for pump_key in PUMP_SENSORS:
|
||||||
|
entities.append(ScreenLogicPumpSensor(coordinator, pump, pump_key))
|
||||||
|
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenLogicSensor(ScreenlogicEntity):
|
||||||
|
"""Representation of a ScreenLogic sensor entity."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Name of the sensor."""
|
||||||
|
return f"{self.gateway_name} {self.sensor['name']}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return self.sensor.get("unit")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Device class of the sensor."""
|
||||||
|
device_class = self.sensor.get("hass_type")
|
||||||
|
if device_class in DEVICE_CLASSES:
|
||||||
|
return device_class
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""State of the sensor."""
|
||||||
|
value = self.sensor["value"]
|
||||||
|
return (value - 1) if "supply" in self._data_key else value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sensor(self):
|
||||||
|
"""Shortcut to access the sensor data."""
|
||||||
|
return self.sensor_data[self._data_key]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sensor_data(self):
|
||||||
|
"""Shortcut to access the sensors data."""
|
||||||
|
return self.coordinator.data["sensors"]
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenLogicPumpSensor(ScreenlogicEntity):
|
||||||
|
"""Representation of a ScreenLogic pump sensor entity."""
|
||||||
|
|
||||||
|
def __init__(self, coordinator, pump, key):
|
||||||
|
"""Initialize of the pump sensor."""
|
||||||
|
super().__init__(coordinator, f"{key}_{pump}")
|
||||||
|
self._pump_id = pump
|
||||||
|
self._key = key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the pump sensor name."""
|
||||||
|
return f"{self.gateway_name} {self.pump_sensor['name']}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return self.pump_sensor.get("unit")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the device class."""
|
||||||
|
device_class = self.pump_sensor.get("hass_type")
|
||||||
|
if device_class in DEVICE_CLASSES:
|
||||||
|
return device_class
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""State of the pump sensor."""
|
||||||
|
return self.pump_sensor["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pump_sensor(self):
|
||||||
|
"""Shortcut to access the pump sensor data."""
|
||||||
|
return self.pumps_data[self._pump_id][self._key]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pumps_data(self):
|
||||||
|
"""Shortcut to access the pump data."""
|
||||||
|
return self.coordinator.data["pumps"]
|
39
homeassistant/components/screenlogic/strings.json
Normal file
39
homeassistant/components/screenlogic/strings.json
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"flow_title": "ScreenLogic {name}",
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"gateway_entry": {
|
||||||
|
"title": "ScreenLogic",
|
||||||
|
"description": "Enter your ScreenLogic Gateway information.",
|
||||||
|
"data": {
|
||||||
|
"ip_address": "[%key:common::config_flow::data::ip%]",
|
||||||
|
"port": "[%key:common::config_flow::data::port%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gateway_select": {
|
||||||
|
"title": "ScreenLogic",
|
||||||
|
"description": "The following ScreenLogic gateways were discovered. Please select one to configure, or choose to manually configure a ScreenLogic gateway.",
|
||||||
|
"data": {
|
||||||
|
"selected_gateway": "Gateway"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options":{
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "ScreenLogic",
|
||||||
|
"description": "Specify settings for {gateway_name}",
|
||||||
|
"data": {
|
||||||
|
"scan_interval": "Seconds between scans"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
homeassistant/components/screenlogic/switch.py
Normal file
63
homeassistant/components/screenlogic/switch.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
"""Support for a ScreenLogic 'circuit' switch."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from screenlogicpy.const import ON_OFF
|
||||||
|
|
||||||
|
from homeassistant.components.switch import SwitchEntity
|
||||||
|
|
||||||
|
from . import ScreenlogicEntity
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up entry."""
|
||||||
|
entities = []
|
||||||
|
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
coordinator = data["coordinator"]
|
||||||
|
|
||||||
|
for switch in data["devices"]["switch"]:
|
||||||
|
entities.append(ScreenLogicSwitch(coordinator, switch))
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity):
|
||||||
|
"""ScreenLogic switch entity."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Get the name of the switch."""
|
||||||
|
return f"{self.gateway_name} {self.circuit['name']}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Get whether the switch is in on state."""
|
||||||
|
return self.circuit["value"] == 1
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs) -> None:
|
||||||
|
"""Send the ON command."""
|
||||||
|
return await self._async_set_circuit(ON_OFF.ON)
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs) -> None:
|
||||||
|
"""Send the OFF command."""
|
||||||
|
return await self._async_set_circuit(ON_OFF.OFF)
|
||||||
|
|
||||||
|
async def _async_set_circuit(self, circuit_value) -> None:
|
||||||
|
if await self.hass.async_add_executor_job(
|
||||||
|
self.gateway.set_circuit, self._data_key, circuit_value
|
||||||
|
):
|
||||||
|
_LOGGER.info("screenlogic turn %s %s", circuit_value, self._data_key)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
else:
|
||||||
|
_LOGGER.info("screenlogic turn %s %s error", circuit_value, self._data_key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def circuit(self):
|
||||||
|
"""Shortcut to access the circuit."""
|
||||||
|
return self.circuits_data[self._data_key]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def circuits_data(self):
|
||||||
|
"""Shortcut to access the circuits data."""
|
||||||
|
return self.coordinator.data["circuits"]
|
39
homeassistant/components/screenlogic/translations/en.json
Normal file
39
homeassistant/components/screenlogic/translations/en.json
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect"
|
||||||
|
},
|
||||||
|
"flow_title": "ScreenLogic {name}",
|
||||||
|
"step": {
|
||||||
|
"gateway_entry": {
|
||||||
|
"data": {
|
||||||
|
"ip_address": "IP Address",
|
||||||
|
"port": "Port"
|
||||||
|
},
|
||||||
|
"description": "Enter your ScreenLogic Gateway information.",
|
||||||
|
"title": "ScreenLogic"
|
||||||
|
},
|
||||||
|
"gateway_select": {
|
||||||
|
"data": {
|
||||||
|
"selected_gateway": "Gateway"
|
||||||
|
},
|
||||||
|
"description": "The following ScreenLogic gateways were discovered. Please select one to configure, or choose to manually configure a ScreenLogic gateway.",
|
||||||
|
"title": "ScreenLogic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"scan_interval": "Seconds between scans"
|
||||||
|
},
|
||||||
|
"description": "Specify settings for {gateway_name}",
|
||||||
|
"title": "ScreenLogic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
homeassistant/components/screenlogic/water_heater.py
Normal file
128
homeassistant/components/screenlogic/water_heater.py
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
"""Support for a ScreenLogic Water Heater."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from screenlogicpy.const import HEAT_MODE
|
||||||
|
|
||||||
|
from homeassistant.components.water_heater import (
|
||||||
|
SUPPORT_OPERATION_MODE,
|
||||||
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
|
WaterHeaterEntity,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
|
|
||||||
|
from . import ScreenlogicEntity
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
||||||
|
|
||||||
|
HEAT_MODE_NAMES = HEAT_MODE.Names
|
||||||
|
|
||||||
|
MODE_NAME_TO_MODE_NUM = {
|
||||||
|
HEAT_MODE_NAMES[num]: num for num in range(len(HEAT_MODE_NAMES))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up entry."""
|
||||||
|
entities = []
|
||||||
|
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
coordinator = data["coordinator"]
|
||||||
|
|
||||||
|
for body in data["devices"]["water_heater"]:
|
||||||
|
entities.append(ScreenLogicWaterHeater(coordinator, body))
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenLogicWaterHeater(ScreenlogicEntity, WaterHeaterEntity):
|
||||||
|
"""Represents the heating functions for a body of water."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Name of the water heater."""
|
||||||
|
ent_name = self.body["heat_status"]["name"]
|
||||||
|
return f"{self.gateway_name} {ent_name}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> str:
|
||||||
|
"""State of the water heater."""
|
||||||
|
return HEAT_MODE.GetFriendlyName(self.body["heat_status"]["value"])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self) -> float:
|
||||||
|
"""Minimum allowed temperature."""
|
||||||
|
return self.body["min_set_point"]["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self) -> float:
|
||||||
|
"""Maximum allowed temperature."""
|
||||||
|
return self.body["max_set_point"]["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self) -> float:
|
||||||
|
"""Return water temperature."""
|
||||||
|
return self.body["last_temperature"]["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self) -> float:
|
||||||
|
"""Target temperature."""
|
||||||
|
return self.body["heat_set_point"]["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature_unit(self) -> str:
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
if self.config_data["is_celcius"]["value"] == 1:
|
||||||
|
return TEMP_CELSIUS
|
||||||
|
return TEMP_FAHRENHEIT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_operation(self) -> str:
|
||||||
|
"""Return operation."""
|
||||||
|
return HEAT_MODE_NAMES[self.body["heat_mode"]["value"]]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def operation_list(self):
|
||||||
|
"""All available operations."""
|
||||||
|
supported_heat_modes = [HEAT_MODE.OFF]
|
||||||
|
# Is solar listed as available equipment?
|
||||||
|
if self.coordinator.data["config"]["equipment_flags"] & 0x1:
|
||||||
|
supported_heat_modes.extend([HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERED])
|
||||||
|
supported_heat_modes.append(HEAT_MODE.HEATER)
|
||||||
|
|
||||||
|
return [HEAT_MODE_NAMES[mode_num] for mode_num in supported_heat_modes]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Supported features of the water heater."""
|
||||||
|
return SUPPORTED_FEATURES
|
||||||
|
|
||||||
|
async def async_set_temperature(self, **kwargs) -> None:
|
||||||
|
"""Change the setpoint of the heater."""
|
||||||
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
|
if await self.hass.async_add_executor_job(
|
||||||
|
self.gateway.set_heat_temp, int(self._data_key), int(temperature)
|
||||||
|
):
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
else:
|
||||||
|
_LOGGER.error("screenlogic set_temperature error")
|
||||||
|
|
||||||
|
async def async_set_operation_mode(self, operation_mode) -> None:
|
||||||
|
"""Set the operation mode."""
|
||||||
|
mode = MODE_NAME_TO_MODE_NUM[operation_mode]
|
||||||
|
if await self.hass.async_add_executor_job(
|
||||||
|
self.gateway.set_heat_mode, int(self._data_key), int(mode)
|
||||||
|
):
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
else:
|
||||||
|
_LOGGER.error("screenlogic set_operation_mode error")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body(self):
|
||||||
|
"""Shortcut to access body data."""
|
||||||
|
return self.bodies_data[self._data_key]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bodies_data(self):
|
||||||
|
"""Shortcut to access bodies data."""
|
||||||
|
return self.coordinator.data["bodies"]
|
|
@ -195,6 +195,7 @@ FLOWS = [
|
||||||
"rpi_power",
|
"rpi_power",
|
||||||
"ruckus_unleashed",
|
"ruckus_unleashed",
|
||||||
"samsungtv",
|
"samsungtv",
|
||||||
|
"screenlogic",
|
||||||
"sense",
|
"sense",
|
||||||
"sentry",
|
"sentry",
|
||||||
"sharkiq",
|
"sharkiq",
|
||||||
|
|
|
@ -109,6 +109,11 @@ DHCP = [
|
||||||
"hostname": "irobot-*",
|
"hostname": "irobot-*",
|
||||||
"macaddress": "501479*"
|
"macaddress": "501479*"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"domain": "screenlogic",
|
||||||
|
"hostname": "pentair: *",
|
||||||
|
"macaddress": "00C033*"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"domain": "sense",
|
"domain": "sense",
|
||||||
"hostname": "sense-*",
|
"hostname": "sense-*",
|
||||||
|
|
|
@ -2008,6 +2008,9 @@ scapy==2.4.4
|
||||||
# homeassistant.components.deutsche_bahn
|
# homeassistant.components.deutsche_bahn
|
||||||
schiene==0.23
|
schiene==0.23
|
||||||
|
|
||||||
|
# homeassistant.components.screenlogic
|
||||||
|
screenlogicpy==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.scsgate
|
# homeassistant.components.scsgate
|
||||||
scsgate==0.1.0
|
scsgate==0.1.0
|
||||||
|
|
||||||
|
|
|
@ -1039,6 +1039,9 @@ samsungtvws==1.6.0
|
||||||
# homeassistant.components.dhcp
|
# homeassistant.components.dhcp
|
||||||
scapy==2.4.4
|
scapy==2.4.4
|
||||||
|
|
||||||
|
# homeassistant.components.screenlogic
|
||||||
|
screenlogicpy==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.emulated_kasa
|
# homeassistant.components.emulated_kasa
|
||||||
# homeassistant.components.sense
|
# homeassistant.components.sense
|
||||||
sense_energy==0.9.0
|
sense_energy==0.9.0
|
||||||
|
|
1
tests/components/screenlogic/__init__.py
Normal file
1
tests/components/screenlogic/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the Screenlogic integration."""
|
249
tests/components/screenlogic/test_config_flow.py
Normal file
249
tests/components/screenlogic/test_config_flow.py
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
"""Test the Pentair ScreenLogic config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from screenlogicpy import ScreenLogicError
|
||||||
|
from screenlogicpy.const import (
|
||||||
|
SL_GATEWAY_IP,
|
||||||
|
SL_GATEWAY_NAME,
|
||||||
|
SL_GATEWAY_PORT,
|
||||||
|
SL_GATEWAY_SUBTYPE,
|
||||||
|
SL_GATEWAY_TYPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
from homeassistant import config_entries, setup
|
||||||
|
from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS
|
||||||
|
from homeassistant.components.screenlogic.config_flow import (
|
||||||
|
GATEWAY_MANUAL_ENTRY,
|
||||||
|
GATEWAY_SELECT_KEY,
|
||||||
|
)
|
||||||
|
from homeassistant.components.screenlogic.const import (
|
||||||
|
DEFAULT_SCAN_INTERVAL,
|
||||||
|
DOMAIN,
|
||||||
|
MIN_SCAN_INTERVAL,
|
||||||
|
)
|
||||||
|
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_discovery(hass):
|
||||||
|
"""Test the flow works with basic discovery."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.discover",
|
||||||
|
return_value=[
|
||||||
|
{
|
||||||
|
SL_GATEWAY_IP: "1.1.1.1",
|
||||||
|
SL_GATEWAY_PORT: 80,
|
||||||
|
SL_GATEWAY_TYPE: 12,
|
||||||
|
SL_GATEWAY_SUBTYPE: 2,
|
||||||
|
SL_GATEWAY_NAME: "Pentair: 01-01-01",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
assert result["step_id"] == "gateway_select"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input={GATEWAY_SELECT_KEY: "00:c0:33:01:01:01"}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] == "create_entry"
|
||||||
|
assert result2["title"] == "Pentair: 01-01-01"
|
||||||
|
assert result2["data"] == {
|
||||||
|
CONF_IP_ADDRESS: "1.1.1.1",
|
||||||
|
CONF_PORT: 80,
|
||||||
|
}
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_discover_none(hass):
|
||||||
|
"""Test when nothing is discovered."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.discover",
|
||||||
|
return_value=[],
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
assert result["step_id"] == "gateway_entry"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_discover_error(hass):
|
||||||
|
"""Test when discovery errors."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.discover",
|
||||||
|
side_effect=ScreenLogicError("Fake error"),
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
assert result["step_id"] == "gateway_entry"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dhcp(hass):
|
||||||
|
"""Test DHCP discovery flow."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": "dhcp"},
|
||||||
|
data={
|
||||||
|
HOSTNAME: "Pentair: 01-01-01",
|
||||||
|
IP_ADDRESS: "1.1.1.1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "gateway_entry"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_manual_entry(hass):
|
||||||
|
"""Test we get the form."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.discover",
|
||||||
|
return_value=[
|
||||||
|
{
|
||||||
|
SL_GATEWAY_IP: "1.1.1.1",
|
||||||
|
SL_GATEWAY_PORT: 80,
|
||||||
|
SL_GATEWAY_TYPE: 12,
|
||||||
|
SL_GATEWAY_SUBTYPE: 2,
|
||||||
|
SL_GATEWAY_NAME: "Pentair: 01-01-01",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
assert result["step_id"] == "gateway_select"
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input={GATEWAY_SELECT_KEY: GATEWAY_MANUAL_ENTRY}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {}
|
||||||
|
assert result2["step_id"] == "gateway_entry"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.login.create_socket",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.login.gateway_connect",
|
||||||
|
return_value="00-C0-33-01-01-01",
|
||||||
|
):
|
||||||
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_IP_ADDRESS: "1.1.1.1",
|
||||||
|
CONF_PORT: 80,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result3["type"] == "create_entry"
|
||||||
|
assert result3["title"] == "Pentair: 01-01-01"
|
||||||
|
assert result3["data"] == {
|
||||||
|
CONF_IP_ADDRESS: "1.1.1.1",
|
||||||
|
CONF_PORT: 80,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_cannot_connect(hass):
|
||||||
|
"""Test we handle cannot connect error."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.login.create_socket",
|
||||||
|
return_value=None,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_IP_ADDRESS: "1.1.1.1",
|
||||||
|
CONF_PORT: 80,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {CONF_IP_ADDRESS: "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_option_flow(hass):
|
||||||
|
"""Test config flow options."""
|
||||||
|
entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={CONF_SCAN_INTERVAL: 15},
|
||||||
|
)
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["data"] == {CONF_SCAN_INTERVAL: 15}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_option_flow_defaults(hass):
|
||||||
|
"""Test config flow options."""
|
||||||
|
entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"], user_input={}
|
||||||
|
)
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_option_flow_input_floor(hass):
|
||||||
|
"""Test config flow options."""
|
||||||
|
entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"], user_input={CONF_SCAN_INTERVAL: 1}
|
||||||
|
)
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_SCAN_INTERVAL: MIN_SCAN_INTERVAL,
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue