Skip to content
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

Add basic Philips Moonlight support (Closes: #351) #359

Merged
merged 9 commits into from
Aug 21, 2018
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Supported devices
- :doc:`Xiaomi Philips LED Ceiling Lamp <ceil>` (:class:`miio.ceil`)
- Xiaomi Philips LED Ball Lamp (:class:`miio.philips_bulb`)
- Xiaomi Philips Zhirui Smart LED Bulb E14 Candle Lamp (:class:`miio.philips_bulb`)
- Xiaomi Philips Zhirui Bedroom Smart Lamp (:class:`miio.philips_moonlight`)
- Xiaomi Universal IR Remote Controller (Chuangmi IR) (:class:`miio.chuangmi_ir`)
- Xiaomi Mi Smart Pedestal Fan V2, V3, SA1 and ZA1 (:class:`miio.fan`)
- Xiaomi Mi Air Humidifier (:class:`miio.airhumidifier`)
Expand Down
1 change: 1 addition & 0 deletions miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from miio.fan import (Fan, FanV2, FanSA1)
from miio.philips_bulb import PhilipsBulb
from miio.philips_eyecare import PhilipsEyecare
from miio.philips_moonlight import PhilipsMoonlight
from miio.powerstrip import PowerStrip
from miio.protocol import Message, Utils
from miio.vacuum import Vacuum, VacuumException
Expand Down
7 changes: 4 additions & 3 deletions miio/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import zeroconf

from . import (Device, Vacuum, ChuangmiPlug, PowerStrip, AirPurifier, AirFresh, Ceil,
PhilipsBulb, PhilipsEyecare, ChuangmiIr, AirHumidifier,
WaterPurifier, WifiSpeaker, WifiRepeater, Yeelight, Fan, Cooker,
AirConditioningCompanion)
PhilipsBulb, PhilipsEyecare, PhilipsMoonlight, ChuangmiIr,
AirHumidifier, WaterPurifier, WifiSpeaker, WifiRepeater,
Yeelight, Fan, Cooker, AirConditioningCompanion)

from .chuangmi_plug import (MODEL_CHUANGMI_PLUG_V1, MODEL_CHUANGMI_PLUG_V3,
MODEL_CHUANGMI_PLUG_M1, )
Expand Down Expand Up @@ -51,6 +51,7 @@
"philips-light-ceiling": Ceil,
"philips-light-zyceiling": Ceil,
"philips-light-sread1": PhilipsEyecare, # name needs to be checked
"philips-light-moonlight": PhilipsMoonlight, # name needs to be checked
"xiaomi-wifispeaker-v1": WifiSpeaker, # name needs to be checked
"xiaomi-repeater-v1": WifiRepeater, # name needs to be checked
"xiaomi-repeater-v3": WifiRepeater, # name needs to be checked
Expand Down
251 changes: 251 additions & 0 deletions miio/philips_moonlight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import logging
from collections import defaultdict
from typing import Any, Dict

import click

from .click_common import command, format_output
from .device import Device, DeviceException

_LOGGER = logging.getLogger(__name__)


class PhilipsMoonlightException(DeviceException):
pass


class PhilipsMoonlightStatus:
"""Container for status reports from Xiaomi Philips Zhirui Bedside Lamp."""

def __init__(self, data: Dict[str, Any]) -> None:
"""
Response of a Moonlight (philips.light.moonlight):

{'pow': 'off', 'sta': 0, 'bri': 1, 'rgb': 16741971, 'cct': 1, 'snm': 0, 'spr': 0,
'spt': 15, 'wke': 0, 'bl': 1, 'ms': 1, 'mb': 1, 'wkp': [0, 24, 0]}
"""
self.data = data

@property
def power(self) -> str:
return self.data["pow"]

@property
def is_on(self) -> bool:
return self.power == "on"

@property
def brightness(self) -> int:
return self.data["bri"]

@property
def color_temperature(self) -> int:
return self.data["cct"]

@property
def rgb(self) -> int:
return self.data["rgb"]

@property
def scene(self) -> int:
return self.data["snm"]

@property
def sleep_assistant(self) -> int:
"""
Example values:

0: Unknown
1: Unknown
2: Sleep assistant enabled
3: Awake
"""
return self.data["sta"]

@property
def sleep_off_time(self) -> int:
return self.data["spr"]

@property
def total_assistant_sleep_time(self) -> int:
return self.data["spt"]

@property
def brand_sleep(self) -> bool:
# sp_sleep_open?
return self.data["ms"] == 1

@property
def brand(self) -> bool:
# sp_xm_bracelet?
return self.data["mb"] == 1

@property
def wake_up_time(self) -> [int, int, int]:
# Example: [weekdays?, hour, minute]
return self.data["wkp"]

def __repr__(self) -> str:
s = "<PhilipsMoonlightStatus power=%s, " \
"brightness=%s, " \
"color_temperature=%s, " \
"rgb=%s, " \
"scene=%s>" % \
(self.power,
self.brightness,
self.color_temperature,
self.rgb,
self.scene)
return s

def __json__(self):
return self.data


class PhilipsMoonlight(Device):
"""Main class representing Xiaomi Philips Zhirui Bedside Lamp.

Not yet implemented features/methods:

add_mb # Add miband
get_band_period # Bracelet work time
get_mb_rssi # Miband RSSI
get_mb_mac # Miband MAC address
enable_mibs
set_band_period
miIO.bleStartSearchBand
miIO.bleGetNearbyBandList

enable_sub_voice # Sub voice control?
enable_voice # Voice control

skip_breath
set_sleep_time
set_wakeup_time
en_sleep
en_wakeup
go_night # Night light / read mode
get_wakeup_time
enable_bl # Night light

"""

@command(
default_output=format_output(
"",
"Power: {result.power}\n"
"Brightness: {result.brightness}\n"
"Color temperature: {result.color_temperature}\n"
"RGB: {result.rgb}\n"
"Scene: {result.scene}\n"
)
)
def status(self) -> PhilipsMoonlightStatus:
"""Retrieve properties."""
properties = ['pow', 'sta', 'bri', 'rgb', 'cct', 'snm', 'spr', 'spt', 'wke', 'bl', 'ms',
'mb', 'wkp']
values = self.send(
"get_prop",
properties
)

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count, values_count)

return PhilipsMoonlightStatus(
defaultdict(lambda: None, zip(properties, values)))

@command(
default_output=format_output("Powering on"),
)
def on(self):
"""Power on."""
return self.send("set_power", ["on"])

@command(
default_output=format_output("Powering off"),
)
def off(self):
"""Power off."""
return self.send("set_power", ["off"])

@command(
click.argument("rgb", type=int),
default_output=format_output("Setting color to {rgb}")
)
def set_rgb(self, rgb):
"""Set color in encoded RGB."""
if rgb < 0 or rgb > 16777215:
raise PhilipsMoonlightException("Invalid color: %s" % rgb)

return self.send("set_rgb", [rgb])

@command(
click.argument("level", type=int),
default_output=format_output("Setting brightness to {level}")
)
def set_brightness(self, level: int):
"""Set brightness level."""
if level < 1 or level > 100:
raise PhilipsMoonlightException("Invalid brightness: %s" % level)

return self.send("set_bright", [level])

@command(
click.argument("level", type=int),
default_output=format_output("Setting color temperature to {level}")
)
def set_color_temperature(self, level: int):
"""Set Correlated Color Temperature."""
if level < 1 or level > 100:
raise PhilipsMoonlightException("Invalid color temperature: %s" % level)

return self.send("set_cct", [level])

@command(
click.argument("brightness", type=int),
click.argument("cct", type=int),
default_output=format_output(
"Setting brightness to {brightness} and color temperature to {cct}")
)
def set_brightness_and_color_temperature(self, brightness: int, cct: int):
"""Set brightness level and the correlated color temperature."""
if brightness < 1 or brightness > 100:
raise PhilipsMoonlightException("Invalid brightness: %s" % brightness)

if cct < 1 or cct > 100:
raise PhilipsMoonlightException("Invalid color temperature: %s" % cct)

return self.send("set_bricct", [brightness, cct])

@command(
click.argument("brightness", type=int),
click.argument("rgb", type=int),
default_output=format_output(
"Setting brightness to {brightness} and color to {rgb}")
)
def set_brightness_and_rgb(self, brightness: int, rgb: int):
"""Set brightness level and the color."""
if brightness < 1 or brightness > 100:
raise PhilipsMoonlightException("Invalid brightness: %s" % brightness)

if rgb < 0 or rgb > 16777215:
raise PhilipsMoonlightException("Invalid color: %s" % rgb)

return self.send("set_brirgb", [brightness, rgb])

@command(
click.argument("number", type=int),
default_output=format_output("Setting fixed scene to {number}")
)
def set_scene(self, number: int):
"""Set scene number."""
if number < 1 or number > 4:
raise PhilipsMoonlightException("Invalid fixed scene number: %s" % number)

return self.send("apply_fixed_scene", [number])
Loading