-
-
Notifications
You must be signed in to change notification settings - Fork 37.2k
Add Philips Moonlight Bedside Lamp support #18496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7221b44
40bf97d
4d2d2dd
0f4add3
e085731
27c9033
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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/ | ||
|
|
@@ -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'] | ||
|
|
||
|
|
@@ -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', | ||
|
|
@@ -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' | ||
|
|
||
| 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' | ||
|
|
@@ -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', | ||
|
|
@@ -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): | ||
|
|
@@ -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.""" | ||
|
|
@@ -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): | ||
|
|
@@ -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): | ||
|
|
@@ -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.""" | ||
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
MartinHjelmare marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 | ||
There was a problem hiding this comment.
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?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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.