Skip to content

Add option to allow to use setpoint instead of override for legacy incomfort RF gateway #135143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/incomfort/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
type InComfortConfigEntry = ConfigEntry[InComfortDataCoordinator]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: InComfortConfigEntry) -> bool:
"""Set up a config entry."""
try:
data = await async_connect_gateway(hass, dict(entry.data))
Expand Down
15 changes: 11 additions & 4 deletions homeassistant/components/incomfort/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import InComfortConfigEntry
from .const import DOMAIN
from .const import CONF_LEGACY_SETPOINT_STATUS, DOMAIN
from .coordinator import InComfortDataCoordinator
from .entity import IncomfortEntity

Expand All @@ -32,9 +32,12 @@ async def async_setup_entry(
) -> None:
"""Set up InComfort/InTouch climate devices."""
incomfort_coordinator = entry.runtime_data
legacy_setpoint_status = entry.options.get(CONF_LEGACY_SETPOINT_STATUS, False)
heaters = incomfort_coordinator.data.heaters
async_add_entities(
InComfortClimate(incomfort_coordinator, h, r) for h in heaters for r in h.rooms
InComfortClimate(incomfort_coordinator, h, r, legacy_setpoint_status)
for h in heaters
for r in h.rooms
)


Expand All @@ -54,12 +57,14 @@ def __init__(
coordinator: InComfortDataCoordinator,
heater: InComfortHeater,
room: InComfortRoom,
legacy_setpoint_status: bool,
) -> None:
"""Initialize the climate device."""
super().__init__(coordinator)

self._heater = heater
self._room = room
self._legacy_setpoint_status = legacy_setpoint_status

self._attr_unique_id = f"{heater.serial_no}_{room.room_no}"
self._attr_device_info = DeviceInfo(
Expand Down Expand Up @@ -91,9 +96,11 @@ def target_temperature(self) -> float | None:

As we set the override, we report back the override. The actual set point is
is returned at a later time.
Some older thermostats return 0.0 as override, in that case we fallback to
the actual setpoint.
Some older thermostats do not clear the override setting in that case, in that case
we fallback to the returning actual setpoint.
"""
if self._legacy_setpoint_status:
return self._room.setpoint
return self._room.override or self._room.setpoint

async def async_set_temperature(self, **kwargs: Any) -> None:
Expand Down
57 changes: 54 additions & 3 deletions homeassistant/components/incomfort/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
"""Config flow support for Intergas InComfort integration."""

from __future__ import annotations

from typing import Any

from aiohttp import ClientResponseError
from incomfortclient import IncomfortError, InvalidHeaterList
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.selector import (
BooleanSelector,
BooleanSelectorConfig,
TextSelector,
TextSelectorConfig,
TextSelectorType,
)

from .const import DOMAIN
from .const import CONF_LEGACY_SETPOINT_STATUS, DOMAIN
from .coordinator import async_connect_gateway

TITLE = "Intergas InComfort/Intouch Lan2RF gateway"
Expand All @@ -34,6 +43,14 @@
}
)

OPTIONS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_LEGACY_SETPOINT_STATUS, default=False): BooleanSelector(
BooleanSelectorConfig()
)
}
)

ERROR_STATUS_MAPPING: dict[int, tuple[str, str]] = {
401: (CONF_PASSWORD, "auth_error"),
404: ("base", "not_found"),
Expand Down Expand Up @@ -66,6 +83,14 @@ async def async_try_connect_gateway(
class InComfortConfigFlow(ConfigFlow, domain=DOMAIN):
"""Config flow to set up an Intergas InComfort boyler and thermostats."""

@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> InComfortOptionsFlowHandler:
"""Get the options flow for this handler."""
return InComfortOptionsFlowHandler()

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
Expand All @@ -81,3 +106,29 @@ async def async_step_user(
return self.async_show_form(
step_id="user", data_schema=CONFIG_SCHEMA, errors=errors
)


class InComfortOptionsFlowHandler(OptionsFlow):
"""Handle InComfort Lan2RF gateway options."""

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the options."""
errors: dict[str, str] | None = None
if user_input is not None:
new_options: dict[str, Any] = self.config_entry.options | user_input
self.hass.config_entries.async_update_entry(
self.config_entry, options=new_options
)
self.hass.config_entries.async_schedule_reload(self.config_entry.entry_id)
return self.async_create_entry(data=new_options)

data_schema = self.add_suggested_values_to_schema(
OPTIONS_SCHEMA, self.config_entry.options
)
return self.async_show_form(
step_id="init",
data_schema=data_schema,
errors=errors,
)
2 changes: 2 additions & 0 deletions homeassistant/components/incomfort/const.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Constants for Intergas InComfort integration."""

DOMAIN = "incomfort"

CONF_LEGACY_SETPOINT_STATUS = "legacy_setpoint_status"
13 changes: 13 additions & 0 deletions homeassistant/components/incomfort/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@
"unknown": "[%key:component::incomfort::config::abort::unknown%]"
}
},
"options": {
"step": {
"init": {
"title": "Intergas InComfort Lan2RF Gateway options",
"data": {
"legacy_setpoint_status": "Legacy setpoint handling"
},
"data_description": {
"legacy_setpoint_status": "Some older gateway models with an older firmware versions might not update the thermostat setpoint and override settings correctly. Enable this option if you experience issues in updating the setpoint for your thermostat. It will use the actual setpoint of the thermostat instead of the override. As side effect is that it might take a few minutes before the setpoint is updated."
}
}
}
},
"issues": {
"deprecated_yaml_import_issue_unknown": {
"title": "YAML import failed with unknown error",
Expand Down
14 changes: 12 additions & 2 deletions tests/components/incomfort/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,22 @@ def mock_entry_data() -> dict[str, Any]:
return MOCK_CONFIG


@pytest.fixture
def mock_entry_options() -> dict[str, Any] | None:
"""Mock config entry options for fixture."""
return None


@pytest.fixture
def mock_config_entry(
hass: HomeAssistant, mock_entry_data: dict[str, Any]
hass: HomeAssistant,
mock_entry_data: dict[str, Any],
mock_entry_options: dict[str, Any],
) -> ConfigEntry:
"""Mock a config entry setup for incomfort integration."""
entry = MockConfigEntry(domain=DOMAIN, data=mock_entry_data)
entry = MockConfigEntry(
domain=DOMAIN, data=mock_entry_data, options=mock_entry_options
)
entry.add_to_hass(hass)
return entry

Expand Down
142 changes: 137 additions & 5 deletions tests/components/incomfort/snapshots/test_climate.ambr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# serializer version: 1
# name: test_setup_platform[legacy_thermostat][climate.thermostat_1-entry]
# name: test_setup_platform[legacy-override][climate.thermostat_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
Expand Down Expand Up @@ -38,7 +38,73 @@
'unit_of_measurement': None,
})
# ---
# name: test_setup_platform[legacy_thermostat][climate.thermostat_1-state]
# name: test_setup_platform[legacy-override][climate.thermostat_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.4,
'friendly_name': 'Thermostat 1',
'hvac_action': <HVACAction.IDLE: 'idle'>,
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 30.0,
'min_temp': 5.0,
'status': dict({
'override': 19.0,
'room_temp': 21.42,
'setpoint': 18.0,
}),
'supported_features': <ClimateEntityFeature: 1>,
'temperature': 18.0,
}),
'context': <ANY>,
'entity_id': 'climate.thermostat_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
})
# ---
# name: test_setup_platform[legacy-zero_override][climate.thermostat_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 30.0,
'min_temp': 5.0,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.thermostat_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'incomfort',
'previous_unique_id': None,
'supported_features': <ClimateEntityFeature: 1>,
'translation_key': None,
'unique_id': 'c0ffeec0ffee_1',
'unit_of_measurement': None,
})
# ---
# name: test_setup_platform[legacy-zero_override][climate.thermostat_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.4,
Expand All @@ -65,7 +131,7 @@
'state': 'heat',
})
# ---
# name: test_setup_platform[new_thermostat][climate.thermostat_1-entry]
# name: test_setup_platform[modern-override][climate.thermostat_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
Expand Down Expand Up @@ -104,7 +170,7 @@
'unit_of_measurement': None,
})
# ---
# name: test_setup_platform[new_thermostat][climate.thermostat_1-state]
# name: test_setup_platform[modern-override][climate.thermostat_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.4,
Expand All @@ -116,7 +182,73 @@
'max_temp': 30.0,
'min_temp': 5.0,
'status': dict({
'override': 18.0,
'override': 19.0,
'room_temp': 21.42,
'setpoint': 18.0,
}),
'supported_features': <ClimateEntityFeature: 1>,
'temperature': 19.0,
}),
'context': <ANY>,
'entity_id': 'climate.thermostat_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
})
# ---
# name: test_setup_platform[modern-zero_override][climate.thermostat_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 30.0,
'min_temp': 5.0,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.thermostat_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'incomfort',
'previous_unique_id': None,
'supported_features': <ClimateEntityFeature: 1>,
'translation_key': None,
'unique_id': 'c0ffeec0ffee_1',
'unit_of_measurement': None,
})
# ---
# name: test_setup_platform[modern-zero_override][climate.thermostat_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.4,
'friendly_name': 'Thermostat 1',
'hvac_action': <HVACAction.IDLE: 'idle'>,
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 30.0,
'min_temp': 5.0,
'status': dict({
'override': 0.0,
'room_temp': 21.42,
'setpoint': 18.0,
}),
Expand Down
Loading