1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-20 19:09:45 +00:00

Fix LG webOS TV entity availability status (#155164)

This commit is contained in:
Shay Levy
2025-10-26 07:20:24 +02:00
committed by GitHub
parent 6fa73f7f6a
commit 07b6358fff
3 changed files with 115 additions and 18 deletions

View File

@@ -162,6 +162,7 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
self._entry = entry self._entry = entry
self._client = entry.runtime_data self._client = entry.runtime_data
self._attr_assumed_state = True self._attr_assumed_state = True
self._unavailable_logged = False
self._device_name = entry.title self._device_name = entry.title
self._attr_unique_id = entry.unique_id self._attr_unique_id = entry.unique_id
self._sources = entry.options.get(CONF_SOURCES) self._sources = entry.options.get(CONF_SOURCES)
@@ -348,19 +349,31 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
): ):
self._source_list["Live TV"] = app self._source_list["Live TV"] = app
def _set_availability(self, available: bool) -> None:
"""Set availability and log changes only once."""
self._attr_available = available
if not available and not self._unavailable_logged:
_LOGGER.info("LG webOS TV entity %s is unavailable", self.entity_id)
self._unavailable_logged = True
elif available and self._unavailable_logged:
_LOGGER.info("LG webOS TV entity %s is back online", self.entity_id)
self._unavailable_logged = False
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
async def async_update(self) -> None: async def async_update(self) -> None:
"""Connect.""" """Connect."""
if self._client.is_connected(): if self._client.is_connected():
return return
with suppress(*WEBOSTV_EXCEPTIONS): try:
try: await self._client.connect()
await self._client.connect() except WEBOSTV_EXCEPTIONS:
except WebOsTvPairError: self._set_availability(bool(self._turn_on))
self._entry.async_start_reauth(self.hass) except WebOsTvPairError:
else: self._entry.async_start_reauth(self.hass)
update_client_key(self.hass, self._entry) else:
self._set_availability(True)
update_client_key(self.hass, self._entry)
@property @property
def supported_features(self) -> MediaPlayerEntityFeature: def supported_features(self) -> MediaPlayerEntityFeature:

View File

@@ -26,9 +26,9 @@ rules:
config-entry-unloading: done config-entry-unloading: done
docs-configuration-parameters: done docs-configuration-parameters: done
docs-installation-parameters: done docs-installation-parameters: done
entity-unavailable: todo entity-unavailable: done
integration-owner: done integration-owner: done
log-when-unavailable: todo log-when-unavailable: done
parallel-updates: done parallel-updates: done
reauthentication-flow: done reauthentication-flow: done
test-coverage: done test-coverage: done

View File

@@ -59,6 +59,7 @@ from homeassistant.const import (
SERVICE_VOLUME_SET, SERVICE_VOLUME_SET,
SERVICE_VOLUME_UP, SERVICE_VOLUME_UP,
STATE_OFF, STATE_OFF,
STATE_UNAVAILABLE,
) )
from homeassistant.core import HomeAssistant, State from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@@ -73,6 +74,16 @@ from tests.test_util.aiohttp import AiohttpClientMocker
from tests.typing import ClientSessionGenerator from tests.typing import ClientSessionGenerator
async def mock_scan_interval(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None:
"""Mock update interval to force an update."""
freezer.tick(timedelta(seconds=11))
async_fire_time_changed(hass)
await hass.async_block_till_done()
@pytest.mark.parametrize( @pytest.mark.parametrize(
("service", "attr_data", "client_call"), ("service", "attr_data", "client_call"),
[ [
@@ -488,9 +499,7 @@ async def test_client_disconnected(
client.is_connected.return_value = False client.is_connected.return_value = False
client.connect.side_effect = TimeoutError client.connect.side_effect = TimeoutError
freezer.tick(timedelta(seconds=20)) await mock_scan_interval(hass, freezer)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert "TimeoutError" not in caplog.text assert "TimeoutError" not in caplog.text
@@ -506,9 +515,7 @@ async def test_client_key_update_on_connect(
client.is_connected.return_value = False client.is_connected.return_value = False
client.client_key = "new_key" client.client_key = "new_key"
freezer.tick(timedelta(seconds=20)) await mock_scan_interval(hass, freezer)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert config_entry.data[CONF_CLIENT_SECRET] == client.client_key assert config_entry.data[CONF_CLIENT_SECRET] == client.client_key
@@ -849,9 +856,7 @@ async def test_reauth_reconnect(
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
freezer.tick(timedelta(seconds=20)) await mock_scan_interval(hass, freezer)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
@@ -886,3 +891,82 @@ async def test_update_media_state(hass: HomeAssistant, client) -> None:
client.tv_state.is_on = False client.tv_state.is_on = False
await client.mock_state_update() await client.mock_state_update()
assert hass.states.get(ENTITY_ID).state == STATE_OFF assert hass.states.get(ENTITY_ID).state == STATE_OFF
async def test_availability(
hass: HomeAssistant,
client,
caplog: pytest.LogCaptureFixture,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test that availability status changes are set and logged correctly."""
await setup_webostv(hass)
# Initially available
assert hass.states.get(ENTITY_ID).state == MediaPlayerState.ON
# Make the entity go offline - should log unavailable message
client.connect.side_effect = TimeoutError
client.is_connected.return_value = False
await mock_scan_interval(hass, freezer)
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
unavailable_log = f"LG webOS TV entity {ENTITY_ID} is unavailable"
assert unavailable_log in caplog.text
# Clear logs and update the offline entity again - should NOT log again
caplog.clear()
await mock_scan_interval(hass, freezer)
assert unavailable_log not in caplog.text
# Bring the entity back online - should log back online message
client.connect.side_effect = None
await mock_scan_interval(hass, freezer)
assert hass.states.get(ENTITY_ID).state == MediaPlayerState.ON
available_log = f"LG webOS TV entity {ENTITY_ID} is back online"
assert available_log in caplog.text
# Clear logs and make update again - should NOT log again
caplog.clear()
await mock_scan_interval(hass, freezer)
assert hass.states.get(ENTITY_ID).state == MediaPlayerState.ON
assert available_log not in caplog.text
# Test offline again to ensure the flag resets properly
client.connect.side_effect = TimeoutError
await mock_scan_interval(hass, freezer)
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
assert unavailable_log in caplog.text
# Test entity that supports turn on are considered available
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "webostv.turn_on",
"entity_id": ENTITY_ID,
},
"action": {
"service": "test.automation",
"data_template": {
"some": ENTITY_ID,
"id": "{{ trigger.id }}",
},
},
},
],
},
)
await mock_scan_interval(hass, freezer)
assert hass.states.get(ENTITY_ID).state == MediaPlayerState.ON
available_log = f"LG webOS TV entity {ENTITY_ID} is back online"
assert available_log in caplog.text