From ae1b8f3456180aee0f27e347f62eb06ea6e2be0e Mon Sep 17 00:00:00 2001 From: mj23000 Date: Mon, 28 Oct 2024 14:28:02 +0100 Subject: [PATCH] Store BangOlufsenData in config_entry runtime data Standardize config_entry naming from entry to config_entry Remove client as init parameter for all entity classes Toggle seek support flag instead of raising an exception when the source is not seekable --- custom_components/bang_olufsen/__init__.py | 63 +++++++++-------- .../bang_olufsen/binary_sensor.py | 25 +++---- custom_components/bang_olufsen/button.py | 21 +++--- custom_components/bang_olufsen/const.py | 68 ++++++++++++++++--- custom_components/bang_olufsen/coordinator.py | 15 ++-- custom_components/bang_olufsen/entity.py | 25 ++++--- custom_components/bang_olufsen/event.py | 32 ++++----- custom_components/bang_olufsen/manifest.json | 2 +- .../bang_olufsen/media_player.py | 54 ++++++++------- custom_components/bang_olufsen/number.py | 27 ++++---- custom_components/bang_olufsen/select.py | 25 +++---- custom_components/bang_olufsen/sensor.py | 57 +++++++--------- custom_components/bang_olufsen/strings.json | 3 - custom_components/bang_olufsen/switch.py | 23 +++---- custom_components/bang_olufsen/text.py | 30 ++++---- .../bang_olufsen/translations/en.json | 3 - custom_components/bang_olufsen/util.py | 4 ++ 17 files changed, 249 insertions(+), 228 deletions(-) diff --git a/custom_components/bang_olufsen/__init__.py b/custom_components/bang_olufsen/__init__.py index 1dbedc8..723d9a3 100644 --- a/custom_components/bang_olufsen/__init__.py +++ b/custom_components/bang_olufsen/__init__.py @@ -8,7 +8,6 @@ from mozart_api.exceptions import ApiException from mozart_api.mozart_client import MozartClient -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_MODEL, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady @@ -17,7 +16,7 @@ from .const import DOMAIN from .coordinator import BangOlufsenCoordinator -from .util import BangOlufsenData, get_remote +from .util import BangOlufsenConfigEntry, BangOlufsenData, get_remote PLATFORMS = [ Platform.BINARY_SENSOR, @@ -45,23 +44,27 @@ async def _start_websocket_listener(data: BangOlufsenData) -> None: await data.client.connect_notifications(remote_control=True, reconnect=True) -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_setup_entry( + hass: HomeAssistant, config_entry: BangOlufsenConfigEntry +) -> bool: """Set up from a config entry.""" # Remove casts to str - assert entry.unique_id + assert config_entry.unique_id # Create device now as BangOlufsenWebsocket needs a device for debug logging, firing events etc. # And in order to ensure entity platforms (button, binary_sensor) have device name before the primary (media_player) is initialized device_registry = dr.async_get(hass) device_registry.async_get_or_create( - config_entry_id=entry.entry_id, - identifiers={(DOMAIN, entry.unique_id)}, - name=entry.title, - model=entry.data[CONF_MODEL], + config_entry_id=config_entry.entry_id, + identifiers={(DOMAIN, config_entry.unique_id)}, + name=config_entry.title, + model=config_entry.data[CONF_MODEL], ) - client = MozartClient(host=entry.data[CONF_HOST], ssl_context=get_default_context()) + client = MozartClient( + host=config_entry.data[CONF_HOST], ssl_context=get_default_context() + ) # Check API and WebSocket connection try: @@ -74,34 +77,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: TimeoutError, ) as error: await client.close_api_client() - raise ConfigEntryNotReady(f"Unable to connect to {entry.title}") from error + raise ConfigEntryNotReady( + f"Unable to connect to {config_entry.title}" + ) from error # Initialize coordinator - coordinator = BangOlufsenCoordinator(hass, entry, client) + coordinator = BangOlufsenCoordinator(hass, config_entry, client) await coordinator.async_config_entry_first_refresh() # Add the coordinator and API client - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = BangOlufsenData( - coordinator, - client, - ) + config_entry.runtime_data = BangOlufsenData(coordinator, client) # Check for connected Beoremote One if remote := await get_remote(client): assert remote.serial_number # Create Beoremote One device - assert entry.unique_id + assert config_entry.unique_id device_registry = dr.async_get(hass) device_registry.async_get_or_create( - config_entry_id=entry.entry_id, + config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, remote.serial_number)}, name=f"Beoremote One {remote.serial_number}", model="Beoremote One", serial_number=remote.serial_number, sw_version=remote.app_version, manufacturer="Bang & Olufsen", - via_device=(DOMAIN, entry.unique_id), + via_device=(DOMAIN, config_entry.unique_id), ) else: # If the remote is no longer available, then delete the device. @@ -110,34 +112,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: device_registry = dr.async_get(hass) devices = device_registry.devices.get_devices_for_config_entry_id( - entry.entry_id + config_entry.entry_id ) for device in devices: assert device.model is not None if device.model == "Beoremote One": device_registry.async_remove_device(device.id) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) # Start WebSocket connection when all entities have been initialized - entry.async_create_background_task( + config_entry.async_create_background_task( hass, - _start_websocket_listener(hass.data[DOMAIN][entry.entry_id]), - f"{DOMAIN}-{entry.unique_id}-websocket_starter", + _start_websocket_listener(config_entry.runtime_data), + f"{DOMAIN}-{config_entry.unique_id}-websocket_starter", ) return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_unload_entry( + hass: HomeAssistant, config_entry: BangOlufsenConfigEntry +) -> bool: """Unload a config entry.""" # Close the API client and WebSocket notification listener - hass.data[DOMAIN][entry.entry_id].client.disconnect_notifications() - await hass.data[DOMAIN][entry.entry_id].client.close_api_client() - - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) + config_entry.runtime_data.client.disconnect_notifications() + await config_entry.runtime_data.client.close_api_client() - return unload_ok + return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) diff --git a/custom_components/bang_olufsen/binary_sensor.py b/custom_components/bang_olufsen/binary_sensor.py index 802e8e5..c59fbd5 100644 --- a/custom_components/bang_olufsen/binary_sensor.py +++ b/custom_components/bang_olufsen/binary_sensor.py @@ -3,50 +3,45 @@ from __future__ import annotations from mozart_api.models import BatteryState -from mozart_api.mozart_client import MozartClient from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import CONNECTION_STATUS, DOMAIN, WebsocketNotification +from .const import CONNECTION_STATUS, WebsocketNotification from .entity import BangOlufsenEntity -from .util import BangOlufsenData, set_platform_initialized +from .util import BangOlufsenConfigEntry, set_platform_initialized async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: BangOlufsenConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Binary Sensor entities from config entry.""" - data: BangOlufsenData = hass.data[DOMAIN][config_entry.entry_id] entities: list[BangOlufsenEntity] = [] # Check if device has a battery - battery_state = await data.client.get_battery_state() + battery_state = await config_entry.runtime_data.client.get_battery_state() if battery_state.battery_level and battery_state.battery_level > 0: - entities.append( - BangOlufsenBinarySensorBatteryCharging(config_entry, data.client) - ) + entities.append(BangOlufsenBinarySensorBatteryCharging(config_entry)) async_add_entities(new_entities=entities) - set_platform_initialized(data) + set_platform_initialized(config_entry.runtime_data) class BangOlufsenBinarySensor(BangOlufsenEntity, BinarySensorEntity): """Base Binary Sensor class.""" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the Binary Sensor.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_is_on = False @@ -57,9 +52,9 @@ class BangOlufsenBinarySensorBatteryCharging(BangOlufsenBinarySensor): _attr_icon = "mdi:battery-charging" _attr_translation_key = "battery_charging" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the battery charging Binary Sensor.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING self._attr_unique_id = f"{self._unique_id}-battery-charging" diff --git a/custom_components/bang_olufsen/button.py b/custom_components/bang_olufsen/button.py index 8522d03..c0bcbbc 100644 --- a/custom_components/bang_olufsen/button.py +++ b/custom_components/bang_olufsen/button.py @@ -7,39 +7,37 @@ from mozart_api.models import Preset from homeassistant.components.button import ButtonEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONNECTION_STATUS, DOMAIN, BangOlufsenSource +from .const import CONNECTION_STATUS, BangOlufsenSource from .entity import BangOlufsenEntity -from .util import BangOlufsenData, set_platform_initialized +from .util import BangOlufsenConfigEntry, set_platform_initialized async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: BangOlufsenConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Button entities from config entry.""" - data: BangOlufsenData = hass.data[DOMAIN][config_entry.entry_id] entities: list[BangOlufsenEntity] = [] # Get available favourites from coordinator. - favourites = data.coordinator.data + favourites = config_entry.runtime_data.coordinator.data entities.extend( [ - BangOlufsenButtonFavourite(config_entry, data, favourites[favourite]) + BangOlufsenButtonFavourite(config_entry, favourites[favourite]) for favourite in favourites ] ) async_add_entities(new_entities=entities) - set_platform_initialized(data) + set_platform_initialized(config_entry.runtime_data) class BangOlufsenButton(ButtonEntity, BangOlufsenEntity): @@ -53,13 +51,12 @@ class BangOlufsenButtonFavourite(CoordinatorEntity, BangOlufsenButton): def __init__( self, - entry: ConfigEntry, - data: BangOlufsenData, + config_entry: BangOlufsenConfigEntry, favourite: Preset, ) -> None: """Init a favourite Button.""" - CoordinatorEntity.__init__(self, data.coordinator) - BangOlufsenButton.__init__(self, entry, data.client) + CoordinatorEntity.__init__(self, config_entry.runtime_data.coordinator) + BangOlufsenButton.__init__(self, config_entry) self._favourite_id: int = int(cast(str, favourite.name)[6:]) self._favourite: Preset = favourite diff --git a/custom_components/bang_olufsen/const.py b/custom_components/bang_olufsen/const.py index be32c12..c7c9233 100644 --- a/custom_components/bang_olufsen/const.py +++ b/custom_components/bang_olufsen/const.py @@ -17,16 +17,56 @@ class BangOlufsenSource: """Class used for associating device source ids with friendly names. May not include all sources.""" - URI_STREAMER: Final[Source] = Source(name="Audio Streamer", id="uriStreamer") - BLUETOOTH: Final[Source] = Source(name="Bluetooth", id="bluetooth") - CHROMECAST: Final[Source] = Source(name="Chromecast built-in", id="chromeCast") - LINE_IN: Final[Source] = Source(name="Line-In", id="lineIn") - SPDIF: Final[Source] = Source(name="Optical", id="spdif") - NET_RADIO: Final[Source] = Source(name="B&O Radio", id="netRadio") - DEEZER: Final[Source] = Source(name="Deezer", id="deezer") - TIDAL: Final[Source] = Source(name="Tidal", id="tidal") - USB_IN: Final[Source] = Source(name="USB", id="usbIn") - UNKNOWN: Final[Source] = Source(name="Unknown Source", id="unknown") + URI_STREAMER: Final[Source] = Source( + name="Audio Streamer", + id="uriStreamer", + is_seekable=False, + ) + BLUETOOTH: Final[Source] = Source( + name="Bluetooth", + id="bluetooth", + is_seekable=False, + ) + CHROMECAST: Final[Source] = Source( + name="Chromecast built-in", + id="chromeCast", + is_seekable=False, + ) + LINE_IN: Final[Source] = Source( + name="Line-In", + id="lineIn", + is_seekable=False, + ) + SPDIF: Final[Source] = Source( + name="Optical", + id="spdif", + is_seekable=False, + ) + NET_RADIO: Final[Source] = Source( + name="B&O Radio", + id="netRadio", + is_seekable=False, + ) + DEEZER: Final[Source] = Source( + name="Deezer", + id="deezer", + is_seekable=True, + ) + TIDAL: Final[Source] = Source( + name="Tidal", + id="tidal", + is_seekable=True, + ) + USB_IN: Final[Source] = Source( + name="USB", + id="usbIn", + is_seekable=False, + ) + UNKNOWN: Final[Source] = Source( + name="Unknown Source", + id="unknown", + is_seekable=False, + ) BANG_OLUFSEN_STATES: dict[str, MediaPlayerState] = { @@ -188,6 +228,7 @@ class WebsocketNotification(StrEnum): is_playable=False, name="Audio Streamer", type=SourceTypeEnum(value="uriStreamer"), + is_seekable=False, ), Source( id="bluetooth", @@ -195,6 +236,7 @@ class WebsocketNotification(StrEnum): is_playable=False, name="Bluetooth", type=SourceTypeEnum(value="bluetooth"), + is_seekable=False, ), Source( id="spotify", @@ -202,6 +244,7 @@ class WebsocketNotification(StrEnum): is_playable=False, name="Spotify Connect", type=SourceTypeEnum(value="spotify"), + is_seekable=True, ), Source( id="lineIn", @@ -209,6 +252,7 @@ class WebsocketNotification(StrEnum): is_playable=True, name="Line-In", type=SourceTypeEnum(value="lineIn"), + is_seekable=False, ), Source( id="spdif", @@ -216,6 +260,7 @@ class WebsocketNotification(StrEnum): is_playable=True, name="Optical", type=SourceTypeEnum(value="spdif"), + is_seekable=False, ), Source( id="netRadio", @@ -223,6 +268,7 @@ class WebsocketNotification(StrEnum): is_playable=True, name="B&O Radio", type=SourceTypeEnum(value="netRadio"), + is_seekable=False, ), Source( id="deezer", @@ -230,6 +276,7 @@ class WebsocketNotification(StrEnum): is_playable=True, name="Deezer", type=SourceTypeEnum(value="deezer"), + is_seekable=True, ), Source( id="tidalConnect", @@ -237,6 +284,7 @@ class WebsocketNotification(StrEnum): is_playable=True, name="Tidal Connect", type=SourceTypeEnum(value="tidalConnect"), + is_seekable=True, ), ] ) diff --git a/custom_components/bang_olufsen/coordinator.py b/custom_components/bang_olufsen/coordinator.py index 426b94c..0705790 100644 --- a/custom_components/bang_olufsen/coordinator.py +++ b/custom_components/bang_olufsen/coordinator.py @@ -26,7 +26,6 @@ ) from mozart_api.mozart_client import MozartClient -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -41,6 +40,7 @@ WebsocketNotification, ) from .entity import BangOlufsenBase +from .util import BangOlufsenConfigEntry _LOGGER = logging.getLogger(__name__) @@ -49,7 +49,10 @@ class BangOlufsenCoordinator(DataUpdateCoordinator, BangOlufsenBase): """The entity coordinator and WebSocket listener(s).""" def __init__( - self, hass: HomeAssistant, entry: ConfigEntry, client: MozartClient + self, + hass: HomeAssistant, + config_entry: BangOlufsenConfigEntry, + client: MozartClient, ) -> None: """Initialize the entity coordinator.""" DataUpdateCoordinator.__init__( @@ -60,7 +63,7 @@ def __init__( update_interval=timedelta(seconds=15), always_update=False, ) - BangOlufsenBase.__init__(self, entry, client) + BangOlufsenBase.__init__(self, config_entry, client) self._device = self.get_device() @@ -131,12 +134,12 @@ def _update_connection_status(self) -> None: def on_connection(self) -> None: """Handle WebSocket connection made.""" - _LOGGER.debug("Connected to the %s notification channel", self.entry.title) + _LOGGER.debug("Connected to the %s notification channel", self._entry.title) self._update_connection_status() def on_connection_lost(self) -> None: """Handle WebSocket connection lost.""" - _LOGGER.error("Lost connection to the %s", self.entry.title) + _LOGGER.error("Lost connection to the %s", self._entry.title) self._update_connection_status() def on_active_listening_mode(self, notification: ListeningModeProps) -> None: @@ -220,7 +223,7 @@ def on_notification_notification( self.hass.loop.call_later( 5, self.hass.config_entries.async_schedule_reload, - self.entry.entry_id, + self._entry.entry_id, ) elif notification_type in ( diff --git a/custom_components/bang_olufsen/entity.py b/custom_components/bang_olufsen/entity.py index fbe2dcf..2c60865 100644 --- a/custom_components/bang_olufsen/entity.py +++ b/custom_components/bang_olufsen/entity.py @@ -25,30 +25,37 @@ ) from mozart_api.mozart_client import MozartClient -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST from homeassistant.core import callback from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity from .const import DOMAIN +from .util import BangOlufsenConfigEntry class BangOlufsenBase: """Base class for BangOlufsen Home Assistant objects.""" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__( + self, config_entry: BangOlufsenConfigEntry, client: MozartClient | None = None + ) -> None: """Initialize the object.""" - # Set the MozartClient - self._client = client + # Set the MozartClient. + # Allowing the client to be set directly allows the coordinator to be initialized before being added to runtime_data. + if client: + self._client = client + else: + self._client = config_entry.runtime_data.client # Get the input from the config entry. - self.entry = entry + # Use _entry instead of config_entry to avoid conflicts with Home Assistant classes such as DataUpdateCoordinator. + self._entry = config_entry # Set the configuration variables. - self._host: str = self.entry.data[CONF_HOST] - self._unique_id: str = cast(str, self.entry.unique_id) + self._host: str = self._entry.data[CONF_HOST] + self._unique_id: str = cast(str, self._entry.unique_id) # Objects that get directly updated by notifications. self._active_listening_mode = ListeningModeProps() @@ -79,9 +86,9 @@ class BangOlufsenEntity(Entity, BangOlufsenBase): _attr_has_entity_name = True _attr_should_poll = False - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Initialize the object.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, self._unique_id)}) diff --git a/custom_components/bang_olufsen/event.py b/custom_components/bang_olufsen/event.py index 032fbe4..50719c1 100644 --- a/custom_components/bang_olufsen/event.py +++ b/custom_components/bang_olufsen/event.py @@ -3,7 +3,6 @@ from __future__ import annotations from mozart_api.models import PairedRemote -from mozart_api.mozart_client import MozartClient from homeassistant.components.event import EventDeviceClass, EventEntity from homeassistant.config_entries import ConfigEntry @@ -31,16 +30,15 @@ WebsocketNotification, ) from .entity import BangOlufsenEntity -from .util import BangOlufsenData, get_remote, set_platform_initialized +from .util import BangOlufsenConfigEntry, get_remote, set_platform_initialized async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: BangOlufsenConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Sensor entities from config entry.""" - data: BangOlufsenData = hass.data[DOMAIN][config_entry.entry_id] entities: list[EventEntity] = [] @@ -48,23 +46,22 @@ async def async_setup_entry( if config_entry.data[CONF_MODEL] in MODEL_SUPPORT_MAP[MODEL_SUPPORT_DEVICE_BUTTONS]: entities.extend( [ - BangOlufsenButtonEvent(config_entry, data.client, button_type) + BangOlufsenButtonEvent(config_entry, button_type) for button_type in DEVICE_BUTTONS ] ) # Check if device supports proximity detection. if config_entry.data[CONF_MODEL] in MODEL_SUPPORT_MAP[MODEL_SUPPORT_PROXIMITY]: - entities.append(BangOlufsenEventProximity(config_entry, data.client)) + entities.append(BangOlufsenEventProximity(config_entry)) # Check for connected Beoremote One - if remote := await get_remote(data.client): + if remote := await get_remote(config_entry.runtime_data.client): # Add Light keys entities.extend( [ BangOlufsenRemoteKeyEvent( config_entry, - data.client, remote, f"{BEO_REMOTE_SUBMENU_LIGHT}/{key_type}", ) @@ -77,7 +74,6 @@ async def async_setup_entry( [ BangOlufsenRemoteKeyEvent( config_entry, - data.client, remote, f"{BEO_REMOTE_SUBMENU_CONTROL}/{key_type}", ) @@ -87,7 +83,7 @@ async def async_setup_entry( async_add_entities(new_entities=entities) - set_platform_initialized(data) + set_platform_initialized(config_entry.runtime_data) class BangOlufsenEvent(BangOlufsenEntity, EventEntity): @@ -109,11 +105,9 @@ class BangOlufsenButtonEvent(BangOlufsenEvent): _attr_event_types = DEVICE_BUTTON_EVENTS _attr_icon = "mdi:gesture-tap-button" - def __init__( - self, entry: ConfigEntry, client: MozartClient, button_type: str - ) -> None: + def __init__(self, config_entry: ConfigEntry, button_type: str) -> None: """Initialize Button.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_unique_id = f"{self._unique_id}_{button_type}" @@ -149,13 +143,13 @@ class BangOlufsenRemoteKeyEvent(BangOlufsenEvent): def __init__( self, - entry: ConfigEntry, - client: MozartClient, + config_entry: ConfigEntry, remote: PairedRemote, key_type: str, ) -> None: """Initialize Beoremote One key.""" - super().__init__(entry, client) + super().__init__(config_entry) + assert remote.serial_number self._attr_unique_id = f"{remote.serial_number}_{key_type}" @@ -193,9 +187,9 @@ class BangOlufsenEventProximity(BangOlufsenEvent): _attr_icon = "mdi:account-question" _attr_translation_key = "proximity" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Init the proximity event.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_unique_id = f"{self._unique_id}_proximity" diff --git a/custom_components/bang_olufsen/manifest.json b/custom_components/bang_olufsen/manifest.json index 2ea5169..3fa1ab2 100644 --- a/custom_components/bang_olufsen/manifest.json +++ b/custom_components/bang_olufsen/manifest.json @@ -9,6 +9,6 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/bang-olufsen/bang_olufsen-hacs/issues", "requirements": ["mozart-api==4.1.1.116.0"], - "version": "2.5.4", + "version": "2.5.5", "zeroconf": ["_bangolufsen._tcp.local."] } diff --git a/custom_components/bang_olufsen/media_player.py b/custom_components/bang_olufsen/media_player.py index 664ba79..3775899 100644 --- a/custom_components/bang_olufsen/media_player.py +++ b/custom_components/bang_olufsen/media_player.py @@ -99,7 +99,11 @@ WebsocketNotification, ) from .entity import BangOlufsenEntity -from .util import BangOlufsenData, get_serial_number_from_jid, set_platform_initialized +from .util import ( + BangOlufsenConfigEntry, + get_serial_number_from_jid, + set_platform_initialized, +) SCAN_INTERVAL = timedelta(seconds=30) @@ -129,19 +133,18 @@ async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: BangOlufsenConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up a Media Player entity from config entry.""" - data: BangOlufsenData = hass.data[DOMAIN][config_entry.entry_id] entities: list[BangOlufsenEntity] = [] - entities.append(BangOlufsenMediaPlayer(config_entry, data)) - - set_platform_initialized(data) + entities.append(BangOlufsenMediaPlayer(config_entry)) async_add_entities(new_entities=entities, update_before_add=True) + set_platform_initialized(config_entry.runtime_data) + # Register services. platform = async_get_current_platform() @@ -229,15 +232,14 @@ class BangOlufsenMediaPlayer(MediaPlayerEntity, BangOlufsenEntity): _attr_device_class = MediaPlayerDeviceClass.SPEAKER _attr_icon = "mdi:speaker-wireless" _attr_name: None | str = None - _attr_supported_features = BANG_OLUFSEN_FEATURES - def __init__(self, entry: ConfigEntry, data: BangOlufsenData) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize the media player.""" - super().__init__(entry, data.client) + super().__init__(config_entry) self._attr_should_poll = True - self._beolink_jid: str = self.entry.data[CONF_BEOLINK_JID] - self._model: str = self.entry.data[CONF_MODEL] + self._beolink_jid: str = self._entry.data[CONF_BEOLINK_JID] + self._model: str = self._entry.data[CONF_MODEL] self._attr_device_info = DeviceInfo( configuration_url=f"http://{self._host}/#/", @@ -680,6 +682,17 @@ async def _async_update_sound_modes( self.async_write_ha_state() + @property + def supported_features(self) -> MediaPlayerEntityFeature: + """Flag media player features that are supported.""" + features = BANG_OLUFSEN_FEATURES + + # Add seeking if supported by the current source + if self._source_change.is_seekable is True: + features |= MediaPlayerEntityFeature.SEEK + + return features + @property def state(self) -> MediaPlayerState: """Return the current state of the media player.""" @@ -853,21 +866,12 @@ async def async_media_next_track(self) -> None: async def async_media_seek(self, position: float) -> None: """Seek to position in ms.""" - if self._source_change.is_seekable: - await self._client.seek_to_position(position_ms=int(position * 1000)) - # Try to prevent the playback progress from bouncing in the UI. - self._attr_media_position_updated_at = utcnow() - self._playback_progress = PlaybackProgress(progress=int(position)) + await self._client.seek_to_position(position_ms=int(position * 1000)) + # Try to prevent the playback progress from bouncing in the UI. + self._attr_media_position_updated_at = utcnow() + self._playback_progress = PlaybackProgress(progress=int(position)) - self.async_write_ha_state() - else: - raise HomeAssistantError( - translation_domain=DOMAIN, - translation_key="non_seekable_source", - translation_placeholders={ - "invalid_source": cast(str, self._source_change.name), - }, - ) + self.async_write_ha_state() async def async_media_previous_track(self) -> None: """Send the previous track command.""" diff --git a/custom_components/bang_olufsen/number.py b/custom_components/bang_olufsen/number.py index 2c8e818..eee220e 100644 --- a/custom_components/bang_olufsen/number.py +++ b/custom_components/bang_olufsen/number.py @@ -3,35 +3,32 @@ from __future__ import annotations from mozart_api.models import Bass, SoundSettings, Treble -from mozart_api.mozart_client import MozartClient from homeassistant.components.number import NumberEntity, NumberMode -from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import BASS_TREBLE_RANGE, CONNECTION_STATUS, DOMAIN, WebsocketNotification +from .const import BASS_TREBLE_RANGE, CONNECTION_STATUS, WebsocketNotification from .entity import BangOlufsenEntity -from .util import BangOlufsenData, set_platform_initialized +from .util import BangOlufsenConfigEntry, set_platform_initialized async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: BangOlufsenConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Number entities from config entry.""" - data: BangOlufsenData = hass.data[DOMAIN][config_entry.entry_id] entities: list[BangOlufsenEntity] = [ - BangOlufsenNumberBass(config_entry, data.client), - BangOlufsenNumberTreble(config_entry, data.client), + BangOlufsenNumberBass(config_entry), + BangOlufsenNumberTreble(config_entry), ] async_add_entities(new_entities=entities) - set_platform_initialized(data) + set_platform_initialized(config_entry.runtime_data) class BangOlufsenNumber(BangOlufsenEntity, NumberEntity): @@ -39,9 +36,9 @@ class BangOlufsenNumber(BangOlufsenEntity, NumberEntity): _attr_mode = NumberMode.AUTO - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the Number.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_entity_category = EntityCategory.CONFIG self._attr_native_value = 0.0 @@ -55,9 +52,9 @@ class BangOlufsenNumberTreble(BangOlufsenNumber): _attr_native_min_value = float(BASS_TREBLE_RANGE.start) _attr_translation_key = "treble" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the treble Number.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_mode = NumberMode.SLIDER self._attr_unique_id = f"{self._unique_id}-treble" @@ -100,9 +97,9 @@ class BangOlufsenNumberBass(BangOlufsenNumber): _attr_native_min_value = float(BASS_TREBLE_RANGE.start) _attr_translation_key = "bass" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the bass Number.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_mode = NumberMode.SLIDER self._attr_unique_id = f"{self._unique_id}-bass" diff --git a/custom_components/bang_olufsen/select.py b/custom_components/bang_olufsen/select.py index 216b73a..aaedb83 100644 --- a/custom_components/bang_olufsen/select.py +++ b/custom_components/bang_olufsen/select.py @@ -5,54 +5,49 @@ import logging from mozart_api.models import SpeakerGroupOverview -from mozart_api.mozart_client import MozartClient from homeassistant.components.select import SelectEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import CONNECTION_STATUS, DOMAIN, WebsocketNotification +from .const import CONNECTION_STATUS, WebsocketNotification from .entity import BangOlufsenEntity -from .util import BangOlufsenData, set_platform_initialized +from .util import BangOlufsenConfigEntry, set_platform_initialized _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: BangOlufsenConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Select entities from config entry.""" - data: BangOlufsenData = hass.data[DOMAIN][config_entry.entry_id] entities: list[BangOlufsenEntity] = [] # Create the listening position entity if supported - scenes = await data.client.get_all_scenes() + scenes = await config_entry.runtime_data.client.get_all_scenes() for scene_key in scenes: scene = scenes[scene_key] if scene.tags is not None and "listeningposition" in scene.tags: - entities.append( - BangOlufsenSelectListeningPosition(config_entry, data.client) - ) + entities.append(BangOlufsenSelectListeningPosition(config_entry)) break async_add_entities(new_entities=entities) - set_platform_initialized(data) + set_platform_initialized(config_entry.runtime_data) class BangOlufsenSelect(BangOlufsenEntity, SelectEntity): """Select for Mozart settings.""" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the Select.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_entity_category = EntityCategory.CONFIG self._attr_current_option = None @@ -65,9 +60,9 @@ class BangOlufsenSelectListeningPosition(BangOlufsenSelect): _attr_icon = "mdi:sine-wave" _attr_translation_key = "listening_position" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the listening position select.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_unique_id = f"{self._unique_id}-listening-position" diff --git a/custom_components/bang_olufsen/sensor.py b/custom_components/bang_olufsen/sensor.py index 6b71244..7daf8d4 100644 --- a/custom_components/bang_olufsen/sensor.py +++ b/custom_components/bang_olufsen/sensor.py @@ -7,14 +7,12 @@ from inflection import titleize, underscore from mozart_api.models import BatteryState, PairedRemote, PlaybackContentMetadata -from mozart_api.mozart_client import MozartClient from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorStateClass, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -22,52 +20,49 @@ from .const import CONNECTION_STATUS, DOMAIN, WebsocketNotification from .entity import BangOlufsenEntity -from .util import BangOlufsenData, get_remote, set_platform_initialized +from .util import BangOlufsenConfigEntry, get_remote, set_platform_initialized SCAN_INTERVAL = timedelta(minutes=15) async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: BangOlufsenConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Sensor entities from config entry.""" - data: BangOlufsenData = hass.data[DOMAIN][config_entry.entry_id] entities: list[BangOlufsenEntity] = [ - BangOlufsenSensorInputSignal(config_entry, data.client), - BangOlufsenSensorMediaId(config_entry, data.client), + BangOlufsenSensorInputSignal(config_entry), + BangOlufsenSensorMediaId(config_entry), ] # Check if device has a battery - battery_state = await data.client.get_battery_state() + battery_state = await config_entry.runtime_data.client.get_battery_state() if battery_state.battery_level and battery_state.battery_level > 0: entities.extend( [ - BangOlufsenSensorBatteryChargingTime(config_entry, data.client), - BangOlufsenSensorBatteryLevel(config_entry, data.client), - BangOlufsenSensorBatteryPlayingTime(config_entry, data.client), + BangOlufsenSensorBatteryChargingTime(config_entry), + BangOlufsenSensorBatteryLevel(config_entry), + BangOlufsenSensorBatteryPlayingTime(config_entry), ] ) # Check for connected Beoremote One - if remote := await get_remote(data.client): - entities.append( - BangOlufsenSensorRemoteBatteryLevel(config_entry, data.client, remote) - ) + if remote := await get_remote(config_entry.runtime_data.client): + entities.append(BangOlufsenSensorRemoteBatteryLevel(config_entry, remote)) async_add_entities(new_entities=entities) - set_platform_initialized(data) + set_platform_initialized(config_entry.runtime_data) class BangOlufsenSensor(BangOlufsenEntity, SensorEntity): """Base Sensor class.""" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the Sensor.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_state_class = SensorStateClass.MEASUREMENT @@ -79,9 +74,9 @@ class BangOlufsenSensorBatteryLevel(BangOlufsenSensor): _attr_native_unit_of_measurement = "%" _attr_translation_key = "battery_level" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the battery level Sensor.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_device_class = SensorDeviceClass.BATTERY self._attr_unique_id = f"{self._unique_id}-battery-level" @@ -118,10 +113,10 @@ class BangOlufsenSensorRemoteBatteryLevel(BangOlufsenSensor): _attr_should_poll = True def __init__( - self, entry: ConfigEntry, client: MozartClient, remote: PairedRemote + self, config_entry: BangOlufsenConfigEntry, remote: PairedRemote ) -> None: """Init the battery level Sensor.""" - super().__init__(entry, client) + super().__init__(config_entry) assert remote.serial_number self._attr_device_class = SensorDeviceClass.BATTERY @@ -150,9 +145,9 @@ class BangOlufsenSensorBatteryChargingTime(BangOlufsenSensor): _attr_native_unit_of_measurement = "min" _attr_translation_key = "battery_charging_time" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the battery charging time Sensor.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_device_class = SensorDeviceClass.DURATION self._attr_unique_id = f"{self._unique_id}-battery-charging-time" @@ -199,9 +194,9 @@ class BangOlufsenSensorBatteryPlayingTime(BangOlufsenSensor): _attr_native_unit_of_measurement = "min" _attr_translation_key = "battery_playing_time" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the battery playing time Sensor.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_unique_id = f"{self._unique_id}-battery-playing-time" @@ -245,9 +240,9 @@ class BangOlufsenSensorMediaId(BangOlufsenSensor): _attr_translation_key = "media_id" _attr_icon = "mdi:information" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the media id Sensor.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_device_class = None self._attr_state_class = None @@ -266,7 +261,7 @@ async def async_added_to_hass(self) -> None: self.async_on_remove( async_dispatcher_connect( self.hass, - f"{self.entry.unique_id}_{WebsocketNotification.PLAYBACK_METADATA}", + f"{self._entry.unique_id}_{WebsocketNotification.PLAYBACK_METADATA}", self._update_playback_metadata, ) ) @@ -284,9 +279,9 @@ class BangOlufsenSensorInputSignal(BangOlufsenSensor): _attr_icon = "mdi:audio-input-stereo-minijack" _attr_translation_key = "input_signal" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the input signal Sensor.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_device_class = None self._attr_state_class = None diff --git a/custom_components/bang_olufsen/strings.json b/custom_components/bang_olufsen/strings.json index 3fe20e2..8bb9483 100644 --- a/custom_components/bang_olufsen/strings.json +++ b/custom_components/bang_olufsen/strings.json @@ -947,9 +947,6 @@ "m3u_invalid_format": { "message": "Media sources with the .m3u extension are not supported." }, - "non_seekable_source": { - "message": "{invalid_source} does not support seeking." - }, "invalid_source": { "message": "Invalid source: {invalid_source}. Valid sources are: {valid_sources}" }, diff --git a/custom_components/bang_olufsen/switch.py b/custom_components/bang_olufsen/switch.py index 6c3b17a..bc35bdd 100644 --- a/custom_components/bang_olufsen/switch.py +++ b/custom_components/bang_olufsen/switch.py @@ -5,42 +5,37 @@ from typing import Any from mozart_api.models import Loudness, SoundSettings -from mozart_api.mozart_client import MozartClient from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import CONNECTION_STATUS, DOMAIN, WebsocketNotification +from .const import CONNECTION_STATUS, WebsocketNotification from .entity import BangOlufsenEntity -from .util import BangOlufsenData, set_platform_initialized +from .util import BangOlufsenConfigEntry, set_platform_initialized async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: BangOlufsenConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Switches from config_entry.""" - data: BangOlufsenData = hass.data[DOMAIN][config_entry.entry_id] - entities: list[BangOlufsenEntity] = [ - BangOlufsenSwitchLoudness(config_entry, data.client) - ] + entities: list[BangOlufsenEntity] = [BangOlufsenSwitchLoudness(config_entry)] async_add_entities(new_entities=entities) - set_platform_initialized(data) + set_platform_initialized(config_entry.runtime_data) class BangOlufsenSwitch(BangOlufsenEntity, SwitchEntity): """Base Switch class.""" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the Switch.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_device_class = SwitchDeviceClass.SWITCH self._attr_entity_category = EntityCategory.CONFIG @@ -52,9 +47,9 @@ class BangOlufsenSwitchLoudness(BangOlufsenSwitch): _attr_icon = "mdi:music-note-plus" _attr_translation_key = "loudness" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the loudness Switch.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_unique_id = f"{self._unique_id}-loudness" diff --git a/custom_components/bang_olufsen/text.py b/custom_components/bang_olufsen/text.py index 922c2d8..3614274 100644 --- a/custom_components/bang_olufsen/text.py +++ b/custom_components/bang_olufsen/text.py @@ -3,10 +3,8 @@ from __future__ import annotations from mozart_api.models import HomeControlUri, ProductFriendlyName -from mozart_api.mozart_client import MozartClient from homeassistant.components.text import TextEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_MODEL, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -14,41 +12,37 @@ from .const import ( CONNECTION_STATUS, - DOMAIN, MODEL_SUPPORT_HOME_CONTROL, MODEL_SUPPORT_MAP, WebsocketNotification, ) from .entity import BangOlufsenEntity -from .util import BangOlufsenData, set_platform_initialized +from .util import BangOlufsenConfigEntry, set_platform_initialized async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: BangOlufsenConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Text entities from config entry.""" - data: BangOlufsenData = hass.data[DOMAIN][config_entry.entry_id] - entities: list[BangOlufsenEntity] = [ - BangOlufsenTextFriendlyName(config_entry, data.client) - ] + entities: list[BangOlufsenEntity] = [BangOlufsenTextFriendlyName(config_entry)] # Add the Home Control URI entity if the device supports it if config_entry.data[CONF_MODEL] in MODEL_SUPPORT_MAP[MODEL_SUPPORT_HOME_CONTROL]: - entities.append(BangOlufsenTextHomeControlUri(config_entry, data.client)) + entities.append(BangOlufsenTextHomeControlUri(config_entry)) async_add_entities(new_entities=entities) - set_platform_initialized(data) + set_platform_initialized(config_entry.runtime_data) class BangOlufsenText(TextEntity, BangOlufsenEntity): """Base Text class.""" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the Text.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_entity_category = EntityCategory.CONFIG @@ -59,9 +53,9 @@ class BangOlufsenTextFriendlyName(BangOlufsenText): _attr_icon = "mdi:id-card" _attr_translation_key = "friendly_name" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the friendly name Text.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_unique_id = f"{self._unique_id}-friendly-name" @@ -77,7 +71,7 @@ async def async_added_to_hass(self) -> None: self.async_on_remove( async_dispatcher_connect( self.hass, - f"{self.entry.unique_id}_{WebsocketNotification.CONFIGURATION}", + f"{self._entry.unique_id}_{WebsocketNotification.CONFIGURATION}", self._update_friendly_name, ) ) @@ -108,9 +102,9 @@ class BangOlufsenTextHomeControlUri(BangOlufsenText): _attr_icon = "mdi:link-variant" _attr_translation_key = "home_control_uri" - def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: + def __init__(self, config_entry: BangOlufsenConfigEntry) -> None: """Init the Home Control URI Text.""" - super().__init__(entry, client) + super().__init__(config_entry) self._attr_unique_id = f"{self._unique_id}-home-control-uri" diff --git a/custom_components/bang_olufsen/translations/en.json b/custom_components/bang_olufsen/translations/en.json index ca141e9..e858f1f 100644 --- a/custom_components/bang_olufsen/translations/en.json +++ b/custom_components/bang_olufsen/translations/en.json @@ -902,9 +902,6 @@ "m3u_invalid_format": { "message": "Media sources with the .m3u extension are not supported." }, - "non_seekable_source": { - "message": "{invalid_source} does not support seeking." - }, "play_media_error": { "message": "An error occurred while attempting to play {media_type}: {error_message}." } diff --git a/custom_components/bang_olufsen/util.py b/custom_components/bang_olufsen/util.py index c99a09e..68584fa 100644 --- a/custom_components/bang_olufsen/util.py +++ b/custom_components/bang_olufsen/util.py @@ -8,6 +8,7 @@ from mozart_api.models import PairedRemote from mozart_api.mozart_client import MozartClient +from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -20,6 +21,9 @@ class BangOlufsenData: platforms_initialized: int = 0 +type BangOlufsenConfigEntry = ConfigEntry[BangOlufsenData] + + def set_platform_initialized(data: BangOlufsenData) -> None: """Increment platforms_initialized to indicate that a platform has been initialized.""" data.platforms_initialized += 1