mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Rework Onkyo discovery and interview (#120668)
* Rework Onkyo discovery and interview * Move class outside setup function * Revert changing default name * Rename to volume resolution
This commit is contained in:
parent
4e7e896601
commit
e6c61f207d
@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -137,6 +138,16 @@ ONKYO_SELECT_OUTPUT_SCHEMA = vol.Schema(
|
|||||||
SERVICE_SELECT_HDMI_OUTPUT = "onkyo_select_hdmi_output"
|
SERVICE_SELECT_HDMI_OUTPUT = "onkyo_select_hdmi_output"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReceiverInfo:
|
||||||
|
"""Onkyo Receiver information."""
|
||||||
|
|
||||||
|
host: str
|
||||||
|
port: int
|
||||||
|
model_name: str
|
||||||
|
identifier: str
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
@ -169,43 +180,62 @@ async def async_setup_platform(
|
|||||||
)
|
)
|
||||||
|
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
name = config[CONF_NAME]
|
name = config.get(CONF_NAME)
|
||||||
max_volume = config[CONF_MAX_VOLUME]
|
max_volume = config[CONF_MAX_VOLUME]
|
||||||
receiver_max_volume = config[CONF_RECEIVER_MAX_VOLUME]
|
receiver_max_volume = config[CONF_RECEIVER_MAX_VOLUME]
|
||||||
sources = config[CONF_SOURCES]
|
sources = config[CONF_SOURCES]
|
||||||
|
|
||||||
@callback
|
async def async_setup_receiver(
|
||||||
def async_onkyo_update_callback(message: tuple[str, str, Any], origin: str) -> None:
|
info: ReceiverInfo, discovered: bool, name: str | None
|
||||||
"""Process new message from receiver."""
|
) -> None:
|
||||||
receiver = receivers[origin]
|
@callback
|
||||||
_LOGGER.debug("Received update callback from %s: %s", receiver.name, message)
|
def async_onkyo_update_callback(
|
||||||
|
message: tuple[str, str, Any], origin: str
|
||||||
zone, _, value = message
|
) -> None:
|
||||||
entity = entities[origin].get(zone)
|
"""Process new message from receiver."""
|
||||||
if entity is not None:
|
receiver = receivers[origin]
|
||||||
if entity.enabled:
|
_LOGGER.debug(
|
||||||
entity.process_update(message)
|
"Received update callback from %s: %s", receiver.name, message
|
||||||
elif zone in ZONES and value != "N/A":
|
|
||||||
# When we receive the status for a zone, and the value is not "N/A",
|
|
||||||
# then zone is available on the receiver, so we create the entity for it.
|
|
||||||
_LOGGER.debug("Discovered %s on %s", ZONES[zone], receiver.name)
|
|
||||||
zone_entity = OnkyoMediaPlayer(
|
|
||||||
receiver, sources, zone, max_volume, receiver_max_volume
|
|
||||||
)
|
)
|
||||||
entities[origin][zone] = zone_entity
|
|
||||||
async_add_entities([zone_entity])
|
|
||||||
|
|
||||||
@callback
|
zone, _, value = message
|
||||||
def async_onkyo_connect_callback(origin: str) -> None:
|
entity = entities[origin].get(zone)
|
||||||
"""Receiver (re)connected."""
|
if entity is not None:
|
||||||
receiver = receivers[origin]
|
if entity.enabled:
|
||||||
_LOGGER.debug("Receiver (re)connected: %s (%s)", receiver.name, receiver.host)
|
entity.process_update(message)
|
||||||
|
elif zone in ZONES and value != "N/A":
|
||||||
|
# When we receive the status for a zone, and the value is not "N/A",
|
||||||
|
# then zone is available on the receiver, so we create the entity for it.
|
||||||
|
_LOGGER.debug("Discovered %s on %s", ZONES[zone], receiver.name)
|
||||||
|
zone_entity = OnkyoMediaPlayer(
|
||||||
|
receiver, sources, zone, max_volume, receiver_max_volume
|
||||||
|
)
|
||||||
|
entities[origin][zone] = zone_entity
|
||||||
|
async_add_entities([zone_entity])
|
||||||
|
|
||||||
for entity in entities[origin].values():
|
@callback
|
||||||
entity.backfill_state()
|
def async_onkyo_connect_callback(origin: str) -> None:
|
||||||
|
"""Receiver (re)connected."""
|
||||||
|
receiver = receivers[origin]
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Receiver (re)connected: %s (%s)", receiver.name, receiver.host
|
||||||
|
)
|
||||||
|
|
||||||
def setup_receiver(receiver: pyeiscp.Connection) -> None:
|
for entity in entities[origin].values():
|
||||||
KNOWN_HOSTS.append(receiver.host)
|
entity.backfill_state()
|
||||||
|
|
||||||
|
_LOGGER.debug("Creating receiver: %s (%s)", info.model_name, info.host)
|
||||||
|
receiver = await pyeiscp.Connection.create(
|
||||||
|
host=info.host,
|
||||||
|
port=info.port,
|
||||||
|
update_callback=async_onkyo_update_callback,
|
||||||
|
connect_callback=async_onkyo_connect_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
receiver.model_name = info.model_name
|
||||||
|
receiver.identifier = info.identifier
|
||||||
|
receiver.name = name or info.model_name
|
||||||
|
receiver.discovered = discovered
|
||||||
|
|
||||||
# Store the receiver object and create a dictionary to store its entities.
|
# Store the receiver object and create a dictionary to store its entities.
|
||||||
receivers[receiver.host] = receiver
|
receivers[receiver.host] = receiver
|
||||||
@ -224,34 +254,38 @@ async def async_setup_platform(
|
|||||||
entities[receiver.host]["main"] = main_entity
|
entities[receiver.host]["main"] = main_entity
|
||||||
async_add_entities([main_entity])
|
async_add_entities([main_entity])
|
||||||
|
|
||||||
if host is not None and host not in KNOWN_HOSTS:
|
if host is not None:
|
||||||
|
if host in KNOWN_HOSTS:
|
||||||
|
return
|
||||||
|
|
||||||
_LOGGER.debug("Manually creating receiver: %s (%s)", name, host)
|
_LOGGER.debug("Manually creating receiver: %s (%s)", name, host)
|
||||||
receiver = await pyeiscp.Connection.create(
|
|
||||||
host=host,
|
|
||||||
update_callback=async_onkyo_update_callback,
|
|
||||||
connect_callback=async_onkyo_connect_callback,
|
|
||||||
)
|
|
||||||
|
|
||||||
# The library automatically adds a name and identifier only on discovered hosts,
|
|
||||||
# so manually add them here instead.
|
|
||||||
receiver.name = name
|
|
||||||
receiver.identifier = None
|
|
||||||
|
|
||||||
setup_receiver(receiver)
|
|
||||||
else:
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
async def async_onkyo_discovery_callback(receiver: pyeiscp.Connection):
|
async def async_onkyo_interview_callback(conn: pyeiscp.Connection):
|
||||||
"""Receiver discovered, connection not yet active."""
|
"""Receiver interviewed, connection not yet active."""
|
||||||
_LOGGER.debug("Receiver discovered: %s (%s)", receiver.name, receiver.host)
|
info = ReceiverInfo(conn.host, conn.port, conn.name, conn.identifier)
|
||||||
if receiver.host not in KNOWN_HOSTS:
|
_LOGGER.debug("Receiver interviewed: %s (%s)", info.model_name, info.host)
|
||||||
await receiver.connect()
|
if info.host not in KNOWN_HOSTS:
|
||||||
setup_receiver(receiver)
|
KNOWN_HOSTS.append(info.host)
|
||||||
|
await async_setup_receiver(info, False, name)
|
||||||
|
|
||||||
|
await pyeiscp.Connection.discover(
|
||||||
|
host=host,
|
||||||
|
discovery_callback=async_onkyo_interview_callback,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("Discovering receivers")
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def async_onkyo_discovery_callback(conn: pyeiscp.Connection):
|
||||||
|
"""Receiver discovered, connection not yet active."""
|
||||||
|
info = ReceiverInfo(conn.host, conn.port, conn.name, conn.identifier)
|
||||||
|
_LOGGER.debug("Receiver discovered: %s (%s)", info.model_name, info.host)
|
||||||
|
if info.host not in KNOWN_HOSTS:
|
||||||
|
KNOWN_HOSTS.append(info.host)
|
||||||
|
await async_setup_receiver(info, True, None)
|
||||||
|
|
||||||
_LOGGER.debug("Discovering receivers")
|
|
||||||
await pyeiscp.Connection.discover(
|
await pyeiscp.Connection.discover(
|
||||||
update_callback=async_onkyo_update_callback,
|
|
||||||
connect_callback=async_onkyo_connect_callback,
|
|
||||||
discovery_callback=async_onkyo_discovery_callback,
|
discovery_callback=async_onkyo_discovery_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -279,29 +313,27 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
|||||||
sources: dict[str, str],
|
sources: dict[str, str],
|
||||||
zone: str,
|
zone: str,
|
||||||
max_volume: int,
|
max_volume: int,
|
||||||
receiver_max_volume: int,
|
volume_resolution: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the Onkyo Receiver."""
|
"""Initialize the Onkyo Receiver."""
|
||||||
self._receiver = receiver
|
self._receiver = receiver
|
||||||
name = receiver.name
|
name = receiver.name
|
||||||
self._attr_name = f"{name}{' ' + ZONES[zone] if zone != 'main' else ''}"
|
|
||||||
identifier = receiver.identifier
|
identifier = receiver.identifier
|
||||||
if identifier is not None:
|
self._attr_name = f"{name}{' ' + ZONES[zone] if zone != 'main' else ''}"
|
||||||
# discovered
|
if receiver.discovered:
|
||||||
if zone == "main":
|
if zone == "main":
|
||||||
# keep legacy unique_id
|
# keep legacy unique_id
|
||||||
self._attr_unique_id = f"{name}_{identifier}"
|
self._attr_unique_id = f"{name}_{identifier}"
|
||||||
else:
|
else:
|
||||||
self._attr_unique_id = f"{identifier}_{zone}"
|
self._attr_unique_id = f"{identifier}_{zone}"
|
||||||
else:
|
else:
|
||||||
# not discovered
|
|
||||||
self._attr_unique_id = None
|
self._attr_unique_id = None
|
||||||
|
|
||||||
self._zone = zone
|
self._zone = zone
|
||||||
self._source_mapping = sources
|
self._source_mapping = sources
|
||||||
self._reverse_mapping = {value: key for key, value in sources.items()}
|
self._reverse_mapping = {value: key for key, value in sources.items()}
|
||||||
self._max_volume = max_volume
|
self._max_volume = max_volume
|
||||||
self._receiver_max_volume = receiver_max_volume
|
self._volume_resolution = volume_resolution
|
||||||
|
|
||||||
self._attr_source_list = list(sources.values())
|
self._attr_source_list = list(sources.values())
|
||||||
self._attr_extra_state_attributes = {}
|
self._attr_extra_state_attributes = {}
|
||||||
@ -350,9 +382,9 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
|||||||
will give 80% volume on the receiver. Then we convert that to the correct
|
will give 80% volume on the receiver. Then we convert that to the correct
|
||||||
scale for the receiver.
|
scale for the receiver.
|
||||||
"""
|
"""
|
||||||
# HA_VOL * (MAX VOL / 100) * MAX_RECEIVER_VOL
|
# HA_VOL * (MAX VOL / 100) * VOL_RESOLUTION
|
||||||
self._update_receiver(
|
self._update_receiver(
|
||||||
"volume", int(volume * (self._max_volume / 100) * self._receiver_max_volume)
|
"volume", int(volume * (self._max_volume / 100) * self._volume_resolution)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_volume_up(self) -> None:
|
async def async_volume_up(self) -> None:
|
||||||
@ -430,9 +462,9 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
|||||||
self._attr_extra_state_attributes.pop(ATTR_VIDEO_OUT, None)
|
self._attr_extra_state_attributes.pop(ATTR_VIDEO_OUT, None)
|
||||||
elif command in ["volume", "master-volume"] and value != "N/A":
|
elif command in ["volume", "master-volume"] and value != "N/A":
|
||||||
self._supports_volume = True
|
self._supports_volume = True
|
||||||
# AMP_VOL / (MAX_RECEIVER_VOL * (MAX_VOL / 100))
|
# AMP_VOL / (VOL_RESOLUTION * (MAX_VOL / 100))
|
||||||
self._attr_volume_level = value / (
|
self._attr_volume_level = value / (
|
||||||
self._receiver_max_volume * self._max_volume / 100
|
self._volume_resolution * self._max_volume / 100
|
||||||
)
|
)
|
||||||
elif command in ["muting", "audio-muting"]:
|
elif command in ["muting", "audio-muting"]:
|
||||||
self._attr_is_volume_muted = bool(value == "on")
|
self._attr_is_volume_muted = bool(value == "on")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user