denonavr 0.11.3__py3-none-any.whl → 0.11.6__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
@@ -19,7 +19,7 @@ from .ssdp import async_identify_denonavr_receivers
19
19
  logging.getLogger(__name__).addHandler(logging.NullHandler())
20
20
 
21
21
  __title__ = "denonavr"
22
- __version__ = "0.11.3"
22
+ __version__ = "0.11.6"
23
23
 
24
24
 
25
25
  async def async_discover():
denonavr/api.py CHANGED
@@ -14,26 +14,30 @@ import sys
14
14
  import time
15
15
  import xml.etree.ElementTree as ET
16
16
  from collections import defaultdict
17
+ from collections.abc import Hashable
17
18
  from io import BytesIO
18
19
  from typing import (
19
20
  Awaitable,
20
21
  Callable,
22
+ Coroutine,
21
23
  DefaultDict,
22
24
  Dict,
23
- Hashable,
24
25
  List,
25
26
  Optional,
27
+ Set,
26
28
  Tuple,
27
29
  cast,
28
30
  )
29
31
 
30
32
  import attr
31
33
  import httpx
32
- from asyncstdlib import lru_cache
33
34
  from defusedxml.ElementTree import fromstring
34
35
 
35
36
  from .appcommand import AppCommandCmd
36
37
  from .const import (
38
+ ALL_TELNET_EVENTS,
39
+ ALL_ZONE_TELNET_EVENTS,
40
+ ALL_ZONES,
37
41
  APPCOMMAND0300_URL,
38
42
  APPCOMMAND_CMD_TEXT,
39
43
  APPCOMMAND_NAME,
@@ -45,15 +49,12 @@ from .const import (
45
49
  ZONE2,
46
50
  ZONE3,
47
51
  )
48
- from .decorators import (
49
- async_handle_receiver_exceptions,
50
- cache_clear_on_exception,
51
- set_cache_id,
52
- )
52
+ from .decorators import async_handle_receiver_exceptions, cache_result
53
53
  from .exceptions import (
54
54
  AvrIncompleteResponseError,
55
55
  AvrInvalidResponseError,
56
56
  AvrNetworkError,
57
+ AvrProcessingError,
57
58
  AvrTimoutError,
58
59
  )
59
60
 
@@ -128,9 +129,7 @@ class DenonAVRApi:
128
129
  # Use default port of the receiver if no different port is specified
129
130
  port = port if port is not None else self.port
130
131
 
131
- endpoint = "http://{host}:{port}{request}".format(
132
- host=self.host, port=port, request=request
133
- )
132
+ endpoint = f"http://{self.host}:{port}{request}"
134
133
 
135
134
  client = self.async_client_getter()
136
135
  try:
@@ -155,9 +154,7 @@ class DenonAVRApi:
155
154
  # Use default port of the receiver if no different port is specified
156
155
  port = port if port is not None else self.port
157
156
 
158
- endpoint = "http://{host}:{port}{request}".format(
159
- host=self.host, port=port, request=request
160
- )
157
+ endpoint = f"http://{self.host}:{port}{request}"
161
158
 
162
159
  client = self.async_client_getter()
163
160
  try:
@@ -180,9 +177,7 @@ class DenonAVRApi:
180
177
  # Return text
181
178
  return res.text
182
179
 
183
- @set_cache_id
184
- @cache_clear_on_exception
185
- @lru_cache(maxsize=32)
180
+ @cache_result
186
181
  @async_handle_receiver_exceptions
187
182
  async def async_get_xml(
188
183
  self, request: str, cache_id: Hashable = None
@@ -197,9 +192,7 @@ class DenonAVRApi:
197
192
  # Return ElementTree element
198
193
  return xml_root
199
194
 
200
- @set_cache_id
201
- @cache_clear_on_exception
202
- @lru_cache(maxsize=32)
195
+ @cache_result
203
196
  @async_handle_receiver_exceptions
204
197
  async def async_post_appcommand(
205
198
  self, request: str, cmds: Tuple[AppCommandCmd], cache_id: Hashable = None
@@ -222,7 +215,7 @@ class DenonAVRApi:
222
215
  def add_appcommand_update_tag(self, tag: AppCommandCmd) -> None:
223
216
  """Add appcommand tag for full update."""
224
217
  if tag.cmd_id != "1":
225
- raise ValueError("cmd_id is {} but must be 1".format(tag.cmd_id))
218
+ raise ValueError(f"cmd_id is {tag.cmd_id} but must be 1")
226
219
 
227
220
  # Remove response pattern from tag because it is not relevant for query
228
221
  tag = attr.evolve(tag, response_pattern=tuple())
@@ -234,7 +227,7 @@ class DenonAVRApi:
234
227
  def add_appcommand0300_update_tag(self, tag: AppCommandCmd) -> None:
235
228
  """Add appcommand0300 tag for full update."""
236
229
  if tag.cmd_id != "3":
237
- raise ValueError("cmd_id is {} but must be 3".format(tag.cmd_id))
230
+ raise ValueError(f"cmd_id is {tag.cmd_id} but must be 3")
238
231
 
239
232
  # Remove response pattern from tag because it is not relevant for query
240
233
  tag = attr.evolve(tag, response_pattern=tuple())
@@ -268,16 +261,20 @@ class DenonAVRApi:
268
261
  """
269
262
  if len(cmd_list) != len(xml_root):
270
263
  raise AvrIncompleteResponseError(
271
- "Invalid length of response XML. Query has {} elements, "
272
- "response {}".format(len(cmd_list), len(xml_root)),
264
+ (
265
+ "Invalid length of response XML. Query has"
266
+ f" {len(cmd_list)} elements, response {len(xml_root)}"
267
+ ),
273
268
  request,
274
269
  )
275
270
 
276
271
  for i, child in enumerate(xml_root):
277
272
  if child.tag not in ["cmd", "error"]:
278
273
  raise AvrInvalidResponseError(
279
- 'Returned document contains a tag other than "cmd" and '
280
- '"error": {}'.format(child.tag),
274
+ (
275
+ 'Returned document contains a tag other than "cmd" and'
276
+ f' "error": {child.tag}'
277
+ ),
281
278
  request,
282
279
  )
283
280
  # Find corresponding attributes from request XML if set and add
@@ -354,7 +351,7 @@ class DenonAVRApi:
354
351
  return body_bytes
355
352
 
356
353
  def is_default_async_client(self) -> bool:
357
- """Check if default httpx.AsyncCLient getter is used."""
354
+ """Check if default httpx.AsyncClient getter is used."""
358
355
  return self.async_client_getter is get_default_async_client
359
356
 
360
357
 
@@ -427,11 +424,28 @@ class DenonAVRTelnetApi:
427
424
  _telnet_event_map: Dict[str, List] = attr.ib(
428
425
  default=attr.Factory(telnet_event_map_factory)
429
426
  )
430
- _callbacks: Dict[str, Callable] = attr.ib(
427
+ _callback_tasks: Set[asyncio.Task] = attr.ib(attr.Factory(set))
428
+ _send_lock: asyncio.Lock = attr.ib(default=attr.Factory(asyncio.Lock))
429
+ _send_confirmation_timeout: float = attr.ib(converter=float, default=2.0)
430
+ _send_confirmation_event: asyncio.Event = attr.ib(
431
+ default=attr.Factory(asyncio.Event)
432
+ )
433
+ _send_confirmation_command: str = attr.ib(converter=str, default="")
434
+ _send_tasks: Set[asyncio.Task] = attr.ib(attr.Factory(set))
435
+ _callbacks: Dict[str, List[Coroutine]] = attr.ib(
431
436
  validator=attr.validators.instance_of(dict),
432
437
  default=attr.Factory(dict),
433
438
  init=False,
434
439
  )
440
+ _raw_callbacks: List[Coroutine] = attr.ib(
441
+ validator=attr.validators.instance_of(list),
442
+ default=attr.Factory(list),
443
+ init=False,
444
+ )
445
+
446
+ def __attrs_post_init__(self) -> None:
447
+ """Initialize special attributes."""
448
+ self._register_raw_callback(self._async_send_confirmation_callback)
435
449
 
436
450
  async def async_connect(self) -> None:
437
451
  """Connect to the receiver asynchronously."""
@@ -457,27 +471,43 @@ class DenonAVRTelnetApi:
457
471
  )
458
472
  except asyncio.TimeoutError as err:
459
473
  _LOGGER.debug("%s: Timeout exception on telnet connect", self.host)
460
- raise AvrTimoutError(
461
- "TimeoutException: {}".format(err), "telnet connect"
462
- ) from err
474
+ raise AvrTimoutError(f"TimeoutException: {err}", "telnet connect") from err
463
475
  except ConnectionRefusedError as err:
464
476
  _LOGGER.debug(
465
477
  "%s: Connection refused on telnet connect", self.host, exc_info=True
466
478
  )
467
479
  raise AvrNetworkError(
468
- "ConnectionRefusedError: {}".format(err), "telnet connect"
480
+ f"ConnectionRefusedError: {err}", "telnet connect"
469
481
  ) from err
470
482
  except (OSError, IOError) as err:
471
483
  _LOGGER.debug(
472
484
  "%s: Connection failed on telnet reconnect", self.host, exc_info=True
473
485
  )
474
- raise AvrNetworkError("OSError: {}".format(err), "telnet connect") from err
486
+ raise AvrNetworkError(f"OSError: {err}", "telnet connect") from err
475
487
  _LOGGER.debug("%s: telnet connection complete", self.host)
476
488
  self._protocol = cast(DenonAVRTelnetProtocol, transport_protocol[1])
477
489
  self._connection_enabled = True
478
490
  self._last_message_time = time.monotonic()
479
491
  self._schedule_monitor()
480
- self._protocol.write("PW?\r")
492
+ # Trigger update of all attributes
493
+ await self.async_send_commands(
494
+ "ZM?",
495
+ "SI?",
496
+ "MV?",
497
+ "MU?",
498
+ "Z2?",
499
+ "Z2MU?",
500
+ "Z3?",
501
+ "Z3MU?",
502
+ "PSTONE CTRL ?",
503
+ "PSBAS ?",
504
+ "PSTRE ?",
505
+ "PSDYNEQ ?",
506
+ "PSMULTEQ: ?",
507
+ "PSREFLEV ?",
508
+ "PSDYNVOL ?",
509
+ "MS?",
510
+ )
481
511
 
482
512
  def _schedule_monitor(self) -> None:
483
513
  """Start the monitor task."""
@@ -568,8 +598,8 @@ class DenonAVRTelnetApi:
568
598
  ) -> None:
569
599
  """Register a callback handler for an event type."""
570
600
  # Validate the passed in type
571
- if event != "ALL" and event not in TELNET_EVENTS:
572
- raise ValueError("{} is not a valid callback type.".format(event))
601
+ if event != ALL_TELNET_EVENTS and event not in TELNET_EVENTS:
602
+ raise ValueError(f"{event} is not a valid callback type.")
573
603
 
574
604
  if event not in self._callbacks.keys():
575
605
  self._callbacks[event] = []
@@ -583,13 +613,24 @@ class DenonAVRTelnetApi:
583
613
  return
584
614
  self._callbacks[event].remove(callback)
585
615
 
616
+ def _register_raw_callback(
617
+ self, callback: Callable[[str], Awaitable[None]]
618
+ ) -> None:
619
+ """Register a callback handler for raw telnet messages."""
620
+ self._raw_callbacks.append(callback)
621
+
622
+ def _unregister_raw_callback(
623
+ self, callback: Callable[[str], Awaitable[None]]
624
+ ) -> None:
625
+ """Unregister a callback handler for raw telnet messages."""
626
+ self._raw_callbacks.remove(callback)
627
+
586
628
  def _process_event(self, message: str) -> None:
587
629
  """Process a realtime event."""
588
630
  _LOGGER.debug("Incoming Telnet message: %s", message)
589
631
  self._last_message_time = time.monotonic()
590
632
  if len(message) < 3:
591
633
  return
592
- zone = MAIN_ZONE
593
634
 
594
635
  # Event is 2 characters
595
636
  event = self._get_event(message)
@@ -603,7 +644,11 @@ class DenonAVRTelnetApi:
603
644
  if parameter[0:3] == "MAX":
604
645
  return
605
646
 
606
- if event in ("Z2", "Z3"):
647
+ # Determine zone
648
+ zone = MAIN_ZONE
649
+ if event in ALL_ZONE_TELNET_EVENTS:
650
+ zone = ALL_ZONES
651
+ elif event in {"Z2", "Z3"}:
607
652
  if event == "Z2":
608
653
  zone = ZONE2
609
654
  else:
@@ -620,10 +665,28 @@ class DenonAVRTelnetApi:
620
665
  if event not in TELNET_EVENTS:
621
666
  return
622
667
 
623
- asyncio.create_task(self._async_run_callbacks(event, zone, parameter))
668
+ task = asyncio.create_task(
669
+ self._async_run_callbacks(message, event, zone, parameter)
670
+ )
671
+ self._callback_tasks.add(task)
672
+ task.add_done_callback(self._callback_tasks.discard)
673
+
674
+ async def _async_run_callbacks(
675
+ self, message: str, event: str, zone: str, parameter: str
676
+ ) -> None:
677
+ """Handle triggering the registered callbacks."""
678
+ for callback in self._raw_callbacks:
679
+ try:
680
+ await callback(message)
681
+ except Exception as err: # pylint: disable=broad-except
682
+ # We don't want a single bad callback to trip up the
683
+ # whole system and prevent further execution
684
+ _LOGGER.error(
685
+ "%s: Raw callback caused an unhandled exception %s",
686
+ self.host,
687
+ err,
688
+ )
624
689
 
625
- async def _async_run_callbacks(self, event: str, zone: str, parameter: str) -> None:
626
- """Handle triggering the registered callbacks for the event."""
627
690
  if event in self._callbacks.keys():
628
691
  for callback in self._callbacks[event]:
629
692
  try:
@@ -637,8 +700,8 @@ class DenonAVRTelnetApi:
637
700
  err,
638
701
  )
639
702
 
640
- if "ALL" in self._callbacks.keys():
641
- for callback in self._callbacks["ALL"]:
703
+ if ALL_TELNET_EVENTS in self._callbacks.keys():
704
+ for callback in self._callbacks[ALL_TELNET_EVENTS]:
642
705
  try:
643
706
  await callback(zone, event, parameter)
644
707
  except Exception as err: # pylint: disable=broad-except
@@ -658,15 +721,47 @@ class DenonAVRTelnetApi:
658
721
  return event
659
722
  return ""
660
723
 
661
- def send_commands(self, *commands: str) -> bool:
724
+ async def _async_send_confirmation_callback(self, message: str) -> None:
725
+ """Confirm that the telnet command has been executed."""
726
+ if len(message) < 3:
727
+ return
728
+ command = self._send_confirmation_command
729
+ if self._get_event(message) == self._get_event(self._send_confirmation_command):
730
+ self._send_confirmation_command = ""
731
+ self._send_confirmation_event.set()
732
+ _LOGGER.debug("Command %s confirmed", command)
733
+
734
+ async def _async_send_command(self, command: str) -> None:
735
+ """Send one telnet command to the receiver."""
736
+ async with self._send_lock:
737
+ self._send_confirmation_command = command
738
+ self._send_confirmation_event.clear()
739
+ if not self.connected or not self.healthy:
740
+ raise AvrProcessingError(
741
+ f"Error sending command {command}. Telnet connected: "
742
+ f"{self.connected}, Connection healthy: {self.healthy}"
743
+ )
744
+ 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 = ""
754
+
755
+ async def async_send_commands(self, *commands: str) -> None:
662
756
  """Send telnet commands to the receiver."""
663
- if not self.connected:
664
- return False
665
- if not self.healthy:
666
- return False
667
757
  for command in commands:
668
- self._protocol.write("{}\r".format(command))
669
- return True
758
+ await self._async_send_command(command)
759
+
760
+ def send_commands(self, *commands: str) -> None:
761
+ """Send telnet commands to the receiver."""
762
+ task = asyncio.create_task(self.async_send_commands(*commands))
763
+ self._send_tasks.add(task)
764
+ task.add_done_callback(self._send_tasks.discard)
670
765
 
671
766
  ##############
672
767
  # Properties #
denonavr/audyssey.py CHANGED
@@ -8,7 +8,8 @@ This module implements the Audyssey settings of Denon AVR receivers.
8
8
  """
9
9
 
10
10
  import logging
11
- from typing import Hashable, List, Optional
11
+ from collections.abc import Hashable
12
+ from typing import List, Optional
12
13
 
13
14
  import attr
14
15
 
@@ -16,11 +17,14 @@ from .appcommand import AppCommandCmd, AppCommandCmdParam, AppCommands
16
17
  from .const import (
17
18
  DENON_ATTR_SETATTR,
18
19
  DYNAMIC_VOLUME_MAP,
19
- DYNAMIC_VOLUME_MAP_LABELS,
20
+ DYNAMIC_VOLUME_MAP_LABELS_APPCOMMAND,
21
+ DYNAMIC_VOLUME_MAP_LABELS_TELNET,
20
22
  MULTI_EQ_MAP,
21
- MULTI_EQ_MAP_LABELS,
23
+ MULTI_EQ_MAP_LABELS_APPCOMMAND,
24
+ MULTI_EQ_MAP_LABELS_TELNET,
22
25
  REF_LVL_OFFSET_MAP,
23
- REF_LVL_OFFSET_MAP_LABELS,
26
+ REF_LVL_OFFSET_MAP_LABELS_APPCOMMAND,
27
+ REF_LVL_OFFSET_MAP_LABELS_TELNET,
24
28
  )
25
29
  from .exceptions import AvrCommandError, AvrProcessingError
26
30
  from .foundation import DenonAVRFoundation, convert_string_int_bool
@@ -129,13 +133,9 @@ class DenonAVRAudyssey(DenonAVRFoundation):
129
133
 
130
134
  try:
131
135
  if res.find("cmd").text != "OK":
132
- raise AvrProcessingError(
133
- "SetAudyssey command {} failed".format(cmd.name)
134
- )
136
+ raise AvrProcessingError(f"SetAudyssey command {cmd.name} failed")
135
137
  except AttributeError as err:
136
- raise AvrProcessingError(
137
- "SetAudyssey command {} failed".format(cmd.name)
138
- ) from err
138
+ raise AvrProcessingError(f"SetAudyssey command {cmd.name} failed") from err
139
139
 
140
140
  ##############
141
141
  # Properties #
@@ -153,7 +153,9 @@ class DenonAVRAudyssey(DenonAVRFoundation):
153
153
  @property
154
154
  def reference_level_offset_setting_list(self) -> List[str]:
155
155
  """Return a list of available reference level offset settings."""
156
- return list(REF_LVL_OFFSET_MAP_LABELS.keys())
156
+ if self._device.telnet_available:
157
+ return list(REF_LVL_OFFSET_MAP_LABELS_TELNET.keys())
158
+ return list(REF_LVL_OFFSET_MAP_LABELS_APPCOMMAND.keys())
157
159
 
158
160
  @property
159
161
  def dynamic_volume(self) -> Optional[str]:
@@ -163,7 +165,9 @@ class DenonAVRAudyssey(DenonAVRFoundation):
163
165
  @property
164
166
  def dynamic_volume_setting_list(self) -> List[str]:
165
167
  """Return a list of available Dynamic Volume settings."""
166
- return list(DYNAMIC_VOLUME_MAP_LABELS.keys())
168
+ if self._device.telnet_available:
169
+ return list(DYNAMIC_VOLUME_MAP_LABELS_TELNET.keys())
170
+ return list(DYNAMIC_VOLUME_MAP_LABELS_APPCOMMAND.keys())
167
171
 
168
172
  @property
169
173
  def multi_eq(self) -> Optional[str]:
@@ -173,13 +177,19 @@ class DenonAVRAudyssey(DenonAVRFoundation):
173
177
  @property
174
178
  def multi_eq_setting_list(self) -> List[str]:
175
179
  """Return a list of available MultiEQ settings."""
176
- return list(MULTI_EQ_MAP_LABELS.keys())
180
+ if self._device.telnet_available:
181
+ return list(MULTI_EQ_MAP_LABELS_TELNET.keys())
182
+ return list(MULTI_EQ_MAP_LABELS_APPCOMMAND.keys())
177
183
 
178
184
  ##########
179
185
  # Setter #
180
186
  ##########
181
187
  async def async_dynamiceq_off(self) -> None:
182
188
  """Turn DynamicEQ off."""
189
+ if self._device.telnet_available:
190
+ telnet_command = self._device.telnet_commands.command_dynamiceq + "OFF"
191
+ await self._device.telnet_api.async_send_commands(telnet_command)
192
+ return
183
193
  cmd = attr.evolve(
184
194
  AppCommands.SetAudysseyDynamicEQ,
185
195
  param_list=(AppCommandCmdParam(name="dynamiceq", text=0),),
@@ -188,6 +198,10 @@ class DenonAVRAudyssey(DenonAVRFoundation):
188
198
 
189
199
  async def async_dynamiceq_on(self) -> None:
190
200
  """Turn DynamicEQ on."""
201
+ if self._device.telnet_available:
202
+ telnet_command = self._device.telnet_commands.command_dynamiceq + "ON"
203
+ await self._device.telnet_api.async_send_commands(telnet_command)
204
+ return
191
205
  cmd = attr.evolve(
192
206
  AppCommands.SetAudysseyDynamicEQ,
193
207
  param_list=(AppCommandCmdParam(name="dynamiceq", text=1),),
@@ -196,9 +210,17 @@ class DenonAVRAudyssey(DenonAVRFoundation):
196
210
 
197
211
  async def async_set_multieq(self, value: str) -> None:
198
212
  """Set MultiEQ mode."""
199
- setting = MULTI_EQ_MAP_LABELS.get(value)
213
+ if self._device.telnet_available:
214
+ setting = MULTI_EQ_MAP_LABELS_TELNET.get(value)
215
+ if setting is None:
216
+ raise AvrCommandError(f"Value {value} not known for MultiEQ")
217
+ telnet_command = self._device.telnet_commands.command_multieq + setting
218
+ await self._device.telnet_api.async_send_commands(telnet_command)
219
+ return
220
+
221
+ setting = MULTI_EQ_MAP_LABELS_APPCOMMAND.get(value)
200
222
  if setting is None:
201
- raise AvrCommandError("Value {} not known for MultiEQ".format(value))
223
+ raise AvrCommandError(f"Value {value} not known for MultiEQ")
202
224
  cmd = attr.evolve(
203
225
  AppCommands.SetAudysseyMultiEQ,
204
226
  param_list=(AppCommandCmdParam(name="multeq", text=setting),),
@@ -212,11 +234,19 @@ class DenonAVRAudyssey(DenonAVRFoundation):
212
234
  raise AvrCommandError(
213
235
  "Reference level could only be set when DynamicEQ is active"
214
236
  )
215
- setting = REF_LVL_OFFSET_MAP_LABELS.get(value)
237
+ if self._device.telnet_available:
238
+ setting = REF_LVL_OFFSET_MAP_LABELS_TELNET.get(value)
239
+ if setting is None:
240
+ raise AvrCommandError(
241
+ f"Value {value} not known for Reference level offset"
242
+ )
243
+ telnet_command = self._device.telnet_commands.command_reflevoffset + setting
244
+ await self._device.telnet_api.async_send_commands(telnet_command)
245
+ return
246
+
247
+ setting = REF_LVL_OFFSET_MAP_LABELS_APPCOMMAND.get(value)
216
248
  if setting is None:
217
- raise AvrCommandError(
218
- "Value {} not known for Reference level offset".format(value)
219
- )
249
+ raise AvrCommandError(f"Value {value} not known for Reference level offset")
220
250
  cmd = attr.evolve(
221
251
  AppCommands.SetAudysseyReflevoffset,
222
252
  param_list=(AppCommandCmdParam(name="reflevoffset", text=setting),),
@@ -225,9 +255,17 @@ class DenonAVRAudyssey(DenonAVRFoundation):
225
255
 
226
256
  async def async_set_dynamicvol(self, value: str) -> None:
227
257
  """Set Dynamic Volume."""
228
- setting = DYNAMIC_VOLUME_MAP_LABELS.get(value)
258
+ if self._device.telnet_available:
259
+ setting = DYNAMIC_VOLUME_MAP_LABELS_TELNET.get(value)
260
+ if setting is None:
261
+ raise AvrCommandError(f"Value {value} not known for Dynamic Volume")
262
+ telnet_command = self._device.telnet_commands.command_dynamicvol + setting
263
+ await self._device.telnet_api.async_send_commands(telnet_command)
264
+ return
265
+
266
+ setting = DYNAMIC_VOLUME_MAP_LABELS_APPCOMMAND.get(value)
229
267
  if setting is None:
230
- raise AvrCommandError("Value {} not known for Dynamic Volume".format(value))
268
+ raise AvrCommandError(f"Value {value} not known for Dynamic Volume")
231
269
  cmd = attr.evolve(
232
270
  AppCommands.SetAudysseyDynamicvol,
233
271
  param_list=(AppCommandCmdParam(name="dynamicvol", text=setting),),