Config entry and device for Coolmaster integration (#27925)

* Config entry and device for Coolmaster integration

* Lint/isort/flake/etc...

* Black formatting

* Code review fixes

* Config flow tests for coolmaster

* Add pycoolmaster requirement to test

* Remove port selection from Coolmaster config flow

* Update config_flow.py

* More idoimatic hash concat
This commit is contained in:
On Freund 2019-10-23 22:47:00 +03:00 committed by Paulus Schoutsen
parent b6fd191dc4
commit 1412862f2a
11 changed files with 265 additions and 33 deletions

View File

@ -126,7 +126,9 @@ omit =
homeassistant/components/comfoconnect/*
homeassistant/components/concord232/alarm_control_panel.py
homeassistant/components/concord232/binary_sensor.py
homeassistant/components/coolmaster/__init__.py
homeassistant/components/coolmaster/climate.py
homeassistant/components/coolmaster/const.py
homeassistant/components/cppm_tracker/device_tracker.py
homeassistant/components/cpuspeed/sensor.py
homeassistant/components/crimereports/sensor.py

View File

@ -1 +1,22 @@
"""The coolmaster component."""
"""The Coolmaster integration."""
async def async_setup(hass, config):
"""Set up Coolmaster components."""
return True
async def async_setup_entry(hass, entry):
"""Set up Coolmaster from a config entry."""
hass.async_add_job(hass.config_entries.async_forward_entry_setup(entry, "climate"))
return True
async def async_unload_entry(hass, entry):
"""Unload a Coolmaster config entry."""
await hass.async_add_job(
hass.config_entries.async_forward_entry_unload(entry, "climate")
)
return True

View File

@ -3,9 +3,8 @@
import logging
from pycoolmasternet import CoolMasterNet
import voluptuous as vol
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
HVAC_MODE_COOL,
HVAC_MODE_DRY,
@ -23,21 +22,11 @@ from homeassistant.const import (
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
import homeassistant.helpers.config_validation as cv
from .const import CONF_SUPPORTED_MODES, DOMAIN
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
DEFAULT_PORT = 10102
AVAILABLE_MODES = [
HVAC_MODE_OFF,
HVAC_MODE_HEAT,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_FAN_ONLY,
]
CM_TO_HA_STATE = {
"heat": HVAC_MODE_HEAT,
"cool": HVAC_MODE_COOL,
@ -50,17 +39,6 @@ HA_STATE_TO_CM = {value: key for key, value in CM_TO_HA_STATE.items()}
FAN_MODES = ["low", "med", "high", "auto"]
CONF_SUPPORTED_MODES = "supported_modes"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SUPPORTED_MODES, default=AVAILABLE_MODES): vol.All(
cv.ensure_list, [vol.In(AVAILABLE_MODES)]
),
}
)
_LOGGER = logging.getLogger(__name__)
@ -69,18 +47,17 @@ def _build_entity(device, supported_modes):
return CoolmasterClimate(device, supported_modes)
def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the CoolMasterNet climate platform."""
supported_modes = config.get(CONF_SUPPORTED_MODES)
host = config[CONF_HOST]
port = config[CONF_PORT]
supported_modes = config_entry.data.get(CONF_SUPPORTED_MODES)
host = config_entry.data[CONF_HOST]
port = config_entry.data[CONF_PORT]
cool = CoolMasterNet(host, port=port)
devices = cool.devices()
devices = await hass.async_add_executor_job(cool.devices)
all_devices = [_build_entity(device, supported_modes) for device in devices]
add_entities(all_devices, True)
async_add_devices(all_devices, True)
class CoolmasterClimate(ClimateDevice):
@ -118,6 +95,16 @@ class CoolmasterClimate(ClimateDevice):
else:
self._unit = TEMP_FAHRENHEIT
@property
def device_info(self):
"""Return device info for this device."""
return {
"identifiers": {(DOMAIN, self.unique_id)},
"name": self.name,
"manufacturer": "CoolAutomation",
"model": "CoolMasterNet",
}
@property
def unique_id(self):
"""Return unique ID for this device."""

View File

@ -0,0 +1,64 @@
"""Config flow to configure Coolmaster."""
from pycoolmasternet import CoolMasterNet
import voluptuous as vol
from homeassistant import core, config_entries
from homeassistant.const import CONF_HOST, CONF_PORT
# pylint: disable=unused-import
from .const import AVAILABLE_MODES, CONF_SUPPORTED_MODES, DEFAULT_PORT, DOMAIN
MODES_SCHEMA = {vol.Required(mode, default=True): bool for mode in AVAILABLE_MODES}
DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str, **MODES_SCHEMA})
async def validate_connection(hass: core.HomeAssistant, host):
"""Validate that we can connect to the Coolmaster instance."""
cool = CoolMasterNet(host, port=DEFAULT_PORT)
devices = await hass.async_add_executor_job(cool.devices)
return len(devices) > 0
class CoolmasterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a Coolmaster config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
def _async_get_entry(self, data):
supported_modes = [
key for (key, value) in data.items() if key in AVAILABLE_MODES and value
]
return self.async_create_entry(
title=data[CONF_HOST],
data={
CONF_HOST: data[CONF_HOST],
CONF_PORT: DEFAULT_PORT,
CONF_SUPPORTED_MODES: supported_modes,
},
)
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if user_input is None:
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA)
errors = {}
host = user_input[CONF_HOST]
try:
result = await validate_connection(self.hass, host)
if not result:
errors["base"] = "no_units"
except (ConnectionRefusedError, TimeoutError):
errors["base"] = "connection_error"
if errors:
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)
return self._async_get_entry(user_input)

View File

@ -0,0 +1,25 @@
"""Constants for the Coolmaster integration."""
from homeassistant.components.climate.const import (
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
)
DOMAIN = "coolmaster"
DEFAULT_PORT = 10102
CONF_SUPPORTED_MODES = "supported_modes"
AVAILABLE_MODES = [
HVAC_MODE_OFF,
HVAC_MODE_HEAT,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_FAN_ONLY,
]

View File

@ -1,6 +1,7 @@
{
"domain": "coolmaster",
"name": "Coolmaster",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/coolmaster",
"requirements": [
"pycoolmasternet==0.0.4"

View File

@ -0,0 +1,23 @@
{
"config": {
"title": "CoolMasterNet",
"step": {
"user": {
"title": "Setup your CoolMasterNet connection details.",
"data": {
"host": "Host",
"off": "Can be turned off",
"heat": "Support heat mode",
"cool": "Support cool mode",
"heat_cool": "Support automatic heat/cool mode",
"dry": "Support dry mode",
"fan_only": "Support fan only mode"
}
}
},
"error": {
"connection_error": "Failed to connect to CoolMasterNet instance. Please check your host.",
"no_units": "Could not find any HVAC units in CoolMasterNet host."
}
}
}

View File

@ -14,6 +14,7 @@ FLOWS = [
"axis",
"cast",
"cert_expiry",
"coolmaster",
"daikin",
"deconz",
"dialogflow",

View File

@ -398,6 +398,9 @@ pybotvac==0.0.17
# homeassistant.components.cast
pychromecast==4.0.1
# homeassistant.components.coolmaster
pycoolmasternet==0.0.4
# homeassistant.components.daikin
pydaikin==1.6.1

View File

@ -0,0 +1 @@
"""Tests for the Coolmaster component."""

View File

@ -0,0 +1,104 @@
"""Test the Coolmaster config flow."""
from unittest.mock import patch
from homeassistant import config_entries, setup
from homeassistant.components.coolmaster.const import DOMAIN, AVAILABLE_MODES
# from homeassistant.components.coolmaster.config_flow import validate_connection
from tests.common import mock_coro
def _flow_data():
options = {"host": "1.1.1.1"}
for mode in AVAILABLE_MODES:
options[mode] = True
return options
async def test_form(hass):
"""Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == "form"
assert result["errors"] is None
with patch(
"homeassistant.components.coolmaster.config_flow.validate_connection",
return_value=mock_coro(True),
), patch(
"homeassistant.components.coolmaster.async_setup", return_value=mock_coro(True)
) as mock_setup, patch(
"homeassistant.components.coolmaster.async_setup_entry",
return_value=mock_coro(True),
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], _flow_data()
)
assert result2["type"] == "create_entry"
assert result2["title"] == "1.1.1.1"
assert result2["data"] == {
"host": "1.1.1.1",
"port": 10102,
"supported_modes": AVAILABLE_MODES,
}
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_timeout(hass):
"""Test we handle a connection timeout."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.coolmaster.config_flow.validate_connection",
side_effect=TimeoutError(),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], _flow_data()
)
assert result2["type"] == "form"
assert result2["errors"] == {"base": "connection_error"}
async def test_form_connection_refused(hass):
"""Test we handle a connection error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.coolmaster.config_flow.validate_connection",
side_effect=ConnectionRefusedError(),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], _flow_data()
)
assert result2["type"] == "form"
assert result2["errors"] == {"base": "connection_error"}
async def test_form_no_units(hass):
"""Test we handle no units found."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.coolmaster.config_flow.validate_connection",
return_value=mock_coro(False),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], _flow_data()
)
assert result2["type"] == "form"
assert result2["errors"] == {"base": "no_units"}