mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Simplify esphome (#22868)
* Add ESPHome climate support * Adjust line length * Update .coveragerc * Update climate.py * Simplify esphome integration * Undo change * Update cover.py
This commit is contained in:
parent
10e8f4f70a
commit
3186109172
@ -1,6 +1,7 @@
|
|||||||
"""Support for esphome devices."""
|
"""Support for esphome devices."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Callable, Tuple
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Callable, Tuple
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
@ -520,6 +521,51 @@ async def platform_async_setup_entry(hass: HomeAssistantType,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def esphome_state_property(func):
|
||||||
|
"""Wrap a state property of an esphome entity.
|
||||||
|
|
||||||
|
This checks if the state object in the entity is set, and
|
||||||
|
prevents writing NAN values to the Home Assistant state machine.
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def _wrapper(self):
|
||||||
|
if self._state is None:
|
||||||
|
return None
|
||||||
|
val = func(self)
|
||||||
|
if isinstance(val, float) and math.isnan(val):
|
||||||
|
# Home Assistant doesn't use NAN values in state machine
|
||||||
|
# (not JSON serializable)
|
||||||
|
return None
|
||||||
|
return val
|
||||||
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class EsphomeEnumMapper:
|
||||||
|
"""Helper class to convert between hass and esphome enum values."""
|
||||||
|
|
||||||
|
def __init__(self, func: Callable[[], Dict[int, str]]):
|
||||||
|
"""Construct a EsphomeEnumMapper."""
|
||||||
|
self._func = func
|
||||||
|
|
||||||
|
def from_esphome(self, value: int) -> str:
|
||||||
|
"""Convert from an esphome int representation to a hass string."""
|
||||||
|
return self._func()[value]
|
||||||
|
|
||||||
|
def from_hass(self, value: str) -> int:
|
||||||
|
"""Convert from a hass string to a esphome int representation."""
|
||||||
|
inverse = {v: k for k, v in self._func().items()}
|
||||||
|
return inverse[value]
|
||||||
|
|
||||||
|
|
||||||
|
def esphome_map_enum(func: Callable[[], Dict[int, str]]):
|
||||||
|
"""Map esphome int enum values to hass string constants.
|
||||||
|
|
||||||
|
This class has to be used as a decorator. This ensures the aioesphomeapi
|
||||||
|
import is only happening at runtime.
|
||||||
|
"""
|
||||||
|
return EsphomeEnumMapper(func)
|
||||||
|
|
||||||
|
|
||||||
class EsphomeEntity(Entity):
|
class EsphomeEntity(Entity):
|
||||||
"""Define a generic esphome entity."""
|
"""Define a generic esphome entity."""
|
||||||
|
|
||||||
@ -555,11 +601,11 @@ class EsphomeEntity(Entity):
|
|||||||
self.async_schedule_update_ha_state)
|
self.async_schedule_update_ha_state)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _on_update(self):
|
async def _on_update(self) -> None:
|
||||||
"""Update the entity state when state or static info changed."""
|
"""Update the entity state when state or static info changed."""
|
||||||
self.async_schedule_update_ha_state()
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self):
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
"""Unregister callbacks."""
|
"""Unregister callbacks."""
|
||||||
for remove_callback in self._remove_callbacks:
|
for remove_callback in self._remove_callbacks:
|
||||||
remove_callback()
|
remove_callback()
|
||||||
@ -608,7 +654,7 @@ class EsphomeEntity(Entity):
|
|||||||
return self._static_info.unique_id
|
return self._static_info.unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self) -> Dict[str, Any]:
|
||||||
"""Return device registry information for this entity."""
|
"""Return device registry information for this entity."""
|
||||||
return {
|
return {
|
||||||
'connections': {(dr.CONNECTION_NETWORK_MAC,
|
'connections': {(dr.CONNECTION_NETWORK_MAC,
|
||||||
|
@ -38,7 +38,7 @@ class EsphomeBinarySensor(EsphomeEntity, BinarySensorDevice):
|
|||||||
return super()._state
|
return super()._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self) -> Optional[bool]:
|
||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
if self._static_info.is_status_binary_sensor:
|
if self._static_info.is_status_binary_sensor:
|
||||||
# Status binary sensors indicated connected state.
|
# Status binary sensors indicated connected state.
|
||||||
@ -49,12 +49,12 @@ class EsphomeBinarySensor(EsphomeEntity, BinarySensorDevice):
|
|||||||
return self._state.state
|
return self._state.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self) -> str:
|
||||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||||
return self._static_info.device_class
|
return self._static_info.device_class
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
if self._static_info.is_status_binary_sensor:
|
if self._static_info.is_status_binary_sensor:
|
||||||
return True
|
return True
|
||||||
|
@ -47,7 +47,7 @@ class EsphomeCamera(Camera, EsphomeEntity):
|
|||||||
def _state(self) -> Optional['CameraState']:
|
def _state(self) -> Optional['CameraState']:
|
||||||
return super()._state
|
return super()._state
|
||||||
|
|
||||||
async def _on_update(self):
|
async def _on_update(self) -> None:
|
||||||
"""Notify listeners of new image when update arrives."""
|
"""Notify listeners of new image when update arrives."""
|
||||||
await super()._on_update()
|
await super()._on_update()
|
||||||
async with self._image_cond:
|
async with self._image_cond:
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""Support for ESPHome climate devices."""
|
"""Support for ESPHome climate devices."""
|
||||||
import logging
|
import logging
|
||||||
import math
|
|
||||||
from typing import TYPE_CHECKING, List, Optional
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
@ -13,7 +12,8 @@ from homeassistant.const import (
|
|||||||
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
|
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
|
||||||
STATE_OFF, TEMP_CELSIUS)
|
STATE_OFF, TEMP_CELSIUS)
|
||||||
|
|
||||||
from . import EsphomeEntity, platform_async_setup_entry
|
from . import EsphomeEntity, platform_async_setup_entry, \
|
||||||
|
esphome_state_property, esphome_map_enum
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
@ -35,18 +35,8 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _ha_climate_mode_to_esphome(mode: str) -> 'ClimateMode':
|
@esphome_map_enum
|
||||||
# pylint: disable=redefined-outer-name
|
def _climate_modes():
|
||||||
from aioesphomeapi import ClimateMode # noqa
|
|
||||||
return {
|
|
||||||
STATE_OFF: ClimateMode.OFF,
|
|
||||||
STATE_AUTO: ClimateMode.AUTO,
|
|
||||||
STATE_COOL: ClimateMode.COOL,
|
|
||||||
STATE_HEAT: ClimateMode.HEAT,
|
|
||||||
}[mode]
|
|
||||||
|
|
||||||
|
|
||||||
def _esphome_climate_mode_to_ha(mode: 'ClimateMode') -> str:
|
|
||||||
# pylint: disable=redefined-outer-name
|
# pylint: disable=redefined-outer-name
|
||||||
from aioesphomeapi import ClimateMode # noqa
|
from aioesphomeapi import ClimateMode # noqa
|
||||||
return {
|
return {
|
||||||
@ -54,7 +44,7 @@ def _esphome_climate_mode_to_ha(mode: 'ClimateMode') -> str:
|
|||||||
ClimateMode.AUTO: STATE_AUTO,
|
ClimateMode.AUTO: STATE_AUTO,
|
||||||
ClimateMode.COOL: STATE_COOL,
|
ClimateMode.COOL: STATE_COOL,
|
||||||
ClimateMode.HEAT: STATE_HEAT,
|
ClimateMode.HEAT: STATE_HEAT,
|
||||||
}[mode]
|
}
|
||||||
|
|
||||||
|
|
||||||
class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
||||||
@ -87,12 +77,12 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||||||
def operation_list(self) -> List[str]:
|
def operation_list(self) -> List[str]:
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return [
|
return [
|
||||||
_esphome_climate_mode_to_ha(mode)
|
_climate_modes.from_esphome(mode)
|
||||||
for mode in self._static_info.supported_modes
|
for mode in self._static_info.supported_modes
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_step(self):
|
def target_temperature_step(self) -> float:
|
||||||
"""Return the supported step of target temperature."""
|
"""Return the supported step of target temperature."""
|
||||||
# Round to one digit because of floating point math
|
# Round to one digit because of floating point math
|
||||||
return round(self._static_info.visual_temperature_step, 1)
|
return round(self._static_info.visual_temperature_step, 1)
|
||||||
@ -120,61 +110,41 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||||||
features |= SUPPORT_AWAY_MODE
|
features |= SUPPORT_AWAY_MODE
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def current_operation(self) -> Optional[str]:
|
def current_operation(self) -> Optional[str]:
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
if self._state is None:
|
return _climate_modes.from_esphome(self._state.mode)
|
||||||
return None
|
|
||||||
return _esphome_climate_mode_to_ha(self._state.mode)
|
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def current_temperature(self) -> Optional[float]:
|
def current_temperature(self) -> Optional[float]:
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
if math.isnan(self._state.current_temperature):
|
|
||||||
return None
|
|
||||||
return self._state.current_temperature
|
return self._state.current_temperature
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def target_temperature(self) -> Optional[float]:
|
def target_temperature(self) -> Optional[float]:
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
if math.isnan(self._state.target_temperature):
|
|
||||||
return None
|
|
||||||
return self._state.target_temperature
|
return self._state.target_temperature
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def target_temperature_low(self):
|
def target_temperature_low(self) -> Optional[float]:
|
||||||
"""Return the lowbound target temperature we try to reach."""
|
"""Return the lowbound target temperature we try to reach."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
if math.isnan(self._state.target_temperature_low):
|
|
||||||
return None
|
|
||||||
return self._state.target_temperature_low
|
return self._state.target_temperature_low
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def target_temperature_high(self):
|
def target_temperature_high(self) -> Optional[float]:
|
||||||
"""Return the highbound target temperature we try to reach."""
|
"""Return the highbound target temperature we try to reach."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
if math.isnan(self._state.target_temperature_high):
|
|
||||||
return None
|
|
||||||
return self._state.target_temperature_high
|
return self._state.target_temperature_high
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def is_away_mode_on(self):
|
def is_away_mode_on(self) -> Optional[bool]:
|
||||||
"""Return true if away mode is on."""
|
"""Return true if away mode is on."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return self._state.away
|
return self._state.away
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs) -> None:
|
||||||
"""Set new target temperature (and operation mode if set)."""
|
"""Set new target temperature (and operation mode if set)."""
|
||||||
data = {'key': self._static_info.key}
|
data = {'key': self._static_info.key}
|
||||||
if ATTR_OPERATION_MODE in kwargs:
|
if ATTR_OPERATION_MODE in kwargs:
|
||||||
data['mode'] = _ha_climate_mode_to_esphome(
|
data['mode'] = _climate_modes.from_hass(
|
||||||
kwargs[ATTR_OPERATION_MODE])
|
kwargs[ATTR_OPERATION_MODE])
|
||||||
if ATTR_TEMPERATURE in kwargs:
|
if ATTR_TEMPERATURE in kwargs:
|
||||||
data['target_temperature'] = kwargs[ATTR_TEMPERATURE]
|
data['target_temperature'] = kwargs[ATTR_TEMPERATURE]
|
||||||
@ -184,14 +154,14 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||||||
data['target_temperature_high'] = kwargs[ATTR_TARGET_TEMP_HIGH]
|
data['target_temperature_high'] = kwargs[ATTR_TARGET_TEMP_HIGH]
|
||||||
await self._client.climate_command(**data)
|
await self._client.climate_command(**data)
|
||||||
|
|
||||||
async def async_set_operation_mode(self, operation_mode):
|
async def async_set_operation_mode(self, operation_mode) -> None:
|
||||||
"""Set new target operation mode."""
|
"""Set new target operation mode."""
|
||||||
await self._client.climate_command(
|
await self._client.climate_command(
|
||||||
key=self._static_info.key,
|
key=self._static_info.key,
|
||||||
mode=_ha_climate_mode_to_esphome(operation_mode),
|
mode=_climate_modes.from_hass(operation_mode),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_turn_away_mode_on(self):
|
async def async_turn_away_mode_on(self) -> None:
|
||||||
"""Turn away mode on."""
|
"""Turn away mode on."""
|
||||||
await self._client.climate_command(key=self._static_info.key,
|
await self._client.climate_command(key=self._static_info.key,
|
||||||
away=True)
|
away=True)
|
||||||
|
@ -9,7 +9,7 @@ from homeassistant.components.cover import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
from . import EsphomeEntity, platform_async_setup_entry
|
from . import EsphomeEntity, platform_async_setup_entry, esphome_state_property
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
@ -51,7 +51,7 @@ class EsphomeCover(EsphomeEntity, CoverDevice):
|
|||||||
return flags
|
return flags
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self) -> str:
|
||||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||||
return self._static_info.device_class
|
return self._static_info.device_class
|
||||||
|
|
||||||
@ -64,41 +64,35 @@ class EsphomeCover(EsphomeEntity, CoverDevice):
|
|||||||
def _state(self) -> Optional['CoverState']:
|
def _state(self) -> Optional['CoverState']:
|
||||||
return super()._state
|
return super()._state
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def is_closed(self) -> Optional[bool]:
|
def is_closed(self) -> Optional[bool]:
|
||||||
"""Return if the cover is closed or not."""
|
"""Return if the cover is closed or not."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
# Check closed state with api version due to a protocol change
|
# Check closed state with api version due to a protocol change
|
||||||
return self._state.is_closed(self._client.api_version)
|
return self._state.is_closed(self._client.api_version)
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def is_opening(self):
|
def is_opening(self) -> bool:
|
||||||
"""Return if the cover is opening or not."""
|
"""Return if the cover is opening or not."""
|
||||||
from aioesphomeapi import CoverOperation
|
from aioesphomeapi import CoverOperation
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return self._state.current_operation == CoverOperation.IS_OPENING
|
return self._state.current_operation == CoverOperation.IS_OPENING
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def is_closing(self):
|
def is_closing(self) -> bool:
|
||||||
"""Return if the cover is closing or not."""
|
"""Return if the cover is closing or not."""
|
||||||
from aioesphomeapi import CoverOperation
|
from aioesphomeapi import CoverOperation
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return self._state.current_operation == CoverOperation.IS_CLOSING
|
return self._state.current_operation == CoverOperation.IS_CLOSING
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def current_cover_position(self) -> Optional[float]:
|
def current_cover_position(self) -> Optional[float]:
|
||||||
"""Return current position of cover. 0 is closed, 100 is open."""
|
"""Return current position of cover. 0 is closed, 100 is open."""
|
||||||
if self._state is None or not self._static_info.supports_position:
|
if not self._static_info.supports_position:
|
||||||
return None
|
return None
|
||||||
return self._state.position * 100.0
|
return self._state.position * 100.0
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def current_cover_tilt_position(self) -> Optional[float]:
|
def current_cover_tilt_position(self) -> Optional[float]:
|
||||||
"""Return current position of cover tilt. 0 is closed, 100 is open."""
|
"""Return current position of cover tilt. 0 is closed, 100 is open."""
|
||||||
if self._state is None or not self._static_info.supports_tilt:
|
if not self._static_info.supports_tilt:
|
||||||
return None
|
return None
|
||||||
return self._state.tilt * 100.0
|
return self._state.tilt * 100.0
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@ from homeassistant.components.fan import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
from . import EsphomeEntity, platform_async_setup_entry
|
from . import EsphomeEntity, platform_async_setup_entry, \
|
||||||
|
esphome_state_property, esphome_map_enum
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
@ -31,24 +32,15 @@ async def async_setup_entry(hass: HomeAssistantType,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _ha_fan_speed_to_esphome(speed: str) -> 'FanSpeed':
|
@esphome_map_enum
|
||||||
# pylint: disable=redefined-outer-name
|
def _fan_speeds():
|
||||||
from aioesphomeapi import FanSpeed # noqa
|
|
||||||
return {
|
|
||||||
SPEED_LOW: FanSpeed.LOW,
|
|
||||||
SPEED_MEDIUM: FanSpeed.MEDIUM,
|
|
||||||
SPEED_HIGH: FanSpeed.HIGH,
|
|
||||||
}[speed]
|
|
||||||
|
|
||||||
|
|
||||||
def _esphome_fan_speed_to_ha(speed: 'FanSpeed') -> str:
|
|
||||||
# pylint: disable=redefined-outer-name
|
# pylint: disable=redefined-outer-name
|
||||||
from aioesphomeapi import FanSpeed # noqa
|
from aioesphomeapi import FanSpeed # noqa
|
||||||
return {
|
return {
|
||||||
FanSpeed.LOW: SPEED_LOW,
|
FanSpeed.LOW: SPEED_LOW,
|
||||||
FanSpeed.MEDIUM: SPEED_MEDIUM,
|
FanSpeed.MEDIUM: SPEED_MEDIUM,
|
||||||
FanSpeed.HIGH: SPEED_HIGH,
|
FanSpeed.HIGH: SPEED_HIGH,
|
||||||
}[speed]
|
}
|
||||||
|
|
||||||
|
|
||||||
class EsphomeFan(EsphomeEntity, FanEntity):
|
class EsphomeFan(EsphomeEntity, FanEntity):
|
||||||
@ -69,7 +61,7 @@ class EsphomeFan(EsphomeEntity, FanEntity):
|
|||||||
return
|
return
|
||||||
|
|
||||||
await self._client.fan_command(
|
await self._client.fan_command(
|
||||||
self._static_info.key, speed=_ha_fan_speed_to_esphome(speed))
|
self._static_info.key, speed=_fan_speeds.from_hass(speed))
|
||||||
|
|
||||||
async def async_turn_on(self, speed: Optional[str] = None,
|
async def async_turn_on(self, speed: Optional[str] = None,
|
||||||
**kwargs) -> None:
|
**kwargs) -> None:
|
||||||
@ -79,7 +71,7 @@ class EsphomeFan(EsphomeEntity, FanEntity):
|
|||||||
return
|
return
|
||||||
data = {'key': self._static_info.key, 'state': True}
|
data = {'key': self._static_info.key, 'state': True}
|
||||||
if speed is not None:
|
if speed is not None:
|
||||||
data['speed'] = _ha_fan_speed_to_esphome(speed)
|
data['speed'] = _fan_speeds.from_hass(speed)
|
||||||
await self._client.fan_command(**data)
|
await self._client.fan_command(**data)
|
||||||
|
|
||||||
# pylint: disable=arguments-differ
|
# pylint: disable=arguments-differ
|
||||||
@ -87,32 +79,26 @@ class EsphomeFan(EsphomeEntity, FanEntity):
|
|||||||
"""Turn off the fan."""
|
"""Turn off the fan."""
|
||||||
await self._client.fan_command(key=self._static_info.key, state=False)
|
await self._client.fan_command(key=self._static_info.key, state=False)
|
||||||
|
|
||||||
async def async_oscillate(self, oscillating: bool):
|
async def async_oscillate(self, oscillating: bool) -> None:
|
||||||
"""Oscillate the fan."""
|
"""Oscillate the fan."""
|
||||||
await self._client.fan_command(key=self._static_info.key,
|
await self._client.fan_command(key=self._static_info.key,
|
||||||
oscillating=oscillating)
|
oscillating=oscillating)
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def is_on(self) -> Optional[bool]:
|
def is_on(self) -> Optional[bool]:
|
||||||
"""Return true if the entity is on."""
|
"""Return true if the entity is on."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return self._state.state
|
return self._state.state
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def speed(self) -> Optional[str]:
|
def speed(self) -> Optional[str]:
|
||||||
"""Return the current speed."""
|
"""Return the current speed."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
if not self._static_info.supports_speed:
|
if not self._static_info.supports_speed:
|
||||||
return None
|
return None
|
||||||
return _esphome_fan_speed_to_ha(self._state.speed)
|
return _fan_speeds.from_esphome(self._state.speed)
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def oscillating(self) -> None:
|
def oscillating(self) -> None:
|
||||||
"""Return the oscillation state."""
|
"""Return the oscillation state."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
if not self._static_info.supports_oscillation:
|
if not self._static_info.supports_oscillation:
|
||||||
return None
|
return None
|
||||||
return self._state.oscillating
|
return self._state.oscillating
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
|
|
||||||
from . import EsphomeEntity, platform_async_setup_entry
|
from . import EsphomeEntity, platform_async_setup_entry, esphome_state_property
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
@ -51,11 +51,9 @@ class EsphomeLight(EsphomeEntity, Light):
|
|||||||
def _state(self) -> Optional['LightState']:
|
def _state(self) -> Optional['LightState']:
|
||||||
return super()._state
|
return super()._state
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def is_on(self) -> Optional[bool]:
|
def is_on(self) -> Optional[bool]:
|
||||||
"""Return true if the switch is on."""
|
"""Return true if the switch is on."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return self._state.state
|
return self._state.state
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs) -> None:
|
async def async_turn_on(self, **kwargs) -> None:
|
||||||
@ -88,42 +86,32 @@ class EsphomeLight(EsphomeEntity, Light):
|
|||||||
data['transition_length'] = kwargs[ATTR_TRANSITION]
|
data['transition_length'] = kwargs[ATTR_TRANSITION]
|
||||||
await self._client.light_command(**data)
|
await self._client.light_command(**data)
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def brightness(self) -> Optional[int]:
|
def brightness(self) -> Optional[int]:
|
||||||
"""Return the brightness of this light between 0..255."""
|
"""Return the brightness of this light between 0..255."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return round(self._state.brightness * 255)
|
return round(self._state.brightness * 255)
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def hs_color(self) -> Optional[Tuple[float, float]]:
|
def hs_color(self) -> Optional[Tuple[float, float]]:
|
||||||
"""Return the hue and saturation color value [float, float]."""
|
"""Return the hue and saturation color value [float, float]."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return color_util.color_RGB_to_hs(
|
return color_util.color_RGB_to_hs(
|
||||||
self._state.red * 255,
|
self._state.red * 255,
|
||||||
self._state.green * 255,
|
self._state.green * 255,
|
||||||
self._state.blue * 255)
|
self._state.blue * 255)
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def color_temp(self) -> Optional[float]:
|
def color_temp(self) -> Optional[float]:
|
||||||
"""Return the CT color value in mireds."""
|
"""Return the CT color value in mireds."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return self._state.color_temperature
|
return self._state.color_temperature
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def white_value(self) -> Optional[int]:
|
def white_value(self) -> Optional[int]:
|
||||||
"""Return the white value of this light between 0..255."""
|
"""Return the white value of this light between 0..255."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return round(self._state.white * 255)
|
return round(self._state.white * 255)
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def effect(self) -> Optional[str]:
|
def effect(self) -> Optional[str]:
|
||||||
"""Return the current effect."""
|
"""Return the current effect."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return self._state.effect
|
return self._state.effect
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Optional
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
from . import EsphomeEntity, platform_async_setup_entry
|
from . import EsphomeEntity, platform_async_setup_entry, esphome_state_property
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
@ -53,11 +53,9 @@ class EsphomeSensor(EsphomeEntity):
|
|||||||
"""Return the icon."""
|
"""Return the icon."""
|
||||||
return self._static_info.icon
|
return self._static_info.icon
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def state(self) -> Optional[str]:
|
def state(self) -> Optional[str]:
|
||||||
"""Return the state of the entity."""
|
"""Return the state of the entity."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
if math.isnan(self._state.state):
|
if math.isnan(self._state.state):
|
||||||
return None
|
return None
|
||||||
return '{:.{prec}f}'.format(
|
return '{:.{prec}f}'.format(
|
||||||
@ -85,9 +83,7 @@ class EsphomeTextSensor(EsphomeEntity):
|
|||||||
"""Return the icon."""
|
"""Return the icon."""
|
||||||
return self._static_info.icon
|
return self._static_info.icon
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def state(self) -> Optional[str]:
|
def state(self) -> Optional[str]:
|
||||||
"""Return the state of the entity."""
|
"""Return the state of the entity."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return self._state.state
|
return self._state.state
|
||||||
|
@ -6,7 +6,7 @@ from homeassistant.components.switch import SwitchDevice
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
from . import EsphomeEntity, platform_async_setup_entry
|
from . import EsphomeEntity, platform_async_setup_entry, esphome_state_property
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
@ -50,17 +50,15 @@ class EsphomeSwitch(EsphomeEntity, SwitchDevice):
|
|||||||
"""Return true if we do optimistic updates."""
|
"""Return true if we do optimistic updates."""
|
||||||
return self._static_info.assumed_state
|
return self._static_info.assumed_state
|
||||||
|
|
||||||
@property
|
@esphome_state_property
|
||||||
def is_on(self):
|
def is_on(self) -> Optional[bool]:
|
||||||
"""Return true if the switch is on."""
|
"""Return true if the switch is on."""
|
||||||
if self._state is None:
|
|
||||||
return None
|
|
||||||
return self._state.state
|
return self._state.state
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs) -> None:
|
||||||
"""Turn the entity on."""
|
"""Turn the entity on."""
|
||||||
await self._client.switch_command(self._static_info.key, True)
|
await self._client.switch_command(self._static_info.key, True)
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs) -> None:
|
||||||
"""Turn the entity off."""
|
"""Turn the entity off."""
|
||||||
await self._client.switch_command(self._static_info.key, False)
|
await self._client.switch_command(self._static_info.key, False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user