Skip to content

Commit

Permalink
Synchronize state between entities, fixes for nightlight
Browse files Browse the repository at this point in the history
  • Loading branch information
zewelor committed Mar 3, 2019
1 parent 3179689 commit d85bc75
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 52 deletions.
88 changes: 71 additions & 17 deletions homeassistant/components/yeelight/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from homeassistant.helpers import discovery
from homeassistant.helpers.discovery import load_platform
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval

REQUIREMENTS = ['yeelight==0.4.3']

Expand All @@ -36,15 +37,6 @@
CONF_FLOW_PARAMS = 'flow_params'
CONF_CUSTOM_EFFECTS = 'custom_effects'

LEGACY_DEVICE_TYPE_MAP = {
'color1': 'rgb',
'mono1': 'white',
'strip1': 'strip',
'bslamp1': 'bedside',
'ceiling1': 'ceiling',
}


ATTR_MODE = 'mode'
ATTR_COUNT = 'count'
ATTR_ACTION = 'action'
Expand All @@ -54,6 +46,9 @@
ACTION_STAY = 'stay'
ACTION_OFF = 'off'

MODE_MOONLIGHT = 'moonlight'
MODE_DAYLIGHT = 'normal'

SCAN_INTERVAL = timedelta(seconds=30)

YEELIGHT_RGB_TRANSITION = 'RGBTransition'
Expand Down Expand Up @@ -130,10 +125,8 @@ def device_discovered(service, info):
_LOGGER.debug("Adding autodetected %s", info[ATTR_HOSTNAME])

device_type = info[ATTR_DEVICE_TYPE]
legacy_device_type = \
LEGACY_DEVICE_TYPE_MAP.get(device_type, device_type)

name = "yeelight_%s_%s" % (legacy_device_type,
name = "yeelight_%s_%s" % (device_type,
info[ATTR_PROPERTIES]['mac'])
ipaddr = info[CONF_HOST]
device_config = DEVICE_SCHEMA({
Expand All @@ -145,6 +138,14 @@ def device_discovered(service, info):

discovery.listen(hass, SERVICE_YEELIGHT, device_discovered)

def async_update(event):
for device in hass.data[DATA_YEELIGHT][CONF_DEVICES].values():
device.update()

async_track_time_interval(
hass, async_update, conf[CONF_SCAN_INTERVAL]
)

for ipaddr, device_config in conf[CONF_DEVICES].items():
_LOGGER.debug("Adding configured %s", device_config[CONF_NAME])
_setup_device(hass, config, ipaddr, device_config)
Expand All @@ -159,6 +160,7 @@ def _setup_device(hass, hass_config, ipaddr, device_config):
return

device = YeelightDevice(ipaddr, device_config)

devices[ipaddr] = device

platform_config = device_config.copy()
Expand All @@ -182,6 +184,15 @@ def __init__(self, ipaddr, config):
self._name = config.get(CONF_NAME)
self._model = config.get(CONF_MODEL)
self._bulb_device = None
self.device_updated_cbs = []

def register_device_updated_cb(self, device_updated_cb):
"""Register device updated callback."""
self.device_updated_cbs.append(device_updated_cb)

def unregister_device_updated_cb(self, device_updated_cb):
"""Unregister device updated callback."""
self.device_updated_cbs.remove(device_updated_cb)

@property
def bulb(self):
Expand All @@ -192,7 +203,7 @@ def bulb(self):
self._bulb_device = yeelight.Bulb(self._ipaddr,
model=self._model)
# force init for type
self._bulb_device.get_properties(UPDATE_REQUEST_PROPERTIES)
self._update_properties()

except yeelight.BulbException as ex:
_LOGGER.error("Failed to connect to bulb %s, %s: %s",
Expand All @@ -211,14 +222,57 @@ def config(self):
return self._config

@property
def is_nightlight_supported(self):
def is_nightlight_supported(self) -> bool:
"""Return true / false if nightlight is supported"""

return self._model in NIGHTLIGHT_SUPPORTED_MODELS

@property
def is_nightlight_enabled(self) -> bool:
"""Return true / false if nightlight is currently enabled"""
return self.bulb.last_properties.get('active_mode') == '1'

def turn_on(self, duration=DEFAULT_TRANSITION):
"""Turn on device"""
import yeelight
try:
self._bulb_device.turn_on(duration=duration)
except yeelight.BulbException as ex:
_LOGGER.error("Unable to turn the bulb on: %s", ex)
return

self.update()

def turn_off(self, duration=DEFAULT_TRANSITION):
"""Turn off device"""
import yeelight
try:
self._bulb_device.turn_off(duration=duration)
except yeelight.BulbException as ex:
_LOGGER.error("Unable to turn the bulb on: %s", ex)
return

self.update()

def set_mode(self, mode: str):
"""Set a power mode."""
import yeelight
try:
self.bulb.set_power_mode(yeelight.enums.PowerMode[mode.upper()])
except yeelight.BulbException as ex:
_LOGGER.error("Unable to set the power mode: %s", ex)

self.update()

def update(self):
"""Read new properties from the device"""
if self.bulb:
return self.bulb.get_properties(UPDATE_REQUEST_PROPERTIES)
if not self.bulb:
return

self._update_properties()

for device_updated_cb in self.device_updated_cbs:
device_updated_cb(self)

return {}
def _update_properties(self):
self._bulb_device.get_properties(UPDATE_REQUEST_PROPERTIES)
49 changes: 27 additions & 22 deletions homeassistant/components/yeelight/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
color_temperature_mired_to_kelvin as mired_to_kelvin,
color_temperature_kelvin_to_mired as kelvin_to_mired)
from homeassistant.const import CONF_NAME, CONF_DEVICES, CONF_LIGHTS, CONF_HOST
from homeassistant.core import callback
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_COLOR_TEMP,
ATTR_FLASH, FLASH_SHORT, FLASH_LONG, ATTR_EFFECT, SUPPORT_BRIGHTNESS,
Expand Down Expand Up @@ -179,6 +180,21 @@ def __init__(self, device, custom_effects=None):
else:
self._custom_effects = {}

self.register_callbacks()

@callback
def register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
def after_update_callback(device):
"""Call after device was updated."""
self.async_schedule_update_ha_state(True)
self._device.register_device_updated_cb(after_update_callback)

@property
def should_poll(self):
"""No polling needed"""
return False

@property
def available(self) -> bool:
"""Return if bulb is available."""
Expand Down Expand Up @@ -290,12 +306,13 @@ def update(self) -> None:
"""Update properties from the bulb."""
import yeelight
try:
self._device.update()

if self._bulb.bulb_type == yeelight.BulbType.Color:
self._supported_features = SUPPORT_YEELIGHT_RGB
elif self._bulb.bulb_type == yeelight.BulbType.WhiteTemp:
self._supported_features = SUPPORT_YEELIGHT_WHITE_TEMP
if self._device.is_nightlight_enabled:
self._supported_features = SUPPORT_YEELIGHT
else:
self._supported_features = SUPPORT_YEELIGHT_WHITE_TEMP

if self._min_mireds is None:
model_specs = self._bulb.get_model_specs()
Expand All @@ -306,7 +323,11 @@ def update(self) -> None:

self._is_on = self._properties.get('power') == 'on'

bright = self._properties.get('bright', None)
if self._device.is_nightlight_enabled:
bright = self._properties.get('nl_br', None)
else:
bright = self._properties.get('bright', None)

if bright:
self._brightness = round(255 * (int(bright) / 100))

Expand Down Expand Up @@ -446,11 +467,7 @@ def turn_on(self, **kwargs) -> None:
if ATTR_TRANSITION in kwargs: # passed kwarg overrides config
duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s

try:
self._bulb.turn_on(duration=duration)
except yeelight.BulbException as ex:
_LOGGER.error("Unable to turn the bulb on: %s", ex)
return
self._device.turn_on(duration=duration)

if self.config[CONF_MODE_MUSIC] and not self._bulb.music_mode:
try:
Expand Down Expand Up @@ -482,23 +499,11 @@ def turn_on(self, **kwargs) -> None:

def turn_off(self, **kwargs) -> None:
"""Turn off."""
import yeelight
duration = int(self.config[CONF_TRANSITION]) # in ms
if ATTR_TRANSITION in kwargs: # passed kwarg overrides config
duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s
try:
self._bulb.turn_off(duration=duration)
except yeelight.BulbException as ex:
_LOGGER.error("Unable to turn the bulb off: %s", ex)

def set_mode(self, mode: str):
"""Set a power mode."""
import yeelight
try:
self._bulb.set_power_mode(yeelight.enums.PowerMode[mode.upper()])
self.async_schedule_update_ha_state(True)
except yeelight.BulbException as ex:
_LOGGER.error("Unable to set the power mode: %s", ex)
self._device.turn_off(duration=duration)

def start_flow(self, transitions, count=0, action=ACTION_RECOVER):
"""Start flow."""
Expand Down
33 changes: 20 additions & 13 deletions homeassistant/components/yeelight/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
from netdisco.const import ATTR_HOST

from homeassistant.const import CONF_DEVICES
from homeassistant.core import callback
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.yeelight import DATA_YEELIGHT
from homeassistant.components.yeelight import DATA_YEELIGHT, MODE_MOONLIGHT, \
MODE_DAYLIGHT

DEPENDENCIES = ['yeelight']

_LOGGER = logging.getLogger(__name__)


def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Yeelight switches."""

Expand All @@ -31,10 +32,24 @@ class YeelightPowerModeSwitch(ToggleEntity):

def __init__(self, device):
self._device = device
self.register_callbacks()

@callback
def register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
def after_update_callback(device):
"""Call after device was updated."""
self.async_schedule_update_ha_state(True)
self._device.register_device_updated_cb(after_update_callback)

@property
def should_poll(self):
"""No polling needed"""
return False

@property
def is_on(self) -> bool:
return self._bulb.last_properties.get('active_mode') == '1'
return self._device.is_nightlight_enabled

@property
def name(self):
Expand All @@ -50,15 +65,7 @@ def icon(self):
return 'mdi:weather-night'

def turn_on(self, **kwargs) -> None:
import yeelight

self._bulb.set_power_mode(yeelight.enums.PowerMode.MOONLIGHT)
self._device.update()
self.async_schedule_update_ha_state(True)
self._device.set_mode(MODE_MOONLIGHT)

def turn_off(self, **kwargs) -> None:
import yeelight

self._bulb.set_power_mode(yeelight.enums.PowerMode.NORMAL)
self._device.update()
self.async_schedule_update_ha_state(True)
self._device.set_mode(MODE_DAYLIGHT)

0 comments on commit d85bc75

Please sign in to comment.