denonavr 1.2.0__py3-none-any.whl → 1.3.0__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/foundation.py CHANGED
@@ -22,12 +22,13 @@ from .const import (
22
22
  APPCOMMAND_CMD_TEXT,
23
23
  APPCOMMAND_NAME,
24
24
  AUDIO_RESTORER_MAP,
25
- AUDIO_RESTORER_MAP_LABELS,
25
+ AUDIO_RESTORER_MAP_REVERSE,
26
+ AUTO_STANDBY_MAP,
26
27
  AVR,
27
28
  AVR_X,
28
29
  AVR_X_2016,
29
- BLUETOOTH_OUTPUT_MAP_LABELS,
30
30
  BLUETOOTH_OUTPUT_MODES_MAP,
31
+ BLUETOOTH_OUTPUT_MODES_MAP_REVERSE,
31
32
  CHANNEL_VOLUME_MAP,
32
33
  DENON_ATTR_SETATTR,
33
34
  DENONAVR_TELNET_COMMANDS,
@@ -36,20 +37,22 @@ from .const import (
36
37
  DEVICEINFO_AVR_X_PATTERN,
37
38
  DEVICEINFO_COMMAPI_PATTERN,
38
39
  DIMMER_MODE_MAP,
39
- DIMMER_MODE_MAP_LABELS,
40
+ DIMMER_MODE_MAP_REVERSE,
41
+ DIMMER_MODE_MAP_TELNET,
40
42
  ECO_MODE_MAP,
41
- ECO_MODE_MAP_LABELS,
43
+ ECO_MODE_MAP_REVERSE,
44
+ ECO_MODE_MAP_TELNET,
42
45
  HDMI_OUTPUT_MAP,
43
- HDMI_OUTPUT_MAP_LABELS,
46
+ HDMI_OUTPUT_MAP_REVERSE,
44
47
  ILLUMINATION_MAP,
45
- ILLUMINATION_MAP_LABELS,
48
+ ILLUMINATION_MAP_REVERSE,
46
49
  MAIN_ZONE,
47
50
  POWER_STATES,
48
51
  SETTINGS_MENU_STATES,
49
52
  VALID_RECEIVER_TYPES,
50
53
  VALID_ZONES,
51
54
  VIDEO_PROCESSING_MODES_MAP,
52
- VIDEO_PROCESSING_MODES_MAP_LABELS,
55
+ VIDEO_PROCESSING_MODES_MAP_REVERSE,
53
56
  ZONE2,
54
57
  ZONE2_TELNET_COMMANDS,
55
58
  ZONE2_URLS,
@@ -64,6 +67,7 @@ from .const import (
64
67
  HDMIAudioDecodes,
65
68
  HDMIOutputs,
66
69
  Illuminations,
70
+ InputModes,
67
71
  PanelLocks,
68
72
  ReceiverType,
69
73
  ReceiverURLs,
@@ -74,10 +78,7 @@ from .const import (
74
78
  )
75
79
  from .exceptions import (
76
80
  AvrCommandError,
77
- AvrForbiddenError,
78
- AvrIncompleteResponseError,
79
81
  AvrNetworkError,
80
- AvrProcessingError,
81
82
  AvrRequestError,
82
83
  AvrTimoutError,
83
84
  )
@@ -133,6 +134,7 @@ class DenonAVRDeviceInfo:
133
134
  zone: str = attr.ib(
134
135
  validator=attr.validators.in_(VALID_ZONES), default=MAIN_ZONE, kw_only=True
135
136
  )
137
+ zones: int = attr.ib(converter=attr.converters.optional(int), default=0)
136
138
  friendly_name: Optional[str] = attr.ib(
137
139
  converter=attr.converters.optional(str), default=None
138
140
  )
@@ -155,11 +157,11 @@ class DenonAVRDeviceInfo:
155
157
  converter=attr.converters.optional(convert_on_off_bool), default=None
156
158
  )
157
159
  _dimmer: Optional[str] = attr.ib(
158
- converter=attr.converters.optional(str), default=None
160
+ converter=attr.converters.optional(DIMMER_MODE_MAP.get), default=None
159
161
  )
160
162
  _dimmer_modes = get_args(DimmerModes)
161
163
  _auto_standby: Optional[str] = attr.ib(
162
- converter=attr.converters.optional(str), default=None
164
+ converter=attr.converters.optional(AUTO_STANDBY_MAP.get), default=None
163
165
  )
164
166
  _auto_standbys = get_args(AutoStandbys)
165
167
  _sleep: Optional[Union[str, int]] = attr.ib(
@@ -169,11 +171,11 @@ class DenonAVRDeviceInfo:
169
171
  converter=attr.converters.optional(int), default=None
170
172
  )
171
173
  _eco_mode: Optional[str] = attr.ib(
172
- converter=attr.converters.optional(str), default=None
174
+ converter=attr.converters.optional(ECO_MODE_MAP.get), default=None
173
175
  )
174
176
  _eco_modes = get_args(EcoModes)
175
177
  _hdmi_output: Optional[str] = attr.ib(
176
- converter=attr.converters.optional(str), default=None
178
+ converter=attr.converters.optional(HDMI_OUTPUT_MAP.get), default=None
177
179
  )
178
180
  _hdmi_outputs = get_args(HDMIOutputs)
179
181
  _hdmi_audio_decode: Optional[str] = attr.ib(
@@ -181,7 +183,7 @@ class DenonAVRDeviceInfo:
181
183
  )
182
184
  _hdmi_audio_decodes = get_args(HDMIAudioDecodes)
183
185
  _video_processing_mode: Optional[str] = attr.ib(
184
- converter=attr.converters.optional(str), default=None
186
+ converter=attr.converters.optional(VIDEO_PROCESSING_MODES_MAP.get), default=None
185
187
  )
186
188
  _video_processing_modes = get_args(VideoProcessingModes)
187
189
  _tactile_transducer: Optional[str] = attr.ib(
@@ -200,20 +202,20 @@ class DenonAVRDeviceInfo:
200
202
  _room_sizes = get_args(RoomSizes)
201
203
  _triggers: Optional[Dict[int, str]] = attr.ib(default=None)
202
204
  _speaker_preset: Optional[int] = attr.ib(
203
- converter=attr.converters.optional(str), default=None
205
+ converter=attr.converters.optional(int), default=None
204
206
  )
205
207
  _bt_transmitter: Optional[bool] = attr.ib(
206
208
  converter=attr.converters.optional(convert_on_off_bool), default=None
207
209
  )
208
210
  _bt_output_mode: Optional[str] = attr.ib(
209
- converter=attr.converters.optional(str), default=None
211
+ converter=attr.converters.optional(BLUETOOTH_OUTPUT_MODES_MAP.get), default=None
210
212
  )
211
213
  _bt_output_modes = get_args(BluetoothOutputModes)
212
214
  _delay_time: Optional[int] = attr.ib(
213
215
  converter=attr.converters.optional(int), default=None
214
216
  )
215
217
  _audio_restorer: Optional[str] = attr.ib(
216
- converter=attr.converters.optional(str), default=None
218
+ converter=attr.converters.optional(AUDIO_RESTORER_MAP.get), default=None
217
219
  )
218
220
  _audio_restorers = get_args(AudioRestorers)
219
221
  _panel_locks = get_args(PanelLocks)
@@ -224,14 +226,14 @@ class DenonAVRDeviceInfo:
224
226
  converter=attr.converters.optional(convert_on_off_bool), default=None
225
227
  )
226
228
  _illumination: Optional[str] = attr.ib(
227
- converter=attr.converters.optional(str), default=None
229
+ converter=attr.converters.optional(ILLUMINATION_MAP.get), default=None
228
230
  )
229
231
  _illuminations = get_args(Illuminations)
230
232
  _auto_lip_sync: Optional[bool] = attr.ib(
231
233
  converter=attr.converters.optional(convert_on_off_bool), default=None
232
234
  )
235
+ _input_modes = get_args(InputModes)
233
236
  _is_setup: bool = attr.ib(converter=bool, default=False, init=False)
234
- _allow_recovery: bool = attr.ib(converter=bool, default=True, init=True)
235
237
  _setup_lock: asyncio.Lock = attr.ib(default=attr.Factory(asyncio.Lock))
236
238
 
237
239
  def __attrs_post_init__(self) -> None:
@@ -256,45 +258,31 @@ class DenonAVRDeviceInfo:
256
258
 
257
259
  def _settings_menu_callback(self, zone: str, event: str, parameter: str) -> None:
258
260
  """Handle a settings menu event."""
259
- if (
260
- event == "MN"
261
- and parameter[0:3] == "MEN"
262
- and parameter[4:] in SETTINGS_MENU_STATES
263
- ):
261
+ if parameter[0:3] == "MEN" and parameter[4:] in SETTINGS_MENU_STATES:
264
262
  self._settings_menu = parameter[4:]
265
263
 
266
264
  def _dimmer_callback(self, zone: str, event: str, parameter: str) -> None:
267
265
  """Handle a dimmer change event."""
268
- if event == "DIM" and parameter[1:] in DIMMER_MODE_MAP_LABELS:
269
- self._dimmer = DIMMER_MODE_MAP_LABELS[parameter[1:]]
266
+ if parameter[1:] in DIMMER_MODE_MAP_TELNET:
267
+ self._dimmer = parameter[1:]
270
268
 
271
269
  def _auto_standby_callback(self, zone: str, event: str, parameter: str) -> None:
272
270
  """Handle a auto standby change event."""
273
- if zone == "Main" and event == "STBY":
271
+ if zone == self.zone:
274
272
  self._auto_standby = parameter
275
273
 
276
274
  def _auto_sleep_callback(self, zone: str, event: str, parameter: str) -> None:
277
275
  """Handle a sleep change event."""
278
- if event != "SLP":
279
- return
280
-
281
- if parameter == "OFF":
276
+ if zone == self.zone:
282
277
  self._sleep = parameter
283
- else:
284
- self._sleep = int(parameter)
285
278
 
286
279
  def _room_size_callback(self, zone: str, event: str, parameter: str) -> None:
287
280
  """Handle a room size change event."""
288
- if parameter[:3] != "RSZ":
289
- return
290
-
291
- self._room_size = parameter[4:]
281
+ if zone == self.zone and parameter[:3] == "RSZ":
282
+ self._room_size = parameter[4:]
292
283
 
293
284
  def _trigger_callback(self, zone: str, event: str, parameter: str) -> None:
294
285
  """Handle a trigger change event."""
295
- if event != "TR":
296
- return
297
-
298
286
  values = parameter.split()
299
287
  if len(values) != 2:
300
288
  return
@@ -306,34 +294,32 @@ class DenonAVRDeviceInfo:
306
294
 
307
295
  def _delay_callback(self, zone: str, event: str, parameter: str) -> None:
308
296
  """Handle a delay change event."""
309
- if event == "PS" and parameter[0:5] == "DELAY":
310
- self._delay = int(parameter[6:])
297
+ if zone == self.zone and parameter[0:5] == "DELAY":
298
+ self._delay = parameter[6:]
311
299
 
312
300
  def _eco_mode_callback(self, zone: str, event: str, parameter: str) -> None:
313
301
  """Handle an Eco-mode change event."""
314
- if event == "ECO" and parameter in ECO_MODE_MAP_LABELS:
315
- self._eco_mode = ECO_MODE_MAP_LABELS[parameter]
302
+ if zone == self.zone and parameter in ECO_MODE_MAP_TELNET:
303
+ self._eco_mode = parameter
316
304
 
317
305
  def _hdmi_output_callback(self, zone: str, event: str, parameter: str) -> None:
318
306
  """Handle a HDMI output change event."""
319
- if event == "VS" and parameter[0:4] == "MONI":
320
- self._hdmi_output = HDMI_OUTPUT_MAP_LABELS[parameter]
307
+ if zone == self.zone and parameter[0:4] == "MONI":
308
+ self._hdmi_output = parameter
321
309
 
322
310
  def _hdmi_audio_decode_callback(
323
311
  self, zone: str, event: str, parameter: str
324
312
  ) -> None:
325
313
  """Handle a HDMI Audio Decode mode change event."""
326
- if event == "VS" and parameter[0:5] == "AUDIO":
314
+ if zone == self.zone and parameter[0:5] == "AUDIO":
327
315
  self._hdmi_audio_decode = parameter[6:]
328
316
 
329
317
  def _video_processing_mode_callback(
330
318
  self, zone: str, event: str, parameter: str
331
319
  ) -> None:
332
320
  """Handle a Video Processing Mode change event."""
333
- if event == "VS" and parameter[0:3] == "VPM":
334
- self._video_processing_mode = VIDEO_PROCESSING_MODES_MAP_LABELS[
335
- parameter[3:]
336
- ]
321
+ if zone == self.zone and parameter[0:3] == "VPM":
322
+ self._video_processing_mode = parameter[3:]
337
323
 
338
324
  def _tactile_transducer_callback(
339
325
  self, zone: str, event: str, parameter: str
@@ -353,40 +339,37 @@ class DenonAVRDeviceInfo:
353
339
  elif key == "TTRLEV":
354
340
  self._tactile_transducer_level = CHANNEL_VOLUME_MAP[value]
355
341
  elif key == "TTRLPF":
356
- self._tactile_transducer_lpf = f"{int(value)} Hz"
342
+ self._tactile_transducer_lpf = f"{value} Hz"
357
343
 
358
344
  def _speaker_preset_callback(self, zone: str, event: str, parameter: str) -> None:
359
345
  """Handle a speaker preset change event."""
360
- if event != "SP":
361
- return
362
-
363
346
  if parameter[0:2] == "PR":
364
- self._speaker_preset = int(parameter[3:])
347
+ self._speaker_preset = parameter[3:]
365
348
 
366
349
  def _bt_callback(self, zone: str, event: str, parameter: str) -> None:
367
350
  """Handle a Bluetooth change event."""
368
- if event != "BT" or parameter[0:2] != "TX":
351
+ if parameter[0:2] != "TX":
369
352
  return
370
353
 
371
354
  if parameter[3:] in ("ON", "OFF"):
372
355
  self._bt_transmitter = parameter[3:]
373
356
  else:
374
- self._bt_output_mode = BLUETOOTH_OUTPUT_MAP_LABELS[parameter[3:]]
357
+ self._bt_output_mode = parameter[3:]
375
358
 
376
359
  def _delay_time_callback(self, zone: str, event: str, parameter: str) -> None:
377
360
  """Handle a delay time change event."""
378
361
  # do not match "DELAY" as it's another event
379
- if event != "PS" or parameter[0:3] != "DEL" or parameter[0:5] == "DELAY":
362
+ if parameter[0:3] != "DEL" or parameter[0:5] == "DELAY":
380
363
  return
381
364
 
382
365
  self._delay_time = int(parameter[4:])
383
366
 
384
367
  def _audio_restorer_callback(self, zone: str, event: str, parameter: str) -> None:
385
368
  """Handle an audio restorer change event."""
386
- if event != "PS" or parameter[0:4] != "RSTR":
369
+ if parameter[0:4] != "RSTR":
387
370
  return
388
371
 
389
- self._audio_restorer = AUDIO_RESTORER_MAP_LABELS[parameter[5:]]
372
+ self._audio_restorer = parameter[5:]
390
373
 
391
374
  def _graphic_eq_callback(self, zone: str, event: str, parameter: str) -> None:
392
375
  """Handle a Graphic EQ change event."""
@@ -404,14 +387,12 @@ class DenonAVRDeviceInfo:
404
387
 
405
388
  def _illumination_callback(self, zone: str, event: str, parameter: str) -> None:
406
389
  """Handle an illumination change event."""
407
- if event != "ILB" or parameter[0:3] != "ILL":
408
- return
409
-
410
- self._illumination = ILLUMINATION_MAP_LABELS[parameter[4:]]
390
+ if parameter[0:3] == "ILL":
391
+ self._illumination = parameter[4:]
411
392
 
412
393
  def _auto_lip_sync_callback(self, zone: str, event: str, parameter: str) -> None:
413
394
  """Handle a auto lip sync change event."""
414
- if event != "PS" or parameter[0:3] != "HOS":
395
+ if parameter[0:3] != "HOS":
415
396
  return
416
397
 
417
398
  if parameter[6:] == "HOSALS":
@@ -423,17 +404,6 @@ class DenonAVRDeviceInfo:
423
404
 
424
405
  self._auto_lip_sync = auto_lip_sync
425
406
 
426
- def get_own_zone(self) -> str:
427
- """
428
- Get zone from actual instance.
429
-
430
- These zone information are used to evaluate responses of HTTP POST
431
- commands.
432
- """
433
- if self.zone == MAIN_ZONE:
434
- return "zone1"
435
- return self.zone.lower()
436
-
437
407
  async def async_setup(self) -> None:
438
408
  """Ensure that configuration is loaded from receiver asynchronously."""
439
409
  async with self._setup_lock:
@@ -454,12 +424,22 @@ class DenonAVRDeviceInfo:
454
424
 
455
425
  # Add tags for a potential AppCommand.xml update
456
426
  self.api.add_appcommand_update_tag(AppCommands.GetAllZonePowerStatus)
427
+ self.api.add_appcommand_update_tag(AppCommands.GetAutoStandby)
428
+ self.api.add_appcommand_update_tag(AppCommands.GetDimmer)
429
+ self.api.add_appcommand_update_tag(AppCommands.GetECO)
457
430
 
458
431
  power_event = "ZM"
459
432
  if self.zone == ZONE2:
460
433
  power_event = "Z2"
461
434
  elif self.zone == ZONE3:
462
435
  power_event = "Z3"
436
+ elif self.zones == 1:
437
+ # ZM events do not always work when the receiver has only one zone
438
+ # In this case it is safe to turn the entire device on and off
439
+ power_event = "PW"
440
+ self.telnet_commands = self.telnet_commands._replace(
441
+ command_power_on="PWON", command_power_standby="PWSTANDBY"
442
+ )
463
443
  self.telnet_api.register_callback(power_event, self._power_callback)
464
444
 
465
445
  self.telnet_api.register_callback("MN", self._settings_menu_callback)
@@ -499,8 +479,13 @@ class DenonAVRDeviceInfo:
499
479
  if not self._is_setup:
500
480
  await self.async_setup()
501
481
 
502
- # Update power status
503
- await self.async_update_power(global_update=global_update, cache_id=cache_id)
482
+ if self.use_avr_2016_update:
483
+ await self.async_update_appcommand(
484
+ global_update=global_update, cache_id=cache_id
485
+ )
486
+ else:
487
+ await self.async_update_status_xml(cache_id=cache_id)
488
+
504
489
  _LOGGER.debug("Finished device update")
505
490
 
506
491
  async def async_identify_receiver(self) -> None:
@@ -542,6 +527,10 @@ class DenonAVRDeviceInfo:
542
527
  err,
543
528
  )
544
529
  else:
530
+ device_zones = xml.find("./DeviceZones")
531
+ if device_zones is not None:
532
+ self.zones = device_zones.text
533
+
545
534
  is_avr_x = self._is_avr_x(xml)
546
535
  if is_avr_x:
547
536
  self.receiver = r_type
@@ -568,7 +557,7 @@ class DenonAVRDeviceInfo:
568
557
  # First test by CommApiVers
569
558
  try:
570
559
  if bool(
571
- DEVICEINFO_COMMAPI_PATTERN.search(deviceinfo.find("CommApiVers").text)
560
+ DEVICEINFO_COMMAPI_PATTERN.search(deviceinfo.find("./CommApiVers").text)
572
561
  is not None
573
562
  ):
574
563
  # receiver found , return True
@@ -581,7 +570,7 @@ class DenonAVRDeviceInfo:
581
570
  # if first test did not find AVR-X device, check by model name
582
571
  try:
583
572
  if bool(
584
- DEVICEINFO_AVR_X_PATTERN.search(deviceinfo.find("ModelName").text)
573
+ DEVICEINFO_AVR_X_PATTERN.search(deviceinfo.find("./ModelName").text)
585
574
  is not None
586
575
  ):
587
576
  # receiver found , return True
@@ -641,49 +630,6 @@ class DenonAVRDeviceInfo:
641
630
  else:
642
631
  self._set_friendly_name(xml)
643
632
 
644
- async def async_verify_avr_2016_update_method(
645
- self, *, cache_id: Hashable = None
646
- ) -> None:
647
- """Verify if avr 2016 update method is working."""
648
- # Nothing to do if Appcommand.xml interface is not supported
649
- if self._is_setup and not self.use_avr_2016_update:
650
- return
651
-
652
- try:
653
- # Result is cached that it can be reused during update
654
- await self.api.async_get_global_appcommand(cache_id=cache_id)
655
- except (AvrTimoutError, AvrNetworkError) as err:
656
- _LOGGER.debug("Connection error when verifying update method: %s", err)
657
- raise
658
- except AvrForbiddenError:
659
- # Recovery in case receiver changes port from 80 to 8080 which
660
- # might happen at Denon AVR-X 2016 receivers
661
- if self._allow_recovery:
662
- self._allow_recovery = False
663
- _LOGGER.warning(
664
- "AppCommand.xml returns HTTP status 403. Running setup"
665
- " again once to check if receiver interface switched "
666
- "ports"
667
- )
668
- self._is_setup = False
669
- await self.async_setup()
670
- await self.async_verify_avr_2016_update_method(cache_id=cache_id)
671
- else:
672
- raise
673
- except AvrIncompleteResponseError as err:
674
- _LOGGER.debug("Request error when verifying update method: %s", err)
675
- # Only AVR_X devices support both interfaces
676
- if self.receiver == AVR_X:
677
- _LOGGER.warning(
678
- "Error verifying Appcommand.xml update method, it returns "
679
- "an incomplete result set. Deactivating the interface"
680
- )
681
- self.use_avr_2016_update = False
682
- else:
683
- if not self._allow_recovery:
684
- _LOGGER.info("AppCommand.xml recovered from HTTP status 403 error")
685
- self._allow_recovery = True
686
-
687
633
  def _set_friendly_name(self, xml: ET.Element) -> None:
688
634
  """Set FriendlyName from result xml."""
689
635
  # friendlyname tag of AppCommand.xml, FriendlyName tag main zone xml
@@ -750,86 +696,97 @@ class DenonAVRDeviceInfo:
750
696
  self.model_name = device_info["modelName"]
751
697
  self.serial_number = device_info["serialNumber"]
752
698
 
753
- async def async_update_power(
754
- self, global_update: bool = False, cache_id: Optional[Hashable] = None
755
- ) -> None:
756
- """Update power status of device."""
757
- if self.use_avr_2016_update is None:
758
- raise AvrProcessingError(
759
- "Device is not setup correctly, update method not set"
760
- )
761
-
762
- if self.use_avr_2016_update:
763
- await self.async_update_power_appcommand(
764
- global_update=global_update, cache_id=cache_id
765
- )
766
- else:
767
- await self.async_update_power_status_xml(cache_id=cache_id)
768
-
769
- async def async_update_power_appcommand(
699
+ async def async_update_appcommand(
770
700
  self, global_update: bool = False, cache_id: Optional[Hashable] = None
771
701
  ) -> None:
772
- """Update power status from AppCommand.xml."""
702
+ """Update status from AppCommand.xml."""
773
703
  power_appcommand = AppCommands.GetAllZonePowerStatus
704
+ dimmer_appcommand = AppCommands.GetDimmer
705
+ autostandby_appcommand = AppCommands.GetAutoStandby
706
+ eco_appcommand = AppCommands.GetECO
707
+ appcommands = (
708
+ power_appcommand,
709
+ autostandby_appcommand,
710
+ dimmer_appcommand,
711
+ eco_appcommand,
712
+ )
713
+
774
714
  try:
775
715
  if global_update:
776
716
  xml = await self.api.async_get_global_appcommand(cache_id=cache_id)
777
717
  else:
778
718
  xml = await self.api.async_post_appcommand(
779
- self.urls.appcommand, tuple(power_appcommand), cache_id=cache_id
719
+ self.urls.appcommand, appcommands, cache_id=cache_id
780
720
  )
781
721
  except AvrRequestError as err:
782
- _LOGGER.debug("Error when getting power status: %s", err)
722
+ _LOGGER.debug("Error when getting device status: %s", err)
783
723
  raise
784
724
 
785
725
  # Extract relevant information
786
- zone = self.get_own_zone()
787
-
788
- # Search for power tag
789
- power_tag = xml.find(
790
- f"./cmd[@{APPCOMMAND_CMD_TEXT}='{power_appcommand.cmd_text}']/{zone}"
791
- )
726
+ for appcommand in appcommands:
727
+ for i, item in enumerate(
728
+ create_appcommand_search_strings(appcommand, self.zone)
729
+ ):
730
+ tag = xml.find(item)
792
731
 
793
- if power_tag is None:
794
- raise AvrProcessingError(
795
- f"Power attribute of zone {self.zone} not found on update"
796
- )
732
+ if tag is None:
733
+ _LOGGER.debug(
734
+ "%s attribute of zone %s not found on update",
735
+ appcommand.response_pattern[0].update_attribute,
736
+ self.zone,
737
+ )
738
+ continue
797
739
 
798
- self._power = power_tag.text
740
+ setattr(self, appcommand.response_pattern[i].update_attribute, tag.text)
799
741
 
800
- async def async_update_power_status_xml(
742
+ async def async_update_status_xml(
801
743
  self, cache_id: Optional[Hashable] = None
802
744
  ) -> None:
803
- """Update power status from status xml."""
745
+ """Update status from status xml."""
804
746
  # URLs to be scanned
805
747
  urls = [self.urls.status]
806
748
  if self.zone == MAIN_ZONE:
807
749
  urls.append(self.urls.mainzone)
808
750
  else:
809
751
  urls.append(f"{self.urls.mainzone}?ZoneName={self.zone}")
810
- # Tags in XML which might contain information about zones power status
811
- # ordered by their priority
812
- tags = ["./ZonePower/value", "./Power/value"]
813
752
 
814
- for tag in tags:
815
- for url in urls:
816
- try:
817
- xml = await self.api.async_get_xml(url, cache_id=cache_id)
818
- except AvrRequestError as err:
819
- _LOGGER.debug(
820
- "Error when getting power status from url %s: %s", url, err
821
- )
822
- continue
753
+ # There are different XML tags which might contain information
754
+ # about zone status attributes.
755
+ attribute_searchstrings = {
756
+ "_power": ["./ZonePower/value", "./Power/value"],
757
+ "_eco_mode": ["./ECOMode/value"],
758
+ }
823
759
 
824
- # Search for power tag
825
- power_tag = xml.find(tag)
826
- if power_tag is not None and power_tag.text is not None:
827
- self._power = power_tag.text
828
- return
760
+ for url in urls:
761
+ if len(attribute_searchstrings) == 0:
762
+ break
829
763
 
830
- raise AvrProcessingError(
831
- f"Power attribute of zone {self.zone} not found on update"
832
- )
764
+ try:
765
+ xml = await self.api.async_get_xml(url, cache_id=cache_id)
766
+ except AvrRequestError as err:
767
+ _LOGGER.debug(
768
+ "Error when getting device status from url %s: %s", url, err
769
+ )
770
+ continue
771
+
772
+ attributes_found = []
773
+ for attribute, searchstrings in attribute_searchstrings.items():
774
+ for searchstring in searchstrings:
775
+ tag = xml.find(searchstring)
776
+ if tag is not None and tag.text is not None:
777
+ setattr(self, attribute, tag.text)
778
+ attributes_found.append(attribute)
779
+ break
780
+
781
+ for attribute in attributes_found:
782
+ attribute_searchstrings.pop(attribute)
783
+
784
+ if len(attribute_searchstrings) > 0:
785
+ _LOGGER.debug(
786
+ "%s attributes of zone %s not found on update",
787
+ attribute_searchstrings.keys(),
788
+ self.zone,
789
+ )
833
790
 
834
791
  ##############
835
792
  # Properties #
@@ -857,8 +814,6 @@ class DenonAVRDeviceInfo:
857
814
  """
858
815
  Returns the dimmer state of the device.
859
816
 
860
- Only available if using Telnet.
861
-
862
817
  Possible values are: "Off", "Dark", "Dim" and "Bright"
863
818
  """
864
819
  return self._dimmer
@@ -868,9 +823,7 @@ class DenonAVRDeviceInfo:
868
823
  """
869
824
  Return the auto-standby state of the device.
870
825
 
871
- Only available if using Telnet.
872
-
873
- Possible values are: "OFF", "15M", "30M", "60M"
826
+ Possible values are: "OFF", "15M", "30M", "60M", "2H", "4H", "8H"
874
827
  """
875
828
  return self._auto_standby
876
829
 
@@ -899,8 +852,6 @@ class DenonAVRDeviceInfo:
899
852
  """
900
853
  Returns the eco-mode for the device.
901
854
 
902
- Only available if using Telnet.
903
-
904
855
  Possible values are: "Off", "On", "Auto"
905
856
  """
906
857
  return self._eco_mode
@@ -977,7 +928,7 @@ class DenonAVRDeviceInfo:
977
928
  return self._room_size
978
929
 
979
930
  @property
980
- def triggers(self) -> Dict[int, str]:
931
+ def triggers(self) -> Optional[Dict[int, str]]:
981
932
  """
982
933
  Return the triggers and their statuses for the device.
983
934
 
@@ -1082,6 +1033,8 @@ class DenonAVRDeviceInfo:
1082
1033
  @property
1083
1034
  def is_denon(self) -> bool:
1084
1035
  """Return true if the receiver is a Denon device."""
1036
+ if not self.manufacturer:
1037
+ return True # Fallback to Denon
1085
1038
  return "denon" in self.manufacturer.lower()
1086
1039
 
1087
1040
  ##########
@@ -1108,7 +1061,7 @@ class DenonAVRDeviceInfo:
1108
1061
  ##########
1109
1062
 
1110
1063
  async def async_power_on(self) -> None:
1111
- """Turn on receiver via HTTP get command."""
1064
+ """Turn on receiver."""
1112
1065
  if self.telnet_available:
1113
1066
  await self.telnet_api.async_send_commands(
1114
1067
  self.telnet_commands.command_power_on
@@ -1117,7 +1070,7 @@ class DenonAVRDeviceInfo:
1117
1070
  await self.api.async_get_command(self.urls.command_power_on)
1118
1071
 
1119
1072
  async def async_power_off(self) -> None:
1120
- """Turn off receiver via HTTP get command."""
1073
+ """Turn off receiver."""
1121
1074
  if self.telnet_available:
1122
1075
  await self.telnet_api.async_send_commands(
1123
1076
  self.telnet_commands.command_power_standby
@@ -1126,7 +1079,7 @@ class DenonAVRDeviceInfo:
1126
1079
  await self.api.async_get_command(self.urls.command_power_standby)
1127
1080
 
1128
1081
  async def async_cursor_up(self) -> None:
1129
- """Cursor Up on receiver via HTTP get command."""
1082
+ """Cursor Up on receiver."""
1130
1083
  if self.telnet_available:
1131
1084
  await self.telnet_api.async_send_commands(
1132
1085
  self.telnet_commands.command_cusor_up, skip_confirmation=True
@@ -1135,7 +1088,7 @@ class DenonAVRDeviceInfo:
1135
1088
  await self.api.async_get_command(self.urls.command_cusor_up)
1136
1089
 
1137
1090
  async def async_cursor_down(self) -> None:
1138
- """Cursor Down on receiver via HTTP get command."""
1091
+ """Cursor Down on receiver."""
1139
1092
  if self.telnet_available:
1140
1093
  await self.telnet_api.async_send_commands(
1141
1094
  self.telnet_commands.command_cusor_down, skip_confirmation=True
@@ -1144,7 +1097,7 @@ class DenonAVRDeviceInfo:
1144
1097
  await self.api.async_get_command(self.urls.command_cusor_down)
1145
1098
 
1146
1099
  async def async_cursor_left(self) -> None:
1147
- """Cursor Left on receiver via HTTP get command."""
1100
+ """Cursor Left on receiver."""
1148
1101
  if self.telnet_available:
1149
1102
  await self.telnet_api.async_send_commands(
1150
1103
  self.telnet_commands.command_cusor_left, skip_confirmation=True
@@ -1153,7 +1106,7 @@ class DenonAVRDeviceInfo:
1153
1106
  await self.api.async_get_command(self.urls.command_cusor_left)
1154
1107
 
1155
1108
  async def async_cursor_right(self) -> None:
1156
- """Cursor Right on receiver via HTTP get command."""
1109
+ """Cursor Right on receiver."""
1157
1110
  if self.telnet_available:
1158
1111
  await self.telnet_api.async_send_commands(
1159
1112
  self.telnet_commands.command_cusor_right, skip_confirmation=True
@@ -1162,7 +1115,7 @@ class DenonAVRDeviceInfo:
1162
1115
  await self.api.async_get_command(self.urls.command_cusor_right)
1163
1116
 
1164
1117
  async def async_cursor_enter(self) -> None:
1165
- """Cursor Enter on receiver via HTTP get command."""
1118
+ """Cursor Enter on receiver."""
1166
1119
  if self.telnet_available:
1167
1120
  await self.telnet_api.async_send_commands(
1168
1121
  self.telnet_commands.command_cusor_enter, skip_confirmation=True
@@ -1171,7 +1124,7 @@ class DenonAVRDeviceInfo:
1171
1124
  await self.api.async_get_command(self.urls.command_cusor_enter)
1172
1125
 
1173
1126
  async def async_back(self) -> None:
1174
- """Back command on receiver via HTTP get command."""
1127
+ """Back command on receiver."""
1175
1128
  if self.telnet_available:
1176
1129
  await self.telnet_api.async_send_commands(
1177
1130
  self.telnet_commands.command_back, skip_confirmation=True
@@ -1180,7 +1133,7 @@ class DenonAVRDeviceInfo:
1180
1133
  await self.api.async_get_command(self.urls.command_back)
1181
1134
 
1182
1135
  async def async_info(self) -> None:
1183
- """Info OSD on receiver via HTTP get command."""
1136
+ """Info OSD on receiver."""
1184
1137
  if self.telnet_available:
1185
1138
  await self.telnet_api.async_send_commands(
1186
1139
  self.telnet_commands.command_info, skip_confirmation=True
@@ -1189,7 +1142,7 @@ class DenonAVRDeviceInfo:
1189
1142
  await self.api.async_get_command(self.urls.command_info)
1190
1143
 
1191
1144
  async def async_options(self) -> None:
1192
- """Options menu on receiver via HTTP get command."""
1145
+ """Options menu on receiver."""
1193
1146
  if self.telnet_available:
1194
1147
  await self.telnet_api.async_send_commands(
1195
1148
  self.telnet_commands.command_options, skip_confirmation=True
@@ -1199,7 +1152,7 @@ class DenonAVRDeviceInfo:
1199
1152
 
1200
1153
  async def async_settings_menu(self) -> None:
1201
1154
  """
1202
- Options menu on receiver via HTTP get command.
1155
+ Options menu on receiver.
1203
1156
 
1204
1157
  Only available if using Telnet.
1205
1158
  """
@@ -1213,7 +1166,7 @@ class DenonAVRDeviceInfo:
1213
1166
  )
1214
1167
 
1215
1168
  async def async_channel_level_adjust(self) -> None:
1216
- """Toggle the channel level adjust menu on receiver via HTTP get command."""
1169
+ """Toggle the channel level adjust menu on receiver."""
1217
1170
  if self.telnet_available:
1218
1171
  await self.telnet_api.async_send_commands(
1219
1172
  self.telnet_commands.command_channel_level_adjust
@@ -1222,7 +1175,7 @@ class DenonAVRDeviceInfo:
1222
1175
  await self.api.async_get_command(self.urls.command_channel_level_adjust)
1223
1176
 
1224
1177
  async def async_dimmer_toggle(self) -> None:
1225
- """Toggle dimmer on receiver via HTTP get command."""
1178
+ """Toggle dimmer on receiver."""
1226
1179
  if self.telnet_available:
1227
1180
  await self.telnet_api.async_send_commands(
1228
1181
  self.telnet_commands.command_dimmer_toggle
@@ -1231,11 +1184,11 @@ class DenonAVRDeviceInfo:
1231
1184
  await self.api.async_get_command(self.urls.command_dimmer_toggle)
1232
1185
 
1233
1186
  async def async_dimmer(self, mode: DimmerModes) -> None:
1234
- """Set dimmer mode on receiver via HTTP get command."""
1187
+ """Set dimmer mode on receiver."""
1235
1188
  if mode not in self._dimmer_modes:
1236
1189
  raise AvrCommandError("Invalid dimmer mode")
1237
1190
 
1238
- mapped_mode = DIMMER_MODE_MAP[mode]
1191
+ mapped_mode = DIMMER_MODE_MAP_REVERSE[mode]
1239
1192
  if self.telnet_available:
1240
1193
  await self.telnet_api.async_send_commands(
1241
1194
  self.telnet_commands.command_dimmer_set.format(mode=mapped_mode)
@@ -1246,7 +1199,7 @@ class DenonAVRDeviceInfo:
1246
1199
  )
1247
1200
 
1248
1201
  async def async_tactile_transducer_on(self) -> None:
1249
- """Turn on tactile transducer on receiver via HTTP get command."""
1202
+ """Turn on tactile transducer on receiver."""
1250
1203
  if self.telnet_available:
1251
1204
  await self.telnet_api.async_send_commands(
1252
1205
  self.telnet_commands.command_tactile_transducer.format(mode="ON")
@@ -1257,7 +1210,7 @@ class DenonAVRDeviceInfo:
1257
1210
  )
1258
1211
 
1259
1212
  async def async_auto_standby(self, auto_standby: AutoStandbys) -> None:
1260
- """Set auto standby on receiver via HTTP get command."""
1213
+ """Set auto standby on receiver."""
1261
1214
  if auto_standby not in self._auto_standbys:
1262
1215
  raise AvrCommandError("Invalid Auto Standby mode")
1263
1216
  if self.telnet_available:
@@ -1271,7 +1224,7 @@ class DenonAVRDeviceInfo:
1271
1224
 
1272
1225
  async def async_sleep(self, sleep: Union[Literal["OFF"], int]) -> None:
1273
1226
  """
1274
- Set auto standby on receiver via HTTP get command.
1227
+ Set auto standby on receiver.
1275
1228
 
1276
1229
  Valid sleep values are "OFF" and 1-120 (in minutes)
1277
1230
  """
@@ -1289,7 +1242,7 @@ class DenonAVRDeviceInfo:
1289
1242
  )
1290
1243
 
1291
1244
  async def async_tactile_transducer_off(self) -> None:
1292
- """Turn on tactile transducer on receiver via HTTP get command."""
1245
+ """Turn on tactile transducer on receiver."""
1293
1246
  if self.telnet_available:
1294
1247
  await self.telnet_api.async_send_commands(
1295
1248
  self.telnet_commands.command_tactile_transducer.format(mode="OFF")
@@ -1301,7 +1254,7 @@ class DenonAVRDeviceInfo:
1301
1254
 
1302
1255
  async def async_tactile_transducer_toggle(self) -> None:
1303
1256
  """
1304
- Turn on tactile transducer on receiver via HTTP get command.
1257
+ Turn on tactile transducer on receiver.
1305
1258
 
1306
1259
  Only available if using Telnet.
1307
1260
  """
@@ -1311,7 +1264,7 @@ class DenonAVRDeviceInfo:
1311
1264
  await self.async_tactile_transducer_on()
1312
1265
 
1313
1266
  async def async_tactile_transducer_level_up(self) -> None:
1314
- """Increase the transducer level on receiver via HTTP get command."""
1267
+ """Increase the transducer level on receiver."""
1315
1268
  if self.telnet_available:
1316
1269
  await self.telnet_api.async_send_commands(
1317
1270
  self.telnet_commands.command_tactile_transducer_level.format(mode="UP")
@@ -1322,7 +1275,7 @@ class DenonAVRDeviceInfo:
1322
1275
  )
1323
1276
 
1324
1277
  async def async_tactile_transducer_level_down(self) -> None:
1325
- """Decrease the transducer level on receiver via HTTP get command."""
1278
+ """Decrease the transducer level on receiver."""
1326
1279
  if self.telnet_available:
1327
1280
  await self.telnet_api.async_send_commands(
1328
1281
  self.telnet_commands.command_tactile_transducer_level.format(
@@ -1335,7 +1288,7 @@ class DenonAVRDeviceInfo:
1335
1288
  )
1336
1289
 
1337
1290
  async def async_transducer_lpf(self, lpf: TransducerLPFs):
1338
- """Set transducer low pass filter on receiver via HTTP get command."""
1291
+ """Set transducer low pass filter on receiver."""
1339
1292
  if lpf not in self._tactile_transducer_lpfs:
1340
1293
  raise AvrCommandError("Invalid tactile transducer low pass filter")
1341
1294
 
@@ -1354,7 +1307,7 @@ class DenonAVRDeviceInfo:
1354
1307
  )
1355
1308
 
1356
1309
  async def async_room_size(self, room_size: RoomSizes) -> None:
1357
- """Set room size on receiver via HTTP get command."""
1310
+ """Set room size on receiver."""
1358
1311
  if room_size not in self._room_sizes:
1359
1312
  raise AvrCommandError("Invalid room size")
1360
1313
 
@@ -1369,7 +1322,7 @@ class DenonAVRDeviceInfo:
1369
1322
 
1370
1323
  async def async_trigger_on(self, trigger: int) -> None:
1371
1324
  """
1372
- Set trigger to ON on receiver via HTTP get command.
1325
+ Set trigger to ON on receiver.
1373
1326
 
1374
1327
  :param trigger: Trigger number to set to ON. Valid values are 1-3.
1375
1328
  """
@@ -1387,7 +1340,7 @@ class DenonAVRDeviceInfo:
1387
1340
 
1388
1341
  async def async_trigger_off(self, trigger: int) -> None:
1389
1342
  """
1390
- Set trigger to OFF on receiver via HTTP get command.
1343
+ Set trigger to OFF on receiver.
1391
1344
 
1392
1345
  :param trigger: Trigger number to set to OFF. Valid values are 1-3.
1393
1346
  """
@@ -1405,7 +1358,7 @@ class DenonAVRDeviceInfo:
1405
1358
 
1406
1359
  async def async_trigger_toggle(self, trigger: int) -> None:
1407
1360
  """
1408
- Toggle trigger on receiver via HTTP get command.
1361
+ Toggle trigger on receiver.
1409
1362
 
1410
1363
  Only available if using Telnet.
1411
1364
 
@@ -1422,7 +1375,7 @@ class DenonAVRDeviceInfo:
1422
1375
 
1423
1376
  async def async_quick_select_mode(self, quick_select_number: int) -> None:
1424
1377
  """
1425
- Set quick select mode on receiver via HTTP get command.
1378
+ Set quick select mode on receiver.
1426
1379
 
1427
1380
  :param quick_select_number: Quick select number to set. Valid values are 1-5.
1428
1381
  """
@@ -1446,7 +1399,7 @@ class DenonAVRDeviceInfo:
1446
1399
 
1447
1400
  async def async_quick_select_memory(self, quick_select_number: int) -> None:
1448
1401
  """
1449
- Set quick select memory on receiver via HTTP get command.
1402
+ Set quick select memory on receiver.
1450
1403
 
1451
1404
  :param quick_select_number: Quick select number to set. Valid values are 1-5.
1452
1405
  """
@@ -1469,7 +1422,7 @@ class DenonAVRDeviceInfo:
1469
1422
  await self.api.async_get_command(command.format(number=quick_select_number))
1470
1423
 
1471
1424
  async def async_delay_up(self) -> None:
1472
- """Delay up on receiver via HTTP get command."""
1425
+ """Delay up on receiver."""
1473
1426
  if self.telnet_available:
1474
1427
  await self.telnet_api.async_send_commands(
1475
1428
  self.telnet_commands.command_delay_up
@@ -1478,7 +1431,7 @@ class DenonAVRDeviceInfo:
1478
1431
  await self.api.async_get_command(self.urls.command_delay_up)
1479
1432
 
1480
1433
  async def async_delay_down(self) -> None:
1481
- """Delay down on receiver via HTTP get command."""
1434
+ """Delay down on receiver."""
1482
1435
  if self.telnet_available:
1483
1436
  await self.telnet_api.async_send_commands(
1484
1437
  self.telnet_commands.command_delay_down
@@ -1491,7 +1444,7 @@ class DenonAVRDeviceInfo:
1491
1444
  if mode not in self._eco_modes:
1492
1445
  raise AvrCommandError("Invalid Eco mode")
1493
1446
 
1494
- mapped_mode = ECO_MODE_MAP[mode]
1447
+ mapped_mode = ECO_MODE_MAP_REVERSE[mode]
1495
1448
  if self.telnet_available:
1496
1449
  await self.telnet_api.async_send_commands(
1497
1450
  self.telnet_commands.command_eco_mode.format(mode=mapped_mode)
@@ -1506,7 +1459,7 @@ class DenonAVRDeviceInfo:
1506
1459
  if output not in self._hdmi_outputs:
1507
1460
  raise AvrCommandError("Invalid HDMI output mode")
1508
1461
 
1509
- mapped_output = HDMI_OUTPUT_MAP[output]
1462
+ mapped_output = HDMI_OUTPUT_MAP_REVERSE[output]
1510
1463
  if self.telnet_available:
1511
1464
  await self.telnet_api.async_send_commands(
1512
1465
  self.telnet_commands.command_hdmi_output.format(output=mapped_output)
@@ -1517,7 +1470,7 @@ class DenonAVRDeviceInfo:
1517
1470
  )
1518
1471
 
1519
1472
  async def async_hdmi_audio_decode(self, mode: HDMIAudioDecodes) -> None:
1520
- """Set HDMI Audio Decode mode on receiver via HTTP get command."""
1473
+ """Set HDMI Audio Decode mode on receiver."""
1521
1474
  if mode not in self._hdmi_audio_decodes:
1522
1475
  raise AvrCommandError("Invalid HDMI Audio Decode mode")
1523
1476
 
@@ -1531,10 +1484,10 @@ class DenonAVRDeviceInfo:
1531
1484
  )
1532
1485
 
1533
1486
  async def async_video_processing_mode(self, mode: VideoProcessingModes) -> None:
1534
- """Set video processing mode on receiver via HTTP get command."""
1487
+ """Set video processing mode on receiver."""
1535
1488
  if mode not in self._video_processing_modes:
1536
1489
  raise AvrCommandError("Invalid video processing mode")
1537
- processing_mode = VIDEO_PROCESSING_MODES_MAP[mode]
1490
+ processing_mode = VIDEO_PROCESSING_MODES_MAP_REVERSE[mode]
1538
1491
  if self.telnet_available:
1539
1492
  await self.telnet_api.async_send_commands(
1540
1493
  self.telnet_commands.command_video_processing_mode.format(
@@ -1547,7 +1500,7 @@ class DenonAVRDeviceInfo:
1547
1500
  )
1548
1501
 
1549
1502
  async def async_status(self) -> None:
1550
- """Get status of receiver via HTTP get command."""
1503
+ """Get status of receiver."""
1551
1504
  if not self.is_denon:
1552
1505
  raise AvrCommandError("Status command is only supported for Denon devices")
1553
1506
 
@@ -1559,7 +1512,7 @@ class DenonAVRDeviceInfo:
1559
1512
  await self.api.async_get_command(self.urls.command_status)
1560
1513
 
1561
1514
  async def async_system_reset(self) -> None:
1562
- """DANGER! Reset the receiver via HTTP get command."""
1515
+ """DANGER! Reset the receiver."""
1563
1516
  if self.telnet_available:
1564
1517
  await self.telnet_api.async_send_commands(
1565
1518
  self.telnet_commands.command_system_reset
@@ -1568,7 +1521,7 @@ class DenonAVRDeviceInfo:
1568
1521
  await self.api.async_get_command(self.urls.command_system_reset)
1569
1522
 
1570
1523
  async def async_network_restart(self) -> None:
1571
- """Restart the network on the receiver via HTTP get command."""
1524
+ """Restart the network on the receiver."""
1572
1525
  if self.telnet_available:
1573
1526
  await self.telnet_api.async_send_commands(
1574
1527
  self.telnet_commands.command_network_restart
@@ -1578,7 +1531,7 @@ class DenonAVRDeviceInfo:
1578
1531
 
1579
1532
  async def async_speaker_preset(self, preset: int) -> None:
1580
1533
  """
1581
- Set speaker preset on receiver via HTTP get command.
1534
+ Set speaker preset on receiver.
1582
1535
 
1583
1536
  Valid preset values are 1-2.
1584
1537
  """
@@ -1596,7 +1549,7 @@ class DenonAVRDeviceInfo:
1596
1549
 
1597
1550
  async def async_speaker_preset_toggle(self) -> None:
1598
1551
  """
1599
- Toggle speaker preset on receiver via HTTP get command.
1552
+ Toggle speaker preset on receiver.
1600
1553
 
1601
1554
  Only available if using Telnet.
1602
1555
  """
@@ -1606,7 +1559,7 @@ class DenonAVRDeviceInfo:
1606
1559
  async def async_bt_transmitter_on(
1607
1560
  self,
1608
1561
  ) -> None:
1609
- """Turn on Bluetooth transmitter on receiver via HTTP get command."""
1562
+ """Turn on Bluetooth transmitter on receiver."""
1610
1563
  if self.telnet_available:
1611
1564
  await self.telnet_api.async_send_commands(
1612
1565
  self.telnet_commands.command_bluetooth_transmitter.format(mode="ON")
@@ -1619,7 +1572,7 @@ class DenonAVRDeviceInfo:
1619
1572
  async def async_bt_transmitter_off(
1620
1573
  self,
1621
1574
  ) -> None:
1622
- """Turn off Bluetooth transmitter on receiver via HTTP get command."""
1575
+ """Turn off Bluetooth transmitter on receiver."""
1623
1576
  if self.telnet_available:
1624
1577
  await self.telnet_api.async_send_commands(
1625
1578
  self.telnet_commands.command_bluetooth_transmitter.format(mode="OFF")
@@ -1631,7 +1584,7 @@ class DenonAVRDeviceInfo:
1631
1584
 
1632
1585
  async def async_bt_transmitter_toggle(self) -> None:
1633
1586
  """
1634
- Toggle Bluetooth transmitter mode on receiver via HTTP get command.
1587
+ Toggle Bluetooth transmitter mode on receiver.
1635
1588
 
1636
1589
  Only available if using Telnet.
1637
1590
  """
@@ -1641,11 +1594,11 @@ class DenonAVRDeviceInfo:
1641
1594
  await self.async_bt_transmitter_on()
1642
1595
 
1643
1596
  async def async_bt_output_mode(self, mode: BluetoothOutputModes) -> None:
1644
- """Set Bluetooth transmitter mode on receiver via HTTP get command."""
1597
+ """Set Bluetooth transmitter mode on receiver."""
1645
1598
  if mode not in self._bt_output_modes:
1646
1599
  raise AvrCommandError("Invalid Bluetooth output mode")
1647
1600
 
1648
- mapped_mode = BLUETOOTH_OUTPUT_MODES_MAP[mode]
1601
+ mapped_mode = BLUETOOTH_OUTPUT_MODES_MAP_REVERSE[mode]
1649
1602
  if self.telnet_available:
1650
1603
  await self.telnet_api.async_send_commands(
1651
1604
  self.telnet_commands.command_bluetooth_transmitter.format(
@@ -1659,7 +1612,7 @@ class DenonAVRDeviceInfo:
1659
1612
 
1660
1613
  async def async_bt_output_mode_toggle(self) -> None:
1661
1614
  """
1662
- Toggle Bluetooth output mode on receiver via HTTP get command.
1615
+ Toggle Bluetooth output mode on receiver.
1663
1616
 
1664
1617
  Only available if using Telnet.
1665
1618
  """
@@ -1669,7 +1622,7 @@ class DenonAVRDeviceInfo:
1669
1622
  await self.async_bt_output_mode("Bluetooth + Speakers")
1670
1623
 
1671
1624
  async def async_delay_time_up(self) -> None:
1672
- """Delay time up on receiver via HTTP get command."""
1625
+ """Delay time up on receiver."""
1673
1626
  if self.telnet_available:
1674
1627
  await self.telnet_api.async_send_commands(
1675
1628
  self.telnet_commands.command_delay_time.format(value="UP")
@@ -1680,7 +1633,7 @@ class DenonAVRDeviceInfo:
1680
1633
  )
1681
1634
 
1682
1635
  async def async_delay_time_down(self) -> None:
1683
- """Delay time up on receiver via HTTP get command."""
1636
+ """Delay time up on receiver."""
1684
1637
  if self.telnet_available:
1685
1638
  await self.telnet_api.async_send_commands(
1686
1639
  self.telnet_commands.command_delay_time.format(value="DOWN")
@@ -1692,7 +1645,7 @@ class DenonAVRDeviceInfo:
1692
1645
 
1693
1646
  async def async_delay_time(self, delay_time: int) -> None:
1694
1647
  """
1695
- Set delay time on receiver via HTTP get command.
1648
+ Set delay time on receiver.
1696
1649
 
1697
1650
  :param delay_time: Delay time in ms. Valid values are 0-999.
1698
1651
  """
@@ -1709,11 +1662,11 @@ class DenonAVRDeviceInfo:
1709
1662
  )
1710
1663
 
1711
1664
  async def async_audio_restorer(self, mode: AudioRestorers):
1712
- """Set audio restorer on receiver via HTTP get command."""
1665
+ """Set audio restorer on receiver."""
1713
1666
  if mode not in self._audio_restorers:
1714
1667
  raise AvrCommandError("Invalid audio restorer mode")
1715
1668
 
1716
- mapped_mode = AUDIO_RESTORER_MAP[mode]
1669
+ mapped_mode = AUDIO_RESTORER_MAP_REVERSE[mode]
1717
1670
  if self.telnet_available:
1718
1671
  await self.telnet_api.async_send_commands(
1719
1672
  self.telnet_commands.command_audio_restorer.format(mode=mapped_mode)
@@ -1724,7 +1677,7 @@ class DenonAVRDeviceInfo:
1724
1677
  )
1725
1678
 
1726
1679
  async def async_remote_control_lock(self):
1727
- """Set remote control lock on receiver via HTTP get command."""
1680
+ """Set remote control lock on receiver."""
1728
1681
  if self.telnet_available:
1729
1682
  await self.telnet_api.async_send_commands(
1730
1683
  self.telnet_commands.command_remote_control_lock.format(mode="ON")
@@ -1735,7 +1688,7 @@ class DenonAVRDeviceInfo:
1735
1688
  )
1736
1689
 
1737
1690
  async def async_remote_control_unlock(self):
1738
- """Set remote control unlock on receiver via HTTP get command."""
1691
+ """Set remote control unlock on receiver."""
1739
1692
  if self.telnet_available:
1740
1693
  await self.telnet_api.async_send_commands(
1741
1694
  self.telnet_commands.command_remote_control_lock.format(mode="OFF")
@@ -1746,7 +1699,7 @@ class DenonAVRDeviceInfo:
1746
1699
  )
1747
1700
 
1748
1701
  async def async_panel_lock(self, panel_lock_mode: PanelLocks):
1749
- """Set panel lock on receiver via HTTP get command."""
1702
+ """Set panel lock on receiver."""
1750
1703
  if panel_lock_mode not in self._panel_locks:
1751
1704
  raise AvrCommandError("Invalid panel lock mode")
1752
1705
 
@@ -1770,7 +1723,7 @@ class DenonAVRDeviceInfo:
1770
1723
  )
1771
1724
 
1772
1725
  async def async_panel_unlock(self):
1773
- """Set panel unlock on receiver via HTTP get command."""
1726
+ """Set panel unlock on receiver."""
1774
1727
  if self.telnet_available:
1775
1728
  await self.telnet_api.async_send_commands(
1776
1729
  self.telnet_commands.command_panel_lock.format(mode="OFF")
@@ -1781,7 +1734,7 @@ class DenonAVRDeviceInfo:
1781
1734
  )
1782
1735
 
1783
1736
  async def async_graphic_eq_on(self) -> None:
1784
- """Turn on Graphic EQ on receiver via HTTP get command."""
1737
+ """Turn on Graphic EQ on receiver."""
1785
1738
  if self.telnet_available:
1786
1739
  await self.telnet_api.async_send_commands(
1787
1740
  self.telnet_commands.command_graphic_eq.format(mode="ON")
@@ -1792,7 +1745,7 @@ class DenonAVRDeviceInfo:
1792
1745
  )
1793
1746
 
1794
1747
  async def async_graphic_eq_off(self) -> None:
1795
- """Turn off Graphic EQ on receiver via HTTP get command."""
1748
+ """Turn off Graphic EQ on receiver."""
1796
1749
  if self.telnet_available:
1797
1750
  await self.telnet_api.async_send_commands(
1798
1751
  self.telnet_commands.command_graphic_eq.format(mode="OFF")
@@ -1804,7 +1757,7 @@ class DenonAVRDeviceInfo:
1804
1757
 
1805
1758
  async def async_graphic_eq_toggle(self) -> None:
1806
1759
  """
1807
- Toggle Graphic EQ on receiver via HTTP get command.
1760
+ Toggle Graphic EQ on receiver.
1808
1761
 
1809
1762
  Only available if using Telnet.
1810
1763
  """
@@ -1814,7 +1767,7 @@ class DenonAVRDeviceInfo:
1814
1767
  await self.async_graphic_eq_on()
1815
1768
 
1816
1769
  async def async_headphone_eq_on(self) -> None:
1817
- """Turn on Headphone EQ on receiver via HTTP get command."""
1770
+ """Turn on Headphone EQ on receiver."""
1818
1771
  if self.telnet_available:
1819
1772
  await self.telnet_api.async_send_commands(
1820
1773
  self.telnet_commands.command_headphone_eq.format(mode="ON")
@@ -1825,7 +1778,7 @@ class DenonAVRDeviceInfo:
1825
1778
  )
1826
1779
 
1827
1780
  async def async_headphone_eq_off(self) -> None:
1828
- """Turn off Headphone EQ on receiver via HTTP get command."""
1781
+ """Turn off Headphone EQ on receiver."""
1829
1782
  if self.telnet_available:
1830
1783
  await self.telnet_api.async_send_commands(
1831
1784
  self.telnet_commands.command_headphone_eq.format(mode="OFF")
@@ -1837,7 +1790,7 @@ class DenonAVRDeviceInfo:
1837
1790
 
1838
1791
  async def async_headphone_eq_toggle(self) -> None:
1839
1792
  """
1840
- Toggle Headphone EQ on receiver via HTTP get command.
1793
+ Toggle Headphone EQ on receiver.
1841
1794
 
1842
1795
  Only available if using Telnet.
1843
1796
  """
@@ -1847,7 +1800,7 @@ class DenonAVRDeviceInfo:
1847
1800
  await self.async_headphone_eq_on()
1848
1801
 
1849
1802
  async def async_hdmi_cec_on(self) -> None:
1850
- """Turn on HDMI CEC on receiver via HTTP get command."""
1803
+ """Turn on HDMI CEC on receiver."""
1851
1804
  if self.telnet_available:
1852
1805
  await self.telnet_api.async_send_commands(
1853
1806
  self.telnet_commands.command_denon_hdmi_cec_on
@@ -1862,7 +1815,7 @@ class DenonAVRDeviceInfo:
1862
1815
  )
1863
1816
 
1864
1817
  async def async_hdmi_cec_off(self) -> None:
1865
- """Turn off HDMI CEC on receiver via HTTP get command."""
1818
+ """Turn off HDMI CEC on receiver."""
1866
1819
  if self.telnet_available:
1867
1820
  await self.telnet_api.async_send_commands(
1868
1821
  self.telnet_commands.command_denon_hdmi_cec_off
@@ -1878,7 +1831,7 @@ class DenonAVRDeviceInfo:
1878
1831
 
1879
1832
  async def async_illumination(self, mode: Illuminations):
1880
1833
  """
1881
- Set illumination mode on receiver via HTTP get command.
1834
+ Set illumination mode on receiver.
1882
1835
 
1883
1836
  Only available on Marantz devices.
1884
1837
  """
@@ -1888,7 +1841,7 @@ class DenonAVRDeviceInfo:
1888
1841
  if mode not in self._illuminations:
1889
1842
  raise AvrCommandError("Invalid illumination mode")
1890
1843
 
1891
- mapped_mode = ILLUMINATION_MAP[mode]
1844
+ mapped_mode = ILLUMINATION_MAP_REVERSE[mode]
1892
1845
  if self.telnet_available:
1893
1846
  await self.telnet_api.async_send_commands(
1894
1847
  self.telnet_commands.command_illumination.format(mode=mapped_mode)
@@ -1900,7 +1853,7 @@ class DenonAVRDeviceInfo:
1900
1853
 
1901
1854
  async def async_auto_lip_sync_on(self) -> None:
1902
1855
  """
1903
- Turn on auto lip sync on receiver via HTTP get command.
1856
+ Turn on auto lip sync on receiver.
1904
1857
 
1905
1858
  Only available on Marantz devices.
1906
1859
  """
@@ -1918,7 +1871,7 @@ class DenonAVRDeviceInfo:
1918
1871
 
1919
1872
  async def async_auto_lip_sync_off(self) -> None:
1920
1873
  """
1921
- Turn off auto lip sync on receiver via HTTP get command.
1874
+ Turn off auto lip sync on receiver.
1922
1875
 
1923
1876
  Only available on Marantz devices.
1924
1877
  """
@@ -1936,7 +1889,7 @@ class DenonAVRDeviceInfo:
1936
1889
 
1937
1890
  async def async_auto_lip_sync_toggle(self) -> None:
1938
1891
  """
1939
- Toggle auto lip sync on receiver via HTTP get command.
1892
+ Toggle auto lip sync on receiver.
1940
1893
 
1941
1894
  Only available on Marantz devices and when using Telnet.
1942
1895
  """
@@ -1948,6 +1901,121 @@ class DenonAVRDeviceInfo:
1948
1901
  else:
1949
1902
  await self.async_auto_lip_sync_on()
1950
1903
 
1904
+ async def async_page_up(self) -> None:
1905
+ """Page Up on receiver."""
1906
+ if self.telnet_available:
1907
+ command = (
1908
+ self.telnet_commands.command_page_up_denon
1909
+ if self.is_denon
1910
+ else self.telnet_commands.command_page_up_marantz
1911
+ )
1912
+ await self.telnet_api.async_send_commands(command)
1913
+ else:
1914
+ command = (
1915
+ self.urls.command_page_up_denon
1916
+ if self.is_denon
1917
+ else self.urls.command_page_up_marantz
1918
+ )
1919
+ await self.api.async_get_command(command)
1920
+
1921
+ async def async_page_down(self) -> None:
1922
+ """Page Down on receiver."""
1923
+ if self.telnet_available:
1924
+ command = (
1925
+ self.telnet_commands.command_page_down_denon
1926
+ if self.is_denon
1927
+ else self.telnet_commands.command_page_down_marantz
1928
+ )
1929
+ await self.telnet_api.async_send_commands(command)
1930
+ else:
1931
+ command = (
1932
+ self.urls.command_page_down_denon
1933
+ if self.is_denon
1934
+ else self.urls.command_page_down_marantz
1935
+ )
1936
+ await self.api.async_get_command(command)
1937
+
1938
+ async def async_input_mode(self, mode: InputModes):
1939
+ """Set input mode on receiver."""
1940
+ if mode not in self._input_modes:
1941
+ raise AvrCommandError("Invalid input mode")
1942
+
1943
+ if mode == "Select":
1944
+ command = (
1945
+ self.telnet_commands.command_input_mode_select_denon
1946
+ if self.telnet_available
1947
+ else (
1948
+ self.urls.command_input_mode_select_denon
1949
+ if self.is_denon
1950
+ else (
1951
+ self.telnet_commands.command_input_mode_select_marantz
1952
+ if self.telnet_available
1953
+ else self.urls.command_input_mode_select_marantz
1954
+ )
1955
+ )
1956
+ )
1957
+ elif mode == "Auto":
1958
+ command = (
1959
+ self.telnet_commands.command_input_mode_auto_denon
1960
+ if self.telnet_available
1961
+ else (
1962
+ self.urls.command_input_mode_auto_denon
1963
+ if self.is_denon
1964
+ else (
1965
+ self.telnet_commands.command_input_mode_auto_marantz
1966
+ if self.telnet_available
1967
+ else self.urls.command_input_mode_auto_marantz
1968
+ )
1969
+ )
1970
+ )
1971
+ elif mode == "HDMI":
1972
+ command = (
1973
+ self.telnet_commands.command_input_mode_hdmi_denon
1974
+ if self.telnet_available
1975
+ else (
1976
+ self.urls.command_input_mode_hdmi_denon
1977
+ if self.is_denon
1978
+ else (
1979
+ self.telnet_commands.command_input_mode_hdmi_marantz
1980
+ if self.telnet_available
1981
+ else self.urls.command_input_mode_hdmi_marantz
1982
+ )
1983
+ )
1984
+ )
1985
+ elif mode == "Digital":
1986
+ command = (
1987
+ self.telnet_commands.command_input_mode_digital_denon
1988
+ if self.telnet_available
1989
+ else (
1990
+ self.urls.command_input_mode_digital_denon
1991
+ if self.is_denon
1992
+ else (
1993
+ self.telnet_commands.command_input_mode_digital_marantz
1994
+ if self.telnet_available
1995
+ else self.urls.command_input_mode_digital_marantz
1996
+ )
1997
+ )
1998
+ )
1999
+ else:
2000
+ command = (
2001
+ self.telnet_commands.command_input_mode_analog_denon
2002
+ if self.telnet_available
2003
+ else (
2004
+ self.urls.command_input_mode_analog_denon
2005
+ if self.is_denon
2006
+ else (
2007
+ self.telnet_commands.command_input_mode_analog_marantz
2008
+ if self.telnet_available
2009
+ else self.urls.command_input_mode_analog_marantz
2010
+ )
2011
+ )
2012
+ )
2013
+
2014
+ if self.telnet_available:
2015
+ await self.telnet_api.async_send_commands(command)
2016
+ else:
2017
+ await self.api.async_get_command(command)
2018
+
1951
2019
 
1952
2020
  @attr.s(auto_attribs=True, on_setattr=DENON_ATTR_SETATTR)
1953
2021
  class DenonAVRFoundation:
@@ -1997,11 +2065,11 @@ class DenonAVRFoundation:
1997
2065
  raise
1998
2066
 
1999
2067
  # Extract relevant information
2000
- zone = self._device.get_own_zone()
2001
-
2002
2068
  attrs = deepcopy(update_attrs)
2003
2069
  for app_command in attrs.keys():
2004
- search_strings = self.create_appcommand_search_strings(app_command, zone)
2070
+ search_strings = create_appcommand_search_strings(
2071
+ app_command, self._device.zone
2072
+ )
2005
2073
  start = 0
2006
2074
  success = 0
2007
2075
  for i, pattern in enumerate(app_command.response_pattern):
@@ -2040,9 +2108,10 @@ class DenonAVRFoundation:
2040
2108
 
2041
2109
  # Check if each attribute was updated
2042
2110
  if update_attrs:
2043
- raise AvrProcessingError(
2044
- f"Some attributes of zone {self._device.zone} not found on update:"
2045
- f" {update_attrs}"
2111
+ _LOGGER.debug(
2112
+ "Some attributes of zone %s not found on update: %s",
2113
+ self._device.zone,
2114
+ update_attrs,
2046
2115
  )
2047
2116
 
2048
2117
  async def async_update_attrs_status_xml(
@@ -2100,39 +2169,58 @@ class DenonAVRFoundation:
2100
2169
 
2101
2170
  # Check if each attribute was updated
2102
2171
  if update_attrs:
2103
- raise AvrProcessingError(
2104
- f"Some attributes of zone {self._device.zone} not found on update:"
2105
- f" {update_attrs}"
2106
- )
2107
-
2108
- @staticmethod
2109
- def create_appcommand_search_strings(
2110
- app_command_cmd: AppCommandCmd, zone: str
2111
- ) -> List[str]:
2112
- """Create search pattern for AppCommand(0300).xml response."""
2113
- result = []
2114
-
2115
- for resp in app_command_cmd.response_pattern:
2116
- string = "./cmd"
2117
- # Text of cmd tag in query was added as attribute to response
2118
- if app_command_cmd.cmd_text:
2119
- string = (
2120
- string + f"[@{APPCOMMAND_CMD_TEXT}='{app_command_cmd.cmd_text}']"
2121
- )
2122
- # Text of name tag in query was added as attribute to response
2123
- if app_command_cmd.name:
2124
- string = string + f"[@{APPCOMMAND_NAME}='{app_command_cmd.name}']"
2125
- # Some results include a zone tag
2126
- if resp.add_zone:
2127
- string = string + f"/{zone}"
2128
- # Suffix like /status, /volume
2129
- string = string + resp.suffix
2130
-
2131
- # A complete search string with all strributes set looks like
2132
- # ./cmd[@cmd_text={cmd_text}][@name={name}]/zone1/volume
2133
- result.append(string)
2172
+ _LOGGER.debug(
2173
+ "Some attributes of zone %s not found on update: %s",
2174
+ self._device.zone,
2175
+ update_attrs,
2176
+ )
2177
+
2178
+
2179
+ def create_appcommand_search_strings(
2180
+ app_command_cmd: AppCommandCmd, zone: str
2181
+ ) -> List[str]:
2182
+ """Create search pattern for AppCommand(0300).xml response."""
2183
+ result = []
2184
+
2185
+ zone_element = get_zone_element(zone)
2186
+
2187
+ for resp in app_command_cmd.response_pattern:
2188
+ string = "./cmd"
2189
+ # Text of cmd tag in query was added as attribute to response
2190
+ if app_command_cmd.cmd_text:
2191
+ string = string + f"[@{APPCOMMAND_CMD_TEXT}='{app_command_cmd.cmd_text}']"
2192
+ # Text of name tag in query was added as attribute to response
2193
+ if app_command_cmd.name:
2194
+ string = string + f"[@{APPCOMMAND_NAME}='{app_command_cmd.name}']"
2195
+ # Prefix like /list/listvalue/
2196
+ if resp.prefix:
2197
+ string = string + resp.prefix
2198
+ # Some results include a zone element
2199
+ if resp.add_zone:
2200
+ string = string + f"/{zone_element}"
2201
+ # If the result can be identified by a "zone" element in the same node
2202
+ if resp.search_zone_text:
2203
+ string = string + f"/zone[.='{zone}']/.."
2204
+ # Suffix like /status, /volume
2205
+ string = string + resp.suffix
2206
+
2207
+ # A complete search string with all attributes looks like
2208
+ # ./cmd[@cmd_text={cmd_text}][@name={name}]/zone1/volume
2209
+ result.append(string)
2210
+
2211
+ return result
2212
+
2213
+
2214
+ def get_zone_element(zone: str) -> str:
2215
+ """
2216
+ Get a zone element for evaluation of receiver XML responses.
2134
2217
 
2135
- return result
2218
+ This zone element are used to evaluate XML responses of HTTP POST
2219
+ commands.
2220
+ """
2221
+ if zone == MAIN_ZONE:
2222
+ return "zone1"
2223
+ return zone.lower()
2136
2224
 
2137
2225
 
2138
2226
  def set_api_host(
@@ -2159,7 +2247,7 @@ def set_api_timeout(
2159
2247
  return value
2160
2248
 
2161
2249
 
2162
- def convert_string_int_bool(value: Union[str, bool]) -> bool:
2250
+ def convert_string_int_bool(value: Union[str, bool]) -> Optional[bool]:
2163
2251
  """Convert an integer from string format to bool."""
2164
2252
  if value is None:
2165
2253
  return None