348 files

This commit is contained in:
Home Assistant Version Control
2026-07-23 19:02:12 +00:00
parent 0ed1687503
commit 016af2e013
348 changed files with 12368 additions and 11689 deletions
+70 -1
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import copy
import logging
from datetime import timedelta
from functools import wraps
from pathlib import Path
@@ -14,6 +15,7 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError, Unauthorized
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.service import async_set_service_schema
from homeassistant.util import dt as dt_util
from .const import (
ATTR_AS_PARENT,
@@ -39,12 +41,14 @@ from .const import (
ATTR_CHILD_ID,
ATTR_CHORE_ASSIGNED_TO,
ATTR_CHORE_DESCRIPTION,
ATTR_CHORE_EXPIRES_IN_MINUTES,
ATTR_CHORE_ID,
ATTR_CHORE_NAME,
ATTR_CHORE_ONE_SHOT,
ATTR_CHORE_ORDER,
ATTR_CHORE_POINTS,
ATTR_CHORE_REQUIRES_APPROVAL,
ATTR_CHORE_SPEED_BONUS_POINTS,
ATTR_CHORE_TIME_CATEGORY,
ATTR_PENALTY_ASSIGNED_TO,
ATTR_PENALTY_DESCRIPTION,
@@ -85,6 +89,7 @@ from .const import (
SERVICE_PAUSE_TIMED_TASK,
SERVICE_POSTPONE_MANDATORY_CHORE,
SERVICE_PREVIEW_SOUND,
SERVICE_READ_ALOUD,
SERVICE_RECORD_ALLOWANCE_PAYOUT,
SERVICE_REJECT_CHORE,
SERVICE_REJECT_REWARD,
@@ -96,6 +101,7 @@ from .const import (
SERVICE_SET_CHORE_MANUAL_START,
SERVICE_SET_CHORE_ORDER,
SERVICE_SKIP_CHORE,
SERVICE_SPIN_ROULETTE,
SERVICE_START_TIMED_TASK,
SERVICE_STOP_TIMED_TASK,
SERVICE_TEST_NOTIFICATION,
@@ -619,6 +625,37 @@ async def _async_register_services(hass: HomeAssistant) -> None:
await _async_require_linked_child(hass, call, coordinator, requester_id)
await coordinator.async_request_swap(call.data["chore_id"], requester_id)
async def handle_read_aloud(call: ServiceCall) -> None:
"""Speak a child's outstanding chores to a media player (#684)."""
coordinator = _get_coordinator(hass)
if not coordinator:
_LOGGER.error("No TaskMate coordinator available")
return
try:
await coordinator.async_read_aloud(
child_id=call.data[ATTR_CHILD_ID],
media_player=call.data.get("media_player", ""),
tts_entity=call.data.get("tts_entity", ""),
message=call.data.get("message", ""),
)
except ValueError as err:
raise ServiceValidationError(str(err)) from err
async def handle_spin_roulette(call: ServiceCall) -> None:
"""A child spins for a random chore at a multiplier (#677)."""
coordinator = _get_coordinator(hass)
if not coordinator:
_LOGGER.error("No TaskMate coordinator available")
return
child_id = call.data[ATTR_CHILD_ID]
await _async_require_linked_child(hass, call, coordinator, child_id)
try:
await coordinator.async_spin_roulette(child_id)
except ValueError as err:
# Roulette off, no spins left, nothing to spin for — all normal
# states the child should be told about, not server errors.
raise ServiceValidationError(str(err)) from err
async def handle_choose_avatar(call: ServiceCall) -> None:
"""A child switches to an avatar they've unlocked."""
coordinator = _get_coordinator(hass)
@@ -882,7 +919,14 @@ async def _async_register_services(hass: HomeAssistant) -> None:
if not coordinator:
_LOGGER.error("No TaskMate coordinator available")
return
schedule_mode = "one_shot" if call.data.get(ATTR_CHORE_ONE_SHOT, False) else "specific_days"
expires_in = call.data.get(ATTR_CHORE_EXPIRES_IN_MINUTES, 0) or 0
# A chore with a deadline is inherently a one-off — it exists to be done
# now, so it should never linger into tomorrow.
one_shot = call.data.get(ATTR_CHORE_ONE_SHOT, False) or bool(expires_in)
schedule_mode = "one_shot" if one_shot else "specific_days"
deadline_at = ""
if expires_in:
deadline_at = (dt_util.now() + timedelta(minutes=expires_in)).isoformat()
await coordinator.async_add_chore(
name=call.data[ATTR_CHORE_NAME],
description=call.data.get(ATTR_CHORE_DESCRIPTION, ""),
@@ -892,6 +936,8 @@ async def _async_register_services(hass: HomeAssistant) -> None:
requires_approval=call.data.get(ATTR_CHORE_REQUIRES_APPROVAL, True),
difficulty=call.data.get("difficulty", "medium"),
schedule_mode=schedule_mode,
deadline_at=deadline_at,
speed_bonus_points=call.data.get(ATTR_CHORE_SPEED_BONUS_POINTS, 0),
)
async def handle_add_badge(call: ServiceCall) -> None:
@@ -1188,6 +1234,25 @@ async def _async_register_services(hass: HomeAssistant) -> None:
),
)
hass.services.async_register(
DOMAIN,
SERVICE_READ_ALOUD,
_safe(handle_read_aloud),
schema=vol.Schema({
vol.Required(ATTR_CHILD_ID): cv.string,
vol.Optional("media_player", default=""): cv.string,
vol.Optional("tts_entity", default=""): cv.string,
vol.Optional("message", default=""): cv.string,
}),
)
hass.services.async_register(
DOMAIN,
SERVICE_SPIN_ROULETTE,
_safe(handle_spin_roulette),
schema=vol.Schema({vol.Required(ATTR_CHILD_ID): cv.string}),
)
hass.services.async_register(
DOMAIN,
SERVICE_CHOOSE_AVATAR,
@@ -1398,6 +1463,10 @@ async def _async_register_services(hass: HomeAssistant) -> None:
vol.Optional("difficulty", default=DEFAULT_DIFFICULTY): vol.In(DIFFICULTY_TIERS),
vol.Optional(ATTR_CHORE_ONE_SHOT, default=False): cv.boolean,
vol.Optional(ATTR_CHORE_REQUIRES_APPROVAL, default=True): cv.boolean,
vol.Optional(ATTR_CHORE_EXPIRES_IN_MINUTES, default=0): vol.All(
cv.positive_int, vol.Range(max=10080)
),
vol.Optional(ATTR_CHORE_SPEED_BONUS_POINTS, default=0): cv.positive_int,
}),
)