mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Add button platform for Squeezebox integration (#140697)
* initial * trans key correction * base class updates * model tidy up * Update homeassistant/components/squeezebox/strings.json Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/squeezebox/entity.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/squeezebox/media_player.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/squeezebox/media_player.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/squeezebox/button.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * review updates * update * move manufacturer to library * updates * list concat * review updates * Update tests/components/squeezebox/test_button.py --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
70ed120c6e
commit
e48a25e952
@ -53,6 +53,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.MEDIA_PLAYER,
|
||||
Platform.SENSOR,
|
||||
]
|
||||
|
155
homeassistant/components/squeezebox/button.py
Normal file
155
homeassistant/components/squeezebox/button.py
Normal file
@ -0,0 +1,155 @@
|
||||
"""Platform for button integration for squeezebox."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import SqueezeboxConfigEntry
|
||||
from .const import SIGNAL_PLAYER_DISCOVERED
|
||||
from .coordinator import SqueezeBoxPlayerUpdateCoordinator
|
||||
from .entity import SqueezeboxEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
HARDWARE_MODELS_WITH_SCREEN = [
|
||||
"Squeezebox Boom",
|
||||
"Squeezebox Radio",
|
||||
"Transporter",
|
||||
"Squeezebox Touch",
|
||||
"Squeezebox",
|
||||
"SliMP3",
|
||||
"Squeezebox 1",
|
||||
"Squeezebox 2",
|
||||
"Squeezebox 3",
|
||||
]
|
||||
|
||||
HARDWARE_MODELS_WITH_TONE = [
|
||||
*HARDWARE_MODELS_WITH_SCREEN,
|
||||
"Squeezebox Receiver",
|
||||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SqueezeboxButtonEntityDescription(ButtonEntityDescription):
|
||||
"""Squeezebox Button description."""
|
||||
|
||||
press_action: str
|
||||
|
||||
|
||||
BUTTON_ENTITIES: tuple[SqueezeboxButtonEntityDescription, ...] = tuple(
|
||||
SqueezeboxButtonEntityDescription(
|
||||
key=f"preset_{i}",
|
||||
translation_key="preset",
|
||||
translation_placeholders={"index": str(i)},
|
||||
press_action=f"preset_{i}.single",
|
||||
)
|
||||
for i in range(1, 7)
|
||||
)
|
||||
|
||||
SCREEN_BUTTON_ENTITIES: tuple[SqueezeboxButtonEntityDescription, ...] = (
|
||||
SqueezeboxButtonEntityDescription(
|
||||
key="brightness_up",
|
||||
translation_key="brightness_up",
|
||||
press_action="brightness_up",
|
||||
),
|
||||
SqueezeboxButtonEntityDescription(
|
||||
key="brightness_down",
|
||||
translation_key="brightness_down",
|
||||
press_action="brightness_down",
|
||||
),
|
||||
)
|
||||
|
||||
TONE_BUTTON_ENTITIES: tuple[SqueezeboxButtonEntityDescription, ...] = (
|
||||
SqueezeboxButtonEntityDescription(
|
||||
key="bass_up",
|
||||
translation_key="bass_up",
|
||||
press_action="bass_up",
|
||||
),
|
||||
SqueezeboxButtonEntityDescription(
|
||||
key="bass_down",
|
||||
translation_key="bass_down",
|
||||
press_action="bass_down",
|
||||
),
|
||||
SqueezeboxButtonEntityDescription(
|
||||
key="treble_up",
|
||||
translation_key="treble_up",
|
||||
press_action="treble_up",
|
||||
),
|
||||
SqueezeboxButtonEntityDescription(
|
||||
key="treble_down",
|
||||
translation_key="treble_down",
|
||||
press_action="treble_down",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: SqueezeboxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Squeezebox button platform from a server config entry."""
|
||||
|
||||
# Add button entities when player discovered
|
||||
async def _player_discovered(
|
||||
player_coordinator: SqueezeBoxPlayerUpdateCoordinator,
|
||||
) -> None:
|
||||
_LOGGER.debug(
|
||||
"Setting up button entity for player %s, model %s",
|
||||
player_coordinator.player.name,
|
||||
player_coordinator.player.model,
|
||||
)
|
||||
|
||||
entities: list[SqueezeboxButtonEntity] = []
|
||||
|
||||
entities.extend(
|
||||
SqueezeboxButtonEntity(player_coordinator, description)
|
||||
for description in BUTTON_ENTITIES
|
||||
)
|
||||
|
||||
entities.extend(
|
||||
SqueezeboxButtonEntity(player_coordinator, description)
|
||||
for description in TONE_BUTTON_ENTITIES
|
||||
if player_coordinator.player.model in HARDWARE_MODELS_WITH_TONE
|
||||
)
|
||||
|
||||
entities.extend(
|
||||
SqueezeboxButtonEntity(player_coordinator, description)
|
||||
for description in SCREEN_BUTTON_ENTITIES
|
||||
if player_coordinator.player.model in HARDWARE_MODELS_WITH_SCREEN
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
entry.async_on_unload(
|
||||
async_dispatcher_connect(hass, SIGNAL_PLAYER_DISCOVERED, _player_discovered)
|
||||
)
|
||||
|
||||
|
||||
class SqueezeboxButtonEntity(SqueezeboxEntity, ButtonEntity):
|
||||
"""Representation of Buttons for Squeezebox entities."""
|
||||
|
||||
entity_description: SqueezeboxButtonEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: SqueezeBoxPlayerUpdateCoordinator,
|
||||
entity_description: SqueezeboxButtonEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the SqueezeBox Button."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = (
|
||||
f"{format_mac(self._player.player_id)}_{entity_description.key}"
|
||||
)
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Execute the button action."""
|
||||
await self._player.async_query("button", self.entity_description.press_action)
|
@ -1,11 +1,37 @@
|
||||
"""Base class for Squeezebox Sensor entities."""
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_NETWORK_MAC,
|
||||
DeviceInfo,
|
||||
format_mac,
|
||||
)
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, STATUS_QUERY_UUID
|
||||
from .coordinator import LMSStatusDataUpdateCoordinator
|
||||
from .coordinator import (
|
||||
LMSStatusDataUpdateCoordinator,
|
||||
SqueezeBoxPlayerUpdateCoordinator,
|
||||
)
|
||||
|
||||
|
||||
class SqueezeboxEntity(CoordinatorEntity[SqueezeBoxPlayerUpdateCoordinator]):
|
||||
"""Base entity class for Squeezebox entities."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, coordinator: SqueezeBoxPlayerUpdateCoordinator) -> None:
|
||||
"""Initialize the SqueezeBox entity."""
|
||||
super().__init__(coordinator)
|
||||
self._player = coordinator.player
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, format_mac(self._player.player_id))},
|
||||
name=self._player.name,
|
||||
connections={(CONNECTION_NETWORK_MAC, format_mac(self._player.player_id))},
|
||||
via_device=(DOMAIN, coordinator.server_uuid),
|
||||
model=self._player.model,
|
||||
manufacturer=self._player.creator,
|
||||
)
|
||||
|
||||
|
||||
class LMSStatusEntity(CoordinatorEntity[LMSStatusDataUpdateCoordinator]):
|
||||
|
@ -35,15 +35,10 @@ from homeassistant.helpers import (
|
||||
entity_platform,
|
||||
entity_registry as er,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_NETWORK_MAC,
|
||||
DeviceInfo,
|
||||
format_mac,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.start import async_at_start
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .browse_media import (
|
||||
@ -68,6 +63,7 @@ from .const import (
|
||||
SQUEEZEBOX_SOURCE_STRINGS,
|
||||
)
|
||||
from .coordinator import SqueezeBoxPlayerUpdateCoordinator
|
||||
from .entity import SqueezeboxEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import SqueezeboxConfigEntry
|
||||
@ -181,9 +177,7 @@ def get_announce_timeout(extra: dict) -> int | None:
|
||||
return announce_timeout
|
||||
|
||||
|
||||
class SqueezeBoxMediaPlayerEntity(
|
||||
CoordinatorEntity[SqueezeBoxPlayerUpdateCoordinator], MediaPlayerEntity
|
||||
):
|
||||
class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity):
|
||||
"""Representation of the media player features of a SqueezeBox device.
|
||||
|
||||
Wraps a pysqueezebox.Player() object.
|
||||
@ -217,30 +211,10 @@ class SqueezeBoxMediaPlayerEntity(
|
||||
def __init__(self, coordinator: SqueezeBoxPlayerUpdateCoordinator) -> None:
|
||||
"""Initialize the SqueezeBox device."""
|
||||
super().__init__(coordinator)
|
||||
player = coordinator.player
|
||||
self._player = player
|
||||
self._query_result: bool | dict = {}
|
||||
self._remove_dispatcher: Callable | None = None
|
||||
self._previous_media_position = 0
|
||||
self._attr_unique_id = format_mac(player.player_id)
|
||||
_manufacturer = None
|
||||
if player.model.startswith("SqueezeLite") or "SqueezePlay" in player.model:
|
||||
_manufacturer = "Ralph Irving"
|
||||
elif (
|
||||
"Squeezebox" in player.model
|
||||
or "Transporter" in player.model
|
||||
or "Slim" in player.model
|
||||
):
|
||||
_manufacturer = "Logitech"
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||
name=player.name,
|
||||
connections={(CONNECTION_NETWORK_MAC, self._attr_unique_id)},
|
||||
via_device=(DOMAIN, coordinator.server_uuid),
|
||||
model=player.model,
|
||||
manufacturer=_manufacturer,
|
||||
)
|
||||
self._attr_unique_id = format_mac(self._player.player_id)
|
||||
self._browse_data = BrowseData()
|
||||
|
||||
@callback
|
||||
|
@ -63,6 +63,29 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"button": {
|
||||
"preset": {
|
||||
"name": "Preset {index}"
|
||||
},
|
||||
"brightness_up": {
|
||||
"name": "Brightness up"
|
||||
},
|
||||
"brightness_down": {
|
||||
"name": "Brightness down"
|
||||
},
|
||||
"bass_up": {
|
||||
"name": "Bass up"
|
||||
},
|
||||
"bass_down": {
|
||||
"name": "Bass down"
|
||||
},
|
||||
"treble_up": {
|
||||
"name": "Treble up"
|
||||
},
|
||||
"treble_down": {
|
||||
"name": "Treble down"
|
||||
}
|
||||
},
|
||||
"binary_sensor": {
|
||||
"rescan": {
|
||||
"name": "Library rescan"
|
||||
|
@ -269,6 +269,7 @@ def mock_pysqueezebox_player(uuid: str) -> MagicMock:
|
||||
mock_player.title = None
|
||||
mock_player.image_url = None
|
||||
mock_player.model = "SqueezeLite"
|
||||
mock_player.creator = "Ralph Irving & Adrian Smith"
|
||||
|
||||
return mock_player
|
||||
|
||||
@ -309,7 +310,27 @@ async def configure_squeezebox_media_player_platform(
|
||||
) -> None:
|
||||
"""Configure a squeezebox config entry with appropriate mocks for media_player."""
|
||||
with (
|
||||
patch("homeassistant.components.squeezebox.PLATFORMS", [Platform.MEDIA_PLAYER]),
|
||||
patch(
|
||||
"homeassistant.components.squeezebox.PLATFORMS",
|
||||
[Platform.MEDIA_PLAYER],
|
||||
),
|
||||
patch("homeassistant.components.squeezebox.Server", return_value=lms),
|
||||
):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
|
||||
async def configure_squeezebox_media_player_button_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
lms: MagicMock,
|
||||
) -> None:
|
||||
"""Configure a squeezebox config entry with appropriate mocks for media_player."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.squeezebox.PLATFORMS",
|
||||
[Platform.BUTTON],
|
||||
),
|
||||
patch("homeassistant.components.squeezebox.Server", return_value=lms),
|
||||
):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
@ -325,6 +346,15 @@ async def configured_player(
|
||||
return (await lms.async_get_players())[0]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def configured_player_with_button(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry, lms: MagicMock
|
||||
) -> MagicMock:
|
||||
"""Fixture mocking calls to pysqueezebox Player from a configured squeezebox."""
|
||||
await configure_squeezebox_media_player_button_platform(hass, config_entry, lms)
|
||||
return (await lms.async_get_players())[0]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def configured_players(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry, lms_factory: MagicMock
|
||||
|
@ -24,7 +24,7 @@
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Ralph Irving',
|
||||
'manufacturer': 'Ralph Irving & Adrian Smith',
|
||||
'model': 'SqueezeLite',
|
||||
'model_id': None,
|
||||
'name': 'Test Player',
|
||||
|
23
tests/components/squeezebox/test_button.py
Normal file
23
tests/components/squeezebox/test_button.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""Tests for the squeezebox button component."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
async def test_squeezebox_press(
|
||||
hass: HomeAssistant, configured_player_with_button: MagicMock
|
||||
) -> None:
|
||||
"""Test press service call."""
|
||||
await hass.services.async_call(
|
||||
BUTTON_DOMAIN,
|
||||
SERVICE_PRESS,
|
||||
{ATTR_ENTITY_ID: "button.test_player_preset_1"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
configured_player_with_button.async_query.assert_called_with(
|
||||
"button", "preset_1.single"
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user