aioesphomeapi/aioesphomeapi/model.py

430 lines
11 KiB
Python
Raw Normal View History

2019-04-07 19:03:26 +02:00
import enum
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Type, TypeVar
2019-04-07 19:03:26 +02:00
import attr
if TYPE_CHECKING:
from .api_pb2 import HomeassistantServiceMap # type: ignore
# 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, ...)
_T = TypeVar("_T", bound="APIIntEnum")
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 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
2020-01-30 07:03:58 +01:00
@attr.s
2019-04-07 19:03:26 +02:00
class APIVersion:
major = attr.ib(type=int, default=0)
minor = attr.ib(type=int, default=0)
2019-04-07 19:03:26 +02:00
@attr.s
class DeviceInfo:
uses_password = attr.ib(type=bool, default=False)
name = attr.ib(type=str, default="")
mac_address = attr.ib(type=str, default="")
compilation_time = attr.ib(type=str, default="")
model = attr.ib(type=str, default="")
has_deep_sleep = attr.ib(type=bool, default=False)
esphome_version = attr.ib(type=str, default="")
2019-04-07 19:03:26 +02:00
@attr.s
class EntityInfo:
object_id = attr.ib(type=str, default="")
key = attr.ib(type=int, default=0)
name = attr.ib(type=str, default="")
unique_id = attr.ib(type=str, default="")
2019-04-07 19:03:26 +02:00
@attr.s
class EntityState:
key = attr.ib(type=int, default=0)
2019-04-07 19:03:26 +02:00
# ==================== BINARY SENSOR ====================
@attr.s
class BinarySensorInfo(EntityInfo):
device_class = attr.ib(type=str, default="")
is_status_binary_sensor = attr.ib(type=bool, default=False)
2019-04-07 19:03:26 +02:00
@attr.s
class BinarySensorState(EntityState):
state = attr.ib(type=bool, default=False)
2019-11-12 14:55:35 +01:00
missing_state = attr.ib(type=bool, default=False)
2019-04-07 19:03:26 +02:00
# ==================== COVER ====================
@attr.s
class CoverInfo(EntityInfo):
assumed_state = attr.ib(type=bool, default=False)
supports_position = attr.ib(type=bool, default=False)
supports_tilt = attr.ib(type=bool, default=False)
device_class = attr.ib(type=str, default="")
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
@attr.s
class CoverState(EntityState):
legacy_state = attr.ib(
type=LegacyCoverState,
converter=LegacyCoverState.convert, # type: ignore
default=LegacyCoverState.OPEN,
)
position = attr.ib(type=float, default=0.0)
tilt = attr.ib(type=float, default=0.0)
current_operation = attr.ib(
type=CoverOperation,
converter=CoverOperation.convert, # type: ignore
default=CoverOperation.IDLE,
)
2019-04-07 19:03:26 +02:00
def is_closed(self, api_version: APIVersion) -> bool:
2019-04-07 19:03:26 +02:00
if api_version >= APIVersion(1, 1):
return self.position == 0.0
return self.legacy_state == LegacyCoverState.CLOSED
# ==================== FAN ====================
@attr.s
class FanInfo(EntityInfo):
supports_oscillation = attr.ib(type=bool, default=False)
supports_speed = attr.ib(type=bool, default=False)
2020-12-14 04:16:37 +01:00
supports_direction = attr.ib(type=bool, default=False)
2021-06-18 16:16:20 +02:00
supported_speed_levels = attr.ib(type=int, default=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
2019-04-07 19:03:26 +02:00
@attr.s
class FanState(EntityState):
state = attr.ib(type=bool, default=False)
oscillating = attr.ib(type=bool, default=False)
speed = attr.ib(
type=Optional[FanSpeed],
converter=FanSpeed.convert, # type: ignore
default=FanSpeed.LOW,
)
speed_level = attr.ib(type=int, default=0)
direction = attr.ib(
type=FanDirection,
converter=FanDirection.convert, # type: ignore
default=FanDirection.FORWARD,
)
2019-04-07 19:03:26 +02:00
# ==================== LIGHT ====================
@attr.s
class LightInfo(EntityInfo):
supports_brightness = attr.ib(type=bool, default=False)
supports_rgb = attr.ib(type=bool, default=False)
supports_white_value = attr.ib(type=bool, default=False)
supports_color_temperature = attr.ib(type=bool, default=False)
min_mireds = attr.ib(type=float, default=0.0)
max_mireds = attr.ib(type=float, default=0.0)
effects = attr.ib(type=List[str], converter=list, factory=list)
2019-04-07 19:03:26 +02:00
@attr.s
class LightState(EntityState):
state = attr.ib(type=bool, default=False)
brightness = attr.ib(type=float, default=0.0)
red = attr.ib(type=float, default=0.0)
green = attr.ib(type=float, default=0.0)
blue = attr.ib(type=float, default=0.0)
white = attr.ib(type=float, default=0.0)
color_temperature = attr.ib(type=float, default=0.0)
effect = attr.ib(type=str, default="")
2019-04-07 19:03:26 +02:00
# ==================== SENSOR ====================
2021-06-18 16:57:07 +02:00
class SensorStateClass(APIIntEnum):
NONE = 0
MEASUREMENT = 1
2019-04-07 19:03:26 +02:00
@attr.s
class SensorInfo(EntityInfo):
icon = attr.ib(type=str, default="")
device_class = attr.ib(type=str, default="")
unit_of_measurement = attr.ib(type=str, default="")
accuracy_decimals = attr.ib(type=int, default=0)
force_update = attr.ib(type=bool, default=False)
state_class = attr.ib(
type=SensorStateClass,
converter=SensorStateClass.convert, # type: ignore
default=SensorStateClass.NONE,
)
2019-04-07 19:03:26 +02:00
@attr.s
class SensorState(EntityState):
state = attr.ib(type=float, default=0.0)
2019-11-12 14:55:35 +01:00
missing_state = attr.ib(type=bool, default=False)
2019-04-07 19:03:26 +02:00
# ==================== SWITCH ====================
@attr.s
class SwitchInfo(EntityInfo):
icon = attr.ib(type=str, default="")
assumed_state = attr.ib(type=bool, default=False)
2019-04-07 19:03:26 +02:00
@attr.s
class SwitchState(EntityState):
state = attr.ib(type=bool, default=False)
2019-04-07 19:03:26 +02:00
# ==================== TEXT SENSOR ====================
@attr.s
class TextSensorInfo(EntityInfo):
icon = attr.ib(type=str, default="")
2019-04-07 19:03:26 +02:00
@attr.s
class TextSensorState(EntityState):
state = attr.ib(type=str, default="")
2019-11-12 14:55:35 +01:00
missing_state = attr.ib(type=bool, default=False)
2019-04-07 19:03:26 +02:00
# ==================== CAMERA ====================
@attr.s
class CameraInfo(EntityInfo):
pass
@attr.s
class CameraState(EntityState):
image = attr.ib(type=bytes, 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
AUTO = 1
COOL = 2
HEAT = 3
FAN_ONLY = 4
DRY = 5
2021-06-18 16:57:07 +02:00
class ClimateFanMode(APIIntEnum):
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):
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
IDLE = 4
DRYING = 5
FAN = 6
2019-10-17 21:25:54 +02:00
2019-04-07 19:03:26 +02:00
@attr.s
class ClimateInfo(EntityInfo):
supports_current_temperature = attr.ib(type=bool, default=False)
supports_two_point_target_temperature = attr.ib(type=bool, default=False)
supported_modes = attr.ib(
type=List[ClimateMode],
converter=ClimateMode.convert_list, # type: ignore
factory=list,
)
visual_min_temperature = attr.ib(type=float, default=0.0)
visual_max_temperature = attr.ib(type=float, default=0.0)
visual_temperature_step = attr.ib(type=float, default=0.0)
supports_away = attr.ib(type=bool, default=False)
supports_action = attr.ib(type=bool, default=False)
supported_fan_modes = attr.ib(
type=List[ClimateFanMode],
converter=ClimateFanMode.convert_list, # type: ignore
factory=list,
)
supported_swing_modes = attr.ib(
type=List[ClimateSwingMode],
converter=ClimateSwingMode.convert_list, # type: ignore
factory=list,
)
2019-04-07 19:03:26 +02:00
@attr.s
class ClimateState(EntityState):
mode = attr.ib(
type=ClimateMode,
converter=ClimateMode.convert, # type: ignore
default=ClimateMode.OFF,
)
action = attr.ib(
type=ClimateAction,
converter=ClimateAction.convert, # type: ignore
default=ClimateAction.OFF,
)
current_temperature = attr.ib(type=float, default=0.0)
target_temperature = attr.ib(type=float, default=0.0)
target_temperature_low = attr.ib(type=float, default=0.0)
target_temperature_high = attr.ib(type=float, default=0.0)
away = attr.ib(type=bool, default=False)
fan_mode = attr.ib(
type=Optional[ClimateFanMode],
converter=ClimateFanMode.convert, # type: ignore
default=ClimateFanMode.ON,
)
swing_mode = attr.ib(
type=Optional[ClimateSwingMode],
converter=ClimateSwingMode.convert, # type: ignore
default=ClimateSwingMode.OFF,
)
2019-04-07 19:03:26 +02:00
COMPONENT_TYPE_TO_INFO = {
"binary_sensor": BinarySensorInfo,
"cover": CoverInfo,
"fan": FanInfo,
"light": LightInfo,
"sensor": SensorInfo,
"switch": SwitchInfo,
"text_sensor": TextSensorInfo,
"camera": CameraInfo,
"climate": ClimateInfo,
2019-04-07 19:03:26 +02:00
}
# ==================== USER-DEFINED SERVICES ====================
def _convert_homeassistant_service_map(
value: Iterable["HomeassistantServiceMap"],
) -> Dict[str, str]:
return {v.key: v.value for v in value}
2019-04-07 19:03:26 +02:00
@attr.s
class HomeassistantServiceCall:
service = attr.ib(type=str, default="")
is_event = attr.ib(type=bool, default=False)
data = attr.ib(
type=Dict[str, str], converter=_convert_homeassistant_service_map, factory=dict
)
data_template = attr.ib(
type=Dict[str, str], converter=_convert_homeassistant_service_map, factory=dict
)
variables = attr.ib(
type=Dict[str, str], converter=_convert_homeassistant_service_map, factory=dict
)
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
BOOL_ARRAY = 4
INT_ARRAY = 5
FLOAT_ARRAY = 6
STRING_ARRAY = 7
2019-04-07 19:03:26 +02:00
_K = TypeVar("_K")
def _attr_obj_from_dict(cls: Type[_K], **kwargs: Any) -> _K:
return cls(**{key: kwargs[key] for key in attr.fields_dict(cls)}) # type: ignore
2019-04-07 19:03:26 +02:00
@attr.s
class UserServiceArg:
name = attr.ib(type=str, default="")
type_ = attr.ib(
type=UserServiceArgType,
converter=UserServiceArgType.convert, # type: ignore
default=UserServiceArgType.BOOL,
)
2019-04-07 19:03:26 +02:00
@attr.s
class UserService:
name = attr.ib(type=str, default="")
key = attr.ib(type=int, default=0)
args = attr.ib(type=List[UserServiceArg], converter=list, factory=list)
2019-04-07 19:03:26 +02:00
@classmethod
def from_dict(cls, dict_: Dict[str, Any]) -> "UserService":
2019-04-07 19:03:26 +02:00
args = []
for arg in dict_.get("args", []):
2019-04-07 19:03:26 +02:00
args.append(_attr_obj_from_dict(UserServiceArg, **arg))
return cls(
name=dict_.get("name", ""),
key=dict_.get("key", 0),
args=args, # type: ignore
2019-04-07 19:03:26 +02:00
)
def to_dict(self) -> Dict[str, Any]:
2019-04-07 19:03:26 +02:00
return {
"name": self.name,
"key": self.key,
"args": [attr.asdict(arg) for arg in self.args],
2019-04-07 19:03:26 +02:00
}