Simplify async_setup_entry in bluesound integration (#122874)

* Use async_added_to_hass and async_will_remove_from_hass

* Remove self._hass
This commit is contained in:
Louis Christ 2024-07-31 19:04:17 +02:00 committed by GitHub
parent 69a8c5dc9f
commit ae9e8ca419
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 29 additions and 125 deletions

View File

@ -67,15 +67,4 @@ async def async_setup_entry(
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
player = None
for player in hass.data[DOMAIN]:
if player.unique_id == config_entry.unique_id:
break
if player is None:
return False
player.stop_polling()
hass.data[DOMAIN].remove(player)
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

View File

@ -3,8 +3,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from asyncio import CancelledError from asyncio import CancelledError, Task
from collections.abc import Callable
from contextlib import suppress from contextlib import suppress
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
@ -31,15 +30,11 @@ from homeassistant.const import (
CONF_HOSTS, CONF_HOSTS,
CONF_NAME, CONF_NAME,
CONF_PORT, CONF_PORT,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP,
) )
from homeassistant.core import ( from homeassistant.core import (
DOMAIN as HOMEASSISTANT_DOMAIN, DOMAIN as HOMEASSISTANT_DOMAIN,
Event,
HomeAssistant, HomeAssistant,
ServiceCall, ServiceCall,
callback,
) )
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.exceptions import ServiceValidationError from homeassistant.exceptions import ServiceValidationError
@ -50,7 +45,6 @@ from homeassistant.helpers.device_registry import (
format_mac, format_mac,
) )
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -123,59 +117,6 @@ SERVICE_TO_METHOD = {
} }
def _add_player(
hass: HomeAssistant,
async_add_entities: AddEntitiesCallback,
host: str,
port: int,
player: Player,
sync_status: SyncStatus,
):
"""Add Bluesound players."""
@callback
def _init_bluesound_player(event: Event | None = None):
"""Start polling."""
hass.async_create_task(bluesound_player.async_init())
@callback
def _start_polling(event: Event | None = None):
"""Start polling."""
bluesound_player.start_polling()
@callback
def _stop_polling(event: Event | None = None):
"""Stop polling."""
bluesound_player.stop_polling()
@callback
def _add_bluesound_player_cb():
"""Add player after first sync fetch."""
if bluesound_player.id in [x.id for x in hass.data[DATA_BLUESOUND]]:
_LOGGER.warning("Player already added %s", bluesound_player.id)
return
hass.data[DATA_BLUESOUND].append(bluesound_player)
async_add_entities([bluesound_player])
_LOGGER.debug("Added device with name: %s", bluesound_player.name)
if hass.is_running:
_start_polling()
else:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _start_polling)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_polling)
bluesound_player = BluesoundPlayer(
hass, host, port, player, sync_status, _add_bluesound_player_cb
)
if hass.is_running:
_init_bluesound_player()
else:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _init_bluesound_player)
async def _async_import(hass: HomeAssistant, config: ConfigType) -> None: async def _async_import(hass: HomeAssistant, config: ConfigType) -> None:
"""Import config entry from configuration.yaml.""" """Import config entry from configuration.yaml."""
if not hass.config_entries.async_entries(DOMAIN): if not hass.config_entries.async_entries(DOMAIN):
@ -252,18 +193,16 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the Bluesound entry.""" """Set up the Bluesound entry."""
host = config_entry.data[CONF_HOST] bluesound_player = BluesoundPlayer(
port = config_entry.data[CONF_PORT] config_entry.data[CONF_HOST],
config_entry.data[CONF_PORT],
_add_player(
hass,
async_add_entities,
host,
port,
config_entry.runtime_data.player, config_entry.runtime_data.player,
config_entry.runtime_data.sync_status, config_entry.runtime_data.sync_status,
) )
hass.data[DATA_BLUESOUND].append(bluesound_player)
async_add_entities([bluesound_player])
async def async_setup_platform( async def async_setup_platform(
hass: HomeAssistant, hass: HomeAssistant,
@ -290,36 +229,30 @@ class BluesoundPlayer(MediaPlayerEntity):
def __init__( def __init__(
self, self,
hass: HomeAssistant,
host: str, host: str,
port: int, port: int,
player: Player, player: Player,
sync_status: SyncStatus, sync_status: SyncStatus,
init_callback: Callable[[], None],
) -> None: ) -> None:
"""Initialize the media player.""" """Initialize the media player."""
self.host = host self.host = host
self._hass = hass
self.port = port self.port = port
self._polling_task = None # The actual polling task. self._polling_task: Task[None] | None = None # The actual polling task.
self._id = None self._id = sync_status.id
self._last_status_update = None self._last_status_update = None
self._sync_status: SyncStatus | None = None self._sync_status = sync_status
self._status: Status | None = None self._status: Status | None = None
self._inputs: list[Input] = [] self._inputs: list[Input] = []
self._presets: list[Preset] = [] self._presets: list[Preset] = []
self._is_online = False self._is_online = False
self._retry_remove = None
self._muted = False self._muted = False
self._master: BluesoundPlayer | None = None self._master: BluesoundPlayer | None = None
self._is_master = False self._is_master = False
self._group_name = None self._group_name = None
self._group_list: list[str] = [] self._group_list: list[str] = []
self._bluesound_device_name = None self._bluesound_device_name = sync_status.name
self._player = player self._player = player
self._init_callback = init_callback
self._attr_unique_id = format_unique_id(sync_status.mac, port) self._attr_unique_id = format_unique_id(sync_status.mac, port)
# there should always be one player with the default port per mac # there should always be one player with the default port per mac
if port is DEFAULT_PORT: if port is DEFAULT_PORT:
@ -349,23 +282,18 @@ class BluesoundPlayer(MediaPlayerEntity):
except ValueError: except ValueError:
return -1 return -1
async def force_update_sync_status(self, on_updated_cb=None) -> bool: async def force_update_sync_status(self) -> bool:
"""Update the internal status.""" """Update the internal status."""
sync_status = await self._player.sync_status() sync_status = await self._player.sync_status()
self._sync_status = sync_status self._sync_status = sync_status
if not self._id:
self._id = sync_status.id
if not self._bluesound_device_name:
self._bluesound_device_name = sync_status.name
if sync_status.master is not None: if sync_status.master is not None:
self._is_master = False self._is_master = False
master_id = f"{sync_status.master.ip}:{sync_status.master.port}" master_id = f"{sync_status.master.ip}:{sync_status.master.port}"
master_device = [ master_device = [
device device
for device in self._hass.data[DATA_BLUESOUND] for device in self.hass.data[DATA_BLUESOUND]
if device.id == master_id if device.id == master_id
] ]
@ -380,8 +308,6 @@ class BluesoundPlayer(MediaPlayerEntity):
slaves = self._sync_status.slaves slaves = self._sync_status.slaves
self._is_master = slaves is not None self._is_master = slaves is not None
if on_updated_cb:
on_updated_cb()
return True return True
async def _start_poll_command(self): async def _start_poll_command(self):
@ -401,32 +327,21 @@ class BluesoundPlayer(MediaPlayerEntity):
_LOGGER.exception("Unexpected error in %s:%s", self.name, self.port) _LOGGER.exception("Unexpected error in %s:%s", self.name, self.port)
raise raise
def start_polling(self): async def async_added_to_hass(self) -> None:
"""Start the polling task.""" """Start the polling task."""
self._polling_task = self._hass.async_create_task(self._start_poll_command()) await super().async_added_to_hass()
def stop_polling(self): self._polling_task = self.hass.async_create_task(self._start_poll_command())
async def async_will_remove_from_hass(self) -> None:
"""Stop the polling task.""" """Stop the polling task."""
self._polling_task.cancel() await super().async_will_remove_from_hass()
async def async_init(self, triggered=None): assert self._polling_task is not None
"""Initialize the player async.""" if self._polling_task.cancel():
try: await self._polling_task
if self._retry_remove is not None:
self._retry_remove()
self._retry_remove = None
await self.force_update_sync_status(self._init_callback) self.hass.data[DATA_BLUESOUND].remove(self)
except (TimeoutError, ClientError):
_LOGGER.error("Node %s:%s is offline, retrying later", self.host, self.port)
self._retry_remove = async_track_time_interval(
self._hass, self.async_init, NODE_RETRY_INITIATION
)
except Exception:
_LOGGER.exception(
"Unexpected when initiating error in %s:%s", self.host, self.port
)
raise
async def async_update(self) -> None: async def async_update(self) -> None:
"""Update internal status of the entity.""" """Update internal status of the entity."""
@ -490,13 +405,13 @@ class BluesoundPlayer(MediaPlayerEntity):
"""Trigger sync status update on all devices.""" """Trigger sync status update on all devices."""
_LOGGER.debug("Trigger sync status on all devices") _LOGGER.debug("Trigger sync status on all devices")
for player in self._hass.data[DATA_BLUESOUND]: for player in self.hass.data[DATA_BLUESOUND]:
await player.force_update_sync_status() await player.force_update_sync_status()
@Throttle(SYNC_STATUS_INTERVAL) @Throttle(SYNC_STATUS_INTERVAL)
async def async_update_sync_status(self, on_updated_cb=None): async def async_update_sync_status(self):
"""Update sync status.""" """Update sync status."""
await self.force_update_sync_status(on_updated_cb) await self.force_update_sync_status()
@Throttle(UPDATE_CAPTURE_INTERVAL) @Throttle(UPDATE_CAPTURE_INTERVAL)
async def async_update_captures(self) -> list[Input] | None: async def async_update_captures(self) -> list[Input] | None:
@ -615,7 +530,7 @@ class BluesoundPlayer(MediaPlayerEntity):
if self._status is not None: if self._status is not None:
volume = self._status.volume volume = self._status.volume
if self.is_grouped and self._sync_status is not None: if self.is_grouped:
volume = self._sync_status.volume volume = self._sync_status.volume
if volume is None: if volume is None:
@ -630,7 +545,7 @@ class BluesoundPlayer(MediaPlayerEntity):
if self._status is not None: if self._status is not None:
mute = self._status.mute mute = self._status.mute
if self.is_grouped and self._sync_status is not None: if self.is_grouped:
mute = self._sync_status.mute_volume is not None mute = self._sync_status.mute_volume is not None
return mute return mute
@ -778,7 +693,7 @@ class BluesoundPlayer(MediaPlayerEntity):
device_group = self._group_name.split("+") device_group = self._group_name.split("+")
sorted_entities = sorted( sorted_entities = sorted(
self._hass.data[DATA_BLUESOUND], self.hass.data[DATA_BLUESOUND],
key=lambda entity: entity.is_master, key=lambda entity: entity.is_master,
reverse=True, reverse=True,
) )