mirror of
https://github.com/home-assistant/core.git
synced 2025-08-05 13:45:12 +02:00
Update integration with feedback from emontnemery
This commit is contained in:
@@ -4,17 +4,15 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from mozart_api.exceptions import ServiceException
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from mozart_api.mozart_client import MozartClient
|
||||
from urllib3.exceptions import MaxRetryError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from .const import DOMAIN, WEBSOCKET_CONNECTION_DELAY
|
||||
from .const import DOMAIN
|
||||
from .websocket import BangOlufsenWebsocket
|
||||
|
||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||
@@ -36,9 +34,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
)
|
||||
|
||||
# Check connection and try to initialize it.
|
||||
async with client:
|
||||
try:
|
||||
await client.get_battery_state(_request_timeout=3)
|
||||
except (MaxRetryError, ServiceException, Exception) as error:
|
||||
except (TimeoutError, ClientConnectorError, Exception) as error:
|
||||
raise ConfigEntryNotReady(f"Unable to connect to {entry.title}") from error
|
||||
|
||||
websocket = BangOlufsenWebsocket(hass, entry)
|
||||
@@ -47,7 +46,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = BangOlufsenData(websocket)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
async_call_later(hass, WEBSOCKET_CONNECTION_DELAY, websocket.connect_websocket)
|
||||
websocket.connect_websocket()
|
||||
# async_call_later(hass, WEBSOCKET_CONNECTION_DELAY, websocket.connect_websocket)
|
||||
|
||||
return True
|
||||
|
||||
|
@@ -5,9 +5,8 @@ import ipaddress
|
||||
import logging
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from mozart_api.exceptions import ApiException, NotFoundException
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from mozart_api.mozart_client import MozartClient
|
||||
from urllib3.exceptions import MaxRetryError, NewConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.zeroconf import ZeroconfServiceInfo
|
||||
@@ -62,6 +61,7 @@ class BangOlufsenConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
async def _compile_data(self) -> UserInput:
|
||||
"""Compile data for entry creation."""
|
||||
# Get current volume settings
|
||||
async with self._client:
|
||||
volume_settings = await self._client.get_volume_settings()
|
||||
|
||||
# Create a dict containing all necessary information for setup
|
||||
@@ -94,7 +94,6 @@ class BangOlufsenConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
# Check if the IP address is a valid address.
|
||||
try:
|
||||
ipaddress.ip_address(self._host)
|
||||
|
||||
except ValueError:
|
||||
return self.async_abort(reason="value_error")
|
||||
|
||||
@@ -102,20 +101,16 @@ class BangOlufsenConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
# Try to get information from Beolink self method.
|
||||
try:
|
||||
beolink_self = await self._client.get_beolink_self(_request_timeout=3)
|
||||
async with self._client:
|
||||
beolink_self = await self._client.get_beolink_self(
|
||||
_request_timeout=3
|
||||
)
|
||||
|
||||
except (
|
||||
ApiException,
|
||||
NewConnectionError,
|
||||
MaxRetryError,
|
||||
NotFoundException,
|
||||
) as error:
|
||||
except (TimeoutError, ClientConnectorError) as error:
|
||||
return self.async_abort(
|
||||
reason={
|
||||
ApiException: "api_exception",
|
||||
NewConnectionError: "new_connection_error",
|
||||
MaxRetryError: "max_retry_error",
|
||||
NotFoundException: "not_found_exception",
|
||||
TimeoutError: "timeout_error",
|
||||
ClientConnectorError: "client_connector_error",
|
||||
}[type(error)]
|
||||
)
|
||||
|
||||
|
@@ -148,7 +148,6 @@ HIDDEN_SOURCE_IDS: Final[tuple] = (
|
||||
"wpl",
|
||||
"pl",
|
||||
"beolink",
|
||||
"classicsAdapter",
|
||||
"usbIn",
|
||||
)
|
||||
|
||||
@@ -157,57 +156,57 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||
items=[
|
||||
Source(
|
||||
id="uriStreamer",
|
||||
isEnabled=True,
|
||||
isPlayable=False,
|
||||
is_enabled=True,
|
||||
is_playable=False,
|
||||
name="Audio Streamer",
|
||||
type=SourceTypeEnum(value="uriStreamer"),
|
||||
),
|
||||
Source(
|
||||
id="bluetooth",
|
||||
isEnabled=True,
|
||||
isPlayable=False,
|
||||
is_enabled=True,
|
||||
is_playable=False,
|
||||
name="Bluetooth",
|
||||
type=SourceTypeEnum(value="bluetooth"),
|
||||
),
|
||||
Source(
|
||||
id="spotify",
|
||||
isEnabled=True,
|
||||
isPlayable=False,
|
||||
is_enabled=True,
|
||||
is_playable=False,
|
||||
name="Spotify Connect",
|
||||
type=SourceTypeEnum(value="spotify"),
|
||||
),
|
||||
Source(
|
||||
id="lineIn",
|
||||
isEnabled=True,
|
||||
isPlayable=True,
|
||||
is_enabled=True,
|
||||
is_playable=True,
|
||||
name="Line-In",
|
||||
type=SourceTypeEnum(value="lineIn"),
|
||||
),
|
||||
Source(
|
||||
id="spdif",
|
||||
isEnabled=True,
|
||||
isPlayable=True,
|
||||
is_enabled=True,
|
||||
is_playable=True,
|
||||
name="Optical",
|
||||
type=SourceTypeEnum(value="spdif"),
|
||||
),
|
||||
Source(
|
||||
id="netRadio",
|
||||
isEnabled=True,
|
||||
isPlayable=True,
|
||||
is_enabled=True,
|
||||
is_playable=True,
|
||||
name="B&O Radio",
|
||||
type=SourceTypeEnum(value="netRadio"),
|
||||
),
|
||||
Source(
|
||||
id="deezer",
|
||||
isEnabled=True,
|
||||
isPlayable=True,
|
||||
is_enabled=True,
|
||||
is_playable=True,
|
||||
name="Deezer",
|
||||
type=SourceTypeEnum(value="deezer"),
|
||||
),
|
||||
Source(
|
||||
id="tidalConnect",
|
||||
isEnabled=True,
|
||||
isPlayable=True,
|
||||
is_enabled=True,
|
||||
is_playable=True,
|
||||
name="Tidal Connect",
|
||||
type=SourceTypeEnum(value="tidalConnect"),
|
||||
),
|
||||
|
@@ -48,7 +48,7 @@ class BangOlufsenVariables:
|
||||
|
||||
# Objects that get directly updated by notifications.
|
||||
self._playback_metadata: PlaybackContentMetadata = PlaybackContentMetadata()
|
||||
self._playback_progress: PlaybackProgress = PlaybackProgress(totalDuration=0)
|
||||
self._playback_progress: PlaybackProgress = PlaybackProgress(total_duration=0)
|
||||
self._playback_source: Source = Source()
|
||||
self._playback_state: RenderingState = RenderingState()
|
||||
self._source_change: Source = Source()
|
||||
@@ -77,3 +77,7 @@ class BangOlufsenEntity(Entity, BangOlufsenVariables):
|
||||
self._attr_available = connection_state
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Close API client."""
|
||||
await self._client.close()
|
||||
|
@@ -6,6 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/bangolufsen",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["mozart-api==3.2.1.150.4"],
|
||||
"requirements": ["mozart-api==3.2.1.150.5"],
|
||||
"zeroconf": ["_bangolufsen._tcp.local."]
|
||||
}
|
||||
|
@@ -57,6 +57,7 @@ from .const import (
|
||||
CONF_BEOLINK_JID,
|
||||
CONF_DEFAULT_VOLUME,
|
||||
CONF_MAX_VOLUME,
|
||||
CONNECTION_STATUS,
|
||||
DOMAIN,
|
||||
FALLBACK_SOURCES,
|
||||
HIDDEN_SOURCE_IDS,
|
||||
@@ -131,10 +132,9 @@ class BangOlufsenMediaPlayer(MediaPlayerEntity, BangOlufsenEntity):
|
||||
# Misc. variables.
|
||||
self._audio_sources: dict[str, str] = {}
|
||||
self._media_image: Art = Art()
|
||||
# self._queue_settings: PlayQueueSettings = PlayQueueSettings()
|
||||
self._software_status: SoftwareUpdateStatus = SoftwareUpdateStatus(
|
||||
softwareVersion="",
|
||||
state=SoftwareUpdateState(secondsRemaining=0, value="idle"),
|
||||
software_version="",
|
||||
state=SoftwareUpdateState(seconds_remaining=0, value="idle"),
|
||||
)
|
||||
self._sources: dict[str, str] = {}
|
||||
self._state: str = MediaPlayerState.IDLE
|
||||
@@ -144,6 +144,14 @@ class BangOlufsenMediaPlayer(MediaPlayerEntity, BangOlufsenEntity):
|
||||
"""Turn on the dispatchers."""
|
||||
await self._initialize()
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{self._unique_id}_{CONNECTION_STATUS}",
|
||||
self._update_connection_state,
|
||||
)
|
||||
)
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
@@ -615,10 +623,10 @@ class BangOlufsenMediaPlayer(MediaPlayerEntity, BangOlufsenEntity):
|
||||
elif media_type == BANGOLUFSEN_MEDIA_TYPE.RADIO:
|
||||
await self._client.run_provided_scene(
|
||||
scene_properties=SceneProperties(
|
||||
actionList=[
|
||||
action_list=[
|
||||
Action(
|
||||
type="radio",
|
||||
radioStationId=media_id,
|
||||
radio_station_id=media_id,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -637,7 +645,7 @@ class BangOlufsenMediaPlayer(MediaPlayerEntity, BangOlufsenEntity):
|
||||
|
||||
# Play Deezer flow.
|
||||
await self._client.start_deezer_flow(
|
||||
user_flow=UserFlow(userId=deezer_id)
|
||||
user_flow=UserFlow(user_id=deezer_id)
|
||||
)
|
||||
|
||||
# Play a Deezer playlist or album.
|
||||
@@ -649,7 +657,7 @@ class BangOlufsenMediaPlayer(MediaPlayerEntity, BangOlufsenEntity):
|
||||
await self._client.add_to_queue(
|
||||
play_queue_item=PlayQueueItem(
|
||||
provider=PlayQueueItemType(value="deezer"),
|
||||
startNowFromPosition=start_from,
|
||||
start_now_from_position=start_from,
|
||||
type="playlist",
|
||||
uri=media_id,
|
||||
)
|
||||
@@ -660,7 +668,7 @@ class BangOlufsenMediaPlayer(MediaPlayerEntity, BangOlufsenEntity):
|
||||
await self._client.add_to_queue(
|
||||
play_queue_item=PlayQueueItem(
|
||||
provider=PlayQueueItemType(value="deezer"),
|
||||
startNowFromPosition=0,
|
||||
start_now_from_position=0,
|
||||
type="track",
|
||||
uri=media_id,
|
||||
)
|
||||
|
@@ -3,11 +3,8 @@
|
||||
"abort": {
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"already_configured": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||
"api_exception": "An error occurred while initializing the device. Try waiting or restarting the device.",
|
||||
"max_retry_error": "[%key:common::config_flow::error::timeout_connect%]",
|
||||
"new_connection_error": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"value_error": "[%key:common::config_flow::error::invalid_host%]",
|
||||
"not_found_exception": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
"timeout_error": "[%key:common::config_flow::error::timeout_connect%]",
|
||||
"client_connector_error": "[%key:common::config_flow::error::invalid_host%]"
|
||||
},
|
||||
"flow_title": "{name}",
|
||||
"step": {
|
||||
@@ -18,7 +15,7 @@
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::ip%]",
|
||||
"model": "Device model"
|
||||
"model": "[%key:common::generic::model%]"
|
||||
},
|
||||
"description": "Manually configure your Bang & Olufsen device."
|
||||
}
|
||||
|
@@ -1270,7 +1270,7 @@ motionblinds==0.6.18
|
||||
motioneye-client==0.3.14
|
||||
|
||||
# homeassistant.components.bangolufsen
|
||||
mozart-api==3.2.1.150.4
|
||||
mozart-api==3.2.1.150.5
|
||||
|
||||
# homeassistant.components.mullvad
|
||||
mullvad-api==1.0.0
|
||||
|
@@ -994,7 +994,7 @@ motionblinds==0.6.18
|
||||
motioneye-client==0.3.14
|
||||
|
||||
# homeassistant.components.bangolufsen
|
||||
mozart-api==3.2.1.150.4
|
||||
mozart-api==3.2.1.150.5
|
||||
|
||||
# homeassistant.components.mullvad
|
||||
mullvad-api==1.0.0
|
||||
|
@@ -23,6 +23,12 @@ from tests.common import MockConfigEntry
|
||||
class MockMozartClient:
|
||||
"""Class for mocking MozartClient objects and methods."""
|
||||
|
||||
async def __aenter__(self):
|
||||
"""Mock async context entry."""
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
"""Mock async context exit."""
|
||||
|
||||
# API call results
|
||||
get_beolink_self_result = BeolinkPeer(
|
||||
friendly_name=TEST_FRIENDLY_NAME, jid=TEST_JID_1
|
||||
|
@@ -3,9 +3,8 @@
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
from mozart_api.exceptions import ApiException, NotFoundException
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
import pytest
|
||||
from urllib3.exceptions import MaxRetryError, NewConnectionError
|
||||
|
||||
from homeassistant.components.bangolufsen.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
|
||||
@@ -25,11 +24,11 @@ from .const import (
|
||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||
|
||||
|
||||
async def test_config_flow_max_retry_error(
|
||||
async def test_config_flow_timeout_error(
|
||||
hass: HomeAssistant, mock_client: MockMozartClient
|
||||
) -> None:
|
||||
"""Test we handle not_mozart_device."""
|
||||
mock_client.get_beolink_self.side_effect = MaxRetryError(pool=Mock(), url="")
|
||||
"""Test we handle timeout_error."""
|
||||
mock_client.get_beolink_self.side_effect = TimeoutError()
|
||||
|
||||
result_user = await hass.config_entries.flow.async_init(
|
||||
handler=DOMAIN,
|
||||
@@ -37,17 +36,17 @@ async def test_config_flow_max_retry_error(
|
||||
data=TEST_DATA_USER,
|
||||
)
|
||||
assert result_user["type"] == FlowResultType.ABORT
|
||||
assert result_user["reason"] == "max_retry_error"
|
||||
assert result_user["reason"] == "timeout_error"
|
||||
|
||||
assert mock_client.get_beolink_self.call_count == 1
|
||||
assert mock_client.get_volume_settings.call_count == 0
|
||||
|
||||
|
||||
async def test_config_flow_api_exception(
|
||||
async def test_config_flow_client_connector_error(
|
||||
hass: HomeAssistant, mock_client: MockMozartClient
|
||||
) -> None:
|
||||
"""Test we handle api_exception."""
|
||||
mock_client.get_beolink_self.side_effect = ApiException()
|
||||
"""Test we handle client_connector_error."""
|
||||
mock_client.get_beolink_self.side_effect = ClientConnectorError(Mock(), Mock())
|
||||
|
||||
result_user = await hass.config_entries.flow.async_init(
|
||||
handler=DOMAIN,
|
||||
@@ -55,44 +54,7 @@ async def test_config_flow_api_exception(
|
||||
data=TEST_DATA_USER,
|
||||
)
|
||||
assert result_user["type"] == FlowResultType.ABORT
|
||||
assert result_user["reason"] == "api_exception"
|
||||
|
||||
assert mock_client.get_beolink_self.call_count == 1
|
||||
assert mock_client.get_volume_settings.call_count == 0
|
||||
|
||||
|
||||
async def test_config_flow_new_connection_error(
|
||||
hass: HomeAssistant, mock_client: MockMozartClient
|
||||
) -> None:
|
||||
"""Test we handle new_connection_error."""
|
||||
mock_client.get_beolink_self.side_effect = NewConnectionError(Mock(), "")
|
||||
|
||||
result_user = await hass.config_entries.flow.async_init(
|
||||
handler=DOMAIN,
|
||||
context={CONF_SOURCE: SOURCE_USER},
|
||||
data=TEST_DATA_USER,
|
||||
)
|
||||
assert result_user["type"] == FlowResultType.ABORT
|
||||
assert result_user["reason"] == "new_connection_error"
|
||||
|
||||
assert mock_client.get_beolink_self.call_count == 1
|
||||
assert mock_client.get_volume_settings.call_count == 0
|
||||
|
||||
|
||||
async def test_config_flow_not_found_exception(
|
||||
hass: HomeAssistant,
|
||||
mock_client: MockMozartClient,
|
||||
) -> None:
|
||||
"""Test we handle not_found_exception."""
|
||||
mock_client.get_beolink_self.side_effect = NotFoundException()
|
||||
|
||||
result_user = await hass.config_entries.flow.async_init(
|
||||
handler=DOMAIN,
|
||||
context={CONF_SOURCE: SOURCE_USER},
|
||||
data=TEST_DATA_USER,
|
||||
)
|
||||
assert result_user["type"] == FlowResultType.ABORT
|
||||
assert result_user["reason"] == "not_found_exception"
|
||||
assert result_user["reason"] == "client_connector_error"
|
||||
|
||||
assert mock_client.get_beolink_self.call_count == 1
|
||||
assert mock_client.get_volume_settings.call_count == 0
|
||||
@@ -181,28 +143,3 @@ async def test_config_flow_zeroconf_not_mozart_device(hass: HomeAssistant) -> No
|
||||
|
||||
assert result_user["type"] == FlowResultType.ABORT
|
||||
assert result_user["reason"] == "not_mozart_device"
|
||||
|
||||
|
||||
# async def test_config_flow_options(hass: HomeAssistant, mock_config_entry) -> None:
|
||||
# """Test config flow options."""
|
||||
|
||||
# mock_config_entry.add_to_hass(hass)
|
||||
|
||||
# assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
# result_user = await hass.config_entries.options.async_init(
|
||||
# mock_config_entry.entry_id
|
||||
# )
|
||||
|
||||
# assert result_user["type"] == FlowResultType.FORM
|
||||
# assert result_user["step_id"] == "init"
|
||||
|
||||
# result_confirm = await hass.config_entries.options.async_configure(
|
||||
# flow_id=result_user["flow_id"],
|
||||
# user_input=TEST_DATA_OPTIONS,
|
||||
# )
|
||||
|
||||
# assert result_confirm["type"] == FlowResultType.CREATE_ENTRY
|
||||
# new_data = TEST_DATA_CONFIRM
|
||||
# new_data.update(TEST_DATA_OPTIONS)
|
||||
# assert result_confirm["data"] == new_data
|
||||
|
Reference in New Issue
Block a user