1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Mark entities as unavailable in Onkyo (#159521)

This commit is contained in:
Artur Pragacz
2025-12-23 08:39:34 +01:00
committed by GitHub
parent 64f0a615df
commit 02ab11c1bd
5 changed files with 72 additions and 11 deletions

View File

@@ -115,6 +115,12 @@ async def async_setup_entry(
if entity.enabled:
await entity.query_state()
async def disconnect_callback() -> None:
for entity in entities.values():
if entity.enabled:
entity.cancel_tasks()
entity.async_write_ha_state()
async def update_callback(message: Status) -> None:
if isinstance(message, status.Raw):
return
@@ -146,6 +152,7 @@ async def async_setup_entry(
async_add_entities([zone_entity])
manager.callbacks.connect.append(connect_callback)
manager.callbacks.disconnect.append(disconnect_callback)
manager.callbacks.update.append(update_callback)
@@ -225,13 +232,13 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
await self.query_state()
async def async_will_remove_from_hass(self) -> None:
"""Cancel the tasks when the entity is removed."""
if self._query_state_task is not None:
self._query_state_task.cancel()
self._query_state_task = None
if self._query_av_info_task is not None:
self._query_av_info_task.cancel()
self._query_av_info_task = None
"""Entity will be removed from hass."""
self.cancel_tasks()
@property
def available(self) -> bool:
"""Return if entity is available."""
return self._manager.connected
async def query_state(self) -> None:
"""Query the receiver for all the info, that we care about."""
@@ -247,6 +254,15 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
await self._manager.write(query.AudioInformation())
await self._manager.write(query.VideoInformation())
def cancel_tasks(self) -> None:
"""Cancel the tasks."""
if self._query_state_task is not None:
self._query_state_task.cancel()
self._query_state_task = None
if self._query_av_info_task is not None:
self._query_av_info_task.cancel()
self._query_av_info_task = None
async def async_turn_on(self) -> None:
"""Turn the media player on."""
message = command.Power(self._zone, command.Power.Param.ON)

View File

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

View File

@@ -28,11 +28,13 @@ class Callbacks:
"""Receiver callbacks."""
connect: list[Callable[[bool], Awaitable[None]]] = field(default_factory=list)
disconnect: list[Callable[[], Awaitable[None]]] = field(default_factory=list)
update: list[Callable[[Status], Awaitable[None]]] = field(default_factory=list)
def clear(self) -> None:
"""Clear all callbacks."""
self.connect.clear()
self.disconnect.clear()
self.update.clear()
@@ -43,6 +45,7 @@ class ReceiverManager:
entry: OnkyoConfigEntry
info: ReceiverInfo
receiver: Receiver | None = None
connected: bool = False
callbacks: Callbacks
_started: asyncio.Event
@@ -83,6 +86,7 @@ class ReceiverManager:
while True:
try:
async with connect(self.info, retry=reconnect) as self.receiver:
self.connected = True
if not reconnect:
self._started.set()
else:
@@ -96,7 +100,9 @@ class ReceiverManager:
reconnect = True
finally:
self.connected = False
_LOGGER.info("Disconnected: %s", self.info)
await self.on_disconnect()
async def on_connect(self, reconnect: bool) -> None:
"""Receiver (re)connected."""
@@ -109,8 +115,13 @@ class ReceiverManager:
for callback in self.callbacks.connect:
await callback(reconnect)
async def on_disconnect(self) -> None:
"""Receiver disconnected."""
for callback in self.callbacks.disconnect:
await callback()
async def on_update(self, message: Status) -> None:
"""Process new message from the receiver."""
"""New message from the receiver."""
for callback in self.callbacks.update:
await callback(message)

View File

@@ -76,11 +76,20 @@ async def test_reconnect(
assert mock_config_entry.state is ConfigEntryState.LOADED
manager = mock_config_entry.runtime_data.manager
assert manager.connected is True
async def disconnect_assert() -> None:
assert manager.connected is False
manager.callbacks.disconnect.append(disconnect_assert)
mock_connect.reset_mock()
assert mock_connect.call_count == 0
read_queue.put_nowait(None) # Simulate a disconnect
# Simulate a disconnect
read_queue.put_nowait(None)
await asyncio.sleep(0)
assert mock_connect.call_count == 1

View File

@@ -32,6 +32,7 @@ from homeassistant.const import (
SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET,
SERVICE_VOLUME_UP,
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant
@@ -81,6 +82,30 @@ async def test_entities(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_availability(hass: HomeAssistant, read_queue: asyncio.Queue) -> None:
"""Test entity availability on disconnect and reconnect."""
assert (state := hass.states.get(ENTITY_ID)) is not None
assert state.state != STATE_UNAVAILABLE
# Simulate a disconnect
read_queue.put_nowait(None)
await asyncio.sleep(0)
assert (state := hass.states.get(ENTITY_ID)) is not None
assert state.state == STATE_UNAVAILABLE
# Simulate first status update after reconnect
read_queue.put_nowait(
status.Power(
Code.from_kind_zone(Kind.POWER, Zone.MAIN), None, status.Power.Param.ON
)
)
await asyncio.sleep(0)
assert (state := hass.states.get(ENTITY_ID)) is not None
assert state.state != STATE_UNAVAILABLE
@pytest.mark.parametrize(
("action", "action_data", "message"),
[