Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unified and scalable command line interface #191

Merged
merged 16 commits into from
Mar 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions miio/airpurifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import re
from typing import Any, Dict, Optional
from collections import defaultdict
import click
from .device import Device, DeviceException
from .click_common import command, format_output, EnumType

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -352,10 +354,45 @@ def __repr__(self) -> str:
self.button_pressed)
return s

def __json__(self):
return self.data


class AirPurifier(Device):
"""Main class representing the air purifier."""

@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.value}\n"
"Learn mode: {result.learn_mode}\n"
"Sleep mode: {result.sleep_mode.value}\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."""

Expand Down Expand Up @@ -388,18 +425,32 @@ def status(self) -> AirPurifierStatus:
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, 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("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 > 16:
Expand All @@ -411,52 +462,100 @@ def set_favorite_level(self, level: int):
# should be between 0 and 16.
return self.send("set_level_favorite", [level]) # 0 ... 16

@command(
click.argument("brightness", type=EnumType(LedBrightness, False)),
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 favorite level 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.

Expand All @@ -467,6 +566,9 @@ def set_extra_features(self, value: int):

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')
33 changes: 33 additions & 0 deletions miio/chuangmi_plug.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import logging
import click
from typing import Dict, Any, Optional
from collections import defaultdict
from .device import Device
from .utils import deprecated
from .click_common import command, format_output

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -84,6 +86,9 @@ def __repr__(self) -> str:
self.wifi_led)
return s

def __json__(self):
return self.data


class ChuangmiPlug(Device):
"""Main class representing the Chuangmi Plug V1 and V3."""
Expand All @@ -98,6 +103,15 @@ def __init__(self, ip: str = None, token: str = None, start_id: int = 0,
else:
self.model = MODEL_CHUANGMI_PLUG_M1

@command(
default_output=format_output(
"",
"Power: {result.power}\n"
"USB Power: {result.usb_power}\n"
"Temperature: {result.temperature} °C\n"
"Load power: {result.load_power}\n"
"WiFi LED: {result.wifi_led}\n")
)
def status(self) -> ChuangmiPlugStatus:
"""Retrieve properties."""
properties = AVAILABLE_PROPERTIES[self.model]
Expand All @@ -123,28 +137,47 @@ def status(self) -> ChuangmiPlugStatus:
return ChuangmiPlugStatus(
defaultdict(lambda: None, zip(properties, values)))

@command(
default_output = format_output("Powering on"),

Choose a reason for hiding this comment

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

unexpected spaces around keyword / parameter equals

)
def on(self):
"""Power on."""
if self.model == MODEL_CHUANGMI_PLUG_V1:
return self.send("set_on", [])

return self.send("set_power", ["on"])

@command(
default_output = format_output("Powering off"),

Choose a reason for hiding this comment

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

unexpected spaces around keyword / parameter equals

)
def off(self):
"""Power off."""
if self.model == MODEL_CHUANGMI_PLUG_V1:
return self.send("set_off", [])

return self.send("set_power", ["off"])

@command(
default_output = format_output("Powering USB on"),

Choose a reason for hiding this comment

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

unexpected spaces around keyword / parameter equals

)
def usb_on(self):
"""Power on."""
return self.send("set_usb_on", [])

@command(
default_output = format_output("Powering USB off"),

Choose a reason for hiding this comment

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

unexpected spaces around keyword / parameter equals

)
def usb_off(self):
"""Power off."""
return self.send("set_usb_off", [])

@command(
click.argument("wifi_led", type=bool),
default_output=format_output(
lambda wifi_led: "Turning on WiFi LED"
if wifi_led else "Turning off WiFi LED"
)
)
def set_wifi_led(self, led: bool):
"""Set the wifi led on/off."""
if led:
Expand Down
45 changes: 45 additions & 0 deletions miio/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# -*- coding: UTF-8 -*-
import logging
import click
from miio.click_common import (
ExceptionHandlerGroup, DeviceGroupMeta, GlobalContextObject,
json_output,
)

_LOGGER = logging.getLogger(__name__)


@click.group(cls=ExceptionHandlerGroup)
@click.option('-d', '--debug', default=False, count=True)
@click.option('-o', '--output', type=click.Choice([
'default', 'json', 'json_pretty',
]), default='default')
@click.pass_context
def cli(ctx, debug: int, output: str):
if debug:
logging.basicConfig(level=logging.DEBUG)
_LOGGER.info("Debug mode active")
else:
logging.basicConfig(level=logging.INFO)

if output in ('json', 'json_pretty'):
output_func = json_output(pretty=output == 'json_pretty')
else:
output_func = None

ctx.obj = GlobalContextObject(
debug=debug,
output=output_func,
)


for device_class in DeviceGroupMeta.device_classes:
cli.add_command(device_class.get_device_group())


def create_cli():
return cli(auto_envvar_prefix="MIIO")


if __name__ == '__main__':
create_cli()
Loading