mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Add Moehlenhoff Alpha2 underfloor heating system integration (#42771)
* Add Moehlenhoff Alpha2 underfloor heating system integration * isort changes * flake8 changes * Do not exclude config_flow.py * pylint changes * Add config_flow test * correct requirements_test_all.txt * more tests * Update test description * Test connection and catch TimeoutError in async_setup_entry * Add version to manifest file * Remove version from manifest file * Replace tests.async_mock.patch by unittest.mock.patch * Update moehlenhoff-alpha2 to version 1.0.1 * Update requirements for moehlenhoff-alpha2 1.0.1 * Update moehlenhoff-alpha2 to 1.0.2 * Use async_setup_platforms * Use async_unload_platforms * Separate connection and devices for each entry_id * Use async_track_time_interval to schedule updates * Check if input is valid before checking uniqueness * Move Exception handling to validate_input * Catch aiohttp.client_exceptions.ClientConnectorError * Remove translation files * Mock TimeoutError * Fix data update * Replace current callback implementation with ha dispatcher * Return False in should_poll * Remove unused argument * Remove CONNECTION_CLASS * Use _async_current_entries * Call async_schedule_update_ha_state after data update * Remove unneeded async_setup Co-authored-by: Milan Meulemans <milan.meulemans@live.be> * Remove unneeded async_setup_platform Co-authored-by: Milan Meulemans <milan.meulemans@live.be> * Set Schema attribute host required Co-authored-by: Milan Meulemans <milan.meulemans@live.be> * Remove unused Exception class Co-authored-by: Milan Meulemans <milan.meulemans@live.be> * Update manifest.json Co-authored-by: Milan Meulemans <milan.meulemans@live.be> * pylint constructor return type None * Replace properties by class variables * use pass instead of return * Remove unused sync update method * remove property hvac_action * remove pass * rework exception handling * Update homeassistant/components/moehlenhoff_alpha2/config_flow.py Co-authored-by: Milan Meulemans <milan.meulemans@live.be> * Correct indentation * catch Exception in validate_input * Replace HomeAssistantType with HomeAssistant * Update to moehlenhoff-alpha2 1.0.3 * Allow to switch between heating and cooling mode * Update moehlenhoff-alpha2 to version 1.0.4 * Update heatarea data after setting target temperature * Support hvac_action * Fix heatarea update with multiple bases * Update data after setting preset mode * Use custom preset modes like defined by device * Fix config flow test * Fix test_duplicate_error * Rename property to extra_state_attributes Rename property device_state_attributes to extra_state_attributes and return lowercase keys in dict. * Refactor using DataUpdateCoordinator * Remove _attr_should_poll * Raise HomeAssistantError on communication error Catch HTTPError instead of broad except and reraise as HomeAssistantError * Change DataUpdateCoordinator name to alpha2_base * Refresh coordinator before setting data * Raise ValueError on invalid heat area mode * Rename heatarea to heat_area * Set type annotation in class attribute * Move coordinator to top * Move exception handling to the coordinator * Use heat_area_id directly * Sore get_cooling() result into local var * Add explanation of status attributes and remove BLOCK_HC * Fix pylint warnings * from __future__ import annotations * Use Platform Enum * Move data handling to coordinator * Remove property extra_state_attributes * Add missing annotations * Update moehlenhoff-alpha2 to version 1.1.2 * Rework tests based on the scaffold template * Set also heat/cool/day/night temp with target temp * Remove unneeded code from tests Co-authored-by: Milan Meulemans <milan.meulemans@live.be>
This commit is contained in:
@ -721,6 +721,9 @@ omit =
|
||||
homeassistant/components/mochad/*
|
||||
homeassistant/components/modbus/climate.py
|
||||
homeassistant/components/modem_callerid/sensor.py
|
||||
homeassistant/components/moehlenhoff_alpha2/__init__.py
|
||||
homeassistant/components/moehlenhoff_alpha2/climate.py
|
||||
homeassistant/components/moehlenhoff_alpha2/const.py
|
||||
homeassistant/components/motion_blinds/__init__.py
|
||||
homeassistant/components/motion_blinds/const.py
|
||||
homeassistant/components/motion_blinds/cover.py
|
||||
|
@ -574,6 +574,8 @@ homeassistant/components/modem_callerid/* @tkdrob
|
||||
tests/components/modem_callerid/* @tkdrob
|
||||
homeassistant/components/modern_forms/* @wonderslug
|
||||
tests/components/modern_forms/* @wonderslug
|
||||
homeassistant/components/moehlenhoff_alpha2/* @j-a-n
|
||||
tests/components/moehlenhoff_alpha2/* @j-a-n
|
||||
homeassistant/components/monoprice/* @etsinko @OnFreund
|
||||
tests/components/monoprice/* @etsinko @OnFreund
|
||||
homeassistant/components/moon/* @fabaff
|
||||
|
159
homeassistant/components/moehlenhoff_alpha2/__init__.py
Normal file
159
homeassistant/components/moehlenhoff_alpha2/__init__.py
Normal file
@ -0,0 +1,159 @@
|
||||
"""Support for the Moehlenhoff Alpha2."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
from moehlenhoff_alpha2 import Alpha2Base
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.CLIMATE]
|
||||
|
||||
UPDATE_INTERVAL = timedelta(seconds=60)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up a config entry."""
|
||||
base = Alpha2Base(entry.data["host"])
|
||||
coordinator = Alpha2BaseCoordinator(hass, base)
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
if unload_ok and entry.entry_id in hass.data[DOMAIN]:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
class Alpha2BaseCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
"""Keep the base instance in one place and centralize the update."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, base: Alpha2Base) -> None:
|
||||
"""Initialize Alpha2Base data updater."""
|
||||
self.base = base
|
||||
super().__init__(
|
||||
hass=hass,
|
||||
logger=_LOGGER,
|
||||
name="alpha2_base",
|
||||
update_interval=UPDATE_INTERVAL,
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, dict]:
|
||||
"""Fetch the latest data from the source."""
|
||||
await self.base.update_data()
|
||||
return {ha["ID"]: ha for ha in self.base.heat_areas if ha.get("ID")}
|
||||
|
||||
def get_cooling(self) -> bool:
|
||||
"""Return if cooling mode is enabled."""
|
||||
return self.base.cooling
|
||||
|
||||
async def async_set_cooling(self, enabled: bool) -> None:
|
||||
"""Enable or disable cooling mode."""
|
||||
await self.base.set_cooling(enabled)
|
||||
for update_callback in self._listeners:
|
||||
update_callback()
|
||||
|
||||
async def async_set_target_temperature(
|
||||
self, heat_area_id: str, target_temperature: float
|
||||
) -> None:
|
||||
"""Set the target temperature of the given heat area."""
|
||||
_LOGGER.debug(
|
||||
"Setting target temperature of heat area %s to %0.1f",
|
||||
heat_area_id,
|
||||
target_temperature,
|
||||
)
|
||||
|
||||
update_data = {"T_TARGET": target_temperature}
|
||||
is_cooling = self.get_cooling()
|
||||
heat_area_mode = self.data[heat_area_id]["HEATAREA_MODE"]
|
||||
if heat_area_mode == 1:
|
||||
if is_cooling:
|
||||
update_data["T_COOL_DAY"] = target_temperature
|
||||
else:
|
||||
update_data["T_HEAT_DAY"] = target_temperature
|
||||
elif heat_area_mode == 2:
|
||||
if is_cooling:
|
||||
update_data["T_COOL_NIGHT"] = target_temperature
|
||||
else:
|
||||
update_data["T_HEAT_NIGHT"] = target_temperature
|
||||
|
||||
try:
|
||||
await self.base.update_heat_area(heat_area_id, update_data)
|
||||
except aiohttp.ClientError as http_err:
|
||||
raise HomeAssistantError(
|
||||
"Failed to set target temperature, communication error with alpha2 base"
|
||||
) from http_err
|
||||
self.data[heat_area_id].update(update_data)
|
||||
for update_callback in self._listeners:
|
||||
update_callback()
|
||||
|
||||
async def async_set_heat_area_mode(
|
||||
self, heat_area_id: str, heat_area_mode: int
|
||||
) -> None:
|
||||
"""Set the mode of the given heat area."""
|
||||
# HEATAREA_MODE: 0=Auto, 1=Tag, 2=Nacht
|
||||
if heat_area_mode not in (0, 1, 2):
|
||||
ValueError(f"Invalid heat area mode: {heat_area_mode}")
|
||||
_LOGGER.debug(
|
||||
"Setting mode of heat area %s to %d",
|
||||
heat_area_id,
|
||||
heat_area_mode,
|
||||
)
|
||||
try:
|
||||
await self.base.update_heat_area(
|
||||
heat_area_id, {"HEATAREA_MODE": heat_area_mode}
|
||||
)
|
||||
except aiohttp.ClientError as http_err:
|
||||
raise HomeAssistantError(
|
||||
"Failed to set heat area mode, communication error with alpha2 base"
|
||||
) from http_err
|
||||
|
||||
self.data[heat_area_id]["HEATAREA_MODE"] = heat_area_mode
|
||||
is_cooling = self.get_cooling()
|
||||
if heat_area_mode == 1:
|
||||
if is_cooling:
|
||||
self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][
|
||||
"T_COOL_DAY"
|
||||
]
|
||||
else:
|
||||
self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][
|
||||
"T_HEAT_DAY"
|
||||
]
|
||||
elif heat_area_mode == 2:
|
||||
if is_cooling:
|
||||
self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][
|
||||
"T_COOL_NIGHT"
|
||||
]
|
||||
else:
|
||||
self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][
|
||||
"T_HEAT_NIGHT"
|
||||
]
|
||||
for update_callback in self._listeners:
|
||||
update_callback()
|
131
homeassistant/components/moehlenhoff_alpha2/climate.py
Normal file
131
homeassistant/components/moehlenhoff_alpha2/climate.py
Normal file
@ -0,0 +1,131 @@
|
||||
"""Support for Alpha2 room control unit via Alpha2 base."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_COOL,
|
||||
CURRENT_HVAC_HEAT,
|
||||
CURRENT_HVAC_IDLE,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import Alpha2BaseCoordinator
|
||||
from .const import DOMAIN, PRESET_AUTO, PRESET_DAY, PRESET_NIGHT
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Alpha2Climate entities from a config_entry."""
|
||||
|
||||
coordinator: Alpha2BaseCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
Alpha2Climate(coordinator, heat_area_id) for heat_area_id in coordinator.data
|
||||
)
|
||||
|
||||
|
||||
# https://developers.home-assistant.io/docs/core/entity/climate/
|
||||
class Alpha2Climate(CoordinatorEntity, ClimateEntity):
|
||||
"""Alpha2 ClimateEntity."""
|
||||
|
||||
coordinator: Alpha2BaseCoordinator
|
||||
target_temperature_step = 0.2
|
||||
|
||||
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
_attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_COOL]
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
_attr_preset_modes = [PRESET_AUTO, PRESET_DAY, PRESET_NIGHT]
|
||||
|
||||
def __init__(self, coordinator: Alpha2BaseCoordinator, heat_area_id: str) -> None:
|
||||
"""Initialize Alpha2 ClimateEntity."""
|
||||
super().__init__(coordinator)
|
||||
self.heat_area_id = heat_area_id
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the climate device."""
|
||||
return self.coordinator.data[self.heat_area_id]["HEATAREA_NAME"]
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
"""Return the minimum temperature."""
|
||||
return float(self.coordinator.data[self.heat_area_id].get("T_TARGET_MIN", 0.0))
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
"""Return the maximum temperature."""
|
||||
return float(self.coordinator.data[self.heat_area_id].get("T_TARGET_MAX", 30.0))
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float:
|
||||
"""Return the current temperature."""
|
||||
return float(self.coordinator.data[self.heat_area_id].get("T_ACTUAL", 0.0))
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return current hvac mode."""
|
||||
if self.coordinator.get_cooling():
|
||||
return HVAC_MODE_COOL
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
await self.coordinator.async_set_cooling(hvac_mode == HVAC_MODE_COOL)
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> str:
|
||||
"""Return the current running hvac operation."""
|
||||
if not self.coordinator.data[self.heat_area_id]["_HEATCTRL_STATE"]:
|
||||
return CURRENT_HVAC_IDLE
|
||||
if self.coordinator.get_cooling():
|
||||
return CURRENT_HVAC_COOL
|
||||
return CURRENT_HVAC_HEAT
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> float:
|
||||
"""Return the temperature we try to reach."""
|
||||
return float(self.coordinator.data[self.heat_area_id].get("T_TARGET", 0.0))
|
||||
|
||||
async def async_set_temperature(self, **kwargs) -> None:
|
||||
"""Set new target temperatures."""
|
||||
target_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if target_temperature is None:
|
||||
return
|
||||
|
||||
await self.coordinator.async_set_target_temperature(
|
||||
self.heat_area_id, target_temperature
|
||||
)
|
||||
|
||||
@property
|
||||
def preset_mode(self) -> str:
|
||||
"""Return the current preset mode."""
|
||||
if self.coordinator.data[self.heat_area_id]["HEATAREA_MODE"] == 1:
|
||||
return PRESET_DAY
|
||||
if self.coordinator.data[self.heat_area_id]["HEATAREA_MODE"] == 2:
|
||||
return PRESET_NIGHT
|
||||
return PRESET_AUTO
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new operation mode."""
|
||||
heat_area_mode = 0
|
||||
if preset_mode == PRESET_DAY:
|
||||
heat_area_mode = 1
|
||||
elif preset_mode == PRESET_NIGHT:
|
||||
heat_area_mode = 2
|
||||
|
||||
await self.coordinator.async_set_heat_area_mode(
|
||||
self.heat_area_id, heat_area_mode
|
||||
)
|
55
homeassistant/components/moehlenhoff_alpha2/config_flow.py
Normal file
55
homeassistant/components/moehlenhoff_alpha2/config_flow.py
Normal file
@ -0,0 +1,55 @@
|
||||
"""Alpha2 config flow."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
from moehlenhoff_alpha2 import Alpha2Base
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_SCHEMA = vol.Schema({vol.Required("host"): str})
|
||||
|
||||
|
||||
async def validate_input(data):
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
|
||||
base = Alpha2Base(data["host"])
|
||||
try:
|
||||
await base.update_data()
|
||||
except (aiohttp.client_exceptions.ClientConnectorError, asyncio.TimeoutError):
|
||||
return {"error": "cannot_connect"}
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
return {"error": "unknown"}
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return {"title": base.name}
|
||||
|
||||
|
||||
class Alpha2BaseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Möhlenhoff Alpha2 config flow."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
self._async_abort_entries_match({"host": user_input["host"]})
|
||||
result = await validate_input(user_input)
|
||||
if result.get("error"):
|
||||
errors["base"] = result["error"]
|
||||
else:
|
||||
return self.async_create_entry(title=result["title"], data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||
)
|
6
homeassistant/components/moehlenhoff_alpha2/const.py
Normal file
6
homeassistant/components/moehlenhoff_alpha2/const.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""Constants for the Alpha2 integration."""
|
||||
|
||||
DOMAIN = "moehlenhoff_alpha2"
|
||||
PRESET_AUTO = "auto"
|
||||
PRESET_DAY = "day"
|
||||
PRESET_NIGHT = "night"
|
11
homeassistant/components/moehlenhoff_alpha2/manifest.json
Normal file
11
homeassistant/components/moehlenhoff_alpha2/manifest.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"domain": "moehlenhoff_alpha2",
|
||||
"name": "Möhlenhoff Alpha 2",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/moehlenhoff_alpha2",
|
||||
"requirements": ["moehlenhoff-alpha2==1.1.2"],
|
||||
"iot_class": "local_push",
|
||||
"codeowners": [
|
||||
"@j-a-n"
|
||||
]
|
||||
}
|
19
homeassistant/components/moehlenhoff_alpha2/strings.json
Normal file
19
homeassistant/components/moehlenhoff_alpha2/strings.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"title": "Möhlenhoff Alpha2",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
}
|
||||
}
|
@ -199,6 +199,7 @@ FLOWS = [
|
||||
"mobile_app",
|
||||
"modem_callerid",
|
||||
"modern_forms",
|
||||
"moehlenhoff_alpha2",
|
||||
"monoprice",
|
||||
"motion_blinds",
|
||||
"motioneye",
|
||||
|
@ -1048,6 +1048,9 @@ minio==5.0.10
|
||||
# homeassistant.components.mitemp_bt
|
||||
mitemp_bt==0.0.5
|
||||
|
||||
# homeassistant.components.moehlenhoff_alpha2
|
||||
moehlenhoff-alpha2==1.1.2
|
||||
|
||||
# homeassistant.components.motion_blinds
|
||||
motionblinds==0.5.11
|
||||
|
||||
|
@ -657,6 +657,9 @@ millheater==0.9.0
|
||||
# homeassistant.components.minio
|
||||
minio==5.0.10
|
||||
|
||||
# homeassistant.components.moehlenhoff_alpha2
|
||||
moehlenhoff-alpha2==1.1.2
|
||||
|
||||
# homeassistant.components.motion_blinds
|
||||
motionblinds==0.5.11
|
||||
|
||||
|
1
tests/components/moehlenhoff_alpha2/__init__.py
Normal file
1
tests/components/moehlenhoff_alpha2/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the moehlenhoff_alpha2 integration."""
|
106
tests/components/moehlenhoff_alpha2/test_config_flow.py
Normal file
106
tests/components/moehlenhoff_alpha2/test_config_flow.py
Normal file
@ -0,0 +1,106 @@
|
||||
"""Test the moehlenhoff_alpha2 config flow."""
|
||||
import asyncio
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.moehlenhoff_alpha2.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import (
|
||||
RESULT_TYPE_ABORT,
|
||||
RESULT_TYPE_CREATE_ENTRY,
|
||||
RESULT_TYPE_FORM,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
MOCK_BASE_ID = "fake-base-id"
|
||||
MOCK_BASE_NAME = "fake-base-name"
|
||||
MOCK_BASE_HOST = "fake-base-host"
|
||||
|
||||
|
||||
async def mock_update_data(self):
|
||||
"""Mock moehlenhoff_alpha2.Alpha2Base.update_data."""
|
||||
self.static_data = {
|
||||
"Devices": {
|
||||
"Device": {"ID": MOCK_BASE_ID, "NAME": MOCK_BASE_NAME, "HEATAREA": []}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 not result["errors"]
|
||||
|
||||
with patch("moehlenhoff_alpha2.Alpha2Base.update_data", mock_update_data), patch(
|
||||
"homeassistant.components.moehlenhoff_alpha2.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
flow_id=result["flow_id"],
|
||||
user_input={"host": MOCK_BASE_HOST},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["title"] == MOCK_BASE_NAME
|
||||
assert result2["data"] == {"host": MOCK_BASE_HOST}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_duplicate_error(hass: HomeAssistant) -> None:
|
||||
"""Test that errors are shown when duplicates are added."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={"host": MOCK_BASE_HOST},
|
||||
source=config_entries.SOURCE_USER,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
assert config_entry.data["host"] == MOCK_BASE_HOST
|
||||
|
||||
with patch("moehlenhoff_alpha2.Alpha2Base.update_data", mock_update_data):
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
data={"host": MOCK_BASE_HOST},
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_form_cannot_connect_error(hass: HomeAssistant) -> None:
|
||||
"""Test connection error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
with patch(
|
||||
"moehlenhoff_alpha2.Alpha2Base.update_data", side_effect=asyncio.TimeoutError
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
flow_id=result["flow_id"],
|
||||
user_input={"host": MOCK_BASE_HOST},
|
||||
)
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_form_unexpected_error(hass: HomeAssistant) -> None:
|
||||
"""Test unexpected error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
with patch("moehlenhoff_alpha2.Alpha2Base.update_data", side_effect=Exception):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
flow_id=result["flow_id"],
|
||||
user_input={"host": MOCK_BASE_HOST},
|
||||
)
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
assert result2["errors"] == {"base": "unknown"}
|
Reference in New Issue
Block a user