Add the notification section to the options flow

Notify target (empty default, household fills their own service),
three strict HH:MM automation times (08:00/15:00/20:00 — the report
deliberately before the midnight sweep), and four independent enable
toggles, all leniently stored and round-tripped.
This commit is contained in:
2026-09-19 14:18:18 -04:00
parent 8c421abeb2
commit 3c832c1cee
3 changed files with 285 additions and 19 deletions
+20
View File
@@ -73,6 +73,26 @@ CONF_UPDATE_INTERVAL = "update_interval"
DEFAULT_UPDATE_INTERVAL = 300
MIN_UPDATE_INTERVAL = 30
# Notification automations (Feature 11). The notify target is a free-text
# Home Assistant service name (e.g. "notify.mobile_app_dads_phone") the
# household fills in — never a hard-coded personal target; the times are
# strict HH:MM local; each of the four automations has an independent
# enable toggle. Per the owner-settled resolution the end-of-day report
# defaults to 20:00 — deliberately BEFORE the midnight missed sweep —
# and never defaults to the rollover time.
CONF_NOTIFY_TARGET = "notify_target"
CONF_MORNING_SUMMARY_TIME = "morning_summary_time"
DEFAULT_MORNING_SUMMARY_TIME = "08:00"
CONF_AFTERNOON_REMINDER_TIME = "afternoon_reminder_time"
DEFAULT_AFTERNOON_REMINDER_TIME = "15:00"
CONF_END_OF_DAY_REPORT_TIME = "end_of_day_report_time"
DEFAULT_END_OF_DAY_REPORT_TIME = "20:00"
CONF_MORNING_SUMMARY_ENABLED = "morning_summary_enabled"
CONF_AFTERNOON_REMINDER_ENABLED = "afternoon_reminder_enabled"
CONF_END_OF_DAY_REPORT_ENABLED = "end_of_day_report_enabled"
CONF_CELEBRATION_ENABLED = "celebration_enabled"
DEFAULT_AUTOMATION_ENABLED = True
# Quest windows (D-008): a definition may declare several day windows;
# each produces its own instance per day and groups the panel's Quest
# Log into three columns. The clock ranges are stored here so they stay
+110 -1
View File
@@ -11,12 +11,24 @@ from homeassistant.helpers import config_validation as cv
from .const import (
CONF_ADMIN_USER_IDS,
CONF_AFTERNOON_REMINDER_ENABLED,
CONF_AFTERNOON_REMINDER_TIME,
CONF_CELEBRATION_ENABLED,
CONF_DAY_ROLLOVER_TIME,
CONF_END_OF_DAY_REPORT_ENABLED,
CONF_END_OF_DAY_REPORT_TIME,
CONF_HORIZON_DAYS,
CONF_MORNING_SUMMARY_ENABLED,
CONF_MORNING_SUMMARY_TIME,
CONF_NOTIFY_TARGET,
CONF_PANEL_IDLE_TIMEOUT,
CONF_UPDATE_INTERVAL,
DEFAULT_AFTERNOON_REMINDER_TIME,
DEFAULT_AUTOMATION_ENABLED,
DEFAULT_DAY_ROLLOVER_TIME,
DEFAULT_END_OF_DAY_REPORT_TIME,
DEFAULT_HORIZON_DAYS,
DEFAULT_MORNING_SUMMARY_TIME,
DEFAULT_PANEL_IDLE_TIMEOUT,
DEFAULT_UPDATE_INTERVAL,
DOMAIN,
@@ -71,9 +83,47 @@ def _async_validate(user_input: dict[str, Any]) -> dict[str, str]:
):
errors[CONF_UPDATE_INTERVAL] = "invalid"
# The notification section (Feature 11) is OPTIONAL in raw submits
# the same way: a missing key keeps the current value. The three
# times are strict HH:MM; the notify target is a non-empty HA
# service name; the toggles are real booleans.
for time_key in (
CONF_MORNING_SUMMARY_TIME,
CONF_AFTERNOON_REMINDER_TIME,
CONF_END_OF_DAY_REPORT_TIME,
):
time_value = user_input.get(time_key)
if time_value is not None and (
not isinstance(time_value, str) or not _parse_time(time_value)
):
errors[time_key] = "invalid_time"
notify_target = user_input.get(CONF_NOTIFY_TARGET)
if notify_target is not None and (
not isinstance(notify_target, str)
or not notify_target.strip()
or notify_target != notify_target.strip()
):
errors[CONF_NOTIFY_TARGET] = "invalid"
for toggle_key in (
CONF_MORNING_SUMMARY_ENABLED,
CONF_AFTERNOON_REMINDER_ENABLED,
CONF_END_OF_DAY_REPORT_ENABLED,
CONF_CELEBRATION_ENABLED,
):
toggle_value = user_input.get(toggle_key)
if toggle_value is not None and not isinstance(toggle_value, bool):
errors[toggle_key] = "invalid"
return errors
def _strict_bool(value: object) -> bool:
"""Require a real bool; voluptuous ``bool`` would coerce strings."""
if not isinstance(value, bool):
raise vol.Invalid(f"expected a boolean, got {value!r}")
return value
def _build_schema(
current: dict[str, Any],
*,
@@ -85,7 +135,10 @@ def _build_schema(
The admin picker is a multi-select of EXISTING Home Assistant users
(never a free-text field — the allowlist stores user IDs); its
default is the current database allowlist, so an untouched submit
re-saves exactly who is already allowed.
re-saves exactly who is already allowed. The notification section
(Feature 11) carries the household's notify target, the three
automation times, and four enable toggles — defaults from
:mod:`~.const`, never a hard-coded personal target.
"""
fields: dict[Any, Any] = {
vol.Required(
@@ -100,6 +153,37 @@ def _build_schema(
vol.Required(
CONF_UPDATE_INTERVAL, default=current[CONF_UPDATE_INTERVAL]
): int,
vol.Required(
CONF_NOTIFY_TARGET, default=current[CONF_NOTIFY_TARGET]
): str,
vol.Required(
CONF_MORNING_SUMMARY_TIME,
default=current[CONF_MORNING_SUMMARY_TIME],
): str,
vol.Required(
CONF_AFTERNOON_REMINDER_TIME,
default=current[CONF_AFTERNOON_REMINDER_TIME],
): str,
vol.Required(
CONF_END_OF_DAY_REPORT_TIME,
default=current[CONF_END_OF_DAY_REPORT_TIME],
): str,
vol.Required(
CONF_MORNING_SUMMARY_ENABLED,
default=current[CONF_MORNING_SUMMARY_ENABLED],
): _strict_bool,
vol.Required(
CONF_AFTERNOON_REMINDER_ENABLED,
default=current[CONF_AFTERNOON_REMINDER_ENABLED],
): _strict_bool,
vol.Required(
CONF_END_OF_DAY_REPORT_ENABLED,
default=current[CONF_END_OF_DAY_REPORT_ENABLED],
): _strict_bool,
vol.Required(
CONF_CELEBRATION_ENABLED,
default=current[CONF_CELEBRATION_ENABLED],
): _strict_bool,
}
if admin_choices is not None:
# HA's supported multi-select validator: the frontend can
@@ -233,6 +317,23 @@ class NestQuestOptionsFlow(config_entries.OptionsFlowWithConfigEntry):
self._current_values()[CONF_UPDATE_INTERVAL],
),
}
# The notification section (Feature 11) stores the household's
# choices the same fall-back-to-current way, so raw submits
# that predate the section keep their stored values.
current = self._current_values()
for notification_key in (
CONF_NOTIFY_TARGET,
CONF_MORNING_SUMMARY_TIME,
CONF_AFTERNOON_REMINDER_TIME,
CONF_END_OF_DAY_REPORT_TIME,
CONF_MORNING_SUMMARY_ENABLED,
CONF_AFTERNOON_REMINDER_ENABLED,
CONF_END_OF_DAY_REPORT_ENABLED,
CONF_CELEBRATION_ENABLED,
):
data[notification_key] = user_input.get(
notification_key, current[notification_key]
)
if admin_ids_present:
data[CONF_ADMIN_USER_IDS] = admin_ids
elif admin_default:
@@ -254,6 +355,14 @@ class NestQuestOptionsFlow(config_entries.OptionsFlowWithConfigEntry):
(CONF_DAY_ROLLOVER_TIME, DEFAULT_DAY_ROLLOVER_TIME),
(CONF_PANEL_IDLE_TIMEOUT, DEFAULT_PANEL_IDLE_TIMEOUT),
(CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL),
(CONF_NOTIFY_TARGET, ""),
(CONF_MORNING_SUMMARY_TIME, DEFAULT_MORNING_SUMMARY_TIME),
(CONF_AFTERNOON_REMINDER_TIME, DEFAULT_AFTERNOON_REMINDER_TIME),
(CONF_END_OF_DAY_REPORT_TIME, DEFAULT_END_OF_DAY_REPORT_TIME),
(CONF_MORNING_SUMMARY_ENABLED, DEFAULT_AUTOMATION_ENABLED),
(CONF_AFTERNOON_REMINDER_ENABLED, DEFAULT_AUTOMATION_ENABLED),
(CONF_END_OF_DAY_REPORT_ENABLED, DEFAULT_AUTOMATION_ENABLED),
(CONF_CELEBRATION_ENABLED, DEFAULT_AUTOMATION_ENABLED),
):
if key in options:
values[key] = options[key]