diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py index ee8dc0bd161..b25f2745365 100644 --- a/homeassistant/components/light/xiaomi_miio.py +++ b/homeassistant/components/light/xiaomi_miio.py @@ -1,5 +1,5 @@ """ -Support for Xiaomi Philips Lights (LED Ball & Ceil). +Support for Xiaomi Philips Lights (LED Ball & Ceiling Lamp, Eyecare Lamp 2). For more details about this platform, please refer to the documentation https://home-assistant.io/components/light.xiaomi_philipslight/ @@ -21,7 +21,7 @@ from homeassistant.exceptions import PlatformNotReady _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Xiaomi Philips Light' -PLATFORM = 'xiaomi_miio' + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)), @@ -42,9 +42,7 @@ ATTR_MODEL = 'model' @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the light from config.""" - from mirobo import Ceil, DeviceException - if PLATFORM not in hass.data: - hass.data[PLATFORM] = {} + from miio import Device, DeviceException host = config.get(CONF_HOST) name = config.get(CONF_NAME) @@ -52,23 +50,43 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) + devices = [] try: - light = Ceil(host, token) + light = Device(host, token) device_info = light.info() _LOGGER.info("%s %s %s initialized", - device_info.raw['model'], - device_info.raw['fw_ver'], - device_info.raw['hw_ver']) + device_info.model, + device_info.firmware_version, + device_info.hardware_version) + + if device_info.model == 'philips.light.sread1': + from miio import PhilipsEyecare + light = PhilipsEyecare(host, token) + device = XiaomiPhilipsEyecareLamp(name, light, device_info) + devices.append(device) + elif device_info.model == 'philips.light.ceil': + from miio import Ceil + light = Ceil(host, token) + device = XiaomiPhilipsCeilingLamp(name, light, device_info) + devices.append(device) + elif device_info.model == 'philips.light.bulb': + from miio import Ceil + light = Ceil(host, token) + device = XiaomiPhilipsLightBall(name, light, device_info) + devices.append(device) + else: + _LOGGER.error( + 'Unsupported device found! Please create an issue at ' + 'https://github.com/rytilahti/python-miio/issues ' + 'and provide the following data: %s', device_info.model) - philips_light = XiaomiPhilipsLight(name, light, device_info) - hass.data[PLATFORM][host] = philips_light except DeviceException: raise PlatformNotReady - async_add_devices([philips_light], update_before_add=True) + async_add_devices(devices, update_before_add=True) -class XiaomiPhilipsLight(Light): +class XiaomiPhilipsGenericLight(Light): """Representation of a Xiaomi Philips Light.""" def __init__(self, name, light, device_info): @@ -82,7 +100,7 @@ class XiaomiPhilipsLight(Light): self._light = light self._state = None self._state_attrs = { - ATTR_MODEL: self._device_info.raw['model'], + ATTR_MODEL: self._device_info.model, } @property @@ -115,30 +133,15 @@ class XiaomiPhilipsLight(Light): """Return the brightness of this light between 0..255.""" return self._brightness - @property - def color_temp(self): - """Return the color temperature.""" - return self._color_temp - - @property - def min_mireds(self): - """Return the coldest color_temp that this light supports.""" - return 175 - - @property - def max_mireds(self): - """Return the warmest color_temp that this light supports.""" - return 333 - @property def supported_features(self): """Return the supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP + return SUPPORT_BRIGHTNESS @asyncio.coroutine def _try_command(self, mask_error, func, *args, **kwargs): """Call a light command handling error messages.""" - from mirobo import DeviceException + from miio import DeviceException try: result = yield from self.hass.async_add_job( partial(func, *args, **kwargs)) @@ -168,6 +171,68 @@ class XiaomiPhilipsLight(Light): if result: self._brightness = brightness + self._state = yield from self._try_command( + "Turning the light on failed.", self._light.on) + + @asyncio.coroutine + def async_turn_off(self, **kwargs): + """Turn the light off.""" + self._state = yield from self._try_command( + "Turning the light off failed.", self._light.off) + + @asyncio.coroutine + def async_update(self): + """Fetch state from the device.""" + from miio import DeviceException + try: + state = yield from self.hass.async_add_job(self._light.status) + _LOGGER.debug("Got new state: %s", state) + + self._state = state.is_on + self._brightness = int(255 * 0.01 * state.brightness) + + except DeviceException as ex: + _LOGGER.error("Got exception while fetching the state: %s", ex) + + @staticmethod + def translate(value, left_min, left_max, right_min, right_max): + """Map a value from left span to right span.""" + left_span = left_max - left_min + right_span = right_max - right_min + value_scaled = float(value - left_min) / float(left_span) + return int(right_min + (value_scaled * right_span)) + + +class XiaomiPhilipsLightBall(XiaomiPhilipsGenericLight, Light): + """Representation of a Xiaomi Philips Light Ball.""" + + def __init__(self, name, light, device_info): + """Initialize the light device.""" + super().__init__(name, light, device_info) + + @property + def color_temp(self): + """Return the color temperature.""" + return self._color_temp + + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + return 175 + + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + return 333 + + @property + def supported_features(self): + """Return the supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP + + @asyncio.coroutine + def async_turn_on(self, **kwargs): + """Turn the light on.""" if ATTR_COLOR_TEMP in kwargs: color_temp = kwargs[ATTR_COLOR_TEMP] percent_color_temp = self.translate( @@ -186,42 +251,64 @@ class XiaomiPhilipsLight(Light): if result: self._color_temp = color_temp - result = yield from self._try_command( + if ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + percent_brightness = int(100 * brightness / 255) + + _LOGGER.debug( + "Setting brightness: %s %s%%", + self.brightness, percent_brightness) + + result = yield from self._try_command( + "Setting brightness failed: %s", + self._light.set_brightness, percent_brightness) + + if result: + self._brightness = brightness + + self._state = yield from self._try_command( "Turning the light on failed.", self._light.on) - if result: - self._state = True - - @asyncio.coroutine - def async_turn_off(self, **kwargs): - """Turn the light off.""" - result = yield from self._try_command( - "Turning the light off failed.", self._light.off) - - if result: - self._state = True - @asyncio.coroutine def async_update(self): """Fetch state from the device.""" - from mirobo import DeviceException + from miio import DeviceException try: state = yield from self.hass.async_add_job(self._light.status) _LOGGER.debug("Got new state: %s", state) self._state = state.is_on self._brightness = int(255 * 0.01 * state.brightness) - self._color_temp = self.translate(state.color_temperature, - CCT_MIN, CCT_MAX, - self.max_mireds, self.min_mireds) + self._color_temp = self.translate( + state.color_temperature, + CCT_MIN, CCT_MAX, + self.max_mireds, self.min_mireds) except DeviceException as ex: _LOGGER.error("Got exception while fetching the state: %s", ex) - @staticmethod - def translate(value, left_min, left_max, right_min, right_max): - """Map a value from left span to right span.""" - left_span = left_max - left_min - right_span = right_max - right_min - value_scaled = float(value - left_min) / float(left_span) - return int(right_min + (value_scaled * right_span)) + +class XiaomiPhilipsCeilingLamp(XiaomiPhilipsLightBall, Light): + """Representation of a Xiaomi Philips Ceiling Lamp.""" + + def __init__(self, name, light, device_info): + """Initialize the light device.""" + super().__init__(name, light, device_info) + + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + return 175 + + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + return 370 + + +class XiaomiPhilipsEyecareLamp(XiaomiPhilipsGenericLight, Light): + """Representation of a Xiaomi Philips Eyecare Lamp 2.""" + + def __init__(self, name, light, device_info): + """Initialize the light device.""" + super().__init__(name, light, device_info)