Add Roon volume hooks (#102470)

* Add ability for roon to call HA for volume changes.

* Fix merge errors.

* Fix mypy errors.

* Remove config option for hooks.

* WIP split entities.

* Tidy, fix test.

* Tidy after review.

* Remove event tests for now.

* Recview comments.

* remove trace.

* Bump pyroon to 0.1.5, deregister volume hooks.

* Remove type annotations.

* Add new file .coveragerc.

* Remove ghost constants.

* Review changes.

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
pull/103840/head
Greg Dowling 2023-11-12 10:58:15 +00:00 committed by GitHub
parent 5a452155fc
commit 6a7e87f1c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 131 additions and 7 deletions

View File

@ -1067,6 +1067,7 @@ omit =
homeassistant/components/roomba/sensor.py homeassistant/components/roomba/sensor.py
homeassistant/components/roomba/vacuum.py homeassistant/components/roomba/vacuum.py
homeassistant/components/roon/__init__.py homeassistant/components/roon/__init__.py
homeassistant/components/roon/event.py
homeassistant/components/roon/media_browser.py homeassistant/components/roon/media_browser.py
homeassistant/components/roon/media_player.py homeassistant/components/roon/media_player.py
homeassistant/components/roon/server.py homeassistant/components/roon/server.py

View File

@ -7,7 +7,7 @@ from homeassistant.helpers import device_registry as dr
from .const import CONF_ROON_NAME, DOMAIN from .const import CONF_ROON_NAME, DOMAIN
from .server import RoonServer from .server import RoonServer
PLATFORMS = [Platform.MEDIA_PLAYER] PLATFORMS = [Platform.EVENT, Platform.MEDIA_PLAYER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -13,8 +13,8 @@ DEFAULT_NAME = "Roon Labs Music Player"
ROON_APPINFO = { ROON_APPINFO = {
"extension_id": "home_assistant", "extension_id": "home_assistant",
"display_name": "Roon Integration for Home Assistant", "display_name": "Home Assistant",
"display_version": "1.0.0", "display_version": "1.0.1",
"publisher": "home_assistant", "publisher": "home_assistant",
"email": "home_assistant@users.noreply.github.com", "email": "home_assistant@users.noreply.github.com",
"website": "https://www.home-assistant.io/", "website": "https://www.home-assistant.io/",

View File

@ -0,0 +1,109 @@
"""Roon event entities."""
import logging
from typing import cast
from homeassistant.components.event import EventDeviceClass, EventEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Roon Event from Config Entry."""
roon_server = hass.data[DOMAIN][config_entry.entry_id]
event_entities = set()
@callback
def async_add_roon_volume_entity(player_data):
"""Add or update Roon event Entity."""
dev_id = player_data["dev_id"]
if dev_id in event_entities:
return
# new player!
event_entity = RoonEventEntity(roon_server, player_data)
event_entities.add(dev_id)
async_add_entities([event_entity])
# start listening for players to be added from the server component
config_entry.async_on_unload(
async_dispatcher_connect(
hass, "roon_media_player", async_add_roon_volume_entity
)
)
class RoonEventEntity(EventEntity):
"""Representation of a Roon Event entity."""
_attr_device_class = EventDeviceClass.BUTTON
_attr_event_types = ["volume_up", "volume_down"]
_attr_translation_key = "volume"
def __init__(self, server, player_data):
"""Initialize the entity."""
self._server = server
self._player_data = player_data
player_name = player_data["display_name"]
self._attr_name = f"{player_name} roon volume"
self._attr_unique_id = self._player_data["dev_id"]
if self._player_data.get("source_controls"):
dev_model = self._player_data["source_controls"][0].get("display_name")
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
# Instead of setting the device name to the entity name, roon
# should be updated to set has_entity_name = True, and set the entity
# name to None
name=cast(str | None, self.name),
manufacturer="RoonLabs",
model=dev_model,
via_device=(DOMAIN, self._server.roon_id),
)
@callback
def _roonapi_volume_callback(
self, control_key: str, event: str, value: int
) -> None:
"""Callbacks from the roon api with volume request."""
if event != "set_volume":
_LOGGER.debug("Received unsupported roon volume event %s", event)
return
if value > 0:
event = "volume_up"
else:
event = "volume_down"
self._trigger_event(event)
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""Register volume hooks with the roon api."""
self._server.roonapi.register_volume_control(
self.unique_id,
self.name,
self._roonapi_volume_callback,
0,
"incremental",
0,
0,
0,
False,
)
async def async_will_remove_from_hass(self) -> None:
"""Unregister volume hooks from the roon api."""
self._server.roonapi.unregister_volume_control(self.unique_id)

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/roon", "documentation": "https://www.home-assistant.io/integrations/roon",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["roonapi"], "loggers": ["roonapi"],
"requirements": ["roonapi==0.1.4"] "requirements": ["roonapi==0.1.5"]
} }

View File

@ -105,7 +105,7 @@ class RoonServer:
self._exit = True self._exit = True
def roonapi_state_callback(self, event, changed_zones): def roonapi_state_callback(self, event, changed_zones):
"""Callbacks from the roon api websockets.""" """Callbacks from the roon api websocket with state change."""
self.hass.add_job(self.async_update_changed_players(changed_zones)) self.hass.add_job(self.async_update_changed_players(changed_zones))
async def async_do_loop(self): async def async_do_loop(self):

View File

@ -22,6 +22,20 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
} }
}, },
"entity": {
"event": {
"volume": {
"state_attributes": {
"event_type": {
"state": {
"volume_up": "Volume up",
"volume_down": "Volume down"
}
}
}
}
}
},
"services": { "services": {
"transfer": { "transfer": {
"name": "Transfer", "name": "Transfer",

View File

@ -2361,7 +2361,7 @@ rokuecp==0.18.1
roombapy==1.6.8 roombapy==1.6.8
# homeassistant.components.roon # homeassistant.components.roon
roonapi==0.1.4 roonapi==0.1.5
# homeassistant.components.rova # homeassistant.components.rova
rova==0.3.0 rova==0.3.0

View File

@ -1757,7 +1757,7 @@ rokuecp==0.18.1
roombapy==1.6.8 roombapy==1.6.8
# homeassistant.components.roon # homeassistant.components.roon
roonapi==0.1.4 roonapi==0.1.5
# homeassistant.components.rpi_power # homeassistant.components.rpi_power
rpi-bad-power==0.1.0 rpi-bad-power==0.1.0