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

media_player: add Denon remote support #328

Merged
merged 3 commits into from Sep 10, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ omit =
homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/squeezebox.py
Expand Down
192 changes: 192 additions & 0 deletions homeassistant/components/media_player/denon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
"""
homeassistant.components.media_player.denon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to Denon Network Receivers.
Developed for a Denon DRA-N5, see
http://www.denon.co.uk/chg/product/compactsystems/networkmusicsystems/ceolpiccolo

A few notes:
- As long as this module is active and connected, the receiver does
not seem to accept additional telnet connections.

- Be careful with the volume. 50% or even 100% are very loud.

- To be able to wake up the receiver, activate the "remote" setting
in the receiver's settings.

- Play and pause are supported, toggling is not possible.

- Seeking cannot be implemented as the UI sends absolute positions.
Only seeking via simulated button presses is possible.

Configuration:

To use your Denon you will need to add something like the following to
your config/configuration.yaml:

media_player:
platform: denon
name: Music station
host: 192.168.0.123

Variables:

host
*Required
The ip of the player. Example: 192.168.0.123

name
*Optional
The name of the device.
"""
import telnetlib
import logging

from homeassistant.components.media_player import (
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
DOMAIN)
from homeassistant.const import (
CONF_HOST, STATE_OFF, STATE_IDLE, STATE_UNKNOWN)

_LOGGER = logging.getLogger(__name__)

SUPPORT_DENON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF


# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Denon platform. """
if not config.get(CONF_HOST):
_LOGGER.error(
"Missing required configuration items in %s: %s",
DOMAIN,
CONF_HOST)
return False

add_devices([
DenonDevice(
config.get('name', 'Music station'),
config.get('host'))
])

return True


class DenonDevice(MediaPlayerDevice):
""" Represents a Denon device. """

# pylint: disable=too-many-public-methods

def __init__(self, name, host):
self._name = name
self._host = host
self._telnet = telnetlib.Telnet(self._host)

def query(self, message):
""" Send request and await response from server """
try:
# unspecified command, should be ignored
self._telnet.write("?".encode('UTF-8') + b'\r')
except (EOFError, BrokenPipeError, ConnectionResetError):
self._telnet.open(self._host)

_LOGGER.error(self._telnet.read_very_eager()) # skip
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be here?


_LOGGER.error(message)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be here?

self._telnet.write(message.encode('ASCII') + b'\r')
# timeout 200ms, defined by protocol
resp = self._telnet.read_until(b'\r', timeout=0.2)\
.decode('UTF-8').strip()

if message == "PW?":
# workaround; PW? sends also SISTATUS
_LOGGER.error(self._telnet.read_until(b'\r', timeout=0.2))
_LOGGER.error(resp)
return resp

@property
def name(self):
""" Returns the name of the device. """
return self._name

@property
def state(self):
""" Returns the state of the device. """
pwstate = self.query('PW?')
if pwstate == "PWSTANDBY":
return STATE_OFF
if pwstate == "PWON":
return STATE_IDLE
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like their API doesn't allow you to determine if the receiver is playing or not. You may want this to return STATE_ON instead of STATE_IDLE.


return STATE_UNKNOWN

@property
def volume_level(self):
""" Volume level of the media player (0..1). """
return int(self.query('MV?')[len('MV'):]) / 60

@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return self.query('MU?') == "MUON"

@property
def media_title(self):
""" Current media source. """
return self.query('SI?')[len('SI'):]

@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_DENON

def turn_off(self):
""" turn_off media player. """
self.query('PWSTANDBY')

def volume_up(self):
""" volume_up media player. """
self.query('MVUP')

def volume_down(self):
""" volume_down media player. """
self.query('MVDOWN')

def set_volume_level(self, volume):
""" set volume level, range 0..1. """
# 60dB max
self.query('MV' + str(round(volume * 60)).zfill(2))

def mute_volume(self, mute):
""" mute (true) or unmute (false) media player. """
self.query('MU' + ('ON' if mute else 'OFF'))

def media_play_pause(self):
""" media_play_pause media player. """
raise NotImplementedError()

def media_play(self):
""" media_play media player. """
self.query('NS9A')

def media_pause(self):
""" media_pause media player. """
self.query('NS9B')

def media_next_track(self):
""" Send next track command. """
self.query('NS9D')

def media_previous_track(self):
self.query('NS9E')

def media_seek(self, position):
raise NotImplementedError()

def turn_on(self):
""" turn the media player on. """
self.query('PWON')