diff --git a/homeassistant/components/sonos/number.py b/homeassistant/components/sonos/number.py index 375ed58035b2..c74c5933ecf3 100644 --- a/homeassistant/components/sonos/number.py +++ b/homeassistant/components/sonos/number.py @@ -21,6 +21,7 @@ LEVEL_TYPES = { "bass": (-10, 10), "balance": (-100, 100), "treble": (-10, 10), + "sub_crossover": (50, 110), "sub_gain": (-15, 15), "surround_level": (-15, 15), "music_surround_level": (-15, 15), diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index b73ca6a77e4d..fea5b5de7deb 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -154,6 +154,7 @@ class SonosSpeaker: self.dialog_level: bool | None = None self.night_mode: bool | None = None self.sub_enabled: bool | None = None + self.sub_crossover: int | None = None self.sub_gain: int | None = None self.surround_enabled: bool | None = None self.surround_mode: bool | None = None @@ -561,6 +562,7 @@ class SonosSpeaker: "audio_delay", "bass", "treble", + "sub_crossover", "sub_gain", "surround_level", "music_surround_level", diff --git a/homeassistant/components/sonos/strings.json b/homeassistant/components/sonos/strings.json index fb10167f1d0b..6f45195c46be 100644 --- a/homeassistant/components/sonos/strings.json +++ b/homeassistant/components/sonos/strings.json @@ -36,6 +36,9 @@ "treble": { "name": "Treble" }, + "sub_crossover": { + "name": "Sub crossover frequency" + }, "sub_gain": { "name": "Sub gain" }, diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 648ca12803cf..8bd8224e7260 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -108,8 +108,26 @@ def config_entry_fixture(): class MockSoCo(MagicMock): """Mock the Soco Object.""" + uid = "RINCON_test" + play_mode = "NORMAL" + mute = False + night_mode = True + dialog_level = True + loudness = True + volume = 19 audio_delay = 2 + balance = (61, 100) + bass = 1 + treble = -1 + mic_enabled = False + sub_crossover = None # Default to None for non-Amp devices + sub_enabled = False sub_gain = 5 + surround_enabled = True + surround_mode = True + surround_level = 3 + music_surround_level = 4 + soundbar_audio_input_format = "Dolby 5.1" @property def visible_zones(self): @@ -143,10 +161,7 @@ class SoCoMockFactory: mock_soco.mock_add_spec(SoCo) mock_soco.ip_address = ip_address if ip_address != "192.168.42.2": - mock_soco.uid = f"RINCON_test_{ip_address}" - else: - mock_soco.uid = "RINCON_test" - mock_soco.play_mode = "NORMAL" + mock_soco.uid += f"_{ip_address}" mock_soco.music_library = self.music_library mock_soco.get_current_track_info.return_value = self.current_track_info mock_soco.music_source_from_uri = SoCo.music_source_from_uri @@ -161,23 +176,6 @@ class SoCoMockFactory: mock_soco.contentDirectory = SonosMockService("ContentDirectory", ip_address) mock_soco.deviceProperties = SonosMockService("DeviceProperties", ip_address) mock_soco.alarmClock = self.alarm_clock - mock_soco.mute = False - mock_soco.night_mode = True - mock_soco.dialog_level = True - mock_soco.loudness = True - mock_soco.volume = 19 - mock_soco.audio_delay = 2 - mock_soco.balance = (61, 100) - mock_soco.bass = 1 - mock_soco.treble = -1 - mock_soco.mic_enabled = False - mock_soco.sub_enabled = False - mock_soco.sub_gain = 5 - mock_soco.surround_enabled = True - mock_soco.surround_mode = True - mock_soco.surround_level = 3 - mock_soco.music_surround_level = 4 - mock_soco.soundbar_audio_input_format = "Dolby 5.1" mock_soco.get_battery_info.return_value = self.battery_info mock_soco.all_zones = {mock_soco} mock_soco.group.coordinator = mock_soco diff --git a/tests/components/sonos/test_number.py b/tests/components/sonos/test_number.py index 38456058d8a1..d58b84ab6cb0 100644 --- a/tests/components/sonos/test_number.py +++ b/tests/components/sonos/test_number.py @@ -6,6 +6,8 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +CROSSOVER_ENTITY = "number.zone_a_sub_crossover_frequency" + async def test_number_entities( hass: HomeAssistant, async_autosetup_sonos, soco, entity_registry: er.EntityRegistry @@ -62,3 +64,32 @@ async def test_number_entities( blocking=True, ) mock_sub_gain.assert_called_once_with(-8) + + # sub_crossover is only available on Sonos Amp devices, see test_amp_number_entities + assert CROSSOVER_ENTITY not in entity_registry.entities + + +async def test_amp_number_entities( + hass: HomeAssistant, async_setup_sonos, soco, entity_registry: er.EntityRegistry +) -> None: + """Test the sub_crossover feature only available on Sonos Amp devices. + + The sub_crossover value will be None on all other device types. + """ + with patch.object(soco, "sub_crossover", 50): + await async_setup_sonos() + + sub_crossover_number = entity_registry.entities[CROSSOVER_ENTITY] + sub_crossover_state = hass.states.get(sub_crossover_number.entity_id) + assert sub_crossover_state.state == "50" + + with patch.object( + type(soco), "sub_crossover", new_callable=PropertyMock + ) as mock_sub_crossover: + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: sub_crossover_number.entity_id, "value": 110}, + blocking=True, + ) + mock_sub_crossover.assert_called_once_with(110)