Fix nibe_heatpump climate for models without cooling support (#114599)

* fix nibe_heatpump climate for models without cooling support

* add test for set temperature with no cooling support

* fixup use self._coil_setpoint_cool None

* fixup add new test to explicitly test unsupported cooling
This commit is contained in:
tizianodeg 2024-05-08 21:02:43 +02:00 committed by GitHub
parent 7862596ef3
commit 92b246fda9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 294 additions and 14 deletions

View File

@ -112,7 +112,12 @@ class NibeClimateEntity(CoordinatorEntity[Coordinator], ClimateEntity):
self._coil_current = _get(climate.current)
self._coil_setpoint_heat = _get(climate.setpoint_heat)
self._coil_setpoint_cool = _get(climate.setpoint_cool)
self._coil_setpoint_cool: None | Coil
try:
self._coil_setpoint_cool = _get(climate.setpoint_cool)
except CoilNotFoundException:
self._coil_setpoint_cool = None
self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.HEAT]
self._coil_prio = _get(unit.prio)
self._coil_mixing_valve_state = _get(climate.mixing_valve_state)
if climate.active_accessory is None:
@ -147,8 +152,10 @@ class NibeClimateEntity(CoordinatorEntity[Coordinator], ClimateEntity):
self._attr_hvac_mode = mode
setpoint_heat = _get_float(self._coil_setpoint_heat)
setpoint_cool = _get_float(self._coil_setpoint_cool)
if self._coil_setpoint_cool:
setpoint_cool = _get_float(self._coil_setpoint_cool)
else:
setpoint_cool = None
if mode == HVACMode.HEAT_COOL:
self._attr_target_temperature = None
self._attr_target_temperature_low = setpoint_heat
@ -207,9 +214,12 @@ class NibeClimateEntity(CoordinatorEntity[Coordinator], ClimateEntity):
self._coil_setpoint_heat, temperature
)
elif hvac_mode == HVACMode.COOL:
await coordinator.async_write_coil(
self._coil_setpoint_cool, temperature
)
if self._coil_setpoint_cool:
await coordinator.async_write_coil(
self._coil_setpoint_cool, temperature
)
else:
raise ValueError(f"{hvac_mode} mode not supported for {self.name}")
else:
raise ValueError(
"'set_temperature' requires 'hvac_mode' when passing"
@ -220,7 +230,10 @@ class NibeClimateEntity(CoordinatorEntity[Coordinator], ClimateEntity):
if (temperature := kwargs.get(ATTR_TARGET_TEMP_LOW)) is not None:
await coordinator.async_write_coil(self._coil_setpoint_heat, temperature)
if (temperature := kwargs.get(ATTR_TARGET_TEMP_HIGH)) is not None:
if (
self._coil_setpoint_cool
and (temperature := kwargs.get(ATTR_TARGET_TEMP_HIGH)) is not None
):
await coordinator.async_write_coil(self._coil_setpoint_cool, temperature)
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:

View File

@ -319,6 +319,214 @@
'state': 'auto',
})
# ---
# name: test_basic[Model.F730-s1-climate.climate_system_s1][cooling]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.5,
'friendly_name': 'Climate System S1',
'hvac_action': <HVACAction.COOLING: 'cooling'>,
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'supported_features': <ClimateEntityFeature: 3>,
'target_temp_high': None,
'target_temp_low': 21.0,
'target_temp_step': 0.5,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.climate_system_s1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat_cool',
})
# ---
# name: test_basic[Model.F730-s1-climate.climate_system_s1][heating (auto)]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.5,
'friendly_name': 'Climate System S1',
'hvac_action': <HVACAction.HEATING: 'heating'>,
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'supported_features': <ClimateEntityFeature: 3>,
'target_temp_high': None,
'target_temp_low': None,
'target_temp_step': 0.5,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.climate_system_s1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'auto',
})
# ---
# name: test_basic[Model.F730-s1-climate.climate_system_s1][heating (only)]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.5,
'friendly_name': 'Climate System S1',
'hvac_action': <HVACAction.HEATING: 'heating'>,
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'supported_features': <ClimateEntityFeature: 3>,
'target_temp_high': None,
'target_temp_low': None,
'target_temp_step': 0.5,
'temperature': 21.0,
}),
'context': <ANY>,
'entity_id': 'climate.climate_system_s1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
})
# ---
# name: test_basic[Model.F730-s1-climate.climate_system_s1][heating]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.5,
'friendly_name': 'Climate System S1',
'hvac_action': <HVACAction.HEATING: 'heating'>,
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'supported_features': <ClimateEntityFeature: 3>,
'target_temp_high': None,
'target_temp_low': 21.0,
'target_temp_step': 0.5,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.climate_system_s1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat_cool',
})
# ---
# name: test_basic[Model.F730-s1-climate.climate_system_s1][idle (mixing valve)]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.5,
'friendly_name': 'Climate System S1',
'hvac_action': <HVACAction.IDLE: 'idle'>,
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'supported_features': <ClimateEntityFeature: 3>,
'target_temp_high': None,
'target_temp_low': 21.0,
'target_temp_step': 0.5,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.climate_system_s1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat_cool',
})
# ---
# name: test_basic[Model.F730-s1-climate.climate_system_s1][initial]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.5,
'friendly_name': 'Climate System S1',
'hvac_action': <HVACAction.IDLE: 'idle'>,
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'supported_features': <ClimateEntityFeature: 3>,
'target_temp_high': None,
'target_temp_low': 21.0,
'target_temp_step': 0.5,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.climate_system_s1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat_cool',
})
# ---
# name: test_basic[Model.F730-s1-climate.climate_system_s1][off (auto)]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.5,
'friendly_name': 'Climate System S1',
'hvac_action': <HVACAction.OFF: 'off'>,
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'supported_features': <ClimateEntityFeature: 3>,
'target_temp_high': None,
'target_temp_low': None,
'target_temp_step': 0.5,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.climate_system_s1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'auto',
})
# ---
# name: test_basic[Model.F730-s1-climate.climate_system_s1][unavailable]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.5,
'friendly_name': 'Climate System S1',
'hvac_action': <HVACAction.OFF: 'off'>,
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'supported_features': <ClimateEntityFeature: 3>,
'target_temp_high': None,
'target_temp_low': None,
'target_temp_step': 0.5,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.climate_system_s1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'auto',
})
# ---
# name: test_basic[Model.S320-s1-climate.climate_system_s1][cooling]
StateSnapshot({
'attributes': ReadOnlyDict({

View File

@ -62,6 +62,7 @@ def _setup_climate_group(
[
(Model.S320, "s1", "climate.climate_system_s1"),
(Model.F1155, "s2", "climate.climate_system_s2"),
(Model.F730, "s1", "climate.climate_system_s1"),
],
)
async def test_basic(
@ -139,7 +140,7 @@ async def test_active_accessory(
(Model.F1155, "s2", "climate.climate_system_s2"),
],
)
async def test_set_temperature(
async def test_set_temperature_supported_cooling(
hass: HomeAssistant,
mock_connection: MockConnection,
model: Model,
@ -149,7 +150,7 @@ async def test_set_temperature(
entity_registry_enabled_by_default: None,
snapshot: SnapshotAssertion,
) -> None:
"""Test setting temperature."""
"""Test setting temperature for models with cooling support."""
climate, _ = _setup_climate_group(coils, model, climate_id)
await async_add_model(hass, model)
@ -226,6 +227,62 @@ async def test_set_temperature(
mock_connection.write_coil.reset_mock()
@pytest.mark.parametrize(
("model", "climate_id", "entity_id"),
[
(Model.F730, "s1", "climate.climate_system_s1"),
],
)
async def test_set_temperature_unsupported_cooling(
hass: HomeAssistant,
mock_connection: MockConnection,
model: Model,
climate_id: str,
entity_id: str,
coils: dict[int, Any],
entity_registry_enabled_by_default: None,
snapshot: SnapshotAssertion,
) -> None:
"""Test setting temperature for models that do not support cooling."""
climate, _ = _setup_climate_group(coils, model, climate_id)
await async_add_model(hass, model)
coil_setpoint_heat = mock_connection.heatpump.get_coil_by_address(
climate.setpoint_heat
)
# Set temperature to heat
await hass.services.async_call(
PLATFORM_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_TEMPERATURE: 22,
ATTR_HVAC_MODE: HVACMode.HEAT,
},
blocking=True,
)
await hass.async_block_till_done()
assert mock_connection.write_coil.mock_calls == [
call(CoilData(coil_setpoint_heat, 22))
]
# Attempt to set temperature to cool should raise ValueError
with pytest.raises(ValueError):
await hass.services.async_call(
PLATFORM_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_TEMPERATURE: 22,
ATTR_HVAC_MODE: HVACMode.COOL,
},
blocking=True,
)
mock_connection.write_coil.reset_mock()
@pytest.mark.parametrize(
("hvac_mode", "cooling_with_room_sensor", "use_room_sensor"),
[
@ -239,6 +296,7 @@ async def test_set_temperature(
[
(Model.S320, "s1", "climate.climate_system_s1"),
(Model.F1155, "s2", "climate.climate_system_s2"),
(Model.F730, "s1", "climate.climate_system_s1"),
],
)
async def test_set_hvac_mode(
@ -283,10 +341,11 @@ async def test_set_hvac_mode(
@pytest.mark.parametrize(
("model", "climate_id", "entity_id"),
("model", "climate_id", "entity_id", "unsupported_mode"),
[
(Model.S320, "s1", "climate.climate_system_s1"),
(Model.F1155, "s2", "climate.climate_system_s2"),
(Model.S320, "s1", "climate.climate_system_s1", HVACMode.DRY),
(Model.F1155, "s2", "climate.climate_system_s2", HVACMode.DRY),
(Model.F730, "s1", "climate.climate_system_s1", HVACMode.COOL),
],
)
async def test_set_invalid_hvac_mode(
@ -295,6 +354,7 @@ async def test_set_invalid_hvac_mode(
model: Model,
climate_id: str,
entity_id: str,
unsupported_mode: str,
coils: dict[int, Any],
entity_registry_enabled_by_default: None,
) -> None:
@ -302,14 +362,13 @@ async def test_set_invalid_hvac_mode(
_setup_climate_group(coils, model, climate_id)
await async_add_model(hass, model)
with pytest.raises(ValueError):
await hass.services.async_call(
PLATFORM_DOMAIN,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_HVAC_MODE: HVACMode.DRY,
ATTR_HVAC_MODE: unsupported_mode,
},
blocking=True,
)