Skip to content
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
356 changes: 267 additions & 89 deletions roborock/data/zeo/zeo_code_mappings.py

Large diffs are not rendered by default.

151 changes: 151 additions & 0 deletions roborock/data/zeo/zeo_containers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""Data containers for Zeo (washing machine / dryer) devices."""

from dataclasses import dataclass

from ..containers import RoborockBase
from .zeo_code_mappings import (
ZeoDryAndCare,
ZeoDryingMethod,
ZeoDryingMode,
ZeoMode,
ZeoProgram,
ZeoRinse,
ZeoSoak,
ZeoSpin,
ZeoSteamVolume,
ZeoTemperature,
)


@dataclass
class ZeoStartParams(RoborockBase):
"""All parameters that may be bundled with a START command.

``mode`` and ``program`` are mandatory for every device. Every other
field is optional — when ``None`` it is simply omitted from the MQTT
payload, so the same superset works for washers and dryers alike.
"""

mode: ZeoMode
program: ZeoProgram

# Washer
temperature: ZeoTemperature | None = None
rinse: ZeoRinse | None = None
spin: ZeoSpin | None = None
drying_mode: ZeoDryingMode | None = None

# Dryer
drying_method: ZeoDryingMethod | None = None
steam_volume: ZeoSteamVolume | None = None
total_time: int | None = None

# Optional across both device families
soak: ZeoSoak | None = None
dry_and_care: ZeoDryAndCare | None = None


# ── DP 222 (LoadCloudProgram) bitfield decoder ──────────────────────────
# The official app packs all custom-program parameters into a single
# 32-bit integer at DP 222. This mirrors WasherDpsCache.customMode in
# module 725 of the React Native plugin bundle.


@dataclass
class ZeoCustomMode(RoborockBase):
"""Decoded custom programme parameters from DP 222 (LoadCloudProgram).

Null / absent fields are represented as ``0`` which matches the
official app's behaviour (the right-shifted-and-masked value is
always non-negative).
"""

program: ZeoProgram
"""Wash program (bits 0-7)."""

mode: ZeoMode
"""Wash mode (bits 8-9)."""

temperature: ZeoTemperature
"""Temperature (bits 10-12)."""

rinse: ZeoRinse
"""Rinse cycle (bits 13-15)."""

spin: ZeoSpin
"""Spin speed (bits 16-18)."""

drying_mode: ZeoDryingMode
"""Drying mode (bits 19-21)."""

soak: ZeoSoak
"""Soak (bits 22-24)."""

dry_and_care: ZeoDryAndCare
"""Dry-care mode (bits 25-27)."""

steam_volume: ZeoSteamVolume
"""Steam volume (bits 28-30)."""

total_time_min: int = 0
"""Total programme time in minutes (from DP 239)."""

@classmethod
def from_raw(cls, raw: int, total_time_min: int | None = None) -> "ZeoCustomMode":
"""Decode a raw 32-bit custom-programme value."""
return cls(
program=ZeoProgram(raw & 0xFF),
mode=ZeoMode((raw >> 8) & 0x3),
temperature=ZeoTemperature((raw >> 10) & 0x7),
rinse=ZeoRinse((raw >> 13) & 0x7),
spin=ZeoSpin((raw >> 16) & 0x7),
drying_mode=ZeoDryingMode((raw >> 19) & 0x7),
soak=ZeoSoak((raw >> 22) & 0x7),
dry_and_care=ZeoDryAndCare((raw >> 25) & 0x7),
steam_volume=ZeoSteamVolume((raw >> 28) & 0x7),
total_time_min=total_time_min or 0,
)


@dataclass
class ZeoDryerCustomMode(RoborockBase):
"""Decoded custom programme from DP 222 for a standalone dryer.

Dryers pack a different (shorter) bitfield than washers — only 5
fields after program/mode. Mirrors ``WasherDpsCache.dryerCustomMode``
in module 725 of the plugin bundle.
"""

program: ZeoProgram
"""Drying program (bits 0-7)."""

mode: ZeoMode
"""Drying mode (bits 8-10)."""

drying_mode: ZeoDryingMode
"""Drying level (bits 11-13)."""

drying_method: ZeoDryingMethod
"""Drying method (bits 14-16)."""

steam_volume: ZeoSteamVolume
"""Steam volume (bits 17-19)."""

total_time_min: int = 0
"""Total programme time in minutes (from DP 239)."""

@classmethod
def from_raw(cls, raw: int, total_time_min: int | None = None) -> "ZeoDryerCustomMode":
"""Decode a raw 32-bit dryer custom-programme value."""
return cls(
program=ZeoProgram(raw & 0xFF),
# Dryer mode spans bits 8-10 (0x700, 3 bits) because the
# temperature field is absent from the dryer bitfield and
# bit 10 is re-allocated to mode. Washer uses 2 bits
# (0x300, bits 8-9) to make room for temperature at 10-12.
mode=ZeoMode((raw >> 8) & 0x7),
drying_mode=ZeoDryingMode((raw >> 11) & 0x7),
drying_method=ZeoDryingMethod((raw >> 14) & 0x7),
steam_volume=ZeoSteamVolume((raw >> 17) & 0x7),
total_time_min=total_time_min or 0,
)
2 changes: 2 additions & 0 deletions roborock/devices/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ async def connect(self) -> None:
await self.v1_properties.start()
elif self.b01_q10_properties is not None:
await self.b01_q10_properties.start()
if self.zeo is not None:
await self.zeo.start()
except RoborockException:
# Expected: start() can fail transiently. Unsubscribe before propagating
# so the retry by connect_loop() gets a clean channel.
Expand Down
Loading
Loading