From 73df6469badb21d34bc17d40f24f5ce1aed089a8 Mon Sep 17 00:00:00 2001 From: Derek Gordon Date: Sun, 23 Aug 2026 12:50:01 +0000 Subject: [PATCH 1/2] Downgrade getMetaInfo warning to debug for expected non-JSON responses WiiM devices return "Failed" or an empty body (HTTP 200) from the getMetaInfo endpoint when nothing is playing. Both trigger a JSONDecodeError, and session_call_api_json logs a WARNING for every occurrence. On a 5-second poll cycle this produces hundreds of warning lines per day in Home Assistant logs. The exception handler in bridge.py already treats "Failed" as a non-error, but it only ran after the warning was already logged. Empty responses were not handled at all and re-raised. Log at DEBUG instead of WARNING when the raw response is empty or "Failed". Broaden the bridge.py catch so empty responses are also treated as a silent non-error. Closes #128 --- src/linkplay/bridge.py | 3 ++- src/linkplay/utils.py | 6 +++++- tests/linkplay/test_bridge.py | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/linkplay/bridge.py b/src/linkplay/bridge.py index 0f6e5f3..a10d2d6 100644 --- a/src/linkplay/bridge.py +++ b/src/linkplay/bridge.py @@ -159,7 +159,8 @@ async def update_status(self) -> None: MetaInfo, dict[MetaInfoMetaData, str] ] = await self.bridge.json_request(LinkPlayCommand.META_INFO) # type: ignore[assignment] except LinkPlayInvalidDataException as exc: - if getattr(exc, "data", None) == "Failed": + raw = getattr(exc, "data", None) or "" + if raw.strip() in ("", "Failed"): self.metainfo = {} else: raise diff --git a/src/linkplay/utils.py b/src/linkplay/utils.py index 8a22c91..ae3c0b2 100644 --- a/src/linkplay/utils.py +++ b/src/linkplay/utils.py @@ -97,7 +97,11 @@ async def session_call_api_json( return json.loads(result) # type: ignore except json.JSONDecodeError as jsonexc: url = API_ENDPOINT.format(endpoint, command) - LOGGER.warning("Unexpected json for %s: %s", url, jsonexc) + stripped = result.strip() if result else "" + if stripped in ("", "Failed"): + LOGGER.debug("Non-JSON response for %s: %r", url, stripped) + else: + LOGGER.warning("Unexpected json for %s: %s", url, jsonexc) raise LinkPlayInvalidDataException( message=f"Unexpected JSON ({result}) received from '{url}'", data=result ) from jsonexc diff --git a/tests/linkplay/test_bridge.py b/tests/linkplay/test_bridge.py index 100a8f1..737a00d 100644 --- a/tests/linkplay/test_bridge.py +++ b/tests/linkplay/test_bridge.py @@ -602,6 +602,32 @@ async def mock_session_call_api_json_side_effect(endpoint, session, command): mock_api.assert_called_with("http://1.2.3.4", None, LinkPlayCommand.META_INFO) +async def test_meta_info_empty_response_handling(): + """Test that the player handles an empty META_INFO response correctly.""" + + async def mock_session_call_api_json_side_effect(endpoint, session, command): + if command == LinkPlayCommand.META_INFO: + return "" + return "{}" + + with patch( + "linkplay.utils.session_call_api", + new=AsyncMock(side_effect=mock_session_call_api_json_side_effect), + ): + mock_bridge = LinkPlayBridge( + endpoint=LinkPlayApiEndpoint( + protocol="http", port=80, endpoint="1.2.3.4", session=None + ) + ) + mock_bridge.device = MagicMock() + mock_bridge.device.manufacturer = MANUFACTURER_WIIM + + player = LinkPlayPlayer(mock_bridge) + await player.update_status() + + assert player.metainfo == {} + + async def test_audio_output_control(): """Test that the player handles a audio output control correctly.""" From 773636cb99115be34bd3118504a3831d7d098b59 Mon Sep 17 00:00:00 2001 From: Derek Gordon Date: Sun, 23 Aug 2026 12:50:54 +0000 Subject: [PATCH 2/2] Use isinstance check instead of falsy coercion for exception data Only suppress LinkPlayInvalidDataException when exc.data is an actual string whose stripped value is empty or "Failed". Exceptions raised without a data field (data=None) now re-raise instead of being silently swallowed. --- src/linkplay/bridge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linkplay/bridge.py b/src/linkplay/bridge.py index a10d2d6..131ad60 100644 --- a/src/linkplay/bridge.py +++ b/src/linkplay/bridge.py @@ -159,8 +159,8 @@ async def update_status(self) -> None: MetaInfo, dict[MetaInfoMetaData, str] ] = await self.bridge.json_request(LinkPlayCommand.META_INFO) # type: ignore[assignment] except LinkPlayInvalidDataException as exc: - raw = getattr(exc, "data", None) or "" - if raw.strip() in ("", "Failed"): + raw = getattr(exc, "data", None) + if isinstance(raw, str) and raw.strip() in ("", "Failed"): self.metainfo = {} else: raise