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. 2 3Potentially other devices may fall into this category in the future. 4""" 5 6from __future__ import annotations 7 8from typing import Any 9 10from roborock import B01Props 11from roborock.data import HomeDataDevice, HomeDataProduct, Q7MapList, Q7MapListEntry 12from roborock.data.b01_q7.b01_q7_code_mappings import ( 13 CleanPathPreferenceMapping, 14 CleanRepeatMapping, 15 CleanTaskTypeMapping, 16 CleanTypeMapping, 17 SCDeviceCleanParam, 18 SCWindMapping, 19 WaterLevelMapping, 20) 21from roborock.devices.rpc.b01_q7_channel import MapRpcChannel, send_decoded_command 22from roborock.devices.traits import Trait 23from roborock.devices.transport.mqtt_channel import MqttChannel 24from roborock.exceptions import RoborockException 25from roborock.protocols.b01_q7_protocol import B01_Q7_DPS, CommandType, ParamsType, Q7RequestMessage, create_map_key 26from roborock.roborock_message import RoborockB01Props 27from roborock.roborock_typing import RoborockB01Q7Methods 28 29from .clean_summary import CleanSummaryTrait 30from .map import MapTrait 31from .map_content import MapContentTrait 32 33__all__ = [ 34 "Q7PropertiesApi", 35 "CleanSummaryTrait", 36 "MapTrait", 37 "MapContentTrait", 38 "Q7MapList", 39 "Q7MapListEntry", 40] 41 42 43class Q7PropertiesApi(Trait): 44 """API for interacting with B01 Q7 devices.""" 45 46 clean_summary: CleanSummaryTrait 47 """Trait for clean records / clean summary (Q7 `service.get_record_list`).""" 48 49 map: MapTrait 50 """Trait for map list metadata + raw map payload retrieval.""" 51 52 map_content: MapContentTrait 53 """Trait for fetching parsed current map content.""" 54 55 def __init__( 56 self, channel: MqttChannel, map_rpc_channel: MapRpcChannel, device: HomeDataDevice, product: HomeDataProduct 57 ) -> None: 58 """Initialize the Q7 API.""" 59 self._channel = channel 60 self._map_rpc_channel = map_rpc_channel 61 self._device = device 62 self._product = product 63 64 if not device.sn or not product.model: 65 raise ValueError("B01 Q7 map content requires device serial number and product model metadata") 66 67 self.clean_summary = CleanSummaryTrait(channel) 68 self.map = MapTrait(channel) 69 self.map_content = MapContentTrait( 70 self._map_rpc_channel, 71 self.map, 72 ) 73 74 async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None: 75 """Query the device for the values of the given Q7 properties.""" 76 result = await self.send( 77 RoborockB01Q7Methods.GET_PROP, 78 {"property": props}, 79 ) 80 if not isinstance(result, dict): 81 raise TypeError(f"Unexpected response type for GET_PROP: {type(result).__name__}: {result!r}") 82 return B01Props.from_dict(result) 83 84 async def set_prop(self, prop: RoborockB01Props, value: Any) -> None: 85 """Set a property on the device.""" 86 await self.send( 87 command=RoborockB01Q7Methods.SET_PROP, 88 params={prop: value}, 89 ) 90 91 async def set_fan_speed(self, fan_speed: SCWindMapping) -> None: 92 """Set the fan speed (wind).""" 93 await self.set_prop(RoborockB01Props.WIND, fan_speed.code) 94 95 async def set_water_level(self, water_level: WaterLevelMapping) -> None: 96 """Set the water level (water).""" 97 await self.set_prop(RoborockB01Props.WATER, water_level.code) 98 99 async def set_mode(self, mode: CleanTypeMapping) -> None: 100 """Set the cleaning mode (vacuum, mop, or vacuum and mop).""" 101 await self.set_prop(RoborockB01Props.MODE, mode.code) 102 103 async def set_clean_path_preference(self, preference: CleanPathPreferenceMapping) -> None: 104 """Set the cleaning path preference (route).""" 105 await self.set_prop(RoborockB01Props.CLEAN_PATH_PREFERENCE, preference.code) 106 107 async def set_repeat_state(self, repeat: CleanRepeatMapping) -> None: 108 """Set the cleaning repeat state (cycles).""" 109 await self.set_prop(RoborockB01Props.REPEAT_STATE, repeat.code) 110 111 async def set_volume(self, volume: int) -> None: 112 """Set the robot voice volume (0-100).""" 113 await self.set_prop(RoborockB01Props.VOLUME, volume) 114 115 async def set_child_lock(self, enabled: bool) -> None: 116 """Enable or disable the child lock.""" 117 await self.set_prop(RoborockB01Props.CHILD_LOCK, int(enabled)) 118 119 async def set_do_not_disturb(self, enabled: bool, begin_time: int, end_time: int) -> None: 120 """Configure do-not-disturb. 121 122 The device expects all three values together via ``service.set_quiet_time`` 123 (individual ``prop.set`` calls are ignored). ``begin_time``/``end_time`` are 124 minutes since midnight and must be in the range 0-1439 (inclusive). 125 126 Ranges that cross midnight are supported by passing a ``begin_time`` that is 127 greater than ``end_time`` (e.g. 22:00-07:00 is ``begin_time=1320``, 128 ``end_time=420``). 129 """ 130 for name, value in (("begin_time", begin_time), ("end_time", end_time)): 131 if not 0 <= value <= 1439: 132 raise ValueError(f"{name} must be between 0 and 1439 minutes since midnight, got {value}") 133 await self.send( 134 RoborockB01Q7Methods.SET_QUIET_TIME, 135 { 136 "is_open": int(enabled), 137 "quiet_begin_time": begin_time, 138 "quiet_end_time": end_time, 139 }, 140 ) 141 142 async def start_clean(self) -> None: 143 """Start cleaning.""" 144 await self.send( 145 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 146 params={ 147 "clean_type": CleanTaskTypeMapping.ALL.code, 148 "ctrl_value": SCDeviceCleanParam.START.code, 149 "room_ids": [], 150 }, 151 ) 152 153 async def clean_segments(self, segment_ids: list[int]) -> None: 154 """Start segment cleaning for the given ids (Q7 uses room ids).""" 155 await self.send( 156 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 157 params={ 158 "clean_type": CleanTaskTypeMapping.ROOM.code, 159 "ctrl_value": SCDeviceCleanParam.START.code, 160 "room_ids": segment_ids, 161 }, 162 ) 163 164 async def pause_clean(self) -> None: 165 """Pause cleaning.""" 166 await self.send( 167 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 168 params={ 169 "clean_type": CleanTaskTypeMapping.ALL.code, 170 "ctrl_value": SCDeviceCleanParam.PAUSE.code, 171 "room_ids": [], 172 }, 173 ) 174 175 async def stop_clean(self) -> None: 176 """Stop cleaning.""" 177 await self.send( 178 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 179 params={ 180 "clean_type": CleanTaskTypeMapping.ALL.code, 181 "ctrl_value": SCDeviceCleanParam.STOP.code, 182 "room_ids": [], 183 }, 184 ) 185 186 async def return_to_dock(self) -> None: 187 """Return to dock.""" 188 await self.send( 189 command=RoborockB01Q7Methods.START_RECHARGE, 190 params={}, 191 ) 192 193 async def find_me(self) -> None: 194 """Locate the robot.""" 195 await self.send( 196 command=RoborockB01Q7Methods.FIND_DEVICE, 197 params={}, 198 ) 199 200 async def send(self, command: CommandType, params: ParamsType) -> Any: 201 """Send a command to the device.""" 202 return await send_decoded_command( 203 self._channel, 204 Q7RequestMessage(dps=B01_Q7_DPS, command=command, params=params), 205 ) 206 207 208def create(product: HomeDataProduct, device: HomeDataDevice, channel: MqttChannel) -> Q7PropertiesApi: 209 """Create traits for B01 Q7 devices.""" 210 if device.sn is None or product.model is None: 211 raise RoborockException( 212 f"Device serial number and product model are required (sn:: {device.sn}, model: {product.model})" 213 ) 214 map_rpc_channel = MapRpcChannel(channel, map_key=create_map_key(serial=device.sn, model=product.model)) 215 return Q7PropertiesApi(channel, device=device, product=product, map_rpc_channel=map_rpc_channel)
44class Q7PropertiesApi(Trait): 45 """API for interacting with B01 Q7 devices.""" 46 47 clean_summary: CleanSummaryTrait 48 """Trait for clean records / clean summary (Q7 `service.get_record_list`).""" 49 50 map: MapTrait 51 """Trait for map list metadata + raw map payload retrieval.""" 52 53 map_content: MapContentTrait 54 """Trait for fetching parsed current map content.""" 55 56 def __init__( 57 self, channel: MqttChannel, map_rpc_channel: MapRpcChannel, device: HomeDataDevice, product: HomeDataProduct 58 ) -> None: 59 """Initialize the Q7 API.""" 60 self._channel = channel 61 self._map_rpc_channel = map_rpc_channel 62 self._device = device 63 self._product = product 64 65 if not device.sn or not product.model: 66 raise ValueError("B01 Q7 map content requires device serial number and product model metadata") 67 68 self.clean_summary = CleanSummaryTrait(channel) 69 self.map = MapTrait(channel) 70 self.map_content = MapContentTrait( 71 self._map_rpc_channel, 72 self.map, 73 ) 74 75 async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None: 76 """Query the device for the values of the given Q7 properties.""" 77 result = await self.send( 78 RoborockB01Q7Methods.GET_PROP, 79 {"property": props}, 80 ) 81 if not isinstance(result, dict): 82 raise TypeError(f"Unexpected response type for GET_PROP: {type(result).__name__}: {result!r}") 83 return B01Props.from_dict(result) 84 85 async def set_prop(self, prop: RoborockB01Props, value: Any) -> None: 86 """Set a property on the device.""" 87 await self.send( 88 command=RoborockB01Q7Methods.SET_PROP, 89 params={prop: value}, 90 ) 91 92 async def set_fan_speed(self, fan_speed: SCWindMapping) -> None: 93 """Set the fan speed (wind).""" 94 await self.set_prop(RoborockB01Props.WIND, fan_speed.code) 95 96 async def set_water_level(self, water_level: WaterLevelMapping) -> None: 97 """Set the water level (water).""" 98 await self.set_prop(RoborockB01Props.WATER, water_level.code) 99 100 async def set_mode(self, mode: CleanTypeMapping) -> None: 101 """Set the cleaning mode (vacuum, mop, or vacuum and mop).""" 102 await self.set_prop(RoborockB01Props.MODE, mode.code) 103 104 async def set_clean_path_preference(self, preference: CleanPathPreferenceMapping) -> None: 105 """Set the cleaning path preference (route).""" 106 await self.set_prop(RoborockB01Props.CLEAN_PATH_PREFERENCE, preference.code) 107 108 async def set_repeat_state(self, repeat: CleanRepeatMapping) -> None: 109 """Set the cleaning repeat state (cycles).""" 110 await self.set_prop(RoborockB01Props.REPEAT_STATE, repeat.code) 111 112 async def set_volume(self, volume: int) -> None: 113 """Set the robot voice volume (0-100).""" 114 await self.set_prop(RoborockB01Props.VOLUME, volume) 115 116 async def set_child_lock(self, enabled: bool) -> None: 117 """Enable or disable the child lock.""" 118 await self.set_prop(RoborockB01Props.CHILD_LOCK, int(enabled)) 119 120 async def set_do_not_disturb(self, enabled: bool, begin_time: int, end_time: int) -> None: 121 """Configure do-not-disturb. 122 123 The device expects all three values together via ``service.set_quiet_time`` 124 (individual ``prop.set`` calls are ignored). ``begin_time``/``end_time`` are 125 minutes since midnight and must be in the range 0-1439 (inclusive). 126 127 Ranges that cross midnight are supported by passing a ``begin_time`` that is 128 greater than ``end_time`` (e.g. 22:00-07:00 is ``begin_time=1320``, 129 ``end_time=420``). 130 """ 131 for name, value in (("begin_time", begin_time), ("end_time", end_time)): 132 if not 0 <= value <= 1439: 133 raise ValueError(f"{name} must be between 0 and 1439 minutes since midnight, got {value}") 134 await self.send( 135 RoborockB01Q7Methods.SET_QUIET_TIME, 136 { 137 "is_open": int(enabled), 138 "quiet_begin_time": begin_time, 139 "quiet_end_time": end_time, 140 }, 141 ) 142 143 async def start_clean(self) -> None: 144 """Start cleaning.""" 145 await self.send( 146 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 147 params={ 148 "clean_type": CleanTaskTypeMapping.ALL.code, 149 "ctrl_value": SCDeviceCleanParam.START.code, 150 "room_ids": [], 151 }, 152 ) 153 154 async def clean_segments(self, segment_ids: list[int]) -> None: 155 """Start segment cleaning for the given ids (Q7 uses room ids).""" 156 await self.send( 157 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 158 params={ 159 "clean_type": CleanTaskTypeMapping.ROOM.code, 160 "ctrl_value": SCDeviceCleanParam.START.code, 161 "room_ids": segment_ids, 162 }, 163 ) 164 165 async def pause_clean(self) -> None: 166 """Pause cleaning.""" 167 await self.send( 168 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 169 params={ 170 "clean_type": CleanTaskTypeMapping.ALL.code, 171 "ctrl_value": SCDeviceCleanParam.PAUSE.code, 172 "room_ids": [], 173 }, 174 ) 175 176 async def stop_clean(self) -> None: 177 """Stop cleaning.""" 178 await self.send( 179 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 180 params={ 181 "clean_type": CleanTaskTypeMapping.ALL.code, 182 "ctrl_value": SCDeviceCleanParam.STOP.code, 183 "room_ids": [], 184 }, 185 ) 186 187 async def return_to_dock(self) -> None: 188 """Return to dock.""" 189 await self.send( 190 command=RoborockB01Q7Methods.START_RECHARGE, 191 params={}, 192 ) 193 194 async def find_me(self) -> None: 195 """Locate the robot.""" 196 await self.send( 197 command=RoborockB01Q7Methods.FIND_DEVICE, 198 params={}, 199 ) 200 201 async def send(self, command: CommandType, params: ParamsType) -> Any: 202 """Send a command to the device.""" 203 return await send_decoded_command( 204 self._channel, 205 Q7RequestMessage(dps=B01_Q7_DPS, command=command, params=params), 206 )
API for interacting with B01 Q7 devices.
56 def __init__( 57 self, channel: MqttChannel, map_rpc_channel: MapRpcChannel, device: HomeDataDevice, product: HomeDataProduct 58 ) -> None: 59 """Initialize the Q7 API.""" 60 self._channel = channel 61 self._map_rpc_channel = map_rpc_channel 62 self._device = device 63 self._product = product 64 65 if not device.sn or not product.model: 66 raise ValueError("B01 Q7 map content requires device serial number and product model metadata") 67 68 self.clean_summary = CleanSummaryTrait(channel) 69 self.map = MapTrait(channel) 70 self.map_content = MapContentTrait( 71 self._map_rpc_channel, 72 self.map, 73 )
Initialize the Q7 API.
Trait for clean records / clean summary (Q7 service.get_record_list).
75 async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None: 76 """Query the device for the values of the given Q7 properties.""" 77 result = await self.send( 78 RoborockB01Q7Methods.GET_PROP, 79 {"property": props}, 80 ) 81 if not isinstance(result, dict): 82 raise TypeError(f"Unexpected response type for GET_PROP: {type(result).__name__}: {result!r}") 83 return B01Props.from_dict(result)
Query the device for the values of the given Q7 properties.
85 async def set_prop(self, prop: RoborockB01Props, value: Any) -> None: 86 """Set a property on the device.""" 87 await self.send( 88 command=RoborockB01Q7Methods.SET_PROP, 89 params={prop: value}, 90 )
Set a property on the device.
92 async def set_fan_speed(self, fan_speed: SCWindMapping) -> None: 93 """Set the fan speed (wind).""" 94 await self.set_prop(RoborockB01Props.WIND, fan_speed.code)
Set the fan speed (wind).
96 async def set_water_level(self, water_level: WaterLevelMapping) -> None: 97 """Set the water level (water).""" 98 await self.set_prop(RoborockB01Props.WATER, water_level.code)
Set the water level (water).
100 async def set_mode(self, mode: CleanTypeMapping) -> None: 101 """Set the cleaning mode (vacuum, mop, or vacuum and mop).""" 102 await self.set_prop(RoborockB01Props.MODE, mode.code)
Set the cleaning mode (vacuum, mop, or vacuum and mop).
104 async def set_clean_path_preference(self, preference: CleanPathPreferenceMapping) -> None: 105 """Set the cleaning path preference (route).""" 106 await self.set_prop(RoborockB01Props.CLEAN_PATH_PREFERENCE, preference.code)
Set the cleaning path preference (route).
108 async def set_repeat_state(self, repeat: CleanRepeatMapping) -> None: 109 """Set the cleaning repeat state (cycles).""" 110 await self.set_prop(RoborockB01Props.REPEAT_STATE, repeat.code)
Set the cleaning repeat state (cycles).
112 async def set_volume(self, volume: int) -> None: 113 """Set the robot voice volume (0-100).""" 114 await self.set_prop(RoborockB01Props.VOLUME, volume)
Set the robot voice volume (0-100).
116 async def set_child_lock(self, enabled: bool) -> None: 117 """Enable or disable the child lock.""" 118 await self.set_prop(RoborockB01Props.CHILD_LOCK, int(enabled))
Enable or disable the child lock.
120 async def set_do_not_disturb(self, enabled: bool, begin_time: int, end_time: int) -> None: 121 """Configure do-not-disturb. 122 123 The device expects all three values together via ``service.set_quiet_time`` 124 (individual ``prop.set`` calls are ignored). ``begin_time``/``end_time`` are 125 minutes since midnight and must be in the range 0-1439 (inclusive). 126 127 Ranges that cross midnight are supported by passing a ``begin_time`` that is 128 greater than ``end_time`` (e.g. 22:00-07:00 is ``begin_time=1320``, 129 ``end_time=420``). 130 """ 131 for name, value in (("begin_time", begin_time), ("end_time", end_time)): 132 if not 0 <= value <= 1439: 133 raise ValueError(f"{name} must be between 0 and 1439 minutes since midnight, got {value}") 134 await self.send( 135 RoborockB01Q7Methods.SET_QUIET_TIME, 136 { 137 "is_open": int(enabled), 138 "quiet_begin_time": begin_time, 139 "quiet_end_time": end_time, 140 }, 141 )
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).
143 async def start_clean(self) -> None: 144 """Start cleaning.""" 145 await self.send( 146 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 147 params={ 148 "clean_type": CleanTaskTypeMapping.ALL.code, 149 "ctrl_value": SCDeviceCleanParam.START.code, 150 "room_ids": [], 151 }, 152 )
Start cleaning.
154 async def clean_segments(self, segment_ids: list[int]) -> None: 155 """Start segment cleaning for the given ids (Q7 uses room ids).""" 156 await self.send( 157 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 158 params={ 159 "clean_type": CleanTaskTypeMapping.ROOM.code, 160 "ctrl_value": SCDeviceCleanParam.START.code, 161 "room_ids": segment_ids, 162 }, 163 )
Start segment cleaning for the given ids (Q7 uses room ids).
165 async def pause_clean(self) -> None: 166 """Pause cleaning.""" 167 await self.send( 168 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 169 params={ 170 "clean_type": CleanTaskTypeMapping.ALL.code, 171 "ctrl_value": SCDeviceCleanParam.PAUSE.code, 172 "room_ids": [], 173 }, 174 )
Pause cleaning.
176 async def stop_clean(self) -> None: 177 """Stop cleaning.""" 178 await self.send( 179 command=RoborockB01Q7Methods.SET_ROOM_CLEAN, 180 params={ 181 "clean_type": CleanTaskTypeMapping.ALL.code, 182 "ctrl_value": SCDeviceCleanParam.STOP.code, 183 "room_ids": [], 184 }, 185 )
Stop cleaning.
187 async def return_to_dock(self) -> None: 188 """Return to dock.""" 189 await self.send( 190 command=RoborockB01Q7Methods.START_RECHARGE, 191 params={}, 192 )
Return to dock.
194 async def find_me(self) -> None: 195 """Locate the robot.""" 196 await self.send( 197 command=RoborockB01Q7Methods.FIND_DEVICE, 198 params={}, 199 )
Locate the robot.
201 async def send(self, command: CommandType, params: ParamsType) -> Any: 202 """Send a command to the device.""" 203 return await send_decoded_command( 204 self._channel, 205 Q7RequestMessage(dps=B01_Q7_DPS, command=command, params=params), 206 )
Send a command to the device.
25class CleanSummaryTrait(CleanRecordSummary, Trait): 26 """B01/Q7 clean summary + clean record access (via record list service).""" 27 28 def __init__(self, channel: MqttChannel) -> None: 29 """Initialize the clean summary trait. 30 31 Args: 32 channel: MQTT channel used to communicate with the device. 33 """ 34 super().__init__() 35 self._channel = channel 36 37 async def refresh(self) -> None: 38 """Refresh totals and last record detail from the device.""" 39 record_list = await self._get_record_list() 40 41 self.total_time = record_list.total_time 42 self.total_area = record_list.total_area 43 self.total_count = record_list.total_count 44 45 details = await self._get_clean_record_details(record_list=record_list) 46 self.last_record_detail = details[0] if details else None 47 48 async def _get_record_list(self) -> CleanRecordList: 49 """Fetch the raw device clean record list (`service.get_record_list`).""" 50 result = await send_decoded_command( 51 self._channel, 52 Q7RequestMessage(dps=B01_Q7_DPS, command=RoborockB01Q7Methods.GET_RECORD_LIST, params={}), 53 ) 54 55 if not isinstance(result, dict): 56 raise TypeError(f"Unexpected response type for GET_RECORD_LIST: {type(result).__name__}: {result!r}") 57 return CleanRecordList.from_dict(result) 58 59 async def _get_clean_record_details(self, *, record_list: CleanRecordList) -> list[CleanRecordDetail]: 60 """Return parsed record detail objects (newest-first).""" 61 details: list[CleanRecordDetail] = [] 62 for item in record_list.record_list: 63 try: 64 parsed = item.detail_parsed 65 except RoborockException as ex: 66 # Rather than failing if something goes wrong here, we should fail and log to tell the user. 67 _LOGGER.debug("Failed to parse record detail: %s", ex) 68 continue 69 if parsed is not None: 70 details.append(parsed) 71 72 # The server returns the newest record at the end of record_list; reverse so newest is first (index 0). 73 details.reverse() 74 return details
B01/Q7 clean summary + clean record access (via record list service).
28 def __init__(self, channel: MqttChannel) -> None: 29 """Initialize the clean summary trait. 30 31 Args: 32 channel: MQTT channel used to communicate with the device. 33 """ 34 super().__init__() 35 self._channel = channel
Initialize the clean summary trait.
Args: channel: MQTT channel used to communicate with the device.
37 async def refresh(self) -> None: 38 """Refresh totals and last record detail from the device.""" 39 record_list = await self._get_record_list() 40 41 self.total_time = record_list.total_time 42 self.total_area = record_list.total_area 43 self.total_count = record_list.total_count 44 45 details = await self._get_clean_record_details(record_list=record_list) 46 self.last_record_detail = details[0] if details else None
Refresh totals and last record detail from the device.
13class MapTrait(Q7MapList, Trait): 14 """Map trait for B01/Q7 devices, responsible for fetching and caching map list metadata. 15 16 The MapContent is fetched from the MapContent trait, which relies on this trait to determine the 17 current map ID to fetch. 18 """ 19 20 def __init__(self, channel: MqttChannel) -> None: 21 super().__init__() 22 self._channel = channel 23 24 async def refresh(self) -> None: 25 """Refresh cached map list metadata from the device.""" 26 response = await send_decoded_command( 27 self._channel, 28 Q7RequestMessage(dps=B01_Q7_DPS, command=RoborockB01Q7Methods.GET_MAP_LIST, params={}), 29 ) 30 if not isinstance(response, dict): 31 raise RoborockException( 32 f"Unexpected response type for GET_MAP_LIST: {type(response).__name__}: {response!r}" 33 ) 34 35 if (parsed := Q7MapList.from_dict(response)) is None: 36 raise RoborockException(f"Failed to decode map list response: {response!r}") 37 38 self.map_list = parsed.map_list
Map trait for B01/Q7 devices, responsible for fetching and caching map list metadata.
The MapContent is fetched from the MapContent trait, which relies on this trait to determine the current map ID to fetch.
24 async def refresh(self) -> None: 25 """Refresh cached map list metadata from the device.""" 26 response = await send_decoded_command( 27 self._channel, 28 Q7RequestMessage(dps=B01_Q7_DPS, command=RoborockB01Q7Methods.GET_MAP_LIST, params={}), 29 ) 30 if not isinstance(response, dict): 31 raise RoborockException( 32 f"Unexpected response type for GET_MAP_LIST: {type(response).__name__}: {response!r}" 33 ) 34 35 if (parsed := Q7MapList.from_dict(response)) is None: 36 raise RoborockException(f"Failed to decode map list response: {response!r}") 37 38 self.map_list = parsed.map_list
Refresh cached map list metadata from the device.
Inherited Members
54class MapContentTrait(MapContent, Trait): 55 """Trait for fetching parsed map content for Q7 devices.""" 56 57 def __init__( 58 self, 59 map_rpc_channel: MapRpcChannel, 60 map_trait: MapTrait, 61 *, 62 map_parser_config: B01MapParserConfig | None = None, 63 ) -> None: 64 super().__init__() 65 self._map_rpc_channel = map_rpc_channel 66 self._map_trait = map_trait 67 self._map_parser = B01MapParser(map_parser_config) 68 # Map uploads are serialized per-device to avoid response cross-wiring. 69 self._map_command_lock = asyncio.Lock() 70 71 async def refresh(self) -> None: 72 """Fetch, decode, and parse the current map payload. 73 74 This relies on the Map Trait already having fetched the map list metadata 75 so it can determine the current map_id. 76 """ 77 # Users must call first 78 if (map_id := self._map_trait.current_map_id) is None: 79 raise RoborockException("Unable to determine current map ID") 80 81 request = Q7RequestMessage( 82 dps=B01_Q7_DPS, 83 command=RoborockB01Q7Methods.UPLOAD_BY_MAPID, 84 params={"map_id": map_id}, 85 ) 86 async with self._map_command_lock: 87 raw_payload = await self._map_rpc_channel.send_map_command(request) 88 89 try: 90 parsed_data = self._map_parser.parse(raw_payload) 91 except RoborockException: 92 raise 93 except Exception as ex: 94 raise RoborockException("Failed to parse B01 map data") from ex 95 96 if parsed_data.image_content is None: 97 raise RoborockException("Failed to render B01 map image") 98 99 self.image_content = parsed_data.image_content 100 self.map_data = parsed_data.map_data 101 self.raw_api_response = raw_payload
Trait for fetching parsed map content for Q7 devices.
57 def __init__( 58 self, 59 map_rpc_channel: MapRpcChannel, 60 map_trait: MapTrait, 61 *, 62 map_parser_config: B01MapParserConfig | None = None, 63 ) -> None: 64 super().__init__() 65 self._map_rpc_channel = map_rpc_channel 66 self._map_trait = map_trait 67 self._map_parser = B01MapParser(map_parser_config) 68 # Map uploads are serialized per-device to avoid response cross-wiring. 69 self._map_command_lock = asyncio.Lock()
71 async def refresh(self) -> None: 72 """Fetch, decode, and parse the current map payload. 73 74 This relies on the Map Trait already having fetched the map list metadata 75 so it can determine the current map_id. 76 """ 77 # Users must call first 78 if (map_id := self._map_trait.current_map_id) is None: 79 raise RoborockException("Unable to determine current map ID") 80 81 request = Q7RequestMessage( 82 dps=B01_Q7_DPS, 83 command=RoborockB01Q7Methods.UPLOAD_BY_MAPID, 84 params={"map_id": map_id}, 85 ) 86 async with self._map_command_lock: 87 raw_payload = await self._map_rpc_channel.send_map_command(request) 88 89 try: 90 parsed_data = self._map_parser.parse(raw_payload) 91 except RoborockException: 92 raise 93 except Exception as ex: 94 raise RoborockException("Failed to parse B01 map data") from ex 95 96 if parsed_data.image_content is None: 97 raise RoborockException("Failed to render B01 map image") 98 99 self.image_content = parsed_data.image_content 100 self.map_data = parsed_data.map_data 101 self.raw_api_response = raw_payload
Fetch, decode, and parse the current map payload.
This relies on the Map Trait already having fetched the map list metadata so it can determine the current map_id.
Inherited Members
87@dataclass 88class Q7MapList(RoborockBase): 89 """Map list response returned by `service.get_map_list`.""" 90 91 map_list: list[Q7MapListEntry] = field(default_factory=list) 92 93 @property 94 def current_map_id(self) -> int | None: 95 """Current map id, preferring the entry marked current.""" 96 if not self.map_list: 97 return None 98 99 ordered = sorted(self.map_list, key=lambda entry: entry.cur or False, reverse=True) 100 first = next(iter(ordered), None) 101 if first is None or not isinstance(first.id, int): 102 return None 103 return first.id
Map list response returned by service.get_map_list.
93 @property 94 def current_map_id(self) -> int | None: 95 """Current map id, preferring the entry marked current.""" 96 if not self.map_list: 97 return None 98 99 ordered = sorted(self.map_list, key=lambda entry: entry.cur or False, reverse=True) 100 first = next(iter(ordered), None) 101 if first is None or not isinstance(first.id, int): 102 return None 103 return first.id
Current map id, preferring the entry marked current.
Inherited Members
79@dataclass 80class Q7MapListEntry(RoborockBase): 81 """Single map list entry returned by `service.get_map_list`.""" 82 83 id: int | None = None 84 cur: bool | None = None
Single map list entry returned by service.get_map_list.