Add support for when device is not logged in to HEOS (#22913)
This commit is contained in:
parent
0d2646ba25
commit
4110bd0acf
5 changed files with 82 additions and 17 deletions
|
@ -75,12 +75,16 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||||
await controller.disconnect()
|
await controller.disconnect()
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect_controller)
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect_controller)
|
||||||
|
|
||||||
|
# Get players and sources
|
||||||
try:
|
try:
|
||||||
players, favorites, inputs = await asyncio.gather(
|
players = await controller.get_players()
|
||||||
controller.get_players(),
|
favorites = {}
|
||||||
controller.get_favorites(),
|
if controller.is_signed_in:
|
||||||
controller.get_input_sources()
|
favorites = await controller.get_favorites()
|
||||||
)
|
else:
|
||||||
|
_LOGGER.warning("%s is not logged in to your HEOS account and will"
|
||||||
|
" be unable to retrieve your favorites", host)
|
||||||
|
inputs = await controller.get_input_sources()
|
||||||
except (asyncio.TimeoutError, ConnectionError, CommandError) as error:
|
except (asyncio.TimeoutError, ConnectionError, CommandError) as error:
|
||||||
await controller.disconnect()
|
await controller.disconnect()
|
||||||
_LOGGER.debug("Unable to retrieve players and sources: %s", error,
|
_LOGGER.debug("Unable to retrieve players and sources: %s", error,
|
||||||
|
@ -175,9 +179,11 @@ class SourceManager:
|
||||||
retry_attempts = 0
|
retry_attempts = 0
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
return await asyncio.gather(
|
favorites = {}
|
||||||
controller.get_favorites(),
|
if controller.is_signed_in:
|
||||||
controller.get_input_sources())
|
favorites = await controller.get_favorites()
|
||||||
|
inputs = await controller.get_input_sources()
|
||||||
|
return favorites, inputs
|
||||||
except (asyncio.TimeoutError, ConnectionError, CommandError) \
|
except (asyncio.TimeoutError, ConnectionError, CommandError) \
|
||||||
as error:
|
as error:
|
||||||
if retry_attempts < self.max_retry_attempts:
|
if retry_attempts < self.max_retry_attempts:
|
||||||
|
@ -192,7 +198,8 @@ class SourceManager:
|
||||||
return
|
return
|
||||||
|
|
||||||
async def update_sources(event):
|
async def update_sources(event):
|
||||||
if event in const.EVENT_SOURCES_CHANGED:
|
if event in (const.EVENT_SOURCES_CHANGED,
|
||||||
|
const.EVENT_USER_CHANGED):
|
||||||
sources = await get_sources()
|
sources = await get_sources()
|
||||||
# If throttled, it will return None
|
# If throttled, it will return None
|
||||||
if sources:
|
if sources:
|
||||||
|
|
|
@ -141,7 +141,7 @@ class HeosMediaPlayer(MediaPlayerDevice):
|
||||||
|
|
||||||
async def async_set_volume_level(self, volume):
|
async def async_set_volume_level(self, volume):
|
||||||
"""Set volume level, range 0..1."""
|
"""Set volume level, range 0..1."""
|
||||||
await self._player.set_volume(volume * 100)
|
await self._player.set_volume(int(volume * 100))
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Update supported features of the player."""
|
"""Update supported features of the player."""
|
||||||
|
|
|
@ -28,6 +28,8 @@ def controller_fixture(players, favorites, input_sources, dispatcher):
|
||||||
mock_heos.players = players
|
mock_heos.players = players
|
||||||
mock_heos.get_favorites.return_value = favorites
|
mock_heos.get_favorites.return_value = favorites
|
||||||
mock_heos.get_input_sources.return_value = input_sources
|
mock_heos.get_input_sources.return_value = input_sources
|
||||||
|
mock_heos.is_signed_in = True
|
||||||
|
mock_heos.signed_in_username = "user@user.com"
|
||||||
yield mock_heos
|
yield mock_heos
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,7 @@ from asynctest import patch
|
||||||
from pyheos import CommandError, const
|
from pyheos import CommandError, const
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.heos import (
|
from homeassistant.components.heos import async_setup_entry, async_unload_entry
|
||||||
SourceManager, async_setup_entry, async_unload_entry)
|
|
||||||
from homeassistant.components.heos.const import (
|
from homeassistant.components.heos.const import (
|
||||||
DATA_CONTROLLER, DATA_SOURCE_MANAGER, DOMAIN)
|
DATA_CONTROLLER, DATA_SOURCE_MANAGER, DOMAIN)
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
|
@ -61,7 +60,7 @@ async def test_async_setup_no_config_returns_true(hass, config_entry):
|
||||||
|
|
||||||
|
|
||||||
async def test_async_setup_entry_loads_platforms(
|
async def test_async_setup_entry_loads_platforms(
|
||||||
hass, config_entry, controller):
|
hass, config_entry, controller, input_sources, favorites):
|
||||||
"""Test load connects to heos, retrieves players, and loads platforms."""
|
"""Test load connects to heos, retrieves players, and loads platforms."""
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
with patch.object(
|
with patch.object(
|
||||||
|
@ -71,10 +70,39 @@ async def test_async_setup_entry_loads_platforms(
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert forward_mock.call_count == 1
|
assert forward_mock.call_count == 1
|
||||||
assert controller.connect.call_count == 1
|
assert controller.connect.call_count == 1
|
||||||
|
assert controller.get_players.call_count == 1
|
||||||
|
assert controller.get_favorites.call_count == 1
|
||||||
|
assert controller.get_input_sources.call_count == 1
|
||||||
controller.disconnect.assert_not_called()
|
controller.disconnect.assert_not_called()
|
||||||
assert hass.data[DOMAIN][DATA_CONTROLLER] == controller
|
assert hass.data[DOMAIN][DATA_CONTROLLER] == controller
|
||||||
assert hass.data[DOMAIN][MEDIA_PLAYER_DOMAIN] == controller.players
|
assert hass.data[DOMAIN][MEDIA_PLAYER_DOMAIN] == controller.players
|
||||||
assert isinstance(hass.data[DOMAIN][DATA_SOURCE_MANAGER], SourceManager)
|
assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].favorites == favorites
|
||||||
|
assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].inputs == input_sources
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_setup_entry_not_signed_in_loads_platforms(
|
||||||
|
hass, config_entry, controller, input_sources, caplog):
|
||||||
|
"""Test setup does not retrieve favorites when not logged in."""
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
controller.is_signed_in = False
|
||||||
|
controller.signed_in_username = None
|
||||||
|
with patch.object(
|
||||||
|
hass.config_entries, 'async_forward_entry_setup') as forward_mock:
|
||||||
|
assert await async_setup_entry(hass, config_entry)
|
||||||
|
# Assert platforms loaded
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert forward_mock.call_count == 1
|
||||||
|
assert controller.connect.call_count == 1
|
||||||
|
assert controller.get_players.call_count == 1
|
||||||
|
assert controller.get_favorites.call_count == 0
|
||||||
|
assert controller.get_input_sources.call_count == 1
|
||||||
|
controller.disconnect.assert_not_called()
|
||||||
|
assert hass.data[DOMAIN][DATA_CONTROLLER] == controller
|
||||||
|
assert hass.data[DOMAIN][MEDIA_PLAYER_DOMAIN] == controller.players
|
||||||
|
assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].favorites == {}
|
||||||
|
assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].inputs == input_sources
|
||||||
|
assert "127.0.0.1 is not logged in to your HEOS account and will be " \
|
||||||
|
"unable to retrieve your favorites" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_async_setup_entry_connect_failure(
|
async def test_async_setup_entry_connect_failure(
|
||||||
|
@ -138,4 +166,3 @@ async def test_update_sources_retry(hass, config_entry, config, controller,
|
||||||
while "Unable to update sources" not in caplog.text:
|
while "Unable to update sources" not in caplog.text:
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
assert controller.get_favorites.call_count == 2
|
assert controller.get_favorites.call_count == 2
|
||||||
assert controller.get_input_sources.call_count == 2
|
|
||||||
|
|
|
@ -116,7 +116,12 @@ async def test_updates_start_from_signals(
|
||||||
state = hass.states.get('media_player.test_player')
|
state = hass.states.get('media_player.test_player')
|
||||||
assert state.state == STATE_PLAYING
|
assert state.state == STATE_PLAYING
|
||||||
|
|
||||||
# Test sources event update
|
|
||||||
|
async def test_updates_from_sources_updated(
|
||||||
|
hass, config_entry, config, controller, input_sources):
|
||||||
|
"""Tests player updates from changes in sources list."""
|
||||||
|
await setup_platform(hass, config_entry, config)
|
||||||
|
player = controller.players[1]
|
||||||
event = asyncio.Event()
|
event = asyncio.Event()
|
||||||
|
|
||||||
async def set_signal():
|
async def set_signal():
|
||||||
|
@ -124,11 +129,34 @@ async def test_updates_start_from_signals(
|
||||||
hass.helpers.dispatcher.async_dispatcher_connect(
|
hass.helpers.dispatcher.async_dispatcher_connect(
|
||||||
SIGNAL_HEOS_SOURCES_UPDATED, set_signal)
|
SIGNAL_HEOS_SOURCES_UPDATED, set_signal)
|
||||||
|
|
||||||
favorites.clear()
|
input_sources.clear()
|
||||||
player.heos.dispatcher.send(
|
player.heos.dispatcher.send(
|
||||||
const.SIGNAL_CONTROLLER_EVENT, const.EVENT_SOURCES_CHANGED)
|
const.SIGNAL_CONTROLLER_EVENT, const.EVENT_SOURCES_CHANGED)
|
||||||
await event.wait()
|
await event.wait()
|
||||||
source_list = hass.data[DOMAIN][DATA_SOURCE_MANAGER].source_list
|
source_list = hass.data[DOMAIN][DATA_SOURCE_MANAGER].source_list
|
||||||
|
assert len(source_list) == 2
|
||||||
|
state = hass.states.get('media_player.test_player')
|
||||||
|
assert state.attributes[ATTR_INPUT_SOURCE_LIST] == source_list
|
||||||
|
|
||||||
|
|
||||||
|
async def test_updates_from_user_changed(
|
||||||
|
hass, config_entry, config, controller):
|
||||||
|
"""Tests player updates from changes in user."""
|
||||||
|
await setup_platform(hass, config_entry, config)
|
||||||
|
player = controller.players[1]
|
||||||
|
event = asyncio.Event()
|
||||||
|
|
||||||
|
async def set_signal():
|
||||||
|
event.set()
|
||||||
|
hass.helpers.dispatcher.async_dispatcher_connect(
|
||||||
|
SIGNAL_HEOS_SOURCES_UPDATED, set_signal)
|
||||||
|
|
||||||
|
controller.is_signed_in = False
|
||||||
|
controller.signed_in_username = None
|
||||||
|
player.heos.dispatcher.send(
|
||||||
|
const.SIGNAL_CONTROLLER_EVENT, const.EVENT_USER_CHANGED)
|
||||||
|
await event.wait()
|
||||||
|
source_list = hass.data[DOMAIN][DATA_SOURCE_MANAGER].source_list
|
||||||
assert len(source_list) == 1
|
assert len(source_list) == 1
|
||||||
state = hass.states.get('media_player.test_player')
|
state = hass.states.get('media_player.test_player')
|
||||||
assert state.attributes[ATTR_INPUT_SOURCE_LIST] == source_list
|
assert state.attributes[ATTR_INPUT_SOURCE_LIST] == source_list
|
||||||
|
@ -194,6 +222,7 @@ async def test_services(hass, config_entry, config, controller):
|
||||||
{ATTR_ENTITY_ID: 'media_player.test_player',
|
{ATTR_ENTITY_ID: 'media_player.test_player',
|
||||||
ATTR_MEDIA_VOLUME_LEVEL: 1}, blocking=True)
|
ATTR_MEDIA_VOLUME_LEVEL: 1}, blocking=True)
|
||||||
player.set_volume.assert_called_once_with(100)
|
player.set_volume.assert_called_once_with(100)
|
||||||
|
assert isinstance(player.set_volume.call_args[0][0], int)
|
||||||
|
|
||||||
|
|
||||||
async def test_select_favorite(
|
async def test_select_favorite(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue