Add Dryer Wrinkle Prevent switch to SmartThings (#141085)

* Add Dryer Wrinkle Prevent switch to SmartThings

* Fix
This commit is contained in:
Joost Lekkerkerker 2025-03-22 19:05:21 +01:00 committed by GitHub
parent 4b4d75063c
commit c56b087d0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 236 additions and 10 deletions

View File

@ -27,6 +27,14 @@
"stop": "mdi:stop"
}
}
},
"switch": {
"wrinkle_prevent": {
"default": "mdi:tumble-dryer",
"state": {
"off": "mdi:tumble-dryer-off"
}
}
}
}
}

View File

@ -442,6 +442,11 @@
"freeze_protection": "Freeze protection"
}
}
},
"switch": {
"wrinkle_prevent": {
"name": "Wrinkle prevent"
}
}
},
"issues": {

View File

@ -2,15 +2,16 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from pysmartthings import Attribute, Capability, Command
from pysmartthings import Attribute, Capability, Command, SmartThings
from homeassistant.components.switch import SwitchEntity
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import SmartThingsConfigEntry
from . import FullDevice, SmartThingsConfigEntry
from .const import MAIN
from .entity import SmartThingsEntity
@ -29,6 +30,37 @@ AC_CAPABILITIES = (
)
@dataclass(frozen=True, kw_only=True)
class SmartThingsSwitchEntityDescription(SwitchEntityDescription):
"""Describe a SmartThings switch entity."""
status_attribute: Attribute
@dataclass(frozen=True, kw_only=True)
class SmartThingsCommandSwitchEntityDescription(SmartThingsSwitchEntityDescription):
"""Describe a SmartThings switch entity."""
command: Command
SWITCH = SmartThingsSwitchEntityDescription(
key=Capability.SWITCH,
status_attribute=Attribute.SWITCH,
name=None,
)
CAPABILITY_TO_COMMAND_SWITCHES: dict[
Capability | str, SmartThingsCommandSwitchEntityDescription
] = {
Capability.CUSTOM_DRYER_WRINKLE_PREVENT: SmartThingsCommandSwitchEntityDescription(
key=Capability.CUSTOM_DRYER_WRINKLE_PREVENT,
translation_key="wrinkle_prevent",
status_attribute=Attribute.DRYER_WRINKLE_PREVENT,
command=Command.SET_DRYER_WRINKLE_PREVENT,
)
}
async def async_setup_entry(
hass: HomeAssistant,
entry: SmartThingsConfigEntry,
@ -36,35 +68,89 @@ async def async_setup_entry(
) -> None:
"""Add switches for a config entry."""
entry_data = entry.runtime_data
async_add_entities(
SmartThingsSwitch(entry_data.client, device, {Capability.SWITCH})
entities: list[SmartThingsEntity] = [
SmartThingsSwitch(entry_data.client, device, SWITCH, Capability.SWITCH)
for device in entry_data.devices.values()
if Capability.SWITCH in device.status[MAIN]
and not any(capability in device.status[MAIN] for capability in CAPABILITIES)
and not all(capability in device.status[MAIN] for capability in AC_CAPABILITIES)
]
entities.extend(
SmartThingsCommandSwitch(
entry_data.client,
device,
description,
Capability(capability),
)
for device in entry_data.devices.values()
for capability, description in CAPABILITY_TO_COMMAND_SWITCHES.items()
if capability in device.status[MAIN]
)
async_add_entities(entities)
class SmartThingsSwitch(SmartThingsEntity, SwitchEntity):
"""Define a SmartThings switch."""
_attr_name = None
entity_description: SmartThingsSwitchEntityDescription
def __init__(
self,
client: SmartThings,
device: FullDevice,
entity_description: SmartThingsSwitchEntityDescription,
capability: Capability,
) -> None:
"""Initialize the switch."""
super().__init__(client, device, {capability})
self.entity_description = entity_description
self.switch_capability = capability
self._attr_unique_id = device.device.device_id
if capability is not Capability.SWITCH:
self._attr_unique_id = f"{device.device.device_id}_{MAIN}_{capability}"
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.execute_device_command(
Capability.SWITCH,
self.switch_capability,
Command.OFF,
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.execute_device_command(
Capability.SWITCH,
self.switch_capability,
Command.ON,
)
@property
def is_on(self) -> bool:
"""Return true if light is on."""
return self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH) == "on"
"""Return true if switch is on."""
return (
self.get_attribute_value(
self.switch_capability, self.entity_description.status_attribute
)
== "on"
)
class SmartThingsCommandSwitch(SmartThingsSwitch):
"""Define a SmartThings command switch."""
entity_description: SmartThingsCommandSwitchEntityDescription
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.execute_device_command(
self.switch_capability,
self.entity_description.command,
"off",
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.execute_device_command(
self.switch_capability,
self.entity_description.command,
"on",
)

View File

@ -281,6 +281,53 @@
'state': 'off',
})
# ---
# name: test_all_entities[da_wm_wd_000001][switch.dryer_wrinkle_prevent-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.dryer_wrinkle_prevent',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Wrinkle prevent',
'platform': 'smartthings',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'wrinkle_prevent',
'unique_id': '02f7256e-8353-5bdd-547f-bd5b1647e01b_main_custom.dryerWrinklePrevent',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[da_wm_wd_000001][switch.dryer_wrinkle_prevent-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Dryer Wrinkle prevent',
}),
'context': <ANY>,
'entity_id': 'switch.dryer_wrinkle_prevent',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_entities[da_wm_wd_000001_1][switch.seca_roupa-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -328,6 +375,53 @@
'state': 'off',
})
# ---
# name: test_all_entities[da_wm_wd_000001_1][switch.seca_roupa_wrinkle_prevent-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.seca_roupa_wrinkle_prevent',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Wrinkle prevent',
'platform': 'smartthings',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'wrinkle_prevent',
'unique_id': '3a6c4e05-811d-5041-e956-3d04c424cbcd_main_custom.dryerWrinklePrevent',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[da_wm_wd_000001_1][switch.seca_roupa_wrinkle_prevent-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Seca-Roupa Wrinkle prevent',
}),
'context': <ANY>,
'entity_id': 'switch.seca_roupa_wrinkle_prevent',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_entities[da_wm_wm_000001][switch.washer-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@ -66,6 +66,39 @@ async def test_switch_turn_on_off(
)
@pytest.mark.parametrize("device_fixture", ["da_wm_wd_000001"])
@pytest.mark.parametrize(
("action", "argument"),
[
(SERVICE_TURN_ON, "on"),
(SERVICE_TURN_OFF, "off"),
],
)
async def test_command_switch_turn_on_off(
hass: HomeAssistant,
devices: AsyncMock,
mock_config_entry: MockConfigEntry,
action: str,
argument: str,
) -> None:
"""Test switch turn on and off command."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
SWITCH_DOMAIN,
action,
{ATTR_ENTITY_ID: "switch.dryer_wrinkle_prevent"},
blocking=True,
)
devices.execute_device_command.assert_called_once_with(
"02f7256e-8353-5bdd-547f-bd5b1647e01b",
Capability.CUSTOM_DRYER_WRINKLE_PREVENT,
Command.SET_DRYER_WRINKLE_PREVENT,
MAIN,
argument,
)
@pytest.mark.parametrize("device_fixture", ["c2c_arlo_pro_3_switch"])
async def test_state_update(
hass: HomeAssistant,