mirror of
https://github.com/home-assistant/core.git
synced 2026-04-20 16:39:02 +02:00
Add child lock and wireless charging switches for air purifier (#167140)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -114,21 +114,25 @@ PLATFORMS_BY_TYPE = {
|
||||
Platform.FAN,
|
||||
Platform.SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.SWITCH,
|
||||
],
|
||||
SupportedModels.AIR_PURIFIER_US.value: [
|
||||
Platform.FAN,
|
||||
Platform.SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.SWITCH,
|
||||
],
|
||||
SupportedModels.AIR_PURIFIER_TABLE_JP.value: [
|
||||
Platform.FAN,
|
||||
Platform.SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.SWITCH,
|
||||
],
|
||||
SupportedModels.AIR_PURIFIER_TABLE_US.value: [
|
||||
Platform.FAN,
|
||||
Platform.SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.SWITCH,
|
||||
],
|
||||
SupportedModels.EVAPORATIVE_HUMIDIFIER.value: [
|
||||
Platform.HUMIDIFIER,
|
||||
|
||||
@@ -145,6 +145,20 @@
|
||||
"medium": "mdi:water"
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"child_lock": {
|
||||
"state": {
|
||||
"off": "mdi:lock-open",
|
||||
"on": "mdi:lock"
|
||||
}
|
||||
},
|
||||
"wireless_charging": {
|
||||
"state": {
|
||||
"off": "mdi:battery-charging-wireless-outline",
|
||||
"on": "mdi:battery-charging-wireless"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
||||
@@ -326,6 +326,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"child_lock": {
|
||||
"name": "Child lock"
|
||||
},
|
||||
"wireless_charging": {
|
||||
"name": "Wireless charging"
|
||||
}
|
||||
},
|
||||
"vacuum": {
|
||||
|
||||
@@ -2,22 +2,61 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import switchbot
|
||||
|
||||
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDeviceClass,
|
||||
SwitchEntity,
|
||||
SwitchEntityDescription,
|
||||
)
|
||||
from homeassistant.const import STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import AIRPURIFIER_BASIC_MODELS, AIRPURIFIER_TABLE_MODELS, DOMAIN
|
||||
from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator
|
||||
from .entity import SwitchbotSwitchedEntity, exception_handler
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SwitchbotSwitchEntityDescription(SwitchEntityDescription):
|
||||
"""Describes a Switchbot switch entity."""
|
||||
|
||||
is_on_fn: Callable[[switchbot.SwitchbotDevice], bool | None]
|
||||
turn_on_fn: Callable[[switchbot.SwitchbotDevice], Awaitable[Any]]
|
||||
turn_off_fn: Callable[[switchbot.SwitchbotDevice], Awaitable[Any]]
|
||||
|
||||
|
||||
AIRPURIFIER_BASIC_SWITCHES: tuple[SwitchbotSwitchEntityDescription, ...] = (
|
||||
SwitchbotSwitchEntityDescription(
|
||||
key="child_lock",
|
||||
translation_key="child_lock",
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
is_on_fn=lambda device: device.is_child_lock_on(),
|
||||
turn_on_fn=lambda device: device.open_child_lock(),
|
||||
turn_off_fn=lambda device: device.close_child_lock(),
|
||||
),
|
||||
)
|
||||
|
||||
AIRPURIFIER_TABLE_SWITCHES: tuple[SwitchbotSwitchEntityDescription, ...] = (
|
||||
*AIRPURIFIER_BASIC_SWITCHES,
|
||||
SwitchbotSwitchEntityDescription(
|
||||
key="wireless_charging",
|
||||
translation_key="wireless_charging",
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
is_on_fn=lambda device: device.is_wireless_charging_on(),
|
||||
turn_on_fn=lambda device: device.open_wireless_charging(),
|
||||
turn_off_fn=lambda device: device.close_wireless_charging(),
|
||||
),
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -36,10 +75,64 @@ async def async_setup_entry(
|
||||
for channel in range(1, coordinator.device.channel + 1)
|
||||
]
|
||||
async_add_entities(entries)
|
||||
elif coordinator.model in AIRPURIFIER_BASIC_MODELS:
|
||||
async_add_entities(
|
||||
[
|
||||
SwitchbotGenericSwitch(coordinator, desc)
|
||||
for desc in AIRPURIFIER_BASIC_SWITCHES
|
||||
]
|
||||
)
|
||||
elif coordinator.model in AIRPURIFIER_TABLE_MODELS:
|
||||
async_add_entities(
|
||||
[
|
||||
SwitchbotGenericSwitch(coordinator, desc)
|
||||
for desc in AIRPURIFIER_TABLE_SWITCHES
|
||||
]
|
||||
)
|
||||
else:
|
||||
async_add_entities([SwitchBotSwitch(coordinator)])
|
||||
|
||||
|
||||
class SwitchbotGenericSwitch(SwitchbotSwitchedEntity, SwitchEntity):
|
||||
"""Representation of a Switchbot switch controlled via entity description."""
|
||||
|
||||
entity_description: SwitchbotSwitchEntityDescription
|
||||
_device: switchbot.SwitchbotDevice
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: SwitchbotDataUpdateCoordinator,
|
||||
description: SwitchbotSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the Switchbot generic switch."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.base_unique_id}-{description.key}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if device is on."""
|
||||
return self.entity_description.is_on_fn(self._device)
|
||||
|
||||
@exception_handler
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on."""
|
||||
_LOGGER.debug(
|
||||
"Turning on %s for %s", self.entity_description.key, self._address
|
||||
)
|
||||
await self.entity_description.turn_on_fn(self._device)
|
||||
self.async_write_ha_state()
|
||||
|
||||
@exception_handler
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off."""
|
||||
_LOGGER.debug(
|
||||
"Turning off %s for %s", self.entity_description.key, self._address
|
||||
)
|
||||
await self.entity_description.turn_off_fn(self._device)
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class SwitchBotSwitch(SwitchbotSwitchedEntity, SwitchEntity, RestoreEntity):
|
||||
"""Representation of a Switchbot switch."""
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from . import (
|
||||
AIR_PURIFIER_JP_SERVICE_INFO,
|
||||
AIR_PURIFIER_TABLE_JP_SERVICE_INFO,
|
||||
AIR_PURIFIER_TABLE_US_SERVICE_INFO,
|
||||
AIR_PURIFIER_US_SERVICE_INFO,
|
||||
PLUG_MINI_EU_SERVICE_INFO,
|
||||
RELAY_SWITCH_1_SERVICE_INFO,
|
||||
RELAY_SWITCH_2PM_SERVICE_INFO,
|
||||
@@ -294,3 +298,101 @@ async def test_relay_switch_control_with_exception(
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"service_info",
|
||||
"sensor_type",
|
||||
"entity_id",
|
||||
"turn_on_method",
|
||||
"turn_off_method",
|
||||
),
|
||||
[
|
||||
(
|
||||
AIR_PURIFIER_JP_SERVICE_INFO,
|
||||
"air_purifier_jp",
|
||||
"switch.test_name_child_lock",
|
||||
"open_child_lock",
|
||||
"close_child_lock",
|
||||
),
|
||||
(
|
||||
AIR_PURIFIER_TABLE_JP_SERVICE_INFO,
|
||||
"air_purifier_table_jp",
|
||||
"switch.test_name_child_lock",
|
||||
"open_child_lock",
|
||||
"close_child_lock",
|
||||
),
|
||||
(
|
||||
AIR_PURIFIER_US_SERVICE_INFO,
|
||||
"air_purifier_us",
|
||||
"switch.test_name_child_lock",
|
||||
"open_child_lock",
|
||||
"close_child_lock",
|
||||
),
|
||||
(
|
||||
AIR_PURIFIER_TABLE_US_SERVICE_INFO,
|
||||
"air_purifier_table_us",
|
||||
"switch.test_name_child_lock",
|
||||
"open_child_lock",
|
||||
"close_child_lock",
|
||||
),
|
||||
(
|
||||
AIR_PURIFIER_TABLE_JP_SERVICE_INFO,
|
||||
"air_purifier_table_jp",
|
||||
"switch.test_name_wireless_charging",
|
||||
"open_wireless_charging",
|
||||
"close_wireless_charging",
|
||||
),
|
||||
(
|
||||
AIR_PURIFIER_TABLE_US_SERVICE_INFO,
|
||||
"air_purifier_table_us",
|
||||
"switch.test_name_wireless_charging",
|
||||
"open_wireless_charging",
|
||||
"close_wireless_charging",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_air_purifier_switch_control(
|
||||
hass: HomeAssistant,
|
||||
mock_entry_encrypted_factory: Callable[[str], MockConfigEntry],
|
||||
service_info: BluetoothServiceInfoBleak,
|
||||
sensor_type: str,
|
||||
entity_id: str,
|
||||
turn_on_method: str,
|
||||
turn_off_method: str,
|
||||
) -> None:
|
||||
"""Test air purifier switch control."""
|
||||
inject_bluetooth_service_info(hass, service_info)
|
||||
|
||||
entry = mock_entry_encrypted_factory(sensor_type=sensor_type)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
mocked_turn_on = AsyncMock(return_value=True)
|
||||
mocked_turn_off = AsyncMock(return_value=True)
|
||||
|
||||
with patch.multiple(
|
||||
"homeassistant.components.switchbot.switch.switchbot.SwitchbotAirPurifier",
|
||||
update=AsyncMock(return_value=None),
|
||||
**{turn_on_method: mocked_turn_on, turn_off_method: mocked_turn_off},
|
||||
):
|
||||
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_turn_on.assert_awaited_once()
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mocked_turn_off.assert_awaited_once()
|
||||
|
||||
Reference in New Issue
Block a user