diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index 6585393d12e..7a6cb36ab7b 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -4,6 +4,7 @@ from datetime import timedelta import logging from typing import Dict +from pyheos import CommandError, Heos, const as heos_const import voluptuous as vol from homeassistant.components.media_player.const import ( @@ -57,7 +58,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Initialize config entry which represents the HEOS controller.""" - from pyheos import Heos, CommandError host = entry.data[CONF_HOST] # Setting all_progress_events=False ensures that we only receive a # media position update upon start of playback or when media changes @@ -137,16 +137,15 @@ class ControllerManager: async def connect_listeners(self): """Subscribe to events of interest.""" - from pyheos import const self._device_registry, self._entity_registry = await asyncio.gather( self._hass.helpers.device_registry.async_get_registry(), self._hass.helpers.entity_registry.async_get_registry()) # Handle controller events self._signals.append(self.controller.dispatcher.connect( - const.SIGNAL_CONTROLLER_EVENT, self._controller_event)) + heos_const.SIGNAL_CONTROLLER_EVENT, self._controller_event)) # Handle connection-related events self._signals.append(self.controller.dispatcher.connect( - const.SIGNAL_HEOS_EVENT, self._heos_event)) + heos_const.SIGNAL_HEOS_EVENT, self._heos_event)) async def disconnect(self): """Disconnect subscriptions.""" @@ -158,21 +157,19 @@ class ControllerManager: async def _controller_event(self, event, data): """Handle controller event.""" - from pyheos import const - if event == const.EVENT_PLAYERS_CHANGED: - self.update_ids(data[const.DATA_MAPPED_IDS]) + if event == heos_const.EVENT_PLAYERS_CHANGED: + self.update_ids(data[heos_const.DATA_MAPPED_IDS]) # Update players self._hass.helpers.dispatcher.async_dispatcher_send( SIGNAL_HEOS_UPDATED) async def _heos_event(self, event): """Handle connection event.""" - from pyheos import CommandError, const - if event == const.EVENT_CONNECTED: + if event == heos_const.EVENT_CONNECTED: try: # Retrieve latest players and refresh status data = await self.controller.load_players() - self.update_ids(data[const.DATA_MAPPED_IDS]) + self.update_ids(data[heos_const.DATA_MAPPED_IDS]) except (CommandError, asyncio.TimeoutError, ConnectionError) as ex: _LOGGER.error("Unable to refresh players: %s", ex) # Update players @@ -241,9 +238,8 @@ class SourceManager: def get_current_source(self, now_playing_media): """Determine current source from now playing media.""" - from pyheos import const # Match input by input_name:media_id - if now_playing_media.source_id == const.MUSIC_SOURCE_AUX_INPUT: + if now_playing_media.source_id == heos_const.MUSIC_SOURCE_AUX_INPUT: return next((input_source.name for input_source in self.inputs if input_source.input_name == now_playing_media.media_id), None) @@ -260,8 +256,6 @@ class SourceManager: physical event therefore throttle it. Retrieving sources immediately after the event may fail so retry. """ - from pyheos import CommandError, const - @Throttle(MIN_UPDATE_SOURCES) async def get_sources(): retry_attempts = 0 @@ -286,9 +280,9 @@ class SourceManager: return async def update_sources(event, data=None): - if event in (const.EVENT_SOURCES_CHANGED, - const.EVENT_USER_CHANGED, - const.EVENT_CONNECTED): + if event in (heos_const.EVENT_SOURCES_CHANGED, + heos_const.EVENT_USER_CHANGED, + heos_const.EVENT_CONNECTED): sources = await get_sources() # If throttled, it will return None if sources: @@ -300,6 +294,6 @@ class SourceManager: SIGNAL_HEOS_UPDATED) controller.dispatcher.connect( - const.SIGNAL_CONTROLLER_EVENT, update_sources) + heos_const.SIGNAL_CONTROLLER_EVENT, update_sources) controller.dispatcher.connect( - const.SIGNAL_HEOS_EVENT, update_sources) + heos_const.SIGNAL_HEOS_EVENT, update_sources) diff --git a/homeassistant/components/heos/config_flow.py b/homeassistant/components/heos/config_flow.py index 656058877db..064813a86a7 100644 --- a/homeassistant/components/heos/config_flow.py +++ b/homeassistant/components/heos/config_flow.py @@ -1,6 +1,7 @@ """Config flow to configure Heos.""" import asyncio +from pyheos import Heos import voluptuous as vol from homeassistant import config_entries @@ -44,7 +45,6 @@ class HeosFlowHandler(config_entries.ConfigFlow): async def async_step_user(self, user_input=None): """Obtain host and validate connection.""" - from pyheos import Heos self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {}) # Only a single entry is needed for all devices if self._async_current_entries(): diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 00a3b721efb..ff5c2d707f2 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -5,6 +5,8 @@ import logging from operator import ior from typing import Sequence +from pyheos import CommandError, const as heos_const + from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, @@ -25,6 +27,20 @@ BASE_SUPPORTED_FEATURES = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \ SUPPORT_SHUFFLE_SET | SUPPORT_SELECT_SOURCE | \ SUPPORT_PLAY_MEDIA +PLAY_STATE_TO_STATE = { + heos_const.PLAY_STATE_PLAY: STATE_PLAYING, + heos_const.PLAY_STATE_STOP: STATE_IDLE, + heos_const.PLAY_STATE_PAUSE: STATE_PAUSED +} + +CONTROL_TO_SUPPORT = { + heos_const.CONTROL_PLAY: SUPPORT_PLAY, + heos_const.CONTROL_PAUSE: SUPPORT_PAUSE, + heos_const.CONTROL_STOP: SUPPORT_STOP, + heos_const.CONTROL_PLAY_PREVIOUS: SUPPORT_PREVIOUS_TRACK, + heos_const.CONTROL_PLAY_NEXT: SUPPORT_NEXT_TRACK +} + _LOGGER = logging.getLogger(__name__) @@ -47,7 +63,6 @@ def log_command_error(command: str): def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): - from pyheos import CommandError try: await func(*args, **kwargs) except (CommandError, asyncio.TimeoutError, ConnectionError, @@ -62,31 +77,17 @@ class HeosMediaPlayer(MediaPlayerDevice): def __init__(self, player): """Initialize.""" - from pyheos import const self._media_position_updated_at = None self._player = player self._signals = [] self._supported_features = BASE_SUPPORTED_FEATURES self._source_manager = None - self._play_state_to_state = { - const.PLAY_STATE_PLAY: STATE_PLAYING, - const.PLAY_STATE_STOP: STATE_IDLE, - const.PLAY_STATE_PAUSE: STATE_PAUSED - } - self._control_to_support = { - const.CONTROL_PLAY: SUPPORT_PLAY, - const.CONTROL_PAUSE: SUPPORT_PAUSE, - const.CONTROL_STOP: SUPPORT_STOP, - const.CONTROL_PLAY_PREVIOUS: SUPPORT_PREVIOUS_TRACK, - const.CONTROL_PLAY_NEXT: SUPPORT_NEXT_TRACK - } async def _player_update(self, player_id, event): """Handle player attribute updated.""" - from pyheos import const if self._player.player_id != player_id: return - if event == const.EVENT_PLAYER_NOW_PLAYING_PROGRESS: + if event == heos_const.EVENT_PLAYER_NOW_PLAYING_PROGRESS: self._media_position_updated_at = utcnow() await self.async_update_ha_state(True) @@ -96,11 +97,10 @@ class HeosMediaPlayer(MediaPlayerDevice): async def async_added_to_hass(self): """Device added to hass.""" - from pyheos import const self._source_manager = self.hass.data[HEOS_DOMAIN][DATA_SOURCE_MANAGER] # Update state when attributes of the player change self._signals.append(self._player.heos.dispatcher.connect( - const.SIGNAL_PLAYER_EVENT, self._player_update)) + heos_const.SIGNAL_PLAYER_EVENT, self._player_update)) # Update state when heos changes self._signals.append( self.hass.helpers.dispatcher.async_dispatcher_connect( @@ -163,14 +163,13 @@ class HeosMediaPlayer(MediaPlayerDevice): return if media_type == MEDIA_TYPE_PLAYLIST: - from pyheos import const playlists = await self._player.heos.get_playlists() playlist = next((p for p in playlists if p.name == media_id), None) if not playlist: raise ValueError("Invalid playlist '{}'".format(media_id)) - add_queue_option = const.ADD_QUEUE_ADD_TO_END \ + add_queue_option = heos_const.ADD_QUEUE_ADD_TO_END \ if kwargs.get(ATTR_MEDIA_ENQUEUE) \ - else const.ADD_QUEUE_REPLACE_AND_PLAY + else heos_const.ADD_QUEUE_REPLACE_AND_PLAY await self._player.add_to_queue(playlist, add_queue_option) return @@ -208,7 +207,7 @@ class HeosMediaPlayer(MediaPlayerDevice): async def async_update(self): """Update supported features of the player.""" controls = self._player.now_playing_media.supported_controls - current_support = [self._control_to_support[control] + current_support = [CONTROL_TO_SUPPORT[control] for control in controls] self._supported_features = reduce(ior, current_support, BASE_SUPPORTED_FEATURES) @@ -343,7 +342,7 @@ class HeosMediaPlayer(MediaPlayerDevice): @property def state(self) -> str: """State of the player.""" - return self._play_state_to_state[self._player.state] + return PLAY_STATE_TO_STATE[self._player.state] @property def supported_features(self) -> int: diff --git a/tests/components/heos/conftest.py b/tests/components/heos/conftest.py index 11a2ece3442..175a180e4a3 100644 --- a/tests/components/heos/conftest.py +++ b/tests/components/heos/conftest.py @@ -2,7 +2,7 @@ from typing import Dict, Sequence from asynctest.mock import Mock, patch as patch -from pyheos import Dispatcher, HeosPlayer, HeosSource, InputSource, const +from pyheos import Dispatcher, Heos, HeosPlayer, HeosSource, InputSource, const import pytest from homeassistant.components.heos import DOMAIN @@ -22,20 +22,23 @@ def config_entry_fixture(): def controller_fixture( players, favorites, input_sources, playlists, change_data, dispatcher): """Create a mock Heos controller fixture.""" - with patch("pyheos.Heos", autospec=True) as mock: - mock_heos = mock.return_value - for player in players.values(): - player.heos = mock_heos - mock_heos.dispatcher = dispatcher - mock_heos.get_players.return_value = players - mock_heos.players = players - mock_heos.get_favorites.return_value = favorites - mock_heos.get_input_sources.return_value = input_sources - mock_heos.get_playlists.return_value = playlists - mock_heos.load_players.return_value = change_data - mock_heos.is_signed_in = True - mock_heos.signed_in_username = "user@user.com" - mock_heos.connection_state = const.STATE_CONNECTED + mock_heos = Mock(Heos) + for player in players.values(): + player.heos = mock_heos + mock_heos.dispatcher = dispatcher + mock_heos.get_players.return_value = players + mock_heos.players = players + mock_heos.get_favorites.return_value = favorites + mock_heos.get_input_sources.return_value = input_sources + mock_heos.get_playlists.return_value = playlists + mock_heos.load_players.return_value = change_data + mock_heos.is_signed_in = True + mock_heos.signed_in_username = "user@user.com" + mock_heos.connection_state = const.STATE_CONNECTED + mock = Mock(return_value=mock_heos) + + with patch("homeassistant.components.heos.Heos", new=mock), \ + patch("homeassistant.components.heos.config_flow.Heos", new=mock): yield mock_heos