Skip to content
New issue

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

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

Already on GitHub? # to your account

Update dreamevacuum_miot.py with Xaiomi X10 #1924

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Changes from 6 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
167 changes: 161 additions & 6 deletions miio/integrations/dreame/vacuum/dreamevacuum_miot.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
DREAME_MOP_2 = "dreame.vacuum.p2150o"
DREAME_TROUVER_FINDER = "dreame.vacuum.p2036"
DREAME_D10_PLUS = "dreame.vacuum.r2205"
DREAME_X10_MOP = "dreame.vacuum.r2209"

_DREAME_1C_MAPPING: MiotMapping = {
# https://home.miot-spec.com/spec/dreame.vacuum.mc1808
Expand Down Expand Up @@ -164,6 +165,55 @@
"play_sound": {"siid": 7, "aiid": 2},
}

_DREAME_X10_MOP_MAPPING: MiotMapping = {
# https://home.miot-spec.com/spec/dreame.vacuum.r2209
"battery_level": {"siid": 3, "piid": 1},
"charging_state": {"siid": 3, "piid": 2},
"device_fault": {"siid": 2, "piid": 2},
"device_status": {"siid": 2, "piid": 1},
"brush_left_time": {"siid": 9, "piid": 1},
"brush_life_level": {"siid": 9, "piid": 2},
"brush_left_time2": {"siid": 10, "piid": 1},
"brush_life_level2": {"siid": 10, "piid": 2},
"filter_life_level": {"siid": 11, "piid": 1},
"filter_left_time": {"siid": 11, "piid": 2},
"sensor_life_level": {"siid": 16, "piid": 1},
"sensor_left_time": {"siid": 16, "piid": 2},
"operating_mode": {"siid": 4, "piid": 1}, # work-mode
"cleaning_mode": {"siid": 4, "piid": 4},
"delete_timer": {"siid": 8, "aiid": 1},
"timer_enable": {"siid": 5, "piid": 1}, # do-not-disturb -> enable
"cleaning_time": {"siid": 4, "piid": 2},
"cleaning_area": {"siid": 4, "piid": 3},
"first_clean_time": {"siid": 12, "piid": 1},
"total_clean_time": {"siid": 12, "piid": 2},
"total_clean_times": {"siid": 12, "piid": 3},
"total_clean_area": {"siid": 12, "piid": 4},
"start_time": {"siid": 5, "piid": 2},
"stop_time": {"siid": 5, "piid": 3}, # end-time
"map_view": {"siid": 6, "piid": 1}, # map-data
"frame_info": {"siid": 6, "piid": 2},
"volume": {"siid": 7, "piid": 1},
"voice_package": {"siid": 7, "piid": 2}, # voice-packet-id
"water_flow": {"siid": 4, "piid": 5}, # mop-mode
"water_box_carriage_status": {"siid": 4, "piid": 6}, # waterbox-status
"dust_auto_collect": {"siid": 15, "piid": 1}, # auto-collect-dust
"dust_collect_every": {"siid": 15, "piid": 2}, # Collect dust every n-th cleaing
"timezone": {"siid": 8, "piid": 1}, # time-zone
"home": {"siid": 3, "aiid": 1}, # start-charge
"locate": {"siid": 7, "aiid": 1}, # audio -> position
"start_clean": {"siid": 4, "aiid": 1},
"stop_clean": {"siid": 4, "aiid": 2},
"start_room_sweap": {"siid": 4, "aiid": 1},
"reset_mainbrush_life": {"siid": 9, "aiid": 1},
"reset_filter_life": {"siid": 11, "aiid": 1},
"reset_sidebrush_life": {"siid": 10, "aiid": 1},
"reset_sensor_life": {"siid": 16, "aiid": 1},
"start_dust_collect": {"siid": 15, "aiid": 1},
"move": {"siid": 21, "aiid": 1}, # not in documentation
"play_sound": {"siid": 7, "aiid": 2},
}

MIOT_MAPPING: Dict[str, MiotMapping] = {
DREAME_1C: _DREAME_1C_MAPPING,
DREAME_F9: _DREAME_F9_MAPPING,
Expand All @@ -176,6 +226,7 @@
DREAME_MOP_2: _DREAME_F9_MAPPING,
DREAME_TROUVER_FINDER: _DREAME_TROUVER_FINDER_MAPPING,
DREAME_D10_PLUS: _DREAME_TROUVER_FINDER_MAPPING,
DREAME_X10_MOP: _DREAME_X10_MOP_MAPPING,
}


Expand Down Expand Up @@ -213,6 +264,7 @@
ManualCleaning = 13
Sleeping = 14
ManualPaused = 17
RoomCleaning = 18
ZonedCleaning = 19


Expand All @@ -237,16 +289,42 @@
Upgrading = 14


class DeviceStatusX10(FormattableEnum):
Sweeping = 1
Idle = 2
Paused = 3
Error = 4
GoCharging = 5
Charging = 6
SweepingAndMopping = 7
Building = 11
Mopping = 12
ChargingComplete = 13


class WaterFlow(FormattableEnum):
Low = 1
Medium = 2
High = 3


class DustAutoCollect(FormattableEnum):
Off = 0
On = 1
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use boolean instead.



def _enum_as_dict(cls):
return {x.name: x.value for x in list(cls)}


def _get_device_status_enum_class(model):
"""Return device status enum class for model"""
if model == DREAME_X10_MOP:
return DeviceStatusX10

Check warning on line 323 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L323

Added line #L323 was not covered by tests
else:
return DeviceStatus


def _get_cleaning_mode_enum_class(model):
"""Return cleaning mode enum class for model if found or None."""
if model == DREAME_1C:
Expand All @@ -261,6 +339,7 @@
DREAME_MOP_2_ULTRA,
DREAME_MOP_2,
DREAME_TROUVER_FINDER,
DREAME_X10_MOP,
):
return CleaningModeDreameF9
return None
Expand Down Expand Up @@ -333,6 +412,14 @@
def filter_life_level(self) -> str:
return self.data["filter_life_level"]

@property
def sensor_left_time(self) -> str:
return self.data["sensor_left_time"]

Check warning on line 417 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L417

Added line #L417 was not covered by tests

@property
def sensor_life_level(self) -> str:
return self.data["sensor_life_level"]

Check warning on line 421 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L421

Added line #L421 was not covered by tests

@property
def device_fault(self) -> Optional[FaultStatus]:
try:
Expand All @@ -358,11 +445,17 @@
return None

@property
def device_status(self) -> Optional[DeviceStatus]:
def device_status(self):
device_status = self.data["device_status"]
device_status_enum_class = _get_device_status_enum_class(self.model)

if not device_status_enum_class:
_LOGGER.error(f"Unknown model for device status ({self.model})")
return None

Check warning on line 454 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L453-L454

Added lines #L453 - L454 were not covered by tests
try:
return DeviceStatus(self.data["device_status"])
except TypeError:
_LOGGER.error("Unknown DeviceStatus (%s)", self.data["device_status"])
return device_status_enum_class(device_status)
except ValueError:
_LOGGER.error(f"Unknown DeviceStatus ({device_status})")

Check warning on line 458 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L457-L458

Added lines #L457 - L458 were not covered by tests
return None

@property
Expand Down Expand Up @@ -444,6 +537,7 @@
return self.data.get("life_brush_main")

# TODO: get/set water flow for Dreame 1C

@property
def water_flow(self) -> Optional[WaterFlow]:
try:
Expand All @@ -464,6 +558,24 @@
return self.data["water_box_carriage_status"] == 1
return None

@property
def dust_auto_collect(self) -> Optional[DustAutoCollect]:
try:
dust_auto_collect = self.data["dust_auto_collect"]
except KeyError:
return None
try:
return DustAutoCollect(dust_auto_collect)
except ValueError:
_LOGGER.error(

Check warning on line 570 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L563-L570

Added lines #L563 - L570 were not covered by tests
"Unknown DustAutoCollect (%s)", self.data["dust_auto_collect"]
)
return None

Check warning on line 573 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L573

Added line #L573 was not covered by tests

@property
def dust_collect_every(self) -> str:
return self.data["dust_collect_every"]

Check warning on line 577 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L577

Added line #L577 was not covered by tests


class DreameVacuum(MiotDevice):
_mappings = MIOT_MAPPING
Expand All @@ -480,6 +592,8 @@
"Device status: {result.device_status}\n"
"Filter left level: {result.filter_left_time}\n"
"Filter life level: {result.filter_life_level}\n"
"Sensor left level: {result.sensor_left_time}\n"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add descriptors to the status class(es) (@sensor, @setting, @action) so we can get rid of the manual output formatting altogether, and use the standardized interface for controls (i.e., miiocli commands like status, actions, settings, call and set).

This makes them visible for the cli and the future homeassistant integration (https://github.com/rytilahti/homeassistant-xiaomi-ng) when it's all done across the library.

"Sensor life level: {result.sensor_life_level}\n"
"Life brush main: {result.life_brush_main}\n"
"Life brush side: {result.life_brush_side}\n"
"Life sieve: {result.life_sieve}\n"
Expand All @@ -495,6 +609,8 @@
"Volume: {result.volume}\n"
"Water flow: {result.water_flow}\n"
"Water box attached: {result.is_water_box_carriage_attached} \n"
"Dust auto collect: {result.dust_auto_collect}\n"
"Dust collect every n-th cleaning: {result.dust_collect_every} \n"
"Cleaning time: {result.cleaning_time}\n"
"Cleaning area: {result.cleaning_area}\n"
"First clean time: {result.first_clean_time}\n"
Expand All @@ -515,6 +631,7 @@
)

# TODO: check the actual limit for this

MANUAL_ROTATION_MAX = 120
MANUAL_ROTATION_MIN = -MANUAL_ROTATION_MAX
MANUAL_DISTANCE_MAX = 300
Expand Down Expand Up @@ -555,6 +672,16 @@
"""Reset side brush life."""
return self.call_action_from_mapping("reset_sidebrush_life")

@command()
def reset_sensor_life(self) -> None:
"""Reset sensor life."""
return self.call_action_from_mapping("reset_sensor_life")

Check warning on line 678 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L678

Added line #L678 was not covered by tests

@command()
def start_dust_collect(self) -> None:
"""Start dust collect"""
return self.call_action_from_mapping("start_dust_collect")

Check warning on line 683 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L683

Added line #L683 was not covered by tests

@command()
def play_sound(self) -> None:
"""Play sound."""
Expand Down Expand Up @@ -692,6 +819,36 @@
],
)

@command(
click.argument("room", default=3, type=int),
click.argument("clean_mode", default=1, type=int),
)
def start_room_sweap(self, room: int, clean_mode: int) -> None:
"""Start room cleaning."""

cleaningmode_enum = _get_cleaning_mode_enum_class(self.model)
cleaningmode = None

Check warning on line 830 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L829-L830

Added lines #L829 - L830 were not covered by tests
if not cleaningmode_enum:
return
try:
cleaningmode = cleaningmode_enum(clean_mode)
except ValueError:
_LOGGER.error(f"Unknown cleaning mode value passed {clean_mode}")
return None
self.call_action_from_mapping(

Check warning on line 838 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L832-L838

Added lines #L832 - L838 were not covered by tests
"start_room_sweap",
[
{
"piid": 1,
"value": 18,
},
{
"piid": 10,
"value": f"\u007b\u0022selects\u0022:[[{room},1,{cleaningmode.value},3,1]]\u007d",
},
],
)

@command(
click.argument("url", type=str),
click.argument("md5sum", type=str, required=False),
Expand Down Expand Up @@ -724,7 +881,6 @@
t = threading.Thread(target=server.serve_once)
t.start()
click.echo(f"Hosting file at {local_url}")

params = [
{"piid": 3, "value": voice_id},
{"piid": 4, "value": local_url},
Expand All @@ -734,5 +890,4 @@
result_status = self.call_action_from_mapping("set_voice", params=params)
if result_status["code"] == 0:
click.echo("Installation complete!")

return result_status
Loading