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 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
160 changes: 154 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_status": {"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,6 +289,19 @@
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
Expand All @@ -247,6 +312,14 @@
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 318 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

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

Added line #L318 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 +334,7 @@
DREAME_MOP_2_ULTRA,
DREAME_MOP_2,
DREAME_TROUVER_FINDER,
DREAME_X10_MOP,
):
return CleaningModeDreameF9
return None
Expand Down Expand Up @@ -333,6 +407,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 412 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

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

Added line #L412 was not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L416 was not covered by tests

@property
def device_fault(self) -> Optional[FaultStatus]:
try:
Expand All @@ -358,11 +440,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 449 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L448-L449

Added lines #L448 - L449 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 453 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L452 - L453 were not covered by tests
return None

@property
Expand Down Expand Up @@ -444,6 +532,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 +553,18 @@
return self.data["water_box_carriage_status"] == 1
return None

@property
def dust_auto_collect(self) -> Optional[bool]:
"""Return True if auto dust collect is enabled, None if function is not present."""

if "dust_auto_collect_status" in self.data:
return self.data["dust_auto_collect_status"] == 1
return None

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

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L561-L562

Added lines #L561 - L562 were not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L566 was not covered by tests


class DreameVacuum(MiotDevice):
_mappings = MIOT_MAPPING
Expand All @@ -480,6 +581,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 +598,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 +620,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 +661,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 667 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

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

Added line #L667 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 672 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

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

Added line #L672 was not covered by tests

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

@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.

:param int room: ID of the room to be vacuumed
:param int clean_mode: Value of fan speed to be used for room cleaning
"""

cleaningmode_enum = _get_cleaning_mode_enum_class(self.model)
cleaningmode = None

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

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L822-L823

Added lines #L822 - L823 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 831 in miio/integrations/dreame/vacuum/dreamevacuum_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/dreame/vacuum/dreamevacuum_miot.py#L825-L831

Added lines #L825 - L831 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 +874,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 +883,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