phlyght/phlyght/models.py

659 lines
20 KiB
Python

from typing import (
Any,
AnyStr,
Final,
Generic,
Literal,
Optional,
Type,
TypeAlias,
TypeVar,
ClassVar,
TypeVarTuple,
)
from uuid import UUID
from enum import Enum, auto
from pydantic import BaseModel, Field
try:
from ujson import loads, dumps # type: ignore
except ImportError:
from orjson import loads, dumps # type: ignore
except ImportError: # type: ignore
from json import loads, dumps # noqa
__all__ = ("Archetype", "RoomType", "HueEnts")
# mypy: enable-incomplete-feature=TypeVarTuple
_T_M = TypeVarTuple("_T_M")
_T = TypeVar("_T")
class Entity(BaseModel):
__module__ = "phlyght"
__cache__: dict[str, type] = {}
id: UUID = Field(description="The unique identifier of the entity.")
class Config:
__root__: Optional["Entity"]
json_loads = loads
json_dumps = dumps
@classmethod
def get_entities(cls) -> dict[str, type]:
return cls.__cache__
@classmethod
def __prepare__(cls, name, bases, **kwds):
return super().__prepare__(name, bases, **kwds)
def __init_subclass__(cls, **kwargs):
super().__init_subclass__()
Entity.__cache__[
_.get_default() if (_ := cls.__fields__.get("type")) else "unknown"
] = cls
def __hash__(self) -> int:
return hash(self.id)
class RoomType(Enum):
@staticmethod
def _generate_next_value_(name, start, count, last_values):
return name.lower()
LIVING_ROOM = auto()
KITCHEN = auto()
DINING = auto()
BEDROOM = auto()
KIDS_BEDROOM = auto()
BATHROOM = auto()
NURSERY = auto()
RECREATION = auto()
OFFICE = auto()
GYM = auto()
HALLWAY = auto()
TOILET = auto()
FRONT_DOOR = auto()
GARAGE = auto()
TERRACE = auto()
GARDEN = auto()
DRIVEWAY = auto()
CARPORT = auto()
HOME = auto()
DOWNSTAIRS = auto()
UPSTAIRS = auto()
TOP_FLOOR = auto()
ATTIC = auto()
GUEST_ROOM = auto()
STAIRCASE = auto()
LOUNGE = auto()
MAN_CAVE = auto()
COMPUTER = auto()
STUDIO = auto()
MUSIC = auto()
TV = auto()
READING = auto()
CLOSET = auto()
STORAGE = auto()
LAUNDRY_ROOM = auto()
BALCONY = auto()
PORCH = auto()
BARBECUE = auto()
POOL = auto()
OTHER = auto()
class Archetype(Enum):
@staticmethod
def _generate_next_value_(name, start, count, last_values):
return name.lower()
BRIDGE_V2 = auto()
UNKNOWN_ARCHETYPE = auto()
CLASSIC_BULB = auto()
SULTAN_BULB = auto()
FLOOD_BULB = auto()
SPOT_BULB = auto()
CANDLE_BULB = auto()
LUSTER_BULB = auto()
PENDANT_ROUND = auto()
PENDANT_LONG = auto()
CEILING_ROUND = auto()
CEILING_SQUARE = auto()
FLOOR_SHADE = auto()
FLOOR_LANTERN = auto()
TABLE_SHADE = auto()
RECESSED_CEILING = auto()
RECESSED_FLOOR = auto()
SINGLE_SPOT = auto()
DOUBLE_SPOT = auto()
TABLE_WASH = auto()
WALL_LANTERN = auto()
WALL_SHADE = auto()
FLEXIBLE_LAMP = auto()
GROUND_SPOT = auto()
WALL_SPOT = auto()
PLUG = auto()
HUE_GO = auto()
HUE_LIGHTSTRIP = auto()
HUE_IRIS = auto()
HUE_BLOOM = auto()
BOLLARD = auto()
WALL_WASHER = auto()
HUE_PLAY = auto()
VINTAGE_BULB = auto()
CHRISTMAS_TREE = auto()
HUE_CENTRIS = auto()
HUE_LIGHTSTRIP_TV = auto()
HUE_TUBE = auto()
HUE_SIGNE = auto()
class Attributes:
class Action(BaseModel):
on: Optional["Attributes.On"]
dimming: Optional["Attributes.Dimming"]
color: Optional["Attributes.ColorPoint"]
color_temperature: Optional[dict[str, float]]
gradient: Optional[dict[str, list["Attributes.ColorPoint"]]]
effects: Optional[dict[str, str]]
dynamics: Optional[dict[str, float]]
class Actions(BaseModel):
target: Optional["Attributes.Identifier"]
action: Optional["Attributes.Action"]
dimming: Optional["Attributes.Dimming"]
color: Optional["Attributes.ColorPoint"]
class Button(BaseModel):
last_event: Literal[
"initial_press",
"repeat",
"short_release",
"long_release",
"double_short_release",
"long_press",
]
class Color(BaseModel):
xy: Optional["Attributes.XY"]
gamut: Optional["Attributes.Gamut"]
gamut_type: Optional[Literal["A", "B", "C"]]
class ColorPoint(BaseModel):
xy: Optional["Attributes.XY"]
class ColorTemp(BaseModel):
mirek: Optional[int]
mirek_valid: Optional[bool]
mirek_schema: Optional[dict[str, float]]
class Dependee(BaseModel):
type: Optional[str]
target: Optional["Attributes.Identifier"]
level: Optional[str]
class Dimming(BaseModel):
class Config:
frozen = True
allow_mutation = False
validate_assignment = True
brightness: Optional[float]
min_dim_level: Optional[float] = Field(0, repr=False)
class Dynamics(BaseModel):
status: Optional[str]
status_values: Optional[list[str]]
speed: Optional[float]
speed_valid: Optional[bool]
class Effects(BaseModel):
effect: Optional[list[str]]
status_values: Optional[list[str]]
status: Optional[str]
effect_values: Optional[list[str]]
class EntChannel(BaseModel):
channel_id: int = Field(ge=0, le=255)
position: Optional["Attributes.XYZ"] = None
members: Optional[list["Attributes.SegmentRef"]]
class EntLocation(BaseModel):
service_location: Optional[list["Attributes.ServiceLocation"]]
class Gamut(BaseModel):
red: Optional["Attributes.XY"]
green: Optional["Attributes.XY"]
blue: Optional["Attributes.XY"]
class Gradient(BaseModel):
points: Optional[list["Attributes.ColorPoint"]]
points_capable: Optional[int]
class Identifier(BaseModel):
class Config:
frozen = True
allow_mutation = False
validate_assignment = True
rid: str
rtype: str
class LightLevelValue(BaseModel):
light_level: Optional[int]
light_level_valid: Optional[bool]
class Metadata(BaseModel):
class Config:
frozen = True
allow_mutation = False
validate_assignment = True
name: Optional[str]
archetype: Optional[Archetype | RoomType] = Archetype.UNKNOWN_ARCHETYPE
image: Optional["Attributes.Identifier"] = Field(None, repr=False)
class Motion(BaseModel):
motion: Optional[bool]
motion_valid: Optional[bool]
class On(BaseModel):
class Config:
frozen = True
allow_mutation = False
validate_assignment = True
on: bool = Field(..., alias="on")
class Palette(BaseModel):
color: Optional[list["Attributes.PaletteColor"]]
dimming: Optional[list["Attributes.Dimming"]]
color_temperature: Optional[list["Attributes.PaletteTemperature"]]
class PaletteColor(BaseModel):
color: Optional["Attributes.ColorPoint"]
dimming: Optional["Attributes.Dimming"]
class PaletteTemperature(BaseModel):
color_temperature: dict[str, float]
dimming: Optional["Attributes.Dimming"]
class PowerState(BaseModel):
battery_state: Literal["normal", "low", "critical"]
battery_level: float = Field(le=100.0, ge=0.0)
class ProductData(BaseModel):
model_id: Optional[str]
manufacturer_name: Optional[str]
product_name: Optional[str]
product_archetype: Optional[Archetype]
certified: Optional[bool]
software_version: Optional[str]
hardware_platform_type: Optional[str]
class RelativeRotary(BaseModel):
last_event: Optional["Attributes.RotaryEvent"]
class RotaryEvent(BaseModel):
action: Optional[Literal["start", "repeat", "unknown"]]
rotation: Optional["Attributes.RotaryRotation"]
class RotaryRotation(BaseModel):
direction: Optional[Literal["clock_wise", "counter_clock_wise"]]
duration: Optional[int]
steps: Optional[int]
class Segment(BaseModel):
start: int = Field(..., ge=0)
length: int = Field(..., ge=1)
class SegmentManager(BaseModel):
configurable: Optional[bool]
max_segments: int = Field(..., ge=1)
segments: Optional[list["Attributes.Segment"]]
class SegmentRef(BaseModel):
service: Optional["Attributes.Identifier"]
index: Optional[int]
class ServiceLocation(BaseModel):
service: Optional["Attributes.Identifier"]
position: Optional["Attributes.XYZ"] = None
positions: list[Type["Attributes.XYZ"]] = Field(max_items=2, min_items=1)
class StreamProxy(BaseModel):
mode: Literal["auto", "manual"]
node: Optional["Attributes.Identifier"]
class Temp(BaseModel):
temperature: float = Field(lt=100.0, gt=-100.0)
temperature_valid: Optional[bool]
class TimedEffects(BaseModel):
effect: Optional[str]
duration: Optional[int]
status_values: Optional[list[str]]
status: Optional[str]
effect_values: Optional[list[str]]
class XY(BaseModel):
class Config:
frozen = True
allow_mutation = False
validate_assignment = True
x: Optional[float]
y: Optional[float]
class XYZ(BaseModel):
x: float = Field(ge=-1.0, le=1.0)
y: float = Field(ge=-1.0, le=1.0)
z: float = Field(ge=-1.0, le=1.0)
class HueEntsV1:
class UserConfiguration(Entity):
name: str
swupdate: dict
swupdate2: dict
whitelist: list[str]
portalstate: dict
apiversion: str
swversion: str
proxyaddress: str
for k, v in Attributes.__dict__.items():
if k.startswith("__"):
continue
v.update_forward_refs()
class HueEntsV2:
class BehaviorInstance(Entity):
type: Final[Literal["behavior_instance"]] = "behavior_instance"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
script_id: Optional[str]
enabled: Optional[bool]
state: Optional[dict[str, Any]]
configuration: Optional[dict[str, Any]]
dependees: Optional[list[Attributes.Dependee]]
status: Optional[Literal["initializing", "running", "disabled", "errored"]]
last_error: Optional[str]
metadata: Optional[dict[Literal["name"], str]]
migrated_from: Optional[str] = None
class BehaviorScript(Entity):
type: Final[Literal["behavior_script"]] = "behavior_script"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
description: Optional[str]
configuration_schema: Optional[dict[str, Any]]
trigger_schema: Optional[dict[str, Any]]
state_schema: Optional[dict[str, Any]]
version: Optional[str]
metadata: Optional[dict[str, str]]
class Bridge(Entity):
type: Final[Literal["bridge"]] = "bridge"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
bridge_id: Optional[str]
time_zone: Optional[dict[str, str]]
class BridgeHome(Entity):
type: Final[Literal["bridge_home"]] = "bridge_home"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
services: Optional[list[Attributes.Identifier]]
children: Optional[list[Attributes.Identifier]]
class Button(Entity):
type: Final[Literal["button"]] = "button"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
owner: Optional[Attributes.Identifier]
metadata: Optional[dict[Literal["control_id"], int]]
button: Optional[Attributes.Button]
class Device(Entity):
type: Final[Literal["device"]] = "device"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
services: Optional[list[Attributes.Identifier]]
metadata: Optional[Attributes.Metadata]
product_data: Optional[Attributes.ProductData]
class DevicePower(Entity):
type: Final[Literal["device_power"]] = "device_power"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
owner: Optional[Attributes.Identifier]
power_state: Optional[Attributes.PowerState]
class Entertainment(Entity):
type: Final[Literal["entertainment"]] = "entertainment"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
owner: Optional[Attributes.Identifier]
renderer: Optional[bool]
proxy: Optional[bool]
max_streams: Optional[int] = Field(1, ge=1)
segments: Optional[Attributes.SegmentManager] = None
class EntertainmentConfiguration(Entity):
type: Final[
Literal["entertainment_configuration"]
] = "entertainment_configuration"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
metadata: Optional[dict[Literal["name"], str]]
name: Optional[str] = ""
configuration_type: Optional[
Literal["screen", "monitor", "music", "3dspace", "other"]
]
status: Optional[Literal["active", "inactive"]]
active_streamer: Optional[Attributes.Identifier] = None
stream_proxy: Optional[Attributes.StreamProxy]
channels: Optional[list[Attributes.EntChannel]]
locations: Optional[Attributes.EntLocation] = None
light_services: Optional[list[Attributes.Identifier]]
class GeofenceClient(Entity):
type: Final[Literal["geofence_client"]] = "geofence_client"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
name: Optional[str]
class Geolocation(Entity):
type: Final[Literal["geolocation"]] = "geolocation"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
is_configured: Optional[bool] = False
class GroupedLight(Entity):
type: Final[Literal["grouped_light"]] = "grouped_light"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
on: Optional[Attributes.On] = Field(repr=False)
alert: Optional[dict[str, list[str]]]
class Homekit(Entity):
id: UUID
type: Final[Literal["resource"]] = "resource"
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
status: Optional[Literal["paired", "pairing", "unpaired"]] = "unpaired"
class Light(Entity):
id: UUID
id_v1: Optional[str] = Field(
..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
owner: Optional[Attributes.Identifier]
metadata: Optional[Attributes.Metadata]
on: Optional[Attributes.On]
dimming: Optional[Attributes.Dimming]
dimming_delta: Optional[dict]
color_temperature: Optional[Attributes.ColorTemp]
color_temperature_delta: Optional[dict]
color: Optional[Attributes.Color]
gradient: Optional[Attributes.Gradient]
dynamics: Optional[Attributes.Dynamics]
alert: Optional[dict[str, list[str]]]
signaling: Optional[dict]
mode: Optional[str]
effects: Optional[Attributes.Effects]
type: Final[Literal["light"]] = "light"
class LightLevel(Entity):
type: Final[Literal["light_level"]] = "light_level"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
owner: Optional[Attributes.Identifier]
enabled: Optional[bool]
light: Optional[Attributes.LightLevelValue]
class Motion(Entity):
type: Final[Literal["motion"]] = "motion"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
owner: Optional[Attributes.Identifier]
enabled: Optional[bool]
motion: Optional[Attributes.Motion]
class RelativeRotary(Entity):
id: UUID
type: Final[Literal["relative_rotary"]] = "relative_rotary"
owner: Optional[Attributes.Identifier]
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
relative_rotary: Optional[Attributes.RelativeRotary]
class Resource(Entity):
id: UUID
type: Final[Literal["device"]] = "device"
id_v1: str = Field("", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$")
class Room(Entity):
type: Final[Literal["room"]] = "room"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
services: Optional[list[Attributes.Identifier]]
metadata: Optional[Attributes.Metadata]
children: Optional[list[Attributes.Identifier]]
class Scene(Entity):
id: UUID
id_v1: Optional[str] = Field(
..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
metadata: Optional[Attributes.Metadata]
group: Optional[Attributes.Identifier]
actions: Optional[list[Attributes.Actions]]
palette: Optional[Attributes.Palette]
speed: Optional[float]
auto_dynamic: Optional[bool]
type: Final[Literal["scene"]] = "scene"
class Temperature(Entity):
type: Final[Literal["temperature"]] = "temperature"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
owner: Optional[Attributes.Identifier]
enabled: Optional[bool]
temperature: Optional[Attributes.Temp]
class ZGPConnectivity(Entity):
type: Final[Literal["zgp_connectivity"]] = "zgp_connectivity"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
owner: Optional[Attributes.Identifier]
status: Optional[
Literal[
"connected",
"disconnected",
"connectivity_issue",
"unidirectional_incoming",
]
]
source_id: Optional[str]
class ZigbeeConnectivity(Entity):
type: Final[Literal["zigbee_connectivity"]] = "zigbee_connectivity"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
owner: Optional[Attributes.Identifier]
status: Optional[
Literal[
"connected",
"disconnected",
"connectivity_issue",
"unidirectional_incoming",
]
]
mac_address: Optional[str]
class Zone(Entity):
type: Final[Literal["zone"]] = "zone"
id: UUID
id_v1: Optional[str] = Field(
"", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$"
)
services: Optional[list[Attributes.Identifier]]
metadata: Attributes.Metadata
children: Optional[list[Attributes.Identifier]]
for k, v in HueEntsV2.__dict__.items():
if k.startswith("__"):
continue
v.update_forward_refs()