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" "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" "freeze_protection": "Freeze protection"
} }
} }
},
"switch": {
"wrinkle_prevent": {
"name": "Wrinkle prevent"
}
} }
}, },
"issues": { "issues": {

View File

@ -2,15 +2,16 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from typing import Any 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.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import SmartThingsConfigEntry from . import FullDevice, SmartThingsConfigEntry
from .const import MAIN from .const import MAIN
from .entity import SmartThingsEntity 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( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: SmartThingsConfigEntry, entry: SmartThingsConfigEntry,
@ -36,35 +68,89 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Add switches for a config entry.""" """Add switches for a config entry."""
entry_data = entry.runtime_data entry_data = entry.runtime_data
async_add_entities( entities: list[SmartThingsEntity] = [
SmartThingsSwitch(entry_data.client, device, {Capability.SWITCH}) SmartThingsSwitch(entry_data.client, device, SWITCH, Capability.SWITCH)
for device in entry_data.devices.values() for device in entry_data.devices.values()
if Capability.SWITCH in device.status[MAIN] if Capability.SWITCH in device.status[MAIN]
and not any(capability in device.status[MAIN] for capability in CAPABILITIES) 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) 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): class SmartThingsSwitch(SmartThingsEntity, SwitchEntity):
"""Define a SmartThings switch.""" """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: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off.""" """Turn the switch off."""
await self.execute_device_command( await self.execute_device_command(
Capability.SWITCH, self.switch_capability,
Command.OFF, Command.OFF,
) )
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on.""" """Turn the switch on."""
await self.execute_device_command( await self.execute_device_command(
Capability.SWITCH, self.switch_capability,
Command.ON, Command.ON,
) )
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return true if light is on.""" """Return true if switch is on."""
return self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH) == "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', '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] # name: test_all_entities[da_wm_wd_000001_1][switch.seca_roupa-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@ -328,6 +375,53 @@
'state': 'off', '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] # name: test_all_entities[da_wm_wm_000001][switch.washer-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ '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"]) @pytest.mark.parametrize("device_fixture", ["c2c_arlo_pro_3_switch"])
async def test_state_update( async def test_state_update(
hass: HomeAssistant, hass: HomeAssistant,