1
mirror of https://github.com/home-assistant/core synced 2024-10-01 05:30:36 +02:00

Add cool mode to HomematicIP climate (#28525)

* Add cool mode to HomematicIP climate

* Update test

* remove preset_party

* Fix profile_names check
This commit is contained in:
SukramJ 2019-11-07 16:41:33 +01:00 committed by Martin Hjelmare
parent 899306c8ec
commit 9d3d35ad79
3 changed files with 293 additions and 59 deletions

View File

@ -4,13 +4,15 @@ from typing import Awaitable
from homematicip.aio.device import AsyncHeatingThermostat, AsyncHeatingThermostatCompact
from homematicip.aio.group import AsyncHeatingGroup
from homematicip.base.enums import AbsenceType, GroupType
from homematicip.base.enums import AbsenceType
from homematicip.functionalHomes import IndoorClimateHome
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_BOOST,
PRESET_ECO,
@ -30,6 +32,9 @@ COOLING_PROFILES = {"PROFILE_4": 3, "PROFILE_5": 4, "PROFILE_6": 5}
_LOGGER = logging.getLogger(__name__)
ATTR_PRESET_END_TIME = "preset_end_time"
PERMANENT_END_TIME = "permanent"
HMIP_AUTOMATIC_CM = "AUTOMATIC"
HMIP_MANUAL_CM = "MANUAL"
HMIP_ECO_CM = "ECO"
@ -55,15 +60,20 @@ async def async_setup_entry(
class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
"""Representation of a HomematicIP heating group."""
"""Representation of a HomematicIP heating group.
Heat mode is supported for all heating devices incl. their defined profiles.
Boost is available for radiator thermostats only.
Cool mode is only available for floor heating systems, if basically enabled in the hmip app.
"""
def __init__(self, hap: HomematicipHAP, device: AsyncHeatingGroup) -> None:
"""Initialize heating group."""
device.modelType = "HmIP-Heating-Group"
super().__init__(hap, device)
self._simple_heating = None
if device.actualTemperature is None:
self._simple_heating = _get_first_heating_thermostat(device)
super().__init__(hap, device)
self._simple_heating = self._get_first_radiator_thermostat()
@property
def device_info(self):
@ -105,54 +115,66 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
"""Return hvac operation ie."""
if self._disabled_by_cooling_mode:
return HVAC_MODE_OFF
if self._device.boostMode:
return HVAC_MODE_HEAT
if self._device.controlMode == HMIP_MANUAL_CM:
return HVAC_MODE_HEAT
return HVAC_MODE_HEAT if self._heat_mode_enabled else HVAC_MODE_COOL
return HVAC_MODE_AUTO
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
"""Return the list of available hvac operation modes."""
if self._disabled_by_cooling_mode:
return [HVAC_MODE_OFF]
Need to be a subset of HVAC_MODES.
"""
return [HVAC_MODE_AUTO, HVAC_MODE_HEAT]
return (
[HVAC_MODE_AUTO, HVAC_MODE_HEAT]
if self._heat_mode_enabled
else [HVAC_MODE_AUTO, HVAC_MODE_COOL]
)
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp.
Requires SUPPORT_PRESET_MODE.
"""
"""Return the current preset mode."""
if self._device.boostMode:
return PRESET_BOOST
if self.hvac_mode == HVAC_MODE_HEAT:
if self.hvac_mode in (HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF):
return PRESET_NONE
if self._device.controlMode == HMIP_ECO_CM:
absence_type = self._home.get_functionalHome(IndoorClimateHome).absenceType
if absence_type == AbsenceType.VACATION:
if self._indoor_climate.absenceType == AbsenceType.VACATION:
return PRESET_AWAY
if absence_type in [
if self._indoor_climate.absenceType in [
AbsenceType.PARTY,
AbsenceType.PERIOD,
AbsenceType.PERMANENT,
AbsenceType.PARTY,
]:
return PRESET_ECO
if self._device.activeProfile:
return self._device.activeProfile.name
return (
self._device.activeProfile.name
if self._device.activeProfile.name in self._device_profile_names
else None
)
@property
def preset_modes(self):
"""Return a list of available preset modes incl profiles."""
presets = [PRESET_NONE, PRESET_BOOST]
presets.extend(self._device_profile_names)
"""Return a list of available preset modes incl. hmip profiles."""
# Boost is only available if a radiator thermostat is in the room,
# and heat mode is enabled.
profile_names = self._device_profile_names
presets = []
if self._heat_mode_enabled and self._has_radiator_thermostat:
if not profile_names:
presets.append(PRESET_NONE)
presets.append(PRESET_BOOST)
presets.extend(profile_names)
return presets
@property
@ -170,10 +192,15 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
await self._device.set_point_temperature(temperature)
if self.min_temp <= temperature <= self.max_temp:
await self._device.set_point_temperature(temperature)
async def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
"""Set new target hvac mode."""
if hvac_mode not in self.hvac_modes:
return
if hvac_mode == HVAC_MODE_AUTO:
await self._device.set_control_mode(HMIP_AUTOMATIC_CM)
else:
@ -181,18 +208,44 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
async def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
"""Set new preset mode."""
if preset_mode not in self.preset_modes:
return
if self._device.boostMode and preset_mode != PRESET_BOOST:
await self._device.set_boost(False)
if preset_mode == PRESET_BOOST:
await self._device.set_boost()
if preset_mode in self._device_profile_names:
profile_idx = self._get_profile_idx_by_name(preset_mode)
await self.async_set_hvac_mode(HVAC_MODE_AUTO)
if self._device.controlMode != HMIP_AUTOMATIC_CM:
await self.async_set_hvac_mode(HVAC_MODE_AUTO)
await self._device.set_active_profile(profile_idx)
@property
def device_state_attributes(self):
"""Return the state attributes of the access point."""
state_attr = super().device_state_attributes
if self._device.controlMode == HMIP_ECO_CM:
if self._indoor_climate.absenceType in [
AbsenceType.PARTY,
AbsenceType.PERIOD,
AbsenceType.VACATION,
]:
state_attr[ATTR_PRESET_END_TIME] = self._indoor_climate.absenceEndTime
elif self._indoor_climate.absenceType == AbsenceType.PERMANENT:
state_attr[ATTR_PRESET_END_TIME] = PERMANENT_END_TIME
return state_attr
@property
def _indoor_climate(self):
"""Return the hmip indoor climate functional home of this group."""
return self._home.get_functionalHome(IndoorClimateHome)
@property
def _device_profiles(self):
"""Return the relevant profiles of the device."""
"""Return the relevant profiles."""
return [
profile
for profile in self._device.profiles
@ -218,17 +271,36 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
return relevant_index[index_name[0]]
@property
def _relevant_profile_group(self):
"""Return the relevant profile groups."""
return (
HEATING_PROFILES
if self._device.groupType == GroupType.HEATING
else COOLING_PROFILES
def _heat_mode_enabled(self):
"""Return, if heating mode is enabled."""
return not self._device.cooling
@property
def _disabled_by_cooling_mode(self):
"""Return, if group is disabled by the cooling mode."""
return self._device.cooling and (
self._device.coolingIgnored or not self._device.coolingAllowed
)
@property
def _relevant_profile_group(self):
"""Return the relevant profile groups."""
if self._disabled_by_cooling_mode:
return []
def _get_first_heating_thermostat(heating_group: AsyncHeatingGroup):
"""Return the first HeatingThermostat from a HeatingGroup."""
for device in heating_group.devices:
if isinstance(device, (AsyncHeatingThermostat, AsyncHeatingThermostatCompact)):
return device
return HEATING_PROFILES if self._heat_mode_enabled else COOLING_PROFILES
@property
def _has_radiator_thermostat(self) -> bool:
"""Return, if a radiator thermostat is in the hmip heating group."""
return bool(self._get_first_radiator_thermostat())
def _get_first_radiator_thermostat(self):
"""Return the first radiator thermostat from the hmip heating group."""
for device in self._device.devices:
if isinstance(
device, (AsyncHeatingThermostat, AsyncHeatingThermostatCompact)
):
return device
return None

View File

@ -10,13 +10,18 @@ from homeassistant.components.climate.const import (
ATTR_PRESET_MODE,
ATTR_PRESET_MODES,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_BOOST,
PRESET_ECO,
PRESET_NONE,
)
from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN
from homeassistant.components.homematicip_cloud.climate import (
ATTR_PRESET_END_TIME,
PERMANENT_END_TIME,
)
from homeassistant.setup import async_setup_component
from .helper import HAPID, async_manipulate_test_data, get_and_check_entity_basics
@ -33,7 +38,7 @@ async def test_manually_configured_platform(hass):
assert not hass.data.get(HMIPC_DOMAIN)
async def test_hmip_heating_group(hass, default_mock_hap):
async def test_hmip_heating_group_heat(hass, default_mock_hap):
"""Test HomematicipHeatingGroup."""
entity_id = "climate.badezimmer"
entity_name = "Badezimmer"
@ -50,12 +55,7 @@ async def test_hmip_heating_group(hass, default_mock_hap):
assert ha_state.attributes["temperature"] == 5.0
assert ha_state.attributes["current_humidity"] == 47
assert ha_state.attributes[ATTR_PRESET_MODE] == "STD"
assert ha_state.attributes[ATTR_PRESET_MODES] == [
PRESET_NONE,
PRESET_BOOST,
"STD",
"Winter",
]
assert ha_state.attributes[ATTR_PRESET_MODES] == [PRESET_BOOST, "STD", "Winter"]
service_call_counter = len(hmip_device.mock_calls)
@ -114,12 +114,12 @@ async def test_hmip_heating_group(hass, default_mock_hap):
await hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": PRESET_NONE},
{"entity_id": entity_id, "preset_mode": "STD"},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 9
assert hmip_device.mock_calls[-1][0] == "set_boost"
assert hmip_device.mock_calls[-1][1] == (False,)
assert len(hmip_device.mock_calls) == service_call_counter + 11
assert hmip_device.mock_calls[-1][0] == "set_active_profile"
assert hmip_device.mock_calls[-1][1] == (0,)
await async_manipulate_test_data(hass, hmip_device, "boostMode", False)
ha_state = hass.states.get(entity_id)
assert ha_state.attributes[ATTR_PRESET_MODE] == "STD"
@ -132,7 +132,7 @@ async def test_hmip_heating_group(hass, default_mock_hap):
blocking=True,
)
# No new service call should be in mock_calls.
assert len(hmip_device.mock_calls) == service_call_counter + 10
assert len(hmip_device.mock_calls) == service_call_counter + 12
# Only fire event from last async_manipulate_test_data available.
assert hmip_device.mock_calls[-1][0] == "fire_update_event"
@ -158,7 +158,6 @@ async def test_hmip_heating_group(hass, default_mock_hap):
ha_state = hass.states.get(entity_id)
assert ha_state.attributes[ATTR_PRESET_MODE] == PRESET_ECO
# Not required for hmip, but a posiblity to send no temperature.
await hass.services.async_call(
"climate",
"set_preset_mode",
@ -166,10 +165,173 @@ async def test_hmip_heating_group(hass, default_mock_hap):
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 16
assert len(hmip_device.mock_calls) == service_call_counter + 18
assert hmip_device.mock_calls[-1][0] == "set_active_profile"
assert hmip_device.mock_calls[-1][1] == (1,)
default_mock_hap.home.get_functionalHome(
IndoorClimateHome
).absenceType = AbsenceType.PERMANENT
await async_manipulate_test_data(hass, hmip_device, "controlMode", "ECO")
ha_state = hass.states.get(entity_id)
assert ha_state.attributes[ATTR_PRESET_END_TIME] == PERMANENT_END_TIME
await hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": entity_id, "hvac_mode": HVAC_MODE_HEAT},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 20
assert hmip_device.mock_calls[-1][0] == "set_control_mode"
assert hmip_device.mock_calls[-1][1] == ("MANUAL",)
await async_manipulate_test_data(hass, hmip_device, "controlMode", "MANUAL")
ha_state = hass.states.get(entity_id)
assert ha_state.state == HVAC_MODE_HEAT
await hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": "Winter"},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 23
assert hmip_device.mock_calls[-1][0] == "set_active_profile"
assert hmip_device.mock_calls[-1][1] == (1,)
hmip_device.activeProfile = hmip_device.profiles[0]
await async_manipulate_test_data(hass, hmip_device, "controlMode", "AUTOMATIC")
ha_state = hass.states.get(entity_id)
assert ha_state.state == HVAC_MODE_AUTO
await hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": entity_id, "hvac_mode": "dry"},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 24
# Only fire event from last async_manipulate_test_data available.
assert hmip_device.mock_calls[-1][0] == "fire_update_event"
async def test_hmip_heating_group_cool(hass, default_mock_hap):
"""Test HomematicipHeatingGroup."""
entity_id = "climate.badezimmer"
entity_name = "Badezimmer"
device_model = None
ha_state, hmip_device = get_and_check_entity_basics(
hass, default_mock_hap, entity_id, entity_name, device_model
)
hmip_device.activeProfile = hmip_device.profiles[3]
await async_manipulate_test_data(hass, hmip_device, "cooling", True)
await async_manipulate_test_data(hass, hmip_device, "coolingAllowed", True)
await async_manipulate_test_data(hass, hmip_device, "coolingIgnored", False)
ha_state = hass.states.get(entity_id)
assert ha_state.state == HVAC_MODE_AUTO
assert ha_state.attributes["current_temperature"] == 23.8
assert ha_state.attributes["min_temp"] == 5.0
assert ha_state.attributes["max_temp"] == 30.0
assert ha_state.attributes["temperature"] == 5.0
assert ha_state.attributes["current_humidity"] == 47
assert ha_state.attributes[ATTR_PRESET_MODE] == "Cool1"
assert ha_state.attributes[ATTR_PRESET_MODES] == ["Cool1", "Cool2"]
service_call_counter = len(hmip_device.mock_calls)
await hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": entity_id, "hvac_mode": HVAC_MODE_COOL},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 1
assert hmip_device.mock_calls[-1][0] == "set_control_mode"
assert hmip_device.mock_calls[-1][1] == ("MANUAL",)
await async_manipulate_test_data(hass, hmip_device, "controlMode", "MANUAL")
ha_state = hass.states.get(entity_id)
assert ha_state.state == HVAC_MODE_COOL
await hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": entity_id, "hvac_mode": HVAC_MODE_AUTO},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 3
assert hmip_device.mock_calls[-1][0] == "set_control_mode"
assert hmip_device.mock_calls[-1][1] == ("AUTOMATIC",)
await async_manipulate_test_data(hass, hmip_device, "controlMode", "AUTO")
ha_state = hass.states.get(entity_id)
assert ha_state.state == HVAC_MODE_AUTO
await hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": "Cool2"},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 6
assert hmip_device.mock_calls[-1][0] == "set_active_profile"
assert hmip_device.mock_calls[-1][1] == (4,)
hmip_device.activeProfile = hmip_device.profiles[4]
await async_manipulate_test_data(hass, hmip_device, "cooling", True)
await async_manipulate_test_data(hass, hmip_device, "coolingAllowed", False)
await async_manipulate_test_data(hass, hmip_device, "coolingIgnored", False)
ha_state = hass.states.get(entity_id)
assert ha_state.state == HVAC_MODE_OFF
assert ha_state.attributes[ATTR_PRESET_MODE] == "none"
assert ha_state.attributes[ATTR_PRESET_MODES] == []
hmip_device.activeProfile = hmip_device.profiles[4]
await async_manipulate_test_data(hass, hmip_device, "cooling", True)
await async_manipulate_test_data(hass, hmip_device, "coolingAllowed", True)
await async_manipulate_test_data(hass, hmip_device, "coolingIgnored", True)
ha_state = hass.states.get(entity_id)
assert ha_state.state == HVAC_MODE_OFF
assert ha_state.attributes[ATTR_PRESET_MODE] == "none"
assert ha_state.attributes[ATTR_PRESET_MODES] == []
await hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": "Cool2"},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 12
# fire_update_event shows that set_active_profile has not been called.
assert hmip_device.mock_calls[-1][0] == "fire_update_event"
hmip_device.activeProfile = hmip_device.profiles[4]
await async_manipulate_test_data(hass, hmip_device, "cooling", True)
await async_manipulate_test_data(hass, hmip_device, "coolingAllowed", True)
await async_manipulate_test_data(hass, hmip_device, "coolingIgnored", False)
ha_state = hass.states.get(entity_id)
assert ha_state.state == HVAC_MODE_AUTO
assert ha_state.attributes[ATTR_PRESET_MODE] == "Cool2"
assert ha_state.attributes[ATTR_PRESET_MODES] == ["Cool1", "Cool2"]
await hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": "Cool2"},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 17
assert hmip_device.mock_calls[-1][0] == "set_active_profile"
assert hmip_device.mock_calls[-1][1] == (4,)
async def test_hmip_climate_services(hass, mock_hap_with_service):
"""Test HomematicipHeatingGroup."""

View File

@ -4662,7 +4662,7 @@
"enabled": true,
"groupId": "00000000-0000-0000-0000-000000000021",
"index": "PROFILE_4",
"name": "",
"name": "Cool1",
"profileId": "00000000-0000-0000-0000-000000000041",
"visible": true
},
@ -4670,9 +4670,9 @@
"enabled": true,
"groupId": "00000000-0000-0000-0000-000000000021",
"index": "PROFILE_5",
"name": "",
"name": "Cool2",
"profileId": "00000000-0000-0000-0000-000000000042",
"visible": false
"visible": true
},
"PROFILE_6": {
"enabled": true,