diff --git a/.coveragerc b/.coveragerc index 1f1efb96e7b..f48943388c3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1017,6 +1017,7 @@ omit = homeassistant/components/sensibo/diagnostics.py homeassistant/components/sensibo/entity.py homeassistant/components/sensibo/number.py + homeassistant/components/sensibo/select.py homeassistant/components/serial/sensor.py homeassistant/components/serial_pm/sensor.py homeassistant/components/sesame/lock.py diff --git a/homeassistant/components/sensibo/const.py b/homeassistant/components/sensibo/const.py index 683a403cb08..9558376f079 100644 --- a/homeassistant/components/sensibo/const.py +++ b/homeassistant/components/sensibo/const.py @@ -12,7 +12,7 @@ LOGGER = logging.getLogger(__package__) DEFAULT_SCAN_INTERVAL = 60 DOMAIN = "sensibo" -PLATFORMS = [Platform.CLIMATE, Platform.NUMBER] +PLATFORMS = [Platform.CLIMATE, Platform.NUMBER, Platform.SELECT] ALL = ["all"] DEFAULT_NAME = "Sensibo" TIMEOUT = 8 diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index 089cc0406bd..bceede8664e 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -91,6 +91,8 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): running = ac_states.get("on") fan_mode = ac_states.get("fanLevel") swing_mode = ac_states.get("swing") + horizontal_swing_mode = ac_states.get("horizontalSwing") + light_mode = ac_states.get("light") available = dev["connectionStatus"].get("isAlive", True) capabilities = dev["remoteCapabilities"] hvac_modes = list(capabilities["modes"]) @@ -99,6 +101,8 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): current_capabilities = capabilities["modes"][ac_states.get("mode")] fan_modes = current_capabilities.get("fanLevels") swing_modes = current_capabilities.get("swing") + horizontal_swing_modes = current_capabilities.get("horizontalSwing") + light_modes = current_capabilities.get("light") temperature_unit_key = dev.get("temperatureUnit") or ac_states.get( "temperatureUnit" ) @@ -123,6 +127,10 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): full_features.add("swing") if "fanLevels" in capabilities["modes"][mode]: full_features.add("fanLevel") + if "horizontalSwing" in capabilities["modes"][mode]: + full_features.add("horizontalSwing") + if "light" in capabilities["modes"][mode]: + full_features.add("light") state = hvac_mode if hvac_mode else "off" @@ -167,10 +175,14 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): "on": running, "fan_mode": fan_mode, "swing_mode": swing_mode, + "horizontal_swing_mode": horizontal_swing_mode, + "light_mode": light_mode, "available": available, "hvac_modes": hvac_modes, "fan_modes": fan_modes, "swing_modes": swing_modes, + "horizontal_swing_modes": horizontal_swing_modes, + "light_modes": light_modes, "temp_unit": temperature_unit_key, "temp_list": temperatures_list, "temp_step": temperature_step, diff --git a/homeassistant/components/sensibo/entity.py b/homeassistant/components/sensibo/entity.py index ff68b0ebfd1..026cca4ddff 100644 --- a/homeassistant/components/sensibo/entity.py +++ b/homeassistant/components/sensibo/entity.py @@ -41,6 +41,11 @@ class SensiboBaseEntity(CoordinatorEntity): suggested_area=device["name"], ) + @property + def device_data(self) -> dict[str, Any]: + """Return data for device.""" + return self.coordinator.data.parsed[self._device_id] + async def async_send_command( self, command: str, params: dict[str, Any] ) -> dict[str, Any]: diff --git a/homeassistant/components/sensibo/select.py b/homeassistant/components/sensibo/select.py new file mode 100644 index 00000000000..3f615f06afe --- /dev/null +++ b/homeassistant/components/sensibo/select.py @@ -0,0 +1,117 @@ +"""Number platform for Sensibo integration.""" +from __future__ import annotations + +from dataclasses import dataclass + +from homeassistant.components.select import SelectEntity, SelectEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import SensiboDataUpdateCoordinator +from .entity import SensiboBaseEntity + + +@dataclass +class SensiboSelectDescriptionMixin: + """Mixin values for Sensibo entities.""" + + remote_key: str + remote_options: str + + +@dataclass +class SensiboSelectEntityDescription( + SelectEntityDescription, SensiboSelectDescriptionMixin +): + """Class describing Sensibo Number entities.""" + + +SELECT_TYPES = ( + SensiboSelectEntityDescription( + key="horizontalSwing", + remote_key="horizontal_swing_mode", + remote_options="horizontal_swing_modes", + name="Horizontal Swing", + icon="mdi:air-conditioner", + ), + SensiboSelectEntityDescription( + key="light", + remote_key="light_mode", + remote_options="light_modes", + name="Light", + icon="mdi:flashlight", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Sensibo number platform.""" + + coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + SensiboSelect(coordinator, device_id, description) + for device_id, device_data in coordinator.data.parsed.items() + for description in SELECT_TYPES + if device_data["hvac_modes"] and description.key in device_data["full_features"] + ) + + +class SensiboSelect(SensiboBaseEntity, SelectEntity): + """Representation of a Sensibo Select.""" + + entity_description: SensiboSelectEntityDescription + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + entity_description: SensiboSelectEntityDescription, + ) -> None: + """Initiate Sensibo Select.""" + super().__init__(coordinator, device_id) + self.entity_description = entity_description + self._attr_unique_id = f"{device_id}-{entity_description.key}" + self._attr_name = ( + f"{coordinator.data.parsed[device_id]['name']} {entity_description.name}" + ) + + @property + def current_option(self) -> str | None: + """Return the current selected option.""" + return self.device_data[self.entity_description.remote_key] + + @property + def options(self) -> list[str]: + """Return possible options.""" + return self.device_data[self.entity_description.remote_options] or [] + + async def async_select_option(self, option: str) -> None: + """Set state to the selected option.""" + if self.entity_description.key not in self.device_data["active_features"]: + raise HomeAssistantError( + f"Current mode {self.device_data['hvac_mode']} doesn't support setting {self.entity_description.name}" + ) + + params = { + "name": self.entity_description.key, + "value": option, + "ac_states": self.device_data["ac_states"], + "assumed_state": False, + } + result = await self.async_send_command("set_ac_state", params) + + if result["result"]["status"] == "Success": + self.device_data[self.entity_description.remote_key] = option + self.async_write_ha_state() + return + + failure = result["result"]["failureReason"] + raise HomeAssistantError( + f"Could not set state for device {self.name} due to reason {failure}" + )