Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 178 additions & 90 deletions homeassistant/components/light/xiaomi_miio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""
Support for Xiaomi Philips Lights (LED Ball & Ceiling Lamp, Eyecare Lamp 2).
Support for Xiaomi Philips Lights.

LED Ball, Candle, Downlight, Ceiling, Eyecare 2, Bedside & Desklamp Lamp.

For more details about this platform, please refer to the documentation
https://home-assistant.io/components/light.xiaomi_miio/
Expand All @@ -19,7 +21,7 @@
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt
from homeassistant.util import color, dt

REQUIREMENTS = ['python-miio==0.4.4', 'construct==2.9.45']

Expand All @@ -38,6 +40,7 @@
['philips.light.sread1',
'philips.light.ceiling',
'philips.light.zyceiling',
'philips.light.moonlight',
'philips.light.bulb',
'philips.light.candle',
'philips.light.candle2',
Expand All @@ -63,6 +66,13 @@
ATTR_REMINDER = 'reminder'
ATTR_EYECARE_MODE = 'eyecare_mode'

# Moonlight
ATTR_SLEEP_ASSISTANT = 'sleep_assistant'
ATTR_SLEEP_OFF_TIME = 'sleep_off_time'
ATTR_TOTAL_ASSISTANT_SLEEP_TIME = 'total_assistant_sleep_time'
ATTR_BRAND_SLEEP = 'brand_sleep'
ATTR_BRAND = 'brand'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be documented (although I saw that it's a TBD). What does brand mean in this context?

Copy link
Copy Markdown
Member Author

@syssi syssi Nov 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as home-assistant/home-assistant.io#7498 gets merged I will extend the docs.


SERVICE_SET_SCENE = 'xiaomi_miio_set_scene'
SERVICE_SET_DELAYED_TURN_OFF = 'xiaomi_miio_set_delayed_turn_off'
SERVICE_REMINDER_ON = 'xiaomi_miio_reminder_on'
Expand Down Expand Up @@ -151,6 +161,12 @@ async def async_setup_platform(hass, config, async_add_entities,
device = XiaomiPhilipsCeilingLamp(name, light, model, unique_id)
devices.append(device)
hass.data[DATA_KEY][host] = device
elif model == 'philips.light.moonlight':
from miio import PhilipsMoonlight
light = PhilipsMoonlight(host, token)
device = XiaomiPhilipsMoonlightLamp(name, light, model, unique_id)
devices.append(device)
hass.data[DATA_KEY][host] = device
elif model in ['philips.light.bulb',
'philips.light.candle',
'philips.light.candle2',
Expand Down Expand Up @@ -307,15 +323,15 @@ async def async_update(self):
from miio import DeviceException
try:
state = await self.hass.async_add_executor_job(self._light.status)
_LOGGER.debug("Got new state: %s", state)

self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)

except DeviceException as ex:
self._available = False
_LOGGER.error("Got exception while fetching the state: %s", ex)
return

_LOGGER.debug("Got new state: %s", state)
self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)


class XiaomiPhilipsGenericLight(XiaomiPhilipsAbstractLight):
Expand All @@ -335,25 +351,25 @@ async def async_update(self):
from miio import DeviceException
try:
state = await self.hass.async_add_executor_job(self._light.status)
_LOGGER.debug("Got new state: %s", state)

self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)

delayed_turn_off = self.delayed_turn_off_timestamp(
state.delay_off_countdown,
dt.utcnow(),
self._state_attrs[ATTR_DELAYED_TURN_OFF])

self._state_attrs.update({
ATTR_SCENE: state.scene,
ATTR_DELAYED_TURN_OFF: delayed_turn_off,
})

except DeviceException as ex:
self._available = False
_LOGGER.error("Got exception while fetching the state: %s", ex)
return

_LOGGER.debug("Got new state: %s", state)
self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)

delayed_turn_off = self.delayed_turn_off_timestamp(
state.delay_off_countdown,
dt.utcnow(),
self._state_attrs[ATTR_DELAYED_TURN_OFF])

self._state_attrs.update({
ATTR_SCENE: state.scene,
ATTR_DELAYED_TURN_OFF: delayed_turn_off,
})

async def async_set_scene(self, scene: int = 1):
"""Set the fixed scene."""
Expand Down Expand Up @@ -485,29 +501,29 @@ async def async_update(self):
from miio import DeviceException
try:
state = await self.hass.async_add_executor_job(self._light.status)
_LOGGER.debug("Got new state: %s", state)

self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)
self._color_temp = self.translate(
state.color_temperature,
CCT_MIN, CCT_MAX,
self.max_mireds, self.min_mireds)

delayed_turn_off = self.delayed_turn_off_timestamp(
state.delay_off_countdown,
dt.utcnow(),
self._state_attrs[ATTR_DELAYED_TURN_OFF])

self._state_attrs.update({
ATTR_SCENE: state.scene,
ATTR_DELAYED_TURN_OFF: delayed_turn_off,
})

except DeviceException as ex:
self._available = False
_LOGGER.error("Got exception while fetching the state: %s", ex)
return

_LOGGER.debug("Got new state: %s", state)
self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)
self._color_temp = self.translate(
state.color_temperature,
CCT_MIN, CCT_MAX,
self.max_mireds, self.min_mireds)

delayed_turn_off = self.delayed_turn_off_timestamp(
state.delay_off_countdown,
dt.utcnow(),
self._state_attrs[ATTR_DELAYED_TURN_OFF])

self._state_attrs.update({
ATTR_SCENE: state.scene,
ATTR_DELAYED_TURN_OFF: delayed_turn_off,
})

@staticmethod
def translate(value, left_min, left_max, right_min, right_max):
Expand Down Expand Up @@ -545,32 +561,32 @@ async def async_update(self):
from miio import DeviceException
try:
state = await self.hass.async_add_executor_job(self._light.status)
_LOGGER.debug("Got new state: %s", state)

self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)
self._color_temp = self.translate(
state.color_temperature,
CCT_MIN, CCT_MAX,
self.max_mireds, self.min_mireds)

delayed_turn_off = self.delayed_turn_off_timestamp(
state.delay_off_countdown,
dt.utcnow(),
self._state_attrs[ATTR_DELAYED_TURN_OFF])

self._state_attrs.update({
ATTR_SCENE: state.scene,
ATTR_DELAYED_TURN_OFF: delayed_turn_off,
ATTR_NIGHT_LIGHT_MODE: state.smart_night_light,
ATTR_AUTOMATIC_COLOR_TEMPERATURE:
state.automatic_color_temperature,
})

except DeviceException as ex:
self._available = False
_LOGGER.error("Got exception while fetching the state: %s", ex)
return

_LOGGER.debug("Got new state: %s", state)
self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)
self._color_temp = self.translate(
state.color_temperature,
CCT_MIN, CCT_MAX,
self.max_mireds, self.min_mireds)

delayed_turn_off = self.delayed_turn_off_timestamp(
state.delay_off_countdown,
dt.utcnow(),
self._state_attrs[ATTR_DELAYED_TURN_OFF])

self._state_attrs.update({
ATTR_SCENE: state.scene,
ATTR_DELAYED_TURN_OFF: delayed_turn_off,
ATTR_NIGHT_LIGHT_MODE: state.smart_night_light,
ATTR_AUTOMATIC_COLOR_TEMPERATURE:
state.automatic_color_temperature,
})


class XiaomiPhilipsEyecareLamp(XiaomiPhilipsGenericLight):
Expand All @@ -591,28 +607,28 @@ async def async_update(self):
from miio import DeviceException
try:
state = await self.hass.async_add_executor_job(self._light.status)
_LOGGER.debug("Got new state: %s", state)

self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)

delayed_turn_off = self.delayed_turn_off_timestamp(
state.delay_off_countdown,
dt.utcnow(),
self._state_attrs[ATTR_DELAYED_TURN_OFF])

self._state_attrs.update({
ATTR_SCENE: state.scene,
ATTR_DELAYED_TURN_OFF: delayed_turn_off,
ATTR_REMINDER: state.reminder,
ATTR_NIGHT_LIGHT_MODE: state.smart_night_light,
ATTR_EYECARE_MODE: state.eyecare,
})

except DeviceException as ex:
self._available = False
_LOGGER.error("Got exception while fetching the state: %s", ex)
return

_LOGGER.debug("Got new state: %s", state)
self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)

delayed_turn_off = self.delayed_turn_off_timestamp(
state.delay_off_countdown,
dt.utcnow(),
self._state_attrs[ATTR_DELAYED_TURN_OFF])

self._state_attrs.update({
ATTR_SCENE: state.scene,
ATTR_DELAYED_TURN_OFF: delayed_turn_off,
ATTR_REMINDER: state.reminder,
ATTR_NIGHT_LIGHT_MODE: state.smart_night_light,
ATTR_EYECARE_MODE: state.eyecare,
})

async def async_set_delayed_turn_off(self, time_period: timedelta):
"""Set delayed turn off."""
Expand Down Expand Up @@ -719,12 +735,84 @@ async def async_update(self):
from miio import DeviceException
try:
state = await self.hass.async_add_executor_job(self._light.status)
_LOGGER.debug("Got new state: %s", state)
except DeviceException as ex:
self._available = False
_LOGGER.error("Got exception while fetching the state: %s", ex)
return

_LOGGER.debug("Got new state: %s", state)
self._available = True
self._state = state.ambient
self._brightness = ceil((255 / 100.0) * state.ambient_brightness)


class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb):
"""Representation of a Xiaomi Philips Zhirui Bedside Lamp."""

def __init__(self, name, light, model, unique_id):
"""Initialize the light device."""
super().__init__(name, light, model, unique_id)

self._hs_color = None
self._state_attrs.pop(ATTR_DELAYED_TURN_OFF)
self._state_attrs.update({
ATTR_SLEEP_ASSISTANT: None,
ATTR_SLEEP_OFF_TIME: None,
ATTR_TOTAL_ASSISTANT_SLEEP_TIME: None,
ATTR_BRAND_SLEEP: None,
ATTR_BRAND: None,
})

self._available = True
self._state = state.ambient
self._brightness = ceil((255 / 100.0) * state.ambient_brightness)
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
return 153

@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
return 588
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if these should also be defined in python-miio (in kelvin, though). What do you think, does it make sense?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We should provide specs and features in the long run by python-miio.


@property
def hs_color(self) -> tuple:
"""Return the hs color value."""
return self._hs_color

@property
def supported_features(self):
"""Return the supported features."""
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP

async def async_update(self):
"""Fetch state from the device."""
from miio import DeviceException
try:
state = await self.hass.async_add_executor_job(self._light.status)
except DeviceException as ex:
self._available = False
_LOGGER.error("Got exception while fetching the state: %s", ex)
return

_LOGGER.debug("Got new state: %s", state)
self._available = True
self._state = state.is_on
self._brightness = ceil((255 / 100.0) * state.brightness)
self._color_temp = self.translate(
state.color_temperature,
CCT_MIN, CCT_MAX,
self.max_mireds, self.min_mireds)
self._hs_color = color.color_RGB_to_hs(*state.rgb)

self._state_attrs.update({
ATTR_SCENE: state.scene,
ATTR_SLEEP_ASSISTANT: state.sleep_assistant,
ATTR_SLEEP_OFF_TIME: state.sleep_off_time,
ATTR_TOTAL_ASSISTANT_SLEEP_TIME:
state.total_assistant_sleep_time,
ATTR_BRAND_SLEEP: state.brand_sleep,
ATTR_BRAND: state.brand,
})

async def async_set_delayed_turn_off(self, time_period: timedelta):
"""Set delayed turn off. Unsupported."""
return