Skip to content

Commit

Permalink
Add basic remote control functionality
Browse files Browse the repository at this point in the history
* the amounts are unclear, 179 gives the maximum rotation which is not exactly that many degrees..
* new commands subcommands for manual:
  - manual start
  - manual stop
  - manual forward [amount] (up to 0.29)
  - manual backward [amount] (same)
  - manual left [degrees] (up to 179)
  - manual right [degrees] (up to 179)

* respective methods for above commands in Vacuum class
  - manual_control_once() to allow automatic execution of one command (sleep()s in-between)
  • Loading branch information
rytilahti committed Jul 8, 2017
1 parent f05fff0 commit 57fb79f
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 9 deletions.
97 changes: 90 additions & 7 deletions mirobo/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pretty_cron
import ast
import sys
import json
from typing import Any

if sys.version_info < (3, 4):
Expand Down Expand Up @@ -40,16 +41,19 @@ def cli(ctx, ip: str, token: str, debug: int, id_file: str):
click.echo("You have to give ip and token!")
sys.exit(-1)

start_id = 0
start_id = manual_seq = 0
try:
with open(id_file, 'r') as f:
start_id = int(f.read())
_LOGGER.debug("Read stored message id: %s" % start_id)
except (FileNotFoundError, ValueError) as ex:
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)
except (FileNotFoundError, TypeError) as ex:
_LOGGER.error("Unable to read the stored msgid: %s" % ex)
pass

vac = mirobo.Vacuum(ip, token, start_id, debug)
vac.manual_seqnum = manual_seq
_LOGGER.debug("Connecting to %s with token %s", ip, token)

ctx.obj = vac
Expand All @@ -63,9 +67,12 @@ def cli(ctx, ip: str, token: str, debug: int, id_file: str):
@pass_dev
def cleanup(vac: mirobo.Vacuum, **kwargs):
id_file = kwargs['id_file']
_LOGGER.debug("Writing %s to %s" % (vac.raw_id, id_file))
seqs = {'seq': vac.raw_id, 'manual_seq': vac.manual_seqnum}
_LOGGER.debug("Writing %s to %s" % (seqs, id_file))
with open(id_file, 'w') as f:
f.write(str(vac.raw_id))
json.dump(seqs, f)
#with open(id_file, 'w') as f:
# f.write(str(vac.raw_id))


@cli.command()
Expand Down Expand Up @@ -144,6 +151,82 @@ def home(vac: mirobo.Vacuum):
click.echo("Requesting return to home: %s" % vac.home())


@cli.group()
@pass_dev
#@click.argument('command', required=False)
def manual(vac: mirobo.Vacuum):
"""Control the robot manually."""
command = ''
if command == 'start':
click.echo("Starting manual control")
return vac.manual_start()
if command == 'stop':
click.echo("Stopping manual control")
return vac.manual_stop()
#if not vac.manual_mode and command :

@manual.command()
@pass_dev
def start(vac: mirobo.Vacuum):
"""Activate the manual mode."""
click.echo("Activating manual controls")
return vac.manual_start()


@manual.command()
@pass_dev
def stop(vac: mirobo.Vacuum):
"""Deactivate the manual mode."""
click.echo("Deactivating manual controls")
return vac.manual_stop()


@manual.command()
@pass_dev
@click.argument('degrees', type=int)
def left(vac: mirobo.Vacuum, degrees: int):
"""Turn to left."""
click.echo("Turning %s degrees left" % degrees)
return vac.manual_control(degrees, 0)


@manual.command()
@pass_dev
@click.argument('degrees', type=int)
def right(vac: mirobo.Vacuum, degrees: int):
"""Turn to right."""
click.echo("Turning right")
return vac.manual_control(-degrees, 0)


@manual.command()
@click.argument('amount', type=float)
@pass_dev
def forward(vac: mirobo.Vacuum, amount: float):
"""Run forwards."""
click.echo("Moving forwards")
return vac.manual_control(0, amount)


@manual.command()
@click.argument('amount', type=float)
@pass_dev
def backward(vac: mirobo.Vacuum, amount:float):
"""Run backwards."""
click.echo("Moving backwards")
return vac.manual_control(0, -amount)


@manual.command()
@pass_dev
@click.argument('rotation', type=float)
@click.argument('velocity', type=float)
@click.argument('duration', type=int)
def move(vac: mirobo.Vacuum, rotation: float, velocity: float, duration: int):
"""Pass raw manual values"""
return vac.manual_control(rotation, velocity, duration)


@cli.command()
@click.argument('cmd', required=False)
@click.argument('start_hr', required=False)
Expand Down Expand Up @@ -209,7 +292,7 @@ def timer(vac: mirobo.Vacuum, timer):
@cli.command()
@pass_dev
def find(vac: mirobo.Vacuum):
"""Finds the robot."""
"""Find the robot."""
click.echo("Sending find the robot calls.")
click.echo(vac.find())

Expand Down
4 changes: 2 additions & 2 deletions mirobo/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ def state(self) -> str:
states = {
1: 'Unknown 1',
2: 'Charger disconnected',
3: 'Idle?',
3: 'Idle',
4: 'Unknown 4',
5: 'Cleaning',
6: 'Returning home',
7: 'Unknown 7',
7: 'Manual mode',
8: 'Charging',
9: 'Unknown 9',
10: 'Paused',
Expand Down
64 changes: 64 additions & 0 deletions mirobo/vacuum.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import logging
import math
import time
from typing import List

from .containers import (VacuumStatus, ConsumableStatus,
Expand All @@ -15,40 +17,94 @@ class VacuumException(DeviceException):
class Vacuum(Device):
"""Main class representing the vacuum."""

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

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

def stop(self):
"""Stop cleaning."""
return self.send("app_stop")

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

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

def home(self):
"""Stop cleaning and return home."""
self.send("app_stop")
return self.send("app_charge")

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

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

def manual_control_once(self, rotation: int, velocity: float, duration: int=1500):
"""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

def manual_control(self, rotation: int, velocity: float, duration: int=1500):
"""Give a command over manual control interface."""
if rotation <= -180 or rotation >= 180:
raise DeviceException("Given rotation is invalid, should be ]-3.1,3.1[, was %s" % rotation)
if velocity <= -0.3 or velocity >= 0.3:
raise DeviceException("Given velocity is invalid, should be ]-0.3, 0.3[, was: %s" % 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])

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

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

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

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

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

def clean_details(self, id_: int) -> List[CleaningDetails]:
"""Return details about specific cleaning."""
details = self.send("get_clean_record", [id_])

res = list()
Expand All @@ -58,9 +114,11 @@ def clean_details(self, id_: int) -> List[CleaningDetails]:
return res

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

def timer(self) -> List[Timer]:
"""Return a list of timers."""
timers = list()
for rec in self.send("get_timer", [""]):
timers.append(Timer(rec))
Expand All @@ -74,24 +132,30 @@ def set_timer(self, details):
return self.send("upd_timer", ["ts", "on"])

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 self.send("get_dnd_timer")

def set_dnd(self, start_hr: int, start_min: int,
end_hr: int, end_min: int):
"""Set do-not-disturb."""
return self.send("set_dnd_timer",
[start_hr, start_min, end_hr, end_min])

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

def set_fan_speed(self, speed: int):
"""Set fan speed."""
# speed = [38, 60 or 77]
return self.send("set_custom_mode", [speed])

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

def raw_command(self, cmd, params):
"""Send a raw command to the robot."""
return self.send(cmd, params)

0 comments on commit 57fb79f

Please sign in to comment.