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:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user