Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Dreame Vacuum MB1808 1C #1254

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions examples/dreame_vacuum_mb1808.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
from getkey import getkey, keys
from miio import DreameVacuumMB1808
import time
import logging
from threading import Thread

logger = logging.getLogger(__name__)

class Control:
def __init__(self, ip, token):
self.vac = DreameVacuumMB1808(ip, token)
self.speed = 0
self.is_remote_controlled = False
self.next_keep_going_timestamp = 0
self.should_quit = False
@staticmethod
def print_data(*data):
for d in data:
print(f'{d["did"]:40s} {str(d["decoded"]):>6s}')
def info(self):
self.print_data(*self.vac.get_properties(
'battery_level',
'battery_state',
'robot_fault',
'robot_status',
))

self.print_data(*self.vac.get_properties(
'clean_work_mode',
'clean_time_duration',
'clean_size',
'clean_area',
'clean_timer',
'clean_mode',
'clean_delete_timer',
'clean_water_box',
'clean_object_name',
'clean_start_time',
'clean_total_clean_time',
'clean_total_clean_times',
'clean_total_clean_area',
'clean_clean_log_start_time',
'clean_button_led',
'clean_task_done',
'clean_mopmode',
'clean_clean_info',
'clean_clean_status',
'clean_save_map_status',
))

self.print_data(*self.vac.get_properties(
'annoy_enable',
'annoy_start_time',
'annoy_stop_time',
'remote_deg',
'remote_speed',
'consumable_life_sieve',
'consumable_life_brush_side',
'consumable_life_brush_main',
'warn_code',
'audio_volume',
'audio_voice_packets',
'time_zone',
'main_brush_time_left',
'main_brush_life_level',
'filter_life_level',
'filter_time_left',
'side_brush_time_left',
'side_brush_life_level',
))
def remote_action(self, deg, speed):
self.vac.action("remote_start", remote_deg=deg, remote_speed=speed)
self.next_keep_going_timestamp = time.time() + 1
self.is_remote_controlled = True
print(deg, speed)
def remote_keep_going(self):
while not self.should_quit:
if self.is_remote_controlled:
if time.time() > self.next_keep_going_timestamp:
print(self.next_keep_going_timestamp, 'keep')
self.remote_action(0, self.speed)
time.sleep(.1)
def run(self):
thread = Thread(target = self.remote_keep_going)
thread.start()
while not self.should_quit:
time.sleep(.1)
key = getkey()
if key == keys.LEFT: self.remote_action(15, self.speed)
elif key == keys.RIGHT: self.remote_action(-15, self.speed)
elif key == keys.UP:
if self.speed < 0:
self.speed = 0
else:
self.speed = min(max(self.speed**1.3, 10), 500)
self.remote_action(0, self.speed)
elif key == keys.DOWN:
if self.speed > 15:
self.speed = self.speed**.9
elif self.speed > 0:
self.speed = 0
else:
self.speed = -10
self.remote_action(0, self.speed)
elif key == ' ':
self.speed = 0
self.remote_action(0, self.speed)
elif key == 'Q': self.should_quit = True
elif key == 'q': self.is_remote_controlled = False
elif key == 'i': self.info()
elif key == 'I': self.vac.action("identify")
elif key == 'H':
self.vac.action("battery_start_charge")
self.is_remote_controlled = False
elif key == 's':
self.vac.action("robot_start_sweep")
self.is_remote_controlled = False
elif key == 'S':
self.vac.action("robot_stop_sweeping")
self.is_remote_controlled = False


token = '11111111111111111111111111111111'
ip = '192.168.1.1'
c = Control(ip, token)
c.run()
1 change: 1 addition & 0 deletions miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from miio.huizuo import Huizuo, HuizuoLampFan, HuizuoLampHeater, HuizuoLampScene
from miio.integrations.petwaterdispenser import PetWaterDispenser
from miio.integrations.vacuum.dreame.dreamevacuum_miot import DreameVacuumMiot
from miio.integrations.vacuum.dreame.dreame_vacuum_mb1808 import DreameVacuumMB1808
from miio.integrations.vacuum.mijia import G1Vacuum
from miio.integrations.vacuum.roborock import RoborockVacuum, Vacuum, VacuumException
from miio.integrations.vacuum.roborock.vacuumcontainers import (
Expand Down
139 changes: 139 additions & 0 deletions miio/integrations/vacuum/dreame/dreame_vacuum_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from miio.device import Device
import logging
import time
from enum import Enum
logger = logging.getLogger(__name__)

class ChargingState(Enum):
Unknown = -1
Charging = 1
Discharging = 2
Charging2 = 4
GoCharging = 5

class CleaningMode(Enum):
Unknown = -1
Quiet = 0
Default = 1
Medium = 2
Strong = 3

class OperatingMode(Enum):
Unknown = -1
Paused = 1
Cleaning = 2
GoCharging = 3
Charging = 6
ManualCleaning = 13
Sleeping = 14
ManualPaused = 17
ZonedCleaning = 19

class FaultStatus(Enum):
Unknown = -1
NoFaults = 0

class DeviceStatus(Enum):
Unknown = -1
Sweeping = 1
Idle = 2
Paused = 3
Error = 4
GoCharging = 5
Charging = 6
ManualSweeping = 13

class MopMode(Enum):
Unknown = -1
Low = 1
Mid = 2
High = 3

class WarningCode(Enum):
Unknown = -1
Normal = 0
Drop = 1
Cliff = 2
Bumper = 3
Gesture = 4
BumperRepeat = 5
DropRepeat = 6
OpticalFlow = 7
NoBox = 8
NoTankbox = 9
WaterboxEmpty = 10
BoxFull = 11
Brush = 12
SideBrush = 13
Fan = 14
LeftWheelMotor = 15
RightWheelMotor = 16
TurnSuffocate = 17
ForwardSuffocate = 18
ChargerGet = 19
BatteryLow = 20
ChargeFault = 21
BatteryPercentage = 22
Heart = 23
CameraOcclusion = 24
CameraFault = 25
EventBattery = 26
ForwardLooking = 27
Gyroscope = 28

def percent_format(val):
return f'{val}%'

def minutes_format(val):
return f'{val}min ({val/60:.3f}h / {val/60/24:.3f} days)'

def hours_format(val):
return f'{val}h ({val/24:.3f} days)'

def epoch_time_format(val):
return time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(val))

class DreameVacuumBase(Device):
def get_properties(self, *prop):
if len(prop) > 20:
raise Exception(f'number of properties cannot exceed 20. {len(prop)} found.')
prop_list = []
for p in prop:
ppar = self.commands[p]
if 'r' in ppar['mode']:
prop_list.append({'did': p, 'siid': ppar['siid'], 'piid': ppar['piid']})
else:
logger.warning(f'{p} has no read mode')
res = self.send('get_properties', prop_list)
for r in res:
if 'value' in r:
r['decoded'] = self.commands[r['did']]['mode']['r'](r['value'])
else:
r['decoded'] = 'N/A'
return res

def set_prop_str(self, par, val):
ppar = self.commands[par]
if 'w' in ppar['mode']:
return {'did': par, 'siid': ppar['siid'], 'piid': ppar['piid'], 'value': ppar['mode']['w'](val)}
else:
logger.warning(f'{par} has no write mode')
return None

def set_properties(self, **kwargs):
prop_list = [self.set_prop_str(p, v) for p, v in kwargs.items()]
res = self.send('set_properties', prop_list)
return res

def action(self, act, **pars):
pars_list = []
apar = self.commands[act]
if 'a' in apar['mode']:
for p, val in pars.items():
par_dic = self.set_prop_str(p, val)
pars_list.append({'piid': par_dic['piid'], 'value': par_dic['value']})
act_dic = {'did': act, 'siid': apar['siid'], 'aiid': apar['aiid'], 'in': pars_list}
return self.send('action', act_dic)
else:
logger.warning(f'{act} has no command mode')
return None
92 changes: 92 additions & 0 deletions miio/integrations/vacuum/dreame/dreame_vacuum_mb1808.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from .dreame_vacuum_base import DreameVacuumBase
from .dreame_vacuum_base import ChargingState, CleaningMode, OperatingMode,\
FaultStatus, DeviceStatus, MopMode, WarningCode
from .dreame_vacuum_base import percent_format,\
minutes_format, hours_format, epoch_time_format

import logging
logger = logging.getLogger(__name__)

class DreameVacuumMB1808(DreameVacuumBase):
def __init__(self, ip, token, *args, **kwargs):
self.commands = {
# Battery
'battery_level' : {'siid': 2, 'piid': 1, 'mode': {'r': percent_format}},
'battery_state' : {'siid': 2, 'piid': 2, 'mode': {'r': ChargingState}},
'battery_start_charge' : {'siid': 2, 'aiid': 1, 'mode': 'a'},
# Robot Cleaner
'robot_fault' : {'siid': 3, 'piid': 1, 'mode': {'r': bool}},
'robot_status' : {'siid': 3, 'piid': 2, 'mode': {'r': DeviceStatus}},
'robot_start_sweep' : {'siid': 3, 'aiid': 1, 'mode': 'a'},
'robot_stop_sweeping' : {'siid': 3, 'aiid': 2, 'mode': 'a'},
# Identify I am here
'identify' : {'siid': 17, 'aiid': 1, 'mode': 'a'},
# Clean
'clean_work_mode' : {'siid': 18, 'piid': 1, 'mode': {'r': OperatingMode}},
'clean_time_duration' : {'siid': 18, 'piid': 2, 'mode': {'r': minutes_format}},
'clean_size' : {'siid': 18, 'piid': 3, 'mode': {'r': str}},
'clean_area' : {'siid': 18, 'piid': 4, 'mode': {'r': str}},
'clean_timer' : {'siid': 18, 'piid': 5, 'mode': {'r': str, 'w': str}},
'clean_mode' : {'siid': 18, 'piid': 6, 'mode': {'r': CleaningMode, 'w': int}},
'clean_delete_timer' : {'siid': 18, 'piid': 8, 'mode': {'w': int}},
'clean_water_box' : {'siid': 18, 'piid': 9, 'mode': {'r': int}},
'clean_object_name' : {'siid': 18, 'piid': 11, 'mode': {'r': str}},
'clean_start_time' : {'siid': 18, 'piid': 12, 'mode': {'r': str}},
'clean_total_clean_time' : {'siid': 18, 'piid': 13, 'mode': {'r': minutes_format}},
'clean_total_clean_times' : {'siid': 18, 'piid': 14, 'mode': {'r': int}},
'clean_total_clean_area' : {'siid': 18, 'piid': 15, 'mode': {'r': int}},
'clean_clean_log_start_time' : {'siid': 18, 'piid': 16, 'mode': {'r': epoch_time_format}},
'clean_button_led' : {'siid': 18, 'piid': 17, 'mode': {'r': int}},
'clean_task_done' : {'siid': 18, 'piid': 18, 'mode': {'r': bool}},
'clean_mopmode' : {'siid': 18, 'piid': 20, 'mode': {'r': MopMode, 'w': int}},
'clean_clean_info' : {'siid': 18, 'piid': 21, 'mode': {'w': str}}, # rectangle to clean "x1,y1,x2,y2"
'clean_clean_status' : {'siid': 18, 'piid': 22, 'mode': {'r': percent_format}},
'clean_save_map_status' : {'siid': 18, 'piid': 23, 'mode': {'r': bool, 'w': int}},
'clean_start' : {'siid': 18, 'aiid': 1, 'mode': 'a'}, # in clean_info
'clean_stop' : {'siid': 18, 'aiid': 2, 'mode': 'a'},
# Consumable
'consumable_life_sieve' : {'siid': 19, 'piid': 1, 'mode': {'r': str, 'w': str}},
'consumable_life_brush_side' : {'siid': 19, 'piid': 2, 'mode': {'r': str, 'w': str}},
'consumable_life_brush_main' : {'siid': 19, 'piid': 3, 'mode': {'r': str, 'w': str}},
# Annoy
'annoy_enable' : {'siid': 20, 'piid': 1, 'mode': {'r': bool, 'w': int}},
'annoy_start_time' : {'siid': 20, 'piid': 2, 'mode': {'r': str, 'w': str}},
'annoy_stop_time' : {'siid': 20, 'piid': 3, 'mode': {'r': str, 'w': str}},
# Remote
'remote_deg' : {'siid': 21, 'piid': 1, 'mode': {'w': str}},
'remote_speed' : {'siid': 21, 'piid': 2, 'mode': {'w': str}},
'remote_start' : {'siid': 21, 'aiid': 1, 'mode': 'a'}, # in = [deg, speed]
'remote_stop' : {'siid': 21, 'aiid': 2, 'mode': 'a'},
'remote_exit' : {'siid': 21, 'aiid': 3, 'mode': 'a'},
# Warnings
'warn_code' : {'siid': 22, 'piid': 1, 'mode': {'r': WarningCode}},
# Map
'map_view' : {'siid': 23, 'piid': 1, 'mode': {'r': WarningCode}},
'map_frame_info' : {'siid': 23, 'piid': 2, 'mode': {'w': str}},
'map_object_name' : {'siid': 23, 'piid': 3, 'mode': {'r': str}},
'map_extend_data' : {'siid': 23, 'piid': 4, 'mode': {'r': str, 'w': str}},
'map_robot_time' : {'siid': 23, 'piid': 5, 'mode': {'r': minutes_format}},
'map_req' : {'siid': 23, 'aiid': 1, 'mode': 'a'}, # in = [frame_info]
'map_update' : {'siid': 23, 'aiid': 2, 'mode': 'a'}, # in = [map_extend_data]
# Audio
'audio_volume' : {'siid': 24, 'piid': 1, 'mode': {'r': percent_format, 'w': int}},
'audio_voice_packets' : {'siid': 24, 'piid': 3, 'mode': {'r': str, 'w': str}},
'audio_position' : {'siid': 24, 'aiid': 1, 'mode': 'a'},
'audio_set_voice' : {'siid': 24, 'aiid': 2, 'mode': 'a'}, # in = voice_packets ???
'audio_play_sound' : {'siid': 24, 'aiid': 3, 'mode': 'a'}, # in = voice_packets ???
# Time
'time_zone' : {'siid': 25, 'piid': 1, 'mode': {'r': str, 'w': str}},
# Main Cleaning Brush
'main_brush_time_left' : {'siid': 26, 'piid': 1, 'mode': {'r': hours_format}},
'main_brush_life_level' : {'siid': 26, 'piid': 2, 'mode': {'r': percent_format}},
'main_brush_reset_life' : {'siid': 26, 'aiid': 1, 'mode': 'a'},
# Filter
'filter_life_level' : {'siid': 27, 'piid': 1, 'mode': {'r': percent_format}},
'filter_time_left' : {'siid': 27, 'piid': 2, 'mode': {'r': hours_format}},
'filter_reset_life' : {'siid': 27, 'aiid': 1, 'mode': 'a'},
# Side Cleaning Brush
'side_brush_time_left' : {'siid': 28, 'piid': 1, 'mode': {'r': hours_format}},
'side_brush_life_level' : {'siid': 28, 'piid': 2, 'mode': {'r': percent_format}},
'side_brush_reset_life' : {'siid': 28, 'aiid': 1, 'mode': 'a'},
}
super().__init__(ip, token, *args, **kwargs)