Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HEOS integration #21721

Merged
merged 18 commits into from
Mar 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -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/*
Expand Down
52 changes: 52 additions & 0 deletions homeassistant/components/heos/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Denon HEOS Media Player."""

import asyncio
import logging

import voluptuous as vol
easink marked this conversation as resolved.
Show resolved Hide resolved

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use async_timeout in the core, so that's available as a replacement for asyncio.wait_for. I would recommend using the former instead, but it's not required.

https://github.com/aio-libs/async-timeout

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async_timeout has more appealing syntax, but I want to remove this later anyway and restructuring the connect function.

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)
easink marked this conversation as resolved.
Show resolved Hide resolved

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
152 changes: 152 additions & 0 deletions homeassistant/components/heos/media_player.py
Original file line number Diff line number Diff line change
@@ -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):
andrewsayre marked this conversation as resolved.
Show resolved Hide resolved
"""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()
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down