177 lines
6.1 KiB
Python
177 lines
6.1 KiB
Python
import asyncio
|
|
|
|
from homeassistant.components.light import *
|
|
from homeassistant.const import STATE_ON
|
|
from homeassistant.core import callback
|
|
from homeassistant.helpers.restore_state import RestoreEntity
|
|
|
|
from . import DOMAIN
|
|
from .core.converters import ZIGBEE, MESH_GROUP_MODEL, Converter
|
|
from .core.device import XDevice
|
|
from .core.entity import XEntity
|
|
from .core.gateway import XGateway
|
|
|
|
CONF_DEFAULT_TRANSITION = 'default_transition'
|
|
|
|
|
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
def setup(gateway: XGateway, device: XDevice, conv: Converter):
|
|
if conv.attr in device.entities:
|
|
entity: XEntity = device.entities[conv.attr]
|
|
entity.gw = gateway
|
|
elif device.type == ZIGBEE:
|
|
entity = XiaomiZigbeeLight(gateway, device, conv)
|
|
elif device.model == MESH_GROUP_MODEL:
|
|
entity = XiaomiMeshGroup(gateway, device, conv)
|
|
else:
|
|
entity = XiaomiMeshLight(gateway, device, conv)
|
|
async_add_entities([entity])
|
|
|
|
gw: XGateway = hass.data[DOMAIN][config_entry.entry_id]
|
|
gw.add_setup(__name__, setup)
|
|
|
|
|
|
# noinspection PyAbstractClass
|
|
class XiaomiLight(XEntity, LightEntity, RestoreEntity):
|
|
_attr_is_on = None
|
|
|
|
def __init__(self, gateway: 'XGateway', device: XDevice, conv: Converter):
|
|
super().__init__(gateway, device, conv)
|
|
|
|
for conv in device.converters:
|
|
if conv.attr == ATTR_BRIGHTNESS:
|
|
self._attr_supported_features |= (
|
|
SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION
|
|
)
|
|
elif conv.attr == ATTR_COLOR_TEMP:
|
|
self._attr_supported_features |= SUPPORT_COLOR_TEMP
|
|
if hasattr(conv, "minm") and hasattr(conv, "maxm"):
|
|
self._attr_min_mireds = conv.minm
|
|
self._attr_max_mireds = conv.maxm
|
|
elif hasattr(conv, "mink") and hasattr(conv, "maxk"):
|
|
self._attr_min_mireds = int(1000000 / conv.maxk)
|
|
self._attr_max_mireds = int(1000000 / conv.mink)
|
|
|
|
@callback
|
|
def async_set_state(self, data: dict):
|
|
if self.attr in data:
|
|
self._attr_is_on = data[self.attr]
|
|
# sometimes brightness and color_temp stored as string in Xiaomi DB
|
|
if ATTR_BRIGHTNESS in data:
|
|
self._attr_brightness = data[ATTR_BRIGHTNESS]
|
|
if ATTR_COLOR_TEMP in data:
|
|
self._attr_color_temp = data[ATTR_COLOR_TEMP]
|
|
|
|
@callback
|
|
def async_restore_last_state(self, state: str, attrs: dict):
|
|
self._attr_is_on = state == STATE_ON
|
|
self._attr_brightness = attrs.get(ATTR_BRIGHTNESS)
|
|
self._attr_color_temp = attrs.get(ATTR_COLOR_TEMP)
|
|
|
|
async def async_update(self):
|
|
await self.device_read(self.subscribed_attrs)
|
|
|
|
|
|
# noinspection PyAbstractClass
|
|
class XiaomiZigbeeLight(XiaomiLight):
|
|
async def async_turn_on(self, **kwargs):
|
|
if ATTR_TRANSITION in kwargs:
|
|
tr = kwargs.pop(ATTR_TRANSITION)
|
|
elif CONF_DEFAULT_TRANSITION in self.customize:
|
|
tr = self.customize[CONF_DEFAULT_TRANSITION]
|
|
else:
|
|
tr = None
|
|
|
|
if tr is not None:
|
|
if kwargs:
|
|
# For the Aqara bulb, it is important that the brightness
|
|
# parameter comes before the color_temp parameter. Only this
|
|
# way transition will work. So we use `kwargs.pop` func to set
|
|
# the exact order of parameters.
|
|
for k in (ATTR_BRIGHTNESS, ATTR_COLOR_TEMP):
|
|
if k in kwargs:
|
|
kwargs[k] = (kwargs.pop(k), tr)
|
|
else:
|
|
kwargs[ATTR_BRIGHTNESS] = (255, tr)
|
|
|
|
if not kwargs:
|
|
kwargs[self.attr] = True
|
|
|
|
await self.device_send(kwargs)
|
|
|
|
async def async_turn_off(self, **kwargs):
|
|
if ATTR_TRANSITION in kwargs:
|
|
tr = kwargs[ATTR_TRANSITION]
|
|
elif CONF_DEFAULT_TRANSITION in self.customize:
|
|
tr = self.customize[CONF_DEFAULT_TRANSITION]
|
|
else:
|
|
tr = None
|
|
|
|
if tr is not None:
|
|
await self.device_send({ATTR_BRIGHTNESS: (0, tr)})
|
|
else:
|
|
await self.device_send({self.attr: False})
|
|
|
|
|
|
# noinspection PyAbstractClass
|
|
class XiaomiMeshBase(XiaomiLight):
|
|
async def async_turn_on(self, **kwargs):
|
|
kwargs[self.attr] = True
|
|
await self.device_send(kwargs)
|
|
|
|
async def async_turn_off(self, **kwargs):
|
|
kwargs[self.attr] = False
|
|
await self.device_send(kwargs)
|
|
|
|
|
|
# noinspection PyAbstractClass
|
|
class XiaomiMeshLight(XiaomiMeshBase):
|
|
@callback
|
|
def async_set_state(self, data: dict):
|
|
super().async_set_state(data)
|
|
|
|
if "group" not in self.device.entities:
|
|
return
|
|
# convert light attr to group attr
|
|
if self.attr in data:
|
|
data["group"] = data.pop(self.attr)
|
|
group = self.device.entities["group"]
|
|
group.async_set_state(data)
|
|
group.async_write_ha_state()
|
|
|
|
|
|
# noinspection PyAbstractClass
|
|
class XiaomiMeshGroup(XiaomiMeshBase):
|
|
def __init__(self, gateway: 'XGateway', device: XDevice, conv: Converter):
|
|
super().__init__(gateway, device, conv)
|
|
|
|
if not device.extra["childs"]:
|
|
device.available = False
|
|
return
|
|
|
|
for did in device.extra["childs"]:
|
|
child = gateway.devices[did]
|
|
child.entities[self.attr] = self
|
|
|
|
async def async_will_remove_from_hass(self) -> None:
|
|
await super().async_will_remove_from_hass()
|
|
if not self.device.extra["childs"]:
|
|
return
|
|
for did in self.device.extra["childs"]:
|
|
child = self.gw.devices[did]
|
|
child.entities.pop(self.attr)
|
|
|
|
async def async_update(self):
|
|
# To update a group - request an update of its children
|
|
# update_ha_state for all child light entities
|
|
try:
|
|
childs = []
|
|
for did in self.device.extra["childs"]:
|
|
light = self.gw.devices[did].entities.get("light")
|
|
childs.append(light.async_update_ha_state(True))
|
|
if childs:
|
|
await asyncio.gather(*childs)
|
|
|
|
except Exception as e:
|
|
self.debug("Can't update child states", exc_info=e)
|