-
-
Notifications
You must be signed in to change notification settings - Fork 28.5k
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
||
_LOGGER.error(message) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be here?