171 lines
6.1 KiB
Python
171 lines
6.1 KiB
Python
"""The Yandex Smart Home request handlers."""
|
|
|
|
import logging
|
|
from typing import Any, Callable, Coroutine
|
|
|
|
from homeassistant.const import ATTR_ENTITY_ID
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.util.decorator import Registry
|
|
|
|
from .const import ATTR_CAPABILITY, ATTR_ERROR_CODE, EVENT_DEVICE_ACTION
|
|
from .device import Device, async_get_device_description, async_get_device_states, async_get_devices
|
|
from .helpers import ActionNotAllowed, APIError, RequestData
|
|
from .schema import (
|
|
ActionRequest,
|
|
ActionResult,
|
|
ActionResultCapability,
|
|
ActionResultCapabilityState,
|
|
ActionResultDevice,
|
|
DeviceDescription,
|
|
DeviceList,
|
|
DeviceStates,
|
|
FailedActionResult,
|
|
Response,
|
|
ResponseCode,
|
|
ResponsePayload,
|
|
StatesRequest,
|
|
SuccessActionResult,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
HANDLERS: Registry[
|
|
str,
|
|
Callable[
|
|
[HomeAssistant, RequestData, str],
|
|
Coroutine[Any, Any, ResponsePayload | None],
|
|
],
|
|
] = Registry()
|
|
|
|
|
|
async def async_handle_request(hass: HomeAssistant, data: RequestData, action: str, payload: str) -> Response:
|
|
"""Handle incoming API request."""
|
|
handler = HANDLERS.get(action)
|
|
|
|
if handler is None:
|
|
_LOGGER.error(f"Unexpected action '{action}'")
|
|
return Response(request_id=data.request_id)
|
|
|
|
try:
|
|
return Response(request_id=data.request_id, payload=await handler(hass, data, payload))
|
|
except APIError as err:
|
|
_LOGGER.error(f"{err.message} ({err.code})")
|
|
return Response(request_id=data.request_id)
|
|
except Exception:
|
|
# return always 200 due to blocking error on device page
|
|
_LOGGER.exception("Unexpected exception")
|
|
return Response(request_id=data.request_id)
|
|
|
|
|
|
@HANDLERS.register("/user/devices")
|
|
async def async_device_list(hass: HomeAssistant, data: RequestData, _payload: str) -> DeviceList:
|
|
"""Handle request that return information about supported user devices.
|
|
|
|
https://yandex.ru/dev/dialogs/smart-home/doc/reference/get-devices.html
|
|
"""
|
|
assert data.request_user_id
|
|
|
|
devices: list[DeviceDescription] = []
|
|
for device in await async_get_devices(hass, data.entry_data):
|
|
if (description := await async_get_device_description(hass, device)) is not None:
|
|
devices.append(description)
|
|
|
|
data.entry_data.link_platform(data.platform)
|
|
return DeviceList(user_id=data.request_user_id, devices=devices)
|
|
|
|
|
|
@HANDLERS.register("/user/devices/query")
|
|
async def async_devices_query(hass: HomeAssistant, data: RequestData, payload: str) -> DeviceStates:
|
|
"""Handle request that return information about the states of user devices.
|
|
|
|
https://yandex.ru/dev/dialogs/smart-home/doc/reference/post-devices-query.html
|
|
"""
|
|
request = StatesRequest.parse_raw(payload)
|
|
states = await async_get_device_states(hass, data.entry_data, [rd.id for rd in request.devices])
|
|
return DeviceStates(devices=states)
|
|
|
|
|
|
@HANDLERS.register("/user/devices/action")
|
|
async def async_devices_action(hass: HomeAssistant, data: RequestData, payload: str) -> ActionResult:
|
|
"""Handle request that changes current state of user devices.
|
|
|
|
https://yandex.ru/dev/dialogs/smart-home/doc/reference/post-action.html
|
|
"""
|
|
request = ActionRequest.parse_raw(payload)
|
|
results: list[ActionResultDevice] = []
|
|
|
|
for device_id, actions in [(rd.id, rd.capabilities) for rd in request.payload.devices]:
|
|
state = hass.states.get(device_id)
|
|
device = Device(hass, data.entry_data, device_id, state)
|
|
|
|
if state and not device.should_expose:
|
|
data.entry_data.mark_entity_unexposed(state.entity_id)
|
|
|
|
if device.unavailable:
|
|
hass.bus.async_fire(
|
|
EVENT_DEVICE_ACTION,
|
|
{ATTR_ENTITY_ID: device_id, ATTR_ERROR_CODE: ResponseCode.DEVICE_UNREACHABLE.value},
|
|
context=data.context,
|
|
)
|
|
|
|
results.append(
|
|
ActionResultDevice(
|
|
id=device_id, action_result=FailedActionResult(error_code=ResponseCode.DEVICE_UNREACHABLE)
|
|
)
|
|
)
|
|
continue
|
|
|
|
capability_results: list[ActionResultCapability] = []
|
|
for action in actions:
|
|
try:
|
|
value = await device.execute(data.context, action)
|
|
hass.bus.async_fire(
|
|
EVENT_DEVICE_ACTION,
|
|
{ATTR_ENTITY_ID: device_id, ATTR_CAPABILITY: action.as_dict()},
|
|
context=data.context,
|
|
)
|
|
except (APIError, ActionNotAllowed) as err:
|
|
if isinstance(err, APIError):
|
|
_LOGGER.error(f"{err.message} ({err.code.value})")
|
|
|
|
hass.bus.async_fire(
|
|
EVENT_DEVICE_ACTION,
|
|
{ATTR_ENTITY_ID: device_id, ATTR_CAPABILITY: action.as_dict(), ATTR_ERROR_CODE: err.code.value},
|
|
context=data.context,
|
|
)
|
|
|
|
capability_results.append(
|
|
ActionResultCapability(
|
|
type=action.type,
|
|
state=ActionResultCapabilityState(
|
|
instance=action.state.instance,
|
|
action_result=FailedActionResult(error_code=ResponseCode(err.code)),
|
|
),
|
|
)
|
|
)
|
|
continue
|
|
|
|
capability_results.append(
|
|
ActionResultCapability(
|
|
type=action.type,
|
|
state=ActionResultCapabilityState(
|
|
instance=action.state.instance,
|
|
value=value,
|
|
action_result=SuccessActionResult(),
|
|
),
|
|
)
|
|
)
|
|
|
|
results.append(ActionResultDevice(id=device_id, capabilities=capability_results))
|
|
|
|
return ActionResult(devices=results)
|
|
|
|
|
|
@HANDLERS.register("/user/unlink")
|
|
async def async_user_unlink(_hass: HomeAssistant, data: RequestData, _payload: str) -> None:
|
|
"""Handle request indicates that the user has unlink the account.
|
|
|
|
https://yandex.ru/dev/dialogs/smart-home/doc/reference/unlink.html
|
|
"""
|
|
data.entry_data.unlink_platform(data.platform)
|