denonavr 0.11.2__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.2"
22
+ __version__ = "0.11.6"
23
23
 
24
24
 
25
25
  async def async_discover():
denonavr/api.py CHANGED
@@ -13,16 +13,31 @@ import logging
13
13
  import sys
14
14
  import time
15
15
  import xml.etree.ElementTree as ET
16
+ from collections import defaultdict
17
+ from collections.abc import Hashable
16
18
  from io import BytesIO
17
- from typing import Awaitable, Callable, Dict, Hashable, Optional, Tuple, cast
19
+ from typing import (
20
+ Awaitable,
21
+ Callable,
22
+ Coroutine,
23
+ DefaultDict,
24
+ Dict,
25
+ List,
26
+ Optional,
27
+ Set,
28
+ Tuple,
29
+ cast,
30
+ )
18
31
 
19
32
  import attr
20
33
  import httpx
21
- from asyncstdlib import lru_cache
22
34
  from defusedxml.ElementTree import fromstring
23
35
 
24
36
  from .appcommand import AppCommandCmd
25
37
  from .const import (
38
+ ALL_TELNET_EVENTS,
39
+ ALL_ZONE_TELNET_EVENTS,
40
+ ALL_ZONES,
26
41
  APPCOMMAND0300_URL,
27
42
  APPCOMMAND_CMD_TEXT,
28
43
  APPCOMMAND_NAME,
@@ -34,15 +49,12 @@ from .const import (
34
49
  ZONE2,
35
50
  ZONE3,
36
51
  )
37
- from .decorators import (
38
- async_handle_receiver_exceptions,
39
- cache_clear_on_exception,
40
- set_cache_id,
41
- )
52
+ from .decorators import async_handle_receiver_exceptions, cache_result
42
53
  from .exceptions import (
43
54
  AvrIncompleteResponseError,
44
55
  AvrInvalidResponseError,
45
56
  AvrNetworkError,
57
+ AvrProcessingError,
46
58
  AvrTimoutError,
47
59
  )
48
60
 
@@ -61,6 +73,16 @@ def get_default_async_client() -> httpx.AsyncClient:
61
73
  return httpx.AsyncClient()
62
74
 
63
75
 
76
+ def telnet_event_map_factory() -> Dict[str, List]:
77
+ """Create telnet event map."""
78
+ event_map: DefaultDict[str, List] = defaultdict(list)
79
+ for event in TELNET_EVENTS:
80
+ event_map[event[0:2]].append(event)
81
+ for value in event_map.values():
82
+ value.sort(key=len, reverse=True)
83
+ return dict(event_map)
84
+
85
+
64
86
  @attr.s(auto_attribs=True, hash=False, on_setattr=DENON_ATTR_SETATTR)
65
87
  class DenonAVRApi:
66
88
  """Perform API calls to Denon AVR REST interface."""
@@ -107,9 +129,7 @@ class DenonAVRApi:
107
129
  # Use default port of the receiver if no different port is specified
108
130
  port = port if port is not None else self.port
109
131
 
110
- endpoint = "http://{host}:{port}{request}".format(
111
- host=self.host, port=port, request=request
112
- )
132
+ endpoint = f"http://{self.host}:{port}{request}"
113
133
 
114
134
  client = self.async_client_getter()
115
135
  try:
@@ -134,9 +154,7 @@ class DenonAVRApi:
134
154
  # Use default port of the receiver if no different port is specified
135
155
  port = port if port is not None else self.port
136
156
 
137
- endpoint = "http://{host}:{port}{request}".format(
138
- host=self.host, port=port, request=request
139
- )
157
+ endpoint = f"http://{self.host}:{port}{request}"
140
158
 
141
159
  client = self.async_client_getter()
142
160
  try:
@@ -159,9 +177,7 @@ class DenonAVRApi:
159
177
  # Return text
160
178
  return res.text
161
179
 
162
- @set_cache_id
163
- @cache_clear_on_exception
164
- @lru_cache(maxsize=32)
180
+ @cache_result
165
181
  @async_handle_receiver_exceptions
166
182
  async def async_get_xml(
167
183
  self, request: str, cache_id: Hashable = None
@@ -176,9 +192,7 @@ class DenonAVRApi:
176
192
  # Return ElementTree element
177
193
  return xml_root
178
194
 
179
- @set_cache_id
180
- @cache_clear_on_exception
181
- @lru_cache(maxsize=32)
195
+ @cache_result
182
196
  @async_handle_receiver_exceptions
183
197
  async def async_post_appcommand(
184
198
  self, request: str, cmds: Tuple[AppCommandCmd], cache_id: Hashable = None
@@ -201,7 +215,7 @@ class DenonAVRApi:
201
215
  def add_appcommand_update_tag(self, tag: AppCommandCmd) -> None:
202
216
  """Add appcommand tag for full update."""
203
217
  if tag.cmd_id != "1":
204
- 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")
205
219
 
206
220
  # Remove response pattern from tag because it is not relevant for query
207
221
  tag = attr.evolve(tag, response_pattern=tuple())
@@ -213,7 +227,7 @@ class DenonAVRApi:
213
227
  def add_appcommand0300_update_tag(self, tag: AppCommandCmd) -> None:
214
228
  """Add appcommand0300 tag for full update."""
215
229
  if tag.cmd_id != "3":
216
- 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")
217
231
 
218
232
  # Remove response pattern from tag because it is not relevant for query
219
233
  tag = attr.evolve(tag, response_pattern=tuple())
@@ -247,16 +261,20 @@ class DenonAVRApi:
247
261
  """
248
262
  if len(cmd_list) != len(xml_root):
249
263
  raise AvrIncompleteResponseError(
250
- "Invalid length of response XML. Query has {} elements, "
251
- "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
+ ),
252
268
  request,
253
269
  )
254
270
 
255
271
  for i, child in enumerate(xml_root):
256
272
  if child.tag not in ["cmd", "error"]:
257
273
  raise AvrInvalidResponseError(
258
- 'Returned document contains a tag other than "cmd" and '
259
- '"error": {}'.format(child.tag),
274
+ (
275
+ 'Returned document contains a tag other than "cmd" and'
276
+ f' "error": {child.tag}'
277
+ ),
260
278
  request,
261
279
  )
262
280
  # Find corresponding attributes from request XML if set and add
@@ -333,7 +351,7 @@ class DenonAVRApi:
333
351
  return body_bytes
334
352
 
335
353
  def is_default_async_client(self) -> bool:
336
- """Check if default httpx.AsyncCLient getter is used."""
354
+ """Check if default httpx.AsyncClient getter is used."""
337
355
  return self.async_client_getter is get_default_async_client
338
356
 
339
357
 
@@ -352,12 +370,17 @@ class DenonAVRTelnetProtocol(asyncio.Protocol):
352
370
  @property
353
371
  def connected(self) -> bool:
354
372
  """Return True if transport is connected."""
355
- return self.transport is not None
373
+ if self.transport is None:
374
+ return False
375
+ return not self.transport.is_closing()
356
376
 
357
377
  def write(self, data: str) -> None:
358
378
  """Write data to the transport."""
359
- if self.transport is not None:
360
- self.transport.write(data.encode("utf-8"))
379
+ if self.transport is None:
380
+ return
381
+ if self.transport.is_closing():
382
+ return
383
+ self.transport.write(data.encode("utf-8"))
361
384
 
362
385
  def close(self) -> None:
363
386
  """Close the connection."""
@@ -398,17 +421,37 @@ class DenonAVRTelnetApi:
398
421
  _reconnect_task: asyncio.Task = attr.ib(default=None)
399
422
  _monitor_handle: asyncio.TimerHandle = attr.ib(default=None)
400
423
  _protocol: DenonAVRTelnetProtocol = attr.ib(default=None)
401
- _callbacks: Dict[str, Callable] = attr.ib(
424
+ _telnet_event_map: Dict[str, List] = attr.ib(
425
+ default=attr.Factory(telnet_event_map_factory)
426
+ )
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(
402
436
  validator=attr.validators.instance_of(dict),
403
437
  default=attr.Factory(dict),
404
438
  init=False,
405
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)
406
449
 
407
450
  async def async_connect(self) -> None:
408
451
  """Connect to the receiver asynchronously."""
409
452
  _LOGGER.debug("%s: telnet connecting", self.host)
410
453
  async with self._connect_lock:
411
- if self.connected is True:
454
+ if self.connected:
412
455
  return
413
456
  await self._async_establish_connection()
414
457
 
@@ -428,27 +471,43 @@ class DenonAVRTelnetApi:
428
471
  )
429
472
  except asyncio.TimeoutError as err:
430
473
  _LOGGER.debug("%s: Timeout exception on telnet connect", self.host)
431
- raise AvrTimoutError(
432
- "TimeoutException: {}".format(err), "telnet connect"
433
- ) from err
474
+ raise AvrTimoutError(f"TimeoutException: {err}", "telnet connect") from err
434
475
  except ConnectionRefusedError as err:
435
476
  _LOGGER.debug(
436
477
  "%s: Connection refused on telnet connect", self.host, exc_info=True
437
478
  )
438
479
  raise AvrNetworkError(
439
- "ConnectionRefusedError: {}".format(err), "telnet connect"
480
+ f"ConnectionRefusedError: {err}", "telnet connect"
440
481
  ) from err
441
482
  except (OSError, IOError) as err:
442
483
  _LOGGER.debug(
443
484
  "%s: Connection failed on telnet reconnect", self.host, exc_info=True
444
485
  )
445
- raise AvrNetworkError("OSError: {}".format(err), "telnet connect") from err
486
+ raise AvrNetworkError(f"OSError: {err}", "telnet connect") from err
446
487
  _LOGGER.debug("%s: telnet connection complete", self.host)
447
488
  self._protocol = cast(DenonAVRTelnetProtocol, transport_protocol[1])
448
489
  self._connection_enabled = True
449
490
  self._last_message_time = time.monotonic()
450
491
  self._schedule_monitor()
451
- 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
+ )
452
511
 
453
512
  def _schedule_monitor(self) -> None:
454
513
  """Start the monitor task."""
@@ -468,7 +527,7 @@ class DenonAVRTelnetApi:
468
527
  _LOGGER.info(
469
528
  "%s: Keep alive failed, disconnecting and reconnecting", self.host
470
529
  )
471
- if self._protocol:
530
+ if self._protocol is not None:
472
531
  self._protocol.close()
473
532
  self._handle_disconnected()
474
533
  return
@@ -511,7 +570,7 @@ class DenonAVRTelnetApi:
511
570
  """Reconnect to the receiver asynchronously."""
512
571
  backoff = 0.5
513
572
 
514
- while self._connection_enabled is True and not self.healthy:
573
+ while self._connection_enabled and not self.healthy:
515
574
  async with self._connect_lock:
516
575
  try:
517
576
  await self._async_establish_connection()
@@ -539,8 +598,8 @@ class DenonAVRTelnetApi:
539
598
  ) -> None:
540
599
  """Register a callback handler for an event type."""
541
600
  # Validate the passed in type
542
- if event != "ALL" and event not in TELNET_EVENTS:
543
- 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.")
544
603
 
545
604
  if event not in self._callbacks.keys():
546
605
  self._callbacks[event] = []
@@ -554,18 +613,29 @@ class DenonAVRTelnetApi:
554
613
  return
555
614
  self._callbacks[event].remove(callback)
556
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
+
557
628
  def _process_event(self, message: str) -> None:
558
629
  """Process a realtime event."""
559
630
  _LOGGER.debug("Incoming Telnet message: %s", message)
560
631
  self._last_message_time = time.monotonic()
561
632
  if len(message) < 3:
562
633
  return
563
- zone = MAIN_ZONE
564
634
 
565
635
  # Event is 2 characters
566
- event = message[0:2]
636
+ event = self._get_event(message)
567
637
  # Parameter is the remaining characters
568
- parameter = message[2:]
638
+ parameter = message[len(event) :]
569
639
 
570
640
  if event == "MV":
571
641
  # This seems undocumented by Denon and appears to basically be a
@@ -574,29 +644,49 @@ class DenonAVRTelnetApi:
574
644
  if parameter[0:3] == "MAX":
575
645
  return
576
646
 
577
- 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"}:
578
652
  if event == "Z2":
579
653
  zone = ZONE2
580
654
  else:
581
655
  zone = ZONE3
582
656
 
583
- if parameter in ("ON", "OFF"):
584
- event = "PW"
585
- elif parameter in TELNET_SOURCES:
657
+ if parameter in TELNET_SOURCES:
586
658
  event = "SI"
587
659
  elif parameter.isdigit():
588
660
  event = "MV"
589
- elif parameter[0:2] in TELNET_EVENTS:
590
- event = parameter[0:2]
591
- parameter = parameter[2:]
661
+ elif self._get_event(parameter):
662
+ event = self._get_event(parameter)
663
+ parameter = parameter[len(event) :]
592
664
 
593
665
  if event not in TELNET_EVENTS:
594
666
  return
595
667
 
596
- 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
+ )
597
689
 
598
- async def _async_run_callbacks(self, event: str, zone: str, parameter: str) -> None:
599
- """Handle triggering the registered callbacks for the event."""
600
690
  if event in self._callbacks.keys():
601
691
  for callback in self._callbacks[event]:
602
692
  try:
@@ -610,8 +700,8 @@ class DenonAVRTelnetApi:
610
700
  err,
611
701
  )
612
702
 
613
- if "ALL" in self._callbacks.keys():
614
- for callback in self._callbacks["ALL"]:
703
+ if ALL_TELNET_EVENTS in self._callbacks.keys():
704
+ for callback in self._callbacks[ALL_TELNET_EVENTS]:
615
705
  try:
616
706
  await callback(zone, event, parameter)
617
707
  except Exception as err: # pylint: disable=broad-except
@@ -623,10 +713,55 @@ class DenonAVRTelnetApi:
623
713
  err,
624
714
  )
625
715
 
626
- def send_commands(self, *commands: str) -> None:
716
+ def _get_event(self, message: str) -> str:
717
+ """Get event of a telnet message."""
718
+ events = self._telnet_event_map.get(message[0:2], [""])
719
+ for event in events:
720
+ if message.startswith(event):
721
+ return event
722
+ return ""
723
+
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:
627
756
  """Send telnet commands to the receiver."""
628
757
  for command in commands:
629
- self._protocol.write("{}\r".format(command))
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)
630
765
 
631
766
  ##############
632
767
  # Properties #
@@ -637,6 +772,6 @@ class DenonAVRTelnetApi:
637
772
  return self._connection_enabled
638
773
 
639
774
  @property
640
- def healthy(self) -> Optional[bool]:
775
+ def healthy(self) -> bool:
641
776
  """Return True if telnet connection is healthy."""
642
777
  return self._protocol is not None and self._protocol.connected
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
@@ -96,7 +100,7 @@ class DenonAVRAudyssey(DenonAVRFoundation):
96
100
  ) -> None:
97
101
  """Update Audyssey asynchronously."""
98
102
  # Ensure instance is setup before updating
99
- if self._is_setup is False:
103
+ if not self._is_setup:
100
104
  self.setup()
101
105
 
102
106
  # Update state
@@ -106,7 +110,13 @@ class DenonAVRAudyssey(DenonAVRFoundation):
106
110
  self, global_update: bool = False, cache_id: Optional[Hashable] = None
107
111
  ):
108
112
  """Update Audyssey status of device."""
109
- if self._device.use_avr_2016_update is True:
113
+ if self._device.use_avr_2016_update is None:
114
+ raise AvrProcessingError(
115
+ "Device is not setup correctly, update method not set"
116
+ )
117
+
118
+ # Audyssey is only available for avr 2016 update
119
+ if self._device.use_avr_2016_update:
110
120
  await self.async_update_attrs_appcommand(
111
121
  self.appcommand0300_attrs,
112
122
  appcommand0300=True,
@@ -114,13 +124,6 @@ class DenonAVRAudyssey(DenonAVRFoundation):
114
124
  cache_id=cache_id,
115
125
  ignore_missing_response=True,
116
126
  )
117
- elif self._device.use_avr_2016_update is False:
118
- # Not available
119
- pass
120
- else:
121
- raise AvrProcessingError(
122
- "Device is not setup correctly, update method not set"
123
- )
124
127
 
125
128
  async def _async_set_audyssey(self, cmd: AppCommandCmd) -> None:
126
129
  """Set Audyssey parameter."""
@@ -130,13 +133,9 @@ class DenonAVRAudyssey(DenonAVRFoundation):
130
133
 
131
134
  try:
132
135
  if res.find("cmd").text != "OK":
133
- raise AvrProcessingError(
134
- "SetAudyssey command {} failed".format(cmd.name)
135
- )
136
+ raise AvrProcessingError(f"SetAudyssey command {cmd.name} failed")
136
137
  except AttributeError as err:
137
- raise AvrProcessingError(
138
- "SetAudyssey command {} failed".format(cmd.name)
139
- ) from err
138
+ raise AvrProcessingError(f"SetAudyssey command {cmd.name} failed") from err
140
139
 
141
140
  ##############
142
141
  # Properties #
@@ -154,7 +153,9 @@ class DenonAVRAudyssey(DenonAVRFoundation):
154
153
  @property
155
154
  def reference_level_offset_setting_list(self) -> List[str]:
156
155
  """Return a list of available reference level offset settings."""
157
- 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())
158
159
 
159
160
  @property
160
161
  def dynamic_volume(self) -> Optional[str]:
@@ -164,7 +165,9 @@ class DenonAVRAudyssey(DenonAVRFoundation):
164
165
  @property
165
166
  def dynamic_volume_setting_list(self) -> List[str]:
166
167
  """Return a list of available Dynamic Volume settings."""
167
- 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())
168
171
 
169
172
  @property
170
173
  def multi_eq(self) -> Optional[str]:
@@ -174,13 +177,19 @@ class DenonAVRAudyssey(DenonAVRFoundation):
174
177
  @property
175
178
  def multi_eq_setting_list(self) -> List[str]:
176
179
  """Return a list of available MultiEQ settings."""
177
- 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())
178
183
 
179
184
  ##########
180
185
  # Setter #
181
186
  ##########
182
187
  async def async_dynamiceq_off(self) -> None:
183
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
184
193
  cmd = attr.evolve(
185
194
  AppCommands.SetAudysseyDynamicEQ,
186
195
  param_list=(AppCommandCmdParam(name="dynamiceq", text=0),),
@@ -189,6 +198,10 @@ class DenonAVRAudyssey(DenonAVRFoundation):
189
198
 
190
199
  async def async_dynamiceq_on(self) -> None:
191
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
192
205
  cmd = attr.evolve(
193
206
  AppCommands.SetAudysseyDynamicEQ,
194
207
  param_list=(AppCommandCmdParam(name="dynamiceq", text=1),),
@@ -197,9 +210,17 @@ class DenonAVRAudyssey(DenonAVRFoundation):
197
210
 
198
211
  async def async_set_multieq(self, value: str) -> None:
199
212
  """Set MultiEQ mode."""
200
- 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)
201
222
  if setting is None:
202
- raise AvrCommandError("Value {} not known for MultiEQ".format(value))
223
+ raise AvrCommandError(f"Value {value} not known for MultiEQ")
203
224
  cmd = attr.evolve(
204
225
  AppCommands.SetAudysseyMultiEQ,
205
226
  param_list=(AppCommandCmdParam(name="multeq", text=setting),),
@@ -209,15 +230,23 @@ class DenonAVRAudyssey(DenonAVRFoundation):
209
230
  async def async_set_reflevoffset(self, value: str) -> None:
210
231
  """Set Reference Level Offset."""
211
232
  # Reference level offset can only be used with DynamicEQ
212
- if self._dynamiceq is False:
233
+ if not self._dynamiceq:
213
234
  raise AvrCommandError(
214
235
  "Reference level could only be set when DynamicEQ is active"
215
236
  )
216
- 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)
217
248
  if setting is None:
218
- raise AvrCommandError(
219
- "Value {} not known for Reference level offset".format(value)
220
- )
249
+ raise AvrCommandError(f"Value {value} not known for Reference level offset")
221
250
  cmd = attr.evolve(
222
251
  AppCommands.SetAudysseyReflevoffset,
223
252
  param_list=(AppCommandCmdParam(name="reflevoffset", text=setting),),
@@ -226,9 +255,17 @@ class DenonAVRAudyssey(DenonAVRFoundation):
226
255
 
227
256
  async def async_set_dynamicvol(self, value: str) -> None:
228
257
  """Set Dynamic Volume."""
229
- 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)
230
267
  if setting is None:
231
- raise AvrCommandError("Value {} not known for Dynamic Volume".format(value))
268
+ raise AvrCommandError(f"Value {value} not known for Dynamic Volume")
232
269
  cmd = attr.evolve(
233
270
  AppCommands.SetAudysseyDynamicvol,
234
271
  param_list=(AppCommandCmdParam(name="dynamicvol", text=setting),),
@@ -237,7 +274,7 @@ class DenonAVRAudyssey(DenonAVRFoundation):
237
274
 
238
275
  async def async_toggle_dynamic_eq(self) -> None:
239
276
  """Toggle DynamicEQ."""
240
- if self._dynamiceq is True:
277
+ if self._dynamiceq:
241
278
  await self.async_dynamiceq_off()
242
279
  else:
243
280
  await self.async_dynamiceq_on()