Add Mill config flow (#35136)
* Mill config, wip * add tests * fix merge * update tests * unique id * Mill strings * mill config flow * mill config flow * test import * test import * req * ccoverage * Apply suggestions from code review Co-authored-by: J. Nick Koston <nick@koston.org> * style * Apply suggestions from code review Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * update strings * add test Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Paulus Schoutsen <balloob@gmail.com>pull/35460/head
parent
8050d8555e
commit
f302c6fd65
homeassistant/components/mill
tests/components/mill
|
@ -452,6 +452,7 @@ omit =
|
||||||
homeassistant/components/miflora/sensor.py
|
homeassistant/components/miflora/sensor.py
|
||||||
homeassistant/components/mikrotik/hub.py
|
homeassistant/components/mikrotik/hub.py
|
||||||
homeassistant/components/mikrotik/device_tracker.py
|
homeassistant/components/mikrotik/device_tracker.py
|
||||||
|
homeassistant/components/mill/__init__.py
|
||||||
homeassistant/components/mill/climate.py
|
homeassistant/components/mill/climate.py
|
||||||
homeassistant/components/mill/const.py
|
homeassistant/components/mill/const.py
|
||||||
homeassistant/components/minecraft_server/__init__.py
|
homeassistant/components/minecraft_server/__init__.py
|
||||||
|
|
|
@ -1 +1,25 @@
|
||||||
"""The mill component."""
|
"""The mill component."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Set up the Mill platform."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry):
|
||||||
|
"""Set up the Mill heater."""
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, "climate")
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, config_entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = await hass.config_entries.async_forward_entry_unload(
|
||||||
|
config_entry, "climate"
|
||||||
|
)
|
||||||
|
return unload_ok
|
||||||
|
|
|
@ -4,7 +4,7 @@ import logging
|
||||||
from mill import Mill
|
from mill import Mill
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity
|
from homeassistant.components.climate import ClimateEntity
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
CURRENT_HVAC_HEAT,
|
CURRENT_HVAC_HEAT,
|
||||||
CURRENT_HVAC_IDLE,
|
CURRENT_HVAC_IDLE,
|
||||||
|
@ -20,6 +20,7 @@ from homeassistant.const import (
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ from .const import (
|
||||||
ATTR_ROOM_NAME,
|
ATTR_ROOM_NAME,
|
||||||
ATTR_SLEEP_TEMP,
|
ATTR_SLEEP_TEMP,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
MANUFACTURER,
|
||||||
MAX_TEMP,
|
MAX_TEMP,
|
||||||
MIN_TEMP,
|
MIN_TEMP,
|
||||||
SERVICE_SET_ROOM_TEMP,
|
SERVICE_SET_ROOM_TEMP,
|
||||||
|
@ -38,10 +40,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
||||||
{vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string}
|
|
||||||
)
|
|
||||||
|
|
||||||
SET_ROOM_TEMP_SCHEMA = vol.Schema(
|
SET_ROOM_TEMP_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_ROOM_NAME): cv.string,
|
vol.Required(ATTR_ROOM_NAME): cv.string,
|
||||||
|
@ -52,16 +50,15 @@ SET_ROOM_TEMP_SCHEMA = vol.Schema(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
"""Set up the Mill heater."""
|
"""Set up the Mill climate."""
|
||||||
mill_data_connection = Mill(
|
mill_data_connection = Mill(
|
||||||
config[CONF_USERNAME],
|
entry.data[CONF_USERNAME],
|
||||||
config[CONF_PASSWORD],
|
entry.data[CONF_PASSWORD],
|
||||||
websession=async_get_clientsession(hass),
|
websession=async_get_clientsession(hass),
|
||||||
)
|
)
|
||||||
if not await mill_data_connection.connect():
|
if not await mill_data_connection.connect():
|
||||||
_LOGGER.error("Failed to connect to Mill")
|
raise ConfigEntryNotReady
|
||||||
return
|
|
||||||
|
|
||||||
await mill_data_connection.find_all_heaters()
|
await mill_data_connection.find_all_heaters()
|
||||||
|
|
||||||
|
@ -218,3 +215,19 @@ class MillHeater(ClimateEntity):
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Retrieve latest state."""
|
"""Retrieve latest state."""
|
||||||
self._heater = await self._conn.update_device(self._heater.device_id)
|
self._heater = await self._conn.update_device(self._heater.device_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_id(self):
|
||||||
|
"""Return the ID of the physical device this sensor is part of."""
|
||||||
|
return self._heater.device_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device_info of the device."""
|
||||||
|
device_info = {
|
||||||
|
"identifiers": {(DOMAIN, self.device_id)},
|
||||||
|
"name": self.name,
|
||||||
|
"manufacturer": MANUFACTURER,
|
||||||
|
"model": f"generation {1 if self._heater.is_gen1 else 2}",
|
||||||
|
}
|
||||||
|
return device_info
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
"""Adds config flow for Mill integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from mill import Mill
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import DOMAIN # pylint:disable=unused-import
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DATA_SCHEMA = vol.Schema(
|
||||||
|
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MillConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Mill integration."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle the initial step."""
|
||||||
|
if user_input is None:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=DATA_SCHEMA, errors={},
|
||||||
|
)
|
||||||
|
|
||||||
|
username = user_input[CONF_USERNAME].replace(" ", "")
|
||||||
|
password = user_input[CONF_PASSWORD].replace(" ", "")
|
||||||
|
|
||||||
|
mill_data_connection = Mill(
|
||||||
|
username, password, websession=async_get_clientsession(self.hass),
|
||||||
|
)
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if not await mill_data_connection.connect():
|
||||||
|
errors["connection_error"] = "connection_error"
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=DATA_SCHEMA, errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
unique_id = username
|
||||||
|
|
||||||
|
await self.async_set_unique_id(unique_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=unique_id, data={CONF_USERNAME: username, CONF_PASSWORD: password},
|
||||||
|
)
|
|
@ -4,6 +4,7 @@ ATTR_AWAY_TEMP = "away_temp"
|
||||||
ATTR_COMFORT_TEMP = "comfort_temp"
|
ATTR_COMFORT_TEMP = "comfort_temp"
|
||||||
ATTR_ROOM_NAME = "room_name"
|
ATTR_ROOM_NAME = "room_name"
|
||||||
ATTR_SLEEP_TEMP = "sleep_temp"
|
ATTR_SLEEP_TEMP = "sleep_temp"
|
||||||
|
MANUFACTURER = "Mill"
|
||||||
MAX_TEMP = 35
|
MAX_TEMP = 35
|
||||||
MIN_TEMP = 5
|
MIN_TEMP = 5
|
||||||
DOMAIN = "mill"
|
DOMAIN = "mill"
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"connection_error": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -373,6 +373,9 @@ meteofrance==0.3.7
|
||||||
# homeassistant.components.mfi
|
# homeassistant.components.mfi
|
||||||
mficlient==0.3.0
|
mficlient==0.3.0
|
||||||
|
|
||||||
|
# homeassistant.components.mill
|
||||||
|
millheater==0.3.4
|
||||||
|
|
||||||
# homeassistant.components.minio
|
# homeassistant.components.minio
|
||||||
minio==4.0.9
|
minio==4.0.9
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for Mill."""
|
|
@ -0,0 +1,86 @@
|
||||||
|
"""Tests for Mill config flow."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.mill.const import DOMAIN
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
|
||||||
|
from tests.async_mock import patch
|
||||||
|
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": "user"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert 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",
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch("mill.Mill.connect", return_value=True):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": "user"}, data=test_data
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == test_data[CONF_USERNAME]
|
||||||
|
assert result["data"] == test_data
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_entry_already_exists(hass):
|
||||||
|
"""Test user input for config_entry that already exists."""
|
||||||
|
|
||||||
|
test_data = {
|
||||||
|
CONF_USERNAME: "user",
|
||||||
|
CONF_PASSWORD: "pswd",
|
||||||
|
}
|
||||||
|
|
||||||
|
first_entry = MockConfigEntry(
|
||||||
|
domain="mill", data=test_data, unique_id=test_data[CONF_USERNAME],
|
||||||
|
)
|
||||||
|
first_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch("mill.Mill.connect", return_value=True):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": "user"}, data=test_data
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_connection_error(hass):
|
||||||
|
"""Test connection error."""
|
||||||
|
|
||||||
|
test_data = {
|
||||||
|
CONF_USERNAME: "user",
|
||||||
|
CONF_PASSWORD: "pswd",
|
||||||
|
}
|
||||||
|
|
||||||
|
first_entry = MockConfigEntry(
|
||||||
|
domain="mill", data=test_data, unique_id=test_data[CONF_USERNAME],
|
||||||
|
)
|
||||||
|
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": "user"}, data=test_data
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"]["connection_error"] == "connection_error"
|
Loading…
Reference in New Issue