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

Unit tests for philips lights #133

Merged
merged 7 commits into from
Nov 26, 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
37 changes: 28 additions & 9 deletions miio/ceil.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
_LOGGER = logging.getLogger(__name__)


class CeilException(Exception):
pass


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]
# {'power': 'off', 'bright': 0, 'snm': 4, 'dv': 0,
# 'cctsw': [[0, 3], [0, 2], [0, 1]], 'bl': 1,
# 'mb': 1, 'ac': 1, 'mssw': 1, 'cct': 99}

# NOTE: Only 8 properties can be requested at the same time
self.data = data

Expand Down Expand Up @@ -47,14 +52,14 @@ def color_temperature(self) -> int:
return self.data["cct"]

@property
def smart_night_light(self) -> int:
def smart_night_light(self) -> bool:
"""Smart night mode state."""
return self.data["bl"]
return self.data["bl"] == 1

@property
def automatic_color_temperature(self) -> int:
def automatic_color_temperature(self) -> bool:
"""Automatic color temperature state."""
return self.data["ac"]
return self.data["ac"] == 1

def __str__(self) -> str:
s = "<CeilStatus power=%s, brightness=%s, " \
Expand Down Expand Up @@ -100,19 +105,33 @@ def off(self):

def set_brightness(self, level: int):
"""Set brightness level."""
if level < 1 or level > 100:
raise CeilException("Invalid brightness: %s" % level)

return self.send("set_bright", [level])

def set_color_temperature(self, level: int):
"""Set Correlated Color Temperature."""
if level < 1 or level > 100:
raise CeilException("Invalid color temperature: %s" % level)

return self.send("set_cct", [level])

def delay_off(self, seconds: int):
"""Set delay off seconds."""

if seconds < 1:
raise CeilException(
"Invalid value for a delayed turn off: %s" % seconds)

return self.send("delay_off", [seconds])

def set_scene(self, num: int):
def set_scene(self, number: int):
"""Set scene number."""
return self.send("apply_fixed_scene", [num])
if number < 1 or number > 4:
raise CeilException("Invalid fixed scene number: %s" % number)

return self.send("apply_fixed_scene", [number])

def smart_night_light_on(self):
"""Smart Night Light On."""
Expand Down
60 changes: 39 additions & 21 deletions miio/philips_bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
_LOGGER = logging.getLogger(__name__)


class PhilipsBulbException(Exception):
pass


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

def __init__(self, data: Dict[str, Any]) -> None:
# ['power': 'on', 'bright': 85, 'cct': 9, 'snm': 0, 'dv': 0]
# {'power': 'on', 'bright': 85, 'cct': 9, 'snm': 0, 'dv': 0}
self.data = data

@property
Expand Down Expand Up @@ -48,6 +52,25 @@ def __str__(self) -> str:
class PhilipsBulb(Device):
"""Main class representing Xiaomi Philips LED Ball Lamp."""

def status(self) -> PhilipsBulbStatus:
"""Retrieve properties."""
properties = ['power', 'bright', 'cct', 'snm', 'dv', ]
values = self.send(
"get_prop",
properties
)

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 PhilipsBulbStatus(
defaultdict(lambda: None, zip(properties, values)))

def on(self):
"""Power on."""
return self.send("set_power", ["on"])
Expand All @@ -58,35 +81,30 @@ def off(self):

def set_brightness(self, level: int):
"""Set brightness level."""
if level < 1 or level > 100:
raise PhilipsBulbException("Invalid brightness: %s" % level)

return self.send("set_bright", [level])

def set_color_temperature(self, level: int):
"""Set Correlated Color Temperature."""
if level < 1 or level > 100:
raise PhilipsBulbException("Invalid color temperature: %s" % level)

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])
if seconds < 1:
raise PhilipsBulbException(
"Invalid value for a delayed turn off: %s" % seconds)

def status(self) -> PhilipsBulbStatus:
"""Retrieve properties."""
properties = ['power', 'bright', 'cct', 'snm', 'dv', ]
values = self.send(
"get_prop",
properties
)
return self.send("delay_off", [seconds])

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)
def set_scene(self, number: int):
"""Set scene number."""
if number < 1 or number > 4:
raise PhilipsBulbException("Invalid fixed scene number: %s" % number)

Choose a reason for hiding this comment

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

line too long (81 > 79 characters)


return PhilipsBulbStatus(
defaultdict(lambda: None, zip(properties, values)))
return self.send("apply_fixed_scene", [number])
25 changes: 22 additions & 3 deletions miio/philips_eyecare.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
_LOGGER = logging.getLogger(__name__)


class PhilipsEyecareException(Exception):
pass


class PhilipsEyecareStatus:
"""Container for status reports from Xiaomi Philips Eyecare Smart Lamp 2"""

def __init__(self, data: Dict[str, Any]) -> None:
# ['power': 'off', 'bright': 5, 'notifystatus': 'off',
# 'ambstatus': 'off': 'ambvalue': 41, 'eyecare': 'on',
# 'ambstatus': 'off', 'ambvalue': 41, 'eyecare': 'on',
# 'scene_num': 3, 'bls': 'on', 'dvalue': 0]
self.data = data

Expand Down Expand Up @@ -118,14 +122,25 @@ def eyecare_off(self):

def set_brightness(self, level: int):
"""Set brightness level."""
if level < 1 or level > 100:
raise PhilipsEyecareException("Invalid brightness: %s" % level)

return self.send("set_bright", [level])

def set_scene(self, num: int):
def set_scene(self, number: int):
"""Set eyecare user scene."""
return self.send("set_user_scene", [num])
if number < 1 or number > 4:
raise PhilipsEyecareException("Invalid fixed scene number: %s" % number)

Choose a reason for hiding this comment

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

line too long (84 > 79 characters)


return self.send("set_user_scene", [number])

def delay_off(self, minutes: int):
"""Set delay off minutes."""

if minutes < 0:
raise PhilipsEyecareException(
"Invalid value for a delayed turn off: %s" % minutes)

return self.send("delay_off", [minutes])

def smart_night_light_on(self):
Expand Down Expand Up @@ -154,4 +169,8 @@ def ambient_off(self):

def set_ambient_brightness(self, level: int):
"""Set Ambient Light brightness level."""
if level < 1 or level > 100:
raise PhilipsEyecareException(
"Invalid ambient brightness: %s" % level)

return self.send("set_amb_bright", [level])
126 changes: 126 additions & 0 deletions miio/tests/test_ceil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from unittest import TestCase
from miio import Ceil
from .dummies import DummyDevice
import pytest


class DummyCeil(DummyDevice, Ceil):
def __init__(self, *args, **kwargs):
self.state = {'power': 'on',
'bright': 50,
'snm': 4,
'dv': 0,
'cctsw': [[0, 3], [0, 2], [0, 1]],
'bl': 1,
'mb': 1,
'ac': 1,
'mssw': 1,
'cct': 99
}
self.return_values = {
'get_prop': self._get_state,
'set_power': lambda x: self._set_state("power", x),
'set_bright': lambda x: self._set_state("bright", x),
'apply_fixed_scene': lambda x: self._set_state("snm", x),
'delay_off': lambda x: self._set_state("dv", x),
'enable_bl': lambda x: self._set_state("bl", x),
'enable_ac': lambda x: self._set_state("ac", x),
'set_cct': lambda x: self._set_state("cct", x),
}
super().__init__(args, kwargs)


@pytest.fixture(scope="class")
def ceil(request):
request.cls.device = DummyCeil()
# TODO add ability to test on a real device


@pytest.mark.usefixtures("ceil")
class TestCeil(TestCase):
def is_on(self):
return self.device.status().is_on

def state(self):
return self.device.status()

def test_on(self):
self.device.off() # ensure off

start_state = self.is_on()
assert start_state is False

self.device.on()
assert self.is_on() is True

def test_off(self):
self.device.on() # ensure on

assert self.is_on() is True
self.device.off()
assert self.is_on() is False
Copy link
Owner

Choose a reason for hiding this comment

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

Considering quite a few devices has the ability for turning on and off, maybe it makes sense to have a single set of tests for all of those devices. For now this is fine though.


def test_status(self):
self.device._reset_state()

assert self.is_on() is True
assert self.state().brightness == self.device.start_state["bright"]
assert self.state().color_temperature == self.device.start_state["cct"]
assert self.state().scene == self.device.start_state["snm"]
assert self.state().delay_off_countdown == self.device.start_state["dv"]

Choose a reason for hiding this comment

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

line too long (80 > 79 characters)

assert self.state().smart_night_light is (self.device.start_state["bl"] == 1)

Choose a reason for hiding this comment

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

line too long (85 > 79 characters)

assert self.state().automatic_color_temperature is (self.device.start_state["ac"] == 1)

Choose a reason for hiding this comment

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

line too long (95 > 79 characters)


def test_set_brightness(self):
def brightness():
return self.device.status().brightness

self.device.set_brightness(10)
assert brightness() == 10
self.device.set_brightness(20)
assert brightness() == 20
Copy link
Owner

Choose a reason for hiding this comment

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

Test against incorrect values (brightness <0, >100), the bulbs should raise an exception if an incorrect value is thrown at them I think.


def test_set_color_temperature(self):
def color_temperature():
return self.device.status().color_temperature

self.device.set_color_temperature(30)
assert color_temperature() == 30
self.device.set_color_temperature(20)
assert color_temperature() == 20
Copy link
Owner

Choose a reason for hiding this comment

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

Same as above for incorrect values.


def test_delay_off(self):
def delay_off_countdown():
return self.device.status().delay_off_countdown

self.device.delay_off(100)
assert delay_off_countdown() == 100
self.device.delay_off(200)
assert delay_off_countdown() == 200
Copy link
Owner

Choose a reason for hiding this comment

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

Same as above.


def test_set_scene(self):
def scene():
return self.device.status().scene

self.device.set_scene(1)
assert scene() == 1
self.device.set_scene(2)
assert scene() == 2
Copy link
Owner

Choose a reason for hiding this comment

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

Same as above.


def test_smart_night_light_on(self):
def smart_night_light():
return self.device.status().smart_night_light

self.device.smart_night_light_off()
assert smart_night_light() is False
self.device.smart_night_light_on()
assert smart_night_light() is True

def test_automatic_color_temperature_on(self):
def automatic_color_temperature():
return self.device.status().automatic_color_temperature

self.device.automatic_color_temperature_on()
assert automatic_color_temperature() is True
self.device.automatic_color_temperature_off()
assert automatic_color_temperature() is False