Skip to content

Commit

Permalink
Add time platform (#81949)
Browse files Browse the repository at this point in the history
  • Loading branch information
raman325 authored Apr 30, 2023
1 parent 6f63ed0 commit c0d0c89
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,8 @@ build.json @home-assistant/supervisor
/tests/components/tile/ @bachya
/homeassistant/components/tilt_ble/ @apt-itude
/tests/components/tilt_ble/ @apt-itude
/homeassistant/components/time/ @home-assistant/core
/tests/components/time/ @home-assistant/core
/homeassistant/components/time_date/ @fabaff
/tests/components/time_date/ @fabaff
/homeassistant/components/tmb/ @alemuro
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/demo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
Platform.STT,
Platform.SWITCH,
Platform.TEXT,
Platform.TIME,
Platform.UPDATE,
Platform.VACUUM,
Platform.WATER_HEATER,
Expand Down
63 changes: 63 additions & 0 deletions homeassistant/components/demo/time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Demo platform that offers a fake time entity."""
from __future__ import annotations

from datetime import time

from homeassistant.components.time import TimeEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import DEVICE_DEFAULT_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from . import DOMAIN


async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Demo time entity."""
async_add_entities([DemoTime("time", "Time", time(12, 0, 0), "mdi:clock", False)])


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Demo config entry."""
await async_setup_platform(hass, {}, async_add_entities)


class DemoTime(TimeEntity):
"""Representation of a Demo time entity."""

_attr_should_poll = False

def __init__(
self,
unique_id: str,
name: str,
state: time,
icon: str,
assumed_state: bool,
) -> None:
"""Initialize the Demo time entity."""
self._attr_assumed_state = assumed_state
self._attr_icon = icon
self._attr_name = name or DEVICE_DEFAULT_NAME
self._attr_native_value = state
self._attr_unique_id = unique_id

self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)}, name=self.name
)

async def async_set_value(self, value: time) -> None:
"""Update the time."""
self._attr_native_value = value
self.async_write_ha_state()
109 changes: 109 additions & 0 deletions homeassistant/components/time/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Component to allow setting time as platforms."""
from __future__ import annotations

from dataclasses import dataclass
from datetime import time, timedelta
import logging
from typing import final

import voluptuous as vol

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TIME
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType

from .const import DOMAIN, SERVICE_SET_VALUE

SCAN_INTERVAL = timedelta(seconds=30)

ENTITY_ID_FORMAT = DOMAIN + ".{}"

_LOGGER = logging.getLogger(__name__)

__all__ = ["DOMAIN", "TimeEntity", "TimeEntityDescription"]


async def _async_set_value(entity: TimeEntity, service_call: ServiceCall) -> None:
"""Service call wrapper to set a new date."""
return await entity.async_set_value(service_call.data[ATTR_TIME])


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Time entities."""
component = hass.data[DOMAIN] = EntityComponent[TimeEntity](
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
)
await component.async_setup(config)

component.async_register_entity_service(
SERVICE_SET_VALUE, {vol.Required(ATTR_TIME): cv.time}, _async_set_value
)

return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
component: EntityComponent[TimeEntity] = hass.data[DOMAIN]
return await component.async_setup_entry(entry)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
component: EntityComponent[TimeEntity] = hass.data[DOMAIN]
return await component.async_unload_entry(entry)


@dataclass
class TimeEntityDescription(EntityDescription):
"""A class that describes time entities."""


class TimeEntity(Entity):
"""Representation of a Time entity."""

entity_description: TimeEntityDescription
_attr_native_value: time | None
_attr_device_class: None = None
_attr_state: None = None

@property
@final
def device_class(self) -> None:
"""Return the device class for the entity."""
return None

@property
@final
def state_attributes(self) -> None:
"""Return the state attributes."""
return None

@property
@final
def state(self) -> str | None:
"""Return the entity state."""
if self.native_value is None:
return None
return self.native_value.isoformat()

@property
def native_value(self) -> time | None:
"""Return the value reported by the time."""
return self._attr_native_value

def set_value(self, value: time) -> None:
"""Change the time."""
raise NotImplementedError()

async def async_set_value(self, value: time) -> None:
"""Change the time."""
await self.hass.async_add_executor_job(self.set_value, value)
5 changes: 5 additions & 0 deletions homeassistant/components/time/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Provides the constants needed for the component."""

DOMAIN = "time"

SERVICE_SET_VALUE = "set_value"
8 changes: 8 additions & 0 deletions homeassistant/components/time/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"domain": "time",
"name": "Time",
"codeowners": ["@home-assistant/core"],
"documentation": "https://www.home-assistant.io/integrations/time",
"integration_type": "entity",
"quality_scale": "internal"
}
14 changes: 14 additions & 0 deletions homeassistant/components/time/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
set_value:
name: Set Time
description: Set the time for a time entity.
target:
entity:
domain: time
fields:
time:
name: Time
description: The time to set.
required: true
example: "22:15"
selector:
time:
8 changes: 8 additions & 0 deletions homeassistant/components/time/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"title": "Time",
"entity_component": {
"_": {
"name": "[%key:component::time::title%]"
}
}
}
1 change: 1 addition & 0 deletions homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class Platform(StrEnum):
STT = "stt"
SWITCH = "switch"
TEXT = "text"
TIME = "time"
TTS = "tts"
VACUUM = "vacuum"
UPDATE = "update"
Expand Down
34 changes: 34 additions & 0 deletions tests/components/demo/test_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""The tests for the demo time component."""
import pytest

from homeassistant.components.time import ATTR_TIME, DOMAIN, SERVICE_SET_VALUE
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component

ENTITY_TIME = "time.time"


@pytest.fixture(autouse=True)
async def setup_demo_datetime(hass: HomeAssistant) -> None:
"""Initialize setup demo time."""
assert await async_setup_component(hass, DOMAIN, {"time": {"platform": "demo"}})
await hass.async_block_till_done()


def test_setup_params(hass: HomeAssistant) -> None:
"""Test the initial parameters."""
state = hass.states.get(ENTITY_TIME)
assert state.state == "12:00:00"


async def test_set_value(hass: HomeAssistant) -> None:
"""Test set value service."""
await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{ATTR_ENTITY_ID: ENTITY_TIME, ATTR_TIME: "01:02:03"},
blocking=True,
)
state = hass.states.get(ENTITY_TIME)
assert state.state == "01:02:03"
1 change: 1 addition & 0 deletions tests/components/time/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the time component."""
52 changes: 52 additions & 0 deletions tests/components/time/test_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""The tests for the time component."""
from datetime import time

from homeassistant.components.time import DOMAIN, SERVICE_SET_VALUE, TimeEntity
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
ATTR_TIME,
CONF_PLATFORM,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component


class MockTimeEntity(TimeEntity):
"""Mock time device to use in tests."""

def __init__(self, native_value=time(12, 0, 0)) -> None:
"""Initialize mock time entity."""
self._attr_native_value = native_value

async def async_set_value(self, value: time) -> None:
"""Set the value of the time."""
self._attr_native_value = value


async def test_date(hass: HomeAssistant, enable_custom_integrations: None) -> None:
"""Test time entity."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()

assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
await hass.async_block_till_done()

state = hass.states.get("time.test")
assert state.state == "01:02:03"
assert state.attributes == {ATTR_FRIENDLY_NAME: "test"}

await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{ATTR_TIME: time(2, 3, 4), ATTR_ENTITY_ID: "time.test"},
blocking=True,
)
await hass.async_block_till_done()

state = hass.states.get("time.test")
assert state.state == "02:03:04"

date_entity = MockTimeEntity(native_value=None)
assert date_entity.state is None
assert date_entity.state_attributes is None
50 changes: 50 additions & 0 deletions tests/testing_config/custom_components/test/time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Provide a mock time platform.
Call init before using it in your tests to ensure clean test data.
"""
from datetime import time

from homeassistant.components.time import TimeEntity

from tests.common import MockEntity

UNIQUE_TIME = "unique_time"

ENTITIES = []


class MockTimeEntity(MockEntity, TimeEntity):
"""Mock time class."""

@property
def native_value(self):
"""Return the native value of this time."""
return self._handle("native_value")

def set_value(self, value: time) -> None:
"""Change the time."""
self._values["native_value"] = value


def init(empty=False):
"""Initialize the platform with entities."""
global ENTITIES

ENTITIES = (
[]
if empty
else [
MockTimeEntity(
name="test",
unique_id=UNIQUE_TIME,
native_value=time(1, 2, 3),
),
]
)


async def async_setup_platform(
hass, config, async_add_entities_callback, discovery_info=None
):
"""Return mock entities."""
async_add_entities_callback(ENTITIES)

0 comments on commit c0d0c89

Please sign in to comment.