From 023bce7d0147ffd8d38f6a26b5df3765a9f022c5 Mon Sep 17 00:00:00 2001 From: Roman Novatorov Date: Wed, 28 Oct 2020 16:44:57 +0300 Subject: [PATCH] Vacuum: Implement TUI for the manual mode --- miio/__init__.py | 1 + miio/vacuum_cli.py | 7 ++++ miio/vacuum_tui.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 miio/vacuum_tui.py diff --git a/miio/__init__.py b/miio/__init__.py index d304229d5..f108e507c 100644 --- a/miio/__init__.py +++ b/miio/__init__.py @@ -42,6 +42,7 @@ from miio.pwzn_relay import PwznRelay from miio.toiletlid import Toiletlid from miio.vacuum import Vacuum, VacuumException +from miio.vacuum_tui import VacuumTUI from miio.vacuumcontainers import ( CleaningDetails, CleaningSummary, diff --git a/miio/vacuum_cli.py b/miio/vacuum_cli.py index 73c3b1943..916e8ab7a 100644 --- a/miio/vacuum_cli.py +++ b/miio/vacuum_cli.py @@ -231,6 +231,13 @@ def manual(vac: miio.Vacuum): # if not vac.manual_mode and command : +@manual.command() +@pass_dev +def tui(vac: miio.Vacuum): + """TUI for the manual mode.""" + miio.VacuumTUI.run(vac) + + @manual.command() @pass_dev def start(vac: miio.Vacuum): # noqa: F811 # redef of start diff --git a/miio/vacuum_tui.py b/miio/vacuum_tui.py new file mode 100644 index 000000000..5b1222062 --- /dev/null +++ b/miio/vacuum_tui.py @@ -0,0 +1,91 @@ +import curses +import enum +from typing import Tuple, Type + +from .vacuum import Vacuum + + +class Control(enum.Enum): + + Quit = "q" + Forward = "l" + ForwardFast = "L" + Backward = "k" + BackwardFast = "K" + Left = "j" + LeftFast = "J" + Right = ";" + RightFast = ":" + + +class VacuumTUI: + def __init__(self, vac: Vacuum): + self.vac = vac + self.rot = 0 + self.rot_delta = 30 + self.rot_min = Vacuum.MANUAL_ROTATION_MIN + self.rot_max = Vacuum.MANUAL_ROTATION_MAX + self.vel = 0.0 + self.vel_delta = 0.1 + self.vel_min = Vacuum.MANUAL_VELOCITY_MIN + self.vel_max = Vacuum.MANUAL_VELOCITY_MAX + self.dur = 10 * 1000 + + @classmethod + def run(cls: Type["VacuumTUI"], vac: Vacuum) -> None: + vac.manual_start() + try: + tui = cls(vac=vac) + curses.wrapper(tui.main) + finally: + vac.manual_stop() + + def main(self, screen: curses.window) -> None: + done = False + while not done: + key = screen.getkey() + text, done = self.handle(key) + screen.clear() + screen.addstr(text) + screen.refresh() + + def handle(self, key: str) -> Tuple[str, bool]: + try: + ctl = Control(key) + except ValueError as e: + return "ignoring %s: %s\n" % (key, e), False + + if ctl == Control.Quit: + return "exiting normally\n", True + + self.control(ctl) + return self.info(), False + + def control(self, ctl: Control) -> None: + if ctl == Control.Forward: + self.vel = min(self.vel + self.vel_delta, self.vel_max) + elif ctl == Control.ForwardFast: + self.vel = 0 if self.vel < 0 else self.vel_max + + elif ctl == Control.Backward: + self.vel = max(self.vel - self.vel_delta, self.vel_min) + elif ctl == Control.BackwardFast: + self.vel = 0 if self.vel > 0 else self.vel_min + + elif ctl == Control.Left: + self.rot = min(self.rot + self.rot_delta, self.rot_max) + elif ctl == Control.LeftFast: + self.rot = 0 if self.rot < 0 else self.rot_max + + elif ctl == Control.Right: + self.rot = max(self.rot - self.rot_delta, self.rot_min) + elif ctl == Control.RightFast: + self.rot = 0 if self.rot > 0 else self.rot_min + + else: + raise RuntimeError("unreachable") + + self.vac.manual_control(rotation=self.rot, velocity=self.vel, duration=self.dur) + + def info(self) -> str: + return f"rotation={self.rot}\nvelocity={self.vel}\n"