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 zone support for Dyson 360 Heurist #10

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
62 changes: 62 additions & 0 deletions libdyson/cloud/cloud_360_heurist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Dyson 360 Heurist cloud client."""

from datetime import datetime
from typing import List

import attr
from dateutil import parser

from .cloud_device import DysonCloudDevice


@attr.s(auto_attribs=True, frozen=True)
class Zone:
"""Represent a zone within a persistent map."""

id: str
name: str
icon: str
area: float # In square meters

@classmethod
def from_raw(cls, raw: dict):
return cls(
raw["id"],
raw["name"],
raw["icon"],
raw["area"],
)


@attr.s(auto_attribs=True, frozen=True)
class PersistentMap:
"""Represent a persistent map created by the user."""

id: str
name: str
last_visited: datetime # UTC
zones_definition_last_updated_date: datetime # UTC
zones: List[Zone]

@classmethod
def from_raw(cls, raw: dict):
"""Parse raw data from cloud API."""
return cls(
raw["id"],
raw["name"],
parser.isoparse(raw["lastVisited"]),
parser.isoparse(raw["zonesDefinitionLastUpdatedDate"]),
[Zone.from_raw(raw) for raw in raw["zones"]],
)


class DysonCloud360Heurist(DysonCloudDevice):
"""Dyson 360 Heurist cloud client."""

def get_persistent_maps(self) -> List[PersistentMap]:
"""Get the persistent maps from the cloud."""
response = self._account.request(
"GET",
f"/v1/app/{self._serial}/persistent-map-metadata",
)
return [PersistentMap.from_raw(raw) for raw in response.json()]
26 changes: 25 additions & 1 deletion libdyson/dyson_360_heurist.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Dyson 360 Heurist vacuum robot."""

from typing import Optional
from typing import Optional, List

from .const import DEVICE_TYPE_360_HEURIST, CleaningMode, VacuumHeuristPowerMode
from .dyson_vacuum_device import DysonVacuumDevice
from .utils import mqtt_time_from_datetime
from .cloud.cloud_360_heurist import PersistentMap


class Dyson360Heurist(DysonVacuumDevice):
Expand Down Expand Up @@ -56,6 +58,28 @@ def start_all_zones(self) -> None:
"START", {"cleaningMode": "global", "fullCleanType": "immediate"}
)

def start_zones(self, persistent_map: PersistentMap, zone_names: List[str]) -> None:
"""Start cleaning of specific zone(s)."""
zone_ids = []
for zone_name in zone_names:
zone = next(filter(lambda z: z.name == zone_name, persistent_map.zones), None)
if zone is None:
raise ValueError("Invalid zone %s", zone_name)
zone_ids.append(zone.id)

data = {
"cleaningMode": "zoneConfigured",
"fullCleanType": "immediate",
"cleaningProgramme": {
"persistentMapId": persistent_map.id,
"zonesDefinitionLastUpdatedDate": mqtt_time_from_datetime(persistent_map.zones_definition_last_updated_date),
"orderedZones": [],
"unorderedZones": zone_ids,
},
}

self._send_command("START", data)

def set_default_power_mode(self, power_mode: VacuumHeuristPowerMode) -> None:
"""Set default power mode."""
self._send_command(
Expand Down
9 changes: 7 additions & 2 deletions libdyson/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import base64
import hashlib
import re
import time
from datetime import datetime
from typing import Tuple

from .const import DEVICE_TYPE_360_EYE
Expand All @@ -12,7 +12,12 @@

def mqtt_time():
"""Return current time string for mqtt messages."""
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
return mqtt_time_from_datetime(datetime.utcnow())


def mqtt_time_from_datetime(dt: datetime):
"""Return time string for mqtt messages."""
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")


def get_credential_from_wifi_password(wifi_password: str) -> str:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ cryptography>=3.1
requests
zeroconf
attrs
python-dateutil
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 this to setup.py as well.