python
This commit is contained in:
16
custom_components/yandex_smart_home/schema/__init__.py
Normal file
16
custom_components/yandex_smart_home/schema/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Yandex Smart Home API schemas."""
|
||||
|
||||
# ruff: noqa
|
||||
from .callback import *
|
||||
from .capability import *
|
||||
from .capability_color import *
|
||||
from .capability_mode import *
|
||||
from .capability_onoff import *
|
||||
from .capability_range import *
|
||||
from .capability_toggle import *
|
||||
from .capability_video import *
|
||||
from .device import *
|
||||
from .property import *
|
||||
from .property_event import *
|
||||
from .property_float import *
|
||||
from .response import *
|
||||
46
custom_components/yandex_smart_home/schema/base.py
Normal file
46
custom_components/yandex_smart_home/schema/base.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Base class for API response schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, model_serializer
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class APIModel(BaseModel):
|
||||
"""Base API response model."""
|
||||
|
||||
model_config = ConfigDict(
|
||||
extra="forbid",
|
||||
populate_by_name=True,
|
||||
arbitrary_types_allowed=True,
|
||||
validate_assignment=True,
|
||||
# strict=False, # можно включить, если нужно более строгое приведение типов
|
||||
)
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def serialize(self, handler, info):
|
||||
"""Serialize model excluding None values."""
|
||||
dumped = handler(self)
|
||||
if info.mode == "json":
|
||||
# для json() — исключаем None и используем ensure_ascii=False
|
||||
return {k: v for k, v in dumped.items() if v is not None}
|
||||
return dumped
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
"""Generate a dictionary representation of the model (exclude None)."""
|
||||
return self.model_dump(exclude_none=True, by_alias=False)
|
||||
|
||||
def as_json(self) -> str:
|
||||
"""Generate a JSON representation of the model (exclude None, ensure_ascii=False)."""
|
||||
return self.model_dump_json(exclude_none=True, ensure_ascii=False)
|
||||
|
||||
|
||||
class GenericAPIModel(APIModel, Generic[T]):
|
||||
"""Base generic API response model."""
|
||||
|
||||
# В pydantic v2 GenericModel больше не нужен — достаточно Generic + BaseModel
|
||||
# Если нужно что-то специфическое — добавляй поля/валидаторы здесь
|
||||
pass
|
||||
59
custom_components/yandex_smart_home/schema/callback.py
Normal file
59
custom_components/yandex_smart_home/schema/callback.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""Schema for event notification service.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/reference-alerts/resources-alerts.html
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
import time
|
||||
from typing import Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .base import APIModel
|
||||
from .device import DeviceState
|
||||
|
||||
|
||||
class CallbackStatesRequestPayload(APIModel):
|
||||
"""Payload of request body for notification about device state change."""
|
||||
|
||||
user_id: str
|
||||
devices: list[DeviceState]
|
||||
|
||||
|
||||
class CallbackStatesRequest(APIModel):
|
||||
"""Request body for notification about device state change."""
|
||||
|
||||
ts: float = Field(default_factory=time.time)
|
||||
payload: CallbackStatesRequestPayload
|
||||
|
||||
|
||||
class CallbackDiscoveryRequestPayload(APIModel):
|
||||
"""Payload of request body for notification about change of devices' parameters."""
|
||||
|
||||
user_id: str
|
||||
|
||||
|
||||
class CallbackDiscoveryRequest(APIModel):
|
||||
"""Request body for notification about change of devices' parameters."""
|
||||
|
||||
ts: float = Field(default_factory=time.time)
|
||||
payload: CallbackDiscoveryRequestPayload
|
||||
|
||||
|
||||
class CallbackResponseStatus(StrEnum):
|
||||
"""Status of a callback request."""
|
||||
|
||||
OK = "ok"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
class CallbackResponse(APIModel):
|
||||
"""Response on a callback request."""
|
||||
|
||||
status: CallbackResponseStatus
|
||||
error_code: str | None = None
|
||||
error_message: str | None = None
|
||||
|
||||
|
||||
CallbackRequest: type = Union[CallbackDiscoveryRequest, CallbackStatesRequest]
|
||||
139
custom_components/yandex_smart_home/schema/capability.py
Normal file
139
custom_components/yandex_smart_home/schema/capability.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""Schema for device capabilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Annotated, Any, Literal, TypeVar, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .base import APIModel
|
||||
from .capability_color import (
|
||||
ColorSettingCapabilityInstance,
|
||||
ColorSettingCapabilityInstanceActionState,
|
||||
ColorSettingCapabilityParameters,
|
||||
RGBInstanceActionState,
|
||||
SceneInstanceActionState,
|
||||
TemperatureKInstanceActionState,
|
||||
)
|
||||
from .capability_mode import ModeCapabilityInstance, ModeCapabilityInstanceActionState, ModeCapabilityParameters
|
||||
from .capability_onoff import OnOffCapabilityInstance, OnOffCapabilityInstanceActionState, OnOffCapabilityParameters
|
||||
from .capability_range import RangeCapabilityInstance, RangeCapabilityInstanceActionState, RangeCapabilityParameters
|
||||
from .capability_toggle import ToggleCapabilityInstance, ToggleCapabilityInstanceActionState, ToggleCapabilityParameters
|
||||
from .capability_video import (
|
||||
GetStreamInstanceActionResultValue,
|
||||
GetStreamInstanceActionState,
|
||||
VideoStreamCapabilityInstance,
|
||||
VideoStreamCapabilityParameters,
|
||||
)
|
||||
|
||||
|
||||
class CapabilityType(StrEnum):
|
||||
ON_OFF = "devices.capabilities.on_off"
|
||||
COLOR_SETTING = "devices.capabilities.color_setting"
|
||||
MODE = "devices.capabilities.mode"
|
||||
RANGE = "devices.capabilities.range"
|
||||
TOGGLE = "devices.capabilities.toggle"
|
||||
VIDEO_STREAM = "devices.capabilities.video_stream"
|
||||
|
||||
@property
|
||||
def short(self) -> str:
|
||||
return str(self).replace("devices.capabilities.", "")
|
||||
|
||||
|
||||
CapabilityParameters = Union[
|
||||
OnOffCapabilityParameters,
|
||||
ColorSettingCapabilityParameters,
|
||||
ModeCapabilityParameters,
|
||||
RangeCapabilityParameters,
|
||||
ToggleCapabilityParameters,
|
||||
VideoStreamCapabilityParameters,
|
||||
]
|
||||
|
||||
|
||||
CapabilityInstance = Union[
|
||||
OnOffCapabilityInstance,
|
||||
ColorSettingCapabilityInstance,
|
||||
ModeCapabilityInstance,
|
||||
RangeCapabilityInstance,
|
||||
ToggleCapabilityInstance,
|
||||
VideoStreamCapabilityInstance,
|
||||
]
|
||||
|
||||
|
||||
class CapabilityDescription(APIModel):
|
||||
type: CapabilityType
|
||||
retrievable: bool
|
||||
reportable: bool
|
||||
parameters: CapabilityParameters | None = None
|
||||
|
||||
|
||||
class CapabilityInstanceStateValue(APIModel):
|
||||
instance: CapabilityInstance
|
||||
value: Any
|
||||
|
||||
|
||||
class CapabilityInstanceState(APIModel):
|
||||
type: CapabilityType
|
||||
state: CapabilityInstanceStateValue
|
||||
|
||||
|
||||
class OnOffCapabilityInstanceAction(APIModel):
|
||||
type: Literal[CapabilityType.ON_OFF] = CapabilityType.ON_OFF
|
||||
state: OnOffCapabilityInstanceActionState
|
||||
|
||||
|
||||
class ColorSettingCapabilityInstanceAction(APIModel):
|
||||
type: Literal[CapabilityType.COLOR_SETTING] = CapabilityType.COLOR_SETTING
|
||||
state: ColorSettingCapabilityInstanceActionState
|
||||
|
||||
|
||||
class ModeCapabilityInstanceAction(APIModel):
|
||||
type: Literal[CapabilityType.MODE] = CapabilityType.MODE
|
||||
state: ModeCapabilityInstanceActionState
|
||||
|
||||
|
||||
class RangeCapabilityInstanceAction(APIModel):
|
||||
type: Literal[CapabilityType.RANGE] = CapabilityType.RANGE
|
||||
state: RangeCapabilityInstanceActionState
|
||||
|
||||
|
||||
class ToggleCapabilityInstanceAction(APIModel):
|
||||
type: Literal[CapabilityType.TOGGLE] = CapabilityType.TOGGLE
|
||||
state: ToggleCapabilityInstanceActionState
|
||||
|
||||
|
||||
class VideoStreamCapabilityInstanceAction(APIModel):
|
||||
type: Literal[CapabilityType.VIDEO_STREAM] = CapabilityType.VIDEO_STREAM
|
||||
state: GetStreamInstanceActionState
|
||||
|
||||
|
||||
CapabilityInstanceAction = Annotated[
|
||||
Union[
|
||||
OnOffCapabilityInstanceAction,
|
||||
ColorSettingCapabilityInstanceAction,
|
||||
ModeCapabilityInstanceAction,
|
||||
RangeCapabilityInstanceAction,
|
||||
ToggleCapabilityInstanceAction,
|
||||
VideoStreamCapabilityInstanceAction,
|
||||
],
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
CapabilityInstanceActionState = TypeVar(
|
||||
"CapabilityInstanceActionState",
|
||||
OnOffCapabilityInstanceActionState,
|
||||
ColorSettingCapabilityInstanceActionState,
|
||||
RGBInstanceActionState,
|
||||
TemperatureKInstanceActionState,
|
||||
SceneInstanceActionState,
|
||||
ModeCapabilityInstanceActionState,
|
||||
RangeCapabilityInstanceActionState,
|
||||
ToggleCapabilityInstanceActionState,
|
||||
GetStreamInstanceActionState,
|
||||
contravariant=True,
|
||||
)
|
||||
|
||||
|
||||
CapabilityInstanceActionResultValue = GetStreamInstanceActionResultValue | None
|
||||
116
custom_components/yandex_smart_home/schema/capability_color.py
Normal file
116
custom_components/yandex_smart_home/schema/capability_color.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""Schema for color_setting capability.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/color_setting.html
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Annotated, Any, Literal, Optional, Self, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from .base import APIModel
|
||||
|
||||
|
||||
class ColorSettingCapabilityInstance(StrEnum):
|
||||
"""Instance of a color_setting capability."""
|
||||
|
||||
BASE = "base"
|
||||
RGB = "rgb"
|
||||
HSV = "hsv"
|
||||
TEMPERATURE_K = "temperature_k"
|
||||
SCENE = "scene"
|
||||
|
||||
|
||||
class ColorScene(StrEnum):
|
||||
"""Color scene."""
|
||||
|
||||
ALARM = "alarm"
|
||||
ALICE = "alice"
|
||||
CANDLE = "candle"
|
||||
DINNER = "dinner"
|
||||
FANTASY = "fantasy"
|
||||
GARLAND = "garland"
|
||||
JUNGLE = "jungle"
|
||||
MOVIE = "movie"
|
||||
NEON = "neon"
|
||||
NIGHT = "night"
|
||||
OCEAN = "ocean"
|
||||
PARTY = "party"
|
||||
READING = "reading"
|
||||
REST = "rest"
|
||||
ROMANCE = "romance"
|
||||
SIREN = "siren"
|
||||
SUNRISE = "sunrise"
|
||||
SUNSET = "sunset"
|
||||
|
||||
|
||||
class CapabilityParameterColorModel(StrEnum):
|
||||
"""Color model."""
|
||||
|
||||
RGB = "rgb"
|
||||
HSV = "hsv"
|
||||
|
||||
|
||||
class CapabilityParameterTemperatureK(APIModel):
|
||||
"""Color temperature range."""
|
||||
|
||||
min: int
|
||||
max: int
|
||||
|
||||
|
||||
class CapabilityParameterColorScene(APIModel):
|
||||
"""Parameter of a scene instance."""
|
||||
|
||||
scenes: list[dict[Literal["id"], ColorScene]]
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, scenes: list[ColorScene]) -> Self:
|
||||
return cls(scenes=[{"id": s} for s in scenes])
|
||||
|
||||
|
||||
class ColorSettingCapabilityParameters(APIModel):
|
||||
"""Parameters of a color_setting capability."""
|
||||
|
||||
color_model: Optional[CapabilityParameterColorModel] = None
|
||||
temperature_k: Optional[CapabilityParameterTemperatureK] = None
|
||||
color_scene: Optional[CapabilityParameterColorScene] = None
|
||||
|
||||
@model_validator(mode='after')
|
||||
def any_of(self) -> Self:
|
||||
"""Проверяем, что хотя бы одно поле заполнено (после полной валидации)."""
|
||||
if not any([
|
||||
self.color_model is not None,
|
||||
self.temperature_k is not None,
|
||||
self.color_scene is not None,
|
||||
]):
|
||||
raise ValueError("one of color_model, temperature_k or color_scene must have a value")
|
||||
return self
|
||||
|
||||
|
||||
class RGBInstanceActionState(APIModel):
|
||||
"""New value for a rgb instance."""
|
||||
|
||||
instance: Literal[ColorSettingCapabilityInstance.RGB] = ColorSettingCapabilityInstance.RGB
|
||||
value: int
|
||||
|
||||
|
||||
class TemperatureKInstanceActionState(APIModel):
|
||||
"""New value for a temperature_k instance."""
|
||||
|
||||
instance: Literal[ColorSettingCapabilityInstance.TEMPERATURE_K] = ColorSettingCapabilityInstance.TEMPERATURE_K
|
||||
value: int
|
||||
|
||||
|
||||
class SceneInstanceActionState(APIModel):
|
||||
"""New value for a scene instance."""
|
||||
|
||||
instance: Literal[ColorSettingCapabilityInstance.SCENE] = ColorSettingCapabilityInstance.SCENE
|
||||
value: ColorScene
|
||||
|
||||
|
||||
ColorSettingCapabilityInstanceActionState = Annotated[
|
||||
Union[RGBInstanceActionState, TemperatureKInstanceActionState, SceneInstanceActionState],
|
||||
Field(discriminator="instance"),
|
||||
]
|
||||
"""New value for an instance of color_setting capability."""
|
||||
133
custom_components/yandex_smart_home/schema/capability_mode.py
Normal file
133
custom_components/yandex_smart_home/schema/capability_mode.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""Schema for mode capability.
|
||||
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/mode.html
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Literal, Self
|
||||
|
||||
from .base import APIModel
|
||||
|
||||
|
||||
class ModeCapabilityInstance(StrEnum):
|
||||
"""Instance of a mode capability.
|
||||
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/mode-instance.html
|
||||
"""
|
||||
|
||||
CLEANUP_MODE = "cleanup_mode"
|
||||
COFFEE_MODE = "coffee_mode"
|
||||
DISHWASHING = "dishwashing"
|
||||
FAN_SPEED = "fan_speed"
|
||||
HEAT = "heat"
|
||||
INPUT_SOURCE = "input_source"
|
||||
PROGRAM = "program"
|
||||
SWING = "swing"
|
||||
TEA_MODE = "tea_mode"
|
||||
THERMOSTAT = "thermostat"
|
||||
VENTILATION_MODE = "ventilation_mode"
|
||||
WORK_SPEED = "work_speed"
|
||||
|
||||
|
||||
class ModeCapabilityMode(StrEnum):
|
||||
"""Mode value of a mode capability.
|
||||
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/mode-instance-modes.html
|
||||
"""
|
||||
|
||||
WET_CLEANING = "wet_cleaning"
|
||||
DRY_CLEANING = "dry_cleaning"
|
||||
MIXED_CLEANING = "mixed_cleaning"
|
||||
AUTO = "auto"
|
||||
ECO = "eco"
|
||||
SMART = "smart"
|
||||
TURBO = "turbo"
|
||||
COOL = "cool"
|
||||
DRY = "dry"
|
||||
FAN_ONLY = "fan_only"
|
||||
HEAT = "heat"
|
||||
PREHEAT = "preheat"
|
||||
HIGH = "high"
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
MAX = "max"
|
||||
MIN = "min"
|
||||
FAST = "fast"
|
||||
SLOW = "slow"
|
||||
EXPRESS = "express"
|
||||
NORMAL = "normal"
|
||||
QUIET = "quiet"
|
||||
HORIZONTAL = "horizontal"
|
||||
STATIONARY = "stationary"
|
||||
VERTICAL = "vertical"
|
||||
SUPPLY_AIR = "supply_air"
|
||||
EXTRACTION_AIR = "extraction_air"
|
||||
ONE = "one"
|
||||
TWO = "two"
|
||||
THREE = "three"
|
||||
FOUR = "four"
|
||||
FIVE = "five"
|
||||
SIX = "six"
|
||||
SEVEN = "seven"
|
||||
EIGHT = "eight"
|
||||
NINE = "nine"
|
||||
TEN = "ten"
|
||||
AMERICANO = "americano"
|
||||
CAPPUCCINO = "cappuccino"
|
||||
DOUBLE = "double"
|
||||
ESPRESSO = "espresso"
|
||||
DOUBLE_ESPRESSO = "double_espresso"
|
||||
LATTE = "latte"
|
||||
BLACK_TEA = "black_tea"
|
||||
FLOWER_TEA = "flower_tea"
|
||||
GREEN_TEA = "green_tea"
|
||||
HERBAL_TEA = "herbal_tea"
|
||||
OOLONG_TEA = "oolong_tea"
|
||||
PUERH_TEA = "puerh_tea"
|
||||
RED_TEA = "red_tea"
|
||||
WHITE_TEA = "white_tea"
|
||||
GLASS = "glass"
|
||||
INTENSIVE = "intensive"
|
||||
PRE_RINSE = "pre_rinse"
|
||||
ASPIC = "aspic"
|
||||
BABY_FOOD = "baby_food"
|
||||
BAKING = "baking"
|
||||
BREAD = "bread"
|
||||
BOILING = "boiling"
|
||||
CEREALS = "cereals"
|
||||
CHEESECAKE = "cheesecake"
|
||||
DEEP_FRYER = "deep_fryer"
|
||||
DESSERT = "dessert"
|
||||
FOWL = "fowl"
|
||||
FRYING = "frying"
|
||||
MACARONI = "macaroni"
|
||||
MILK_PORRIDGE = "milk_porridge"
|
||||
MULTICOOKER = "multicooker"
|
||||
PASTA = "pasta"
|
||||
PILAF = "pilaf"
|
||||
PIZZA = "pizza"
|
||||
SAUCE = "sauce"
|
||||
SLOW_COOK = "slow_cook"
|
||||
SOUP = "soup"
|
||||
STEAM = "steam"
|
||||
STEWING = "stewing"
|
||||
VACUUM = "vacuum"
|
||||
YOGURT = "yogurt"
|
||||
|
||||
|
||||
class ModeCapabilityParameters(APIModel):
|
||||
"""Parameters of a mode capability."""
|
||||
|
||||
instance: ModeCapabilityInstance
|
||||
modes: list[dict[Literal["value"], ModeCapabilityMode]]
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, instance: ModeCapabilityInstance, modes: list[ModeCapabilityMode]) -> Self:
|
||||
return cls(instance=instance, modes=[{"value": m} for m in modes])
|
||||
|
||||
|
||||
class ModeCapabilityInstanceActionState(APIModel):
|
||||
"""New value for a mode capability."""
|
||||
|
||||
instance: ModeCapabilityInstance
|
||||
value: ModeCapabilityMode
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Schema for on_off capability.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/on_off.html
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from enum import StrEnum
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
from .base import APIModel
|
||||
from .response import SuccessActionResult
|
||||
|
||||
|
||||
class OnOffCapabilityInstance(StrEnum):
|
||||
"""Instance of an on_off capability."""
|
||||
ON = "on"
|
||||
|
||||
|
||||
class OnOffCapabilityParameters(APIModel):
|
||||
"""Parameters of a on_off capability."""
|
||||
split: bool = False
|
||||
|
||||
|
||||
class OnOffCapabilityInstanceActionState(APIModel):
|
||||
"""New value for an on_off capability."""
|
||||
instance: Literal[OnOffCapabilityInstance.ON] = OnOffCapabilityInstance.ON
|
||||
value: bool
|
||||
|
||||
|
||||
class OnOffCapabilityInstanceActionResultValue(BaseModel):
|
||||
"""ActionResult value for on_off capability."""
|
||||
on: bool
|
||||
106
custom_components/yandex_smart_home/schema/capability_range.py
Normal file
106
custom_components/yandex_smart_home/schema/capability_range.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""Schema for range capability.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/range.html
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator, computed_field
|
||||
|
||||
from .base import APIModel
|
||||
|
||||
|
||||
class RangeCapabilityUnit(StrEnum):
|
||||
"""Unit used in a range capability."""
|
||||
|
||||
PERCENT = "unit.percent"
|
||||
TEMPERATURE_CELSIUS = "unit.temperature.celsius"
|
||||
|
||||
|
||||
class RangeCapabilityInstance(StrEnum):
|
||||
"""Instance of a range capability.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/range-instance.html
|
||||
"""
|
||||
|
||||
BRIGHTNESS = "brightness"
|
||||
CHANNEL = "channel"
|
||||
HUMIDITY = "humidity"
|
||||
OPEN = "open"
|
||||
TEMPERATURE = "temperature"
|
||||
VOLUME = "volume"
|
||||
|
||||
|
||||
class RangeCapabilityRange(APIModel):
|
||||
"""Value range of a range capability."""
|
||||
|
||||
min: float
|
||||
max: float
|
||||
precision: float
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"[{self.min}, {self.max}]"
|
||||
|
||||
|
||||
class RangeCapabilityParameters(APIModel):
|
||||
"""Parameters of a range capability."""
|
||||
|
||||
instance: RangeCapabilityInstance
|
||||
unit: Optional[RangeCapabilityUnit] = Field(default=None, exclude=True) # исключаем из сериализации, если нужно
|
||||
random_access: bool
|
||||
range: Optional[RangeCapabilityRange] = Field(default=None)
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def computed_unit(self) -> RangeCapabilityUnit:
|
||||
"""Вычисляемый unit на основе instance."""
|
||||
match self.instance:
|
||||
case RangeCapabilityInstance.BRIGHTNESS:
|
||||
return RangeCapabilityUnit.PERCENT
|
||||
case RangeCapabilityInstance.HUMIDITY:
|
||||
return RangeCapabilityUnit.PERCENT
|
||||
case RangeCapabilityInstance.OPEN:
|
||||
return RangeCapabilityUnit.PERCENT
|
||||
case RangeCapabilityInstance.TEMPERATURE:
|
||||
return RangeCapabilityUnit.TEMPERATURE_CELSIUS
|
||||
case _:
|
||||
return self.unit or RangeCapabilityUnit.PERCENT # fallback
|
||||
|
||||
@model_validator(mode='after')
|
||||
def validate_range(self) -> Self:
|
||||
"""Force range boundaries for a capability instance."""
|
||||
r = self.range
|
||||
if r:
|
||||
match self.instance:
|
||||
case RangeCapabilityInstance.HUMIDITY | RangeCapabilityInstance.OPEN:
|
||||
r.min = max(0.0, r.min)
|
||||
r.max = min(100.0, r.max)
|
||||
case RangeCapabilityInstance.BRIGHTNESS:
|
||||
r.min = max(0.0, min(1.0, r.min))
|
||||
r.max = 100.0
|
||||
r.precision = 1.0
|
||||
else:
|
||||
if self.instance in (
|
||||
RangeCapabilityInstance.BRIGHTNESS,
|
||||
RangeCapabilityInstance.HUMIDITY,
|
||||
RangeCapabilityInstance.OPEN,
|
||||
RangeCapabilityInstance.TEMPERATURE,
|
||||
):
|
||||
raise ValueError(f"range field required for {self.instance}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class RangeCapabilityInstanceActionState(APIModel):
|
||||
"""New value for a range capability."""
|
||||
|
||||
instance: RangeCapabilityInstance
|
||||
value: float
|
||||
relative: bool = False
|
||||
|
||||
@field_validator("relative", mode="before")
|
||||
@classmethod
|
||||
def set_relative(cls, v: Any) -> bool:
|
||||
"""Update relative value."""
|
||||
return False if v is None else v
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Schema for toggle capability.
|
||||
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/toggle.html
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from .base import APIModel
|
||||
|
||||
|
||||
class ToggleCapabilityInstance(StrEnum):
|
||||
"""Instance of a toggle capability.
|
||||
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/toggle-instance.html
|
||||
"""
|
||||
|
||||
BACKLIGHT = "backlight"
|
||||
CONTROLS_LOCKED = "controls_locked"
|
||||
IONIZATION = "ionization"
|
||||
KEEP_WARM = "keep_warm"
|
||||
MUTE = "mute"
|
||||
OSCILLATION = "oscillation"
|
||||
PAUSE = "pause"
|
||||
|
||||
|
||||
class ToggleCapabilityParameters(APIModel):
|
||||
"""Parameters of a toggle capability."""
|
||||
|
||||
instance: ToggleCapabilityInstance
|
||||
|
||||
|
||||
class ToggleCapabilityInstanceActionState(APIModel):
|
||||
"""New value for a toggle capability."""
|
||||
|
||||
instance: ToggleCapabilityInstance
|
||||
value: bool
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Schema for video_stream capability.
|
||||
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/video_stream.html
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Literal, List
|
||||
|
||||
from .base import APIModel
|
||||
|
||||
StreamProtocols = List[Literal["hls"]]
|
||||
|
||||
|
||||
class VideoStreamCapabilityInstance(StrEnum):
|
||||
"""Instance of a video_stream capability."""
|
||||
|
||||
GET_STREAM = "get_stream"
|
||||
|
||||
|
||||
class VideoStreamCapabilityParameters(APIModel):
|
||||
"""Parameters of a video_stream capability."""
|
||||
|
||||
protocols: StreamProtocols
|
||||
|
||||
|
||||
class GetStreamInstanceActionStateValue(APIModel):
|
||||
"""New state value for a get_stream instance."""
|
||||
|
||||
protocols: StreamProtocols
|
||||
|
||||
|
||||
class GetStreamInstanceActionState(APIModel):
|
||||
"""New value for a get_stream instance."""
|
||||
|
||||
instance: Literal[VideoStreamCapabilityInstance.GET_STREAM]
|
||||
value: GetStreamInstanceActionStateValue
|
||||
|
||||
|
||||
class GetStreamInstanceActionResultValue(APIModel):
|
||||
"""New value after a get_stream instance state changed."""
|
||||
|
||||
stream_url: str
|
||||
protocol: Literal["hls"]
|
||||
199
custom_components/yandex_smart_home/schema/device.py
Normal file
199
custom_components/yandex_smart_home/schema/device.py
Normal file
@@ -0,0 +1,199 @@
|
||||
"""Schema for an user device.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/reference/get-devices.html
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/reference/post-devices-query.html
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/reference/post-action.html
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Any, List, Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .base import APIModel
|
||||
from .capability import (
|
||||
CapabilityDescription,
|
||||
CapabilityInstance,
|
||||
CapabilityInstanceAction,
|
||||
CapabilityInstanceActionResultValue,
|
||||
CapabilityInstanceState,
|
||||
CapabilityType,
|
||||
)
|
||||
from .property import PropertyDescription, PropertyInstanceState
|
||||
from .response import ResponseCode, ResponsePayload
|
||||
|
||||
|
||||
class DeviceType(StrEnum):
|
||||
"""User device type.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/device-types.html
|
||||
"""
|
||||
LIGHT = "devices.types.light"
|
||||
LIGHT_STRIP = "devices.types.light.strip"
|
||||
LIGHT_CEILING = "devices.types.light.ceiling"
|
||||
LIGHT_LAMP = "devices.types.light.lamp"
|
||||
LIGHT_GARLAND = "devices.types.light.garland"
|
||||
SOCKET = "devices.types.socket"
|
||||
SWITCH = "devices.types.switch"
|
||||
SWITCH_RELAY = "devices.types.switch.relay"
|
||||
THERMOSTAT = "devices.types.thermostat"
|
||||
THERMOSTAT_AC = "devices.types.thermostat.ac"
|
||||
MEDIA_DEVICE = "devices.types.media_device"
|
||||
MEDIA_DEVICE_TV = "devices.types.media_device.tv"
|
||||
MEDIA_DEVICE_TV_BOX = "devices.types.media_device.tv_box"
|
||||
MEDIA_DEVICE_RECIEVER = "devices.types.media_device.receiver"
|
||||
CAMERA = "devices.types.camera"
|
||||
COOKING = "devices.types.cooking"
|
||||
COFFEE_MAKER = "devices.types.cooking.coffee_maker"
|
||||
KETTLE = "devices.types.cooking.kettle"
|
||||
MULTICOOKER = "devices.types.cooking.multicooker"
|
||||
OPENABLE = "devices.types.openable"
|
||||
OPENABLE_CURTAIN = "devices.types.openable.curtain"
|
||||
OPENABLE_VALVE = "devices.types.openable.valve"
|
||||
HUMIDIFIER = "devices.types.humidifier"
|
||||
PURIFIER = "devices.types.purifier"
|
||||
VACUUM_CLEANER = "devices.types.vacuum_cleaner"
|
||||
WASHING_MACHINE = "devices.types.washing_machine"
|
||||
DISHWASHER = "devices.types.dishwasher"
|
||||
IRON = "devices.types.iron"
|
||||
SENSOR = "devices.types.sensor"
|
||||
SENSOR_MOTION = "devices.types.sensor.motion"
|
||||
SENSOR_VIBRATION = "devices.types.sensor.vibration"
|
||||
SENSOR_ILLUMINATION = "devices.types.sensor.illumination"
|
||||
SENSOR_OPEN = "devices.types.sensor.open"
|
||||
SENSOR_CLIMATE = "devices.types.sensor.climate"
|
||||
SENSOR_WATER_LEAK = "devices.types.sensor.water_leak"
|
||||
SENSOR_BUTTON = "devices.types.sensor.button"
|
||||
SENSOR_GAS = "devices.types.sensor.gas"
|
||||
SENSOR_SMOKE = "devices.types.sensor.smoke"
|
||||
SMART_METER = "devices.types.smart_meter"
|
||||
SMART_METER_COLD_WATER = "devices.types.smart_meter.cold_water"
|
||||
SMART_METER_ELECTRICITY = "devices.types.smart_meter.electricity"
|
||||
SMART_METER_GAS = "devices.types.smart_meter.gas"
|
||||
SMART_METER_HEAT = "devices.types.smart_meter.heat"
|
||||
SMART_METER_HOT_WATER = "devices.types.smart_meter.hot_water"
|
||||
PET_DRINKING_FOUNTAIN = "devices.types.pet_drinking_fountain"
|
||||
PET_FEEDER = "devices.types.pet_feeder"
|
||||
VENTILATION = "devices.types.ventilation"
|
||||
VENTILATION_FAN = "devices.types.ventilation.fan"
|
||||
OTHER = "devices.types.other"
|
||||
|
||||
|
||||
class DeviceInfo(APIModel):
|
||||
"""Extended device info."""
|
||||
|
||||
manufacturer: Optional[str] = Field(default=None)
|
||||
model: Optional[str] = Field(default=None)
|
||||
hw_version: Optional[str] = Field(default=None)
|
||||
sw_version: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class DeviceDescription(APIModel):
|
||||
"""Device description for a device list request."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
room: Optional[str] = None
|
||||
type: DeviceType
|
||||
capabilities: Optional[List[CapabilityDescription]] = None
|
||||
properties: Optional[List[PropertyDescription]] = None
|
||||
device_info: Optional[DeviceInfo] = None
|
||||
|
||||
|
||||
class DeviceState(APIModel):
|
||||
"""Device state for a state query request."""
|
||||
|
||||
id: str
|
||||
capabilities: Optional[List[CapabilityInstanceState]] = None
|
||||
properties: Optional[List[PropertyInstanceState]] = None
|
||||
error_code: Optional[ResponseCode] = None
|
||||
error_message: Optional[str] = None
|
||||
|
||||
|
||||
class DeviceList(ResponsePayload):
|
||||
"""Response payload for a device list request."""
|
||||
|
||||
user_id: str
|
||||
devices: List[DeviceDescription]
|
||||
|
||||
|
||||
class DeviceStates(ResponsePayload):
|
||||
"""Response payload for a state query request."""
|
||||
|
||||
devices: List[DeviceState]
|
||||
|
||||
|
||||
class StatesRequestDevice(APIModel):
|
||||
"""Device for a state query request."""
|
||||
|
||||
id: str
|
||||
custom_data: Optional[dict[str, Any]] = None
|
||||
|
||||
|
||||
class StatesRequest(APIModel):
|
||||
"""Request body for a state query request."""
|
||||
|
||||
devices: List[StatesRequestDevice]
|
||||
|
||||
|
||||
class ActionRequestDevice(APIModel):
|
||||
"""Device for a state change request."""
|
||||
|
||||
id: str
|
||||
capabilities: List[CapabilityInstanceAction]
|
||||
|
||||
|
||||
class ActionRequestPayload(APIModel):
|
||||
"""Request payload for state change request."""
|
||||
|
||||
action_id: Optional[str] = Field(default=None) # ← добавлено для совместимости с Яндексом
|
||||
devices: List[ActionRequestDevice]
|
||||
|
||||
|
||||
class ActionRequest(APIModel):
|
||||
"""Request body for a state change request."""
|
||||
|
||||
payload: ActionRequestPayload
|
||||
|
||||
|
||||
class SuccessActionResult(APIModel):
|
||||
"""Success device action result."""
|
||||
|
||||
status: Literal["DONE"] = "DONE"
|
||||
|
||||
|
||||
class FailedActionResult(APIModel):
|
||||
"""Failed device action result."""
|
||||
|
||||
status: Literal["ERROR"] = "ERROR"
|
||||
error_code: ResponseCode
|
||||
|
||||
|
||||
class ActionResultCapabilityState(APIModel):
|
||||
"""Result of capability instance state change."""
|
||||
|
||||
instance: CapabilityInstance
|
||||
value: Optional[CapabilityInstanceActionResultValue] = None
|
||||
action_result: Union[SuccessActionResult, FailedActionResult]
|
||||
|
||||
|
||||
class ActionResultCapability(APIModel):
|
||||
"""Result of capability state change."""
|
||||
|
||||
type: CapabilityType
|
||||
state: ActionResultCapabilityState
|
||||
|
||||
|
||||
class ActionResultDevice(APIModel):
|
||||
"""Device for a state change response."""
|
||||
|
||||
id: str
|
||||
capabilities: Optional[List[ActionResultCapability]] = None
|
||||
action_result: Optional[Union[FailedActionResult, SuccessActionResult]] = None
|
||||
|
||||
|
||||
class ActionResult(ResponsePayload):
|
||||
"""Response for a device state change."""
|
||||
|
||||
devices: List[ActionResultDevice]
|
||||
65
custom_components/yandex_smart_home/schema/property.py
Normal file
65
custom_components/yandex_smart_home/schema/property.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""Schema for device property.
|
||||
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/properties-types.html
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Any, Literal
|
||||
|
||||
from .base import APIModel
|
||||
from .property_event import EventPropertyInstance, EventPropertyParameters
|
||||
from .property_float import FloatPropertyInstance, FloatPropertyParameters
|
||||
|
||||
|
||||
class PropertyType(StrEnum):
|
||||
"""Property type."""
|
||||
|
||||
FLOAT = "devices.properties.float"
|
||||
EVENT = "devices.properties.event"
|
||||
|
||||
@property
|
||||
def short(self) -> str:
|
||||
"""Return short version of the property type."""
|
||||
return str(self).replace("devices.properties.", "")
|
||||
|
||||
|
||||
class FloatPropertyDescription(APIModel):
|
||||
"""Description of a float property for a device list request."""
|
||||
|
||||
type: Literal[PropertyType.FLOAT] = PropertyType.FLOAT
|
||||
retrievable: bool
|
||||
reportable: bool
|
||||
parameters: FloatPropertyParameters
|
||||
|
||||
|
||||
class EventPropertyDescription(APIModel):
|
||||
"""Description of an event property for a device list request."""
|
||||
|
||||
type: Literal[PropertyType.EVENT] = PropertyType.EVENT
|
||||
retrievable: bool
|
||||
reportable: bool
|
||||
parameters: EventPropertyParameters[Any]
|
||||
|
||||
|
||||
PropertyDescription = FloatPropertyDescription | EventPropertyDescription
|
||||
"""Description of a property for a device list request."""
|
||||
|
||||
PropertyParameters = FloatPropertyParameters | EventPropertyParameters[Any]
|
||||
"""Parameters of a property for a device list request."""
|
||||
|
||||
PropertyInstance = FloatPropertyInstance | EventPropertyInstance
|
||||
"""All property instances."""
|
||||
|
||||
|
||||
class PropertyInstanceStateValue(APIModel):
|
||||
"""Property instance value."""
|
||||
|
||||
instance: PropertyInstance
|
||||
value: Any
|
||||
|
||||
|
||||
class PropertyInstanceState(APIModel):
|
||||
"""Property state for state query and callback requests."""
|
||||
|
||||
type: PropertyType
|
||||
state: PropertyInstanceStateValue
|
||||
195
custom_components/yandex_smart_home/schema/property_event.py
Normal file
195
custom_components/yandex_smart_home/schema/property_event.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""Schema for event property.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/event.html
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Any, Generic, Literal, TypeVar
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, field_validator
|
||||
|
||||
from .base import APIModel
|
||||
|
||||
|
||||
class EventPropertyInstance(StrEnum):
|
||||
"""Instance of an event property.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/event-instance.html
|
||||
"""
|
||||
|
||||
VIBRATION = "vibration"
|
||||
OPEN = "open"
|
||||
BUTTON = "button"
|
||||
MOTION = "motion"
|
||||
SMOKE = "smoke"
|
||||
GAS = "gas"
|
||||
BATTERY_LEVEL = "battery_level"
|
||||
FOOD_LEVEL = "food_level"
|
||||
WATER_LEVEL = "water_level"
|
||||
WATER_LEAK = "water_leak"
|
||||
|
||||
|
||||
class EventInstanceEvent(StrEnum):
|
||||
"""Base class for an instance event."""
|
||||
|
||||
...
|
||||
|
||||
|
||||
class VibrationInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a vibration instance."""
|
||||
|
||||
TILT = "tilt"
|
||||
FALL = "fall"
|
||||
VIBRATION = "vibration"
|
||||
|
||||
|
||||
class OpenInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a open instance."""
|
||||
|
||||
OPENED = "opened"
|
||||
CLOSED = "closed"
|
||||
|
||||
|
||||
class ButtonInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a button instance."""
|
||||
|
||||
CLICK = "click"
|
||||
DOUBLE_CLICK = "double_click"
|
||||
LONG_PRESS = "long_press"
|
||||
|
||||
|
||||
class MotionInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a motion instance."""
|
||||
|
||||
DETECTED = "detected"
|
||||
NOT_DETECTED = "not_detected"
|
||||
|
||||
|
||||
class SmokeInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a smoke instance."""
|
||||
|
||||
DETECTED = "detected"
|
||||
NOT_DETECTED = "not_detected"
|
||||
HIGH = "high"
|
||||
|
||||
|
||||
class GasInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a gas instance."""
|
||||
|
||||
DETECTED = "detected"
|
||||
NOT_DETECTED = "not_detected"
|
||||
HIGH = "high"
|
||||
|
||||
|
||||
class BatteryLevelInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a battery_level instance."""
|
||||
|
||||
LOW = "low"
|
||||
NORMAL = "normal"
|
||||
HIGH = "high"
|
||||
|
||||
|
||||
class FoodLevelInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a food_level instance."""
|
||||
|
||||
EMPTY = "empty"
|
||||
LOW = "low"
|
||||
NORMAL = "normal"
|
||||
|
||||
|
||||
class WaterLevelInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a water_level instance."""
|
||||
|
||||
EMPTY = "empty"
|
||||
LOW = "low"
|
||||
NORMAL = "normal"
|
||||
|
||||
|
||||
class WaterLeakInstanceEvent(EventInstanceEvent):
|
||||
"""Event of a water_leak instance."""
|
||||
|
||||
DRY = "dry"
|
||||
LEAK = "leak"
|
||||
|
||||
|
||||
EventInstanceEventT = TypeVar("EventInstanceEventT", bound=EventInstanceEvent)
|
||||
|
||||
|
||||
def get_event_class_for_instance(instance: EventPropertyInstance) -> type[EventInstanceEvent]:
|
||||
"""Return EventInstanceEvent enum for event property instance."""
|
||||
return {
|
||||
EventPropertyInstance.VIBRATION: VibrationInstanceEvent,
|
||||
EventPropertyInstance.OPEN: OpenInstanceEvent,
|
||||
EventPropertyInstance.BUTTON: ButtonInstanceEvent,
|
||||
EventPropertyInstance.MOTION: MotionInstanceEvent,
|
||||
EventPropertyInstance.SMOKE: SmokeInstanceEvent,
|
||||
EventPropertyInstance.GAS: GasInstanceEvent,
|
||||
EventPropertyInstance.BATTERY_LEVEL: BatteryLevelInstanceEvent,
|
||||
EventPropertyInstance.FOOD_LEVEL: FoodLevelInstanceEvent,
|
||||
EventPropertyInstance.WATER_LEVEL: WaterLevelInstanceEvent,
|
||||
EventPropertyInstance.WATER_LEAK: WaterLeakInstanceEvent,
|
||||
}[instance]
|
||||
|
||||
|
||||
def get_supported_events_for_instance(instance: EventPropertyInstance) -> list[EventInstanceEvent]:
|
||||
"""Return list of supported events for event property instance."""
|
||||
return list(get_event_class_for_instance(instance).__members__.values())
|
||||
|
||||
|
||||
class EventPropertyParameters(APIModel, Generic[EventInstanceEventT]):
|
||||
"""Parameters of an event property."""
|
||||
|
||||
instance: EventPropertyInstance
|
||||
events: list[dict[Literal["value"], EventInstanceEventT]] = []
|
||||
|
||||
@field_validator("events", mode="before")
|
||||
@classmethod
|
||||
def set_events(cls, v: Any) -> list[dict[Literal["value"], Any]]:
|
||||
"""Update events list value."""
|
||||
if not v:
|
||||
# Получаем тип события из generic параметра
|
||||
# В v2 это cls.model_fields["events"].annotation.__args__[1]
|
||||
event_type = cls.model_fields["events"].annotation.__args__[1]
|
||||
return [{"value": m} for m in event_type.__members__.values()]
|
||||
|
||||
return v
|
||||
|
||||
|
||||
class VibrationEventPropertyParameters(EventPropertyParameters[VibrationInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.VIBRATION] = EventPropertyInstance.VIBRATION
|
||||
|
||||
|
||||
class OpenEventPropertyParameters(EventPropertyParameters[OpenInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.OPEN] = EventPropertyInstance.OPEN
|
||||
|
||||
|
||||
class ButtonEventPropertyParameters(EventPropertyParameters[ButtonInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.BUTTON] = EventPropertyInstance.BUTTON
|
||||
|
||||
|
||||
class MotionEventPropertyParameters(EventPropertyParameters[MotionInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.MOTION] = EventPropertyInstance.MOTION
|
||||
|
||||
|
||||
class SmokeEventPropertyParameters(EventPropertyParameters[SmokeInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.SMOKE] = EventPropertyInstance.SMOKE
|
||||
|
||||
|
||||
class GasEventPropertyParameters(EventPropertyParameters[GasInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.GAS] = EventPropertyInstance.GAS
|
||||
|
||||
|
||||
class BatteryLevelEventPropertyParameters(EventPropertyParameters[BatteryLevelInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.BATTERY_LEVEL] = EventPropertyInstance.BATTERY_LEVEL
|
||||
|
||||
|
||||
class FoodLevelEventPropertyParameters(EventPropertyParameters[FoodLevelInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.FOOD_LEVEL] = EventPropertyInstance.FOOD_LEVEL
|
||||
|
||||
|
||||
class WaterLevelEventPropertyParameters(EventPropertyParameters[WaterLevelInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.WATER_LEVEL] = EventPropertyInstance.WATER_LEVEL
|
||||
|
||||
|
||||
class WaterLeakEventPropertyParameters(EventPropertyParameters[WaterLeakInstanceEvent]):
|
||||
instance: Literal[EventPropertyInstance.WATER_LEAK] = EventPropertyInstance.WATER_LEAK
|
||||
195
custom_components/yandex_smart_home/schema/property_float.py
Normal file
195
custom_components/yandex_smart_home/schema/property_float.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""Schema for float property.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/float.html
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .base import APIModel
|
||||
|
||||
|
||||
class FloatPropertyInstance(StrEnum):
|
||||
"""Instance of a float property.
|
||||
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/float-instance.html
|
||||
"""
|
||||
AMPERAGE = "amperage"
|
||||
BATTERY_LEVEL = "battery_level"
|
||||
CO2_LEVEL = "co2_level"
|
||||
ELECTRICITY_METER = "electricity_meter"
|
||||
FOOD_LEVEL = "food_level"
|
||||
GAS_METER = "gas_meter"
|
||||
HEAT_METER = "heat_meter"
|
||||
HUMIDITY = "humidity"
|
||||
ILLUMINATION = "illumination"
|
||||
METER = "meter"
|
||||
PM10_DENSITY = "pm10_density"
|
||||
PM1_DENSITY = "pm1_density"
|
||||
PM2_5_DENSITY = "pm2.5_density"
|
||||
POWER = "power"
|
||||
PRESSURE = "pressure"
|
||||
TEMPERATURE = "temperature"
|
||||
TVOC = "tvoc"
|
||||
VOLTAGE = "voltage"
|
||||
WATER_LEVEL = "water_level"
|
||||
WATER_METER = "water_meter"
|
||||
|
||||
|
||||
class FloatUnit(StrEnum):
|
||||
"""Unit used in a float property."""
|
||||
|
||||
AMPERE = "unit.ampere"
|
||||
CUBIC_METER = "unit.cubic_meter"
|
||||
GIGACALORIE = "unit.gigacalorie"
|
||||
KILOWATT_HOUR = "unit.kilowatt_hour"
|
||||
LUX = "unit.illumination.lux"
|
||||
MCG_M3 = "unit.density.mcg_m3"
|
||||
PERCENT = "unit.percent"
|
||||
PPM = "unit.ppm"
|
||||
VOLT = "unit.volt"
|
||||
WATT = "unit.watt"
|
||||
|
||||
|
||||
class PressureUnit(StrEnum):
|
||||
"""Pressure unit."""
|
||||
|
||||
PASCAL = "unit.pressure.pascal"
|
||||
MMHG = "unit.pressure.mmhg"
|
||||
ATM = "unit.pressure.atm"
|
||||
BAR = "unit.pressure.bar"
|
||||
|
||||
|
||||
class TemperatureUnit(StrEnum):
|
||||
"""Temperature unit."""
|
||||
|
||||
CELSIUS = "unit.temperature.celsius"
|
||||
KELVIN = "unit.temperature.kelvin"
|
||||
|
||||
|
||||
class FloatPropertyParameters(APIModel):
|
||||
"""Parameters of a float property."""
|
||||
|
||||
instance: FloatPropertyInstance
|
||||
unit: FloatUnit | PressureUnit | TemperatureUnit | None = None
|
||||
|
||||
@property
|
||||
def range(self) -> tuple[int | None, int | None]:
|
||||
"""Return value range."""
|
||||
return None, None
|
||||
|
||||
|
||||
class FloatPropertyAboveZeroMixin:
|
||||
"""Mixin for a property that has value only above zero."""
|
||||
|
||||
@property
|
||||
def range(self) -> tuple[int | None, int | None]:
|
||||
"""Return value range."""
|
||||
return 0, None
|
||||
|
||||
|
||||
class PercentFloatPropertyParameters(FloatPropertyParameters):
|
||||
unit: Literal[FloatUnit.PERCENT] = FloatUnit.PERCENT
|
||||
|
||||
@property
|
||||
def range(self) -> tuple[int | None, int | None]:
|
||||
"""Return value range."""
|
||||
return 0, 100
|
||||
|
||||
|
||||
class DensityFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
unit: Literal[FloatUnit.MCG_M3] = FloatUnit.MCG_M3
|
||||
|
||||
|
||||
class AmperageFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.AMPERAGE] = FloatPropertyInstance.AMPERAGE
|
||||
unit: Literal[FloatUnit.AMPERE] = FloatUnit.AMPERE
|
||||
|
||||
|
||||
class BatteryLevelFloatPropertyParameters(PercentFloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.BATTERY_LEVEL] = FloatPropertyInstance.BATTERY_LEVEL
|
||||
|
||||
|
||||
class CO2LevelFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.CO2_LEVEL] = FloatPropertyInstance.CO2_LEVEL
|
||||
unit: Literal[FloatUnit.PPM] = FloatUnit.PPM
|
||||
|
||||
|
||||
class ElectricityMeterFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.ELECTRICITY_METER] = FloatPropertyInstance.ELECTRICITY_METER
|
||||
unit: Literal[FloatUnit.KILOWATT_HOUR] = FloatUnit.KILOWATT_HOUR
|
||||
|
||||
|
||||
class FoodLevelFloatPropertyParameters(PercentFloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.FOOD_LEVEL] = FloatPropertyInstance.FOOD_LEVEL
|
||||
|
||||
|
||||
class GasMeterFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.GAS_METER] = FloatPropertyInstance.GAS_METER
|
||||
unit: Literal[FloatUnit.CUBIC_METER] = FloatUnit.CUBIC_METER
|
||||
|
||||
|
||||
class HeatMeterFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.HEAT_METER] = FloatPropertyInstance.HEAT_METER
|
||||
unit: Literal[FloatUnit.GIGACALORIE] = FloatUnit.GIGACALORIE
|
||||
|
||||
|
||||
class HumidityFloatPropertyParameters(PercentFloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.HUMIDITY] = FloatPropertyInstance.HUMIDITY
|
||||
|
||||
|
||||
class IlluminationFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.ILLUMINATION] = FloatPropertyInstance.ILLUMINATION
|
||||
unit: Literal[FloatUnit.LUX] = FloatUnit.LUX
|
||||
|
||||
|
||||
class MeterFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.METER] = FloatPropertyInstance.METER
|
||||
unit: None = Field(default=None)
|
||||
|
||||
|
||||
class PM1DensityFloatPropertyParameters(DensityFloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.PM1_DENSITY] = FloatPropertyInstance.PM1_DENSITY
|
||||
|
||||
|
||||
class PM25DensityFloatPropertyParameters(DensityFloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.PM2_5_DENSITY] = FloatPropertyInstance.PM2_5_DENSITY
|
||||
|
||||
|
||||
class PM10DensityFloatPropertyParameters(DensityFloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.PM10_DENSITY] = FloatPropertyInstance.PM10_DENSITY
|
||||
|
||||
|
||||
class PowerFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.POWER] = FloatPropertyInstance.POWER
|
||||
unit: Literal[FloatUnit.WATT] = FloatUnit.WATT
|
||||
|
||||
|
||||
class PressureFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.PRESSURE] = FloatPropertyInstance.PRESSURE
|
||||
unit: PressureUnit
|
||||
|
||||
|
||||
class TemperatureFloatPropertyParameters(FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.TEMPERATURE] = FloatPropertyInstance.TEMPERATURE
|
||||
unit: TemperatureUnit
|
||||
|
||||
|
||||
class TVOCFloatPropertyParameters(DensityFloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.TVOC] = FloatPropertyInstance.TVOC
|
||||
|
||||
|
||||
class VoltageFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.VOLTAGE] = FloatPropertyInstance.VOLTAGE
|
||||
unit: Literal[FloatUnit.VOLT] = FloatUnit.VOLT
|
||||
|
||||
|
||||
class WaterLevelFloatPropertyParameters(PercentFloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.WATER_LEVEL] = FloatPropertyInstance.WATER_LEVEL
|
||||
|
||||
|
||||
class WaterMeterFloatPropertyParameters(FloatPropertyAboveZeroMixin, FloatPropertyParameters):
|
||||
instance: Literal[FloatPropertyInstance.WATER_METER] = FloatPropertyInstance.WATER_METER
|
||||
unit: Literal[FloatUnit.CUBIC_METER] = FloatUnit.CUBIC_METER
|
||||
59
custom_components/yandex_smart_home/schema/response.py
Normal file
59
custom_components/yandex_smart_home/schema/response.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""Schema for an API response for Yandex Smart Home."""
|
||||
|
||||
from __future__ import annotations
|
||||
from enum import StrEnum
|
||||
from typing import Optional, Any, Dict, Literal
|
||||
from .base import APIModel
|
||||
|
||||
|
||||
class ResponseCode(StrEnum):
|
||||
"""Response code."""
|
||||
|
||||
DOOR_OPEN = "DOOR_OPEN"
|
||||
LID_OPEN = "LID_OPEN"
|
||||
REMOTE_CONTROL_DISABLED = "REMOTE_CONTROL_DISABLED"
|
||||
NOT_ENOUGH_WATER = "NOT_ENOUGH_WATER"
|
||||
LOW_CHARGE_LEVEL = "LOW_CHARGE_LEVEL"
|
||||
CONTAINER_FULL = "CONTAINER_FULL"
|
||||
CONTAINER_EMPTY = "CONTAINER_EMPTY"
|
||||
DRIP_TRAY_FULL = "DRIP_TRAY_FULL"
|
||||
DEVICE_STUCK = "DEVICE_STUCK"
|
||||
DEVICE_OFF = "DEVICE_OFF"
|
||||
FIRMWARE_OUT_OF_DATE = "FIRMWARE_OUT_OF_DATE"
|
||||
NOT_ENOUGH_DETERGENT = "NOT_ENOUGH_DETERGENT"
|
||||
HUMAN_INVOLVEMENT_NEEDED = "HUMAN_INVOLVEMENT_NEEDED"
|
||||
DEVICE_UNREACHABLE = "DEVICE_UNREACHABLE"
|
||||
DEVICE_BUSY = "DEVICE_BUSY"
|
||||
INTERNAL_ERROR = "INTERNAL_ERROR"
|
||||
INVALID_ACTION = "INVALID_ACTION"
|
||||
INVALID_VALUE = "INVALID_VALUE"
|
||||
NOT_SUPPORTED_IN_CURRENT_MODE = "NOT_SUPPORTED_IN_CURRENT_MODE"
|
||||
ACCOUNT_LINKING_ERROR = "ACCOUNT_LINKING_ERROR"
|
||||
DEVICE_NOT_FOUND = "DEVICE_NOT_FOUND"
|
||||
|
||||
|
||||
class ResponsePayload(APIModel):
|
||||
"""Base class for an API response payload."""
|
||||
|
||||
|
||||
class Error(ResponsePayload):
|
||||
"""Error payload."""
|
||||
error_code: ResponseCode
|
||||
error_message: Optional[str] = None
|
||||
|
||||
|
||||
class SuccessActionResult(APIModel):
|
||||
"""Represents a successful action result."""
|
||||
status: Literal["DONE"] = "DONE"
|
||||
|
||||
|
||||
class FailedActionResult(APIModel):
|
||||
"""Represents a failed action result."""
|
||||
status: Literal["ERROR"] = "ERROR"
|
||||
error_code: ResponseCode
|
||||
|
||||
|
||||
class Response(APIModel):
|
||||
"""Base API response."""
|
||||
request_id: Optional[str] = None
|
||||
payload: Optional[Dict[str, Any]] = None
|
||||
Reference in New Issue
Block a user