示例#1
0
class PhilipsWhiteBulb(Device):
    """Main class representing Xiaomi Philips White LED Ball Lamp."""

    _supported_models = [MODEL_PHILIPS_LIGHT_HBULB]

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Brightness: {result.brightness}\n"
        "Delayed turn off: {result.delay_off_countdown}\n"
        "Color temperature: {result.color_temperature}\n"
        "Scene: {result.scene}\n",
    ))
    def status(self) -> PhilipsBulbStatus:
        """Retrieve properties."""

        properties = AVAILABLE_PROPERTIES.get(
            self.model, AVAILABLE_PROPERTIES[MODEL_PHILIPS_LIGHT_BULB])
        values = self.get_properties(properties)

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

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

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

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

        return self.send("set_bright", [level])

    @command(
        click.argument("seconds", type=int),
        default_output=format_output(
            "Setting delayed turn off to {seconds} seconds"),
    )
    def delay_off(self, seconds: int):
        """Set delay off seconds."""

        if seconds < 1:
            raise PhilipsBulbException(
                "Invalid value for a delayed turn off: %s" % seconds)

        return self.send("delay_off", [seconds])
示例#2
0
class PhilipsBulb(PhilipsWhiteBulb):
    """Support for philips bulbs that support color temperature and scenes."""

    _supported_models = list(AVAILABLE_PROPERTIES.keys())

    @command(
        click.argument("level", type=int),
        default_output=format_output("Setting color temperature to {level}"),
    )
    def set_color_temperature(self, level: int):
        """Set Correlated Color Temperature."""
        if level < 1 or level > 100:
            raise PhilipsBulbException("Invalid color temperature: %s" % level)

        return self.send("set_cct", [level])

    @command(
        click.argument("brightness", type=int),
        click.argument("cct", type=int),
        default_output=format_output(
            "Setting brightness to {brightness} and color temperature to {cct}"
        ),
    )
    def set_brightness_and_color_temperature(self, brightness: int, cct: int):
        """Set brightness level and the correlated color temperature."""
        if brightness < 1 or brightness > 100:
            raise PhilipsBulbException("Invalid brightness: %s" % brightness)

        if cct < 1 or cct > 100:
            raise PhilipsBulbException("Invalid color temperature: %s" % cct)

        return self.send("set_bricct", [brightness, cct])

    @command(
        click.argument("number", type=int),
        default_output=format_output("Setting fixed scene to {number}"),
    )
    def set_scene(self, number: int):
        """Set scene number."""
        if number < 1 or number > 4:
            raise PhilipsBulbException("Invalid fixed scene number: %s" %
                                       number)

        return self.send("apply_fixed_scene", [number])
示例#3
0
class AirFreshT2017(AirFreshA1):
    """Main class representing the air fresh t2017."""
    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Mode: {result.mode}\n"
        "PM2.5: {result.pm25}\n"
        "CO2: {result.co2}\n"
        "Temperature: {result.temperature}\n"
        "Favorite speed: {result.favorite_speed}\n"
        "Control speed: {result.control_speed}\n"
        "Dust filter life: {result.dust_filter_life_remaining} %, "
        "{result.dust_filter_life_remaining_days} days\n"
        "Upper filter life remaining: {result.upper_filter_life_remaining} %, "
        "{result.upper_filter_life_remaining_days} days\n"
        "PTC: {result.ptc}\n"
        "PTC level: {result.ptc_level}\n"
        "PTC status: {result.ptc_status}\n"
        "Child lock: {result.child_lock}\n"
        "Buzzer: {result.buzzer}\n"
        "Display: {result.display}\n"
        "Display orientation: {result.display_orientation}\n",
    ))
    @command(
        click.argument("speed", type=int),
        default_output=format_output("Setting favorite speed to {speed}"),
    )
    def set_favorite_speed(self, speed: int):
        """Sets the fan speed in favorite mode."""
        if speed < 60 or speed > 300:
            raise AirFreshException("Invalid favorite speed: %s" % speed)

        return self.send("set_favourite_speed", [speed])

    @command(default_output=format_output("Resetting dust filter"))
    def reset_dust_filter(self):
        """Resets filter lifetime of the dust filter."""
        return self.send("set_filter_reset", ["intermediate"])

    @command(default_output=format_output("Resetting upper filter"))
    def reset_upper_filter(self):
        """Resets filter lifetime of the upper filter."""
        return self.send("set_filter_reset", ["efficient"])

    @command(
        click.argument("orientation", type=EnumType(DisplayOrientation)),
        default_output=format_output(
            "Setting orientation to '{orientation.value}'"),
    )
    def set_display_orientation(self, orientation: DisplayOrientation):
        """Set display orientation."""
        return self.send("set_screen_direction", [orientation.value])

    @command(
        click.argument("level", type=EnumType(PtcLevel)),
        default_output=format_output("Setting ptc level to '{level.value}'"),
    )
    def set_ptc_level(self, level: PtcLevel):
        """Set PTC level."""
        return self.send("set_ptc_level", [level.value])
示例#4
0
class FanLeshow(Device):
    """Main class representing the Xiaomi Rosou SS4 Ventilator."""

    _supported_models = list(AVAILABLE_PROPERTIES.keys())

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Mode: {result.mode}\n"
        "Speed: {result.speed}\n"
        "Buzzer: {result.buzzer}\n"
        "Oscillate: {result.oscillate}\n"
        "Power-off time: {result.delay_off_countdown}\n"
        "Error detected: {result.error_detected}\n",
    ))
    def status(self) -> FanLeshowStatus:
        """Retrieve properties."""
        properties = AVAILABLE_PROPERTIES.get(
            self.model, AVAILABLE_PROPERTIES[MODEL_FAN_LESHOW_SS4])
        values = self.get_properties(properties, max_properties=15)

        return FanLeshowStatus(dict(zip(properties, values)))

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

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

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

    @command(
        click.argument("speed", type=int),
        default_output=format_output(
            "Setting speed of the manual mode to {speed}"),
    )
    def set_speed(self, speed: int):
        """Set a speed level between 0 and 100."""
        if speed < 0 or speed > 100:
            raise FanLeshowException("Invalid speed: %s" % speed)

        return self.send("set_blow", [speed])

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

    @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("set_sound", [int(buzzer)])

    @command(
        click.argument("minutes", type=int),
        default_output=format_output(
            "Setting delayed turn off to {minutes} minutes"),
    )
    def delay_off(self, minutes: int):
        """Set delay off minutes."""

        if minutes < 0 or minutes > 540:
            raise FanLeshowException(
                "Invalid value for a delayed turn off: %s" % minutes)

        return self.send("set_timer", [minutes])
示例#5
0
class AirPurifierMiot(MiotDevice):
    """Main class representing the air purifier which uses MIoT protocol."""

    _mappings = _MAPPINGS

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Anion: {result.anion}\n"
        "AQI: {result.aqi} μg/m³\n"
        "Average AQI: {result.average_aqi} μg/m³\n"
        "Humidity: {result.humidity} %\n"
        "Temperature: {result.temperature} °C\n"
        "PM10 Density: {result.pm10_density} μg/m³\n"
        "Fan Level: {result.fan_level}\n"
        "Mode: {result.mode}\n"
        "LED: {result.led}\n"
        "LED brightness: {result.led_brightness}\n"
        "LED brightness level: {result.led_brightness_level}\n"
        "Buzzer: {result.buzzer}\n"
        "Buzzer vol.: {result.buzzer_volume}\n"
        "Child lock: {result.child_lock}\n"
        "Favorite level: {result.favorite_level}\n"
        "Filter life remaining: {result.filter_life_remaining} %\n"
        "Filter hours used: {result.filter_hours_used}\n"
        "Filter left time: {result.filter_left_time} days\n"
        "Use time: {result.use_time} s\n"
        "Purify volume: {result.purify_volume} m³\n"
        "Motor speed: {result.motor_speed} rpm\n"
        "Filter RFID product id: {result.filter_rfid_product_id}\n"
        "Filter RFID tag: {result.filter_rfid_tag}\n"
        "Filter type: {result.filter_type}\n",
    ))
    def status(self) -> AirPurifierMiotStatus:
        """Retrieve properties."""
        # Some devices update the aqi information only every 30min.
        # This forces the device to poll the sensor for 5 seconds,
        # so that we get always the most recent values. See #1281.
        if self.model == "zhimi.airpurifier.mb3":
            self.set_property("aqi_realtime_update_duration", 5)

        return AirPurifierMiotStatus(
            {
                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)

    @command(
        click.argument("rpm", type=int),
        default_output=format_output(
            "Setting favorite motor speed '{rpm}' rpm"),
    )
    def set_favorite_rpm(self, rpm: int):
        """Set favorite motor speed."""
        if "favorite_rpm" not in self._get_mapping():
            raise AirPurifierMiotException(
                "Unsupported favorite rpm for model '%s'" % self.model)

        # Note: documentation says the maximum is 2300, however, the purifier may return an error for rpm over 2200.
        if rpm < 300 or rpm > 2300 or rpm % 10 != 0:
            raise AirPurifierMiotException(
                "Invalid favorite motor speed: %s. Must be between 300 and 2300 and divisible by 10"
                % rpm)
        return self.set_property("favorite_rpm", rpm)

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

    @command(
        click.argument("anion", type=bool),
        default_output=format_output(
            lambda anion: "Turning on anion"
            if anion else "Turing off anion", ),
    )
    def set_anion(self, anion: bool):
        """Set anion on/off."""
        if "anion" not in self._get_mapping():
            raise AirPurifierMiotException("Unsupported anion for model '%s'" %
                                           self.model)
        return self.set_property("anion", anion)

    @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."""
        if "buzzer" not in self._get_mapping():
            raise AirPurifierMiotException(
                "Unsupported buzzer for model '%s'" % self.model)

        return self.set_property("buzzer", buzzer)

    @command(
        click.argument("lock", type=bool),
        default_output=format_output(lambda lock: "Turning on child lock"
                                     if lock else "Turning off child lock"),
    )
    def set_child_lock(self, lock: bool):
        """Set child lock on/off."""
        if "child_lock" not in self._get_mapping():
            raise AirPurifierMiotException(
                "Unsupported child lock for model '%s'" % self.model)
        return self.set_property("child_lock", lock)

    @command(
        click.argument("level", type=int),
        default_output=format_output("Setting fan level to '{level}'"),
    )
    def set_fan_level(self, level: int):
        """Set fan level."""
        if "fan_level" not in self._get_mapping():
            raise AirPurifierMiotException(
                "Unsupported fan level for model '%s'" % self.model)

        if level < 1 or level > 3:
            raise AirPurifierMiotException("Invalid fan level: %s" % level)
        return self.set_property("fan_level", level)

    @command(
        click.argument("volume", type=int),
        default_output=format_output("Setting sound volume to {volume}"),
    )
    def set_volume(self, volume: int):
        """Set buzzer volume."""
        if "volume" not in self._get_mapping():
            raise AirPurifierMiotException(
                "Unsupported volume for model '%s'" % self.model)

        if volume < 0 or volume > 100:
            raise AirPurifierMiotException(
                "Invalid volume: %s. Must be between 0 and 100" % volume)
        return self.set_property("buzzer_volume", volume)

    @command(
        click.argument("level", type=int),
        default_output=format_output("Setting favorite level to {level}"),
    )
    def set_favorite_level(self, level: int):
        """Set the favorite level used when the mode is `favorite`.

        Needs to be between 0 and 14.
        """
        if "favorite_level" not in self._get_mapping():
            raise AirPurifierMiotException(
                "Unsupported favorite level for model '%s'" % self.model)

        if level < 0 or level > 14:
            raise AirPurifierMiotException("Invalid favorite level: %s" %
                                           level)

        return self.set_property("favorite_level", level)

    @command(
        click.argument("brightness", type=EnumType(LedBrightness)),
        default_output=format_output("Setting LED brightness to {brightness}"),
    )
    def set_led_brightness(self, brightness: LedBrightness):
        """Set led brightness."""
        if "led_brightness" not in self._get_mapping():
            raise AirPurifierMiotException(
                "Unsupported led brightness for model '%s'" % self.model)

        value = brightness.value
        if (self.model in ("zhimi.airp.va2", "zhimi.airp.mb5",
                           "zhimi.airp.vb4", "zhimi.airp.rmb1") and value):
            value = 2 - value
        return self.set_property("led_brightness", 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."""
        if "led" not in self._get_mapping():
            raise AirPurifierMiotException("Unsupported led for model '%s'" %
                                           self.model)
        return self.set_property("led", led)

    @command(
        click.argument("level", type=int),
        default_output=format_output(
            "Setting LED brightness level to {level}"),
    )
    def set_led_brightness_level(self, level: int):
        """Set led brightness level (0..8)."""
        if "led_brightness_level" not in self._get_mapping():
            raise AirPurifierMiotException(
                "Unsupported led brightness level for model '%s'" % self.model)
        if level < 0 or level > 8:
            raise AirPurifierMiotException("Invalid brightness level: %s" %
                                           level)

        return self.set_property("led_brightness_level", level)
示例#6
0
class PetWaterDispenser(MiotDevice):
    """Main class representing the Pet Waterer / Pet Drinking Fountain / Smart Pet Water
    Dispenser."""

    _mappings = MIOT_MAPPING

    @command(default_output=format_output(
        "",
        "On: {result.is_on}\n"
        "Mode: {result.mode}\n"
        "LED on: {result.is_led_on}\n"
        "Lid up: {result.is_lid_up}\n"
        "No water: {result.is_no_water}\n"
        "Time without water: {result.no_water_minutes}\n"
        "Pump blocked: {result.is_pump_blocked}\n"
        "Error detected: {result.is_error_detected}\n"
        "Days before cleaning left: {result.before_cleaning_days}\n"
        "Cotton filter live left: {result.cotton_left_days}\n"
        "Sponge filter live left: {result.sponge_filter_left_days}\n"
        "Location: {result.location}\n"
        "Timezone: {result.timezone}\n",
    ))
    def status(self) -> PetWaterDispenserStatus:
        """Retrieve properties."""
        data = {
            prop["did"]: prop["value"] if prop["code"] == 0 else None
            for prop in self.get_properties_for_mapping()
        }

        _LOGGER.debug(data)

        return PetWaterDispenserStatus(data)

    @command(default_output=format_output("Turning device on"))
    def on(self) -> List[Dict[str, Any]]:
        """Turn device on."""
        return self.set_property("on", True)

    @command(default_output=format_output("Turning device off"))
    def off(self) -> List[Dict[str, Any]]:
        """Turn device off."""
        return self.set_property("on", False)

    @command(
        click.argument("led", type=bool),
        default_output=format_output(lambda led: "Turning LED on"
                                     if led else "Turning LED off"),
    )
    def set_led(self, led: bool) -> List[Dict[str, Any]]:
        """Toggle indicator light on/off."""
        if led:
            return self.set_property("indicator_light", True)
        return self.set_property("indicator_light", False)

    @command(
        click.argument("mode", type=EnumType(OperatingMode)),
        default_output=format_output('Changing mode to "{mode.name}"'),
    )
    def set_mode(self, mode: OperatingMode) -> List[Dict[str, Any]]:
        """Switch operation mode."""
        return self.set_property("mode", mode.value)

    @command(default_output=format_output("Resetting sponge filter"))
    def reset_sponge_filter(self) -> Dict[str, Any]:
        """Reset sponge filter."""
        return self.call_action("reset_filter_life")

    @command(default_output=format_output("Resetting cotton filter"))
    def reset_cotton_filter(self) -> Dict[str, Any]:
        """Reset cotton filter."""
        return self.call_action("reset_cotton_life")

    @command(default_output=format_output("Resetting all filters"))
    def reset_all_filters(self) -> List[Dict[str, Any]]:
        """Reset all filters [cotton, sponge]."""
        return [self.reset_cotton_filter(), self.reset_sponge_filter()]

    @command(default_output=format_output("Resetting cleaning time"))
    def reset_cleaning_time(self) -> Dict[str, Any]:
        """Reset cleaning time counter."""
        return self.call_action("reset_clean_time")

    @command(default_output=format_output("Resetting device"))
    def reset(self) -> Dict[str, Any]:
        """Reset device."""
        return self.call_action("reset_device")

    @command(
        click.argument("timezone", type=click.IntRange(-12, 12)),
        default_output=format_output('Changing timezone to "{timezone}"'),
    )
    def set_timezone(self, timezone: int) -> List[Dict[str, Any]]:
        """Change timezone."""
        return self.set_property("timezone", timezone)

    @command(
        click.argument("location", type=str),
        default_output=format_output('Changing location to "{location}"'),
    )
    def set_location(self, location: str) -> List[Dict[str, Any]]:
        """Change location."""
        return self.set_property("location", location)
示例#7
0
class IHCooker(Device):
    """Main class representing the induction cooker.

    Custom recipes can be build with the profile_v1/v2 structure.
    """

    _supported_models = SUPPORTED_MODELS

    @command(
        default_output=format_output(
            "",
            "Mode: {result.mode}\n"
            "Recipe ID: {result.recipe_id}\n"
            "Recipe Name: {result.recipe_name}\n"
            "Stage: {result.stage}\n"
            "Stage Mode: {result.stage_mode}\n"
            "Target Temp: {result.target_temp}\n"
            "Temperature: {result.temperature}\n"
            "Temperature upperbound: {result.temperature_upperbound}\n"
            "Fire selected: {result.fire_selected}\n"
            "Fire current: {result.fire_current}\n"
            "WiFi Led: {result.wifi_led_setting}\n"
            "Hardware version: {result.hardware_version}\n"
            "Firmware version: {result.firmware_version}\n",
        )
    )
    def status(self) -> IHCookerStatus:
        """Retrieve properties."""
        properties_new = [
            "func",
            "menu",
            "action",
            "t_func",
            "version",
            "profiles",
            "set_wifi_led",
            "play",
        ]
        properties_old = [
            "func",
            "menu",
            "action",
            "t_func",
            "version",
            "profiles",
            "play",
        ]

        values = self.send("get_prop", ["all"])

        if len(values) not in [len(properties_new), len(properties_old)]:
            raise IHCookerException(
                "Count (%d or %d) of requested properties does not match the "
                "count (%s) of received values."
                % (len(properties_new), len(properties_old), len(values)),
            )
        elif len(values) == len(properties_new):
            properties = properties_new
        elif len(values) == len(properties_old):
            properties = properties_old

        return IHCookerStatus(
            self.model, defaultdict(lambda: None, zip(properties, values))
        )

    @command(
        click.argument("profile", type=str),
        click.argument("skip_confirmation", type=bool, default=False),
        default_output=format_output("Cooking profile requested."),
    )
    def start(self, profile: Union[str, c.Container, dict], skip_confirmation=False):
        """Start cooking a profile.

        :arg

        Please do not use skip_confirmation=True, as this is potentially unsafe.
        """

        profile = self._prepare_profile(profile)
        profile.menu_settings.save_recipe = False
        profile.menu_settings.confirm_start = not skip_confirmation
        if skip_confirmation:
            warnings.warn(
                "You're starting a profile without confirmation, which is a potentially unsafe."
            )
            self.send("set_start", [self._profile_obj.build(profile).hex()])
        else:
            self.send("set_menu1", [self._profile_obj.build(profile).hex()])

    @command(
        click.argument("temperature", type=int),
        click.argument("skip_confirmation", type=bool, default=False),
        click.argument("minutes", type=int, default=60),
        click.argument("power", type=int, default=DEFAULT_FIRE_LEVEL),
        click.argument("menu_location", type=int, default=None),
        default_output=format_output("Cooking with temperature requested."),
    )
    def start_temp(
        self,
        temperature,
        minutes=60,
        power=DEFAULT_FIRE_LEVEL,
        skip_confirmation=False,
        menu_location=None,
    ):
        """Start cooking at a fixed temperature and duration.

        Temperature in celcius.

        Please do not use skip_confirmation=True, as this is potentially unsafe.
        """

        profile = dict(
            recipe_name="%d Degrees" % temperature,
            menu_settings=dict(save_recipe=False, confirm_start=skip_confirmation),
            duration_minutes=minutes,
            menu_location=menu_location,
            stages=[
                dict(
                    temp_target=temperature,
                    minutes=minutes,
                    mode=StageMode.TemperatureMode,
                    power=power,
                )
            ],
        )
        profile = self._prepare_profile(profile)

        if menu_location is not None:
            self.set_menu(profile, menu_location, True)
        else:
            self.start(profile, skip_confirmation)

    @command(
        click.argument("power", type=int),
        click.argument("skip_confirmation", type=bool),
        click.argument("minutes", type=int),
        default_output=format_output("Cooking with temperature requested."),
    )
    def start_fire(self, power, minutes=60, skip_confirmation=False):
        """Start cooking at a fixed fire power and duration.

        Fire: 0-99.

        Please do not use skip_confirmation=True, as this is potentially unsafe.
        """

        if 0 < power > 100:
            raise ValueError("power should be in range [0,99].")
        profile = dict(
            recipe_name="%d fire power" % power,
            menu_settings=dict(save_recipe=False, confirm_start=skip_confirmation),
            duration_minutes=minutes,
            stages=[dict(power=power, minutes=minutes, mode=StageMode.FireMode)],
        )
        profile = self._prepare_profile(profile)
        self.start(profile, skip_confirmation)

    @command(default_output=format_output("Cooking stopped"))
    def stop(self):
        """Stop cooking."""
        self.send("set_func", ["end"])

    @command(default_output=format_output("Recipe deleted"))
    def delete_recipe(self, location):
        """Delete recipe at location [0,7]"""
        if location >= 9 or location < 1:
            raise IHCookerException("location %d must be in [1,8]." % location)
        self.send("set_delete1", [self._device_prefix + "%0d" % location])

    @command(default_output=format_output("Factory reset"))
    def factory_reset(self):
        """Reset device to factory settings, removing menu settings.

        It is unclear if this can change the language setting of the device.
        """

        self.send("set_factory_reset", [self._device_prefix])

    @command(
        click.argument("profile", type=str),
        default_output=format_output(""),
    )
    def profile_to_json(self, profile: Union[str, c.Container, dict]):
        """Convert profile to json."""
        profile = self._prepare_profile(profile)

        res = dict(profile)
        res["menu_settings"] = dict(res["menu_settings"])
        del res["menu_settings"]["_io"]
        del res["_io"]
        del res["crc"]
        res["stages"] = [
            {k: v for k, v in s.items() if k != "_io"} for s in res["stages"]
        ]

        return json.dumps(res)

    @command(
        click.argument("json_str", type=str),
        default_output=format_output(""),
    )
    def json_to_profile(self, json_str: str):
        """Convert json to profile."""

        profile = self._profile_obj.build(self._prepare_profile(json.loads(json_str)))

        return str(profile.hex())

    @command(
        click.argument("value", type=bool),
        default_output=format_output("WiFi led setting changed."),
    )
    def set_wifi_led(self, value: bool):
        """Keep wifi-led on when idle."""
        return self.send(
            "set_wifi_state", [self._device_prefix + "01" if value else "00"]
        )

    @command(
        click.argument("power", type=int),
        default_output=format_output("Fire power set."),
    )
    def set_power(self, power: int):
        """Set fire power."""
        if not 0 <= power < 100:
            raise ValueError("Power should be in range [0,99]")
        return self.send(
            "set_fire", [self._device_prefix + "0005"]
        )  # + f'{power:02x}'])

    @command(
        click.argument("profile", type=str),
        click.argument("location", type=int),
        click.argument("confirm_start", type=bool),
        default_output=format_output("Setting menu."),
    )
    def set_menu(
        self,
        profile: Union[str, c.Container, dict],
        location: int,
        confirm_start=False,
    ):
        """Updates one of the menu options with the profile.

        Args:
        - location, int in range(1, 9)
        - skip_confirmation, if True, request confirmation to start recipe as well.
        """
        profile = self._prepare_profile(profile)

        if location >= 9 or location < 0:
            raise IHCookerException("location %d must be in [0,9]." % location)
        profile.menu_settings.save_recipe = True
        profile.confirm_start = confirm_start
        profile.menu_location = location

        self.send("set_menu1", [self._profile_obj.build(profile).hex()])

    @property
    def _profile_obj(self) -> c.Struct:
        if self.model in MODEL_VERSION1:
            return profile_v1
        elif self.model == MODEL_KOREA1:
            return profile_korea
        else:
            return profile_v2

    def _prepare_profile(self, profile: Union[str, c.Container, dict]) -> c.Container:
        if not isinstance(profile, (dict, c.Container, str)):
            raise ValueError("Invalid profile object")

        if isinstance(profile, str):
            if profile.strip().startswith("{"):
                # Assuming JSON string.
                profile = json.loads(profile)
            else:
                profile = self._profile_obj.parse(bytes.fromhex(profile))
        if isinstance(profile, dict):
            for k in profile.keys():
                if k not in profile_keys:
                    raise ValueError("Invalid key %s in profile dict." % k)
            for stage in profile.get("stages", []):
                for i, k in enumerate(stage.keys()):
                    if k not in stage_keys:
                        raise ValueError("Invalid key %s in stage %d." % (k, i))
            for k in profile.get("menu_settings", {}).keys():
                if k not in menu_keys:
                    raise ValueError("Invalid key %s in menu_settings." % (k))

            profile = self._profile_obj.parse(self._profile_obj.build(profile))
        elif isinstance(profile, c.Container):
            pass

        profile.device_version = DEVICE_ID[self.model]
        return profile

    @property
    def _device_prefix(self):
        if self.model not in DEVICE_ID:
            raise IHCookerException(
                "Model %s currently unsupported, please report this on github."
                % self.model
            )

        prefix = "03%02d" % DEVICE_ID.get(self.model, None)
        return prefix
示例#8
0
class AirFresh(Device):
    """Main class representing the air fresh."""

    _supported_models = list(AVAILABLE_PROPERTIES.keys())

    @command(
        default_output=format_output(
            "",
            "Power: {result.power}\n"
            "Heater (PTC): {result.ptc}\n"
            "AQI: {result.aqi} μg/m³\n"
            "Average AQI: {result.average_aqi} μg/m³\n"
            "Temperature: {result.temperature} °C\n"
            "NTC temperature: {result.ntc_temperature} °C\n"
            "Humidity: {result.humidity} %\n"
            "CO2: {result.co2} %\n"
            "Mode: {result.mode.value}\n"
            "LED: {result.led}\n"
            "LED brightness: {result.led_brightness}\n"
            "Buzzer: {result.buzzer}\n"
            "Child lock: {result.child_lock}\n"
            "Filter life remaining: {result.filter_life_remaining} %\n"
            "Filter hours used: {result.filter_hours_used}\n"
            "Use time: {result.use_time} s\n"
            "Motor speed: {result.motor_speed} rpm\n",
        )
    )
    def status(self) -> AirFreshStatus:
        """Retrieve properties."""

        properties = AVAILABLE_PROPERTIES.get(
            self.model, AVAILABLE_PROPERTIES[MODEL_AIRFRESH_VA2]
        )
        values = self.get_properties(properties, max_properties=15)

        return AirFreshStatus(
            defaultdict(lambda: None, zip(properties, values)), self.model
        )

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

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

    @command(
        click.argument("mode", type=EnumType(OperationMode)),
        default_output=format_output("Setting mode to '{mode.value}'"),
    )
    def set_mode(self, mode: OperationMode):
        """Set mode."""
        return self.send("set_mode", [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."""
        if led:
            return self.send("set_led", ["on"])
        else:
            return self.send("set_led", ["off"])

    @command(
        click.argument("brightness", type=EnumType(LedBrightness)),
        default_output=format_output("Setting LED brightness to {brightness}"),
    )
    def set_led_brightness(self, brightness: LedBrightness):
        """Set led brightness."""
        return self.send("set_led_level", [brightness.value])

    @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."""
        if buzzer:
            return self.send("set_buzzer", ["on"])
        else:
            return self.send("set_buzzer", ["off"])

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

    @command(
        click.argument("value", type=int),
        default_output=format_output("Setting extra to {value}"),
    )
    def set_extra_features(self, value: int):
        """Storage register to enable extra features at the app."""
        if value < 0:
            raise AirFreshException("Invalid app extra value: %s" % value)

        return self.send("set_app_extra", [value])

    @command(default_output=format_output("Resetting filter"))
    def reset_filter(self):
        """Resets filter hours used and remaining life."""
        return self.send("reset_filter1")

    @command(
        click.argument("ptc", type=bool),
        default_output=format_output(
            lambda buzzer: "Turning on PTC" if buzzer else "Turning off PTC"
        ),
    )
    def set_ptc(self, ptc: bool):
        """Set PTC on/off."""
        if ptc:
            return self.send("set_ptc_state", ["on"])
        else:
            return self.send("set_ptc_state", ["off"])
示例#9
0
class AirHumidifierJsqs(MiotDevice):
    """Main class representing the air humidifier which uses MIoT protocol."""

    _mappings = MIOT_MAPPING

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Error: {result.error}\n"
        "Target Humidity: {result.target_humidity} %\n"
        "Relative Humidity: {result.relative_humidity} %\n"
        "Temperature: {result.temperature} °C\n"
        "Water tank detached: {result.tank_filed}\n"
        "Mode: {result.mode}\n"
        "LED light: {result.led_light}\n"
        "Buzzer: {result.buzzer}\n"
        "Overwet protection: {result.overwet_protect}\n",
    ))
    def status(self) -> AirHumidifierJsqsStatus:
        """Retrieve properties."""

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

    @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(
        click.argument("humidity", type=int),
        default_output=format_output("Setting target humidity {humidity}%"),
    )
    def set_target_humidity(self, humidity: int):
        """Set target humidity."""
        if humidity < 40 or humidity > 80:
            raise AirHumidifierJsqsException(
                "Invalid target humidity: %s. Must be between 40 and 80" %
                humidity)
        return self.set_property("target_humidity", humidity)

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

    @command(
        click.argument("light", type=bool),
        default_output=format_output(lambda light: "Turning on LED light"
                                     if light else "Turning off LED light"),
    )
    def set_light(self, light: bool):
        """Set led light."""
        return self.set_property("led_light", light)

    @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.set_property("buzzer", buzzer)

    @command(
        click.argument("overwet", type=bool),
        default_output=format_output(lambda overwet: "Turning on overwet"
                                     if overwet else "Turning off overwet"),
    )
    def set_overwet_protect(self, overwet: bool):
        """Set overwet mode on/off."""
        return self.set_property("overwet_protect", overwet)
示例#10
0
class PhilipsRwread(Device):
    """Main class representing Xiaomi Philips RW Read."""

    _supported_models = list(AVAILABLE_PROPERTIES.keys())

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Brightness: {result.brightness}\n"
        "Delayed turn off: {result.delay_off_countdown}\n"
        "Scene: {result.scene}\n"
        "Motion detection: {result.motion_detection}\n"
        "Motion detection sensitivity: {result.motion_detection_sensitivity}\n"
        "Child lock: {result.child_lock}\n",
    ))
    def status(self) -> PhilipsRwreadStatus:
        """Retrieve properties."""
        properties = AVAILABLE_PROPERTIES.get(
            self.model, AVAILABLE_PROPERTIES[MODEL_PHILIPS_LIGHT_RWREAD])
        values = self.get_properties(properties)

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

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

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

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

        return self.send("set_bright", [level])

    @command(
        click.argument("number", type=int),
        default_output=format_output("Setting fixed scene to {number}"),
    )
    def set_scene(self, number: int):
        """Set one of the fixed eyecare user scenes."""
        if number < 1 or number > 4:
            raise PhilipsRwreadException("Invalid fixed scene number: %s" %
                                         number)

        return self.send("apply_fixed_scene", [number])

    @command(
        click.argument("seconds", type=int),
        default_output=format_output(
            "Setting delayed turn off to {seconds} seconds"),
    )
    def delay_off(self, seconds: int):
        """Set delay off in seconds."""

        if seconds < 0:
            raise PhilipsRwreadException(
                "Invalid value for a delayed turn off: %s" % seconds)

        return self.send("delay_off", [seconds])

    @command(
        click.argument("motion_detection", type=bool),
        default_output=format_output(
            lambda motion_detection: "Turning on motion detection"
            if motion_detection else "Turning off motion detection"),
    )
    def set_motion_detection(self, motion_detection: bool):
        """Set motion detection on/off."""
        return self.send("enable_flm", [int(motion_detection)])

    @command(
        click.argument("sensitivity",
                       type=EnumType(MotionDetectionSensitivity)),
        default_output=format_output(
            "Setting motion detection sensitivity to {sensitivity}"),
    )
    def set_motion_detection_sensitivity(
            self, sensitivity: MotionDetectionSensitivity):
        """Set motion detection sensitivity."""
        return self.send("set_flmvalue", [sensitivity.value])

    @command(
        click.argument("lock", type=bool),
        default_output=format_output(lambda lock: "Turning on child lock"
                                     if lock else "Turning off child lock"),
    )
    def set_child_lock(self, lock: bool):
        """Set child lock on/off."""
        return self.send("enable_chl", [int(lock)])
示例#11
0
class AirHumidifierJsq(Device):
    """Implementation of Xiaomi Zero Fog Humidifier: shuii.humidifier.jsq001."""

    _supported_models = [MODEL_HUMIDIFIER_JSQ001]

    @command(
        default_output=format_output(
            "",
            "Power: {result.power}\n"
            "Mode: {result.mode}\n"
            "Temperature: {result.temperature} °C\n"
            "Humidity: {result.humidity} %\n"
            "Buzzer: {result.buzzer}\n"
            "LED brightness: {result.led_brightness}\n"
            "Child lock: {result.child_lock}\n"
            "No water: {result.no_water}\n"
            "Lid opened: {result.lid_opened}\n",
        )
    )
    def status(self) -> AirHumidifierStatus:
        """Retrieve properties."""

        values = self.send("get_props")

        # Response of an Air Humidifier (shuii.humidifier.jsq001):
        # [24, 37, 3, 1, 0, 2, 0, 0, 0]
        #
        # status[0] : temperature (degrees, int)
        # status[1]: humidity (percentage, int)
        # status[2]: mode ( 0: Intelligent, 1: Level1, ..., 5:Level4)
        # status[3]: buzzer (0: off, 1: on)
        # status[4]: lock (0: off, 1: on)
        # status[5]: brightness (0: off, 1: low, 2: high)
        # status[6]: power (0: off, 1: on)
        # status[7]: water level state (0: ok, 1: add water)
        # status[8]: lid state (0: ok, 1: lid is opened)

        properties = AVAILABLE_PROPERTIES.get(
            self.model, AVAILABLE_PROPERTIES[MODEL_HUMIDIFIER_JSQ001]
        )
        if len(properties) != len(values):
            _LOGGER.error(
                "Count (%s) of requested properties (%s) does not match the "
                "count (%s) of received values (%s).",
                len(properties),
                properties,
                len(values),
                values,
            )

        return AirHumidifierStatus({k: v for k, v in zip(properties, values)})

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

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

    @command(
        click.argument("mode", type=EnumType(OperationMode)),
        default_output=format_output("Setting mode to '{mode.value}'"),
    )
    def set_mode(self, mode: OperationMode):
        """Set mode."""
        value = mode.value
        if value not in (om.value for om in OperationMode):
            raise AirHumidifierException(f"{value} is not a valid OperationMode value")

        return self.send("set_mode", [value])

    @command(
        click.argument("brightness", type=EnumType(LedBrightness)),
        default_output=format_output("Setting LED brightness to {brightness}"),
    )
    def set_led_brightness(self, brightness: LedBrightness):
        """Set led brightness."""
        value = brightness.value
        if value not in (lb.value for lb in LedBrightness):
            raise AirHumidifierException(f"{value} is not a valid LedBrightness value")

        return self.send("set_brightness", [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."""
        brightness = LedBrightness.High if led else LedBrightness.Off
        return self.set_led_brightness(brightness)

    @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("set_buzzer", [int(bool(buzzer))])

    @command(
        click.argument("lock", type=bool),
        default_output=format_output(
            lambda lock: "Turning on child lock" if lock else "Turning off child lock"
        ),
    )
    def set_child_lock(self, lock: bool):
        """Set child lock on/off."""
        return self.send("set_lock", [int(bool(lock))])
示例#12
0
class FanZA5(MiotDevice):
    _mappings = MIOT_MAPPING

    @command(default_output=format_output(
        "",
        "Angle: {result.angle}\n"
        "Battery Supported: {result.battery_supported}\n"
        "Buttons Pressed: {result.buttons_pressed}\n"
        "Buzzer: {result.buzzer}\n"
        "Child Lock: {result.child_lock}\n"
        "Delay Off Countdown: {result.delay_off_countdown}\n"
        "Fan Level: {result.fan_level}\n"
        "Fan Speed: {result.fan_speed}\n"
        "Humidity: {result.humidity}\n"
        "Ionizer: {result.ionizer}\n"
        "LED Brightness: {result.led_brightness}\n"
        "Mode: {result.mode.name}\n"
        "Oscillate: {result.oscillate}\n"
        "Power: {result.power}\n"
        "Powersupply Attached: {result.powersupply_attached}\n"
        "Speed RPM: {result.speed_rpm}\n"
        "Temperature: {result.temperature}\n",
    ))
    def status(self):
        """Retrieve properties."""
        return FanStatusZA5({
            prop["did"]: prop["value"] if prop["code"] == 0 else None
            for prop in self.get_properties_for_mapping()
        })

    @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(
        click.argument("on", type=bool),
        default_output=format_output(lambda on: "Turning on ionizer"
                                     if on else "Turning off ionizer"),
    )
    def set_ionizer(self, on: bool):
        """Set ionizer on/off."""
        return self.set_property("anion", on)

    @command(
        click.argument("speed", type=int),
        default_output=format_output("Setting speed to {speed}%"),
    )
    def set_speed(self, speed: int):
        """Set fan speed."""
        if speed < 1 or speed > 100:
            raise FanException("Invalid speed: %s" % speed)

        return self.set_property("fan_speed", speed)

    @command(
        click.argument("angle", type=int),
        default_output=format_output("Setting angle to {angle}"),
    )
    def set_angle(self, angle: int):
        """Set the oscillation angle."""
        if angle not in SUPPORTED_ANGLES[self.model]:
            raise FanException("Unsupported angle. Supported values: " +
                               ", ".join(
                                   f"{i}"
                                   for i in SUPPORTED_ANGLES[self.model]))

        return self.set_property("swing_mode_angle", angle)

    @command(
        click.argument("oscillate", type=bool),
        default_output=format_output(
            lambda oscillate: "Turning on oscillate"
            if oscillate else "Turning off oscillate"),
    )
    def set_oscillate(self, oscillate: bool):
        """Set oscillate on/off."""
        return self.set_property("swing_mode", oscillate)

    @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.set_property("buzzer", buzzer)

    @command(
        click.argument("lock", type=bool),
        default_output=format_output(lambda lock: "Turning on child lock"
                                     if lock else "Turning off child lock"),
    )
    def set_child_lock(self, lock: bool):
        """Set child lock on/off."""
        return self.set_property("child_lock", lock)

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

        return self.set_property("light", brightness)

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

    @command(
        click.argument("seconds", type=int),
        default_output=format_output(
            "Setting delayed turn off to {seconds} seconds"),
    )
    def delay_off(self, seconds: int):
        """Set delay off seconds."""

        if seconds < 0 or seconds > 10 * 60 * 60:
            raise FanException("Invalid value for a delayed turn off: %s" %
                               seconds)

        return self.set_property("power_off_time", seconds)

    @command(
        click.argument("direction", type=EnumType(MoveDirection)),
        default_output=format_output("Rotating the fan to the {direction}"),
    )
    def set_rotate(self, direction: MoveDirection):
        """Rotate fan 7.5 degrees horizontally to given direction."""
        status = self.status()
        if status.oscillate:
            raise FanException(
                "Rotation requires oscillation to be turned off to function.")
        return self.set_property("set_move", direction.name.lower())
class AirCondition(Device):
    def __init__(self,
                 ip: str = None,
                 token: str = None,
                 model: str = MODEL_AIRCONDITION_MA2,
                 start_id: int = 0,
                 debug: int = 0,
                 lazy_discover: bool = True) -> None:
        super().__init__(ip, token, start_id, debug, lazy_discover)

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

    @command(default_output=format_output(
        "", "Power: {result.power}\n"
        "Mode: {result.mode}\n"
        "Target Temp: {result.target_temp} °C\n"
        "Temperature: {result.temperature} °C\n"
        "Wind Level: {result.wind_level}\n"))
    def status(self) -> AirConditionStatus:
        """
        Retrieve properties.
        'power': 1          => 0 means off, 1 means on
        'mode': 2           => 2 means cool, 3 means dry, 4 means fan only, 5 means heat
        'settemp': 26.5     => target temperature
        'temperature': 27   => current temperature
        'swing': 0          => 0 means off, 1 means on
        'wind_level': 0     => 0~7 mean auto,level 1 ~ level 7
        'dry': 0            => 0 means off, 1 means on
        'energysave': 0     => 0 means off, 1 means on
        'sleep': 0          => 0 means off, 1 means on
        'auxheat': 0        => 0 means off, 1 means on
        'light': 1          => 0 means off, 1 means on
        'beep': 1           => 0 means off, 1 means on
        'timer': '0,0,0,0'
        'clean': '0,0,0,1'
        'examine': '0,0,"none"'
        """

        properties = [
            'power', 'mode', 'settemp', 'temperature', 'swing', 'wind_level'
        ]

        # Something weird. A single request is limited to 1 property.
        # Therefore the properties are divided into multiple requests
        _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.debug(
                "Count (%s) of requested properties does not match the "
                "count (%s) of received values.", properties_count,
                values_count)

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

    @command(
        default_output=format_output("Powering the air condition on"), )
    def on(self):
        """Turn the air condition on."""
        return self.send("set_power", [1])

    @command(
        default_output=format_output("Powering the air condition off"), )
    def off(self):
        """Turn the air condition off."""
        return self.send("set_power", [0])

    @command(click.argument("temperature", type=float),
             default_output=format_output(
                 "Setting target temperature to {temperature} degrees"))
    def set_temperature(self, temperature: float):
        """Set target temperature."""
        return self.send("set_temp", [temperature])

    @command(click.argument("wind_level", type=int),
             default_output=format_output("Setting wind level to {wind_level}")
             )
    def set_wind_level(self, wind_level: int):
        """Set wind level."""
        if wind_level < 0 or wind_level > 7:
            raise AirConditionException("Invalid wind level level: %s",
                                        wind_level)

        return self.send("set_wind_level", [wind_level])

    @command(
        click.argument("swing", type=bool),
        default_output=format_output(lambda swing: "Turning on swing mode"
                                     if swing else "Turning off swing mode"))
    def set_swing(self, swing: bool):
        """Set swing on/off."""
        if swing:
            return self.send("set_swing", [1])
        else:
            return self.send("set_swing", [0])

    @command(click.argument("dry", type=bool),
             default_output=format_output(lambda dry: "Turning on dry mode"
                                          if dry else "Turning off dry mode"))
    def set_dry(self, dry: bool):
        """Set dry on/off."""
        if dry:
            return self.send("set_dry", [1])
        else:
            return self.send("set_dry", [0])

    @command(click.argument("energysave", type=bool),
             default_output=format_output(
                 lambda energysave: "Turning on energysave mode"
                 if energysave else "Turning off energysave mode"))
    def set_energysave(self, energysave: bool):
        """Set energysave on/off."""
        if energysave:
            return self.send("set_energysave", [1])
        else:
            return self.send("set_energysave", [0])

    @command(
        click.argument("sleep", type=bool),
        default_output=format_output(lambda sleep: "Turning on sleep mode"
                                     if sleep else "Turning off sleep mode"))
    def set_sleep(self, sleep: bool):
        """Set sleep on/off."""
        if sleep:
            return self.send("set_sleep", [1])
        else:
            return self.send("set_sleep", [0])

    @command(click.argument("mode", type=EnumType(OperationMode, False)),
             default_output=format_output(
                 "Setting operation mode to '{mode.value}'"))
    def set_mode(self, mode: OperationMode):
        """Set operation mode."""
        return self.send("set_mode", [mode.value])
示例#14
0
class DreameVacuumMiot(MiotDevice):
    """Interface for Vacuum 1C STYTJ01ZHM (dreame.vacuum.mc1808)"""

    mapping = _MAPPING

    @command(default_output=format_output(
        "\n",
        "Battery level: {result.battery_level}\n"
        "Brush life level: {result.brush_life_level}\n"
        "Brush left time: {result.brush_left_time}\n"
        "Charging state: {result.charging_state.name}\n"
        "Cleaning mode: {result.cleaning_mode.name}\n"
        "Device fault: {result.device_fault.name}\n"
        "Device status: {result.device_status.name}\n"
        "Filter left level: {result.filter_left_time}\n"
        "Filter life level: {result.filter_life_level}\n"
        "Life brush main: {result.life_brush_main}\n"
        "Life brush side: {result.life_brush_side}\n"
        "Life sieve: {result.life_sieve}\n"
        "Map view: {result.map_view}\n"
        "Operating mode: {result.operating_mode.name}\n"
        "Side cleaning brush left time: {result.brush_left_time2}\n"
        "Side cleaning brush life level: {result.brush_life_level2}\n"
        "Timer enabled: {result.timer_enable}\n"
        "Timer start time: {result.start_time}\n"
        "Timer stop time: {result.stop_time}\n"
        "Voice package: {result.voice_package}\n"
        "Volume: {result.volume}\n",
    ))
    def status(self) -> DreameVacuumStatus:
        """State of the vacuum."""

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

    def send_action(self, siid, aiid, params=None):
        """Send action to device."""

        # {"did":"<mydeviceID>","siid":18,"aiid":1,"in":[{"piid":1,"value":2}]
        if params is None:
            params = []
        payload = {
            "did": f"call-{siid}-{aiid}",
            "siid": siid,
            "aiid": aiid,
            "in": params,
        }
        return self.send("action", payload)

    @command()
    def start(self) -> None:
        """Start cleaning."""
        return self.send_action(3, 1)

    @command()
    def stop(self) -> None:
        """Stop cleaning."""
        return self.send_action(3, 2)

    @command()
    def home(self) -> None:
        """Return to home."""
        return self.send_action(2, 1)

    @command()
    def identify(self) -> None:
        """Locate the device (i am here)."""
        return self.send_action(17, 1)

    @command()
    def reset_mainbrush_life(self) -> None:
        """Reset main brush life."""
        return self.send_action(26, 1)

    @command()
    def reset_filter_life(self) -> None:
        """Reset filter life."""
        return self.send_action(27, 1)

    @command()
    def reset_sidebrush_life(self) -> None:
        """Reset side brush life."""
        return self.send_action(28, 1)
示例#15
0
class AirPurifier(Device):
    """Main class representing the air purifier."""

    _supported_models = SUPPORTED_MODELS

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "AQI: {result.aqi} μg/m³\n"
        "Average AQI: {result.average_aqi} μg/m³\n"
        "Temperature: {result.temperature} °C\n"
        "Humidity: {result.humidity} %\n"
        "Mode: {result.mode.value}\n"
        "LED: {result.led}\n"
        "LED brightness: {result.led_brightness}\n"
        "Illuminance: {result.illuminance} lx\n"
        "Buzzer: {result.buzzer}\n"
        "Child lock: {result.child_lock}\n"
        "Favorite level: {result.favorite_level}\n"
        "Filter life remaining: {result.filter_life_remaining} %\n"
        "Filter hours used: {result.filter_hours_used}\n"
        "Use time: {result.use_time} s\n"
        "Purify volume: {result.purify_volume} m³\n"
        "Motor speed: {result.motor_speed} rpm\n"
        "Motor 2 speed: {result.motor2_speed} rpm\n"
        "Sound volume: {result.volume} %\n"
        "Filter RFID product id: {result.filter_rfid_product_id}\n"
        "Filter RFID tag: {result.filter_rfid_tag}\n"
        "Filter type: {result.filter_type}\n"
        "Learn mode: {result.learn_mode}\n"
        "Sleep mode: {result.sleep_mode}\n"
        "Sleep time: {result.sleep_time}\n"
        "Sleep mode learn count: {result.sleep_mode_learn_count}\n"
        "AQI sensor enabled on power off: {result.auto_detect}\n",
    ))
    def status(self) -> AirPurifierStatus:
        """Retrieve properties."""

        properties = [
            "power",
            "aqi",
            "average_aqi",
            "humidity",
            "temp_dec",
            "mode",
            "favorite_level",
            "filter1_life",
            "f1_hour_used",
            "use_time",
            "motor1_speed",
            "motor2_speed",
            "purify_volume",
            "f1_hour",
            "led",
            # Second request
            "led_b",
            "bright",
            "buzzer",
            "child_lock",
            "volume",
            "rfid_product_id",
            "rfid_tag",
            "act_sleep",
            "sleep_mode",
            "sleep_time",
            "sleep_data_num",
            "app_extra",
            "act_det",
            "button_pressed",
        ]

        values = self.get_properties(properties, max_properties=15)

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

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

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

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

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

        # Possible alternative property: set_speed_favorite

        # Set the favorite level used when the mode is `favorite`,
        # should be  between 0 and 17.
        return self.send("set_level_favorite", [level])  # 0 ... 17

    @command(
        click.argument("brightness", type=EnumType(LedBrightness)),
        default_output=format_output("Setting LED brightness to {brightness}"),
    )
    def set_led_brightness(self, brightness: LedBrightness):
        """Set led brightness."""
        return self.send("set_led_b", [brightness.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."""
        if led:
            return self.send("set_led", ["on"])
        else:
            return self.send("set_led", ["off"])

    @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."""
        if buzzer:
            return self.send("set_buzzer", ["on"])
        else:
            return self.send("set_buzzer", ["off"])

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

    @command(
        click.argument("volume", type=int),
        default_output=format_output("Setting sound volume to {volume}"),
    )
    def set_volume(self, volume: int):
        """Set volume of sound notifications [0-100]."""
        if volume < 0 or volume > 100:
            raise AirPurifierException("Invalid volume: %s" % volume)

        return self.send("set_volume", [volume])

    @command(
        click.argument("learn_mode", type=bool),
        default_output=format_output(
            lambda learn_mode: "Turning on learn mode"
            if learn_mode else "Turning off learn mode"),
    )
    def set_learn_mode(self, learn_mode: bool):
        """Set the Learn Mode on/off."""
        if learn_mode:
            return self.send("set_act_sleep", ["single"])
        else:
            return self.send("set_act_sleep", ["close"])

    @command(
        click.argument("auto_detect", type=bool),
        default_output=format_output(
            lambda auto_detect: "Turning on auto detect"
            if auto_detect else "Turning off auto detect"),
    )
    def set_auto_detect(self, auto_detect: bool):
        """Set auto detect on/off.

        It's a feature of the AirPurifier V1 & V3
        """
        if auto_detect:
            return self.send("set_act_det", ["on"])
        else:
            return self.send("set_act_det", ["off"])

    @command(
        click.argument("value", type=int),
        default_output=format_output("Setting extra to {value}"),
    )
    def set_extra_features(self, value: int):
        """Storage register to enable extra features at the app.

        app_extra=1 unlocks a turbo mode supported feature
        """
        if value < 0:
            raise AirPurifierException("Invalid app extra value: %s" % value)

        return self.send("set_app_extra", [value])

    @command(default_output=format_output("Resetting filter"))
    def reset_filter(self):
        """Resets filter hours used and remaining life."""
        return self.send("reset_filter1")
示例#16
0
class AirFresh(Device):
    """Main class representing the air fresh."""
    @command(default_output=format_output(
        "", "Power: {result.power}\n"
        "AQI: {result.aqi} μg/m³\n"
        "CO2: {result.co2} mg/m³\n"
        "Temperature: {result.temperature} °C\n"
        "Humidity: {result.humidity} %\n"
        "Mode: {result.mode.value}\n"
        "LED: {result.led}\n"
        "Buzzer: {result.buzzer}\n"
        "Child lock: {result.child_lock}\n"
        "Filter hours used: {result.filter_hours_used}\n"
        "Motor speed: {result.motor_speed} rpm\n"))
    def status(self) -> AirFreshStatus:
        """Retrieve properties."""

        properties = [
            "power", "mode", "aqi", "co2", "led_level", "temp_dec", "humidity",
            "buzzer", "child_lock", "f1_hour_used", "motor1_speed"
        ]

        # A single request is limited to 16 properties. Therefore the
        # properties are divided into multiple requests
        _props = properties.copy()
        values = []
        while _props:
            values.extend(self.send("get_prop", _props[:15]))
            _props[:] = _props[15:]

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

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

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

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

    @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_mode", [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."""
        if led:
            return self.send("set_led", ['on'])
        else:
            return self.send("set_led", ['off'])

    @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."""
        if buzzer:
            return self.send("set_buzzer", ["on"])
        else:
            return self.send("set_buzzer", ["off"])

    @command(
        click.argument("lock", type=bool),
        default_output=format_output(lambda lock: "Turning on child lock"
                                     if lock else "Turning off child lock"))
    def set_child_lock(self, lock: bool):
        """Set child lock on/off."""
        if lock:
            return self.send("set_child_lock", ["on"])
        else:
            return self.send("set_child_lock", ["off"])
示例#17
0
class AirFreshA1(Device):
    """Main class representing the air fresh a1."""

    _supported_models = list(AVAILABLE_PROPERTIES.keys())

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Mode: {result.mode}\n"
        "PM2.5: {result.pm25}\n"
        "CO2: {result.co2}\n"
        "Temperature: {result.temperature}\n"
        "Favorite speed: {result.favorite_speed}\n"
        "Control speed: {result.control_speed}\n"
        "Dust filter life: {result.dust_filter_life_remaining} %, "
        "{result.dust_filter_life_remaining_days} days\n"
        "PTC: {result.ptc}\n"
        "PTC status: {result.ptc_status}\n"
        "Child lock: {result.child_lock}\n"
        "Buzzer: {result.buzzer}\n"
        "Display: {result.display}\n",
    ))
    def status(self) -> AirFreshStatus:
        """Retrieve properties."""

        properties = AVAILABLE_PROPERTIES.get(
            self.model, AVAILABLE_PROPERTIES[MODEL_AIRFRESH_A1])
        values = self.get_properties(properties, max_properties=15)

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

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

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

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

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

    @command(
        click.argument("ptc", type=bool),
        default_output=format_output(lambda ptc: "Turning on ptc"
                                     if ptc else "Turning off ptc"),
    )
    def set_ptc(self, ptc: bool):
        """Turn ptc on/off."""
        return self.send("set_ptc_on", [ptc])

    @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 sound on/off."""
        return self.send("set_sound", [buzzer])

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

    @command(default_output=format_output("Resetting dust filter"))
    def reset_dust_filter(self):
        """Resets filter lifetime of the dust filter."""
        return self.send("set_filter_rate", [100])

    @command(
        click.argument("speed", type=int),
        default_output=format_output("Setting favorite speed to {speed}"),
    )
    def set_favorite_speed(self, speed: int):
        """Sets the fan speed in favorite mode."""
        if speed < 0 or speed > 150:
            raise AirFreshException("Invalid favorite speed: %s" % speed)

        return self.send("set_favourite_speed", [speed])

    @command()
    def set_ptc_timer(self):
        """
        value = time.index + '-' +
            time.hexSum + '-' +
            time.startTime + '-' +
            time.ptcTimer.endTime + '-' +
            time.level + '-' +
            time.status;
        return self.send("set_ptc_timer", [value])
        """
        raise NotImplementedError()

    @command()
    def get_ptc_timer(self):
        """Returns a list of PTC timers.

        Response unknown.
        """
        return self.send("get_ptc_timer")

    @command()
    def get_timer(self):
        """Response unknown."""
        return self.send("get_timer")
示例#18
0
class AirCondition(Device):
    def __init__(self,
                 ip: str = None,
                 token: str = None,
                 model: str = ZHIMI_AC_MA1,
                 start_id: int = 0,
                 debug: int = 0,
                 lazy_discover: bool = True) -> None:
        super().__init__(ip, token, start_id, debug, lazy_discover)

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

    @command(default_output=format_output(
        "", "Power: {result.power}\n"
        "Temperature: {result.temperature} °C\n"
        "Target temperature: {result.target_temperature} °C\n"
        "Mode: {result.mode}\n"))
    def status(self) -> AirConditionStatus:
        """Retrieve properties."""

        properties = [
            'power',
            'mode',
            'st_temp_dec',
            'temp_dec',
            'vertical_swing',
            'vertical_end',
            'vertical_rt',
            'speed_level',
            'lcd_auto',
            'lcd_level',
            'volume',
            'silent',
            'comfort',
            'idle_timer',
            'open_timer',
        ]

        # A single request is limited to 1 properties. Therefore the
        # properties are divided into multiple requests
        _props = properties.copy()
        values = []
        while _props:
            values.extend(self.send("get_prop", _props[:1]))
            _LOGGER.debug("AAA propertie: (%s), value: (%s)", _props[:1],
                          values)
            _props[:] = _props[1:]

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

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

    @command(
        default_output=format_output("Powering the air condition on"), )
    def on(self):
        """Turn the air condition on."""
        return self.send("set_power", ["on"])

    @command(
        default_output=format_output("Powering the air condition off"), )
    def off(self):
        """Turn the air condition off."""
        return self.send("set_power", ["off"])

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

    @command(click.argument("temperature", type=float),
             default_output=format_output(
                 "Setting target temperature to {temperature} degrees"))
    def set_temperature(self, temperature: float):
        """Set target temperature."""
        return self.send("set_temperature", [temperature * 10])

    @command(click.argument("fan_speed", type=int),
             default_output=format_output("Setting fan speed to {fan_speed}"))
    def set_fan_speed(self, fan_speed: int):
        """Set fan speed."""
        if fan_speed < 0 or fan_speed > 5:
            raise AirConditionException("Invalid wind level: %s", fan_speed)
        return self.send("set_spd_level", [fan_speed])

    @command(click.argument("swing", type=str),
             default_output=format_output("Setting swing mode to {swing}"))
    def set_swing(self, swing: str):
        """Set swing on/off."""
        return self.send("set_vertical", [swing])

    @command(click.argument("swing_end", type=bool),
             default_output=format_output(
                 "Setting vertical swing end degrees to {swing_end}"))
    def set_ver_range(self, swing_end: int):
        """Set vertical swing end."""
        return self.send("set_ver_range", [0, swing_end])

    @command(
        click.argument("volume", type=str),
        default_output=format_output(lambda volume: "Turning on volume mode"
                                     "Setting volume mode to {volume}"))
    def set_volume(self, volume: str):
        """Set volume on/off."""
        return self.send("set_volume_sw", [volume])

    @command(
        click.argument("comfort", type=str),
        default_output=format_output("Setting comfort preset to {comfort}"))
    def set_comfort(self, comfort: str):
        """Set comfort on/off."""
        return self.send("set_comfort", [comfort])

    @command(click.argument("sleep", type=str),
             default_output=format_output("setting sleep mode to {sleep}"))
    def set_sleep(self, sleep: str):
        """Set sleep on/off."""
        return self.send("set_silent", [sleep])

    @command(click.argument("lcd_level", type=int),
             default_output=format_output("Setting lcd level to {lcd_level}"))
    def set_lcd_level(self, lcd_level: int):
        """Set lcd level."""
        if lcd_level == 6:
            return self.send("set_lcd_auto", ["on"])
        else:
            return self.send("set_lcd", [lcd_level])

    @command(
        click.argument("angle", type=int),
        default_output=format_output("Setting swing vertical angle to {angle}")
    )
    def set_swing_angle(self, angle: int):
        """Set swing vertical angle."""
        return self.send("set_ver_pos", [angle])

    @command(click.argument("timer", type=int),
             default_output=format_output(
                 "Setting AC idle timer to {timer} minutes."))
    def set_idle_timer(self, timer: int):
        """Set AC idle timer."""
        return self.send("set_idle_timer", [timer * 60])

    @command(click.argument("timer", type=int),
             default_output=format_output(
                 "Setting AC open timer to {timer} minutes."))
    def set_open_timer(self, timer: int):
        """Set AC open timer."""
        return self.send("set_open_timer", [timer * 60])
示例#19
0
class G1Vacuum(MiotDevice, VacuumInterface):
    """Support for G1 vacuum (G1, mijia.vacuum.v2)."""

    _mappings = MIOT_MAPPING

    @command(
        default_output=format_output(
            "",
            "State: {result.state}\n"
            "Error: {result.error}\n"
            "Battery: {result.battery}%\n"
            "Mode: {result.operating_mode}\n"
            "Mop State: {result.mop_state}\n"
            "Charge Status: {result.charge_state}\n"
            "Fan speed: {result.fan_speed}\n"
            "Water level: {result.water_level}\n"
            "Main Brush Life Level: {result.main_brush_life_level}%\n"
            "Main Brush Life Time: {result.main_brush_time_left}\n"
            "Side Brush Life Level: {result.side_brush_life_level}%\n"
            "Side Brush Life Time: {result.side_brush_time_left}\n"
            "Filter Life Level: {result.filter_life_level}%\n"
            "Filter Life Time: {result.filter_time_left}\n"
            "Clean Area: {result.clean_area}\n"
            "Clean Time: {result.clean_time}\n",
        )
    )
    def status(self) -> G1Status:
        """Retrieve properties."""

        return G1Status(
            {
                # 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)
            }
        )

    @command(
        default_output=format_output(
            "",
            "Total Cleaning Count: {result.total_clean_count}\n"
            "Total Cleaning Time: {result.total_clean_time}\n"
            "Total Cleaning Area: {result.total_clean_area}\n",
        )
    )
    def cleaning_summary(self) -> G1CleaningSummary:
        """Retrieve properties."""

        return G1CleaningSummary(
            {
                # 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)
            }
        )

    @command()
    def home(self):
        """Home."""
        return self.call_action("home")

    @command()
    def start(self) -> None:
        """Start Cleaning."""
        return self.call_action("start")

    @command()
    def stop(self):
        """Stop Cleaning."""
        return self.call_action("stop")

    @command()
    def find(self) -> None:
        """Find the robot."""
        return self.call_action("find")

    @command(click.argument("consumable", type=G1Consumable))
    def consumable_reset(self, consumable: G1Consumable):
        """Reset consumable information.

        CONSUMABLE=main_brush_life_level|side_brush_life_level|filter_life_level
        """
        if consumable.name == G1Consumable.MainBrush:
            return self.call_action("reset_main_brush_life_level")
        elif consumable.name == G1Consumable.SideBrush:
            return self.call_action("reset_side_brush_life_level")
        elif consumable.name == G1Consumable.Filter:
            return self.call_action("reset_filter_life_level")

    @command(
        click.argument("fan_speed", type=EnumType(G1FanSpeed)),
        default_output=format_output("Setting fan speed to {fan_speed}"),
    )
    def set_fan_speed(self, fan_speed: G1FanSpeed):
        """Set fan speed."""
        return self.set_property("fan_speed", fan_speed.value)

    @command()
    def fan_speed_presets(self) -> FanspeedPresets:
        """Return available fan speed presets."""
        return {x.name: x.value for x in G1FanSpeed}

    @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)
示例#20
0
class PhilipsMoonlight(Device):
    """Main class representing Xiaomi Philips Zhirui Bedside Lamp.

    Not yet implemented features/methods:

    add_mb                          # Add miband
    get_band_period                 # Bracelet work time
    get_mb_rssi                     # Miband RSSI
    get_mb_mac                      # Miband MAC address
    enable_mibs
    set_band_period
    miIO.bleStartSearchBand
    miIO.bleGetNearbyBandList

    enable_sub_voice                # Sub voice control?
    enable_voice                    # Voice control

    skip_breath
    set_sleep_time
    set_wakeup_time
    en_sleep
    en_wakeup
    go_night                        # Night light / read mode
    get_wakeup_time
    enable_bl                       # Night light
    """

    _supported_models = ["philips.light.moonlight"]

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Brightness: {result.brightness}\n"
        "Color temperature: {result.color_temperature}\n"
        "RGB: {result.rgb}\n"
        "Scene: {result.scene}\n",
    ))
    def status(self) -> PhilipsMoonlightStatus:
        """Retrieve properties."""
        properties = [
            "pow",
            "sta",
            "bri",
            "rgb",
            "cct",
            "snm",
            "spr",
            "spt",
            "wke",
            "bl",
            "ms",
            "mb",
            "wkp",
        ]
        values = self.get_properties(properties)

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

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

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

    @command(
        click.argument("rgb",
                       default=[255] * 3,
                       type=click.Tuple([int, int, int])),
        default_output=format_output("Setting color to {rgb}"),
    )
    def set_rgb(self, rgb: Tuple[int, int, int]):
        """Set color in RGB."""
        for color in rgb:
            if color < 0 or color > 255:
                raise PhilipsMoonlightException("Invalid color: %s" % color)

        return self.send("set_rgb", [*rgb])

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

        return self.send("set_bright", [level])

    @command(
        click.argument("level", type=int),
        default_output=format_output("Setting color temperature to {level}"),
    )
    def set_color_temperature(self, level: int):
        """Set Correlated Color Temperature."""
        if level < 1 or level > 100:
            raise PhilipsMoonlightException("Invalid color temperature: %s" %
                                            level)

        return self.send("set_cct", [level])

    @command(
        click.argument("brightness", type=int),
        click.argument("cct", type=int),
        default_output=format_output(
            "Setting brightness to {brightness} and color temperature to {cct}"
        ),
    )
    def set_brightness_and_color_temperature(self, brightness: int, cct: int):
        """Set brightness level and the correlated color temperature."""
        if brightness < 1 or brightness > 100:
            raise PhilipsMoonlightException("Invalid brightness: %s" %
                                            brightness)

        if cct < 1 or cct > 100:
            raise PhilipsMoonlightException("Invalid color temperature: %s" %
                                            cct)

        return self.send("set_bricct", [brightness, cct])

    @command(
        click.argument("brightness", type=int),
        click.argument("rgb",
                       default=[255] * 3,
                       type=click.Tuple([int, int, int])),
        default_output=format_output(
            "Setting brightness to {brightness} and color to {rgb}"),
    )
    def set_brightness_and_rgb(self, brightness: int, rgb: Tuple[int, int,
                                                                 int]):
        """Set brightness level and the color."""
        if brightness < 1 or brightness > 100:
            raise PhilipsMoonlightException("Invalid brightness: %s" %
                                            brightness)

        for color in rgb:
            if color < 0 or color > 255:
                raise PhilipsMoonlightException("Invalid color: %s" % color)

        return self.send("set_brirgb", [*rgb, brightness])

    @command(
        click.argument("number", type=int),
        default_output=format_output("Setting fixed scene to {number}"),
    )
    def set_scene(self, number: int):
        """Set scene number."""
        if number < 1 or number > 6:
            raise PhilipsMoonlightException("Invalid fixed scene number: %s" %
                                            number)

        if number == 6:
            return self.send("go_night")

        return self.send("apply_fixed_scene", [number])
示例#21
0
class AirHumidifierMjjsq(Device):
    """Support for deerma.humidifier.(mj)jsq."""

    _supported_models = list(AVAILABLE_PROPERTIES.keys())

    @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"
        "Wet protection: {result.wet_protection}\n",
    ))
    def status(self) -> AirHumidifierStatus:
        """Retrieve properties."""

        properties = AVAILABLE_PROPERTIES.get(
            self.model, AVAILABLE_PROPERTIES[MODEL_HUMIDIFIER_MJJSQ])
        values = self.get_properties(properties, max_properties=1)

        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)),
        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])

    @command(
        click.argument("protection", type=bool),
        default_output=format_output(
            lambda protection: "Turning on wet protection"
            if protection else "Turning off wet protection"),
    )
    def set_wet_protection(self, protection: bool):
        """Turn wet protection on/off."""
        return self.send("Set_wet_and_protect", [int(protection)])
示例#22
0
class AirHumidifierMiot(MiotDevice):
    """Main class representing the air humidifier which uses MIoT protocol."""

    _mappings = _MAPPINGS

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Error: {result.error}\n"
        "Target Humidity: {result.target_humidity} %\n"
        "Humidity: {result.humidity} %\n"
        "Temperature: {result.temperature} °C\n"
        "Temperature: {result.fahrenheit} °F\n"
        "Water Level: {result.water_level} %\n"
        "Water tank detached: {result.water_tank_detached}\n"
        "Mode: {result.mode}\n"
        "LED brightness: {result.led_brightness}\n"
        "Buzzer: {result.buzzer}\n"
        "Child lock: {result.child_lock}\n"
        "Dry mode: {result.dry}\n"
        "Button pressed {result.button_pressed}\n"
        "Target motor speed: {result.motor_speed} rpm\n"
        "Actual motor speed: {result.actual_speed} rpm\n"
        "Use time: {result.use_time} s\n"
        "Power time: {result.power_time} s\n"
        "Clean mode: {result.clean_mode}\n",
    ))
    def status(self) -> AirHumidifierMiotStatus:
        """Retrieve properties."""

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

    @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(
        click.argument("rpm", type=int),
        default_output=format_output("Setting motor speed '{rpm}' rpm"),
    )
    def set_speed(self, rpm: int):
        """Set motor speed."""
        if rpm < 200 or rpm > 2000 or rpm % 10 != 0:
            raise AirHumidifierMiotException(
                "Invalid motor speed: %s. Must be between 200 and 2000 and divisible by 10"
                % rpm)
        return self.set_property("speed_level", rpm)

    @command(
        click.argument("humidity", type=int),
        default_output=format_output("Setting target humidity {humidity}%"),
    )
    def set_target_humidity(self, humidity: int):
        """Set target humidity."""
        if humidity < 30 or humidity > 80:
            raise AirHumidifierMiotException(
                "Invalid target humidity: %s. Must be between 30 and 80" %
                humidity)
        return self.set_property("target_humidity", humidity)

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

    @command(
        click.argument("brightness", type=EnumType(LedBrightness)),
        default_output=format_output("Setting LED brightness to {brightness}"),
    )
    def set_led_brightness(self, brightness: LedBrightness):
        """Set led brightness."""
        return self.set_property("led_brightness", brightness.value)

    @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.set_property("buzzer", buzzer)

    @command(
        click.argument("lock", type=bool),
        default_output=format_output(lambda lock: "Turning on child lock"
                                     if lock else "Turning off child lock"),
    )
    def set_child_lock(self, lock: bool):
        """Set child lock on/off."""
        return self.set_property("child_lock", lock)

    @command(
        click.argument("dry", type=bool),
        default_output=format_output(lambda dry: "Turning on dry mode"
                                     if dry else "Turning off dry mode"),
    )
    def set_dry(self, dry: bool):
        """Set dry mode on/off."""
        return self.set_property("dry", dry)

    @command(
        click.argument("clean_mode", type=bool),
        default_output=format_output(
            lambda clean_mode: "Turning on clean mode"
            if clean_mode else "Turning off clean mode"),
    )
    def set_clean_mode(self, clean_mode: bool):
        """Set clean mode on/off."""
        return self.set_property("clean_mode", clean_mode)
示例#23
0
class Yeelight(Device):
    """A rudimentary support for Yeelight bulbs.

    The API is the same as defined in
    https://www.yeelight.com/download/Yeelight_Inter-Operation_Spec.pdf
    and only partially implmented here.

    For a more complete implementation please refer to python-yeelight package
    (https://yeelight.readthedocs.io/en/latest/),
    which however requires enabling the developer mode on the bulbs.
    """

    _supported_models: List[str] = []
    _spec_helper = None

    def __init__(
        self,
        ip: str = None,
        token: str = None,
        start_id: int = 0,
        debug: int = 0,
        lazy_discover: bool = True,
        model: str = None,
    ) -> None:
        super().__init__(ip,
                         token,
                         start_id,
                         debug,
                         lazy_discover,
                         model=model)
        if Yeelight._spec_helper is None:
            Yeelight._spec_helper = YeelightSpecHelper()
            Yeelight._supported_models = Yeelight._spec_helper.supported_models

        self._model_info = Yeelight._spec_helper.get_model_info(self.model)
        self._light_type = YeelightSubLightType.Main
        self._light_info = self._model_info.lamps[self._light_type]
        self._color_temp_range = self._light_info.color_temp

    @command(default_output=format_output("", "{result.cli_format}"))
    def status(self) -> YeelightStatus:
        """Retrieve properties."""
        properties = [
            # general properties
            "name",
            "lan_ctrl",
            "save_state",
            "delayoff",
            "music_on",
            # light properties
            "power",
            "bright",
            "color_mode",
            "rgb",
            "hue",
            "sat",
            "ct",
            "flowing",
            "flow_params",
            # moonlight properties
            "active_mode",
            "nl_br",
            # background light properties
            "bg_power",
            "bg_bright",
            "bg_lmode",
            "bg_rgb",
            "bg_hue",
            "bg_sat",
            "bg_ct",
            "bg_flowing",
            "bg_flow_params",
        ]

        values = self.get_properties(properties)

        return YeelightStatus(dict(zip(properties, values)))

    @property
    def valid_temperature_range(self) -> ColorTempRange:
        return self._color_temp_range

    @command(
        click.option("--transition", type=int, required=False, default=0),
        click.option("--mode", type=int, required=False, default=0),
        default_output=format_output("Powering on"),
    )
    def on(self, transition=0, mode=0):
        """Power on.

        set_power ["on|off", "sudden|smooth", time_in_ms, mode]
        where mode:
        0: last mode
        1: normal mode
        2: rgb mode
        3: hsv mode
        4: color flow
        5: moonlight
        """
        if transition > 0 or mode > 0:
            return self.send("set_power", ["on", "smooth", transition, mode])
        return self.send("set_power", ["on"])

    @command(
        click.option("--transition", type=int, required=False, default=0),
        default_output=format_output("Powering off"),
    )
    def off(self, transition=0):
        """Power off."""
        if transition > 0:
            return self.send("set_power", ["off", "smooth", transition])
        return self.send("set_power", ["off"])

    @command(
        click.argument("level", type=int),
        click.option("--transition", type=int, required=False, default=0),
        default_output=format_output("Setting brightness to {level}"),
    )
    def set_brightness(self, level, transition=0):
        """Set brightness."""
        if level < 0 or level > 100:
            raise YeelightException("Invalid brightness: %s" % level)
        if transition > 0:
            return self.send("set_bright", [level, "smooth", transition])
        return self.send("set_bright", [level])

    @command(
        click.argument("level", type=int),
        click.option("--transition", type=int, required=False, default=0),
        default_output=format_output("Setting color temperature to {level}"),
    )
    def set_color_temp(self, level, transition=500):
        """Set color temp in kelvin."""
        if (level > self.valid_temperature_range.max
                or level < self.valid_temperature_range.min):
            raise YeelightException("Invalid color temperature: %s" % level)
        if transition > 0:
            return self.send("set_ct_abx", [level, "smooth", transition])
        else:
            # Bedside lamp requires transition
            return self.send("set_ct_abx", [level, "sudden", 0])

    @command(
        click.argument("rgb",
                       default=[255] * 3,
                       type=click.Tuple([int, int, int])),
        default_output=format_output("Setting color to {rgb}"),
    )
    def set_rgb(self, rgb: Tuple[int, int, int]):
        """Set color in RGB."""
        for color in rgb:
            if color < 0 or color > 255:
                raise YeelightException("Invalid color: %s" % color)

        return self.send("set_rgb", [rgb_to_int(rgb)])

    def set_hsv(self, hsv):
        """Set color in HSV."""
        return self.send("set_hsv", [hsv])

    @command(
        click.argument("enable", type=bool),
        default_output=format_output("Setting developer mode to {enable}"),
    )
    def set_developer_mode(self, enable: bool) -> bool:
        """Enable or disable the developer mode."""
        return self.send("set_ps", ["cfg_lan_ctrl", str(int(enable))])

    @command(
        click.argument("enable", type=bool),
        default_output=format_output("Setting save state on change {enable}"),
    )
    def set_save_state_on_change(self, enable: bool) -> bool:
        """Enable or disable saving the state on changes."""
        return self.send("set_ps", ["cfg_save_state", str(int(enable))])

    @command(
        click.argument("name", type=str),
        default_output=format_output("Setting name to {name}"),
    )
    def set_name(self, name: str) -> bool:
        """Set an internal name for the bulb."""
        return self.send("set_name", [name])

    @command(default_output=format_output("Toggling the bulb"))
    def toggle(self):
        """Toggle bulb state."""
        return self.send("toggle")

    @command(
        default_output=format_output("Setting current settings to default"))
    def set_default(self):
        """Set current state as default."""
        return self.send("set_default")

    @command(click.argument("table", default="evtRuleTbl"))
    def dump_ble_debug(self, table):
        """Dump the BLE debug table, defaults to evtRuleTbl.

        Some Yeelight devices offer support for BLE remotes.
        This command allows dumping the information about paired remotes,
        that can be used to decrypt the beacon payloads from these devices.

        Example:

        [{'mac': 'xxx', 'evtid': 4097, 'pid': 950, 'beaconkey': 'xxx'},
         {'mac': 'xxx', 'evtid': 4097, 'pid': 339, 'beaconkey': 'xxx'}]
        """
        return self.send("ble_dbg_tbl_dump", {"table": table})

    def set_scene(self, scene, *vals):
        """Set the scene."""
        raise NotImplementedError("Setting the scene is not implemented yet.")
示例#24
0
class PhilipsEyecare(Device):
    """Main class representing Xiaomi Philips Eyecare Smart Lamp 2."""

    _supported_models = ["philips.light.sread1", "philips.light.sread2"]

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Brightness: {result.brightness}\n"
        "Ambient light: {result.ambient}\n"
        "Ambient light brightness: {result.ambient_brightness}\n"
        "Eyecare mode: {result.eyecare}\n"
        "Scene: {result.scene}\n"
        "Eye fatigue reminder: {result.reminder}\n"
        "Smart night light: {result.smart_night_light}\n"
        "Delayed turn off: {result.delay_off_countdown}\n",
    ))
    def status(self) -> PhilipsEyecareStatus:
        """Retrieve properties."""
        properties = [
            "power",
            "bright",
            "notifystatus",
            "ambstatus",
            "ambvalue",
            "eyecare",
            "scene_num",
            "bls",
            "dvalue",
        ]
        values = self.get_properties(properties)

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

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

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

    @command(default_output=format_output("Turning on eyecare mode"))
    def eyecare_on(self):
        """Turn the eyecare mode on."""
        return self.send("set_eyecare", ["on"])

    @command(default_output=format_output("Turning off eyecare mode"))
    def eyecare_off(self):
        """Turn the eyecare mode off."""
        return self.send("set_eyecare", ["off"])

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

        return self.send("set_bright", [level])

    @command(
        click.argument("number", type=int),
        default_output=format_output("Setting fixed scene to {number}"),
    )
    def set_scene(self, number: int):
        """Set one of the fixed eyecare user scenes."""
        if number < 1 or number > 4:
            raise PhilipsEyecareException("Invalid fixed scene number: %s" %
                                          number)

        return self.send("set_user_scene", [number])

    @command(
        click.argument("minutes", type=int),
        default_output=format_output(
            "Setting delayed turn off to {minutes} minutes"),
    )
    def delay_off(self, minutes: int):
        """Set delay off minutes."""

        if minutes < 0:
            raise PhilipsEyecareException(
                "Invalid value for a delayed turn off: %s" % minutes)

        return self.send("delay_off", [minutes])

    @command(default_output=format_output("Turning on smart night light"))
    def smart_night_light_on(self):
        """Turn the smart night light mode on."""
        return self.send("enable_bl", ["on"])

    @command(default_output=format_output("Turning off smart night light"))
    def smart_night_light_off(self):
        """Turn the smart night light mode off."""
        return self.send("enable_bl", ["off"])

    @command(default_output=format_output("Turning on eye fatigue reminder"))
    def reminder_on(self):
        """Enable the eye fatigue reminder / notification."""
        return self.send("set_notifyuser", ["on"])

    @command(default_output=format_output("Turning off eye fatigue reminder"))
    def reminder_off(self):
        """Disable the eye fatigue reminder / notification."""
        return self.send("set_notifyuser", ["off"])

    @command(default_output=format_output("Turning on ambient light"))
    def ambient_on(self):
        """Turn the ambient light on."""
        return self.send("enable_amb", ["on"])

    @command(default_output=format_output("Turning off ambient light"))
    def ambient_off(self):
        """Turn the ambient light off."""
        return self.send("enable_amb", ["off"])

    @command(
        click.argument("level", type=int),
        default_output=format_output("Setting brightness to {level}"),
    )
    def set_ambient_brightness(self, level: int):
        """Set the brightness of the ambient light."""
        if level < 1 or level > 100:
            raise PhilipsEyecareException("Invalid ambient brightness: %s" %
                                          level)

        return self.send("set_amb_bright", [level])
示例#25
0
class AirDogPurifier(Device):
    def __init__(
        self,
        ip: str = None,
        token: str = None,
        delay: float = 0,
        start_id: int = 0,
        debug: int = 0,
        lazy_discover: bool = True,
    ) -> None:
        super().__init__(ip, token, start_id, debug, lazy_discover)
        self.delay = delay

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "AQI: {result.aqi} μg/m³\n"
        "Mode: {result.mode.value}\n"
        "Child lock: {result.child_lock}\n"
        "Speed: {result.speed}\n"
        "Clean: {result.clean}\n",
    ))
    def status(self) -> AirDogPurifierStatus:
        """Retrieve properties."""

        properties = [
            "power",
            "mode",
            "speed",
            "lock",
            "pm",
            "clean",
        ]

        values = self.get_properties(properties, max_properties=6)

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

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

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

    @command(
        click.argument("mode", type=EnumType(OperationMode, False)),
        click.argument("speed", type=int),
        default_output=format_output(
            "Setting mode to '{mode.value}', speed '{speed}'"),
    )
    def set_mode(self, mode: OperationMode, speed: int = 1):
        """Set mode."""

        if mode.value == OperationMode.Auto.value:
            result = self.send("set_wind", [0, 1])
            sleep(self.delay)
            return result

        if mode.value == OperationMode.Sleep.value:
            result = self.send("set_wind", [2, 1])
            sleep(self.delay)
            return result

        if mode.value == OperationMode.Manual.value:
            if speed < 0 or speed > 4:
                raise AirDogPurifierException("Invalid speed: %s" % speed)
            result = self.send("set_wind", [1, speed])
            sleep(self.delay)
            return result

        raise AirDogPurifierException("not supported mode: %s" % mode.value)

    @command(
        click.argument("speed", type=int),
        default_output=format_output("Setting speed to {speed}"),
    )
    def set_speed(self, speed: int):
        """Set speed."""
        if speed < 0 or speed > 4:
            raise AirDogPurifierException("Invalid speed: %s" % speed)

        result = self.send("set_wind", [1, speed])  # 0 ... 4
        sleep(self.delay)
        return result

    @command(
        click.argument("lock", type=bool),
        default_output=format_output(lambda lock: "Turning on child lock"
                                     if lock else "Turning off child lock"),
    )
    def set_child_lock(self, lock: bool):
        """Set child lock on/off."""
        if lock:
            result = self.send("set_lock", [1])
            sleep(self.delay)
            return result
        else:
            result = self.send("set_lock", [0])
            sleep(self.delay)
            return result

    @command(default_output=format_output("Resetting"))
    def clean(self):
        result = self.send("set_clean", [])
        sleep(self.delay)
        return result
示例#26
0
class FanMiot(MiotDevice):
    _mappings = MIOT_MAPPING

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Operation mode: {result.mode}\n"
        "Speed: {result.speed}\n"
        "Oscillate: {result.oscillate}\n"
        "Angle: {result.angle}\n"
        "LED: {result.led}\n"
        "Buzzer: {result.buzzer}\n"
        "Child lock: {result.child_lock}\n"
        "Power-off time: {result.delay_off_countdown}\n",
    ))
    def status(self) -> FanStatusMiot:
        """Retrieve properties."""
        return FanStatusMiot({
            prop["did"]: prop["value"] if prop["code"] == 0 else None
            for prop in self.get_properties_for_mapping()
        })

    @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(
        click.argument("mode", type=EnumType(OperationMode)),
        default_output=format_output("Setting mode to '{mode.value}'"),
    )
    def set_mode(self, mode: OperationMode):
        """Set mode."""
        return self.set_property("mode", OperationModeMiot[mode.name].value)

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

        return self.set_property("fan_speed", speed)

    @command(
        click.argument("angle", type=int),
        default_output=format_output("Setting angle to {angle}"),
    )
    def set_angle(self, angle: int):
        """Set the oscillation angle."""
        if angle not in SUPPORTED_ANGLES[self.model]:
            raise FanException("Unsupported angle. Supported values: " +
                               ", ".join(
                                   f"{i}"
                                   for i in SUPPORTED_ANGLES[self.model]))

        return self.set_property("swing_mode_angle", angle)

    @command(
        click.argument("oscillate", type=bool),
        default_output=format_output(
            lambda oscillate: "Turning on oscillate"
            if oscillate else "Turning off oscillate"),
    )
    def set_oscillate(self, oscillate: bool):
        """Set oscillate on/off."""
        return self.set_property("swing_mode", oscillate)

    @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.set_property("light", 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.set_property("buzzer", buzzer)

    @command(
        click.argument("lock", type=bool),
        default_output=format_output(lambda lock: "Turning on child lock"
                                     if lock else "Turning off child lock"),
    )
    def set_child_lock(self, lock: bool):
        """Set child lock on/off."""
        return self.set_property("child_lock", lock)

    @command(
        click.argument("minutes", type=int),
        default_output=format_output(
            "Setting delayed turn off to {minutes} minutes"),
    )
    def delay_off(self, minutes: int):
        """Set delay off minutes."""

        if minutes < 0 or minutes > 480:
            raise FanException("Invalid value for a delayed turn off: %s" %
                               minutes)

        return self.set_property("power_off_time", minutes)

    @command(
        click.argument("direction", type=EnumType(MoveDirection)),
        default_output=format_output("Rotating the fan to the {direction}"),
    )
    def set_rotate(self, direction: MoveDirection):
        """Rotate fan to given direction."""
        # Values for: P9,P10,P11,P15,P18,...
        # { "value": 0, "description": "NONE" },
        # { "value": 1, "description": "LEFT" },
        # { "value": 2, "description": "RIGHT" }
        value = 0
        if direction == MoveDirection.Left:
            value = 1
        elif direction == MoveDirection.Right:
            value = 2
        return self.set_property("set_move", value)
示例#27
0
class ViomiWaterHeater(Device):
    """Main class representing the Viomi Waterheaters."""

    _supported_models = [MODEL_WATERHEATER_E1]

    @command(default_output=format_output(
        "",
        "Operation status: {result.status.name} ({result.status.value})\n"
        "Velocity: {result.velocity}\n"
        "Water temperature: {result.water_temperature}°C\n"
        "Target temperature: {result.target_temperature}°C\n"
        "Error status: {result.error}\n"
        "Remaining hot water volume: {result.hot_water_volume}%\n"
        "Device cleaning is required: {result.cleaning_required}\n"
        "Operation mode type: {result.mode.name} ({result.mode.value})\n"
        "Booking mode start time at: {result.service_time_start}\n"
        "Booking mode end time at: {result.service_time_end}\n",
    ))
    def status(self) -> ViomiWaterHeaterStatus:
        """Retrieve properties."""
        properties = SUPPORTED_MODELS.get(
            self.model,
            SUPPORTED_MODELS[MODEL_WATERHEATER_E1])["available_properties"]
        values = self.get_properties(properties, max_properties=1)

        return ViomiWaterHeaterStatus(dict(zip(properties, values)))

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

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

    @command(
        click.argument("temperature", type=int),
        default_output=format_output(
            "Setting target temperature to {temperature}"),
    )
    def set_target_temperature(self, temperature: int):
        """Set target temperature."""
        min_temp: int
        max_temp: int

        min_temp, max_temp = SUPPORTED_MODELS.get(
            self.model,
            SUPPORTED_MODELS[MODEL_WATERHEATER_E1])["temperature_range"]

        if not min_temp <= temperature <= max_temp:
            raise ViomiWaterHeaterException(
                "Invalid target temperature: %s" % temperature +
                ". Supported range: [{min_temp}, {max_temp}")

        return self.send("set_temp", [temperature])

    @command(default_output=format_output("Setting bacteriostatic mode on"))
    def set_bacteriostatic_mode(self):
        """Set bacteriostatic operational mode (water disinfection mode)."""
        mode: OperationMode
        bacteriostatic_mode: bool
        bacteriostatic_temp: int

        bacteriostatic_mode = SUPPORTED_MODELS.get(
            self.model,
            SUPPORTED_MODELS[MODEL_WATERHEATER_E1])["bacteriostatic_mode"]

        if not bacteriostatic_mode:
            raise ViomiWaterHeaterException(
                "Bacteriostatic mode is not supported.")

        bacteriostatic_temp = SUPPORTED_MODELS.get(
            self.model, SUPPORTED_MODELS[MODEL_WATERHEATER_E1]
        )["bacteriostatic_temperature"]

        mode = OperationMode(self.send("get_prop", ["modeType"])[0])

        # No Bacteriostatic operational mode in Thermostatic mode.
        if mode == OperationMode.Thermostatic:
            raise ViomiWaterHeaterException(
                "Bacteriostatic operational mode is "
                "not supported in Thermostatic mode.")

        return self.send("set_temp", [bacteriostatic_temp])

    @command(
        click.argument("time_start", type=int),
        click.argument("time_end", type=int),
        default_output=format_output(
            lambda time_start, time_end:
            "Setting up the Booking mode operational interval from: %02d:00 " %
            time_start + "to: %02d:00 " % time_end + "(duration: %s hours)." %
            (time_end - time_start
             if time_end - time_start > 0 else time_end - time_start + 24)),
    )
    def set_service_time(self, time_start, time_end):
        """Setting up the Booking mode operational interval."""
        if not (0 <= time_start <= 23) or not (0 <= time_end <= 23):
            raise ViomiWaterHeaterException(
                "Booking mode operational interval parameters "
                "must be within [0, 23].")
        """ First parameter of set_appoint means to activate or not Booking mode:

        0 - set interval only;
        1 - set interval and change mode.
        """
        return self.send("set_appoint", [0, time_start, time_end])

    @command(
        click.argument("mode", type=EnumType(OperationMode)),
        default_output=format_output(
            "Setting operation mode to {mode.name} ({mode.value})"),
    )
    def set_mode(self, mode: OperationMode):
        """Set operation mode."""
        booking_time_start: int
        booking_time_end: int

        # The Booking mode must be activated in a special way
        if mode == OperationMode.Booking:
            booking_time_start = self.send("get_prop", ["appointStart"])[0]
            booking_time_end = self.send("get_prop", ["appointEnd"])[0]
            return self.send("set_appoint",
                             [1, booking_time_start, booking_time_end])

        # Other operational modes
        return self.send("set_mode", [mode.value])
示例#28
0
class Fan1C(MiotDevice):
    # TODO Fan1C should be merged to FanMiot, or moved into its separate file
    _mappings = FAN1C_MAPPINGS

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

    @command(default_output=format_output(
        "",
        "Power: {result.power}\n"
        "Operation mode: {result.mode}\n"
        "Speed: {result.speed}\n"
        "Oscillate: {result.oscillate}\n"
        "LED: {result.led}\n"
        "Buzzer: {result.buzzer}\n"
        "Child lock: {result.child_lock}\n"
        "Power-off time: {result.delay_off_countdown}\n",
    ))
    def status(self) -> FanStatus1C:
        """Retrieve properties."""
        return FanStatus1C({
            prop["did"]: prop["value"] if prop["code"] == 0 else None
            for prop in self.get_properties_for_mapping()
        })

    @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(
        click.argument("mode", type=EnumType(OperationMode)),
        default_output=format_output("Setting mode to '{mode.value}'"),
    )
    def set_mode(self, mode: OperationMode):
        """Set mode."""
        return self.set_property("mode", OperationModeMiot[mode.name].value)

    @command(
        click.argument("speed", type=int),
        default_output=format_output("Setting speed to {speed}"),
    )
    def set_speed(self, speed: int):
        """Set speed."""
        if speed not in (1, 2, 3):
            raise FanException("Invalid speed: %s" % speed)

        return self.set_property("fan_level", speed)

    @command(
        click.argument("oscillate", type=bool),
        default_output=format_output(
            lambda oscillate: "Turning on oscillate"
            if oscillate else "Turning off oscillate"),
    )
    def set_oscillate(self, oscillate: bool):
        """Set oscillate on/off."""
        return self.set_property("swing_mode", oscillate)

    @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.set_property("light", 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.set_property("buzzer", buzzer)

    @command(
        click.argument("lock", type=bool),
        default_output=format_output(lambda lock: "Turning on child lock"
                                     if lock else "Turning off child lock"),
    )
    def set_child_lock(self, lock: bool):
        """Set child lock on/off."""
        return self.set_property("child_lock", lock)

    @command(
        click.argument("minutes", type=int),
        default_output=format_output(
            "Setting delayed turn off to {minutes} minutes"),
    )
    def delay_off(self, minutes: int):
        """Set delay off minutes."""

        if minutes < 0 or minutes > 480:
            raise FanException("Invalid value for a delayed turn off: %s" %
                               minutes)

        return self.set_property("power_off_time", minutes)
示例#29
0
class AirQualityMonitor(Device):
    """Xiaomi PM2.5 Air Quality Monitor."""
    def __init__(self,
                 ip: str = None,
                 token: str = None,
                 start_id: int = 0,
                 debug: int = 0,
                 lazy_discover: bool = True,
                 model: str = MODEL_AIRQUALITYMONITOR_S1) -> None:
        super().__init__(ip, token, start_id, debug, lazy_discover)

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

        self.device_info = None

    @command(default_output=format_output(""))
    def status(self) -> AirQualityMonitorStatus:
        """Return device status."""

        properties = AVAILABLE_PROPERTIES[self.model]

        values = self.send("get_prop", properties)

        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 AirQualityMonitorStatus(defaultdict(lambda: None, values))

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

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

    @command(
        click.argument("display_clock", type=bool),
        default_output=format_output(lambda led: "Turning on display clock"
                                     if led else "Turning off display clock"))
    def set_display_clock(self, display_clock: bool):
        """Enable/disable displaying a clock instead the AQI."""
        if display_clock:
            self.send("set_time_state", ["on"])
        else:
            self.send("set_time_state", ["off"])

    @command(click.argument("auto_close", type=bool),
             default_output=format_output(lambda led: "Turning on auto close"
                                          if led else "Turning off auto close")
             )
    def set_auto_close(self, auto_close: bool):
        """Purpose unknown."""
        if auto_close:
            self.send("set_auto_close", ["on"])
        else:
            self.send("set_auto_close", ["off"])

    @command(click.argument("night_mode", type=bool),
             default_output=format_output(lambda led: "Turning on night mode"
                                          if led else "Turning off night mode")
             )
    def set_night_mode(self, night_mode: bool):
        """Decrease the brightness of the display."""
        if night_mode:
            self.send("set_night_state", ["on"])
        else:
            self.send("set_night_state", ["off"])

    @command(
        click.argument("begin_hour", type=int),
        click.argument("begin_minute", type=int),
        click.argument("end_hour", type=int),
        click.argument("end_minute", type=int),
        default_output=format_output(
            "Setting night time to {begin_hour}:{begin_minute} - {end_hour}:{end_minute}"
        ))
    def set_night_time(self, begin_hour: int, begin_minute: int, end_hour: int,
                       end_minute: int):
        """Enable night mode daily at bedtime."""
        begin = begin_hour * 3600 + begin_minute * 60
        end = end_hour * 3600 + end_minute * 60

        if begin < 0 or begin > 86399 or end < 0 or end > 86399:
            raise Exception("Begin or/and end time invalid.")

        self.send("set_night_time", [begin, end])
示例#30
0
class ViomiVacuum(Device, VacuumInterface):
    """Interface for Viomi vacuums (viomi.vacuum.v7)."""

    _supported_models = SUPPORTED_MODELS

    timeout = 5
    retry_count = 10

    def __init__(
        self,
        ip: str,
        token: str = None,
        start_id: int = 0,
        debug: int = 0,
        *,
        model: str = None,
    ) -> None:
        super().__init__(ip, token, start_id, debug, model=model)
        self.manual_seqnum = -1
        self._cache: Dict[str, Any] = {
            "edge_state": None,
            "rooms": {},
            "maps": {}
        }

    @command(default_output=format_output(
        "\n", "General\n"
        "=======\n\n"
        "Hardware version: {result.hw_info}\n"
        "State: {result.state}\n"
        "Working: {result.is_on}\n"
        "Battery status: {result.error}\n"
        "Battery: {result.battery}\n"
        "Charging: {result.charging}\n"
        "Box type: {result.bin_type}\n"
        "Fan speed: {result.fanspeed}\n"
        "Water grade: {result.water_grade}\n"
        "Mop mode: {result.mop_mode}\n"
        "Mop installed: {result.mop_installed}\n"
        "Vacuum along the edges: {result.edge_state}\n"
        "Mop route pattern: {result.mop_route}\n"
        "Secondary Cleanup: {result.repeat_cleaning}\n"
        "Sound Volume: {result.sound_volume}\n"
        "Clean time: {result.clean_time}\n"
        "Clean area: {result.clean_area} m²\n"
        "\n"
        "Map\n"
        "===\n\n"
        "Current map ID: {result.current_map_id}\n"
        "Remember map: {result.remember_map}\n"
        "Has map: {result.has_map}\n"
        "Has new map: {result.has_new_map}\n"
        "Number of maps: {result.map_number}\n"
        "\n"
        "Unknown properties\n"
        "=================\n\n"
        "Light state: {result.light_state}\n"
        # "Order time: {result.order_time}\n"
        # "Start time: {result.start_time}\n"
        # "water_percent: {result.water_percent}\n"
        # "zone_data: {result.zone_data}\n",
    ))
    def status(self) -> ViomiVacuumStatus:
        """Retrieve properties."""
        properties = [
            "battary_life",
            "box_type",
            "cur_mapid",
            "err_state",
            "has_map",
            "has_newmap",
            "hw_info",
            "is_charge",
            "is_mop",
            "is_work",
            "light_state",
            "map_num",
            "mode",
            "mop_route",
            "mop_type",
            "remember_map",
            "repeat_state",
            "run_state",
            "s_area",
            "s_time",
            "suction_grade",
            "v_state",
            "water_grade",
            # The following list of properties existing but
            # there are not used in the code
            # "order_time",
            # "start_time",
            # "water_percent",
            # "zone_data",
            # "sw_info",
            # "main_brush_hours",
            # "main_brush_life",
            # "side_brush_hours",
            # "side_brush_life",
            # "mop_hours",
            # "mop_life",
            # "hypa_hours",
            # "hypa_life",
        ]

        values = self.get_properties(properties)

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

    @command()
    def home(self):
        """Return to home."""
        self.send("set_charge", [1])

    @command()
    def start(self):
        """Start cleaning."""
        # params: [edge, 1, roomIds.length, *list_of_room_ids]
        # - edge: see ViomiEdgeState
        # - 1: start cleaning (2 pause, 0 stop)
        # - roomIds.length
        # - *room_id_list
        # 3rd param of set_mode_withroom is room_array_len and next are
        # room ids ([0, 1, 3, 11, 12, 13] = start cleaning rooms 11-13).
        # room ids are encoded in map and it's part of cloud api so best way
        # to get it is log between device <> mi home app
        # (before map format is supported).
        self._cache["edge_state"] = self.get_properties(["mode"])
        self.send("set_mode_withroom", self._cache["edge_state"] + [1, 0])

    @command(
        click.option(
            "--rooms",
            "-r",
            multiple=True,
            help="Rooms name or room id. Can be used multiple times",
        ))
    def start_with_room(self, rooms):
        """Start cleaning specific rooms."""
        if not self._cache["rooms"]:
            self.get_rooms()
        reverse_rooms = {v: k for k, v in self._cache["rooms"].items()}
        room_ids = []
        for room in rooms:
            if room in self._cache["rooms"]:
                room_ids.append(int(room))
            elif room in reverse_rooms:
                room_ids.append(int(reverse_rooms[room]))
            else:
                room_keys = ", ".join(self._cache["rooms"].keys())
                room_ids = ", ".join(self._cache["rooms"].values())
                raise DeviceException(
                    f"Room {room} is unknown, it must be in {room_keys} or {room_ids}"
                )

        self._cache["edge_state"] = self.get_properties(["mode"])
        self.send(
            "set_mode_withroom",
            self._cache["edge_state"] + [1, len(room_ids)] + room_ids,
        )

    @command()
    def pause(self):
        """Pause cleaning."""
        # params: [edge_state, 0]
        # - edge: see ViomiEdgeState
        # - 2: pause cleaning
        if not self._cache["edge_state"]:
            self._cache["edge_state"] = self.get_properties(["mode"])
        self.send("set_mode", self._cache["edge_state"] + [2])

    @command()
    def stop(self):
        """Validate that Stop cleaning."""
        # params: [edge_state, 0]
        # - edge: see ViomiEdgeState
        # - 0: stop cleaning
        if not self._cache["edge_state"]:
            self._cache["edge_state"] = self.get_properties(["mode"])
        self.send("set_mode", self._cache["edge_state"] + [0])

    @command(click.argument("mode", type=EnumType(ViomiMode)))
    def clean_mode(self, mode: ViomiMode):
        """Set the cleaning mode.

        [vacuum, vacuumAndMop, mop, cleanzone, cleanspot]
        """
        self.send("set_mop", [mode.value])

    @command(click.argument("speed", type=EnumType(ViomiVacuumSpeed)))
    def set_fan_speed(self, speed: ViomiVacuumSpeed):
        """Set fanspeed [silent, standard, medium, turbo]."""
        self.send("set_suction", [speed.value])

    @command()
    def fan_speed_presets(self) -> FanspeedPresets:
        """Return available fan speed presets."""
        return {x.name: x.value for x in list(ViomiVacuumSpeed)}

    @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()}"
            )
        self.send("set_suction", [speed_preset])

    @command(click.argument("watergrade", type=EnumType(ViomiWaterGrade)))
    def set_water_grade(self, watergrade: ViomiWaterGrade):
        """Set water grade.

        [low, medium, high]
        """
        self.send("set_suction", [watergrade.value])

    def get_positions(self, plan_multiplicator=1) -> List[ViomiPositionPoint]:
        """Return the last positions.

        plan_multiplicator scale up the coordinates values
        """
        results = self.send("get_curpos", [])
        positions = []
        # Group result 4 by 4
        for res in [i for i in zip(*(results[i::4] for i in range(4)))]:
            # ignore type require for mypy error
            # "ViomiPositionPoint" gets multiple values for keyword argument "plan_multiplicator"
            positions.append(
                ViomiPositionPoint(
                    *res,
                    plan_multiplicator=plan_multiplicator)  # type: ignore
            )
        return positions

    @command()
    def get_current_position(self) -> Optional[ViomiPositionPoint]:
        """Return the current position."""
        positions = self.get_positions()
        if positions:
            return positions[-1]
        return None

    # MISSING cleaning history

    @command()
    def get_scheduled_cleanup(self):
        """Not implemented yet."""
        # Needs to reads and understand the return of:
        # self.send("get_ordertime", [])
        # [id, enabled, repeatdays, hour, minute, ?, ? , ?, ?, ?, ?, nb_of_rooms, room_id, room_name, room_id, room_name, ...]
        raise NotImplementedError()

    @command()
    def add_timer(self):
        """Not implemented yet."""
        # Needs to reads and understand:
        # self.send("set_ordertime", [????])
        raise NotImplementedError()

    @command()
    def delete_timer(self):
        """Not implemented yet."""
        # Needs to reads and understand:
        # self.send("det_ordertime", [shedule_id])
        raise NotImplementedError()

    @command(click.argument("state", type=EnumType(ViomiEdgeState)))
    def set_edge(self, state: ViomiEdgeState):
        """Vacuum along edges.

        This is valid for a single cleaning.
        """
        return self.send("set_mode", [state.value])

    @command(click.argument("state", type=bool))
    def set_repeat(self, state: bool):
        """Set or Unset repeat mode (Secondary cleanup)."""
        return self.send("set_repeat", [int(state)])

    @command(click.argument("mop_mode", type=EnumType(ViomiRoutePattern)))
    def set_route_pattern(self, mop_mode: ViomiRoutePattern):
        """Set the mop route pattern."""
        self.send("set_moproute", [mop_mode.value])

    @command()
    def dnd_status(self):
        """Returns do-not-disturb status."""
        status = self.send("get_notdisturb")
        return DNDStatus(
            dict(
                enabled=status[0],
                start_hour=status[1],
                start_minute=status[2],
                end_hour=status[3],
                end_minute=status[4],
            ))

    @command(
        click.option("--disable", is_flag=True),
        click.argument("start_hr", type=int),
        click.argument("start_min", type=int),
        click.argument("end_hr", type=int),
        click.argument("end_min", type=int),
    )
    def set_dnd(self, disable: bool, start_hr: int, start_min: int,
                end_hr: int, end_min: int):
        """Set do-not-disturb.

        :param int start_hr: Start hour
        :param int start_min: Start minute
        :param int end_hr: End hour
        :param int end_min: End minute
        """
        return self.send(
            "set_notdisturb",
            [0 if disable else 1, start_hr, start_min, end_hr, end_min],
        )

    @command(click.argument("volume", type=click.IntRange(0, 10)))
    def set_sound_volume(self, volume: int):
        """Switch the voice on or off."""
        enabled = 1
        if volume == 0:
            enabled = 0
        return self.send("set_voice", [enabled, volume])

    @command(click.argument("state", type=bool))
    def set_remember_map(self, state: bool):
        """Set remember map state."""
        return self.send("set_remember", [int(state)])

    # MISSING: Virtual wall/restricted area

    @command()
    def get_maps(self) -> List[Dict[str, Any]]:
        """Return map list.

        [{'name': 'MapName1', 'id': 1598622255, 'cur': False},
         {'name': 'MapName2', 'id': 1599508355, 'cur': True},
          ...]
        """
        if not self._cache["maps"]:
            self._cache["maps"] = self.send("get_map")
        return self._cache["maps"]

    @command(click.argument("map_id", type=int))
    def set_map(self, map_id: int):
        """Change current map."""
        maps = self.get_maps()
        if map_id not in [m["id"] for m in maps]:
            raise ViomiVacuumException(f"Map id {map_id} doesn't exists")
        return self.send("set_map", [map_id])

    @command(click.argument("map_id", type=int))
    def delete_map(self, map_id: int):
        """Delete map."""
        maps = self.get_maps()
        if map_id not in [m["id"] for m in maps]:
            raise ViomiVacuumException(f"Map id {map_id} doesn't exists")
        return self.send("del_map", [map_id])

    @command(
        click.argument("map_id", type=int),
        click.argument("map_name", type=str),
    )
    def rename_map(self, map_id: int, map_name: str):
        """Rename map."""
        maps = self.get_maps()
        if map_id not in [m["id"] for m in maps]:
            raise ViomiVacuumException(f"Map id {map_id} doesn't exists")
        return self.send("rename_map", {"mapID": map_id, "name": map_name})

    @command(
        click.option("--map-id", type=int, default=None),
        click.option("--map-name", type=str, default=None),
        click.option("--refresh", type=bool, default=False),
    )
    def get_rooms(self,
                  map_id: int = None,
                  map_name: str = None,
                  refresh: bool = False):
        """Return room ids and names."""
        if self._cache["rooms"] and not refresh:
            return self._cache["rooms"]

        # TODO: map_name and map_id are just dead code here?
        if map_name:
            maps = self.get_maps()
            map_ids = [map_["id"] for map_ in maps if map_["name"] == map_name]
            if not map_ids:
                map_names = ", ".join([m["name"] for m in maps])
                raise ViomiVacuumException(
                    f"Error: Bad map name, should be in {map_names}")
        elif map_id:
            maps = self.get_maps()
            if map_id not in [m["id"] for m in maps]:
                map_ids_str = ", ".join([str(m["id"]) for m in maps])
                raise ViomiVacuumException(
                    f"Error: Bad map id, should be in {map_ids_str}")
        # Get scheduled cleanup
        schedules = self.send("get_ordertime", [])
        scheduled_found, rooms = _get_rooms_from_schedules(schedules)
        if not scheduled_found:
            msg = ("Fake schedule not found. "
                   "Please create a scheduled cleanup with the "
                   "following properties:\n"
                   "* Hour: 00\n"
                   "* Minute: 00\n"
                   "* Select all (minus one) the rooms one by one\n"
                   "* Set as inactive scheduled cleanup\n"
                   "Then create a scheduled cleanup with the room missed at "
                   "previous step with the following properties:\n"
                   "* Hour: 00\n"
                   "* Minute: 00\n"
                   "* Select only the missed room\n"
                   "* Set as inactive scheduled cleanup\n")
            raise ViomiVacuumException(msg)

        self._cache["rooms"] = rooms
        return rooms

    # MISSING Area editor

    # MISSING Reset map

    # MISSING Device leveling

    # MISSING Looking for the vacuum-mop

    @command()
    def consumable_status(self) -> ViomiConsumableStatus:
        """Return information about consumables."""
        return ViomiConsumableStatus(self.send("get_consumables"))

    @command(
        click.argument("direction", type=EnumType(ViomiMovementDirection)),
        click.option(
            "--duration",
            type=float,
            default=0.5,
            help="number of seconds to perform this movement",
        ),
    )
    def move(self, direction: ViomiMovementDirection, duration=0.5):
        """Manual movement."""
        start = time.time()
        while time.time() - start < duration:
            self.send("set_direction", [direction.value])
            time.sleep(0.1)
        self.send("set_direction", [ViomiMovementDirection.Stop.value])

    @command(click.argument("language", type=EnumType(ViomiLanguage)))
    def set_language(self, language: ViomiLanguage):
        """Set the device's audio language.

        This seems doing nothing on STYJ02YM
        """
        return self.send("set_language", [language.value])

    @command(click.argument("state", type=EnumType(ViomiLedState)))
    def led(self, state: ViomiLedState):
        """Switch the button leds on or off.

        This seems doing nothing on STYJ02YM
        """
        return self.send("set_light", [state.value])

    @command(click.argument("mode", type=EnumType(ViomiCarpetTurbo)))
    def carpet_mode(self, mode: ViomiCarpetTurbo):
        """Set the carpet mode.

        This seems doing nothing on STYJ02YM
        """
        return self.send("set_carpetturbo", [mode.value])