mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Mill local access (#59549)
* Mill local Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Mill local Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Mill local Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Update homeassistant/components/mill/config_flow.py Co-authored-by: Allen Porter <allen@thebends.org> * Update homeassistant/components/mill/config_flow.py Co-authored-by: Allen Porter <allen@thebends.org> * Update homeassistant/components/mill/config_flow.py Co-authored-by: Allen Porter <allen@thebends.org> * Fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * coveragerc Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> Co-authored-by: Allen Porter <allen@thebends.org>
This commit is contained in:
committed by
GitHub
parent
406cbcfe2d
commit
a3d5aec778
@ -641,7 +641,6 @@ omit =
|
||||
homeassistant/components/miflora/sensor.py
|
||||
homeassistant/components/mikrotik/hub.py
|
||||
homeassistant/components/mikrotik/device_tracker.py
|
||||
homeassistant/components/mill/__init__.py
|
||||
homeassistant/components/mill/climate.py
|
||||
homeassistant/components/mill/const.py
|
||||
homeassistant/components/mill/sensor.py
|
||||
|
@ -1,16 +1,19 @@
|
||||
"""The mill component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from mill import Mill
|
||||
from mill_local import Mill as MillLocal
|
||||
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import CLOUD, CONNECTION_TYPE, DOMAIN, LOCAL
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -23,8 +26,9 @@ class MillDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
update_interval: timedelta | None = None,
|
||||
*,
|
||||
mill_data_connection: Mill,
|
||||
mill_data_connection: Mill | MillLocal,
|
||||
) -> None:
|
||||
"""Initialize global Mill data updater."""
|
||||
self.mill_data_connection = mill_data_connection
|
||||
@ -34,26 +38,42 @@ class MillDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_method=mill_data_connection.fetch_heater_and_sensor_data,
|
||||
update_interval=timedelta(seconds=30),
|
||||
update_interval=update_interval,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry):
|
||||
"""Set up the Mill heater."""
|
||||
mill_data_connection = Mill(
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
websession=async_get_clientsession(hass),
|
||||
)
|
||||
hass.data.setdefault(DOMAIN, {LOCAL: {}, CLOUD: {}})
|
||||
|
||||
if entry.data.get(CONNECTION_TYPE) == LOCAL:
|
||||
mill_data_connection = MillLocal(
|
||||
entry.data[CONF_IP_ADDRESS],
|
||||
websession=async_get_clientsession(hass),
|
||||
)
|
||||
update_interval = timedelta(seconds=15)
|
||||
key = entry.data[CONF_IP_ADDRESS]
|
||||
conn_type = LOCAL
|
||||
else:
|
||||
mill_data_connection = Mill(
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
websession=async_get_clientsession(hass),
|
||||
)
|
||||
update_interval = timedelta(seconds=30)
|
||||
key = entry.data[CONF_USERNAME]
|
||||
conn_type = CLOUD
|
||||
|
||||
if not await mill_data_connection.connect():
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
hass.data[DOMAIN] = MillDataUpdateCoordinator(
|
||||
data_coordinator = MillDataUpdateCoordinator(
|
||||
hass,
|
||||
mill_data_connection=mill_data_connection,
|
||||
update_interval=update_interval,
|
||||
)
|
||||
|
||||
await hass.data[DOMAIN].async_config_entry_first_refresh()
|
||||
hass.data[DOMAIN][conn_type][key] = data_coordinator
|
||||
await data_coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
return True
|
||||
|
@ -12,7 +12,13 @@ from homeassistant.components.climate.const import (
|
||||
SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_USERNAME,
|
||||
PRECISION_WHOLE,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
@ -23,15 +29,16 @@ from .const import (
|
||||
ATTR_COMFORT_TEMP,
|
||||
ATTR_ROOM_NAME,
|
||||
ATTR_SLEEP_TEMP,
|
||||
CLOUD,
|
||||
CONNECTION_TYPE,
|
||||
DOMAIN,
|
||||
LOCAL,
|
||||
MANUFACTURER,
|
||||
MAX_TEMP,
|
||||
MIN_TEMP,
|
||||
SERVICE_SET_ROOM_TEMP,
|
||||
)
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
|
||||
SET_ROOM_TEMP_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ROOM_NAME): cv.string,
|
||||
@ -44,8 +51,12 @@ SET_ROOM_TEMP_SCHEMA = vol.Schema(
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up the Mill climate."""
|
||||
if entry.data.get(CONNECTION_TYPE) == LOCAL:
|
||||
mill_data_coordinator = hass.data[DOMAIN][LOCAL][entry.data[CONF_IP_ADDRESS]]
|
||||
async_add_entities([LocalMillHeater(mill_data_coordinator)])
|
||||
return
|
||||
|
||||
mill_data_coordinator = hass.data[DOMAIN]
|
||||
mill_data_coordinator = hass.data[DOMAIN][CLOUD][entry.data[CONF_USERNAME]]
|
||||
|
||||
entities = [
|
||||
MillHeater(mill_data_coordinator, mill_device)
|
||||
@ -75,7 +86,7 @@ class MillHeater(CoordinatorEntity, ClimateEntity):
|
||||
_attr_fan_modes = [FAN_ON, HVAC_MODE_OFF]
|
||||
_attr_max_temp = MAX_TEMP
|
||||
_attr_min_temp = MIN_TEMP
|
||||
_attr_supported_features = SUPPORT_FLAGS
|
||||
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
|
||||
@ -169,3 +180,47 @@ class MillHeater(CoordinatorEntity, ClimateEntity):
|
||||
self._attr_hvac_mode = HVAC_MODE_HEAT
|
||||
else:
|
||||
self._attr_hvac_mode = HVAC_MODE_OFF
|
||||
|
||||
|
||||
class LocalMillHeater(CoordinatorEntity, ClimateEntity):
|
||||
"""Representation of a Mill Thermostat device."""
|
||||
|
||||
_attr_hvac_mode = HVAC_MODE_HEAT
|
||||
_attr_hvac_modes = [HVAC_MODE_HEAT]
|
||||
_attr_max_temp = MAX_TEMP
|
||||
_attr_min_temp = MIN_TEMP
|
||||
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
|
||||
def __init__(self, coordinator):
|
||||
"""Initialize the thermostat."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_name = coordinator.mill_data_connection.name
|
||||
self._update_attr()
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
|
||||
return
|
||||
await self.coordinator.mill_data_connection.set_target_temperature(
|
||||
int(temperature)
|
||||
)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._update_attr()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _update_attr(self) -> None:
|
||||
data = self.coordinator.data
|
||||
self._attr_target_temperature = data["set_temperature"]
|
||||
self._attr_current_temperature = data["ambient_temperature"]
|
||||
|
||||
if data["current_power"] > 0:
|
||||
self._attr_hvac_action = CURRENT_HVAC_HEAT
|
||||
else:
|
||||
self._attr_hvac_action = CURRENT_HVAC_IDLE
|
||||
|
@ -1,16 +1,13 @@
|
||||
"""Adds config flow for Mill integration."""
|
||||
from mill import Mill
|
||||
from mill_local import Mill as MillLocal
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
|
||||
)
|
||||
from .const import CLOUD, CONNECTION_TYPE, DOMAIN, LOCAL
|
||||
|
||||
|
||||
class MillConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
@ -20,10 +17,68 @@ class MillConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONNECTION_TYPE, default=CLOUD): vol.In(
|
||||
(
|
||||
CLOUD,
|
||||
LOCAL,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=DATA_SCHEMA,
|
||||
data_schema=data_schema,
|
||||
)
|
||||
|
||||
if user_input[CONNECTION_TYPE] == LOCAL:
|
||||
return await self.async_step_local()
|
||||
return await self.async_step_cloud()
|
||||
|
||||
async def async_step_local(self, user_input=None):
|
||||
"""Handle the local step."""
|
||||
data_schema = vol.Schema({vol.Required(CONF_IP_ADDRESS): str})
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="local",
|
||||
data_schema=data_schema,
|
||||
)
|
||||
|
||||
mill_data_connection = MillLocal(
|
||||
user_input[CONF_IP_ADDRESS],
|
||||
websession=async_get_clientsession(self.hass),
|
||||
)
|
||||
|
||||
await self.async_set_unique_id(mill_data_connection.device_ip)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
if not await mill_data_connection.connect():
|
||||
return self.async_show_form(
|
||||
step_id="local",
|
||||
data_schema=data_schema,
|
||||
errors={"base": "cannot_connect"},
|
||||
)
|
||||
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_IP_ADDRESS],
|
||||
data={
|
||||
CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS],
|
||||
CONNECTION_TYPE: LOCAL,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_cloud(self, user_input=None):
|
||||
"""Handle the cloud step."""
|
||||
data_schema = vol.Schema(
|
||||
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
|
||||
)
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="cloud",
|
||||
data_schema=data_schema,
|
||||
errors={},
|
||||
)
|
||||
|
||||
@ -39,10 +94,10 @@ class MillConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
errors = {}
|
||||
|
||||
if not await mill_data_connection.connect():
|
||||
errors["cannot_connect"] = "cannot_connect"
|
||||
errors["base"] = "cannot_connect"
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=DATA_SCHEMA,
|
||||
step_id="cloud",
|
||||
data_schema=data_schema,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@ -53,5 +108,9 @@ class MillConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
return self.async_create_entry(
|
||||
title=unique_id,
|
||||
data={CONF_USERNAME: username, CONF_PASSWORD: password},
|
||||
data={
|
||||
CONF_USERNAME: username,
|
||||
CONF_PASSWORD: password,
|
||||
CONNECTION_TYPE: CLOUD,
|
||||
},
|
||||
)
|
||||
|
@ -5,11 +5,14 @@ ATTR_COMFORT_TEMP = "comfort_temp"
|
||||
ATTR_ROOM_NAME = "room_name"
|
||||
ATTR_SLEEP_TEMP = "sleep_temp"
|
||||
BATTERY = "battery"
|
||||
CLOUD = "Cloud"
|
||||
CONNECTION_TYPE = "connection_type"
|
||||
CONSUMPTION_TODAY = "day_consumption"
|
||||
CONSUMPTION_YEAR = "year_consumption"
|
||||
DOMAIN = "mill"
|
||||
ECO2 = "eco2"
|
||||
HUMIDITY = "humidity"
|
||||
LOCAL = "Local"
|
||||
MANUFACTURER = "Mill"
|
||||
MAX_TEMP = 35
|
||||
MIN_TEMP = 5
|
||||
|
@ -2,8 +2,8 @@
|
||||
"domain": "mill",
|
||||
"name": "Mill",
|
||||
"documentation": "https://www.home-assistant.io/integrations/mill",
|
||||
"requirements": ["millheater==0.8.0"],
|
||||
"requirements": ["millheater==0.8.0", "mill-local==0.1.0"],
|
||||
"codeowners": ["@danielhiversen"],
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
"iot_class": "local_polling"
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ from homeassistant.components.sensor import (
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONF_USERNAME,
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
PERCENTAGE,
|
||||
@ -28,11 +29,14 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import (
|
||||
BATTERY,
|
||||
CLOUD,
|
||||
CONNECTION_TYPE,
|
||||
CONSUMPTION_TODAY,
|
||||
CONSUMPTION_YEAR,
|
||||
DOMAIN,
|
||||
ECO2,
|
||||
HUMIDITY,
|
||||
LOCAL,
|
||||
MANUFACTURER,
|
||||
TEMPERATURE,
|
||||
TVOC,
|
||||
@ -95,8 +99,10 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up the Mill sensor."""
|
||||
if entry.data.get(CONNECTION_TYPE) == LOCAL:
|
||||
return
|
||||
|
||||
mill_data_coordinator = hass.data[DOMAIN]
|
||||
mill_data_coordinator = hass.data[DOMAIN][CLOUD][entry.data[CONF_USERNAME]]
|
||||
|
||||
entities = [
|
||||
MillSensor(
|
||||
|
@ -8,10 +8,22 @@
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"connection_type": "Select connection type"
|
||||
},
|
||||
"description": "Select connection type. Local requires generation 3 heaters"
|
||||
},
|
||||
"cloud": {
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
},
|
||||
"local": {
|
||||
"data": {
|
||||
"ip_address": "[%key:common::config_flow::data::ip%]"
|
||||
},
|
||||
"description": "Local IP address of the device."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,23 @@
|
||||
"cannot_connect": "Failed to connect"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"cloud": {
|
||||
"data": {
|
||||
"password": "Password",
|
||||
"username": "Username"
|
||||
}
|
||||
},
|
||||
"local": {
|
||||
"data": {
|
||||
"ip_address": "IP Address"
|
||||
},
|
||||
"description": "Local IP address of the device."
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"connection_type": "Select connection type"
|
||||
},
|
||||
"description": "Select connection type. Local requires generation 3 heaters"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1010,6 +1010,9 @@ micloud==0.4
|
||||
# homeassistant.components.miflora
|
||||
miflora==0.7.0
|
||||
|
||||
# homeassistant.components.mill
|
||||
mill-local==0.1.0
|
||||
|
||||
# homeassistant.components.mill
|
||||
millheater==0.8.0
|
||||
|
||||
|
@ -611,6 +611,9 @@ mficlient==0.3.0
|
||||
# homeassistant.components.xiaomi_miio
|
||||
micloud==0.4
|
||||
|
||||
# homeassistant.components.mill
|
||||
mill-local==0.1.0
|
||||
|
||||
# homeassistant.components.mill
|
||||
millheater==0.8.0
|
||||
|
||||
|
@ -1,47 +1,57 @@
|
||||
"""Tests for Mill config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.mill.const import DOMAIN
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.components.mill.const import CLOUD, CONNECTION_TYPE, DOMAIN, LOCAL
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.data_entry_flow import RESULT_TYPE_FORM
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture(name="mill_setup", autouse=True)
|
||||
def mill_setup_fixture():
|
||||
"""Patch mill setup entry."""
|
||||
with patch("homeassistant.components.mill.async_setup_entry", return_value=True):
|
||||
yield
|
||||
|
||||
|
||||
async def test_show_config_form(hass):
|
||||
"""Test show configuration form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
|
||||
async def test_create_entry(hass):
|
||||
"""Test create entry from user input."""
|
||||
test_data = {
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pswd",
|
||||
}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONNECTION_TYPE: CLOUD,
|
||||
},
|
||||
)
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
|
||||
with patch("mill.Mill.connect", return_value=True):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=test_data
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pswd",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == test_data[CONF_USERNAME]
|
||||
assert result["data"] == test_data
|
||||
assert result["title"] == "user"
|
||||
assert result["data"] == {
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pswd",
|
||||
CONNECTION_TYPE: CLOUD,
|
||||
}
|
||||
|
||||
|
||||
async def test_flow_entry_already_exists(hass):
|
||||
@ -59,10 +69,26 @@ async def test_flow_entry_already_exists(hass):
|
||||
)
|
||||
first_entry.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["errors"] is None
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONNECTION_TYPE: CLOUD,
|
||||
},
|
||||
)
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
|
||||
with patch("mill.Mill.connect", return_value=True):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=test_data
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
test_data,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
@ -70,23 +96,152 @@ async def test_flow_entry_already_exists(hass):
|
||||
|
||||
async def test_connection_error(hass):
|
||||
"""Test connection error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONNECTION_TYPE: CLOUD,
|
||||
},
|
||||
)
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
|
||||
with patch("mill.Mill.connect", return_value=False):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pswd",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_local_create_entry(hass):
|
||||
"""Test create entry from user input."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONNECTION_TYPE: LOCAL,
|
||||
},
|
||||
)
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
|
||||
test_data = {
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "pswd",
|
||||
CONF_IP_ADDRESS: "192.168.1.59",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"mill_local.Mill.connect",
|
||||
return_value={
|
||||
"name": "panel heater gen. 3",
|
||||
"version": "0x210927",
|
||||
"operation_key": "",
|
||||
"status": "ok",
|
||||
},
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
test_data,
|
||||
)
|
||||
|
||||
test_data[CONNECTION_TYPE] = LOCAL
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == test_data[CONF_IP_ADDRESS]
|
||||
assert result["data"] == test_data
|
||||
|
||||
|
||||
async def test_local_flow_entry_already_exists(hass):
|
||||
"""Test user input for config_entry that already exists."""
|
||||
|
||||
test_data = {
|
||||
CONF_IP_ADDRESS: "192.168.1.59",
|
||||
}
|
||||
|
||||
first_entry = MockConfigEntry(
|
||||
domain="mill",
|
||||
data=test_data,
|
||||
unique_id=test_data[CONF_USERNAME],
|
||||
unique_id=test_data[CONF_IP_ADDRESS],
|
||||
)
|
||||
first_entry.add_to_hass(hass)
|
||||
|
||||
with patch("mill.Mill.connect", return_value=False):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=test_data
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONNECTION_TYPE: LOCAL,
|
||||
},
|
||||
)
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
|
||||
test_data = {
|
||||
CONF_IP_ADDRESS: "192.168.1.59",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"mill_local.Mill.connect",
|
||||
return_value={
|
||||
"name": "panel heater gen. 3",
|
||||
"version": "0x210927",
|
||||
"operation_key": "",
|
||||
"status": "ok",
|
||||
},
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
test_data,
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"]["cannot_connect"] == "cannot_connect"
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_local_connection_error(hass):
|
||||
"""Test connection error."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONNECTION_TYPE: LOCAL,
|
||||
},
|
||||
)
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
|
||||
test_data = {
|
||||
CONF_IP_ADDRESS: "192.168.1.59",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"mill_local.Mill.connect",
|
||||
return_value=None,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
test_data,
|
||||
)
|
||||
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
121
tests/components/mill/test_init.py
Normal file
121
tests/components/mill/test_init.py
Normal file
@ -0,0 +1,121 @@
|
||||
"""Tests for Mill init."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components import mill
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry, mock_coro
|
||||
|
||||
|
||||
async def test_setup_with_cloud_config(hass):
|
||||
"""Test setup of cloud config."""
|
||||
entry = MockConfigEntry(
|
||||
domain=mill.DOMAIN,
|
||||
data={
|
||||
mill.CONF_USERNAME: "user",
|
||||
mill.CONF_PASSWORD: "pswd",
|
||||
mill.CONNECTION_TYPE: mill.CLOUD,
|
||||
},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"mill.Mill.fetch_heater_and_sensor_data", return_value={}
|
||||
) as mock_fetch, patch("mill.Mill.connect", return_value=True) as mock_connect:
|
||||
assert await async_setup_component(hass, "mill", entry)
|
||||
assert len(mock_fetch.mock_calls) == 1
|
||||
assert len(mock_connect.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_setup_with_cloud_config_fails(hass):
|
||||
"""Test setup of cloud config."""
|
||||
entry = MockConfigEntry(
|
||||
domain=mill.DOMAIN,
|
||||
data={
|
||||
mill.CONF_USERNAME: "user",
|
||||
mill.CONF_PASSWORD: "pswd",
|
||||
mill.CONNECTION_TYPE: mill.CLOUD,
|
||||
},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch("mill.Mill.connect", return_value=False):
|
||||
assert await async_setup_component(hass, "mill", entry)
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_setup_with_old_cloud_config(hass):
|
||||
"""Test setup of old cloud config."""
|
||||
entry = MockConfigEntry(
|
||||
domain=mill.DOMAIN,
|
||||
data={
|
||||
mill.CONF_USERNAME: "user",
|
||||
mill.CONF_PASSWORD: "pswd",
|
||||
},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch("mill.Mill.fetch_heater_and_sensor_data", return_value={}), patch(
|
||||
"mill.Mill.connect", return_value=True
|
||||
) as mock_connect:
|
||||
assert await async_setup_component(hass, "mill", entry)
|
||||
|
||||
assert len(mock_connect.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_setup_with_local_config(hass):
|
||||
"""Test setup of local config."""
|
||||
entry = MockConfigEntry(
|
||||
domain=mill.DOMAIN,
|
||||
data={
|
||||
mill.CONF_IP_ADDRESS: "192.168.1.59",
|
||||
mill.CONNECTION_TYPE: mill.LOCAL,
|
||||
},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"mill_local.Mill.fetch_heater_and_sensor_data",
|
||||
return_value={
|
||||
"ambient_temperature": 20,
|
||||
"set_temperature": 22,
|
||||
"current_power": 0,
|
||||
},
|
||||
) as mock_fetch, patch(
|
||||
"mill_local.Mill.connect",
|
||||
return_value={
|
||||
"name": "panel heater gen. 3",
|
||||
"version": "0x210927",
|
||||
"operation_key": "",
|
||||
"status": "ok",
|
||||
},
|
||||
) as mock_connect:
|
||||
assert await async_setup_component(hass, "mill", entry)
|
||||
|
||||
assert len(mock_fetch.mock_calls) == 1
|
||||
assert len(mock_connect.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_unload_entry(hass):
|
||||
"""Test removing mill client."""
|
||||
entry = MockConfigEntry(
|
||||
domain=mill.DOMAIN,
|
||||
data={
|
||||
mill.CONF_USERNAME: "user",
|
||||
mill.CONF_PASSWORD: "pswd",
|
||||
mill.CONNECTION_TYPE: mill.CLOUD,
|
||||
},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries, "async_forward_entry_unload", return_value=mock_coro(True)
|
||||
) as unload_entry, patch(
|
||||
"mill.Mill.fetch_heater_and_sensor_data", return_value={}
|
||||
), patch(
|
||||
"mill.Mill.connect", return_value=True
|
||||
):
|
||||
assert await async_setup_component(hass, "mill", entry)
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
|
||||
assert unload_entry.call_count == 2
|
||||
assert entry.entry_id not in hass.data[mill.DOMAIN]
|
Reference in New Issue
Block a user