Ejemplo n.º 1
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])
Ejemplo n.º 2
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")
Ejemplo n.º 3
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"])
Ejemplo n.º 4
0
class FanP5(Device):
    """Support for dmaker.fan.p5."""

    _supported_models = [MODEL_FAN_P5]

    def __init__(
        self,
        ip: str = None,
        token: str = None,
        start_id: int = 0,
        debug: int = 0,
        lazy_discover: bool = True,
        model: str = MODEL_FAN_P5,
    ) -> 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"
        "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) -> FanStatusP5:
        """Retrieve properties."""
        properties = AVAILABLE_PROPERTIES[self.model]
        values = self.get_properties(properties, max_properties=15)

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

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

    @command(default_output=format_output("Powering off"))
    def off(self):
        """Power off."""
        return self.send("s_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("s_mode", [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 > 100:
            raise FanException("Invalid speed: %s" % speed)

        return self.send("s_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 [30, 60, 90, 120, 140]:
            raise FanException(
                "Unsupported angle. Supported values: 30, 60, 90, 120, 140")

        return self.send("s_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."""
        if oscillate:
            return self.send("s_roll", [True])
        else:
            return self.send("s_roll", [False])

    @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("s_light", [True])
        else:
            return self.send("s_light", [False])

    @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("s_sound", [True])
        else:
            return self.send("s_sound", [False])

    @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("s_lock", [True])
        else:
            return self.send("s_lock", [False])

    @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 FanException("Invalid value for a delayed turn off: %s" %
                               minutes)

        return self.send("s_t_off", [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 the fan by -5/+5 degrees left/right."""
        return self.send("m_roll", [direction.value])
Ejemplo n.º 5
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])
Ejemplo n.º 6
0
class Vacuum(Device):
    """Main class representing the vacuum."""

    _supported_models = SUPPORTED_MODELS

    def __init__(self,
                 ip: str,
                 token: str = None,
                 start_id: int = 0,
                 debug: int = 0,
                 *,
                 model=None):
        super().__init__(ip, token, start_id, debug, model=model)
        self.manual_seqnum = -1

    @command()
    def start(self):
        """Start cleaning."""
        return self.send("app_start")

    @command()
    def stop(self):
        """Stop cleaning.

        Note, prefer 'pause' instead of this for wider support. Some newer vacuum models
        do not support this command.
        """
        return self.send("app_stop")

    @command()
    def spot(self):
        """Start spot cleaning."""
        return self.send("app_spot")

    @command()
    def pause(self):
        """Pause cleaning."""
        return self.send("app_pause")

    @command()
    def resume_or_start(self):
        """A shortcut for resuming or starting cleaning."""
        status = self.status()
        if status.in_zone_cleaning and (status.is_paused or status.got_error):
            return self.resume_zoned_clean()
        if status.in_segment_cleaning and (status.is_paused
                                           or status.got_error):
            return self.resume_segment_clean()

        return self.start()

    def _fetch_info(self) -> DeviceInfo:
        """Return info about the device.

        This is overrides the base class info to account for gen1 devices that do not
        respond to info query properly when not connected to the cloud.
        """
        try:
            info = super()._fetch_info()
            return info
        except (TypeError, DeviceInfoUnavailableException):
            # cloud-blocked gen1 vacuums will not return proper payloads
            dummy_v1 = DeviceInfo({
                "model": ROCKROBO_V1,
                "token": self.token,
                "netif": {
                    "localIp": self.ip
                },
                "fw_ver": "1.0_dummy",
            })

            self._info = dummy_v1
            _LOGGER.debug("Unable to query info, falling back to dummy %s",
                          dummy_v1.model)
            return self._info

    @command()
    def home(self):
        """Stop cleaning and return home."""

        PAUSE_BEFORE_HOME = [
            ROCKROBO_V1,
        ]

        if self.model in PAUSE_BEFORE_HOME:
            self.send("app_pause")

        return self.send("app_charge")

    @command(click.argument("x_coord", type=int),
             click.argument("y_coord", type=int))
    def goto(self, x_coord: int, y_coord: int):
        """Go to specific target.

        :param int x_coord: x coordinate
        :param int y_coord: y coordinate
        """
        return self.send("app_goto_target", [x_coord, y_coord])

    @command(click.argument("zones", type=LiteralParamType(), required=True))
    def zoned_clean(self, zones: List):
        """Clean zones.

        :param List zones: List of zones to clean: [[x1,y1,x2,y2, iterations],[x1,y1,x2,y2, iterations]]
        """
        return self.send("app_zoned_clean", zones)

    @command()
    def resume_zoned_clean(self):
        """Resume zone cleaning after being paused."""
        return self.send("resume_zoned_clean")

    @command()
    def manual_start(self):
        """Start manual control mode."""
        self.manual_seqnum = 0
        return self.send("app_rc_start")

    @command()
    def manual_stop(self):
        """Stop manual control mode."""
        self.manual_seqnum = 0
        return self.send("app_rc_end")

    MANUAL_ROTATION_MAX = 180
    MANUAL_ROTATION_MIN = -MANUAL_ROTATION_MAX
    MANUAL_VELOCITY_MAX = 0.3
    MANUAL_VELOCITY_MIN = -MANUAL_VELOCITY_MAX
    MANUAL_DURATION_DEFAULT = 1500

    @command(
        click.argument("rotation", type=int),
        click.argument("velocity", type=float),
        click.argument("duration",
                       type=int,
                       required=False,
                       default=MANUAL_DURATION_DEFAULT),
    )
    def manual_control_once(self,
                            rotation: int,
                            velocity: float,
                            duration: int = MANUAL_DURATION_DEFAULT):
        """Starts the remote control mode and executes the action once before
        deactivating the mode."""
        number_of_tries = 3
        self.manual_start()
        while number_of_tries > 0:
            if self.status().state_code == 7:
                time.sleep(5)
                self.manual_control(rotation, velocity, duration)
                time.sleep(5)
                return self.manual_stop()

            time.sleep(2)
            number_of_tries -= 1

    @command(
        click.argument("rotation", type=int),
        click.argument("velocity", type=float),
        click.argument("duration",
                       type=int,
                       required=False,
                       default=MANUAL_DURATION_DEFAULT),
    )
    def manual_control(self,
                       rotation: int,
                       velocity: float,
                       duration: int = MANUAL_DURATION_DEFAULT):
        """Give a command over manual control interface."""
        if rotation < self.MANUAL_ROTATION_MIN or rotation > self.MANUAL_ROTATION_MAX:
            raise DeviceException(
                "Given rotation is invalid, should be ]%s, %s[, was %s" %
                (self.MANUAL_ROTATION_MIN, self.MANUAL_ROTATION_MAX, rotation))
        if velocity < self.MANUAL_VELOCITY_MIN or velocity > self.MANUAL_VELOCITY_MAX:
            raise DeviceException(
                "Given velocity is invalid, should be ]%s, %s[, was: %s" %
                (self.MANUAL_VELOCITY_MIN, self.MANUAL_VELOCITY_MAX, velocity))

        self.manual_seqnum += 1
        params = {
            "omega": round(math.radians(rotation), 1),
            "velocity": velocity,
            "duration": duration,
            "seqnum": self.manual_seqnum,
        }

        self.send("app_rc_move", [params])

    @command()
    def status(self) -> VacuumStatus:
        """Return status of the vacuum."""
        return VacuumStatus(self.send("get_status")[0])

    def enable_log_upload(self):
        raise NotImplementedError("unknown parameters")
        # return self.send("enable_log_upload")

    @command()
    def log_upload_status(self):
        # {"result": [{"log_upload_status": 7}], "id": 1}
        return self.send("get_log_upload_status")

    @command()
    def consumable_status(self) -> ConsumableStatus:
        """Return information about consumables."""
        return ConsumableStatus(self.send("get_consumable")[0])

    @command(click.argument("consumable", type=Consumable))
    def consumable_reset(self, consumable: Consumable):
        """Reset consumable information."""
        return self.send("reset_consumable", [consumable.value])

    @command()
    def map(self):
        """Return map token."""
        # returns ['retry'] without internet
        return self.send("get_map_v1")

    @command(click.argument("start", type=bool))
    def edit_map(self, start):
        """Start map editing?"""
        if start:
            return self.send("start_edit_map")[0] == "ok"
        else:
            return self.send("end_edit_map")[0] == "ok"

    @command(click.option("--version", default=1))
    def fresh_map(self, version):
        """Return fresh map?"""
        if version not in [1, 2]:
            raise VacuumException("Unknown map version: %s" % version)

        if version == 1:
            return self.send("get_fresh_map")
        elif version == 2:
            return self.send("get_fresh_map_v2")

    @command(click.option("--version", default=1))
    def persist_map(self, version):
        """Return fresh map?"""
        if version not in [1, 2]:
            raise VacuumException("Unknown map version: %s" % version)

        if version == 1:
            return self.send("get_persist_map")
        elif version == 2:
            return self.send("get_persist_map_v2")

    @command(
        click.argument("x1", type=int),
        click.argument("y1", type=int),
        click.argument("x2", type=int),
        click.argument("y2", type=int),
    )
    def create_software_barrier(self, x1, y1, x2, y2):
        """Create software barrier (gen2 only?).

        NOTE: Multiple nogo zones and barriers could be added by passing
        a list of them to save_map.

        Requires new fw version.
        3.3.9_001633+?
        """
        # First parameter indicates the type, 1 = barrier
        payload = [1, x1, y1, x2, y2]
        return self.send("save_map", payload)[0] == "ok"

    @command(
        click.argument("x1", type=int),
        click.argument("y1", type=int),
        click.argument("x2", type=int),
        click.argument("y2", type=int),
        click.argument("x3", type=int),
        click.argument("y3", type=int),
        click.argument("x4", type=int),
        click.argument("y4", type=int),
    )
    def create_nogo_zone(self, x1, y1, x2, y2, x3, y3, x4, y4):
        """Create a rectangular no-go zone (gen2 only?).

        NOTE: Multiple nogo zones and barriers could be added by passing
        a list of them to save_map.

        Requires new fw version.
        3.3.9_001633+?
        """
        # First parameter indicates the type, 0 = zone
        payload = [0, x1, y1, x2, y2, x3, y3, x4, y4]
        return self.send("save_map", payload)[0] == "ok"

    @command(click.argument("enable", type=bool))
    def enable_lab_mode(self, enable):
        """Enable persistent maps and software barriers.

        This is required to use create_nogo_zone and create_software_barrier commands.
        """
        return self.send("set_lab_status", int(enable))["ok"]

    @command()
    def clean_history(self) -> CleaningSummary:
        """Return generic cleaning history."""
        return CleaningSummary(self.send("get_clean_summary"))

    @command()
    def last_clean_details(self) -> Optional[CleaningDetails]:
        """Return details from the last cleaning.

        Returns None if there has been no cleanups.
        """
        history = self.clean_history()
        if not history.ids:
            return None

        last_clean_id = history.ids.pop(0)
        return self.clean_details(last_clean_id)

    @command(
        click.argument("id_", type=int, metavar="ID"), )
    def clean_details(
            self, id_: int
    ) -> Union[List[CleaningDetails], Optional[CleaningDetails]]:
        """Return details about specific cleaning."""
        details = self.send("get_clean_record", [id_])

        if not details:
            _LOGGER.warning("No cleaning record found for id %s", id_)
            return None

        res = CleaningDetails(details.pop())
        return res

    @command()
    def find(self):
        """Find the robot."""
        return self.send("find_me", [""])

    @command()
    def timer(self) -> List[Timer]:
        """Return a list of timers."""
        timers: List[Timer] = list()
        res = self.send("get_timer", [""])
        if not res:
            return timers

        timezone = pytz.timezone(self.timezone())
        for rec in res:
            try:
                timers.append(Timer(rec, timezone=timezone))
            except Exception as ex:
                _LOGGER.warning("Unable to add timer for %s: %s", rec, ex)

        return timers

    @command(
        click.argument("cron"),
        click.argument("command", required=False, default=""),
        click.argument("parameters", required=False, default=""),
    )
    def add_timer(self, cron: str, command: str, parameters: str):
        """Add a timer.

        :param cron: schedule in cron format
        :param command: ignored by the vacuum.
        :param parameters: ignored by the vacuum.
        """
        import time

        ts = int(round(time.time() * 1000))
        return self.send("set_timer",
                         [[str(ts), [cron, [command, parameters]]]])

    @command(click.argument("timer_id", type=int))
    def delete_timer(self, timer_id: int):
        """Delete a timer with given ID.

        :param int timer_id: Timer ID
        """
        return self.send("del_timer", [str(timer_id)])

    @command(click.argument("timer_id", type=int),
             click.argument("mode", type=TimerState))
    def update_timer(self, timer_id: int, mode: TimerState):
        """Update a timer with given ID.

        :param int timer_id: Timer ID
        :param TimerStae mode: either On or Off
        """
        if mode != TimerState.On and mode != TimerState.Off:
            raise DeviceException("Only 'On' or 'Off' are  allowed")
        return self.send("upd_timer", [str(timer_id), mode.value])

    @command()
    def dnd_status(self):
        """Returns do-not-disturb status."""
        # {'result': [{'enabled': 1, 'start_minute': 0, 'end_minute': 0,
        #  'start_hour': 22, 'end_hour': 8}], 'id': 1}
        return DNDStatus(self.send("get_dnd_timer")[0])

    @command(
        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, 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_dnd_timer",
                         [start_hr, start_min, end_hr, end_min])

    @command()
    def disable_dnd(self):
        """Disable do-not-disturb."""
        return self.send("close_dnd_timer", [""])

    @command(click.argument("speed", type=int))
    def set_fan_speed(self, speed: int):
        """Set fan speed.

        :param int speed: Fan speed to set
        """
        # speed = [38, 60 or 77]
        return self.send("set_custom_mode", [speed])

    @command()
    def fan_speed(self):
        """Return fan speed."""
        return self.send("get_custom_mode")[0]

    @command()
    def fan_speed_presets(self) -> Dict[str, int]:
        """Return dictionary containing supported fan speeds."""
        def _enum_as_dict(cls):
            return {x.name: x.value for x in list(cls)}

        if self.model is None:
            return _enum_as_dict(FanspeedV1)

        fanspeeds: Type[FanspeedEnum] = FanspeedV1

        if self.model == ROCKROBO_V1:
            _LOGGER.debug("Got robov1, checking for firmware version")
            fw_version = self.info().firmware_version
            version, build = fw_version.split("_")
            version = tuple(map(int, version.split(".")))
            if version >= (3, 5, 8):
                fanspeeds = FanspeedV3
            elif version == (3, 5, 7):
                fanspeeds = FanspeedV2
            else:
                fanspeeds = FanspeedV1
        elif self.model == ROCKROBO_E2:
            fanspeeds = FanspeedE2
        elif self.model == ROCKROBO_S7:
            self._fanspeeds = FanspeedS7
        else:
            fanspeeds = FanspeedV2

        _LOGGER.debug("Using fanspeeds %s for %s", fanspeeds, self.model)

        return _enum_as_dict(fanspeeds)

    @command()
    def sound_info(self):
        """Get voice settings."""
        return SoundStatus(self.send("get_current_sound")[0])

    @command(
        click.argument("url"),
        click.argument("md5sum"),
        click.argument("sound_id", type=int),
    )
    def install_sound(self, url: str, md5sum: str, sound_id: int):
        """Install sound from the given url."""
        payload = {"url": url, "md5": md5sum, "sid": int(sound_id)}
        return SoundInstallStatus(self.send("dnld_install_sound", payload)[0])

    @command()
    def sound_install_progress(self):
        """Get sound installation progress."""
        return SoundInstallStatus(self.send("get_sound_progress")[0])

    @command()
    def sound_volume(self) -> int:
        """Get sound volume."""
        return self.send("get_sound_volume")[0]

    @command(click.argument("vol", type=int))
    def set_sound_volume(self, vol: int):
        """Set sound volume [0-100]."""
        return self.send("change_sound_volume", [vol])

    @command()
    def test_sound_volume(self):
        """Test current sound volume."""
        return self.send("test_sound_volume")

    @command()
    def serial_number(self):
        """Get serial number."""
        serial = self.send("get_serial_number")
        if isinstance(serial, list):
            return serial[0]["serial_number"]
        return serial

    @command()
    def locale(self):
        """Return locale information."""
        return self.send("app_get_locale")

    @command()
    def timezone(self):
        """Get the timezone."""
        res = self.send("get_timezone")

        def _fallback_timezone(data):
            fallback = "UTC"
            _LOGGER.error(
                "Unsupported timezone format (%s), falling back to %s", data,
                fallback)
            return fallback

        if isinstance(res, int):
            return _fallback_timezone(res)

        res = res[0]
        if isinstance(res, dict):
            # Xiaowa E25 example
            # {'olson': 'Europe/Berlin', 'posix': 'CET-1CEST,M3.5.0,M10.5.0/3'}
            if "olson" not in res:
                return _fallback_timezone(res)

            return res["olson"]

        return res

    def set_timezone(self, new_zone):
        """Set the timezone."""
        return self.send("set_timezone", [new_zone])[0] == "ok"

    def configure_wifi(self, ssid, password, uid=0, timezone=None):
        """Configure the wifi settings."""
        extra_params = {}
        if timezone is not None:
            now = datetime.datetime.now(pytz.timezone(timezone))
            offset_as_float = now.utcoffset().total_seconds() / 60 / 60
            extra_params["tz"] = timezone
            extra_params["gmt_offset"] = offset_as_float

        return super().configure_wifi(ssid, password, uid, extra_params)

    @command()
    def carpet_mode(self):
        """Get carpet mode settings."""
        return CarpetModeStatus(self.send("get_carpet_mode")[0])

    @command(
        click.argument("enabled", required=True, type=bool),
        click.argument("stall_time", required=False, default=10, type=int),
        click.argument("low", required=False, default=400, type=int),
        click.argument("high", required=False, default=500, type=int),
        click.argument("integral", required=False, default=450, type=int),
    )
    def set_carpet_mode(
        self,
        enabled: bool,
        stall_time: int = 10,
        low: int = 400,
        high: int = 500,
        integral: int = 450,
    ):
        """Set the carpet mode."""
        click.echo("Setting carpet mode: %s" % enabled)
        data = {
            "enable": int(enabled),
            "stall_time": stall_time,
            "current_low": low,
            "current_high": high,
            "current_integral": integral,
        }
        return self.send("set_carpet_mode", [data])[0] == "ok"

    @command()
    def carpet_cleaning_mode(self) -> Optional[CarpetCleaningMode]:
        """Get carpet cleaning mode/avoidance setting."""
        try:
            return CarpetCleaningMode(
                self.send("get_carpet_clean_mode")[0]["carpet_clean_mode"])
        except Exception as err:
            _LOGGER.warning("Error while requesting carpet clean mode: %s",
                            err)
            return None

    @command(click.argument("mode", type=EnumType(CarpetCleaningMode)))
    def set_carpet_cleaning_mode(self, mode: CarpetCleaningMode):
        """Set carpet cleaning mode/avoidance setting."""
        return (self.send("set_carpet_clean_mode",
                          {"carpet_clean_mode": mode.value})[0] == "ok")

    @command()
    def stop_zoned_clean(self):
        """Stop cleaning a zone."""
        return self.send("stop_zoned_clean")

    @command()
    def stop_segment_clean(self):
        """Stop cleaning a segment."""
        return self.send("stop_segment_clean")

    @command()
    def resume_segment_clean(self):
        """Resuming cleaning a segment."""
        return self.send("resume_segment_clean")

    @command(click.argument("segments", type=LiteralParamType(),
                            required=True))
    def segment_clean(self, segments: List):
        """Clean segments.

        :param List segments: List of segments to clean: [16,17,18]
        """
        return self.send("app_segment_clean", segments)

    @command()
    def get_room_mapping(self):
        """Retrieves a list of segments."""
        return self.send("get_room_mapping")

    @command()
    def get_backup_maps(self):
        """Get backup maps."""
        return self.send("get_recover_maps")

    @command(click.argument("id", type=int))
    def use_backup_map(self, id: int):
        """Set backup map."""
        click.echo("Setting the map %s as active" % id)
        return self.send("recover_map", [id])

    @command()
    def get_segment_status(self):
        """Get the status of a segment."""
        return self.send("get_segment_status")

    def name_segment(self):
        raise NotImplementedError("unknown parameters")
        # return self.send("name_segment")

    def merge_segment(self):
        raise NotImplementedError("unknown parameters")
        # return self.send("merge_segment")

    def split_segment(self):
        raise NotImplementedError("unknown parameters")
        # return self.send("split_segment")

    @command()
    def waterflow(self) -> WaterFlow:
        """Get water flow setting."""
        return WaterFlow(self.send("get_water_box_custom_mode")[0])

    @command(click.argument("waterflow", type=EnumType(WaterFlow)))
    def set_waterflow(self, waterflow: WaterFlow):
        """Set water flow setting."""
        return self.send("set_water_box_custom_mode", [waterflow.value])

    @command()
    def mop_mode(self) -> Optional[MopMode]:
        """Get mop mode setting."""
        try:
            return MopMode(self.send("get_mop_mode")[0])
        except ValueError as err:
            _LOGGER.warning("Device returned unknown MopMode: %s", err)
            return None

    @command(click.argument("mop_mode", type=EnumType(MopMode)))
    def set_mop_mode(self, mop_mode: MopMode):
        """Set mop mode setting."""
        return self.send("set_mop_mode", [mop_mode.value])[0] == "ok"

    @command()
    def child_lock(self) -> bool:
        """Get child lock setting."""
        return self.send("get_child_lock_status")["lock_status"] == 1

    @command(click.argument("lock", type=bool))
    def set_child_lock(self, lock: bool) -> bool:
        """Set child lock setting."""
        return self.send("set_child_lock_status",
                         {"lock_status": int(lock)})[0] == "ok"

    @classmethod
    def get_device_group(cls):
        @click.pass_context
        def callback(ctx, *args, id_file, **kwargs):
            gco = ctx.find_object(GlobalContextObject)
            if gco:
                kwargs["debug"] = gco.debug

            start_id = manual_seq = 0
            with contextlib.suppress(FileNotFoundError, TypeError,
                                     ValueError), open(id_file, "r") as f:
                x = json.load(f)
                start_id = x.get("seq", 0)
                manual_seq = x.get("manual_seq", 0)
                _LOGGER.debug("Read stored sequence ids: %s", x)

            ctx.obj = cls(*args, start_id=start_id, **kwargs)
            ctx.obj.manual_seqnum = manual_seq

        dg = DeviceGroup(
            cls,
            params=DeviceGroup.DEFAULT_PARAMS + [
                click.Option(
                    ["--id-file"],
                    type=click.Path(dir_okay=False, writable=True),
                    default=os.path.join(user_cache_dir("python-miio"),
                                         "python-mirobo.seq"),
                )
            ],
            callback=callback,
        )

        @dg.resultcallback()
        @dg.device_pass
        def cleanup(vac: Vacuum, *args, **kwargs):
            if vac.ip is None:  # dummy Device for discovery, skip teardown
                return
            id_file = kwargs["id_file"]
            seqs = {
                "seq": vac._protocol.raw_id,
                "manual_seq": vac.manual_seqnum
            }
            _LOGGER.debug("Writing %s to %s", seqs, id_file)
            path_obj = pathlib.Path(id_file)
            cache_dir = path_obj.parents[0]
            cache_dir.mkdir(parents=True, exist_ok=True)
            with open(id_file, "w") as f:
                json.dump(seqs, f)

        return dg
Ejemplo n.º 7
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)
Ejemplo n.º 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"])
Ejemplo n.º 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)
Ejemplo n.º 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)])
Ejemplo n.º 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))])
Ejemplo n.º 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())
Ejemplo n.º 13
0
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])
Ejemplo n.º 14
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")
Ejemplo n.º 15
0
class RoidmiVacuumMiot(MiotDevice, VacuumInterface):
    """Interface for Vacuum Eve Plus (roidmi.vacuum.v60)"""

    _mappings = _MAPPINGS

    @command()
    def status(self) -> RoidmiVacuumStatus:
        """State of the vacuum."""
        return RoidmiVacuumStatus({
            prop["did"]: prop["value"] if prop["code"] == 0 else None
            # max_properties limmit to 10 to avoid "Checksum error" messages from the device.
            for prop in self.get_properties_for_mapping()
        })

    @command()
    def consumable_status(self) -> RoidmiConsumableStatus:
        """Return information about consumables."""
        return RoidmiConsumableStatus({
            prop["did"]: prop["value"] if prop["code"] == 0 else None
            # max_properties limmit to 10 to avoid "Checksum error" messages from the device.
            for prop in self.get_properties_for_mapping()
        })

    @command()
    def cleaning_summary(self) -> RoidmiCleaningSummary:
        """Return information about cleaning runs."""
        return RoidmiCleaningSummary({
            prop["did"]: prop["value"] if prop["code"] == 0 else None
            # max_properties limmit to 10 to avoid "Checksum error" messages from the device.
            for prop in self.get_properties_for_mapping()
        })

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

    # @command(click.argument("roomstr", type=str, required=False))
    # def start_room_sweep_unknown(self, roomstr: str=None) -> None:
    #     """Start room cleaning.

    #     roomstr: empty means start room clean of all rooms. FIXME: the syntax of an non-empty roomstr is still unknown
    #     """
    #     return self.call_action("start_room_sweep", roomstr)

    # @command(
    # click.argument("sweep_mode", type=EnumType(SweepMode)),
    # click.argument("clean_info", type=str),
    # )
    # def start_sweep_unknown(self, sweep_mode: SweepMode, clean_info: str=None) -> None:
    #     """Start sweep with mode.

    #     FIXME: the syntax of start_sweep is unknown
    #     """
    #     return self.call_action("start_sweep", [sweep_mode.value, clean_info])

    @command()
    def stop(self) -> None:
        """Stop cleaning."""
        return self.call_action("stop")

    @command()
    def home(self) -> None:
        """Return to home."""
        return self.call_action("home")

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

    @command(click.argument("on", type=bool))
    def set_station_led(self, on: bool):
        """Enable station led display."""
        return self.set_property("station_led", on)

    @command(click.argument("on", type=bool))
    def set_led(self, on: bool):
        """Enable vacuum led."""
        return self.set_property("led_switch", on)

    @command(click.argument("vol", type=int))
    def set_sound_volume(self, vol: int):
        """Set sound volume [0-100]."""
        return self.set_property("volume", vol)

    @command(click.argument("value", type=bool))
    def set_sound_muted(self, value: bool):
        """Set sound volume muted."""
        return self.set_property("mute", value)

    @command(click.argument("fanspeed_mode", type=EnumType(FanSpeed)))
    def set_fanspeed(self, fanspeed_mode: FanSpeed):
        """Set fan speed."""
        return self.set_property("fanspeed_mode", fanspeed_mode.value)

    @command()
    def fan_speed_presets(self) -> FanspeedPresets:
        """Return available fan speed presets."""
        return {
            "Sweep": 0,
            "Silent": 1,
            "Basic": 2,
            "Strong": 3,
            "FullSpeed": 4
        }

    @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("fanspeed_mode", speed_preset)

    @command(click.argument("sweep_type", type=EnumType(SweepType)))
    def set_sweep_type(self, sweep_type: SweepType):
        """Set sweep_type."""
        return self.set_property("sweep_type", sweep_type.value)

    @command(click.argument("path_mode", type=EnumType(PathMode)))
    def set_path_mode(self, path_mode: PathMode):
        """Set path_mode."""
        return self.set_property("path_mode", path_mode.value)

    @command(click.argument("dust_collection_frequency", type=int))
    def set_dust_collection_frequency(self, dust_collection_frequency: int):
        """Set frequency for emptying the dust bin.

        Example: 2 means the dust bin is emptied every second cleaning.
        """
        return self.set_property("work_station_freq",
                                 dust_collection_frequency)

    @command(click.argument("timing", type=str))
    def set_timing(self, timing: str):
        """Set repeated clean timing.

        Set timing to 9:00 Monday-Friday, rooms:[12,10]
        timing = '{"time":[[32400,1,3,0,[1,2,3,4,5],0,[12,10],null]],"tz":2,"tzs":7200}'
        See also :func:`RoidmiVacuumStatus.timing`

        NOTE: setting timing will override existing settings
        """
        return self.set_property("timing", timing)

    @command(click.argument("auto_boost", type=bool))
    def set_carpet_mode(self, auto_boost: bool):
        """Set auto boost on carpet."""
        return self.set_property("auto_boost", auto_boost)

    def _set_dnd(self, start_int: int, end_int: int, active: bool):
        value_str = json.dumps({"time": [start_int, end_int, int(active)]})
        return self.set_property("forbid_mode", value_str)

    @command(
        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, 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
        """
        start_int = int(
            timedelta(hours=start_hr, minutes=start_min).total_seconds())
        end_int = int(timedelta(hours=end_hr, minutes=end_min).total_seconds())
        return self._set_dnd(start_int, end_int, active=True)

    @command()
    def disable_dnd(self):
        """Disable do-not-disturb."""
        # The current do not disturb is read back for a better user expierence,
        # as start/end time must be set together with enabled=False
        try:
            current_dnd_str = self.get_property_by(
                **self._get_mapping()["forbid_mode"])[0]["value"]
            current_dnd_dict = json.loads(current_dnd_str)
        except Exception:
            # In case reading current DND back fails, DND is disabled anyway
            return self._set_dnd(0, 0, active=False)
        return self._set_dnd(current_dnd_dict["time"][0],
                             current_dnd_dict["time"][1],
                             active=False)

    @command(click.argument("water_level", type=EnumType(WaterLevel)))
    def set_water_level(self, water_level: WaterLevel):
        """Set water_level."""
        return self.set_property("water_level", water_level.value)

    @command(click.argument("double_clean", type=bool))
    def set_double_clean(self, double_clean: bool):
        """Set double clean (True/False)."""
        return self.set_property("double_clean", double_clean)

    @command(click.argument("lidar_collision", type=bool))
    def set_lidar_collision_sensor(self, lidar_collision: bool):
        """When ON, the robot will use lidar as the main detection sensor to help reduce
        collisions."""
        return self.set_property("lidar_collision", lidar_collision)

    @command()
    def start_dust(self) -> None:
        """Start base dust collection."""
        return self.call_action("start_station_dust_collection")

    # @command(click.argument("voice", type=str))
    #     def set_voice_unknown(self, voice: str) -> None:
    #     """Set voice.

    #     FIXME: the syntax of voice is unknown (assumed to be json format)
    #     """
    #     return self.call_action("set_voice", voice)

    @command()
    def reset_filter_life(self) -> None:
        """Reset filter life."""
        return self.call_action("reset_filter_life")

    @command()
    def reset_mainbrush_life(self) -> None:
        """Reset main brush life."""
        return self.call_action("reset_main_brush_life")

    @command()
    def reset_sidebrush_life(self) -> None:
        """Reset side brushes life."""
        return self.call_action("reset_side_brushes_life")

    @command()
    def reset_sensor_dirty_life(self) -> None:
        """Reset sensor dirty life."""
        return self.call_action("reset_sensor_dirty_life")
Ejemplo n.º 16
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)
Ejemplo n.º 17
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)
Ejemplo n.º 18
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)])
Ejemplo n.º 19
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)
Ejemplo n.º 20
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
Ejemplo n.º 21
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)
Ejemplo n.º 22
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)
Ejemplo n.º 23
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])
Ejemplo n.º 24
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])
Ejemplo n.º 25
0
class AirDogX3(Device):
    """Support for Airdog air purifiers (airdog.airpurifier.x*)."""

    _supported_models = list(AVAILABLE_PROPERTIES.keys())

    @command(
        default_output=format_output(
            "",
            "Power: {result.power}\n"
            "Mode: {result.mode}\n"
            "Speed: {result.speed}\n"
            "Child lock: {result.child_lock}\n"
            "Clean filters: {result.clean_filters}\n"
            "PM2.5: {result.pm25}\n"
            "Formaldehyde: {result.hcho}\n",
        )
    )
    def status(self) -> AirDogStatus:
        """Retrieve properties."""

        properties = AVAILABLE_PROPERTIES.get(
            self.model, AVAILABLE_PROPERTIES[MODEL_AIRDOG_X3]
        )
        values = self.get_properties(properties, max_properties=10)

        return AirDogStatus(defaultdict(lambda: None, 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)),
        click.argument("speed", type=int, required=False, default=1),
        default_output=format_output(
            "Setting mode to '{mode.value}' and speed to {speed}"
        ),
    )
    def set_mode_and_speed(self, mode: OperationMode, speed: int = 1):
        """Set mode and speed."""
        if mode.value not in (om.value for om in OperationMode):
            raise AirDogException(f"{mode.value} is not a valid OperationMode value")

        if mode in [OperationMode.Auto, OperationMode.Idle]:
            speed = 1

        if self.model == MODEL_AIRDOG_X3:
            max_speed = 4
        else:
            # airdog.airpurifier.x7, airdog.airpurifier.x7sm
            max_speed = 5

        if speed < 1 or speed > max_speed:
            raise AirDogException("Invalid speed: %s" % speed)

        return self.send("set_wind", [OperationModeMapping[mode.name].value, speed])

    @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(lock)])

    @command(default_output=format_output("Setting filters cleaned"))
    def set_filters_cleaned(self):
        """Set filters cleaned."""
        return self.send("set_clean")