Skip to content

Commit

Permalink
Unit tests for philips lights (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
syssi committed Nov 26, 2017
1 parent 94ca8be commit 3df66fd
Show file tree
Hide file tree
Showing 6 changed files with 534 additions and 33 deletions.
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)

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)

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

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"]
assert self.state().smart_night_light is (self.device.start_state["bl"] == 1)
assert self.state().automatic_color_temperature is (self.device.start_state["ac"] == 1)

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

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

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

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

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

0 comments on commit 3df66fd

Please sign in to comment.