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 <fcastelli@suse.com>
This commit is contained in:
Flavio Castelli 2016-04-17 22:17:13 +02:00 committed by Paulus Schoutsen
parent a482c4b2a1
commit b5f1c1332a

View file

@ -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