#!/usr/bin/env python3 import click from datetime import datetime class EufyRawIR: """ Low level IR signal modulation """ _IR_PREAMBLE = (3000, 3000) _IR_0 = (400, 600) _IR_1 = (400, 1600) _IR_TRAILER = (400, 20000) def _modulate(self, message): signal = list(self._IR_PREAMBLE) for b in message: for i in range(8): if (b << i) & 0x80: signal.extend(list(self._IR_1)) else: signal.extend(list(self._IR_0)) signal.extend(list(self._IR_TRAILER)) return 3*signal def sendRaw(self, message): print(self._modulate(message)) class EufyIR(EufyRawIR): """ High level Eufy commands """ _HEADER = 0x68 CLEAN_AUTO = 0x5d CLEAN_SPOT = 0x8c CLEAN_EDGE = 0x9c CLEAN_ROOM = 0xad STOP = 0x4f POWER_MAX = 0x1c POWER_BOOSTIQ = 0x1d POWER_STANDARD = 0x1e MOVE_FORWARD = 0x2f MOVE_BACKWARD = 0x7f MOVE_CCW = 0x3f MOVE_CW = 0x6f SET_TIME = 0xbf SET_SCHEDULE = 0xcf CANCEL_SCHEDULE = 0xdf RETURN_BASE = 0xef def __init__(self): self._schedule = 0xff def _message(self, code): now = datetime.now() msg = [self._HEADER, code, now.hour, now.minute, self._schedule] checksum = sum(msg) & 0xff msg.append(checksum) return msg def send(self, code): self.sendRaw(self._message(code)) def cleanAuto(self): self.send(self.CLEAN_AUTO) def cleanSpot(self): self.send(self.CLEAN_SPOT) def cleanEdge(self): self.send(self.CLEAN_EDGE) def cleanRoom(self): self.send(self.CLEAN_ROOM) def start(self): self.cleanAuto() def stop(self): self.send(self.STOP) def powerMax(self): self.send(self.POWER_MAX) def powerBoostIQ(self): self.send(self.POWER_BOOSTIQ) def powerStandard(self): self.send(self.POWER_STANDARD) def moveForward(self): self.send(self.MOVE_FORWARD) def moveBackward(self): self.send(self.MOVE_BACKWARD) def moveCCW(self): self.send(self.MOVE_CCW) def moveLeft(self): self.moveCCW() def moveCW(self): self.send(self.MOVE_CW) def moveRight(self): self.moveCW() def setTime(self): self.send(self.SET_TIME) def setSchedule(self, t): # FIXME: properly store schedule self.send(self.SET_SCHEDULE, t) def cancelSchedule(self): # FIXME: properly store schedule self.send(self.CANCEL_SCHEDULE) def returnBase(self): self.send(self.RETURN_BASE) @click.group() def cli(): pass @cli.command() @click.argument('mode', required=False, type=click.Choice(['auto', 'spot', 'edge', 'room'])) def clean(mode): eufy = EufyIR() dict( auto=eufy.cleanAuto, spot=eufy.cleanSpot, edge=eufy.cleanEdge, room=eufy.cleanRoom ).get(mode, eufy.start)() @cli.command() @click.argument('mode', required=True, type=click.Choice(['standard', 'boostiq', 'max'])) def power(mode): eufy = EufyIR() { 'standard': eufy.powerStandard, 'boostiq': eufy.powerBoostIQ, 'max': eufy.powerMax }.get(mode)() @cli.command() @click.argument('mode', required=True, type=click.Choice(['forward', 'backward', 'ccw', 'cw', 'left', 'right'])) def move(mode): eufy = EufyIR() dict( forward=eufy.moveForward, backward=eufy.moveBackward, ccw=eufy.moveCCW, cw=eufy.moveCW, left=eufy.moveLeft, right=eufy.moveRight ).get(mode)() if __name__ == '__main__': cli()