Add vicare config flow (#56691)

* Configuration via UI

Config flow / YAML deprecation
- Support discovery via MAC address
- Support import of YAML config
- Switch to ConfigEntry, get rid of platform setup

* Fix review comments

* More tests for vicare yaml import
This commit is contained in:
Hans Oischinger 2021-11-22 15:06:42 +01:00 committed by GitHub
parent a7382c8092
commit 38b976e6d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 564 additions and 95 deletions

View file

@ -1194,7 +1194,12 @@ omit =
homeassistant/components/vesync/light.py
homeassistant/components/vesync/switch.py
homeassistant/components/viaggiatreno/sensor.py
homeassistant/components/vicare/*
homeassistant/components/vicare/binary_sensor.py
homeassistant/components/vicare/climate.py
homeassistant/components/vicare/const.py
homeassistant/components/vicare/__init__.py
homeassistant/components/vicare/sensor.py
homeassistant/components/vicare/water_heater.py
homeassistant/components/vilfo/__init__.py
homeassistant/components/vilfo/sensor.py
homeassistant/components/vilfo/const.py

View file

@ -9,6 +9,7 @@ from PyViCare.PyViCare import PyViCare
from PyViCare.PyViCareDevice import Device
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_CLIENT_ID,
CONF_NAME,
@ -16,7 +17,7 @@ from homeassistant.const import (
CONF_SCAN_INTERVAL,
CONF_USERNAME,
)
from homeassistant.helpers import discovery
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.storage import STORAGE_DIR
@ -30,7 +31,6 @@ from .const import (
VICARE_API,
VICARE_CIRCUITS,
VICARE_DEVICE_CONFIG,
VICARE_NAME,
HeatingType,
)
@ -61,8 +61,8 @@ CONFIG_SCHEMA = vol.Schema(
): int, # Ignored: All circuits are now supported. Will be removed when switching to Setup via UI.
vol.Optional(CONF_NAME, default="ViCare"): cv.string,
vol.Optional(
CONF_HEATING_TYPE, default=DEFAULT_HEATING_TYPE
): cv.enum(HeatingType),
CONF_HEATING_TYPE, default=DEFAULT_HEATING_TYPE.value
): vol.In([e.value for e in HeatingType]),
}
),
)
@ -71,44 +71,75 @@ CONFIG_SCHEMA = vol.Schema(
)
def setup(hass, config):
"""Create the ViCare component."""
conf = config[DOMAIN]
params = {"token_file": hass.config.path(STORAGE_DIR, "vicare_token.save")}
async def async_setup(hass: HomeAssistant, config) -> bool:
"""Set up the ViCare component from yaml."""
if DOMAIN not in config:
# Setup via UI. No need to continue yaml-based setup
return True
params["cacheDuration"] = conf.get(CONF_SCAN_INTERVAL)
params["client_id"] = conf.get(CONF_CLIENT_ID)
hass.data[DOMAIN] = {}
hass.data[DOMAIN][VICARE_NAME] = conf[CONF_NAME]
setup_vicare_api(hass, conf, hass.data[DOMAIN])
hass.data[DOMAIN][CONF_HEATING_TYPE] = conf[CONF_HEATING_TYPE]
for platform in PLATFORMS:
discovery.load_platform(hass, platform, DOMAIN, {}, config)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config[DOMAIN],
)
)
return True
def setup_vicare_api(hass, conf, entity_data):
"""Set up PyVicare API."""
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up from config entry."""
_LOGGER.debug("Setting up ViCare component")
hass.data[DOMAIN] = {}
hass.data[DOMAIN][entry.entry_id] = {}
await hass.async_add_executor_job(setup_vicare_api, hass, entry)
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True
def vicare_login(hass, entry_data):
"""Login via PyVicare API."""
vicare_api = PyViCare()
vicare_api.setCacheDuration(conf[CONF_SCAN_INTERVAL])
vicare_api.setCacheDuration(entry_data[CONF_SCAN_INTERVAL])
vicare_api.initWithCredentials(
conf[CONF_USERNAME],
conf[CONF_PASSWORD],
conf[CONF_CLIENT_ID],
entry_data[CONF_USERNAME],
entry_data[CONF_PASSWORD],
entry_data[CONF_CLIENT_ID],
hass.config.path(STORAGE_DIR, "vicare_token.save"),
)
return vicare_api
def setup_vicare_api(hass, entry):
"""Set up PyVicare API."""
vicare_api = vicare_login(hass, entry.data)
device = vicare_api.devices[0]
for device in vicare_api.devices:
_LOGGER.info(
"Found device: %s (online: %s)", device.getModel(), str(device.isOnline())
)
entity_data[VICARE_DEVICE_CONFIG] = device
entity_data[VICARE_API] = getattr(
device, HEATING_TYPE_TO_CREATOR_METHOD[conf[CONF_HEATING_TYPE]]
# Currently we only support a single device
device = vicare_api.devices[0]
hass.data[DOMAIN][entry.entry_id][VICARE_DEVICE_CONFIG] = device
hass.data[DOMAIN][entry.entry_id][VICARE_API] = getattr(
device,
HEATING_TYPE_TO_CREATOR_METHOD[HeatingType(entry.data[CONF_HEATING_TYPE])],
)()
entity_data[VICARE_CIRCUITS] = entity_data[VICARE_API].circuits
hass.data[DOMAIN][entry.entry_id][VICARE_CIRCUITS] = hass.data[DOMAIN][
entry.entry_id
][VICARE_API].circuits
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload ViCare config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View file

@ -17,9 +17,10 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import CONF_NAME
from . import ViCareRequiredKeysMixin
from .const import DOMAIN, VICARE_API, VICARE_DEVICE_CONFIG, VICARE_NAME
from .const import DOMAIN, VICARE_API, VICARE_CIRCUITS, VICARE_DEVICE_CONFIG
_LOGGER = logging.getLogger(__name__)
@ -84,7 +85,7 @@ def _build_entity(name, vicare_api, device_config, sensor):
async def _entities_from_descriptions(
hass, name, all_devices, sensor_descriptions, iterables
hass, name, all_devices, sensor_descriptions, iterables, config_entry
):
"""Create entities from descriptions and list of burners/circuits."""
for description in sensor_descriptions:
@ -96,33 +97,30 @@ async def _entities_from_descriptions(
_build_entity,
f"{name} {description.name}{suffix}",
current,
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG],
description,
)
if entity is not None:
all_devices.append(entity)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Create the ViCare binary sensor devices."""
if discovery_info is None:
return
name = hass.data[DOMAIN][VICARE_NAME]
api = hass.data[DOMAIN][VICARE_API]
name = config_entry.data[CONF_NAME]
api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API]
all_devices = []
for description in CIRCUIT_SENSORS:
for circuit in api.circuits:
for circuit in hass.data[DOMAIN][config_entry.entry_id][VICARE_CIRCUITS]:
suffix = ""
if len(api.circuits) > 1:
if len(hass.data[DOMAIN][config_entry.entry_id][VICARE_CIRCUITS]) > 1:
suffix = f" {circuit.id}"
entity = await hass.async_add_executor_job(
_build_entity,
f"{name} {description.name}{suffix}",
circuit,
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG],
description,
)
if entity is not None:
@ -130,19 +128,19 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
try:
await _entities_from_descriptions(
hass, name, all_devices, BURNER_SENSORS, api.burners
hass, name, all_devices, BURNER_SENSORS, api.burners, config_entry
)
except PyViCareNotSupportedFeatureError:
_LOGGER.info("No burners found")
try:
await _entities_from_descriptions(
hass, name, all_devices, COMPRESSOR_SENSORS, api.compressors
hass, name, all_devices, COMPRESSOR_SENSORS, api.compressors, config_entry
)
except PyViCareNotSupportedFeatureError:
_LOGGER.info("No compressors found")
async_add_entities(all_devices)
async_add_devices(all_devices)
class ViCareBinarySensor(BinarySensorEntity):

View file

@ -22,7 +22,12 @@ from homeassistant.components.climate.const import (
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
from homeassistant.const import (
ATTR_TEMPERATURE,
CONF_NAME,
PRECISION_WHOLE,
TEMP_CELSIUS,
)
from homeassistant.helpers import entity_platform
import homeassistant.helpers.config_validation as cv
@ -32,7 +37,6 @@ from .const import (
VICARE_API,
VICARE_CIRCUITS,
VICARE_DEVICE_CONFIG,
VICARE_NAME,
)
_LOGGER = logging.getLogger(__name__)
@ -99,33 +103,26 @@ def _build_entity(name, vicare_api, circuit, device_config, heating_type):
return ViCareClimate(name, vicare_api, device_config, circuit, heating_type)
async def async_setup_platform(
hass, hass_config, async_add_entities, discovery_info=None
):
"""Create the ViCare climate devices."""
# Legacy setup. Remove after configuration.yaml deprecation end
if discovery_info is None:
return
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the ViCare climate platform."""
name = config_entry.data[CONF_NAME]
name = hass.data[DOMAIN][VICARE_NAME]
all_devices = []
for circuit in hass.data[DOMAIN][VICARE_CIRCUITS]:
for circuit in hass.data[DOMAIN][config_entry.entry_id][VICARE_CIRCUITS]:
suffix = ""
if len(hass.data[DOMAIN][VICARE_CIRCUITS]) > 1:
if len(hass.data[DOMAIN][config_entry.entry_id][VICARE_CIRCUITS]) > 1:
suffix = f" {circuit.id}"
entity = _build_entity(
f"{name} Heating{suffix}",
hass.data[DOMAIN][VICARE_API],
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
hass.data[DOMAIN][config_entry.entry_id][VICARE_API],
hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG],
circuit,
hass.data[DOMAIN][CONF_HEATING_TYPE],
config_entry.data[CONF_HEATING_TYPE],
)
if entity is not None:
all_devices.append(entity)
async_add_entities(all_devices)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
@ -134,6 +131,8 @@ async def async_setup_platform(
"set_vicare_mode",
)
async_add_devices(all_devices)
class ViCareClimate(ClimateEntity):
"""Representation of the ViCare heating climate device."""

View file

@ -0,0 +1,110 @@
"""Config flow for ViCare integration."""
from __future__ import annotations
import logging
from typing import Any
from PyViCare.PyViCareUtils import PyViCareInvalidCredentialsError
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.dhcp import MAC_ADDRESS
from homeassistant.const import (
CONF_CLIENT_ID,
CONF_NAME,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import format_mac
from . import vicare_login
from .const import (
CONF_CIRCUIT,
CONF_HEATING_TYPE,
DEFAULT_HEATING_TYPE,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
HeatingType,
)
_LOGGER = logging.getLogger(__name__)
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for ViCare."""
VERSION = 1
async def async_step_user(self, user_input: dict[str, Any] | None = None):
"""Invoke when a user initiates a flow via the user interface."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
data_schema = {
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_HEATING_TYPE, default=DEFAULT_HEATING_TYPE.value): vol.In(
[e.value for e in HeatingType]
),
vol.Optional(CONF_NAME, default="ViCare"): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): vol.All(
vol.Coerce(int), vol.Range(min=30)
),
}
errors: dict[str, str] = {}
if user_input is not None:
try:
await self.hass.async_add_executor_job(
vicare_login, self.hass, user_input
)
return self.async_create_entry(
title=user_input[CONF_NAME], data=user_input
)
except PyViCareInvalidCredentialsError as ex:
_LOGGER.debug("Could not log in to ViCare, %s", ex)
errors["base"] = "invalid_auth"
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(data_schema),
errors=errors,
)
async def async_step_dhcp(self, discovery_info):
"""Invoke when a Viessmann MAC address is discovered on the network."""
formatted_mac = format_mac(discovery_info[MAC_ADDRESS])
_LOGGER.info("Found device with mac %s", formatted_mac)
await self.async_set_unique_id(formatted_mac)
self._abort_if_unique_id_configured()
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return await self.async_step_user()
async def async_step_import(self, import_info):
"""Handle a flow initiated by a YAML config import."""
await self.async_set_unique_id("Configuration.yaml")
self._abort_if_unique_id_configured()
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
# Remove now unsupported config parameters
if import_info.get(CONF_CIRCUIT):
import_info.pop(CONF_CIRCUIT)
# Add former optional config if missing
if import_info.get(CONF_HEATING_TYPE) is None:
import_info[CONF_HEATING_TYPE] = DEFAULT_HEATING_TYPE.value
return self.async_create_entry(
title="Configuration.yaml",
data=import_info,
)

View file

@ -14,7 +14,6 @@ PLATFORMS = ["climate", "sensor", "binary_sensor", "water_heater"]
VICARE_DEVICE_CONFIG = "device_conf"
VICARE_API = "api"
VICARE_NAME = "name"
VICARE_CIRCUITS = "circuits"
CONF_CIRCUIT = "circuit"

View file

@ -4,5 +4,11 @@
"documentation": "https://www.home-assistant.io/integrations/vicare",
"codeowners": ["@oischinger"],
"requirements": ["PyViCare==2.13.1"],
"iot_class": "cloud_polling"
"iot_class": "cloud_polling",
"config_flow": true,
"dhcp": [
{
"macaddress": "B87424*"
}
]
}

View file

@ -21,6 +21,7 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
)
from homeassistant.const import (
CONF_NAME,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
@ -36,8 +37,8 @@ from . import ViCareRequiredKeysMixin
from .const import (
DOMAIN,
VICARE_API,
VICARE_CIRCUITS,
VICARE_DEVICE_CONFIG,
VICARE_NAME,
VICARE_UNIT_TO_DEVICE_CLASS,
VICARE_UNIT_TO_UNIT_OF_MEASUREMENT,
)
@ -338,7 +339,7 @@ def _build_entity(name, vicare_api, device_config, sensor):
async def _entities_from_descriptions(
hass, name, all_devices, sensor_descriptions, iterables
hass, name, all_devices, sensor_descriptions, iterables, config_entry
):
"""Create entities from descriptions and list of burners/circuits."""
for description in sensor_descriptions:
@ -350,20 +351,17 @@ async def _entities_from_descriptions(
_build_entity,
f"{name} {description.name}{suffix}",
current,
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG],
description,
)
if entity is not None:
all_devices.append(entity)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Create the ViCare sensor devices."""
if discovery_info is None:
return
name = hass.data[DOMAIN][VICARE_NAME]
api = hass.data[DOMAIN][VICARE_API]
name = config_entry.data[CONF_NAME]
api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API]
all_devices = []
for description in GLOBAL_SENSORS:
@ -371,22 +369,22 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
_build_entity,
f"{name} {description.name}",
api,
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG],
description,
)
if entity is not None:
all_devices.append(entity)
for description in CIRCUIT_SENSORS:
for circuit in api.circuits:
for circuit in hass.data[DOMAIN][config_entry.entry_id][VICARE_CIRCUITS]:
suffix = ""
if len(api.circuits) > 1:
if len(hass.data[DOMAIN][config_entry.entry_id][VICARE_CIRCUITS]) > 1:
suffix = f" {circuit.id}"
entity = await hass.async_add_executor_job(
_build_entity,
f"{name} {description.name}{suffix}",
circuit,
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG],
description,
)
if entity is not None:
@ -394,19 +392,19 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
try:
await _entities_from_descriptions(
hass, name, all_devices, BURNER_SENSORS, api.burners
hass, name, all_devices, BURNER_SENSORS, api.burners, config_entry
)
except PyViCareNotSupportedFeatureError:
_LOGGER.info("No burners found")
try:
await _entities_from_descriptions(
hass, name, all_devices, COMPRESSOR_SENSORS, api.compressors
hass, name, all_devices, COMPRESSOR_SENSORS, api.compressors, config_entry
)
except PyViCareNotSupportedFeatureError:
_LOGGER.info("No compressors found")
async_add_entities(all_devices)
async_add_devices(all_devices)
class ViCareSensor(SensorEntity):

View file

@ -0,0 +1,20 @@
{
"config": {
"flow_title": "{name}",
"step": {
"user": {
"data": {
"password": "Password",
"client_id": "API Key",
"username": "Username",
"heating_type": "Heating type"
},
"description": "Setup ViCare to control your Viessmann device.\nMinimum needed: username, password, API key.",
"title": "Setup ViCare"
}
},
"error": {
"invalid_auth": "Invalid authentication"
}
}
}

View file

@ -13,7 +13,12 @@ from homeassistant.components.water_heater import (
SUPPORT_TARGET_TEMPERATURE,
WaterHeaterEntity,
)
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
from homeassistant.const import (
ATTR_TEMPERATURE,
CONF_NAME,
PRECISION_WHOLE,
TEMP_CELSIUS,
)
from .const import (
CONF_HEATING_TYPE,
@ -21,7 +26,6 @@ from .const import (
VICARE_API,
VICARE_CIRCUITS,
VICARE_DEVICE_CONFIG,
VICARE_NAME,
)
_LOGGER = logging.getLogger(__name__)
@ -66,29 +70,26 @@ def _build_entity(name, vicare_api, circuit, device_config, heating_type):
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Create the ViCare water_heater devices."""
if discovery_info is None:
return
name = hass.data[DOMAIN][VICARE_NAME]
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the ViCare climate platform."""
name = config_entry.data[CONF_NAME]
all_devices = []
for circuit in hass.data[DOMAIN][VICARE_CIRCUITS]:
for circuit in hass.data[DOMAIN][config_entry.entry_id][VICARE_CIRCUITS]:
suffix = ""
if len(hass.data[DOMAIN][VICARE_CIRCUITS]) > 1:
if len(hass.data[DOMAIN][config_entry.entry_id][VICARE_CIRCUITS]) > 1:
suffix = f" {circuit.id}"
entity = _build_entity(
f"{name} Water{suffix}",
hass.data[DOMAIN][VICARE_API],
hass.data[DOMAIN][config_entry.entry_id][VICARE_API],
circuit,
hass.data[DOMAIN][VICARE_DEVICE_CONFIG],
hass.data[DOMAIN][CONF_HEATING_TYPE],
hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG],
config_entry.data[CONF_HEATING_TYPE],
)
if entity is not None:
all_devices.append(entity)
async_add_entities(all_devices)
async_add_devices(all_devices)
class ViCareWater(WaterHeaterEntity):

View file

@ -318,6 +318,7 @@ FLOWS = [
"vera",
"verisure",
"vesync",
"vicare",
"vilfo",
"vizio",
"vlc_telnet",

View file

@ -544,6 +544,10 @@ DHCP = [
"domain": "verisure",
"macaddress": "0023C1*"
},
{
"domain": "vicare",
"macaddress": "B87424*"
},
{
"domain": "yeelight",
"hostname": "yeelink-*"

View file

@ -32,6 +32,9 @@ PyTransportNSW==0.1.1
# homeassistant.components.camera
PyTurboJPEG==1.6.1
# homeassistant.components.vicare
PyViCare==2.13.1
# homeassistant.components.xiaomi_aqara
PyXiaomiGateway==0.13.4

View file

@ -0,0 +1,20 @@
"""Test for ViCare."""
from homeassistant.components.vicare.const import CONF_HEATING_TYPE
from homeassistant.const import (
CONF_CLIENT_ID,
CONF_NAME,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
)
ENTRY_CONFIG = {
CONF_USERNAME: "foo@bar.com",
CONF_PASSWORD: "1234",
CONF_CLIENT_ID: "5678",
CONF_HEATING_TYPE: "auto",
CONF_SCAN_INTERVAL: 60,
CONF_NAME: "ViCare",
}
MOCK_MAC = "B874241B7B9"

View file

@ -0,0 +1,274 @@
"""Test the ViCare config flow."""
from unittest.mock import patch
from PyViCare.PyViCareUtils import PyViCareInvalidCredentialsError
from homeassistant import config_entries, data_entry_flow, setup
from homeassistant.components import dhcp
from homeassistant.components.vicare.const import (
CONF_CIRCUIT,
CONF_HEATING_TYPE,
DOMAIN,
)
from homeassistant.const import CONF_CLIENT_ID, CONF_PASSWORD, CONF_USERNAME
from . import ENTRY_CONFIG, MOCK_MAC
from tests.common import MockConfigEntry
async def test_form(hass):
"""Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert len(result["errors"]) == 0
with patch(
"homeassistant.components.vicare.config_flow.vicare_login",
return_value=None,
), patch(
"homeassistant.components.vicare.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.vicare.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "foo@bar.com",
CONF_PASSWORD: "1234",
CONF_CLIENT_ID: "5678",
},
)
await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result2["title"] == "ViCare"
assert result2["data"] == ENTRY_CONFIG
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_import(hass):
"""Test that the import works."""
await setup.async_setup_component(hass, "persistent_notification", {})
with patch(
"homeassistant.components.vicare.config_flow.vicare_login",
return_value=True,
), patch(
"homeassistant.components.vicare.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.vicare.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=ENTRY_CONFIG,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Configuration.yaml"
assert result["data"] == ENTRY_CONFIG
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_import_removes_circuit(hass):
"""Test that the import works."""
await setup.async_setup_component(hass, "persistent_notification", {})
with patch(
"homeassistant.components.vicare.config_flow.vicare_login",
return_value=True,
), patch(
"homeassistant.components.vicare.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.vicare.async_setup_entry",
return_value=True,
) as mock_setup_entry:
ENTRY_CONFIG[CONF_CIRCUIT] = 1
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=ENTRY_CONFIG,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Configuration.yaml"
assert result["data"] == ENTRY_CONFIG
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_import_adds_heating_type(hass):
"""Test that the import works."""
await setup.async_setup_component(hass, "persistent_notification", {})
with patch(
"homeassistant.components.vicare.config_flow.vicare_login",
return_value=True,
), patch(
"homeassistant.components.vicare.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.vicare.async_setup_entry",
return_value=True,
) as mock_setup_entry:
del ENTRY_CONFIG[CONF_HEATING_TYPE]
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=ENTRY_CONFIG,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Configuration.yaml"
assert result["data"] == ENTRY_CONFIG
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_invalid_login(hass) -> None:
"""Test a flow with an invalid Vicare login."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.vicare.config_flow.vicare_login",
side_effect=PyViCareInvalidCredentialsError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "foo@bar.com",
CONF_PASSWORD: "1234",
CONF_CLIENT_ID: "5678",
},
)
await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result2["step_id"] == "user"
assert result2["errors"] == {"base": "invalid_auth"}
async def test_form_dhcp(hass):
"""Test we can setup from dhcp."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data={
dhcp.MAC_ADDRESS: MOCK_MAC,
},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
with patch(
"homeassistant.components.vicare.config_flow.vicare_login",
return_value=None,
), patch(
"homeassistant.components.vicare.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.vicare.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "foo@bar.com",
CONF_PASSWORD: "1234",
CONF_CLIENT_ID: "5678",
},
)
await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result2["title"] == "ViCare"
assert result2["data"] == ENTRY_CONFIG
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_import_already_configured(hass):
"""Test that configuring same instance is rejectes."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="Configuration.yaml",
data=ENTRY_CONFIG,
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=ENTRY_CONFIG,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
async def test_import_single_instance_allowed(hass):
"""Test that configuring more than one instance is rejected."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="Configuration.yaml",
data=ENTRY_CONFIG,
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=ENTRY_CONFIG,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
async def test_dhcp_single_instance_allowed(hass):
"""Test that configuring more than one instance is rejected."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="Configuration.yaml",
data=ENTRY_CONFIG,
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data={
dhcp.MAC_ADDRESS: MOCK_MAC,
},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "single_instance_allowed"
async def test_user_input_single_instance_allowed(hass):
"""Test that configuring more than one instance is rejected."""
await setup.async_setup_component(hass, "persistent_notification", {})
mock_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="ViCare",
data=ENTRY_CONFIG,
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "single_instance_allowed"