Skip to content

馃Ч add support for xiaomi.vacuum.d103cn - #2868

Open
drrhaos wants to merge 2 commits into
al-one:masterfrom
drrhaos:feature-support-d103cn
Open

馃Ч add support for xiaomi.vacuum.d103cn#2868
drrhaos wants to merge 2 commits into
al-one:masterfrom
drrhaos:feature-support-d103cn

Conversation

@drrhaos

@drrhaos drrhaos commented Jun 20, 2026

Copy link
Copy Markdown
  • Add xiaomi.vacuum.d103cn device customizes with chunked polling, button actions, and sensor properties (cleaning_area, cleaning_time, dry_left_time)
  • Introduce MiotXiaomiVacuumEntity with per-room cleaning via start_room_sweep / start_room_clean MIOT action
  • Auto-create room buttons from device props, get_room_mapping, or Xiaomi Cloud fallback

Add device customizes with optimized polling and room sweep buttons
via MiotXiaomiVacuumEntity for per-room cleaning.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for the xiaomi.vacuum.d103cn vacuum model. It adds custom device configurations and implements a new MiotXiaomiVacuumEntity class to handle dynamic room button generation for room-specific sweeping. The room data is retrieved via device properties, direct commands, or Xiaomi Cloud integration. The review feedback highlights opportunities to optimize performance by batching the registration of room buttons instead of adding them individually in a loop, and suggests improving the robustness of room data parsing and mapping by explicitly handling potential None values to prevent invalid configurations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +420 to +437
async def _setup_room_buttons(self):
rooms = await self._get_rooms()
if not rooms:
return
add_buttons = self.device.entry.adders.get('button')
if not add_buttons:
return
from .button import ButtonSubEntity
for rid, rname in rooms:
sub = f'room_{rid}'
self._subs[sub] = ButtonSubEntity(self, sub, option={
'name': f'{self.device_name} {rname}',
'async_press_action': self.async_start_room_sweep,
'press_kwargs': {'room_id': rid},
'state_attrs': {'room_id': rid, 'room_name': rname},
})
add_buttons([self._subs[sub]], update_before_add=False)
self.logger.info('Room buttons added: %s', rooms)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling add_buttons inside the loop for each room is inefficient as it triggers multiple update cycles in Home Assistant. It is highly recommended to batch all the new button entities into a list and call add_buttons once after the loop.

    async def _setup_room_buttons(self):
        rooms = await self._get_rooms()
        if not rooms:
            return
        add_buttons = self.device.entry.adders.get('button')
        if not add_buttons:
            return
        from .button import ButtonSubEntity
        new_buttons = []
        for rid, rname in rooms:
            sub = f'room_{rid}'
            button = ButtonSubEntity(self, sub, option={
                'name': f'{self.device_name} {rname}',
                'async_press_action': self.async_start_room_sweep,
                'press_kwargs': {'room_id': rid},
                'state_attrs': {'room_id': rid, 'room_name': rname},
            })
            self._subs[sub] = button
            new_buttons.append(button)
        if new_buttons:
            add_buttons(new_buttons, update_before_add=False)
        self.logger.info('Room buttons added: %s', rooms)

Comment on lines +511 to +538
@staticmethod
def _parse_room_data(data):
if not data:
return None
if isinstance(data, str):
try:
data = json.loads(data)
except (json.JSONDecodeError, TypeError):
return None
rooms = []
if isinstance(data, list):
for item in data:
if isinstance(item, (list, tuple)):
rid = str(item[0])
name = str(item[1]) if len(item) > 1 else f'Room {rid}'
rooms.append((rid, name))
elif isinstance(item, dict):
rid = str(item.get('id') or item.get('ID'))
name = str(item.get('name') or item.get('Name') or f'Room {rid}')
rooms.append((rid, name))
else:
rid = str(item)
rooms.append((rid, f'Room {rid}'))
elif isinstance(data, dict):
for rid, name in data.items():
rid = str(rid)
rooms.append((rid, str(name) if name else f'Room {rid}'))
return rooms or None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When parsing room data from dictionaries, if both id and ID keys are missing, rid will resolve to the string 'None'. This can lead to invalid room buttons and errors when trying to sweep. Additionally, we should guard against None values in lists and dictionary keys to ensure robust parsing.

    @staticmethod
    def _parse_room_data(data):
        if not data:
            return None
        if isinstance(data, str):
            try:
                data = json.loads(data)
            except (json.JSONDecodeError, TypeError):
                return None
        rooms = []
        if isinstance(data, list):
            for item in data:
                if isinstance(item, (list, tuple)):
                    if item and item[0] is not None:
                        rid = str(item[0])
                        name = str(item[1]) if len(item) > 1 and item[1] is not None else f'Room {rid}'
                        rooms.append((rid, name))
                elif isinstance(item, dict):
                    rid = item.get('id') or item.get('ID')
                    if rid is not None:
                        rid = str(rid)
                        name = str(item.get('name') or item.get('Name') or f'Room {rid}')
                        rooms.append((rid, name))
                else:
                    if item is not None:
                        rid = str(item)
                        rooms.append((rid, f'Room {rid}'))
        elif isinstance(data, dict):
            for rid, name in data.items():
                if rid is not None:
                    rid = str(rid)
                    rooms.append((rid, str(name) if name else f'Room {rid}'))
        return rooms or None

Comment on lines +540 to +559
async def _process_room_mapping(self, data):
if not data or not isinstance(data, list):
return None
cloud_rooms = {}
if self.xiaomi_cloud:
try:
homes = await self.xiaomi_cloud.async_get_homerooms() or []
for home in homes:
for room in home.get('roomlist', []):
cloud_rooms[room['id']] = room['name']
except Exception:
pass
rooms = []
for item in data:
if isinstance(item, (list, tuple)) and len(item) >= 2:
seg_id = str(item[0])
cloud_id = item[1]
name = str(item[2]) if len(item) > 2 else cloud_rooms.get(cloud_id, f'Room {seg_id}')
rooms.append((seg_id, name))
return rooms or None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Improve robustness when processing room mapping by explicitly checking for None values in the list items and fallback names. This prevents potential string representation of 'None' from being used as room names or IDs.

    async def _process_room_mapping(self, data):
        if not data or not isinstance(data, list):
            return None
        cloud_rooms = {}
        if self.xiaomi_cloud:
            try:
                homes = await self.xiaomi_cloud.async_get_homerooms() or []
                for home in homes:
                    for room in home.get('roomlist', []):
                        r_id = room.get('id')
                        r_name = room.get('name')
                        if r_id is not None and r_name is not None:
                            cloud_rooms[r_id] = r_name
            except Exception:
                pass
        rooms = []
        for item in data:
            if isinstance(item, (list, tuple)) and len(item) >= 2:
                if item[0] is not None:
                    seg_id = str(item[0])
                    cloud_id = item[1]
                    name = None
                    if len(item) > 2 and item[2] is not None:
                        name = str(item[2])
                    if not name and cloud_id is not None:
                        name = cloud_rooms.get(cloud_id)
                    if not name:
                        name = f'Room {seg_id}'
                    rooms.append((seg_id, name))
        return rooms or None

Refactor room button creation and normalization methods to improve robustness against invalid data. Added checks for room entries and streamlined the addition of room buttons for better performance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant