1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Add channel muting switches to Onkyo (#162605)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Artur Pragacz
2026-02-18 23:26:57 +01:00
committed by GitHub
parent be25603b76
commit ba547c6bdb
6 changed files with 1126 additions and 2 deletions
+4 -1
View File
@@ -17,12 +17,13 @@ from .const import (
InputSource,
ListeningMode,
)
from .coordinator import ChannelMutingCoordinator
from .receiver import ReceiverManager, async_interview
from .services import async_setup_services
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.MEDIA_PLAYER]
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.SWITCH]
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
@@ -66,6 +67,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: OnkyoConfigEntry) -> boo
entry.runtime_data = OnkyoData(manager, sources, sound_modes)
ChannelMutingCoordinator(hass, entry, manager)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
if error := await manager.start():
@@ -0,0 +1,167 @@
"""Onkyo coordinators."""
from __future__ import annotations
import asyncio
from enum import StrEnum
import logging
from typing import TYPE_CHECKING, cast
from aioonkyo import Kind, Status, Zone, command, query, status
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
from .receiver import ReceiverManager
if TYPE_CHECKING:
from . import OnkyoConfigEntry
_LOGGER = logging.getLogger(__name__)
POWER_ON_QUERY_DELAY = 4
class Channel(StrEnum):
"""Audio channel."""
FRONT_LEFT = "front_left"
FRONT_RIGHT = "front_right"
CENTER = "center"
SURROUND_LEFT = "surround_left"
SURROUND_RIGHT = "surround_right"
SURROUND_BACK_LEFT = "surround_back_left"
SURROUND_BACK_RIGHT = "surround_back_right"
SUBWOOFER = "subwoofer"
HEIGHT_1_LEFT = "height_1_left"
HEIGHT_1_RIGHT = "height_1_right"
HEIGHT_2_LEFT = "height_2_left"
HEIGHT_2_RIGHT = "height_2_right"
SUBWOOFER_2 = "subwoofer_2"
ChannelMutingData = dict[Channel, status.ChannelMuting.Param]
ChannelMutingDesired = dict[Channel, command.ChannelMuting.Param]
class ChannelMutingCoordinator(DataUpdateCoordinator[ChannelMutingData]):
"""Coordinator for channel muting state."""
config_entry: OnkyoConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: OnkyoConfigEntry,
manager: ReceiverManager,
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name="onkyo_channel_muting",
update_interval=None,
)
self.manager = manager
self.data = ChannelMutingData()
self._desired = ChannelMutingDesired()
self._entities_added = False
self._query_state_task: asyncio.Task[None] | None = None
manager.callbacks.connect.append(self._connect_callback)
manager.callbacks.disconnect.append(self._disconnect_callback)
manager.callbacks.update.append(self._update_callback)
config_entry.async_on_unload(self._cancel_tasks)
async def _connect_callback(self, _reconnect: bool) -> None:
"""Receiver (re)connected."""
await self.manager.write(query.ChannelMuting())
async def _disconnect_callback(self) -> None:
"""Receiver disconnected."""
self._cancel_tasks()
self.async_set_updated_data(self.data)
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
def _query_state(self, delay: float = 0) -> None:
"""Query the receiver for all the info, that we care about."""
if self._query_state_task is not None:
self._query_state_task.cancel()
self._query_state_task = None
async def coro() -> None:
if delay:
await asyncio.sleep(delay)
await self.manager.write(query.ChannelMuting())
self._query_state_task = None
self._query_state_task = asyncio.create_task(coro())
async def _async_update_data(self) -> ChannelMutingData:
"""Respond to a data update request."""
self._query_state()
return self.data
async def async_send_command(
self, channel: Channel, param: command.ChannelMuting.Param
) -> None:
"""Send muting command for a channel."""
self._desired[channel] = param
message_data: ChannelMutingDesired = self.data | self._desired
message = command.ChannelMuting(**message_data) # type: ignore[misc]
await self.manager.write(message)
async def _update_callback(self, message: Status) -> None:
"""New message from the receiver."""
match message:
case status.NotAvailable(kind=Kind.CHANNEL_MUTING):
not_available = True
case status.ChannelMuting():
not_available = False
case status.Power(zone=Zone.MAIN, param=status.Power.Param.ON):
self._query_state(POWER_ON_QUERY_DELAY)
return
case _:
return
if not self._entities_added:
_LOGGER.debug(
"Discovered %s on %s (%s)",
self.name,
self.manager.info.model_name,
self.manager.info.host,
)
self._entities_added = True
async_dispatcher_send(
self.hass,
f"{DOMAIN}_{self.config_entry.entry_id}_channel_muting",
self,
)
if not_available:
self.data.clear()
self._desired.clear()
self.async_set_updated_data(self.data)
else:
message = cast(status.ChannelMuting, message)
self.data = {channel: getattr(message, channel) for channel in Channel}
self._desired = {
channel: desired
for channel, desired in self._desired.items()
if self.data[channel] != desired
}
self.async_set_updated_data(self.data)
@@ -100,7 +100,7 @@ async def async_setup_entry(
entry: OnkyoConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up MediaPlayer for config entry."""
"""Set up media player platform for config entry."""
data = entry.runtime_data
manager = data.manager
+96
View File
@@ -0,0 +1,96 @@
"""Switch platform."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
from aioonkyo import command, status
from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import Channel, ChannelMutingCoordinator
if TYPE_CHECKING:
from . import OnkyoConfigEntry
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: OnkyoConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up switch platform for config entry."""
@callback
def async_add_channel_muting_entities(
coordinator: ChannelMutingCoordinator,
) -> None:
"""Add channel muting switch entities."""
async_add_entities(
OnkyoChannelMutingSwitch(coordinator, channel) for channel in Channel
)
entry.async_on_unload(
async_dispatcher_connect(
hass,
f"{DOMAIN}_{entry.entry_id}_channel_muting",
async_add_channel_muting_entities,
)
)
class OnkyoChannelMutingSwitch(
CoordinatorEntity[ChannelMutingCoordinator], SwitchEntity
):
"""Onkyo Receiver Channel Muting Switch (one per channel)."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: ChannelMutingCoordinator,
channel: Channel,
) -> None:
"""Initialize the switch entity."""
super().__init__(coordinator)
self._channel = channel
name = coordinator.manager.info.model_name
channel_name = channel.replace("_", " ")
identifier = coordinator.manager.info.identifier
self._attr_name = f"{name} Mute {channel_name}"
self._attr_unique_id = f"{identifier}-channel_muting-{channel}"
@property
def available(self) -> bool:
"""Return if entity is available."""
return self.coordinator.manager.connected
async def async_turn_on(self, **kwargs: Any) -> None:
"""Mute the channel."""
await self.coordinator.async_send_command(
self._channel, command.ChannelMuting.Param.ON
)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Unmute the channel."""
await self.coordinator.async_send_command(
self._channel, command.ChannelMuting.Param.OFF
)
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
value = self.coordinator.data.get(self._channel)
self._attr_is_on = (
None if value is None else value == status.ChannelMuting.Param.ON
)
super()._handle_coordinator_update()
@@ -0,0 +1,638 @@
# serializer version: 1
# name: test_entities[switch.tx_nr7100_mute_center-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_center',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute center',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute center',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-center',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_center-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute center',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_center',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_front_left-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_front_left',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute front left',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute front left',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-front_left',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_front_left-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute front left',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_front_left',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_front_right-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_front_right',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute front right',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute front right',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-front_right',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_front_right-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute front right',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_front_right',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_height_1_left-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_height_1_left',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute height 1 left',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute height 1 left',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-height_1_left',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_height_1_left-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute height 1 left',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_height_1_left',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_height_1_right-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_height_1_right',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute height 1 right',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute height 1 right',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-height_1_right',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_height_1_right-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute height 1 right',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_height_1_right',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_height_2_left-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_height_2_left',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute height 2 left',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute height 2 left',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-height_2_left',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_height_2_left-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute height 2 left',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_height_2_left',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_height_2_right-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_height_2_right',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute height 2 right',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute height 2 right',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-height_2_right',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_height_2_right-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute height 2 right',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_height_2_right',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_subwoofer-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_subwoofer',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute subwoofer',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute subwoofer',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-subwoofer',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_subwoofer-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute subwoofer',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_subwoofer',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_subwoofer_2-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_subwoofer_2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute subwoofer 2',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute subwoofer 2',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-subwoofer_2',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_subwoofer_2-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute subwoofer 2',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_subwoofer_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_surround_back_left-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_surround_back_left',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute surround back left',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute surround back left',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-surround_back_left',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_surround_back_left-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute surround back left',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_surround_back_left',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_surround_back_right-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_surround_back_right',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute surround back right',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute surround back right',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-surround_back_right',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_surround_back_right-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute surround back right',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_surround_back_right',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_surround_left-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_surround_left',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute surround left',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute surround left',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-surround_left',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_surround_left-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute surround left',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_surround_left',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.tx_nr7100_mute_surround_right-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.tx_nr7100_mute_surround_right',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'TX-NR7100 Mute surround right',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'TX-NR7100 Mute surround right',
'platform': 'onkyo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '0009B0123456-channel_muting-surround_right',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.tx_nr7100_mute_surround_right-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TX-NR7100 Mute surround right',
}),
'context': <ANY>,
'entity_id': 'switch.tx_nr7100_mute_surround_right',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
+220
View File
@@ -0,0 +1,220 @@
"""Test Onkyo switch platform."""
import asyncio
from collections.abc import AsyncGenerator
from unittest.mock import AsyncMock, patch
from aioonkyo import Code, Instruction, Kind, Zone, command, query, status
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.homeassistant import (
DOMAIN as HOMEASSISTANT_DOMAIN,
SERVICE_UPDATE_ENTITY,
)
from homeassistant.components.onkyo.coordinator import Channel
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
ENTITY_ID = "switch.tx_nr7100_mute_front_left"
def _channel_muting_status(
**overrides: status.ChannelMuting.Param,
) -> status.ChannelMuting:
"""Create a ChannelMuting status with all channels OFF, with overrides."""
params = dict.fromkeys(Channel, status.ChannelMuting.Param.OFF)
params.update(overrides)
return status.ChannelMuting(
Code.from_kind_zone(Kind.CHANNEL_MUTING, Zone.MAIN),
None,
**params,
)
@pytest.fixture(autouse=True)
async def auto_setup_integration(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_receiver: AsyncMock,
read_queue: asyncio.Queue,
writes: list[Instruction],
) -> AsyncGenerator[None]:
"""Auto setup integration."""
read_queue.put_nowait(
_channel_muting_status(
front_right=status.ChannelMuting.Param.ON,
center=status.ChannelMuting.Param.ON,
)
)
with (
patch(
"homeassistant.components.onkyo.coordinator.POWER_ON_QUERY_DELAY",
0,
),
patch("homeassistant.components.onkyo.PLATFORMS", [Platform.SWITCH]),
):
await setup_integration(hass, mock_config_entry)
writes.clear()
yield
async def test_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test entities."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_state_changes(hass: HomeAssistant, read_queue: asyncio.Queue) -> None:
"""Test NotAvailable message clears channel muting state."""
assert (state := hass.states.get(ENTITY_ID)) is not None
assert state.state == STATE_OFF
read_queue.put_nowait(
_channel_muting_status(front_left=status.ChannelMuting.Param.ON)
)
await asyncio.sleep(0)
assert (state := hass.states.get(ENTITY_ID)) is not None
assert state.state == STATE_ON
read_queue.put_nowait(
status.NotAvailable(
Code.from_kind_zone(Kind.CHANNEL_MUTING, Zone.MAIN),
None,
Kind.CHANNEL_MUTING,
)
)
await asyncio.sleep(0)
assert (state := hass.states.get(ENTITY_ID)) is not None
assert state.state == STATE_UNKNOWN
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(
_channel_muting_status(front_left=status.ChannelMuting.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", "message"),
[
(
SERVICE_TURN_ON,
command.ChannelMuting(
front_left=command.ChannelMuting.Param.ON,
front_right=command.ChannelMuting.Param.ON,
center=command.ChannelMuting.Param.ON,
),
),
(
SERVICE_TURN_OFF,
command.ChannelMuting(
front_right=command.ChannelMuting.Param.ON,
center=command.ChannelMuting.Param.ON,
),
),
],
)
async def test_actions(
hass: HomeAssistant,
writes: list[Instruction],
action: str,
message: Instruction,
) -> None:
"""Test actions."""
await hass.services.async_call(
SWITCH_DOMAIN,
action,
{ATTR_ENTITY_ID: ENTITY_ID},
blocking=True,
)
assert writes[0] == message
async def test_query_state_task(
read_queue: asyncio.Queue, writes: list[Instruction]
) -> None:
"""Test query state task."""
read_queue.put_nowait(
status.Power(
Code.from_kind_zone(Kind.POWER, Zone.MAIN), None, status.Power.Param.STANDBY
)
)
read_queue.put_nowait(
status.Power(
Code.from_kind_zone(Kind.POWER, Zone.MAIN), None, status.Power.Param.ON
)
)
read_queue.put_nowait(
status.Power(
Code.from_kind_zone(Kind.POWER, Zone.MAIN), None, status.Power.Param.STANDBY
)
)
read_queue.put_nowait(
status.Power(
Code.from_kind_zone(Kind.POWER, Zone.MAIN), None, status.Power.Param.ON
)
)
await asyncio.sleep(0.1)
queries = [w for w in writes if isinstance(w, query.ChannelMuting)]
assert len(queries) == 1
async def test_update_entity(
hass: HomeAssistant,
writes: list[Instruction],
) -> None:
"""Test manual entity update."""
await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
await hass.services.async_call(
HOMEASSISTANT_DOMAIN,
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: ENTITY_ID},
blocking=True,
)
await asyncio.sleep(0)
queries = [w for w in writes if isinstance(w, query.ChannelMuting)]
assert len(queries) == 1