diff --git a/homeassistant/components/songpal/const.py b/homeassistant/components/songpal/const.py index f12b77800a9..496618f35f0 100644 --- a/homeassistant/components/songpal/const.py +++ b/homeassistant/components/songpal/const.py @@ -3,3 +3,5 @@ DOMAIN = "songpal" SET_SOUND_SETTING = "set_sound_setting" CONF_ENDPOINT = "endpoint" + +ERROR_REQUEST_RETRY = 40000 diff --git a/homeassistant/components/songpal/media_player.py b/homeassistant/components/songpal/media_player.py index 6b7cf73b28b..0d41aec699b 100644 --- a/homeassistant/components/songpal/media_player.py +++ b/homeassistant/components/songpal/media_player.py @@ -34,7 +34,7 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .const import CONF_ENDPOINT, DOMAIN, SET_SOUND_SETTING +from .const import CONF_ENDPOINT, DOMAIN, ERROR_REQUEST_RETRY, SET_SOUND_SETTING _LOGGER = logging.getLogger(__name__) @@ -332,11 +332,27 @@ class SongpalEntity(MediaPlayerEntity): async def async_turn_on(self) -> None: """Turn the device on.""" - return await self._dev.set_power(True) + try: + return await self._dev.set_power(True) + except SongpalException as ex: + if ex.code == ERROR_REQUEST_RETRY: + _LOGGER.debug( + "Swallowing %s, the device might be already in the wanted state", ex + ) + return + raise async def async_turn_off(self) -> None: """Turn the device off.""" - return await self._dev.set_power(False) + try: + return await self._dev.set_power(False) + except SongpalException as ex: + if ex.code == ERROR_REQUEST_RETRY: + _LOGGER.debug( + "Swallowing %s, the device might be already in the wanted state", ex + ) + return + raise async def async_mute_volume(self, mute: bool) -> None: """Mute or unmute the device.""" diff --git a/tests/components/songpal/test_media_player.py b/tests/components/songpal/test_media_player.py index 86a9456fa8b..d5e89e887d1 100644 --- a/tests/components/songpal/test_media_player.py +++ b/tests/components/songpal/test_media_player.py @@ -14,7 +14,10 @@ from songpal import ( from homeassistant.components import media_player, songpal from homeassistant.components.media_player import MediaPlayerEntityFeature -from homeassistant.components.songpal.const import SET_SOUND_SETTING +from homeassistant.components.songpal.const import ( + ERROR_REQUEST_RETRY, + SET_SOUND_SETTING, +) from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -53,6 +56,15 @@ def _get_attributes(hass): return state.as_dict()["attributes"] +async def _call(hass, service, **argv): + await hass.services.async_call( + media_player.DOMAIN, + service, + {"entity_id": ENTITY_ID, **argv}, + blocking=True, + ) + + async def test_setup_platform(hass: HomeAssistant) -> None: """Test the legacy setup platform.""" mocked_device = _create_mocked_device(throw_exception=True) @@ -222,32 +234,24 @@ async def test_services(hass: HomeAssistant) -> None: await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - async def _call(service, **argv): - await hass.services.async_call( - media_player.DOMAIN, - service, - {"entity_id": ENTITY_ID, **argv}, - blocking=True, - ) - - await _call(media_player.SERVICE_TURN_ON) - await _call(media_player.SERVICE_TURN_OFF) - await _call(media_player.SERVICE_TOGGLE) + await _call(hass, media_player.SERVICE_TURN_ON) + await _call(hass, media_player.SERVICE_TURN_OFF) + await _call(hass, media_player.SERVICE_TOGGLE) assert mocked_device.set_power.call_count == 3 mocked_device.set_power.assert_has_calls([call(True), call(False), call(False)]) - await _call(media_player.SERVICE_VOLUME_SET, volume_level=0.6) - await _call(media_player.SERVICE_VOLUME_UP) - await _call(media_player.SERVICE_VOLUME_DOWN) + await _call(hass, media_player.SERVICE_VOLUME_SET, volume_level=0.6) + await _call(hass, media_player.SERVICE_VOLUME_UP) + await _call(hass, media_player.SERVICE_VOLUME_DOWN) assert mocked_device.volume1.set_volume.call_count == 3 mocked_device.volume1.set_volume.assert_has_calls([call(60), call(51), call(49)]) - await _call(media_player.SERVICE_VOLUME_MUTE, is_volume_muted=True) + await _call(hass, media_player.SERVICE_VOLUME_MUTE, is_volume_muted=True) mocked_device.volume1.set_mute.assert_called_once_with(True) - await _call(media_player.SERVICE_SELECT_SOURCE, source="none") + await _call(hass, media_player.SERVICE_SELECT_SOURCE, source="none") mocked_device.input1.activate.assert_not_called() - await _call(media_player.SERVICE_SELECT_SOURCE, source="title1") + await _call(hass, media_player.SERVICE_SELECT_SOURCE, source="title1") mocked_device.input1.activate.assert_called_once() await hass.services.async_call( @@ -366,3 +370,33 @@ async def test_disconnected( assert warning_records[0].message.endswith("Got disconnected, trying to reconnect") assert warning_records[1].message.endswith("Connection reestablished") assert not any(x.levelno == logging.ERROR for x in caplog.records) + + +@pytest.mark.parametrize( + "service", [media_player.SERVICE_TURN_ON, media_player.SERVICE_TURN_OFF] +) +@pytest.mark.parametrize( + ("error_code", "swallow"), [(ERROR_REQUEST_RETRY, True), (1234, False)] +) +async def test_error_swallowing(hass, caplog, service, error_code, swallow): + """Test swallowing specific errors on turn_on and turn_off.""" + mocked_device = _create_mocked_device() + 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() + + type(mocked_device).set_power = AsyncMock( + side_effect=[ + SongpalException("Error to swallow", error=(error_code, "Error to swallow")) + ] + ) + + if swallow: + await _call(hass, service) + assert "Swallowing" in caplog.text + else: + with pytest.raises(SongpalException): + await _call(hass, service)