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

Initial support for lumi.curtain.hagl05 #851

Merged
merged 10 commits into from
Nov 6, 2020
1 change: 1 addition & 0 deletions miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from miio.chuangmi_ir import ChuangmiIr
from miio.chuangmi_plug import ChuangmiPlug, Plug, PlugV1, PlugV3
from miio.cooker import Cooker
from miio.curtain_youpin import CurtainMiot
from miio.device import Device
from miio.exceptions import DeviceError, DeviceException
from miio.fan import Fan, FanP5, FanSA1, FanV2, FanZA1, FanZA4
Expand Down
249 changes: 249 additions & 0 deletions miio/curtain_youpin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import enum
import warnings
import logging

import click

from typing import Any, Dict
from .click_common import EnumType, command, format_output
from .exceptions import DeviceException
from .miot_device import MiotDevice

_LOGGER = logging.getLogger(__name__)
_MAPPING = {
# # Source http://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:curtain:0000A00C:lumi-hagl05:1
# Curtain
"motor_control": {"siid": 2, "piid": 2}, # 0 - Pause, 1 - Open, 2 - Close, 3 - auto
"current_position": {"siid": 2, "piid": 3}, # Range: [0, 100, 1]
"status": {"siid": 2, "piid": 6}, # 0 - Stopped, 1 - Opening, 2 - Closing
"target_position": {"siid": 2, "piid": 7}, # Range: [0, 100, 1]
# curtain_cfg
"manual_enabled": {"siid": 4, "piid": 1}, #
"polarity": {"siid": 4, "piid": 2},
"is_position_limited": {"siid": 4, "piid": 3},
"night_tip_light": {"siid": 4, "piid": 4},
"run_time": {"siid": 4, "piid": 5}, # Range: [0, 255, 1]
# motor_controller
"adjust_value": {"siid": 5, "piid": 1}, # Range: [-100, 100, 1]
}

# Model: ZNCLDJ21LM (also known as "Xiaomiyoupin Curtain Controller (Wi-Fi)"
MODEL_CURTAIN_HAGL05 = "lumi.curtain.hagl05"

class MotorControl(enum.Enum):
Pause = 0
Open = 1
Close = 2
Auto = 3

class Status(enum.Enum):
Stopped = 0
Opening = 1
Closing = 2

class Polarity(enum.Enum):
Positive = 0
Reverse = 1


class CurtainStatus:
def __init__(self, data: Dict[str, Any]) -> None:
""" Response from device
{'id': 1, 'result': [
{'did': 'motor_control', 'siid': 2, 'piid': 2, 'code': -4001},
in7egral marked this conversation as resolved.
Show resolved Hide resolved
{'did': 'current_position', 'siid': 2, 'piid': 3, 'code': 0, 'value': 0},
{'did': 'status', 'siid': 2, 'piid': 6, 'code': 0, 'value': 0},
{'did': 'target_position', 'siid': 2, 'piid': 7, 'code': 0, 'value': 0},
{'did': 'is_manual_enabled', 'siid': 4, 'piid': 1, 'code': 0, 'value': 1},
{'did': 'polarity', 'siid': 4, 'piid': 2, 'code': 0, 'value': 0},
{'did': 'is_position_limited', 'siid': 4, 'piid': 3, 'code': 0, 'value': 0},
{'did': 'night_tip_light', 'siid': 4, 'piid': 4, 'code': 0, 'value': 1},
{'did': 'run_time', 'siid': 4, 'piid': 5, 'code': 0, 'value': 0},
{'did': 'adjust_value', 'siid': 5, 'piid': 1, 'code': -4000}
]}
"""
self.data = data

@property
def status(self) -> Status:
""" Device status """
in7egral marked this conversation as resolved.
Show resolved Hide resolved
return Status(self.data["status"])

""" curtain_cfg """
in7egral marked this conversation as resolved.
Show resolved Hide resolved
@property
def is_manual_enabled(self) -> bool:
"""Manual control enable status
"""
in7egral marked this conversation as resolved.
Show resolved Hide resolved
return self.data["is_manual_enabled"]

@property
def polarity(self) -> Polarity:
"""Motor rotation polarity
"""
in7egral marked this conversation as resolved.
Show resolved Hide resolved
return Polarity(self.data["polarity"])

@property
def is_position_limited(self) -> bool:
"""Position limit
"""
return self.data["is_position_limited"]
in7egral marked this conversation as resolved.
Show resolved Hide resolved

@property
def night_tip_light(self) -> bool:
"""Night tip light status
"""
return self.data["night_tip_light"]
in7egral marked this conversation as resolved.
Show resolved Hide resolved

@property
def run_time(self) -> int:
"""Run time of the motor
"""
return self.data["run_time"]

@property
def current_position(self) -> int:
"""Current curtain position
"""
return self.data["current_position"]

@property
def target_position(self) -> int:
"""Target curtain position
"""
return self.data["target_position"]

@property
def adjust_value(self) -> int:
""" Adjust value
"""
return self.data["adjust_value"]

def __repr__(self) -> str:
s = (
"<CurtainStatus"
"status=%s,"
"polarity=%s,"
"is_position_limited=%s,"
"night_tip_light=%s,"
"run_time=%s,"
"current_position=%s,"
"target_position=%s,"
"adjust_value=%s>"
% (
self.status,
self.polarity,
self.is_position_limited,
self.night_tip_light,
self.run_time,
self.current_position,
self.target_position,
self.adjust_value
)
)
return s

class CurtainMiot(MiotDevice):
"""Main class representing the lumi.curtain.hagl05 curtain."""

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

@command(
default_output=format_output(
"",
"Device status: {result.status}\n"
"Manual enabled: {result.is_manual_enabled}\n"
"Motor polarity: {result.polarity}\n"
"Position limit: {result.is_position_limited}\n"
"Enabled night tip light: {result.night_tip_light}\n"
"Run time: {result.run_time}\n"
"Current position: {result.current_position}\n"
"Target position: {result.target_position}\n"
"Adjust value: {result.adjust_value}\n",
)
)
def status(self) -> CurtainStatus:
"""Retrieve properties."""

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

@command(
click.argument("motor_control", type=EnumType(MotorControl)),
default_output=format_output("Set motor control to {motor_control}"),
)
def set_motor_control(self, motor_control: MotorControl):
"""Set motor control.
"""
self.set_property("motor_control", motor_control.value)
in7egral marked this conversation as resolved.
Show resolved Hide resolved

@command(
click.argument("target_position", type=int),
default_output=format_output("Set target position to {target_position}"),
)
def set_target_position(self, target_position: int):
"""Set target position.
"""
if target_position < 0 or target_position > 100:
raise ValueError("Value must be between [0, 100] value, was %s" % target_position)
self.set_property("target_position", target_position)
in7egral marked this conversation as resolved.
Show resolved Hide resolved

@command(
click.argument("manual_enabled", type=bool),
default_output=format_output("Set manual control {manual_enabled}"),
)
def set_manual_enabled(self, manual_enabled: bool):
"""Set manual control of curtain.
"""
self.set_property("is_manual_enabled", manual_enabled)

@command(
click.argument("polarity", type=EnumType(Polarity)),
default_output=format_output("Set polarity to {polarity}"),
)
def set_polarity(self, polarity: Polarity):
"""Set polarity of the motor.
"""
self.set_property("polarity", polarity.value)

@command(
click.argument("pos_limit", type=bool),
default_output=format_output("Set position limit to {pos_limit}"),
)
def set_position_limit(self, pos_limit: bool):
"""Set position limit parameter.
"""
self.set_property("is_position_limited", pos_limit)

@command(
click.argument("night_tip_light", type=bool),
default_output=format_output("Setting night tip light {night_tip_light"),
)
def set_night_tip_light(self, night_tip_light: bool):
"""Set night tip light.
"""
self.set_property("night_tip_light", night_tip_light)

""" motor_controller """
in7egral marked this conversation as resolved.
Show resolved Hide resolved
@command(
click.argument("adjust_value", type=int),
default_output=format_output("Set adjust value to {adjust_value}"),
)
def set_adjust_value(self, adjust_value: int):
"""Set adjust value.
"""
if adjust_value < -100 or adjust_value > 100:
raise ValueError("Value must be between [-100, 100] value, was %s" % adjust_value)
self.set_property("adjust_value", adjust_value)