Files
core/homeassistant/components/miele/services.py
2025-11-14 09:10:24 +01:00

240 lines
7.5 KiB
Python

"""Services for Miele integration."""
from datetime import timedelta
import logging
from typing import cast
from aiohttp import ClientResponseError
import voluptuous as vol
from homeassistant.const import ATTR_DEVICE_ID, ATTR_TEMPERATURE
from homeassistant.core import (
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
)
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.service import async_extract_config_entry_ids
from .const import DOMAIN
from .coordinator import MieleConfigEntry
ATTR_PROGRAM_ID = "program_id"
ATTR_DURATION = "duration"
SERVICE_SET_PROGRAM = "set_program"
SERVICE_SET_PROGRAM_SCHEMA = vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): str,
vol.Required(ATTR_PROGRAM_ID): cv.positive_int,
},
)
SERVICE_SET_PROGRAM_OVEN = "set_program_oven"
SERVICE_SET_PROGRAM_OVEN_SCHEMA = vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): str,
vol.Required(ATTR_PROGRAM_ID): cv.positive_int,
vol.Optional(ATTR_TEMPERATURE): cv.positive_int,
vol.Optional(ATTR_DURATION): vol.All(
cv.time_period,
vol.Range(min=timedelta(minutes=1), max=timedelta(hours=12)),
),
},
)
SERVICE_GET_PROGRAMS = "get_programs"
SERVICE_GET_PROGRAMS_SCHEMA = vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): str,
},
)
_LOGGER = logging.getLogger(__name__)
async def _extract_config_entry(service_call: ServiceCall) -> MieleConfigEntry:
"""Extract config entry from the service call."""
target_entry_ids = await async_extract_config_entry_ids(service_call)
target_entries: list[MieleConfigEntry] = [
loaded_entry
for loaded_entry in service_call.hass.config_entries.async_loaded_entries(
DOMAIN
)
if loaded_entry.entry_id in target_entry_ids
]
if not target_entries:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_target",
)
return target_entries[0]
async def _get_serial_number(call: ServiceCall) -> str:
"""Extract the serial number from the device identifier."""
device_reg = dr.async_get(call.hass)
device = call.data[ATTR_DEVICE_ID]
device_entry = device_reg.async_get(device)
serial_number = next(
(
identifier[1]
for identifier in cast(dr.DeviceEntry, device_entry).identifiers
if identifier[0] == DOMAIN
),
None,
)
if serial_number is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_target",
)
return serial_number
async def set_program(call: ServiceCall) -> None:
"""Set a program on a Miele appliance."""
_LOGGER.debug("Set program call: %s", call)
config_entry = await _extract_config_entry(call)
api = config_entry.runtime_data.api
serial_number = await _get_serial_number(call)
data = {"programId": call.data[ATTR_PROGRAM_ID]}
try:
await api.set_program(serial_number, data)
except ClientResponseError as ex:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_program_error",
translation_placeholders={
"status": str(ex.status),
"message": ex.message,
},
) from ex
async def set_program_oven(call: ServiceCall) -> None:
"""Set a program on a Miele oven."""
_LOGGER.debug("Set program call: %s", call)
config_entry = await _extract_config_entry(call)
api = config_entry.runtime_data.api
serial_number = await _get_serial_number(call)
data = {"programId": call.data[ATTR_PROGRAM_ID]}
if call.data.get(ATTR_DURATION) is not None:
td = call.data[ATTR_DURATION]
data["duration"] = [
td.seconds // 3600, # hours
(td.seconds // 60) % 60, # minutes
]
if call.data.get(ATTR_TEMPERATURE) is not None:
data["temperature"] = call.data[ATTR_TEMPERATURE]
try:
await api.set_program(serial_number, data)
except ClientResponseError as ex:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_program_oven_error",
translation_placeholders={
"status": str(ex.status),
"message": ex.message,
},
) from ex
async def get_programs(call: ServiceCall) -> ServiceResponse:
"""Get available programs from appliance."""
config_entry = await _extract_config_entry(call)
api = config_entry.runtime_data.api
serial_number = await _get_serial_number(call)
try:
programs = await api.get_programs(serial_number)
except ClientResponseError as ex:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="get_programs_error",
translation_placeholders={
"status": str(ex.status),
"message": ex.message,
},
) from ex
return {
"programs": [
{
"program_id": item["programId"],
"program": item["program"].strip(),
"parameters": (
{
"temperature": (
{
"min": item["parameters"]["temperature"]["min"],
"max": item["parameters"]["temperature"]["max"],
"step": item["parameters"]["temperature"]["step"],
"mandatory": item["parameters"]["temperature"][
"mandatory"
],
}
if "temperature" in item["parameters"]
else {}
),
"duration": (
{
"min": {
"hours": item["parameters"]["duration"]["min"][0],
"minutes": item["parameters"]["duration"]["min"][1],
},
"max": {
"hours": item["parameters"]["duration"]["max"][0],
"minutes": item["parameters"]["duration"]["max"][1],
},
"mandatory": item["parameters"]["duration"][
"mandatory"
],
}
if "duration" in item["parameters"]
else {}
),
}
if item.get("parameters")
else {}
),
}
for item in programs
],
}
async def async_setup_services(hass: HomeAssistant) -> None:
"""Set up services."""
hass.services.async_register(
DOMAIN,
SERVICE_SET_PROGRAM,
set_program,
SERVICE_SET_PROGRAM_SCHEMA,
)
hass.services.async_register(
DOMAIN,
SERVICE_SET_PROGRAM_OVEN,
set_program_oven,
SERVICE_SET_PROGRAM_OVEN_SCHEMA,
)
hass.services.async_register(
DOMAIN,
SERVICE_GET_PROGRAMS,
get_programs,
SERVICE_GET_PROGRAMS_SCHEMA,
supports_response=SupportsResponse.ONLY,
)