From eafbf1d1fdf01b9c0f022e7ef53f765005388725 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 6 Jan 2025 11:09:08 +0100 Subject: [PATCH] Add get device capabilities action call for Sensibo (#134596) * Add get device capabilities action call for Sensibo * Tests * Mod * Fix services --------- Co-authored-by: J. Nick Koston --- homeassistant/components/sensibo/climate.py | 33 +++++++++++++++++-- homeassistant/components/sensibo/icons.json | 3 ++ .../components/sensibo/services.yaml | 18 ++++++++++ homeassistant/components/sensibo/strings.json | 13 ++++++++ .../sensibo/snapshots/test_climate.ambr | 20 +++++++++++ tests/components/sensibo/test_climate.py | 31 +++++++++++++++++ 6 files changed, 115 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index acf58a3bfbf..c289757a2f8 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant.components.climate import ( ATTR_FAN_MODE, + ATTR_HVAC_MODE, ATTR_SWING_MODE, ClimateEntity, ClimateEntityFeature, @@ -21,8 +22,8 @@ from homeassistant.const import ( PRECISION_TENTHS, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError +from homeassistant.core import HomeAssistant, SupportsResponse +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.unit_conversion import TemperatureConverter @@ -39,6 +40,7 @@ SERVICE_ENABLE_PURE_BOOST = "enable_pure_boost" SERVICE_DISABLE_PURE_BOOST = "disable_pure_boost" SERVICE_FULL_STATE = "full_state" SERVICE_ENABLE_CLIMATE_REACT = "enable_climate_react" +SERVICE_GET_DEVICE_CAPABILITIES = "get_device_capabilities" ATTR_HIGH_TEMPERATURE_THRESHOLD = "high_temperature_threshold" ATTR_HIGH_TEMPERATURE_STATE = "high_temperature_state" ATTR_LOW_TEMPERATURE_THRESHOLD = "low_temperature_threshold" @@ -172,7 +174,6 @@ async def async_setup_entry( }, "async_full_ac_state", ) - platform.async_register_entity_service( SERVICE_ENABLE_CLIMATE_REACT, { @@ -186,6 +187,12 @@ async def async_setup_entry( }, "async_enable_climate_react", ) + platform.async_register_entity_service( + SERVICE_GET_DEVICE_CAPABILITIES, + {vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)}, + "async_get_device_capabilities", + supports_response=SupportsResponse.ONLY, + ) class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): @@ -390,6 +397,26 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): assumed_state=False, ) + async def async_get_device_capabilities( + self, hvac_mode: HVACMode + ) -> dict[str, Any]: + """Get capabilities from device.""" + active_features = self.device_data.active_features + mode_capabilities: dict[str, Any] | None = self.device_data.full_capabilities[ + "modes" + ].get(hvac_mode.value) + if not mode_capabilities: + raise ServiceValidationError( + translation_domain=DOMAIN, translation_key="mode_not_exist" + ) + remote_capabilities: dict[str, Any] = {} + for active_feature in active_features: + if active_feature in mode_capabilities: + remote_capabilities[active_feature.lower()] = mode_capabilities[ + active_feature + ] + return remote_capabilities + async def async_assume_state(self, state: str) -> None: """Sync state with api.""" await self.async_send_api_call( diff --git a/homeassistant/components/sensibo/icons.json b/homeassistant/components/sensibo/icons.json index ccab3c198d2..f97f0f1e80d 100644 --- a/homeassistant/components/sensibo/icons.json +++ b/homeassistant/components/sensibo/icons.json @@ -59,6 +59,9 @@ }, "enable_climate_react": { "service": "mdi:wizard-hat" + }, + "get_device_capabilities": { + "service": "mdi:shape-outline" } } } diff --git a/homeassistant/components/sensibo/services.yaml b/homeassistant/components/sensibo/services.yaml index 7d2a644edfd..071f8c65609 100644 --- a/homeassistant/components/sensibo/services.yaml +++ b/homeassistant/components/sensibo/services.yaml @@ -159,3 +159,21 @@ enable_climate_react: - "feelslike" - "humidity" translation_key: smart_type +get_device_capabilities: + target: + entity: + integration: sensibo + domain: climate + fields: + hvac_mode: + required: true + example: "heat" + selector: + select: + options: + - "auto" + - "cool" + - "dry" + - "fan" + - "heat" + translation_key: hvac_mode diff --git a/homeassistant/components/sensibo/strings.json b/homeassistant/components/sensibo/strings.json index 4e26dbd37d3..0970ef32af5 100644 --- a/homeassistant/components/sensibo/strings.json +++ b/homeassistant/components/sensibo/strings.json @@ -494,6 +494,16 @@ "description": "Choose between temperature/feels like/humidity." } } + }, + "get_device_capabilities": { + "name": "Get device mode capabilities", + "description": "Retrieve the device capabilities for a specific device according to api requirements.", + "fields": { + "hvac_mode": { + "name": "[%key:component::climate::services::set_hvac_mode::fields::hvac_mode::name%]", + "description": "[%key:component::climate::services::set_hvac_mode::fields::hvac_mode::description%]" + } + } } }, "selector": { @@ -561,6 +571,9 @@ }, "no_data": { "message": "[%key:component::sensibo::config::error::no_devices%]" + }, + "mode_not_exist": { + "message": "The entity does not support the chosen mode" } } } diff --git a/tests/components/sensibo/snapshots/test_climate.ambr b/tests/components/sensibo/snapshots/test_climate.ambr index bc66503cf5e..71a21b6d5ab 100644 --- a/tests/components/sensibo/snapshots/test_climate.ambr +++ b/tests/components/sensibo/snapshots/test_climate.ambr @@ -227,3 +227,23 @@ 'state': 'off', }) # --- +# name: test_climate_get_device_capabilities + dict({ + 'climate.hallway': dict({ + 'horizontalswing': list([ + 'stopped', + 'fixedLeft', + 'fixedCenterLeft', + ]), + 'light': list([ + 'on', + 'off', + ]), + 'swing': list([ + 'stopped', + 'fixedTop', + 'fixedMiddleTop', + ]), + }), + }) +# --- diff --git a/tests/components/sensibo/test_climate.py b/tests/components/sensibo/test_climate.py index 089d9e107b0..607e120bd27 100644 --- a/tests/components/sensibo/test_climate.py +++ b/tests/components/sensibo/test_climate.py @@ -43,6 +43,7 @@ from homeassistant.components.sensibo.climate import ( SERVICE_ENABLE_PURE_BOOST, SERVICE_ENABLE_TIMER, SERVICE_FULL_STATE, + SERVICE_GET_DEVICE_CAPABILITIES, _find_valid_target_temp, ) from homeassistant.components.sensibo.const import DOMAIN @@ -1187,3 +1188,33 @@ async def test_climate_fan_mode_and_swing_mode_not_supported( state = hass.states.get("climate.hallway") assert state.attributes["fan_mode"] == "high" assert state.attributes["swing_mode"] == "stopped" + + +async def test_climate_get_device_capabilities( + hass: HomeAssistant, + load_int: ConfigEntry, + mock_client: MagicMock, + freezer: FrozenDateTimeFactory, + snapshot: SnapshotAssertion, +) -> None: + """Test the Sensibo climate Get device capabilitites service.""" + + response = await hass.services.async_call( + DOMAIN, + SERVICE_GET_DEVICE_CAPABILITIES, + {ATTR_ENTITY_ID: "climate.hallway", ATTR_HVAC_MODE: "heat"}, + blocking=True, + return_response=True, + ) + assert response == snapshot + + with pytest.raises( + ServiceValidationError, match="The entity does not support the chosen mode" + ): + await hass.services.async_call( + DOMAIN, + SERVICE_GET_DEVICE_CAPABILITIES, + {ATTR_ENTITY_ID: "climate.hallway", ATTR_HVAC_MODE: "heat_cool"}, + blocking=True, + return_response=True, + )