Add datetime platform (#81943)

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
Raman Gupta 2023-05-29 17:24:15 -04:00 committed by GitHub
parent 940942a74a
commit 24290e5d08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 468 additions and 30 deletions

View file

@ -21,6 +21,7 @@ base_platforms: &base_platforms
- homeassistant/components/climate/**
- homeassistant/components/cover/**
- homeassistant/components/date/**
- homeassistant/components/datetime/**
- homeassistant/components/device_tracker/**
- homeassistant/components/diagnostics/**
- homeassistant/components/fan/**

View file

@ -240,6 +240,8 @@ build.json @home-assistant/supervisor
/tests/components/daikin/ @fredrike
/homeassistant/components/date/ @home-assistant/core
/tests/components/date/ @home-assistant/core
/homeassistant/components/datetime/ @home-assistant/core
/tests/components/datetime/ @home-assistant/core
/homeassistant/components/debugpy/ @frenck
/tests/components/debugpy/ @frenck
/homeassistant/components/deconz/ @Kane610

View file

@ -19,6 +19,7 @@ import yarl
from . import config as conf_util, config_entries, core, loader
from .components import http
from .const import (
FORMAT_DATETIME,
REQUIRED_NEXT_PYTHON_HA_RELEASE,
REQUIRED_NEXT_PYTHON_VER,
SIGNAL_BOOTSTRAP_INTEGRATIONS,
@ -347,7 +348,6 @@ def async_enable_logging(
fmt = (
"%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
)
datefmt = "%Y-%m-%d %H:%M:%S"
if not log_no_color:
try:
@ -362,7 +362,7 @@ def async_enable_logging(
logging.getLogger().handlers[0].setFormatter(
ColoredFormatter(
colorfmt,
datefmt=datefmt,
datefmt=FORMAT_DATETIME,
reset=True,
log_colors={
"DEBUG": "cyan",
@ -378,7 +378,7 @@ def async_enable_logging(
# If the above initialization failed for any reason, setup the default
# formatting. If the above succeeds, this will result in a no-op.
logging.basicConfig(format=fmt, datefmt=datefmt, level=logging.INFO)
logging.basicConfig(format=fmt, datefmt=FORMAT_DATETIME, level=logging.INFO)
# Capture warnings.warn(...) and friends messages in logs.
# The standard destination for them is stderr, which may end up unnoticed.
@ -435,7 +435,7 @@ def async_enable_logging(
_LOGGER.error("Error rolling over log file: %s", err)
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))
err_handler.setFormatter(logging.Formatter(fmt, datefmt=FORMAT_DATETIME))
logger = logging.getLogger("")
logger.addHandler(err_handler)

View file

@ -0,0 +1,126 @@
"""Component to allow setting date/time as platforms."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import logging
from typing import final
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa: F401
ENTITY_SERVICE_FIELDS,
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 homeassistant.util import dt as dt_util
from .const import ATTR_DATETIME, DOMAIN, SERVICE_SET_VALUE
SCAN_INTERVAL = timedelta(seconds=30)
ENTITY_ID_FORMAT = DOMAIN + ".{}"
_LOGGER = logging.getLogger(__name__)
__all__ = ["ATTR_DATETIME", "DOMAIN", "DateTimeEntity", "DateTimeEntityDescription"]
async def _async_set_value(entity: DateTimeEntity, service_call: ServiceCall) -> None:
"""Service call wrapper to set a new date/time."""
value: datetime = service_call.data[ATTR_DATETIME]
if value.tzinfo is None:
value = value.replace(
tzinfo=dt_util.get_time_zone(entity.hass.config.time_zone)
)
return await entity.async_set_value(value)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Date/Time entities."""
component = hass.data[DOMAIN] = EntityComponent[DateTimeEntity](
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
)
await component.async_setup(config)
component.async_register_entity_service(
SERVICE_SET_VALUE,
{
vol.Required(ATTR_DATETIME): cv.datetime,
**ENTITY_SERVICE_FIELDS,
},
_async_set_value,
)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
component: EntityComponent[DateTimeEntity] = 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[DateTimeEntity] = hass.data[DOMAIN]
return await component.async_unload_entry(entry)
@dataclass
class DateTimeEntityDescription(EntityDescription):
"""A class that describes date/time entities."""
class DateTimeEntity(Entity):
"""Representation of a Date/time entity."""
entity_description: DateTimeEntityDescription
_attr_device_class: None = None
_attr_state: None = None
_attr_native_value: datetime | None
@property
@final
def device_class(self) -> None:
"""Return entity device class."""
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 (value := self.native_value) is None:
return None
if value.tzinfo is None:
raise ValueError(
f"Invalid datetime: {self.entity_id} provides state '{value}', "
"which is missing timezone information"
)
return value.astimezone(timezone.utc).isoformat(timespec="seconds")
@property
def native_value(self) -> datetime | None:
"""Return the value reported by the datetime."""
return self._attr_native_value
def set_value(self, value: datetime) -> None:
"""Change the date/time."""
raise NotImplementedError()
async def async_set_value(self, value: datetime) -> None:
"""Change the date/time."""
await self.hass.async_add_executor_job(self.set_value, value)

View file

@ -0,0 +1,7 @@
"""Provides the constants needed for the component."""
DOMAIN = "datetime"
ATTR_DATETIME = "datetime"
SERVICE_SET_VALUE = "set_value"

View file

@ -0,0 +1,8 @@
{
"domain": "datetime",
"name": "Date/Time",
"codeowners": ["@home-assistant/core"],
"documentation": "https://www.home-assistant.io/integrations/datetime",
"integration_type": "entity",
"quality_scale": "internal"
}

View file

@ -0,0 +1,14 @@
set_value:
name: Set Date/Time
description: Set the date/time for a datetime entity.
target:
entity:
domain: datetime
fields:
datetime:
name: Date & Time
description: The date/time to set. The time zone of the Home Assistant instance is assumed.
required: true
example: "2022/11/01 22:15"
selector:
datetime:

View file

@ -0,0 +1,8 @@
{
"title": "Date/Time",
"entity_component": {
"_": {
"name": "[%key:component::datetime::title%]"
}
}
}

View file

@ -29,6 +29,7 @@ COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM = [
Platform.CLIMATE,
Platform.COVER,
Platform.DATE,
Platform.DATETIME,
Platform.FAN,
Platform.HUMIDIFIER,
Platform.LIGHT,

View file

@ -0,0 +1,77 @@
"""Demo platform that offers a fake date/time entity."""
from __future__ import annotations
from datetime import datetime, timezone
from homeassistant.components.datetime import DateTimeEntity
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 date/time entity."""
async_add_entities(
[
DemoDateTime(
"datetime",
"Date and Time",
datetime(2020, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
"mdi:calendar-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 DemoDateTime(DateTimeEntity):
"""Representation of a Demo date/time entity."""
_attr_should_poll = False
def __init__(
self,
unique_id: str,
name: str,
state: datetime,
icon: str,
assumed_state: bool,
) -> None:
"""Initialize the Demo date/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={
# Serial numbers are unique identifiers within a specific domain
(DOMAIN, unique_id)
},
name=self.name,
)
async def async_set_value(self, value: datetime) -> None:
"""Update the date/time."""
self._attr_native_value = value
self.async_write_ha_state()

View file

@ -272,7 +272,7 @@ class InputDatetime(collection.CollectionEntity, RestoreEntity):
if self.state is not None:
return
default_value = py_datetime.datetime.today().strftime("%Y-%m-%d 00:00:00")
default_value = py_datetime.datetime.today().strftime(f"{FMT_DATE} 00:00:00")
# Priority 2: Old state
if (old_state := await self.async_get_last_state()) is None:

View file

@ -32,6 +32,7 @@ class Platform(StrEnum):
CLIMATE = "climate"
COVER = "cover"
DATE = "date"
DATETIME = "datetime"
DEVICE_TRACKER = "device_tracker"
FAN = "fan"
GEO_LOCATION = "geo_location"
@ -1165,6 +1166,11 @@ HASSIO_USER_NAME = "Supervisor"
SIGNAL_BOOTSTRAP_INTEGRATIONS = "bootstrap_integrations"
# Date/Time formats
FORMAT_DATE: Final = "%Y-%m-%d"
FORMAT_TIME: Final = "%H:%M:%S"
FORMAT_DATETIME: Final = f"{FORMAT_DATE} {FORMAT_TIME}"
class EntityCategory(StrEnum):
"""Category of an entity.

View file

@ -0,0 +1 @@
"""Tests for the datetime component."""

View file

@ -0,0 +1,98 @@
"""The tests for the datetime component."""
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
import pytest
from homeassistant.components.datetime import (
ATTR_DATETIME,
DOMAIN,
SERVICE_SET_VALUE,
DateTimeEntity,
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, CONF_PLATFORM
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
DEFAULT_VALUE = datetime(2020, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
class MockDateTimeEntity(DateTimeEntity):
"""Mock datetime device to use in tests."""
def __init__(self, native_value: datetime | None = DEFAULT_VALUE) -> None:
"""Initialize mock datetime entity."""
self._attr_native_value = native_value
async def async_set_value(self, value: datetime) -> None:
"""Change the date/time."""
self._attr_native_value = value
async def test_datetime(hass: HomeAssistant, enable_custom_integrations: None) -> None:
"""Test date/time entity."""
hass.config.set_time_zone("UTC")
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("datetime.test")
assert state.state == "2020-01-01T01:02:03+00:00"
assert state.attributes == {ATTR_FRIENDLY_NAME: "test"}
# Test updating datetime
await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{ATTR_DATETIME: datetime(2022, 3, 3, 3, 4, 5), ATTR_ENTITY_ID: "datetime.test"},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get("datetime.test")
assert state.state == "2022-03-03T03:04:05+00:00"
# Test updating datetime with UTC timezone
await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{ATTR_DATETIME: "2022-03-03T03:04:05+00:00", ATTR_ENTITY_ID: "datetime.test"},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get("datetime.test")
assert state.state == "2022-03-03T03:04:05+00:00"
# Test updating datetime with non UTC timezone
await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{ATTR_DATETIME: "2022-03-03T03:04:05-05:00", ATTR_ENTITY_ID: "datetime.test"},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get("datetime.test")
assert state.state == "2022-03-03T08:04:05+00:00"
# Test that non UTC timezone gets converted to UTC
assert (
MockDateTimeEntity(
native_value=datetime(2020, 1, 2, 3, 4, 5, tzinfo=ZoneInfo("US/Eastern"))
).state
== "2020-01-02T08:04:05+00:00"
)
# Test None state
date_entity = MockDateTimeEntity(native_value=None)
assert date_entity.state is None
assert date_entity.state_attributes is None
# Test that timezone is required to process state
with pytest.raises(ValueError):
assert MockDateTimeEntity(
native_value=datetime(2020, 1, 2, 3, 4, 5, tzinfo=None)
).state

View file

@ -0,0 +1,35 @@
"""The tests for the demo datetime component."""
import pytest
from homeassistant.components.datetime import ATTR_DATETIME, DOMAIN, SERVICE_SET_VALUE
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
ENTITY_DATETIME = "datetime.date_and_time"
@pytest.fixture(autouse=True)
async def setup_demo_datetime(hass: HomeAssistant) -> None:
"""Initialize setup demo datetime."""
assert await async_setup_component(hass, DOMAIN, {"datetime": {"platform": "demo"}})
await hass.async_block_till_done()
def test_setup_params(hass: HomeAssistant) -> None:
"""Test the initial parameters."""
state = hass.states.get(ENTITY_DATETIME)
assert state.state == "2020-01-01T12:00:00+00:00"
async def test_set_datetime(hass: HomeAssistant) -> None:
"""Test set datetime service."""
hass.config.set_time_zone("UTC")
await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{ATTR_ENTITY_ID: ENTITY_DATETIME, ATTR_DATETIME: "2021-02-03 01:02:03"},
blocking=True,
)
state = hass.states.get(ENTITY_DATETIME)
assert state.state == "2021-02-03T01:02:03+00:00"

View file

@ -19,12 +19,16 @@ from homeassistant.components.input_datetime import (
CONFIG_SCHEMA,
DEFAULT_TIME,
DOMAIN,
FMT_DATE,
FMT_DATETIME,
FMT_TIME,
SERVICE_RELOAD,
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_NAME
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
ATTR_NAME,
FORMAT_DATE,
FORMAT_DATETIME,
FORMAT_TIME,
)
from homeassistant.core import Context, CoreState, HomeAssistant, State
from homeassistant.exceptions import Unauthorized
from homeassistant.helpers import entity_registry as er
@ -136,7 +140,7 @@ async def test_set_datetime(hass: HomeAssistant) -> None:
await async_set_date_and_time(hass, entity_id, dt_obj)
state = hass.states.get(entity_id)
assert state.state == dt_obj.strftime(FMT_DATETIME)
assert state.state == dt_obj.strftime(FORMAT_DATETIME)
assert state.attributes["has_time"]
assert state.attributes["has_date"]
@ -164,7 +168,7 @@ async def test_set_datetime_2(hass: HomeAssistant) -> None:
await async_set_datetime(hass, entity_id, dt_obj)
state = hass.states.get(entity_id)
assert state.state == dt_obj.strftime(FMT_DATETIME)
assert state.state == dt_obj.strftime(FORMAT_DATETIME)
assert state.attributes["has_time"]
assert state.attributes["has_date"]
@ -192,7 +196,7 @@ async def test_set_datetime_3(hass: HomeAssistant) -> None:
await async_set_timestamp(hass, entity_id, dt_util.as_utc(dt_obj).timestamp())
state = hass.states.get(entity_id)
assert state.state == dt_obj.strftime(FMT_DATETIME)
assert state.state == dt_obj.strftime(FORMAT_DATETIME)
assert state.attributes["has_time"]
assert state.attributes["has_date"]
@ -218,7 +222,7 @@ async def test_set_datetime_time(hass: HomeAssistant) -> None:
await async_set_date_and_time(hass, entity_id, dt_obj)
state = hass.states.get(entity_id)
assert state.state == dt_obj.strftime(FMT_TIME)
assert state.state == dt_obj.strftime(FORMAT_TIME)
assert state.attributes["has_time"]
assert not state.attributes["has_date"]
@ -337,7 +341,7 @@ async def test_restore_state(hass: HomeAssistant) -> None:
"test_bogus_data": {
"has_time": True,
"has_date": True,
"initial": initial.strftime(FMT_DATETIME),
"initial": initial.strftime(FORMAT_DATETIME),
},
"test_was_time": {"has_time": False, "has_date": True},
"test_was_date": {"has_time": True, "has_date": False},
@ -347,22 +351,22 @@ async def test_restore_state(hass: HomeAssistant) -> None:
dt_obj = datetime.datetime(2017, 9, 7, 19, 46)
state_time = hass.states.get("input_datetime.test_time")
assert state_time.state == dt_obj.strftime(FMT_TIME)
assert state_time.state == dt_obj.strftime(FORMAT_TIME)
state_date = hass.states.get("input_datetime.test_date")
assert state_date.state == dt_obj.strftime(FMT_DATE)
assert state_date.state == dt_obj.strftime(FORMAT_DATE)
state_datetime = hass.states.get("input_datetime.test_datetime")
assert state_datetime.state == dt_obj.strftime(FMT_DATETIME)
assert state_datetime.state == dt_obj.strftime(FORMAT_DATETIME)
state_bogus = hass.states.get("input_datetime.test_bogus_data")
assert state_bogus.state == initial.strftime(FMT_DATETIME)
assert state_bogus.state == initial.strftime(FORMAT_DATETIME)
state_was_time = hass.states.get("input_datetime.test_was_time")
assert state_was_time.state == default.strftime(FMT_DATE)
assert state_was_time.state == default.strftime(FORMAT_DATE)
state_was_date = hass.states.get("input_datetime.test_was_date")
assert state_was_date.state == default.strftime(FMT_TIME)
assert state_was_date.state == default.strftime(FORMAT_TIME)
async def test_default_value(hass: HomeAssistant) -> None:
@ -381,15 +385,15 @@ async def test_default_value(hass: HomeAssistant) -> None:
dt_obj = datetime.datetime.combine(datetime.date.today(), DEFAULT_TIME)
state_time = hass.states.get("input_datetime.test_time")
assert state_time.state == dt_obj.strftime(FMT_TIME)
assert state_time.state == dt_obj.strftime(FORMAT_TIME)
assert state_time.attributes.get("timestamp") is not None
state_date = hass.states.get("input_datetime.test_date")
assert state_date.state == dt_obj.strftime(FMT_DATE)
assert state_date.state == dt_obj.strftime(FORMAT_DATE)
assert state_date.attributes.get("timestamp") is not None
state_datetime = hass.states.get("input_datetime.test_datetime")
assert state_datetime.state == dt_obj.strftime(FMT_DATETIME)
assert state_datetime.state == dt_obj.strftime(FORMAT_DATETIME)
assert state_datetime.attributes.get("timestamp") is not None
@ -446,7 +450,7 @@ async def test_reload(
assert state_1 is not None
assert state_2 is None
assert state_3 is not None
assert dt_obj.strftime(FMT_DATE) == state_1.state
assert dt_obj.strftime(FORMAT_DATE) == state_1.state
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt1") == f"{DOMAIN}.dt1"
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt2") is None
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt3") == f"{DOMAIN}.dt3"
@ -484,10 +488,10 @@ async def test_reload(
assert state_1 is not None
assert state_2 is not None
assert state_3 is None
assert state_1.state == DEFAULT_TIME.strftime(FMT_TIME)
assert state_1.state == DEFAULT_TIME.strftime(FORMAT_TIME)
assert state_2.state == datetime.datetime.combine(
datetime.date.today(), DEFAULT_TIME
).strftime(FMT_DATETIME)
).strftime(FORMAT_DATETIME)
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt1") == f"{DOMAIN}.dt1"
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt2") == f"{DOMAIN}.dt2"
@ -705,7 +709,7 @@ async def test_timestamp(hass: HomeAssistant) -> None:
assert (
dt_util.as_local(
dt_util.utc_from_timestamp(state_with_tz.attributes[ATTR_TIMESTAMP])
).strftime(FMT_DATETIME)
).strftime(FORMAT_DATETIME)
== "2020-12-13 01:00:00"
)
@ -719,13 +723,13 @@ async def test_timestamp(hass: HomeAssistant) -> None:
assert (
dt_util.utc_from_timestamp(
state_without_tz.attributes[ATTR_TIMESTAMP]
).strftime(FMT_DATETIME)
).strftime(FORMAT_DATETIME)
== "2020-12-13 18:00:00"
)
assert (
dt_util.as_local(
dt_util.utc_from_timestamp(state_without_tz.attributes[ATTR_TIMESTAMP])
).strftime(FMT_DATETIME)
).strftime(FORMAT_DATETIME)
== "2020-12-13 10:00:00"
)
# Use datetime.datetime.fromtimestamp
@ -734,7 +738,7 @@ async def test_timestamp(hass: HomeAssistant) -> None:
datetime.datetime.fromtimestamp(
state_without_tz.attributes[ATTR_TIMESTAMP], datetime.timezone.utc
)
).strftime(FMT_DATETIME)
).strftime(FORMAT_DATETIME)
== "2020-12-13 10:00:00"
)

View file

@ -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 datetime, timezone
from homeassistant.components.datetime import DateTimeEntity
from tests.common import MockEntity
UNIQUE_DATETIME = "unique_datetime"
ENTITIES = []
class MockDateTimeEntity(MockEntity, DateTimeEntity):
"""Mock date/time class."""
@property
def native_value(self):
"""Return the native value of this date/time."""
return self._handle("native_value")
def set_value(self, value: datetime) -> None:
"""Change the time."""
self._values["native_value"] = value
def init(empty=False):
"""Initialize the platform with entities."""
global ENTITIES
ENTITIES = (
[]
if empty
else [
MockDateTimeEntity(
name="test",
unique_id=UNIQUE_DATETIME,
native_value=datetime(2020, 1, 1, 1, 2, 3, tzinfo=timezone.utc),
),
]
)
async def async_setup_platform(
hass, config, async_add_entities_callback, discovery_info=None
):
"""Return mock entities."""
async_add_entities_callback(ENTITIES)