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
|
@ -452,6 +452,7 @@ 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/minecraft_server/__init__.py
|
||||
|
|
|
@ -1 +1,25 @@
|
|||
"""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
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_HEAT,
|
||||
CURRENT_HVAC_IDLE,
|
||||
|
@ -20,6 +20,7 @@ from homeassistant.const import (
|
|||
CONF_USERNAME,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
|
@ -29,6 +30,7 @@ from .const import (
|
|||
ATTR_ROOM_NAME,
|
||||
ATTR_SLEEP_TEMP,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
MAX_TEMP,
|
||||
MIN_TEMP,
|
||||
SERVICE_SET_ROOM_TEMP,
|
||||
|
@ -38,10 +40,6 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
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(
|
||||
{
|
||||
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):
|
||||
"""Set up the Mill heater."""
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up the Mill climate."""
|
||||
mill_data_connection = Mill(
|
||||
config[CONF_USERNAME],
|
||||
config[CONF_PASSWORD],
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
websession=async_get_clientsession(hass),
|
||||
)
|
||||
if not await mill_data_connection.connect():
|
||||
_LOGGER.error("Failed to connect to Mill")
|
||||
return
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
await mill_data_connection.find_all_heaters()
|
||||
|
||||
|
@ -218,3 +215,19 @@ class MillHeater(ClimateEntity):
|
|||
async def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
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_ROOM_NAME = "room_name"
|
||||
ATTR_SLEEP_TEMP = "sleep_temp"
|
||||
MANUFACTURER = "Mill"
|
||||
MAX_TEMP = 35
|
||||
MIN_TEMP = 5
|
||||
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
|
||||
mficlient==0.3.0
|
||||
|
||||
# homeassistant.components.mill
|
||||
millheater==0.3.4
|
||||
|
||||
# homeassistant.components.minio
|
||||
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