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

Xiaomi Ceiling Lamp: Some refactoring and fault tolerance if a philips light ball is used #45

Merged
merged 6 commits into from
Aug 30, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 35 additions & 20 deletions mirobo/ceil.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import logging
from .device import Device
from typing import Any, Dict
from collections import defaultdict

_LOGGER = logging.getLogger(__name__)

class CeilStatus:
"""Container for status reports from Xiaomi Philips LED Ceiling Lamp"""
Expand All @@ -20,34 +24,36 @@ def is_on(self) -> bool:
return self.power == "on"

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

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what could dv mean? It would be nice to have a real name for it too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I don't know the meaning. Google didn't help as well.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, it can be corrected later on. Btw, looking at the list of properties (with the comment of limitation of 8), is any of those left out properties interesting to include considering that currently 7 properties is being fetched?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just own the the Philips LED Ball. This device just provides the first 5 properties. Maybe @kuduka can provide some more informations.

Copy link
Contributor

@kuduka kuduka Aug 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dv property represents seconds remaining until turn off is activated.

0 means it's deactivated and it won't turn off itself.

At least in ceiling light maximum value is 6 hours.

### 192.168.1.219 => 192.168.1.227 (b8:27:eb:34:b0:bb => 34:ce:00:86:db:44)
{"id":9056,"method":"delay_off","params":[21599]}
### 192.168.1.219 => 192.168.1.227 (b8:27:eb:34:b0:bb => 34:ce:00:86:db:44)
{"id":9057,"method":"get_prop","params":["power","bright","snm","dv","cct","sw","bl","mb","ac","ms"]}
### 192.168.1.227 => 192.168.1.219 (34:ce:00:86:db:44 => b8:27:eb:34:b0:bb)
{"result":["on",35,0,21597,30,[[0,3],[0,2],[0,1]],1,1,1,1],"id":9057}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kuduka do you also have descriptions for the rest of the undocumented properties?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which ones are left undocumented? I think that was the last one on this code.

From product side there are quite a few that are not included on this code related to miband/wall switch/remote controller pairing and its functionality.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'mb', 'ms', 'cctsw' and 'sw' are shown in the example query, but are not currently fetched and there are no property getters for them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, those are related to miband/wall switch integration and they are not supported yet :)

I need to spend some time deciphering them and with current limitation of 8 props I don't know how to do it, see this --> #35 (comment)

Although some times using more than 8 props as shown on this previous PR works fine, so it's kind of a mystery this behavior.

Any ideas how I could query more than 8 props? Doing 2 calls and merging its results? That would be acceptable? Any other ideas out there?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I had forgotten that, my fault.

I think it's just a limitation one has to live with, probably also related to https://gitlab.com/stavros/python-yeelight/issues/17 . The "developer mode" is just a wrapper to allow accessing the API over HTTP without a token, or so it seems at least.

Doing two calls could probably be fine, the question is more whether it is worth it or not. However, it would make sense to document those findings (and create corresponding properties which just do their thing / raise NotImplementedError where not possible) into the source code for anyone reading the code and wondering why those are not used :-)


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

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

@property
def ac(self) -> int:
def automatic_color_temperature(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)
s = "<CeilStatus power=%s, brightness=%s, " \
"color_temperature=%s, scene=%s, dv=%s, " \
"smart_night_light=%s, automatic_color_temperature=%, >" % \
(self.power, self.brightness,
self.color_temperature, self.scene, self.dv,
self.smart_night_light, self.automatic_color_temperature)
return s


Expand All @@ -65,11 +71,11 @@ def off(self):
"""Power off."""
return self.send("set_power", ["off"])

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

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

Expand All @@ -81,20 +87,20 @@ def set_scene(self, num: int):
"""Set scene number."""
return self.send("apply_fixed_scene", [num])

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

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

def ac_on(self):
"""Auto CCT On."""
def automatic_color_temperature_on(self):
"""Automatic color temperature on."""
return self.send("enable_ac", [1])

def ac_off(self):
"""Auto CCT Off."""
def automatic_color_temperature_off(self):
"""Automatic color temperature off."""
return self.send("enable_ac", [0])

def status(self) -> CeilStatus:
Expand All @@ -104,4 +110,13 @@ def status(self) -> CeilStatus:
"get_prop",
properties
)
return CeilStatus(dict(zip(properties, values)))

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 CeilStatus(defaultdict(lambda: None, zip(properties, values)))
45 changes: 23 additions & 22 deletions mirobo/ceil_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
pass_dev = click.make_pass_decorator(mirobo.Ceil)


def validate_bright(ctx, param, value):
def validate_percentage(ctx, param, value):
value = int(value)
if value < 1 or value > 100:
raise click.BadParameter('Should be a positive int between 1-100.')
Expand Down Expand Up @@ -97,12 +97,12 @@ def status(dev: mirobo.Ceil):
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("Brightness: %s" % res.brightness)
click.echo("Color temperature: %s" % res.color_temperature)
click.echo("Scene: %s" % res.scene)
click.echo("dv: %s" % res.dv)
click.echo("Smart Midnight Light: %s" % res.bl)
click.echo("Auto CCT: %s" % res.ac)
click.echo("Smart Night Light: %s" % res.smart_night_light)
click.echo("Auto CCT: %s" % res.automatic_color_temperature)


@cli.command()
Expand All @@ -120,19 +120,20 @@ def off(dev: mirobo.Ceil):


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


@cli.command()
@click.argument('level', callback=validate_bright, required=True,)
@click.argument('level', callback=validate_percentage, required=True,)
@pass_dev
def set_cct(dev: mirobo.Ceil, level):
def set_color_temperature(dev: mirobo.Ceil, level):
"""Set CCT level."""
click.echo("CCT level: %s" % dev.set_cct(level))
click.echo("Correlated color temperatur level: %s" %
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

temperature. Any ideas what "correlated" means and does it bring anything for the user what simply calling it "color temperature" would not?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed "correlated". It won't help anybody and isn't correct because a percentage is delivered.

dev.set_color_temperature(level))


@cli.command()
Expand All @@ -153,30 +154,30 @@ def set_scene(dev: mirobo.Ceil, 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())
def smart_night_light_on(dev: mirobo.Ceil):
"""Smart Night Light on."""
click.echo("Smart Night Light On: %s" % dev.smart_night_light_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())
def smart_night_light_off(dev: mirobo.Ceil):
"""Smart Night Light off."""
click.echo("Smart Night Light Off: %s" % dev.smart_night_light_off())


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


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


if __name__ == "__main__":
Expand Down