From 80d22843cc3fbfa3d7ad6f601e09211f73340e73 Mon Sep 17 00:00:00 2001 From: Maurizio Porrato Date: Mon, 11 Feb 2019 08:06:06 +0000 Subject: [PATCH] Initial commit --- .gitignore | 3 + eufy.py | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ protocol.txt | 115 +++++++++++++++++++++++++++++++++++ 3 files changed, 286 insertions(+) create mode 100644 .gitignore create mode 100755 eufy.py create mode 100644 protocol.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3259bb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*~ +*% +*.py[co] diff --git a/eufy.py b/eufy.py new file mode 100755 index 0000000..85fb95e --- /dev/null +++ b/eufy.py @@ -0,0 +1,168 @@ +#!/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() diff --git a/protocol.txt b/protocol.txt new file mode 100644 index 0000000..50a7b1e --- /dev/null +++ b/protocol.txt @@ -0,0 +1,115 @@ +AGC: H:3ms L:3ms + 0: H:400µs L:600µs + 1: H:400µs L:1.6ms + +Frame: (AGC+48bits+H:400µs+L:20ms)*3 + +Format: 68CCHHMMSSKK + + CC: command + HH: hour + MM: minute + SS: schedule (in quarters of an hour from midnight, 0xff=off) + KK: checksum (sum of all bytes in the frame modulo 256) + + Code | Command +------+--------- + 1c | Max power + 1d | BoostIQ + 1e | Standard power + 2f | Forward + 3f | Turn CCW + 4f | Stop cleaning + 5d | Auto cleaning + 6f | Turn CW + 7f | Backward + 8c | Spot cleaning + 9c | Edge cleaning + ad | Room cleaning + bf | Set time + cf | Set schedule + df | Clear schedule + ef | Return to base + +0110 1000 0101 1101 0000 1111 0010 0111 0010 1000 0010 0011 Start +685d0f272823 + +0110 1000 0100 1111 0000 1111 0010 0111 0010 1000 0001 0101 Stop +684f0f272815 + +0110 1000 0101 1101 0001 0001 0000 0010 0010 1000 0000 0000 Start +685d11022800 + +0110 1000 0100 1111 0001 0001 0000 0010 0010 1000 1111 0010 Stop +684f110228f2 + +0110 1000 1011 1111 0001 0001 0010 0100 0010 1000 1000 0100 Time (5:36pm) +68bf11242884 + +0110 1000 1011 1111 0001 0001 0010 1011 0010 1000 1000 1011 Time (5:43pm) +68bf112b288b + +0110 1000 1011 1111 0001 0001 0010 1011 0010 1000 1000 1011 Time (5:43pm +1.3s) +68bf112b288b + +0110 1000 1011 1111 0001 0010 0000 0110 0010 1000 0110 0111 Time (T:6:06pm A:10:00am) +68bf12062867 + HHMM + +0110 1000 0101 1101 0001 0010 0000 0110 0010 1000 0000 0101 Start +685d12062805 + +0110 1000 0100 1111 0001 0010 0000 0110 0010 1000 1111 0111 Stop (start+0.9s) +684f120628f7 + +0110 1000 1100 1111 0001 0011 0010 0000 0010 1000 1001 0010 Schedule 10:00am +68cf13202892 + +0110 1000 1100 1111 0001 0011 0010 1011 0010 1001 1001 1110 Schedule 10:15am +68cf132b299e + +0110 1000 1100 1111 0001 0011 0011 0000 0101 1111 1101 1001 Schedule 11:45pm +68cf13305fd9 + +0110 1000 1101 1111 0001 0011 0011 0110 1111 1111 1000 1111 Schedule off +68df1336ff8f + +0110 1000 0101 1101 0001 0100 0000 0110 1111 1111 1101 1110 Start +685d1406ffde + +0110 1000 0100 1111 0001 0100 0001 0010 1111 1111 1101 1100 Auto +684f1412ffdc + +0110 1000 0001 1100 0001 0100 0001 1001 1111 1111 1011 0000 Power (BoostIQ->Max) +681c1419ffb0 + +0110 1000 0001 1110 0001 0100 0001 1110 1111 1111 1011 0111 Power (Max->Standard) +681e141effb7 + +0110 1000 0001 1101 0001 0100 0001 1110 1111 1111 1011 0110 Power (Standard->BoostIQ) +681d141effb6 + +0110 1000 0010 1111 0001 0100 0010 1110 1111 1111 1101 1000 Up +682f142effd8 + +0110 1000 0111 1111 0001 0100 0010 1110 1111 1111 0010 1000 Down +687f142eff28 + +0110 1000 0011 1111 0001 0100 0010 1110 1111 1111 1110 1000 Left +683f142effe8 + +0110 1000 0110 1111 0001 0100 0010 1110 1111 1111 0001 1000 Right +686f142eff18 + +0110 1000 1000 1100 0001 0101 0000 0100 1111 1111 0000 1100 Spot +688c1504ff0c + +0110 1000 1001 1100 0001 0101 0000 1000 1111 1111 0010 0000 Edge +689c1508ff20 + +0110 1000 1010 1101 0001 0101 0000 1100 1111 1111 0011 0101 Room +68ad150cff35 + +0110 1000 1110 1111 0001 0101 0001 0000 1111 1111 0111 1011 Base +68ef1510ff7b +