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 <nick@koston.org>
This commit is contained in:
G Johansson 2025-01-06 11:09:08 +01:00 committed by GitHub
parent acd95975e4
commit eafbf1d1fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 115 additions and 3 deletions

View File

@ -9,6 +9,7 @@ import voluptuous as vol
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ATTR_FAN_MODE, ATTR_FAN_MODE,
ATTR_HVAC_MODE,
ATTR_SWING_MODE, ATTR_SWING_MODE,
ClimateEntity, ClimateEntity,
ClimateEntityFeature, ClimateEntityFeature,
@ -21,8 +22,8 @@ from homeassistant.const import (
PRECISION_TENTHS, PRECISION_TENTHS,
UnitOfTemperature, UnitOfTemperature,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, SupportsResponse
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.unit_conversion import TemperatureConverter 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_DISABLE_PURE_BOOST = "disable_pure_boost"
SERVICE_FULL_STATE = "full_state" SERVICE_FULL_STATE = "full_state"
SERVICE_ENABLE_CLIMATE_REACT = "enable_climate_react" SERVICE_ENABLE_CLIMATE_REACT = "enable_climate_react"
SERVICE_GET_DEVICE_CAPABILITIES = "get_device_capabilities"
ATTR_HIGH_TEMPERATURE_THRESHOLD = "high_temperature_threshold" ATTR_HIGH_TEMPERATURE_THRESHOLD = "high_temperature_threshold"
ATTR_HIGH_TEMPERATURE_STATE = "high_temperature_state" ATTR_HIGH_TEMPERATURE_STATE = "high_temperature_state"
ATTR_LOW_TEMPERATURE_THRESHOLD = "low_temperature_threshold" ATTR_LOW_TEMPERATURE_THRESHOLD = "low_temperature_threshold"
@ -172,7 +174,6 @@ async def async_setup_entry(
}, },
"async_full_ac_state", "async_full_ac_state",
) )
platform.async_register_entity_service( platform.async_register_entity_service(
SERVICE_ENABLE_CLIMATE_REACT, SERVICE_ENABLE_CLIMATE_REACT,
{ {
@ -186,6 +187,12 @@ async def async_setup_entry(
}, },
"async_enable_climate_react", "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): class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
@ -390,6 +397,26 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
assumed_state=False, 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: async def async_assume_state(self, state: str) -> None:
"""Sync state with api.""" """Sync state with api."""
await self.async_send_api_call( await self.async_send_api_call(

View File

@ -59,6 +59,9 @@
}, },
"enable_climate_react": { "enable_climate_react": {
"service": "mdi:wizard-hat" "service": "mdi:wizard-hat"
},
"get_device_capabilities": {
"service": "mdi:shape-outline"
} }
} }
} }

View File

@ -159,3 +159,21 @@ enable_climate_react:
- "feelslike" - "feelslike"
- "humidity" - "humidity"
translation_key: smart_type 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

View File

@ -494,6 +494,16 @@
"description": "Choose between temperature/feels like/humidity." "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": { "selector": {
@ -561,6 +571,9 @@
}, },
"no_data": { "no_data": {
"message": "[%key:component::sensibo::config::error::no_devices%]" "message": "[%key:component::sensibo::config::error::no_devices%]"
},
"mode_not_exist": {
"message": "The entity does not support the chosen mode"
} }
} }
} }

View File

@ -227,3 +227,23 @@
'state': 'off', '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',
]),
}),
})
# ---

View File

@ -43,6 +43,7 @@ from homeassistant.components.sensibo.climate import (
SERVICE_ENABLE_PURE_BOOST, SERVICE_ENABLE_PURE_BOOST,
SERVICE_ENABLE_TIMER, SERVICE_ENABLE_TIMER,
SERVICE_FULL_STATE, SERVICE_FULL_STATE,
SERVICE_GET_DEVICE_CAPABILITIES,
_find_valid_target_temp, _find_valid_target_temp,
) )
from homeassistant.components.sensibo.const import DOMAIN 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") state = hass.states.get("climate.hallway")
assert state.attributes["fan_mode"] == "high" assert state.attributes["fan_mode"] == "high"
assert state.attributes["swing_mode"] == "stopped" 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,
)