mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
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:
parent
7862596ef3
commit
92b246fda9
@ -112,7 +112,12 @@ class NibeClimateEntity(CoordinatorEntity[Coordinator], ClimateEntity):
|
|||||||
|
|
||||||
self._coil_current = _get(climate.current)
|
self._coil_current = _get(climate.current)
|
||||||
self._coil_setpoint_heat = _get(climate.setpoint_heat)
|
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_prio = _get(unit.prio)
|
||||||
self._coil_mixing_valve_state = _get(climate.mixing_valve_state)
|
self._coil_mixing_valve_state = _get(climate.mixing_valve_state)
|
||||||
if climate.active_accessory is None:
|
if climate.active_accessory is None:
|
||||||
@ -147,8 +152,10 @@ class NibeClimateEntity(CoordinatorEntity[Coordinator], ClimateEntity):
|
|||||||
self._attr_hvac_mode = mode
|
self._attr_hvac_mode = mode
|
||||||
|
|
||||||
setpoint_heat = _get_float(self._coil_setpoint_heat)
|
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:
|
if mode == HVACMode.HEAT_COOL:
|
||||||
self._attr_target_temperature = None
|
self._attr_target_temperature = None
|
||||||
self._attr_target_temperature_low = setpoint_heat
|
self._attr_target_temperature_low = setpoint_heat
|
||||||
@ -207,9 +214,12 @@ class NibeClimateEntity(CoordinatorEntity[Coordinator], ClimateEntity):
|
|||||||
self._coil_setpoint_heat, temperature
|
self._coil_setpoint_heat, temperature
|
||||||
)
|
)
|
||||||
elif hvac_mode == HVACMode.COOL:
|
elif hvac_mode == HVACMode.COOL:
|
||||||
await coordinator.async_write_coil(
|
if self._coil_setpoint_cool:
|
||||||
self._coil_setpoint_cool, temperature
|
await coordinator.async_write_coil(
|
||||||
)
|
self._coil_setpoint_cool, temperature
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{hvac_mode} mode not supported for {self.name}")
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"'set_temperature' requires 'hvac_mode' when passing"
|
"'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:
|
if (temperature := kwargs.get(ATTR_TARGET_TEMP_LOW)) is not None:
|
||||||
await coordinator.async_write_coil(self._coil_setpoint_heat, temperature)
|
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)
|
await coordinator.async_write_coil(self._coil_setpoint_cool, temperature)
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
|
@ -319,6 +319,214 @@
|
|||||||
'state': 'auto',
|
'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]
|
# name: test_basic[Model.S320-s1-climate.climate_system_s1][cooling]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
|
@ -62,6 +62,7 @@ def _setup_climate_group(
|
|||||||
[
|
[
|
||||||
(Model.S320, "s1", "climate.climate_system_s1"),
|
(Model.S320, "s1", "climate.climate_system_s1"),
|
||||||
(Model.F1155, "s2", "climate.climate_system_s2"),
|
(Model.F1155, "s2", "climate.climate_system_s2"),
|
||||||
|
(Model.F730, "s1", "climate.climate_system_s1"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_basic(
|
async def test_basic(
|
||||||
@ -139,7 +140,7 @@ async def test_active_accessory(
|
|||||||
(Model.F1155, "s2", "climate.climate_system_s2"),
|
(Model.F1155, "s2", "climate.climate_system_s2"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_set_temperature(
|
async def test_set_temperature_supported_cooling(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_connection: MockConnection,
|
mock_connection: MockConnection,
|
||||||
model: Model,
|
model: Model,
|
||||||
@ -149,7 +150,7 @@ async def test_set_temperature(
|
|||||||
entity_registry_enabled_by_default: None,
|
entity_registry_enabled_by_default: None,
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test setting temperature."""
|
"""Test setting temperature for models with cooling support."""
|
||||||
climate, _ = _setup_climate_group(coils, model, climate_id)
|
climate, _ = _setup_climate_group(coils, model, climate_id)
|
||||||
|
|
||||||
await async_add_model(hass, model)
|
await async_add_model(hass, model)
|
||||||
@ -226,6 +227,62 @@ async def test_set_temperature(
|
|||||||
mock_connection.write_coil.reset_mock()
|
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(
|
@pytest.mark.parametrize(
|
||||||
("hvac_mode", "cooling_with_room_sensor", "use_room_sensor"),
|
("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.S320, "s1", "climate.climate_system_s1"),
|
||||||
(Model.F1155, "s2", "climate.climate_system_s2"),
|
(Model.F1155, "s2", "climate.climate_system_s2"),
|
||||||
|
(Model.F730, "s1", "climate.climate_system_s1"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_set_hvac_mode(
|
async def test_set_hvac_mode(
|
||||||
@ -283,10 +341,11 @@ async def test_set_hvac_mode(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("model", "climate_id", "entity_id"),
|
("model", "climate_id", "entity_id", "unsupported_mode"),
|
||||||
[
|
[
|
||||||
(Model.S320, "s1", "climate.climate_system_s1"),
|
(Model.S320, "s1", "climate.climate_system_s1", HVACMode.DRY),
|
||||||
(Model.F1155, "s2", "climate.climate_system_s2"),
|
(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(
|
async def test_set_invalid_hvac_mode(
|
||||||
@ -295,6 +354,7 @@ async def test_set_invalid_hvac_mode(
|
|||||||
model: Model,
|
model: Model,
|
||||||
climate_id: str,
|
climate_id: str,
|
||||||
entity_id: str,
|
entity_id: str,
|
||||||
|
unsupported_mode: str,
|
||||||
coils: dict[int, Any],
|
coils: dict[int, Any],
|
||||||
entity_registry_enabled_by_default: None,
|
entity_registry_enabled_by_default: None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -302,14 +362,13 @@ async def test_set_invalid_hvac_mode(
|
|||||||
_setup_climate_group(coils, model, climate_id)
|
_setup_climate_group(coils, model, climate_id)
|
||||||
|
|
||||||
await async_add_model(hass, model)
|
await async_add_model(hass, model)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
PLATFORM_DOMAIN,
|
PLATFORM_DOMAIN,
|
||||||
SERVICE_SET_HVAC_MODE,
|
SERVICE_SET_HVAC_MODE,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: entity_id,
|
ATTR_ENTITY_ID: entity_id,
|
||||||
ATTR_HVAC_MODE: HVACMode.DRY,
|
ATTR_HVAC_MODE: unsupported_mode,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user