Initialize docker stack repo

This commit is contained in:
g4st3r
2026-03-07 17:28:59 +00:00
commit 319d4eeb3e
458 changed files with 67215 additions and 0 deletions

View 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 *

View File

@@ -0,0 +1,22 @@
"""Base class for API response schemas."""
from typing import Any
from pydantic.v1 import BaseModel
from pydantic.v1.generics import GenericModel
class APIModel(BaseModel):
"""Base API response model."""
def as_json(self) -> str:
"""Generate a JSON representation of the model."""
return super().json(exclude_none=True, ensure_ascii=False)
def as_dict(self) -> dict[str, Any]:
"""Generate a dictionary representation of the model."""
return super().dict(exclude_none=True)
class GenericAPIModel(GenericModel, APIModel):
"""Base generic API response model."""

View File

@@ -0,0 +1,57 @@
"""Schema for event notification service.
https://yandex.ru/dev/dialogs/smart-home/doc/reference-alerts/resources-alerts.html
"""
from enum import StrEnum
import time
from pydantic.v1 import 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=lambda: 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=lambda: 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
error_message: str | None
CallbackRequest = CallbackDiscoveryRequest | CallbackStatesRequest

View File

@@ -0,0 +1,160 @@
"""Schema for device capabilities."""
from enum import StrEnum
from typing import Annotated, Any, Literal, TypeVar, Union
from pydantic.v1 import 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):
"""Capability type."""
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 short version of the capability type."""
return str(self).replace("devices.capabilities.", "")
CapabilityParameters = (
OnOffCapabilityParameters
| ColorSettingCapabilityParameters
| ModeCapabilityParameters
| RangeCapabilityParameters
| ToggleCapabilityParameters
| VideoStreamCapabilityParameters
)
"""Parameters of a capability for a device list request."""
CapabilityInstance = (
OnOffCapabilityInstance
| ColorSettingCapabilityInstance
| ModeCapabilityInstance
| RangeCapabilityInstance
| ToggleCapabilityInstance
| VideoStreamCapabilityInstance
)
"""All capability instances."""
class CapabilityDescription(APIModel):
"""Description of a capability for a device list request."""
type: CapabilityType
retrievable: bool
reportable: bool
parameters: CapabilityParameters | None
class CapabilityInstanceStateValue(APIModel):
"""Capability instance value."""
instance: CapabilityInstance
value: Any
class CapabilityInstanceState(APIModel):
"""Capability state for state query and callback requests."""
type: CapabilityType
state: CapabilityInstanceStateValue
class OnOffCapabilityInstanceAction(APIModel):
"""New capability state for a state change request of on_off capability."""
type: Literal[CapabilityType.ON_OFF] = CapabilityType.ON_OFF
state: OnOffCapabilityInstanceActionState
class ColorSettingCapabilityInstanceAction(APIModel):
"""New capability state for a state change request of color_setting capability."""
type: Literal[CapabilityType.COLOR_SETTING] = CapabilityType.COLOR_SETTING
state: ColorSettingCapabilityInstanceActionState
class ModeCapabilityInstanceAction(APIModel):
"""New capability state for a state change request of mode capability."""
type: Literal[CapabilityType.MODE] = CapabilityType.MODE
state: ModeCapabilityInstanceActionState
class RangeCapabilityInstanceAction(APIModel):
"""New capability state for a state change request of range capability."""
type: Literal[CapabilityType.RANGE] = CapabilityType.RANGE
state: RangeCapabilityInstanceActionState
class ToggleCapabilityInstanceAction(APIModel):
"""New capability state for a state change request of toggle capability."""
type: Literal[CapabilityType.TOGGLE] = CapabilityType.TOGGLE
state: ToggleCapabilityInstanceActionState
class VideoStreamCapabilityInstanceAction(APIModel):
"""New capability state for a state change request of video_stream capability."""
type: Literal[CapabilityType.VIDEO_STREAM] = CapabilityType.VIDEO_STREAM
state: GetStreamInstanceActionState
CapabilityInstanceAction = Annotated[
Union[
OnOffCapabilityInstanceAction,
ColorSettingCapabilityInstanceAction,
ModeCapabilityInstanceAction,
RangeCapabilityInstanceAction,
ToggleCapabilityInstanceAction,
VideoStreamCapabilityInstanceAction,
],
Field(discriminator="type"),
]
"""New capability state including type for a state change request."""
CapabilityInstanceActionState = TypeVar(
"CapabilityInstanceActionState",
OnOffCapabilityInstanceActionState,
ColorSettingCapabilityInstanceActionState,
RGBInstanceActionState,
TemperatureKInstanceActionState,
SceneInstanceActionState,
ModeCapabilityInstanceActionState,
RangeCapabilityInstanceActionState,
ToggleCapabilityInstanceActionState,
GetStreamInstanceActionState,
contravariant=True,
)
"""New capability state for a state change request."""
CapabilityInstanceActionResultValue = GetStreamInstanceActionResultValue | None
"""Result of a capability state change."""

View File

@@ -0,0 +1,111 @@
"""Schema for color_setting capability.
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/color_setting.html
"""
from enum import StrEnum
from typing import Annotated, Any, Literal, Self, Union
from pydantic.v1 import Field, root_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: CapabilityParameterColorModel | None = None
temperature_k: CapabilityParameterTemperatureK | None = None
color_scene: CapabilityParameterColorScene | None = None
@root_validator
def any_of(cls, values: dict[str, Any]) -> dict[str, Any]:
if not any(values.values()):
raise ValueError("one of color_model, temperature_k or color_scene must have a value")
return values
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."""

View 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

View File

@@ -0,0 +1,27 @@
"""Schema for on_off capability.
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/on_off.html
"""
from enum import StrEnum
from .base import APIModel
class OnOffCapabilityInstance(StrEnum):
"""Instance of an on_off capability."""
ON = "on"
class OnOffCapabilityParameters(APIModel):
"""Parameters of a on_off capability."""
split: bool
class OnOffCapabilityInstanceActionState(APIModel):
"""New value for an on_off capability."""
instance: OnOffCapabilityInstance
value: bool

View File

@@ -0,0 +1,99 @@
"""Schema for range capability.
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/range.html
"""
from enum import StrEnum
from typing import Any
from pydantic.v1 import root_validator, validator
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: RangeCapabilityUnit | None = None
random_access: bool
range: RangeCapabilityRange | None = None
@root_validator
def compute_unit(cls, values: dict[str, Any]) -> dict[str, Any]:
"""Return value unit for a capability instance."""
match values.get("instance"):
case RangeCapabilityInstance.BRIGHTNESS:
values["unit"] = RangeCapabilityUnit.PERCENT
case RangeCapabilityInstance.HUMIDITY:
values["unit"] = RangeCapabilityUnit.PERCENT
case RangeCapabilityInstance.OPEN:
values["unit"] = RangeCapabilityUnit.PERCENT
case RangeCapabilityInstance.TEMPERATURE:
values["unit"] = RangeCapabilityUnit.TEMPERATURE_CELSIUS
return values
@root_validator
def validate_range(cls, values: dict[str, Any]) -> dict[str, Any]:
"""Force range boundaries for a capability instance."""
r: RangeCapabilityRange | None
if r := values.get("range"):
match values.get("instance"):
case RangeCapabilityInstance.HUMIDITY | RangeCapabilityInstance.OPEN:
r.min, r.max = max([0.0, r.min]), min([100.0, r.max])
case RangeCapabilityInstance.BRIGHTNESS:
r.min = max(min(r.min, 1.0), 0.0)
r.max = 100.0
r.precision = 1.0
return values
class RangeCapabilityInstanceActionState(APIModel):
"""New value for a range capability."""
instance: RangeCapabilityInstance
value: float
relative: bool = False
@validator("relative", pre=True, always=True)
def set_relative(cls, v: Any) -> Any:
"""Update relative value."""
if v is None: # VK
return False
return v

View File

@@ -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

View File

@@ -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
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"]

View File

@@ -0,0 +1,197 @@
"""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 enum import StrEnum
from typing import Any, Literal
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: str | None = None
model: str | None = None
hw_version: str | None = None
sw_version: str | None = None
class DeviceDescription(APIModel):
"""Device description for a device list request."""
id: str
name: str
description: str | None = None
room: str | None = None
type: DeviceType
capabilities: list[CapabilityDescription] | None = None
properties: list[PropertyDescription] | None = None
device_info: DeviceInfo | None = None
class DeviceState(APIModel):
"""Device state for a state query request."""
id: str
capabilities: list[CapabilityInstanceState] | None = None
properties: list[PropertyInstanceState] | None = None
error_code: ResponseCode | None = None
error_message: str | None = 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: dict[str, Any] | None = 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."""
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: CapabilityInstanceActionResultValue | None = None
action_result: 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: list[ActionResultCapability] | None = None
action_result: FailedActionResult | SuccessActionResult | None = None
class ActionResult(ResponsePayload):
"""Response for a device state change."""
devices: list[ActionResultDevice]

View 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

View File

@@ -0,0 +1,192 @@
"""Schema for event property.
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/event.html
"""
from enum import StrEnum
from typing import Any, Generic, Literal, TypeVar
from pydantic.v1 import validator
from .base import GenericAPIModel
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(GenericAPIModel, Generic[EventInstanceEventT]):
"""Parameters of an event property."""
instance: EventPropertyInstance
events: list[dict[Literal["value"], EventInstanceEventT]] = []
@validator("events", pre=True, always=True)
def set_events(cls, v: Any) -> Any:
"""Update events list value."""
if not v:
instance_event: type[EventInstanceEventT] = cls.__fields__["events"].type_.__args__[1]
return [{"value": m} for m in instance_event.__members__.values()]
return v # pragma: nocover
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

View File

@@ -0,0 +1,194 @@
"""Schema for float property.
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/float.html
"""
from enum import StrEnum
from typing import Literal
from .base import APIModel
class FloatPropertyInstance(StrEnum):
"""Instance of an event 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
@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 = 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

View File

@@ -0,0 +1,52 @@
"""Schema for an API response.
https://yandex.ru/dev/dialogs/smart-home/doc/concepts/response-codes.html
"""
from enum import StrEnum
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: str | None = None
class Response(APIModel):
"""Base API response."""
request_id: str | None = None
payload: ResponsePayload | None = None