roborock.devices.traits.b01

Traits for B01 devices.

 1"""Traits for B01 devices."""
 2
 3from . import q7, q10
 4from .q7 import Q7PropertiesApi
 5from .q10 import Q10PropertiesApi
 6
 7__all__ = [
 8    "Q7PropertiesApi",
 9    "Q10PropertiesApi",
10    "q7",
11    "q10",
12]
class Q7PropertiesApi(roborock.devices.traits.Trait):
 41class Q7PropertiesApi(Trait):
 42    """API for interacting with B01 Q7 devices."""
 43
 44    clean_summary: CleanSummaryTrait
 45    """Trait for clean records / clean summary (Q7 `service.get_record_list`)."""
 46
 47    map: MapTrait
 48    """Trait for map list metadata + raw map payload retrieval."""
 49
 50    map_content: MapContentTrait
 51    """Trait for fetching parsed current map content."""
 52
 53    def __init__(
 54        self,
 55        rpc_channel: Q7RpcChannel,
 56        map_rpc_channel: Q7MapRpcChannel,
 57        device: HomeDataDevice,
 58        product: HomeDataProduct,
 59    ) -> None:
 60        """Initialize the Q7 API."""
 61        self._rpc_channel = rpc_channel
 62        self._map_rpc_channel = map_rpc_channel
 63        self._device = device
 64        self._product = product
 65
 66        if not device.sn or not product.model:
 67            raise ValueError("B01 Q7 map content requires device serial number and product model metadata")
 68
 69        self.clean_summary = CleanSummaryTrait(rpc_channel)
 70        self.map = MapTrait(rpc_channel)
 71        self.map_content = MapContentTrait(
 72            self._map_rpc_channel,
 73            self.map,
 74        )
 75
 76    async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None:
 77        """Query the device for the values of the given Q7 properties."""
 78        result = await self.send(
 79            RoborockB01Q7Methods.GET_PROP,
 80            {"property": props},
 81        )
 82        if not isinstance(result, dict):
 83            raise TypeError(f"Unexpected response type for GET_PROP: {type(result).__name__}: {result!r}")
 84        return B01Props.from_dict(result)
 85
 86    async def set_prop(self, prop: RoborockB01Props, value: Any) -> None:
 87        """Set a property on the device."""
 88        await self.send(
 89            command=RoborockB01Q7Methods.SET_PROP,
 90            params={prop: value},
 91        )
 92
 93    async def set_fan_speed(self, fan_speed: SCWindMapping) -> None:
 94        """Set the fan speed (wind)."""
 95        await self.set_prop(RoborockB01Props.WIND, fan_speed.code)
 96
 97    async def set_water_level(self, water_level: WaterLevelMapping) -> None:
 98        """Set the water level (water)."""
 99        await self.set_prop(RoborockB01Props.WATER, water_level.code)
100
101    async def set_mode(self, mode: CleanTypeMapping) -> None:
102        """Set the cleaning mode (vacuum, mop, or vacuum and mop)."""
103        await self.set_prop(RoborockB01Props.MODE, mode.code)
104
105    async def set_clean_path_preference(self, preference: CleanPathPreferenceMapping) -> None:
106        """Set the cleaning path preference (route)."""
107        await self.set_prop(RoborockB01Props.CLEAN_PATH_PREFERENCE, preference.code)
108
109    async def set_repeat_state(self, repeat: CleanRepeatMapping) -> None:
110        """Set the cleaning repeat state (cycles)."""
111        await self.set_prop(RoborockB01Props.REPEAT_STATE, repeat.code)
112
113    async def set_volume(self, volume: int) -> None:
114        """Set the robot voice volume (0-100)."""
115        await self.set_prop(RoborockB01Props.VOLUME, volume)
116
117    async def set_child_lock(self, enabled: bool) -> None:
118        """Enable or disable the child lock."""
119        await self.set_prop(RoborockB01Props.CHILD_LOCK, int(enabled))
120
121    async def set_do_not_disturb(self, enabled: bool, begin_time: int, end_time: int) -> None:
122        """Configure do-not-disturb.
123
124        The device expects all three values together via ``service.set_quiet_time``
125        (individual ``prop.set`` calls are ignored). ``begin_time``/``end_time`` are
126        minutes since midnight and must be in the range 0-1439 (inclusive).
127
128        Ranges that cross midnight are supported by passing a ``begin_time`` that is
129        greater than ``end_time`` (e.g. 22:00-07:00 is ``begin_time=1320``,
130        ``end_time=420``).
131        """
132        for name, value in (("begin_time", begin_time), ("end_time", end_time)):
133            if not 0 <= value <= 1439:
134                raise ValueError(f"{name} must be between 0 and 1439 minutes since midnight, got {value}")
135        await self.send(
136            RoborockB01Q7Methods.SET_QUIET_TIME,
137            {
138                "is_open": int(enabled),
139                "quiet_begin_time": begin_time,
140                "quiet_end_time": end_time,
141            },
142        )
143
144    async def start_clean(self) -> None:
145        """Start cleaning."""
146        await self.send(
147            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
148            params={
149                "clean_type": CleanTaskTypeMapping.ALL.code,
150                "ctrl_value": SCDeviceCleanParam.START.code,
151                "room_ids": [],
152            },
153        )
154
155    async def clean_segments(self, segment_ids: list[int]) -> None:
156        """Start segment cleaning for the given ids (Q7 uses room ids)."""
157        await self.send(
158            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
159            params={
160                "clean_type": CleanTaskTypeMapping.ROOM.code,
161                "ctrl_value": SCDeviceCleanParam.START.code,
162                "room_ids": segment_ids,
163            },
164        )
165
166    async def pause_clean(self) -> None:
167        """Pause cleaning."""
168        await self.send(
169            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
170            params={
171                "clean_type": CleanTaskTypeMapping.ALL.code,
172                "ctrl_value": SCDeviceCleanParam.PAUSE.code,
173                "room_ids": [],
174            },
175        )
176
177    async def stop_clean(self) -> None:
178        """Stop cleaning."""
179        await self.send(
180            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
181            params={
182                "clean_type": CleanTaskTypeMapping.ALL.code,
183                "ctrl_value": SCDeviceCleanParam.STOP.code,
184                "room_ids": [],
185            },
186        )
187
188    async def return_to_dock(self) -> None:
189        """Return to dock."""
190        await self.send(
191            command=RoborockB01Q7Methods.START_RECHARGE,
192            params={},
193        )
194
195    async def find_me(self) -> None:
196        """Locate the robot."""
197        await self.send(
198            command=RoborockB01Q7Methods.FIND_DEVICE,
199            params={},
200        )
201
202    async def send(self, command: CommandType, params: ParamsType) -> Any:
203        """Send a command to the device."""
204        return await self._rpc_channel.send_command(command, params)

API for interacting with B01 Q7 devices.

Q7PropertiesApi( rpc_channel: roborock.devices.rpc.b01_q7_channel.Q7RpcChannel, map_rpc_channel: roborock.devices.rpc.b01_q7_channel.Q7MapRpcChannel, device: roborock.data.containers.HomeDataDevice, product: roborock.data.containers.HomeDataProduct)
53    def __init__(
54        self,
55        rpc_channel: Q7RpcChannel,
56        map_rpc_channel: Q7MapRpcChannel,
57        device: HomeDataDevice,
58        product: HomeDataProduct,
59    ) -> None:
60        """Initialize the Q7 API."""
61        self._rpc_channel = rpc_channel
62        self._map_rpc_channel = map_rpc_channel
63        self._device = device
64        self._product = product
65
66        if not device.sn or not product.model:
67            raise ValueError("B01 Q7 map content requires device serial number and product model metadata")
68
69        self.clean_summary = CleanSummaryTrait(rpc_channel)
70        self.map = MapTrait(rpc_channel)
71        self.map_content = MapContentTrait(
72            self._map_rpc_channel,
73            self.map,
74        )

Initialize the Q7 API.

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

Trait for map list metadata + raw map payload retrieval.

Trait for fetching parsed current map content.

async def query_values( self, props: list[roborock.roborock_message.RoborockB01Props]) -> roborock.data.b01_q7.b01_q7_containers.B01Props | None:
76    async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None:
77        """Query the device for the values of the given Q7 properties."""
78        result = await self.send(
79            RoborockB01Q7Methods.GET_PROP,
80            {"property": props},
81        )
82        if not isinstance(result, dict):
83            raise TypeError(f"Unexpected response type for GET_PROP: {type(result).__name__}: {result!r}")
84        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:
86    async def set_prop(self, prop: RoborockB01Props, value: Any) -> None:
87        """Set a property on the device."""
88        await self.send(
89            command=RoborockB01Q7Methods.SET_PROP,
90            params={prop: value},
91        )

Set a property on the device.

async def set_fan_speed( self, fan_speed: roborock.data.b01_q7.b01_q7_code_mappings.SCWindMapping) -> None:
93    async def set_fan_speed(self, fan_speed: SCWindMapping) -> None:
94        """Set the fan speed (wind)."""
95        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:
97    async def set_water_level(self, water_level: WaterLevelMapping) -> None:
98        """Set the water level (water)."""
99        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:
101    async def set_mode(self, mode: CleanTypeMapping) -> None:
102        """Set the cleaning mode (vacuum, mop, or vacuum and mop)."""
103        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:
105    async def set_clean_path_preference(self, preference: CleanPathPreferenceMapping) -> None:
106        """Set the cleaning path preference (route)."""
107        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:
109    async def set_repeat_state(self, repeat: CleanRepeatMapping) -> None:
110        """Set the cleaning repeat state (cycles)."""
111        await self.set_prop(RoborockB01Props.REPEAT_STATE, repeat.code)

Set the cleaning repeat state (cycles).

async def set_volume(self, volume: int) -> None:
113    async def set_volume(self, volume: int) -> None:
114        """Set the robot voice volume (0-100)."""
115        await self.set_prop(RoborockB01Props.VOLUME, volume)

Set the robot voice volume (0-100).

async def set_child_lock(self, enabled: bool) -> None:
117    async def set_child_lock(self, enabled: bool) -> None:
118        """Enable or disable the child lock."""
119        await self.set_prop(RoborockB01Props.CHILD_LOCK, int(enabled))

Enable or disable the child lock.

async def set_do_not_disturb(self, enabled: bool, begin_time: int, end_time: int) -> None:
121    async def set_do_not_disturb(self, enabled: bool, begin_time: int, end_time: int) -> None:
122        """Configure do-not-disturb.
123
124        The device expects all three values together via ``service.set_quiet_time``
125        (individual ``prop.set`` calls are ignored). ``begin_time``/``end_time`` are
126        minutes since midnight and must be in the range 0-1439 (inclusive).
127
128        Ranges that cross midnight are supported by passing a ``begin_time`` that is
129        greater than ``end_time`` (e.g. 22:00-07:00 is ``begin_time=1320``,
130        ``end_time=420``).
131        """
132        for name, value in (("begin_time", begin_time), ("end_time", end_time)):
133            if not 0 <= value <= 1439:
134                raise ValueError(f"{name} must be between 0 and 1439 minutes since midnight, got {value}")
135        await self.send(
136            RoborockB01Q7Methods.SET_QUIET_TIME,
137            {
138                "is_open": int(enabled),
139                "quiet_begin_time": begin_time,
140                "quiet_end_time": end_time,
141            },
142        )

Configure do-not-disturb.

The device expects all three values together via service.set_quiet_time (individual prop.set calls are ignored). begin_time/end_time are minutes since midnight and must be in the range 0-1439 (inclusive).

Ranges that cross midnight are supported by passing a begin_time that is greater than end_time (e.g. 22:00-07:00 is begin_time=1320, end_time=420).

async def start_clean(self) -> None:
144    async def start_clean(self) -> None:
145        """Start cleaning."""
146        await self.send(
147            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
148            params={
149                "clean_type": CleanTaskTypeMapping.ALL.code,
150                "ctrl_value": SCDeviceCleanParam.START.code,
151                "room_ids": [],
152            },
153        )

Start cleaning.

async def clean_segments(self, segment_ids: list[int]) -> None:
155    async def clean_segments(self, segment_ids: list[int]) -> None:
156        """Start segment cleaning for the given ids (Q7 uses room ids)."""
157        await self.send(
158            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
159            params={
160                "clean_type": CleanTaskTypeMapping.ROOM.code,
161                "ctrl_value": SCDeviceCleanParam.START.code,
162                "room_ids": segment_ids,
163            },
164        )

Start segment cleaning for the given ids (Q7 uses room ids).

async def pause_clean(self) -> None:
166    async def pause_clean(self) -> None:
167        """Pause cleaning."""
168        await self.send(
169            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
170            params={
171                "clean_type": CleanTaskTypeMapping.ALL.code,
172                "ctrl_value": SCDeviceCleanParam.PAUSE.code,
173                "room_ids": [],
174            },
175        )

Pause cleaning.

async def stop_clean(self) -> None:
177    async def stop_clean(self) -> None:
178        """Stop cleaning."""
179        await self.send(
180            command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
181            params={
182                "clean_type": CleanTaskTypeMapping.ALL.code,
183                "ctrl_value": SCDeviceCleanParam.STOP.code,
184                "room_ids": [],
185            },
186        )

Stop cleaning.

async def return_to_dock(self) -> None:
188    async def return_to_dock(self) -> None:
189        """Return to dock."""
190        await self.send(
191            command=RoborockB01Q7Methods.START_RECHARGE,
192            params={},
193        )

Return to dock.

async def find_me(self) -> None:
195    async def find_me(self) -> None:
196        """Locate the robot."""
197        await self.send(
198            command=RoborockB01Q7Methods.FIND_DEVICE,
199            params={},
200        )

Locate the robot.

async def send( self, command: roborock.roborock_typing.RoborockB01Q7Methods | str, params: list | dict | int | None) -> Any:
202    async def send(self, command: CommandType, params: ParamsType) -> Any:
203        """Send a command to the device."""
204        return await self._rpc_channel.send_command(command, params)

Send a command to the device.

class Q10PropertiesApi(roborock.devices.traits.Trait):
 44class Q10PropertiesApi(Trait):
 45    """API for interacting with B01 devices."""
 46
 47    command: CommandTrait
 48    """Trait for sending commands to Q10 devices."""
 49
 50    status: StatusTrait
 51    """Trait for managing the core status of Q10 devices."""
 52
 53    vacuum: VacuumTrait
 54    """Trait for sending vacuum related commands to Q10 devices."""
 55
 56    remote: RemoteTrait
 57    """Trait for sending remote control related commands to Q10 devices."""
 58
 59    volume: SoundVolumeTrait
 60    """Trait for reading / setting the speaker volume."""
 61
 62    child_lock: ChildLockTrait
 63    """Trait for reading / controlling the child lock."""
 64
 65    do_not_disturb: DoNotDisturbTrait
 66    """Trait for reading / controlling Do Not Disturb."""
 67
 68    dust_collection: DustCollectionTrait
 69    """Trait for reading / controlling dock auto-empty (dust collection)."""
 70
 71    button_light: ButtonLightTrait
 72    """Trait for controlling the indicator / button light (LED)."""
 73
 74    network_info: NetworkInfoTrait
 75    """Trait exposing the device's network information."""
 76
 77    consumable: ConsumableTrait
 78    """Trait exposing remaining life of consumables."""
 79
 80    map: MapContentTrait
 81    """Composed map image plus caller-facing map and trace data."""
 82
 83    _map_dps: MapDpsTrait
 84    """Private source of restricted zones and virtual walls received through DPS."""
 85
 86    clean_history: CleanHistoryTrait
 87    """Trait for fetching the device clean-record history (``dpCleanRecord``)."""
 88
 89    def __init__(self, channel: B01Q10Channel) -> None:
 90        """Initialize the B01Props API."""
 91        self._channel = channel
 92        self.command = CommandTrait(channel)
 93        self.vacuum = VacuumTrait(self.command)
 94        self.remote = RemoteTrait(self.command)
 95        self.status = StatusTrait()
 96        self.volume = SoundVolumeTrait(self.command)
 97        self.child_lock = ChildLockTrait(self.command)
 98        self.do_not_disturb = DoNotDisturbTrait(self.command)
 99        self.dust_collection = DustCollectionTrait(self.command)
100        self.button_light = ButtonLightTrait(self.command)
101        self.network_info = NetworkInfoTrait()
102        self.consumable = ConsumableTrait()
103        self._map_dps = MapDpsTrait()
104        self.map = MapContentTrait(self._map_dps)
105        self.clean_history = CleanHistoryTrait(self.command)
106        # Read-model traits updated from the device's DPS push stream.
107        self._updatable_traits = [
108            self.status,
109            self.volume,
110            self.child_lock,
111            self.do_not_disturb,
112            self.dust_collection,
113            self.network_info,
114            self.consumable,
115            self.clean_history,
116            self._map_dps,
117        ]
118        self._subscribe_task: asyncio.Task[None] | None = None
119
120    async def start(self) -> None:
121        """Start any necessary subscriptions for the trait."""
122        self._subscribe_task = asyncio.create_task(self._subscribe_loop())
123
124    async def close(self) -> None:
125        """Close any resources held by the trait."""
126        if self._subscribe_task is not None:
127            self._subscribe_task.cancel()
128            try:
129                await self._subscribe_task
130            except asyncio.CancelledError:
131                pass  # ignore cancellation errors
132            self._subscribe_task = None
133
134    async def refresh(self) -> None:
135        """Refresh all traits."""
136        # Sending the REQUEST_DPS will cause the device to send all DPS values
137        # to the device. Updates will be received by the subscribe loop below.
138        await self.command.send(B01_Q10_DP.REQUEST_DPS, params={})
139
140    async def _subscribe_loop(self) -> None:
141        """Persistent loop dispatching decoded messages to the read-model traits."""
142        async for message in self._channel.subscribe_stream():
143            self._handle_message(message)
144
145    def _handle_message(self, message: Q10Message) -> None:
146        """Route a single decoded message to the trait responsible for it.
147
148        Map and trace packets arrive as protocol-301 ``MAP_RESPONSE`` pushes (the
149        Q10 is entirely push-driven: there is no synchronous get-map request, a
150        ``dpRequestDps`` just nudges the device to publish its current map). DPS
151        updates feed the read-model traits. More traits can be dispatched here below.
152        """
153        if isinstance(message, Q10MapPacket):
154            self.map.update_from_map_packet(message)
155        elif isinstance(message, Q10TracePacket):
156            self.map.update_from_trace_packet(message)
157        elif isinstance(message, Q10DpsUpdate):
158            _LOGGER.debug("Received Q10 status update: %s", message.dps)
159            # Notify all read-model traits about the new message; each trait
160            # only updates the fields that it is responsible for.
161            for trait in self._updatable_traits:
162                trait.update_from_dps(message.dps)

API for interacting with B01 devices.

Q10PropertiesApi(channel: roborock.devices.rpc.b01_q10_channel.B01Q10Channel)
 89    def __init__(self, channel: B01Q10Channel) -> None:
 90        """Initialize the B01Props API."""
 91        self._channel = channel
 92        self.command = CommandTrait(channel)
 93        self.vacuum = VacuumTrait(self.command)
 94        self.remote = RemoteTrait(self.command)
 95        self.status = StatusTrait()
 96        self.volume = SoundVolumeTrait(self.command)
 97        self.child_lock = ChildLockTrait(self.command)
 98        self.do_not_disturb = DoNotDisturbTrait(self.command)
 99        self.dust_collection = DustCollectionTrait(self.command)
100        self.button_light = ButtonLightTrait(self.command)
101        self.network_info = NetworkInfoTrait()
102        self.consumable = ConsumableTrait()
103        self._map_dps = MapDpsTrait()
104        self.map = MapContentTrait(self._map_dps)
105        self.clean_history = CleanHistoryTrait(self.command)
106        # Read-model traits updated from the device's DPS push stream.
107        self._updatable_traits = [
108            self.status,
109            self.volume,
110            self.child_lock,
111            self.do_not_disturb,
112            self.dust_collection,
113            self.network_info,
114            self.consumable,
115            self.clean_history,
116            self._map_dps,
117        ]
118        self._subscribe_task: asyncio.Task[None] | None = None

Initialize the B01Props API.

command: roborock.devices.traits.b01.q10.command.CommandTrait

Trait for sending commands to Q10 devices.

Trait for managing the core status of Q10 devices.

vacuum: roborock.devices.traits.b01.q10.vacuum.VacuumTrait

Trait for sending vacuum related commands to Q10 devices.

remote: roborock.devices.traits.b01.q10.remote.RemoteTrait

Trait for sending remote control related commands to Q10 devices.

Trait for reading / setting the speaker volume.

Trait for reading / controlling the child lock.

Trait for reading / controlling Do Not Disturb.

Trait for reading / controlling dock auto-empty (dust collection).

Trait for controlling the indicator / button light (LED).

Trait exposing the device's network information.

Trait exposing remaining life of consumables.

Composed map image plus caller-facing map and trace data.

Trait for fetching the device clean-record history (dpCleanRecord).

async def start(self) -> None:
120    async def start(self) -> None:
121        """Start any necessary subscriptions for the trait."""
122        self._subscribe_task = asyncio.create_task(self._subscribe_loop())

Start any necessary subscriptions for the trait.

async def close(self) -> None:
124    async def close(self) -> None:
125        """Close any resources held by the trait."""
126        if self._subscribe_task is not None:
127            self._subscribe_task.cancel()
128            try:
129                await self._subscribe_task
130            except asyncio.CancelledError:
131                pass  # ignore cancellation errors
132            self._subscribe_task = None

Close any resources held by the trait.

async def refresh(self) -> None:
134    async def refresh(self) -> None:
135        """Refresh all traits."""
136        # Sending the REQUEST_DPS will cause the device to send all DPS values
137        # to the device. Updates will be received by the subscribe loop below.
138        await self.command.send(B01_Q10_DP.REQUEST_DPS, params={})

Refresh all traits.