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 Eyecare Smart Lamp 2 #34

Merged
merged 3 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 @@ -4,4 +4,5 @@
from mirobo.vacuum import Vacuum, VacuumException
from mirobo.plug import Plug
from mirobo.strip import Strip
from mirobo.philips_eyecare import PhilipsEyecare
from mirobo.device import Device, DeviceException
134 changes: 134 additions & 0 deletions mirobo/philips_eyecare.py
Original file line number Diff line number Diff line change
@@ -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 = "<PhilipsEyecareStatus power=%s, bright=%s, " \
"notifystatus=%s, ambstatus=%s, ambvalue=%s, " \
"eyecare=%s, scene_num=%s, bls=%s, " \
"dvalue=%s >" % \
(self.power, self.bright,
self.notifystatus, self.ambstatus, self.ambvalue,
self.eyecare, self.scene_num,
self.bls)
return s
199 changes: 199 additions & 0 deletions mirobo/philips_eyecare_cli.py
Original file line number Diff line number Diff line change
@@ -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()
3 changes: 2 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",
0x02f9: "Xiaomi Philips Eyecare Smart Lamp 2",
}
xiaomi_devices = {y: x for x, y in xiaomi_devices_reverse.items()}

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',
'mieye=mirobo.philips_eyecare_cli:cli',
],
},
)