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 dreame.vacuum.ma1808 and dreame.vacuum.mb1808 #1774

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from miio.cloud import CloudDeviceInfo, CloudException, CloudInterface
from miio.devicefactory import DeviceFactory
from miio.integrations.airdog.airpurifier import AirDogX3
from miio.integrations.cuco.plug import CucoPlugMiot
from miio.integrations.cgllc.airmonitor import AirQualityMonitor, AirQualityMonitorCGDN1
from miio.integrations.chuangmi.camera import ChuangmiCamera
from miio.integrations.chuangmi.plug import ChuangmiPlug
Expand Down
6 changes: 1 addition & 5 deletions miio/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
from typing import TYPE_CHECKING, Dict, Optional

import click

try:
from pydantic.v1 import BaseModel, Field
except ImportError:
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field

try:
from rich import print as echo
Expand Down
6 changes: 1 addition & 5 deletions miio/devtools/simulators/miiosimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
from typing import List, Optional, Union

import click

try:
from pydantic.v1 import BaseModel, Field, PrivateAttr
except ImportError:
from pydantic import BaseModel, Field, PrivateAttr
from pydantic import BaseModel, Field, PrivateAttr
from yaml import safe_load

from miio import PushServer
Expand Down
5 changes: 1 addition & 4 deletions miio/devtools/simulators/miotsimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@
from typing import List, Union

import click
from pydantic import Field, validator

try:
from pydantic.v1 import Field, validator
except ImportError:
from pydantic import Field, validator
from miio import PushServer
from miio.miot_cloud import MiotCloud
from miio.miot_models import DeviceModel, MiotAccess, MiotProperty, MiotService
Expand Down
Empty file.
3 changes: 3 additions & 0 deletions miio/integrations/cuco/plug/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .cuco_plug import CucoPlugMiot

__all__ = ["CucoPlugMiot"]
94 changes: 94 additions & 0 deletions miio/integrations/cuco/plug/cuco_plug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import enum
import logging
from typing import Any, Dict

from miio import DeviceStatus, MiotDevice
from miio.click_common import command, format_output

_LOGGER = logging.getLogger(__name__)
_MAPPINGS = {
"cuco.plug.v2eur": {
# Source https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:outlet:0000A002:cuco-v2eur:1
"power": {"siid": 2, "piid": 1},
"fault": {"siid": 2, "piid": 3},
"power_consumption": {"siid": 11, "piid": 1},
"electric_power": {"siid": 11, "piid": 2},
"indicator_light": {"siid": 13, "piid": 1}
}
}


class DeviceFault(enum.Enum):
NoFaults = 0
OverTemperature = 1
Overload = 2


class CucoPlugMiotStatus(DeviceStatus):

def __init__(self, data: Dict[str, Any], model: str) -> None:
self.data = data
self.model = model

@property
def power(self) -> str:
"""Power state."""
return "on" if self.is_on else "off"

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

@property
def power_consumption(self) -> float:
"""True if device is currently on."""
return self.data["power_consumption"]

@property
def electric_power(self) -> int:
"""True if device is currently on."""
return self.data["electric_power"]

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

@property
def fault(self) -> DeviceFault:
value = self.data["fault"]
return DeviceFault(value)


class CucoPlugMiot(MiotDevice):

_mappings = _MAPPINGS

@command(
default_output=format_output(
"",
"Power: {result.power}\n"
"Power consumption: {result.power_consumption}\n"
"Electric power: {result.electric_power} watt\n"
"LED indicator: {result.indicator_light}\n"
)
)
def status(self) -> CucoPlugMiotStatus:
return CucoPlugMiotStatus(
{
prop["did"]: prop["value"] if prop["code"] == 0 else None
for prop in self.get_properties_for_mapping()
},
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)
28 changes: 26 additions & 2 deletions miio/integrations/dreame/vacuum/dreamevacuum_miot.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@


DREAME_1C = "dreame.vacuum.mc1808"
DREAME_1C_A = "dreame.vacuum.ma1808"
DREAME_1C_B = "dreame.vacuum.mb1808"
DREAME_F9 = "dreame.vacuum.p2008"
DREAME_D9 = "dreame.vacuum.p2009"
DREAME_Z10_PRO = "dreame.vacuum.p2028"
Expand Down Expand Up @@ -48,6 +50,7 @@
"total_clean_time": {"siid": 18, "piid": 13},
"total_clean_times": {"siid": 18, "piid": 14},
"total_clean_area": {"siid": 18, "piid": 15},
"water_flow": {"siid": 18, "piid": 20},
"life_sieve": {"siid": 19, "piid": 1},
"life_brush_side": {"siid": 19, "piid": 2},
"life_brush_main": {"siid": 19, "piid": 3},
Expand Down Expand Up @@ -166,6 +169,8 @@

MIOT_MAPPING: Dict[str, MiotMapping] = {
DREAME_1C: _DREAME_1C_MAPPING,
DREAME_1C_A: _DREAME_1C_MAPPING,
DREAME_1C_B: _DREAME_1C_MAPPING,
DREAME_F9: _DREAME_F9_MAPPING,
DREAME_D9: _DREAME_F9_MAPPING,
DREAME_Z10_PRO: _DREAME_F9_MAPPING,
Expand Down Expand Up @@ -249,7 +254,7 @@ def _enum_as_dict(cls):

def _get_cleaning_mode_enum_class(model):
"""Return cleaning mode enum class for model if found or None."""
if model == DREAME_1C:
if model in (DREAME_1C, DREAME_1C_A, DREAME_1C_B):
return CleaningModeDreame1C
elif model in (
DREAME_F9,
Expand Down Expand Up @@ -508,7 +513,9 @@ def status(self) -> DreameVacuumStatus:

return DreameVacuumStatus(
{
prop["did"]: prop["value"] if prop["code"] == 0 else None
prop["did"]: prop["value"]
if prop["code"] == 0 and "value" in prop
else None
for prop in self.get_properties_for_mapping(max_properties=10)
},
self.model,
Expand All @@ -520,6 +527,9 @@ def status(self) -> DreameVacuumStatus:
MANUAL_DISTANCE_MAX = 300
MANUAL_DISTANCE_MIN = -300

VOLUME_MIN = 0
VOLUME_MAX = 100

@command()
def start(self) -> None:
"""Start cleaning."""
Expand Down Expand Up @@ -588,6 +598,20 @@ def set_fan_speed(self, speed: int):
click.echo(f"Setting fanspeed to {fanspeed.name}")
return self.set_property("cleaning_mode", fanspeed.value)

@command(click.argument("volume", type=int))
def set_volume(self, volume: int):
"""Set volume.

:param int volume: Volume to set
"""
if volume < self.VOLUME_MIN or volume > self.VOLUME_MAX:
raise ValueError(
"Given volume is invalid, should be [%s, %s], was: %s"
% (self.VOLUME_MIN, self.VOLUME_MAX, volume)
)
click.echo(f"Setting volume to {volume}")
return self.set_property("volume", volume)

@command()
def fan_speed_presets(self) -> Dict[str, int]:
"""Return available fan speed presets."""
Expand Down
105 changes: 93 additions & 12 deletions miio/integrations/zhimi/heater/heater_miot.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@
# Indicator light (siid=7)
"led_brightness": {"siid": 7, "piid": 1},
},
"leshow.heater.nfj3lx": {
Copy link
Owner

Choose a reason for hiding this comment

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

These are irrelevant changes, right?

# Source https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:heater:0000A01A:leshow-nfj3lx:1
# Heater (siid=2)
"power": {"siid": 2, "piid": 1},
"fault": {"siid": 2, "piid": 2},
"mode": {"siid": 2, "piid": 5},
"target_temperature": {"siid": 2, "piid": 3},
# Countdown (siid=3)
"countdown_time": {"siid": 3, "piid": 1},
# Environment (siid=4)
"temperature": {"siid": 4, "piid": 7},
# Physical Control Locked (siid=5)
"child_lock": {"siid": 5, "piid": 1},
# Alarm (siid=6)
"buzzer": {"siid": 6, "piid": 1},
# Indicator light (siid=7)
"led_brightness": {"siid": 7, "piid": 1},
"sway": {"siid": 8, "piid": 1}
},
}

HEATER_PROPERTIES = {
Expand All @@ -69,17 +88,44 @@
"temperature_range": (16, 28),
"delay_off_range": (0, 8 * 3600),
},
"leshow.heater.nfj3lx": {
"temperature_range": (22, 28),
"delay_off_range": (0, 8 * 3600),
},
}


class LedBrightness(enum.Enum):
"""Note that only Xiaomi Smart Space Heater 1S (zhimi.heater.za2) supports `Dim`."""
class Buzzer(enum.Enum):
Off = 0
On = 1


class Sway(enum.Enum):
Off = 0
On = 1


class LedBrightness(enum.Enum):
On = 0
Off = 1
Dim = 2


class Mode(enum.Enum):
ConstantTemperature = 0
Heat = 1
Warm = 2
NaturalWind = 3


class DeviceFault(enum.Enum):
NoFaults = 0
EnvTempIsTooLow = 1
EnvTempIsTooHigh = 2
PlugTempIsTooLow = 3
PlugTempIsTooHigh = 4


class HeaterMiotStatus(DeviceStatus):
"""Container for status reports from the Xiaomi Smart Space Heater S and 1S."""

Expand Down Expand Up @@ -135,11 +181,6 @@ def child_lock(self) -> bool:
"""True if child lock is on, False otherwise."""
return self.data["child_lock"] is True

@property
def buzzer(self) -> bool:
"""True if buzzer is turned on, False otherwise."""
return self.data["buzzer"] is True

@property
def led_brightness(self) -> LedBrightness:
"""LED indicator brightness."""
Expand All @@ -148,6 +189,26 @@ def led_brightness(self) -> LedBrightness:
value = 3 - value
return LedBrightness(value)

@property
def mode(self) -> Mode:
value = self.data["mode"]
return Mode(value)

@property
def fault(self) -> DeviceFault:
value = self.data["fault"]
return DeviceFault(value)

@property
def buzzer(self) -> Buzzer:
value = self.data["buzzer"]
return Buzzer(value)

@property
def sway(self) -> Sway:
value = self.data["sway"]
return Sway(value)


class HeaterMiot(MiotDevice):
"""Main class representing the Xiaomi Smart Space Heater S (zhimi.heater.mc2) & 1S
Expand Down Expand Up @@ -217,14 +278,14 @@ def set_child_lock(self, lock: bool):
return self.set_property("child_lock", lock)

@command(
click.argument("buzzer", type=bool),
click.argument("buzzer", type=EnumType(Buzzer)),
default_output=format_output(
lambda buzzer: "Turning on buzzer" if buzzer else "Turning off buzzer"
"Setting buzzer to {buzzer}"
),
)
def set_buzzer(self, buzzer: bool):
"""Set buzzer on/off."""
return self.set_property("buzzer", buzzer)
def set_buzzer(self, buzzer: Buzzer):
value = buzzer.value
return self.set_property("buzzer", value)

@command(
click.argument("brightness", type=EnumType(LedBrightness)),
Expand All @@ -241,6 +302,26 @@ def set_led_brightness(self, brightness: LedBrightness):
raise ValueError("Unsupported brightness Dim for model '%s'.", self.model)
return self.set_property("led_brightness", value)

@command(
click.argument("mode", type=EnumType(Mode)),
default_output=format_output(
"Setting Mode to {mode}"
),
)
def set_mode(self, mode: Mode):
value = mode.value
return self.set_property("mode", value)

@command(
click.argument("sway", type=EnumType(Sway)),
default_output=format_output(
"Setting sway to {sway}"
),
)
def set_sway(self, sway: Sway):
value = sway.value
return self.set_property("sway", value)

@command(
click.argument("seconds", type=int),
default_output=format_output("Setting delayed turn off to {seconds} seconds"),
Expand Down
6 changes: 1 addition & 5 deletions miio/miot_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@

import appdirs
from micloud.miotspec import MiotSpec

try:
from pydantic.v1 import BaseModel, Field
except ImportError:
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field

from miio import CloudException
from miio.miot_models import DeviceModel
Expand Down
Loading