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/const.py CHANGED
@@ -61,6 +61,13 @@ TelnetCommands = namedtuple(
61
61
  "command_set_all_zone_stereo",
62
62
  "command_pause",
63
63
  "command_play",
64
+ "command_multieq",
65
+ "command_dynamiceq",
66
+ "command_reflevoffset",
67
+ "command_dynamicvol",
68
+ "command_tonecontrol",
69
+ "command_bass",
70
+ "command_treble",
64
71
  ],
65
72
  )
66
73
 
@@ -113,7 +120,7 @@ CHANGE_INPUT_MAPPING = {
113
120
  "Spotify": "SPOTIFY",
114
121
  }
115
122
 
116
- TELNET_SOURCES = [
123
+ TELNET_SOURCES = {
117
124
  "CD",
118
125
  "PHONO",
119
126
  "TUNER",
@@ -145,7 +152,7 @@ TELNET_SOURCES = [
145
152
  "USB/IPOD",
146
153
  "USB DIRECT",
147
154
  "IPOD DIRECT",
148
- ]
155
+ }
149
156
 
150
157
  TELNET_MAPPING = {
151
158
  "FAVORITES": "Favorites",
@@ -186,6 +193,7 @@ SOUND_MODE_MAPPING = {
186
193
  "DOLBY PL2 CINEMA",
187
194
  "DOLBY PL2 C",
188
195
  "DOLBY PL2 X MOVIE",
196
+ "DOLBY PL2 MOVIE",
189
197
  ],
190
198
  "GAME": [
191
199
  "PLII GAME",
@@ -193,6 +201,7 @@ SOUND_MODE_MAPPING = {
193
201
  "DOLBY PL2 GAME",
194
202
  "DOLBY PL2 G",
195
203
  "DOLBY PL2 X GAME",
204
+ "DOLBY PLII GAME",
196
205
  ],
197
206
  "AUTO": ["None"],
198
207
  "STANDARD": ["None2"],
@@ -208,11 +217,13 @@ SOUND_MODE_MAPPING = {
208
217
  "DOLBY DIGITAL",
209
218
  "DOLBY D + DOLBY SURROUND",
210
219
  "DOLBY D+DS",
220
+ "DOLBY D+ +DS",
211
221
  "DOLBY DIGITAL +",
212
222
  "STANDARD(DOLBY)",
213
223
  "DOLBY SURROUND",
214
224
  "DOLBY D + +DOLBY SURROUND",
215
225
  "NEURAL",
226
+ "NEURAL:X",
216
227
  "DOLBY HD",
217
228
  "DOLBY HD + DOLBY SURROUND",
218
229
  "MULTI IN + DSUR",
@@ -228,12 +239,15 @@ SOUND_MODE_MAPPING = {
228
239
  "DOLBY AUDIO - TRUEHD + DSUR",
229
240
  "DOLBY AUDIO - DOLBY TRUEHD",
230
241
  "DOLBY AUDIO - TRUEHD + NEURAL:X",
242
+ "DOLBY AUDIO - DD + NEURAL:X",
231
243
  "DOLBY AUDIO - DD + DSUR",
232
244
  "DOLBY AUDIO - DD+ + NEURAL:X",
233
245
  "DOLBY AUDIO - DD+ + DSUR",
246
+ "DOLBY AUDIO-DD+ +DSUR",
234
247
  "DOLBY AUDIO - DOLBY DIGITAL",
235
248
  "DOLBY AUDIO-DSUR",
236
249
  "DOLBY AUDIO-DD+DSUR",
250
+ "DOLBY PRO LOGIC",
237
251
  ],
238
252
  "DTS SURROUND": [
239
253
  "DTS SURROUND",
@@ -414,6 +428,7 @@ ZONE3_URLS = ReceiverURLs(
414
428
  )
415
429
 
416
430
  # Telnet Events
431
+ ALL_TELNET_EVENTS = "ALL"
417
432
  TELNET_EVENTS = {
418
433
  "CV",
419
434
  "DC",
@@ -450,6 +465,22 @@ TELNET_EVENTS = {
450
465
  "Z2",
451
466
  "Z3",
452
467
  }
468
+ ALL_ZONE_TELNET_EVENTS = {
469
+ "DIM",
470
+ "HD",
471
+ "NS",
472
+ "NSA",
473
+ "NSE",
474
+ "MN",
475
+ "PW",
476
+ "RM",
477
+ "SY",
478
+ "TF",
479
+ "TM",
480
+ "TP",
481
+ "TR",
482
+ "UG",
483
+ }
453
484
 
454
485
  DENONAVR_TELNET_COMMANDS = TelnetCommands(
455
486
  command_sel_src="SI",
@@ -465,6 +496,13 @@ DENONAVR_TELNET_COMMANDS = TelnetCommands(
465
496
  command_set_all_zone_stereo="MN",
466
497
  command_pause="NS9B",
467
498
  command_play="NS9A",
499
+ command_multieq="PSMULTEQ:",
500
+ command_dynamiceq="PSDYNEQ ",
501
+ command_reflevoffset="PSREFLEV ",
502
+ command_dynamicvol="PSDYNVOL ",
503
+ command_tonecontrol="PSTONE CTRL ",
504
+ command_bass="PSBAS ",
505
+ command_treble="PSTRE ",
468
506
  )
469
507
 
470
508
  ZONE2_TELNET_COMMANDS = TelnetCommands(
@@ -481,6 +519,13 @@ ZONE2_TELNET_COMMANDS = TelnetCommands(
481
519
  command_set_all_zone_stereo="MN",
482
520
  command_pause="NS9B",
483
521
  command_play="NS9A",
522
+ command_multieq="PSMULTEQ:",
523
+ command_dynamiceq="PSDYNEQ ",
524
+ command_reflevoffset="PSREFLEV ",
525
+ command_dynamicvol="PSDYNVOL ",
526
+ command_tonecontrol="PSTONE CTRL ",
527
+ command_bass="PSBAS ",
528
+ command_treble="PSTRE ",
484
529
  )
485
530
 
486
531
  ZONE3_TELNET_COMMANDS = TelnetCommands(
@@ -497,23 +542,31 @@ ZONE3_TELNET_COMMANDS = TelnetCommands(
497
542
  command_set_all_zone_stereo="MN",
498
543
  command_pause="NS9B",
499
544
  command_play="NS9A",
545
+ command_multieq="PSMULTEQ:",
546
+ command_dynamiceq="PSDYNEQ ",
547
+ command_reflevoffset="PSREFLEV ",
548
+ command_dynamicvol="PSDYNVOL ",
549
+ command_tonecontrol="PSTONE CTRL ",
550
+ command_bass="PSBAS ",
551
+ command_treble="PSTRE ",
500
552
  )
501
553
 
502
554
  # States
503
555
  POWER_ON = "ON"
504
556
  POWER_OFF = "OFF"
505
557
  POWER_STANDBY = "STANDBY"
506
- POWER_STATES = [POWER_ON, POWER_OFF, POWER_STANDBY]
558
+ POWER_STATES = {POWER_ON, POWER_OFF, POWER_STANDBY}
507
559
  STATE_ON = "on"
508
560
  STATE_OFF = "off"
509
561
  STATE_PLAYING = "playing"
510
562
  STATE_PAUSED = "paused"
511
563
 
512
564
  # Zones
565
+ ALL_ZONES = "All"
513
566
  MAIN_ZONE = "Main"
514
567
  ZONE2 = "Zone2"
515
568
  ZONE3 = "Zone3"
516
- VALID_ZONES = [MAIN_ZONE, ZONE2, ZONE3]
569
+ VALID_ZONES = {MAIN_ZONE, ZONE2, ZONE3}
517
570
 
518
571
  # Setup additional zones
519
572
  NO_ZONES = None
@@ -526,11 +579,41 @@ APPCOMMAND_CMD_TEXT = "cmd_text"
526
579
  APPCOMMAND_NAME = "name"
527
580
 
528
581
  # Audyssey parameter
529
- MULTI_EQ_MAP = {"0": "Off", "1": "Flat", "2": "L/R Bypass", "3": "Reference"}
530
- MULTI_EQ_MAP_LABELS = {(value, key) for key, value in MULTI_EQ_MAP.items()}
582
+ MULTI_EQ_MAP_APPCOMMAND = {"0": "Off", "1": "Flat", "2": "L/R Bypass", "3": "Reference"}
583
+ MULTI_EQ_MAP_TELNET = {
584
+ "OFF": "Off",
585
+ "FLAT": "Flat",
586
+ "BYP.LR": "L/R Bypass",
587
+ "AUDYSSEY": "Reference",
588
+ "MANUAL": "Manual",
589
+ }
590
+ MULTI_EQ_MAP = {**MULTI_EQ_MAP_APPCOMMAND, **MULTI_EQ_MAP_TELNET}
591
+ MULTI_EQ_MAP_LABELS_APPCOMMAND = {
592
+ value: key for key, value in MULTI_EQ_MAP_APPCOMMAND.items()
593
+ }
594
+ MULTI_EQ_MAP_LABELS_TELNET = {value: key for key, value in MULTI_EQ_MAP_TELNET.items()}
531
595
 
532
- REF_LVL_OFFSET_MAP = {"0": "0dB", "1": "+5dB", "2": "+10dB", "3": "+15dB"}
533
- REF_LVL_OFFSET_MAP_LABELS = {(value, key) for key, value in REF_LVL_OFFSET_MAP.items()}
596
+ REF_LVL_OFFSET_MAP_APPCOMMAND = {"0": "0dB", "1": "+5dB", "2": "+10dB", "3": "+15dB"}
597
+ REF_LVL_OFFSET_MAP_TELNET = {"0": "0dB", "5": "+5dB", "10": "+10dB", "15": "+15dB"}
598
+ REF_LVL_OFFSET_MAP = {**REF_LVL_OFFSET_MAP_APPCOMMAND, **REF_LVL_OFFSET_MAP_TELNET}
599
+ REF_LVL_OFFSET_MAP_LABELS_APPCOMMAND = {
600
+ value: key for key, value in REF_LVL_OFFSET_MAP_APPCOMMAND.items()
601
+ }
602
+ REF_LVL_OFFSET_MAP_LABELS_TELNET = {
603
+ value: key for key, value in REF_LVL_OFFSET_MAP_TELNET.items()
604
+ }
534
605
 
535
- DYNAMIC_VOLUME_MAP = {"0": "Off", "1": "Light", "2": "Medium", "3": "Heavy"}
536
- DYNAMIC_VOLUME_MAP_LABELS = {(value, key) for key, value in DYNAMIC_VOLUME_MAP.items()}
606
+ DYNAMIC_VOLUME_MAP_APPCOMMAND = {"0": "Off", "1": "Light", "2": "Medium", "3": "Heavy"}
607
+ DYNAMIC_VOLUME_MAP_TELNET = {
608
+ "OFF": "Off",
609
+ "LIT": "Light",
610
+ "MED": "Medium",
611
+ "HEV": "Heavy",
612
+ }
613
+ DYNAMIC_VOLUME_MAP = {**DYNAMIC_VOLUME_MAP_APPCOMMAND, **DYNAMIC_VOLUME_MAP_TELNET}
614
+ DYNAMIC_VOLUME_MAP_LABELS_APPCOMMAND = {
615
+ value: key for key, value in DYNAMIC_VOLUME_MAP_APPCOMMAND.items()
616
+ }
617
+ DYNAMIC_VOLUME_MAP_LABELS_TELNET = {
618
+ value: key for key, value in DYNAMIC_VOLUME_MAP_TELNET.items()
619
+ }
denonavr/decorators.py CHANGED
@@ -16,6 +16,7 @@ from functools import wraps
16
16
  from typing import Callable, Coroutine, TypeVar
17
17
 
18
18
  import httpx
19
+ from asyncstdlib import lru_cache
19
20
  from defusedxml import DefusedXmlException
20
21
  from defusedxml.ElementTree import ParseError
21
22
 
@@ -34,7 +35,7 @@ AnyT = TypeVar("AnyT")
34
35
 
35
36
  def async_handle_receiver_exceptions(func: Callable[..., AnyT]) -> Callable[..., AnyT]:
36
37
  """
37
- Handle exceptions raised when calling an Denon AVR endpoint asynchronously.
38
+ Handle exceptions raised when calling a Denon AVR endpoint asynchronously.
38
39
 
39
40
  The decorated function must either have a string variable as second
40
41
  argument or as "request" keyword argument.
@@ -48,24 +49,18 @@ def async_handle_receiver_exceptions(func: Callable[..., AnyT]) -> Callable[...,
48
49
  _LOGGER.debug("HTTP status error on request %s", err.request, exc_info=True)
49
50
  # Separate handling of 403 errors
50
51
  if err.response.status_code == 403:
51
- raise AvrForbiddenError(
52
- "HTTPStatusError: {}".format(err), err.request
53
- ) from err
54
- raise AvrRequestError(
55
- "HTTPStatusError: {}".format(err), err.request
56
- ) from err
52
+ raise AvrForbiddenError(f"HTTPStatusError: {err}", err.request) from err
53
+ raise AvrRequestError(f"HTTPStatusError: {err}", err.request) from err
57
54
  except httpx.TimeoutException as err:
58
55
  _LOGGER.debug(
59
56
  "HTTP timeout exception on request %s", err.request, exc_info=True
60
57
  )
61
- raise AvrTimoutError(
62
- "TimeoutException: {}".format(err), err.request
63
- ) from err
58
+ raise AvrTimoutError(f"TimeoutException: {err}", err.request) from err
64
59
  except httpx.NetworkError as err:
65
60
  _LOGGER.debug(
66
61
  "Network error exception on request %s", err.request, exc_info=True
67
62
  )
68
- raise AvrNetworkError("NetworkError: {}".format(err), err.request) from err
63
+ raise AvrNetworkError(f"NetworkError: {err}", err.request) from err
69
64
  except httpx.RemoteProtocolError as err:
70
65
  _LOGGER.debug(
71
66
  "Remote protocol error exception on request %s",
@@ -73,7 +68,7 @@ def async_handle_receiver_exceptions(func: Callable[..., AnyT]) -> Callable[...,
73
68
  exc_info=True,
74
69
  )
75
70
  raise AvrInvalidResponseError(
76
- "RemoteProtocolError: {}".format(err), err.request
71
+ f"RemoteProtocolError: {err}", err.request
77
72
  ) from err
78
73
  except (
79
74
  ET.ParseError,
@@ -85,50 +80,43 @@ def async_handle_receiver_exceptions(func: Callable[..., AnyT]) -> Callable[...,
85
80
  "Defusedxml parse error on request %s", (args, kwargs), exc_info=True
86
81
  )
87
82
  raise AvrInvalidResponseError(
88
- "XMLParseError: {}".format(err), (args, kwargs)
83
+ f"XMLParseError: {err}", (args, kwargs)
89
84
  ) from err
90
85
 
91
86
  return wrapper
92
87
 
93
88
 
94
- def cache_clear_on_exception(func: Callable[..., AnyT]) -> Callable[..., AnyT]:
89
+ def cache_result(func: Callable[..., AnyT]) -> Callable[..., AnyT]:
95
90
  """
96
- Decorate a function to clear lru_cache if an exception occurs.
91
+ Decorate a function to cache its results with an lru_cache of maxsize 16.
97
92
 
98
- The decorator must be placed right before the @lru_cache decorator.
99
- It prevents memory leaks in home-assistant when receiver instances are
100
- created and deleted right away in case the device is offline on setup.
93
+ This decorator also sets an "cache_id" keyword argument if it is not set yet.
94
+ When an exception occurs it clears lru_cache to prevent memory leaks in
95
+ home-assistant when receiver instances are created and deleted right
96
+ away in case the device is offline on setup.
101
97
  """
98
+ if inspect.signature(func).parameters.get("cache_id") is None:
99
+ raise AttributeError(
100
+ f"Function {func} does not have a 'cache_id' keyword parameter"
101
+ )
102
+
103
+ lru_decorator = lru_cache(maxsize=16)
104
+ cached_func = lru_decorator(func)
102
105
 
103
106
  @wraps(func)
104
107
  async def wrapper(*args, **kwargs):
108
+ if kwargs.get("cache_id") is None:
109
+ kwargs["cache_id"] = time.time()
105
110
  try:
106
- return await func(*args, **kwargs)
111
+ return await cached_func(*args, **kwargs)
107
112
  except Exception as err:
108
113
  _LOGGER.debug("Exception %s raised, clearing cache", err)
109
- func.cache_clear()
114
+ cached_func.cache_clear()
110
115
  raise
111
116
 
112
117
  return wrapper
113
118
 
114
119
 
115
- def set_cache_id(func: Callable[..., AnyT]) -> Callable[..., AnyT]:
116
- """
117
- Decorate a function to add cache_id keyword argument if it is not present.
118
-
119
- The function must be called with a fix cache_id keyword argument to be able
120
- to get cached data. This prevents accidential caching of a function result.
121
- """
122
-
123
- @wraps(func)
124
- def wrapper(*args, **kwargs):
125
- if kwargs.get("cache_id") is None:
126
- kwargs["cache_id"] = time.time()
127
- return func(*args, **kwargs)
128
-
129
- return wrapper
130
-
131
-
132
120
  def run_async_synchronously(async_func: Coroutine) -> Callable:
133
121
  """
134
122
  Decorate to run the configured asynchronous function synchronously instead.
@@ -141,13 +129,11 @@ def run_async_synchronously(async_func: Coroutine) -> Callable:
141
129
  def decorator(func: Callable):
142
130
  # Check if function is a coroutine
143
131
  if not inspect.iscoroutinefunction(async_func):
144
- raise AttributeError(
145
- "Function {} is not a coroutine function".format(async_func)
146
- )
132
+ raise AttributeError(f"Function {async_func} is not a coroutine function")
147
133
  # Check if the signature of both functions is equal
148
134
  if inspect.signature(func) != inspect.signature(async_func):
149
135
  raise AttributeError(
150
- "Functions {} and {} have different signatures".format(func, async_func)
136
+ f"Functions {func} and {async_func} have different signatures"
151
137
  )
152
138
 
153
139
  @wraps(func)
denonavr/denonavr.py CHANGED
@@ -124,7 +124,7 @@ class DenonAVR(DenonAVRFoundation):
124
124
  # Name either set explicitly or name of Main Zone with suffix
125
125
  zonename = None
126
126
  if zname is None and self._name is not None:
127
- zonename = "{} {}".format(self._name, zone)
127
+ zonename = f"{self._name} {zone}"
128
128
  zone_device = attr.evolve(self._device, zone=zone)
129
129
  zone_inst = DenonAVR(
130
130
  host=self._host,
@@ -226,9 +226,13 @@ class DenonAVR(DenonAVRFoundation):
226
226
  def send_get_command(self, request: str) -> str:
227
227
  """Send HTTP GET command to Denon AVR receiver...for compatibility."""
228
228
 
229
- def send_telnet_commands(self, *commands: str) -> bool:
229
+ async def async_send_telnet_commands(self, *commands: str) -> None:
230
230
  """Send telnet commands to the receiver."""
231
- return self._device.telnet_api.send_commands(*commands)
231
+ await self._device.telnet_api.async_send_commands(*commands)
232
+
233
+ def send_telnet_commands(self, *commands: str) -> None:
234
+ """Send telnet commands to the receiver."""
235
+ self._device.telnet_api.send_commands(*commands)
232
236
 
233
237
  def register_callback(
234
238
  self, event: str, callback: Callable[[str, str, str], Awaitable[None]]
@@ -435,6 +439,11 @@ class DenonAVR(DenonAVRFoundation):
435
439
  return None
436
440
  return self._device.receiver.type
437
441
 
442
+ @property
443
+ def telnet_available(self) -> bool:
444
+ """Return True if telnet is connected and healthy."""
445
+ return self._device.telnet_available
446
+
438
447
  @property
439
448
  def telnet_connected(self) -> bool:
440
449
  """Return True if telnet is connected."""
@@ -450,6 +459,16 @@ class DenonAVR(DenonAVRFoundation):
450
459
  """Indicate if all inputs are shown or just active one."""
451
460
  return self._show_all_inputs
452
461
 
462
+ @property
463
+ def tone_control_status(self) -> Optional[bool]:
464
+ """Return value of tone control status."""
465
+ return self.tonecontrol.tone_control_status
466
+
467
+ @property
468
+ def tone_control_adjust(self) -> Optional[bool]:
469
+ """Return value of tone control adjust."""
470
+ return self.tonecontrol.tone_control_adjust
471
+
453
472
  @property
454
473
  def bass(self) -> Optional[int]:
455
474
  """Return value of bass."""
@@ -505,10 +524,6 @@ class DenonAVR(DenonAVRFoundation):
505
524
  """Return a list of available MultiEQ settings."""
506
525
  return self.audyssey.multi_eq_setting_list
507
526
 
508
- async def async_dynamic_eq_off(self) -> None:
509
- """Turn DynamicEQ off."""
510
- await self.audyssey.async_dynamiceq_off()
511
-
512
527
  ##########
513
528
  # Setter #
514
529
  ##########
@@ -525,6 +540,10 @@ class DenonAVR(DenonAVRFoundation):
525
540
  raise AvrCommandError("Provided object is not callable")
526
541
  self._device.api.async_client_getter = async_client_getter
527
542
 
543
+ async def async_dynamic_eq_off(self) -> None:
544
+ """Turn DynamicEQ off."""
545
+ await self.audyssey.async_dynamiceq_off()
546
+
528
547
  @run_async_synchronously(async_func=async_dynamic_eq_off)
529
548
  def dynamic_eq_off(self) -> None:
530
549
  """Turn DynamicEQ off."""
@@ -545,6 +564,30 @@ class DenonAVR(DenonAVRFoundation):
545
564
  def toggle_dynamic_eq(self) -> None:
546
565
  """Toggle DynamicEQ."""
547
566
 
567
+ async def async_set_multieq(self, value: str) -> None:
568
+ """Set MultiEQ mode."""
569
+ await self.audyssey.async_set_multieq(value)
570
+
571
+ @run_async_synchronously(async_func=async_set_multieq)
572
+ def set_multieq(self, value: str) -> None:
573
+ """Set MultiEQ mode."""
574
+
575
+ async def async_set_reflevoffset(self, value: str) -> None:
576
+ """Set Reference Level Offset."""
577
+ await self.audyssey.async_set_reflevoffset(value)
578
+
579
+ @run_async_synchronously(async_func=async_set_reflevoffset)
580
+ def set_reflevoffset(self, value: str) -> None:
581
+ """Set Reference Level Offset."""
582
+
583
+ async def async_set_dynamicvol(self, value: str) -> None:
584
+ """Set Dynamic Volume."""
585
+ await self.audyssey.async_set_dynamicvol(value)
586
+
587
+ @run_async_synchronously(async_func=async_set_dynamicvol)
588
+ def set_dynamicvol(self, value: str) -> None:
589
+ """Set Dynamic Volume."""
590
+
548
591
  async def async_set_input_func(self, input_func: str) -> None:
549
592
  """
550
593
  Set input_func of device.