From db2cbf33c3172c889fcc70e95efb39bdd8d7e0f1 Mon Sep 17 00:00:00 2001 From: Per Sandstrom <Per.J.Sandstrom@gmail.com> Date: Wed, 5 Aug 2015 13:49:45 +0200 Subject: [PATCH] Added support for multiple players --- .../components/media_player/squeezebox.py | 221 ++++++++++-------- 1 file changed, 129 insertions(+), 92 deletions(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 70eab958000..fa05e304c29 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -10,34 +10,24 @@ To use SqueezeBox add something like this to your configuration: media_player: platform: squeezebox - name: SqueezeBox - server: 192.168.1.21 - player: Player1 + host: 192.168.1.21 port: 9090 - user: user + username: user password: password Variables: -name -*Optional -The name of the device - -server +host *Required -The address of the Logitech Media Server - -player -*Required -The unique name of the player +The host name or address of the Logitech Media Server port *Optional Telnet port to Logitech Media Server, default 9090 -user +usermame *Optional -User, if password protection is enabled +Username, if password protection is enabled password *Optional @@ -51,8 +41,10 @@ import urllib.parse from homeassistant.components.media_player import ( MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, - MEDIA_TYPE_MUSIC) + MEDIA_TYPE_MUSIC, DOMAIN) + from homeassistant.const import ( + CONF_HOST, CONF_USERNAME, CONF_PASSWORD, STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN) _LOGGER = logging.getLogger(__name__) @@ -64,15 +56,104 @@ SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the squeezebox platform. """ - add_devices([ - SqueezeBoxDevice( - config.get('name', 'SqueezeBox'), - config.get('server'), - config.get('player'), - config.get('port', '9090'), - config.get('user', None), - config.get('password', None) - )]) + if not config.get(CONF_HOST): + _LOGGER.error( + "Missing required configuration items in {}: {}".format( + DOMAIN, CONF_HOST)) + return False + + lms = LogitechMediaServer( + config.get(CONF_HOST), + config.get('port', '9090'), + config.get(CONF_USERNAME), + config.get(CONF_PASSWORD)) + + if not lms.init_success: + return False + + add_devices(lms.create_players()) + + return True + + +class LogitechMediaServer(object): + def __init__(self, host, port, username, password): + self.host = host + self.port = port + self._username = username + self._password = password + self.http_port = self._get_http_port() + self.init_success = True if self.http_port else False + + def _get_http_port(self): + """ Get http port from media server, it is used to get cover art """ + http_port = None + try: + http_port = self.query('pref', 'httpport', '?') + if not http_port: + _LOGGER.error( + "Unable to read data from server {}:{}".format( + self.host, + self.port)) + return + return http_port + except ConnectionError as ex: + _LOGGER.error( + "Failed to connect to server {}:{} - {}".format( + self.host, + self.port, + ex)) + return + + def create_players(self): + """ Create a list of SqueezeBoxDevices connected to the LMS """ + players = [] + count = self.query('player', 'count', '?') + for index in range(0, int(count)): + player_id = self.query('player', 'id', str(index), '?') + player = SqueezeBoxDevice(self, player_id) + players.append(player) + return players + + def query(self, *parameters): + """ Send request and await response from server """ + telnet = telnetlib.Telnet(self.host, self.port) + if self._username and self._password: + telnet.write('login {username} {password}\n'.format( + username=self._username, + password=self._password).encode('UTF-8')) + telnet.read_until(b'\n', timeout=3) + message = '{}\n'.format(' '.join(parameters)) + telnet.write(message.encode('UTF-8')) + response = telnet.read_until(b'\n', timeout=3)\ + .decode('UTF-8')\ + .split(' ')[-1]\ + .strip() + telnet.write(b'exit\n') + return urllib.parse.unquote(response) + + def get_player_status(self, player): + """ Get ithe status of a player """ + # (title) : Song title + # Requested Information + # a (artist): Artist name 'artist' + # d (duration): Song duration in seconds 'duration' + # K (artwork_url): URL to remote artwork + tags = 'adK' + new_status = {} + telnet = telnetlib.Telnet(self.host, self.port) + telnet.write('{player} status - 1 tags:{tags}\n'.format( + player=player, + tags=tags + ).encode('UTF-8')) + response = telnet.read_until(b'\n', timeout=3)\ + .decode('UTF-8')\ + .split(' ') + telnet.write(b'exit\n') + for item in response: + parts = urllib.parse.unquote(item).partition(':') + new_status[parts[0]] = parts[2] + return new_status # pylint: disable=too-many-instance-attributes @@ -81,17 +162,12 @@ class SqueezeBoxDevice(MediaPlayerDevice): """ Represents a SqueezeBox device. """ # pylint: disable=too-many-arguments - def __init__(self, name, server, player, port, user, password): + def __init__(self, lms, player_id): super(SqueezeBoxDevice, self).__init__() - self._name = name - self._server = server - self._player = player - self._port = port - self._user = user - self._password = password - self._status = {} - self.update() - self._http_port = self._query('pref', 'httpport', '?') + self._lms = lms + self._id = player_id + self._name = self._lms.query(self._id, 'name', '?') + self._status = self._lms.get_player_status(self._id) @property def name(self): @@ -113,47 +189,8 @@ class SqueezeBoxDevice(MediaPlayerDevice): return STATE_UNKNOWN def update(self): - self._get_status() - - def _query(self, *parameters): - """ Send request and await response from server """ - telnet = telnetlib.Telnet(self._server, self._port) - if self._user and self._password: - telnet.write('login {user} {password}\n'.format( - user=self._user, - password=self._password).encode('UTF-8')) - telnet.read_until(b'\n', timeout=3) - message = '{}\n'.format(' '.join(parameters)) - telnet.write(message.encode('UTF-8')) - response = telnet.read_until(b'\n', timeout=3)\ - .decode('UTF-8')\ - .split(' ')[-1]\ - .strip() - telnet.write(b'exit\n') - return urllib.parse.unquote(response) - - def _get_status(self): - """ request status and parse result """ - # (title) : Song title - # Requested Information - # a (artist): Artist name 'artist' - # d (duration): Song duration in seconds 'duration' - # K (artwork_url): URL to remote artwork - tags = 'adK' - new_status = {} - telnet = telnetlib.Telnet(self._server, self._port) - telnet.write('{player} status - 1 tags:{tags}\n'.format( - player=self._player, - tags=tags - ).encode('UTF-8')) - response = telnet.read_until(b'\n', timeout=3)\ - .decode('UTF-8')\ - .split(' ') - telnet.write(b'exit\n') - for item in response: - parts = urllib.parse.unquote(item).partition(':') - new_status[parts[0]] = parts[2] - self._status = new_status + """ Retrieve latest state. """ + self._status = self._lms.get_player_status(self._name) @property def volume_level(self): @@ -190,9 +227,9 @@ class SqueezeBoxDevice(MediaPlayerDevice): return self._status['artwork_url'] return 'http://{server}:{port}/music/current/cover.jpg?player={player}'\ .format( - server=self._server, - port=self._http_port, - player=self._player) + server=self._lms.host, + port=self._lms.http_port, + player=self._id) @property def media_title(self): @@ -212,64 +249,64 @@ class SqueezeBoxDevice(MediaPlayerDevice): def turn_off(self): """ turn_off media player. """ - self._query(self._player, 'power', '0') + self._lms.query(self._id, 'power', '0') self.update_ha_state() def volume_up(self): """ volume_up media player. """ - self._query(self._player, 'mixer', 'volume', '+5') + self._lms.query(self._id, 'mixer', 'volume', '+5') self.update_ha_state() def volume_down(self): """ volume_down media player. """ - self._query(self._player, 'mixer', 'volume', '-5') + self._lms.query(self._id, 'mixer', 'volume', '-5') self.update_ha_state() def set_volume_level(self, volume): """ set volume level, range 0..1. """ volume_percent = str(int(volume*100)) - self._query(self._player, 'mixer', 'volume', volume_percent) + self._lms.query(self._id, 'mixer', 'volume', volume_percent) self.update_ha_state() def mute_volume(self, mute): """ mute (true) or unmute (false) media player. """ mute_numeric = '1' if mute else '0' - self._query(self._player, 'mixer', 'muting', mute_numeric) + self._lms.query(self._id, 'mixer', 'muting', mute_numeric) self.update_ha_state() def media_play_pause(self): """ media_play_pause media player. """ - self._query(self._player, 'pause') + self._lms.query(self._id, 'pause') self.update_ha_state() def media_play(self): """ media_play media player. """ - self._query(self._player, 'play') + self._lms.query(self._id, 'play') self.update_ha_state() def media_pause(self): """ media_pause media player. """ - self._query(self._player, 'pause', '0') + self._lms.query(self._id, 'pause', '0') self.update_ha_state() def media_next_track(self): """ Send next track command. """ - self._query(self._player, 'playlist', 'index', '+1') + self._lms.query(self._id, 'playlist', 'index', '+1') self.update_ha_state() def media_previous_track(self): """ Send next track command. """ - self._query(self._player, 'playlist', 'index', '-1') + self._lms.query(self._id, 'playlist', 'index', '-1') self.update_ha_state() def media_seek(self, position): """ Send seek command. """ - self._query(self._player, 'time', position) + self._lms.query(self._id, 'time', position) self.update_ha_state() def turn_on(self): """ turn the media player on. """ - self._query(self._player, 'power', '1') + self._lms.query(self._id, 'power', '1') self.update_ha_state() def play_youtube(self, media_id):