From 98d20657fccb1e6672c9b00a4aaf313a59192d62 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 9 Sep 2024 10:57:04 +0000 Subject: [PATCH 1/3] Add horizontal swing support to ClimateEntity --- homeassistant/components/climate/__init__.py | 54 ++++++++++++++++++- homeassistant/components/climate/const.py | 9 ++++ homeassistant/components/climate/icons.json | 11 ++++ .../components/climate/reproduce_state.py | 10 ++++ .../components/climate/services.yaml | 15 +++++- .../components/climate/significant_change.py | 2 + homeassistant/components/climate/strings.json | 24 +++++++++ 7 files changed, 123 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 94db8008aa1..7dfcf2ce020 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -70,6 +70,7 @@ from .const import ( # noqa: F401 ATTR_MIN_TEMP, ATTR_PRESET_MODE, ATTR_PRESET_MODES, + ATTR_SWING_HORIZONTAL_MODE, ATTR_SWING_MODE, ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH, @@ -101,6 +102,7 @@ from .const import ( # noqa: F401 SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, + SERVICE_SET_SWING_HORIZONTAL_MODE, SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, SWING_BOTH, @@ -219,6 +221,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: "async_handle_set_swing_mode_service", [ClimateEntityFeature.SWING_MODE], ) + component.async_register_entity_service( + SERVICE_SET_SWING_HORIZONTAL_MODE, + {vol.Required(ATTR_SWING_HORIZONTAL_MODE): cv.string}, + "async_handle_set_swing_horizontal_mode_service", + [ClimateEntityFeature.SWING_MODE], + ) return True @@ -300,6 +308,8 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): _attr_supported_features: ClimateEntityFeature = ClimateEntityFeature(0) _attr_swing_mode: str | None _attr_swing_modes: list[str] | None + _attr_swing_horizontal_mode: str | None + _attr_swing_horizontal_modes: list[str] | None _attr_target_humidity: float | None = None _attr_target_temperature_high: float | None _attr_target_temperature_low: float | None @@ -513,6 +523,9 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if ClimateEntityFeature.SWING_MODE in supported_features: data[ATTR_SWING_MODES] = self.swing_modes + if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features: + data[ATTR_SWING_MODES] = self.swing_horizontal_modes + return data @final @@ -564,6 +577,9 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if ClimateEntityFeature.SWING_MODE in supported_features: data[ATTR_SWING_MODE] = self.swing_mode + if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features: + data[ATTR_SWING_MODE] = self.swing_horizontal_mode + if ClimateEntityFeature.AUX_HEAT in supported_features: data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF if ( @@ -691,11 +707,27 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): """ return self._attr_swing_modes + @cached_property + def swing_horizontal_mode(self) -> str | None: + """Return the horizontal swing setting. + + Requires ClimateEntityFeature.SWING_HORIZONTAL_MODE. + """ + return self._attr_swing_horizontal_mode + + @cached_property + def swing_horizontal_modes(self) -> list[str] | None: + """Return the list of available horizontal swing modes. + + Requires ClimateEntityFeature.SWING_HORIZONTAL_MODE. + """ + return self._attr_swing_horizontal_modes + @final @callback def _valid_mode_or_raise( self, - mode_type: Literal["preset", "swing", "fan", "hvac"], + mode_type: Literal["preset", "horizontal_swing", "swing", "fan", "hvac"], mode: str | HVACMode, modes: list[str] | list[HVACMode] | None, ) -> None: @@ -793,6 +825,26 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): """Set new target swing operation.""" await self.hass.async_add_executor_job(self.set_swing_mode, swing_mode) + @final + async def async_handle_set_swing_horizontal_mode_service( + self, swing_horizontal_mode: str + ) -> None: + """Validate and set new horizontal swing mode.""" + self._valid_mode_or_raise( + "horizontal_swing", swing_horizontal_mode, self.swing_horizontal_modes + ) + await self.async_set_swing_mode(swing_horizontal_mode) + + def set_swing_horizontal_mode(self, swing_mode: str) -> None: + """Set new target horizontal swing operation.""" + raise NotImplementedError + + async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None: + """Set new target horizontal swing operation.""" + await self.hass.async_add_executor_job( + self.set_swing_horizontal_mode, swing_horizontal_mode + ) + @final async def async_handle_set_preset_mode_service(self, preset_mode: str) -> None: """Validate and set new preset mode.""" diff --git a/homeassistant/components/climate/const.py b/homeassistant/components/climate/const.py index a84a2f3c628..63066e46479 100644 --- a/homeassistant/components/climate/const.py +++ b/homeassistant/components/climate/const.py @@ -92,6 +92,11 @@ SWING_BOTH = "both" SWING_VERTICAL = "vertical" SWING_HORIZONTAL = "horizontal" +# Possible horizontal swing state +SWING_ON = "on" +SWING_OFF = "off" +SWING_BOTH = "both" + class HVACAction(StrEnum): """HVAC action for climate devices.""" @@ -134,6 +139,8 @@ ATTR_HVAC_MODES = "hvac_modes" ATTR_HVAC_MODE = "hvac_mode" ATTR_SWING_MODES = "swing_modes" ATTR_SWING_MODE = "swing_mode" +ATTR_SWING_HORIZONTAL_MODE = "swing_horizontal_mode" +ATTR_SWING_HORIZONTAL_MODES = "swing_horizontal_modes" ATTR_TARGET_TEMP_HIGH = "target_temp_high" ATTR_TARGET_TEMP_LOW = "target_temp_low" ATTR_TARGET_TEMP_STEP = "target_temp_step" @@ -153,6 +160,7 @@ SERVICE_SET_PRESET_MODE = "set_preset_mode" SERVICE_SET_HUMIDITY = "set_humidity" SERVICE_SET_HVAC_MODE = "set_hvac_mode" SERVICE_SET_SWING_MODE = "set_swing_mode" +SERVICE_SET_SWING_HORIZONTAL_MODE = "set_swing_horizontal_mode" SERVICE_SET_TEMPERATURE = "set_temperature" @@ -168,6 +176,7 @@ class ClimateEntityFeature(IntFlag): AUX_HEAT = 64 TURN_OFF = 128 TURN_ON = 256 + SWING_HORIZONTAL_MODE = 512 # These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. diff --git a/homeassistant/components/climate/icons.json b/homeassistant/components/climate/icons.json index c9a8d12d01b..866c8633502 100644 --- a/homeassistant/components/climate/icons.json +++ b/homeassistant/components/climate/icons.json @@ -51,6 +51,14 @@ "on": "mdi:arrow-oscillating", "vertical": "mdi:arrow-up-down" } + }, + "swing_horizontal_mode": { + "default": "mdi:circle-medium", + "state": { + "both": "mdi:arrow-all", + "off": "mdi:arrow-oscillating-off", + "on": "mdi:arrow-expand-horizontal" + } } } } @@ -65,6 +73,9 @@ "set_swing_mode": { "service": "mdi:arrow-oscillating" }, + "set_swing_horizontal_mode": { + "service": "mdi:arrow-expand-horizontal" + }, "set_temperature": { "service": "mdi:thermometer" }, diff --git a/homeassistant/components/climate/reproduce_state.py b/homeassistant/components/climate/reproduce_state.py index 99357777fba..d38e243cb62 100644 --- a/homeassistant/components/climate/reproduce_state.py +++ b/homeassistant/components/climate/reproduce_state.py @@ -14,6 +14,7 @@ from .const import ( ATTR_HUMIDITY, ATTR_HVAC_MODE, ATTR_PRESET_MODE, + ATTR_SWING_HORIZONTAL_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -23,6 +24,7 @@ from .const import ( SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, + SERVICE_SET_SWING_HORIZONTAL_MODE, SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, ) @@ -76,6 +78,14 @@ async def _async_reproduce_states( ): await call_service(SERVICE_SET_SWING_MODE, [ATTR_SWING_MODE]) + if ( + ATTR_SWING_HORIZONTAL_MODE in state.attributes + and state.attributes[ATTR_SWING_HORIZONTAL_MODE] is not None + ): + await call_service( + SERVICE_SET_SWING_HORIZONTAL_MODE, [ATTR_SWING_HORIZONTAL_MODE] + ) + if ( ATTR_FAN_MODE in state.attributes and state.attributes[ATTR_FAN_MODE] is not None diff --git a/homeassistant/components/climate/services.yaml b/homeassistant/components/climate/services.yaml index 12a8e6f001f..68421bf2386 100644 --- a/homeassistant/components/climate/services.yaml +++ b/homeassistant/components/climate/services.yaml @@ -131,7 +131,20 @@ set_swing_mode: fields: swing_mode: required: true - example: "horizontal" + example: "on" + selector: + text: + +set_swing_horizontal_mode: + target: + entity: + domain: climate + supported_features: + - climate.ClimateEntityFeature.SWING_HORIZONTAL_MODE + fields: + swing_horizontal_mode: + required: true + example: "on" selector: text: diff --git a/homeassistant/components/climate/significant_change.py b/homeassistant/components/climate/significant_change.py index 0c4cdd4ac6a..4abd0f4f244 100644 --- a/homeassistant/components/climate/significant_change.py +++ b/homeassistant/components/climate/significant_change.py @@ -19,6 +19,7 @@ from . import ( ATTR_HUMIDITY, ATTR_HVAC_ACTION, ATTR_PRESET_MODE, + ATTR_SWING_HORIZONTAL_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -70,6 +71,7 @@ def async_check_significant_change( ATTR_HVAC_ACTION, ATTR_PRESET_MODE, ATTR_SWING_MODE, + ATTR_SWING_HORIZONTAL_MODE, ]: return True diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index 26a06821d84..e8b83ee8716 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -123,6 +123,17 @@ "swing_modes": { "name": "Swing modes" }, + "swing_horizontal_mode": { + "name": "Swing horizontal mode", + "state": { + "off": "[%key:common::state::off%]", + "on": "[%key:common::state::on%]", + "both": "Both" + } + }, + "swing_horizontal_modes": { + "name": "Swing horizontal modes" + }, "target_temp_high": { "name": "Upper target temperature" }, @@ -221,6 +232,16 @@ } } }, + "set_swing_horizontal_mode": { + "name": "Set swing horizontal mode", + "description": "Sets swing horizontal operation mode.", + "fields": { + "swing_horizontal_mode": { + "name": "Swing horizontal mode", + "description": "Swing horizontal operation mode." + } + } + }, "turn_on": { "name": "[%key:common::action::turn_on%]", "description": "Turns climate device on." @@ -264,6 +285,9 @@ "not_valid_swing_mode": { "message": "Swing mode {mode} is not valid. Valid swing modes are: {modes}." }, + "not_valid_horizontal_swing_mode": { + "message": "Horizontal swing mode {mode} is not valid. Valid horizontal swing modes are: {modes}." + }, "not_valid_fan_mode": { "message": "Fan mode {mode} is not valid. Valid fan modes are: {modes}." }, From ae6fee2867b20141a573c9329e803bd467ba25c6 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 9 Sep 2024 18:54:14 +0000 Subject: [PATCH 2/3] Fixes + tests --- homeassistant/components/climate/__init__.py | 12 +++-- homeassistant/components/climate/const.py | 5 +- homeassistant/components/climate/icons.json | 1 - .../components/climate/significant_change.py | 1 + homeassistant/components/climate/strings.json | 15 +++--- tests/components/climate/test_init.py | 47 ++++++++++++++++++- .../climate/test_reproduce_state.py | 4 ++ .../climate/test_significant_change.py | 13 +++++ 8 files changed, 80 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 7dfcf2ce020..6b1c36c6ed2 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -225,7 +225,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: SERVICE_SET_SWING_HORIZONTAL_MODE, {vol.Required(ATTR_SWING_HORIZONTAL_MODE): cv.string}, "async_handle_set_swing_horizontal_mode_service", - [ClimateEntityFeature.SWING_MODE], + [ClimateEntityFeature.SWING_HORIZONTAL_MODE], ) return True @@ -264,6 +264,8 @@ CACHED_PROPERTIES_WITH_ATTR_ = { "fan_modes", "swing_mode", "swing_modes", + "swing_horizontal_mode", + "swing_horizontal_modes", "supported_features", "min_temp", "max_temp", @@ -524,7 +526,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): data[ATTR_SWING_MODES] = self.swing_modes if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features: - data[ATTR_SWING_MODES] = self.swing_horizontal_modes + data[ATTR_SWING_HORIZONTAL_MODE] = self.swing_horizontal_modes return data @@ -578,7 +580,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): data[ATTR_SWING_MODE] = self.swing_mode if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features: - data[ATTR_SWING_MODE] = self.swing_horizontal_mode + data[ATTR_SWING_HORIZONTAL_MODE] = self.swing_horizontal_mode if ClimateEntityFeature.AUX_HEAT in supported_features: data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF @@ -833,9 +835,9 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): self._valid_mode_or_raise( "horizontal_swing", swing_horizontal_mode, self.swing_horizontal_modes ) - await self.async_set_swing_mode(swing_horizontal_mode) + await self.async_set_swing_horizontal_mode(swing_horizontal_mode) - def set_swing_horizontal_mode(self, swing_mode: str) -> None: + def set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None: """Set new target horizontal swing operation.""" raise NotImplementedError diff --git a/homeassistant/components/climate/const.py b/homeassistant/components/climate/const.py index 63066e46479..b22d5df93ba 100644 --- a/homeassistant/components/climate/const.py +++ b/homeassistant/components/climate/const.py @@ -93,9 +93,8 @@ SWING_VERTICAL = "vertical" SWING_HORIZONTAL = "horizontal" # Possible horizontal swing state -SWING_ON = "on" -SWING_OFF = "off" -SWING_BOTH = "both" +SWING_HORIZONTAL_ON = "on" +SWING_HORIZONTAL_OFF = "off" class HVACAction(StrEnum): diff --git a/homeassistant/components/climate/icons.json b/homeassistant/components/climate/icons.json index 866c8633502..8f4ffa6b19f 100644 --- a/homeassistant/components/climate/icons.json +++ b/homeassistant/components/climate/icons.json @@ -55,7 +55,6 @@ "swing_horizontal_mode": { "default": "mdi:circle-medium", "state": { - "both": "mdi:arrow-all", "off": "mdi:arrow-oscillating-off", "on": "mdi:arrow-expand-horizontal" } diff --git a/homeassistant/components/climate/significant_change.py b/homeassistant/components/climate/significant_change.py index 4abd0f4f244..2b7e2c5d8b1 100644 --- a/homeassistant/components/climate/significant_change.py +++ b/homeassistant/components/climate/significant_change.py @@ -35,6 +35,7 @@ SIGNIFICANT_ATTRIBUTES: set[str] = { ATTR_HVAC_ACTION, ATTR_PRESET_MODE, ATTR_SWING_MODE, + ATTR_SWING_HORIZONTAL_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, ATTR_TEMPERATURE, diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index e8b83ee8716..ee8a05ace15 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -124,15 +124,14 @@ "name": "Swing modes" }, "swing_horizontal_mode": { - "name": "Swing horizontal mode", + "name": "Horizontal swing mode", "state": { "off": "[%key:common::state::off%]", - "on": "[%key:common::state::on%]", - "both": "Both" + "on": "[%key:common::state::on%]" } }, "swing_horizontal_modes": { - "name": "Swing horizontal modes" + "name": "Horizontal swing modes" }, "target_temp_high": { "name": "Upper target temperature" @@ -233,12 +232,12 @@ } }, "set_swing_horizontal_mode": { - "name": "Set swing horizontal mode", - "description": "Sets swing horizontal operation mode.", + "name": "Set horizontal swing mode", + "description": "Sets horizontal swing operation mode.", "fields": { "swing_horizontal_mode": { - "name": "Swing horizontal mode", - "description": "Swing horizontal operation mode." + "name": "Horizontal swing mode", + "description": "Horizontal swing operation mode." } } }, diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index aa162e0b683..254fb26a471 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -24,6 +24,7 @@ from homeassistant.components.climate.const import ( ATTR_MAX_TEMP, ATTR_MIN_TEMP, ATTR_PRESET_MODE, + ATTR_SWING_HORIZONTAL_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -31,8 +32,11 @@ from homeassistant.components.climate.const import ( SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, + SERVICE_SET_SWING_HORIZONTAL_MODE, SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, + SWING_HORIZONTAL_OFF, + SWING_HORIZONTAL_ON, ClimateEntityFeature, ) from homeassistant.config_entries import ConfigEntry @@ -104,6 +108,7 @@ class MockClimateEntity(MockEntity, ClimateEntity): ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.SWING_MODE + | ClimateEntityFeature.SWING_HORIZONTAL_MODE ) _attr_preset_mode = "home" _attr_preset_modes = ["home", "away"] @@ -111,6 +116,8 @@ class MockClimateEntity(MockEntity, ClimateEntity): _attr_fan_modes = ["auto", "off"] _attr_swing_mode = "auto" _attr_swing_modes = ["auto", "off"] + _attr_swing_horizontal_mode = "on" + _attr_swing_horizontal_modes = [SWING_HORIZONTAL_ON, SWING_HORIZONTAL_OFF] _attr_temperature_unit = UnitOfTemperature.CELSIUS _attr_target_temperature = 20 _attr_target_temperature_high = 25 @@ -144,6 +151,10 @@ class MockClimateEntity(MockEntity, ClimateEntity): """Set swing mode.""" self._attr_swing_mode = swing_mode + def set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None: + """Set horizontal swing mode.""" + self._attr_swing_horizontal_mode = swing_horizontal_mode + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" self._attr_hvac_mode = hvac_mode @@ -194,7 +205,11 @@ def _create_tuples(enum: type[Enum], constant_prefix: str) -> list[tuple[Enum, s (enum_field, constant_prefix) for enum_field in enum if enum_field - not in [ClimateEntityFeature.TURN_ON, ClimateEntityFeature.TURN_OFF] + not in [ + ClimateEntityFeature.TURN_ON, + ClimateEntityFeature.TURN_OFF, + ClimateEntityFeature.SWING_HORIZONTAL_MODE, + ] ] @@ -339,6 +354,7 @@ async def test_mode_validation( assert state.attributes.get(ATTR_PRESET_MODE) == "home" assert state.attributes.get(ATTR_FAN_MODE) == "auto" assert state.attributes.get(ATTR_SWING_MODE) == "auto" + assert state.attributes.get(ATTR_SWING_HORIZONTAL_MODE) == "on" await hass.services.async_call( DOMAIN, @@ -358,6 +374,15 @@ async def test_mode_validation( }, blocking=True, ) + await hass.services.async_call( + DOMAIN, + SERVICE_SET_SWING_HORIZONTAL_MODE, + { + "entity_id": "climate.test", + "swing_horizontal_mode": "off", + }, + blocking=True, + ) await hass.services.async_call( DOMAIN, SERVICE_SET_FAN_MODE, @@ -371,6 +396,7 @@ async def test_mode_validation( assert state.attributes.get(ATTR_PRESET_MODE) == "away" assert state.attributes.get(ATTR_FAN_MODE) == "off" assert state.attributes.get(ATTR_SWING_MODE) == "off" + assert state.attributes.get(ATTR_SWING_HORIZONTAL_MODE) == "off" await hass.services.async_call( DOMAIN, @@ -427,6 +453,25 @@ async def test_mode_validation( ) assert exc.value.translation_key == "not_valid_swing_mode" + with pytest.raises( + ServiceValidationError, + match="Horizontal swing mode invalid is not valid. Valid horizontal swing modes are: on, off", + ) as exc: + await hass.services.async_call( + DOMAIN, + SERVICE_SET_SWING_HORIZONTAL_MODE, + { + "entity_id": "climate.test", + "swing_horizontal_mode": "invalid", + }, + blocking=True, + ) + assert ( + str(exc.value) + == "Horizontal swing mode invalid is not valid. Valid horizontal swing modes are: on, off" + ) + assert exc.value.translation_key == "not_valid_horizontal_swing_mode" + with pytest.raises( ServiceValidationError, match="Fan mode invalid is not valid. Valid fan modes are: auto, off", diff --git a/tests/components/climate/test_reproduce_state.py b/tests/components/climate/test_reproduce_state.py index 0632ebcc9e4..3bc91467f14 100644 --- a/tests/components/climate/test_reproduce_state.py +++ b/tests/components/climate/test_reproduce_state.py @@ -6,6 +6,7 @@ from homeassistant.components.climate import ( ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_PRESET_MODE, + ATTR_SWING_HORIZONTAL_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -14,6 +15,7 @@ from homeassistant.components.climate import ( SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, + SERVICE_SET_SWING_HORIZONTAL_MODE, SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, HVACMode, @@ -96,6 +98,7 @@ async def test_state_with_context(hass: HomeAssistant) -> None: [ (SERVICE_SET_PRESET_MODE, ATTR_PRESET_MODE), (SERVICE_SET_SWING_MODE, ATTR_SWING_MODE), + (SERVICE_SET_SWING_HORIZONTAL_MODE, ATTR_SWING_HORIZONTAL_MODE), (SERVICE_SET_FAN_MODE, ATTR_FAN_MODE), (SERVICE_SET_HUMIDITY, ATTR_HUMIDITY), (SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE), @@ -122,6 +125,7 @@ async def test_attribute(hass: HomeAssistant, service, attribute) -> None: [ (SERVICE_SET_PRESET_MODE, ATTR_PRESET_MODE), (SERVICE_SET_SWING_MODE, ATTR_SWING_MODE), + (SERVICE_SET_SWING_HORIZONTAL_MODE, ATTR_SWING_HORIZONTAL_MODE), (SERVICE_SET_FAN_MODE, ATTR_FAN_MODE), ], ) diff --git a/tests/components/climate/test_significant_change.py b/tests/components/climate/test_significant_change.py index f060344722a..7d709090357 100644 --- a/tests/components/climate/test_significant_change.py +++ b/tests/components/climate/test_significant_change.py @@ -10,6 +10,7 @@ from homeassistant.components.climate import ( ATTR_HUMIDITY, ATTR_HVAC_ACTION, ATTR_PRESET_MODE, + ATTR_SWING_HORIZONTAL_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -66,6 +67,18 @@ async def test_significant_state_change(hass: HomeAssistant) -> None: ), (METRIC, {ATTR_SWING_MODE: "old_value"}, {ATTR_SWING_MODE: "old_value"}, False), (METRIC, {ATTR_SWING_MODE: "old_value"}, {ATTR_SWING_MODE: "new_value"}, True), + ( + METRIC, + {ATTR_SWING_HORIZONTAL_MODE: "old_value"}, + {ATTR_SWING_HORIZONTAL_MODE: "old_value"}, + False, + ), + ( + METRIC, + {ATTR_SWING_HORIZONTAL_MODE: "old_value"}, + {ATTR_SWING_HORIZONTAL_MODE: "new_value"}, + True, + ), # multiple attributes ( METRIC, From abad0e7943ac85263ec798c1d0467dfce59b235b Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 20 Sep 2024 20:52:54 +0000 Subject: [PATCH 3/3] Fixes --- homeassistant/components/climate/__init__.py | 3 ++- homeassistant/components/demo/climate.py | 23 ++++++++++++++++++++ homeassistant/components/demo/icons.json | 7 ++++++ homeassistant/components/demo/strings.json | 7 ++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 6b1c36c6ed2..eaf1f356019 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -71,6 +71,7 @@ from .const import ( # noqa: F401 ATTR_PRESET_MODE, ATTR_PRESET_MODES, ATTR_SWING_HORIZONTAL_MODE, + ATTR_SWING_HORIZONTAL_MODES, ATTR_SWING_MODE, ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH, @@ -526,7 +527,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): data[ATTR_SWING_MODES] = self.swing_modes if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features: - data[ATTR_SWING_HORIZONTAL_MODE] = self.swing_horizontal_modes + data[ATTR_SWING_HORIZONTAL_MODES] = self.swing_horizontal_modes return data diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py index ff0ed5746ca..5424591f021 100644 --- a/homeassistant/components/demo/climate.py +++ b/homeassistant/components/demo/climate.py @@ -43,6 +43,7 @@ async def async_setup_entry( target_humidity=None, current_humidity=None, swing_mode=None, + swing_horizontal_mode=None, hvac_mode=HVACMode.HEAT, hvac_action=HVACAction.HEATING, target_temp_high=None, @@ -60,6 +61,7 @@ async def async_setup_entry( target_humidity=67.4, current_humidity=54.2, swing_mode="off", + swing_horizontal_mode="auto", hvac_mode=HVACMode.COOL, hvac_action=HVACAction.COOLING, target_temp_high=None, @@ -78,6 +80,7 @@ async def async_setup_entry( target_humidity=None, current_humidity=None, swing_mode="auto", + swing_horizontal_mode=None, hvac_mode=HVACMode.HEAT_COOL, hvac_action=None, target_temp_high=24, @@ -109,6 +112,7 @@ class DemoClimate(ClimateEntity): target_humidity: float | None, current_humidity: float | None, swing_mode: str | None, + swing_horizontal_mode: str | None, hvac_mode: HVACMode, hvac_action: HVACAction | None, target_temp_high: float | None, @@ -129,6 +133,8 @@ class DemoClimate(ClimateEntity): self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY if swing_mode is not None: self._attr_supported_features |= ClimateEntityFeature.SWING_MODE + if swing_horizontal_mode is not None: + self._attr_supported_features |= ClimateEntityFeature.SWING_HORIZONTAL_MODE if HVACMode.HEAT_COOL in hvac_modes or HVACMode.AUTO in hvac_modes: self._attr_supported_features |= ( ClimateEntityFeature.TARGET_TEMPERATURE_RANGE @@ -147,9 +153,11 @@ class DemoClimate(ClimateEntity): self._hvac_action = hvac_action self._hvac_mode = hvac_mode self._current_swing_mode = swing_mode + self._current_swing_horizontal_mode = swing_horizontal_mode self._fan_modes = ["on_low", "on_high", "auto_low", "auto_high", "off"] self._hvac_modes = hvac_modes self._swing_modes = ["auto", "1", "2", "3", "off"] + self._swing_horizontal_modes = ["auto", "rangefull", "off"] self._target_temperature_high = target_temp_high self._target_temperature_low = target_temp_low self._attr_device_info = DeviceInfo( @@ -242,6 +250,16 @@ class DemoClimate(ClimateEntity): """List of available swing modes.""" return self._swing_modes + @property + def swing_horizontal_mode(self) -> str | None: + """Return the swing setting.""" + return self._current_swing_horizontal_mode + + @property + def swing_horizontal_modes(self) -> list[str]: + """List of available swing modes.""" + return self._swing_horizontal_modes + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperatures.""" if kwargs.get(ATTR_TEMPERATURE) is not None: @@ -266,6 +284,11 @@ class DemoClimate(ClimateEntity): self._current_swing_mode = swing_mode self.async_write_ha_state() + async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None: + """Set new swing mode.""" + self._current_swing_horizontal_mode = swing_horizontal_mode + self.async_write_ha_state() + async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new fan mode.""" self._current_fan_mode = fan_mode diff --git a/homeassistant/components/demo/icons.json b/homeassistant/components/demo/icons.json index 17425a6d119..eafcbb9161a 100644 --- a/homeassistant/components/demo/icons.json +++ b/homeassistant/components/demo/icons.json @@ -19,6 +19,13 @@ "auto": "mdi:arrow-oscillating", "off": "mdi:arrow-oscillating-off" } + }, + "swing_horizontal_mode": { + "state": { + "rangefull": "mdi:pan-horizontal", + "auto": "mdi:compare-horizontal", + "off": "mdi:arrow-oscillating-off" + } } } } diff --git a/homeassistant/components/demo/strings.json b/homeassistant/components/demo/strings.json index aa5554e9fcc..da72b33d3ca 100644 --- a/homeassistant/components/demo/strings.json +++ b/homeassistant/components/demo/strings.json @@ -42,6 +42,13 @@ "auto": "Auto", "off": "[%key:common::state::off%]" } + }, + "swing_horizontal_mode": { + "state": { + "rangefull": "Full range", + "auto": "Auto", + "off": "[%key:common::state::off%]" + } } } }