Skip to content

Commit

Permalink
Initial support for HUIZUO PISCES For Bedroom (#868)
Browse files Browse the repository at this point in the history
* Adding Huizuo basic support

* Add huizuo.py to the repo

* Update __init__.py to the latest version

* Fix _LOGGER error

* Enabling iSort in VSCode on Save

* 1. Removed unnecessary click.argument calls
2. Changing "color_temp" instead of "level" during manipulation with color temperature

* 1. Removed Huizuo from discovery - devices are not mdns discoverable
2. Updated based on PR#868

* Re-arranged color_temp parameter for better understanding

* fixing linting issues

* Added only example of JSON payload from the lamp

* Updated README.rst, added separations

* 1. Use MiotDevice class
2. Add tests

* Fixing linting issues

* Fixing linting issue - second try...

* Processing comments from PR#868
  • Loading branch information
darckly committed Nov 27, 2020
1 parent fcb95a3 commit f22a645
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Supported devices
- Xiaomi Philips LED Ball Lamp White (philips.light.hbulb)
- Xiaomi Philips Zhirui Smart LED Bulb E14 Candle Lamp
- Xiaomi Philips Zhirui Bedroom Smart Lamp
- Huayi Huizuo Pisces For Bedroom (huayi.light.pis123)
- Xiaomi Universal IR Remote Controller (Chuangmi IR)
- Xiaomi Mi Smart Pedestal Fan V2, V3, SA1, ZA1, ZA3, ZA4, P5, P9, P10, P11
- Xiaomi Mi Air Humidifier V1, CA1, CA4, CB1, MJJSQ, JSQ001
Expand Down
1 change: 1 addition & 0 deletions miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from miio.fan_miot import FanMiot, FanP9, FanP10, FanP11
from miio.gateway import Gateway
from miio.heater import Heater
from miio.huizuo import Huizuo
from miio.philips_bulb import PhilipsBulb, PhilipsWhiteBulb
from miio.philips_eyecare import PhilipsEyecare
from miio.philips_moonlight import PhilipsMoonlight
Expand Down
153 changes: 153 additions & 0 deletions miio/huizuo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"""
Basic implementation for HUAYI HUIZUO PISCES For Bedroom (huayi.light.pis123) lamp
This lamp is white color only and supports dimming and control of the temperature from 3000K to 6400K
Specs: https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:light:0000A001:huayi-pis123:1
"""

import logging
from typing import Any, Dict

import click

from .click_common import command, format_output
from .exceptions import DeviceException
from .miot_device import MiotDevice

_LOGGER = logging.getLogger(__name__)

_MAPPING = {
"power": {"siid": 2, "piid": 1},
"brightness": {"siid": 2, "piid": 2},
"color_temp": {"siid": 2, "piid": 3},
}

MODEL_HUIZUO_PIS123 = "huayi.light.pis123"

MODELS_SUPPORTED = [MODEL_HUIZUO_PIS123]


class HuizuoException(DeviceException):
pass


class HuizuoStatus:
def __init__(self, data: Dict[str, Any]) -> None:
"""
Response of a Huizuo Pisces For Bedroom (huayi.light.pis123)
{'id': 1, 'result': [
{'did': '', 'siid': 2, 'piid': 1, 'code': 0, 'value': False},
{'did': '', 'siid': 2, 'piid': 2, 'code': 0, 'value': 94},
{'did': '', 'siid': 2, 'piid': 3, 'code': 0, 'value': 6400}
]
}
Explanation (line-by-line):
power = '{"siid":2,"piid":1}' values = true,false
brightless(%) = '{"siid":2,"piid":2}' values = 1-100
color temperature(Kelvin) = '{"siid":2,"piid":3}' values = 3000-6400
"""

self.data = data

@property
def is_on(self) -> bool:
"""Return True if device is on."""
return self.data["power"]

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

@property
def color_temp(self) -> int:
"""Return current color temperature."""
return self.data["color_temp"]

def __repr__(self):
s = "<Huizuo on=%s brightness=%s color_temp=%s>" % (
self.is_on,
self.brightness,
self.color_temp,
)
return s


class Huizuo(MiotDevice):
"""A support for Huizuo PIS123."""

def __init__(
self,
ip: str = None,
token: str = None,
start_id: int = 0,
debug: int = 0,
lazy_discover: bool = True,
model: str = MODEL_HUIZUO_PIS123,
) -> None:
super().__init__(_MAPPING, ip, token, start_id, debug, lazy_discover)

if model in MODELS_SUPPORTED:
self.model = model
else:
self.model = MODEL_HUIZUO_PIS123
_LOGGER.error(
"Device model %s unsupported. Falling back to %s.", model, self.model
)

@command(
default_output=format_output("Powering on"),
)
def on(self):
"""Power on."""
return self.set_property("power", True)

@command(
default_output=format_output("Powering off"),
)
def off(self):
"""Power off."""
return self.set_property("power", False)

@command(
default_output=format_output(
"\n",
"Power: {result.is_on}\n"
"Brightness: {result.brightness}\n"
"Color Temperature: {result.color_temp}\n"
"\n",
)
)
def status(self) -> HuizuoStatus:
"""Retrieve properties."""

return HuizuoStatus(
{
prop["did"]: prop["value"] if prop["code"] == 0 else None
for prop in self.get_properties_for_mapping()
}
)

@command(
click.argument("level", type=int),
default_output=format_output("Setting brightness to {level}"),
)
def set_brightness(self, level):
"""Set brightness."""
if level < 0 or level > 100:
raise HuizuoException("Invalid brightness: %s" % level)

return self.set_property("brightness", level)

@command(
click.argument("color_temp", type=int),
default_output=format_output("Setting color temperature to {color_temp}"),
)
def set_color_temp(self, color_temp):
"""Set color temp in kelvin."""
if color_temp < 3000 or color_temp > 6400:
raise HuizuoException("Invalid color temperature: %s" % color_temp)

return self.set_property("color_temp", color_temp)
87 changes: 87 additions & 0 deletions miio/tests/test_huizuo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from unittest import TestCase

import pytest

from miio import Huizuo
from miio.huizuo import HuizuoException

from .dummies import DummyMiotDevice

_INITIAL_STATE = {
"power": True,
"brightness": 60,
"color_temp": 4000,
}


class DummyHuizuo(DummyMiotDevice, Huizuo):
def __init__(self, *args, **kwargs):
self.state = _INITIAL_STATE
self.return_values = {
"get_prop": self._get_state,
"set_power": lambda x: self._set_state("power", x),
"set_brightness": lambda x: self._set_state("brightness", x),
}
super().__init__(*args, **kwargs)


@pytest.fixture(scope="function")
def huizuo(request):
request.cls.device = DummyHuizuo()


@pytest.mark.usefixtures("huizuo")
class TestHuizuo(TestCase):
def test_on(self):
self.device.off() # ensure off
assert self.device.status().is_on is False

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

def test_off(self):
self.device.on() # ensure on
assert self.device.status().is_on is True

self.device.off()
assert self.device.status().is_on is False

def test_status(self):
status = self.device.status()
assert status.is_on is _INITIAL_STATE["power"]
assert status.brightness is _INITIAL_STATE["brightness"]
assert status.color_temp is _INITIAL_STATE["color_temp"]

def test_brightness(self):
def lamp_brightness():
return self.device.status().brightness

self.device.set_brightness(1)
assert lamp_brightness() == 1
self.device.set_brightness(64)
assert lamp_brightness() == 64
self.device.set_brightness(100)
assert lamp_brightness() == 100

with pytest.raises(HuizuoException):
self.device.set_brightness(-1)

with pytest.raises(HuizuoException):
self.device.set_brightness(101)

def test_color_temp(self):
def lamp_color_temp():
return self.device.status().color_temp

self.device.set_color_temp(3000)
assert lamp_color_temp() == 3000
self.device.set_color_temp(4200)
assert lamp_color_temp() == 4200
self.device.set_color_temp(6400)
assert lamp_color_temp() == 6400

with pytest.raises(HuizuoException):
self.device.set_color_temp(2999)

with pytest.raises(HuizuoException):
self.device.set_color_temp(6401)

0 comments on commit f22a645

Please sign in to comment.