Skip to content

Commit

Permalink
Pull request rytilahti#35 merged for testing.
Browse files Browse the repository at this point in the history
  • Loading branch information
syssi committed Aug 14, 2017
1 parent a69db2d commit bf4c7dd
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 2 deletions.
3 changes: 2 additions & 1 deletion mirobo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# flake8: noqa
from mirobo.protocol import Message, Utils
from mirobo.vacuumcontainers import VacuumStatus, ConsumableStatus, CleaningDetails, CleaningSummary, Timer
from mirobo.containers import VacuumStatus, ConsumableStatus, CleaningDetails, CleaningSummary, Timer
from mirobo.vacuum import Vacuum, VacuumException
from mirobo.plug import Plug
from mirobo.strip import Strip
from mirobo.ceil import Ceil
from mirobo.device import Device, DeviceException
112 changes: 112 additions & 0 deletions mirobo/ceil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from .device import Device
from typing import Any, Dict


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

# TODO: - Auto On/Off Not Supported
# - Adjust Scens 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):
"""Retrieve properties."""
properties = ['power', 'bright', 'snm', 'dv', 'cct'
'sw', 'bl', 'mb', 'ac', 'ms', ]
values = self.send(
"get_prop",
properties
)
return CeilStatus(dict(zip(properties, values)))


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']
# ['off', 0, 4, 0, [[0, 3], [0, 2], [0, 1]], 1, 1, 1]
# NOTE: ms doesn't return any value
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 cctsw(self) -> tuple:
return self.data["cctsw"]

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

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

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

def __str__(self) -> str:
s = "<CeilStatus power=%s, bright=%s, snm=%s, dv=%s, cctsw=%s " \
"bl=%s, mb=%s, ac=%s, >" % \
(self.power, self.bright, self.snm, self.dv, self.cctsw,
self.bl, self.mb, self.ac)
return s
184 changes: 184 additions & 0 deletions mirobo/ceil_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# -*- 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("Scene Number: %s" % res.snm)
click.echo("dv: %s" % res.dv)
click.echo("Scenes with Wall Switch: %s" % res.cctsw)
click.echo("Smart Midnight Light: %s" % res.bl)
click.echo("Auto On/Off When Mi Band is nearby: %s" % res.mb)
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()
4 changes: 3 additions & 1 deletion mirobo/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
0x02f2: "Xiaomi Mi Robot Vacuum",
0x00c4: "Xiaomi Smart Mi Air Purifier",
0x031a: "Xiaomi Smart home gateway",
0x0330: "Yeelight color bulb"
0x0330: "Yeelight color bulb",
0x0374: "Xiaomi Philips LED Ceiling Lamp"
}
xiaomi_devices = {y: x for x, y in xiaomi_devices_reverse.items()}

Expand Down Expand Up @@ -128,6 +129,7 @@ def _decode(self, obj, context):
jsoned = json.loads(decrypted.decode('utf-8'))
except:
_LOGGER.error("unable to parse json, was: %s", decrypted)
jsoned = b'{}'
raise

return jsoned
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'console_scripts': [
'mirobo=mirobo.vacuum_cli:cli',
'miplug=mirobo.plug_cli:cli',
'miceil=mirobo.ceil_cli:cli',
],
},
)

0 comments on commit bf4c7dd

Please sign in to comment.