denonavr 0.11.6__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
denonavr/__init__.py CHANGED
@@ -10,8 +10,6 @@ Automation Library for Denon AVR receivers.
10
10
  # Set default logging handler to avoid "No handler found" warnings.
11
11
  import logging
12
12
 
13
- from .decorators import run_async_synchronously
14
-
15
13
  # Import denonavr module
16
14
  from .denonavr import DenonAVR
17
15
  from .ssdp import async_identify_denonavr_receivers
@@ -19,7 +17,7 @@ from .ssdp import async_identify_denonavr_receivers
19
17
  logging.getLogger(__name__).addHandler(logging.NullHandler())
20
18
 
21
19
  __title__ = "denonavr"
22
- __version__ = "0.11.6"
20
+ __version__ = "1.0.1"
23
21
 
24
22
 
25
23
  async def async_discover():
@@ -33,17 +31,6 @@ async def async_discover():
33
31
  return await async_identify_denonavr_receivers()
34
32
 
35
33
 
36
- @run_async_synchronously(async_func=async_discover)
37
- def discover():
38
- """
39
- Discover all Denon AVR devices in LAN zone.
40
-
41
- Returns a list of dictionaries which includes all discovered Denon AVR
42
- devices with keys "host", "modelName", "friendlyName", "presentationURL".
43
- By default SSDP broadcasts are sent once with a 2 seconds timeout.
44
- """
45
-
46
-
47
34
  async def async_init_all_receivers():
48
35
  """
49
36
  Initialize all discovered Denon AVR receivers in LAN zone.
@@ -58,13 +45,3 @@ async def async_init_all_receivers():
58
45
  init_receiver = DenonAVR(receiver["host"])
59
46
  init_receivers.append(init_receiver)
60
47
  return init_receivers
61
-
62
-
63
- @run_async_synchronously(async_func=async_init_all_receivers)
64
- def init_all_receivers():
65
- """
66
- Initialize all discovered Denon AVR receivers in LAN zone.
67
-
68
- Returns a list of created Denon AVR instances.
69
- By default SSDP broadcasts are sent up to 3 times with a 2 seconds timeout.
70
- """
denonavr/api.py CHANGED
@@ -31,7 +31,8 @@ from typing import (
31
31
 
32
32
  import attr
33
33
  import httpx
34
- from defusedxml.ElementTree import fromstring
34
+ from defusedxml import DefusedXmlException
35
+ from defusedxml.ElementTree import ParseError, fromstring
35
36
 
36
37
  from .appcommand import AppCommandCmd
37
38
  from .const import (
@@ -83,16 +84,86 @@ def telnet_event_map_factory() -> Dict[str, List]:
83
84
  return dict(event_map)
84
85
 
85
86
 
86
- @attr.s(auto_attribs=True, hash=False, on_setattr=DENON_ATTR_SETATTR)
87
+ @attr.s(auto_attribs=True, hash=False)
88
+ class HTTPXAsyncClient:
89
+ """Perform cached HTTP calls with httpx.AsyncClient."""
90
+
91
+ client_getter: Callable[[], httpx.AsyncClient] = attr.ib(
92
+ validator=attr.validators.is_callable(),
93
+ default=get_default_async_client,
94
+ init=False,
95
+ )
96
+
97
+ def __hash__(self) -> int:
98
+ """Hash the class using its ID that caching works."""
99
+ return id(self)
100
+
101
+ @cache_result
102
+ @async_handle_receiver_exceptions
103
+ async def async_get(
104
+ self,
105
+ url: str,
106
+ timeout: float,
107
+ read_timeout: float,
108
+ *,
109
+ cache_id: Hashable = None,
110
+ ) -> httpx.Response:
111
+ """Call GET endpoint of Denon AVR receiver asynchronously."""
112
+ client = self.client_getter()
113
+ try:
114
+ res = await client.get(
115
+ url, timeout=httpx.Timeout(timeout, read=read_timeout)
116
+ )
117
+ res.raise_for_status()
118
+ finally:
119
+ # Close the default AsyncClient but keep custom clients open
120
+ if self.is_default_async_client():
121
+ await client.aclose()
122
+
123
+ return res
124
+
125
+ @cache_result
126
+ @async_handle_receiver_exceptions
127
+ async def async_post(
128
+ self,
129
+ url: str,
130
+ timeout: float,
131
+ read_timeout: float,
132
+ *,
133
+ content: Optional[bytes] = None,
134
+ data: Optional[Dict] = None,
135
+ cache_id: Hashable = None,
136
+ ) -> httpx.Response:
137
+ """Call GET endpoint of Denon AVR receiver asynchronously."""
138
+ client = self.client_getter()
139
+ try:
140
+ res = await client.post(
141
+ url,
142
+ content=content,
143
+ data=data,
144
+ timeout=httpx.Timeout(timeout, read=read_timeout),
145
+ )
146
+ res.raise_for_status()
147
+ finally:
148
+ # Close the default AsyncClient but keep custom clients open
149
+ if self.is_default_async_client():
150
+ await client.aclose()
151
+
152
+ return res
153
+
154
+ def is_default_async_client(self) -> bool:
155
+ """Check if default httpx.AsyncClient getter is used."""
156
+ return self.client_getter is get_default_async_client
157
+
158
+
159
+ @attr.s(auto_attribs=True, on_setattr=DENON_ATTR_SETATTR)
87
160
  class DenonAVRApi:
88
161
  """Perform API calls to Denon AVR REST interface."""
89
162
 
90
163
  host: str = attr.ib(converter=str, default="localhost")
91
164
  port: int = attr.ib(converter=int, default=80)
92
- timeout: httpx.Timeout = attr.ib(
93
- validator=attr.validators.instance_of(httpx.Timeout),
94
- default=httpx.Timeout(2.0, read=15.0),
95
- )
165
+ timeout: float = attr.ib(converter=float, default=2.0)
166
+ read_timeout: float = attr.ib(converter=float, default=15.0)
96
167
  _appcommand_update_tags: Tuple[AppCommandCmd] = attr.ib(
97
168
  validator=attr.validators.deep_iterable(
98
169
  attr.validators.instance_of(AppCommandCmd),
@@ -107,23 +178,18 @@ class DenonAVRApi:
107
178
  ),
108
179
  default=attr.Factory(tuple),
109
180
  )
110
- async_client_getter: Callable[[], httpx.AsyncClient] = attr.ib(
111
- validator=attr.validators.is_callable(),
112
- default=get_default_async_client,
181
+ httpx_async_client: HTTPXAsyncClient = attr.ib(
182
+ validator=attr.validators.instance_of(HTTPXAsyncClient),
183
+ default=attr.Factory(HTTPXAsyncClient),
113
184
  init=False,
114
185
  )
115
186
 
116
- def __hash__(self) -> int:
117
- """
118
- Hash the class in a custom way that caching works.
119
-
120
- It should react on changes of host and port.
121
- """
122
- return hash((self.host, self.port))
123
-
124
- @async_handle_receiver_exceptions
125
187
  async def async_get(
126
- self, request: str, port: Optional[int] = None
188
+ self,
189
+ request: str,
190
+ *,
191
+ port: Optional[int] = None,
192
+ cache_id: Hashable = None,
127
193
  ) -> httpx.Response:
128
194
  """Call GET endpoint of Denon AVR receiver asynchronously."""
129
195
  # Use default port of the receiver if no different port is specified
@@ -131,24 +197,18 @@ class DenonAVRApi:
131
197
 
132
198
  endpoint = f"http://{self.host}:{port}{request}"
133
199
 
134
- client = self.async_client_getter()
135
- try:
136
- res = await client.get(endpoint, timeout=self.timeout)
137
- res.raise_for_status()
138
- finally:
139
- # Close the default AsyncClient but keep custom clients open
140
- if self.is_default_async_client():
141
- await client.aclose()
142
-
143
- return res
200
+ return await self.httpx_async_client.async_get(
201
+ endpoint, self.timeout, self.read_timeout, cache_id=cache_id
202
+ )
144
203
 
145
- @async_handle_receiver_exceptions
146
204
  async def async_post(
147
205
  self,
148
206
  request: str,
207
+ *,
149
208
  content: Optional[bytes] = None,
150
209
  data: Optional[Dict] = None,
151
210
  port: Optional[int] = None,
211
+ cache_id: Hashable = None,
152
212
  ) -> httpx.Response:
153
213
  """Call POST endpoint of Denon AVR receiver asynchronously."""
154
214
  # Use default port of the receiver if no different port is specified
@@ -156,20 +216,15 @@ class DenonAVRApi:
156
216
 
157
217
  endpoint = f"http://{self.host}:{port}{request}"
158
218
 
159
- client = self.async_client_getter()
160
- try:
161
- res = await client.post(
162
- endpoint, content=content, data=data, timeout=self.timeout
163
- )
164
- res.raise_for_status()
165
- finally:
166
- # Close the default AsyncClient but keep custom clients open
167
- if self.is_default_async_client():
168
- await client.aclose()
169
-
170
- return res
219
+ return await self.httpx_async_client.async_post(
220
+ endpoint,
221
+ self.timeout,
222
+ self.read_timeout,
223
+ content=content,
224
+ data=data,
225
+ cache_id=cache_id,
226
+ )
171
227
 
172
- @async_handle_receiver_exceptions
173
228
  async def async_get_command(self, request: str) -> str:
174
229
  """Send HTTP GET command to Denon AVR receiver asynchronously."""
175
230
  # HTTP GET to endpoint
@@ -177,34 +232,46 @@ class DenonAVRApi:
177
232
  # Return text
178
233
  return res.text
179
234
 
180
- @cache_result
181
- @async_handle_receiver_exceptions
182
235
  async def async_get_xml(
183
- self, request: str, cache_id: Hashable = None
236
+ self, request: str, *, cache_id: Hashable = None
184
237
  ) -> ET.Element:
185
238
  """Return XML data from HTTP GET endpoint asynchronously."""
186
239
  # HTTP GET to endpoint
187
- res = await self.async_get(request)
240
+ res = await self.async_get(request, cache_id=cache_id)
188
241
  # create ElementTree
189
- xml_root = fromstring(res.text)
242
+ try:
243
+ xml_root = fromstring(res.text)
244
+ except (
245
+ ET.ParseError,
246
+ DefusedXmlException,
247
+ ParseError,
248
+ UnicodeDecodeError,
249
+ ) as err:
250
+ raise AvrInvalidResponseError(f"XMLParseError: {err}", request) from err
190
251
  # Check validity of XML
191
252
  self.check_xml_validity(request, xml_root)
192
253
  # Return ElementTree element
193
254
  return xml_root
194
255
 
195
- @cache_result
196
- @async_handle_receiver_exceptions
197
256
  async def async_post_appcommand(
198
- self, request: str, cmds: Tuple[AppCommandCmd], cache_id: Hashable = None
257
+ self, request: str, cmds: Tuple[AppCommandCmd], *, cache_id: Hashable = None
199
258
  ) -> ET.Element:
200
259
  """Return XML from Appcommand(0300) endpoint asynchronously."""
201
260
  # Prepare XML body for POST call
202
261
  content = self.prepare_appcommand_body(cmds)
203
262
  _LOGGER.debug("Content for %s endpoint: %s", request, content)
204
263
  # HTTP POST to endpoint
205
- res = await self.async_post(request, content=content)
264
+ res = await self.async_post(request, content=content, cache_id=cache_id)
206
265
  # create ElementTree
207
- xml_root = fromstring(res.text)
266
+ try:
267
+ xml_root = fromstring(res.text)
268
+ except (
269
+ ET.ParseError,
270
+ DefusedXmlException,
271
+ ParseError,
272
+ UnicodeDecodeError,
273
+ ) as err:
274
+ raise AvrInvalidResponseError(f"XMLParseError: {err}", request) from err
208
275
  # Check validity of XML
209
276
  self.check_xml_validity(request, xml_root)
210
277
  # Add query tags to result
@@ -350,10 +417,6 @@ class DenonAVRApi:
350
417
 
351
418
  return body_bytes
352
419
 
353
- def is_default_async_client(self) -> bool:
354
- """Check if default httpx.AsyncClient getter is used."""
355
- return self.async_client_getter is get_default_async_client
356
-
357
420
 
358
421
  class DenonAVRTelnetProtocol(asyncio.Protocol):
359
422
  """Protocol for the Denon AVR Telnet interface."""
@@ -401,9 +464,9 @@ class DenonAVRTelnetProtocol(asyncio.Protocol):
401
464
 
402
465
  def connection_lost(self, exc: Optional[Exception]) -> None:
403
466
  """Handle connection lost."""
404
- self.transport = None
405
467
  self._on_connection_lost()
406
- return super().connection_lost(exc)
468
+ super().connection_lost(exc)
469
+ self.transport = None
407
470
 
408
471
 
409
472
  @attr.s(auto_attribs=True, hash=False, on_setattr=DENON_ATTR_SETATTR)
@@ -413,9 +476,6 @@ class DenonAVRTelnetApi:
413
476
  host: str = attr.ib(converter=str, default="localhost")
414
477
  timeout: float = attr.ib(converter=float, default=2.0)
415
478
  _connection_enabled: bool = attr.ib(default=False)
416
- _healthy: Optional[bool] = attr.ib(
417
- converter=attr.converters.optional(bool), default=None
418
- )
419
479
  _last_message_time: float = attr.ib(default=-1.0)
420
480
  _connect_lock: asyncio.Lock = attr.ib(default=attr.Factory(asyncio.Lock))
421
481
  _reconnect_task: asyncio.Task = attr.ib(default=None)
@@ -474,18 +534,18 @@ class DenonAVRTelnetApi:
474
534
  raise AvrTimoutError(f"TimeoutException: {err}", "telnet connect") from err
475
535
  except ConnectionRefusedError as err:
476
536
  _LOGGER.debug(
477
- "%s: Connection refused on telnet connect", self.host, exc_info=True
537
+ "%s: Connection refused on telnet connect: %s", self.host, err
478
538
  )
479
539
  raise AvrNetworkError(
480
540
  f"ConnectionRefusedError: {err}", "telnet connect"
481
541
  ) from err
482
542
  except (OSError, IOError) as err:
483
543
  _LOGGER.debug(
484
- "%s: Connection failed on telnet reconnect", self.host, exc_info=True
544
+ "%s: Connection failed on telnet reconnect: %s", self.host, err
485
545
  )
486
546
  raise AvrNetworkError(f"OSError: {err}", "telnet connect") from err
487
- _LOGGER.debug("%s: telnet connection complete", self.host)
488
547
  self._protocol = cast(DenonAVRTelnetProtocol, transport_protocol[1])
548
+ _LOGGER.debug("%s: telnet connection established", self.host)
489
549
  self._connection_enabled = True
490
550
  self._last_message_time = time.monotonic()
491
551
  self._schedule_monitor()
@@ -507,6 +567,7 @@ class DenonAVRTelnetApi:
507
567
  "PSREFLEV ?",
508
568
  "PSDYNVOL ?",
509
569
  "MS?",
570
+ skip_confirmation=True,
510
571
  )
511
572
 
512
573
  def _schedule_monitor(self) -> None:
@@ -527,8 +588,6 @@ class DenonAVRTelnetApi:
527
588
  _LOGGER.info(
528
589
  "%s: Keep alive failed, disconnecting and reconnecting", self.host
529
590
  )
530
- if self._protocol is not None:
531
- self._protocol.close()
532
591
  self._handle_disconnected()
533
592
  return
534
593
 
@@ -540,16 +599,20 @@ class DenonAVRTelnetApi:
540
599
 
541
600
  def _handle_disconnected(self) -> None:
542
601
  """Handle disconnected."""
543
- _LOGGER.debug("%s: disconnected", self.host)
544
- self._protocol = None
602
+ _LOGGER.debug("%s: handle disconnected", self.host)
603
+ if self._protocol is not None:
604
+ self._protocol.close()
605
+ self._protocol = None
545
606
  self._stop_monitor()
546
607
  if not self._connection_enabled:
547
608
  return
548
- self._reconnect_task = asyncio.create_task(self._async_reconnect())
609
+ if self._reconnect_task is None:
610
+ self._reconnect_task = asyncio.create_task(self._async_reconnect())
549
611
 
550
612
  async def async_disconnect(self) -> None:
551
613
  """Close the connection to the receiver asynchronously."""
552
614
  async with self._connect_lock:
615
+ _LOGGER.debug("%s: telnet disconnecting", self.host)
553
616
  self._connection_enabled = False
554
617
  self._stop_monitor()
555
618
  reconnect_task = self._reconnect_task
@@ -565,6 +628,7 @@ class DenonAVRTelnetApi:
565
628
  await reconnect_task
566
629
  except asyncio.CancelledError:
567
630
  pass
631
+ _LOGGER.debug("%s: telnet disconnected", self.host)
568
632
 
569
633
  async def _async_reconnect(self) -> None:
570
634
  """Reconnect to the receiver asynchronously."""
@@ -572,27 +636,36 @@ class DenonAVRTelnetApi:
572
636
 
573
637
  while self._connection_enabled and not self.healthy:
574
638
  async with self._connect_lock:
639
+ _LOGGER.debug("%s: Telnet reconnecting", self.host)
575
640
  try:
576
641
  await self._async_establish_connection()
577
642
  except AvrTimoutError:
578
643
  _LOGGER.debug(
579
644
  "%s: Timeout exception on telnet reconnect", self.host
580
645
  )
581
- except AvrNetworkError as ex:
582
- _LOGGER.debug("%s: %s", self.host, ex, exc_info=True)
583
- except Exception: # pylint: disable=broad-except
646
+ except AvrNetworkError as err:
647
+ _LOGGER.debug("%s: %s", self.host, err)
648
+ except AvrProcessingError as err:
649
+ _LOGGER.debug(
650
+ "%s: Failed updating state on telnet reconnect: %s",
651
+ self.host,
652
+ err,
653
+ )
654
+ except Exception as err: # pylint: disable=broad-except
584
655
  _LOGGER.error(
585
656
  "%s: Unexpected exception on telnet reconnect",
586
657
  self.host,
587
- exc_info=True,
658
+ exc_info=err,
588
659
  )
589
660
  else:
590
661
  _LOGGER.info("%s: Telnet reconnected", self.host)
591
- return
662
+ break
592
663
 
593
664
  await asyncio.sleep(backoff)
594
665
  backoff = min(30.0, backoff * 2)
595
666
 
667
+ self._reconnect_task = None
668
+
596
669
  def register_callback(
597
670
  self, event: str, callback: Callable[[str, str, str], Awaitable[None]]
598
671
  ) -> None:
@@ -603,6 +676,8 @@ class DenonAVRTelnetApi:
603
676
 
604
677
  if event not in self._callbacks.keys():
605
678
  self._callbacks[event] = []
679
+ elif callback in self._callbacks[event]:
680
+ return
606
681
  self._callbacks[event].append(callback)
607
682
 
608
683
  def unregister_callback(
@@ -617,6 +692,8 @@ class DenonAVRTelnetApi:
617
692
  self, callback: Callable[[str], Awaitable[None]]
618
693
  ) -> None:
619
694
  """Register a callback handler for raw telnet messages."""
695
+ if callback in self._raw_callbacks:
696
+ return
620
697
  self._raw_callbacks.append(callback)
621
698
 
622
699
  def _unregister_raw_callback(
@@ -682,7 +759,7 @@ class DenonAVRTelnetApi:
682
759
  # We don't want a single bad callback to trip up the
683
760
  # whole system and prevent further execution
684
761
  _LOGGER.error(
685
- "%s: Raw callback caused an unhandled exception %s",
762
+ "%s: Raw callback caused an unhandled exception: %s",
686
763
  self.host,
687
764
  err,
688
765
  )
@@ -695,7 +772,7 @@ class DenonAVRTelnetApi:
695
772
  # We don't want a single bad callback to trip up the
696
773
  # whole system and prevent further execution
697
774
  _LOGGER.error(
698
- "%s: Event callback caused an unhandled exception %s",
775
+ "%s: Event callback caused an unhandled exception: %s",
699
776
  self.host,
700
777
  err,
701
778
  )
@@ -708,7 +785,7 @@ class DenonAVRTelnetApi:
708
785
  # We don't want a single bad callback to trip up the
709
786
  # whole system and prevent further execution
710
787
  _LOGGER.error(
711
- "%s: Event callback caused an unhandled exception %s",
788
+ "%s: Event callback caused an unhandled exception: %s",
712
789
  self.host,
713
790
  err,
714
791
  )
@@ -731,35 +808,45 @@ class DenonAVRTelnetApi:
731
808
  self._send_confirmation_event.set()
732
809
  _LOGGER.debug("Command %s confirmed", command)
733
810
 
734
- async def _async_send_command(self, command: str) -> None:
811
+ async def _async_send_command(
812
+ self, command: str, skip_confirmation: bool = False
813
+ ) -> None:
735
814
  """Send one telnet command to the receiver."""
736
815
  async with self._send_lock:
737
- self._send_confirmation_command = command
738
- self._send_confirmation_event.clear()
816
+ if not skip_confirmation:
817
+ self._send_confirmation_command = command
818
+ self._send_confirmation_event.clear()
739
819
  if not self.connected or not self.healthy:
740
820
  raise AvrProcessingError(
741
821
  f"Error sending command {command}. Telnet connected: "
742
822
  f"{self.connected}, Connection healthy: {self.healthy}"
743
823
  )
744
824
  self._protocol.write(f"{command}\r")
745
- try:
746
- await asyncio.wait_for(
747
- self._send_confirmation_event.wait(),
748
- self._send_confirmation_timeout,
749
- )
750
- except asyncio.TimeoutError:
751
- _LOGGER.info("Timeout waiting for confirmation of command: %s", command)
752
- finally:
753
- self._send_confirmation_command = ""
825
+ if not skip_confirmation:
826
+ try:
827
+ await asyncio.wait_for(
828
+ self._send_confirmation_event.wait(),
829
+ self._send_confirmation_timeout,
830
+ )
831
+ except asyncio.TimeoutError:
832
+ _LOGGER.info(
833
+ "Timeout waiting for confirmation of command: %s", command
834
+ )
835
+ finally:
836
+ self._send_confirmation_command = ""
754
837
 
755
- async def async_send_commands(self, *commands: str) -> None:
838
+ async def async_send_commands(
839
+ self, *commands: str, skip_confirmation: bool = False
840
+ ) -> None:
756
841
  """Send telnet commands to the receiver."""
757
842
  for command in commands:
758
- await self._async_send_command(command)
843
+ await self._async_send_command(command, skip_confirmation=skip_confirmation)
759
844
 
760
- def send_commands(self, *commands: str) -> None:
845
+ def send_commands(self, *commands: str, skip_confirmation: bool = False) -> None:
761
846
  """Send telnet commands to the receiver."""
762
- task = asyncio.create_task(self.async_send_commands(*commands))
847
+ task = asyncio.create_task(
848
+ self.async_send_commands(*commands, skip_confirmation=skip_confirmation)
849
+ )
763
850
  self._send_tasks.add(task)
764
851
  task.add_done_callback(self._send_tasks.discard)
765
852
 
denonavr/audyssey.py CHANGED
@@ -99,12 +99,14 @@ class DenonAVRAudyssey(DenonAVRFoundation):
99
99
  self, global_update: bool = False, cache_id: Optional[Hashable] = None
100
100
  ) -> None:
101
101
  """Update Audyssey asynchronously."""
102
+ _LOGGER.debug("Starting Audyssey update")
102
103
  # Ensure instance is setup before updating
103
104
  if not self._is_setup:
104
105
  self.setup()
105
106
 
106
107
  # Update state
107
108
  await self.async_update_audyssey(global_update=global_update, cache_id=cache_id)
109
+ _LOGGER.debug("Finished Audyssey update")
108
110
 
109
111
  async def async_update_audyssey(
110
112
  self, global_update: bool = False, cache_id: Optional[Hashable] = None
@@ -117,13 +119,16 @@ class DenonAVRAudyssey(DenonAVRFoundation):
117
119
 
118
120
  # Audyssey is only available for avr 2016 update
119
121
  if self._device.use_avr_2016_update:
120
- await self.async_update_attrs_appcommand(
121
- self.appcommand0300_attrs,
122
- appcommand0300=True,
123
- global_update=global_update,
124
- cache_id=cache_id,
125
- ignore_missing_response=True,
126
- )
122
+ try:
123
+ await self.async_update_attrs_appcommand(
124
+ self.appcommand0300_attrs,
125
+ appcommand0300=True,
126
+ global_update=global_update,
127
+ cache_id=cache_id,
128
+ )
129
+ except AvrProcessingError as err:
130
+ # Don't raise an error here, because not all devices support it
131
+ _LOGGER.debug("Updating Audyssey failed: %s", err)
127
132
 
128
133
  async def _async_set_audyssey(self, cmd: AppCommandCmd) -> None:
129
134
  """Set Audyssey parameter."""