Repository: home-assistant/core
File: homeassistant/components/sonos/speaker.py (Python)

Code before:
        speakers: list[SonosSpeaker],
    ) -> None:
        """Form a group with other players."""
        # When joining multiple speakers, build the group incrementally and
        # wait for the grouping to complete after each join. This avoids race
        # conditions in zone topology updates.
        async with config_entry.runtime_data.topology_condition:
            join_list: list[SonosSpeaker] = []
            for speaker in speakers:
                _LOGGER.debug("Join %s to %s", speaker.zone_name, master.zone_name)
                join_list.append(speaker)
                group: list[SonosSpeaker] = await hass.async_add_executor_job(
                    master.join, join_list
                )
                await SonosSpeaker.wait_for_groups(hass, config_entry, [group])
                _LOGGER.debug(
                    "Join Complete %s to %s", speaker.zone_name, master.zone_name
                )

    @soco_error()
    def unjoin(self) -> None:
        """Unjoin the player from a group."""
        if self.sonos_group == [self]:
            return
        self.soco.unjoin()
        self.coordinator = None

    @staticmethod
    async def unjoin_multi(
        hass: HomeAssistant,
        config_entry: SonosConfigEntry,
        speakers: list[SonosSpeaker],
    ) -> None:
        """Unjoin several players from their group."""

        def _unjoin_all(speakers: list[SonosSpeaker]) -> None:
            """Sync helper."""
            # Detach all joined speakers first to prevent inheritance of queues
            coordinators = [s for s in speakers if s.is_coordinator]
            joined_speakers = [s for s in speakers if not s.is_coordinator]

            for speaker in joined_speakers + coordinators:
                speaker.unjoin()

        async with config_entry.runtime_data.topology_condition:
            await hass.async_add_executor_job(_unjoin_all, speakers)
            await SonosSpeaker.wait_for_groups(
                hass,
                config_entry,
                [[s] for s in speakers],
                action="unjoin",

Review comment (suggestion):
The coordinator should not be cleared here as it will be cleared when the Sonos speakers respond back saying the unjoin operation has completed. Optimistically clearing it makes the state inconsistent.

Code after:
        speaker_entity_id = cast(
            str,
            entity_registry.async_get_entity_id(MP_DOMAIN, DOMAIN, self.uid),
        )
        self.sonos_group_entities = [speaker_entity_id]
        self.async_write_entity_states()

    @soco_error()
    def join(self, speakers: list[SonosSpeaker]) -> list[SonosSpeaker]:
        """Form a group with other players."""
        if self.coordinator:
            self.unjoin()
            group = [self]
        else:
            group = self.sonos_group.copy()

        for speaker in speakers:
            if speaker.soco.uid != self.soco.uid:
                if speaker not in group:
                    speaker.soco.join(self.soco)
                    speaker.coordinator = self
                    group.append(speaker)

        return group

    @staticmethod
    async def join_multi(
        hass: HomeAssistant,
        config_entry: SonosConfigEntry,
        master: SonosSpeaker,
        speakers: list[SonosSpeaker],
    ) -> None:
        """Form a group with other players."""
        # When joining multiple speakers, build the group incrementally and
        # wait for the grouping to complete after each join. This avoids race
        # conditions in zone topology updates.
        async with config_entry.runtime_data.topology_condition:
            join_list: list[SonosSpeaker] = []
            for speaker in speakers:
                _LOGGER.debug("Join %s to %s", speaker.zone_name, master.zone_name)
                join_list.append(speaker)
                group: list[SonosSpeaker] = await hass.async_add_executor_job(
                    master.join, join_list
                )
                await SonosSpeaker.wait_for_groups(hass, config_entry, [group])
                _LOGGER.debug(
                    "Join Complete %s to %s", speaker.zone_name, master.zone_name
                )

    @soco_error()
    def unjoin(self) -> None: