Skip to content

Commit

Permalink
Add config flow for Time & Date (home-assistant#104183)
Browse files Browse the repository at this point in the history
Co-authored-by: Erik <[email protected]>
  • Loading branch information
2 people authored and catsmanac committed Jan 23, 2024
1 parent 293b805 commit e22adf2
Show file tree
Hide file tree
Showing 15 changed files with 574 additions and 89 deletions.
17 changes: 17 additions & 0 deletions homeassistant/components/time_date/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
"""The time_date component."""
from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from .const import PLATFORMS


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Time & Date from a config entry."""
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Time & Date config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
130 changes: 130 additions & 0 deletions homeassistant/components/time_date/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""Adds config flow for Time & Date integration."""
from __future__ import annotations

from collections.abc import Mapping
from datetime import timedelta
import logging
from typing import Any

import voluptuous as vol

from homeassistant.components import websocket_api
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
SchemaFlowError,
SchemaFlowFormStep,
)
from homeassistant.helpers.selector import (
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
)
from homeassistant.setup import async_prepare_setup_platform

from .const import CONF_DISPLAY_OPTIONS, DOMAIN, OPTION_TYPES
from .sensor import TimeDateSensor

_LOGGER = logging.getLogger(__name__)

USER_SCHEMA = vol.Schema(
{
vol.Required(CONF_DISPLAY_OPTIONS): SelectSelector(
SelectSelectorConfig(
options=[option for option in OPTION_TYPES if option != "beat"],
mode=SelectSelectorMode.DROPDOWN,
translation_key="display_options",
)
),
}
)


async def validate_input(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Validate rest setup."""
hass = handler.parent_handler.hass
if hass.config.time_zone is None:
raise SchemaFlowError("timezone_not_exist")
return user_input


CONFIG_FLOW = {
"user": SchemaFlowFormStep(
schema=USER_SCHEMA,
preview=DOMAIN,
validate_user_input=validate_input,
)
}


class TimeDateConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config flow for Time & Date."""

config_flow = CONFIG_FLOW

def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
"""Return config entry title."""
return f"Time & Date {options[CONF_DISPLAY_OPTIONS]}"

def async_config_flow_finished(self, options: Mapping[str, Any]) -> None:
"""Abort if instance already exist."""
self._async_abort_entries_match(dict(options))

@staticmethod
async def async_setup_preview(hass: HomeAssistant) -> None:
"""Set up preview WS API."""
websocket_api.async_register_command(hass, ws_start_preview)


@websocket_api.websocket_command(
{
vol.Required("type"): "time_date/start_preview",
vol.Required("flow_id"): str,
vol.Required("flow_type"): vol.Any("config_flow"),
vol.Required("user_input"): dict,
}
)
@websocket_api.async_response
async def ws_start_preview(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Generate a preview."""
validated = USER_SCHEMA(msg["user_input"])

# Create an EntityPlatform, needed for name translations
platform = await async_prepare_setup_platform(hass, {}, SENSOR_DOMAIN, DOMAIN)
entity_platform = EntityPlatform(
hass=hass,
logger=_LOGGER,
domain=SENSOR_DOMAIN,
platform_name=DOMAIN,
platform=platform,
scan_interval=timedelta(seconds=3600),
entity_namespace=None,
)
await entity_platform.async_load_translations()

@callback
def async_preview_updated(state: str, attributes: Mapping[str, Any]) -> None:
"""Forward config entry state events to websocket."""
connection.send_message(
websocket_api.event_message(
msg["id"], {"attributes": attributes, "state": state}
)
)

preview_entity = TimeDateSensor(validated[CONF_DISPLAY_OPTIONS])
preview_entity.hass = hass
preview_entity.platform = entity_platform

connection.send_result(msg["id"])
connection.subscriptions[msg["id"]] = preview_entity.async_start_preview(
async_preview_updated
)
16 changes: 16 additions & 0 deletions homeassistant/components/time_date/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,20 @@

from typing import Final

from homeassistant.const import Platform

CONF_DISPLAY_OPTIONS = "display_options"
DOMAIN: Final = "time_date"
PLATFORMS = [Platform.SENSOR]
TIME_STR_FORMAT = "%H:%M"

OPTION_TYPES = [
"time",
"date",
"date_time",
"date_time_utc",
"date_time_iso",
"time_date",
"beat",
"time_utc",
]
2 changes: 2 additions & 0 deletions homeassistant/components/time_date/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"domain": "time_date",
"name": "Time & Date",
"codeowners": ["@fabaff"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/time_date",
"integration_type": "service",
"iot_class": "local_push",
"quality_scale": "internal"
}
80 changes: 58 additions & 22 deletions homeassistant/components/time_date/sensor.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
"""Support for showing the date and the time."""
from __future__ import annotations

from collections.abc import Callable, Mapping
from datetime import datetime, timedelta
import logging
from typing import Any

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.components.sensor import (
ENTITY_ID_FORMAT,
PLATFORM_SCHEMA,
SensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DISPLAY_OPTIONS, EVENT_CORE_CONFIG_UPDATE
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
Expand All @@ -16,22 +23,12 @@
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util

from .const import DOMAIN
from .const import DOMAIN, OPTION_TYPES

_LOGGER = logging.getLogger(__name__)

TIME_STR_FORMAT = "%H:%M"

OPTION_TYPES = {
"time": "Time",
"date": "Date",
"date_time": "Date & Time",
"date_time_utc": "Date & Time (UTC)",
"date_time_iso": "Date & Time (ISO)",
"time_date": "Time & Date",
"beat": "Internet Time",
"time_utc": "Time (UTC)",
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
Expand Down Expand Up @@ -71,27 +68,37 @@ async def async_setup_platform(
_LOGGER.warning("'beat': is deprecated and will be removed in version 2024.7")

async_add_entities(
[TimeDateSensor(hass, variable) for variable in config[CONF_DISPLAY_OPTIONS]]
[TimeDateSensor(variable) for variable in config[CONF_DISPLAY_OPTIONS]]
)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Time & Date sensor."""

async_add_entities(
[TimeDateSensor(entry.options[CONF_DISPLAY_OPTIONS], entry.entry_id)]
)


class TimeDateSensor(SensorEntity):
"""Implementation of a Time and Date sensor."""

_attr_should_poll = False
_attr_has_entity_name = True
_state: str | None = None
unsub: CALLBACK_TYPE | None = None

def __init__(self, hass: HomeAssistant, option_type: str) -> None:
def __init__(self, option_type: str, entry_id: str | None = None) -> None:
"""Initialize the sensor."""
self._name = OPTION_TYPES[option_type]
self._attr_translation_key = option_type
self.type = option_type
self._state: str | None = None
self.hass = hass
self.unsub: CALLBACK_TYPE | None = None
object_id = "internet_time" if option_type == "beat" else option_type
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._attr_unique_id = option_type if entry_id else None

@property
def name(self) -> str:
"""Return the name of the sensor."""
return self._name
self._update_internal_state(dt_util.utcnow())

@property
def native_value(self) -> str | None:
Expand All @@ -107,6 +114,35 @@ def icon(self) -> str:
return "mdi:calendar"
return "mdi:clock"

@callback
def async_start_preview(
self,
preview_callback: Callable[[str, Mapping[str, Any]], None],
) -> CALLBACK_TYPE:
"""Render a preview."""

@callback
def point_in_time_listener(time_date: datetime | None) -> None:
"""Update preview."""

now = dt_util.utcnow()
self._update_internal_state(now)
self.unsub = async_track_point_in_utc_time(
self.hass, point_in_time_listener, self.get_next_interval(now)
)
calculated_state = self._async_calculate_state()
preview_callback(calculated_state.state, calculated_state.attributes)

@callback
def async_stop_preview() -> None:
"""Stop preview."""
if self.unsub:
self.unsub()
self.unsub = None

point_in_time_listener(None)
return async_stop_preview

async def async_added_to_hass(self) -> None:
"""Set up first update."""

Expand Down
77 changes: 76 additions & 1 deletion homeassistant/components/time_date/strings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,83 @@
{
"title": "Time & Date",
"config": {
"abort": {
"already_configured": "The chosen Time & Date sensor has already been configured"
},
"step": {
"user": {
"description": "Select from the sensor options below",
"data": {
"display_options": "Sensor type"
}
}
},
"error": {
"timezone_not_exist": "Timezone is not set in Home Assistant configuration"
}
},
"options": {
"step": {
"init": {
"data": {
"display_options": "[%key:component::time_date::config::step::user::data::display_options%]"
}
}
}
},
"selector": {
"display_options": {
"options": {
"time": "Time",
"date": "Date",
"date_time": "Date & Time",
"date_time_utc": "Date & Time (UTC)",
"date_time_iso": "Date & Time (ISO)",
"time_date": "Time & Date",
"beat": "Internet time",
"time_utc": "Time (UTC)"
}
}
},
"entity": {
"sensor": {
"time": {
"name": "[%key:component::time_date::selector::display_options::options::time%]"
},
"date": {
"name": "[%key:component::time_date::selector::display_options::options::date%]"
},
"date_time": {
"name": "[%key:component::time_date::selector::display_options::options::date_time%]"
},
"date_time_utc": {
"name": "[%key:component::time_date::selector::display_options::options::date_time_utc%]"
},
"date_time_iso": {
"name": "[%key:component::time_date::selector::display_options::options::date_time_iso%]"
},
"time_date": {
"name": "[%key:component::time_date::selector::display_options::options::time_date%]"
},
"beat": {
"name": "[%key:component::time_date::selector::display_options::options::beat%]"
},
"time_utc": {
"name": "[%key:component::time_date::selector::display_options::options::time_utc%]"
}
}
},
"issues": {
"deprecated_beat": {
"title": "The `{config_key}` Time & Date sensor is being removed",
"description": "Please remove the `{config_key}` key from the `{display_options}` for the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue."
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::time_date::issues::deprecated_beat::title%]",
"description": "Please remove the `{config_key}` key from the {integration} config entry options and click submit to fix this issue."
}
}
}
}
}
}
Loading

0 comments on commit e22adf2

Please sign in to comment.