Add opentherm_gw config flow (#27148)
* Add config flow support to opentherm_gw. Bump pyotgw to 0.5b0 (required for connection testing) Existing entries in configuration.yaml will be converted to config entries and ignored in future runs. * Fix not connecting to Gateway on startup. Pylint fixes. * Add tests for config flow. Remove non-essential options from config flow. Restructure config entry data. * Make sure gw_id is slugified
This commit is contained in:
parent
2e49303401
commit
6ae908b883
17 changed files with 413 additions and 50 deletions
|
@ -464,7 +464,10 @@ omit =
|
|||
homeassistant/components/openhome/media_player.py
|
||||
homeassistant/components/opensensemap/air_quality.py
|
||||
homeassistant/components/opensky/sensor.py
|
||||
homeassistant/components/opentherm_gw/*
|
||||
homeassistant/components/opentherm_gw/__init__.py
|
||||
homeassistant/components/opentherm_gw/binary_sensor.py
|
||||
homeassistant/components/opentherm_gw/climate.py
|
||||
homeassistant/components/opentherm_gw/sensor.py
|
||||
homeassistant/components/openuv/__init__.py
|
||||
homeassistant/components/openuv/binary_sensor.py
|
||||
homeassistant/components/openuv/sensor.py
|
||||
|
|
23
homeassistant/components/opentherm_gw/.translations/en.json
Normal file
23
homeassistant/components/opentherm_gw/.translations/en.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"already_configured": "Gateway already configured",
|
||||
"id_exists": "Gateway id already exists",
|
||||
"serial_error": "Error connecting to device",
|
||||
"timeout": "Connection attempt timed out"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"device": "Path or URL",
|
||||
"floor_temperature": "Floor climate temperature",
|
||||
"id": "ID",
|
||||
"name": "Name",
|
||||
"precision": "Climate temperature precision"
|
||||
},
|
||||
"title": "OpenTherm Gateway"
|
||||
}
|
||||
},
|
||||
"title": "OpenTherm Gateway"
|
||||
}
|
||||
}
|
23
homeassistant/components/opentherm_gw/.translations/nl.json
Normal file
23
homeassistant/components/opentherm_gw/.translations/nl.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"already_configured": "Gateway is reeds geconfigureerd",
|
||||
"id_exists": "Gateway id bestaat reeds",
|
||||
"serial_error": "Kan niet verbinden met de Gateway",
|
||||
"timeout": "Time-out van de verbinding"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"device": "Pad of URL",
|
||||
"floor_temperature": "Thermostaat temperaturen naar beneden afronden",
|
||||
"id": "ID",
|
||||
"name": "Naam",
|
||||
"precision": "Thermostaat temperatuur precisie"
|
||||
},
|
||||
"title": "OpenTherm Gateway"
|
||||
}
|
||||
},
|
||||
"title": "OpenTherm Gateway"
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import pyotgw
|
|||
import pyotgw.vars as gw_vars
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.components.binary_sensor import DOMAIN as COMP_BINARY_SENSOR
|
||||
from homeassistant.components.climate import DOMAIN as COMP_CLIMATE
|
||||
from homeassistant.components.sensor import DOMAIN as COMP_SENSOR
|
||||
|
@ -16,13 +17,13 @@ from homeassistant.const import (
|
|||
ATTR_TEMPERATURE,
|
||||
ATTR_TIME,
|
||||
CONF_DEVICE,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
PRECISION_HALVES,
|
||||
PRECISION_TENTHS,
|
||||
PRECISION_WHOLE,
|
||||
)
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -36,6 +37,7 @@ from .const import (
|
|||
CONF_PRECISION,
|
||||
DATA_GATEWAYS,
|
||||
DATA_OPENTHERM_GW,
|
||||
DOMAIN,
|
||||
SERVICE_RESET_GATEWAY,
|
||||
SERVICE_SET_CLOCK,
|
||||
SERVICE_SET_CONTROL_SETPOINT,
|
||||
|
@ -50,8 +52,6 @@ from .const import (
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "opentherm_gw"
|
||||
|
||||
CLIMATE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_PRECISION): vol.In(
|
||||
|
@ -75,25 +75,38 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up the OpenTherm Gateway component."""
|
||||
if DATA_OPENTHERM_GW not in hass.data:
|
||||
hass.data[DATA_OPENTHERM_GW] = {DATA_GATEWAYS: {}}
|
||||
|
||||
gateway = OpenThermGatewayDevice(hass, config_entry)
|
||||
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] = gateway
|
||||
|
||||
# Schedule directly on the loop to avoid blocking HA startup.
|
||||
hass.loop.create_task(gateway.connect_and_subscribe())
|
||||
|
||||
for comp in [COMP_BINARY_SENSOR, COMP_CLIMATE, COMP_SENSOR]:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, comp)
|
||||
)
|
||||
|
||||
register_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the OpenTherm Gateway component."""
|
||||
if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config:
|
||||
conf = config[DOMAIN]
|
||||
hass.data[DATA_OPENTHERM_GW] = {DATA_GATEWAYS: {}}
|
||||
for gw_id, cfg in conf.items():
|
||||
gateway = OpenThermGatewayDevice(hass, gw_id, cfg)
|
||||
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][gw_id] = gateway
|
||||
for device_id, device_config in conf.items():
|
||||
device_config[CONF_ID] = device_id
|
||||
|
||||
hass.async_create_task(
|
||||
async_load_platform(hass, COMP_CLIMATE, DOMAIN, gw_id, config)
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=device_config
|
||||
)
|
||||
hass.async_create_task(
|
||||
async_load_platform(hass, COMP_BINARY_SENSOR, DOMAIN, gw_id, config)
|
||||
)
|
||||
hass.async_create_task(
|
||||
async_load_platform(hass, COMP_SENSOR, DOMAIN, gw_id, config)
|
||||
)
|
||||
# Schedule directly on the loop to avoid blocking HA startup.
|
||||
hass.loop.create_task(gateway.connect_and_subscribe(cfg[CONF_DEVICE]))
|
||||
register_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -326,20 +339,21 @@ def register_services(hass):
|
|||
class OpenThermGatewayDevice:
|
||||
"""OpenTherm Gateway device class."""
|
||||
|
||||
def __init__(self, hass, gw_id, config):
|
||||
def __init__(self, hass, config_entry):
|
||||
"""Initialize the OpenTherm Gateway."""
|
||||
self.hass = hass
|
||||
self.gw_id = gw_id
|
||||
self.name = config.get(CONF_NAME, gw_id)
|
||||
self.climate_config = config[CONF_CLIMATE]
|
||||
self.device_path = config_entry.data[CONF_DEVICE]
|
||||
self.gw_id = config_entry.data[CONF_ID]
|
||||
self.name = config_entry.data[CONF_NAME]
|
||||
self.climate_config = config_entry.options
|
||||
self.status = {}
|
||||
self.update_signal = f"{DATA_OPENTHERM_GW}_{gw_id}_update"
|
||||
self.update_signal = f"{DATA_OPENTHERM_GW}_{self.gw_id}_update"
|
||||
self.gateway = pyotgw.pyotgw()
|
||||
|
||||
async def connect_and_subscribe(self, device_path):
|
||||
async def connect_and_subscribe(self):
|
||||
"""Connect to serial device and subscribe report handler."""
|
||||
await self.gateway.connect(self.hass.loop, device_path)
|
||||
_LOGGER.debug("Connected to OpenTherm Gateway at %s", device_path)
|
||||
await self.gateway.connect(self.hass.loop, self.device_path)
|
||||
_LOGGER.debug("Connected to OpenTherm Gateway at %s", self.device_path)
|
||||
|
||||
async def cleanup(event):
|
||||
"""Reset overrides on the gateway."""
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorDevice
|
||||
from homeassistant.const import CONF_ID
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
|
@ -12,18 +13,21 @@ from .const import BINARY_SENSOR_INFO, DATA_GATEWAYS, DATA_OPENTHERM_GW
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the OpenTherm Gateway binary sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
|
||||
sensors = []
|
||||
for var, info in BINARY_SENSOR_INFO.items():
|
||||
device_class = info[0]
|
||||
friendly_name_format = info[1]
|
||||
sensors.append(
|
||||
OpenThermBinarySensor(gw_dev, var, device_class, friendly_name_format)
|
||||
OpenThermBinarySensor(
|
||||
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]],
|
||||
var,
|
||||
device_class,
|
||||
friendly_name_format,
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(sensors)
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ from homeassistant.components.climate.const import (
|
|||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_ID,
|
||||
PRECISION_HALVES,
|
||||
PRECISION_TENTHS,
|
||||
PRECISION_WHOLE,
|
||||
|
@ -33,12 +34,16 @@ _LOGGER = logging.getLogger(__name__)
|
|||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the opentherm_gw device."""
|
||||
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up an OpenTherm Gateway climate entity."""
|
||||
ents = []
|
||||
ents.append(
|
||||
OpenThermClimate(
|
||||
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]]
|
||||
)
|
||||
)
|
||||
|
||||
gateway = OpenThermClimate(gw_dev)
|
||||
async_add_entities([gateway])
|
||||
async_add_entities(ents)
|
||||
|
||||
|
||||
class OpenThermClimate(ClimateDevice):
|
||||
|
@ -48,7 +53,7 @@ class OpenThermClimate(ClimateDevice):
|
|||
"""Initialize the device."""
|
||||
self._gateway = gw_dev
|
||||
self.friendly_name = gw_dev.name
|
||||
self.floor_temp = gw_dev.climate_config[CONF_FLOOR_TEMP]
|
||||
self.floor_temp = gw_dev.climate_config.get(CONF_FLOOR_TEMP)
|
||||
self.temp_precision = gw_dev.climate_config.get(CONF_PRECISION)
|
||||
self._current_operation = None
|
||||
self._current_temperature = None
|
||||
|
@ -62,7 +67,7 @@ class OpenThermClimate(ClimateDevice):
|
|||
|
||||
async def async_added_to_hass(self):
|
||||
"""Connect to the OpenTherm Gateway device."""
|
||||
_LOGGER.debug("Added device %s", self.friendly_name)
|
||||
_LOGGER.debug("Added OpenTherm Gateway climate device %s", self.friendly_name)
|
||||
async_dispatcher_connect(
|
||||
self.hass, self._gateway.update_signal, self.receive_report
|
||||
)
|
||||
|
|
91
homeassistant/components/opentherm_gw/config_flow.py
Normal file
91
homeassistant/components/opentherm_gw/config_flow.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
"""OpenTherm Gateway config flow."""
|
||||
import asyncio
|
||||
from serial import SerialException
|
||||
|
||||
import pyotgw
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""OpenTherm Gateway Config Flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
|
||||
|
||||
async def async_step_init(self, info=None):
|
||||
"""Handle config flow initiation."""
|
||||
if info:
|
||||
name = info[CONF_NAME]
|
||||
device = info[CONF_DEVICE]
|
||||
gw_id = cv.slugify(info.get(CONF_ID, name))
|
||||
|
||||
entries = [e.data for e in self.hass.config_entries.async_entries(DOMAIN)]
|
||||
|
||||
if gw_id in [e[CONF_ID] for e in entries]:
|
||||
return self._show_form({"base": "id_exists"})
|
||||
|
||||
if device in [e[CONF_DEVICE] for e in entries]:
|
||||
return self._show_form({"base": "already_configured"})
|
||||
|
||||
async def test_connection():
|
||||
"""Try to connect to the OpenTherm Gateway."""
|
||||
otgw = pyotgw.pyotgw()
|
||||
status = await otgw.connect(self.hass.loop, device)
|
||||
await otgw.disconnect()
|
||||
return status.get(pyotgw.OTGW_ABOUT)
|
||||
|
||||
try:
|
||||
res = await asyncio.wait_for(test_connection(), timeout=10)
|
||||
except asyncio.TimeoutError:
|
||||
return self._show_form({"base": "timeout"})
|
||||
except SerialException:
|
||||
return self._show_form({"base": "serial_error"})
|
||||
|
||||
if res:
|
||||
return self._create_entry(gw_id, name, device)
|
||||
|
||||
return self._show_form()
|
||||
|
||||
async def async_step_user(self, info=None):
|
||||
"""Handle manual initiation of the config flow."""
|
||||
return await self.async_step_init(info)
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""
|
||||
Import an OpenTherm Gateway device as a config entry.
|
||||
|
||||
This flow is triggered by `async_setup` for configured devices.
|
||||
"""
|
||||
formatted_config = {
|
||||
CONF_NAME: import_config.get(CONF_NAME, import_config[CONF_ID]),
|
||||
CONF_DEVICE: import_config[CONF_DEVICE],
|
||||
CONF_ID: import_config[CONF_ID],
|
||||
}
|
||||
return await self.async_step_init(info=formatted_config)
|
||||
|
||||
def _show_form(self, errors=None):
|
||||
"""Show the config flow form with possible errors."""
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): str,
|
||||
vol.Required(CONF_DEVICE): str,
|
||||
vol.Optional(CONF_ID): str,
|
||||
}
|
||||
),
|
||||
errors=errors or {},
|
||||
)
|
||||
|
||||
def _create_entry(self, gw_id, name, device):
|
||||
"""Create entry for the OpenTherm Gateway device."""
|
||||
return self.async_create_entry(
|
||||
title=name, data={CONF_ID: gw_id, CONF_DEVICE: device, CONF_NAME: name}
|
||||
)
|
|
@ -18,6 +18,8 @@ DEVICE_CLASS_COLD = "cold"
|
|||
DEVICE_CLASS_HEAT = "heat"
|
||||
DEVICE_CLASS_PROBLEM = "problem"
|
||||
|
||||
DOMAIN = "opentherm_gw"
|
||||
|
||||
SERVICE_RESET_GATEWAY = "reset_gateway"
|
||||
SERVICE_SET_CLOCK = "set_clock"
|
||||
SERVICE_SET_CONTROL_SETPOINT = "set_control_setpoint"
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
"name": "Opentherm Gateway",
|
||||
"documentation": "https://www.home-assistant.io/integrations/opentherm_gw",
|
||||
"requirements": [
|
||||
"pyotgw==0.4b4"
|
||||
"pyotgw==0.5b0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@mvn23"
|
||||
]
|
||||
],
|
||||
"config_flow": true
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
import logging
|
||||
|
||||
from homeassistant.components.sensor import ENTITY_ID_FORMAT
|
||||
from homeassistant.const import CONF_ID
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity, async_generate_entity_id
|
||||
|
@ -12,19 +13,23 @@ from .const import DATA_GATEWAYS, DATA_OPENTHERM_GW, SENSOR_INFO
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the OpenTherm Gateway sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
|
||||
sensors = []
|
||||
for var, info in SENSOR_INFO.items():
|
||||
device_class = info[0]
|
||||
unit = info[1]
|
||||
friendly_name_format = info[2]
|
||||
sensors.append(
|
||||
OpenThermSensor(gw_dev, var, device_class, unit, friendly_name_format)
|
||||
OpenThermSensor(
|
||||
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]],
|
||||
var,
|
||||
device_class,
|
||||
unit,
|
||||
friendly_name_format,
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(sensors)
|
||||
|
||||
|
||||
|
|
23
homeassistant/components/opentherm_gw/strings.json
Normal file
23
homeassistant/components/opentherm_gw/strings.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"config": {
|
||||
"title": "OpenTherm Gateway",
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "OpenTherm Gateway",
|
||||
"data": {
|
||||
"name": "Name",
|
||||
"device": "Path or URL",
|
||||
"id": "ID",
|
||||
"precision": "Climate temperature precision",
|
||||
"floor_temperature": "Floor climate temperature"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"already_configured": "Gateway already configured",
|
||||
"id_exists": "Gateway id already exists",
|
||||
"serial_error": "Error connecting to device",
|
||||
"timeout": "Connection attempt timed out"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,6 +45,7 @@ FLOWS = [
|
|||
"mqtt",
|
||||
"nest",
|
||||
"notion",
|
||||
"opentherm_gw",
|
||||
"openuv",
|
||||
"owntracks",
|
||||
"plaato",
|
||||
|
|
|
@ -1370,7 +1370,7 @@ pyoppleio==1.0.5
|
|||
pyota==2.0.5
|
||||
|
||||
# homeassistant.components.opentherm_gw
|
||||
pyotgw==0.4b4
|
||||
pyotgw==0.5b0
|
||||
|
||||
# homeassistant.auth.mfa_modules.notify
|
||||
# homeassistant.auth.mfa_modules.totp
|
||||
|
|
|
@ -335,6 +335,9 @@ pynx584==0.4
|
|||
# homeassistant.components.openuv
|
||||
pyopenuv==1.0.9
|
||||
|
||||
# homeassistant.components.opentherm_gw
|
||||
pyotgw==0.5b0
|
||||
|
||||
# homeassistant.auth.mfa_modules.notify
|
||||
# homeassistant.auth.mfa_modules.totp
|
||||
# homeassistant.components.otp
|
||||
|
|
|
@ -138,6 +138,7 @@ TEST_REQUIREMENTS = (
|
|||
"pynws",
|
||||
"pynx584",
|
||||
"pyopenuv",
|
||||
"pyotgw",
|
||||
"pyotp",
|
||||
"pyps4-homeassistant",
|
||||
"pyqwikswitch",
|
||||
|
|
1
tests/components/opentherm_gw/__init__.py
Normal file
1
tests/components/opentherm_gw/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Tests for the Opentherm Gateway integration."""
|
163
tests/components/opentherm_gw/test_config_flow.py
Normal file
163
tests/components/opentherm_gw/test_config_flow.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
"""Test the Opentherm Gateway config flow."""
|
||||
import asyncio
|
||||
from serial import SerialException
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant import config_entries, setup
|
||||
from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME
|
||||
from homeassistant.components.opentherm_gw.const import DOMAIN
|
||||
|
||||
from pyotgw import OTGW_ABOUT
|
||||
from tests.common import mock_coro
|
||||
|
||||
|
||||
async def test_form_user(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"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.opentherm_gw.async_setup",
|
||||
return_value=mock_coro(True),
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.opentherm_gw.async_setup_entry",
|
||||
return_value=mock_coro(True),
|
||||
) as mock_setup_entry, patch(
|
||||
"pyotgw.pyotgw.connect",
|
||||
return_value=mock_coro({OTGW_ABOUT: "OpenTherm Gateway 4.2.5"}),
|
||||
) as mock_pyotgw_connect, patch(
|
||||
"pyotgw.pyotgw.disconnect", return_value=mock_coro(None)
|
||||
) as mock_pyotgw_disconnect:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"}
|
||||
)
|
||||
|
||||
assert result2["type"] == "create_entry"
|
||||
assert result2["title"] == "Test Entry 1"
|
||||
assert result2["data"] == {
|
||||
CONF_NAME: "Test Entry 1",
|
||||
CONF_DEVICE: "/dev/ttyUSB0",
|
||||
CONF_ID: "test_entry_1",
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert len(mock_pyotgw_connect.mock_calls) == 1
|
||||
assert len(mock_pyotgw_disconnect.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_import(hass):
|
||||
"""Test import from existing config."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
with patch(
|
||||
"homeassistant.components.opentherm_gw.async_setup",
|
||||
return_value=mock_coro(True),
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.opentherm_gw.async_setup_entry",
|
||||
return_value=mock_coro(True),
|
||||
) as mock_setup_entry, patch(
|
||||
"pyotgw.pyotgw.connect",
|
||||
return_value=mock_coro({OTGW_ABOUT: "OpenTherm Gateway 4.2.5"}),
|
||||
) as mock_pyotgw_connect, patch(
|
||||
"pyotgw.pyotgw.disconnect", return_value=mock_coro(None)
|
||||
) as mock_pyotgw_disconnect:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_ID: "legacy_gateway", CONF_DEVICE: "/dev/ttyUSB1"},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "legacy_gateway"
|
||||
assert result["data"] == {
|
||||
CONF_NAME: "legacy_gateway",
|
||||
CONF_DEVICE: "/dev/ttyUSB1",
|
||||
CONF_ID: "legacy_gateway",
|
||||
}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert len(mock_pyotgw_connect.mock_calls) == 1
|
||||
assert len(mock_pyotgw_disconnect.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_duplicate_entries(hass):
|
||||
"""Test duplicate device or id errors."""
|
||||
flow1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
flow2 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
flow3 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.opentherm_gw.async_setup",
|
||||
return_value=mock_coro(True),
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.opentherm_gw.async_setup_entry",
|
||||
return_value=mock_coro(True),
|
||||
) as mock_setup_entry, patch(
|
||||
"pyotgw.pyotgw.connect",
|
||||
return_value=mock_coro({OTGW_ABOUT: "OpenTherm Gateway 4.2.5"}),
|
||||
) as mock_pyotgw_connect, patch(
|
||||
"pyotgw.pyotgw.disconnect", return_value=mock_coro(None)
|
||||
) as mock_pyotgw_disconnect:
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
flow1["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"}
|
||||
)
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
flow2["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB1"}
|
||||
)
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
flow3["flow_id"], {CONF_NAME: "Test Entry 2", CONF_DEVICE: "/dev/ttyUSB0"}
|
||||
)
|
||||
assert result1["type"] == "create_entry"
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "id_exists"}
|
||||
assert result3["type"] == "form"
|
||||
assert result3["errors"] == {"base": "already_configured"}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert len(mock_pyotgw_connect.mock_calls) == 1
|
||||
assert len(mock_pyotgw_disconnect.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_connection_timeout(hass):
|
||||
"""Test we handle connection timeout."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"pyotgw.pyotgw.connect", side_effect=(asyncio.TimeoutError)
|
||||
) as mock_connect:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_NAME: "Test Entry 1", CONF_DEVICE: "socket://192.0.2.254:1234"},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "timeout"}
|
||||
assert len(mock_connect.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_connection_error(hass):
|
||||
"""Test we handle serial connection error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch("pyotgw.pyotgw.connect", side_effect=(SerialException)) as mock_connect:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"}
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "serial_error"}
|
||||
assert len(mock_connect.mock_calls) == 1
|
Loading…
Add table
Add a link
Reference in a new issue