mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Yamaha Musiccast Media Browser feature (#54864)
This commit is contained in:
parent
4ae2a26aa3
commit
6eadc0c303
@ -7,6 +7,7 @@ import logging
|
|||||||
from aiomusiccast import MusicCastConnectionException
|
from aiomusiccast import MusicCastConnectionException
|
||||||
from aiomusiccast.musiccast_device import MusicCastData, MusicCastDevice
|
from aiomusiccast.musiccast_device import MusicCastData, MusicCastDevice
|
||||||
|
|
||||||
|
from homeassistant.components import ssdp
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST
|
from homeassistant.const import CONF_HOST
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -19,7 +20,7 @@ from homeassistant.helpers.update_coordinator import (
|
|||||||
UpdateFailed,
|
UpdateFailed,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .const import BRAND, DOMAIN
|
from .const import BRAND, CONF_SERIAL, CONF_UPNP_DESC, DOMAIN
|
||||||
|
|
||||||
PLATFORMS = ["media_player"]
|
PLATFORMS = ["media_player"]
|
||||||
|
|
||||||
@ -27,10 +28,42 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
SCAN_INTERVAL = timedelta(seconds=60)
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_upnp_desc(hass: HomeAssistant, host: str):
|
||||||
|
"""Get the upnp description URL for a given host, using the SSPD scanner."""
|
||||||
|
ssdp_entries = ssdp.async_get_discovery_info_by_st(hass, "upnp:rootdevice")
|
||||||
|
matches = [w for w in ssdp_entries if w.get("_host", "") == host]
|
||||||
|
upnp_desc = None
|
||||||
|
for match in matches:
|
||||||
|
if match.get(ssdp.ATTR_SSDP_LOCATION):
|
||||||
|
upnp_desc = match[ssdp.ATTR_SSDP_LOCATION]
|
||||||
|
break
|
||||||
|
|
||||||
|
if not upnp_desc:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"The upnp_description was not found automatically, setting a default one"
|
||||||
|
)
|
||||||
|
upnp_desc = f"http://{host}:49154/MediaRenderer/desc.xml"
|
||||||
|
return upnp_desc
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up MusicCast from a config entry."""
|
"""Set up MusicCast from a config entry."""
|
||||||
|
|
||||||
client = MusicCastDevice(entry.data[CONF_HOST], async_get_clientsession(hass))
|
if entry.data.get(CONF_UPNP_DESC) is None:
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry,
|
||||||
|
data={
|
||||||
|
CONF_HOST: entry.data[CONF_HOST],
|
||||||
|
CONF_SERIAL: entry.data["serial"],
|
||||||
|
CONF_UPNP_DESC: await get_upnp_desc(hass, entry.data[CONF_HOST]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
client = MusicCastDevice(
|
||||||
|
entry.data[CONF_HOST],
|
||||||
|
async_get_clientsession(hass),
|
||||||
|
entry.data[CONF_UPNP_DESC],
|
||||||
|
)
|
||||||
coordinator = MusicCastDataUpdateCoordinator(hass, client=client)
|
coordinator = MusicCastDataUpdateCoordinator(hass, client=client)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@ from homeassistant.config_entries import ConfigFlow
|
|||||||
from homeassistant.const import CONF_HOST
|
from homeassistant.const import CONF_HOST
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DOMAIN
|
from . import get_upnp_desc
|
||||||
|
from .const import CONF_SERIAL, CONF_UPNP_DESC, DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
serial_number: str | None = None
|
serial_number: str | None = None
|
||||||
host: str
|
host: str
|
||||||
|
upnp_description: str | None = None
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
@ -64,7 +66,8 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
title=host,
|
title=host,
|
||||||
data={
|
data={
|
||||||
CONF_HOST: host,
|
CONF_HOST: host,
|
||||||
"serial": serial_number,
|
CONF_SERIAL: serial_number,
|
||||||
|
CONF_UPNP_DESC: await get_upnp_desc(self.hass, host),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,8 +92,14 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
self.serial_number = discovery_info[ssdp.ATTR_UPNP_SERIAL]
|
self.serial_number = discovery_info[ssdp.ATTR_UPNP_SERIAL]
|
||||||
self.host = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname
|
self.host = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname
|
||||||
|
self.upnp_description = discovery_info[ssdp.ATTR_SSDP_LOCATION]
|
||||||
await self.async_set_unique_id(self.serial_number)
|
await self.async_set_unique_id(self.serial_number)
|
||||||
self._abort_if_unique_id_configured({CONF_HOST: self.host})
|
self._abort_if_unique_id_configured(
|
||||||
|
{
|
||||||
|
CONF_HOST: self.host,
|
||||||
|
CONF_UPNP_DESC: self.upnp_description,
|
||||||
|
}
|
||||||
|
)
|
||||||
self.context.update(
|
self.context.update(
|
||||||
{
|
{
|
||||||
"title_placeholders": {
|
"title_placeholders": {
|
||||||
@ -108,7 +117,8 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
title=self.host,
|
title=self.host,
|
||||||
data={
|
data={
|
||||||
CONF_HOST: self.host,
|
CONF_HOST: self.host,
|
||||||
"serial": self.serial_number,
|
CONF_SERIAL: self.serial_number,
|
||||||
|
CONF_UPNP_DESC: self.upnp_description,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""Constants for the MusicCast integration."""
|
"""Constants for the MusicCast integration."""
|
||||||
|
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
|
MEDIA_CLASS_DIRECTORY,
|
||||||
|
MEDIA_CLASS_TRACK,
|
||||||
REPEAT_MODE_ALL,
|
REPEAT_MODE_ALL,
|
||||||
REPEAT_MODE_OFF,
|
REPEAT_MODE_OFF,
|
||||||
REPEAT_MODE_ONE,
|
REPEAT_MODE_ONE,
|
||||||
@ -17,6 +19,9 @@ ATTR_MC_LINK = "mc_link"
|
|||||||
ATTR_MAIN_SYNC = "main_sync"
|
ATTR_MAIN_SYNC = "main_sync"
|
||||||
ATTR_MC_LINK_SOURCES = [ATTR_MC_LINK, ATTR_MAIN_SYNC]
|
ATTR_MC_LINK_SOURCES = [ATTR_MC_LINK, ATTR_MAIN_SYNC]
|
||||||
|
|
||||||
|
CONF_UPNP_DESC = "upnp_description"
|
||||||
|
CONF_SERIAL = "serial"
|
||||||
|
|
||||||
DEFAULT_ZONE = "main"
|
DEFAULT_ZONE = "main"
|
||||||
HA_REPEAT_MODE_TO_MC_MAPPING = {
|
HA_REPEAT_MODE_TO_MC_MAPPING = {
|
||||||
REPEAT_MODE_OFF: "off",
|
REPEAT_MODE_OFF: "off",
|
||||||
@ -31,3 +36,9 @@ INTERVAL_SECONDS = "interval_seconds"
|
|||||||
MC_REPEAT_MODE_TO_HA_MAPPING = {
|
MC_REPEAT_MODE_TO_HA_MAPPING = {
|
||||||
val: key for key, val in HA_REPEAT_MODE_TO_MC_MAPPING.items()
|
val: key for key, val in HA_REPEAT_MODE_TO_MC_MAPPING.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MEDIA_CLASS_MAPPING = {
|
||||||
|
"track": MEDIA_CLASS_TRACK,
|
||||||
|
"directory": MEDIA_CLASS_DIRECTORY,
|
||||||
|
"categories": MEDIA_CLASS_DIRECTORY,
|
||||||
|
}
|
||||||
|
@ -4,13 +4,16 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/yamaha_musiccast",
|
"documentation": "https://www.home-assistant.io/integrations/yamaha_musiccast",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aiomusiccast==0.8.2"
|
"aiomusiccast==0.9.1"
|
||||||
],
|
],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Yamaha Corporation"
|
"manufacturer": "Yamaha Corporation"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"dependencies": [
|
||||||
|
"ssdp"
|
||||||
|
],
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@vigonotion",
|
"@vigonotion",
|
||||||
|
@ -3,17 +3,26 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiomusiccast import MusicCastGroupException
|
from aiomusiccast import MusicCastGroupException, MusicCastMediaContent
|
||||||
from aiomusiccast.features import ZoneFeature
|
from aiomusiccast.features import ZoneFeature
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity
|
from homeassistant.components.media_player import (
|
||||||
|
PLATFORM_SCHEMA,
|
||||||
|
BrowseMedia,
|
||||||
|
MediaPlayerEntity,
|
||||||
|
)
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
|
MEDIA_CLASS_DIRECTORY,
|
||||||
|
MEDIA_CLASS_TRACK,
|
||||||
|
MEDIA_TYPE_MUSIC,
|
||||||
REPEAT_MODE_OFF,
|
REPEAT_MODE_OFF,
|
||||||
|
SUPPORT_BROWSE_MEDIA,
|
||||||
SUPPORT_GROUPING,
|
SUPPORT_GROUPING,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
SUPPORT_PAUSE,
|
SUPPORT_PAUSE,
|
||||||
SUPPORT_PLAY,
|
SUPPORT_PLAY,
|
||||||
|
SUPPORT_PLAY_MEDIA,
|
||||||
SUPPORT_PREVIOUS_TRACK,
|
SUPPORT_PREVIOUS_TRACK,
|
||||||
SUPPORT_REPEAT_SET,
|
SUPPORT_REPEAT_SET,
|
||||||
SUPPORT_SELECT_SOUND_MODE,
|
SUPPORT_SELECT_SOUND_MODE,
|
||||||
@ -51,22 +60,19 @@ from .const import (
|
|||||||
HA_REPEAT_MODE_TO_MC_MAPPING,
|
HA_REPEAT_MODE_TO_MC_MAPPING,
|
||||||
INTERVAL_SECONDS,
|
INTERVAL_SECONDS,
|
||||||
MC_REPEAT_MODE_TO_HA_MAPPING,
|
MC_REPEAT_MODE_TO_HA_MAPPING,
|
||||||
|
MEDIA_CLASS_MAPPING,
|
||||||
NULL_GROUP,
|
NULL_GROUP,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
MUSIC_PLAYER_BASE_SUPPORT = (
|
MUSIC_PLAYER_BASE_SUPPORT = (
|
||||||
SUPPORT_PAUSE
|
SUPPORT_SHUFFLE_SET
|
||||||
| SUPPORT_PLAY
|
|
||||||
| SUPPORT_SHUFFLE_SET
|
|
||||||
| SUPPORT_REPEAT_SET
|
| SUPPORT_REPEAT_SET
|
||||||
| SUPPORT_PREVIOUS_TRACK
|
|
||||||
| SUPPORT_NEXT_TRACK
|
|
||||||
| SUPPORT_SELECT_SOUND_MODE
|
| SUPPORT_SELECT_SOUND_MODE
|
||||||
| SUPPORT_SELECT_SOURCE
|
| SUPPORT_SELECT_SOURCE
|
||||||
| SUPPORT_STOP
|
|
||||||
| SUPPORT_GROUPING
|
| SUPPORT_GROUPING
|
||||||
|
| SUPPORT_PLAY_MEDIA
|
||||||
)
|
)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
@ -198,6 +204,16 @@ class MusicCastMediaPlayer(MusicCastDeviceEntity, MediaPlayerEntity):
|
|||||||
def _is_tuner(self):
|
def _is_tuner(self):
|
||||||
return self.coordinator.data.zones[self._zone_id].input == "tuner"
|
return self.coordinator.data.zones[self._zone_id].input == "tuner"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_content_id(self):
|
||||||
|
"""Return the content ID of current playing media."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_content_type(self):
|
||||||
|
"""Return the content type of current playing media."""
|
||||||
|
return MEDIA_TYPE_MUSIC
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the player."""
|
"""Return the state of the player."""
|
||||||
@ -308,6 +324,88 @@ class MusicCastMediaPlayer(MusicCastDeviceEntity, MediaPlayerEntity):
|
|||||||
"Service shuffle is not supported for non NetUSB sources."
|
"Service shuffle is not supported for non NetUSB sources."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_play_media(self, media_type: str, media_id: str, **kwargs) -> None:
|
||||||
|
"""Play media."""
|
||||||
|
if self.state == STATE_OFF:
|
||||||
|
await self.async_turn_on()
|
||||||
|
|
||||||
|
if media_id:
|
||||||
|
parts = media_id.split(":")
|
||||||
|
|
||||||
|
if parts[0] == "list":
|
||||||
|
index = parts[3]
|
||||||
|
|
||||||
|
if index == "-1":
|
||||||
|
index = "0"
|
||||||
|
|
||||||
|
await self.coordinator.musiccast.play_list_media(index, self._zone_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
if parts[0] == "presets":
|
||||||
|
index = parts[1]
|
||||||
|
await self.coordinator.musiccast.recall_netusb_preset(
|
||||||
|
self._zone_id, index
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if parts[0] == "http":
|
||||||
|
await self.coordinator.musiccast.play_url_media(
|
||||||
|
self._zone_id, media_id, "HomeAssistant"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise HomeAssistantError(
|
||||||
|
"Only presets, media from media browser and http URLs are supported"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_browse_media(self, media_content_type=None, media_content_id=None):
|
||||||
|
"""Implement the websocket media browsing helper."""
|
||||||
|
if self.state == STATE_OFF:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
"The device has to be turned on to be able to browse media."
|
||||||
|
)
|
||||||
|
|
||||||
|
if media_content_id:
|
||||||
|
media_content_path = media_content_id.split(":")
|
||||||
|
media_content_provider = await MusicCastMediaContent.browse_media(
|
||||||
|
self.coordinator.musiccast, self._zone_id, media_content_path, 24
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
media_content_provider = MusicCastMediaContent.categories(
|
||||||
|
self.coordinator.musiccast, self._zone_id
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_content_type(item):
|
||||||
|
if item.can_play:
|
||||||
|
return MEDIA_CLASS_TRACK
|
||||||
|
return MEDIA_CLASS_DIRECTORY
|
||||||
|
|
||||||
|
children = [
|
||||||
|
BrowseMedia(
|
||||||
|
title=child.title,
|
||||||
|
media_class=MEDIA_CLASS_MAPPING.get(child.content_type),
|
||||||
|
media_content_id=child.content_id,
|
||||||
|
media_content_type=get_content_type(child),
|
||||||
|
can_play=child.can_play,
|
||||||
|
can_expand=child.can_browse,
|
||||||
|
thumbnail=child.thumbnail,
|
||||||
|
)
|
||||||
|
for child in media_content_provider.children
|
||||||
|
]
|
||||||
|
|
||||||
|
overview = BrowseMedia(
|
||||||
|
title=media_content_provider.title,
|
||||||
|
media_class=MEDIA_CLASS_MAPPING.get(media_content_provider.content_type),
|
||||||
|
media_content_id=media_content_provider.content_id,
|
||||||
|
media_content_type=get_content_type(media_content_provider),
|
||||||
|
can_play=False,
|
||||||
|
can_expand=media_content_provider.can_browse,
|
||||||
|
children=children,
|
||||||
|
)
|
||||||
|
|
||||||
|
return overview
|
||||||
|
|
||||||
async def async_select_sound_mode(self, sound_mode):
|
async def async_select_sound_mode(self, sound_mode):
|
||||||
"""Select sound mode."""
|
"""Select sound mode."""
|
||||||
await self.coordinator.musiccast.select_sound_mode(self._zone_id, sound_mode)
|
await self.coordinator.musiccast.select_sound_mode(self._zone_id, sound_mode)
|
||||||
@ -366,6 +464,18 @@ class MusicCastMediaPlayer(MusicCastDeviceEntity, MediaPlayerEntity):
|
|||||||
if ZoneFeature.MUTE in zone.features:
|
if ZoneFeature.MUTE in zone.features:
|
||||||
supported_features |= SUPPORT_VOLUME_MUTE
|
supported_features |= SUPPORT_VOLUME_MUTE
|
||||||
|
|
||||||
|
if self._is_netusb or self._is_tuner:
|
||||||
|
supported_features |= SUPPORT_PREVIOUS_TRACK
|
||||||
|
supported_features |= SUPPORT_NEXT_TRACK
|
||||||
|
|
||||||
|
if self._is_netusb:
|
||||||
|
supported_features |= SUPPORT_PAUSE
|
||||||
|
supported_features |= SUPPORT_PLAY
|
||||||
|
supported_features |= SUPPORT_STOP
|
||||||
|
|
||||||
|
if self.state != STATE_OFF:
|
||||||
|
supported_features |= SUPPORT_BROWSE_MEDIA
|
||||||
|
|
||||||
return supported_features
|
return supported_features
|
||||||
|
|
||||||
async def async_media_previous_track(self):
|
async def async_media_previous_track(self):
|
||||||
|
@ -216,7 +216,7 @@ aiolyric==1.0.7
|
|||||||
aiomodernforms==0.1.8
|
aiomodernforms==0.1.8
|
||||||
|
|
||||||
# homeassistant.components.yamaha_musiccast
|
# homeassistant.components.yamaha_musiccast
|
||||||
aiomusiccast==0.8.2
|
aiomusiccast==0.9.1
|
||||||
|
|
||||||
# homeassistant.components.keyboard_remote
|
# homeassistant.components.keyboard_remote
|
||||||
aionotify==0.2.0
|
aionotify==0.2.0
|
||||||
|
@ -140,7 +140,7 @@ aiolyric==1.0.7
|
|||||||
aiomodernforms==0.1.8
|
aiomodernforms==0.1.8
|
||||||
|
|
||||||
# homeassistant.components.yamaha_musiccast
|
# homeassistant.components.yamaha_musiccast
|
||||||
aiomusiccast==0.8.2
|
aiomusiccast==0.9.1
|
||||||
|
|
||||||
# homeassistant.components.notion
|
# homeassistant.components.notion
|
||||||
aionotion==3.0.2
|
aionotion==3.0.2
|
||||||
|
@ -77,6 +77,30 @@ def mock_ssdp_no_yamaha():
|
|||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_valid_discovery_information():
|
||||||
|
"""Mock that the ssdp scanner returns a useful upnp description."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.ssdp.async_get_discovery_info_by_st",
|
||||||
|
return_value=[
|
||||||
|
{
|
||||||
|
"ssdp_location": "http://127.0.0.1:9000/MediaRenderer/desc.xml",
|
||||||
|
"_host": "127.0.0.1",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_empty_discovery_information():
|
||||||
|
"""Mock that the ssdp scanner returns no upnp description."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.ssdp.async_get_discovery_info_by_st", return_value=[]
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
# User Flows
|
# User Flows
|
||||||
|
|
||||||
|
|
||||||
@ -150,7 +174,9 @@ async def test_user_input_unknown_error(hass, mock_get_device_info_exception):
|
|||||||
assert result2["errors"] == {"base": "unknown"}
|
assert result2["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
|
|
||||||
async def test_user_input_device_found(hass, mock_get_device_info_valid):
|
async def test_user_input_device_found(
|
||||||
|
hass, mock_get_device_info_valid, mock_valid_discovery_information
|
||||||
|
):
|
||||||
"""Test when user specifies an existing device."""
|
"""Test when user specifies an existing device."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
@ -167,6 +193,30 @@ async def test_user_input_device_found(hass, mock_get_device_info_valid):
|
|||||||
assert result2["data"] == {
|
assert result2["data"] == {
|
||||||
"host": "127.0.0.1",
|
"host": "127.0.0.1",
|
||||||
"serial": "1234567890",
|
"serial": "1234567890",
|
||||||
|
"upnp_description": "http://127.0.0.1:9000/MediaRenderer/desc.xml",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_input_device_found_no_ssdp(
|
||||||
|
hass, mock_get_device_info_valid, mock_empty_discovery_information
|
||||||
|
):
|
||||||
|
"""Test when user specifies an existing device, which no discovery data are present for."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{"host": "127.0.0.1"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert isinstance(result2["result"], ConfigEntry)
|
||||||
|
assert result2["data"] == {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"serial": "1234567890",
|
||||||
|
"upnp_description": "http://127.0.0.1:49154/MediaRenderer/desc.xml",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -201,7 +251,9 @@ async def test_import_error(hass, mock_get_device_info_exception):
|
|||||||
assert result["errors"] == {"base": "unknown"}
|
assert result["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
|
|
||||||
async def test_import_device_successful(hass, mock_get_device_info_valid):
|
async def test_import_device_successful(
|
||||||
|
hass, mock_get_device_info_valid, mock_valid_discovery_information
|
||||||
|
):
|
||||||
"""Test when the device was imported successfully."""
|
"""Test when the device was imported successfully."""
|
||||||
config = {"platform": "yamaha_musiccast", "host": "127.0.0.1", "port": 5006}
|
config = {"platform": "yamaha_musiccast", "host": "127.0.0.1", "port": 5006}
|
||||||
|
|
||||||
@ -214,6 +266,7 @@ async def test_import_device_successful(hass, mock_get_device_info_valid):
|
|||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
"host": "127.0.0.1",
|
"host": "127.0.0.1",
|
||||||
"serial": "1234567890",
|
"serial": "1234567890",
|
||||||
|
"upnp_description": "http://127.0.0.1:9000/MediaRenderer/desc.xml",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -262,6 +315,7 @@ async def test_ssdp_discovery_successful_add_device(hass, mock_ssdp_yamaha):
|
|||||||
assert result2["data"] == {
|
assert result2["data"] == {
|
||||||
"host": "127.0.0.1",
|
"host": "127.0.0.1",
|
||||||
"serial": "1234567890",
|
"serial": "1234567890",
|
||||||
|
"upnp_description": "http://127.0.0.1/desc.xml",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -285,3 +339,4 @@ async def test_ssdp_discovery_existing_device_update(hass, mock_ssdp_yamaha):
|
|||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
assert result["reason"] == "already_configured"
|
assert result["reason"] == "already_configured"
|
||||||
assert mock_entry.data[CONF_HOST] == "127.0.0.1"
|
assert mock_entry.data[CONF_HOST] == "127.0.0.1"
|
||||||
|
assert mock_entry.data["upnp_description"] == "http://127.0.0.1/desc.xml"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user