Skip to content

Commit

Permalink
songpal: fully support wireless-only soundbars identifiers.
Browse files Browse the repository at this point in the history
As shown in #64868, a number of newer models don't come wiht a macAddr
attributes, so for those fall back to the wireless address.

This could be hidden by the python-songpal library but for now this will
make it possible to have multiple modern songpal devices on the same
network.
  • Loading branch information
Flameeyes committed Feb 5, 2022
1 parent f3c5f9c commit 966ef5f
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 11 deletions.
9 changes: 7 additions & 2 deletions homeassistant/components/songpal/media_player.py
Expand Up @@ -212,13 +212,18 @@ def name(self):
@property
def unique_id(self):
"""Return a unique ID."""
return self._sysinfo.macAddr
return self._sysinfo.macAddr or self._sysinfo.wirelessMacAddr

@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
connections = set()
if self._sysinfo.macAddr:
connections.add((dr.CONNECTION_NETWORK_MAC, self._sysinfo.macAddr))
if self._sysinfo.wirelessMacAddr:
connections.add((dr.CONNECTION_NETWORK_MAC, self._sysinfo.wirelessMacAddr))
return DeviceInfo(
connections={(dr.CONNECTION_NETWORK_MAC, self._sysinfo.macAddr)},
connections=connections,
identifiers={(DOMAIN, self.unique_id)},
manufacturer="Sony Corporation",
model=self._model,
Expand Down
16 changes: 12 additions & 4 deletions tests/components/songpal/__init__.py
Expand Up @@ -2,6 +2,7 @@
from unittest.mock import AsyncMock, MagicMock, patch

from songpal import SongpalException
from songpal.containers import Sysinfo

from homeassistant.components.songpal.const import CONF_ENDPOINT
from homeassistant.const import CONF_NAME
Expand All @@ -12,6 +13,7 @@
ENDPOINT = f"http://{HOST}:10000/sony"
MODEL = "model"
MAC = "mac"
WIRELESS_MAC = "wmac"
SW_VERSION = "sw_ver"

CONF_DATA = {
Expand All @@ -20,7 +22,7 @@
}


def _create_mocked_device(throw_exception=False):
def _create_mocked_device(throw_exception=False, wired_mac=MAC, wireless_mac=None):
mocked_device = MagicMock()

type(mocked_device).get_supported_methods = AsyncMock(
Expand All @@ -35,9 +37,15 @@ def _create_mocked_device(throw_exception=False):
return_value=interface_info
)

sys_info = MagicMock()
sys_info.macAddr = MAC
sys_info.version = SW_VERSION
sys_info = Sysinfo(
bdAddr=None,
macAddr=wired_mac,
wirelessMacAddr=wireless_mac,
bssid=None,
ssid=None,
bleID=None,
version=SW_VERSION,
)
type(mocked_device).get_system_info = AsyncMock(return_value=sys_info)

volume1 = MagicMock()
Expand Down
100 changes: 95 additions & 5 deletions tests/components/songpal/test_media_player.py
Expand Up @@ -29,6 +29,7 @@
MAC,
MODEL,
SW_VERSION,
WIRELESS_MAC,
_create_mocked_device,
_patch_media_player_device,
)
Expand Down Expand Up @@ -126,6 +127,78 @@ async def test_state(hass):
assert entity.unique_id == MAC


async def test_state_wireless(hass):
"""Test state of the entity with only Wireless MAC."""
mocked_device = _create_mocked_device(wired_mac=None, wireless_mac=WIRELESS_MAC)
entry = MockConfigEntry(domain=songpal.DOMAIN, data=CONF_DATA)
entry.add_to_hass(hass)

with _patch_media_player_device(mocked_device):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()

state = hass.states.get(ENTITY_ID)
assert state.name == FRIENDLY_NAME
assert state.state == STATE_ON
attributes = state.as_dict()["attributes"]
assert attributes["volume_level"] == 0.5
assert attributes["is_volume_muted"] is False
assert attributes["source_list"] == ["title1", "title2"]
assert attributes["source"] == "title2"
assert attributes["supported_features"] == SUPPORT_SONGPAL

device_registry = dr.async_get(hass)
device = device_registry.async_get_device(
identifiers={(songpal.DOMAIN, WIRELESS_MAC)}
)
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, WIRELESS_MAC)}
assert device.manufacturer == "Sony Corporation"
assert device.name == FRIENDLY_NAME
assert device.sw_version == SW_VERSION
assert device.model == MODEL

entity_registry = er.async_get(hass)
entity = entity_registry.async_get(ENTITY_ID)
assert entity.unique_id == WIRELESS_MAC


async def test_state_both(hass):
"""Test state of the entity with both Wired and Wireless MAC."""
mocked_device = _create_mocked_device(wired_mac=MAC, wireless_mac=WIRELESS_MAC)
entry = MockConfigEntry(domain=songpal.DOMAIN, data=CONF_DATA)
entry.add_to_hass(hass)

with _patch_media_player_device(mocked_device):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()

state = hass.states.get(ENTITY_ID)
assert state.name == FRIENDLY_NAME
assert state.state == STATE_ON
attributes = state.as_dict()["attributes"]
assert attributes["volume_level"] == 0.5
assert attributes["is_volume_muted"] is False
assert attributes["source_list"] == ["title1", "title2"]
assert attributes["source"] == "title2"
assert attributes["supported_features"] == SUPPORT_SONGPAL

device_registry = dr.async_get(hass)
device = device_registry.async_get_device(identifiers={(songpal.DOMAIN, MAC)})
assert device.connections == {
(dr.CONNECTION_NETWORK_MAC, MAC),
(dr.CONNECTION_NETWORK_MAC, WIRELESS_MAC),
}
assert device.manufacturer == "Sony Corporation"
assert device.name == FRIENDLY_NAME
assert device.sw_version == SW_VERSION
assert device.model == MODEL

entity_registry = er.async_get(hass)
entity = entity_registry.async_get(ENTITY_ID)
# We prefer the wired mac if present.
assert entity.unique_id == MAC


async def test_services(hass):
"""Test services."""
mocked_device = _create_mocked_device()
Expand Down Expand Up @@ -173,11 +246,7 @@ async def _call(service, **argv):
mocked_device.set_sound_settings.assert_called_once_with("name", "value")
mocked_device.set_sound_settings.reset_mock()

mocked_device2 = _create_mocked_device()
sys_info = MagicMock()
sys_info.macAddr = "mac2"
sys_info.version = SW_VERSION
type(mocked_device2).get_system_info = AsyncMock(return_value=sys_info)
mocked_device2 = _create_mocked_device(wired_mac="mac2")
entry2 = MockConfigEntry(
domain=songpal.DOMAIN, data={CONF_NAME: "d2", CONF_ENDPOINT: ENDPOINT}
)
Expand All @@ -194,6 +263,27 @@ async def _call(service, **argv):
)
mocked_device.set_sound_settings.assert_called_once_with("name", "value")
mocked_device2.set_sound_settings.assert_called_once_with("name", "value")
mocked_device.set_sound_settings.reset_mock()
mocked_device2.set_sound_settings.reset_mock()

mocked_device3 = _create_mocked_device(wired_mac=None, wireless_mac=WIRELESS_MAC)
entry3 = MockConfigEntry(
domain=songpal.DOMAIN, data={CONF_NAME: "d2", CONF_ENDPOINT: ENDPOINT}
)
entry3.add_to_hass(hass)
with _patch_media_player_device(mocked_device3):
await hass.config_entries.async_setup(entry3.entry_id)
await hass.async_block_till_done()

await hass.services.async_call(
songpal.DOMAIN,
SET_SOUND_SETTING,
{"entity_id": "all", "name": "name", "value": "value"},
blocking=True,
)
mocked_device.set_sound_settings.assert_called_once_with("name", "value")
mocked_device2.set_sound_settings.assert_called_once_with("name", "value")
mocked_device3.set_sound_settings.assert_called_once_with("name", "value")


async def test_websocket_events(hass):
Expand Down

0 comments on commit 966ef5f

Please sign in to comment.