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:
Daniel Hjelseth Høyer
2021-11-19 06:44:45 +01:00
committed by GitHub
parent 406cbcfe2d
commit a3d5aec778
13 changed files with 511 additions and 63 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,
},
)

View File

@ -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

View File

@ -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"
}

View File

@ -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(

View File

@ -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."
}
}
}

View File

@ -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"
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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"}

View 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]