Skip to content

Commit

Permalink
Device support for the xiaomi smart wifi socket added (#29)
Browse files Browse the repository at this point in the history
* Device support for the xiaomi smart wifi socket added.

* Code reformatted.

* Some refactoring.

* Bugfix.

* Command line interface called "miplug" added.

* Unused imports removed.
  • Loading branch information
syssi authored and rytilahti committed Jul 21, 2017
1 parent c31647b commit e1ab84c
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 2 deletions.
1 change: 1 addition & 0 deletions mirobo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
from mirobo.protocol import Message, Utils
from mirobo.containers import VacuumStatus, ConsumableStatus, CleaningDetails, CleaningSummary, Timer
from mirobo.vacuum import Vacuum, VacuumException
from mirobo.plug import Plug
from mirobo.device import Device
29 changes: 29 additions & 0 deletions mirobo/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,35 @@ def pretty_time(x: int) -> datetime:
}


class PlugStatus:
"""Container for status reports from the plug."""
def __init__(self, data: Dict[str, Any]) -> None:
self.data = data

@property
def power(self) -> str:
return self.data["power"]

@property
def is_on(self) -> bool:
return self.power == "on"

@property
def temperature(self) -> float:
return self.data["temperature"]

@property
def current(self) -> float:
return self.data["current"]

def __str__(self) -> str:
s = "<PlugStatus power=%s, temperature=%s, current=%s>" % \
(self.power,
self.temperature,
self.current)
return s


class VacuumStatus:
"""Container for status reports from the vacuum."""
def __init__(self, data: Dict[str, Any]) -> None:
Expand Down
23 changes: 23 additions & 0 deletions mirobo/plug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from .device import Device
from .containers import PlugStatus


class Plug(Device):
"""Main class representing the smart wifi socket / plug."""

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

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

def status(self):
"""Retrieve properties."""
properties = ['power', 'temperature', 'current']
values = self.send(
"get_prop",
properties
)
return PlugStatus(dict(zip(properties, values)))
111 changes: 111 additions & 0 deletions mirobo/plug_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# -*- coding: UTF-8 -*-
import logging
import click
import ast
import sys
import ipaddress

if sys.version_info < (3, 4):
print("To use this script you need python 3.4 or newer, got %s" %
sys.version_info)
sys.exit(1)

import mirobo # noqa: E402

_LOGGER = logging.getLogger(__name__)
pass_dev = click.make_pass_decorator(mirobo.Plug)


def validate_ip(ctx, param, value):
try:
ipaddress.ip_address(value)
return value
except ValueError as ex:
raise click.BadParameter("Invalid IP: %s" % ex)


def validate_token(ctx, param, value):
token_len = len(value)
if token_len != 32:
raise click.BadParameter("Token length != 32 chars: %s" % token_len)
return value


@click.group(invoke_without_command=True)
@click.option('--ip', envvar="DEVICE_IP", callback=validate_ip)
@click.option('--token', envvar="DEVICE_TOKEN", callback=validate_token)
@click.option('-d', '--debug', default=False, count=True)
@click.pass_context
def cli(ctx, ip: str, token: str, debug: int):
"""A tool to command Xiaomi Smart Plug."""
if debug:
logging.basicConfig(level=logging.DEBUG)
_LOGGER.info("Debug mode active")
else:
logging.basicConfig(level=logging.INFO)

# if we are scanning, we do not try to connect.
if ctx.invoked_subcommand == "discover":
return

if ip is None or token is None:
click.echo("You have to give ip and token!")
sys.exit(-1)

dev = mirobo.Plug(ip, token, debug)
_LOGGER.debug("Connecting to %s with token %s", ip, token)

ctx.obj = dev

if ctx.invoked_subcommand is None:
ctx.invoke(status)


@cli.command()
def discover():
"""Search for plugs in the network."""
mirobo.Plug.discover()


@cli.command()
@pass_dev
def status(dev: mirobo.Plug):
"""Returns the state information."""
res = dev.status()
if not res:
return # bail out

click.echo(click.style("Power: %s" % res.power, bold=True))
click.echo("Temperature: %s %%" % res.temperature)
click.echo("Current: %s %%" % res.current)


@cli.command()
@pass_dev
def on(dev: mirobo.Plug):
"""Power on."""
click.echo("Power on: %s" % dev.on())


@cli.command()
@pass_dev
def off(dev: mirobo.Plug):
"""Power off."""
click.echo("Power off: %s" % dev.off())


@cli.command()
@click.argument('cmd', required=True)
@click.argument('parameters', required=False)
@pass_dev
def raw_command(dev: mirobo.Plug, cmd, parameters):
"""Run a raw command."""
params = [] # type: Any
if parameters:
params = ast.literal_eval(parameters)
click.echo("Sending cmd %s with params %s" % (cmd, params))
click.echo(dev.raw_command(cmd, params))


if __name__ == "__main__":
cli()
3 changes: 2 additions & 1 deletion mirobo/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
_LOGGER = logging.getLogger(__name__)

# Map of device ids
xiaomi_devices_reverse = {0x02f2: "Xiaomi Mi Robot Vacuum",
xiaomi_devices_reverse = {0x02c1: "Xiaomi Mi Smart WiFi Socket",
0x02f2: "Xiaomi Mi Robot Vacuum",
0x00c4: "Xiaomi Smart Mi Air Purifier",
0x031a: "Xiaomi Smart home gateway",
0x0330: "Yeelight color bulb"
Expand Down
File renamed without changes.
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
install_requires=['construct', 'click', 'cryptography', 'pretty_cron', 'typing'],
entry_points={
'console_scripts': [
'mirobo=mirobo.cli:cli',
'mirobo=mirobo.vacuum_cli:cli',
'miplug=mirobo.plug_cli:cli',
],
},
)

0 comments on commit e1ab84c

Please sign in to comment.