2019-04-07 19:03:26 +02:00
|
|
|
import enum
|
2021-06-29 15:36:14 +02:00
|
|
|
from dataclasses import asdict, dataclass, field, fields
|
|
|
|
from typing import (
|
|
|
|
TYPE_CHECKING,
|
|
|
|
Any,
|
|
|
|
Callable,
|
|
|
|
Dict,
|
|
|
|
Iterable,
|
|
|
|
List,
|
|
|
|
Optional,
|
|
|
|
Type,
|
|
|
|
TypeVar,
|
|
|
|
Union,
|
|
|
|
cast,
|
|
|
|
)
|
2019-04-07 19:03:26 +02:00
|
|
|
|
2021-08-24 01:39:18 +02:00
|
|
|
from .util import fix_float_single_double_conversion
|
|
|
|
|
2021-06-18 17:57:02 +02:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
from .api_pb2 import HomeassistantServiceMap # type: ignore
|
|
|
|
|
2019-10-24 13:59:06 +02:00
|
|
|
# All fields in here should have defaults set
|
|
|
|
# Home Assistant depends on these fields being constructible
|
|
|
|
# with args from a previous version of Home Assistant.
|
|
|
|
# The default value should *always* be the Protobuf default value
|
|
|
|
# for a field (False, 0, empty string, enum with value 0, ...)
|
|
|
|
|
2021-06-18 17:57:02 +02:00
|
|
|
_T = TypeVar("_T", bound="APIIntEnum")
|
2021-06-29 15:36:14 +02:00
|
|
|
_V = TypeVar("_V")
|
2019-04-07 19:03:26 +02:00
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
|
|
|
|
class APIIntEnum(enum.IntEnum):
|
|
|
|
"""Base class for int enum values in API model."""
|
2021-06-18 17:57:02 +02:00
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
@classmethod
|
|
|
|
def convert(cls: Type[_T], value: int) -> Optional[_T]:
|
|
|
|
try:
|
|
|
|
return cls(value)
|
|
|
|
except ValueError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def convert_list(cls: Type[_T], value: List[int]) -> List[_T]:
|
|
|
|
ret = []
|
|
|
|
for x in value:
|
|
|
|
try:
|
|
|
|
ret.append(cls(x))
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
class APIModelBase:
|
|
|
|
def __post_init__(self) -> None:
|
|
|
|
for field_ in fields(type(self)):
|
|
|
|
convert = field_.metadata.get("converter")
|
|
|
|
if convert is None:
|
|
|
|
continue
|
|
|
|
val = getattr(self, field_.name)
|
|
|
|
# use this setattr to prevent FrozenInstanceError
|
|
|
|
super().__setattr__(field_.name, convert(val))
|
|
|
|
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
|
|
return asdict(self)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_dict(
|
|
|
|
cls: Type[_V], data: Dict[str, Any], *, ignore_missing: bool = True
|
|
|
|
) -> _V:
|
|
|
|
init_args = {
|
|
|
|
f.name: data[f.name]
|
|
|
|
for f in fields(cls)
|
|
|
|
if f.name in data or (not ignore_missing)
|
|
|
|
}
|
|
|
|
return cls(**init_args) # type: ignore
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_pb(cls: Type[_V], data: Any) -> _V:
|
|
|
|
init_args = {f.name: getattr(data, f.name) for f in fields(cls)}
|
|
|
|
return cls(**init_args) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
def converter_field(*, converter: Callable[[Any], _V], **kwargs: Any) -> _V:
|
|
|
|
metadata = kwargs.pop("metadata", {})
|
|
|
|
metadata["converter"] = converter
|
|
|
|
return cast(_V, field(metadata=metadata, **kwargs))
|
|
|
|
|
2019-04-07 19:03:26 +02:00
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True, order=True)
|
|
|
|
class APIVersion(APIModelBase):
|
|
|
|
major: int = 0
|
|
|
|
minor: int = 0
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
class DeviceInfo(APIModelBase):
|
|
|
|
uses_password: bool = False
|
|
|
|
name: str = ""
|
|
|
|
mac_address: str = ""
|
|
|
|
compilation_time: str = ""
|
|
|
|
model: str = ""
|
|
|
|
has_deep_sleep: bool = False
|
|
|
|
esphome_version: str = ""
|
2021-06-29 15:45:05 +02:00
|
|
|
project_name: str = ""
|
|
|
|
project_version: str = ""
|
2021-10-28 20:03:39 +02:00
|
|
|
webserver_port: int = 0
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-10-27 00:45:09 +02:00
|
|
|
class EntityCategory(APIIntEnum):
|
|
|
|
NONE = 0
|
|
|
|
CONFIG = 1
|
|
|
|
DIAGNOSTIC = 2
|
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
class EntityInfo(APIModelBase):
|
|
|
|
object_id: str = ""
|
|
|
|
key: int = 0
|
|
|
|
name: str = ""
|
|
|
|
unique_id: str = ""
|
2021-08-03 13:30:51 +02:00
|
|
|
disabled_by_default: bool = False
|
2021-10-10 10:41:37 +02:00
|
|
|
icon: str = ""
|
2021-10-27 00:45:09 +02:00
|
|
|
entity_category: Optional[EntityCategory] = converter_field(
|
|
|
|
default=EntityCategory.NONE, converter=EntityCategory.convert
|
|
|
|
)
|
2019-04-07 19:03:26 +02:00
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class EntityState(APIModelBase):
|
|
|
|
key: int = 0
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== BINARY SENSOR ====================
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class BinarySensorInfo(EntityInfo):
|
2021-06-29 15:36:14 +02:00
|
|
|
device_class: str = ""
|
|
|
|
is_status_binary_sensor: bool = False
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class BinarySensorState(EntityState):
|
2021-06-29 15:36:14 +02:00
|
|
|
state: bool = False
|
|
|
|
missing_state: bool = False
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== COVER ====================
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class CoverInfo(EntityInfo):
|
2021-06-29 15:36:14 +02:00
|
|
|
assumed_state: bool = False
|
|
|
|
supports_position: bool = False
|
|
|
|
supports_tilt: bool = False
|
|
|
|
device_class: str = ""
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
class LegacyCoverState(APIIntEnum):
|
2019-04-07 19:03:26 +02:00
|
|
|
OPEN = 0
|
|
|
|
CLOSED = 1
|
|
|
|
|
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
class LegacyCoverCommand(APIIntEnum):
|
2019-04-07 19:03:26 +02:00
|
|
|
OPEN = 0
|
|
|
|
CLOSE = 1
|
|
|
|
STOP = 2
|
|
|
|
|
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
class CoverOperation(APIIntEnum):
|
2019-04-07 19:03:26 +02:00
|
|
|
IDLE = 0
|
|
|
|
IS_OPENING = 1
|
|
|
|
IS_CLOSING = 2
|
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class CoverState(EntityState):
|
2021-06-29 15:36:14 +02:00
|
|
|
legacy_state: Optional[LegacyCoverState] = converter_field(
|
|
|
|
default=LegacyCoverState.OPEN, converter=LegacyCoverState.convert
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2021-10-21 16:52:29 +02:00
|
|
|
position: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
tilt: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
current_operation: Optional[CoverOperation] = converter_field(
|
|
|
|
default=CoverOperation.IDLE, converter=CoverOperation.convert
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2019-04-07 19:03:26 +02:00
|
|
|
|
2021-06-18 17:57:02 +02:00
|
|
|
def is_closed(self, api_version: APIVersion) -> bool:
|
2021-07-12 20:09:17 +02:00
|
|
|
if api_version < APIVersion(1, 1):
|
|
|
|
return self.legacy_state == LegacyCoverState.CLOSED
|
|
|
|
return self.position == 0.0
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== FAN ====================
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class FanInfo(EntityInfo):
|
2021-06-29 15:36:14 +02:00
|
|
|
supports_oscillation: bool = False
|
|
|
|
supports_speed: bool = False
|
|
|
|
supports_direction: bool = False
|
|
|
|
supported_speed_levels: int = 0
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
class FanSpeed(APIIntEnum):
|
2019-04-07 19:03:26 +02:00
|
|
|
LOW = 0
|
|
|
|
MEDIUM = 1
|
|
|
|
HIGH = 2
|
|
|
|
|
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
class FanDirection(APIIntEnum):
|
2020-12-14 04:16:37 +01:00
|
|
|
FORWARD = 0
|
|
|
|
REVERSE = 1
|
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class FanState(EntityState):
|
2021-06-29 15:36:14 +02:00
|
|
|
state: bool = False
|
|
|
|
oscillating: bool = False
|
|
|
|
speed: Optional[FanSpeed] = converter_field(
|
|
|
|
default=FanSpeed.LOW, converter=FanSpeed.convert
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
speed_level: int = 0
|
|
|
|
direction: Optional[FanDirection] = converter_field(
|
|
|
|
default=FanDirection.FORWARD, converter=FanDirection.convert
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== LIGHT ====================
|
2021-08-25 13:45:28 +02:00
|
|
|
class LightColorCapability(enum.IntFlag):
|
|
|
|
ON_OFF = 1 << 0
|
|
|
|
BRIGHTNESS = 1 << 1
|
|
|
|
WHITE = 1 << 2
|
|
|
|
COLOR_TEMPERATURE = 1 << 3
|
|
|
|
COLD_WARM_WHITE = 1 << 4
|
|
|
|
RGB = 1 << 5
|
2021-07-29 19:16:25 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class LightInfo(EntityInfo):
|
2021-08-25 13:45:28 +02:00
|
|
|
supported_color_modes: List[int] = converter_field(
|
|
|
|
default_factory=list, converter=list
|
2021-07-29 19:16:25 +02:00
|
|
|
)
|
2021-10-21 16:52:29 +02:00
|
|
|
min_mireds: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
max_mireds: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
effects: List[str] = converter_field(default_factory=list, converter=list)
|
2019-04-07 19:03:26 +02:00
|
|
|
|
2021-07-29 19:16:25 +02:00
|
|
|
# deprecated, do not use
|
|
|
|
legacy_supports_brightness: bool = False
|
|
|
|
legacy_supports_rgb: bool = False
|
|
|
|
legacy_supports_white_value: bool = False
|
|
|
|
legacy_supports_color_temperature: bool = False
|
|
|
|
|
2021-08-25 13:45:28 +02:00
|
|
|
def supported_color_modes_compat(self, api_version: APIVersion) -> List[int]:
|
2021-07-29 19:16:25 +02:00
|
|
|
if api_version < APIVersion(1, 6):
|
|
|
|
key = (
|
|
|
|
self.legacy_supports_brightness,
|
|
|
|
self.legacy_supports_rgb,
|
|
|
|
self.legacy_supports_white_value,
|
|
|
|
self.legacy_supports_color_temperature,
|
|
|
|
)
|
|
|
|
# map legacy flags to color modes,
|
|
|
|
# key: (brightness, rgb, white, color_temp)
|
|
|
|
modes_map = {
|
2021-08-25 13:45:28 +02:00
|
|
|
(False, False, False, False): [LightColorCapability.ON_OFF],
|
|
|
|
(True, False, False, False): [
|
|
|
|
LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS
|
|
|
|
],
|
|
|
|
(True, False, False, True): [
|
|
|
|
LightColorCapability.ON_OFF
|
|
|
|
| LightColorCapability.BRIGHTNESS
|
|
|
|
| LightColorCapability.COLOR_TEMPERATURE
|
|
|
|
],
|
|
|
|
(True, True, False, False): [
|
|
|
|
LightColorCapability.ON_OFF
|
|
|
|
| LightColorCapability.BRIGHTNESS
|
|
|
|
| LightColorCapability.RGB
|
|
|
|
],
|
|
|
|
(True, True, True, False): [
|
|
|
|
LightColorCapability.ON_OFF
|
|
|
|
| LightColorCapability.BRIGHTNESS
|
|
|
|
| LightColorCapability.RGB
|
|
|
|
| LightColorCapability.WHITE
|
|
|
|
],
|
|
|
|
(True, True, False, True): [
|
|
|
|
LightColorCapability.ON_OFF
|
|
|
|
| LightColorCapability.BRIGHTNESS
|
|
|
|
| LightColorCapability.RGB
|
|
|
|
| LightColorCapability.COLOR_TEMPERATURE
|
|
|
|
],
|
|
|
|
(True, True, True, True): [
|
|
|
|
LightColorCapability.ON_OFF
|
|
|
|
| LightColorCapability.BRIGHTNESS
|
|
|
|
| LightColorCapability.RGB
|
|
|
|
| LightColorCapability.WHITE
|
|
|
|
| LightColorCapability.COLOR_TEMPERATURE
|
|
|
|
],
|
2021-07-29 19:16:25 +02:00
|
|
|
}
|
|
|
|
|
2021-08-25 13:45:28 +02:00
|
|
|
return cast(List[int], modes_map[key]) if key in modes_map else []
|
2021-07-29 19:16:25 +02:00
|
|
|
|
|
|
|
return self.supported_color_modes
|
|
|
|
|
2019-04-07 19:03:26 +02:00
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class LightState(EntityState):
|
2021-06-29 15:36:14 +02:00
|
|
|
state: bool = False
|
2021-10-21 16:52:29 +02:00
|
|
|
brightness: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
2021-08-25 13:45:28 +02:00
|
|
|
color_mode: int = 0
|
2021-10-21 16:52:29 +02:00
|
|
|
color_brightness: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
red: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
green: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
blue: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
white: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
color_temperature: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
cold_white: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
warm_white: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
effect: str = ""
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== SENSOR ====================
|
2021-06-18 16:57:07 +02:00
|
|
|
class SensorStateClass(APIIntEnum):
|
2021-05-25 23:39:01 +02:00
|
|
|
NONE = 0
|
|
|
|
MEASUREMENT = 1
|
2021-08-23 17:02:10 +02:00
|
|
|
TOTAL_INCREASING = 2
|
2021-05-25 23:39:01 +02:00
|
|
|
|
2021-06-18 17:57:02 +02:00
|
|
|
|
2021-07-20 22:44:55 +02:00
|
|
|
class LastResetType(APIIntEnum):
|
|
|
|
NONE = 0
|
|
|
|
NEVER = 1
|
|
|
|
AUTO = 2
|
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class SensorInfo(EntityInfo):
|
2021-06-29 15:36:14 +02:00
|
|
|
device_class: str = ""
|
|
|
|
unit_of_measurement: str = ""
|
|
|
|
accuracy_decimals: int = 0
|
|
|
|
force_update: bool = False
|
|
|
|
state_class: Optional[SensorStateClass] = converter_field(
|
|
|
|
default=SensorStateClass.NONE, converter=SensorStateClass.convert
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2021-07-20 22:44:55 +02:00
|
|
|
last_reset_type: Optional[LastResetType] = converter_field(
|
|
|
|
default=LastResetType.NONE, converter=LastResetType.convert
|
|
|
|
)
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class SensorState(EntityState):
|
2021-06-29 15:36:14 +02:00
|
|
|
state: float = 0.0
|
|
|
|
missing_state: bool = False
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== SWITCH ====================
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class SwitchInfo(EntityInfo):
|
2021-06-29 15:36:14 +02:00
|
|
|
assumed_state: bool = False
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class SwitchState(EntityState):
|
2021-06-29 15:36:14 +02:00
|
|
|
state: bool = False
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== TEXT SENSOR ====================
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class TextSensorInfo(EntityInfo):
|
2021-10-10 10:41:37 +02:00
|
|
|
pass
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class TextSensorState(EntityState):
|
2021-06-29 15:36:14 +02:00
|
|
|
state: str = ""
|
|
|
|
missing_state: bool = False
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== CAMERA ====================
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class CameraInfo(EntityInfo):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class CameraState(EntityState):
|
2021-07-08 14:00:53 +02:00
|
|
|
data: bytes = field(default_factory=bytes)
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== CLIMATE ====================
|
2021-06-18 16:57:07 +02:00
|
|
|
class ClimateMode(APIIntEnum):
|
2019-04-07 19:03:26 +02:00
|
|
|
OFF = 0
|
2021-06-23 23:40:41 +02:00
|
|
|
HEAT_COOL = 1
|
2019-04-07 19:03:26 +02:00
|
|
|
COOL = 2
|
|
|
|
HEAT = 3
|
2019-11-16 16:34:14 +01:00
|
|
|
FAN_ONLY = 4
|
|
|
|
DRY = 5
|
2021-06-23 23:40:41 +02:00
|
|
|
AUTO = 6
|
2019-11-16 16:34:14 +01:00
|
|
|
|
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
class ClimateFanMode(APIIntEnum):
|
2019-11-16 16:34:14 +01:00
|
|
|
ON = 0
|
|
|
|
OFF = 1
|
|
|
|
AUTO = 2
|
|
|
|
LOW = 3
|
|
|
|
MEDIUM = 4
|
|
|
|
HIGH = 5
|
|
|
|
MIDDLE = 6
|
|
|
|
FOCUS = 7
|
|
|
|
DIFFUSE = 8
|
|
|
|
|
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
class ClimateSwingMode(APIIntEnum):
|
2019-11-16 16:34:14 +01:00
|
|
|
OFF = 0
|
|
|
|
BOTH = 1
|
|
|
|
VERTICAL = 2
|
|
|
|
HORIZONTAL = 3
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
class ClimateAction(APIIntEnum):
|
2019-10-17 21:25:54 +02:00
|
|
|
OFF = 0
|
|
|
|
COOLING = 2
|
|
|
|
HEATING = 3
|
2019-11-23 20:36:12 +01:00
|
|
|
IDLE = 4
|
|
|
|
DRYING = 5
|
|
|
|
FAN = 6
|
2019-10-17 21:25:54 +02:00
|
|
|
|
|
|
|
|
2021-06-23 23:40:41 +02:00
|
|
|
class ClimatePreset(APIIntEnum):
|
|
|
|
NONE = 0
|
|
|
|
HOME = 1
|
|
|
|
AWAY = 2
|
|
|
|
BOOST = 3
|
|
|
|
COMFORT = 4
|
|
|
|
ECO = 5
|
|
|
|
SLEEP = 6
|
|
|
|
ACTIVITY = 7
|
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class ClimateInfo(EntityInfo):
|
2021-06-29 15:36:14 +02:00
|
|
|
supports_current_temperature: bool = False
|
|
|
|
supports_two_point_target_temperature: bool = False
|
|
|
|
supported_modes: List[ClimateMode] = converter_field(
|
|
|
|
default_factory=list, converter=ClimateMode.convert_list
|
|
|
|
)
|
2021-08-24 01:39:18 +02:00
|
|
|
visual_min_temperature: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
visual_max_temperature: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
visual_temperature_step: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
legacy_supports_away: bool = False
|
|
|
|
supports_action: bool = False
|
|
|
|
supported_fan_modes: List[ClimateFanMode] = converter_field(
|
|
|
|
default_factory=list, converter=ClimateFanMode.convert_list
|
|
|
|
)
|
|
|
|
supported_swing_modes: List[ClimateSwingMode] = converter_field(
|
|
|
|
default_factory=list, converter=ClimateSwingMode.convert_list
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
supported_custom_fan_modes: List[str] = converter_field(
|
|
|
|
default_factory=list, converter=list
|
2019-11-16 16:34:14 +01:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
supported_presets: List[ClimatePreset] = converter_field(
|
|
|
|
default_factory=list, converter=ClimatePreset.convert_list
|
2019-11-16 16:34:14 +01:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
supported_custom_presets: List[str] = converter_field(
|
|
|
|
default_factory=list, converter=list
|
2021-06-23 23:40:41 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def supported_presets_compat(self, api_version: APIVersion) -> List[ClimatePreset]:
|
|
|
|
if api_version < APIVersion(1, 5):
|
|
|
|
return (
|
|
|
|
[ClimatePreset.HOME, ClimatePreset.AWAY]
|
|
|
|
if self.legacy_supports_away
|
|
|
|
else []
|
|
|
|
)
|
|
|
|
return self.supported_presets
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2019-04-07 19:03:26 +02:00
|
|
|
class ClimateState(EntityState):
|
2021-06-29 15:36:14 +02:00
|
|
|
mode: Optional[ClimateMode] = converter_field(
|
|
|
|
default=ClimateMode.OFF, converter=ClimateMode.convert
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
action: Optional[ClimateAction] = converter_field(
|
|
|
|
default=ClimateAction.OFF, converter=ClimateAction.convert
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2021-08-24 01:39:18 +02:00
|
|
|
current_temperature: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
target_temperature: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
target_temperature_low: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
target_temperature_high: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
legacy_away: bool = False
|
|
|
|
fan_mode: Optional[ClimateFanMode] = converter_field(
|
|
|
|
default=ClimateFanMode.ON, converter=ClimateFanMode.convert
|
2019-11-16 16:34:14 +01:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
swing_mode: Optional[ClimateSwingMode] = converter_field(
|
|
|
|
default=ClimateSwingMode.OFF, converter=ClimateSwingMode.convert
|
2019-11-16 16:34:14 +01:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
custom_fan_mode: str = ""
|
|
|
|
preset: Optional[ClimatePreset] = converter_field(
|
2021-07-12 20:09:17 +02:00
|
|
|
default=ClimatePreset.NONE, converter=ClimatePreset.convert
|
2021-06-23 23:40:41 +02:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
custom_preset: str = ""
|
2021-06-23 23:40:41 +02:00
|
|
|
|
|
|
|
def preset_compat(self, api_version: APIVersion) -> Optional[ClimatePreset]:
|
|
|
|
if api_version < APIVersion(1, 5):
|
|
|
|
return ClimatePreset.AWAY if self.legacy_away else ClimatePreset.HOME
|
|
|
|
return self.preset
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 12:42:38 +02:00
|
|
|
# ==================== NUMBER ====================
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2021-06-29 12:42:38 +02:00
|
|
|
class NumberInfo(EntityInfo):
|
2021-10-21 16:52:29 +02:00
|
|
|
min_value: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
|
|
|
max_value: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
2021-08-24 01:39:18 +02:00
|
|
|
step: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
2021-06-29 12:42:38 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
2021-06-29 12:42:38 +02:00
|
|
|
class NumberState(EntityState):
|
2021-08-24 01:39:18 +02:00
|
|
|
state: float = converter_field(
|
|
|
|
default=0.0, converter=fix_float_single_double_conversion
|
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
missing_state: bool = False
|
2021-06-29 12:42:38 +02:00
|
|
|
|
|
|
|
|
2021-07-26 20:51:12 +02:00
|
|
|
# ==================== SELECT ====================
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class SelectInfo(EntityInfo):
|
|
|
|
options: List[str] = converter_field(default_factory=list, converter=list)
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class SelectState(EntityState):
|
|
|
|
state: str = ""
|
|
|
|
missing_state: bool = False
|
|
|
|
|
|
|
|
|
2021-09-09 03:11:51 +02:00
|
|
|
# ==================== SIREN ====================
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class SirenInfo(EntityInfo):
|
|
|
|
tones: List[str] = converter_field(default_factory=list, converter=list)
|
|
|
|
supports_volume: bool = False
|
|
|
|
supports_duration: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class SirenState(EntityState):
|
|
|
|
state: bool = False
|
|
|
|
|
|
|
|
|
2021-11-29 01:59:23 +01:00
|
|
|
# ==================== BUTTON ====================
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class ButtonInfo(EntityInfo):
|
2021-11-30 04:53:22 +01:00
|
|
|
device_class: str = ""
|
2021-11-29 01:59:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
# ==================== INFO MAP ====================
|
|
|
|
|
2021-06-30 17:05:44 +02:00
|
|
|
COMPONENT_TYPE_TO_INFO: Dict[str, Type[EntityInfo]] = {
|
2021-06-18 17:57:02 +02:00
|
|
|
"binary_sensor": BinarySensorInfo,
|
|
|
|
"cover": CoverInfo,
|
|
|
|
"fan": FanInfo,
|
|
|
|
"light": LightInfo,
|
|
|
|
"sensor": SensorInfo,
|
|
|
|
"switch": SwitchInfo,
|
|
|
|
"text_sensor": TextSensorInfo,
|
|
|
|
"camera": CameraInfo,
|
|
|
|
"climate": ClimateInfo,
|
2021-06-29 12:42:38 +02:00
|
|
|
"number": NumberInfo,
|
2021-07-26 20:51:12 +02:00
|
|
|
"select": SelectInfo,
|
2021-09-09 03:11:51 +02:00
|
|
|
"siren": SirenInfo,
|
2021-11-29 01:59:23 +01:00
|
|
|
"button": ButtonInfo,
|
2019-04-07 19:03:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== USER-DEFINED SERVICES ====================
|
2021-06-18 17:57:02 +02:00
|
|
|
def _convert_homeassistant_service_map(
|
2021-06-29 15:36:14 +02:00
|
|
|
value: Union[Dict[str, str], Iterable["HomeassistantServiceMap"]],
|
2021-06-18 17:57:02 +02:00
|
|
|
) -> Dict[str, str]:
|
2021-06-29 15:36:14 +02:00
|
|
|
if isinstance(value, dict):
|
|
|
|
# already a dict, don't convert
|
|
|
|
return value
|
|
|
|
return {v.key: v.value for v in value} # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class HomeassistantServiceCall(APIModelBase):
|
|
|
|
service: str = ""
|
|
|
|
is_event: bool = False
|
|
|
|
data: Dict[str, str] = converter_field(
|
|
|
|
default_factory=dict, converter=_convert_homeassistant_service_map
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
data_template: Dict[str, str] = converter_field(
|
|
|
|
default_factory=dict, converter=_convert_homeassistant_service_map
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2021-06-29 15:36:14 +02:00
|
|
|
variables: Dict[str, str] = converter_field(
|
|
|
|
default_factory=dict, converter=_convert_homeassistant_service_map
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-18 16:57:07 +02:00
|
|
|
class UserServiceArgType(APIIntEnum):
|
2019-04-07 19:03:26 +02:00
|
|
|
BOOL = 0
|
|
|
|
INT = 1
|
|
|
|
FLOAT = 2
|
|
|
|
STRING = 3
|
2019-06-17 23:40:23 +02:00
|
|
|
BOOL_ARRAY = 4
|
|
|
|
INT_ARRAY = 5
|
|
|
|
FLOAT_ARRAY = 6
|
|
|
|
STRING_ARRAY = 7
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
class UserServiceArg(APIModelBase):
|
|
|
|
name: str = ""
|
|
|
|
type: Optional[UserServiceArgType] = converter_field(
|
|
|
|
default=UserServiceArgType.BOOL, converter=UserServiceArgType.convert
|
|
|
|
)
|
2021-06-18 17:57:02 +02:00
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@classmethod
|
|
|
|
def convert_list(cls, value: List[Any]) -> List["UserServiceArg"]:
|
|
|
|
ret = []
|
|
|
|
for x in value:
|
|
|
|
if isinstance(x, dict):
|
2021-06-29 16:07:12 +02:00
|
|
|
if "type_" in x and "type" not in x:
|
|
|
|
x = {**x, "type": x["type_"]}
|
|
|
|
ret.append(UserServiceArg.from_dict(x))
|
2021-06-29 15:36:14 +02:00
|
|
|
else:
|
2021-06-29 16:07:12 +02:00
|
|
|
ret.append(UserServiceArg.from_pb(x))
|
2021-06-29 15:36:14 +02:00
|
|
|
return ret
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
class UserService(APIModelBase):
|
|
|
|
name: str = ""
|
|
|
|
key: int = 0
|
|
|
|
args: List[UserServiceArg] = converter_field(
|
|
|
|
default_factory=list, converter=UserServiceArg.convert_list
|
2021-06-18 17:57:02 +02:00
|
|
|
)
|
2019-04-07 19:03:26 +02:00
|
|
|
|
|
|
|
|
2021-06-29 15:36:14 +02:00
|
|
|
class LogLevel(APIIntEnum):
|
|
|
|
LOG_LEVEL_NONE = 0
|
|
|
|
LOG_LEVEL_ERROR = 1
|
|
|
|
LOG_LEVEL_WARN = 2
|
|
|
|
LOG_LEVEL_INFO = 3
|
2021-08-16 05:50:35 +02:00
|
|
|
LOG_LEVEL_CONFIG = 4
|
|
|
|
LOG_LEVEL_DEBUG = 5
|
|
|
|
LOG_LEVEL_VERBOSE = 6
|
|
|
|
LOG_LEVEL_VERY_VERBOSE = 7
|