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

Added support for Xiaomi Philips LED Ceiling Lamp #35

Merged
merged 8 commits into from
Aug 14, 2017
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 mirobo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
from mirobo.plug_v1 import PlugV1
from mirobo.airpurifier import AirPurifier
from mirobo.strip import Strip
from mirobo.ceil import Ceil
from mirobo.philips_eyecare import PhilipsEyecare
from mirobo.device import Device, DeviceException
107 changes: 107 additions & 0 deletions mirobo/ceil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from .device import Device
from typing import Any, Dict

class CeilStatus:
"""Container for status reports from Xiaomi Philips LED Ceiling Lamp"""

def __init__(self, data: Dict[str, Any]) -> None:
# ['power', 'bright', 'snm', 'dv', 'cctsw', 'bl', 'mb', 'ac', 'ms'
# 'sw', 'cct']
# ['off', 0, 4, 0, [[0, 3], [0, 2], [0, 1]], 1, 1, 1, 1, 99]
# NOTE: Only 8 properties can be requested at the same time
self.data = data

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

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

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

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

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

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

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

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

def __str__(self) -> str:
s = "<CeilStatus power=%s, bright=%s, cct=%s, snm=%s, dv=%s, " \
"bl=%s, ac=%, >" % \
(self.power, self.bright, self.cct, self.snm, self.dv,
self.bl, self.ac)
return s


class Ceil(Device):
"""Main class representing Xiaomi Philips LED Ceiling Lamp."""

# TODO: - Auto On/Off Not Supported
# - Adjust Scenes with Wall Switch Not Supported

def on(self):
"""Power on."""
return self.send("set_power", ["on"])

def off(self):
"""Power off."""
return self.send("set_power", ["off"])

def set_bright(self, level: int):
"""Set brightness level."""
return self.send("set_bright", [level])

def set_cct(self, level: int):
"""Set Correlated Color Temperature."""
return self.send("set_cct", [level])

def delay_off(self, seconds: int):
"""Set delay off seconds."""
return self.send("delay_off", [seconds])

def set_scene(self, num: int):
"""Set scene number."""
return self.send("apply_fixed_scene", [num])

def bl_on(self):
"""Smart Midnight Light On."""
return self.send("enable_bl", [1])

def bl_off(self):
"""Smart Midnight Light off."""
return self.send("enable_bl", [0])

def ac_on(self):
"""Auto CCT On."""
return self.send("enable_ac", [1])

def ac_off(self):
"""Auto CCT Off."""
return self.send("enable_ac", [0])

def status(self) -> CeilStatus:
"""Retrieve properties."""
properties = ['power', 'bright', 'cct', 'snm', 'dv', 'bl', 'ac', ]
values = self.send(
"get_prop",
properties
)
return CeilStatus(dict(zip(properties, values)))
183 changes: 183 additions & 0 deletions mirobo/ceil_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# -*- coding: UTF-8 -*-
import logging
import click
import sys
import ipaddress

if sys.version_info < (3, 4):
print("To use this script you need python 3.4 or newer, got %s" %
sys.version_info)
sys.exit(1)

import mirobo # noqa: E402

_LOGGER = logging.getLogger(__name__)
pass_dev = click.make_pass_decorator(mirobo.Ceil)


def validate_bright(ctx, param, value):
value = int(value)
if value < 1 or value > 100:
raise click.BadParameter('Should be a positive int between 1-100.')
return value


def validate_seconds(ctx, param, value):
value = int(value)
if value < 0 or value > 21600:
raise click.BadParameter('Should be a positive int between 1-21600.')
return value


def validate_scene(ctx, param, value):
value = int(value)
if value < 1 or value > 4:
raise click.BadParameter('Should be a positive int between 1-4.')
return value


def validate_ip(ctx, param, value):
try:
ipaddress.ip_address(value)
return value
except ValueError as ex:
raise click.BadParameter("Invalid IP: %s" % ex)


def validate_token(ctx, param, value):
token_len = len(value)
if token_len != 32:
raise click.BadParameter("Token length != 32 chars: %s" % token_len)
return value


@click.group(invoke_without_command=True)
@click.option('--ip', envvar="DEVICE_IP", callback=validate_ip)
@click.option('--token', envvar="DEVICE_TOKEN", callback=validate_token)
@click.option('-d', '--debug', default=False, count=True)
@click.pass_context
def cli(ctx, ip: str, token: str, debug: int):
"""A tool to command Xiaomi Philips LED Ceiling Lamp."""

if debug:
logging.basicConfig(level=logging.DEBUG)
_LOGGER.info("Debug mode active")
else:
logging.basicConfig(level=logging.INFO)

# if we are scanning, we do not try to connect.
if ctx.invoked_subcommand == "discover":
return

if ip is None or token is None:
click.echo("You have to give ip and token!")
sys.exit(-1)

dev = mirobo.Ceil(ip, token, debug)
_LOGGER.debug("Connecting to %s with token %s", ip, token)

ctx.obj = dev

if ctx.invoked_subcommand is None:
ctx.invoke(status)


@cli.command()
def discover():
"""Search for plugs in the network."""
mirobo.Ceil.discover()


@cli.command()
@pass_dev
def status(dev: mirobo.Ceil):
"""Returns the state information."""
res = dev.status()
if not res:
return # bail out

click.echo(click.style("Power: %s" % res.power, bold=True))
click.echo("Brightness: %s" % res.bright)
click.echo("CCT: %s" % res.cct)
click.echo("Scene Number: %s" % res.snm)
click.echo("dv: %s" % res.dv)
click.echo("Smart Midnight Light: %s" % res.bl)
click.echo("Auto CCT: %s" % res.ac)


@cli.command()
@pass_dev
def on(dev: mirobo.Ceil):
"""Power on."""
click.echo("Power on: %s" % dev.on())


@cli.command()
@pass_dev
def off(dev: mirobo.Ceil):
"""Power off."""
click.echo("Power off: %s" % dev.off())


@cli.command()
@click.argument('level', callback=validate_bright, required=True,)
@pass_dev
def set_bright(dev: mirobo.Ceil, level):
"""Set brightness level."""
click.echo("Brightness: %s" % dev.set_bright(level))


@cli.command()
@click.argument('level', callback=validate_bright, required=True,)
@pass_dev
def set_cct(dev: mirobo.Ceil, level):
"""Set CCT level."""
click.echo("CCT level: %s" % dev.set_cct(level))


@cli.command()
@click.argument('seconds', callback=validate_seconds, required=True,)
@pass_dev
def delay_off(dev: mirobo.Ceil, seconds):
"""Set delay off in seconds."""
click.echo("Delay off: %s" % dev.delay_off(seconds))


@cli.command()
@click.argument('scene', callback=validate_scene, required=True,)
@pass_dev
def set_scene(dev: mirobo.Ceil, scene):
"""Set scene number."""
click.echo("Eyecare Scene: %s" % dev.set_scene(scene))


@cli.command()
@pass_dev
def sml_on(dev: mirobo.Ceil):
"""Smart Midnight Light on."""
click.echo("Smart Midnight Light On: %s" % dev.bl_on())


@cli.command()
@pass_dev
def sml_off(dev: mirobo.Ceil):
"""Smart Midnight Light off."""
click.echo("Smart Midnight Light Off: %s" % dev.bl_off())


@cli.command()
@pass_dev
def acct_on(dev: mirobo.Ceil):
"""Auto CCT on."""
click.echo("Auto CCT On: %s" % dev.ac_on())


@cli.command()
@pass_dev
def acct_off(dev: mirobo.Ceil):
"""Auto CCT on."""
click.echo("Auto CCT Off: %s" % dev.ac_off())


if __name__ == "__main__":
cli()
3 changes: 2 additions & 1 deletion mirobo/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
0x00c4: "Xiaomi Smart Mi Air Purifier",
0x031a: "Xiaomi Smart home gateway",
0x0330: "Yeelight color bulb",
0x02f9: "Xiaomi Philips Eyecare Smart Lamp 2",
0x0374: "Xiaomi Philips LED Ceiling Lamp",
0x02f9: "Xiaomi Philips Eyecare Smart Lamp 2"
}
xiaomi_devices = {y: x for x, y in xiaomi_devices_reverse.items()}

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
'console_scripts': [
'mirobo=mirobo.vacuum_cli:cli',
'miplug=mirobo.plug_cli:cli',
'mieye=mirobo.philips_eyecare_cli:cli',
'miceil=mirobo.ceil_cli:cli',
'mieye=mirobo.philips_eyecare_cli:cli'
],
},
)