Add time platform to SmartThings (#164451)

This commit is contained in:
Joost Lekkerkerker
2026-03-02 08:34:53 +01:00
committed by GitHub
parent 78ad1e102d
commit e032740e90
6 changed files with 444 additions and 0 deletions
@@ -107,6 +107,7 @@ PLATFORMS = [
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.TIME,
Platform.UPDATE,
Platform.VACUUM,
Platform.VALVE,
@@ -256,6 +256,14 @@
"off": "mdi:tumble-dryer-off"
}
}
},
"time": {
"do_not_disturb_end_time": {
"default": "mdi:bell-ring"
},
"do_not_disturb_start_time": {
"default": "mdi:bell-cancel"
}
}
}
}
@@ -945,6 +945,14 @@
"name": "Wrinkle prevent"
}
},
"time": {
"do_not_disturb_end_time": {
"name": "Do not disturb end time"
},
"do_not_disturb_start_time": {
"name": "Do not disturb start time"
}
},
"vacuum": {
"vacuum": {
"state_attributes": {
@@ -0,0 +1,102 @@
"""Time platform for SmartThings."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import time
from pysmartthings import Attribute, Capability, Command, SmartThings
from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import FullDevice, SmartThingsConfigEntry
from .const import MAIN
from .entity import SmartThingsEntity
@dataclass(frozen=True, kw_only=True)
class SmartThingsTimeEntityDescription(TimeEntityDescription):
"""Describe a SmartThings time entity."""
attribute: Attribute
DND_ENTITIES = [
SmartThingsTimeEntityDescription(
key=Attribute.START_TIME,
translation_key="do_not_disturb_start_time",
attribute=Attribute.START_TIME,
entity_category=EntityCategory.CONFIG,
),
SmartThingsTimeEntityDescription(
key=Attribute.END_TIME,
translation_key="do_not_disturb_end_time",
attribute=Attribute.END_TIME,
entity_category=EntityCategory.CONFIG,
),
]
async def async_setup_entry(
hass: HomeAssistant,
entry: SmartThingsConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add time entities for a config entry."""
entry_data = entry.runtime_data
async_add_entities(
SmartThingsDnDTime(entry_data.client, device, description)
for device in entry_data.devices.values()
if Capability.CUSTOM_DO_NOT_DISTURB_MODE in device.status.get(MAIN, {})
for description in DND_ENTITIES
)
class SmartThingsDnDTime(SmartThingsEntity, TimeEntity):
"""Define a SmartThings time entity."""
entity_description: SmartThingsTimeEntityDescription
def __init__(
self,
client: SmartThings,
device: FullDevice,
entity_description: SmartThingsTimeEntityDescription,
) -> None:
"""Initialize the time entity."""
super().__init__(client, device, {Capability.CUSTOM_DO_NOT_DISTURB_MODE})
self.entity_description = entity_description
self._attr_unique_id = f"{device.device.device_id}_{MAIN}_{Capability.CUSTOM_DO_NOT_DISTURB_MODE}_{entity_description.attribute}_{entity_description.attribute}"
async def async_set_value(self, value: time) -> None:
"""Set the time value."""
payload = {
"mode": self.get_attribute_value(
Capability.CUSTOM_DO_NOT_DISTURB_MODE, Attribute.DO_NOT_DISTURB
),
"startTime": self.get_attribute_value(
Capability.CUSTOM_DO_NOT_DISTURB_MODE, Attribute.START_TIME
),
"endTime": self.get_attribute_value(
Capability.CUSTOM_DO_NOT_DISTURB_MODE, Attribute.END_TIME
),
}
await self.execute_device_command(
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
Command.SET_DO_NOT_DISTURB_MODE,
{
**payload,
self.entity_description.attribute: f"{value.hour:02d}{value.minute:02d}",
},
)
@property
def native_value(self) -> time:
"""Return the time value."""
state = self.get_attribute_value(
Capability.CUSTOM_DO_NOT_DISTURB_MODE, self.entity_description.attribute
)
return time(int(state[:2]), int(state[3:5]))
@@ -0,0 +1,197 @@
# serializer version: 1
# name: test_all_entities[da_ks_hood_01001][time.range_hood_do_not_disturb_end_time-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': 'time',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'time.range_hood_do_not_disturb_end_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Do not disturb end time',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Do not disturb end time',
'platform': 'smartthings',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'do_not_disturb_end_time',
'unique_id': 'fa5fca25-fa7a-1807-030a-2f72ee0f7bff_main_custom.doNotDisturbMode_endTime_endTime',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[da_ks_hood_01001][time.range_hood_do_not_disturb_end_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Range hood Do not disturb end time',
}),
'context': <ANY>,
'entity_id': 'time.range_hood_do_not_disturb_end_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '00:00:00',
})
# ---
# name: test_all_entities[da_ks_hood_01001][time.range_hood_do_not_disturb_start_time-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': 'time',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'time.range_hood_do_not_disturb_start_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Do not disturb start time',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Do not disturb start time',
'platform': 'smartthings',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'do_not_disturb_start_time',
'unique_id': 'fa5fca25-fa7a-1807-030a-2f72ee0f7bff_main_custom.doNotDisturbMode_startTime_startTime',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[da_ks_hood_01001][time.range_hood_do_not_disturb_start_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Range hood Do not disturb start time',
}),
'context': <ANY>,
'entity_id': 'time.range_hood_do_not_disturb_start_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '00:00:00',
})
# ---
# name: test_all_entities[da_rvc_map_01011][time.robot_vacuum_do_not_disturb_end_time-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': 'time',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'time.robot_vacuum_do_not_disturb_end_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Do not disturb end time',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Do not disturb end time',
'platform': 'smartthings',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'do_not_disturb_end_time',
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_custom.doNotDisturbMode_endTime_endTime',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[da_rvc_map_01011][time.robot_vacuum_do_not_disturb_end_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Robot Vacuum Do not disturb end time',
}),
'context': <ANY>,
'entity_id': 'time.robot_vacuum_do_not_disturb_end_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '06:00:00',
})
# ---
# name: test_all_entities[da_rvc_map_01011][time.robot_vacuum_do_not_disturb_start_time-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': 'time',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'time.robot_vacuum_do_not_disturb_start_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Do not disturb start time',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Do not disturb start time',
'platform': 'smartthings',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'do_not_disturb_start_time',
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_custom.doNotDisturbMode_startTime_startTime',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[da_rvc_map_01011][time.robot_vacuum_do_not_disturb_start_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Robot Vacuum Do not disturb start time',
}),
'context': <ANY>,
'entity_id': 'time.robot_vacuum_do_not_disturb_start_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '22:00:00',
})
# ---
+128
View File
@@ -0,0 +1,128 @@
"""Test for the SmartThings time platform."""
from unittest.mock import AsyncMock
from pysmartthings import Attribute, Capability, Command
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.smartthings import MAIN
from homeassistant.components.time import DOMAIN as TIME_DOMAIN, SERVICE_SET_VALUE
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TIME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration, snapshot_smartthings_entities, trigger_update
from tests.common import MockConfigEntry
async def test_all_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
devices: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test all entities."""
await setup_integration(hass, mock_config_entry)
snapshot_smartthings_entities(hass, entity_registry, snapshot, Platform.TIME)
@pytest.mark.parametrize("device_fixture", ["da_rvc_map_01011"])
async def test_state_update(
hass: HomeAssistant,
devices: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test state update."""
await setup_integration(hass, mock_config_entry)
assert (
hass.states.get("time.robot_vacuum_do_not_disturb_end_time").state == "06:00:00"
)
await trigger_update(
hass,
devices,
"01b28624-5907-c8bc-0325-8ad23f03a637",
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
Attribute.END_TIME,
"0800",
)
assert (
hass.states.get("time.robot_vacuum_do_not_disturb_end_time").state == "08:00:00"
)
@pytest.mark.parametrize("device_fixture", ["da_rvc_map_01011"])
async def test_set_value(
hass: HomeAssistant,
devices: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test setting a value."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
TIME_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: "time.robot_vacuum_do_not_disturb_end_time",
ATTR_TIME: "09:00:00",
},
blocking=True,
)
devices.execute_device_command.assert_called_once_with(
"01b28624-5907-c8bc-0325-8ad23f03a637",
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
Command.SET_DO_NOT_DISTURB_MODE,
MAIN,
argument={
"mode": "on",
"startTime": "2200",
"endTime": "0900",
},
)
@pytest.mark.parametrize("device_fixture", ["da_rvc_map_01011"])
async def test_dnd_mode_updates(
hass: HomeAssistant,
devices: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test setting a value."""
await setup_integration(hass, mock_config_entry)
await trigger_update(
hass,
devices,
"01b28624-5907-c8bc-0325-8ad23f03a637",
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
Attribute.DO_NOT_DISTURB,
"off",
)
await hass.services.async_call(
TIME_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: "time.robot_vacuum_do_not_disturb_end_time",
ATTR_TIME: "09:00:00",
},
blocking=True,
)
devices.execute_device_command.assert_called_once_with(
"01b28624-5907-c8bc-0325-8ad23f03a637",
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
Command.SET_DO_NOT_DISTURB_MODE,
MAIN,
argument={
"mode": "off",
"startTime": "2200",
"endTime": "0900",
},
)