Home Assistant Unofficial Reference 2024.12.1
receiver.py
Go to the documentation of this file.
1 """Onkyo receiver."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections.abc import Callable, Iterable
7 import contextlib
8 from dataclasses import dataclass, field
9 import logging
10 from typing import Any
11 
12 import pyeiscp
13 
14 from .const import DEVICE_DISCOVERY_TIMEOUT, DEVICE_INTERVIEW_TIMEOUT, ZONES
15 
16 _LOGGER = logging.getLogger(__name__)
17 
18 
19 @dataclass
20 class Callbacks:
21  """Onkyo Receiver Callbacks."""
22 
23  connect: list[Callable[[Receiver], None]] = field(default_factory=list)
24  update: list[Callable[[Receiver, tuple[str, str, Any]], None]] = field(
25  default_factory=list
26  )
27 
28 
29 @dataclass
30 class Receiver:
31  """Onkyo receiver."""
32 
33  conn: pyeiscp.Connection
34  model_name: str
35  identifier: str
36  host: str
37  first_connect: bool = True
38  callbacks: Callbacks = field(default_factory=Callbacks)
39 
40  @classmethod
41  async def async_create(cls, info: ReceiverInfo) -> Receiver:
42  """Set up Onkyo Receiver."""
43 
44  receiver: Receiver | None = None
45 
46  def on_connect(_origin: str) -> None:
47  assert receiver is not None
48  receiver.on_connect()
49 
50  def on_update(message: tuple[str, str, Any], _origin: str) -> None:
51  assert receiver is not None
52  receiver.on_update(message)
53 
54  _LOGGER.debug("Creating receiver: %s (%s)", info.model_name, info.host)
55 
56  connection = await pyeiscp.Connection.create(
57  host=info.host,
58  port=info.port,
59  connect_callback=on_connect,
60  update_callback=on_update,
61  auto_connect=False,
62  )
63 
64  return (
65  receiver := cls(
66  conn=connection,
67  model_name=info.model_name,
68  identifier=info.identifier,
69  host=info.host,
70  )
71  )
72 
73  def on_connect(self) -> None:
74  """Receiver (re)connected."""
75  _LOGGER.debug("Receiver (re)connected: %s (%s)", self.model_name, self.host)
76 
77  # Discover what zones are available for the receiver by querying the power.
78  # If we get a response for the specific zone, it means it is available.
79  for zone in ZONES:
80  self.conn.query_property(zone, "power")
81 
82  for callback in self.callbacks.connect:
83  callback(self)
84 
85  self.first_connectfirst_connect = False
86 
87  def on_update(self, message: tuple[str, str, Any]) -> None:
88  """Process new message from the receiver."""
89  _LOGGER.debug("Received update callback from %s: %s", self.model_name, message)
90  for callback in self.callbacks.update:
91  callback(self, message)
92 
93 
94 @dataclass
96  """Onkyo receiver information."""
97 
98  host: str
99  port: int
100  model_name: str
101  identifier: str
102 
103 
104 async def async_interview(host: str) -> ReceiverInfo | None:
105  """Interview Onkyo Receiver."""
106  _LOGGER.debug("Interviewing receiver: %s", host)
107 
108  receiver_info: ReceiverInfo | None = None
109 
110  event = asyncio.Event()
111 
112  async def _callback(conn: pyeiscp.Connection) -> None:
113  """Receiver interviewed, connection not yet active."""
114  nonlocal receiver_info
115  if receiver_info is None:
116  info = ReceiverInfo(host, conn.port, conn.name, conn.identifier)
117  _LOGGER.debug("Receiver interviewed: %s (%s)", info.model_name, info.host)
118  receiver_info = info
119  event.set()
120 
121  timeout = DEVICE_INTERVIEW_TIMEOUT
122 
123  await pyeiscp.Connection.discover(
124  host=host, discovery_callback=_callback, timeout=timeout
125  )
126 
127  with contextlib.suppress(asyncio.TimeoutError):
128  await asyncio.wait_for(event.wait(), timeout)
129 
130  return receiver_info
131 
132 
133 async def async_discover() -> Iterable[ReceiverInfo]:
134  """Discover Onkyo Receivers."""
135  _LOGGER.debug("Discovering receivers")
136 
137  receiver_infos: list[ReceiverInfo] = []
138 
139  async def _callback(conn: pyeiscp.Connection) -> None:
140  """Receiver discovered, connection not yet active."""
141  info = ReceiverInfo(conn.host, conn.port, conn.name, conn.identifier)
142  _LOGGER.debug("Receiver discovered: %s (%s)", info.model_name, info.host)
143  receiver_infos.append(info)
144 
145  timeout = DEVICE_DISCOVERY_TIMEOUT
146 
147  await pyeiscp.Connection.discover(discovery_callback=_callback, timeout=timeout)
148 
149  await asyncio.sleep(timeout)
150 
151  return receiver_infos
Receiver async_create(cls, ReceiverInfo info)
Definition: receiver.py:41
ReceiverInfo|None async_interview(str host)
Definition: receiver.py:104
Iterable[ReceiverInfo] async_discover()
Definition: receiver.py:133