Fix for Bluesound multizone devices (#2511)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 16 Oct 2025 23:09:44 +0000 (01:09 +0200)
committerGitHub <noreply@github.com>
Thu, 16 Oct 2025 23:09:44 +0000 (01:09 +0200)
music_assistant/helpers/util.py
music_assistant/providers/bluesound/provider.py
music_assistant/providers/filesystem_smb/__init__.py
pyproject.toml
requirements_all.txt

index 6eb9111ac2022b8f0830fbcb3d0cce1f2cb7959a..2a373e35388c16e6ca861ce69a93a0d17e6957bd 100644 (file)
@@ -670,6 +670,13 @@ def validate_announcement_chime_url(url: str) -> bool:
         return False
 
 
+async def get_mac_address(ip_address: str) -> str | None:
+    """Get MAC address for given IP address."""
+    from getmac import get_mac_address  # noqa: PLC0415
+
+    return await asyncio.to_thread(get_mac_address, ip=ip_address)
+
+
 class TaskManager:
     """
     Helper class to run many tasks at once.
index 00abdf098f1b936e51002c09bc2de6f18147ea74..22fdb57417002785310044faffcf840b19df6c84 100644 (file)
@@ -2,15 +2,17 @@
 
 from __future__ import annotations
 
-from typing import TYPE_CHECKING, TypedDict
+from typing import TYPE_CHECKING, TypedDict, cast
 
 from zeroconf import ServiceStateChange
 
 from music_assistant.helpers.util import (
+    get_mac_address,
     get_port_from_zeroconf,
     get_primary_ip_address_from_zeroconf,
 )
 from music_assistant.models.player_provider import PlayerProvider
+from music_assistant.providers.bluesound.const import MUSP_MDNS_TYPE
 
 from .player import BluesoundPlayer
 
@@ -46,17 +48,33 @@ class BluesoundPlayerProvider(PlayerProvider):
             return
         name = name.split(".", 1)[0]
         assert info is not None
-        player_id = info.decoded_properties["mac"]
-        assert player_id is not None
 
         ip_address = get_primary_ip_address_from_zeroconf(info)
         port = get_port_from_zeroconf(info)
 
-        assert ip_address is not None
-        assert port is not None
+        if not ip_address or not port:
+            self.logger.debug("Ignoring incomplete mdns discovery for Bluesound player: %s", name)
+            return
+
+        if info.type == MUSP_MDNS_TYPE:
+            # this is a multi-zone device, we need to fetch the mac address of the main device
+            mac_address = await get_mac_address(ip_address)
+            player_id = f"{mac_address}:{port}"
+        else:
+            mac_address = info.decoded_properties.get("mac")
+            player_id = mac_address
+
+        if not mac_address:
+            self.logger.debug(
+                "Ignoring mdns discovery for Bluesound player without MAC address: %s",
+                name,
+            )
+            return
 
         # Handle update of existing player
+        assert player_id is not None  # for type checker
         if bluos_player := self.mass.players.get(player_id):
+            bluos_player = cast("BluesoundPlayer", bluos_player)
             # Check if the IP address has changed
             if ip_address and ip_address != bluos_player.ip_address:
                 self.logger.debug(
@@ -76,7 +94,7 @@ class BluesoundPlayerProvider(PlayerProvider):
             _objectType=info.decoded_properties.get("_objectType", ""),
             ip_address=ip_address,
             port=str(port),
-            mac=info.decoded_properties["mac"],
+            mac=mac_address,
             model=info.decoded_properties.get("model", ""),
             zs=info.decoded_properties.get("zs", False),
         )
index 240a9a85d908bc5a1d85019f5b0ae92df3e1d545..6c73b370487f1c1e792d65938547e045107d8854 100644 (file)
@@ -121,7 +121,7 @@ async def get_config_entries(
             label="Mount options",
             required=False,
             category="advanced",
-            default_value="noserverino,file_mode=0775,dir_mode=0775,uid=0,gid=0",
+            default_value="noserverino,file_mode=0775,dir_mode=0775,uid=0,gid=0,iocharset=utf8",
             description="[optional] Any additional mount options you "
             "want to pass to the mount command if needed for your particular setup.",
         ),
index 2c823f603c8cabab4ec22a88df7626699e3adef1..b673d1d656db69349e9b30e237937a13b8550d3c 100644 (file)
@@ -23,6 +23,7 @@ dependencies = [
   "cryptography==46.0.2",
   "chardet>=5.2.0",
   "ifaddr==0.2.0",
+  "get-mac==0.9.2",
   "mashumaro==3.16",
   "music-assistant-frontend==2.16.6",
   "music-assistant-models==1.1.62",
index d1acb9564f5a84e92477479bdf2814e8b192cd79..39c11e99304dafc131fb5e191474779b7eabce37 100644 (file)
@@ -25,6 +25,7 @@ cryptography==46.0.2
 deezer-python-async==0.3.0
 defusedxml==0.7.1
 duration-parser==1.0.1
+get-mac==0.9.2
 gql[all]==4.0.0
 hass-client==1.2.0
 ibroadcastaio==0.4.0