Support songpal wireless-only soundbar identifiers (#65330)

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.
pull/65956/head
Diego Elio Pettenò 2022-02-06 21:33:58 +00:00 committed by GitHub
parent 63d3a47599
commit c6aa526469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 11 deletions

View File

@ -212,13 +212,18 @@ class SongpalEntity(MediaPlayerEntity):
@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,

View File

@ -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
@ -12,6 +13,7 @@ HOST = "0.0.0.0"
ENDPOINT = f"http://{HOST}:10000/sony"
MODEL = "model"
MAC = "mac"
WIRELESS_MAC = "wmac"
SW_VERSION = "sw_ver"
CONF_DATA = {
@ -20,7 +22,7 @@ CONF_DATA = {
}
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(
@ -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()

View File

@ -29,6 +29,7 @@ from . import (
MAC,
MODEL,
SW_VERSION,
WIRELESS_MAC,
_create_mocked_device,
_patch_media_player_device,
)
@ -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()
@ -173,11 +246,7 @@ async def test_services(hass):
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}
)
@ -194,6 +263,27 @@ async def test_services(hass):
)
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):