Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for advanced networking in the Sonos provider. #1885

Merged
merged 2 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions music_assistant/providers/sonos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
import logging
from typing import TYPE_CHECKING

from music_assistant_models.config_entries import ConfigEntry, ConfigEntryType

from music_assistant.constants import VERBOSE_LOG_LEVEL

from .provider import SonosPlayerProvider
from .provider import CONF_IPS, SonosPlayerProvider

if TYPE_CHECKING:
from music_assistant_models.config_entries import ConfigEntry, ConfigValueType, ProviderConfig
from music_assistant_models.config_entries import ConfigValueType, ProviderConfig
from music_assistant_models.provider import ProviderManifest

from music_assistant import MusicAssistant
Expand Down Expand Up @@ -49,4 +51,20 @@ async def get_config_entries(
values: the (intermediate) raw values for config entries sent with the action.
"""
# ruff: noqa: ARG001
return ()
return (
ConfigEntry(
key=CONF_IPS,
type=ConfigEntryType.STRING,
label="IP addresses (ADVANCED, NOT SUPPORTED)",
description="Additional fixed IP addresses for speakers. "
"Should be formatted as a comma separated list of IP addresses "
"(e.g. '10.0.0.42, 10.0.0.45').\n"
"Invalid addresses may result in the Sonos provider "
"becoming unresponsive and server crashes.\n"
"Bidirectional unicast communication to and between all IPs is required.\n"
"NOT SUPPORTED, USE ON YOU'RE OWN RISK",
category="advanced",
default_value=None,
required=False,
),
)
30 changes: 30 additions & 0 deletions music_assistant/providers/sonos/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
if TYPE_CHECKING:
from zeroconf.asyncio import AsyncServiceInfo

CONF_IPS = "ips"


class SonosPlayerProvider(PlayerProvider):
"""Sonos Player provider."""
Expand Down Expand Up @@ -67,6 +69,34 @@ async def handle_async_init(self) -> None:
"/sonos_queue/v2.3/timePlayed", self._handle_sonos_queue_time_played
)

async def loaded_in_mass(self) -> None:
"""Call after the provider has been loaded."""
await super().loaded_in_mass()

manual_ip_config: str | None
# Handle config option for manual IP's (comma separated list)
if (manual_ip_config := self.config.get_value(CONF_IPS)) is not None:
ips = manual_ip_config.split(",")
for raw_ip in ips:
# strip to ignore whitespace
# (e.g. '10.0.0.42, 10.0.0.43' -> ('10.0.0.42', ' 10.0.0.43'))
ip = raw_ip.strip()
if ip == "":
continue
try:
# get discovery info from SONOS speaker so we can provide an ID & other info
discovery_info = await get_discovery_info(self.mass.http_session, ip)
except ClientError as err:
self.logger.debug(
"Ignoring %s (manual IP) as it is not reachable: %s", ip, str(err)
)
continue
player_id = discovery_info["device"]["id"]
self.sonos_players[player_id] = sonos_player = SonosPlayer(
self, player_id, discovery_info=discovery_info, ip_address=ip
)
await sonos_player.setup()

async def unload(self, is_removed: bool = False) -> None:
"""Handle close/cleanup of the provider."""
# disconnect all players
Expand Down