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

Add support for deerma.humidifier.mjjsq #586

Merged
merged 13 commits into from
Dec 4, 2019
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Supported devices
- Xiaomi Philips Zhirui Bedroom Smart Lamp
- Xiaomi Universal IR Remote Controller (Chuangmi IR)
- Xiaomi Mi Smart Pedestal Fan V2, V3, SA1, ZA1, ZA3, ZA4, P5
- Xiaomi Mi Air Humidifier V1, CA1, CB1
- Xiaomi Mi Air Humidifier V1, CA1, CB1, MJJSQ
- Xiaomi Mi Water Purifier (Basic support: Turn on & off)
- Xiaomi PM2.5 Air Quality Monitor V1, B1, S1
- Xiaomi Smart WiFi Speaker
Expand Down
3 changes: 2 additions & 1 deletion miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
)
from miio.airdehumidifier import AirDehumidifier
from miio.airfresh import AirFresh
from miio.airhumidifier import AirHumidifier
from miio.airhumidifier import AirHumidifier, AirHumidifierCA1, AirHumidifierCB1
from miio.airhumidifier_mjjsq import AirHumidifierMjjsq
from miio.airpurifier import AirPurifier
from miio.airqualitymonitor import AirQualityMonitor
from miio.aqaracamera import AqaraCamera
Expand Down
Empty file modified miio/airdehumidifier.py
100755 → 100644
Empty file.
234 changes: 234 additions & 0 deletions miio/airhumidifier_mjjsq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import enum
import logging
from collections import defaultdict
from typing import Any, Dict

import click

from .click_common import EnumType, command, format_output
from .device import Device, DeviceException

_LOGGER = logging.getLogger(__name__)

MODEL_HUMIDIFIER_MJJSQ = "deerma.humidifier.mjjsq"

AVAILABLE_PROPERTIES = {
MODEL_HUMIDIFIER_MJJSQ: [
"OnOff_State",
"TemperatureValue",
"Humidity_Value",
"HumiSet_Value",
"Humidifier_Gear",
"Led_State",
"TipSound_State",
"waterstatus",
"watertankstatus",
]
}


class AirHumidifierException(DeviceException):
pass


class OperationMode(enum.Enum):
Low = 1
Medium = 2
High = 3
Humidity = 4


class AirHumidifierStatus:
"""Container for status reports from the air humidifier mjjsq."""

def __init__(self, data: Dict[str, Any]) -> None:
"""
Response of a Air Humidifier (deerma.humidifier.mjjsq):

{'Humidifier_Gear': 4, 'Humidity_Value': 44, 'HumiSet_Value': 54,
'Led_State': 1, 'OnOff_State': 0, 'TemperatureValue': 21,
'TipSound_State': 1, 'waterstatus': 1, 'watertankstatus': 1}
"""

self.data = data

@property
def power(self) -> str:
"""Power state."""
return "on" if self.data["OnOff_State"] == 1 else "off"
Copy link
Owner

Choose a reason for hiding this comment

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

Is there a need to have both power and is_on? I'd prefer simply a single boolean variant of one of these.

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 would like to stick on this because of consistency and a nice status output of: "Power: on"

Copy link
Owner

Choose a reason for hiding this comment

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

Ok, your call.


@property
def is_on(self) -> bool:
"""True if device is turned on."""
return self.power == "on"

@property
def mode(self) -> OperationMode:
"""Operation mode. Can be either low, medium, high or humidity."""
return OperationMode(self.data["Humidifier_Gear"])

@property
def temperature(self) -> int:
"""Current temperature in degree celsius."""
return self.data["TemperatureValue"]

@property
def humidity(self) -> int:
"""Current humidity in percent."""
return self.data["Humidity_Value"]

@property
def buzzer(self) -> bool:
"""True if buzzer is turned on."""
return self.data["TipSound_State"] == 1

@property
def led(self) -> bool:
"""True if LED is turned on."""
return self.data["Led_State"] == 1

@property
def target_humidity(self) -> int:
"""Target humiditiy in percent."""
return self.data["HumiSet_Value"]

@property
def no_water(self) -> bool:
"""True if the water tank is empty."""
return self.data["waterstatus"] == 0

@property
def water_tank_detached(self) -> bool:
"""True if the water tank is detached."""
return self.data["watertankstatus"] == 0

def __repr__(self) -> str:
s = (
"<AirHumidiferStatus power=%s, "
"mode=%s, "
"temperature=%s, "
"humidity=%s%%, "
"led_brightness=%s, "
"buzzer=%s, "
"target_humidity=%s%%, "
"no_water=%s, "
"water_tank_detached=%s>"
% (
self.power,
self.mode,
self.temperature,
self.humidity,
self.led,
self.buzzer,
self.target_humidity,
self.no_water,
self.water_tank_detached,
)
)
return s

def __json__(self):
return self.data


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

if model in AVAILABLE_PROPERTIES:
self.model = model
else:
self.model = MODEL_HUMIDIFIER_MJJSQ

@command(
default_output=format_output(
"",
"Power: {result.power}\n"
"Mode: {result.mode}\n"
"Temperature: {result.temperature} °C\n"
"Humidity: {result.humidity} %\n"
"LED: {result.led}\n"
"Buzzer: {result.buzzer}\n"
"Target humidity: {result.target_humidity} %\n"
"No water: {result.no_water}\n"
"Water tank detached: {result.water_tank_detached}\n",
)
)
def status(self) -> AirHumidifierStatus:
"""Retrieve properties."""

properties = AVAILABLE_PROPERTIES[self.model]
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:1]))
_props[:] = _props[1:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.error(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)

return AirHumidifierStatus(defaultdict(lambda: None, zip(properties, values)))

@command(default_output=format_output("Powering on"))
def on(self):
"""Power on."""
return self.send("Set_OnOff", [1])

@command(default_output=format_output("Powering off"))
def off(self):
"""Power off."""
return self.send("Set_OnOff", [0])

@command(
click.argument("mode", type=EnumType(OperationMode, False)),
default_output=format_output("Setting mode to '{mode.value}'"),
)
def set_mode(self, mode: OperationMode):
"""Set mode."""
return self.send("Set_HumidifierGears", [mode.value])

@command(
click.argument("led", type=bool),
default_output=format_output(
lambda led: "Turning on LED" if led else "Turning off LED"
),
)
def set_led(self, led: bool):
"""Turn led on/off."""
return self.send("SetLedState", [int(led)])

@command(
click.argument("buzzer", type=bool),
default_output=format_output(
lambda buzzer: "Turning on buzzer" if buzzer else "Turning off buzzer"
),
)
def set_buzzer(self, buzzer: bool):
"""Set buzzer on/off."""
return self.send("SetTipSound_Status", [int(buzzer)])

@command(
click.argument("humidity", type=int),
default_output=format_output("Setting target humidity to {humidity}"),
)
def set_target_humidity(self, humidity: int):
"""Set the target humidity in percent."""
if humidity < 0 or humidity > 99:
raise AirHumidifierException("Invalid target humidity: %s" % humidity)

return self.send("Set_HumiValue", [humidity])
5 changes: 5 additions & 0 deletions miio/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
AirConditioningCompanion,
AirFresh,
AirHumidifier,
AirHumidifierMjjsq,
AirPurifier,
AirQualityMonitor,
AqaraCamera,
Expand Down Expand Up @@ -42,6 +43,7 @@
MODEL_HUMIDIFIER_CB1,
MODEL_HUMIDIFIER_V1,
)
from .airhumidifier_mjjsq import MODEL_HUMIDIFIER_MJJSQ
from .airqualitymonitor import (
MODEL_AIRQUALITYMONITOR_B1,
MODEL_AIRQUALITYMONITOR_S1,
Expand Down Expand Up @@ -106,6 +108,9 @@
"zhimi-humidifier-v1": partial(AirHumidifier, model=MODEL_HUMIDIFIER_V1),
"zhimi-humidifier-ca1": partial(AirHumidifier, model=MODEL_HUMIDIFIER_CA1),
"zhimi-humidifier-cb1": partial(AirHumidifier, model=MODEL_HUMIDIFIER_CB1),
"deerma-humidifier-mjjsq": partial(
AirHumidifierMjjsq, model=MODEL_HUMIDIFIER_MJJSQ
),
"yunmi-waterpuri-v2": WaterPurifier,
"philips-light-bulb": PhilipsBulb, # cannot be discovered via mdns
"philips-light-candle": PhilipsBulb, # cannot be discovered via mdns
Expand Down
Loading