mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 16:57:10 +00:00
Add config flow to venstar (#58152)
This commit is contained in:
parent
2d6fa5c453
commit
dad5d19a35
@ -1168,6 +1168,7 @@ omit =
|
|||||||
homeassistant/components/velbus/sensor.py
|
homeassistant/components/velbus/sensor.py
|
||||||
homeassistant/components/velbus/switch.py
|
homeassistant/components/velbus/switch.py
|
||||||
homeassistant/components/velux/*
|
homeassistant/components/velux/*
|
||||||
|
homeassistant/components/venstar/__init__.py
|
||||||
homeassistant/components/venstar/climate.py
|
homeassistant/components/venstar/climate.py
|
||||||
homeassistant/components/verisure/__init__.py
|
homeassistant/components/verisure/__init__.py
|
||||||
homeassistant/components/verisure/alarm_control_panel.py
|
homeassistant/components/verisure/alarm_control_panel.py
|
||||||
|
@ -563,6 +563,7 @@ homeassistant/components/utility_meter/* @dgomes
|
|||||||
homeassistant/components/vallox/* @andre-richter
|
homeassistant/components/vallox/* @andre-richter
|
||||||
homeassistant/components/velbus/* @Cereal2nd @brefra
|
homeassistant/components/velbus/* @Cereal2nd @brefra
|
||||||
homeassistant/components/velux/* @Julius2342
|
homeassistant/components/velux/* @Julius2342
|
||||||
|
homeassistant/components/venstar/* @garbled1
|
||||||
homeassistant/components/vera/* @pavoni
|
homeassistant/components/vera/* @pavoni
|
||||||
homeassistant/components/verisure/* @frenck
|
homeassistant/components/verisure/* @frenck
|
||||||
homeassistant/components/versasense/* @flamm3blemuff1n
|
homeassistant/components/versasense/* @flamm3blemuff1n
|
||||||
|
@ -1 +1,109 @@
|
|||||||
"""The venstar component."""
|
"""The venstar component."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from requests import RequestException
|
||||||
|
from venstarcolortouch import VenstarColorTouch
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PIN,
|
||||||
|
CONF_SSL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
)
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from .const import _LOGGER, DOMAIN, VENSTAR_TIMEOUT
|
||||||
|
|
||||||
|
PLATFORMS = ["climate"]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config):
|
||||||
|
"""Set up the Venstar thermostat."""
|
||||||
|
username = config.data.get(CONF_USERNAME)
|
||||||
|
password = config.data.get(CONF_PASSWORD)
|
||||||
|
pin = config.data.get(CONF_PIN)
|
||||||
|
host = config.data[CONF_HOST]
|
||||||
|
timeout = VENSTAR_TIMEOUT
|
||||||
|
protocol = "https" if config.data[CONF_SSL] else "http"
|
||||||
|
|
||||||
|
client = VenstarColorTouch(
|
||||||
|
addr=host,
|
||||||
|
timeout=timeout,
|
||||||
|
user=username,
|
||||||
|
password=password,
|
||||||
|
pin=pin,
|
||||||
|
proto=protocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await hass.async_add_executor_job(client.update_info)
|
||||||
|
except (OSError, RequestException) as ex:
|
||||||
|
raise ConfigEntryNotReady(f"Unable to connect to the thermostat: {ex}") from ex
|
||||||
|
hass.data.setdefault(DOMAIN, {})[config.entry_id] = client
|
||||||
|
hass.config_entries.async_setup_platforms(config, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, config):
|
||||||
|
"""Unload the config config and platforms."""
|
||||||
|
unload_ok = await hass.config_entries.async_unload_platforms(config, PLATFORMS)
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(config.entry_id)
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
class VenstarEntity(Entity):
|
||||||
|
"""Get the latest data and update."""
|
||||||
|
|
||||||
|
def __init__(self, config, client):
|
||||||
|
"""Initialize the data object."""
|
||||||
|
self._config = config
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update the state."""
|
||||||
|
try:
|
||||||
|
info_success = await self.hass.async_add_executor_job(
|
||||||
|
self._client.update_info
|
||||||
|
)
|
||||||
|
except (OSError, RequestException) as ex:
|
||||||
|
_LOGGER.error("Exception during info update: %s", ex)
|
||||||
|
|
||||||
|
# older venstars sometimes cannot handle rapid sequential connections
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sensor_success = await self.hass.async_add_executor_job(
|
||||||
|
self._client.update_sensors
|
||||||
|
)
|
||||||
|
except (OSError, RequestException) as ex:
|
||||||
|
_LOGGER.error("Exception during sensor update: %s", ex)
|
||||||
|
|
||||||
|
if not info_success or not sensor_success:
|
||||||
|
_LOGGER.error("Failed to update data")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the thermostat."""
|
||||||
|
return self._client.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Set unique_id for this entity."""
|
||||||
|
return f"{self._config.entry_id}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device information for this entity."""
|
||||||
|
return {
|
||||||
|
"identifiers": {(DOMAIN, self._config.entry_id)},
|
||||||
|
"name": self._client.name,
|
||||||
|
"manufacturer": "Venstar",
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
"model": f"{self._client.model}-{self._client._type}",
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
"sw_version": self._client._api_ver,
|
||||||
|
}
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
"""Support for Venstar WiFi Thermostats."""
|
"""Support for Venstar WiFi Thermostats."""
|
||||||
import logging
|
|
||||||
|
|
||||||
from venstarcolortouch import VenstarColorTouch
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity
|
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity
|
||||||
@ -27,6 +24,7 @@ from homeassistant.components.climate.const import (
|
|||||||
SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||||
)
|
)
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
@ -42,20 +40,18 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
from . import VenstarEntity
|
||||||
|
from .const import (
|
||||||
ATTR_FAN_STATE = "fan_state"
|
_LOGGER,
|
||||||
ATTR_HVAC_STATE = "hvac_mode"
|
ATTR_FAN_STATE,
|
||||||
|
ATTR_HVAC_STATE,
|
||||||
CONF_HUMIDIFIER = "humidifier"
|
CONF_HUMIDIFIER,
|
||||||
|
DEFAULT_SSL,
|
||||||
DEFAULT_SSL = False
|
DOMAIN,
|
||||||
|
HOLD_MODE_TEMPERATURE,
|
||||||
VALID_FAN_STATES = [STATE_ON, HVAC_MODE_AUTO]
|
VALID_FAN_STATES,
|
||||||
VALID_THERMOSTAT_MODES = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF, HVAC_MODE_AUTO]
|
VALID_THERMOSTAT_MODES,
|
||||||
|
)
|
||||||
HOLD_MODE_OFF = "off"
|
|
||||||
HOLD_MODE_TEMPERATURE = "temperature"
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
@ -72,50 +68,42 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Venstar thermostat."""
|
"""Set up the Venstar thermostat."""
|
||||||
|
client = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
async_add_entities([VenstarThermostat(config_entry, client)], True)
|
||||||
|
|
||||||
username = config.get(CONF_USERNAME)
|
|
||||||
password = config.get(CONF_PASSWORD)
|
|
||||||
pin = config.get(CONF_PIN)
|
|
||||||
host = config.get(CONF_HOST)
|
|
||||||
timeout = config.get(CONF_TIMEOUT)
|
|
||||||
humidifier = config.get(CONF_HUMIDIFIER)
|
|
||||||
|
|
||||||
protocol = "https" if config[CONF_SSL] else "http"
|
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""Set up the Venstar thermostat platform.
|
||||||
|
|
||||||
client = VenstarColorTouch(
|
Venstar uses config flow for configuration now. If an entry exists in
|
||||||
addr=host,
|
configuration.yaml, the import flow will attempt to import it and create
|
||||||
timeout=timeout,
|
a config entry.
|
||||||
user=username,
|
"""
|
||||||
password=password,
|
_LOGGER.warning(
|
||||||
pin=pin,
|
"Loading venstar via platform config is deprecated; The configuration"
|
||||||
proto=protocol,
|
" has been migrated to a config entry and can be safely removed"
|
||||||
)
|
)
|
||||||
|
# No config entry exists and configuration.yaml config exists, trigger the import flow.
|
||||||
add_entities([VenstarThermostat(client, humidifier)], True)
|
if not hass.config_entries.async_entries(DOMAIN):
|
||||||
|
await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VenstarThermostat(ClimateEntity):
|
class VenstarThermostat(VenstarEntity, ClimateEntity):
|
||||||
"""Representation of a Venstar thermostat."""
|
"""Representation of a Venstar thermostat."""
|
||||||
|
|
||||||
def __init__(self, client, humidifier):
|
def __init__(self, config, client):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
self._client = client
|
super().__init__(config, client)
|
||||||
self._humidifier = humidifier
|
|
||||||
self._mode_map = {
|
self._mode_map = {
|
||||||
HVAC_MODE_HEAT: self._client.MODE_HEAT,
|
HVAC_MODE_HEAT: self._client.MODE_HEAT,
|
||||||
HVAC_MODE_COOL: self._client.MODE_COOL,
|
HVAC_MODE_COOL: self._client.MODE_COOL,
|
||||||
HVAC_MODE_AUTO: self._client.MODE_AUTO,
|
HVAC_MODE_AUTO: self._client.MODE_AUTO,
|
||||||
}
|
}
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update the data from the thermostat."""
|
|
||||||
info_success = self._client.update_info()
|
|
||||||
sensor_success = self._client.update_sensors()
|
|
||||||
if not info_success or not sensor_success:
|
|
||||||
_LOGGER.error("Failed to update data")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
@ -124,16 +112,11 @@ class VenstarThermostat(ClimateEntity):
|
|||||||
if self._client.mode == self._client.MODE_AUTO:
|
if self._client.mode == self._client.MODE_AUTO:
|
||||||
features |= SUPPORT_TARGET_TEMPERATURE_RANGE
|
features |= SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||||
|
|
||||||
if self._humidifier and self._client.hum_setpoint is not None:
|
if self._client.hum_setpoint is not None:
|
||||||
features |= SUPPORT_TARGET_HUMIDITY
|
features |= SUPPORT_TARGET_HUMIDITY
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the thermostat."""
|
|
||||||
return self._client.name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def precision(self):
|
def precision(self):
|
||||||
"""Return the precision of the system.
|
"""Return the precision of the system.
|
||||||
|
96
homeassistant/components/venstar/config_flow.py
Normal file
96
homeassistant/components/venstar/config_flow.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
"""Config flow to configure the Venstar integration."""
|
||||||
|
from venstarcolortouch import VenstarColorTouch
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries, core, exceptions
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PIN,
|
||||||
|
CONF_SSL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import _LOGGER, DOMAIN, VENSTAR_TIMEOUT
|
||||||
|
|
||||||
|
DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_HOST): str,
|
||||||
|
vol.Optional(CONF_USERNAME): str,
|
||||||
|
vol.Optional(CONF_PASSWORD): str,
|
||||||
|
vol.Optional(CONF_PIN): str,
|
||||||
|
vol.Optional(CONF_SSL, default=False): bool,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_input(hass: core.HomeAssistant, data):
|
||||||
|
"""Validate the user input allows us to connect."""
|
||||||
|
username = data.get(CONF_USERNAME)
|
||||||
|
password = data.get(CONF_PASSWORD)
|
||||||
|
pin = data.get(CONF_PIN)
|
||||||
|
host = data[CONF_HOST]
|
||||||
|
timeout = VENSTAR_TIMEOUT
|
||||||
|
protocol = "https" if data[CONF_SSL] else "http"
|
||||||
|
|
||||||
|
client = VenstarColorTouch(
|
||||||
|
addr=host,
|
||||||
|
timeout=timeout,
|
||||||
|
user=username,
|
||||||
|
password=password,
|
||||||
|
pin=pin,
|
||||||
|
proto=protocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
# perform a full info pull, because this calls login also.
|
||||||
|
|
||||||
|
info_success = await hass.async_add_executor_job(client.update_info)
|
||||||
|
if not info_success:
|
||||||
|
raise CannotConnect
|
||||||
|
|
||||||
|
return {"title": client.name}
|
||||||
|
|
||||||
|
|
||||||
|
class VenstarConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a venstar config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Create config entry. Show the setup form to the user."""
|
||||||
|
errors = {}
|
||||||
|
info = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = await validate_input(self.hass, user_input)
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
return self.async_create_entry(title=info["title"], data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_import(self, import_data):
|
||||||
|
"""Import entry from configuration.yaml."""
|
||||||
|
self._async_abort_entries_match({CONF_HOST: import_data[CONF_HOST]})
|
||||||
|
return await self.async_step_user(
|
||||||
|
{
|
||||||
|
CONF_HOST: import_data[CONF_HOST],
|
||||||
|
CONF_USERNAME: import_data.get(CONF_USERNAME),
|
||||||
|
CONF_PASSWORD: import_data.get(CONF_PASSWORD),
|
||||||
|
CONF_PIN: import_data.get(CONF_PIN),
|
||||||
|
CONF_SSL: import_data[CONF_SSL],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CannotConnect(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect."""
|
29
homeassistant/components/venstar/const.py
Normal file
29
homeassistant/components/venstar/const.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"""The venstar component."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.climate.const import (
|
||||||
|
HVAC_MODE_AUTO,
|
||||||
|
HVAC_MODE_COOL,
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
)
|
||||||
|
from homeassistant.const import STATE_ON
|
||||||
|
|
||||||
|
DOMAIN = "venstar"
|
||||||
|
|
||||||
|
ATTR_FAN_STATE = "fan_state"
|
||||||
|
ATTR_HVAC_STATE = "hvac_mode"
|
||||||
|
|
||||||
|
CONF_HUMIDIFIER = "humidifier"
|
||||||
|
|
||||||
|
DEFAULT_SSL = False
|
||||||
|
|
||||||
|
VALID_FAN_STATES = [STATE_ON, HVAC_MODE_AUTO]
|
||||||
|
VALID_THERMOSTAT_MODES = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF, HVAC_MODE_AUTO]
|
||||||
|
|
||||||
|
HOLD_MODE_OFF = "off"
|
||||||
|
HOLD_MODE_TEMPERATURE = "temperature"
|
||||||
|
|
||||||
|
VENSTAR_TIMEOUT = 5
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
@ -1,8 +1,11 @@
|
|||||||
{
|
{
|
||||||
"domain": "venstar",
|
"domain": "venstar",
|
||||||
"name": "Venstar",
|
"name": "Venstar",
|
||||||
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/venstar",
|
"documentation": "https://www.home-assistant.io/integrations/venstar",
|
||||||
"requirements": ["venstarcolortouch==0.14"],
|
"requirements": [
|
||||||
"codeowners": [],
|
"venstarcolortouch==0.14"
|
||||||
|
],
|
||||||
|
"codeowners": ["@garbled1"],
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
}
|
}
|
||||||
|
23
homeassistant/components/venstar/strings.json
Normal file
23
homeassistant/components/venstar/strings.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Connect to the Venstar Thermostat",
|
||||||
|
"data": {
|
||||||
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"pin": "[%key:common::config_flow::data::pin%]",
|
||||||
|
"ssl": "[%key:common::config_flow::data::ssl%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
homeassistant/components/venstar/translations/en.json
Normal file
20
homeassistant/components/venstar/translations/en.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Connect to the Venstar Thermostat",
|
||||||
|
"data": {
|
||||||
|
"host": "Hostname or IP",
|
||||||
|
"username": "Username for thermostat (optional)",
|
||||||
|
"password": "Password for thermostat (optional)",
|
||||||
|
"pin": "Pin for Lockscreen (required if lock screen enabled)",
|
||||||
|
"ssl": "Whether to use SSL or not when communicating"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Unable to connect to thermostat, please validate username/password if supplied, hostname/ip, and that LOCAL API is enabled on the thermostat.",
|
||||||
|
"unknown": "An unknown error has occurred."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -306,6 +306,7 @@ FLOWS = [
|
|||||||
"upnp",
|
"upnp",
|
||||||
"uptimerobot",
|
"uptimerobot",
|
||||||
"velbus",
|
"velbus",
|
||||||
|
"venstar",
|
||||||
"vera",
|
"vera",
|
||||||
"verisure",
|
"verisure",
|
||||||
"vesync",
|
"vesync",
|
||||||
|
@ -1 +1,62 @@
|
|||||||
"""Tests for the venstar integration."""
|
"""Tests for the venstar integration."""
|
||||||
|
from requests import RequestException
|
||||||
|
|
||||||
|
|
||||||
|
class VenstarColorTouchMock:
|
||||||
|
"""Mock Venstar Library."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
addr,
|
||||||
|
timeout,
|
||||||
|
user=None,
|
||||||
|
password=None,
|
||||||
|
pin=None,
|
||||||
|
proto="http",
|
||||||
|
SSLCert=False,
|
||||||
|
):
|
||||||
|
"""Initialize the Venstar library."""
|
||||||
|
self.status = {}
|
||||||
|
self.model = "COLORTOUCH"
|
||||||
|
self._api_ver = 5
|
||||||
|
self.name = "TestVenstar"
|
||||||
|
self._info = {}
|
||||||
|
self._sensors = {}
|
||||||
|
self.alerts = {}
|
||||||
|
self.MODE_OFF = 0
|
||||||
|
self.MODE_HEAT = 1
|
||||||
|
self.MODE_COOL = 2
|
||||||
|
self.MODE_AUTO = 3
|
||||||
|
self._type = "residential"
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
"""Mock login."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _request(self, path, data=None):
|
||||||
|
"""Mock request."""
|
||||||
|
self.status = {}
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Mock update."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_info(self):
|
||||||
|
"""Mock update_info."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def broken_update_info(self):
|
||||||
|
"""Mock a update_info that raises Exception."""
|
||||||
|
raise RequestException
|
||||||
|
|
||||||
|
def update_sensors(self):
|
||||||
|
"""Mock update_sensors."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_runtimes(self):
|
||||||
|
"""Mock update_runtimes."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_alerts(self):
|
||||||
|
"""Mock update_alerts."""
|
||||||
|
return True
|
||||||
|
128
tests/components/venstar/test_config_flow.py
Normal file
128
tests/components/venstar/test_config_flow.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
"""Test the Venstar config flow."""
|
||||||
|
import logging
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.venstar.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PIN,
|
||||||
|
CONF_SSL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import (
|
||||||
|
RESULT_TYPE_ABORT,
|
||||||
|
RESULT_TYPE_CREATE_ENTRY,
|
||||||
|
RESULT_TYPE_FORM,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import VenstarColorTouchMock
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TEST_DATA = {
|
||||||
|
CONF_HOST: "1.1.1.1",
|
||||||
|
CONF_USERNAME: "test-username",
|
||||||
|
CONF_PASSWORD: "test-password",
|
||||||
|
CONF_PIN: "test-pin",
|
||||||
|
CONF_SSL: False,
|
||||||
|
}
|
||||||
|
TEST_ID = "VenstarUniqueID"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we get the form."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.venstar.config_flow.VenstarColorTouch.update_info",
|
||||||
|
new=VenstarColorTouchMock.update_info,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.venstar.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
TEST_DATA,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result2["data"] == TEST_DATA
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||||
|
"""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.venstar.config_flow.VenstarColorTouch.update_info",
|
||||||
|
return_value=False,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
TEST_DATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result2["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unknown_error(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle unknown error."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.venstar.config_flow.VenstarColorTouch.update_info",
|
||||||
|
side_effect=Exception,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
TEST_DATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result2["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_already_configured(hass: HomeAssistant) -> None:
|
||||||
|
"""Test when provided credentials are already configured."""
|
||||||
|
MockConfigEntry(domain=DOMAIN, data=TEST_DATA, unique_id=TEST_ID).add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == SOURCE_USER
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.venstar.VenstarColorTouch.update_info",
|
||||||
|
new=VenstarColorTouchMock.update_info,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.venstar.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
TEST_DATA,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] == RESULT_TYPE_ABORT
|
||||||
|
assert result2["reason"] == "already_configured"
|
71
tests/components/venstar/test_init.py
Normal file
71
tests/components/venstar/test_init.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
"""Tests of the initialization of the venstar integration."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant.components.venstar.const import DOMAIN as VENSTAR_DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_SSL
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import VenstarColorTouchMock
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
TEST_HOST = "venstartest.localdomain"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry(hass: HomeAssistant):
|
||||||
|
"""Validate that setup entry also configure the client."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=VENSTAR_DOMAIN,
|
||||||
|
data={
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_SSL: False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.venstar.VenstarColorTouch._request",
|
||||||
|
new=VenstarColorTouchMock._request,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.venstar.VenstarColorTouch.update_sensors",
|
||||||
|
new=VenstarColorTouchMock.update_sensors,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.venstar.VenstarColorTouch.update_info",
|
||||||
|
new=VenstarColorTouchMock.update_info,
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||||
|
|
||||||
|
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry_exception(hass: HomeAssistant):
|
||||||
|
"""Validate that setup entry also configure the client."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=VENSTAR_DOMAIN,
|
||||||
|
data={
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_SSL: False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.venstar.VenstarColorTouch._request",
|
||||||
|
new=VenstarColorTouchMock._request,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.venstar.VenstarColorTouch.update_sensors",
|
||||||
|
new=VenstarColorTouchMock.update_sensors,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.venstar.VenstarColorTouch.update_info",
|
||||||
|
new=VenstarColorTouchMock.broken_update_info,
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
Loading…
x
Reference in New Issue
Block a user