Source code for miio.integrations.ijai.vacuum.pro2vacuum

import logging
from datetime import timedelta
from enum import Enum
from typing import Dict

import click

from miio.click_common import EnumType, command, format_output
from miio.devicestatus import sensor, setting
from miio.miot_device import DeviceStatus, MiotDevice

_LOGGER = logging.getLogger(__name__)
MI_ROBOT_VACUUM_MOP_PRO_2 = "ijai.vacuum.v3"

_MAPPINGS = {
    MI_ROBOT_VACUUM_MOP_PRO_2: {
        # Source  https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:vacuum:0000A006:ijai-v3:1
        # Robot Cleaner (siid=2)
        "state": {"siid": 2, "piid": 1},
        "error_code": {"siid": 2, "piid": 2},  # [0, 3000] step 1
        "sweep_mode": {
            "siid": 2,
            "piid": 4,
        },  # 0 - Sweep, 1 - Sweep And Mop, 2 - Mop
        "sweep_type": {
            "siid": 2,
            "piid": 8,
        },  # 0 - Global, 1 - Mop, 2 - Edge, 3 - Area, 4 - Point, 5 - Remote, 6 - Explore, 7 - Room, 8 - Floor
        "start": {"siid": 2, "aiid": 1},
        "stop": {"siid": 2, "aiid": 2},
        # Battery (siid=3)
        "battery": {"siid": 3, "piid": 1},  # [0, 100] step 1
        "home": {"siid": 3, "aiid": 1},  # Start Charge
        # sweep (siid=7)
        "mop_state": {"siid": 7, "piid": 4},  # 0 - none, 1 - set
        "fan_speed": {
            "siid": 7,
            "piid": 5,
        },  # 0 - off, 1 - power save, 2 - standard, 3 - turbo
        "water_level": {"siid": 7, "piid": 6},  # 0 - low, 1 - medium, 2 - high
        "side_brush_life_level": {"siid": 7, "piid": 8},  # [0, 100] step 1
        "side_brush_time_left": {"siid": 7, "piid": 9},  # [0, 180] step 1
        "main_brush_life_level": {"siid": 7, "piid": 10},  # [0, 100] step 1
        "main_brush_time_left": {"siid": 7, "piid": 11},  # [0, 360] step 1
        "filter_life_level": {"siid": 7, "piid": 12},  # [0, 100] step 1
        "filter_time_left": {"siid": 7, "piid": 13},  # [0, 180] step 1
        "mop_life_level": {"siid": 7, "piid": 14},  # [0, 100] step 1
        "mop_time_left": {"siid": 7, "piid": 15},  # [0, 180] step 1
        "current_language": {"siid": 7, "piid": 21},  # string
        "clean_time": {"siid": 7, "piid": 22},  # [0, 120] step 1
        "clean_area": {"siid": 7, "piid": 23},  # [0, 1200] step 1
    }
}

ERROR_CODES: Dict[int, str] = {2105: "Fully charged"}


def _enum_as_dict(cls):
    return {x.name: x.value for x in list(cls)}


[docs] class DeviceState(Enum): Sleep = 0 Idle = 1 Paused = 2 GoCharging = 3 Charging = 4 Sweeping = 5 SweepingAndMopping = 6 Mopping = 7 Upgrading = 8
[docs] class SweepMode(Enum): Sweep = 0 SweepAndMop = 1 Mop = 2
[docs] class SweepType(Enum): Global = 0 Mop = 1 Edge = 2 Area = 3 Point = 4 Remote = 5 Explore = 6 Room = 7 Floor = 8
[docs] class DoorState(Enum): Off = 0 DustBox = 1 WaterVolume = 2 TwoInOneWaterVolume = 3
[docs] class FanSpeedMode(Enum): Off = 0 EnergySaving = 1 Standard = 2 Turbo = 3
[docs] class WaterLevel(Enum): Low = 0 Medium = 1 High = 2
[docs] class MopRoute(Enum): BowStyle = 0 YStyle = 1
[docs] class Pro2Status(DeviceStatus): """Container for status reports from Mi Robot Vacuum-Mop 2 Pro.""" def __init__(self, data): """Response (MIoT format) of a Mi Robot Vacuum-Mop 2 Pro (ijai.vacuum.v3) Example:: [ {'did': 'state', 'siid': 2, 'piid': 1, 'code': 0, 'value': 5}, {'did': 'error_code', 'siid': 2, 'piid': 2, 'code': 0, 'value': 0}, {'did': 'sweep_mode', 'siid': 2, 'piid': 4, 'code': 0, 'value': 1}, {'did': 'sweep_type', 'siid': 2, 'piid': 8, 'code': 0, 'value': 1}, {'did': 'battery', 'siid': 3, 'piid': 1, 'code': 0, 'value': 100}, {'did': 'mop_state', 'siid': 7, 'piid': 4, 'code': 0, 'value': 0}, {'did': 'fan_speed', 'siid': 7, 'piid': 5, 'code': 0, 'value': 1}, {'did': 'water_level', 'siid': 7, 'piid': 6, 'code': 0, 'value': 2}, {'did': 'side_brush_life_level', 'siid': 7, 'piid': 8, 'code': 0, 'value': 0 }, {'did': 'side_brush_time_left', 'siid': 7, 'piid': 9', 'code': 0, 'value': 0}, {'did': 'main_brush_life_level', 'siid': 7, 'piid': 10, 'code': 0, 'value': 99}, {'did': 'main_brush_time_left', 'siid': 7, 'piid': 11, 'code': 0, 'value': 17959}, {'did': 'filter_life_level', 'siid': 7, 'piid': 12, 'code': 0, 'value': 0}, {'did': 'filter_time_left', 'siid': 7, 'piid': 13, 'code': 0, 'value': 0}, {'did': 'mop_life_level', 'siid': 7, 'piid': 14, 'code': 0, 'value': 0}, {'did': 'mop_time_left', 'siid': 7, 'piid': 15, 'code': 0, 'value': 0}, {'did': 'current_language', 'siid': 7, 'piid': 21, 'code': 0, 'value': 0}, {'did': 'clean_area', 'siid': 7, 'piid': 22, 'code': 0, 'value': 0}, {'did': 'clean_time', 'siid': 7, 'piid': 23, 'code': 0, 'value': 0}, ] """ self.data = data @property @sensor(name="Battery", unit="%", device_class="battery") def battery(self) -> int: """Battery Level.""" return self.data["battery"] @property @sensor("Error", icon="mdi:alert") def error_code(self) -> int: """Error code as returned by the device.""" return int(self.data["error_code"]) @property @sensor("Error", icon="mdi:alert") def error(self) -> str: """Human readable error description, see also :func:`error_code`.""" return ERROR_CODES.get( self.error_code, f"Unknown error code: {self.error_code}" ) @property def state(self) -> DeviceState: """Vacuum Status.""" return DeviceState(self.data["state"]) @property @setting(name="Fan Speed", choices=FanSpeedMode, setter_name="set_fan_speed") def fan_speed(self) -> FanSpeedMode: """Fan Speed.""" return FanSpeedMode(self.data["fan_speed"]) @property @sensor(name="Sweep Type") def sweep_type(self) -> SweepType: """Operating Mode.""" return SweepType(self.data["sweep_type"]) @property @sensor(name="Sweep Mode") def sweep_mode(self) -> SweepMode: """Sweep Mode.""" return SweepMode(self.data["sweep_mode"]) @property @sensor("Mop Attached") def mop_state(self) -> bool: """Mop State.""" return bool(self.data["mop_state"]) @property @sensor("Water Level") def water_level(self) -> WaterLevel: """Water Level.""" return WaterLevel(self.data["water_level"]) @property @sensor("Main Brush Life Level", unit="%") def main_brush_life_level(self) -> int: """Main Brush Life Level(%).""" return self.data["main_brush_life_level"] @property @sensor("Main Brush Life Time Left") def main_brush_time_left(self) -> timedelta: """Main Brush Life Time Left(hours).""" return timedelta(hours=self.data["main_brush_time_left"]) @property @sensor("Side Brush Life Level", unit="%") def side_brush_life_level(self) -> int: """Side Brush Life Level(%).""" return self.data["side_brush_life_level"] @property @sensor("Side Brush Life Time Left") def side_brush_time_left(self) -> timedelta: """Side Brush Life Time Left(hours).""" return timedelta(hours=self.data["side_brush_time_left"]) @property @sensor("Filter Life Level", unit="%") def filter_life_level(self) -> int: """Filter Life Level(%).""" return self.data["filter_life_level"] @property @sensor("Filter Life Time Left") def filter_time_left(self) -> timedelta: """Filter Life Time Left(hours).""" return timedelta(hours=self.data["filter_time_left"]) @property @sensor("Mop Life Level", unit="%") def mop_life_level(self) -> int: """Mop Life Level(%).""" return self.data["mop_life_level"] @property @sensor("Mop Life Time Left") def mop_time_left(self) -> timedelta: """Mop Life Time Left(hours).""" return timedelta(hours=self.data["mop_time_left"]) @property @sensor("Last Clean Area", unit="m2", icon="mdi:texture-box") def clean_area(self) -> int: """Last time clean area(m^2).""" return self.data["clean_area"] @property @sensor("Last Clean Time", icon="mdi:timer-sand") def clean_time(self) -> timedelta: """Last time clean time(mins).""" return timedelta(minutes=self.data["clean_time"]) @property def current_language(self) -> str: """Current Language.""" return self.data["current_language"]
[docs] class Pro2Vacuum(MiotDevice): """Support for Mi Robot Vacuum-Mop 2 Pro (ijai.vacuum.v3).""" _mappings = _MAPPINGS
[docs] @command() def status(self) -> Pro2Status: """Retrieve properties.""" return Pro2Status( { # max_properties limited to 10 to avoid "Checksum error" # messages from the device. prop["did"]: prop["value"] if prop["code"] == 0 else None for prop in self.get_properties_for_mapping(max_properties=10) } )
[docs] @command() def home(self): """Go Home.""" return self.call_action_from_mapping("home")
[docs] @command() def start(self) -> None: """Start Cleaning.""" return self.call_action_from_mapping("start")
[docs] @command() def stop(self): """Stop Cleaning.""" return self.call_action_from_mapping("stop")
[docs] @command( click.argument("fan_speed", type=EnumType(FanSpeedMode)), default_output=format_output("Setting fan speed to {fan_speed}"), ) def set_fan_speed(self, fan_speed: FanSpeedMode): """Set fan speed.""" return self.set_property("fan_speed", fan_speed)
[docs] @command() def fan_speed_presets(self) -> Dict[str, int]: """Return available fan speed presets.""" return _enum_as_dict(FanSpeedMode)
[docs] @command(click.argument("speed", type=int)) def set_fan_speed_preset(self, speed_preset: int) -> None: """Set fan speed preset speed.""" if speed_preset not in self.fan_speed_presets().values(): raise ValueError( f"Invalid preset speed {speed_preset}, not in: {self.fan_speed_presets().values()}" ) return self.set_property("fan_speed", speed_preset)