From 35b23b84292f3fd06f4d02e50d6299d1d0a876d4 Mon Sep 17 00:00:00 2001 From: pwt <1089749+pwt@users.noreply.github.com> Date: Sun, 27 Dec 2020 12:48:36 +0000 Subject: [PATCH] Add 'trueplay' property to SoCo (#775) --- soco/core.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_core.py | 46 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/soco/core.py b/soco/core.py index c65a8882a..14c7880eb 100755 --- a/soco/core.py +++ b/soco/core.py @@ -201,6 +201,7 @@ class SoCo(_SocoSingletonBase): dialog_mode supports_fixed_volume fixed_volume + trueplay status_light buttons_enabled @@ -960,6 +961,52 @@ def dialog_mode(self, dialog_mode): ] ) + @property + def trueplay(self): + """bool: Whether Trueplay is enabled on this device. + True if on, False if off. + + Devices that do not support Trueplay, or which do not have + a current Trueplay calibration, will return `None` on getting + the property, and raise a `NotSupportedException` when + setting the property. + + Can only be set on visible devices. Attempting to set on non-visible + devices will raise a `SoCoNotVisibleException`. + """ + response = self.renderingControl.GetRoomCalibrationStatus([("InstanceID", 0)]) + if response["RoomCalibrationAvailable"] == "0": + return None + else: + return response["RoomCalibrationEnabled"] == "1" + + @trueplay.setter + def trueplay(self, trueplay): + """Toggle the device's TruePlay setting. Only available to + Sonos speakers, not the Connect, Amp, etc., and only available to + speakers that have a current Trueplay calibration. + + :param trueplay: Enable or disable Trueplay. + :type trueplay: bool + :raises NotSupportedException: If the device does not support + Trueplay or doesn't have a current calibration. + :raises SoCoNotVisibleException: If the device is not visible. + """ + response = self.renderingControl.GetRoomCalibrationStatus([("InstanceID", 0)]) + if response["RoomCalibrationAvailable"] == "0": + raise NotSupportedException + + if not self.is_visible: + raise SoCoNotVisibleException + + trueplay_value = "1" if trueplay else "0" + self.renderingControl.SetRoomCalibrationStatus( + [ + ("InstanceID", 0), + ("RoomCalibrationEnabled", trueplay_value), + ] + ) + @property def supports_fixed_volume(self): """bool: Whether the device supports fixed volume output.""" diff --git a/tests/test_core.py b/tests/test_core.py index c73fe392e..d3436ada8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -6,14 +6,12 @@ from soco import SoCo from soco.data_structures import DidlMusicTrack, to_didl_string - from soco.exceptions import ( SoCoSlaveException, SoCoUPnPException, SoCoNotVisibleException, NotSupportedException, ) - from soco.groups import ZoneGroup from soco.xml import XML @@ -1155,6 +1153,49 @@ def test_soco_loudness(self, moco): [("InstanceID", 0), ("Channel", "Master"), ("DesiredLoudness", "0")] ) + def test_soco_trueplay(self, moco): + moco.renderingControl.GetRoomCalibrationStatus.return_value = { + "RoomCalibrationAvailable": "0", + "RoomCalibrationEnabled": "0", + } + assert moco.trueplay is None + moco.renderingControl.GetRoomCalibrationStatus.assert_called_with( + [("InstanceID", 0)] + ) + moco.renderingControl.GetRoomCalibrationStatus.return_value = { + "RoomCalibrationAvailable": "1", + "RoomCalibrationEnabled": "1", + } + assert moco.trueplay + moco.renderingControl.GetRoomCalibrationStatus.assert_called_with( + [("InstanceID", 0)] + ) + # Setter tests for 'is_visible' property, so this needs to be + # mocked. + with mock.patch( + "soco.SoCo.is_visible", new_callable=mock.PropertyMock + ) as mock_is_visible: + mock_is_visible.return_value = True + moco.trueplay = False + moco.renderingControl.SetRoomCalibrationStatus.assert_called_with( + [ + ("InstanceID", 0), + ("RoomCalibrationEnabled", "0"), + ] + ) + moco.trueplay = True + moco.renderingControl.SetRoomCalibrationStatus.assert_called_with( + [ + ("InstanceID", 0), + ("RoomCalibrationEnabled", "1"), + ] + ) + # Check for exception if attempt to set the property on a + # non-visible speaker. + mock_is_visible.return_value = False + with pytest.raises(SoCoNotVisibleException): + moco.trueplay = True + def test_soco_fixed_volume(self, moco): moco.renderingControl.GetSupportsOutputFixed.return_value = { "CurrentSupportsFixed": "1" @@ -1170,7 +1211,6 @@ def test_soco_fixed_volume(self, moco): moco.renderingControl.GetSupportsOutputFixed.assert_called_with( [("InstanceID", 0)] ) - moco.renderingControl.GetOutputFixed.return_value = {"CurrentFixed": "1"} assert moco.fixed_volume moco.renderingControl.GetOutputFixed.assert_called_once_with(