Skip to content

Commit 07b6358

Browse files
authored
Fix LG webOS TV entity availability status (home-assistant#155164)
1 parent 6fa73f7 commit 07b6358

File tree

3 files changed

+115
-18
lines changed

3 files changed

+115
-18
lines changed

homeassistant/components/webostv/media_player.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ def __init__(self, entry: WebOsTvConfigEntry) -> None:
162162
self._entry = entry
163163
self._client = entry.runtime_data
164164
self._attr_assumed_state = True
165+
self._unavailable_logged = False
165166
self._device_name = entry.title
166167
self._attr_unique_id = entry.unique_id
167168
self._sources = entry.options.get(CONF_SOURCES)
@@ -348,19 +349,31 @@ def _update_sources(self) -> None:
348349
):
349350
self._source_list["Live TV"] = app
350351

352+
def _set_availability(self, available: bool) -> None:
353+
"""Set availability and log changes only once."""
354+
self._attr_available = available
355+
if not available and not self._unavailable_logged:
356+
_LOGGER.info("LG webOS TV entity %s is unavailable", self.entity_id)
357+
self._unavailable_logged = True
358+
elif available and self._unavailable_logged:
359+
_LOGGER.info("LG webOS TV entity %s is back online", self.entity_id)
360+
self._unavailable_logged = False
361+
351362
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
352363
async def async_update(self) -> None:
353364
"""Connect."""
354365
if self._client.is_connected():
355366
return
356367

357-
with suppress(*WEBOSTV_EXCEPTIONS):
358-
try:
359-
await self._client.connect()
360-
except WebOsTvPairError:
361-
self._entry.async_start_reauth(self.hass)
362-
else:
363-
update_client_key(self.hass, self._entry)
368+
try:
369+
await self._client.connect()
370+
except WEBOSTV_EXCEPTIONS:
371+
self._set_availability(bool(self._turn_on))
372+
except WebOsTvPairError:
373+
self._entry.async_start_reauth(self.hass)
374+
else:
375+
self._set_availability(True)
376+
update_client_key(self.hass, self._entry)
364377

365378
@property
366379
def supported_features(self) -> MediaPlayerEntityFeature:

homeassistant/components/webostv/quality_scale.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ rules:
2626
config-entry-unloading: done
2727
docs-configuration-parameters: done
2828
docs-installation-parameters: done
29-
entity-unavailable: todo
29+
entity-unavailable: done
3030
integration-owner: done
31-
log-when-unavailable: todo
31+
log-when-unavailable: done
3232
parallel-updates: done
3333
reauthentication-flow: done
3434
test-coverage: done

tests/components/webostv/test_media_player.py

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
SERVICE_VOLUME_SET,
6060
SERVICE_VOLUME_UP,
6161
STATE_OFF,
62+
STATE_UNAVAILABLE,
6263
)
6364
from homeassistant.core import HomeAssistant, State
6465
from homeassistant.exceptions import HomeAssistantError
@@ -73,6 +74,16 @@
7374
from tests.typing import ClientSessionGenerator
7475

7576

77+
async def mock_scan_interval(
78+
hass: HomeAssistant,
79+
freezer: FrozenDateTimeFactory,
80+
) -> None:
81+
"""Mock update interval to force an update."""
82+
freezer.tick(timedelta(seconds=11))
83+
async_fire_time_changed(hass)
84+
await hass.async_block_till_done()
85+
86+
7687
@pytest.mark.parametrize(
7788
("service", "attr_data", "client_call"),
7889
[
@@ -488,9 +499,7 @@ async def test_client_disconnected(
488499
client.is_connected.return_value = False
489500
client.connect.side_effect = TimeoutError
490501

491-
freezer.tick(timedelta(seconds=20))
492-
async_fire_time_changed(hass)
493-
await hass.async_block_till_done()
502+
await mock_scan_interval(hass, freezer)
494503

495504
assert "TimeoutError" not in caplog.text
496505

@@ -506,9 +515,7 @@ async def test_client_key_update_on_connect(
506515
client.is_connected.return_value = False
507516
client.client_key = "new_key"
508517

509-
freezer.tick(timedelta(seconds=20))
510-
async_fire_time_changed(hass)
511-
await hass.async_block_till_done()
518+
await mock_scan_interval(hass, freezer)
512519

513520
assert config_entry.data[CONF_CLIENT_SECRET] == client.client_key
514521

@@ -849,9 +856,7 @@ async def test_reauth_reconnect(
849856

850857
assert entry.state is ConfigEntryState.LOADED
851858

852-
freezer.tick(timedelta(seconds=20))
853-
async_fire_time_changed(hass)
854-
await hass.async_block_till_done()
859+
await mock_scan_interval(hass, freezer)
855860

856861
assert entry.state is ConfigEntryState.LOADED
857862

@@ -886,3 +891,82 @@ async def test_update_media_state(hass: HomeAssistant, client) -> None:
886891
client.tv_state.is_on = False
887892
await client.mock_state_update()
888893
assert hass.states.get(ENTITY_ID).state == STATE_OFF
894+
895+
896+
async def test_availability(
897+
hass: HomeAssistant,
898+
client,
899+
caplog: pytest.LogCaptureFixture,
900+
freezer: FrozenDateTimeFactory,
901+
) -> None:
902+
"""Test that availability status changes are set and logged correctly."""
903+
await setup_webostv(hass)
904+
905+
# Initially available
906+
assert hass.states.get(ENTITY_ID).state == MediaPlayerState.ON
907+
908+
# Make the entity go offline - should log unavailable message
909+
client.connect.side_effect = TimeoutError
910+
client.is_connected.return_value = False
911+
await mock_scan_interval(hass, freezer)
912+
913+
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
914+
unavailable_log = f"LG webOS TV entity {ENTITY_ID} is unavailable"
915+
assert unavailable_log in caplog.text
916+
917+
# Clear logs and update the offline entity again - should NOT log again
918+
caplog.clear()
919+
await mock_scan_interval(hass, freezer)
920+
921+
assert unavailable_log not in caplog.text
922+
923+
# Bring the entity back online - should log back online message
924+
client.connect.side_effect = None
925+
await mock_scan_interval(hass, freezer)
926+
927+
assert hass.states.get(ENTITY_ID).state == MediaPlayerState.ON
928+
available_log = f"LG webOS TV entity {ENTITY_ID} is back online"
929+
assert available_log in caplog.text
930+
931+
# Clear logs and make update again - should NOT log again
932+
caplog.clear()
933+
await mock_scan_interval(hass, freezer)
934+
935+
assert hass.states.get(ENTITY_ID).state == MediaPlayerState.ON
936+
assert available_log not in caplog.text
937+
938+
# Test offline again to ensure the flag resets properly
939+
client.connect.side_effect = TimeoutError
940+
await mock_scan_interval(hass, freezer)
941+
942+
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
943+
assert unavailable_log in caplog.text
944+
945+
# Test entity that supports turn on are considered available
946+
assert await async_setup_component(
947+
hass,
948+
automation.DOMAIN,
949+
{
950+
automation.DOMAIN: [
951+
{
952+
"trigger": {
953+
"platform": "webostv.turn_on",
954+
"entity_id": ENTITY_ID,
955+
},
956+
"action": {
957+
"service": "test.automation",
958+
"data_template": {
959+
"some": ENTITY_ID,
960+
"id": "{{ trigger.id }}",
961+
},
962+
},
963+
},
964+
],
965+
},
966+
)
967+
968+
await mock_scan_interval(hass, freezer)
969+
970+
assert hass.states.get(ENTITY_ID).state == MediaPlayerState.ON
971+
available_log = f"LG webOS TV entity {ENTITY_ID} is back online"
972+
assert available_log in caplog.text

0 commit comments

Comments
 (0)