-
-
Notifications
You must be signed in to change notification settings - Fork 563
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial support for Vacuum 1C STYTJ01ZHM (dreame.vacuum.mc1808)
- Loading branch information
1 parent
cb15f5d
commit ad58bee
Showing
3 changed files
with
472 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,376 @@ | ||
"""Vacuum 1C STYTJ01ZHM (dreame.vacuum.mc1808) | ||
Commands: | ||
home Return to home. | ||
identify Locate the device (i am here). | ||
info Get miIO protocol information from the device. | ||
raw_command Send a raw command to the device. | ||
reset_filter_life Reset filter life. | ||
reset_mainbrush_life Reset main brush life. | ||
reset_sidebrush_life Reset side brush life. | ||
start Start cleaning. | ||
status State of the vacuum. | ||
stop Stop cleaning. | ||
""" | ||
|
||
import logging | ||
from enum import Enum | ||
|
||
from miio.miot_device import MiotDevice | ||
|
||
from .click_common import command, format_output | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
_MAPPING = { | ||
# siid 1: (Device Information): 4 props, 0 actions | ||
# piid: 4 (Current Firmware Version): (string, unit: None) (acc: ['read'], value-list: [], value-range: None) | ||
# piid: 1 (Device Manufacturer): (string, unit: None) (acc: ['read'], value-list: [], value-range: None) | ||
# piid: 2 (Device Model): (string, unit: None) (acc: ['read'], value-list: [], value-range: None) | ||
# piid: 3 (Device Serial Number): (string, unit: None) (acc: ['read'], value-list: [], value-range: None) | ||
# siid 2: (Battery): 2 props, 1 actions | ||
# piid: 1 (Battery Level): (uint8, unit: percentage) (acc: ['read', 'notify'], value-list: [], value-range: [0, 100, 1]) | ||
"battery_level": {"siid": 2, "piid": 1}, | ||
# piid: 2 (Charging State): (uint8, unit: None) (acc: ['read', 'notify'], value-list: [{'value': 1, 'description': 'Charging'}, {'value': 2, 'description': 'Not Charging'}, {'value': 4, 'description': 'Charging'}, {'value': 5, 'description': 'Go Charging'}], value-range: None) | ||
"charging_state": {"siid": 2, "piid": 2}, | ||
# siid 3: (Robot Cleaner): 2 props, 2 actions | ||
# piid: 1 (Device Fault): (uint8, unit: None) (acc: ['read', 'notify'], value-list: [{'value': 0, 'description': 'No faults'}], value-range: None) | ||
"device_fault": {"siid": 3, "piid": 1}, | ||
# piid: 2 (Status): (int8, unit: None) (acc: ['read', 'notify'], value-list: [{'value': 1, 'description': 'Sweeping'}, {'value': 2, 'description': 'Idle'}, {'value': 3, 'description': 'Paused'}, {'value': 4, 'description': 'Error'}, {'value': 5, 'description': 'Go Charging'}, {'value': 6, 'description': 'Charging'}], value-range: None) | ||
"device_status": {"siid": 3, "piid": 2}, | ||
# siid 17: (Identify): 0 props, 1 actions | ||
# siid 26: (Main Cleaning Brush): 2 props, 1 actions | ||
# piid: 1 (Brush Left Time): (uint16, unit: hour) (acc: ['read', 'notify'], value-list: [], value-range: [0, 300, 1]) | ||
"brush_left_time": {"siid": 26, "piid": 1}, | ||
# piid: 2 (Brush Life Level): (uint8, unit: percentage) (acc: ['read', 'notify'], value-list: [], value-range: [0, 100, 1]) | ||
"brush_life_level": {"siid": 26, "piid": 2}, | ||
# siid 27: (Filter): 2 props, 1 actions | ||
# piid: 1 (Filter Life Level): (uint8, unit: percentage) (acc: ['read', 'notify'], value-list: [], value-range: [0, 100, 1]) | ||
"filter_life_level": {"siid": 27, "piid": 1}, | ||
# piid: 2 (Filter Left Time): (uint16, unit: hour) (acc: ['read', 'notify'], value-list: [], value-range: [0, 300, 1]) | ||
"filter_left_time": {"siid": 27, "piid": 2}, | ||
# siid 28: (Side Cleaning Brush): 2 props, 1 actions | ||
# piid: 1 (Brush Left Time): (uint16, unit: hour) (acc: ['read', 'notify'], value-list: [], value-range: [0, 300, 1]) | ||
"brush_left_time2": {"siid": 28, "piid": 1}, | ||
# piid: 2 (Brush Life Level): (uint8, unit: percentage) (acc: ['read', 'notify'], value-list: [], value-range: [0, 100, 1]) | ||
"brush_life_level2": {"siid": 28, "piid": 2}, | ||
# siid 18: (clean): 16 props, 2 actions | ||
# piid: 1 (Operating Mode): (int32, unit: none) (acc: ['read', 'notify'], value-list: [], value-range: [0, 17, 1]) | ||
"operating_mode": {"siid": 18, "piid": 1}, | ||
# piid: 4 (area): (string, unit: None) (acc: ['read', 'write'], value-list: [], value-range: None) | ||
# piid: 6 (Cleaning Mode): (int32, unit: none) (acc: ['read', 'write', 'notify'], value-list: [{'value': 0, 'description': 'quiet'}, {'value': 1, 'description': 'default'}, {'value': 2, 'description': 'medium'}, {'value': 3, 'description': 'strong'}], value-range: None) | ||
"cleaning_mode": {"siid": 18, "piid": 6}, | ||
# piid: 8 (delete-timer): (int32, unit: None) (acc: ['write'], value-list: [], value-range: [0, 100, 1]) | ||
"delete_timer": {"siid": 18, "piid": 8}, | ||
# piid: 13 (): (uint32, unit: minutes) (acc: ['read', 'notify'], value-list: [], value-range: [0, 4294967295, 1]) | ||
#: int = field(metadata={'siid': 18, 'piid': 13, 'access': ['read', 'notify']}) | ||
# piid: 14 (): (uint32, unit: None) (acc: ['read', 'notify'], value-list: [], value-range: [0, 4294967295, 1]) | ||
#: int = field(metadata={'siid': 18, 'piid': 14, 'access': ['read', 'notify']}) | ||
# piid: 15 (): (uint32, unit: None) (acc: ['read', 'notify'], value-list: [], value-range: [0, 4294967295, 1]) | ||
#: int = field(metadata={'siid': 18, 'piid': 15, 'access': ['read', 'notify']}) | ||
# piid: 16 (): (uint32, unit: None) (acc: ['read', 'notify'], value-list: [], value-range: [0, 4294967295, 1]) | ||
#: int = field(metadata={'siid': 18, 'piid': 16, 'access': ['read', 'notify']}) | ||
# piid: 17 (): (uint16, unit: None) (acc: ['read', 'notify'], value-list: [], value-range: [0, 100, 1]) | ||
#: int = field(metadata={'siid': 18, 'piid': 17, 'access': ['read', 'notify']}) | ||
# piid: 18 (): (uint8, unit: None) (acc: ['read', 'notify'], value-list: [{'value': 0, 'description': ''}, {'value': 1, 'description': ''}], value-range: None) | ||
#: int = field(metadata={'siid': 18, 'piid': 18, 'access': ['read', 'notify']}) | ||
# siid 19: (consumable): 3 props, 0 actions | ||
# piid: 1 (life-sieve): (string, unit: None) (acc: ['read', 'write'], value-list: [], value-range: None) | ||
"life_sieve": {"siid": 19, "piid": 1}, | ||
# piid: 2 (life-brush-side): (string, unit: None) (acc: ['read', 'write'], value-list: [], value-range: None) | ||
"life_brush_side": {"siid": 19, "piid": 2}, | ||
# piid: 3 (life-brush-main): (string, unit: None) (acc: ['read', 'write'], value-list: [], value-range: None) | ||
"life_brush_main": {"siid": 19, "piid": 3}, | ||
# siid 20: (annoy): 3 props, 0 actions | ||
# piid: 1 (enable): (bool, unit: None) (acc: ['read', 'write'], value-list: [], value-range: None) | ||
"timer_enable": {"siid": 20, "piid": 1}, | ||
# piid: 2 (start-time): (string, unit: None) (acc: ['read', 'write'], value-list: [], value-range: None) | ||
"start_time": {"siid": 20, "piid": 2}, | ||
# piid: 3 (stop-time): (string, unit: None) (acc: ['read', 'write'], value-list: [], value-range: None) | ||
"stop_time": {"siid": 20, "piid": 3}, | ||
# siid 21: (remote): 2 props, 3 actions | ||
# piid: 1 (deg): (string, unit: None) (acc: ['write'], value-list: [], value-range: None) | ||
"deg": {"siid": 21, "piid": 1, "access": ["write"]}, | ||
# piid: 2 (speed): (string, unit: None) (acc: ['write'], value-list: [], value-range: None) | ||
"speed": {"siid": 21, "piid": 2, "access": ["write"]}, | ||
# siid 22: (warn): 1 props, 0 actions | ||
# siid 23: (map): 3 props, 1 actions | ||
# piid: 1 (map-view): (string, unit: None) (acc: ['read', 'notify'], value-list: [], value-range: None) | ||
"map_view": {"siid": 23, "piid": 1}, | ||
# piid: 2 (frame-info): (string, unit: None) (acc: ['write'], value-list: [], value-range: None) | ||
"frame_info": {"siid": 23, "piid": 2}, | ||
# siid 24: (audio): 2 props, 3 actions | ||
# piid: 1 (volume): (int32, unit: None) (acc: ['read', 'write', 'notify'], value-list: [], value-range: [0, 100, 1]) | ||
"volume": {"siid": 24, "piid": 1}, | ||
# piid: 3 (voice package): (string, unit: none) (acc: ['read', 'write'], value-list: [], value-range: None) | ||
"voice_package": {"siid": 24, "piid": 3} | ||
# siid 25: (): 1 props, 0 actions | ||
# piid: 1 (): (string, unit: None) (acc: ['read', 'notify'], value-list: [], value-range: None) | ||
# : str = field(metadata={'siid': 25, 'piid': 1, 'access': ['read', 'notify']}) | ||
} | ||
|
||
|
||
class ChargingState(Enum): | ||
unknown = -1 | ||
charging = 1 | ||
not_charging = 2 | ||
charging2 = 4 | ||
go_charging = 5 | ||
|
||
|
||
class CleaningMode(Enum): | ||
unknown = -1 | ||
quiet = 0 | ||
default = 1 | ||
medium = 2 | ||
strong = 3 | ||
|
||
|
||
# TODO: Replace with correct operating modes | ||
class OperatingMode(Enum): | ||
unknown = -1 | ||
fixme0 = 0 | ||
fixme1 = 1 | ||
fixme14 = 14 | ||
fixme17 = 17 | ||
|
||
|
||
class FaultStatus(Enum): | ||
unknown = -1 | ||
no_faults = 0 | ||
|
||
|
||
class DeviceStatus(Enum): | ||
unknown = -1 | ||
sweeping = 1 | ||
idle = 2 | ||
paused = 3 | ||
error = 4 | ||
go_charging = 5 | ||
charging = 6 | ||
|
||
|
||
class DreameVacuumStatus: | ||
def __init__(self, data): | ||
self.data = data | ||
|
||
@property | ||
def battery_level(self) -> str: | ||
return self.data["battery_level"] | ||
|
||
@property | ||
def brush_left_time(self) -> str: | ||
return self.data["brush_left_time"] | ||
|
||
@property | ||
def brush_left_time2(self) -> str: | ||
return self.data["brush_left_time2"] | ||
|
||
@property | ||
def brush_life_level2(self) -> str: | ||
return self.data["brush_life_level2"] | ||
|
||
@property | ||
def brush_life_level(self) -> str: | ||
return self.data["brush_life_level"] | ||
|
||
@property | ||
def filter_left_time(self) -> str: | ||
return self.data["filter_left_time"] | ||
|
||
@property | ||
def filter_life_level(self) -> str: | ||
return self.data["filter_life_level"] | ||
|
||
@property | ||
def device_fault(self) -> FaultStatus: | ||
try: | ||
return FaultStatus(self.data["device_fault"]) | ||
except ValueError: | ||
_LOGGER.error("Unknown FaultStatus (%s)", self.data["device_fault"]) | ||
return FaultStatus.Unknown | ||
|
||
@property | ||
def charging_state(self) -> ChargingState: | ||
try: | ||
return ChargingState(self.data["charging_state"]) | ||
except ValueError: | ||
_LOGGER.error("Unknown ChargingStats (%s)", self.data["charging_state"]) | ||
return ChargingState.Unknown | ||
|
||
@property | ||
def operating_mode(self) -> OperatingMode: | ||
try: | ||
return OperatingMode(self.data["operating_mode"]) | ||
except ValueError: | ||
_LOGGER.error("Unknown OperatingMode (%s)", self.data["operating_mode"]) | ||
return OperatingMode.Unknown | ||
|
||
@property | ||
def cleaning_mode(self) -> CleaningMode: | ||
try: | ||
return CleaningMode(self.data["cleaning_mode"]) | ||
except ValueError: | ||
_LOGGER.error("Unknown CleaningMode (%s)", self.data["cleaning_mode"]) | ||
return CleaningMode.Unknown | ||
|
||
@property | ||
def device_status(self) -> DeviceStatus: | ||
try: | ||
return DeviceStatus(self.data["device_status"]) | ||
except TypeError: | ||
_LOGGER.error("Unknown DeviceStatus (%s)", self.data["device_status"]) | ||
return DeviceStatus.Unknown | ||
|
||
@property | ||
def life_sieve(self) -> str: | ||
return self.data["life_sieve"] | ||
|
||
@property | ||
def life_brush_side(self) -> str: | ||
return self.data["life_brush_side"] | ||
|
||
@property | ||
def life_brush_main(self) -> str: | ||
return self.data["life_brush_main"] | ||
|
||
@property | ||
def timer_enable(self) -> str: | ||
return self.data["timer_enable"] | ||
|
||
@property | ||
def start_time(self) -> str: | ||
return self.data["start_time"] | ||
|
||
@property | ||
def stop_time(self) -> str: | ||
return self.data["stop_time"] | ||
|
||
@property | ||
def map_view(self) -> str: | ||
return self.data["map_view"] | ||
|
||
@property | ||
def volume(self) -> str: | ||
return self.data["volume"] | ||
|
||
@property | ||
def voice_package(self) -> str: | ||
return self.data["voice_package"] | ||
|
||
|
||
class DreameVacuumMiot(MiotDevice): | ||
"""Interface for Vacuum 1C STYTJ01ZHM (dreame.vacuum.mc1808)""" | ||
|
||
def __init__( | ||
self, ip: str, token: str = None, start_id: int = 0, debug: int = 0 | ||
) -> None: | ||
super().__init__(_MAPPING, ip, token, start_id, debug) | ||
|
||
@command( | ||
default_output=format_output( | ||
"\n", | ||
"Battery level: {result.battery_level}\n" | ||
"Brush life level: {result.brush_life_level}\n" | ||
"Brush left time: {result.brush_left_time}\n" | ||
"Charging state: {result.charging_state.name}\n" | ||
"Cleaning mode: {result.cleaning_mode.name}\n" | ||
"Device fault: {result.device_fault.name}\n" | ||
"Device status: {result.device_status.name}\n" | ||
"Filter left level: {result.filter_left_time}\n" | ||
"Filter life level: {result.filter_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" | ||
"Map view: {result.map_view}\n" | ||
"Operating mode: {result.operating_mode.name}\n" | ||
"Side cleaning brush left time: {result.brush_left_time2}\n" | ||
"Side cleaning brush life level: {result.brush_life_level2}\n" | ||
"Timer enabled: {result.timer_enable}\n" | ||
"Timer start time: {result.start_time}\n" | ||
"Timer stop time: {result.stop_time}\n" | ||
"Voice package: {result.voice_package}\n" | ||
"Volume: {result.volume}\n", | ||
) | ||
) | ||
def status(self) -> DreameVacuumStatus: | ||
"""State of the vacuum.""" | ||
|
||
return DreameVacuumStatus( | ||
{ | ||
prop["did"]: prop["value"] if prop["code"] == 0 else None | ||
for prop in self.get_properties_for_mapping() | ||
} | ||
) | ||
|
||
def send_action(self, siid, aiid, params=None): | ||
"""Send action to device.""" | ||
|
||
# {"did":"<mydeviceID>","siid":18,"aiid":1,"in":[{"piid":1,"value":2}] | ||
if params is None: | ||
params = [] | ||
payload = { | ||
"did": f"call-{siid}-{aiid}", | ||
"siid": siid, | ||
"aiid": aiid, | ||
"in": params, | ||
} | ||
return self.send("action", payload) | ||
|
||
# siid 3: (Robot Cleaner): 2 props, 2 actions | ||
# aiid 1 Start Sweep: in: [] -> out: [] | ||
@command() | ||
def start(self) -> None: | ||
"""Start cleaning.""" | ||
return self.send_action(3, 1) | ||
|
||
# aiid 2 Stop Sweeping: in: [] -> out: [] | ||
@command() | ||
def stop(self) -> None: | ||
"""Stop cleaning.""" | ||
return self.send_action(3, 2) | ||
|
||
# siid 2: (Battery): 2 props, 1 actions | ||
# aiid 1 Start Charge: in: [] -> out: [] | ||
@command() | ||
def home(self) -> None: | ||
"""Return to home.""" | ||
return self.send_action(2, 1) | ||
|
||
# siid 17: (Identify): 0 props, 1 actions | ||
# aiid 1 Identify: in: [] -> out: [] | ||
@command() | ||
def identify(self) -> None: | ||
"""Locate the device (i am here).""" | ||
return self.send_action(17, 1) | ||
|
||
# siid 26: (Main Cleaning Brush): 2 props, 1 actions | ||
# aiid 1 Reset Brush Life: in: [] -> out: [] | ||
@command() | ||
def reset_mainbrush_life(self) -> None: | ||
"""Reset main brush life.""" | ||
return self.send_action(26, 1) | ||
|
||
# siid 27: (Filter): 2 props, 1 actions | ||
# aiid 1 Reset Filter Life: in: [] -> out: [] | ||
@command() | ||
def reset_filter_life(self) -> None: | ||
"""Reset filter life.""" | ||
return self.send_action(27, 1) | ||
|
||
# siid 28: (Side Cleaning Brush): 2 props, 1 actions | ||
# aiid 1 Reset Brush Life: in: [] -> out: [] | ||
@command() | ||
def reset_sidebrush_life(self) -> None: | ||
"""Reset side brush life.""" | ||
return self.send_action(28, 1) | ||
|
||
def get_properties_for_mapping(self) -> list: | ||
"""Retrieve raw properties based on mapping.""" | ||
|
||
# We send property key in "did" because it's sent back via response and we can identify the property. | ||
properties = [{"did": k, **v} for k, v in self.mapping.items()] | ||
|
||
return self.get_properties( | ||
properties, property_getter="get_properties", max_properties=10 | ||
) |
Oops, something went wrong.