From ac20c00acc535bab7e1d1b8db4a4a0c542bd8557 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Mon, 14 Aug 2017 08:05:07 +0200 Subject: [PATCH] Pull request https://github.com/rytilahti/python-mirobo/pull/34 merged for testing. --- mirobo/__init__.py | 1 + mirobo/philips_eyecare.py | 134 +++++++++++++++++++++++ mirobo/philips_eyecare_cli.py | 199 ++++++++++++++++++++++++++++++++++ mirobo/protocol.py | 3 +- setup.py | 1 + 5 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 mirobo/philips_eyecare.py create mode 100644 mirobo/philips_eyecare_cli.py diff --git a/mirobo/__init__.py b/mirobo/__init__.py index c797c35d7..640652b01 100644 --- a/mirobo/__init__.py +++ b/mirobo/__init__.py @@ -5,4 +5,5 @@ from mirobo.plug import Plug from mirobo.strip import Strip from mirobo.ceil import Ceil +from mirobo.philips_eyecare import PhilipsEyecare from mirobo.device import Device, DeviceException diff --git a/mirobo/philips_eyecare.py b/mirobo/philips_eyecare.py new file mode 100644 index 000000000..eb28590f0 --- /dev/null +++ b/mirobo/philips_eyecare.py @@ -0,0 +1,134 @@ +from .device import Device +from typing import Any, Dict + + +class PhilipsEyecare(Device): + """Main class representing Xiaomi Philips Eyecare Smart Lamp 2.""" + + def on(self): + """Power on.""" + return self.send("set_power", ["on"]) + + def off(self): + """Power off.""" + return self.send("set_power", ["off"]) + + def eyecare_on(self): + """Eyecare on.""" + return self.send("set_eyecare", ["on"]) + + def eyecare_off(self): + """Eyecare off.""" + return self.send("set_eyecare", ["off"]) + + def set_bright(self, level: int): + """Set brightness level.""" + return self.send("set_bright", [level]) + + def set_user_scene(self, num: int): + """Set eyecare user scene.""" + return self.send("set_user_scene", [num]) + + def delay_off(self, minutes: int): + """Set delay off minutes.""" + return self.send("delay_off", [minutes]) + + def bl_on(self): + """Night Light On.""" + return self.send("enable_bl", ["on"]) + + def bl_off(self): + """Night Light Off.""" + return self.send("enable_bl", ["off"]) + + def notify_user_on(self): + """Notify User On.""" + return self.send("set_notifyuser", ["on"]) + + def notify_user_off(self): + """Notify USer Off.""" + return self.send("set_notifyuser", ["off"]) + + def amb_on(self): + """Amblient Light On.""" + return self.send("enable_amb", ["on"]) + + def amb_off(self): + """Ambient Light Off.""" + return self.send("enable_amb", ["off"]) + + def set_amb_bright(self, level: int): + """Set Ambient Light brightness level.""" + return self.send("set_amb_bright", [level]) + + def status(self): + """Retrieve properties.""" + properties = ['power', 'bright', 'notifystatus', 'ambstatus', + 'ambvalue', 'eyecare', 'scene_num', 'bls', + 'dvalue', ] + values = self.send( + "get_prop", + properties + ) + return PhilipsEyecareStatus(dict(zip(properties, values))) + + +class PhilipsEyecareStatus: + """Container for status reports from Xiaomi Philips Eyecare Smart Lamp 2""" + + def __init__(self, data: Dict[str, Any]) -> None: + # ["power","bright","notifystatus","ambstatus","ambvalue","eyecare", + # "scene_num","bls","dvalue"]} + # ["off",5,"off","off",41,"on",3,"on",0] + 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 notifystatus(self) -> str: + return self.data["notifystatus"] + + @property + def ambstatus(self) -> str: + return self.data["ambstatus"] + + @property + def ambvalue(self) -> int: + return self.data["ambvalue"] + + @property + def eyecare(self) -> str: + return self.data["eyecare"] + + @property + def scene_num(self) -> str: + return self.data["scene_num"] + + @property + def bls(self) -> str: + return self.data["bls"] + + @property + def dvalue(self) -> int: + return self.data["dvalue"] + + def __str__(self) -> str: + s = "" % \ + (self.power, self.bright, + self.notifystatus, self.ambstatus, self.ambvalue, + self.eyecare, self.scene_num, + self.bls) + return s diff --git a/mirobo/philips_eyecare_cli.py b/mirobo/philips_eyecare_cli.py new file mode 100644 index 000000000..9657397e7 --- /dev/null +++ b/mirobo/philips_eyecare_cli.py @@ -0,0 +1,199 @@ +# -*- 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.PhilipsEyecare) + + +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_minutes(ctx, param, value): + value = int(value) + if value < 0 or value > 60: + raise click.BadParameter('Should be a positive int between 1-60.') + return value + + +def validate_scene(ctx, param, value): + value = int(value) + if value < 1 or value > 3: + raise click.BadParameter('Should be a positive int between 1-3.') + 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 Eyecare Smart Lamp 2.""" + + 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.PhilipsEyecare(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.PhilipsEyecare.discover() + + +@cli.command() +@pass_dev +def status(dev: mirobo.PhilipsEyecare): + """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("Eye Fatigue Reminder: %s" % res.notifystatus) + click.echo("Ambient Light: %s" % res.ambstatus) + click.echo("Ambient Light Brightness: %s" % res.ambvalue) + click.echo("Eyecare Mode: %s" % res.eyecare) + click.echo("Eyecare Scene: %s" % res.scene_num) + click.echo("Night Light: %s " % res.bls) + click.echo("Delay Off: %s minutes" % res.dvalue) + + +@cli.command() +@pass_dev +def on(dev: mirobo.PhilipsEyecare): + """Power on.""" + click.echo("Power on: %s" % dev.on()) + + +@cli.command() +@pass_dev +def off(dev: mirobo.PhilipsEyecare): + """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.PhilipsEyecare, level): + """Set brightness level.""" + click.echo("Brightness: %s" % dev.set_bright(level)) + + +@cli.command() +@click.argument('scene', callback=validate_scene, required=True,) +@pass_dev +def set_scene(dev: mirobo.PhilipsEyecare, scene): + """Set eyecare scene number.""" + click.echo("Eyecare Scene: %s" % dev.set_user_scene(scene)) + + +@cli.command() +@click.argument('minutes', callback=validate_minutes, required=True,) +@pass_dev +def delay_off(dev: mirobo.PhilipsEyecare, minutes): + """Set delay off in minutes.""" + click.echo("Delay off: %s" % dev.delay_off(minutes)) + + +@cli.command() +@pass_dev +def bl_on(dev: mirobo.PhilipsEyecare): + """Night Light on.""" + click.echo("Night Light On: %s" % dev.bl_on()) + + +@cli.command() +@pass_dev +def bl_off(dev: mirobo.PhilipsEyecare): + """Night Light off.""" + click.echo("Night Light off: %s" % dev.bl_off()) + + +@cli.command() +@pass_dev +def notify_on(dev: mirobo.PhilipsEyecare): + """Eye Fatigue Reminder On.""" + click.echo("Eye Fatigue Reminder On: %s" % dev.notify_user_on()) + + +@cli.command() +@pass_dev +def notify_off(dev: mirobo.PhilipsEyecare): + """Eye Fatigue Reminder off.""" + click.echo("Eye Fatigue Reminder Off: %s" % dev.notify_user_off()) + + +@cli.command() +@pass_dev +def ambient_on(dev: mirobo.PhilipsEyecare): + """Ambient Light on.""" + click.echo("Ambient Light On: %s" % dev.amb_on()) + + +@cli.command() +@pass_dev +def ambient_off(dev: mirobo.PhilipsEyecare): + """Ambient Light off.""" + click.echo("Ambient Light Off: %s" % dev.amb_off()) + + +@cli.command() +@click.argument('level', callback=validate_bright, required=True,) +@pass_dev +def set_amb_bright(dev: mirobo.PhilipsEyecare, level): + """Set Ambient Light brightness level.""" + click.echo("Ambient Light Brightness: %s" % dev.set_amb_bright(level)) + + +if __name__ == "__main__": + cli() diff --git a/mirobo/protocol.py b/mirobo/protocol.py index 054d693c5..f57983f7f 100644 --- a/mirobo/protocol.py +++ b/mirobo/protocol.py @@ -25,7 +25,8 @@ 0x00c4: "Xiaomi Smart Mi Air Purifier", 0x031a: "Xiaomi Smart home gateway", 0x0330: "Yeelight color bulb", - 0x0374: "Xiaomi Philips LED Ceiling Lamp" + 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()} diff --git a/setup.py b/setup.py index 0be69acc1..e8d6cd772 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ 'mirobo=mirobo.vacuum_cli:cli', 'miplug=mirobo.plug_cli:cli', 'miceil=mirobo.ceil_cli:cli', + 'mieye=mirobo.philips_eyecare_cli:cli', ], }, )