From dcc0838cccfa1645c3b812599924f744fa618967 Mon Sep 17 00:00:00 2001 From: dajiaohuang <108231307+dajiaohuang@users.noreply.github.com> Date: Tue, 20 Oct 2026 20:53:57 +0800 Subject: [PATCH] Use the configured heating system for PID gains and options --- custom_components/sat/area.py | 2 +- custom_components/sat/climate.py | 2 +- custom_components/sat/config_flow.py | 4 +- custom_components/sat/util.py | 4 +- tests/test_heating_configuration.py | 55 ++++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 tests/test_heating_configuration.py diff --git a/custom_components/sat/area.py b/custom_components/sat/area.py index c3bca8d5..2c6e8203 100644 --- a/custom_components/sat/area.py +++ b/custom_components/sat/area.py @@ -24,7 +24,7 @@ def __init__(self, config_data: MappingProxyType[str, Any], config_options: Mapp self._hass: HomeAssistant | None = None # Create controllers with the given configuration options - self.pid: PID = create_pid_controller(config_options) + self.pid: PID = create_pid_controller(config_data, config_options) self.heating_curve: HeatingCurve = create_heating_curve_controller(config_data, config_options) @property diff --git a/custom_components/sat/climate.py b/custom_components/sat/climate.py index 932f8d8f..3bd5876e 100644 --- a/custom_components/sat/climate.py +++ b/custom_components/sat/climate.py @@ -166,7 +166,7 @@ def __init__(self, coordinator: SatDataUpdateCoordinator, config_entry: ConfigEn self._window_minimum_open_time = convert_time_str_to_seconds(config_options.get(CONF_WINDOW_MINIMUM_OPEN_TIME)) # Create a PID controller with given configuration options - self.pid = create_pid_controller(config_options) + self.pid = create_pid_controller(config_entry.data, config_options) # Create Area controllers self.areas = Areas(config_entry.data, config_options) diff --git a/custom_components/sat/config_flow.py b/custom_components/sat/config_flow.py index eb1a6c82..63e7c724 100644 --- a/custom_components/sat/config_flow.py +++ b/custom_components/sat/config_flow.py @@ -685,7 +685,7 @@ async def async_step_system_configuration(self, _user_input: dict[str, Any] | No vol.Required(CONF_SYNC_CLIMATES_WITH_MODE, default=options[CONF_SYNC_CLIMATES_WITH_MODE]): bool, } - if options.get(CONF_HEATING_SYSTEM) == HEATING_SYSTEM_HEAT_PUMP: + if self._config_entry.data.get(CONF_HEATING_SYSTEM) == HEATING_SYSTEM_HEAT_PUMP: schema[vol.Required(CONF_CYCLES_PER_HOUR, default=str(options[CONF_CYCLES_PER_HOUR]))] = selector.SelectSelector( selector.SelectSelectorConfig(mode=SelectSelectorMode.DROPDOWN, options=[ selector.SelectOptionDict(value="2", label="Normal (2x per hour)"), @@ -693,7 +693,7 @@ async def async_step_system_configuration(self, _user_input: dict[str, Any] | No ]) ) - if options.get(CONF_HEATING_SYSTEM) == HEATING_SYSTEM_RADIATORS: + if self._config_entry.data.get(CONF_HEATING_SYSTEM) == HEATING_SYSTEM_RADIATORS: schema[vol.Required(CONF_CYCLES_PER_HOUR, default=str(options[CONF_CYCLES_PER_HOUR]))] = selector.SelectSelector( selector.SelectSelectorConfig(mode=SelectSelectorMode.DROPDOWN, options=[ selector.SelectOptionDict(value="3", label="Normal (3x per hour)"), diff --git a/custom_components/sat/util.py b/custom_components/sat/util.py index f7041034..c214a65b 100644 --- a/custom_components/sat/util.py +++ b/custom_components/sat/util.py @@ -18,14 +18,14 @@ from .climate import SatClimate -def create_pid_controller(config_options) -> PID: +def create_pid_controller(config_data, config_options) -> PID: """Create and return a PID controller instance with the given configuration options.""" # Extract the configuration options kp = float(config_options.get(CONF_PROPORTIONAL)) ki = float(config_options.get(CONF_INTEGRAL)) kd = float(config_options.get(CONF_DERIVATIVE)) - heating_system = config_options.get(CONF_HEATING_SYSTEM) + heating_system = config_data.get(CONF_HEATING_SYSTEM) version = int(config_options.get(CONF_PID_CONTROLLER_VERSION)) automatic_gains = bool(config_options.get(CONF_AUTOMATIC_GAINS)) automatic_gains_value = float(config_options.get(CONF_AUTOMATIC_GAINS_VALUE)) diff --git a/tests/test_heating_configuration.py b/tests/test_heating_configuration.py new file mode 100644 index 00000000..f1b59104 --- /dev/null +++ b/tests/test_heating_configuration.py @@ -0,0 +1,55 @@ +"""Configured heating systems must reach controllers and the options form.""" +import pytest +from homeassistant.const import UnitOfTemperature +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.sat.climate import SatClimate +from custom_components.sat.config_flow import SatOptionsFlowHandler +from custom_components.sat.const import ( + DOMAIN, CONF_HEATING_SYSTEM, CONF_AUTOMATIC_GAINS, + CONF_HEATING_CURVE_COEFFICIENT, CONF_ROOMS, CONF_CYCLES_PER_HOUR, + HEATING_SYSTEM_UNDERFLOOR, HEATING_SYSTEM_RADIATORS, HEATING_SYSTEM_HEAT_PUMP, +) +from custom_components.sat.errors import Error +from custom_components.sat.fake import SatFakeCoordinator +from tests.const import DEFAULT_USER_DATA + + +@pytest.mark.parametrize("heating_system, expected_kp", [ + (HEATING_SYSTEM_UNDERFLOOR, 15.0), + (HEATING_SYSTEM_RADIATORS, 20.0), + (HEATING_SYSTEM_HEAT_PUMP, 20.0), +]) +@pytest.mark.parametrize("legacy_option", [None, HEATING_SYSTEM_RADIATORS]) +async def test_climate_and_area_pid_use_setup_system(hass, heating_system, expected_kp, legacy_option): + data = {**DEFAULT_USER_DATA, CONF_HEATING_SYSTEM: heating_system, CONF_ROOMS: ["climate.room"]} + options = {CONF_AUTOMATIC_GAINS: True, CONF_HEATING_CURVE_COEFFICIENT: 2.0} + if legacy_option is not None: + options[CONF_HEATING_SYSTEM] = legacy_option + entry = MockConfigEntry(domain=DOMAIN, data=data, options=options) + coordinator = SatFakeCoordinator(hass, entry.data, entry.options) + climate = SatClimate(coordinator, entry, UnitOfTemperature.CELSIUS) + + for pid in [climate.pid, climate.areas.pids.areas[0].pid]: + pid.update_reset(Error("climate.room", 0), 30) + assert pid.kp == expected_kp + + +@pytest.mark.parametrize("heating_system, expected_cycles", [ + (HEATING_SYSTEM_UNDERFLOOR, None), + (HEATING_SYSTEM_RADIATORS, ["3", "4"]), + (HEATING_SYSTEM_HEAT_PUMP, ["2", "3"]), +]) +@pytest.mark.parametrize("legacy_option", [None, HEATING_SYSTEM_RADIATORS]) +async def test_options_cycles_follow_setup_system(hass, heating_system, expected_cycles, legacy_option): + options = {} if legacy_option is None else {CONF_HEATING_SYSTEM: legacy_option} + entry = MockConfigEntry(domain=DOMAIN, data={**DEFAULT_USER_DATA, CONF_HEATING_SYSTEM: heating_system}, options=options) + flow = SatOptionsFlowHandler(entry) + flow.hass = hass + result = await flow.async_step_system_configuration() + cycle_selector = next((value for key, value in result["data_schema"].schema.items() if key.schema == CONF_CYCLES_PER_HOUR), None) + + if expected_cycles is None: + assert cycle_selector is None + else: + assert [item["value"] for item in cycle_selector.config["options"]] == expected_cycles