mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add opentherm_gw options flow. (#27316)
This commit is contained in:
parent
0888098718
commit
489340160b
@ -19,5 +19,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "OpenTherm Gateway"
|
"title": "OpenTherm Gateway"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"description": "Options for the OpenTherm Gateway",
|
||||||
|
"data": {
|
||||||
|
"floor_temperature": "Floor Temperature",
|
||||||
|
"precision": "Precision"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -75,6 +75,12 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def options_updated(hass, entry):
|
||||||
|
"""Handle options update."""
|
||||||
|
gateway = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][entry.data[CONF_ID]]
|
||||||
|
async_dispatcher_send(hass, gateway.options_update_signal, entry)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry):
|
async def async_setup_entry(hass, config_entry):
|
||||||
"""Set up the OpenTherm Gateway component."""
|
"""Set up the OpenTherm Gateway component."""
|
||||||
if DATA_OPENTHERM_GW not in hass.data:
|
if DATA_OPENTHERM_GW not in hass.data:
|
||||||
@ -83,6 +89,8 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
gateway = OpenThermGatewayDevice(hass, config_entry)
|
gateway = OpenThermGatewayDevice(hass, config_entry)
|
||||||
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] = gateway
|
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] = gateway
|
||||||
|
|
||||||
|
config_entry.add_update_listener(options_updated)
|
||||||
|
|
||||||
# Schedule directly on the loop to avoid blocking HA startup.
|
# Schedule directly on the loop to avoid blocking HA startup.
|
||||||
hass.loop.create_task(gateway.connect_and_subscribe())
|
hass.loop.create_task(gateway.connect_and_subscribe())
|
||||||
|
|
||||||
@ -348,6 +356,7 @@ class OpenThermGatewayDevice:
|
|||||||
self.climate_config = config_entry.options
|
self.climate_config = config_entry.options
|
||||||
self.status = {}
|
self.status = {}
|
||||||
self.update_signal = f"{DATA_OPENTHERM_GW}_{self.gw_id}_update"
|
self.update_signal = f"{DATA_OPENTHERM_GW}_{self.gw_id}_update"
|
||||||
|
self.options_update_signal = f"{DATA_OPENTHERM_GW}_{self.gw_id}_options_update"
|
||||||
self.gateway = pyotgw.pyotgw()
|
self.gateway = pyotgw.pyotgw()
|
||||||
|
|
||||||
async def connect_and_subscribe(self):
|
async def connect_and_subscribe(self):
|
||||||
|
@ -39,7 +39,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
ents = []
|
ents = []
|
||||||
ents.append(
|
ents.append(
|
||||||
OpenThermClimate(
|
OpenThermClimate(
|
||||||
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]]
|
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]],
|
||||||
|
config_entry.options,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,12 +50,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
class OpenThermClimate(ClimateDevice):
|
class OpenThermClimate(ClimateDevice):
|
||||||
"""Representation of a climate device."""
|
"""Representation of a climate device."""
|
||||||
|
|
||||||
def __init__(self, gw_dev):
|
def __init__(self, gw_dev, options):
|
||||||
"""Initialize the device."""
|
"""Initialize the device."""
|
||||||
self._gateway = gw_dev
|
self._gateway = gw_dev
|
||||||
self.friendly_name = gw_dev.name
|
self.friendly_name = gw_dev.name
|
||||||
self.floor_temp = gw_dev.climate_config.get(CONF_FLOOR_TEMP)
|
self.floor_temp = options[CONF_FLOOR_TEMP]
|
||||||
self.temp_precision = gw_dev.climate_config.get(CONF_PRECISION)
|
self.temp_precision = options.get(CONF_PRECISION)
|
||||||
self._current_operation = None
|
self._current_operation = None
|
||||||
self._current_temperature = None
|
self._current_temperature = None
|
||||||
self._hvac_mode = HVAC_MODE_HEAT
|
self._hvac_mode = HVAC_MODE_HEAT
|
||||||
@ -65,12 +66,22 @@ class OpenThermClimate(ClimateDevice):
|
|||||||
self._away_state_a = False
|
self._away_state_a = False
|
||||||
self._away_state_b = False
|
self._away_state_b = False
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update_options(self, entry):
|
||||||
|
"""Update climate entity options."""
|
||||||
|
self.floor_temp = entry.options[CONF_FLOOR_TEMP]
|
||||||
|
self.temp_precision = entry.options.get(CONF_PRECISION)
|
||||||
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Connect to the OpenTherm Gateway device."""
|
"""Connect to the OpenTherm Gateway device."""
|
||||||
_LOGGER.debug("Added OpenTherm Gateway climate device %s", self.friendly_name)
|
_LOGGER.debug("Added OpenTherm Gateway climate device %s", self.friendly_name)
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
self.hass, self._gateway.update_signal, self.receive_report
|
self.hass, self._gateway.update_signal, self.receive_report
|
||||||
)
|
)
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass, self._gateway.options_update_signal, self.update_options
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def receive_report(self, status):
|
def receive_report(self, status):
|
||||||
|
@ -6,11 +6,20 @@ import pyotgw
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME
|
from homeassistant.const import (
|
||||||
|
CONF_DEVICE,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_NAME,
|
||||||
|
PRECISION_HALVES,
|
||||||
|
PRECISION_TENTHS,
|
||||||
|
PRECISION_WHOLE,
|
||||||
|
)
|
||||||
|
from homeassistant.core import callback
|
||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import DOMAIN
|
from . import DOMAIN
|
||||||
|
from .const import CONF_FLOOR_TEMP, CONF_PRECISION
|
||||||
|
|
||||||
|
|
||||||
class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
@ -19,6 +28,12 @@ class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
VERSION = 1
|
VERSION = 1
|
||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry):
|
||||||
|
"""Get the options flow for this handler."""
|
||||||
|
return OpenThermGwOptionsFlow(config_entry)
|
||||||
|
|
||||||
async def async_step_init(self, info=None):
|
async def async_step_init(self, info=None):
|
||||||
"""Handle config flow initiation."""
|
"""Handle config flow initiation."""
|
||||||
if info:
|
if info:
|
||||||
@ -89,3 +104,39 @@ class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=name, data={CONF_ID: gw_id, CONF_DEVICE: device, CONF_NAME: name}
|
title=name, data={CONF_ID: gw_id, CONF_DEVICE: device, CONF_NAME: name}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenThermGwOptionsFlow(config_entries.OptionsFlow):
|
||||||
|
"""Handle opentherm_gw options."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry):
|
||||||
|
"""Initialize the options flow."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Manage the opentherm_gw options."""
|
||||||
|
if user_input is not None:
|
||||||
|
if user_input.get(CONF_PRECISION) == 0:
|
||||||
|
user_input[CONF_PRECISION] = None
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(
|
||||||
|
CONF_PRECISION,
|
||||||
|
default=self.config_entry.options.get(CONF_PRECISION, 0),
|
||||||
|
): vol.All(
|
||||||
|
vol.Coerce(float),
|
||||||
|
vol.In(
|
||||||
|
[0, PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
vol.Optional(
|
||||||
|
CONF_FLOOR_TEMP,
|
||||||
|
default=self.config_entry.options.get(CONF_FLOOR_TEMP, False),
|
||||||
|
): bool,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@ -7,9 +7,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"device": "Path or URL",
|
"device": "Path or URL",
|
||||||
"id": "ID",
|
"id": "ID"
|
||||||
"precision": "Climate temperature precision",
|
|
||||||
"floor_temperature": "Floor climate temperature"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -19,5 +17,16 @@
|
|||||||
"serial_error": "Error connecting to device",
|
"serial_error": "Error connecting to device",
|
||||||
"timeout": "Connection attempt timed out"
|
"timeout": "Connection attempt timed out"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"description": "Options for the OpenTherm Gateway",
|
||||||
|
"data": {
|
||||||
|
"floor_temperature": "Floor Temperature",
|
||||||
|
"precision": "Precision"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,12 +3,16 @@ import asyncio
|
|||||||
from serial import SerialException
|
from serial import SerialException
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from homeassistant import config_entries, setup
|
from homeassistant import config_entries, data_entry_flow, setup
|
||||||
from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME
|
from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME, PRECISION_HALVES
|
||||||
from homeassistant.components.opentherm_gw.const import DOMAIN
|
from homeassistant.components.opentherm_gw.const import (
|
||||||
|
DOMAIN,
|
||||||
|
CONF_FLOOR_TEMP,
|
||||||
|
CONF_PRECISION,
|
||||||
|
)
|
||||||
|
|
||||||
from pyotgw import OTGW_ABOUT
|
from pyotgw import OTGW_ABOUT
|
||||||
from tests.common import mock_coro
|
from tests.common import mock_coro, MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
async def test_form_user(hass):
|
async def test_form_user(hass):
|
||||||
@ -161,3 +165,45 @@ async def test_form_connection_error(hass):
|
|||||||
assert result2["type"] == "form"
|
assert result2["type"] == "form"
|
||||||
assert result2["errors"] == {"base": "serial_error"}
|
assert result2["errors"] == {"base": "serial_error"}
|
||||||
assert len(mock_connect.mock_calls) == 1
|
assert len(mock_connect.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_form(hass):
|
||||||
|
"""Test the options form."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="Mock Gateway",
|
||||||
|
data={
|
||||||
|
CONF_NAME: "Mock Gateway",
|
||||||
|
CONF_DEVICE: "/dev/null",
|
||||||
|
CONF_ID: "mock_gateway",
|
||||||
|
},
|
||||||
|
options={},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.flow.async_init(
|
||||||
|
entry.entry_id, context={"source": "test"}, data=None
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={CONF_FLOOR_TEMP: True, CONF_PRECISION: PRECISION_HALVES},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["data"][CONF_PRECISION] == PRECISION_HALVES
|
||||||
|
assert result["data"][CONF_FLOOR_TEMP] is True
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.flow.async_init(
|
||||||
|
entry.entry_id, context={"source": "test"}, data=None
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.flow.async_configure(
|
||||||
|
result["flow_id"], user_input={CONF_PRECISION: 0}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["data"][CONF_PRECISION] is None
|
||||||
|
assert result["data"][CONF_FLOOR_TEMP] is True
|
||||||
|
Loading…
x
Reference in New Issue
Block a user