Skip to content

Commit

Permalink
[Add] Add 360 Heurist support
Browse files Browse the repository at this point in the history
  • Loading branch information
shenxn committed Mar 15, 2021
1 parent 3a63b4b commit a923da9
Show file tree
Hide file tree
Showing 9 changed files with 410 additions and 168 deletions.
11 changes: 9 additions & 2 deletions libdyson/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .const import (
DEVICE_TYPE_360_EYE,
DEVICE_TYPE_360_HEURIST,
DEVICE_TYPE_PURE_COOL,
DEVICE_TYPE_PURE_COOL_DESK,
DEVICE_TYPE_PURE_COOL_LINK,
Expand All @@ -12,14 +13,18 @@
DEVICE_TYPE_PURE_HOT_COOL_LINK,
DEVICE_TYPE_PURE_HUMIDIFY_COOL,
)
from .const import CleaningMode # noqa: F401
from .const import CleaningType # noqa: F401
from .const import DEVICE_TYPE_NAMES # noqa: F401
from .const import HumidifyOscillationMode # noqa: F401
from .const import MessageType # noqa: F401
from .const import VacuumEyePowerMode # noqa: F401
from .const import VacuumHeuristPowerMode # noqa: F401
from .const import VacuumState # noqa: F401
from .const import WaterHardness # noqa: F401
from .discovery import DysonDiscovery # noqa: F401
from .dyson_360_eye import Dyson360Eye
from .dyson_360_eye import VacuumPowerMode # noqa: F401
from .dyson_360_eye import VacuumState # noqa: F401
from .dyson_360_heurist import Dyson360Heurist
from .dyson_device import DysonDevice
from .dyson_pure_cool import DysonPureCool
from .dyson_pure_cool_link import DysonPureCoolLink
Expand All @@ -33,6 +38,8 @@ def get_device(serial: str, credential: str, device_type: str) -> Optional[Dyson
"""Get a new DysonDevice instance."""
if device_type == DEVICE_TYPE_360_EYE:
return Dyson360Eye(serial, credential)
if device_type == DEVICE_TYPE_360_HEURIST:
return Dyson360Heurist(serial, credential)
if device_type in [
DEVICE_TYPE_PURE_COOL_LINK_DESK,
DEVICE_TYPE_PURE_COOL_LINK,
Expand Down
69 changes: 69 additions & 0 deletions libdyson/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from enum import Enum, auto

DEVICE_TYPE_360_EYE = "N223"
DEVICE_TYPE_360_HEURIST = "276"
DEVICE_TYPE_PURE_COOL_LINK = "475"
DEVICE_TYPE_PURE_COOL_LINK_DESK = "469"
DEVICE_TYPE_PURE_COOL = "438"
Expand All @@ -12,6 +13,7 @@

DEVICE_TYPE_NAMES = {
DEVICE_TYPE_360_EYE: "360 Eye robot vacuum",
DEVICE_TYPE_360_HEURIST: "360 Heurist robot vacuum",
DEVICE_TYPE_PURE_COOL: "Pure Cool",
DEVICE_TYPE_PURE_COOL_DESK: "Pure Cool Desk",
DEVICE_TYPE_PURE_COOL_LINK: "Pure Cool Link",
Expand Down Expand Up @@ -56,3 +58,70 @@ class WaterHardness(Enum):
SOFT = "Soft"
MEDIUM = "Medium"
HARD = "Hard"


class VacuumState(Enum):
"""Dyson vacuum state."""

FAULT_CALL_HELPLINE = "FAULT_CALL_HELPLINE"
FAULT_CONTACT_HELPLINE = "FAULT_CONTACT_HELPLINE"
FAULT_CRITICAL = "FAULT_CRITICAL"
FAULT_GETTING_INFO = "FAULT_GETTING_INFO"
FAULT_LOST = "FAULT_LOST"
FAULT_ON_DOCK = "FAULT_ON_DOCK"
FAULT_ON_DOCK_CHARGED = "FAULT_ON_DOCK_CHARGED"
FAULT_ON_DOCK_CHARGING = "FAULT_ON_DOCK_CHARGING"
FAULT_REPLACE_ON_DOCK = "FAULT_REPLACE_ON_DOCK"
FAULT_RETURN_TO_DOCK = "FAULT_RETURN_TO_DOCK"
FAULT_RUNNING_DIAGNOSTIC = "FAULT_RUNNING_DIAGNOSTIC"
FAULT_USER_RECOVERABLE = "FAULT_USER_RECOVERABLE"
FULL_CLEAN_ABANDONED = "FULL_CLEAN_ABANDONED"
FULL_CLEAN_ABORTED = "FULL_CLEAN_ABORTED"
FULL_CLEAN_CHARGING = "FULL_CLEAN_CHARGING"
FULL_CLEAN_DISCOVERING = "FULL_CLEAN_DISCOVERING"
FULL_CLEAN_FINISHED = "FULL_CLEAN_FINISHED"
FULL_CLEAN_INITIATED = "FULL_CLEAN_INITIATED"
FULL_CLEAN_NEEDS_CHARGE = "FULL_CLEAN_NEEDS_CHARGE"
FULL_CLEAN_PAUSED = "FULL_CLEAN_PAUSED"
FULL_CLEAN_RUNNING = "FULL_CLEAN_RUNNING"
FULL_CLEAN_TRAVERSING = "FULL_CLEAN_TRAVERSING"
INACTIVE_CHARGED = "INACTIVE_CHARGED"
INACTIVE_CHARGING = "INACTIVE_CHARGING"
INACTIVE_DISCHARGING = "INACTIVE_DISCHARGING"
MAPPING_ABORTED = "MAPPING_ABORTED"
MAPPING_CHARGING = "MAPPING_CHARGING"
MAPPING_FINISHED = "MAPPING_FINISHED"
MAPPING_INITIATED = "MAPPING_INITIATED"
MAPPING_NEEDS_CHARGE = "MAPPING_NEEDS_CHARGE"
MAPPING_PAUSED = "MAPPING_PAUSED"
MAPPING_RUNNING = "MAPPING_RUNNING"


class VacuumEyePowerMode(Enum):
"""Dyson 360 Eye power mode."""

QUIET = "halfPower"
MAX = "fullPower"


class VacuumHeuristPowerMode(Enum):
"""Dyson 360 Heurist power mode."""

QUITE = "1"
HIGH = "2"
MAX = "3"


class CleaningType(Enum):
"""Vacuum cleaning type."""

IMMEDIATE = "immediate"
MANUAL = "manual"
SCHEDULED = "scheduled"


class CleaningMode(Enum):
"""Vacuum cleaning mode."""

GLOBAL = "global"
ZONE_CONFIGURED = "zoneConfigured"
136 changes: 6 additions & 130 deletions libdyson/dyson_360_eye.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,10 @@
"""Dyson 360 Eye vacuum robot."""
from enum import Enum
from typing import Optional, Tuple

from .const import DEVICE_TYPE_360_EYE
from .dyson_device import DysonDevice
from .const import DEVICE_TYPE_360_EYE, VacuumEyePowerMode
from .dyson_vacuum_device import DysonVacuumDevice


class VacuumState(Enum):
"""Dyson vacuum state."""

FAULT_CALL_HELPLINE = "FAULT_CALL_HELPLINE"
FAULT_CONTACT_HELPLINE = "FAULT_CONTACT_HELPLINE"
FAULT_CRITICAL = "FAULT_CRITICAL"
FAULT_GETTING_INFO = "FAULT_GETTING_INFO"
FAULT_LOST = "FAULT_LOST"
FAULT_ON_DOCK = "FAULT_ON_DOCK"
FAULT_ON_DOCK_CHARGED = "FAULT_ON_DOCK_CHARGED"
FAULT_ON_DOCK_CHARGING = "FAULT_ON_DOCK_CHARGING"
FAULT_REPLACE_ON_DOCK = "FAULT_REPLACE_ON_DOCK"
FAULT_RETURN_TO_DOCK = "FAULT_RETURN_TO_DOCK"
FAULT_RUNNING_DIAGNOSTIC = "FAULT_RUNNING_DIAGNOSTIC"
FAULT_USER_RECOVERABLE = "FAULT_USER_RECOVERABLE"
FULL_CLEAN_ABANDONED = "FULL_CLEAN_ABANDONED"
FULL_CLEAN_ABORTED = "FULL_CLEAN_ABORTED"
FULL_CLEAN_CHARGING = "FULL_CLEAN_CHARGING"
FULL_CLEAN_DISCOVERING = "FULL_CLEAN_DISCOVERING"
FULL_CLEAN_FINISHED = "FULL_CLEAN_FINISHED"
FULL_CLEAN_INITIATED = "FULL_CLEAN_INITIATED"
FULL_CLEAN_NEEDS_CHARGE = "FULL_CLEAN_NEEDS_CHARGE"
FULL_CLEAN_PAUSED = "FULL_CLEAN_PAUSED"
FULL_CLEAN_RUNNING = "FULL_CLEAN_RUNNING"
FULL_CLEAN_TRAVERSING = "FULL_CLEAN_TRAVERSING"
INACTIVE_CHARGED = "INACTIVE_CHARGED"
INACTIVE_CHARGING = "INACTIVE_CHARGING"
INACTIVE_DISCHARGING = "INACTIVE_DISCHARGING"
MAPPING_ABORTED = "MAPPING_ABORTED"
MAPPING_CHARGING = "MAPPING_CHARGING"
MAPPING_FINISHED = "MAPPING_FINISHED"
MAPPING_INITIATED = "MAPPING_INITIATED"
MAPPING_NEEDS_CHARGE = "MAPPING_NEEDS_CHARGE"
MAPPING_PAUSED = "MAPPING_PAUSED"
MAPPING_RUNNING = "MAPPING_RUNNING"


class VacuumPowerMode(Enum):
"""Dyson vacuum power mode."""

QUIET = "halfPower"
MAX = "fullPower"


class CleaningType(Enum):
"""Vacuum cleaning type."""

IMMEDIATE = "immediate"
MANUAL = "manual"
Scheduled = "scheduled"


class Dyson360Eye(DysonDevice):
class Dyson360Eye(DysonVacuumDevice):
"""Dyson 360 Eye device."""

@property
Expand All @@ -67,85 +13,15 @@ def device_type(self) -> str:
return DEVICE_TYPE_360_EYE

@property
def _status_topic(self) -> str:
"""MQTT status topic."""
return f"{self.device_type}/{self._serial}/status"

@property
def state(self) -> VacuumPowerMode:
"""State of the device."""
return VacuumState(
self._status["state"]
if "state" in self._status
else self._status["newstate"]
)

@property
def power_mode(self) -> VacuumPowerMode:
def power_mode(self) -> VacuumEyePowerMode:
"""Power mode of the device."""
return VacuumPowerMode(self._status["currentVacuumPowerMode"])

@property
def cleaning_type(self) -> Optional[CleaningType]:
"""Return the type of the current cleaning task."""
cleaning_type = self._status["fullCleanType"]
if cleaning_type == "":
return None
return CleaningType(cleaning_type)

@property
def cleaning_id(self) -> Optional[str]:
"""Return the id of the current cleaning task."""
cleaning_id = self._status["cleanId"]
if cleaning_id == "":
return None
return cleaning_id

@property
def battery_level(self) -> int:
"""Battery level of the device in percentage."""
return self._status["batteryChargeLevel"]

@property
def position(self) -> Optional[Tuple[int, int]]:
"""Position (x, y) of the device."""
if (
"globalPosition" in self._status
and len(self._status["globalPosition"]) == 2
):
return tuple(self._status["globalPosition"])
return None

@property
def is_charging(self) -> bool:
"""Whether the device is charging."""
return self.state in [
VacuumState.INACTIVE_CHARGING,
VacuumState.INACTIVE_CHARGED,
VacuumState.FULL_CLEAN_CHARGING,
VacuumState.MAPPING_CHARGING,
]

def _update_status(self, payload: dict) -> None:
self._status = payload
return VacuumEyePowerMode(self._status["currentVacuumPowerMode"])

def start(self) -> None:
"""Start cleaning."""
self._send_command("START", {"fullCleanType": "immediate"})

def pause(self) -> None:
"""Pause cleaning."""
self._send_command("PAUSE")

def resume(self) -> None:
"""Resume cleaning."""
self._send_command("RESUME")

def abort(self) -> None:
"""Abort cleaning."""
self._send_command("ABORT")

def set_power_mode(self, power_mode: VacuumPowerMode) -> None:
def set_power_mode(self, power_mode: VacuumEyePowerMode) -> None:
"""Set power mode."""
self._send_command(
"STATE-SET",
Expand Down
64 changes: 64 additions & 0 deletions libdyson/dyson_360_heurist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""Dyson 360 Heurist vacuum robot."""

from typing import Optional

from .const import DEVICE_TYPE_360_HEURIST, CleaningMode, VacuumHeuristPowerMode
from .dyson_vacuum_device import DysonVacuumDevice


class Dyson360Heurist(DysonVacuumDevice):
"""Dyson 360 Heurist device."""

@property
def device_type(self) -> str:
"""Return the device type."""
return DEVICE_TYPE_360_HEURIST

@property
def current_power_mode(self) -> VacuumHeuristPowerMode:
"""Return current power mode."""
return VacuumHeuristPowerMode(self._status["currentVacuumPowerMode"])

@property
def default_power_mode(self) -> VacuumHeuristPowerMode:
"""Return default power mode."""
return VacuumHeuristPowerMode(self._status["defaultVacuumPowerMode"])

@property
def current_cleaning_mode(self) -> CleaningMode:
"""Return current cleaning mode."""
return CleaningMode(self._status["currentCleaningMode"])

@property
def default_cleaning_mode(self) -> CleaningMode:
"""Return default cleaning mode."""
return CleaningMode(self._status["defaultCleaningMode"])

@property
def is_bin_full(self) -> bool:
"""Return if the bin is full."""
airways = self._status.get("faults", {}).get("AIRWAYS")
if airways is None:
return False
return (
airways.get("active") is True and airways.get("description") == "1.0.-1"
) # Not sure what this means

def _send_command(self, command: str, data: Optional[dict] = None):
if data is None:
data = {}
data["mode-reason"] = "LAPP"
super()._send_command(command, data)

def start_all_zones(self) -> None:
"""Start cleaning of all zones."""
self._send_command(
"START", {"cleaningMode": "global", "fullCleanType": "immediate"}
)

def set_default_power_mode(self, power_mode: VacuumHeuristPowerMode) -> None:
"""Set default power mode."""
self._send_command(
"STATE-SET",
{"defaults": {"defaultVacuumPowerMode": power_mode.value}},
)
Loading

0 comments on commit a923da9

Please sign in to comment.