roborock.devices.traits.b01.q7

Traits for Q7 B01 devices. Potentially other devices may fall into this category in the future.

  1"""Traits for Q7 B01 devices.
  2Potentially other devices may fall into this category in the future."""
  3
  4from typing import Any
  5
  6from roborock import B01Props
  7from roborock.data.b01_q7.b01_q7_code_mappings import (
  8    CleanPathPreferenceMapping,
  9    CleanRepeatMapping,
 10    CleanTaskTypeMapping,
 11    CleanTypeMapping,
 12    SCDeviceCleanParam,
 13    SCWindMapping,
 14    WaterLevelMapping,
 15)
 16from roborock.devices.rpc.b01_q7_channel import send_decoded_command
 17from roborock.devices.traits import Trait
 18from roborock.devices.transport.mqtt_channel import MqttChannel
 19from roborock.protocols.b01_q7_protocol import CommandType, ParamsType, Q7RequestMessage
 20from roborock.roborock_message import RoborockB01Props
 21from roborock.roborock_typing import RoborockB01Q7Methods
 22
 23from .clean_summary import CleanSummaryTrait
 24
 25__all__ = [
 26    "Q7PropertiesApi",
 27    "CleanSummaryTrait",
 28]
 29
 30
 31class Q7PropertiesApi(Trait):
 32    """API for interacting with B01 devices."""
 33
 34    clean_summary: CleanSummaryTrait
 35    """Trait for clean records / clean summary (Q7 `service.get_record_list`)."""
 36
 37    def __init__(self, channel: MqttChannel) -> None:
 38        """Initialize the B01Props API."""
 39        self._channel = channel
 40        self.clean_summary = CleanSummaryTrait(channel)
 41
 42    async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None:
 43        """Query the device for the values of the given Q7 properties."""
 44        result = await self.send(
 45            RoborockB01Q7Methods.GET_PROP,
 46            {"property": props},
 47        )
 48        if not isinstance(result, dict):
 49            raise TypeError(f"Unexpected response type for GET_PROP: {type(result).__name__}: {result!r}")
 50        return B01Props.from_dict(result)
 51
 52    async def set_prop(self, prop: RoborockB01Props, value: Any) -> None:
 53        """Set a property on the device."""
 54        await self.send(
 55            command=RoborockB01Q7Methods.SET_PROP,
 56            params={prop: value},
 57        )
 58
 59    async def set_fan_speed(self, fan_speed: SCWindMapping) -> None:
 60        """Set the fan speed (wind)."""
 61        await self.set_prop(RoborockB01Props.WIND, fan_speed.code)
 62
 63    async def set_water_level(self, water_level: WaterLevelMapping) -> None:
 64        """Set the water level (water)."""
 65        await self.set_prop(RoborockB01Props.WATER, water_level.code)
 66
 67    async def set_mode(self, mode: CleanTypeMapping) -> None:
 68        """Set the cleaning mode (vacuum, mop, or vacuum and mop)."""
 69        await self.set_prop(RoborockB01Props.MODE, mode.code)
 70
 71    async def set_clean_path_preference(self, preference: CleanPathPreferenceMapping) -> None:
 72        """Set the cleaning path preference (route)."""
 73        await self.set_prop(RoborockB01Props.CLEAN_PATH_PREFERENCE, preference.code)
 74
 75    async def set_repeat_state(self, repeat: CleanRepeatMapping) -> None:
 76        """Set the cleaning repeat state (cycles)."""
 77        await self.set_prop(RoborockB01Props.REPEAT_STATE, repeat.code)
 78
 79    async def start_clean(self) -> None:
 80        """Start cleaning."""
 81        await self.send(
 82            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
 83            params={
 84                "clean_type": CleanTaskTypeMapping.ALL.code,
 85                "ctrl_value": SCDeviceCleanParam.START.code,
 86                "room_ids": [],
 87            },
 88        )
 89
 90    async def pause_clean(self) -> None:
 91        """Pause cleaning."""
 92        await self.send(
 93            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
 94            params={
 95                "clean_type": CleanTaskTypeMapping.ALL.code,
 96                "ctrl_value": SCDeviceCleanParam.PAUSE.code,
 97                "room_ids": [],
 98            },
 99        )
100
101    async def stop_clean(self) -> None:
102        """Stop cleaning."""
103        await self.send(
104            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
105            params={
106                "clean_type": CleanTaskTypeMapping.ALL.code,
107                "ctrl_value": SCDeviceCleanParam.STOP.code,
108                "room_ids": [],
109            },
110        )
111
112    async def return_to_dock(self) -> None:
113        """Return to dock."""
114        await self.send(
115            command=RoborockB01Q7Methods.START_RECHARGE,
116            params={},
117        )
118
119    async def find_me(self) -> None:
120        """Locate the robot."""
121        await self.send(
122            command=RoborockB01Q7Methods.FIND_DEVICE,
123            params={},
124        )
125
126    async def send(self, command: CommandType, params: ParamsType) -> Any:
127        """Send a command to the device."""
128        return await send_decoded_command(
129            self._channel,
130            Q7RequestMessage(dps=10000, command=command, params=params),
131        )
132
133
134def create(channel: MqttChannel) -> Q7PropertiesApi:
135    """Create traits for B01 devices."""
136    return Q7PropertiesApi(channel)
class Q7PropertiesApi(roborock.devices.traits.Trait):
 32class Q7PropertiesApi(Trait):
 33    """API for interacting with B01 devices."""
 34
 35    clean_summary: CleanSummaryTrait
 36    """Trait for clean records / clean summary (Q7 `service.get_record_list`)."""
 37
 38    def __init__(self, channel: MqttChannel) -> None:
 39        """Initialize the B01Props API."""
 40        self._channel = channel
 41        self.clean_summary = CleanSummaryTrait(channel)
 42
 43    async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None:
 44        """Query the device for the values of the given Q7 properties."""
 45        result = await self.send(
 46            RoborockB01Q7Methods.GET_PROP,
 47            {"property": props},
 48        )
 49        if not isinstance(result, dict):
 50            raise TypeError(f"Unexpected response type for GET_PROP: {type(result).__name__}: {result!r}")
 51        return B01Props.from_dict(result)
 52
 53    async def set_prop(self, prop: RoborockB01Props, value: Any) -> None:
 54        """Set a property on the device."""
 55        await self.send(
 56            command=RoborockB01Q7Methods.SET_PROP,
 57            params={prop: value},
 58        )
 59
 60    async def set_fan_speed(self, fan_speed: SCWindMapping) -> None:
 61        """Set the fan speed (wind)."""
 62        await self.set_prop(RoborockB01Props.WIND, fan_speed.code)
 63
 64    async def set_water_level(self, water_level: WaterLevelMapping) -> None:
 65        """Set the water level (water)."""
 66        await self.set_prop(RoborockB01Props.WATER, water_level.code)
 67
 68    async def set_mode(self, mode: CleanTypeMapping) -> None:
 69        """Set the cleaning mode (vacuum, mop, or vacuum and mop)."""
 70        await self.set_prop(RoborockB01Props.MODE, mode.code)
 71
 72    async def set_clean_path_preference(self, preference: CleanPathPreferenceMapping) -> None:
 73        """Set the cleaning path preference (route)."""
 74        await self.set_prop(RoborockB01Props.CLEAN_PATH_PREFERENCE, preference.code)
 75
 76    async def set_repeat_state(self, repeat: CleanRepeatMapping) -> None:
 77        """Set the cleaning repeat state (cycles)."""
 78        await self.set_prop(RoborockB01Props.REPEAT_STATE, repeat.code)
 79
 80    async def start_clean(self) -> None:
 81        """Start cleaning."""
 82        await self.send(
 83            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
 84            params={
 85                "clean_type": CleanTaskTypeMapping.ALL.code,
 86                "ctrl_value": SCDeviceCleanParam.START.code,
 87                "room_ids": [],
 88            },
 89        )
 90
 91    async def pause_clean(self) -> None:
 92        """Pause cleaning."""
 93        await self.send(
 94            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
 95            params={
 96                "clean_type": CleanTaskTypeMapping.ALL.code,
 97                "ctrl_value": SCDeviceCleanParam.PAUSE.code,
 98                "room_ids": [],
 99            },
100        )
101
102    async def stop_clean(self) -> None:
103        """Stop cleaning."""
104        await self.send(
105            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
106            params={
107                "clean_type": CleanTaskTypeMapping.ALL.code,
108                "ctrl_value": SCDeviceCleanParam.STOP.code,
109                "room_ids": [],
110            },
111        )
112
113    async def return_to_dock(self) -> None:
114        """Return to dock."""
115        await self.send(
116            command=RoborockB01Q7Methods.START_RECHARGE,
117            params={},
118        )
119
120    async def find_me(self) -> None:
121        """Locate the robot."""
122        await self.send(
123            command=RoborockB01Q7Methods.FIND_DEVICE,
124            params={},
125        )
126
127    async def send(self, command: CommandType, params: ParamsType) -> Any:
128        """Send a command to the device."""
129        return await send_decoded_command(
130            self._channel,
131            Q7RequestMessage(dps=10000, command=command, params=params),
132        )

API for interacting with B01 devices.

Q7PropertiesApi(channel: roborock.devices.transport.mqtt_channel.MqttChannel)
38    def __init__(self, channel: MqttChannel) -> None:
39        """Initialize the B01Props API."""
40        self._channel = channel
41        self.clean_summary = CleanSummaryTrait(channel)

Initialize the B01Props API.

clean_summary: CleanSummaryTrait

Trait for clean records / clean summary (Q7 service.get_record_list).

async def query_values( self, props: list[roborock.roborock_message.RoborockB01Props]) -> roborock.data.b01_q7.b01_q7_containers.B01Props | None:
43    async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None:
44        """Query the device for the values of the given Q7 properties."""
45        result = await self.send(
46            RoborockB01Q7Methods.GET_PROP,
47            {"property": props},
48        )
49        if not isinstance(result, dict):
50            raise TypeError(f"Unexpected response type for GET_PROP: {type(result).__name__}: {result!r}")
51        return B01Props.from_dict(result)

Query the device for the values of the given Q7 properties.

async def set_prop( self, prop: roborock.roborock_message.RoborockB01Props, value: Any) -> None:
53    async def set_prop(self, prop: RoborockB01Props, value: Any) -> None:
54        """Set a property on the device."""
55        await self.send(
56            command=RoborockB01Q7Methods.SET_PROP,
57            params={prop: value},
58        )

Set a property on the device.

async def set_fan_speed( self, fan_speed: roborock.data.b01_q7.b01_q7_code_mappings.SCWindMapping) -> None:
60    async def set_fan_speed(self, fan_speed: SCWindMapping) -> None:
61        """Set the fan speed (wind)."""
62        await self.set_prop(RoborockB01Props.WIND, fan_speed.code)

Set the fan speed (wind).

async def set_water_level( self, water_level: roborock.data.b01_q7.b01_q7_code_mappings.WaterLevelMapping) -> None:
64    async def set_water_level(self, water_level: WaterLevelMapping) -> None:
65        """Set the water level (water)."""
66        await self.set_prop(RoborockB01Props.WATER, water_level.code)

Set the water level (water).

async def set_mode( self, mode: roborock.data.b01_q7.b01_q7_code_mappings.CleanTypeMapping) -> None:
68    async def set_mode(self, mode: CleanTypeMapping) -> None:
69        """Set the cleaning mode (vacuum, mop, or vacuum and mop)."""
70        await self.set_prop(RoborockB01Props.MODE, mode.code)

Set the cleaning mode (vacuum, mop, or vacuum and mop).

async def set_clean_path_preference( self, preference: roborock.data.b01_q7.b01_q7_code_mappings.CleanPathPreferenceMapping) -> None:
72    async def set_clean_path_preference(self, preference: CleanPathPreferenceMapping) -> None:
73        """Set the cleaning path preference (route)."""
74        await self.set_prop(RoborockB01Props.CLEAN_PATH_PREFERENCE, preference.code)

Set the cleaning path preference (route).

async def set_repeat_state( self, repeat: roborock.data.b01_q7.b01_q7_code_mappings.CleanRepeatMapping) -> None:
76    async def set_repeat_state(self, repeat: CleanRepeatMapping) -> None:
77        """Set the cleaning repeat state (cycles)."""
78        await self.set_prop(RoborockB01Props.REPEAT_STATE, repeat.code)

Set the cleaning repeat state (cycles).

async def start_clean(self) -> None:
80    async def start_clean(self) -> None:
81        """Start cleaning."""
82        await self.send(
83            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
84            params={
85                "clean_type": CleanTaskTypeMapping.ALL.code,
86                "ctrl_value": SCDeviceCleanParam.START.code,
87                "room_ids": [],
88            },
89        )

Start cleaning.

async def pause_clean(self) -> None:
 91    async def pause_clean(self) -> None:
 92        """Pause cleaning."""
 93        await self.send(
 94            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
 95            params={
 96                "clean_type": CleanTaskTypeMapping.ALL.code,
 97                "ctrl_value": SCDeviceCleanParam.PAUSE.code,
 98                "room_ids": [],
 99            },
100        )

Pause cleaning.

async def stop_clean(self) -> None:
102    async def stop_clean(self) -> None:
103        """Stop cleaning."""
104        await self.send(
105            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
106            params={
107                "clean_type": CleanTaskTypeMapping.ALL.code,
108                "ctrl_value": SCDeviceCleanParam.STOP.code,
109                "room_ids": [],
110            },
111        )

Stop cleaning.

async def return_to_dock(self) -> None:
113    async def return_to_dock(self) -> None:
114        """Return to dock."""
115        await self.send(
116            command=RoborockB01Q7Methods.START_RECHARGE,
117            params={},
118        )

Return to dock.

async def find_me(self) -> None:
120    async def find_me(self) -> None:
121        """Locate the robot."""
122        await self.send(
123            command=RoborockB01Q7Methods.FIND_DEVICE,
124            params={},
125        )

Locate the robot.

async def send( self, command: roborock.roborock_typing.RoborockB01Q7Methods | str, params: list | dict | int | None) -> Any:
127    async def send(self, command: CommandType, params: ParamsType) -> Any:
128        """Send a command to the device."""
129        return await send_decoded_command(
130            self._channel,
131            Q7RequestMessage(dps=10000, command=command, params=params),
132        )

Send a command to the device.

27class CleanSummaryTrait(CleanRecordSummary, Trait):
28    """B01/Q7 clean summary + clean record access (via record list service)."""
29
30    def __init__(self, channel: MqttChannel) -> None:
31        """Initialize the clean summary trait.
32
33        Args:
34            channel: MQTT channel used to communicate with the device.
35        """
36        super().__init__()
37        self._channel = channel
38
39    async def refresh(self) -> None:
40        """Refresh totals and last record detail from the device."""
41        record_list = await self._get_record_list()
42
43        self.total_time = record_list.total_time
44        self.total_area = record_list.total_area
45        self.total_count = record_list.total_count
46
47        details = await self._get_clean_record_details(record_list=record_list)
48        self.last_record_detail = details[0] if details else None
49
50    async def _get_record_list(self) -> CleanRecordList:
51        """Fetch the raw device clean record list (`service.get_record_list`)."""
52        result = await send_decoded_command(
53            self._channel,
54            Q7RequestMessage(dps=10000, command=RoborockB01Q7Methods.GET_RECORD_LIST, params={}),
55        )
56
57        if not isinstance(result, dict):
58            raise TypeError(f"Unexpected response type for GET_RECORD_LIST: {type(result).__name__}: {result!r}")
59        return CleanRecordList.from_dict(result)
60
61    async def _get_clean_record_details(self, *, record_list: CleanRecordList) -> list[CleanRecordDetail]:
62        """Return parsed record detail objects (newest-first)."""
63        details: list[CleanRecordDetail] = []
64        for item in record_list.record_list:
65            try:
66                parsed = item.detail_parsed
67            except RoborockException as ex:
68                # Rather than failing if something goes wrong here, we should fail and log to tell the user.
69                _LOGGER.debug("Failed to parse record detail: %s", ex)
70                continue
71            if parsed is not None:
72                details.append(parsed)
73
74        # The server returns the newest record at the end of record_list; reverse so newest is first (index 0).
75        details.reverse()
76        return details

B01/Q7 clean summary + clean record access (via record list service).

CleanSummaryTrait(channel: roborock.devices.transport.mqtt_channel.MqttChannel)
30    def __init__(self, channel: MqttChannel) -> None:
31        """Initialize the clean summary trait.
32
33        Args:
34            channel: MQTT channel used to communicate with the device.
35        """
36        super().__init__()
37        self._channel = channel

Initialize the clean summary trait.

Args: channel: MQTT channel used to communicate with the device.

async def refresh(self) -> None:
39    async def refresh(self) -> None:
40        """Refresh totals and last record detail from the device."""
41        record_list = await self._get_record_list()
42
43        self.total_time = record_list.total_time
44        self.total_area = record_list.total_area
45        self.total_count = record_list.total_count
46
47        details = await self._get_clean_record_details(record_list=record_list)
48        self.last_record_detail = details[0] if details else None

Refresh totals and last record detail from the device.