diff --git a/.coveragerc b/.coveragerc index 3cba8519314..662d880af1b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -235,11 +235,15 @@ omit = homeassistant/components/harmony/remote.py homeassistant/components/haveibeenpwned/sensor.py homeassistant/components/hdmi_cec/* +<<<<<<< HEAD homeassistant/components/heatmiser/climate.py homeassistant/components/hikvision/binary_sensor.py homeassistant/components/hikvisioncam/switch.py homeassistant/components/hipchat/notify.py homeassistant/components/hitron_coda/device_tracker.py +======= + homeassistant/components/heos/* +>>>>>>> Update HEOS to support multiple speaker and conformance. homeassistant/components/hive/* homeassistant/components/hlk_sw16/* homeassistant/components/homekit_controller/* diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py new file mode 100644 index 00000000000..e9b775b05d0 --- /dev/null +++ b/homeassistant/components/heos/__init__.py @@ -0,0 +1,52 @@ +"""Denon HEOS Media Player.""" + +import asyncio +import logging + +import voluptuous as vol + +from homeassistant.components.media_player.const import ( + DOMAIN as MEDIA_PLAYER_DOMAIN) +from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.typing import ConfigType, HomeAssistantType + +DOMAIN = 'heos' +REQUIREMENTS = ['aioheos==0.4.0'] + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST): cv.string + }) +}, extra=vol.ALLOW_EXTRA) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistantType, config: ConfigType): + """Set up the HEOS component.""" + from aioheos import AioHeosController + + host = config[DOMAIN][CONF_HOST] + controller = AioHeosController(hass.loop, host) + + try: + await asyncio.wait_for(controller.connect(), timeout=5.0) + except asyncio.TimeoutError: + _LOGGER.error('Timeout during setup.') + return False + + async def controller_close(event): + """Close connection when HASS shutsdown.""" + await controller.close() + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, controller_close) + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][MEDIA_PLAYER_DOMAIN] = controller + + hass.async_create_task(async_load_platform( + hass, MEDIA_PLAYER_DOMAIN, DOMAIN, {}, config)) + + return True diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py new file mode 100644 index 00000000000..8047ffd0775 --- /dev/null +++ b/homeassistant/components/heos/media_player.py @@ -0,0 +1,152 @@ +"""Denon HEOS Media Player.""" + +from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player.const import ( + DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) +from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING + +from . import DOMAIN as HEOS_DOMAIN + +DEPENDENCIES = ["heos"] + +SUPPORT_HEOS = ( + SUPPORT_PLAY + | SUPPORT_STOP + | SUPPORT_PAUSE + | SUPPORT_PLAY_MEDIA + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_STEP +) + +PLAY_STATE_TO_STATE = { + "play": STATE_PLAYING, + "pause": STATE_PAUSED, + "stop": STATE_IDLE, +} + + +async def async_setup_platform(hass, config, async_add_devices, + discover_info=None): + """Set up the HEOS platform.""" + controller = hass.data[HEOS_DOMAIN][DOMAIN] + players = controller.get_players() + devices = [HeosMediaPlayer(p) for p in players] + async_add_devices(devices, True) + + +class HeosMediaPlayer(MediaPlayerDevice): + """The HEOS player.""" + + def __init__(self, player): + """Initialize.""" + self._player = player + + def _update_state(self): + self.async_schedule_update_ha_state() + + async def async_update(self): + """Update the player.""" + self._player.request_update() + + async def async_added_to_hass(self): + """Device added to hass.""" + self._player.state_change_callback = self._update_state + + @property + def unique_id(self): + """Get unique id of the player.""" + return self._player.player_id + + @property + def name(self): + """Return the name of the device.""" + return self._player.name + + @property + def volume_level(self): + """Volume level of the device (0..1).""" + volume = self._player.volume + return float(volume) / 100 + + @property + def state(self): + """Get state.""" + return PLAY_STATE_TO_STATE.get(self._player.play_state) + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def media_content_type(self): + """Content type of current playing media.""" + return MEDIA_TYPE_MUSIC + + @property + def media_artist(self): + """Artist of current playing media.""" + return self._player.media_artist + + @property + def media_title(self): + """Album name of current playing media.""" + return self._player.media_title + + @property + def media_album_name(self): + """Album name of current playing media.""" + return self._player.media_album + + @property + def media_image_url(self): + """Return the image url of current playing media.""" + return self._player.media_image_url + + @property + def media_content_id(self): + """Return the content ID of current playing media.""" + return self._player.media_id + + @property + def is_volume_muted(self): + """Boolean if volume is currently muted.""" + return self._player.mute == "on" + + async def async_mute_volume(self, mute): + """Mute volume.""" + self._player.set_mute(mute) + + async def async_media_next_track(self): + """Go TO next track.""" + self._player.play_next() + + async def async_media_previous_track(self): + """Go TO previous track.""" + self._player.play_previous() + + @property + def supported_features(self): + """Flag of media commands that are supported.""" + return SUPPORT_HEOS + + async def async_set_volume_level(self, volume): + """Set volume level, range 0..1.""" + self._player.set_volume(volume * 100) + + async def async_media_play(self): + """Play media player.""" + self._player.play() + + async def async_media_stop(self): + """Stop media player.""" + self._player.stop() + + async def async_media_pause(self): + """Pause media player.""" + self._player.pause() diff --git a/requirements_all.txt b/requirements_all.txt index 1832aa5cf65..31b62eb824b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -123,6 +123,9 @@ aioftp==0.12.0 # homeassistant.components.harmony.remote aioharmony==0.1.8 +# homeassistant.components.heos +aioheos==0.4.0 + # homeassistant.components.emulated_hue # homeassistant.components.http aiohttp_cors==0.7.0