diff --git a/CODEOWNERS b/CODEOWNERS index b2de3031cf8..98f52070ed1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -599,6 +599,8 @@ build.json @home-assistant/supervisor /tests/components/homekit_controller/ @Jc2k @bdraco /homeassistant/components/homematic/ @pvizeli /tests/components/homematic/ @pvizeli +/homeassistant/components/homematicip_cloud/ @hahn-th +/tests/components/homematicip_cloud/ @hahn-th /homeassistant/components/homewizard/ @DCSBL /tests/components/homewizard/ @DCSBL /homeassistant/components/honeywell/ @rdfurman @mkmer diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index b0eb2a9edfa..dd89efed1c9 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -13,6 +13,7 @@ from homematicip.aio.group import AsyncHeatingGroup from homematicip.base.enums import AbsenceType from homematicip.device import Switch from homematicip.functionalHomes import IndoorClimateHome +from homematicip.group import HeatingCoolingProfile from homeassistant.components.climate import ( PRESET_AWAY, @@ -35,6 +36,14 @@ from .hap import HomematicipHAP HEATING_PROFILES = {"PROFILE_1": 0, "PROFILE_2": 1, "PROFILE_3": 2} COOLING_PROFILES = {"PROFILE_4": 3, "PROFILE_5": 4, "PROFILE_6": 5} +NICE_PROFILE_NAMES = { + "PROFILE_1": "Default", + "PROFILE_2": "Alternative 1", + "PROFILE_3": "Alternative 2", + "PROFILE_4": "Cooling 1", + "PROFILE_5": "Cooling 2", + "PROFILE_6": "Cooling 3", +} ATTR_PRESET_END_TIME = "preset_end_time" PERMANENT_END_TIME = "permanent" @@ -164,8 +173,9 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): return PRESET_ECO return ( - self._device.activeProfile.name - if self._device.activeProfile.name in self._device_profile_names + self._get_qualified_profile_name(self._device.activeProfile) + if self._get_qualified_profile_name(self._device.activeProfile) + in self._device_profile_names else None ) @@ -218,9 +228,6 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): async def async_set_preset_mode(self, preset_mode: str) -> 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: @@ -256,20 +263,30 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): return self._home.get_functionalHome(IndoorClimateHome) @property - def _device_profiles(self) -> list[Any]: + def _device_profiles(self) -> list[HeatingCoolingProfile]: """Return the relevant profiles.""" return [ profile for profile in self._device.profiles - if profile.visible - and profile.name != "" - and profile.index in self._relevant_profile_group + if profile.visible and profile.index in self._relevant_profile_group ] @property def _device_profile_names(self) -> list[str]: """Return a collection of profile names.""" - return [profile.name for profile in self._device_profiles] + return [ + self._get_qualified_profile_name(profile) + for profile in self._device_profiles + ] + + def _get_qualified_profile_name(self, profile: HeatingCoolingProfile) -> str: + """Get a name for the given profile. If exists, this is the name of the profile.""" + if profile.name != "": + return profile.name + if profile.index in NICE_PROFILE_NAMES: + return NICE_PROFILE_NAMES[profile.index] + + return profile.index def _get_profile_idx_by_name(self, profile_name: str) -> int: """Return a profile index by name.""" @@ -277,7 +294,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): index_name = [ profile.index for profile in self._device_profiles - if profile.name == profile_name + if self._get_qualified_profile_name(profile) == profile_name ] return relevant_index[index_name[0]] diff --git a/homeassistant/components/homematicip_cloud/manifest.json b/homeassistant/components/homematicip_cloud/manifest.json index 580a0f637c1..9da4e1bee05 100644 --- a/homeassistant/components/homematicip_cloud/manifest.json +++ b/homeassistant/components/homematicip_cloud/manifest.json @@ -1,7 +1,7 @@ { "domain": "homematicip_cloud", "name": "HomematicIP Cloud", - "codeowners": [], + "codeowners": ["@hahn-th"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homematicip_cloud", "iot_class": "cloud_push", diff --git a/tests/components/homematicip_cloud/fixtures/homematicip_cloud.json b/tests/components/homematicip_cloud/fixtures/homematicip_cloud.json index 83b5f8993bc..922601ca733 100644 --- a/tests/components/homematicip_cloud/fixtures/homematicip_cloud.json +++ b/tests/components/homematicip_cloud/fixtures/homematicip_cloud.json @@ -4791,6 +4791,59 @@ "type": "HEATING_THERMOSTAT", "updateState": "UP_TO_DATE" }, + "3014F71100000000ETRV0013": { + "automaticValveAdaptionNeeded": false, + "availableFirmwareVersion": "2.0.2", + "connectionType": "HMIP_RF", + "firmwareVersion": "2.0.2", + "firmwareVersionInteger": 131074, + "functionalChannels": { + "0": { + "configPending": false, + "deviceId": "3014F71100000000ETRV0013", + "dutyCycle": false, + "functionalChannelType": "DEVICE_OPERATIONLOCK", + "groupIndex": 0, + "groups": ["00000000-0000-0000-0000-000000000014"], + "index": 0, + "label": "", + "lowBat": false, + "operationLockActive": false, + "routerModuleEnabled": false, + "routerModuleSupported": false, + "rssiDeviceValue": -58, + "rssiPeerValue": -58, + "unreach": false, + "supportedOptionalFeatures": {} + }, + "1": { + "deviceId": "3014F71100000000ETRV0013", + "functionalChannelType": "HEATING_THERMOSTAT_CHANNEL", + "groupIndex": 1, + "groups": ["00000000-0000-0000-0005-000000000019"], + "index": 1, + "label": "", + "valveActualTemperature": 20.0, + "setPointTemperature": 5.0, + "temperatureOffset": 0.0, + "valvePosition": 0.0, + "valveState": "ADAPTION_DONE" + } + }, + "homeId": "00000000-0000-0000-0000-000000000001", + "id": "3014F71100000000ETRV0013", + "label": "Heizkörperthermostat4", + "lastStatusUpdate": 1524514007132, + "liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED", + "manufacturerCode": 1, + "modelId": 269, + "modelType": "HMIP-eTRV", + "oem": "eQ-3", + "permanentlyReachable": true, + "serializedGlobalTradeItemNumber": "3014F71100000000ETRV0013", + "type": "HEATING_THERMOSTAT", + "updateState": "UP_TO_DATE" + }, "3014F7110000000000000014": { "automaticValveAdaptionNeeded": false, "availableFirmwareVersion": "2.0.2", @@ -8535,6 +8588,297 @@ "windowOpenTemperature": 5.0, "windowState": null }, + "00000000-0000-0000-0005-000000000019": { + "activeProfile": "PROFILE_1", + "actualTemperature": null, + "boostDuration": 15, + "boostMode": false, + "channels": [ + { + "channelIndex": 1, + "deviceId": "3014F71100000000ETRV0013" + } + ], + "controlMode": "AUTOMATIC", + "controllable": true, + "cooling": null, + "coolingAllowed": false, + "coolingIgnored": false, + "dutyCycle": false, + "ecoAllowed": true, + "ecoIgnored": false, + "externalClockCoolingTemperature": 23.0, + "externalClockEnabled": false, + "externalClockHeatingTemperature": 19.0, + "floorHeatingMode": "FLOOR_HEATING_STANDARD", + "homeId": "00000000-0000-0000-0000-000000000001", + "humidity": null, + "humidityLimitEnabled": true, + "humidityLimitValue": 60, + "id": "00000000-0000-0000-0005-000000000019", + "label": "Vorzimmer3", + "lastSetPointReachedTimestamp": 1557767559939, + "lastSetPointUpdatedTimestamp": 1557767559939, + "lastStatusUpdate": 1524514007132, + "lowBat": false, + "maxTemperature": 30.0, + "metaGroupId": "00000000-0000-0000-0000-000000000014", + "minTemperature": 5.0, + "partyMode": false, + "profiles": { + "PROFILE_1": { + "enabled": true, + "groupId": "00000000-0000-0000-0005-000000000019", + "index": "PROFILE_1", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000058", + "visible": true + }, + "PROFILE_2": { + "enabled": true, + "groupId": "00000000-0000-0000-0005-000000000019", + "index": "PROFILE_2", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000059", + "visible": true + }, + "PROFILE_3": { + "enabled": true, + "groupId": "00000000-0000-0000-0005-000000000019", + "index": "PROFILE_3", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000060", + "visible": false + }, + "PROFILE_4": { + "enabled": false, + "groupId": "00000000-0000-0000-0005-000000000019", + "index": "PROFILE_4", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000061", + "visible": true + }, + "PROFILE_5": { + "enabled": false, + "groupId": "00000000-0000-0000-0005-000000000019", + "index": "PROFILE_5", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000062", + "visible": false + }, + "PROFILE_6": { + "enabled": false, + "groupId": "00000000-0000-0000-0005-000000000019", + "index": "PROFILE_6", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000063", + "visible": false + } + }, + "setPointTemperature": 5.0, + "type": "HEATING", + "unreach": false, + "valvePosition": 0.0, + "valveSilentModeEnabled": false, + "valveSilentModeSupported": false, + "heatingFailureSupported": true, + "windowOpenTemperature": 5.0, + "windowState": null + }, + "00000000-0000-0000-0001-000000000019": { + "activeProfile": "PROFILE_1", + "actualTemperature": null, + "boostDuration": 15, + "boostMode": false, + "channels": [ + { + "channelIndex": 1, + "deviceId": "3014F7110000000000000013" + } + ], + "controlMode": "AUTOMATIC", + "controllable": true, + "cooling": null, + "coolingAllowed": false, + "coolingIgnored": false, + "dutyCycle": false, + "ecoAllowed": true, + "ecoIgnored": false, + "externalClockCoolingTemperature": 23.0, + "externalClockEnabled": false, + "externalClockHeatingTemperature": 19.0, + "floorHeatingMode": "FLOOR_HEATING_STANDARD", + "homeId": "00000000-0000-0000-0000-000000000001", + "humidity": null, + "humidityLimitEnabled": true, + "humidityLimitValue": 60, + "id": "00000000-0000-0000-0001-000000000019", + "label": "Vorzimmer", + "lastSetPointReachedTimestamp": 1557767559939, + "lastSetPointUpdatedTimestamp": 1557767559939, + "lastStatusUpdate": 1524514007132, + "lowBat": false, + "maxTemperature": 30.0, + "metaGroupId": "00000000-0000-0000-0000-000000000014", + "minTemperature": 5.0, + "partyMode": false, + "profiles": { + "PROFILE_1": { + "enabled": true, + "groupId": "00000000-0000-0000-0001-000000000019", + "index": "PROFILE_1", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000058", + "visible": true + }, + "PROFILE_2": { + "enabled": true, + "groupId": "00000000-0000-0000-0001-000000000019", + "index": "PROFILE_2", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000059", + "visible": false + }, + "PROFILE_3": { + "enabled": true, + "groupId": "00000000-0000-0000-0001-000000000019", + "index": "PROFILE_3", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000060", + "visible": false + }, + "PROFILE_4": { + "enabled": false, + "groupId": "00000000-0000-0000-0001-000000000019", + "index": "PROFILE_4", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000061", + "visible": true + }, + "PROFILE_5": { + "enabled": false, + "groupId": "00000000-0000-0000-0001-000000000019", + "index": "PROFILE_5", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000062", + "visible": false + }, + "PROFILE_6": { + "enabled": false, + "groupId": "00000000-0000-0000-0001-000000000019", + "index": "PROFILE_6", + "name": "", + "profileId": "00000000-0000-0000-0000-000000000063", + "visible": false + } + }, + "setPointTemperature": 5.0, + "type": "HEATING", + "unreach": false, + "valvePosition": 0.0, + "valveSilentModeEnabled": false, + "valveSilentModeSupported": false, + "heatingFailureSupported": true, + "windowOpenTemperature": 5.0, + "windowState": null + }, + "00000000-0000-0001-0001-000000000019": { + "activeProfile": "PROFILE_1", + "actualTemperature": null, + "boostDuration": 15, + "boostMode": false, + "channels": [ + { + "channelIndex": 1, + "deviceId": "3014F7110000000000000013" + } + ], + "controlMode": "AUTOMATIC", + "controllable": true, + "cooling": null, + "coolingAllowed": false, + "coolingIgnored": false, + "dutyCycle": false, + "ecoAllowed": true, + "ecoIgnored": false, + "externalClockCoolingTemperature": 23.0, + "externalClockEnabled": false, + "externalClockHeatingTemperature": 19.0, + "floorHeatingMode": "FLOOR_HEATING_STANDARD", + "homeId": "00000000-0000-0000-0000-000000000001", + "humidity": null, + "humidityLimitEnabled": true, + "humidityLimitValue": 60, + "id": "00000000-0000-0001-0001-000000000019", + "label": "Vorzimmer2", + "lastSetPointReachedTimestamp": 1557767559939, + "lastSetPointUpdatedTimestamp": 1557767559939, + "lastStatusUpdate": 1524514007132, + "lowBat": false, + "maxTemperature": 30.0, + "metaGroupId": "00000000-0000-0000-0000-000000000014", + "minTemperature": 5.0, + "partyMode": false, + "profiles": { + "PROFILE_1": { + "enabled": true, + "groupId": "00000000-0000-0001-0001-000000000019", + "index": "PROFILE_1", + "name": "Testprofile", + "profileId": "00000000-0000-0000-0001-000000000058", + "visible": true + }, + "PROFILE_2": { + "enabled": true, + "groupId": "00000000-0000-0001-0001-000000000019", + "index": "PROFILE_2", + "name": "", + "profileId": "00000000-0000-0000-0001-000000000059", + "visible": true + }, + "PROFILE_3": { + "enabled": true, + "groupId": "00000000-0000-0001-0001-000000000019", + "index": "PROFILE_3", + "name": "", + "profileId": "00000000-0000-0000-0001-000000000060", + "visible": false + }, + "PROFILE_4": { + "enabled": false, + "groupId": "00000000-0000-0001-0001-000000000019", + "index": "PROFILE_4", + "name": "", + "profileId": "00000000-0000-0000-0001-000000000061", + "visible": true + }, + "PROFILE_5": { + "enabled": false, + "groupId": "00000000-0000-0001-0001-000000000019", + "index": "PROFILE_5", + "name": "", + "profileId": "00000000-0000-0000-0001-000000000062", + "visible": false + }, + "PROFILE_6": { + "enabled": false, + "groupId": "00000000-0000-0001-0001-000000000019", + "index": "PROFILE_6", + "name": "", + "profileId": "00000000-0000-0000-0001-000000000063", + "visible": false + } + }, + "setPointTemperature": 5.0, + "type": "HEATING", + "unreach": false, + "valvePosition": 0.0, + "valveSilentModeEnabled": false, + "valveSilentModeSupported": false, + "heatingFailureSupported": true, + "windowOpenTemperature": 5.0, + "windowState": null + }, "00000000-AAAA-0000-0000-000000000001": { "actualTemperature": 15.4, "channels": [ diff --git a/tests/components/homematicip_cloud/test_climate.py b/tests/components/homematicip_cloud/test_climate.py index 9ede89859dc..f175e2060df 100644 --- a/tests/components/homematicip_cloud/test_climate.py +++ b/tests/components/homematicip_cloud/test_climate.py @@ -1,6 +1,7 @@ """Tests for HomematicIP Cloud climate.""" import datetime +from unittest.mock import patch from homematicip.base.enums import AbsenceType from homematicip.functionalHomes import IndoorClimateHome @@ -15,7 +16,6 @@ from homeassistant.components.climate import ( PRESET_AWAY, PRESET_BOOST, PRESET_ECO, - PRESET_NONE, HVACAction, HVACMode, ) @@ -217,12 +217,14 @@ async def test_hmip_heating_group_heat( ha_state = hass.states.get(entity_id) assert ha_state.state == HVACMode.AUTO + # hvac mode "dry" is not available. expect a valueerror. 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" @@ -429,14 +431,95 @@ async def test_hmip_heating_group_heat_with_radiator( 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[ATTR_PRESET_MODE] is None + assert ha_state.attributes[ATTR_PRESET_MODE] == "Default" assert ha_state.attributes[ATTR_PRESET_MODES] == [ - PRESET_NONE, PRESET_BOOST, PRESET_ECO, + "Default", ] +async def test_hmip_heating_profile_default_name( + hass: HomeAssistant, default_mock_hap_factory +) -> None: + """Test visible profile 1 without a name should be displayed as 'Default'.""" + entity_id = "climate.vorzimmer3" + entity_name = "Vorzimmer3" + device_model = None + mock_hap = await default_mock_hap_factory.async_get_mock_hap( + test_devices=["Heizkörperthermostat4"], + test_groups=[entity_name], + ) + ha_state, hmip_device = get_and_check_entity_basics( + hass, mock_hap, entity_id, entity_name, device_model + ) + + assert hmip_device + assert ha_state.state == HVACMode.AUTO + assert ha_state.attributes[ATTR_PRESET_MODES] == [ + PRESET_BOOST, + PRESET_ECO, + "Default", + "Alternative 1", + ] + + +async def test_hmip_heating_profile_naming( + hass: HomeAssistant, default_mock_hap_factory +) -> None: + """Test Heating Profile Naming.""" + entity_id = "climate.vorzimmer2" + entity_name = "Vorzimmer2" + device_model = None + mock_hap = await default_mock_hap_factory.async_get_mock_hap( + test_devices=["Heizkörperthermostat2"], + test_groups=[entity_name], + ) + ha_state, hmip_device = get_and_check_entity_basics( + hass, mock_hap, entity_id, entity_name, device_model + ) + + assert hmip_device + assert ha_state.state == HVACMode.AUTO + assert ha_state.attributes[ATTR_PRESET_MODES] == [ + PRESET_BOOST, + PRESET_ECO, + "Testprofile", + "Alternative 1", + ] + + +async def test_hmip_heating_profile_name_not_in_list( + hass: HomeAssistant, default_mock_hap_factory +) -> None: + """Test set profile when profile is not in available profiles.""" + expected_profile = "Testprofile" + entity_id = "climate.vorzimmer2" + entity_name = "Vorzimmer2" + device_model = None + mock_hap = await default_mock_hap_factory.async_get_mock_hap( + test_devices=["Heizkörperthermostat2"], + test_groups=[entity_name], + ) + ha_state, hmip_device = get_and_check_entity_basics( + hass, mock_hap, entity_id, entity_name, device_model + ) + + with patch( + "homeassistant.components.homematicip_cloud.climate.NICE_PROFILE_NAMES", + return_value={}, + ): + await hass.services.async_call( + "climate", + "set_preset_mode", + {"entity_id": entity_id, "preset_mode": expected_profile}, + blocking=True, + ) + + ha_state = hass.states.get(entity_id) + assert ha_state.attributes[ATTR_PRESET_MODE] == expected_profile + + async def test_hmip_climate_services( hass: HomeAssistant, mock_hap_with_service ) -> None: diff --git a/tests/components/homematicip_cloud/test_device.py b/tests/components/homematicip_cloud/test_device.py index 9fc1f518c64..fb7fe7d7deb 100644 --- a/tests/components/homematicip_cloud/test_device.py +++ b/tests/components/homematicip_cloud/test_device.py @@ -26,7 +26,7 @@ async def test_hmip_load_all_supported_devices( test_devices=None, test_groups=None ) - assert len(mock_hap.hmip_device_by_entity_id) == 272 + assert len(mock_hap.hmip_device_by_entity_id) == 278 async def test_hmip_remove_device(