From 8032eecd36b1b3285f74978b94b6a589562db63f Mon Sep 17 00:00:00 2001 From: Teemu R Date: Thu, 18 Jan 2018 23:18:30 +0100 Subject: [PATCH] Add preliminary support for managing sound files (#154) * Add preliminary support for managing sound files Adds install_sound which expects url, md5sum and id * Finalize sound installation * sound_install works now, displays installation state during installation * SoundInstallStatus and SoundStatus are new sound-related containers * get_sound_volume returns now an integer, requires a recent FW * add documentation for installing sound packs * add a link to dustcloud's audio generator, revise a bit --- docs/vacuum.rst | 26 ++++++++++++++++ miio/vacuum.py | 26 ++++++++++++++-- miio/vacuum_cli.py | 26 +++++++++++++++- miio/vacuumcontainers.py | 67 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 3 deletions(-) diff --git a/docs/vacuum.rst b/docs/vacuum.rst index d4479e857..2f97788f1 100644 --- a/docs/vacuum.rst +++ b/docs/vacuum.rst @@ -9,6 +9,7 @@ Following features of the vacuum cleaner are currently supported: is not currently implemented, patches welcome!** - Fetching and setting the schedules. - Setting and querying the timezone. +- Installing sound packs. - Manual control of the robot. **Patches for a nicer API are very welcome.** Use :ref:`mirobo --help ` @@ -126,6 +127,31 @@ Cleaning history Duration: (0:23:54) +Sounds +~~~~~~ + +To get information about current sound settings: + +:: + + mirobo sound + + +You can use dustcloud's `audio generator`_ to create your own language packs, +which will handle both generation and encrypting the package for you. + +To install your newly generated sound pack, you have to host it somewhere accessible to the vacuum, +and you have to know its md5sum. +The last parameter to give to the command is sound id (or `sid`), +which you can choose by yourself. + +:: + + mirobo install_sound http://10.10.20.1:8000/my_sounds.pkg b50cfea27e52ebd5f46038ac7b9330c8 1005 + + +.. _audio generator: https://github.com/dgiese/dustcloud/tree/master/xiaomi.vacuum.gen1/audio_generator + DND functionality ~~~~~~~~~~~~~~~~~ diff --git a/miio/vacuum.py b/miio/vacuum.py index 584c80384..54eb4445d 100644 --- a/miio/vacuum.py +++ b/miio/vacuum.py @@ -7,7 +7,8 @@ import pytz from .vacuumcontainers import (VacuumStatus, ConsumableStatus, DNDStatus, - CleaningSummary, CleaningDetails, Timer) + CleaningSummary, CleaningDetails, Timer, + SoundStatus, SoundInstallStatus) from .device import Device, DeviceException _LOGGER = logging.getLogger(__name__) @@ -214,7 +215,28 @@ def fan_speed(self): def sound_info(self): """Get voice settings.""" - return self.send("get_current_sound") + return SoundStatus(self.send("get_current_sound")[0]) + + 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]) + + def sound_install_progress(self): + """Get sound installation progress.""" + return SoundInstallStatus(self.send("get_sound_progress")[0]) + + def sound_volume(self) -> int: + """Get sound volume.""" + return self.send("get_sound_volume")[0] + + def set_sound_volume(self, vol: int): + """Set sound volume.""" + raise NotImplementedError("unknown command¶meters") def serial_number(self): """Get serial number.""" diff --git a/miio/vacuum_cli.py b/miio/vacuum_cli.py index 5303b9840..c1acfcb49 100644 --- a/miio/vacuum_cli.py +++ b/miio/vacuum_cli.py @@ -6,6 +6,7 @@ import sys import json import ipaddress +import time from pprint import pformat as pf from typing import Any # noqa: F401 @@ -423,9 +424,32 @@ def cleaning_history(vac: miio.Vacuum): @pass_dev def sound(vac: miio.Vacuum): """Query sound settings.""" - click.echo(vac.sound_info()) + click.echo("Current sound: %s" % vac.sound_info()) + click.echo("Current volume: %s" % vac.sound_volume()) + click.echo("Install progress: %s" % vac.sound_install_progress()) +@cli.command() +@click.argument('url') +@click.argument('md5sum') +@click.argument('sid', type=int) +@pass_dev +def install_sound(vac: miio.Vacuum, url: str, md5sum: str, sid: int): + """Install a sound.""" + click.echo("Installing from %s (md5: %s) for id %s" % (url, md5sum, sid)) + click.echo(vac.install_sound(url, md5sum, sid)) + + progress = vac.sound_install_progress() + while progress.is_installing: + print(progress) + progress = vac.sound_install_progress() + time.sleep(0.1) + + if progress.progress == 100 and progress.error == 0: + click.echo("Installation of sid '%s' complete!" % progress.sid) + else: + click.echo("Error during installation: %s" % progress.error) + @cli.command() @pass_dev def serial_number(vac: miio.Vacuum): diff --git a/miio/vacuumcontainers.py b/miio/vacuumcontainers.py index 92fce3554..d11aee07a 100644 --- a/miio/vacuumcontainers.py +++ b/miio/vacuumcontainers.py @@ -1,6 +1,7 @@ # -*- coding: UTF-8 -*# from datetime import datetime, timedelta, time from typing import Any, Dict, List +from enum import IntEnum import warnings import functools import inspect @@ -443,3 +444,69 @@ def action(self) -> str: def __repr__(self) -> str: return "" % (self.id, self.ts, self.enabled, self.cron) + + +class SoundStatus: + """Container for sound status.""" + def __init__(self, data): + # {'sid_in_progress': 0, 'sid_in_use': 1004} + self.data = data + + @property + def current(self): + return self.data['sid_in_use'] + + @property + def being_installed(self): + return self.data['sid_in_progress'] + + def __repr__(self): + return "" % ( + self.current, + self.being_installed) + + +class SoundInstallState(IntEnum): + Unknown = 0 + Downloading = 1 + Installing = 2 + Installed = 3 + Error = 4 + + +class SoundInstallStatus: + """Container for sound installation status.""" + def __init__(self, data): + # {'progress': 0, 'sid_in_progress': 0, 'state': 0, 'error': 0} + self.data = data + + @property + def state(self) -> SoundInstallState: + """Installation state.""" + return SoundInstallState(self.data['state']) + + @property + def progress(self) -> int: + """Progress in percentages.""" + return self.data['progress'] + + @property + def sid(self) -> int: + """Sound ID for the sound being installed.""" + # this is missing on install confirmation, so let's use get + return self.data.get('sid_in_progress', None) + + @property + def error(self) -> int: + """Error code, 0 is no error, other values unknown.""" + return self.data['error'] + + @property + def is_installing(self) -> bool: + """True if install is in progress.""" + return self.sid != 0 and self.progress < 100 and self.error == 0 + + def __repr__(self) -> str: + return "" % (self.sid, self.state, + self.error, self.progress)