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",
"name": "Linn / OpenHome",
"documentation": "https://www.home-assistant.io/integrations/openhome",
"requirements": ["openhomedevice==0.7.2"],
"requirements": ["openhomedevice==2.0.1"],
"codeowners": ["@bazwilliams"],
"iot_class": "local_polling"
}

View File

@ -1,7 +1,11 @@
"""Support for Openhome Devices."""
import asyncio
import functools
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
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)
device = await hass.async_add_executor_job(Device, description)
await device.init()
# if device has already been discovered
if device.Uuid() in openhome_data:
if device.uuid() in openhome_data:
return True
entity = OpenhomeDevice(hass, device)
async_add_entities([entity])
openhome_data.add(device.Uuid())
openhome_data.add(device.uuid())
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_INVOKE_PIN,
{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):
"""Representation of an Openhome device."""
@ -80,64 +104,80 @@ class OpenhomeDevice(MediaPlayerEntity):
self._source = {}
self._name = None
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."""
self._in_standby = self._device.IsInStandby()
self._transport_state = self._device.TransportState()
self._track_information = self._device.TrackInfo()
self._source = self._device.Source()
self._name = self._device.Room().decode("utf-8")
self._supported_features = SUPPORT_OPENHOME
source_index = {}
source_names = []
try:
self._in_standby = await self._device.is_in_standby()
self._transport_state = await self._device.transport_state()
self._track_information = await self._device.track_info()
self._source = await self._device.source()
self._name = await self._device.room()
self._supported_features = SUPPORT_OPENHOME
source_index = {}
source_names = []
if self._device.VolumeEnabled():
self._supported_features |= (
SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET
)
self._volume_level = self._device.VolumeLevel() / 100.0
self._volume_muted = self._device.IsMuted()
if self._device.volume_enabled:
self._supported_features |= (
SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET
)
self._volume_level = await self._device.volume() / 100.0
self._volume_muted = await self._device.is_muted()
for source in self._device.Sources():
source_names.append(source["name"])
source_index[source["name"]] = source["index"]
for source in await self._device.sources():
source_names.append(source["name"])
source_index[source["name"]] = source["index"]
self._source_index = source_index
self._source_names = source_names
self._source_index = source_index
self._source_names = source_names
if self._source["type"] == "Radio":
self._supported_features |= SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA
if self._source["type"] in ("Playlist", "Spotify"):
self._supported_features |= (
SUPPORT_PREVIOUS_TRACK
| SUPPORT_NEXT_TRACK
| SUPPORT_PAUSE
| SUPPORT_PLAY
| SUPPORT_PLAY_MEDIA
)
if self._source["type"] == "Radio":
self._supported_features |= (
SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA
)
if self._source["type"] in ("Playlist", "Spotify"):
self._supported_features |= (
SUPPORT_PREVIOUS_TRACK
| SUPPORT_NEXT_TRACK
| SUPPORT_PAUSE
| SUPPORT_PLAY
| SUPPORT_PLAY_MEDIA
)
if self._in_standby:
self._state = STATE_OFF
elif self._transport_state == "Paused":
self._state = STATE_PAUSED
elif self._transport_state in ("Playing", "Buffering"):
self._state = STATE_PLAYING
elif self._transport_state == "Stopped":
self._state = STATE_IDLE
else:
# Device is playing an external source with no transport controls
self._state = STATE_PLAYING
if self._in_standby:
self._state = STATE_OFF
elif self._transport_state == "Paused":
self._state = STATE_PAUSED
elif self._transport_state in ("Playing", "Buffering"):
self._state = STATE_PLAYING
elif self._transport_state == "Stopped":
self._state = STATE_IDLE
else:
# Device is playing an external source with no transport controls
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."""
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."""
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."""
if media_type != MEDIA_TYPE_MUSIC:
_LOGGER.error(
@ -147,35 +187,48 @@ class OpenhomeDevice(MediaPlayerEntity):
)
return
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."""
self._device.Pause()
await self._device.pause()
def media_stop(self):
@catch_request_errors()
async def async_media_stop(self):
"""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."""
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."""
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."""
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."""
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."""
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
def name(self):
@ -190,7 +243,7 @@ class OpenhomeDevice(MediaPlayerEntity):
@property
def unique_id(self):
"""Return a unique ID."""
return self._device.Uuid()
return self._device.uuid()
@property
def state(self):
@ -239,18 +292,22 @@ class OpenhomeDevice(MediaPlayerEntity):
"""Return true if volume is muted."""
return self._volume_muted
def volume_up(self):
@catch_request_errors()
async def async_volume_up(self):
"""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."""
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."""
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."""
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
# homeassistant.components.openhome
openhomedevice==0.7.2
openhomedevice==2.0.1
# homeassistant.components.opensensemap
opensensemap-api==0.1.5