1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-06 22:36:33 +01:00

Add SwitchBot Standing Fan switch platform (#173579)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Onero-testdev
2026-06-19 02:53:06 +08:00
committed by GitHub
parent c8933ada07
commit ba33e53409
4 changed files with 150 additions and 1 deletions
@@ -96,7 +96,7 @@ PLATFORMS_BY_TYPE = {
],
SupportedModels.HUBMINI_MATTER.value: [Platform.SENSOR],
SupportedModels.CIRCULATOR_FAN.value: [Platform.FAN, Platform.SENSOR],
SupportedModels.STANDING_FAN.value: [Platform.SENSOR],
SupportedModels.STANDING_FAN.value: [Platform.SWITCH, Platform.SENSOR],
SupportedModels.S10_VACUUM.value: [Platform.VACUUM, Platform.SENSOR],
SupportedModels.S20_VACUUM.value: [Platform.VACUUM, Platform.SENSOR],
SupportedModels.K10_VACUUM.value: [Platform.VACUUM, Platform.SENSOR],
@@ -356,6 +356,12 @@
"child_lock": {
"name": "Child lock"
},
"horizontal_oscillation": {
"name": "Horizontal oscillation"
},
"vertical_oscillation": {
"name": "Vertical oscillation"
},
"wireless_charging": {
"name": "Wireless charging"
}
@@ -1,5 +1,6 @@
"""Support for Switchbot bot."""
import abc
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
import logging
@@ -87,6 +88,13 @@ async def async_setup_entry(
for desc in AIRPURIFIER_TABLE_SWITCHES
]
)
elif isinstance(coordinator.device, switchbot.SwitchbotStandingFan):
async_add_entities(
[
SwitchbotFanHorizontalOscillationSwitch(coordinator),
SwitchbotFanVerticalOscillationSwitch(coordinator),
]
)
else:
async_add_entities([SwitchBotSwitch(coordinator)])
@@ -217,3 +225,79 @@ class SwitchbotMultiChannelSwitch(SwitchbotSwitchedEntity, SwitchEntity):
)
await self._device.turn_off(self._channel)
self.async_write_ha_state()
class SwitchbotFanOscillationSwitch(SwitchbotSwitchedEntity, SwitchEntity, abc.ABC):
"""Base class for fan oscillation switch entities."""
_attr_device_class = SwitchDeviceClass.SWITCH
_device: switchbot.SwitchbotStandingFan
@property
@abc.abstractmethod
def is_on(self) -> bool | None:
"""Return true if oscillation is active."""
@abc.abstractmethod
async def async_turn_on(self, **kwargs: Any) -> None:
"""Enable oscillation."""
@abc.abstractmethod
async def async_turn_off(self, **kwargs: Any) -> None:
"""Disable oscillation."""
class SwitchbotFanHorizontalOscillationSwitch(SwitchbotFanOscillationSwitch):
"""Switch entity for fan horizontal (left-right) oscillation."""
_attr_translation_key = "horizontal_oscillation"
def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None:
"""Initialize the horizontal oscillation switch."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.base_unique_id}-horizontal-oscillation"
@property
def is_on(self) -> bool | None:
"""Return true if horizontal oscillation is active."""
return self._device.get_horizontal_oscillating_state()
@exception_handler
async def async_turn_on(self, **kwargs: Any) -> None:
"""Enable horizontal oscillation."""
await self._device.set_horizontal_oscillation(True)
self.async_write_ha_state()
@exception_handler
async def async_turn_off(self, **kwargs: Any) -> None:
"""Disable horizontal oscillation."""
await self._device.set_horizontal_oscillation(False)
self.async_write_ha_state()
class SwitchbotFanVerticalOscillationSwitch(SwitchbotFanOscillationSwitch):
"""Switch entity for fan vertical (up-down) oscillation."""
_attr_translation_key = "vertical_oscillation"
def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None:
"""Initialize the vertical oscillation switch."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.base_unique_id}-vertical-oscillation"
@property
def is_on(self) -> bool | None:
"""Return true if vertical oscillation is active."""
return self._device.get_vertical_oscillating_state()
@exception_handler
async def async_turn_on(self, **kwargs: Any) -> None:
"""Enable vertical oscillation."""
await self._device.set_vertical_oscillation(True)
self.async_write_ha_state()
@exception_handler
async def async_turn_off(self, **kwargs: Any) -> None:
"""Disable vertical oscillation."""
await self._device.set_vertical_oscillation(False)
self.async_write_ha_state()
+59
View File
@@ -25,6 +25,7 @@ from . import (
PLUG_MINI_EU_SERVICE_INFO,
RELAY_SWITCH_1_SERVICE_INFO,
RELAY_SWITCH_2PM_SERVICE_INFO,
STANDING_FAN_SERVICE_INFO,
WOHAND_SERVICE_INFO,
WORELAY_SWITCH_1PM_SERVICE_INFO,
)
@@ -396,3 +397,61 @@ async def test_air_purifier_switch_control(
)
mocked_turn_off.assert_awaited_once()
@pytest.mark.parametrize(
("entity_id", "set_method", "get_state_method"),
[
(
"switch.test_name_horizontal_oscillation",
"set_horizontal_oscillation",
"get_horizontal_oscillating_state",
),
(
"switch.test_name_vertical_oscillation",
"set_vertical_oscillation",
"get_vertical_oscillating_state",
),
],
)
async def test_standing_fan_oscillation_switches(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
entity_id: str,
set_method: str,
get_state_method: str,
) -> None:
"""Test horizontal/vertical oscillation switches for the standing fan."""
inject_bluetooth_service_info(hass, STANDING_FAN_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="standing_fan")
entry.add_to_hass(hass)
mocked_set = AsyncMock(return_value=True)
with patch.multiple(
"homeassistant.components.switchbot.switch.switchbot.SwitchbotStandingFan",
get_basic_info=AsyncMock(return_value=None),
**{
set_method: mocked_set,
get_state_method: lambda self: False,
},
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mocked_set.assert_awaited_once_with(True)
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert mocked_set.await_count == 2
mocked_set.assert_awaited_with(False)