music-assistant-server

7.2 KBPY
schema.py
7.2 KB311 lines • python
1"""
2Dataclass models for the Yandex Smart Home API.
3
4Covers device descriptions, capability states, action requests/results,
5callback payloads, and cloud WebSocket messages.
6
7Reference: https://yandex.ru/dev/dialogs/smart-home/doc/concepts/platform-protocol.html
8Reference: https://github.com/dext0r/yandex_smart_home
9"""
10
11from __future__ import annotations
12
13from dataclasses import dataclass, field
14from enum import StrEnum
15from typing import Any
16
17# ---------------------------------------------------------------------------
18# Enums
19# ---------------------------------------------------------------------------
20
21
22class YandexDeviceType(StrEnum):
23    """Yandex Smart Home device types relevant to MA players."""
24
25    MEDIA_DEVICE = "devices.types.media_device"
26    MEDIA_DEVICE_RECEIVER = "devices.types.media_device.receiver"
27
28
29class YandexCapabilityType(StrEnum):
30    """Yandex Smart Home capability types."""
31
32    ON_OFF = "devices.capabilities.on_off"
33    RANGE = "devices.capabilities.range"
34    TOGGLE = "devices.capabilities.toggle"
35    MODE = "devices.capabilities.mode"
36
37
38class YandexRangeInstance(StrEnum):
39    """Range capability instances."""
40
41    VOLUME = "volume"
42    CHANNEL = "channel"
43
44
45class YandexModeInstance(StrEnum):
46    """Mode capability instances."""
47
48    INPUT_SOURCE = "input_source"
49
50
51class YandexToggleInstance(StrEnum):
52    """Toggle capability instances."""
53
54    MUTE = "mute"
55    PAUSE = "pause"
56
57
58class YandexResponseCode(StrEnum):
59    """Yandex Smart Home API response/error codes."""
60
61    DONE = "DONE"
62    DEVICE_UNREACHABLE = "DEVICE_UNREACHABLE"
63    INVALID_ACTION = "INVALID_ACTION"
64    INTERNAL_ERROR = "INTERNAL_ERROR"
65    DEVICE_NOT_FOUND = "DEVICE_NOT_FOUND"
66
67
68# ---------------------------------------------------------------------------
69# Device description — returned by /user/devices
70# ---------------------------------------------------------------------------
71
72
73@dataclass
74class RangeParameters:
75    """Range capability parameters (min/max/precision)."""
76
77    min: float = 0
78    max: float = 100
79    precision: float = 1
80
81
82@dataclass
83class ModeValue:
84    """A single mode value for mode capabilities."""
85
86    value: str
87
88
89@dataclass
90class CapabilityParameters:
91    """Parameters block inside a capability description."""
92
93    instance: str
94    range: RangeParameters | None = None
95    unit: str | None = None
96    random_access: bool | None = None
97    modes: list[ModeValue] | None = None
98
99
100@dataclass
101class CapabilityDescription:
102    """A single capability in a device description."""
103
104    type: str
105    retrievable: bool = True
106    reportable: bool = True
107    parameters: CapabilityParameters | None = None
108
109
110@dataclass
111class YandexDeviceInfo:
112    """Device info block."""
113
114    manufacturer: str = "Music Assistant"
115    model: str = "MA Player"
116    sw_version: str | None = None
117
118
119@dataclass
120class DeviceDescription:
121    """Full device description for /user/devices response."""
122
123    id: str
124    name: str
125    type: str
126    capabilities: list[CapabilityDescription] = field(default_factory=list)
127    device_info: YandexDeviceInfo | None = None
128    room: str | None = None
129    description: str | None = None
130
131
132# ---------------------------------------------------------------------------
133# Capability state — for /user/devices/query and state callbacks
134# ---------------------------------------------------------------------------
135
136
137@dataclass
138class CapabilityInstanceState:
139    """State of a specific capability instance."""
140
141    instance: str
142    value: Any
143
144
145@dataclass
146class CapabilityState:
147    """A capability with its current state."""
148
149    type: str
150    state: CapabilityInstanceState
151
152
153@dataclass
154class DeviceState:
155    """State of a single device (for query or callback)."""
156
157    id: str
158    capabilities: list[CapabilityState] = field(default_factory=list)
159    error_code: str | None = None
160    error_message: str | None = None
161
162
163# ---------------------------------------------------------------------------
164# Action request — from /user/devices/action
165# ---------------------------------------------------------------------------
166
167
168@dataclass
169class CapabilityActionState:
170    """State portion of an action request capability."""
171
172    instance: str
173    value: Any
174    relative: bool = False
175
176
177@dataclass
178class CapabilityAction:
179    """A single capability action from Yandex."""
180
181    type: str
182    state: CapabilityActionState
183
184
185@dataclass
186class DeviceAction:
187    """Action request for a single device."""
188
189    id: str
190    capabilities: list[CapabilityAction] = field(default_factory=list)
191
192
193@dataclass
194class ActionRequestPayload:
195    """Payload of /user/devices/action request."""
196
197    devices: list[DeviceAction] = field(default_factory=list)
198
199
200# ---------------------------------------------------------------------------
201# Action result
202# ---------------------------------------------------------------------------
203
204
205@dataclass
206class ActionResult:
207    """Result of executing a single capability action."""
208
209    status: str = "DONE"
210    error_code: str | None = None
211    error_message: str | None = None
212
213
214@dataclass
215class CapabilityActionResultState:
216    """
217    State with action result for a single capability in an action response.
218
219    Per Yandex Smart Home API, action_result goes inside 'state' alongside instance.
220    """
221
222    instance: str
223    value: Any = None
224    action_result: ActionResult = field(default_factory=ActionResult)
225
226
227@dataclass
228class CapabilityActionResult:
229    """Result for a single capability in an action response."""
230
231    type: str
232    state: CapabilityActionResultState
233
234
235@dataclass
236class DeviceActionResult:
237    """Action results for a single device."""
238
239    id: str
240    capabilities: list[CapabilityActionResult] = field(default_factory=list)
241
242
243# ---------------------------------------------------------------------------
244# Response payloads
245# ---------------------------------------------------------------------------
246
247
248@dataclass
249class DeviceListPayload:
250    """Payload for /user/devices response."""
251
252    user_id: str
253    devices: list[DeviceDescription] = field(default_factory=list)
254
255
256@dataclass
257class DeviceStatesPayload:
258    """Payload for /user/devices/query response."""
259
260    devices: list[DeviceState] = field(default_factory=list)
261
262
263@dataclass
264class ActionResultPayload:
265    """Payload for /user/devices/action response."""
266
267    devices: list[DeviceActionResult] = field(default_factory=list)
268
269
270# ---------------------------------------------------------------------------
271# Callback — state reporting to Yandex
272# ---------------------------------------------------------------------------
273
274
275@dataclass
276class CallbackPayload:
277    """Payload for callback/state POST."""
278
279    user_id: str
280    devices: list[DeviceState] = field(default_factory=list)
281
282
283@dataclass
284class CallbackRequest:
285    """Full callback state request body."""
286
287    ts: float
288    payload: CallbackPayload
289
290
291# ---------------------------------------------------------------------------
292# Cloud WebSocket messages
293# ---------------------------------------------------------------------------
294
295
296@dataclass
297class CloudRequest:
298    """Incoming message from yaha-cloud.ru WebSocket."""
299
300    request_id: str
301    action: str
302    message: dict[str, Any] | None = None
303
304
305@dataclass
306class CloudResponse:
307    """Outgoing response to yaha-cloud.ru WebSocket."""
308
309    request_id: str
310    payload: dict[str, Any] = field(default_factory=dict)
311