From b5f1c1332ad662ba75de3e5fe3b18a02e86102d3 Mon Sep 17 00:00:00 2001 From: Flavio Castelli Date: Sun, 17 Apr 2016 22:17:13 +0200 Subject: [PATCH] Sonos: better handling of offline players (#1829) When a sonos player goes offline an endless stream of exceptions is raised. That happens because homeassistant keeps trying to refresh its status. This can happen even when a sonos player has gone offline **before** homeassistant is started. The sonos players take some time before realizing one of their mates is no longer online, leading to the same stream of exceptions inside of homeassistant. Three types of exceptions of can be raised: - `requests.packages.urllib3.exceptions.MaxRetryError` - `requests.packages.urllib3.exceptions.NewConnectionError` - `TimeoutError` It's not possible to handle all of them with a single `except` block because they are raised in a random order and after some delays. That means a 2nd or 3rd exception can take place while handling the 1st one. The only solution is to check whether a a player is actually reachable by attempting to connect to a service that must be running on it. Also all the players in a 'unknown' state should not be polled by homeassistant (despite of their brand). I'm going to upstream the `_is_reachable` method I added to the `sonos.py` file into `SoCo`. In the meantime we must ship this piece of code with homeassistant. Signed-off-by: Flavio Castelli --- .../components/media_player/sonos.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 4051f826bf1..f5a4e24440b 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -6,6 +6,7 @@ https://home-assistant.io/components/media_player.sonos/ """ import datetime import logging +import socket from homeassistant.components.media_player import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -13,7 +14,7 @@ from homeassistant.components.media_player import ( SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) from homeassistant.const import ( - STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) + STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_OFF) REQUIREMENTS = ['SoCo==0.11.1'] @@ -36,7 +37,6 @@ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Sonos platform.""" import soco - import socket if discovery_info: add_devices([SonosDevice(hass, soco.SoCo(discovery_info))]) @@ -138,9 +138,14 @@ class SonosDevice(MediaPlayerDevice): """Retrieve latest state.""" self._name = self._player.get_speaker_info()['zone_name'].replace( ' (R)', '').replace(' (L)', '') - self._status = self._player.get_current_transport_info().get( - 'current_transport_state') - self._trackinfo = self._player.get_current_track_info() + + if self.available: + self._status = self._player.get_current_transport_info().get( + 'current_transport_state') + self._trackinfo = self._player.get_current_track_info() + else: + self._status = STATE_OFF + self._trackinfo = {} @property def volume_level(self): @@ -253,3 +258,15 @@ class SonosDevice(MediaPlayerDevice): def play_media(self, media_type, media_id): """Send the play_media command to the media player.""" self._player.play_uri(media_id) + + @property + def available(self): + """Return True if player is reachable, False otherwise.""" + try: + sock = socket.create_connection( + address=(self._player.ip_address, 1443), + timeout=3) + sock.close() + return True + except socket.error: + return False