Openhome component now uses asyncio and handles unavailability (#49574)

This commit is contained in:
Barry Williams 2021-05-26 17:39:44 +01:00 committed by GitHub
parent 42b92748f6
commit 5ac81ccb4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 133 additions and 76 deletions

View File

@ -2,7 +2,7 @@
"domain": "openhome", "domain": "openhome",
"name": "Linn / OpenHome", "name": "Linn / OpenHome",
"documentation": "https://www.home-assistant.io/integrations/openhome", "documentation": "https://www.home-assistant.io/integrations/openhome",
"requirements": ["openhomedevice==0.7.2"], "requirements": ["openhomedevice==2.0.1"],
"codeowners": ["@bazwilliams"], "codeowners": ["@bazwilliams"],
"iot_class": "local_polling" "iot_class": "local_polling"
} }

View File

@ -1,7 +1,11 @@
"""Support for Openhome Devices.""" """Support for Openhome Devices."""
import asyncio
import functools
import logging import logging
from openhomedevice.Device import Device import aiohttp
from async_upnp_client.client import UpnpError
from openhomedevice.device import Device
import voluptuous as vol import voluptuous as vol
from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player import MediaPlayerEntity
@ -43,25 +47,45 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
_LOGGER.info("Openhome device found: %s", name) _LOGGER.info("Openhome device found: %s", name)
device = await hass.async_add_executor_job(Device, description) device = await hass.async_add_executor_job(Device, description)
await device.init()
# if device has already been discovered # if device has already been discovered
if device.Uuid() in openhome_data: if device.uuid() in openhome_data:
return True return True
entity = OpenhomeDevice(hass, device) entity = OpenhomeDevice(hass, device)
async_add_entities([entity]) async_add_entities([entity])
openhome_data.add(device.Uuid()) openhome_data.add(device.uuid())
platform = entity_platform.async_get_current_platform() platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service( platform.async_register_entity_service(
SERVICE_INVOKE_PIN, SERVICE_INVOKE_PIN,
{vol.Required(ATTR_PIN_INDEX): cv.positive_int}, {vol.Required(ATTR_PIN_INDEX): cv.positive_int},
"invoke_pin", "async_invoke_pin",
) )
def catch_request_errors():
"""Catch asyncio.TimeoutError, aiohttp.ClientError, UpnpError errors."""
def call_wrapper(func):
"""Call wrapper for decorator."""
@functools.wraps(func)
async def wrapper(self, *args, **kwargs):
"""Catch asyncio.TimeoutError, aiohttp.ClientError, UpnpError errors."""
try:
return await func(self, *args, **kwargs)
except (asyncio.TimeoutError, aiohttp.ClientError, UpnpError):
_LOGGER.error("Error during call %s", func.__name__)
return wrapper
return call_wrapper
class OpenhomeDevice(MediaPlayerEntity): class OpenhomeDevice(MediaPlayerEntity):
"""Representation of an Openhome device.""" """Representation of an Openhome device."""
@ -80,26 +104,33 @@ class OpenhomeDevice(MediaPlayerEntity):
self._source = {} self._source = {}
self._name = None self._name = None
self._state = STATE_PLAYING self._state = STATE_PLAYING
self._available = True
def update(self): @property
def available(self):
"""Device is available."""
return self._available
async def async_update(self):
"""Update state of device.""" """Update state of device."""
self._in_standby = self._device.IsInStandby() try:
self._transport_state = self._device.TransportState() self._in_standby = await self._device.is_in_standby()
self._track_information = self._device.TrackInfo() self._transport_state = await self._device.transport_state()
self._source = self._device.Source() self._track_information = await self._device.track_info()
self._name = self._device.Room().decode("utf-8") self._source = await self._device.source()
self._name = await self._device.room()
self._supported_features = SUPPORT_OPENHOME self._supported_features = SUPPORT_OPENHOME
source_index = {} source_index = {}
source_names = [] source_names = []
if self._device.VolumeEnabled(): if self._device.volume_enabled:
self._supported_features |= ( self._supported_features |= (
SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET
) )
self._volume_level = self._device.VolumeLevel() / 100.0 self._volume_level = await self._device.volume() / 100.0
self._volume_muted = self._device.IsMuted() self._volume_muted = await self._device.is_muted()
for source in self._device.Sources(): for source in await self._device.sources():
source_names.append(source["name"]) source_names.append(source["name"])
source_index[source["name"]] = source["index"] source_index[source["name"]] = source["index"]
@ -107,7 +138,9 @@ class OpenhomeDevice(MediaPlayerEntity):
self._source_names = source_names self._source_names = source_names
if self._source["type"] == "Radio": if self._source["type"] == "Radio":
self._supported_features |= SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA self._supported_features |= (
SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA
)
if self._source["type"] in ("Playlist", "Spotify"): if self._source["type"] in ("Playlist", "Spotify"):
self._supported_features |= ( self._supported_features |= (
SUPPORT_PREVIOUS_TRACK SUPPORT_PREVIOUS_TRACK
@ -129,15 +162,22 @@ class OpenhomeDevice(MediaPlayerEntity):
# Device is playing an external source with no transport controls # Device is playing an external source with no transport controls
self._state = STATE_PLAYING self._state = STATE_PLAYING
def turn_on(self): self._available = True
except (asyncio.TimeoutError, aiohttp.ClientError, UpnpError):
self._available = False
@catch_request_errors()
async def async_turn_on(self):
"""Bring device out of standby.""" """Bring device out of standby."""
self._device.SetStandby(False) await self._device.set_standby(False)
def turn_off(self): @catch_request_errors()
async def async_turn_off(self):
"""Put device in standby.""" """Put device in standby."""
self._device.SetStandby(True) await self._device.set_standby(True)
def play_media(self, media_type, media_id, **kwargs): @catch_request_errors()
async def async_play_media(self, media_type, media_id, **kwargs):
"""Send the play_media command to the media player.""" """Send the play_media command to the media player."""
if media_type != MEDIA_TYPE_MUSIC: if media_type != MEDIA_TYPE_MUSIC:
_LOGGER.error( _LOGGER.error(
@ -147,35 +187,48 @@ class OpenhomeDevice(MediaPlayerEntity):
) )
return return
track_details = {"title": "Home Assistant", "uri": media_id} track_details = {"title": "Home Assistant", "uri": media_id}
self._device.PlayMedia(track_details) await self._device.play_media(track_details)
def media_pause(self): @catch_request_errors()
async def async_media_pause(self):
"""Send pause command.""" """Send pause command."""
self._device.Pause() await self._device.pause()
def media_stop(self): @catch_request_errors()
async def async_media_stop(self):
"""Send stop command.""" """Send stop command."""
self._device.Stop() await self._device.stop()
def media_play(self): @catch_request_errors()
async def async_media_play(self):
"""Send play command.""" """Send play command."""
self._device.Play() await self._device.play()
def media_next_track(self): @catch_request_errors()
async def async_media_next_track(self):
"""Send next track command.""" """Send next track command."""
self._device.Skip(1) await self._device.skip(1)
def media_previous_track(self): @catch_request_errors()
async def async_media_previous_track(self):
"""Send previous track command.""" """Send previous track command."""
self._device.Skip(-1) await self._device.skip(-1)
def select_source(self, source): @catch_request_errors()
async def async_select_source(self, source):
"""Select input source.""" """Select input source."""
self._device.SetSource(self._source_index[source]) await self._device.set_source(self._source_index[source])
def invoke_pin(self, pin): @catch_request_errors()
async def async_invoke_pin(self, pin):
"""Invoke pin.""" """Invoke pin."""
self._device.InvokePin(pin) try:
if self._device.pins_enabled:
await self._device.invoke_pin(pin)
else:
_LOGGER.error("Pins service not supported")
except (UpnpError):
_LOGGER.error("Error invoking pin %s", pin)
@property @property
def name(self): def name(self):
@ -190,7 +243,7 @@ class OpenhomeDevice(MediaPlayerEntity):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique ID.""" """Return a unique ID."""
return self._device.Uuid() return self._device.uuid()
@property @property
def state(self): def state(self):
@ -239,18 +292,22 @@ class OpenhomeDevice(MediaPlayerEntity):
"""Return true if volume is muted.""" """Return true if volume is muted."""
return self._volume_muted return self._volume_muted
def volume_up(self): @catch_request_errors()
async def async_volume_up(self):
"""Volume up media player.""" """Volume up media player."""
self._device.IncreaseVolume() await self._device.increase_volume()
def volume_down(self): @catch_request_errors()
async def async_volume_down(self):
"""Volume down media player.""" """Volume down media player."""
self._device.DecreaseVolume() await self._device.decrease_volume()
def set_volume_level(self, volume): @catch_request_errors()
async def async_set_volume_level(self, volume):
"""Set volume level, range 0..1.""" """Set volume level, range 0..1."""
self._device.SetVolumeLevel(int(volume * 100)) await self._device.set_volume(int(volume * 100))
def mute_volume(self, mute): @catch_request_errors()
async def async_mute_volume(self, mute):
"""Mute (true) or unmute (false) media player.""" """Mute (true) or unmute (false) media player."""
self._device.SetMute(mute) await self._device.set_mute(mute)

View File

@ -1076,7 +1076,7 @@ openerz-api==0.1.0
openevsewifi==1.1.0 openevsewifi==1.1.0
# homeassistant.components.openhome # homeassistant.components.openhome
openhomedevice==0.7.2 openhomedevice==2.0.1
# homeassistant.components.opensensemap # homeassistant.components.opensensemap
opensensemap-api==0.1.5 opensensemap-api==0.1.5