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 typing import Any 7 8from roborock import B01Props 9from roborock.data import HomeDataDevice, HomeDataProduct, Q7MapList, Q7MapListEntry 10from roborock.data.b01_q7.b01_q7_code_mappings import ( 11 CleanPathPreferenceMapping, 12 CleanRepeatMapping, 13 CleanTaskTypeMapping, 14 CleanTypeMapping, 15 SCDeviceCleanParam, 16 SCWindMapping, 17 WaterLevelMapping, 18) 19from roborock.devices.rpc.b01_q7_channel import Q7MapRpcChannel, Q7RpcChannel 20from roborock.devices.traits import Trait 21from roborock.exceptions import RoborockException 22from roborock.protocols.b01_q7_protocol import CommandType, ParamsType 23from roborock.roborock_message import RoborockB01Props 24from roborock.roborock_typing import RoborockB01Q7Methods 25 26from .clean_summary import CleanSummaryTrait 27from .map import MapTrait 28from .map_content import MapContentTrait 29 30__all__ = [ 31 "Q7PropertiesApi", 32 "CleanSummaryTrait", 33 "MapTrait", 34 "MapContentTrait", 35 "Q7MapList", 36 "Q7MapListEntry", 37] 38 39 40class Q7PropertiesApi(Trait): 41 """API for interacting with B01 Q7 devices.""" 42 43 clean_summary: CleanSummaryTrait 44 """Trait for clean records / clean summary (Q7 `service.get_record_list`).""" 45 46 map: MapTrait 47 """Trait for map list metadata + raw map payload retrieval.""" 48 49 map_content: MapContentTrait 50 """Trait for fetching parsed current map content.""" 51 52 def __init__( 53 self, 54 rpc_channel: Q7RpcChannel, 55 map_rpc_channel: Q7MapRpcChannel, 56 device: HomeDataDevice, 57 product: HomeDataProduct, 58 ) -> None: 59 """Initialize the Q7 API.""" 60 self._rpc_channel = rpc_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(rpc_channel) 69 self.map = MapTrait(rpc_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 self._rpc_channel.send_command(command, params) 204 205 206def create( 207 product: HomeDataProduct, 208 device: HomeDataDevice, 209 rpc_channel: Q7RpcChannel, 210 map_rpc_channel: Q7MapRpcChannel, 211) -> Q7PropertiesApi: 212 """Create traits for B01 Q7 devices.""" 213 if device.sn is None or product.model is None: 214 raise RoborockException( 215 f"Device serial number and product model are required (sn: {device.sn}, model: {product.model})" 216 ) 217 return Q7PropertiesApi( 218 rpc_channel=rpc_channel, 219 map_rpc_channel=map_rpc_channel, 220 device=device, 221 product=product, 222 )
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.
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).
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.
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.
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).
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).
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).
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).
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).
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).
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.
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).
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.
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).
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.
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.
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.
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.
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.
23class CleanSummaryTrait(CleanRecordSummary, Trait): 24 """B01/Q7 clean summary + clean record access (via record list service).""" 25 26 def __init__(self, channel: Q7RpcChannel) -> None: 27 """Initialize the clean summary trait. 28 29 Args: 30 channel: RPC channel used to communicate with the device. 31 """ 32 super().__init__() 33 self._channel = channel 34 35 async def refresh(self) -> None: 36 """Refresh totals and last record detail from the device.""" 37 record_list = await self._get_record_list() 38 39 self.total_time = record_list.total_time 40 self.total_area = record_list.total_area 41 self.total_count = record_list.total_count 42 43 details = await self._get_clean_record_details(record_list=record_list) 44 self.last_record_detail = details[0] if details else None 45 46 async def _get_record_list(self) -> CleanRecordList: 47 """Fetch the raw device clean record list (`service.get_record_list`).""" 48 result = await self._channel.send_command( 49 command=RoborockB01Q7Methods.GET_RECORD_LIST, 50 params={}, 51 ) 52 53 if not isinstance(result, dict): 54 raise TypeError(f"Unexpected response type for GET_RECORD_LIST: {type(result).__name__}: {result!r}") 55 return CleanRecordList.from_dict(result) 56 57 async def _get_clean_record_details(self, *, record_list: CleanRecordList) -> list[CleanRecordDetail]: 58 """Return parsed record detail objects (newest-first).""" 59 details: list[CleanRecordDetail] = [] 60 for item in record_list.record_list: 61 try: 62 parsed = item.detail_parsed 63 except RoborockException as ex: 64 # Rather than failing if something goes wrong here, we should fail and log to tell the user. 65 _LOGGER.debug("Failed to parse record detail: %s", ex) 66 continue 67 if parsed is not None: 68 details.append(parsed) 69 70 # The server returns the newest record at the end of record_list; reverse so newest is first (index 0). 71 details.reverse() 72 return details
B01/Q7 clean summary + clean record access (via record list service).
26 def __init__(self, channel: Q7RpcChannel) -> None: 27 """Initialize the clean summary trait. 28 29 Args: 30 channel: RPC channel used to communicate with the device. 31 """ 32 super().__init__() 33 self._channel = channel
Initialize the clean summary trait.
Args: channel: RPC channel used to communicate with the device.
35 async def refresh(self) -> None: 36 """Refresh totals and last record detail from the device.""" 37 record_list = await self._get_record_list() 38 39 self.total_time = record_list.total_time 40 self.total_area = record_list.total_area 41 self.total_count = record_list.total_count 42 43 details = await self._get_clean_record_details(record_list=record_list) 44 self.last_record_detail = details[0] if details else None
Refresh totals and last record detail from the device.
11class MapTrait(Q7MapList, Trait): 12 """Map trait for B01/Q7 devices, responsible for fetching and caching map list metadata. 13 14 The MapContent is fetched from the MapContent trait, which relies on this trait to determine the 15 current map ID to fetch. 16 """ 17 18 def __init__(self, channel: Q7RpcChannel) -> None: 19 super().__init__() 20 self._channel = channel 21 22 async def refresh(self) -> None: 23 """Refresh cached map list metadata from the device.""" 24 response = await self._channel.send_command( 25 command=RoborockB01Q7Methods.GET_MAP_LIST, 26 params={}, 27 ) 28 if not isinstance(response, dict): 29 raise RoborockException( 30 f"Unexpected response type for GET_MAP_LIST: {type(response).__name__}: {response!r}" 31 ) 32 33 if (parsed := Q7MapList.from_dict(response)) is None: 34 raise RoborockException(f"Failed to decode map list response: {response!r}") 35 36 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.
22 async def refresh(self) -> None: 23 """Refresh cached map list metadata from the device.""" 24 response = await self._channel.send_command( 25 command=RoborockB01Q7Methods.GET_MAP_LIST, 26 params={}, 27 ) 28 if not isinstance(response, dict): 29 raise RoborockException( 30 f"Unexpected response type for GET_MAP_LIST: {type(response).__name__}: {response!r}" 31 ) 32 33 if (parsed := Q7MapList.from_dict(response)) is None: 34 raise RoborockException(f"Failed to decode map list response: {response!r}") 35 36 self.map_list = parsed.map_list
Refresh cached map list metadata from the device.
Inherited Members
53class MapContentTrait(MapContent, Trait): 54 """Trait for fetching parsed map content for Q7 devices.""" 55 56 def __init__( 57 self, 58 map_rpc_channel: Q7MapRpcChannel, 59 map_trait: MapTrait, 60 *, 61 map_parser_config: B01MapParserConfig | None = None, 62 ) -> None: 63 super().__init__() 64 self._map_rpc_channel = map_rpc_channel 65 self._map_trait = map_trait 66 self._map_parser = B01MapParser(map_parser_config) 67 # Map uploads are serialized per-device to avoid response cross-wiring. 68 self._map_command_lock = asyncio.Lock() 69 70 async def refresh(self) -> None: 71 """Fetch, decode, and parse the current map payload. 72 73 This relies on the Map Trait already having fetched the map list metadata 74 so it can determine the current map_id. 75 """ 76 # Users must call first 77 if (map_id := self._map_trait.current_map_id) is None: 78 raise RoborockException("Unable to determine current map ID") 79 80 async with self._map_command_lock: 81 raw_payload = await self._map_rpc_channel.send_map_command( 82 RoborockB01Q7Methods.UPLOAD_BY_MAPID, 83 {"map_id": map_id}, 84 ) 85 86 try: 87 parsed_data = self._map_parser.parse(raw_payload) 88 except RoborockException: 89 raise 90 except Exception as ex: 91 raise RoborockException("Failed to parse B01 map data") from ex 92 93 if parsed_data.image_content is None: 94 raise RoborockException("Failed to render B01 map image") 95 96 self.image_content = parsed_data.image_content 97 self.map_data = parsed_data.map_data 98 self.raw_api_response = raw_payload
Trait for fetching parsed map content for Q7 devices.
56 def __init__( 57 self, 58 map_rpc_channel: Q7MapRpcChannel, 59 map_trait: MapTrait, 60 *, 61 map_parser_config: B01MapParserConfig | None = None, 62 ) -> None: 63 super().__init__() 64 self._map_rpc_channel = map_rpc_channel 65 self._map_trait = map_trait 66 self._map_parser = B01MapParser(map_parser_config) 67 # Map uploads are serialized per-device to avoid response cross-wiring. 68 self._map_command_lock = asyncio.Lock()
70 async def refresh(self) -> None: 71 """Fetch, decode, and parse the current map payload. 72 73 This relies on the Map Trait already having fetched the map list metadata 74 so it can determine the current map_id. 75 """ 76 # Users must call first 77 if (map_id := self._map_trait.current_map_id) is None: 78 raise RoborockException("Unable to determine current map ID") 79 80 async with self._map_command_lock: 81 raw_payload = await self._map_rpc_channel.send_map_command( 82 RoborockB01Q7Methods.UPLOAD_BY_MAPID, 83 {"map_id": map_id}, 84 ) 85 86 try: 87 parsed_data = self._map_parser.parse(raw_payload) 88 except RoborockException: 89 raise 90 except Exception as ex: 91 raise RoborockException("Failed to parse B01 map data") from ex 92 93 if parsed_data.image_content is None: 94 raise RoborockException("Failed to render B01 map image") 95 96 self.image_content = parsed_data.image_content 97 self.map_data = parsed_data.map_data 98 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.